Compare commits

...

1617 Commits

Author SHA1 Message Date
kongfei
b873bd161e install requests lib for python3 2023-07-25 11:18:28 +08:00
yimiaoxiehou
60b76b9ccc add static ttf file route (#1641)
Co-authored-by: chenzebin <chenzebin@ut.cn>
2023-07-20 19:40:03 +08:00
ning
ef39ee2f66 Merge branch 'main' of github.com:ccfos/nightingale 2023-07-20 18:01:21 +08:00
ning
6c83c2ef9b fix: panic when query data get cli is nil 2023-07-20 18:01:09 +08:00
李明
9495ec67ab feat: support index pattern datasource_id param (#1640)
* feat:support index pattern datasource id param
2023-07-20 14:09:21 +08:00
青牛踏雪
bb5680f6c4 fix windows dashboards label_values (#1639) 2023-07-20 11:49:20 +08:00
李明
acbe49f518 index pattern basic op (#1635)
* index pattern basic op
2023-07-20 11:21:51 +08:00
青牛踏雪
9dd55938c2 add Gitlab dashboard and alert rules based on categraf acquisition (#1636) 2023-07-20 11:17:21 +08:00
shardingHe
5433e6e27e AlertAggrView update verify (#1637)
Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-07-19 20:33:56 +08:00
ning
2dd6eb5f0f fix: get targets 2023-07-19 15:56:25 +08:00
Yening Qin
1731713dbb fix: get all target by guest user (#1634)
* fix: targets api get all

* code refactor
2023-07-19 15:08:10 +08:00
Ulric Qin
327ddb7bad code refactor 2023-07-17 19:47:58 +08:00
Ulric Qin
9e4adc1fa2 code refactor 2023-07-17 17:47:14 +08:00
Ulric Qin
bce7fdb470 code refactor 2023-07-17 17:13:56 +08:00
Ulric Qin
b79422962c code refactor 2023-07-17 17:13:39 +08:00
Ulric Qin
e5989ae5c2 rename integration Mongo to MongoDB 2023-07-17 17:11:27 +08:00
Ulric Qin
64feafa3a6 code refactor 2023-07-17 12:18:15 +08:00
Ulric Qin
52e4fa4d0d rename obs to dumper 2023-07-17 07:04:01 +08:00
Ulric Qin
6462c02861 rename obs to dumper 2023-07-17 07:01:19 +08:00
Ulric Qin
c657182659 refactor forward series 2023-07-16 11:35:56 +08:00
Ulric Qin
04d93eff34 refactor observe functions 2023-07-16 10:29:55 +08:00
Ulric Qin
40d60aeb4a add observe 2023-07-16 10:06:41 +08:00
Ulric Qin
ac875fa1b9 fix logger format output 2023-07-16 06:38:57 +08:00
shardingHe
b7c3e8a4f5 add interface of validation rule (#1606)
* add interface of validation rule

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
Co-authored-by: Yening Qin <710leo@gmail.com>
2023-07-14 14:16:35 +08:00
ning
2524e15947 Merge branch 'main' of github.com:ccfos/nightingale 2023-07-14 11:45:07 +08:00
ning
995c579403 docs: update built-in alert rule 2023-07-14 11:44:55 +08:00
Ulric Qin
848b7ac1ae Merge branch 'main' of github.com:ccfos/nightingale 2023-07-14 11:37:50 +08:00
Ulric Qin
9476b5ba7c code refactor 2023-07-14 11:37:39 +08:00
ning
7b58696bdc Merge branch 'main' of github.com:ccfos/nightingale 2023-07-14 11:16:19 +08:00
ning
6159178d99 set alert_rule.promql empty 2023-07-14 11:16:06 +08:00
青牛踏雪
99e5e0c117 add MinIO dashboard and alert rules based on categraf acquisition (#1625)
* add MinIO  dashboard and alert rules based on categraf acquisition

* add MinIO dashboard and alert rules based on categraf acquisition

* add MinIO dashboard and alert rules based on categraf acquisition

* add MinIO dashboard and alert rules based on categraf acquisition
2023-07-14 10:37:35 +08:00
Yening Qin
be1a3c1d8b sub and mute rule by severity (#1621)
* sub severity

* mute by severity
2023-07-13 11:16:32 +08:00
Yening Qin
f6378b055c docs: optimize the name of the integrations directory 2023-07-12 18:09:05 +08:00
Yening Qin
2574bb19cd rename integration ceph 2023-07-12 17:59:59 +08:00
Yening Qin
aa9d43cc69 rename integration 2023-07-12 17:58:20 +08:00
李明
d7f18ebec1 add mute hook (#1617) 2023-07-12 16:39:43 +08:00
Ulric Qin
b40f6976bb code refactor 2023-07-12 15:31:09 +08:00
Ulric Qin
cd1db57b7c code refactor 2023-07-12 15:05:58 +08:00
青牛踏雪
5a6ca42c75 add ceph dashboard and alert rules based on categraf acquisition (#1619) 2023-07-11 15:08:00 +08:00
Ulric Qin
80874a743c refactor logic: do not extract ident when ignore_ident exists 2023-07-11 14:56:53 +08:00
ulricqin
6cc612564f fix alert mute compute (#1618) 2023-07-11 10:12:09 +08:00
Yening Qin
909bbb5e66 refactor alert eval (#1616) 2023-07-10 18:49:15 +08:00
青牛踏雪
ff3ea7de58 update postgresql dashboard and alert rules based on categraf acquisition (#1613) 2023-07-07 15:09:27 +08:00
kongfei605
dd316e6ce1 alerts rule and dashboards for pg (#1612) 2023-07-07 14:04:55 +08:00
kongfei605
ba893e77cd update title of tidb alerts (#1611) 2023-07-07 14:04:15 +08:00
青牛踏雪
21904f1e39 add kafka dashboard and alert rules based on categraf acquisition (#1607)
* add kafka dashboard and alert rules based on categraf acquisition

* add kafka dashboard and alert rules based on categraf acquisition
2023-07-06 19:54:37 +08:00
kongfei605
b5d5ecbab2 Merge pull request #1605 from longzhuquan/main
添加TiDB大盘,告警规则
2023-07-06 16:11:19 +08:00
Talon
ee612908ac feat(Login): add rsa to password (#1604) 2023-07-06 16:09:04 +08:00
Yong Wang (IT)
2ee04dffac 添加TiDB大盘,告警规则 2023-07-06 15:54:39 +08:00
青牛踏雪
be25adf990 add dashboard and alert rules based on categraf acquisition (#1603) 2023-07-06 15:50:53 +08:00
dependabot[bot]
ab72b6e1ba build(deps): bump google.golang.org/grpc from 1.51.0 to 1.53.0 (#1602) 2023-07-06 15:50:04 +08:00
laiwei
a4718e7a45 use star-history 2023-07-04 11:26:01 +08:00
青牛踏雪
f948d50d8b add springboot actuator 2.0 dashboard (#1601) 2023-07-03 19:38:40 +08:00
Ulric Qin
cb797d5913 Merge branch 'main' of github.com:ccfos/nightingale 2023-07-03 19:13:34 +08:00
Ulric Qin
8941c192de code refactor 2023-07-03 19:13:23 +08:00
alick-liming
5b726c1e61 optimize i18n format (#1600) 2023-07-01 15:45:59 +08:00
xtan
03871a0bf0 feat: provide alert info to ibex via stdin (#1599)
* feat: provide alert info to ibex via stdin

* refactor: rename tags to stdin

* refactor: format json to ibex
2023-06-30 19:03:56 +08:00
青牛踏雪
e002e9cb8f add VictoriaMetrics New Alerts Rule & add VictoriaMetrics Images. (#1598) 2023-06-29 15:47:48 +08:00
qifenggang
d414831c79 heartbeat update target table update_at field (#1595)
Co-authored-by: qifenggang <qifenggang@sina.com>
2023-06-29 15:47:11 +08:00
alick-liming
89807ada94 i18n const -> var (#1594) 2023-06-28 22:24:53 +08:00
青牛踏雪
351a31b079 fix ipmi readme.md (#1592)
* fix ipmi readme.md

* fix ipmi readme.md
2023-06-28 14:25:12 +08:00
青牛踏雪
af0127c905 add the ipmi dashboards & alerts rules (#1588) 2023-06-27 21:07:10 +08:00
青牛踏雪
95612e7140 add the kube-state-metrics, prometheus, kube-controller-plane alarm & record rules (#1586) 2023-06-26 16:45:45 +08:00
ning
a338b5233c code refactor 2023-06-25 20:30:58 +08:00
ning
ad26225f63 refactor: recording rule model 2023-06-25 19:14:19 +08:00
ning
16db570f18 refactor datasource 2023-06-25 17:04:02 +08:00
ning
97c68360a1 Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-06-25 10:23:16 +08:00
ning
00192b9d0f code refactor 2023-06-25 10:23:04 +08:00
Ulric Qin
e745253d08 refactor integrations and add configuration: UseFileAssets 2023-06-22 18:27:25 +08:00
ning
76905c55d5 refactor: loki datasource check 2023-06-21 21:36:21 +08:00
kongfei605
d4bce5456b snmp & smart dashboards (#1581)
* snmp & smart dashboards

* update

* update snmp
2023-06-21 20:42:50 +08:00
Ulric Qin
58136d30e6 code refactor 2023-06-21 14:59:38 +08:00
Ulric Qin
563fb0330a code refactor 2023-06-21 14:35:36 +08:00
Ulric Qin
c2ab3b4240 Merge branch 'main' of github.com:ccfos/nightingale 2023-06-21 14:05:22 +08:00
Ulric Qin
f5dde6e4d6 fix wrong descriptions 2023-06-21 14:05:08 +08:00
青牛踏雪
a9779703dd add AliYun monitor dashboard & readme.md (#1579) 2023-06-20 15:37:08 +08:00
青牛踏雪
9f4a9e77ae add vmware RabbitMQ monitor dashboard & alerts & readme.md (#1578) 2023-06-20 13:45:11 +08:00
Ulric Qin
df37071c3d refactor pushgw 2023-06-20 10:34:14 +08:00
ning
fa164ac5d2 Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-06-16 18:17:21 +08:00
ning
f5de4c3f22 refactor db2fe 2023-06-16 18:17:14 +08:00
ning
dd9099af0a refactor db2fe 2023-06-16 18:13:28 +08:00
dependabot[bot]
5bdb63a818 build(deps): bump golang.org/x/image (#1575)
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.0.0-20190501045829-6d32002ffd75 to 0.5.0.
- [Commits](https://github.com/golang/image/commits/v0.5.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-16 17:58:27 +08:00
ning
8a4c709e87 refactor models 2023-06-16 17:44:26 +08:00
xtan
75f6e07c40 feat: add verification code for login (#1566)
* feat: add verification code for login

* feat: 支持图形验证码开关
2023-06-16 14:47:11 +08:00
Yening Qin
de9b11a049 recording rule add query configs (#1574)
* add query config

* migrate table
2023-06-16 14:40:54 +08:00
ning
067b3f91a7 refactor: change default notify tpl 2023-06-15 19:21:18 +08:00
ning
5d215a89b6 refactor: optimize alert mute when time is 23:59:xx 2023-06-15 18:02:25 +08:00
ning
63679c15dd Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-06-15 17:01:29 +08:00
ning
38229a43dc refactor: notify tpl 2023-06-15 17:01:17 +08:00
青牛踏雪
1d1ae238d4 add elasticsearch_by_categraf monitor dashboard & alerts & markdown (#1573) 2023-06-15 16:30:06 +08:00
ning
c2d300c0f1 Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-06-15 16:17:38 +08:00
ning
bcb89017a0 refactor: remove default notify template file 2023-06-15 16:17:26 +08:00
Ulric Qin
e04a3eed5f Merge branch 'main' of github.com:ccfos/nightingale 2023-06-15 15:26:13 +08:00
Ulric Qin
e77cf40938 add n9e v6 dashboard 2023-06-15 15:25:58 +08:00
ning
cb66b19d70 refactor: change configs.cval length 2023-06-15 14:35:21 +08:00
ning
9edf05c19a Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-06-15 11:22:39 +08:00
ning
6a6b4a2283 update ops 2023-06-15 11:22:26 +08:00
青牛踏雪
0473bb3925 add springboot actuator monitor dashboard & alerts & markdown (#1571) 2023-06-15 08:04:00 +08:00
ning
4afc3a60a4 Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-06-14 13:56:13 +08:00
shardingHe
e9c9a3ac58 feat: notify tpl support add and delete (#1567)
* notifyTpl add and delete

* notifyTpl add and delete

* optimization notifyTpl

* optimization notifyTpl

* optimization notifyTpl

* optimization notifyTpl

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-06-14 13:51:53 +08:00
ning
98260e239e Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-06-14 13:26:18 +08:00
ning
f751b2034d fix: recovery event tags being lost after promql modification 2023-06-14 13:26:01 +08:00
青牛踏雪
9ce22a33f0 add vmware vsphere monitor dashboard & alerts & readme.md (#1565) 2023-06-13 07:18:04 +08:00
laiwei
3da64ca0fe refine readme 2023-06-12 20:49:25 +08:00
ning
9a883dc02c refactor: feishu_card sender 2023-06-12 12:21:49 +08:00
Ulric Qin
5ab6fe7e56 code refactor 2023-06-12 10:22:51 +08:00
shardingHe
c730eaa860 Move feishucard (#1563)
* Fix an exception situation where the prod and cate fields cannot be updated.

* add feishucard.tpl

* move feishucard to v6

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-06-11 21:04:02 +08:00
ning
5ba2d6bc8e fix: concurrent map writes 2023-06-09 17:49:34 +08:00
ning
64feee79ff Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-06-09 10:07:54 +08:00
ning
c490ab09ad fix: cli upgrade 2023-06-09 10:07:42 +08:00
shardingHe
61762e894c Fix: an exception situation where the prod and cate fields cannot be updated. (#1561)
Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-06-08 15:05:29 +08:00
ning
ac4ff33dff refactor: remove phone space 2023-06-07 10:22:44 +08:00
ning
72abeea51f add user login log 2023-06-06 13:41:22 +08:00
ning
6ec2b42669 code refactor 2023-06-05 15:30:10 +08:00
ning
a93e967d30 refactor: update target_up 2023-06-05 14:59:56 +08:00
ning
b5984b7871 Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-06-05 14:42:40 +08:00
ning
70ccbbc929 update target_up 2023-06-05 14:42:28 +08:00
dependabot[bot]
79d4fc508c build(deps): bump github.com/gin-gonic/gin from 1.9.0 to 1.9.1 (#1559)
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.9.0 to 1.9.1.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.9.0...v1.9.1)

---
updated-dependencies:
- dependency-name: github.com/gin-gonic/gin
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-03 20:38:05 +08:00
ning
794f0f874f change HostDatasourceId 2023-06-02 13:12:53 +08:00
Ulric Qin
aff53e8be3 Merge branch 'main' of github.com:ccfos/nightingale 2023-06-02 12:11:47 +08:00
Ulric Qin
2de6847323 refactor fe.sh 2023-06-02 12:11:35 +08:00
ning
eed037a3a1 change heartbeat api 2023-06-02 11:57:15 +08:00
ning
4099c467bb code refactor 2023-06-02 11:42:40 +08:00
ning
6b51adbc9a code refactor 2023-06-02 11:29:30 +08:00
ning
307be1dda2 fix: datasource bind to alert engine where update 2023-06-02 11:22:05 +08:00
ning
7da6145ec6 fix: promClients hit 2023-06-02 10:19:42 +08:00
Ulric Qin
0e4298a592 use standard http client instead of beego client 2023-06-02 09:58:10 +08:00
Ulric Qin
037fab74eb code refactor 2023-06-02 09:20:24 +08:00
Ulric Qin
fb849928c9 code refactor 2023-06-02 08:33:41 +08:00
Ulric Qin
7833aae0a1 code refactor 2023-06-02 08:13:08 +08:00
Ulric Qin
6edd71b1f0 code refactor 2023-06-02 08:06:40 +08:00
ulricqin
2f2f310a40 add hearbeat api for pushgw (#1560) 2023-06-02 08:06:23 +08:00
Ulric Qin
14bfdaa2ee code refactor 2023-06-02 07:39:04 +08:00
Ulric Qin
ffd0a69e43 fix: leaking connections 2023-06-02 07:31:21 +08:00
Ulric Qin
5b79d0ef46 code refactor 2023-06-01 21:20:21 +08:00
Ulric Qin
8f2a885a7d code refactor 2023-06-01 21:16:51 +08:00
Ulric Qin
31f6300c16 code refactor 2023-06-01 21:09:04 +08:00
Ulric Qin
54710c22f0 code refactor 2023-06-01 21:03:18 +08:00
Ulric Qin
352aa2b6b1 code refactor 2023-06-01 20:57:36 +08:00
Ulric Qin
624e5b5e62 debug 2023-06-01 20:45:51 +08:00
ning
65e3b5c8f1 fix: goreleaser 2023-06-01 20:28:28 +08:00
ning
750732f203 docs: update makefile 2023-06-01 20:16:23 +08:00
ning
9957711643 add n9e-edge 2023-06-01 20:01:29 +08:00
Ulric Qin
8f4fb0d28b code refactor for fe.sh 2023-06-01 19:28:54 +08:00
Ulric Qin
5d63f23cfc code refactor 2023-06-01 18:27:00 +08:00
Ulric Qin
c0fb8d22db code refactor 2023-06-01 18:02:01 +08:00
ulricqin
1732b297b1 refactor basic auth configurations: merge HTTP.Pushgw and HTTP.Heartbeat to HTTP.APIForAgent; merge HTTP.Alert and HTTP.Service to HTTP.APIForService (#1558) 2023-06-01 16:23:19 +08:00
ning
f1a5c2065c change alert.toml.example 2023-06-01 14:35:47 +08:00
Yening Qin
6b9ceda9c1 fix: host filter (#1557)
* fix host filter
2023-06-01 14:16:47 +08:00
ning
7390d42e62 refactor: change Makefile 2023-05-31 14:39:31 +08:00
ning
a35f879dc0 refactor: change event notify log 2023-05-31 14:19:41 +08:00
xtan
3fd4ea4853 feat: embed front-end files into n9e executable (#1556)
* feat: embed front-end files into n9e executable
2023-05-31 10:30:01 +08:00
ning
20f0a9d16d fix: webhook update note 2023-05-26 15:41:16 +08:00
ning
5d4151983a refactor: init alert 2023-05-25 14:42:18 +08:00
Yening Qin
83b5f12474 refactor: n9e-alert and n9e-pushgw sync config by http api (#1545)
* get alert mute by api

* add service api

* fix sync datasource

* change event persist

* add hearbeat

* change pushgw update target

* code refactor

* fix get user members

* refactor get alert rules

* update AlertCurEventGetByRuleIdAndDsId

* refactor get from api

* add role perm list and change get datasource

* refactor: get ops and metrics

* change some logs

* change get datasource
2023-05-23 20:53:04 +08:00
ning
8c7bfb4f4a Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-05-23 13:48:39 +08:00
ning
4ccf887920 fix panic where atertRuleCache.Get is nil 2023-05-23 13:48:26 +08:00
Ulric Qin
546d9cb2cc code refactor 2023-05-18 09:42:33 +08:00
Ulric Qin
391b42a399 code refactor 2023-05-18 09:40:18 +08:00
ning
a916a0fc6b refactor: set default script timeout 2023-05-17 15:25:00 +08:00
ning
da9f5fbb12 fix: hashring use lock 2023-05-17 14:45:28 +08:00
Yening Qin
ad3cf58bf3 feat: add ExtraSenders (#1536)
* refactor-sender

* update  upgrade.sql
2023-05-16 19:44:12 +08:00
ning
a77dc15e36 fix: ts fill tags 2023-05-16 10:19:16 +08:00
ning
9ad51aeeff refactor: rule prod check 2023-05-15 13:10:33 +08:00
ning
2c7f030ea5 fix ident extract 2023-05-12 14:01:50 +08:00
ning
039be7fc6c rename es dashbaord name 2023-05-11 18:54:07 +08:00
ning
9bff2509a8 Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-05-11 16:39:23 +08:00
ning
35b3cbb697 feat: add get datasource ids api 2023-05-11 16:39:11 +08:00
kongfei605
d81275b9c8 Merge pull request #1534 from dreamking02/patch-1
Update config.toml
2023-05-10 20:34:05 +08:00
dreamking02
e29dd58823 Update config.toml 2023-05-10 18:46:40 +08:00
ning
b64aa03ccf refactor: FillSeverities 2023-05-10 16:14:36 +08:00
ning
3893cb00a5 refactor: FillSeverities 2023-05-10 15:12:26 +08:00
ning
4b6985c8af Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-05-09 20:49:00 +08:00
MoonStrider
7cc9470823 Update alert_rule.go (#1528)
fix: rule.FillSeverities
2023-05-09 20:48:34 +08:00
ning
b97dfce0ad refactor: get node debug log 2023-05-09 20:19:48 +08:00
ning
357d3dff78 refactor: get node debug log 2023-05-09 19:43:59 +08:00
ning
d0604f0c97 refactor: alert rule sync 2023-05-09 19:21:36 +08:00
ning
8fafa0075b fix: filter host by tags 2023-05-09 15:14:28 +08:00
ning
caa23fbba1 refactor: oidc attributes username assignable 2023-05-09 10:18:11 +08:00
ning
4b9fea3cb2 refactor: ident extract 2023-05-09 10:08:49 +08:00
ning
f61a04f43f refactor: cas login 2023-05-09 10:08:09 +08:00
Ulric Qin
ef3588ff46 add host_table_view_demo.json 2023-05-06 17:41:40 +08:00
ning
3e3210bb81 Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-05-06 14:24:14 +08:00
ning
da7ef5a92e refactor: set heartbeat ip 2023-05-06 14:24:00 +08:00
Ulric Qin
82b91164fe Merge branch 'main' of github.com:ccfos/nightingale 2023-05-06 11:58:34 +08:00
Ulric Qin
033d45309f add snmp markdown 2023-05-06 11:58:21 +08:00
ning
60e9fb21f1 docs: update upgrade.sql 2023-05-06 10:41:19 +08:00
ning
508006ad01 refactor: notify template 2023-05-05 19:51:22 +08:00
Ulric Qin
97d7b0574a code refactor 2023-05-05 18:21:16 +08:00
Ulric Qin
c44aebd404 code refactor 2023-05-05 16:23:36 +08:00
Ulric Qin
2afa921a5d code refactor 2023-05-05 16:13:29 +08:00
Ulric Qin
313c820f1f code refactor 2023-05-05 16:09:35 +08:00
Ulric Qin
02f0b4579b update net_response dashboard 2023-05-05 16:05:54 +08:00
0x0034
36eb308ef6 fix: 修正添加loki 数据源校验问题 (#1524)
Co-authored-by: 若尘 <ruochen@ruochendeMacBook-Pro.local>
2023-05-05 14:45:47 +08:00
dependabot[bot]
cd2db571cf build(deps): bump github.com/gin-gonic/gin from 1.8.2 to 1.9.0 (#1523)
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.8.2 to 1.9.0.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.8.2...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/gin-gonic/gin
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-05 11:14:01 +08:00
kongfei605
a0cf12b171 Merge pull request #1522 from ccfos/dashboard
add canal dashboards
2023-05-05 10:52:11 +08:00
kongfei
8358ab4b81 add canal dashboards 2023-05-05 10:46:53 +08:00
青牛踏雪
0fc6cb8ef2 fix vm old dashboard to new (#1521) 2023-05-04 20:13:01 +08:00
xiechenglong
e1ab013c45 fix:Unknown column 'rw' in 'field list' (#1519)
Co-authored-by: xiechenglong <xiechenglong@inspur.com>
2023-05-04 15:17:15 +08:00
xtan
d984ad8bf4 docs: pg sql script and gitignore (#1518) 2023-05-04 08:59:17 +08:00
kongfei605
86fe3c7c43 chmod 755 wait for aarch64 (#1517) 2023-04-28 16:39:57 +08:00
青牛踏雪
0f4478318e update victoriametrics url links. @cyancow (#1516)
* add taoskeeper 3.x dashboard

* modify directory name taos to TDEngine

* add kubernetes dashboard based on categraf collection.

* add apiserver kubelet node alerts to k8s

* modify node name to node-exporter

* add victoriametrics dashboard based on categraf collection.

* up victoriametrics url links.

* Update README.md

---------

Co-authored-by: ulricqin <ulricqin@qq.com>
2023-04-27 21:28:04 +08:00
Ulric Qin
c0d0eb0e69 code refactor 2023-04-27 21:22:48 +08:00
Ulric Qin
b62762b2e6 Merge branch 'main' of github.com:ccfos/nightingale 2023-04-27 21:20:39 +08:00
ning
810ca0e469 Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-04-27 21:19:58 +08:00
青牛踏雪
33e3b224b9 add victoriametrics dashboard based on categraf collection. (#1515)
* add taoskeeper 3.x dashboard

* modify directory name taos to TDEngine

* add kubernetes dashboard based on categraf collection.

* add apiserver kubelet node alerts to k8s

* modify node name to node-exporter

* add victoriametrics dashboard based on categraf collection.
2023-04-27 21:19:46 +08:00
ning
24d7b2b1bf update dockerfile 2023-04-27 21:19:42 +08:00
Ulric Qin
1d5ff1b28d code refactor 2023-04-27 21:18:41 +08:00
ning
ed5c8c5758 fix Dockerfile 2023-04-27 20:23:28 +08:00
Ulric Qin
01f7860900 code refactor 2023-04-27 20:22:23 +08:00
Ulric Qin
a6bb03c8ba update http dash 2023-04-27 20:20:39 +08:00
Ulric Qin
e9150b2ae0 rename dir of net_response 2023-04-27 20:19:17 +08:00
Ulric Qin
30d1ebd808 update http icon and n9e icon 2023-04-27 19:51:58 +08:00
Ulric Qin
2f69d92055 add markdown readme of procstat 2023-04-27 19:46:47 +08:00
Ulric Qin
deeb40b4a0 Merge branch 'main' of github.com:ccfos/nightingale 2023-04-27 19:35:10 +08:00
Ulric Qin
37f68fd52b add procstat integrations 2023-04-27 19:34:57 +08:00
ning
73828e50b5 update fe.sh 2023-04-27 18:23:40 +08:00
kongfei605
7e73850117 Merge pull request #1514 from ccfos/docker_release
update dockerfile for github-action
2023-04-27 18:08:00 +08:00
kongfei
3a075e7681 update dockerfile for github-action 2023-04-27 18:06:49 +08:00
ulricqin
4ec5612d78 add processes dashboards and alerts (#1513) 2023-04-27 16:11:21 +08:00
Yening Qin
817ed0ab1b fix get engine cluster list (#1512)
* fix: get engine cluster list
2023-04-27 15:33:14 +08:00
Yening Qin
63aa615761 compatible with TDSQL-C Mysql (#1511) 2023-04-27 14:28:59 +08:00
ning
2a36902760 fix: alert rule batch update severity 2023-04-27 11:54:14 +08:00
ning
bca9331182 compatible with TDSQL-C Mysql 2023-04-27 10:45:41 +08:00
alick-liming
199a23e385 refactor: get ClientIP (#1502)
* 调整ClientIP获取
2023-04-27 10:26:34 +08:00
ning
c733f16cc7 auto change n9e version in docker-compose.yaml 2023-04-26 17:32:07 +08:00
ning
81585649aa Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-04-26 17:15:18 +08:00
ning
2c4422d657 auto change n9e version in docker-compose.yaml 2023-04-26 17:15:05 +08:00
青牛踏雪
aaf66cb386 docs: add apiserver kubelet node alerts template to k8s (#1508)
* add apiserver kubelet node alerts to k8s

* modify node name to node-exporter
2023-04-26 14:18:13 +08:00
Ulric Qin
cfed4d8318 Merge branch 'main' of github.com:ccfos/nightingale 2023-04-25 15:02:26 +08:00
Ulric Qin
606cd538ec update dingtalk title 2023-04-25 15:02:13 +08:00
kongfei605
bafb3b2546 Merge pull request #1506 from ccfos/docker_release
update dockerfile
2023-04-25 11:56:32 +08:00
kongfei
9a0224697f typo 2023-04-25 11:55:58 +08:00
kongfei
23156552db update dockerfile 2023-04-25 11:54:29 +08:00
青牛踏雪
36bca795fa add kubernetes dashboard based on categraf collection. (#1503)
* add taoskeeper 3.x dashboard

* modify directory name taos to TDEngine

* add kubernetes dashboard based on categraf collection.
2023-04-24 19:58:12 +08:00
Ulric Qin
b5503ae93e update static files router 2023-04-24 19:42:02 +08:00
青牛踏雪
3c102e47ed add taoskeeper 3.x dashboard (#1501)
* add taoskeeper 3.x dashboard

* modify directory name taos to TDEngine
2023-04-24 19:28:17 +08:00
xtan
60bf8139b1 feat: add eventid to ibex task_record (#1497) 2023-04-24 18:01:48 +08:00
alick-liming
fc0d077c9f feat:1.verify notify template 2.heartbeat add remote_addr 3. gid auto busi group (#1498)
* 1.通知模版校验 2.对象列表remote_addr

* 1.bgid参数调整 2.语句优化

* 代码优化

* 代码调整
2023-04-24 16:02:45 +08:00
kongfei605
3a610f7ea0 fix standard output option for dashboards (#1500) 2023-04-24 13:18:06 +08:00
xtan
f8990ee85e fix: fix alert mute error for pg (#1496) 2023-04-21 14:05:26 +08:00
ning
88040bf277 modify fe.sh 2023-04-20 19:28:36 +08:00
ning
1e15dc1f30 fix batch update recording rule datasource 2023-04-20 18:02:50 +08:00
ning
9880b466db add /datasource/brief 2023-04-20 17:23:18 +08:00
ning
b7780ebbdb update n9e.sql 2023-04-20 16:50:59 +08:00
ning
1fa524b710 fix: set default ibex conf 2023-04-20 16:40:58 +08:00
ning
aa2c0cffce refactor docker-compose 2023-04-20 16:35:12 +08:00
ning
ed1c89fb7e refactor: heartbeat cluster name to engine name 2023-04-20 15:08:21 +08:00
ning
988327dead refactor built in board 2023-04-20 15:02:36 +08:00
xtan
5db168224e docs: docker-compose versions based on pg and vm (#1488) 2023-04-19 11:23:41 +08:00
idcdog
7622eba87f Adjust data source validation logic to support victoria-metrics clusters (#1487)
* fix: 调整数据源校验逻辑以便支持victoria-metrics集群
2023-04-19 11:06:53 +08:00
xtan
1cb58fedf7 docs: n9e and ibex init sql for postgresql (#1485) 2023-04-18 16:18:35 +08:00
ning
7dcaec0a7b update readme 2023-04-17 19:42:29 +08:00
ning
4f315cb6d5 host event append busigroup label 2023-04-17 17:33:54 +08:00
ning
9a2d898214 refactor: datasource check 2023-04-17 16:57:28 +08:00
ning
530561c038 refactor: datasource check 2023-04-17 16:19:19 +08:00
ning
fc68d2d598 update goreleaser 2023-04-14 19:02:14 +08:00
ning
1b40c38a7a modify docker-compose.yaml 2023-04-14 18:21:30 +08:00
Yening Qin
d39d4cb91d change builtin board (#1481) 2023-04-14 12:24:50 +08:00
lwangrabbit
e415538ffd fix: sendmm run ok with illegal token (#1476)
Co-authored-by: wanglipeng <wanglipeng@huayun.com>
2023-04-13 17:02:04 +08:00
Yening Qin
05c767a803 datasource check (#1479) 2023-04-13 16:59:25 +08:00
Ulric Qin
923cff1c19 Merge branch 'main' of github.com:ccfos/nightingale 2023-04-13 10:14:09 +08:00
Ulric Qin
ef18d2a95f fix pub static files router 2023-04-13 10:13:43 +08:00
laiwei
3abc4d0bfd update readme for v6 2023-04-12 20:13:33 +08:00
monch
a3ec69fe4a refactor: 优化钉钉通知被@时的排版 (#1475) 2023-04-11 18:10:04 +08:00
ning
403466f872 Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-04-11 16:30:47 +08:00
ning
81abd2f02a fix: datasource update 2023-04-11 16:30:35 +08:00
Ulric Qin
263c77cbbf update discord to slack 2023-04-07 17:09:32 +08:00
710leo
ef42a78e59 update n9e.sql 2023-04-06 22:13:57 +08:00
ning
4c7746b3b4 refactor: target miss add append tags 2023-04-06 17:15:02 +08:00
ning
b142a5726e target miss add append tags 2023-04-06 17:12:46 +08:00
ning
cc68b75489 fix: get builtin icon 2023-04-06 14:42:34 +08:00
ning
1ce79e29d5 fix: panic when template is nil 2023-04-03 17:25:52 +08:00
ning
ee167ce0ba update readme 2023-04-03 12:12:20 +08:00
idcdog
544cd02ef1 fix: the issue of the 'skip ssl validation' request in elasticsearch not taking effect (#1457) 2023-04-01 20:54:41 +08:00
ning
34ad6bc220 fix push data 2023-04-01 11:46:09 +08:00
ning
c7c694e70b refactor: ignore redis is nil 2023-03-31 16:20:33 +08:00
ning
dc26bb78d8 fix: redis get nil 2023-03-31 10:17:46 +08:00
ning
a0c635b830 update Dockerfile.goreleaser 2023-03-30 17:22:47 +08:00
ning
0e95c29b7d fix: goreleaser 2023-03-30 16:57:12 +08:00
ning
cab9fed700 fix: dockerfile 2023-03-30 16:39:05 +08:00
Yening Qin
4ad47fb8f4 refactor: push series (#1455) 2023-03-30 14:50:53 +08:00
ning
50345cb823 update initsql 2023-03-29 16:14:19 +08:00
ning
95bb67e66d Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-03-29 11:51:51 +08:00
ning
90fbd9f16a fix: busigroup append tag 2023-03-29 11:51:39 +08:00
kongfei605
5c8411eba1 update docker image (#1449) 2023-03-29 11:16:19 +08:00
ning
03edb84d09 fix: annotations panic 2023-03-28 20:16:09 +08:00
ning
958a8c3ed1 fix: ldap user roles set 2023-03-28 16:25:49 +08:00
Yening Qin
a2a0b41909 refactor: redis mset and mget (#1446)
* refactor redis mset
2023-03-28 15:39:43 +08:00
Tripitakav
64e1085766 fix nil pointer (#1443) 2023-03-27 19:20:31 +08:00
ning
5c97986908 update upgrade.sql 2023-03-27 15:51:52 +08:00
ning
66e291e3c3 fix: target_up show 2023-03-27 12:07:25 +08:00
ning
365fcd5dd7 update upgrade.sql 2023-03-24 23:08:37 +08:00
ning
63690ba084 fix: cli upgrade alert_mute 2023-03-24 21:01:34 +08:00
ning
bc6616ce7c refactor: update goreleaser 2023-03-24 17:37:35 +08:00
ning
b96ff22a21 fix: cur event query prod 2023-03-24 17:18:42 +08:00
ning
bfec911e9c fix: upgrade set datasoruce status 2023-03-24 16:03:31 +08:00
ning
76a94db7c1 refactor: update upgrade.sql 2023-03-24 16:00:30 +08:00
ning
eef67c956f refactor: update upgrade.sql 2023-03-24 15:31:10 +08:00
ning
2a405c85e0 refactor: alert use enabled datasource 2023-03-24 11:56:25 +08:00
ning
a2bdeb4f0e refactor: alert use enabled datasource 2023-03-24 11:26:27 +08:00
ning
5a880f002e refactor: alert rule import 2023-03-23 17:35:12 +08:00
ning
e4733e9a04 Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-03-23 17:26:35 +08:00
ning
a9595aea18 refactor: alert rule add severities 2023-03-23 17:26:22 +08:00
Yening Qin
101390b4ae refactor: change builtin alert rule list api (#1440) 2023-03-23 16:40:43 +08:00
ning
39e80ea786 feat: add query-instant-batch api 2023-03-23 11:18:45 +08:00
ning
f118cadaea fix: panic when processor is nil 2023-03-23 10:31:19 +08:00
ning
bad49d2773 refactor: datasource struct 2023-03-22 17:43:47 +08:00
ning
a897ae6db8 Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-03-22 17:38:44 +08:00
ning
aac135c498 fix: webhooh post 2023-03-22 17:38:27 +08:00
Ulric Qin
e7621ae200 code refactor 2023-03-22 13:41:43 +08:00
Ulric Qin
c3702cde43 code refactor 2023-03-22 13:36:57 +08:00
Ulric Qin
578ce375b5 code refactor 2023-03-22 13:24:46 +08:00
Ulric Qin
a00be34e8e add notify_feishu.py 2023-03-22 12:11:30 +08:00
ning
02d02463f7 fix: target insert 2023-03-21 16:33:06 +08:00
ning
96a1d4e903 feat: event add cluster name 2023-03-17 15:25:36 +08:00
ning
e2b57396e3 refactor: ident heartbeat 2023-03-17 14:59:18 +08:00
ning
381654dec5 refactor: wrap ident in redis 2023-03-16 18:05:28 +08:00
ning
82ac0fa625 update readme 2023-03-16 16:38:32 +08:00
ning
e4d65808bf add readme 2023-03-16 16:33:25 +08:00
ning
34965d818b update readme 2023-03-16 16:01:03 +08:00
ning
d4eadef378 Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-03-16 15:13:11 +08:00
ning
300405dc50 fix: email sender 2023-03-16 15:12:58 +08:00
Ulric Qin
49bb5e1ee3 debug send users 2023-03-16 14:54:01 +08:00
ning
45659ee98f refactor notify config cache sync 2023-03-16 14:04:10 +08:00
ning
82b98967d8 fix: alert rule update channels 2023-03-16 12:25:24 +08:00
ning
6336d6de66 update docker integrations 2023-03-16 11:46:35 +08:00
Ulric Qin
e8fd80b6d5 refactor 2023-03-16 11:36:48 +08:00
Yening Qin
dca4e4c83b feat: smtp ibex config manage on the web page (#1428)
* feat: smtp ibex config manage on the web page
2023-03-15 23:36:38 +08:00
ning
6514891b3a smtp ibex config to web 2023-03-15 16:55:58 +08:00
ning
3383ca12fa Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-03-15 14:46:27 +08:00
ning
86b5c9668b update event example 2023-03-15 14:46:13 +08:00
ryan
540ef0244d fix: prometheus job n9e's targets wrong (#1424)
"nwebapi:18000","nserver:19000" -> "n9e:17000"
2023-03-15 12:04:55 +08:00
ning
0b25f77e61 refactor: alert rule import 2023-03-14 15:08:45 +08:00
ning
2206e8d2c1 update integrations 2023-03-14 14:42:10 +08:00
710leo
644df733d3 refactor: delete no use api 2023-03-13 23:56:23 +08:00
710leo
2f9d7843d8 fix: alert rule batch update 2023-03-13 23:33:49 +08:00
Ulric Qin
9d1486b058 update categraf config 2023-03-13 22:16:13 +08:00
ning
d52b675516 update config.toml 2023-03-13 20:47:15 +08:00
ning
cae75d3930 update config.toml 2023-03-13 20:43:42 +08:00
ning
8f6d256300 refactor: change api auth 2023-03-13 20:31:53 +08:00
Yening Qin
e74d6a3ee5 add docker compose (#1420)
* add docker compose
2023-03-13 18:54:35 +08:00
ning
7ecc9a4614 refactor: target get api 2023-03-13 16:46:58 +08:00
ning
0c7f97c826 code refactor 2023-03-13 16:35:19 +08:00
ning
b47d4f5385 refactor offset 2023-03-13 16:26:01 +08:00
ning
91a38ffc5f update offset check 2023-03-13 15:57:10 +08:00
ning
4e4c0f5d82 code refactor 2023-03-13 15:48:14 +08:00
ning
88d0b277ca refactor host check 2023-03-13 15:39:42 +08:00
ning
e3b0ed1fca host add info 2023-03-13 14:18:27 +08:00
ning
a29b5b90d2 code refactor 2023-03-13 13:03:22 +08:00
ning
992d5cdebd Merge branch 'main' of ssh://github.com/ccfos/nightingale 2023-03-13 13:00:55 +08:00
ning
848900a2bf code refactor 2023-03-13 12:59:57 +08:00
Ulric Qin
814af8085d Merge branch 'main' of github.com:ccfos/nightingale 2023-03-13 11:53:38 +08:00
Ulric Qin
4715c8e073 update wechat img 2023-03-13 11:53:26 +08:00
ning
d442e37051 fix: get miss host 2023-03-13 11:51:00 +08:00
ning
e2226f3f34 code refactor 2023-03-13 11:19:14 +08:00
ning
7d8a4af2ec refactor config 2023-03-12 12:27:48 +08:00
ning
5208138a40 refactor config 2023-03-12 12:13:26 +08:00
ning
fce91ffedb refactor: delete no use columns 2023-03-11 22:28:20 +08:00
ning
2310b3d1e5 delete no use conf 2023-03-11 11:46:55 +08:00
Yening Qin
462e9dd696 refactor: host heartbeat (#1417)
* refactor host heartbeat
2023-03-11 11:44:01 +08:00
Ulric Qin
4f6a0bf56b code refactor 2023-03-10 18:43:51 +08:00
Ulric Qin
bc708b4e11 update sql 2023-03-10 17:59:24 +08:00
ning
2b1244616a update goreleaser 2023-03-10 16:40:19 +08:00
ning
274da279f5 update goreleaser 2023-03-10 14:24:46 +08:00
ning
2b7ab746f5 update goreleaser 2023-03-10 11:26:41 +08:00
ning
10427f5a47 update goreleaser 2023-03-10 11:18:19 +08:00
ning
c366a641a4 update goreleaser 2023-03-10 11:10:57 +08:00
ning
e044954798 fix sso init 2023-03-10 10:51:36 +08:00
ning
b51b93c846 fix sso config put 2023-03-10 10:40:39 +08:00
ning
717941a9bc update upgrade cli 2023-03-09 23:59:06 +08:00
ning
1180f1fcfd update upgrade cli 2023-03-09 23:28:55 +08:00
ning
b94b494f6d update upgrade cli 2023-03-09 22:59:19 +08:00
ning
480dde89af delete no use code 2023-03-09 21:32:11 +08:00
ning
6c587ea4ef refactor i18n 2023-03-09 21:17:59 +08:00
ning
82a6786457 refactor sso login 2023-03-09 20:57:56 +08:00
Ulric Qin
70d41f0c77 fix sql 2023-03-09 19:29:49 +08:00
ning
21a0e755b2 fix init sql 2023-03-09 19:28:38 +08:00
ning
1aed12d93d n9e.sql delete no use sql 2023-03-09 19:21:51 +08:00
ning
f07964c9c9 refactor: udpate upgrade cli 2023-03-09 19:17:19 +08:00
Ulric Qin
5156ec13b1 code refactor 2023-03-09 19:11:09 +08:00
ning
550a12a3f7 update upgrade cli 2023-03-09 19:07:10 +08:00
Ulric Qin
1426ccce53 Merge branch 'main' of github.com:ccfos/nightingale 2023-03-09 19:05:14 +08:00
Ulric Qin
ef1fe403ba fix table schema 2023-03-09 19:04:50 +08:00
dependabot[bot]
eb9ad34748 build(deps): bump golang.org/x/net from 0.4.0 to 0.7.0 (#1414)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.4.0 to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.4.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-09 18:44:52 +08:00
Ulric Qin
615d909e5d delete no used column of table notify_tpl 2023-03-09 18:42:59 +08:00
ning
a51eaabe85 update pushgw.writers example 2023-03-09 18:27:34 +08:00
ning
fba99a1001 udpate config 2023-03-09 18:24:41 +08:00
ning
e910c1fb22 delete no use config 2023-03-09 18:22:45 +08:00
ning
21cad3e56c update readme 2023-03-09 18:20:32 +08:00
ning
69ca0e87e9 merge v5 2023-03-09 17:57:47 +08:00
ning
178de1fe73 v6 release 2023-03-09 17:43:51 +08:00
Yening Qin
87899cbedb compatible with alert rule older data structures (#1373)
* change alert rule
2023-02-08 13:30:09 +08:00
Yening Qin
d4257d11f2 fix: query https api (#1368) 2023-02-01 22:33:39 +08:00
zheng
00b9c31f29 fix cas 3.0 logic (#1365) 2023-01-30 10:43:01 +08:00
Ulric Qin
1c8c6b92a9 bugfix import 2023-01-29 13:23:27 +08:00
Yening Qin
9c9fe800e4 optimize heartbeat and rule sync (#1362) 2023-01-29 13:11:12 +08:00
xiaoziv
9aeeaa191e refactor: engine code refactor (#1361)
* engine code refactor

Co-authored-by: Yening Qin <710leo@gmail.com>
2023-01-18 13:10:18 +08:00
ning
e69112958b fix: build error 2023-01-17 20:41:23 +08:00
xiaoziv
6d8317927e subscribe code refactor (#1360)
* subscribe code refactor

* remove mute interface
2023-01-17 20:34:41 +08:00
Tripitakav
072f1bd51f feat: effective time support array (#1357)
* feat: effective time support array
2023-01-17 11:29:02 +08:00
MoonStill
25dbc62ff4 fix: host tag is overridden by ident (#1359)
* fix: host tag is overridden by ident

* fix: host tag is overridden by ident

Co-authored-by: deploy <deploy@izuler.io>
2023-01-16 23:35:46 +08:00
xiaoziv
b233067789 refactor: subscribe refactor (#1353)
* refactor: subscribe code refactor
2023-01-16 21:55:17 +08:00
kongfei605
d531178c9b convert tplx.Funcmap to text.funcmap (#1352) 2023-01-10 21:02:20 +08:00
ning
174df1495c refactor: change some log level 2023-01-10 19:59:41 +08:00
ning
ffe423148d fix: push event api 2023-01-10 19:08:39 +08:00
ning
926559c9a7 refactor: motify log print 2023-01-10 15:54:55 +08:00
Yening Qin
136642f126 optimize handle external event (#1350)
* optimize handle external event
2023-01-10 13:30:45 +08:00
Ulric Qin
a054828fcc Merge branch 'main' of github.com:ccfos/nightingale 2023-01-06 23:39:49 +08:00
Ulric Qin
e46e946689 code refactor 2023-01-06 23:39:37 +08:00
ning
cf083c543b fix: alert mute sync 2023-01-06 16:22:41 +08:00
xiaoziv
2e1508fdd3 feat: rule engine rewrite (#1340)
* feat: rule engine rewrite

* rename filter to muteStrategy

* rename file

* fix bg strategy match bug

* fix deadlock

* Update mute_strategy.go

* Update rule_helper.go

* use rule from cache

* add comment

* add IdentDeletedMuteStrategy

* rename strategy

* rename eventTags to tagsMap

Co-authored-by: ulricqin <ulricqin@qq.com>
2023-01-06 16:16:22 +08:00
kongfei605
954543a5b2 Parse rules without html escaper (#1345) 2023-01-06 11:02:36 +08:00
Ulric Qin
71a402c33c code refactor 2023-01-06 10:51:33 +08:00
Ulric Qin
e30a5a316f code refactor 2023-01-06 10:50:18 +08:00
jsp-kld
0c9b7de391 Dashboard for VMware (#1331)
by [vsphere-monitor](https://github.com/jsp-kld/vsphere-monitor)
2022-12-20 21:00:59 +08:00
Yening Qin
063b6f63df fix load prom options from database and add more log (#1330)
* add more log
* fix PromOptions set
2022-12-20 11:59:01 +08:00
lsy1990
44b780093a support fetch user info based on query type (#1326)
* support fetch user info based on query type

* refector on type
2022-12-17 17:36:40 +08:00
710leo
780ad19dd9 fix: alert mute 2022-12-17 12:37:06 +08:00
710leo
c6d133772a fix: sync alert mute and subscribe when cluster is blank 2022-12-17 10:44:27 +08:00
Allen Zhou
c5bb8a4a13 target tags can rewrite labels deined in categraf config file (#1321)
Co-authored-by: allenz <godloveallen@foxmail.com>
2022-12-14 11:20:51 +08:00
Ulric Qin
06c1664577 rename Writer.Name to Writer.ClusterName 2022-12-13 22:52:04 +08:00
710leo
96a4c1ebfa delete GaugeCronDuration cluster label 2022-12-13 16:50:17 +08:00
Yening Qin
b0c05368f7 n9e server support multi cluster alert (#1318)
* support multi

* refactor

* code refactor

* refactor

* code refactor

* fix run mult cluster rule

* code refactor

* add alerting_engine api

* add alerting_engine api

* update sql

* refactor recording push

* refactor

* refactor

* delete useless cluster

* split to fields

* change stats

* change stats
2022-12-13 16:24:23 +08:00
Ulric Qin
eebf2cff49 add api: userFindAll 2022-12-12 12:55:08 +08:00
Ulric Qin
30d021bc19 Merge branch 'main' of github.com:ccfos/nightingale 2022-12-12 11:20:37 +08:00
Ulric Qin
b4ea395fe3 update README 2022-12-12 11:20:06 +08:00
zhousbo
9f4d1a1ea7 fix: support redis sentinel password (#1315) 2022-12-09 22:55:25 +08:00
lsy1990
ed06da90d9 support fetch user group by user name (#1311) 2022-12-07 20:50:17 +08:00
hubo
9461b549d2 replace lable host to ident (#1302) 2022-11-30 20:12:38 +08:00
lunuan
3b1b595461 update dashboard template for mongodb (#1293)
Co-authored-by: LiuHX <huaxingliu@fintopia.tech>
2022-11-30 18:33:24 +08:00
Windy
4257de69fd fix: webapi conf sso section typo (#1298) 2022-11-30 14:27:47 +08:00
Mystery0 M
ddc86f20ee feat: add telegram notify support (#1295)
* feat: add telegram notify support
2022-11-30 14:20:21 +08:00
Ulric Qin
bf27162a9b modify default settings of DisableUsageReport 2022-11-28 20:57:42 +08:00
Ulric Qin
f8ac0a9b4a refactor forwarding logic 2022-11-23 20:40:18 +08:00
Yening Qin
7a190b152c feat: add timeseries sample log filter (#1281)
feat: add timeseries sample log filter
2022-11-22 21:53:34 +08:00
Ulric Qin
99fbdae121 refactor boardPutConfigs 2022-11-11 12:11:39 +08:00
kongfei605
aa26ddfb48 Merge pull request #1263 from ccfos/router_easyjson
regenerate easyjson file for router_opentsdb
2022-11-10 12:50:22 +08:00
kongfei
ba5aba9cdf sync main branch code 2022-11-10 12:47:46 +08:00
kongfei
3400803672 regenerate easyjosn file for router_opentsdb 2022-11-10 12:41:16 +08:00
kongfei605
f11377b289 replace json with easyjson for router (#1261) 2022-11-10 11:11:20 +08:00
kongfei
1165312532 replace json with easyjson for router 2022-11-10 11:00:38 +08:00
JellyTony
8a145d5ba2 feat: 报警脚本超时时间改为可配置 (#1253)
* Update docker-compose.yaml

* Update docker-compose.yaml

* feat: 报警脚本超时时间改为可配置

* feat: docker 镜像Alerting 增加 超时时间

Co-authored-by: ulricqin <ulricqin@qq.com>
Co-authored-by: JeffreyBool <zhanggaoyuan@mediatrack.cn>
2022-11-04 15:18:36 +08:00
47
352415662a feat:CAS and OAuth2 login (#1236)
* Feat(cas login):Add CAS login

Signed-off-by: root <foursevenlove@gmail.com>

* Fix(CAS login):1.print logs of CAS Authentication Response's Attributes 2.modify fileds of ssoClient and CAS config.

Signed-off-by: root <foursevenlove@gmail.com>

* Fix(CAS login):Fields modifing

Signed-off-by: root <foursevenlove@gmail.com>

* Feat(OAuth Login):1.Add OAuth2 login 2.Add display name

Signed-off-by: root <foursevenlove@gmail.com>

* Fix(webapi.conf):Add example

Signed-off-by: root <foursevenlove@gmail.com>

* fix(webapi.conf):Modify default value of username in OAuth2

Signed-off-by: root <foursevenlove@gmail.com>

* Fix:Error handling

Signed-off-by: root <foursevenlove@gmail.com>

Signed-off-by: root <foursevenlove@gmail.com>
2022-11-02 14:31:59 +08:00
Ulric Qin
65d8f80637 Merge branch 'main' of github.com:ccfos/nightingale 2022-11-02 08:35:06 +08:00
Ulric Qin
b3700c7251 add Headers configuration demo 2022-11-02 08:34:49 +08:00
chenginger
106a8e490a alert mute cannot refresh the bug (#1242)
bugfix:mute cannot be refreshed after being modified
2022-11-01 15:41:01 +08:00
Ulric Qin
5332f797a6 add alert duration in wecom.tpl 2022-10-30 17:11:51 +08:00
Ulric Qin
aff0dbfea1 use json-iterator/go instead encoding/json 2022-10-28 10:22:04 +08:00
Ulric Qin
da5dd683d6 bugfix 2022-10-25 09:46:32 +08:00
zheng
15892d6e57 规则名称支持变量 (#1217)
* 规则名称支持变量

* parse rule_name
2022-10-20 20:18:15 +08:00
xtan
fbff60eefb docs: pg init sql (#1210)
Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-10-20 12:32:13 +08:00
xtan
62867ddbf2 feat: conf file password supports ciphertext (#1207)
Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-10-20 12:31:48 +08:00
Ulric Qin
5d4acb6cc3 update sql 2022-10-19 12:25:50 +08:00
Yening Qin
b893483d26 prom client support add header (#1203)
* prom client support add header
2022-10-18 20:44:38 +08:00
Ulric Qin
4130a5df02 board: support ident field 2022-10-18 19:46:08 +08:00
Ulric Qin
445d03e096 Merge branch 'main' of github.com:ccfos/nightingale 2022-10-12 20:37:34 +08:00
Ulric Qin
577c402a5b support: callback_del 2022-10-12 20:37:21 +08:00
zheng
40bbbfd475 bugfix: duplicate recoding rule metric name (#1186) 2022-10-12 18:36:04 +08:00
Ulric Qin
0d05ad85f2 callback_add callback_del 2022-10-12 18:29:59 +08:00
Ulric Qin
e70622d18c bugfix: update cluster when heartbeat 2022-10-02 21:21:17 +08:00
gengleiming
562f98ddaf bug: user update by multifields, param need '...' (#1170)
bug: 多字段更新用户时,参数作为slice接受,需要拆包之后再往下传入。该bug导致user.Update方法只能成功更新第一个参数对应的字段
2022-09-26 14:45:58 +08:00
Ulric Qin
ee07969c8a code refactor 2022-09-23 10:52:52 +08:00
Ulric Qin
5b0e24cd40 code refactor 2022-09-23 10:12:12 +08:00
Ulric Qin
78b2e54910 bugfix: do not make event if target is nil 2022-09-23 10:06:10 +08:00
Ulric Qin
2e64c83632 bugfix: fix nil target 2022-09-23 10:03:10 +08:00
Ulric Qin
537d5d2386 add newline for categraf config.toml 2022-09-17 09:24:19 +08:00
Ulric Qin
86899b8c48 update README 2022-09-16 19:40:34 +08:00
Ulric Qin
fcc45ebf2a update readme 2022-09-16 19:39:41 +08:00
KurolZ
95727e9c00 feat: support for sharing dashboards (#1150)
* feat: support for sharing dashboards

* merge dashboard get interface

* update read-only permission verification
2022-09-16 15:03:12 +08:00
Yening Qin
3a3ad5d9d9 feat: add configs service api (#1155)
* add configs service api
2022-09-16 12:06:39 +08:00
xtan
7209da192f docs: fix pg init sql (#1154)
Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-09-15 18:28:09 +08:00
Ulric Qin
98f3508424 add configuration ForceUseServerTS 2022-09-09 13:44:50 +08:00
Ulric Qin
c33900ee1b code refactor 2022-09-07 13:57:17 +08:00
Ulric Qin
a2490104b9 add freedomkk-qfeng as active contributor 2022-09-07 13:56:51 +08:00
Ulric Qin
1a25c3804e add lsy1990 as active contributor 2022-09-07 13:55:12 +08:00
xtan
23eb766c14 feat: alert_subscribe add name and disabled (#1145)
* feat: alert_subscribe add name and disabled

* feat: alert_subscribe add name and disabled

Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-09-07 12:24:38 +08:00
SunnyBoy-WYH
a7bad003f5 feat: alert-mute support edit and disable (#1144)
* batch query prom for single panel

* make code better:

1.extract server/api.go

2.make webapi reading prom with reusing server's API,not a new prom client

* clear code

* clear code

* format code
clear code

* move reader.go,reuse webapi/prom/prom.go clusterTypes clients cache

* clear code,extract common method

* feat: add edit and disabled for alert mute

* fix cr problem

* disabled add default 0
2022-09-05 12:11:50 +08:00
xtan
e4e48cfda0 docs: pg init sql (#1142)
Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-09-03 18:59:31 +08:00
Ulric Qin
d0ce4c25e5 Merge branch 'main' of github.com:ccfos/nightingale 2022-09-02 12:19:47 +08:00
Ulric Qin
01aea821b9 extract ident from append tags 2022-09-02 12:19:34 +08:00
xtan
bdc1c1c60b feat: compatible with redis4 to 7 (#1141)
Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-09-01 18:10:31 +08:00
Yening Qin
09f37b8076 refactor: change alert mute clean (#1140)
refactor: change alert mute clean
2022-09-01 11:18:04 +08:00
kongfei605
fc4c4b96bf Merge pull request #1138 from ccfos/mm_notification
mm notification support at someone
2022-08-31 23:05:28 +08:00
kongfei
5c60c2c85e mm notification support at someone 2022-08-31 23:03:29 +08:00
kongfei
1e9bd900e9 update notify.py 2022-08-31 17:55:26 +08:00
kongfei605
1ca000af2c Merge pull request #1137 from kongfei605/notification
add mattermost notification
2022-08-31 15:57:23 +08:00
kongfei
81fade557b fix dingtalk notification url 2022-08-31 15:45:59 +08:00
kongfei
b82f646636 update configurarion in docker 2022-08-31 15:37:34 +08:00
kongfei
26a3d2dafa add mm notification with notify plugin 2022-08-31 15:31:27 +08:00
kongfei
5e931ebe8e add mm notification 2022-08-31 14:11:28 +08:00
Ulric Qin
8c45479c02 add primary key 2022-08-29 11:27:53 +08:00
Ulric Qin
940313bd4e use big nodata interval 2022-08-27 18:15:56 +08:00
xiaoziv
5057cd0ae6 add id column for table user_group_member and role_operation (#1126)
Co-authored-by: Ziv <xiaozheng@tuya.com>
2022-08-27 10:40:11 +08:00
Ulric Qin
a4be2c73ac Merge branch 'main' of github.com:ccfos/nightingale 2022-08-27 10:35:27 +08:00
Ulric Qin
a38e50d6b8 bugfix: server hearbeat 2022-08-27 10:35:15 +08:00
laiwei
89f66dd5d1 improve commuinity guide (#1133)
* improve community governance

* improve guide

* update contributors guide
2022-08-26 19:54:02 +08:00
ulricqin
3963470603 add configuration ForceUseServerTS (#1128) 2022-08-22 23:22:58 +08:00
xiaoziv
640b6e6825 fix: add board check when del group (#1124)
* fix: add board check when del group

* Update busi_group.go

Co-authored-by: Ziv <xiaozheng@tuya.com>
Co-authored-by: ulricqin <ulricqin@qq.com>
2022-08-22 19:08:40 +08:00
ulricqin
e7d2c45f9d Manage bindings of n9e-server and datasource in web (#1127)
* manage bindings of n9e-server and datasource

* fix sync memsto
2022-08-22 18:39:29 +08:00
Yening Qin
80ee54898a feat: alert rule support cate (#1123)
* alert rule support cate

* his_event add cate

* change RecoverEvent time

* add get event api

* event query by cate
2022-08-22 14:17:17 +08:00
xtan
fe68cebbf9 docs: sync pg init sql (#1122)
Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-08-19 15:57:06 +08:00
Ulric Qin
c1fec215a9 add some api for server cluster bindings 2022-08-18 09:27:43 +08:00
Ulric Qin
388228a631 collect target total number 2022-08-17 20:16:42 +08:00
ulricqin
b4ddd03691 read prom url from database (#1119)
* add model alerting_engine

* heartbeat using db

* reader.Client from database

* fix sql
2022-08-17 17:20:42 +08:00
Yening Qin
b92e4abf86 feat: support handle event api (#1113)
* support handle event service api
2022-08-17 11:22:49 +08:00
Ulric Qin
a1c458b764 use hostname:port as identity 2022-08-17 10:28:57 +08:00
laiwei
acb4b8e33e improve community governance (#1115) 2022-08-16 14:20:57 +08:00
zheng
54eab51e54 添加告警规则执行日志 (#1112) 2022-08-15 17:17:28 +08:00
HK.MF
be89fde030 add aws cloudwatch rds metrics descriptions
Co-authored-by: e <hackermofrom@gmail.com>
2022-08-13 19:52:07 +08:00
jeff
37711ea6b2 add ping监控指标中文说明 (#1110)
add ping监控指标中文说明
2022-08-12 16:42:26 +08:00
xiaoziv
3b5c8d8357 optimize error report (#1109)
* optimize error report

* code refactor

* add /-/reload as reload route like prometheus

Co-authored-by: ziv <xiaozheng@tuya.com>
2022-08-12 14:10:03 +08:00
JellyTony
635369e3fd Update docker-compose.yaml (#1107)
* Update docker-compose.yaml

* Update docker-compose.yaml

Co-authored-by: ulricqin <ulricqin@qq.com>
2022-08-12 13:16:02 +08:00
ulricqin
6c2c945bd9 event.Cluster use target.Cluster instead of rule.Cluster (#1108) 2022-08-12 13:13:06 +08:00
xiaoziv
48d24c79d6 use slim base image (#1105)
Co-authored-by: ziv <xiaozheng@tuya.com>
2022-08-11 19:35:33 +08:00
xiaoziv
c6a1761a7b support tpls reload (#1104)
Co-authored-by: ziv <xiaozheng@tuya.com>
2022-08-11 17:05:41 +08:00
Ulric Qin
23d7e5a7de add disk_util for target table 2022-08-10 17:05:29 +08:00
xiaoziv
b1b2c7d6b0 feat: support ident disk usage metric (#1100)
* feat: support ident disk usage metric

* code refactor

Co-authored-by: ziv <xiaozheng@tuya.com>
2022-08-10 17:00:49 +08:00
Ulric Qin
f34c3c6a2c comment default WriteRelabels 2022-08-10 16:53:41 +08:00
Ulric Qin
454dc7f983 go mod tidy 2022-08-10 16:52:51 +08:00
Resurgence
c1e92b56b9 feat: add write_relabel action before n9e remote writing to multi tsdb (#1098)
* add write relabel config

* change parse relabel Regex field time when config loaded
2022-08-10 16:50:52 +08:00
xiaoziv
fd93fd7182 feat: support i18n metric desc (#1097)
* support i18n metric desc

* code refactor

* code refactor

Co-authored-by: ziv <xiaozheng@tuya.com>
2022-08-10 13:21:11 +08:00
Ulric Qin
1a446f0749 fix configurations: TargetMetrics 2022-08-10 10:36:02 +08:00
Ulric Qin
f18ed76593 escape TargetMetrics 2022-08-09 20:07:17 +08:00
Ulric Qin
9b3a9f29d9 extract promql to webapi.conf 2022-08-09 20:01:54 +08:00
Ulric Qin
49965fd5d5 fix target mem util 2022-08-09 17:19:27 +08:00
Ulric Qin
a248e054fa add some host metrics for targets get api 2022-08-09 17:11:24 +08:00
ning
bbb35d36be fix: categraf panic when use docker compose 2022-08-09 10:44:18 +08:00
xiaoziv
fd3e51cbb1 fix i18n header bug (#1095)
Co-authored-by: ziv <xiaozheng@tuya.com>
2022-08-08 20:28:36 +08:00
xiaoziv
bd0480216c feat: support i18n request headerkey (#1094)
Co-authored-by: ziv <xiaozheng@tuya.com>
2022-08-08 19:02:14 +08:00
Ulric Qin
2c963258cf code refactor 2022-08-08 15:26:11 +08:00
Yening Qin
b4f267fb01 feat: prom support tls (#1091) 2022-08-08 12:17:52 +08:00
xiaoziv
ea46401db2 remove record rule check (#1090)
Co-authored-by: ziv <xiaozheng@tuya.com>
2022-08-06 18:15:04 +08:00
xiaoziv
58e777eb00 support graph url (#1088)
Co-authored-by: ziv <xiaozheng@tuya.com>
2022-08-06 18:12:33 +08:00
xiaoziv
04a9161f75 feat: support rule convert from prometheus/vmalert (#1087)
* feat: support rule convert from prometheus/vmalert

* Update rule_converter.py

* Update rule_converter.py

Co-authored-by: ulricqin <ulricqin@qq.com>
2022-08-04 20:06:54 +08:00
xtan
1ed8f38833 feat: add first trigger time (#1086)
Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-08-04 19:29:44 +08:00
Ulric Qin
bb17751a81 fix typo 2022-08-02 12:21:01 +08:00
ulricqin
a8dcb1fe83 add retry controller for poster (#1082) 2022-08-02 12:20:02 +08:00
Ulric Qin
1ea30e03a4 check user exists when refresh token 2022-08-01 14:44:22 +08:00
kongfei605
ba0eafa065 docker compose use latest version of n9e and categraf (#1079) 2022-07-29 17:38:28 +08:00
xtan
c78c8d07f2 refactor: error info return (#1077)
Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-07-29 17:38:03 +08:00
Ulric Qin
8fe9e57c03 Merge branch 'main' of github.com:ccfos/nightingale 2022-07-29 17:35:49 +08:00
Ulric Qin
64646d2ace refactor linux dashboard 2022-07-29 17:35:23 +08:00
ning
e747e73145 add debug log for ldap login 2022-07-29 15:38:45 +08:00
xtan
896f85efdf refactor: add error log (#1076)
* refactor: add error log

* refactor: update error log

* refactor: fix error log

Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-07-29 11:41:39 +08:00
Ulric Qin
77e4499a32 refactor linux dashboard 2022-07-27 19:05:00 +08:00
ulricqin
7c351e09e5 add api: /board/:bid/pure (#1073) 2022-07-27 14:30:35 +08:00
xiaoziv
14ad3b1b0a fix proxy auth username error (#1072) 2022-07-27 14:13:48 +08:00
Ulric Qin
184867d07c feature: query busigroup by ident 2022-07-27 13:13:17 +08:00
Ulric Qin
3476b95b35 fix: query busigroup by ident 2022-07-26 18:23:14 +08:00
Ulric Qin
76e105c93a query busigroup by ident 2022-07-26 17:59:57 +08:00
Ulric Qin
39705787c9 Merge branch 'main' of github.com:ccfos/nightingale 2022-07-26 15:54:42 +08:00
Ulric Qin
293680a9cd use english comma 2022-07-26 15:54:25 +08:00
Yening Qin
05005357fb feat: push event api add mute (#1070) 2022-07-25 16:05:35 +08:00
ulricqin
ba7ff133e6 modify prometheus query batch response format (#1068) 2022-07-23 17:50:16 +08:00
ulricqin
0bd7ba9549 code refactor notify (#1066) 2022-07-22 18:12:42 +08:00
ulricqin
17c7361620 code refactor notify plugin (#1065) 2022-07-22 17:56:52 +08:00
lsy1990
c45cbd02cc supply plugin to notify maintainer (#1063) 2022-07-22 17:02:49 +08:00
hwloser
04cb501ab4 [fix] fix the docker problem of apple chip (#1060)
Co-authored-by: huanwei <huanwei@huanweideMacBook-Pro.local>
2022-07-21 14:46:27 +08:00
Yening Qin
ba6f089c78 fix: get alert rules by api (#1059)
* fix event push api
2022-07-19 12:10:02 +08:00
Ulric Qin
ab0cb6fc47 Merge branch 'main' of github.com:ccfos/nightingale 2022-07-18 17:08:07 +08:00
Ulric Qin
2847a315b1 add server-dash.json 2022-07-18 17:05:45 +08:00
Yening Qin
65439df7fb fix event push api (#1057) 2022-07-18 14:37:31 +08:00
laiwei
b6436b09ce update community governance (#1056)
* update readme to add badge
* update community gov
2022-07-17 21:57:16 +08:00
Ulric Qin
92354d5765 code refactor 2022-07-17 13:22:16 +08:00
SunnyBoy-WYH
05651ad744 Query batch feature (#1052)
* batch query prom for single panel

* make code better:

1.extract server/api.go

2.make webapi reading prom with reusing server's API,not a new prom client

* clear code

* clear code

* format code
clear code

* move reader.go,reuse webapi/prom/prom.go clusterTypes clients cache

* clear code,extract common method
2022-07-17 12:52:33 +08:00
Ulric Qin
b7ff82d722 alertSubscribePut can modify cluster 2022-07-13 19:24:09 +08:00
Ulric Qin
a285966560 fix func RecordingRuleGetsByCluster 2022-07-13 11:01:27 +08:00
xiaoziv
538880b0e0 [feature] support multiple cluster config with mute&subscribe (#1046)
* [feature] support multiple cluster config with mute&subscribe

* [feature] support multiple cluster config with mute&subscribe
2022-07-13 10:56:57 +08:00
kongfei605
299270f74e keep build version in Makefile consistency with goreleaser (#1047) 2022-07-12 23:43:24 +08:00
xiaoziv
9c69362650 [feat(#984)] multiple cluster support (#1045)
* [feat(#984)] multiple cluster support

* add stats ClusterAll handle
2022-07-12 19:30:42 +08:00
ulricqin
d508aef7e5 fix mute: parse regexp (#1044) 2022-07-12 16:39:23 +08:00
Ulric Qin
616674b643 code refactor 2022-07-11 13:10:40 +08:00
zheng
94847d9059 get rule node (#1042) 2022-07-11 13:06:11 +08:00
Ulric Qin
cbd416495c modify server.conf in docker env 2022-07-11 13:04:04 +08:00
Ulric Qin
cc32194fb6 code refactor 2022-07-10 11:27:36 +08:00
Ulric Qin
f5e2b43526 go mod tidy 2022-07-10 10:28:43 +08:00
xiaoziv
5bc8f0b9b1 Feature mute enhancement (#1041)
* [feature(#1029)] alert mute enhancement

* handle error of proxy user Add
2022-07-09 22:06:33 +08:00
xtan
7359a69223 fix: fix plugin error (#1038)
* fix: fix plugin error

* fix-plugin

Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-07-08 18:21:09 +08:00
xtan
04d64d09d7 fix: fix version info (#1036)
Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-07-08 16:22:15 +08:00
xiaoziv
43343182e4 [feature] add proxy auth support (#1035)
Co-authored-by: ziv <xiazoheng@tuya.com>
2022-07-08 15:19:22 +08:00
Ulric Qin
072ab98fcf use ForwardDuration in goroutine 2022-07-08 12:53:32 +08:00
Ulric Qin
35ef6b9265 duplicate label key checker 2022-07-08 12:02:57 +08:00
Ulric Qin
eaa53f2533 check duplicate label key 2022-07-08 11:48:44 +08:00
Ulric Qin
de322c4daf add n9e_server.json 2022-07-08 10:03:04 +08:00
Ulric Qin
936c751a93 Merge branch 'main' of github.com:ccfos/nightingale 2022-07-08 09:48:22 +08:00
Ulric Qin
796a7014a1 use goroutine to forward data 2022-07-08 09:48:08 +08:00
xtan
f4368302ea fix: pg sql for recording rule (#1034)
Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-07-07 15:53:12 +08:00
kongfei605
01e611a9f9 auto release with github action (#1032)
* auto release with github action

* build arm64 artifacts
2022-07-07 14:17:23 +08:00
Yening Qin
315e0ef903 fix: get clusters by api (#1030) 2022-07-07 12:29:35 +08:00
Ulric Qin
98d5dfff8e add namespace and subsystem prefix for metrics 2022-07-07 12:23:06 +08:00
Ulric Qin
6b4705608b add forward stat 2022-07-07 12:13:45 +08:00
Ulric Qin
5907817cba n9e-server: add http request stat 2022-07-07 10:52:04 +08:00
Ulric Qin
aa97ac54d1 register GaugeSampleQueueSize 2022-07-07 10:17:15 +08:00
Ulric Qin
8fe548aba9 rename mapkey alertname to rulename 2022-07-07 10:06:34 +08:00
Tripitakav
18a9288b75 fix mute bug (#1025)
Co-authored-by: tripitakav <chengzhi.shang@longbridge.sg>
2022-07-07 10:05:39 +08:00
ulricqin
fe82886f09 report sample queue size (#1027)
* report sample queue size

* report sample channel size
2022-07-07 10:00:08 +08:00
xtan
32e6993eea fix: fix event api for service (#1026)
Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-07-07 09:58:05 +08:00
ning
56b61909a3 fix: event service api 2022-07-07 09:44:26 +08:00
ulricqin
2ef541cdd7 refactor recording rule and and field disabled (#1022) 2022-07-06 17:21:14 +08:00
laiwei
6b1d283cda Merge pull request #1019 from ccfos/community-guide
add community guide and governance docs (draft)
2022-07-06 17:08:14 +08:00
laiwei
c8e5566c81 add stargazers chart 2022-07-06 16:25:54 +08:00
laiwei
7f3d9df089 update 2022-07-06 16:20:58 +08:00
laiwei
99aa4dbca8 update format 2022-07-06 16:12:59 +08:00
Ulric Qin
c193b8abd4 remove drop table sql 2022-07-06 16:08:24 +08:00
laiwei
4efdc4f169 update format 2022-07-06 16:06:13 +08:00
Tripitakav
1304a4630b Add recording rule (#1015)
* add prometheus recording rules

* fix recording rule sql

* add record rule note

* fix copy error

* add some regx

Co-authored-by: 尚承志 <chengzhi.shang@longbridge.sg>
2022-07-06 15:58:08 +08:00
laiwei
2cc3f939a7 Merge branch 'main' into community-guide 2022-07-06 15:44:11 +08:00
laiwei
d0260e564c Merge remote-tracking branch 'origin/main' into community-guide 2022-07-06 15:40:52 +08:00
laiwei
2a24179423 update readme to add community governance 2022-07-06 15:40:25 +08:00
laiwei
34082b44f1 Merge branch 'main' of github.com:ccfos/nightingale 2022-07-06 13:12:04 +08:00
UlricQin
bfe340d24d upgrade 5.9.4 2022-07-05 16:54:15 +08:00
xtan
a9288e376d feat: persist notify cur number (#1013)
Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-07-05 16:42:20 +08:00
laiwei
cb9a03d010 update guide 2022-07-05 00:43:36 +08:00
laiwei
e62366b755 community guide 2022-07-05 00:37:01 +08:00
Ulric Qin
2a2a96d9fc add contains funcmap 2022-07-04 20:03:11 +08:00
ysyneu
64a671ae13 update kafka alerts and dashboard (#1012)
* update kafka alerts and dashboard

* update kafka dashboard

Co-authored-by: yushuangyu <yushuangyu@flashcat.cloud>
2022-07-04 19:56:51 +08:00
Ulric Qin
45945876d8 update README 2022-07-03 08:57:13 +08:00
Henry Chia
90dacd0085 fix typo (#1004)
* 修改拼写错误

修改拼写错误
exsits -> exists

* Update router_login.go
2022-06-29 19:08:58 +08:00
ning
540ef68dc8 fix: alert mute add by service 2022-06-29 11:11:12 +08:00
zheng
54cc981956 fix ForDuration (#999) 2022-06-28 16:13:23 +08:00
xtan
2e8ea354d7 refactor: now categraf v0.1.6 is fine (#993)
Co-authored-by: tanxiao <tanxiao@asiainfo.com>
Co-authored-by: ulricqin <ulric.qin@gmail.com>
2022-06-28 11:01:23 +08:00
Ulric Qin
217f52294e update categraf image version to v0.1.9 2022-06-27 15:06:14 +08:00
Ulric Qin
f9b2675077 upgrade 5.9.3 2022-06-27 15:01:34 +08:00
Ulric Qin
95fd2d99b2 code refactor 2022-06-27 12:28:40 +08:00
Ulric Qin
dba2b23e9e code refactor 2022-06-27 12:25:26 +08:00
Ulric Qin
acbc199143 code refactor 2022-06-27 12:23:25 +08:00
Ulric Qin
2449c8715e update doc 2022-06-27 12:18:42 +08:00
Ulric Qin
c62593c0eb update README 2022-06-23 10:37:47 +08:00
Ulric Qin
00cbc9342f modify vx-qrcode.png 2022-06-22 16:56:08 +08:00
Ulric Qin
df5a3a37f2 refactor docker-compose.yaml for categraf 2022-06-22 10:41:59 +08:00
xtan
5ec14c588b Feat:update docker-compose from telegraf to categraf (#992)
Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-06-22 10:25:57 +08:00
Ulric Qin
d78a3a638a update issue template 2022-06-20 15:20:50 +08:00
Ulric Qin
19d2cbfa27 add issue_template 2022-06-20 15:17:10 +08:00
chenxuan
f9af916352 fix alert put api not verify bug (#987) 2022-06-20 11:50:14 +08:00
xtan
90db12b513 Fix:fix target_up nodata judge for prometheus scrape (#986) 2022-06-17 22:44:25 +08:00
Ulric Qin
7d326ef306 use metrics as hash key 2022-06-17 09:56:10 +08:00
Ulric Qin
d0b005fb14 code refactor: set createBy when update metric_view 2022-06-16 13:17:58 +08:00
Ulric Qin
118060cf77 UPDATE README 2022-06-15 15:38:11 +08:00
Ulric Qin
d2ef68daac upgrade 5.9.2 2022-06-15 14:40:43 +08:00
Ulric Qin
8393b93c53 Merge branch 'main' of github.com:ccfos/nightingale 2022-06-15 14:01:22 +08:00
Ulric Qin
63adcc2cd9 bugfix for alert-aggr-views 2022-06-15 14:01:01 +08:00
xtan
60c842c704 fix: NotifyMaxNumber for postgres db (#978)
* fix: NotifyMaxNumber for postgres db

* fix: sql for pg

Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-06-13 10:56:14 +08:00
Ulric Qin
f6fd6aed7f add some categraf alerts.json 2022-06-11 17:54:52 +08:00
Ulric Qin
cb92368e5b add categraf dashboard 2022-06-11 17:40:43 +08:00
Ulric Qin
8cd97db362 add some categraf dashboard 2022-06-11 17:38:10 +08:00
Ulric Qin
94e1359895 fix handler: NotifyMaxNumber 2022-06-10 17:49:48 +08:00
Ulric Qin
1bcc5b77ec remote write and read: support header 2022-06-10 17:37:33 +08:00
Ulric Qin
ae622e0c08 fix 2022-06-10 16:36:47 +08:00
Ulric Qin
c951f7d822 support max notify number 2022-06-10 16:26:53 +08:00
Ulric Qin
6a366acc74 modify log level 2022-06-10 15:39:45 +08:00
Ulric Qin
a5f7d5e9cf modify log level 2022-06-10 15:15:13 +08:00
Ulric Qin
ea2249c30c forward samples in sequence 2022-06-10 14:20:18 +08:00
Ulric Qin
a8c60c9f2b alert_aggr_view support modify by admin 2022-06-10 13:55:26 +08:00
xtan
0581e02cf3 Feat:add common template functions (#976)
* Feat:增加常用模板函数

* Feat:修改增加模板函数的实现方式

Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-06-08 20:40:45 +08:00
Ulric Qin
efec811b91 update README 2022-06-08 13:40:06 +08:00
Ulric Qin
f85209c817 add gif in README 2022-06-08 11:19:57 +08:00
Ulric Qin
7fda5a9a4b update README 2022-06-08 11:17:03 +08:00
Ulric Qin
ab689fc0db update README 2022-06-08 11:12:51 +08:00
Ulric Qin
cdcdbe8f70 update README 2022-06-08 11:11:15 +08:00
Ulric Qin
46ca8a409a update README 2022-06-08 10:53:58 +08:00
Ulric Qin
e5c1641b6b code refactor: move struct ReaderOptions to config 2022-06-07 18:00:11 +08:00
Ulric Qin
3e475e7e08 Merge branch 'main' of github.com:ccfos/nightingale 2022-06-07 17:56:49 +08:00
Ulric Qin
3899144f8f add header for writer post 2022-06-07 17:56:23 +08:00
ning
b8cb9e7734 fix: linux_by_telegraf dashboard 2022-06-07 14:17:17 +08:00
xtan
c62b9edf87 fix:pg数据库脚本同步 (#974)
Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-06-06 11:48:57 +08:00
ning
0e5aea40e8 Merge branch 'main' of github.com:ccfos/nightingale 2022-06-02 11:07:40 +08:00
ning
1dbfcd3dc8 refactor: service api 2022-06-02 11:07:31 +08:00
xtan
a4ef5fca46 pg数据库初始化脚本字段同步 (#969)
Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-06-01 19:57:17 +08:00
Ulric Qin
7cf309345f Merge branch 'main' of github.com:ccfos/nightingale 2022-06-01 12:58:36 +08:00
Ulric Qin
495632a064 fix alert rule delete by service 2022-06-01 12:58:09 +08:00
xtan
f6591e80ea Feat:提供基于Postgres的数据库初始化脚本 (#967)
Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-05-31 18:06:42 +08:00
Ulric Qin
ab5e8c366e code refactor 2022-05-31 14:44:57 +08:00
Ulric Qin
ce35e23a0f modify alert rule verify 2022-05-31 13:08:09 +08:00
Ulric Qin
ece263ea45 update readme 2022-05-30 09:44:19 +08:00
Ulric Qin
f777318cc7 update README 2022-05-30 09:13:44 +08:00
Ulric Qin
c29f3ecdeb update README 2022-05-30 09:12:16 +08:00
Ulric Qin
8f8740ad94 update README 2022-05-30 09:10:58 +08:00
Ulric Qin
9acabba761 use go1.18 and tidy go.mod 2022-05-30 09:08:54 +08:00
Ulric Qin
c3adcc877a use standalone mode when RedisType is blank 2022-05-30 08:39:35 +08:00
xtan
7f92e921b4 Feat:增加对redis集群模式、哨兵模式的支持 (#965)
* 修复go plugin相关错误

* Feat:增加对redis集群模式、哨兵模式的支持

Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-05-30 08:36:17 +08:00
caojiaqiang
e22a4394f7 feat: 告警处理出错给Maintainer管理员发送告警信息 (#955)
* feat: 告警处理出错给管理员发送告警信息

* feat: 告警处理出错给管理员发送告警信息,发送信息自己拼接,不使用模版

* feat: 告警处理出错给管理员发送告警信息,不实用AlertCurEvent结构

* feat: 告警处理出错给管理员发送告警信息,日志打印、文本发送优化
2022-05-27 19:00:41 +08:00
xtan
070e5051c6 修复go plugin相关错误 (#964)
Co-authored-by: tanxiao <tanxiao@asiainfo.com>
2022-05-27 17:38:15 +08:00
Yening Qin
c040dffb5f feat: add some service api
* feat: add some service api
2022-05-25 15:14:52 +08:00
Ulric Qin
c2f2a7d5e2 use post method to get datasources 2022-05-23 13:31:05 +08:00
Ulric Qin
fd29d18312 delete no use code 2022-05-23 13:29:08 +08:00
Ulric Qin
2f724075b2 loop load clusters from api 2022-05-23 13:13:35 +08:00
Ulric Qin
06224e4b20 refactor 2022-05-22 17:03:57 +08:00
Ulric Qin
f81888cd8a get prometheus info from api. code skelton 2022-05-22 16:56:58 +08:00
Ulric Qin
6a7b543ad6 add mutex for prom transport 2022-05-22 12:45:25 +08:00
Ulric Qin
6ba93527ba upgrade server.conf and webapi.conf in docker environment 2022-05-21 18:10:22 +08:00
Ulric Qin
d6d2639e3a upgrade 5.8.0 2022-05-21 17:49:33 +08:00
ulricqin
ecc51001c3 New Dashboard and support variables in alert_rule_note (#953)
* change alert rule

* Db connect update (#939)

* update target's cluster field when clustername modified in server.conf

* code refactor

* db connect update

* delete DriverName

Co-authored-by: Ulric Qin <ulric.qin@gmail.com>
Co-authored-by: zhangjiandong <zhang.jiandong@baiso.com>

* update sql struct

* change sql

* add some files for new dashboard

* add new board apis

* fix query data

* add dashboard migrate api

* rule note support template

* add value as data for template

* parse rule note before persist

* use prometheus var names

* fixbug rule note template

* refactor sql

* add logo

* refactor: add some log

* mv package poster to pkg

* add version

* compute user total in usage reporter

* feat: add some service api

Co-authored-by: 710leo <710leo@gmail.com>
Co-authored-by: countingwww <871138993@qq.com>
Co-authored-by: zhangjiandong <zhang.jiandong@baiso.com>
2022-05-20 23:48:49 +08:00
GitHamburg
e2232bfa12 update server.conf, add DisableUsageReport (#949) 2022-05-20 13:13:10 +08:00
Ulric Qin
2bea8b7c84 add usage report 2022-05-17 19:24:06 +08:00
Ulric Qin
dd5ae29f82 delete no use code 2022-05-12 10:58:28 +08:00
Ulric Qin
cb741a5521 add wait tool for docker-compose 2022-05-11 12:43:34 +08:00
Ulric Qin
9d434a36d6 add wait tool for docker-compose 2022-05-11 12:42:08 +08:00
Ulric Qin
e89760f374 code refactor 2022-05-08 16:04:20 +08:00
Ulric Qin
02dd70480d update target's cluster field when clustername modified in server.conf 2022-05-08 16:02:50 +08:00
UlricQin
c8e59cdd0c upgrade 5.7.0 2022-04-28 14:30:23 +08:00
Ulric Qin
882952de3e feature: builtin metric_view can be modified by admin 2022-04-27 10:51:12 +08:00
Ulric Qin
279bec6eaa Delete redundant judgment logic 2022-04-24 10:39:24 +08:00
Ulric Qin
614ed283c0 rename MinVersion to TLSMinVersion 2022-04-22 22:25:02 +08:00
Ulric Qin
06672d5ff9 fix user group search 2022-04-22 22:18:58 +08:00
Ulric Qin
78b8cfd365 add tls configurations for webapi 2022-04-22 22:14:59 +08:00
Ulric Qin
e0f0e08852 support redis tls 2022-04-22 21:48:56 +08:00
Ulric Qin
e00f102703 give default configuration value for QueueCount 2022-04-21 12:29:43 +08:00
Ulric Qin
3921627fa2 Merge branch 'main' of github.com:didi/nightingale 2022-04-21 12:26:44 +08:00
Ulric Qin
7a1a65c31b add queue count control chan number 2022-04-21 12:24:26 +08:00
Curith
5e763f1a8b use const http status text instead of a variable (#921) 2022-04-21 11:30:25 +08:00
Ulric Qin
808fa5839a 5.6.4 dev 2022-04-21 11:08:39 +08:00
Ulric Qin
a0c5f94017 use goroutine to send metrics to backend 2022-04-21 11:07:56 +08:00
zheng
9ba1c2c32d 优化钉钉@ 方式,允许关闭at (#917)
token_xxx?noat=1
2022-04-19 15:14:18 +08:00
zheng
5333fb8eab 优化告警格式,增加 监控对象 (#918) 2022-04-19 15:05:07 +08:00
Ulric Qin
ee4a918fc7 Merge branch 'main' of github.com:didi/nightingale 2022-04-18 13:41:59 +08:00
Ulric Qin
1dbfe3417b upgrade 5.6.3 2022-04-18 13:41:20 +08:00
Ulric Qin
c829732af0 add configs for docker env 2022-04-18 13:40:40 +08:00
Yening Qin
1b313a3202 doc: improve readme (#916)
* doc: improve readme
2022-04-16 23:30:08 +08:00
qzh
5732c4403b perf: 合并targets_up指标为一个ident,减少资源利用。 (#915) 2022-04-15 21:17:16 +08:00
Yening Qin
6033a0a743 fix: err is nil (#914) 2022-04-15 14:35:26 +08:00
zheng
e8cfe46381 按告警级别和数量排序 (#913) 2022-04-15 14:33:44 +08:00
Ulric Qin
e94f807d52 delete no used code 2022-04-15 11:10:16 +08:00
Ulric Qin
c15490e756 code refactor 2022-04-14 19:09:25 +08:00
Ulric Qin
b25c523528 code refactor, use NotifyBuiltinChannels to control 2022-04-14 18:56:14 +08:00
Ulric Qin
6d27da8ad8 delete no used code 2022-04-14 17:32:51 +08:00
Ulric Qin
d0e6788724 upgrade 5.6.2 2022-04-14 17:20:07 +08:00
Ulric Qin
1633308000 modify queue size 2022-04-14 17:19:14 +08:00
UlricQin
08141e36cb use 5.6.1 image version 2022-04-14 14:40:10 +08:00
Ulric Qin
b5cfdb1ef6 upgrade 5.6.1 2022-04-14 12:58:02 +08:00
Ulric Qin
3a97a67c7e third time: code refactor for pr 906. use channel as queue for all the receivers 2022-04-14 12:57:30 +08:00
Ulric Qin
8d6101ec5a second time: code refactor for pr 906. new concurrent-map when init; move lock to WritersType 2022-04-14 12:43:39 +08:00
Ulric Qin
e73da37bc0 first time: code refactor for pr 906 2022-04-14 11:11:14 +08:00
qzh
3d587a5762 perf(opentsdb): 数据拉取以ident分发,并把list方式改为chan方式,提高消费效率。如果有多个prometheus实例,也可以通过header中的Ident字段进行一致性hash分发。 (#906)
Co-authored-by: zhihao.qu <zhihao.qu@ly.com>
2022-04-14 10:31:36 +08:00
zheng
42a6be95e8 fix dashboard name (#911) 2022-04-13 21:34:25 +08:00
zheng
ee8c367933 修复大盘目录错误 (#910) 2022-04-13 18:39:41 +08:00
Lars Lehtonen
a20e19922e src/pkg/ibex: fix dropped error (#907) 2022-04-13 10:43:32 +08:00
Ulric Qin
d6d588c5aa upgrade image version 2022-04-11 15:00:26 +08:00
Ulric Qin
1ba0f5ab74 upgrade to 5.6.0 2022-04-08 11:43:11 +08:00
Ulric Qin
b838cb1c6f return last insert object of metric view 2022-04-08 11:07:30 +08:00
Ulric Qin
7eb665e401 modify default sql of alert_aggr_view 2022-04-08 10:11:55 +08:00
Ulric Qin
cb3e371094 parse tags for cur_events 2022-04-07 18:30:11 +08:00
ning
ea30f38b9b doc: modify linux dashboard 2022-04-07 16:06:48 +08:00
ysyneu
8187334ef6 add alerts and dashboard templates for Elasticsearch, MongoDB and Linux Process (#904)
Co-authored-by: yushuangyu <yushuangyu@flashcat.cloud>
2022-04-07 15:53:09 +08:00
Ulric Qin
ac24e8b028 fix: import builtin dashboards 2022-04-07 14:09:14 +08:00
Ulric Qin
30ba544f35 fix order metric_view 2022-04-07 12:01:16 +08:00
Ulric Qin
14b1bc3710 upgrade 5.5.1 2022-04-07 11:43:49 +08:00
Ulric Qin
e8c0d6b987 order by cate and name 2022-04-07 11:37:49 +08:00
Ulric Qin
d0efb206d9 add preset metric_view 2022-04-06 19:04:02 +08:00
Ulric Qin
8abb04afde use hostname+pid instead of ip 2022-04-06 10:27:28 +08:00
Ulric Qin
f7318cfc5a alter table user to users 2022-04-05 09:12:30 +08:00
laiwei
067727165a improve readme (#898)
* improve readme

* resize img of readme
2022-04-02 12:31:10 +08:00
Ulric Qin
544c93c7cf Merge branch 'main' of github.com:didi/nightingale 2022-04-02 12:28:58 +08:00
Ulric Qin
66bc023e51 bugfix: list builtin alerts and dashboards 2022-04-02 12:21:03 +08:00
ning
c5ea2d0d24 doc: add linux telegraf dashboard 2022-04-02 11:15:45 +08:00
Ulric Qin
9e8d9b44b1 fix NotifyRecovered logic 2022-04-01 15:20:52 +08:00
Ulric Qin
db15eaab04 add linux_by_telegraf alerts 2022-03-31 16:06:14 +08:00
Ulric Qin
9d016212c8 move sender package to common 2022-03-31 15:31:21 +08:00
Ulric Qin
a4158c476e mv poster to common package 2022-03-31 15:27:14 +08:00
Ulric Qin
0f1148e096 add jmx_by_exporter dashboard 2022-03-31 15:01:03 +08:00
Ulric Qin
5d17f006f0 check smtp configurations 2022-03-31 12:02:57 +08:00
Ulric Qin
16d303a6fb rename var 2022-03-31 10:40:14 +08:00
Ulric Qin
70e5ac4898 add alert_aggr_view 2022-03-31 10:24:42 +08:00
UlricQin
a914de63c6 upgrade docker-compose to 5.5.0 2022-03-30 15:37:55 +08:00
Ulric Qin
dec518369b update readme 2022-03-30 14:31:21 +08:00
Ulric Qin
926a4e642a Merge branch 'main' of gitee.com:n9e/nightingale 2022-03-30 13:10:42 +08:00
UlricQin
3236883cce update server.conf 2022-03-30 13:09:58 +08:00
ning
be1c3b17d6 doc: add node_exporter kafka_exporter zk_exporter's dashboard and alert template 2022-03-30 13:03:14 +08:00
Yening Qin
a67356639b feat: support OIDC (#893)
* feat: support oidc

* refactor: sso -> oidc

* refactor: add AccessToken

* refactor: change some naming
2022-03-30 11:01:02 +08:00
Lars Lehtonen
7b3cb2eb00 fix router errors (#894) 2022-03-30 10:57:54 +08:00
dependabot[bot]
8459ffb690 Bump github.com/gogo/protobuf from 1.1.1 to 1.3.2 (#895)
Bumps [github.com/gogo/protobuf](https://github.com/gogo/protobuf) from 1.1.1 to 1.3.2.
- [Release notes](https://github.com/gogo/protobuf/releases)
- [Commits](https://github.com/gogo/protobuf/compare/v1.1.1...v1.3.2)

---
updated-dependencies:
- dependency-name: github.com/gogo/protobuf
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-30 10:18:31 +08:00
Ulric Qin
b260a20646 give blank method for datadog-agent 2022-03-28 16:41:31 +08:00
Ulric Qin
db29adff5d Merge branch 'main' of github.com:didi/nightingale 2022-03-28 10:25:56 +08:00
Ulric Qin
d3576440d4 fix index of metric_view and alert_aggr_view 2022-03-28 10:15:53 +08:00
Ulric Qin
c557e383b6 add metric_view crud method 2022-03-27 19:06:31 +08:00
laiwei
768a1e37e9 add contributing of readme 2022-03-25 16:02:46 +08:00
Ulric Qin
46e2fc6ab6 add windows metrics description 2022-03-23 18:08:32 +08:00
Ulric Qin
dacf004797 add windows alerts 2022-03-23 17:47:22 +08:00
Ulric Qin
44ed81218a update promql of windows dashboard 2022-03-23 17:34:06 +08:00
Ulric Qin
d802abc86c add windows dashboard 2022-03-23 17:19:50 +08:00
Ulric Qin
4c22284ca7 add cluster field when import builtin alerts 2022-03-23 14:48:28 +08:00
Ulric Qin
929c970b42 import builtin dashboard 2022-03-23 14:04:55 +08:00
Ulric Qin
496c8d8356 handle alerts builtin 2022-03-23 13:58:45 +08:00
Ulric Qin
e707f1a23d Merge branch 'main' of github.com:didi/nightingale 2022-03-23 13:36:43 +08:00
Ulric Qin
e7145018ef add alerts and dashboards 2022-03-23 13:36:22 +08:00
Jeyrce.Lu
18164fdb16 perf: optimize alert plugin call(#886) (#891) 2022-03-22 18:10:35 +08:00
Ulric Qin
3b9e40c5d4 add severity in card 2022-03-22 15:49:59 +08:00
Ulric Qin
6d20b8ef72 fill notify groups of events 2022-03-22 15:36:51 +08:00
Ulric Qin
8bdd35975e AlertCurEventGetByIds 2022-03-22 15:24:25 +08:00
Ulric Qin
9ccdd6c3e7 fix nil pointer 2022-03-22 15:18:45 +08:00
Ulric Qin
30365a2256 code refactor 2022-03-22 15:14:56 +08:00
Ulric Qin
cdd4100a30 code refactor 2022-03-22 14:43:30 +08:00
Ulric Qin
2cd9f50357 code refactor 2022-03-22 14:38:56 +08:00
Ulric Qin
106345ff49 add debug log 2022-03-22 14:26:37 +08:00
Ulric Qin
7c8c961aef query alerts card 2022-03-22 14:10:10 +08:00
Ulric Qin
e1bd7f0267 verify alert_aggr_view 2022-03-22 11:38:16 +08:00
Ulric Qin
025c5809be add alert_aggr_view crud 2022-03-22 11:19:06 +08:00
Ulric Qin
d45fdd50e7 modify sql: add group_name for event 2022-03-21 17:35:10 +08:00
Ulric Qin
f4388d36de update readme, remove gitee docs links 2022-03-21 17:11:31 +08:00
Ulric Qin
4a62339c69 do not math.Round for metric value 2022-03-21 16:43:28 +08:00
Ulric Qin
5a9b8d6bd0 add configuration: BusiGroupLabelKey 2022-03-21 14:13:04 +08:00
Ulric Qin
8ce71de693 code refactor for append labels 2022-03-21 14:04:32 +08:00
Ulric Qin
6d9846f1f5 sync busi_group 2022-03-21 12:06:53 +08:00
Ulric Qin
c9be9b0538 add label_value field for busi_group 2022-03-21 11:44:51 +08:00
Ulric Qin
65f7214e67 update redis metrics 2022-03-20 16:33:39 +08:00
Jeyrce.Lu
302cebbbec [#886] Feature: 提供一种go plugin 告警通知方式 (#887)
* [#886] Feature: 提供一种go plugin 告警通知方式

* fix: 移除下层并发
2022-03-20 10:27:17 +08:00
zheng
46c60a32fd 修复无法删除空dashboard问题 (#889) 2022-03-17 19:07:24 +08:00
Ulric Qin
7ec6d84c7d use text for chart.configs 2022-03-17 18:57:57 +08:00
Ulric Qin
0bbdb03ace add metrics for mysqld_exporter 2022-03-17 10:38:13 +08:00
Ulric Qin
149d074206 add metrics of mysqld_exporter 2022-03-16 19:43:32 +08:00
Ulric Qin
0b491826ee modify metrics order of mysqld_exporters 2022-03-16 15:18:26 +08:00
Ulric Qin
e6d4f2540c add some mysql metric descriptions 2022-03-16 14:44:16 +08:00
Ulric Qin
fcc75710cb add some mysql metrics of mysqld_exporter 2022-03-16 13:23:35 +08:00
UlricQin
de65c5a6cf docker-compose use 5.4.1 2022-03-14 19:04:37 +08:00
Ulric Qin
fde52167b3 delete no use code 2022-03-07 18:21:10 +08:00
Ulric Qin
1ffdf3d283 bugfix: AdminRole 2022-03-07 18:19:19 +08:00
Ulric Qin
94a49c17f7 persist recovered events 2022-03-03 10:44:06 +08:00
Ulric Qin
e515039ad4 use bgrwCheck func to check alert_rule put 2022-03-03 10:25:52 +08:00
Ulric Qin
93f88296da update notify.py in docker dir 2022-03-01 17:07:20 +08:00
Ulric Qin
1f4e8e752e update docker-compose configs 2022-03-01 17:04:10 +08:00
Ulric Qin
fed9b9a19d upgrade docker-compose 2022-03-01 16:32:58 +08:00
Ulric Qin
fbcc71340d upgrade 5.4.0 2022-03-01 16:27:51 +08:00
Ulric Qin
c6356df81f +NotifyBuiltinEnable 2022-03-01 16:27:21 +08:00
Ulric Qin
085bd39684 modify mailbody 2022-03-01 14:02:38 +08:00
Ulric Qin
b73bef8a0c lower NotifyConcurrency 2022-03-01 13:52:03 +08:00
Ulric Qin
9c662de129 add smtp log 2022-03-01 13:50:51 +08:00
Ulric Qin
caa37b087c use batch send mail 2022-03-01 13:44:46 +08:00
Ulric Qin
b63c853889 use smtp.DialAndSend func 2022-03-01 13:27:23 +08:00
Ulric Qin
2ff79c7780 use golang as sender 2022-03-01 11:16:55 +08:00
Ulric Qin
403cb5a6ad not stable version 2022-02-28 23:50:02 +08:00
zheng
b43f196d86 优化只保留5位小数 (#878)
* 优化只保留5位小数

* 优化小数点保留方法
2022-02-28 18:06:23 +08:00
Ulric Qin
483b353494 Merge branch 'main' of github.com:didi/nightingale 2022-02-26 12:00:25 +08:00
Ulric Qin
cddc99981d modify perm of read tasks 2022-02-26 12:00:08 +08:00
zheng
01f1f50880 限制timestamp不能大于当前时间5分钟 (#872) 2022-02-18 19:17:28 +08:00
Ulric Qin
8664c3df37 refactor 2022-02-18 16:29:49 +08:00
eshun
f009c43878 add windows support (#867)
* add windows support

* add windows support

* add windows support

Co-authored-by: 78552423@qq.com <chenyz0812>
2022-02-18 15:55:00 +08:00
Ulric Qin
f8482601a8 Merge branch 'main' of github.com:didi/nightingale 2022-02-17 19:29:51 +08:00
Ulric Qin
8c4ab88888 return all busi-groups when subscribe 2022-02-17 19:28:35 +08:00
张哲铭
37421dd56a 兼容使用pg数据库,contacts字段json格式无法转换的问题 (#868) 2022-02-15 17:58:33 +08:00
UlricQin
5c2581a90a upgrade 5.3.4 2022-02-15 17:19:47 +08:00
Ulric Qin
6a3a630759 modify Makefile 2022-02-14 16:18:30 +08:00
Ulric Qin
fff5110e9a copy metrics.yaml from https://articles.zsxq.com/id_izcsnhl3dtd6.html 2022-02-13 14:18:15 +08:00
UlricQin
d31fe9cb71 modify user-groups query limit 2022-02-11 13:05:20 +08:00
UlricQin
bd762172d4 add space in error log 2022-02-10 17:54:52 +08:00
UlricQin
b32a7b3a9e add global callback 2022-02-10 17:32:06 +08:00
UlricQin
3ccc09674e query user-groups 2022-02-10 15:45:38 +08:00
UlricQin
c10f10010a upgrade 5.3.3 2022-01-29 13:58:59 +08:00
UlricQin
9beef8f36a add last_sent_time for alert_cur_event 2022-01-29 13:46:10 +08:00
UlricQin
8408220870 upgrade 5.3.2 2022-01-29 11:12:06 +08:00
UlricQin
2e63993b7f fix 2022-01-29 11:08:32 +08:00
UlricQin
b482c7a076 recover_duration done 2022-01-29 11:01:44 +08:00
laiwei
733abd5568 update introduction of nightingale in readme 2022-01-28 13:31:46 +08:00
Ulric Qin
dd1147f534 refactor telegraf.service 2022-01-26 09:15:41 +08:00
UlricQin
19c90d356c refactor make pack 2022-01-26 09:08:28 +08:00
Ulric Qin
c042e39d54 upgrade 2022-01-26 09:03:57 +08:00
Ulric Qin
598ae07fc2 add feature: recover_duration 2022-01-26 08:59:30 +08:00
Ulric Qin
e5d7612af9 n9e-server support basic auth for Reader 2022-01-21 23:34:25 +08:00
UlricQin
f3924dab5b delete pendings when recoverRule 2022-01-12 13:50:29 +08:00
UlricQin
ac6f49e63d upgrade 5.3.0 2022-01-11 11:56:09 +08:00
UlricQin
7f4cb3888f support falcon datamodel 2022-01-11 11:25:03 +08:00
UlricQin
120c2fe52a fix proxy Host header 2022-01-10 20:16:44 +08:00
UlricQin
b9c674d662 prometheus proxy add Header Host 2022-01-08 19:40:43 +08:00
Ulric Qin
dcee4677ed Merge branch 'main' of github.com:didi/nightingale 2022-01-08 17:52:42 +08:00
Ulric Qin
d590f6d5c1 enable_in_bg logic 2022-01-08 17:52:29 +08:00
UlricQin
850a370f9d add targets apis 2022-01-06 11:48:30 +08:00
UlricQin
40e7ede5e3 Merge branch 'main' of github.com:didi/nightingale 2022-01-04 16:47:15 +08:00
UlricQin
9a2257dd1e ldap user default role configuration 2022-01-04 16:47:03 +08:00
Ulric Qin
7b4eddc967 code refactor 2021-12-31 13:52:44 +08:00
Ulric Qin
843e37b99d code refactor 2021-12-31 13:50:12 +08:00
Ulric Qin
19981ce649 refactor 2021-12-31 13:49:14 +08:00
Ulric Qin
2740af3571 add arch.png 2021-12-31 13:45:54 +08:00
Ulric Qin
b693e80d75 check basicauth 2021-12-31 12:07:23 +08:00
Ulric Qin
e9ce679649 handle python2 encoding 2021-12-31 11:13:57 +08:00
Ulric Qin
a56d6b568b refactor log print 2021-12-30 09:37:52 +08:00
Ulric Qin
904d09d91c add datadog deflate encoding 2021-12-29 14:59:05 +08:00
Ulric Qin
3700f7a10b update datadog url 2021-12-29 14:52:22 +08:00
Ulric Qin
d57415d23d add datadog receiver 2021-12-28 11:00:48 +08:00
Ulric Qin
86649d8314 Merge branch 'main' of github.com:didi/nightingale 2021-12-27 13:30:56 +08:00
Ulric Qin
06eca94492 add datadogSeries 2021-12-27 13:30:45 +08:00
JeffreyBool
ef6f6f95c0 增加 server 指标采集 (#850)
* 修改名称

* 增加 server 指标采集
2021-12-25 22:31:49 +08:00
JeffreyBool
991a3e2ab5 添加 n9e-webapi 指标采集 (#848)
* 添加自定义发现文件

* 添加 webapi 指标
2021-12-25 22:11:41 +08:00
Ulric Qin
08c6659804 use longer varchar 2021-12-24 20:03:53 +08:00
Ulric Qin
74e4724e66 delete no use code: repeater.go 2021-12-23 22:54:37 +08:00
Ulric Qin
1ea8694769 refactor fireEvent 2021-12-23 22:43:18 +08:00
Ulric Qin
218140066b fix r.rule.NotifyRepeatStep unit 2021-12-23 22:26:53 +08:00
Ulric Qin
837cfab1bd refactor repeater 2021-12-23 22:19:49 +08:00
Ulric Qin
3428b11ea8 configuration for metrics.yaml and templates 2021-12-23 12:53:32 +08:00
Ulric Qin
f661a6bd37 refactor dingtalk.tpl 2021-12-17 13:04:39 +08:00
Ulric Qin
c3c1aa5aff refactor dingtalk.tpl 2021-12-17 12:24:24 +08:00
Ulric Qin
7bcb6acb03 refactor 2021-12-17 12:11:15 +08:00
Ulric Qin
5b22d65dba add space line 2021-12-17 12:09:35 +08:00
Ulric Qin
8570c2d287 modify dingtalk markdown 2021-12-17 12:05:41 +08:00
Ulric Qin
acc797666d test markdown 2021-12-17 11:20:32 +08:00
Ulric Qin
b62a42bed8 dingtalk use markdown 2021-12-17 11:05:15 +08:00
Ulric Qin
b452be880b update README 2021-12-16 19:57:55 +08:00
Ulric Qin
49176ae240 support grafana-agent 2021-12-16 17:58:49 +08:00
Ulric Qin
8eb4a39e7d fix index out of range 2021-12-16 17:07:27 +08:00
Ulric Qin
0f65a1f5dd add remote write api support 2021-12-16 16:59:51 +08:00
Ulric Qin
a71edc4040 extract IamLeader function and fix repeat 2021-12-15 20:52:00 +08:00
Ulric Qin
23b6cf1a68 fix repeat sender 2021-12-15 19:37:55 +08:00
Ulric Qin
3babc6c50a fix tple 2021-12-15 19:22:18 +08:00
Ulric Qin
a4ef00fe3e add send time 2021-12-15 19:16:39 +08:00
Ulric Qin
0f3bbf6368 use NotifyRepeatNext as TriggerTime when repeat notify 2021-12-15 18:37:48 +08:00
Ulric Qin
95ebc44f05 refactor notify.py 2021-12-14 21:39:01 +08:00
UlricQin
64945637e0 upgrade 5.0.0-ga-06 2021-12-14 15:29:19 +08:00
Ulric Qin
0baf977bc9 feishu done flag 2021-12-14 15:08:20 +08:00
Ulric Qin
caa33c29e9 refactor creating busi group 2021-12-13 11:12:49 +08:00
Ulric Qin
d5050338f3 use last_eval_time for filter 2021-12-11 18:14:23 +08:00
Ulric Qin
7f0877bf28 add table column: last_eval_time in alert_his_event 2021-12-11 18:07:01 +08:00
Ulric Qin
d4c4257517 code refactor for i18n when occur duplicate tagkey 2021-12-11 17:25:45 +08:00
Ulric Qin
61f76afa0d handle duplicate tagkey 2021-12-11 17:23:18 +08:00
UlricQin
fe86cb4b74 update version 2021-12-11 17:22:41 +08:00
Ulric Qin
5634f48725 remove perm of targets 2021-12-10 09:49:11 +08:00
Ulric Qin
964d50b4e7 add perm function in routers 2021-12-10 09:44:06 +08:00
Ulric Qin
d2cb48a2ef remove writer name 2021-12-09 23:07:45 +08:00
Ulric Qin
53411dc5d9 add perm 2021-12-09 22:08:22 +08:00
Ulric Qin
cab6089a37 add perm control busi-group adding 2021-12-09 22:04:16 +08:00
Ulric Qin
32fea64f3e use configuration file to control AnonymousAccess 2021-12-09 16:59:02 +08:00
Ulric Qin
bf4e0ca7c0 modify github template 2021-12-09 14:22:39 +08:00
Ulric Qin
39bd02f741 Merge branch 'main' of gitee.com:n9e/nightingale 2021-12-09 12:58:40 +08:00
Ulric Qin
930b1181ee add tmp jvm-dash.json 2021-12-09 12:58:08 +08:00
UlricQin
1aac8c1e25 delete dirty files 2021-12-08 23:59:54 +08:00
Ulric Qin
3e8b110809 upgrade 5.0.0-ga-04 2021-12-08 23:57:20 +08:00
Ulric Qin
e0c1bebb13 modify n9eetc dir 2021-12-08 23:55:51 +08:00
UlricQin
7ccb2aaa9c upgrade 5.0.0-ga-03 2021-12-08 22:48:02 +08:00
Ulric Qin
aa2e5f15ee update recover event 2021-12-08 22:31:48 +08:00
Ulric Qin
ed5e93f373 modify event url 2021-12-08 21:36:21 +08:00
Ulric Qin
48247ea7fe At least one team have rw permission 2021-12-08 13:18:53 +08:00
Ulric Qin
12a5f335bd get event detail no need login 2021-12-08 10:04:31 +08:00
Ulric Qin
5e19eadd61 add recover_time only when IsRecovered 2021-12-08 00:17:42 +08:00
Ulric Qin
0e88f0074c add recover_time 2021-12-08 00:07:25 +08:00
Ulric Qin
2bfc67686d refactor alert_subscribe.user_group_ids 2021-12-07 19:33:39 +08:00
Ulric Qin
6c2c8f9900 add feishu support 2021-12-07 18:39:44 +08:00
Ulric Qin
766bf9e401 code refactor 2021-12-07 13:50:42 +08:00
Ulric Qin
4f8fedbaa0 delete no use code 2021-12-07 13:44:14 +08:00
Ulric Qin
b108c9f11a refactor: The business group must retain at least one team 2021-12-06 21:33:36 +08:00
Ulric Qin
cc380c85b9 upgrade docker-compose's n9e image version to 5.0.0-ga-02 2021-12-06 20:34:45 +08:00
Ulric Qin
62165ce01d add operations 2021-12-06 19:23:43 +08:00
Ulric Qin
c8b05649f5 modify rule operations 2021-12-06 19:20:33 +08:00
UlricQin
94fb62fcca upgrade 5.0.0-ga-02 2021-12-06 19:06:38 +08:00
Ulric Qin
bef8e8e548 bugfix: handle rule judge 2021-12-06 18:44:56 +08:00
Ulric Qin
88063cd30e bugfix: callback ibex 2021-12-06 18:20:44 +08:00
Ulric Qin
2185fbff65 Merge branch 'main' of gitee.com:n9e/nightingale into main 2021-12-06 15:19:30 +08:00
Ulric Qin
a94a602d4f remove jwtAuth in prom api 2021-12-06 15:18:56 +08:00
UlricQin
6ed13bdccb add pub when pack 2021-12-06 10:56:55 +08:00
UlricQin
ff79ad1338 add disk and diskio metric description 2021-12-06 10:33:58 +08:00
UlricQin
f6703e11c4 add some metric desn 2021-12-06 09:40:14 +08:00
UlricQin
2e1936dcce modify doc 2021-12-06 08:35:21 +08:00
UlricQin
698ac2758f upgrade docker-compose for test 2021-12-05 22:12:05 +08:00
UlricQin
8cef8e5c9e upgrade docker-compose 2021-12-05 22:05:53 +08:00
UlricQin
c5c53466fb refactor Dockerfile 2021-12-05 21:57:36 +08:00
UlricQin
acd2e9398b add pub in Dockerfile 2021-12-05 21:41:17 +08:00
UlricQin
df97166f07 add api: check perm 2021-12-05 20:40:13 +08:00
UlricQin
022fef2b9e add telegraf.service 2021-12-05 15:39:49 +08:00
UlricQin
5f05e8fcaf ignore 2021-12-05 15:07:26 +08:00
UlricQin
7e353eb0e8 ga-01 done 2021-12-04 17:11:39 +08:00
UlricQin
499389d2c3 modify default group 2021-12-04 16:53:20 +08:00
UlricQin
7274f606dc delete no use binary 2021-12-04 16:51:53 +08:00
UlricQin
da52f125f3 ignore .payload 2021-12-04 12:09:44 +08:00
UlricQin
b418dec3ab bugfix: event mute 2021-12-04 12:07:30 +08:00
UlricQin
79401183ca bugfix 2021-12-02 17:37:42 +08:00
UlricQin
270d3b7e5b code refactor 2021-12-02 17:34:54 +08:00
UlricQin
4e3f9914f1 use i18n error when import rules and dashboards 2021-12-02 10:19:10 +08:00
UlricQin
dd8e1f2d71 add api: /api/n9e/version 2021-12-01 16:46:37 +08:00
UlricQin
f63f019e87 refactor readme 2021-12-01 15:35:38 +08:00
UlricQin
11e7c41908 add EngineDelay 2021-12-01 14:09:08 +08:00
UlricQin
57c2fd9b73 update jwt 2021-12-01 11:40:49 +08:00
UlricQin
dc9fe38735 modify args: hours->days 2021-12-01 11:26:44 +08:00
UlricQin
622d4ac165 refactor 2021-12-01 10:14:35 +08:00
Ulric Qin
3090e13be7 verify tpl tags modify 2021-11-30 18:16:09 +08:00
UlricQin
f96a36aa43 bugfix 2021-11-30 14:25:02 +08:00
UlricQin
6ad24419ab engine wait 2min 2021-11-30 12:33:37 +08:00
UlricQin
04319a6b41 add /v1/n9e/users 2021-11-30 11:57:55 +08:00
UlricQin
952f6b139d add api: get one alert-subscribe 2021-11-30 11:49:08 +08:00
UlricQin
f58cb923d4 add todo item 2021-11-29 20:29:21 +08:00
UlricQin
d43067bad4 bugfix 2021-11-29 20:06:45 +08:00
UlricQin
c17ade64e1 bugfix 2021-11-29 19:56:36 +08:00
UlricQin
4ddbba1400 bugfix 2021-11-29 15:36:15 +08:00
UlricQin
536e2a3b7c modify img width 2021-11-28 19:19:27 +08:00
UlricQin
fe97e158ef add doc 2021-11-28 19:16:30 +08:00
UlricQin
6e3ad3dd6b version 5.1 2021-11-28 18:57:49 +08:00
UlricQin
7a2b07eebd code refactor 2021-11-18 09:09:38 +08:00
UlricQin
70235eeeee Merge branch 'master' of https://github.com/didi/nightingale 2021-11-18 09:08:17 +08:00
UlricQin
f0f5af4fb0 code refactor 2021-11-18 09:07:53 +08:00
710leo
0254a4ec34 refactor: ldap search request 2021-11-11 15:16:26 +08:00
710leo
6d24b07573 feat: add get-user-by-token api 2021-09-09 23:18:58 +08:00
Ulric Qin
0d19ec267f set http.Server.ReadTimeout to 30*time.Second 2021-09-04 19:48:11 +08:00
Ulric Qin
c63987d726 add limit for local call 2021-09-04 19:45:38 +08:00
Ulric Qin
086dcad81f move var Version to config package 2021-09-04 19:35:34 +08:00
UlricQin
eaacf04c68 feature: load alert_events to memory when start 2021-09-04 16:41:55 +08:00
UlricQin
ee859df057 Merge branch 'master' of github.com:didi/nightingale 2021-09-04 16:21:01 +08:00
UlricQin
d809c6ffa9 bugfix: cannot delete alert_event when recovered 2021-09-04 16:20:46 +08:00
710leo
0cc4d85b37 refactor: remove processor logic 2021-09-04 12:43:49 +08:00
710leo
7c2f49146d refactor: modify history_alert_event column 2021-09-04 10:58:31 +08:00
UlricQin
19c2fb6f82 remove processor logic 2021-09-04 10:47:21 +08:00
710leo
882a97566b docs: upgrade 5.0.0-rc7 2021-09-03 10:46:41 +08:00
UlricQin
b1d67af206 code refactor 2021-09-03 09:51:55 +08:00
UlricQin
ca1daaaea3 code refactor 2021-09-03 09:49:19 +08:00
UlricQin
b24b1d17e6 code refactor 2021-09-03 09:23:33 +08:00
710leo
86b2dcd248 fix: support new static file 2021-09-02 12:23:20 +08:00
UlricQin
bd84c433cd set alias blank if __alias__ not found 2021-08-31 16:50:57 +08:00
Ulric Qin
238a611cbb Merge branch 'master' of github.com:didi/nightingale 2021-08-28 17:22:40 +08:00
Ulric Qin
8b951a306d code refactor 2021-08-28 17:21:29 +08:00
UlricQin
d685fa2a30 code refactor 2021-08-27 18:58:06 +08:00
Istil
ae0b036ae4 feat: support to mute by resource classpath_prefix (#785) 2021-08-27 18:21:36 +08:00
UlricQin
351dee6a12 Merge branch 'master' of github.com:didi/nightingale 2021-08-26 12:22:19 +08:00
UlricQin
8b748a7840 delete user/group when get alert rule if user/group is deleted 2021-08-26 12:21:57 +08:00
710leo
afb01b0258 fix: batch update alert rule 2021-08-26 11:03:32 +08:00
UlricQin
d92ca5f2a9 Merge branch 'master' of github.com:didi/nightingale 2021-08-24 10:35:17 +08:00
UlricQin
f21909adb8 check resource exists when bind classpath 2021-08-24 10:35:00 +08:00
710leo
2bf4a25dd1 refactor: delete classpaths tree api 2021-08-23 19:48:42 +08:00
710leo
2096e6ea8d feat: support get user by name api 2021-08-23 19:46:18 +08:00
710leo
25cad60ef8 refactor: event process 2021-08-23 18:35:54 +08:00
UlricQin
8e5b72c833 Update bug_report.md 2021-08-23 17:04:56 +08:00
UlricQin
90d9510d23 Update bug_report.md 2021-08-23 17:03:34 +08:00
710leo
58093eedf0 feat: script collect support append_tags and fix timeout 2021-08-23 16:59:55 +08:00
Istil
754ad160dc feat: support to deal with alert_event (#777) 2021-08-23 16:39:07 +08:00
UlricQin
12e3b3a490 add goproxy 2021-08-23 10:11:53 +08:00
Ulric Qin
eca12a6587 remove csrf 2021-08-22 17:55:49 +08:00
Ulric Qin
5106b73699 del unused code 2021-08-22 09:43:42 +08:00
Ulric Qin
98fe1e0121 feat: add timer: CleanStalePoints 2021-08-22 09:30:41 +08:00
Ulric Qin
de99077b32 code refactor: remove unused field(TagsLst) of MetricPoint 2021-08-22 08:45:29 +08:00
Ulric Qin
e288a3d3a9 Merge branch 'master' of github.com:didi/nightingale 2021-08-21 21:52:47 +08:00
Ulric Qin
0588d39911 bugfix: handle status when create alert_rule 2021-08-21 21:52:08 +08:00
710leo
55ec76a23d refactor: fix conflicts 2021-08-20 19:52:37 +08:00
710leo
a2eab9e5ab refactor: delete csrf check and some v1 api 2021-08-20 19:45:17 +08:00
Istil
f651af970f feat: support classpaths prefix tree display (#769) 2021-08-18 16:43:39 +08:00
Ulric Qin
1eecb324d0 code refactor: exec pull_prom early 2021-08-15 10:28:37 +08:00
Ulric Qin
60a964ae55 bugfix: for range goroutine 2021-08-15 10:08:08 +08:00
Ulric Qin
a7cf8f9ec9 !1 fix judge prom
Merge pull request !1 from Ulric Qin/judge_prom_bugfix
2021-08-14 16:20:17 +00:00
Ulric Qin
0b4e3b9656 fix judge prom 2021-08-14 23:17:06 +08:00
710leo
ca8a8701b4 fix: collect append tags 2021-08-13 16:01:33 +08:00
710leo
4ca83fcc1a docs: upgrade 5.0.0-rc5 2021-08-12 17:53:19 +08:00
710leo
509e1ef00a refactor: remove useless code 2021-08-12 17:41:57 +08:00
ning1875
42fc0527cb 1. Move the default ql to the configuration (#764)
2. add slowLogRecordSecond to  log slow query
3. Create a slice with a specified length to avoid dynamic expansion
4. slow query print fetch series time took and the result series num
2021-08-10 15:25:54 +08:00
UlricQin
8b508fc514 code refactor 2021-08-06 18:03:36 +08:00
ning1875
2f060cfb43 1. set Config.Heartbeat.LocalAddr when ip can not fetch by auto detect (#761)
2. upgrade snappy to v0.0.3 to avoid the following failure on Go 1.16
2021-08-06 12:19:36 +08:00
Istil
4eb79fb017 feat: support history alert events store (#760) 2021-08-06 12:16:32 +08:00
ning1875
c38d595cb8 1. fix CommonQuerySeries.Warning range nil pointer err (#759) 2021-08-05 11:04:50 +08:00
710leo
e29407486d update service file 2021-08-04 22:33:43 +08:00
710leo
4d3ca94e4c docs: upgrade 5.0.0-rc4 2021-08-04 17:56:26 +08:00
710leo
e27ec8136e refactor: remote write log print 2021-08-04 14:26:53 +08:00
ning1875
9383976918 1. delete recovery event from cache after event is really mark recovery (#758) 2021-08-04 12:14:47 +08:00
710leo
8764270f47 feat: support permission check api 2021-08-04 11:05:54 +08:00
Istil
2ef85d9aae feat: support batch edit notify-users, notify-channels and append-tags of alert rule (#757) 2021-08-03 14:20:22 +08:00
201806060205
5064e5b0b1 feat: support batch edit notify groups of alert rule (#756) 2021-08-02 20:16:43 +08:00
ning1875
72244b1983 1. remove some todo (#755) 2021-08-02 17:37:55 +08:00
710leo
e14d3eac4d refactor: get notify content by tpl 2021-08-01 18:32:36 +08:00
UlricQin
62cbe4a833 Merge branch 'master' of https://github.com/didi/nightingale 2021-07-30 16:03:44 +08:00
UlricQin
6040e79d50 sort classpaths of event 2021-07-30 16:03:25 +08:00
710leo
ffe3dd6bca refactor: alert rule name duplicate check 2021-07-30 13:30:50 +08:00
710leo
853053f56d fix: alert rule put 2021-07-30 10:58:42 +08:00
710leo
dde431b422 Merge branch 'master' of https://github.com/didi/nightingale 2021-07-30 10:38:54 +08:00
ning1875
e6cf77f34b 1. query nodata at one ts fill none (#753) 2021-07-30 10:19:03 +08:00
UlricQin
834a5e83ee Merge branch 'master' of https://github.com/didi/nightingale 2021-07-30 10:14:34 +08:00
UlricQin
f2173bff26 log regexp checker 2021-07-30 10:14:17 +08:00
710leo
c4b1da66d4 docs: change metric_description sql 2021-07-29 20:32:19 +08:00
ning1875
9c9f2973f8 1. fix remote write err assert on recoverableError (#749) 2021-07-28 12:11:36 +08:00
UlricQin
0c35f32c5c fix send_email 2021-07-27 18:31:50 +08:00
UlricQin
f2e3e3dbf1 add WECOM url 2021-07-27 15:42:14 +08:00
李伟强
5045098c91 Update notify.py (#748) 2021-07-27 15:35:37 +08:00
ning1875
20e34bfe15 1. tag-pair support fuzzy matching search (#747) 2021-07-27 15:07:54 +08:00
dependabot[bot]
0f63feed22 build(deps): bump github.com/gin-gonic/gin from 1.6.3 to 1.7.0 (#746)
Bumps [github.com/gin-gonic/gin](https://github.com/gin-gonic/gin) from 1.6.3 to 1.7.0.
- [Release notes](https://github.com/gin-gonic/gin/releases)
- [Changelog](https://github.com/gin-gonic/gin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gin-gonic/gin/compare/v1.6.3...v1.7.0)

---
updated-dependencies:
- dependency-name: github.com/gin-gonic/gin
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-27 15:06:58 +08:00
ning1875
ec6f3098bb notify.py add sys.encoding to avoid coding error (#744) 2021-07-27 11:38:42 +08:00
UlricQin
53f08eae30 Update build.sh (#745) 2021-07-27 11:28:11 +08:00
ning1875
cade83f075 添加debug日志 (#743)
* 1. notify.py 支持安装channel反射发送
2. 支持钉钉群发送
3. 生成告警模板信息

* 1. notify.py 支持安装channel反射发送
2. 支持钉钉群发送
3. 增加二开说明

* 1. notify.py 用户创建一个虚拟的用户保存上述im群 的机器人token信息 user的contacts map中

* 1. notify.py alerts目录改为原来的

* 1. notify.py dingtalk send continue匹配

* 1. push型告警支持多条件 任意一个触发就触发

* 1. prometheus查询接口 tag-keys tag-values支持 params为空的情况

* 1. prometheus查询接口 ident匹配全部改为精确匹配
2. tagKey 提示改为tag_key

* 1. prometheus查询接口 支持instance_query 对外暴露

* 1. prometheus instance_query改名为instant-query
2. page group中去掉数据查询相关path

* 1. prometheus range_query 时间戳改为秒级
2. 查询支持传入分辨率参数

* 1. 新增jmx_exporter内置大盘

* 1. 新增blackbox_exporter内置大盘
2. 新增blackbox_exporter内置告警策略

* 1. 添加一些debug帮助定位恢复的告警在db event中删除的过程
2021-07-26 17:36:32 +08:00
710leo
af93088d2f refactor: change collect config 2021-07-26 14:44:03 +08:00
710leo
e396ad4f67 refactor: change collect config 2021-07-25 21:41:37 +08:00
Ulric Qin
979a77eafa simplify logs 2021-07-25 18:52:06 +08:00
UlricQin
c0b42cf29a bugfix: reuse error var in remoteWriteProm (#741)
* fix: reuse error var when remoteWritePost

* add debug log

* remove logs
2021-07-25 18:23:47 +08:00
710leo
ae4f20bca1 Merge branch 'master' of https://github.com/didi/nightingale 2021-07-25 18:19:48 +08:00
710leo
b947a466a9 fix: alert ineffective on sunday 2021-07-25 18:19:18 +08:00
710leo
4063998ddb docs: change user.role to user.roles in sql 2021-07-24 23:48:13 +08:00
UlricQin
266397bac3 hehe... 2021-07-24 23:47:33 +08:00
UlricQin
f023b99fa9 Merge branch 'master' of https://github.com/didi/nightingale 2021-07-23 21:15:41 +08:00
UlricQin
bb148f9bea add more log 2021-07-23 21:15:23 +08:00
710leo
7bdcbf2e95 Merge branch 'master' of https://github.com/didi/nightingale 2021-07-23 12:15:19 +08:00
710leo
070d1947e8 feat: temporary support for creating collection configurations api 2021-07-23 12:15:08 +08:00
UlricQin
a4c244cb61 modify user_group_ids of alert_rule_group when user_group deleted 2021-07-23 12:03:47 +08:00
UlricQin
e089271f78 code refactor 2021-07-22 23:41:22 +08:00
UlricQin
24887cce83 use group get api: handle empty usergroup 2021-07-22 23:34:06 +08:00
UlricQin
0f7a81ff11 fill objs when return alert event 2021-07-22 23:30:12 +08:00
ning1875
2d15445482 新增blackbox_exporter支持 (#740)
* 1. notify.py 支持安装channel反射发送
2. 支持钉钉群发送
3. 生成告警模板信息

* 1. notify.py 支持安装channel反射发送
2. 支持钉钉群发送
3. 增加二开说明

* 1. notify.py 用户创建一个虚拟的用户保存上述im群 的机器人token信息 user的contacts map中

* 1. notify.py alerts目录改为原来的

* 1. notify.py dingtalk send continue匹配

* 1. push型告警支持多条件 任意一个触发就触发

* 1. prometheus查询接口 tag-keys tag-values支持 params为空的情况

* 1. prometheus查询接口 ident匹配全部改为精确匹配
2. tagKey 提示改为tag_key

* 1. prometheus查询接口 支持instance_query 对外暴露

* 1. prometheus instance_query改名为instant-query
2. page group中去掉数据查询相关path

* 1. prometheus range_query 时间戳改为秒级
2. 查询支持传入分辨率参数

* 1. 新增jmx_exporter内置大盘

* 1. 新增blackbox_exporter内置大盘
2. 新增blackbox_exporter内置告警策略
2021-07-22 15:23:49 +08:00
UlricQin
3236d7dfd1 code refactor for name checker 2021-07-22 14:51:14 +08:00
UlricQin
8a06cac5f1 bugfix: check perm for alert_rule_delete_batch 2021-07-22 14:44:44 +08:00
UlricQin
60d5c6b55e modify readme 2021-07-22 09:51:39 +08:00
UlricQin
77c6e0dbff Merge branch 'master' of https://github.com/didi/nightingale 2021-07-21 18:13:43 +08:00
UlricQin
3e22aabe28 tag value support blank 2021-07-21 18:11:45 +08:00
ning1875
bedea9eb05 添加jmx_exporter内置大盘图 (#739)
* 1. notify.py 支持安装channel反射发送
2. 支持钉钉群发送
3. 生成告警模板信息

* 1. notify.py 支持安装channel反射发送
2. 支持钉钉群发送
3. 增加二开说明

* 1. notify.py 用户创建一个虚拟的用户保存上述im群 的机器人token信息 user的contacts map中

* 1. notify.py alerts目录改为原来的

* 1. notify.py dingtalk send continue匹配

* 1. push型告警支持多条件 任意一个触发就触发

* 1. prometheus查询接口 tag-keys tag-values支持 params为空的情况

* 1. prometheus查询接口 ident匹配全部改为精确匹配
2. tagKey 提示改为tag_key

* 1. prometheus查询接口 支持instance_query 对外暴露

* 1. prometheus instance_query改名为instant-query
2. page group中去掉数据查询相关path

* 1. prometheus range_query 时间戳改为秒级
2. 查询支持传入分辨率参数

* 1. 新增jmx_exporter内置大盘
2021-07-21 14:20:01 +08:00
ning1875
0d8e5ec77c prometheus range_query 时间戳和分辨率 (#738)
* 1. notify.py 支持安装channel反射发送
2. 支持钉钉群发送
3. 生成告警模板信息

* 1. notify.py 支持安装channel反射发送
2. 支持钉钉群发送
3. 增加二开说明

* 1. notify.py 用户创建一个虚拟的用户保存上述im群 的机器人token信息 user的contacts map中

* 1. notify.py alerts目录改为原来的

* 1. notify.py dingtalk send continue匹配

* 1. push型告警支持多条件 任意一个触发就触发

* 1. prometheus查询接口 tag-keys tag-values支持 params为空的情况

* 1. prometheus查询接口 ident匹配全部改为精确匹配
2. tagKey 提示改为tag_key

* 1. prometheus查询接口 支持instance_query 对外暴露

* 1. prometheus instance_query改名为instant-query
2. page group中去掉数据查询相关path

* 1. prometheus range_query 时间戳改为秒级
2. 查询支持传入分辨率参数
2021-07-20 16:15:06 +08:00
UlricQin
de840af331 Multi roles support (#737)
* support multi user roles

* resources list support search tags and note
2021-07-20 14:40:50 +08:00
710leo
255b2b2320 fix: router path duplicate 2021-07-19 19:31:58 +08:00
ning1875
034b7a642e refactor: rename instance_query to instant_query 2021-07-19 19:17:22 +08:00
710leo
407f9ca6ad refactor: change query default resolution 2021-07-19 16:27:37 +08:00
ning1875
bf1d8b1be4 feat: add instance_query api (#731) 2021-07-19 16:14:52 +08:00
UlricQin
562f3ea937 code refactor 2021-07-18 16:39:10 +08:00
UlricQin
0a29fb89c4 test xorm 2021-07-18 16:30:02 +08:00
UlricQin
27daddcb72 bugfix: query alert event 2021-07-18 16:16:57 +08:00
UlricQin
c7b00ee8c6 rename default dash 2021-07-18 15:08:14 +08:00
UlricQin
1d7c7fd8af add i18n configuration 2021-07-18 09:04:01 +08:00
710leo
6b06e78b61 Merge branch 'master' of https://github.com/didi/nightingale 2021-07-17 19:33:53 +08:00
710leo
9ec1882032 docs: update changelog 2021-07-17 19:33:46 +08:00
Ulric Qin
18fc86d68a refactor plugin example 2021-07-17 19:27:10 +08:00
710leo
a628d5bb59 docs: change tpl and sql 2021-07-17 18:48:55 +08:00
710leo
df1e1cd334 docs: upgrade 5.0.0-rc2 2021-07-17 17:03:47 +08:00
710leo
d6c6eaa064 refactor: series push api 2021-07-17 15:37:16 +08:00
yubo
b4bdb08dc1 fix: support gzip/zlib with series push (#734) 2021-07-17 14:56:10 +08:00
Ulric Qin
ae9c21e293 code refactor 2021-07-16 23:46:11 +08:00
Ulric Qin
b65c8f696b check tag key 2021-07-16 23:30:09 +08:00
UlricQin
88e6e4bf56 add plugin example 2021-07-16 20:29:34 +08:00
UlricQin
8f4597045d rename preset classpath all->all.resources 2021-07-16 20:02:56 +08:00
UlricQin
a7f12ad871 support query data use guest 2021-07-16 18:41:11 +08:00
UlricQin
4e791d50d4 refactor warning info at api: check regexp 2021-07-16 08:30:48 +08:00
UlricQin
9758e55b72 code refactor: extract _s and _e func 2021-07-15 18:46:40 +08:00
710leo
473239cc9a refactor: format history points timestamp 2021-07-10 02:42:57 +08:00
710leo
477cac6ca9 fix: process event mute 2021-07-10 02:32:28 +08:00
710leo
258e9738f7 feat: add tpl & status api 2021-07-09 20:13:28 +08:00
ning1875
39de0892f1 fix: query index api 2021-07-08 20:37:23 +08:00
710leo
36ec4e09fd docs: add docs 2021-07-06 23:00:44 +08:00
710leo
aa4e6b7f36 feat: dashboards import and export 2021-07-06 22:52:36 +08:00
qinyening
6440645c5a feat: alert rule api fill users and groups (#723) 2021-07-06 21:33:19 +08:00
qinyening
4585519943 fix: tag-keys tag-values query when params are empty (#722)
Co-authored-by: ning1875 <907974064@qq.com>
2021-07-06 20:42:27 +08:00
ning1875
1f16bc9a7b refactor: send dingtalk notify 2021-07-04 18:53:33 +08:00
710leo
4b9cbf9aee refactor: add metric description in sql 2021-07-02 09:14:14 +08:00
710leo
e0cc7dbffa refactor: log regex check api 2021-06-29 20:09:40 +08:00
ning1875
fd9d78061b feat: notify support mail and dingding 2021-06-29 14:55:30 +08:00
UlricQin
b03d57f40a do not cache ident alias mapper when ident is blank 2021-06-28 18:17:14 +08:00
qinyening
4e6e70c14d release v5.0.0-rc1 (#708)
* release v5.0.0-rc1
2021-06-28 00:42:39 +08:00
710leo
2ef9a77325 upgrade 4.0.3 2021-06-27 18:13:26 +08:00
710leo
18b9fb3ee2 add some log 2021-06-25 11:46:34 +08:00
710leo
02f2554cc1 fix: nodata repeated recovery alerting 2021-06-22 23:11:55 +08:00
stonelgh
07961c9f21 m3db: fix Errorf calls (#703) 2021-06-21 15:06:44 +08:00
wjkxiaowu
f770b3cf14 add system env when plugin run (#699)
Co-authored-by: root <root@localhost.localdomain>
2021-06-15 11:13:51 +08:00
UlricQin
62dd006d50 Update README.md 2021-06-14 20:57:43 +08:00
qinyening
9ff845d375 Update README.md 2021-06-09 19:32:21 +08:00
moses
58860dca48 去除配置文件重复项 (#694) 2021-06-09 15:15:08 +08:00
710leo
f2e397f533 upgrade 4.0.2 2021-06-02 01:05:20 +08:00
710leo
5afff12848 upgrade 4.0.2 2021-06-02 00:45:35 +08:00
yubo
37abf19f0d add m3db client timeout check (#693) 2021-05-31 15:35:00 +08:00
710leo
bbbd7faeb1 bugfix: user and team info cache 2021-05-27 20:55:47 +08:00
710leo
c1382dc0aa Merge branch 'master' of https://github.com/didi/nightingale 2021-05-27 00:46:32 +08:00
710leo
a73f2654df bugfix: aggr output and alert 2021-05-27 00:46:21 +08:00
UlricQin
abc9a6ffbf Merge branch 'master' of https://github.com/didi/nightingale 2021-05-26 16:12:02 +08:00
UlricQin
87e32a159b upgrade toolkits/pkg 2021-05-26 16:11:52 +08:00
710leo
22f0aee55d add event write perm check 2021-05-25 17:54:09 +08:00
710leo
01420ff1d8 optimize user information filling 2021-05-16 17:42:53 +08:00
710leo
c4b5d13348 optimize user information filling 2021-05-16 15:42:30 +08:00
hubo
9cf2d47eef agent 增加默认tags功能, agent 增加正则匹配磁盘挂载类型过滤功能 (#683)
* agent 增加默认tags功能, agent 增加正则匹配磁盘挂载类型过滤功能

* agent 增加默认tags功能, agent 增加正则匹配磁盘挂载类型过滤功能

Co-authored-by: huboc <huboc@zbj.com>
2021-05-08 19:17:01 +08:00
Paul Chu
a9d6d6f820 支持节点迁移 (#680)
* enable promethues summary

* ADD: 添加节点迁移的方法

* FIX: node move session commit

* ADD: 注册迁移节点的接口

* MOD: fix error handle

Co-authored-by: zhupeiyuan <zhupeiyuan@fenbi.com>
2021-05-07 11:10:05 +08:00
Ulric Qin
f70d303942 fix http_response compile error 2021-05-06 17:00:18 +08:00
UlricQin
967c3aa591 Merge branch 'master' of https://github.com/didi/nightingale 2021-04-29 11:32:28 +08:00
UlricQin
3a47fb2c79 use n9e-3.8.0.tar.gz in Dockerfile 2021-04-29 11:32:19 +08:00
peng19940915
1112186d1c 新增postgresql监控 (#671)
* add postgresql & remove http_response status_code tag

* add postgresql & remove http_response status_code tag

Co-authored-by: leiyupeng <susu898287771@>
2021-04-27 23:16:07 +08:00
yubo
f40332f197 bugfix: add user.Type (#667) 2021-04-26 19:15:33 +08:00
UlricQin
a11813f4b2 Merge branch 'master' of https://github.com/didi/nightingale 2021-04-26 09:16:11 +08:00
UlricQin
13d396a388 code refactor 2021-04-26 09:15:56 +08:00
Ulric Qin
3d3458d577 add LimitNOFILE example in service files 2021-04-24 13:25:33 +08:00
Ulric Qin
e142785a9d add ams-builtin-token as server default token and refactor nginx.conf 2021-04-24 12:44:25 +08:00
yubo
ddac3a9871 add connect timeout options (#664) 2021-04-20 19:10:25 +08:00
joyexpr
bdb15aa0bb perf: mem and disk size calc from %d to %.1f (#662)
Co-authored-by: 周晓明 <zhouxiaoming@star-net.cn>
2021-04-20 19:04:40 +08:00
joyexpr
6693b131d8 perf: control command support mod name with n9e- prefix (#661)
Co-authored-by: 周晓明 <zhouxiaoming@star-net.cn>
2021-04-20 10:30:48 +08:00
joyexpr
41efc66d25 fix: send mail not work(wrong notifyType and subject) (#660) 2021-04-19 23:57:20 +08:00
710leo
a5197b4ced upgrade 4.0.1 2021-04-19 21:37:12 +08:00
710leo
d49d40768c organize configuration 2021-04-19 21:28:02 +08:00
710leo
c71264ab30 fix send message 2021-04-19 20:10:29 +08:00
710leo
8f1fd17f5c add configuration 2021-04-19 16:44:07 +08:00
UlricQin
7179bb79a0 default setting: udp not enable 2021-04-17 18:47:10 +08:00
710leo
bb64a2f1ec support static files 2021-04-16 19:21:02 +08:00
710leo
3f0dfd63d4 support static files 2021-04-15 21:23:59 +08:00
710leo
46f7ec7af9 complete version information 2021-04-15 19:35:25 +08:00
yubo
999c1b4239 bugfix: use InviteMustGet instead of InviteGet (#654)
* add fmt import
2021-04-14 20:57:27 +08:00
yubo
f6b2535cdb bugfix: use InviteMustGet instead of InviteGet (#653) 2021-04-14 12:48:26 +08:00
yubo
5f1c868006 feature: logout when the user is invalidated (#652) 2021-04-13 14:33:21 +08:00
qinyening
59366e4d3a 发布v4版本 (#651)
* init
2021-04-13 11:38:40 +08:00
710leo
bea0532872 update changelog 2021-04-12 10:46:58 +08:00
710leo
e684c583fb upgrade 3.8.0 2021-04-09 17:42:17 +08:00
710leo
eed2f073a0 Merge branch 'master' of https://github.com/didi/nightingale 2021-04-09 15:34:06 +08:00
710leo
31a03aa331 alert event modify filling user detail 2021-04-09 15:33:52 +08:00
yubo
71984c72b5 feature: add password changed notify (#647)
* feature: add password changed notify
2021-04-09 11:21:09 +08:00
yubo
72573e32cb feature: add get self permissions by nodeID (#643) 2021-04-07 13:12:00 +08:00
chixianliangGithub
50f4cc10c4 去除重复代码 (#641) 2021-04-03 16:00:32 +08:00
710leo
c2f98583e1 add ntp in agent conf 2021-03-31 11:39:37 +08:00
yubo
1ff6d0a2dc feature: add [start,end) param for clude, endpointMetric, endpoints api (#639) 2021-03-30 18:10:14 +08:00
yubo
92ac8b09c0 prober plugin use all mode as default (#634) 2021-03-26 11:17:31 +08:00
Paul Chu
384e993ca1 enable promethues summary (#630) 2021-03-24 16:08:42 +00:00
yubo
c1241fdfbc bugfix: created_at -> create_at for rdb.user table (#632) 2021-03-24 19:10:01 +08:00
yubo
be9d6ac660 use logger.Warning instead of fmt.Printf at loading plugins (#629) 2021-03-23 18:37:03 +08:00
yubo
30b469ddbd add subject for rdb rst-code/login-code mail (#628) 2021-03-22 17:27:01 +08:00
UlricQin
22ee99f222 Merge branch 'master' of https://github.com/didi/nightingale 2021-03-19 13:19:51 +08:00
UlricQin
f4675f0a34 upgrade 3.7.1 2021-03-19 13:19:27 +08:00
yubo
111c6fc1bf feature: support node event notify with webhook (#627)
* feature: support node event notify with webhook
2021-03-19 13:06:41 +08:00
710leo
0cd2761021 Merge branch 'master' of https://github.com/didi/nightingale 2021-03-19 11:12:41 +08:00
710leo
0a7c8988c6 stra add user group detail 2021-03-19 11:12:32 +08:00
UlricQin
7947533182 monapi support new timestamp 2021-03-19 10:48:40 +08:00
710leo
184c39d311 add some audit log 2021-03-18 21:22:50 +08:00
UlricQin
d89eaec596 bugfix: GetTeamsNameByIds 2021-03-18 10:03:20 +08:00
yubo
40ce0d75ed prettify msg (#620) 2021-03-17 11:57:30 +08:00
ning1875
61bd28db31 日志采集字段变更 whether_attache_one_log_line--> whether_attach_one_log_line (#619)
* m3db writetagged应该并发做,不然会导致transfer rpc变慢

* go func指针传参问题

* 新增k8s-mon三个大盘文件

* 新增k8s-mon三个大盘文件

* 修改k8s-mon三个大盘文件

* 日志采集新增带上最后一条日志 到extra字段中,为后续报警做准备

* 日志采集字段变更 whether_attache_one_log_line--> whether_attach_one_log_line

* 日志采集带上日志
2021-03-15 16:03:02 +08:00
710leo
b1426945d4 fix agent proc.cpu.util 2021-03-13 18:21:21 +08:00
ning1875
dec9097ce7 transfer写m3db出错时打印metric信息帮助定位 (#615)
* m3db writetagged应该并发做,不然会导致transfer rpc变慢

* go func指针传参问题

* 新增k8s-mon三个大盘文件

* 新增k8s-mon三个大盘文件

* 修改k8s-mon三个大盘文件

* transfer写m3db出错时打印metric信息帮助定位
2021-03-13 13:21:05 +08:00
ning1875
7bb93e8351 日志采集新增带上最后一条日志 到extra字段中,为后续报警做准备 (#614)
* m3db writetagged应该并发做,不然会导致transfer rpc变慢

* go func指针传参问题

* 新增k8s-mon三个大盘文件

* 新增k8s-mon三个大盘文件

* 修改k8s-mon三个大盘文件

* 日志采集新增带上最后一条日志 到extra字段中,为后续报警做准备
2021-03-13 13:19:45 +08:00
alick-liming
7a84223d5b Aggr lanteness (#611)
* aggr lateness

* default value

* test

* test

Co-authored-by: alickliming <alickliming@didiglobal.com>
2021-03-12 15:10:42 +08:00
yubo
398628870c bugfix: add prober.plugins Stop() for release resource (#610) 2021-03-11 16:22:55 +08:00
yubo
3e426537c7 add maxSeriesPoints for config.transfer.m3db (#609) 2021-03-10 17:50:38 +08:00
HONG YANG
bf1bd3ef5a “massage” (#603) 2021-03-10 17:36:58 +08:00
yubo
b85b1e44ef bugfix: auth password history size (#607) 2021-03-10 17:35:12 +08:00
yubo
ff194c0382 add sample.out for mysql & redist (#605) 2021-03-09 19:10:40 +08:00
Zayscott
078f7cfc90 新增n9e模块监控大盘 (#602)
* Update changelog

* Create n9e_mudules

* Update changelog
2021-03-05 12:59:52 +08:00
stiei13wangluo
bd72a773f4 telegraf dns_query plugins (#601)
* dns_query

* dns_query

Co-authored-by: root <root@localhost.localdomain>
2021-03-05 11:54:13 +08:00
Zayscott
9637fb4bf3 Update changelog (#600) 2021-03-04 17:36:15 +08:00
yubo
22dc5c909c feature: add dryrun for collect_rule add/update (#599)
* feature: add dryrun for collect_rule add/update

* ignore sso when it is disable
2021-03-04 17:35:40 +08:00
UlricQin
cd4336d438 code refactor 2021-03-02 17:07:53 +08:00
UlricQin
fb619e0fa9 add comment 2021-03-02 17:06:47 +08:00
UlricQin
08dbc3c035 Delete n9e_rdb-v3.4.0.sql 2021-03-02 17:03:41 +08:00
UlricQin
3fba4390c5 generate upgrade sql for 3.7.0 2021-03-02 16:58:09 +08:00
UlricQin
500585a0a0 upgrade 3.7.0 2021-03-02 14:48:27 +08:00
Feng_Qi
acaa88f1a9 add ping/net_response/http_response support (#594)
* fix port check and push debug log

1:如果服务没有监听在 0.0.0.0 上,而是监听在特定地址上的话,在 127.0.0.1 上无法检测到端口。修改为如果 127.0.0.1 检测不到话,在 identity 的地址上再检测一次。
2. http push 部分缺乏 debug 日志,把 debug log 改到 push 里面以补全。

* Update cron.go

* notify add resource name and note

* Update notify.go

* Update notify.go

修复一个当 name/note 为空值且 resource 只有一台时, 由于被 config.Set 清空
因此获取下标 index out of range 导致 panic 的 bug

* add ping, net_response, http_response plugin

增加
ping
net_response
http_response
的插件支持

* Update all.go

* add example config yml

* Update notify.go
2021-02-28 07:56:35 +08:00
yubo
005dc47868 fix: https://github.com/didi/nightingale/issues/583 (#590) 2021-02-25 15:37:35 +08:00
yubo
9c1c894e29 feature: support dlopen for prober plugin (#588) 2021-02-23 18:04:03 +08:00
yubo
b055bc73c5 add a demo plugin for prober (#586)
* add a demo plugin for prober

* update demo plugin
2021-02-23 11:41:38 +08:00
yubo
322cbf27dc use testhttp instead of http for ut (#585)
* use testhttp instead of http for ut
* bugfix: add username check
2021-02-22 11:25:02 +08:00
UlricQin
417a13c1be bugfix: judge: redis conn pools 2021-02-07 17:07:00 +08:00
UlricQin
05819497e4 Update README.md 2021-02-06 11:28:07 +08:00
yubo
66c93f472a update vendor for local_build (#578) 2021-02-03 19:10:19 +08:00
710leo
023b23a0ef fix build monapi 2021-02-03 17:01:54 +08:00
710leo
900896c045 add sync stra log 2021-02-03 16:55:14 +08:00
yubo
db97453c54 build error fix: replace grpc to 1.29.1 (#577) 2021-02-03 16:42:27 +08:00
sun763625521
3fdd61edfc 新增rabbitmq、haproxy组件采集 (#575)
* add

* add prober plugin for rabbitmq

* add prober plugin for haproxy

Co-authored-by: root <root@localhost.localdomain>
Co-authored-by: UlricQin <ulric.qin@gmail.com>
2021-02-03 15:02:48 +08:00
yubo
e839c6bd6b bugfix: update session param has a mistake (#576)
* add cache counter for login part.1

* add login counter api

* feature: prober support multi-metric with different tags

* bugfix: session counter reset

* add models.stats for counter

* bugfix: update session param has a mistake
2021-02-03 15:00:14 +08:00
lynxcat
2d9bc50401 新增zookeeper,tengine采集 (#574)
* add prober plugin for elasticsearch

* 新增zookeeper,tengine插件,补齐了prober采集插件的测试

* 添加zookeeper插件描述

Co-authored-by: lynxcat <lynxcatdeng@gmail.com>
2021-02-03 14:43:39 +08:00
qinyening
c48d8b93dd add some permssion api (#572) 2021-02-03 11:01:29 +08:00
alick-liming
e2e96a04d1 权限调整 (#571) 2021-02-02 14:03:05 +08:00
yubo
c724896ecd adjust session GC interval (#569)
* keep at least 4 history passwords

* adjust gc time for session
2021-02-01 23:29:38 +08:00
燕小乙
914aaa0a96 修改k8s-mon ksm,控制平面大盘 (#567)
* m3db writetagged应该并发做,不然会导致transfer rpc变慢

* go func指针传参问题

* 新增k8s-mon三个大盘文件

* 新增k8s-mon三个大盘文件

* 修改k8s-mon三个大盘文件
2021-01-31 11:56:44 +08:00
UlricQin
b5becda6fc go mod vendor 2021-01-31 11:04:17 +08:00
Ulric Qin
37868777e7 release 3.6.0 2021-01-31 10:59:01 +08:00
Ulric Qin
3663ed0235 uniq res bindings 2021-01-31 10:52:04 +08:00
yubo
7fa84af66a add session get api (#566) 2021-01-31 10:48:37 +08:00
Ulric Qin
55718a09e0 refactor bug_report.md 2021-01-30 10:14:57 +08:00
yubo
3754e0cbe3 remove local telegraf plugins.inputs (#563) 2021-01-29 23:49:48 +08:00
lynxcat
3df2536bb6 add prober plugin for elasticsearch (#562)
Co-authored-by: lynxcat <lynxcatdeng@gmail.com>
2021-01-29 23:47:50 +08:00
UlricQin
9e07f1924c Update README.md 2021-01-29 08:28:40 +08:00
yubo
fbf4544849 add accumulator for prober & generate default plugin config (#560)
* add accumulator for prober & generate default plugin config

* add prometheus plugin

* add prober plugin test util
2021-01-29 08:26:28 +08:00
lynxcat
2d4e6bb8da prober nginx 采集插件 (#557)
* add a method to get the Endpoint

* 增加nginx插件,修改control。支持./control build prober job这种多个参数

* 修改提示

Co-authored-by: lynxcat <lynxcatdeng@gmail.com>
2021-01-28 17:23:46 +08:00
UlricQin
211dfc62e4 code refactor 2021-01-28 10:07:48 +08:00
UlricQin
679c5892a4 Revert "add Prometheus as a plugin for prober (#556)" (#558)
This reverts commit 1dac755787.
2021-01-27 23:51:42 +08:00
yubo
1dac755787 add Prometheus as a plugin for prober (#556)
* update changelog

* add prometheus as a plugin for prober

* bugfix: add counter type for summary & histogram

* ignore summary, histogram for prometheus plugin
2021-01-27 23:47:53 +08:00
燕小乙
1f4e0f5e73 新增k8s-mon三个大盘文件 (#555)
* m3db writetagged应该并发做,不然会导致transfer rpc变慢

* go func指针传参问题

* 新增k8s-mon三个大盘文件

* 新增k8s-mon三个大盘文件
2021-01-27 19:20:00 +08:00
UlricQin
b616894f2e code refactor 2021-01-27 17:46:56 +08:00
UlricQin
7a910709f9 code refactor 2021-01-27 10:12:37 +08:00
UlricQin
d61f8dac2e Merge branch 'master' of https://github.com/didi/nightingale 2021-01-27 08:53:14 +08:00
UlricQin
6d1fecc408 modify README 2021-01-27 08:52:40 +08:00
UlricQin
366d44959e upgrade go mod 2021-01-26 20:45:50 +08:00
UlricQin
d37c8f5387 add doc 2021-01-26 20:38:42 +08:00
UlricQin
06c5ca412a code refactor 2021-01-26 18:14:45 +08:00
yubo
afa95f79cd update changelog (#552) 2021-01-26 18:13:42 +08:00
yubo
8fe3457e0a support anonymous struct field for monapi.plugins.template (#547)
* move get collectrule api from /api/mon to /v1/mon

* support anonymous struct field for monapi.plugins.template

* add tls with mysql, redis and mongodb

* add rdb.user.pwdExpiresAt
2021-01-25 20:43:15 +08:00
alick-liming
7bfd60be86 资源排行去掉内置租户 (#544)
Co-authored-by: alickliming <alickliming@didi.global.com>
2021-01-25 10:28:40 +08:00
UlricQin
b7284ada94 use more conns for mysql 2021-01-25 10:19:50 +08:00
yubo
66e2dc73f9 remove prober RPC.port from config (#543)
* remove prober rpc.port from yml config

* remove prober.config.rpcPort && add prober.plugins.config.metrics checker
2021-01-24 14:09:08 +08:00
Ulric Qin
d254e5670b rebuild table collect_rule 2021-01-23 17:02:37 +08:00
Ulric Qin
9c945b33fb is tag value is blank, use nil instead 2021-01-23 14:08:44 +08:00
yubo
c53a66d20e remove sql.mon.collect_rule.created (#542) 2021-01-23 13:28:45 +08:00
Ulric Qin
335b113327 release 3.5.1 2021-01-23 11:04:58 +08:00
Ulric Qin
122590265d ignore EOF error 2021-01-23 10:59:57 +08:00
lynxcat
aab2f8b090 add a method to get the Endpoint (#540)
Co-authored-by: lynxcat <lynxcatdeng@gmail.com>
2021-01-23 08:50:36 +08:00
shaojie
f0a4c130f6 后端日志格式更改 (#539) 2021-01-23 08:06:02 +08:00
yubo
029f0a09ba bugfix: return err when unable to get monapi.collectRule (#537) 2021-01-22 17:39:02 +08:00
yubo
8fe3d2b0b3 bugfix: replace ref with instantiated variable for prober.rules.updatedAt (#536)
* add mon.plugins.redis descriptions

* bugfix: add region field for instances/heartbeat

* bugfix: replace ref with instantiated variable for prober.rules.updatedAt
2021-01-22 16:20:07 +08:00
UlricQin
25c31fcb2e +alarmEnabled true 2021-01-22 14:34:20 +08:00
UlricQin
2a74809294 add doc 2021-01-22 11:52:15 +08:00
UlricQin
09154e40aa 3.5.0 release 2021-01-22 11:45:05 +08:00
UlricQin
2e9b236406 3.5.0 release 2021-01-22 11:29:49 +08:00
Ulric Qin
1f88b72dba Merge branch 'master' of github.com:didi/nightingale 2021-01-21 20:49:39 +08:00
Ulric Qin
0c133afbaf wget tarball 2021-01-21 20:49:29 +08:00
710leo
2f87121e27 fix typo 2021-01-21 20:23:13 +08:00
710leo
b72e2d3fe0 Merge branch 'master' of https://github.com/didi/nightingale 2021-01-21 20:18:48 +08:00
710leo
4119414079 add tree search by user 2021-01-21 20:18:16 +08:00
Ulric Qin
955fe6795d test modify screen tpl 2021-01-21 20:16:50 +08:00
市民233
fbbb59971c feat: update go proxy module url (#532) 2021-01-21 20:03:38 +08:00
市民233
0a6df20b7d fix aliyun repo golang mod 404 (#531)
aliyun repo golang mod 404, just connect directly

go module公共代理仓库资料404,就直连
2021-01-21 16:36:46 +08:00
yubo
d640d86160 add mon.plugins.redis descriptions (#529)
* add mon.plugins.redis descriptions

* bugfix: add region field for instances/heartbeat
2021-01-21 16:35:31 +08:00
qinyening
6a70bed30f modify proc info collect (#528) 2021-01-21 00:38:18 +08:00
yubo
91503cfd25 update template document for mysql,mongo and redis (#526)
* update mysql document

* update template document for mysql,mongo and redis

* use TelegrafPlugin interface

* add mon.plugins.github as an exmpale
2021-01-20 23:07:56 +08:00
710leo
56feba9b45 Merge branch 'master' of https://github.com/didi/nightingale 2021-01-19 19:25:02 +08:00
710leo
5eec7c317c bugfix: arbitrary file reading 2021-01-19 19:24:46 +08:00
UlricQin
04c650528f Merge branch 'master' of https://github.com/didi/nightingale 2021-01-19 18:42:18 +08:00
UlricQin
e8ba0fb0bb fix ssrf 2021-01-19 18:41:51 +08:00
710leo
22cb24da09 add license 2021-01-18 23:39:23 +08:00
yubo
8204641656 add logger with monapi.plugins as telegraf.Logger interface (#522) 2021-01-18 19:25:47 +08:00
710leo
c5ba127b9e Merge branch 'master' of https://github.com/didi/nightingale 2021-01-18 13:27:02 +08:00
710leo
d2be562619 add alert & screen template 2021-01-18 13:26:37 +08:00
UlricQin
a4c8638448 code refactor 2021-01-15 19:59:26 +08:00
alick-liming
51cf58fcdf ams agent直接挂载到节点 (#518)
* ams agent直接挂载到节点

* 代码调整

Co-authored-by: alickliming <alickliming@didi.global.com>
2021-01-15 19:54:25 +08:00
qinyening
b00b7817f2 Support screen and alert template (#517) 2021-01-15 15:58:21 +08:00
燕小乙
6b1e432f6d m3db writetagged应该并发做,不然会导致transfer rpc变慢 (#514)
* m3db writetagged应该并发做,不然会导致transfer rpc变慢

* go func指针传参问题
2021-01-15 09:16:06 +08:00
UlricQin
f590194fba bugfix: same stra in nid 2021-01-14 10:06:49 +08:00
710leo
72ec59bdac Support unassigned tenant search 2021-01-13 14:04:56 +08:00
UlricQin
a07df519ab add 3.4.1 doc 2021-01-13 12:45:26 +08:00
UlricQin
7bf3049f4a upgrade 3.4.1 bugfix log_collect 2021-01-13 12:39:02 +08:00
yubo
a88315ee74 bugfix: call collect.Decode before get() (#507)
* add models.user.i18n

* bugfix: call collect.Decode before get()
2021-01-13 12:34:44 +08:00
UlricQin
c182c70b8d bugfix: check node is nil 2021-01-13 10:10:00 +08:00
alick-liming
fab8568633 新增用户添加组织字段 (#505)
Co-authored-by: alickliming <alickliming@didi.global.com>
2021-01-13 10:00:58 +08:00
yubo
74545012ed add models.user.i18n (#504) 2021-01-12 20:27:49 +08:00
UlricQin
903a1654b6 fix sql inject 2021-01-12 18:38:12 +08:00
yubo
7161c1ac4e adjust some file, variable name for prober module (#503)
* move pulgins_config.go to config dir

* add mongodb, redis yml
2021-01-11 22:08:27 +08:00
alick-liming
a9cf307cbf 租户项目粒度某类资源top数量 (#499)
* 租户项目粒度某类资源top数量

* 租户项目粒度某类资源top数量

* resname->rescate

Co-authored-by: alickliming <alickliming@didi.global.com>
2021-01-11 12:58:32 +08:00
alick-liming
f9cfcaeabe 告警hours支持 (#497)
Co-authored-by: alickliming <alickliming@didi.global.com>
2021-01-11 11:42:28 +08:00
yubo
b9aacf28e5 add start,end for transfer.index.fullmatch get (#494)
* add start,end with transfer.index.fullmatch get

* bugfix: should cleanup token before destory session when auth.extra.mode.enable
2021-01-09 14:09:06 +08:00
710leo
54512491b7 fix alert history tag display 2021-01-08 20:08:47 +08:00
UlricQin
a3fee54f7a edit changelog 2021-01-08 17:40:12 +08:00
yubo
e5f05aa724 add err log for session start (#493)
* add validate for some plugins

* restore getSessionUserWithCache method

* add validate for i18n.config

* use embed dict for i18n

* add err log for session start
2021-01-08 16:59:26 +08:00
UlricQin
312c2d1574 move patch sql to 3.4.0 2021-01-08 16:11:38 +08:00
yubo
7289636d35 use embed dict for i18n (#492)
* add validate for some plugins

* restore getSessionUserWithCache method

* add validate for i18n.config

* use embed dict for i18n
2021-01-08 15:11:41 +08:00
UlricQin
733da1ea94 converge delete 2021-01-08 15:02:31 +08:00
UlricQin
a1b4344943 use beego client in sender_sms 2021-01-08 10:28:45 +08:00
UlricQin
3c26beb48c Update README.md 2021-01-07 22:55:07 +08:00
yubo
c0049326b6 Add mongdb as a plugin (#489)
* bugfix: whiteList list return empty

* support multi-dict for i18n && add mongodb for monapi as a plugin

* use 10day as max lifetime for extra mode auth

* bugfix: ignore i18n with default value

* Spelling mistakes
2021-01-07 20:06:48 +08:00
yubo
543d345aea Dev (#487)
* add rdb config auth.debug for white_list

* update prober config support mode param

* feature: support access-token control with max connection, idle time, ...

* add token/session delete with auth check

* enable debug user for auth

* skip init sso db if not enable
2021-01-07 09:13:04 +08:00
UlricQin
fb1354898c Merge branch 'master' of https://github.com/didi/nightingale 2021-01-06 12:17:15 +08:00
UlricQin
e0263edc54 fix genNameAndNoteByResources when note is blank 2021-01-06 12:17:06 +08:00
alick-liming
b3a7d7c9a8 用户组织下拉接口 (#486)
Co-authored-by: alickliming <alickliming@didi.global.com>
2021-01-06 11:14:38 +08:00
alick-liming
a8008a9418 Nodename search (#484)
* node name search

* node name search

* node name search

* node name search

Co-authored-by: alickliming <alickliming@didi.global.com>
2021-01-05 14:31:18 +08:00
Ulric Qin
e82f560c44 code refactor 2021-01-03 18:13:48 +08:00
Ulric Qin
3589c7de69 compatible for blank tag value 2021-01-03 17:57:11 +08:00
Ulric Qin
72cf2c7578 add changelog for 3.4.0 2021-01-01 12:12:04 +08:00
Ulric Qin
eddd77d2d9 add patch sql for hbs 2021-01-01 11:42:17 +08:00
UlricQin
5d61468de6 add vendor 2021-01-01 11:11:47 +08:00
Ulric Qin
e85debddfc upgrade 3.4.0 2021-01-01 10:46:59 +08:00
yubo
d45ea02562 Rdb (#479)
* use collector interface

* mysql can work fine

* add basecollector

* add prober & monapi.plugins

* enable mysql plugins work

* rename collector -> manager

* add white list access check for rdb

* add cache module for authConfig & session

* rollback n9e_rdb_3.3.0.sql

* add sql ddl document

* add white_list, pwd, login access control

* add email code for login & reset password

* use sessionUsername instead of cookieUsername

* remove cookie name and data from session

* rename userName to username

* add remote_addr with session connection

* add get user by sid with cache

* enable cookie life time could be zero

* go mod tidy

* Rdb with session & monapi with telegraf (#456)

* use collector interface

* mysql can work fine

* add basecollector

* add prober & monapi.plugins

* enable mysql plugins work

* rename collector -> manager

* add white list access check for rdb

* add cache module for authConfig & session

* rollback n9e_rdb_3.3.0.sql

* add sql ddl document

* add white_list, pwd, login access control

* add email code for login & reset password

* use sessionUsername instead of cookieUsername

* remove cookie name and data from session

* rename userName to username

* add remote_addr with session connection

* add get user by sid with cache

* enable cookie life time could be zero

* go mod tidy

* add plugins config for prober

* add prober plugin expression parse

* update transfer default config for m3

* Rdb (#458)

* bugfix: session gc

* use flag for pwdMustInclude

* change user login function

* delete invite token after use

* bugfix: login response

* add sessionStart middle ware

* add auth module

* add i18n for rdb

* add i18n.zh for rdb.auth

* add mon plugins(redis, mongodb)

* update config

* add sub struct into definitions

* clean up sid cache after session destory

* bugfix: get user return nil when not found

* update i18n

* bugfix: ignore cache nologin user

* add user for callback output

* add password change api

* update default configfile & sql patch

* merge mon http middleware from rdb

* remove sso logout, sso already supporte one time auth
2021-01-01 10:41:30 +08:00
710leo
bad43090ff Add user dispname under the node 2020-12-31 18:09:42 +08:00
710leo
c2867d9638 Modify alert function 2020-12-31 15:11:59 +08:00
UlricQin
6dbbbac344 bugfix: insert task_meta sql inject 2020-12-31 13:01:43 +08:00
UlricQin
e903f609a5 Update README.md 2020-12-30 09:19:10 +08:00
Tiny
9dd1f1f90b 钉钉告警可以at特定指定人员 (#475)
* - add dingtalk robot @ special person
should config phone number in user IM field

* send msg by each robot when there are more than one robot in stra
2020-12-29 19:43:02 +08:00
Ulric Qin
4b22390faf refactor ip shell 2020-12-29 12:00:17 +08:00
710leo
9dbfef3df8 Optimize log output 2020-12-27 15:25:50 +08:00
UlricQin
28c794b2da modify stra.timeout default = 5000 2020-12-23 20:37:31 +08:00
UlricQin
ee96ce5046 test port listen on 127.0.0.1 and identity.ip and ::1 2020-12-23 15:57:11 +08:00
UlricQin
28d311e759 bugfix sql 注入 2020-12-22 11:56:20 +08:00
710leo
d355393074 fix: 同比变化率告警函数 2020-12-20 13:00:45 +08:00
moses
7b220da936 变化率绝对值算法问题 (#462) 2020-12-20 12:42:01 +08:00
Paul Chu
a1130b0e7c MOD: agent 文件系统 rw 探测 (#465) 2020-12-20 12:34:21 +08:00
710leo
8bba55e441 fix node role save 2020-12-17 20:26:13 +08:00
Paul Chu
d6d2e32b2e FIX: 修复订阅大盘图表 (#461)
* FIX: 修复短信报警模板的转义问题

报警说明里的信息由于 html template 的转义,会将部分字符转义为 html 表示,但是短信内容不需要转义。
向 template 模板添加 unescaped 处理函数,并在模板文件中使用 unescaped 标识不需要转义的字段,实现避免转义

* FIX: html template func 需要在 phase 之前添加

* FIX: use the filename as template name

* FIX: template name

* FIX: 修复订阅大盘图表

Co-authored-by: zhupeiyuan <zhupeiyuan@fenbi.com>
2020-12-17 15:24:02 +08:00
Feng_Qi
37c8317410 告警信息增加设备名称(name)和设备备注(note) (#460)
* fix port check and push debug log

1:如果服务没有监听在 0.0.0.0 上,而是监听在特定地址上的话,在 127.0.0.1 上无法检测到端口。修改为如果 127.0.0.1 检测不到话,在 identity 的地址上再检测一次。
2. http push 部分缺乏 debug 日志,把 debug log 改到 push 里面以补全。

* Update cron.go

* notify add resource name and note

* Update notify.go
2020-12-16 19:51:06 +08:00
yubo
19337b230c change transfer.m3db.env default -> default_env (#459) 2020-12-16 19:37:16 +08:00
UlricQin
d0ec6d8244 Merge branch 'master' of https://github.com/didi/nightingale 2020-12-16 15:22:08 +08:00
UlricQin
a232e7dfcd add some logs 2020-12-16 15:21:44 +08:00
qinyening
9b0a8dbb07 Agent 默认不采集 disk.rw.error 指标 (#455)
* configurable FsRWMetrics collect
2020-12-12 17:27:57 +08:00
ysicing
2e7a2a07ac fix: ldap登录如果配置允许coverAttributes,会导致panic (#454)
fix #453

Signed-off-by: ysicing <i@ysicing.me>
2020-12-12 03:46:13 +08:00
UlricQin
36e119770a code refactor 2020-12-10 09:58:48 +08:00
UlricQin
ac2efd2baf bugfix 2020-12-09 20:20:21 +08:00
alick-liming
5bd9c5fefc 支持全局数据统计 1.rdb分类资源数量 2.告警数量 (#447) 2020-12-08 23:47:09 +08:00
Ulric Qin
68bccb4d3f update readme 2020-12-06 08:16:28 +08:00
Ulric Qin
284b2c0db3 update readme 2020-12-06 08:08:58 +08:00
710leo
83a63da6c4 update vendor 2020-12-04 21:30:02 +08:00
710leo
dc7c0885a7 feat: support get nodes by ids 2020-12-04 21:27:14 +08:00
UlricQin
82a42f3649 host filter support id field 2020-12-04 18:56:28 +08:00
Paul Chu
30b600fe36 BUGFIX: 修复短信报警模板的转义问题 (#440)
* FIX: 修复短信报警模板的转义问题

报警说明里的信息由于 html template 的转义,会将部分字符转义为 html 表示,但是短信内容不需要转义。
向 template 模板添加 unescaped 处理函数,并在模板文件中使用 unescaped 标识不需要转义的字段,实现避免转义

* FIX: html template func 需要在 phase 之前添加

* FIX: use the filename as template name

* FIX: template name

Co-authored-by: zhupeiyuan <zhupeiyuan@fenbi.com>
2020-12-04 16:18:05 +08:00
Ulric Qin
eedfc99064 upgrade 3.3.1 2020-12-03 21:22:50 +08:00
Ulric Qin
80c316201d code refactor 2020-12-03 21:11:39 +08:00
710leo
a8f7f6a04e judge refactor 2020-12-03 11:50:45 +08:00
alick-liming
94eb306692 ams agent上报注册代码调整 (#436)
* rdb资源增加volume

* rdb用户增加创建时间

* rdb用户添加时间

* rdb新增添加用户时间代码调整

* test

* 1.agent上报扩展字段 2.rdb标签批量修改

* 代码调整

* 代码调整

* ams扩展代码调整

* test

* test

* 测试

* 错误调整

* ams agent上报注册代码调整

* map clear返回值去掉

Co-authored-by: alickliming <alickliming@didi.global.com>
2020-12-02 11:49:26 +08:00
Hayden
e673c5340c Update README.md (#434)
Change the README
2020-12-02 11:48:12 +08:00
DemoLiang
3c7c836b64 [FIX][issue#433]优化告警收敛,查询告警事件统计数据数据时使用created做between查询数据库时耗时非常久的问题,修改问使用etime 可以命中etime索引,提升查询性能,4C8G查询100W告警事件大概9s耗时,修改为etime大概700ms耗时 (#435) 2020-12-02 11:38:35 +08:00
Rick
7068faaa92 clean cache, reduce image size (#430)
* clean cache, reduce image size

* remove set -ex, image size indeed reduced
2020-12-02 04:16:24 +08:00
Ulric Qin
d063bc0e78 for jeff, dirty data 2020-12-01 23:39:03 +08:00
Ulric Qin
c6442ed68a add agent log 2020-12-01 22:32:13 +08:00
Ulric Qin
ebb95a8292 add debug log 2020-12-01 22:13:16 +08:00
alick-liming
7a185b5054 ams 扩展字段bugfix (#429)
* rdb资源增加volume

* rdb用户增加创建时间

* rdb用户添加时间

* rdb新增添加用户时间代码调整

* test

* 1.agent上报扩展字段 2.rdb标签批量修改

* 代码调整

* 代码调整

* ams扩展代码调整

* test

* test

* 测试

* 错误调整

Co-authored-by: alickliming <alickliming@didi.global.com>
2020-12-01 19:43:23 +08:00
710leo
0bd9b5b0d1 update dict.json 2020-12-01 10:56:44 +08:00
qinyening
74e85cdadc support i18n (#431) 2020-11-30 21:11:46 +08:00
alick-liming
82dadb31b5 1. ams扩展字段 2.rdb标签批量修改 (#428)
* rdb资源增加volume

* rdb用户增加创建时间

* rdb用户添加时间

* rdb新增添加用户时间代码调整

* test

* 1.agent上报扩展字段 2.rdb标签批量修改

* 代码调整

* 代码调整

Co-authored-by: alickliming <alickliming@didi.global.com>
2020-11-30 15:17:43 +08:00
UlricQin
b65e56b9fd report change value 2020-11-30 14:36:07 +08:00
qinyening
0b696202e7 support get users by org (#426)
* support get users by org
2020-11-28 11:42:46 +08:00
Ulric Qin
5d0c6c0c6e add build_local 2020-11-28 10:24:26 +08:00
snow_white
99d2d7a2ae 修复metricIndex的TagkvMap初始化两次造成的内存浪费 (#424)
Co-authored-by: zhuxingtao <zhuxingtao@baijiahulian.com>
2020-11-28 10:12:49 +08:00
UlricQin
0e8b626528 Update m3db-install.md 2020-11-28 09:18:38 +08:00
yubo
d80ce1d8c5 update auth/logout (#417) 2020-11-24 18:56:18 +08:00
UlricQin
86c0520076 fix job scheduler 2020-11-24 16:26:54 +08:00
UlricQin
966870200b code refactor 2020-11-23 11:05:58 +08:00
Ulric Qin
a3004a5140 add send_voice 2020-11-21 02:09:00 +08:00
710leo
dc97556807 update changelog 2020-11-21 00:50:15 +08:00
710leo
79d8aeee11 monapi get index configurable 2020-11-21 00:43:33 +08:00
710leo
d5430256c7 fix rdb sql 2020-11-20 18:21:10 +08:00
UlricQin
6691801721 code refactor 2020-11-20 18:04:35 +08:00
yubo
46c1c972ab docs: add m3db install (#414)
* docs: add m3db install

* change transfer default settings
2020-11-20 07:37:00 +08:00
UlricQin
4339a653ce Update README.md 2020-11-19 08:27:45 +08:00
yubo
299122f965 add nid to transfer query data/index (#411) 2020-11-18 23:46:51 +08:00
dujiashu
a6b160caed fix typo and add render (#410) 2020-11-18 18:16:56 +08:00
710leo
a5672bc1b3 Merge branch 'master' of https://github.com/didi/nightingale 2020-11-18 16:17:30 +08:00
710leo
6e5dff6454 update sql 2020-11-18 16:17:21 +08:00
yubo
38e060f704 When the time exceeds the limit, adjust the end time (#409) 2020-11-18 16:14:58 +08:00
710leo
227652ec8f Merge branch 'master' of https://github.com/didi/nightingale 2020-11-18 16:14:12 +08:00
710leo
fa263eb68d fix sql 2020-11-18 16:14:01 +08:00
yubo
d3992b81ef use consolFun instead of aggrFunc with resample (#408) 2020-11-18 11:13:45 +08:00
yubo
c430657738 transfer query end = end+1 (#406) 2020-11-17 22:08:32 +08:00
yubo
d78301567b M3db 2 (#404)
* bugfix: transfer ignore counter when tag is empty

* add m3db benchmark
2020-11-17 22:07:50 +08:00
710leo
bddd93cd80 udpate rdb sql 2020-11-17 18:32:52 +08:00
qinyening
a659820b07 refactor judge (#405)
* refactor judge
* rdb user add organization typ status
2020-11-17 18:27:22 +08:00
UlricQin
d32f9ef763 del no use code for transfer 2020-11-16 16:45:04 +08:00
yubo
920dd9a947 Add http_middleware to transfer (#402)
* validate ui query, add aggrFun support for resample

* add http_middleware to transfer
2020-11-16 16:33:02 +08:00
dujiashu
3f352a393b code refactory (#403)
* support tt automation by job

* format

* import order

* use map to avoid repetition

* add api for sync from ccp by force

* fix bug

* code refactory

* delete unused code

* delete func

* code refactory

* rename labels key

Co-authored-by: dujiashu <dujiashu@didiglobal.com>
2020-11-16 16:29:42 +08:00
yubo
86df27587e bugfix: query index by clude only get last record (#401)
* support time limit for m3db query

* bugfix: query index by clude only get last record
2020-11-16 13:25:35 +08:00
UlricQin
7b1ccd956b Merge branch 'master' of https://github.com/didi/nightingale 2020-11-16 11:03:54 +08:00
UlricQin
e928faf4f9 refactor /api/rdb/nodes?cate=xx 2020-11-16 11:01:36 +08:00
Ulric Qin
70e2cefd98 go mod vendor 2020-11-16 08:56:04 +08:00
Ulric Qin
49e8dbe5f6 准备发版3.3.0 2020-11-14 17:23:30 +08:00
Ulric Qin
250dd0c92d no cache for *.json 2020-11-13 21:32:10 +08:00
dujiashu
d4adafbcb7 support sync container by force (#399)
* support tt automation by job

* format

* import order

* use map to avoid repetition

* add api for sync from ccp by force

* fix bug

* code refactory

* delete unused code

* delete func

Co-authored-by: dujiashu <dujiashu@didiglobal.com>
2020-11-13 21:22:23 +08:00
alick-liming
ffc98f31c9 rdb新增用户添加时间代码调整 (#398)
* rdb资源增加volume

* rdb用户增加创建时间

* rdb用户添加时间

* rdb新增添加用户时间代码调整

* test

Co-authored-by: alickliming <alickliming@didi.global.com>
2020-11-13 14:18:49 +08:00
alick-liming
1a71de851a rdb新增用户添加时间 (#397)
* rdb资源增加volume

* rdb用户增加创建时间

* rdb用户添加时间

Co-authored-by: alickliming <alickliming@didi.global.com>
2020-11-13 13:39:28 +08:00
yubo
dd67efe0f6 support time limit for m3db query (#396) 2020-11-12 15:50:31 +08:00
yubo
033383eea4 validate ui query, add aggrFun support for resample (#392) 2020-11-12 11:50:40 +08:00
UlricQin
ee873a4ae2 code refactor 2020-11-12 11:24:54 +08:00
alick-liming
5667a6ee09 rdb资源增加volume (#394)
Co-authored-by: alickliming <alickliming@didi.global.com>
2020-11-11 20:49:48 +08:00
UlricQin
d26a4a35ab code refactor 2020-11-11 16:44:57 +08:00
gcxfd
d7acc88c05 sed ip to host for docker (#391) 2020-11-11 13:50:09 +08:00
710leo
1ab6dfceb1 fix docker-compose redis connection 2020-11-10 23:55:25 +08:00
yubo
a90c746626 bugfix: variable scope problem (#389)
* support openID2.0

* generate UUID if it's not set

* add m3db support

* add test shell

* update transfer.yml

* remove klog

* use remote m3 repo

* remove some file

* add description for tansfer.m3db config

* add query data for ui

* bugfix: Variable scope problem
2020-11-10 16:21:09 +08:00
gcxfd
fb1b5802ab fix redis connection (#386) 2020-11-10 16:06:18 +08:00
yubo
69ceeff9b8 M3db (#388)
* support openID2.0

* generate UUID if it's not set

* add m3db support

* add test shell

* update transfer.yml

* remove klog

* use remote m3 repo

* remove some file

* add description for tansfer.m3db config

* add query data for ui
2020-11-10 14:58:27 +08:00
yubo
b2baef0643 Auth with SSO (#387)
* add logout v2 for sso

* support sms-code login

* use db instead of memory cache for login code

* feature: support reset password by sms code

* remove deprecated api/code

* feature: support image captcha

* use db instead of memory cache for sso.auth.state

* add authLogin for login, v1/login; support (*)[.local].tpl for tpl file

* add username to sms-code api

* disable captcha by default in rdb.yml
2020-11-10 13:23:37 +08:00
yubo
2d1a2fd187 M3db (#385)
* support openID2.0

* generate UUID if it's not set

* add m3db support

* add test shell

* update transfer.yml

* remove klog

* use remote m3 repo

* remove some file
2020-11-09 19:52:44 +08:00
Ulric Qin
6d02d8876a Merge branch 'master' of github.com:didi/nightingale 2020-11-07 08:18:52 +08:00
Ulric Qin
712d0051d9 code refactor 2020-11-07 08:18:33 +08:00
UlricQin
e64a892275 add cleaner config 2020-11-06 17:25:14 +08:00
alick-liming
2e8a8966d7 rdb资源数量统计接口增加容器和交换机 (#383)
* rdb 资源cate分类统计接口

* 代码调节

* rdb:rabbitmq资源操作名称统一

* rdb控制台资源统计增加交换机,弹性云服务器

Co-authored-by: alickliming <alickliming@didi.global.com>
2020-11-06 10:16:05 +08:00
alick-liming
f9b3db4058 rdb:rabbitmq资源操作名称统一 (#382)
* rdb 资源cate分类统计接口

* 代码调节

* rdb:rabbitmq资源操作名称统一

Co-authored-by: alickliming <alickliming@didi.global.com>
2020-11-05 19:25:31 +08:00
alick-liming
633f224be6 rdb资源cate分类统计接口 (#380)
* rdb 资源cate分类统计接口

* 代码调节

Co-authored-by: alickliming <alickliming@didi.global.com>
2020-11-05 14:06:57 +08:00
DemoLiang
79501b46fe [FIX] 修复告警策略配置不发送告警恢复的逻辑判断跟注释不相符的问题,当前告警策略定义的是0 发送告警恢复,1不发送告警恢复,而实际代码逻辑上现在判断的是0不发送告警恢复,修改为1 不发送告警恢复的判断 (#373) 2020-11-05 12:32:17 +08:00
yubo
df55398100 add checkPassword to reset password by sms code (#378)
* add logout v2 for sso

* support sms-code login

* use db instead of memory cache for login code

* feature: support reset password by sms code

* remove deprecated api/code

* feature: support image captcha

* use db instead of memory cache for sso.auth.state

* add authLogin for login, v1/login; support (*)[.local].tpl for tpl file

* add username to sms-code api
2020-11-05 12:20:02 +08:00
alick-liming
2bcb20d710 rdb:资源解除注册,统一成单个uuid来处理 (#376)
* im wechat

* im wechat

* im add wechat_robot dingtalk_robot

* metaq 资源解除注册改为单个解除方式

Co-authored-by: alickliming <alickliming@didi.global.com>
2020-11-04 22:29:37 +08:00
UlricQin
79ae96f15d add some api 2020-11-04 21:47:55 +08:00
UlricQin
ac6d269d90 code refactor 2020-11-03 12:18:17 +08:00
UlricQin
a4026e8c25 code refactor 2020-11-03 12:17:17 +08:00
UlricQin
b448dad860 Merge branch 'master' of https://github.com/didi/nightingale 2020-11-03 12:11:32 +08:00
yubo
17762d9daa merge login & v1Login (#375)
* add logout v2 for sso

* support sms-code login

* use db instead of memory cache for login code

* feature: support reset password by sms code

* remove deprecated api/code

* feature: support image captcha

* use db instead of memory cache for sso.auth.state

* add authLogin for login, v1/login; support (*)[.local].tpl for tpl file
2020-11-02 15:54:15 +08:00
UlricQin
205201668c check password more strict 2020-11-02 13:44:14 +08:00
UlricQin
522cfca0af login fail, check your username and password 2020-11-02 13:27:23 +08:00
yubo
9bef8ddee3 login with sso,captcha,sms-code (#374)
* add logout v2 for sso

* support sms-code login

* use db instead of memory cache for login code

* feature: support reset password by sms code

* remove deprecated api/code

* feature: support image captcha

* use db instead of memory cache for sso.auth.state
2020-11-02 12:47:41 +08:00
UlricQin
7999c1fbe5 render user json do not return uuid 2020-11-02 12:38:22 +08:00
dujiashu
bec893d662 validate hosts unique (#371)
* support tt automation by job

* format

* import order

* use map to avoid repetition

Co-authored-by: dujiashu <dujiashu@didiglobal.com>
2020-11-01 11:07:17 +08:00
dujiashu
6f999f6a87 support tt automation by job (#370)
* support tt automation by job

* format

* import order

Co-authored-by: dujiashu <dujiashu@didiglobal.com>
2020-10-31 23:13:28 +08:00
710leo
10fef82225 modify node specification 2020-10-30 13:27:44 +08:00
710leo
ebbcfa3157 upgrade 3.2.0 2020-10-29 18:15:08 +08:00
qinyening
313144bebf agent支持metrics指标采集能力 (#368) 2020-10-29 16:54:48 +08:00
yubo
c6b5a5b400 feature: support reset password by sms code (#365)
* add logout v2 for sso

* support sms-code login

* use db instead of memory cache for login code

* feature: support reset password by sms code
2020-10-29 07:03:57 +08:00
yubo
1fdcbd848c Dev (#361)
* add logout v2 for sso

* support sms-code login

* use db instead of memory cache for login code
2020-10-27 17:51:39 +08:00
UlricQin
e63e741ad6 job api for tt 2020-10-27 09:57:58 +08:00
Ulric Qin
282aede691 upgrade 3.1.6 2020-10-25 20:20:10 +08:00
Ulric Qin
e5b95921cf add some validator for hostFieldNew 2020-10-25 20:06:46 +08:00
Ulric Qin
8c6726800f host field management done 2020-10-25 20:00:08 +08:00
Ulric Qin
6987b3b4d4 add host fields 2020-10-25 19:33:34 +08:00
Ulric Qin
28a2196143 use ips when recycle and del hosts 2020-10-25 18:16:20 +08:00
yubo
5b9a03a261 add OAuth2.0 callback/authorize V2 for UI (#353)
* support openID2.0

* generate UUID if it's not set

* change OAuth2 callback method to API style
2020-10-23 15:22:06 +08:00
710leo
228ffcfbc9 Merge branch 'master' of https://github.com/didi/nightingale 2020-10-22 21:23:59 +08:00
710leo
cc3b3575b6 sync from internal 2020-10-22 21:23:39 +08:00
Ulric Qin
ecacc71596 pack script 2020-10-22 17:50:32 +08:00
Ulric Qin
b2ad4b6995 upgrade 3.1.5 2020-10-22 17:49:15 +08:00
710leo
86929d8f69 fix monapi clean stra 2020-10-22 11:26:56 +08:00
710leo
39fa7e3e17 refactor /v1/rdb/node/:id/resources 2020-10-21 17:43:35 +08:00
Ulric Qin
70bc909565 code refactor 2020-10-19 22:29:09 +08:00
UlricQin
9ebe967e28 code refactor 2020-10-19 10:04:20 +08:00
Ulric Qin
93c35fd0ec upgrade 3.1.4 2020-10-18 10:17:39 +08:00
qinyening
2e80e82fc4 change hbs api & change perm point (#344)
* change hbs api & change perm point
2020-10-17 17:32:15 +08:00
UlricQin
2d19a1e86a code refactor 2020-10-15 20:29:06 +08:00
yubo
91700ab93e [ADD] generate UUID if it's not set (#338)
* support openID2.0

* generate UUID if it's not set
2020-10-14 15:02:58 +08:00
yubo
ecc736be8b support openID2.0 (#337) 2020-10-14 13:30:53 +08:00
Ulric Qin
8feb2287cc code refactor 2020-10-13 13:14:38 +08:00
Ulric Qin
e8d907156c upgrade 3.1.3 2020-10-13 13:09:02 +08:00
Ulric Qin
744980f119 cannot modify node-category to tenant 2020-10-13 09:35:34 +08:00
Ulric Qin
7f4dd8859e code refactor 2020-10-12 18:04:59 +08:00
Ulric Qin
f3962266b4 add changelog 2020-10-12 11:56:35 +08:00
UlricQin
b867c985ed bugfix: job callback for mon use method: post 2020-10-12 11:53:08 +08:00
Ulric Qin
0baa06cee0 rdb support wechat_robot and dingtalk_robot 2020-10-11 08:33:30 +08:00
alick-liming
e84d7f8741 im add wechat_robot and dingtalk_robot (#332)
* im wechat

* im wechat

* im add wechat_robot dingtalk_robot

Co-authored-by: alickliming <alickliming@didi.global.com>
2020-10-11 08:24:04 +08:00
UlricQin
8c5e9534b2 code refactor 2020-10-10 10:55:38 +08:00
UlricQin
8f5a9c9349 code refactor 2020-10-10 10:54:34 +08:00
Ulric Qin
e11d1dd5d9 code refactor 2020-10-09 23:34:28 +08:00
Ulric Qin
df7210f9ad code refactor 2020-10-09 23:31:04 +08:00
Ulric Qin
8eead759d9 code refactor 2020-10-09 23:02:31 +08:00
Ulric Qin
c6b9fd181f pack exclude etc/*.local.yml 2020-10-09 23:02:02 +08:00
Ulric Qin
c4ad9f1e88 refactor wechat sender 2020-10-09 22:47:10 +08:00
alick-liming
8455994118 im wechat (#330)
* im wechat

* im wechat

Co-authored-by: alickliming <alickliming@didi.global.com>
2020-10-09 22:42:19 +08:00
frank0417
2495405511 remove duplicate RDB sql (#328) 2020-10-09 16:24:28 +08:00
Ulric Qin
68f6312953 add gitee site url 2020-10-08 20:20:33 +08:00
Ulric Qin
e87b8dc368 code refactor 2020-10-08 20:13:08 +08:00
Ulric Qin
6083484d19 add some doc 2020-10-08 20:12:25 +08:00
Ulric Qin
3bdd0a7265 modify tarball download url 2020-10-08 19:54:40 +08:00
Ulric Qin
9d509efe35 upgrade 3.0.1 2020-10-08 19:47:49 +08:00
Ulric Qin
97acc7d1d0 del stras if node not exists 2020-10-08 19:46:54 +08:00
Ulric Qin
3534aa7e69 bugfix: GetLeafNidsForMon check node is nil 2020-10-08 19:39:52 +08:00
Ulric Qin
700370f70f add arch image and stra.json 2020-10-08 15:23:39 +08:00
Ulric Qin
95c96b3894 add service files 2020-10-08 10:55:27 +08:00
Ulric Qin
fdce55cf3b upgrade httpbli 2020-10-08 10:26:21 +08:00
Ulric Qin
9d118e0ef3 exclude *.local.yml when pack 2020-10-02 11:22:35 +08:00
Ulric Qin
18ef32a34c bugfix: log collect locker 2020-10-02 11:17:22 +08:00
Ulric Qin
cfd81a91fc add doc 2020-09-30 11:07:11 +08:00
Ulric Qin
87ebbcd7ec upgrade pub dir 2020-09-29 17:42:53 +08:00
yimeng
4b2ebf4761 docker-compose v0.2 (#318)
* fix regular dot

* add yarn.lock to .gitignore

* add docker-compose

* add docker-compose

* Delete PORTForm.tsx

* Delete .gitignore

* add .gitignore

* Update .gitignore

* add ifconfig

* fix dockerfile filename

* remove nginx.sh in Dockerfile

* 1 修复agent缺失命令 2 二次构建二进制 3 mysql启动等待

* 1 修复agent缺失命令 2 二次构建二进制 3 mysql启动等待
2020-09-29 11:18:07 +08:00
yimeng
1482cfcf32 docker-compose快速体验夜莺v3 (#316)
* fix regular dot

* add yarn.lock to .gitignore

* add docker-compose

* add docker-compose

* Delete PORTForm.tsx

* Delete .gitignore

* add .gitignore

* Update .gitignore
2020-09-28 19:05:38 -05:00
Ulric Qin
472ed62c12 Merge branch 'master' of https://github.com/didi/nightingale 2020-09-28 22:55:48 +08:00
Ulric Qin
04822e9d8f bugfix: address.yml: give default config for rdb.addresses 2020-09-28 22:55:26 +08:00
alick-liming
fe81c6cad7 配置调整,连接检查 (#314)
* 1.rabbitmq 配置文件 2.连接检查

* 代码调整

* 启动协程

* rabbitmq 连接检查代码调优

* rabbitmq 连接检查代码调优

Co-authored-by: alickliming <alickliming@didi.global.com>
2020-09-28 17:20:31 +08:00
UlricQin
6e6771ff3b upgrade pub dir 2020-09-28 14:54:12 +08:00
UlricQin
e5fc1bef44 code refactor 2020-09-28 12:33:27 +08:00
litianshun
68b213b737 influxdb query sql bug fix (#313)
* bug fix influxdb query data, query index

Co-authored-by: litianshun <litianshun@meicai.cn>
2020-09-28 11:10:16 +08:00
UlricQin
ceb86a2d5f bugfix: sql schema 2020-09-28 10:07:10 +08:00
UlricQin
6bd28ba82e code refactor 2020-09-27 23:27:11 +08:00
UlricQin
dbc0c0ad40 add install doc 2020-09-27 23:20:23 +08:00
Ulric Qin
6baebfad11 pub dir set to /home/n9e/pub 2020-09-27 23:03:45 +08:00
UlricQin
536b154aaa add some snapshot 2020-09-27 23:02:54 +08:00
UlricQin
db75a5fb74 modify nginx for static files 2020-09-27 19:57:43 +08:00
Ulric Qin
be1e161f31 add vendor Makefile 2020-09-27 19:27:10 +08:00
Ulric Qin
7864114d7c add readme 2020-09-26 23:59:34 +08:00
Ulric Qin
405114a893 code refactor 2020-09-26 23:30:34 +08:00
Ulric Qin
a90adf1212 Merge branch 'master' of github.com:didi/nightingale 2020-09-26 23:27:26 +08:00
Ulric Qin
6c5fb4cd35 code refactor 2020-09-26 23:26:58 +08:00
Ulric Qin
a443710669 go mod refactor 2020-09-26 23:11:34 +08:00
Ulric Qin
9e881bc9a5 use rabbitmq 2020-09-26 23:09:03 +08:00
Ulric Qin
4197cfba98 add nginx.conf 2020-09-26 22:58:14 +08:00
Ulric Qin
4ec1086fc9 add tsdb.yml 2020-09-26 22:41:34 +08:00
Ulric Qin
3d11f2cacc code refactor 2020-09-26 22:38:24 +08:00
Ulric Qin
b4ce2e8167 code refactor 2020-09-26 22:33:31 +08:00
Ulric Qin
5527ed0a86 code refactor 2020-09-26 22:28:24 +08:00
Ulric Qin
107ad572f8 code refactor 2020-09-26 22:21:23 +08:00
Ulric Qin
f16315328b refactor control 2020-09-26 21:58:37 +08:00
Ulric Qin
ae705e1b40 refactor operation log new 2020-09-26 21:45:23 +08:00
710leo
2d9287805e add vendor 2020-09-26 17:02:52 +08:00
710leo
ed35ddc388 3.0.0 2020-09-26 16:53:10 +08:00
710leo
8379624581 clean code 2020-09-26 16:37:46 +08:00
710leo
86b31575eb clean code 2020-09-26 16:28:27 +08:00
UlricQin
654e4278fa Update control 2020-09-22 12:56:38 +08:00
sunyu
f9c6c0465a 支持容器化部署配置自监控,兼容之前的配置 (#310)
* "配置化collector推送地址"

* "配置化collector推送地址"

* "兼容collector之前的配置"

Co-authored-by: 孙宇 <suny129@chinaunicom.cn>
Co-authored-by: suny129 <1061691533@qq.com>
2020-09-22 10:43:52 +08:00
UlricQin
9cb3bd564b code refactor: stats.init 2020-09-22 10:42:50 +08:00
sunyu
bc884175be Configure collector push address (#296)
* "配置化collector推送地址"

* "配置化collector推送地址"

Co-authored-by: 孙宇 <suny129@chinaunicom.cn>
2020-09-16 20:33:00 +08:00
qinyening
fb6e789909 Merge pull request #300 from zhuxingtao/master
fix: misspelling
2020-09-12 12:41:06 +08:00
zhuxingtao
944982898e fix: misspelling 2020-09-09 19:23:40 +08:00
chixianliangGithub
a8357dffb9 更改代码顺序,提高代码性能 (#291)
Co-authored-by: chixl <chixl@t3go.cn>
2020-09-02 15:05:38 +08:00
ronething-bot
50f5c6a98d fix: link error (#284) 2020-08-28 10:06:32 +08:00
UlricQin
f5d050f3f2 delete /api/transfer/v2 2020-08-25 16:19:47 +08:00
UlricQin
2a79303241 add QueryDataV2 2020-08-25 15:29:23 +08:00
xingren23
a72fa5b8dd collect config sys enable,default true (#272)
Co-authored-by: wangzhiguo04 <wangzhiguo04@meicai.cn>
2020-07-26 12:31:31 +08:00
xingren23
66421ae557 refactor collect push , sys -> core (#271)
Co-authored-by: wangzhiguo04 <wangzhiguo04@meicai.cn>
2020-07-26 12:28:45 +08:00
jsers
4b2f6a2c27 web build 2020-07-23 10:11:57 +08:00
jsers
9b99fe61e0 Alarm stra allows to configure the same metrics (#260) 2020-07-23 10:11:27 +08:00
jsers
e0584066a9 feat: add stddev func option (#211) 2020-07-23 10:10:57 +08:00
710leo
d70e60d4a5 fix exclude leaf nid when sync stra 2020-07-22 21:11:00 +08:00
youtwo123
7abec2ccb8 [transfer] fix and trigger generates event twice bug (#266)
* [transfer] fix and trigger generates event twice bug
* [monapi] stra excl all leaf nodes under exclNid
2020-07-22 17:08:38 +08:00
UlricQin
16a39410b8 del space line 2020-07-20 10:00:57 +08:00
杨善阳
b63c4e510c 修改adress.go的convPort方法,支持IPv6地址之间建立连接。 (#248)
monapi:
  http: '[::]:5800'
  address:
  - '[::1]'
2020-07-20 09:58:03 +08:00
xingren23
520dda70c0 refactor transfer datasources for ui/judge, implement tsdb(+index) an… (#246)
* refactor transfer datasources for ui/judge, implement tsdb(+index) and influxdb

* fix error string; fix import identidy ; refactor pushendpoint init

* fix influx queryData

Co-authored-by: wangzhiguo04 <wangzhiguo04@meicai.cn>
2020-07-20 09:57:22 +08:00
UlricQin
b6169ac706 agent interface /v1/push compatible with open-falcon 2020-07-11 10:42:34 +08:00
hutaishi
080d921791 本身403组件名的,写成404组件名了。 (#244) 2020-07-09 10:13:24 +08:00
710leo
a283329e4d Change tagkv count limit 2020-07-08 18:14:49 +08:00
710leo
5bb48df01d Merge branch 'master' of https://github.com/didi/nightingale 2020-07-07 14:12:02 +08:00
710leo
b3e961a3c6 filterString rm ':' 2020-07-07 14:11:50 +08:00
UlricQin
420b61ab52 use release mode in collector 2020-07-06 14:46:39 +08:00
UlricQin
dbd81eed2b code refactor 2020-07-03 12:38:20 +08:00
710leo
d0a00236ba fix upgrading link in readme 2020-07-02 14:52:54 +08:00
UlricQin
01aa9352aa set judge.query.maxConn default to 100 2020-07-01 16:27:26 +08:00
Ulric Qin
5c258520a2 upgrade 2.7.2: refactor index syncing 2020-06-30 00:11:12 +08:00
710leo
c232260e46 Fix change 2020-06-30 00:04:31 +08:00
710leo
bce825ff32 Change push index from async to sync 2020-06-29 23:57:15 +08:00
710leo
3c1ed52bb9 Change push index from async to sync 2020-06-29 23:49:20 +08:00
Ulric Qin
982fc6aaa2 upgrade 2.7.1 2020-06-29 16:12:23 +08:00
Ulric Qin
19890460b9 index.rebuildInterval modify to 6h 2020-06-28 17:05:22 +08:00
Ulric Qin
a46824c8ab Merge branch 'master' of github.com:didi/nightingale 2020-06-28 17:03:05 +08:00
Ulric Qin
bbfa03c894 index.rebuildInterval modify to 12h 2020-06-28 17:02:51 +08:00
710leo
56d1f7b6eb Fix judge get index addrs 2020-06-27 15:34:49 +08:00
710leo
1d2e183839 Delete collect which does not find nid 2020-06-26 18:20:37 +08:00
710leo
d741f24e8c Not allowed same collect name in same nodepath 2020-06-26 16:31:28 +08:00
710leo
22489f2dec Support tagkv check 2020-06-26 16:12:54 +08:00
mt
163c116871 transfer support kafka (#227)
* 修改:     etc/transfer.yml
	修改:     go.mod
	修改:     go.sum
	修改:     src/modules/transfer/backend/init.go
	新文件:   src/modules/transfer/backend/kafka.go
	修改:     src/modules/transfer/backend/sender.go
	修改:     src/modules/transfer/http/routes/push_router.go
	修改:     src/modules/transfer/rpc/push.go
	新文件:   vendor/github.com/Shopify/sarama/.gitignore
	新文件:   vendor/github.com/Shopify/sarama/.golangci.yml
	新文件:   vendor/github.com/Shopify/sarama/CHANGELOG.md
	新文件:   vendor/github.com/Shopify/sarama/LICENSE
	新文件:   vendor/github.com/Shopify/sarama/Makefile
	新文件:   vendor/github.com/Shopify/sarama/README.md
	新文件:   vendor/github.com/Shopify/sarama/Vagrantfile
	新文件:   vendor/github.com/Shopify/sarama/acl_bindings.go
	新文件:   vendor/github.com/Shopify/sarama/acl_create_request.go
	新文件:   vendor/github.com/Shopify/sarama/acl_create_response.go
	新文件:   vendor/github.com/Shopify/sarama/acl_delete_request.go
	新文件:   vendor/github.com/Shopify/sarama/acl_delete_response.go
	新文件:   vendor/github.com/Shopify/sarama/acl_describe_request.go
	新文件:   vendor/github.com/Shopify/sarama/acl_describe_response.go
	新文件:   vendor/github.com/Shopify/sarama/acl_filter.go
	新文件:   vendor/github.com/Shopify/sarama/acl_types.go
	新文件:   vendor/github.com/Shopify/sarama/add_offsets_to_txn_request.go
	新文件:   vendor/github.com/Shopify/sarama/add_offsets_to_txn_response.go
	新文件:   vendor/github.com/Shopify/sarama/add_partitions_to_txn_request.go
	新文件:   vendor/github.com/Shopify/sarama/add_partitions_to_txn_response.go
	新文件:   vendor/github.com/Shopify/sarama/admin.go
	新文件:   vendor/github.com/Shopify/sarama/alter_configs_request.go
	新文件:   vendor/github.com/Shopify/sarama/alter_configs_response.go
	新文件:   vendor/github.com/Shopify/sarama/alter_partition_reassignments_request.go
	新文件:   vendor/github.com/Shopify/sarama/alter_partition_reassignments_response.go
	新文件:   vendor/github.com/Shopify/sarama/api_versions_request.go
	新文件:   vendor/github.com/Shopify/sarama/api_versions_response.go
	新文件:   vendor/github.com/Shopify/sarama/async_producer.go
	新文件:   vendor/github.com/Shopify/sarama/balance_strategy.go
	新文件:   vendor/github.com/Shopify/sarama/broker.go
	新文件:   vendor/github.com/Shopify/sarama/client.go
	新文件:   vendor/github.com/Shopify/sarama/compress.go
	新文件:   vendor/github.com/Shopify/sarama/config.go
	新文件:   vendor/github.com/Shopify/sarama/config_resource_type.go
	新文件:   vendor/github.com/Shopify/sarama/consumer.go
	新文件:   vendor/github.com/Shopify/sarama/consumer_group.go
	新文件:   vendor/github.com/Shopify/sarama/consumer_group_members.go
	新文件:   vendor/github.com/Shopify/sarama/consumer_metadata_request.go
	新文件:   vendor/github.com/Shopify/sarama/consumer_metadata_response.go
	新文件:   vendor/github.com/Shopify/sarama/control_record.go
	新文件:   vendor/github.com/Shopify/sarama/crc32_field.go
	新文件:   vendor/github.com/Shopify/sarama/create_partitions_request.go
	新文件:   vendor/github.com/Shopify/sarama/create_partitions_response.go
	新文件:   vendor/github.com/Shopify/sarama/create_topics_request.go
	新文件:   vendor/github.com/Shopify/sarama/create_topics_response.go
	新文件:   vendor/github.com/Shopify/sarama/decompress.go
	新文件:   vendor/github.com/Shopify/sarama/delete_groups_request.go
	新文件:   vendor/github.com/Shopify/sarama/delete_groups_response.go
	新文件:   vendor/github.com/Shopify/sarama/delete_records_request.go
	新文件:   vendor/github.com/Shopify/sarama/delete_records_response.go
	新文件:   vendor/github.com/Shopify/sarama/delete_topics_request.go
	新文件:   vendor/github.com/Shopify/sarama/delete_topics_response.go
	新文件:   vendor/github.com/Shopify/sarama/describe_configs_request.go
	新文件:   vendor/github.com/Shopify/sarama/describe_configs_response.go
	新文件:   vendor/github.com/Shopify/sarama/describe_groups_request.go
	新文件:   vendor/github.com/Shopify/sarama/describe_groups_response.go
	新文件:   vendor/github.com/Shopify/sarama/describe_log_dirs_request.go
	新文件:   vendor/github.com/Shopify/sarama/describe_log_dirs_response.go
	新文件:   vendor/github.com/Shopify/sarama/dev.yml
	新文件:   vendor/github.com/Shopify/sarama/encoder_decoder.go
	新文件:   vendor/github.com/Shopify/sarama/end_txn_request.go
	新文件:   vendor/github.com/Shopify/sarama/end_txn_response.go
	新文件:   vendor/github.com/Shopify/sarama/errors.go
	新文件:   vendor/github.com/Shopify/sarama/fetch_request.go
	新文件:   vendor/github.com/Shopify/sarama/fetch_response.go
	新文件:   vendor/github.com/Shopify/sarama/find_coordinator_request.go
	新文件:   vendor/github.com/Shopify/sarama/find_coordinator_response.go
	新文件:   vendor/github.com/Shopify/sarama/go.mod
	新文件:   vendor/github.com/Shopify/sarama/go.sum
	新文件:   vendor/github.com/Shopify/sarama/gssapi_kerberos.go
	新文件:   vendor/github.com/Shopify/sarama/heartbeat_request.go
	新文件:   vendor/github.com/Shopify/sarama/heartbeat_response.go
	新文件:   vendor/github.com/Shopify/sarama/init_producer_id_request.go
	新文件:   vendor/github.com/Shopify/sarama/init_producer_id_response.go
	新文件:   vendor/github.com/Shopify/sarama/join_group_request.go
	新文件:   vendor/github.com/Shopify/sarama/join_group_response.go
	新文件:   vendor/github.com/Shopify/sarama/kerberos_client.go
	新文件:   vendor/github.com/Shopify/sarama/leave_group_request.go
	新文件:   vendor/github.com/Shopify/sarama/leave_group_response.go
	新文件:   vendor/github.com/Shopify/sarama/length_field.go
	新文件:   vendor/github.com/Shopify/sarama/list_groups_request.go
	新文件:   vendor/github.com/Shopify/sarama/list_groups_response.go
	新文件:   vendor/github.com/Shopify/sarama/list_partition_reassignments_request.go
	新文件:   vendor/github.com/Shopify/sarama/list_partition_reassignments_response.go
	新文件:   vendor/github.com/Shopify/sarama/message.go
	新文件:   vendor/github.com/Shopify/sarama/message_set.go
	新文件:   vendor/github.com/Shopify/sarama/metadata_request.go
	新文件:   vendor/github.com/Shopify/sarama/metadata_response.go
	新文件:   vendor/github.com/Shopify/sarama/metrics.go
	新文件:   vendor/github.com/Shopify/sarama/mockbroker.go
	新文件:   vendor/github.com/Shopify/sarama/mockkerberos.go
	新文件:   vendor/github.com/Shopify/sarama/mockresponses.go
	新文件:   vendor/github.com/Shopify/sarama/offset_commit_request.go
	新文件:   vendor/github.com/Shopify/sarama/offset_commit_response.go
	新文件:   vendor/github.com/Shopify/sarama/offset_fetch_request.go
	新文件:   vendor/github.com/Shopify/sarama/offset_fetch_response.go
	新文件:   vendor/github.com/Shopify/sarama/offset_manager.go
	新文件:   vendor/github.com/Shopify/sarama/offset_request.go
	新文件:   vendor/github.com/Shopify/sarama/offset_response.go
	新文件:   vendor/github.com/Shopify/sarama/packet_decoder.go
	新文件:   vendor/github.com/Shopify/sarama/packet_encoder.go
	新文件:   vendor/github.com/Shopify/sarama/partitioner.go
	新文件:   vendor/github.com/Shopify/sarama/prep_encoder.go
	新文件:   vendor/github.com/Shopify/sarama/produce_request.go
	新文件:   vendor/github.com/Shopify/sarama/produce_response.go
	新文件:   vendor/github.com/Shopify/sarama/produce_set.go
	新文件:   vendor/github.com/Shopify/sarama/real_decoder.go
	新文件:   vendor/github.com/Shopify/sarama/real_encoder.go
	新文件:   vendor/github.com/Shopify/sarama/record.go
	新文件:   vendor/github.com/Shopify/sarama/record_batch.go
	新文件:   vendor/github.com/Shopify/sarama/records.go
	新文件:   vendor/github.com/Shopify/sarama/request.go
	新文件:   vendor/github.com/Shopify/sarama/response_header.go
	新文件:   vendor/github.com/Shopify/sarama/sarama.go
	新文件:   vendor/github.com/Shopify/sarama/sasl_authenticate_request.go
	新文件:   vendor/github.com/Shopify/sarama/sasl_authenticate_response.go
	新文件:   vendor/github.com/Shopify/sarama/sasl_handshake_request.go
	新文件:   vendor/github.com/Shopify/sarama/sasl_handshake_response.go
	新文件:   vendor/github.com/Shopify/sarama/sticky_assignor_user_data.go
	新文件:   vendor/github.com/Shopify/sarama/sync_group_request.go
	新文件:   vendor/github.com/Shopify/sarama/sync_group_response.go
	新文件:   vendor/github.com/Shopify/sarama/sync_producer.go
	新文件:   vendor/github.com/Shopify/sarama/timestamp.go
	新文件:   vendor/github.com/Shopify/sarama/txn_offset_commit_request.go
	新文件:   vendor/github.com/Shopify/sarama/txn_offset_commit_response.go
	新文件:   vendor/github.com/Shopify/sarama/utils.go
	新文件:   vendor/github.com/Shopify/sarama/zstd.go
	新文件:   vendor/github.com/eapache/go-resiliency/LICENSE
	新文件:   vendor/github.com/eapache/go-resiliency/breaker/README.md
	新文件:   vendor/github.com/eapache/go-resiliency/breaker/breaker.go
	新文件:   vendor/github.com/eapache/go-xerial-snappy/.gitignore
	新文件:   vendor/github.com/eapache/go-xerial-snappy/.travis.yml
	新文件:   vendor/github.com/eapache/go-xerial-snappy/LICENSE
	新文件:   vendor/github.com/eapache/go-xerial-snappy/README.md
	新文件:   vendor/github.com/eapache/go-xerial-snappy/fuzz.go
	新文件:   vendor/github.com/eapache/go-xerial-snappy/snappy.go
	新文件:   vendor/github.com/eapache/queue/.gitignore
	新文件:   vendor/github.com/eapache/queue/.travis.yml
	新文件:   vendor/github.com/eapache/queue/LICENSE
	新文件:   vendor/github.com/eapache/queue/README.md
	新文件:   vendor/github.com/eapache/queue/queue.go
	新文件:   vendor/github.com/golang/snappy/.gitignore
	新文件:   vendor/github.com/golang/snappy/AUTHORS
	新文件:   vendor/github.com/golang/snappy/CONTRIBUTORS
	新文件:   vendor/github.com/golang/snappy/LICENSE
	新文件:   vendor/github.com/golang/snappy/README
	新文件:   vendor/github.com/golang/snappy/decode.go
	新文件:   vendor/github.com/golang/snappy/decode_amd64.go
	新文件:   vendor/github.com/golang/snappy/decode_amd64.s
	新文件:   vendor/github.com/golang/snappy/decode_other.go
	新文件:   vendor/github.com/golang/snappy/encode.go
	新文件:   vendor/github.com/golang/snappy/encode_amd64.go
	新文件:   vendor/github.com/golang/snappy/encode_amd64.s
	新文件:   vendor/github.com/golang/snappy/encode_other.go
	新文件:   vendor/github.com/golang/snappy/go.mod
	新文件:   vendor/github.com/golang/snappy/snappy.go
	新文件:   vendor/github.com/hashicorp/go-uuid/.travis.yml
	新文件:   vendor/github.com/hashicorp/go-uuid/LICENSE
	新文件:   vendor/github.com/hashicorp/go-uuid/README.md
	新文件:   vendor/github.com/hashicorp/go-uuid/go.mod
	新文件:   vendor/github.com/hashicorp/go-uuid/uuid.go
	新文件:   vendor/github.com/jcmturner/gofork/LICENSE
	新文件:   vendor/github.com/jcmturner/gofork/encoding/asn1/README.md
	新文件:   vendor/github.com/jcmturner/gofork/encoding/asn1/asn1.go
	新文件:   vendor/github.com/jcmturner/gofork/encoding/asn1/common.go
	新文件:   vendor/github.com/jcmturner/gofork/encoding/asn1/marshal.go
	新文件:   vendor/github.com/jcmturner/gofork/x/crypto/pbkdf2/pbkdf2.go
	新文件:   vendor/github.com/klauspost/compress/LICENSE
	新文件:   vendor/github.com/klauspost/compress/fse/README.md
	新文件:   vendor/github.com/klauspost/compress/fse/bitreader.go
	新文件:   vendor/github.com/klauspost/compress/fse/bitwriter.go
	新文件:   vendor/github.com/klauspost/compress/fse/bytereader.go
	新文件:   vendor/github.com/klauspost/compress/fse/compress.go
	新文件:   vendor/github.com/klauspost/compress/fse/decompress.go
	新文件:   vendor/github.com/klauspost/compress/fse/fse.go
	新文件:   vendor/github.com/klauspost/compress/huff0/.gitignore
	新文件:   vendor/github.com/klauspost/compress/huff0/README.md
	新文件:   vendor/github.com/klauspost/compress/huff0/bitreader.go
	新文件:   vendor/github.com/klauspost/compress/huff0/bitwriter.go
	新文件:   vendor/github.com/klauspost/compress/huff0/bytereader.go
	新文件:   vendor/github.com/klauspost/compress/huff0/compress.go
	新文件:   vendor/github.com/klauspost/compress/huff0/decompress.go
	新文件:   vendor/github.com/klauspost/compress/huff0/huff0.go
	新文件:   vendor/github.com/klauspost/compress/snappy/.gitignore
	新文件:   vendor/github.com/klauspost/compress/snappy/AUTHORS
	新文件:   vendor/github.com/klauspost/compress/snappy/CONTRIBUTORS
	新文件:   vendor/github.com/klauspost/compress/snappy/LICENSE
	新文件:   vendor/github.com/klauspost/compress/snappy/README
	新文件:   vendor/github.com/klauspost/compress/snappy/decode.go
	新文件:   vendor/github.com/klauspost/compress/snappy/decode_amd64.go
	新文件:   vendor/github.com/klauspost/compress/snappy/decode_amd64.s
	新文件:   vendor/github.com/klauspost/compress/snappy/decode_other.go
	新文件:   vendor/github.com/klauspost/compress/snappy/encode.go
	新文件:   vendor/github.com/klauspost/compress/snappy/encode_amd64.go
	新文件:   vendor/github.com/klauspost/compress/snappy/encode_amd64.s
	新文件:   vendor/github.com/klauspost/compress/snappy/encode_other.go
	新文件:   vendor/github.com/klauspost/compress/snappy/runbench.cmd
	新文件:   vendor/github.com/klauspost/compress/snappy/snappy.go
	新文件:   vendor/github.com/klauspost/compress/zstd/README.md
	新文件:   vendor/github.com/klauspost/compress/zstd/bitreader.go
	新文件:   vendor/github.com/klauspost/compress/zstd/bitwriter.go
	新文件:   vendor/github.com/klauspost/compress/zstd/blockdec.go
	新文件:   vendor/github.com/klauspost/compress/zstd/blockenc.go
	新文件:   vendor/github.com/klauspost/compress/zstd/blocktype_string.go
	新文件:   vendor/github.com/klauspost/compress/zstd/bytebuf.go
	新文件:   vendor/github.com/klauspost/compress/zstd/bytereader.go
	新文件:   vendor/github.com/klauspost/compress/zstd/decoder.go
	新文件:   vendor/github.com/klauspost/compress/zstd/decoder_options.go
	新文件:   vendor/github.com/klauspost/compress/zstd/enc_dfast.go
	新文件:   vendor/github.com/klauspost/compress/zstd/enc_fast.go
	新文件:   vendor/github.com/klauspost/compress/zstd/enc_params.go
	新文件:   vendor/github.com/klauspost/compress/zstd/encoder.go
	新文件:   vendor/github.com/klauspost/compress/zstd/encoder_options.go
	新文件:   vendor/github.com/klauspost/compress/zstd/framedec.go
	新文件:   vendor/github.com/klauspost/compress/zstd/frameenc.go
	新文件:   vendor/github.com/klauspost/compress/zstd/fse_decoder.go
	新文件:   vendor/github.com/klauspost/compress/zstd/fse_encoder.go
	新文件:   vendor/github.com/klauspost/compress/zstd/fse_predefined.go
	新文件:   vendor/github.com/klauspost/compress/zstd/hash.go
	新文件:   vendor/github.com/klauspost/compress/zstd/history.go
	新文件:   vendor/github.com/klauspost/compress/zstd/internal/xxhash/LICENSE.txt
	新文件:   vendor/github.com/klauspost/compress/zstd/internal/xxhash/README.md
	新文件:   vendor/github.com/klauspost/compress/zstd/internal/xxhash/xxhash.go
	新文件:   vendor/github.com/klauspost/compress/zstd/internal/xxhash/xxhash_amd64.go
	新文件:   vendor/github.com/klauspost/compress/zstd/internal/xxhash/xxhash_amd64.s
	新文件:   vendor/github.com/klauspost/compress/zstd/internal/xxhash/xxhash_other.go
	新文件:   vendor/github.com/klauspost/compress/zstd/internal/xxhash/xxhash_safe.go
	新文件:   vendor/github.com/klauspost/compress/zstd/seqdec.go
	新文件:   vendor/github.com/klauspost/compress/zstd/seqenc.go
	新文件:   vendor/github.com/klauspost/compress/zstd/snappy.go
	新文件:   vendor/github.com/klauspost/compress/zstd/zstd.go
	新文件:   vendor/github.com/pierrec/lz4/.gitignore
	新文件:   vendor/github.com/pierrec/lz4/.travis.yml
	新文件:   vendor/github.com/pierrec/lz4/LICENSE
	新文件:   vendor/github.com/pierrec/lz4/README.md
	新文件:   vendor/github.com/pierrec/lz4/block.go
	新文件:   vendor/github.com/pierrec/lz4/debug.go
	新文件:   vendor/github.com/pierrec/lz4/debug_stub.go
	新文件:   vendor/github.com/pierrec/lz4/decode_amd64.go
	新文件:   vendor/github.com/pierrec/lz4/decode_amd64.s
	新文件:   vendor/github.com/pierrec/lz4/decode_other.go
	新文件:   vendor/github.com/pierrec/lz4/errors.go
	新文件:   vendor/github.com/pierrec/lz4/internal/xxh32/xxh32zero.go
	新文件:   vendor/github.com/pierrec/lz4/lz4.go
	新文件:   vendor/github.com/pierrec/lz4/lz4_go1.10.go
	新文件:   vendor/github.com/pierrec/lz4/lz4_notgo1.10.go
	新文件:   vendor/github.com/pierrec/lz4/reader.go
	新文件:   vendor/github.com/pierrec/lz4/writer.go
	新文件:   vendor/github.com/rcrowley/go-metrics/.gitignore
	新文件:   vendor/github.com/rcrowley/go-metrics/.travis.yml
	新文件:   vendor/github.com/rcrowley/go-metrics/LICENSE
	新文件:   vendor/github.com/rcrowley/go-metrics/README.md
	新文件:   vendor/github.com/rcrowley/go-metrics/counter.go
	新文件:   vendor/github.com/rcrowley/go-metrics/debug.go
	新文件:   vendor/github.com/rcrowley/go-metrics/ewma.go
	新文件:   vendor/github.com/rcrowley/go-metrics/gauge.go
	新文件:   vendor/github.com/rcrowley/go-metrics/gauge_float64.go
	新文件:   vendor/github.com/rcrowley/go-metrics/graphite.go
	新文件:   vendor/github.com/rcrowley/go-metrics/healthcheck.go
	新文件:   vendor/github.com/rcrowley/go-metrics/histogram.go
	新文件:   vendor/github.com/rcrowley/go-metrics/json.go
	新文件:   vendor/github.com/rcrowley/go-metrics/log.go
	新文件:   vendor/github.com/rcrowley/go-metrics/memory.md
	新文件:   vendor/github.com/rcrowley/go-metrics/meter.go
	新文件:   vendor/github.com/rcrowley/go-metrics/metrics.go
	新文件:   vendor/github.com/rcrowley/go-metrics/opentsdb.go
	新文件:   vendor/github.com/rcrowley/go-metrics/registry.go
	新文件:   vendor/github.com/rcrowley/go-metrics/runtime.go
	新文件:   vendor/github.com/rcrowley/go-metrics/runtime_cgo.go
	新文件:   vendor/github.com/rcrowley/go-metrics/runtime_gccpufraction.go
	新文件:   vendor/github.com/rcrowley/go-metrics/runtime_no_cgo.go
	新文件:   vendor/github.com/rcrowley/go-metrics/runtime_no_gccpufraction.go
	新文件:   vendor/github.com/rcrowley/go-metrics/sample.go
	新文件:   vendor/github.com/rcrowley/go-metrics/syslog.go
	新文件:   vendor/github.com/rcrowley/go-metrics/timer.go
	新文件:   vendor/github.com/rcrowley/go-metrics/validate.sh
	新文件:   vendor/github.com/rcrowley/go-metrics/writer.go
	删除:     vendor/github.com/shirou/gopsutil/mem/types_openbsd.go
	删除:     vendor/github.com/shirou/gopsutil/process/types_darwin.go
	删除:     vendor/github.com/shirou/gopsutil/process/types_freebsd.go
	删除:     vendor/github.com/shirou/gopsutil/process/types_openbsd.go
	删除:     vendor/github.com/ugorji/go/codec/xml.go
	新文件:   vendor/golang.org/x/crypto/AUTHORS
	新文件:   vendor/golang.org/x/crypto/CONTRIBUTORS
	新文件:   vendor/golang.org/x/crypto/LICENSE
	新文件:   vendor/golang.org/x/crypto/PATENTS
	新文件:   vendor/golang.org/x/crypto/md4/md4.go
	新文件:   vendor/golang.org/x/crypto/md4/md4block.go
	新文件:   vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go
	新文件:   vendor/golang.org/x/net/AUTHORS
	新文件:   vendor/golang.org/x/net/CONTRIBUTORS
	新文件:   vendor/golang.org/x/net/LICENSE
	新文件:   vendor/golang.org/x/net/PATENTS
	新文件:   vendor/golang.org/x/net/internal/socks/client.go
	新文件:   vendor/golang.org/x/net/internal/socks/socks.go
	新文件:   vendor/golang.org/x/net/proxy/dial.go
	新文件:   vendor/golang.org/x/net/proxy/direct.go
	新文件:   vendor/golang.org/x/net/proxy/per_host.go
	新文件:   vendor/golang.org/x/net/proxy/proxy.go
	新文件:   vendor/golang.org/x/net/proxy/socks5.go
	删除:     vendor/golang.org/x/sys/unix/mkasm_darwin.go
	删除:     vendor/golang.org/x/sys/unix/mkpost.go
	删除:     vendor/golang.org/x/sys/unix/mksyscall.go
	删除:     vendor/golang.org/x/sys/unix/mksyscall_aix_ppc.go
	删除:     vendor/golang.org/x/sys/unix/mksyscall_aix_ppc64.go
	删除:     vendor/golang.org/x/sys/unix/mksyscall_solaris.go
	删除:     vendor/golang.org/x/sys/unix/mksysctl_openbsd.go
	删除:     vendor/golang.org/x/sys/unix/mksysnum.go
	删除:     vendor/golang.org/x/sys/unix/types_aix.go
	删除:     vendor/golang.org/x/sys/unix/types_darwin.go
	删除:     vendor/golang.org/x/sys/unix/types_dragonfly.go
	删除:     vendor/golang.org/x/sys/unix/types_freebsd.go
	删除:     vendor/golang.org/x/sys/unix/types_netbsd.go
	删除:     vendor/golang.org/x/sys/unix/types_openbsd.go
	删除:     vendor/golang.org/x/sys/unix/types_solaris.go
	删除:     vendor/golang.org/x/text/unicode/norm/maketables.go
	删除:     vendor/golang.org/x/text/unicode/norm/triegen.go
	删除:     vendor/golang.org/x/tools/go/gcexportdata/main.go
	新文件:   vendor/gopkg.in/jcmturner/aescts.v1/.gitignore
	新文件:   vendor/gopkg.in/jcmturner/aescts.v1/LICENSE
	新文件:   vendor/gopkg.in/jcmturner/aescts.v1/README.md
	新文件:   vendor/gopkg.in/jcmturner/aescts.v1/aescts.go
	新文件:   vendor/gopkg.in/jcmturner/dnsutils.v1/.gitignore
	新文件:   vendor/gopkg.in/jcmturner/dnsutils.v1/.travis.yml
	新文件:   vendor/gopkg.in/jcmturner/dnsutils.v1/LICENSE
	新文件:   vendor/gopkg.in/jcmturner/dnsutils.v1/srv.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/LICENSE
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/asn1tools/tools.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/client/ASExchange.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/client/TGSExchange.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/client/cache.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/client/client.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/client/network.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/client/passwd.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/client/session.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/client/settings.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/config/error.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/config/hosts.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/config/krb5conf.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/credentials/ccache.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/credentials/credentials.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/aes128-cts-hmac-sha1-96.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/aes128-cts-hmac-sha256-128.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/aes256-cts-hmac-sha1-96.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/aes256-cts-hmac-sha384-192.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/common/common.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/crypto.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/des3-cbc-sha1-kd.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/etype/etype.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/rc4-hmac.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3961/encryption.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3961/keyDerivation.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3961/nfold.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3962/encryption.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/rfc3962/keyDerivation.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/rfc4757/checksum.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/rfc4757/encryption.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/rfc4757/keyDerivation.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/rfc4757/msgtype.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/rfc8009/encryption.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/crypto/rfc8009/keyDerivation.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/gssapi/MICToken.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/gssapi/README.md
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/gssapi/contextFlags.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/gssapi/gssapi.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/gssapi/wrapToken.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/iana/addrtype/constants.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/iana/adtype/constants.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/iana/asnAppTag/constants.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype/constants.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/iana/constants.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/iana/errorcode/constants.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/iana/etypeID/constants.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/iana/flags/constants.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/iana/keyusage/constants.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/iana/msgtype/constants.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/iana/nametype/constants.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/iana/patype/constants.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/kadmin/changepasswddata.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/kadmin/message.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/kadmin/passwd.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/keytab/keytab.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/krberror/error.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/messages/APRep.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/messages/APReq.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/messages/KDCRep.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/messages/KDCReq.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/messages/KRBCred.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/messages/KRBError.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/messages/KRBPriv.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/messages/KRBSafe.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/messages/Ticket.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/pac/client_claims.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/pac/client_info.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/pac/credentials_info.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/pac/device_claims.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/pac/device_info.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/pac/kerb_validation_info.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/pac/pac_type.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/pac/s4u_delegation_info.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/pac/signature_data.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/pac/supplemental_cred.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/pac/upn_dns_info.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/types/Authenticator.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/types/AuthorizationData.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/types/Cryptosystem.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/types/HostAddress.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/types/KerberosFlags.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/types/PAData.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/types/PrincipalName.go
	新文件:   vendor/gopkg.in/jcmturner/gokrb5.v7/types/TypedData.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/LICENSE
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/mstypes/claims.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/mstypes/common.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/mstypes/filetime.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/mstypes/group_membership.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/mstypes/kerb_sid_and_attributes.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/mstypes/reader.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/mstypes/rpc_unicode_string.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/mstypes/sid.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/mstypes/user_session_key.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/ndr/arrays.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/ndr/decoder.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/ndr/error.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/ndr/header.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/ndr/pipe.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/ndr/primitives.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/ndr/rawbytes.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/ndr/strings.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/ndr/tags.go
	新文件:   vendor/gopkg.in/jcmturner/rpc.v1/ndr/union.go
	修改:     vendor/gopkg.in/yaml.v2/.travis.yml
	修改:     vendor/gopkg.in/yaml.v2/decode.go
	修改:     vendor/gopkg.in/yaml.v2/scannerc.go
	修改:     vendor/gopkg.in/yaml.v2/yaml.go
	修改:     vendor/gopkg.in/yaml.v2/yamlh.go
	修改:     vendor/modules.txt

* Update sender.go

* Update sender.go

* Update kafka.go

* Update kafka.go

Co-authored-by: 马涛 <matao@staff.sina.com.cn>
2020-06-26 12:07:44 +08:00
Ulric Qin
7d5d791376 support SUBTRACT 2020-06-20 22:59:47 +08:00
Ulric Qin
4b3f11418e code refactor 2020-06-20 22:17:38 +08:00
Ulric Qin
0b4d1639c6 support type: SUBTRACT 2020-06-20 22:08:10 +08:00
dongdong
018d19857d Fix stddev function (#224)
* Correct stddev function usage

* Simplify code

* Fix CI error
2020-06-20 13:56:52 +08:00
jsers
4233c36fd0 web build 2020-06-18 16:04:49 +08:00
jsers
8375caaaba fix: clear useless data when subscribing 2020-06-18 16:04:03 +08:00
jsers
12ae7bbf56 web build 2020-06-17 11:40:09 +08:00
jsers
8555ad5118 style: optimize alarm scene table style (#198) 2020-06-17 11:39:15 +08:00
jsers
eeffa02f59 web build 2020-06-17 11:12:22 +08:00
jsers
7003e3a03b fix: graph legend is not available 2020-06-17 11:10:19 +08:00
jsers
fc023fd833 fix: Graph typescript error 2020-06-17 11:07:58 +08:00
dongdong
b45a968a9a Add stddev funtion for judge (#214) 2020-06-16 14:26:00 +08:00
710leo
8dac520a79 change go.mod 2020-06-15 15:57:10 +08:00
710leo
6237673725 Change module to v2 in go.mod file 2020-06-15 15:35:23 +08:00
UlricQin
0f9ec99c1d upgrade 2.7.0 2020-06-13 18:06:57 +08:00
UlricQin
433da35d34 delete no use config 2020-06-11 20:28:58 +08:00
UlricQin
1a675ed40e refactor port listening checker 2020-06-11 17:31:46 +08:00
matao3754
a1c47b7ca3 添加endpoint屏蔽,并且修改页面对应必填项,去除mcache不必要的锁
* 修改:     go.mod
	修改:     go.sum
	重命名:   pub/index-c6eeb66b35b8fd41c6bf.css -> pub/index-835a6df5e01917561f12.css
	新文件:   pub/index-835a6df5e01917561f12.js
	新文件:   pub/index-835a6df5e01917561f12.js.map
	删除:     pub/index-c6eeb66b35b8fd41c6bf.js
	删除:     pub/index-c6eeb66b35b8fd41c6bf.js.map
	修改:     pub/index.html
	修改:     src/model/user.go
	修改:     src/modules/monapi/cron/mask.go
	修改:     src/modules/monapi/mcache/mask.go
	修改:     web/package-lock.json
	修改:     web/src/pages/Monitor/Silence/CustomForm.tsx

* Update mask.go

Co-authored-by: 马涛 <matao@staff.sina.com.cn>
2020-06-07 09:36:58 +08:00
jsers
6c8e7b024f web build 2020-06-03 17:41:35 +08:00
jsers
76d7b8c3b8 fix: carry tags when strategy batch cloning (#190) 2020-06-03 17:41:35 +08:00
710leo
2b1387620b Merge branch 'master' of github.com:didi/nightingale 2020-06-01 21:12:19 +08:00
Ulric Qin
2d0fa2d26f upgrade 2.6.1 2020-06-01 20:54:47 +08:00
710leo
3eca4b3dac Refactor: remove tsdb xxhash key 2020-06-01 20:48:55 +08:00
jsers
436ae6c610 web build 2020-06-01 18:07:07 +08:00
jsers
70c00f1424 fix: HTML encode in the tooltip 2020-06-01 18:07:07 +08:00
UlricQin
61fc79ff47 add snmp funcs 2020-06-01 16:36:12 +08:00
UlricQin
7dfefedf77 fix time location parse 2020-06-01 16:26:41 +08:00
jsers
48ce07eaa1 web build (#183) 2020-06-01 13:10:06 +08:00
yanli
61d0f87f5b Update LOGForm.tsx (#166) 2020-06-01 09:54:27 +08:00
yanli
2ee7668382 Server Add log time format 02 / 01 / 2006:15:04:05 (#165)
Server Add log time format 02 / 01 / 2006:15:04:05
2020-06-01 09:53:58 +08:00
yanli
eaf1d1be6d Add log time format 02 / 01 / 2006:15:04:05 (#164)
* Add log time format 02 / 01 / 2006:15:04:05

* Server Add log time format 02 / 01 / 2006:15:04:05
2020-06-01 09:53:36 +08:00
jsers
36a24add6e web build 2020-05-29 17:34:42 +08:00
jsers
e66c14b086 feat: add stdin and env filed in the plugin collect page 2020-05-29 17:34:42 +08:00
710leo
6b646e3510 Merge branch 'master' of github.com:didi/nightingale 2020-05-29 16:46:49 +08:00
710leo
a727a7f377 Change plugin collect env format 2020-05-29 16:41:33 +08:00
UlricQin
a366f14434 upgrade 2.4.1: bugfix: default token 2020-05-29 16:02:00 +08:00
UlricQin
834669bf36 upgrade 2.4.0: support grafana 2020-05-28 20:28:48 +08:00
Ulric Qin
3c1c43ae9c monapi support grafana 2020-05-28 18:42:42 +08:00
710leo
0d2860dd8e Plugin collect support stdin and env 2020-05-27 21:05:38 +08:00
710leo
ea25842f9d Optimize alert function 2020-05-27 21:01:08 +08:00
sven
4b21874251 bug fix nodata (#157)
* bug fix nodata
2020-05-26 12:33:39 +08:00
2631 changed files with 125172 additions and 613913 deletions

View File

@@ -1 +0,0 @@
web

View File

@@ -1,18 +0,0 @@
---
name: Bug Report
about: Report a bug encountered while operating Nightingale
labels: kind/bug
---
**What happened**:
**What you expected to happen**:
**How to reproduce it (as minimally and precisely as possible)**:
**Anything else we need to know?**:
**Environment**:
- OS (e.g: `cat /etc/os-release`):
- Logs:
- Others:

67
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
name: Bug Report
description: Report a bug encountered while running Nightingale
labels: ["kind/bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking time to fill out this bug report!
The more detailed the form is filled in, the easier the problem will be solved.
- type: textarea
id: config
attributes:
label: Relevant server.conf | webapi.conf
description: Place config in the toml code section. This will be automatically formatted into toml, so no need for backticks.
render: toml
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant logs
description: categraf | telegraf | server | webapi | prometheus | chrome request/response ...
render: text
validations:
required: true
- type: input
id: system-info
attributes:
label: System info
description: Include nightingale version, operating system, and other relevant details
placeholder: ex. n9e 5.9.2, n9e-fe 5.5.0, categraf 0.1.0, Ubuntu 20.04, Docker 20.10.8
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Steps to reproduce
description: Describe the steps to reproduce the bug.
value: |
1.
2.
3.
...
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
description: Describe what you expected to happen when you performed the above steps.
validations:
required: true
- type: textarea
id: actual-behavior
attributes:
label: Actual behavior
description: Describe what actually happened when you performed the above steps.
validations:
required: true
- type: textarea
id: additional-info
attributes:
label: Additional info
description: Include gist of relevant config, logs, etc.
validations:
required: false

View File

@@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Nightingale community
url: https://n9e.didiyun.com/community/
about: List of communication channels for the Nightingale community.
- name: Nightingale docs
url: https://n9e.github.io/
about: You may want to read through the document before asking questions.

View File

@@ -1,26 +1,32 @@
name: Go
name: Release
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
tags:
- 'v*'
env:
GO_VERSION: 1.18
jobs:
build:
name: Build
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.13
uses: actions/setup-go@v1
with:
go-version: 1.13
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Build
run: ./control build
- name: Checkout Source Code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Go Environment
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
- uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v3
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

34
.gitignore vendored
View File

@@ -29,25 +29,33 @@ _test
/build
/dist
/etc/*.local.yml
/etc/log/log.test.json
/etc/*.local.conf
/etc/plugins/*.local.yml
/etc/script/rules.yaml
/etc/script/alert-rules.json
/etc/script/record-rules.json
/data*
/tarball
/run
/vendor
/tmp
/pub
/n9e
/docker/pub
/docker/n9e
/docker/mysqldata
/docker/experience_pg_vm/pgdata
/etc.local*
/front/statik/statik.go
.alerts
.idea
.index
.vscode
.DS_Store
.cache-loader
.payload
queries.active
/n9e-*
/src/modules/index/index
/src/modules/collector/collector
/src/modules/transfer/transfer
/src/modules/tsdb/tsdb
/src/modules/monapi/monapi
/web/node_modules
/web/.cache-loader
/web/yarn.lock
n9e.sql

122
.goreleaser.yaml Normal file
View File

@@ -0,0 +1,122 @@
before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
- go install github.com/rakyll/statik
snapshot:
name_template: '{{ .Tag }}'
checksum:
name_template: 'checksums.txt'
changelog:
skip: true
builds:
- id: build
hooks:
pre:
- cmd: sh -x ./fe.sh
output: true
main: ./cmd/center/
binary: n9e
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- amd64
- arm64
ldflags:
- -s -w
- -X github.com/ccfos/nightingale/v6/pkg/version.Version={{ .Tag }}-{{.Commit}}
- id: build-cli
main: ./cmd/cli/
binary: n9e-cli
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- amd64
- arm64
ldflags:
- -s -w
- -X github.com/ccfos/nightingale/v6/pkg/version.Version={{ .Tag }}-{{.Commit}}
- id: build-edge
main: ./cmd/edge/
binary: n9e-edge
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- amd64
- arm64
ldflags:
- -s -w
- -X github.com/ccfos/nightingale/v6/pkg/version.Version={{ .Tag }}-{{.Commit}}
archives:
- id: n9e
builds:
- build
- build-cli
- build-edge
format: tar.gz
format_overrides:
- goos: windows
format: zip
name_template: "n9e-v{{ .Version }}-{{ .Os }}-{{ .Arch }}"
wrap_in_directory: false
files:
- docker/*
- etc/*
- integrations/*
- cli/*
- n9e.sql
release:
github:
owner: ccfos
name: nightingale
name_template: "v{{ .Version }}"
dockers:
- image_templates:
- flashcatcloud/nightingale:{{ .Version }}-amd64
goos: linux
goarch: amd64
ids:
- build
dockerfile: docker/Dockerfile.goreleaser
extra_files:
- etc
- integrations
use: buildx
build_flag_templates:
- "--platform=linux/amd64"
- image_templates:
- flashcatcloud/nightingale:{{ .Version }}-arm64v8
goos: linux
goarch: arm64
ids:
- build
dockerfile: docker/Dockerfile.goreleaser.arm64
extra_files:
- etc
- integrations
use: buildx
build_flag_templates:
- "--platform=linux/arm64/v8"
docker_manifests:
- name_template: flashcatcloud/nightingale:{{ .Version }}
image_templates:
- flashcatcloud/nightingale:{{ .Version }}-amd64
- flashcatcloud/nightingale:{{ .Version }}-arm64v8
- name_template: flashcatcloud/nightingale:latest
image_templates:
- flashcatcloud/nightingale:{{ .Version }}-amd64
- flashcatcloud/nightingale:{{ .Version }}-arm64v8

View File

@@ -1,11 +0,0 @@
FROM golang:1.13
LABEL maintainer="llitfkitfk@gmail.com,chenjiandongx@qq.com"
WORKDIR /app
RUN apt-get update && apt-get install net-tools -y
COPY . .
RUN ./control build docker
RUN mv /app/bin/* /usr/local/bin

View File

@@ -430,4 +430,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.

41
Makefile Normal file
View File

@@ -0,0 +1,41 @@
.PHONY: prebuild build
ROOT:=$(shell pwd -P)
GIT_COMMIT:=$(shell git --work-tree ${ROOT} rev-parse 'HEAD^{commit}')
_GIT_VERSION:=$(shell git --work-tree ${ROOT} describe --tags --abbrev=14 "${GIT_COMMIT}^{commit}" 2>/dev/null)
TAG=$(shell echo "${_GIT_VERSION}" | awk -F"-" '{print $$1}')
RELEASE_VERSION:="$(TAG)-$(GIT_COMMIT)"
all: prebuild build
prebuild:
echo "begin download and embed the front-end file..."
sh fe.sh
echo "front-end file download and embedding completed."
build:
go build -ldflags "-w -s -X github.com/ccfos/nightingale/v6/pkg/version.Version=$(RELEASE_VERSION)" -o n9e ./cmd/center/main.go
build-edge:
go build -ldflags "-w -s -X github.com/ccfos/nightingale/v6/pkg/version.Version=$(RELEASE_VERSION)" -o n9e-edge ./cmd/edge/
build-alert:
go build -ldflags "-w -s -X github.com/ccfos/nightingale/v6/pkg/version.Version=$(RELEASE_VERSION)" -o n9e-alert ./cmd/alert/main.go
build-pushgw:
go build -ldflags "-w -s -X github.com/ccfos/nightingale/v6/pkg/version.Version=$(RELEASE_VERSION)" -o n9e-pushgw ./cmd/pushgw/main.go
build-cli:
go build -ldflags "-w -s -X github.com/ccfos/nightingale/v6/pkg/version.Version=$(RELEASE_VERSION)" -o n9e-cli ./cmd/cli/main.go
run:
nohup ./n9e > n9e.log 2>&1 &
run-alert:
nohup ./n9e-alert > n9e-alert.log 2>&1 &
run-pushgw:
nohup ./n9e-pushgw > n9e-pushgw.log 2>&1 &
release:
goreleaser --skip-validate --skip-publish --snapshot

102
README.md
View File

@@ -1,54 +1,74 @@
<img src="https://s3-gz01.didistatic.com/n9e-pub/image/n9e-logo-bg-white.png" width="200" alt="Nightingale"/>
<br>
<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>
</p>
[中文简介](README_ZH.md)
<p align="center">
<a href="https://flashcat.cloud/docs/">
<img alt="Docs" src="https://img.shields.io/badge/docs-get%20started-brightgreen"/></a>
<a href="https://hub.docker.com/u/flashcatcloud">
<img alt="Docker pulls" src="https://img.shields.io/docker/pulls/flashcatcloud/nightingale"/></a>
<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">
<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>
Nightingale is a fork of Open-Falcon, and all the core modules have been greatly optimized. It integrates the best practices of DiDi. You can think of it as the next generation of Open-Falcon, and use directly in production environment.
<p align="center">
告警管理专家,一体化的开源可观测平台
</p>
## Documentation
[English](./README_en.md) | [中文](./README.md)
Nightingale user manual: [https://n9e.didiyun.com/](https://n9e.didiyun.com/)
夜莺Nightingale是中国计算机学会托管的开源云原生可观测工具,最早由滴滴于 2020 年孵化并开源,并于 2022 年正式捐赠予中国计算机学会。夜莺采用 All-in-One 的设计理念,集数据采集、可视化、监控告警、数据分析于一体,与云原生生态紧密集成,融入了顶级互联网公司可观测性最佳实践,沉淀了众多社区专家经验,开箱即用。
## Compile
## 资料
```bash
mkdir -p $GOPATH/src/github.com/didi
cd $GOPATH/src/github.com/didi
git clone https://github.com/didi/nightingale.git
cd nightingale
./control build
```
## Quickstart with Docker
We has offered a Docker demo for the users who want to give it a try. Before you get started, make sure you have installed **Docker** & **docker-compose** and there are some details you should know.
* We highly recommend users prepare a new VM environment to use it.
* All the core components will be installed on your OS according to the `docker-compose.yaml`.
* Nightingale will use the following ports, `80`, `5800`, `5810`, `5811`, `5820`, `5821`, `5830`, `5831`, `5840`, `5841`, `6379`, `2058`, `3306`.
Okay. Run it! Once the docker finish its jobs, visits http://your-env-ip in your broswer. Default username and password is `root:root`.
```bash
$ docker-compose up -d
```
![dashboard](https://user-images.githubusercontent.com/19553554/78956965-8b9c6180-7b16-11ea-9747-6ed5e62b068d.png)
## Upgrading
If upgrade `version<1.4.0` to `v1.4.0`, follow the operating instructions in [v1.4.0](https://github.com/didi/nightingale/releases/tag/V1.4.0) release
- 文档:[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)
## Team
## 功能和特点
[ulricqin](https://github.com/ulricqin) [710leo](https://github.com/710leo) [jsers](https://github.com/jsers) [hujter](https://github.com/hujter) [n4mine](https://github.com/n4mine) [heli567](https://github.com/heli567)
- 统一接入各种时序库:支持对接 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 数据源,实现日志、链路、指标多维度的统一可观测
## Community
Nightingale is developed in open. Here we set up an organization, [github.com/n9e](https://github.com/n9e), which is used to communicate and contribute. We sincerely hope more developers can use their creativity to make lots of related projects for the Nightingale ecosystem.
## 产品演示
![演示](doc/img/n9e-screenshot-gif-v6.gif)
## 部署架构
![架构](doc/img/n9e-arch-latest.png)
## 加入交流群
欢迎加入 QQ 交流群群号479290895QQ 群适合群友互助,夜莺研发人员通常不在群里。如果要报 bug 请到[这里](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Fbug&projects=&template=bug_report.yml),提问到[这里](https://answer.flashcat.cloud/)。
## Stargazers over time
[![Stargazers over time](https://api.star-history.com/svg?repos=ccfos/nightingale&type=Date)](https://star-history.com/#ccfos/nightingale&Date)
## Contributors
<a href="https://github.com/ccfos/nightingale/graphs/contributors">
<img src="https://contrib.rocks/image?repo=ccfos/nightingale" />
</a>
## 社区治理
[夜莺开源项目和社区治理架构(草案)](./doc/community-governance.md)
## License
<img alt="Apache-2.0 license" src="https://s3-gz01.didistatic.com/n9e-pub/image/apache.jpeg" width="128">
Nightingale is available under the Apache-2.0 license. See the [LICENSE](LICENSE) file for more info.
[Apache License V2.0](https://github.com/didi/nightingale/blob/main/LICENSE)

View File

@@ -1,52 +0,0 @@
<img src="https://s3-gz01.didistatic.com/n9e-pub/image/n9e-logo-bg-white.png" width="200" alt="Nightingale"/>
<br>
[English Introduction](README.md)
Nightingale 是一套衍生自 Open-Falcon 的互联网监控解决方案,融入了部分滴滴生产环境的最佳实践,灵活易用,稳定可靠,是一个生产环境直接可用的版本 :-)
## 文档
使用手册请参考:[夜莺使用手册](https://n9e.didiyun.com/)
## 编译
```bash
mkdir -p $GOPATH/src/github.com/didi
cd $GOPATH/src/github.com/didi
git clone https://github.com/didi/nightingale.git
cd nightingale
./control build
```
## 快速开始
使用 docker 和 docker-compose 环境可以快速部署一整套 nightingale 系统,涵盖了所有的核心组件。
* 强烈建议使用一个新的虚拟环境来部署和测试这个系统。
* 系统组件占用了以下端口,`80`, `5800`, `5810`, `5811`, `5820`, `5821`, `5830`, `5831`, `5840`, `5841`, `6379`, `2058`, `3306`,部署前请确保这些端口没有被使用。
使用 docker-compose 一键构建部署,完成以后可以使用浏览器打开 http://your-env-ip。 默认的登录账号密码均为 `root`
```bash
$ docker-compose up -d
```
![dashboard](https://user-images.githubusercontent.com/19553554/78956965-8b9c6180-7b16-11ea-9747-6ed5e62b068d.png)
## 版本升级
如果需要从 `v1.4.0` 之前的版本升级到 `v1.4.0` , 按照 [v1.4.0](https://github.com/didi/nightingale/releases/tag/V1.4.0) release 说明操作即可
## 团队
[ulricqin](https://github.com/ulricqin) [710leo](https://github.com/710leo) [jsers](https://github.com/jsers) [hujter](https://github.com/hujter) [n4mine](https://github.com/n4mine) [heli567](https://github.com/heli567)
## 社区
[github.com/n9e](https://github.com/n9e) 是为夜莺所创建的 Organization用于收集和开发夜莺周边项目。
## License
<img alt="Apache-2.0 license" src="https://s3-gz01.didistatic.com/n9e-pub/image/apache.jpeg" width="128">
Nightingale 基于 Apache-2.0 许可证进行分发和使用,更多信息参见 [LICENSE](LICENSE)。

104
README_en.md Normal file
View File

@@ -0,0 +1,104 @@
<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>
</p>
<p align="center">
<img alt="GitHub latest release" src="https://img.shields.io/github/v/release/ccfos/nightingale"/>
<a href="https://n9e.github.io">
<img alt="Docs" src="https://img.shields.io/badge/docs-get%20started-brightgreen"/></a>
<a href="https://hub.docker.com/u/flashcatcloud">
<img alt="Docker pulls" src="https://img.shields.io/docker/pulls/flashcatcloud/nightingale"/></a>
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/ccfos/nightingale">
<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">
<a href="https://github.com/ccfos/nightingale/graphs/contributors">
<img alt="GitHub contributors" src="https://img.shields.io/github/contributors-anon/ccfos/nightingale"/></a>
<a href="https://n9e-talk.slack.com/">
<img alt="GitHub contributors" src="https://img.shields.io/badge/join%20slack-%23n9e-brightgreen.svg"/></a>
<img alt="License" src="https://img.shields.io/badge/license-Apache--2.0-blue"/>
</p>
<p align="center">
An open-source cloud-native monitoring system that is <b>all-in-one</b> <br/>
<b>Out-of-the-box</b>, it integrates data collection, visualization, and monitoring alert <br/>
We recommend upgrading your <b>Prometheus + AlertManager + Grafana</b> combination to Nightingale!
</p>
[English](./README.md) | [中文](./README_ZH.md)
## Highlighted Features
- **Out-of-the-box**
- Supports multiple deployment methods such as **Docker, Helm Chart, and cloud services**, integrates data collection, monitoring, and alerting into one system, and comes with various monitoring dashboards, quick views, and alert rule templates. **It greatly reduces the construction cost, learning cost, and usage cost of cloud-native monitoring systems**.
- **Professional Alerting**
- Provides visual alert configuration and management, supports various alert rules, offers the ability to configure silence and subscription rules, supports multiple alert delivery channels, and has features such as alert self-healing and event management.
- **Cloud-Native**
- Quickly builds an enterprise-level cloud-native monitoring system through a turnkey approach, supports multiple collectors such as [Categraf](https://github.com/flashcatcloud/categraf), Telegraf, and Grafana-agent, supports multiple data sources such as Prometheus, VictoriaMetrics, M3DB, ElasticSearch, and Jaeger, and is compatible with importing Grafana dashboards. **It seamlessly integrates with the cloud-native ecosystem**.
- **High Performance and High Availability**
- Due to the multi-data-source management engine of Nightingale and its excellent architecture design, and utilizing a high-performance time-series database, it can handle data collection, storage, and alert analysis scenarios with billions of time-series data, saving a lot of costs.
- Nightingale components can be horizontally scaled with no single point of failure. It has been deployed in thousands of enterprises and tested in harsh production practices. Many leading Internet companies have used Nightingale for cluster machines with hundreds of nodes, processing billions of time-series data.
- **Flexible Extension and Centralized Management**
- Nightingale can be deployed on a 1-core 1G cloud host, deployed in a cluster of hundreds of machines, or run in Kubernetes. Time-series databases, alert engines, and other components can also be decentralized to various data centers and regions, balancing edge deployment with centralized management. **It solves the problem of data fragmentation and lack of unified views**.
#### If you are using Prometheus and have one or more of the following requirement scenarios, it is recommended that you upgrade to Nightingale:
- Multiple systems such as Prometheus, Alertmanager, Grafana, etc. are fragmented and lack a unified view and cannot be used out of the box;
- The way to manage Prometheus and Alertmanager by modifying configuration files has a big learning curve and is difficult to collaborate;
- Too much data to scale-up your Prometheus cluster;
- Multiple Prometheus clusters running in production environments, which faced high management and usage costs;
#### If you are using Zabbix and have the following scenarios, it is recommended that you upgrade to Nightingale:
- Monitoring too much data and wanting a better scalable solution;
- A high learning curve and a desire for better efficiency of collaborative use in a multi-person, multi-team model;
- Microservice and cloud-native architectures with variable monitoring data lifecycles and high monitoring data dimension bases, which are not easily adaptable to the Zabbix data model;
#### If you are using [open-falcon](https://github.com/open-falcon/falcon-plus), we recommend you to upgrade to Nightingale
- For more information about open-falcon and Nightingale, please refer to read [Ten features and trends of cloud-native monitoring](https://mp.weixin.qq.com/s?__biz=MzkzNjI5OTM5Nw==&mid=2247483738&idx=1&sn=e8bdbb974a2cd003c1abcc2b5405dd18&chksm=c2a19fb0f5d616a63185cd79277a79a6b80118ef2185890d0683d2bb20451bd9303c78d083c5#rd)。
## Getting Started
[English Doc](https://n9e.github.io/) | [中文文档](http://n9e.flashcat.cloud/)
## Screenshots
https://user-images.githubusercontent.com/792850/216888712-2565fcea-9df5-47bd-a49e-d60af9bd76e8.mp4
## Architecture
<img src="doc/img/arch-product.png" width="600">
Nightingale monitoring can receive monitoring data reported by various collectors (such as [Categraf](https://github.com/flashcatcloud/categraf) , telegraf, grafana-agent, Prometheus, etc.) and write them to various popular time-series databases (such as Prometheus, M3DB, VictoriaMetrics, Thanos, TDEngine, etc.). It provides configuration capabilities for alert rules, silence rules, and subscription rules, as well as the ability to view monitoring data. It also provides automatic alarm self-healing mechanisms (such as automatically calling back to a webhook address or executing a script after an alarm is triggered), and the ability to store and manage historical alarm events and view them in groups.
If the performance of a standalone time-series database (such as Prometheus) has bottlenecks or poor disaster recovery, we recommend using [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics). The VictoriaMetrics architecture is relatively simple, has excellent performance, and is easy to deploy and maintain. The architecture diagram is as shown above. For more detailed documentation on VictoriaMetrics, please refer to its [official website](https://victoriametrics.com/).
**We welcome you to participate in the Nightingale open-source project and community in various ways, including but not limited to**
- Adding and improving documentation => [n9e.github.io](https://n9e.github.io/)
- Sharing your best practices and experience in using Nightingale monitoring => [Article sharing]((https://n9e.github.io/docs/prologue/share/))
- Submitting product suggestions => [github issue](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Ffeature&template=enhancement.md)
- Submitting code to make Nightingale monitoring faster, more stable, and easier to use => [github pull request](https://github.com/didi/nightingale/pulls)
**Respecting, recognizing, and recording the work of every contributor** is the first guiding principle of the Nightingale open-source community. We advocate effective questioning, which not only respects the developer's time but also contributes to the accumulation of knowledge in the entire community
- Before asking a question, please first refer to the [FAQ](https://www.gitlink.org.cn/ccfos/nightingale/wiki/faq)
- We use [GitHub Discussions](https://github.com/ccfos/nightingale/discussions) as the communication forum. You can search and ask questions here.
- We also recommend that you join ours [Slack channel](https://n9e-talk.slack.com/) to exchange experiences with other Nightingale users.
## Who is using Nightingale
You can register your usage and share your experience by posting on **[Who is Using Nightingale](https://github.com/ccfos/nightingale/issues/897)**.
## Stargazers over time
[![Stargazers over time](https://starchart.cc/ccfos/nightingale.svg)](https://starchart.cc/ccfos/nightingale)
## Contributors
<a href="https://github.com/ccfos/nightingale/graphs/contributors">
<img src="https://contrib.rocks/image?repo=ccfos/nightingale" />
</a>
## License
[Apache License V2.0](https://github.com/didi/nightingale/blob/main/LICENSE)

78
alert/aconf/conf.go Normal file
View File

@@ -0,0 +1,78 @@
package aconf
import (
"path"
"github.com/toolkits/pkg/runner"
)
type Alert struct {
Disable bool
EngineDelay int64
Heartbeat HeartbeatConfig
Alerting Alerting
}
type SMTPConfig struct {
Host string
Port int
User string
Pass string
From string
InsecureSkipVerify bool
Batch int
}
type HeartbeatConfig struct {
IP string
Interval int64
Endpoint string
EngineName string
}
type Alerting struct {
Timeout int64
TemplatesDir string
NotifyConcurrency int
}
type CallPlugin struct {
Enable bool
PluginPath string
Caller string
}
type RedisPub struct {
Enable bool
ChannelPrefix string
ChannelKey string
}
type Ibex struct {
Address string
BasicAuthUser string
BasicAuthPass string
Timeout int64
}
func (a *Alert) PreCheck() {
if a.Alerting.TemplatesDir == "" {
a.Alerting.TemplatesDir = path.Join(runner.Cwd, "etc", "template")
}
if a.Alerting.NotifyConcurrency == 0 {
a.Alerting.NotifyConcurrency = 10
}
if a.Heartbeat.Interval == 0 {
a.Heartbeat.Interval = 1000
}
if a.Heartbeat.EngineName == "" {
a.Heartbeat.EngineName = "default"
}
if a.EngineDelay == 0 {
a.EngineDelay = 30
}
}

95
alert/alert.go Normal file
View File

@@ -0,0 +1,95 @@
package alert
import (
"context"
"fmt"
"github.com/ccfos/nightingale/v6/alert/aconf"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/alert/dispatch"
"github.com/ccfos/nightingale/v6/alert/eval"
"github.com/ccfos/nightingale/v6/alert/naming"
"github.com/ccfos/nightingale/v6/alert/process"
"github.com/ccfos/nightingale/v6/alert/queue"
"github.com/ccfos/nightingale/v6/alert/record"
"github.com/ccfos/nightingale/v6/alert/router"
"github.com/ccfos/nightingale/v6/alert/sender"
"github.com/ccfos/nightingale/v6/conf"
"github.com/ccfos/nightingale/v6/dumper"
"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/httpx"
"github.com/ccfos/nightingale/v6/pkg/logx"
"github.com/ccfos/nightingale/v6/prom"
"github.com/ccfos/nightingale/v6/pushgw/pconf"
"github.com/ccfos/nightingale/v6/pushgw/writer"
)
func Initialize(configDir string, cryptoKey string) (func(), error) {
config, err := conf.InitConfig(configDir, cryptoKey)
if err != nil {
return nil, fmt.Errorf("failed to init config: %v", err)
}
logxClean, err := logx.Init(config.Log)
if err != nil {
return nil, err
}
ctx := ctx.NewContext(context.Background(), nil, false, config.CenterApi)
syncStats := memsto.NewSyncStats()
alertStats := astats.NewSyncStats()
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)
dsCache := memsto.NewDatasourceCache(ctx, syncStats)
userCache := memsto.NewUserCache(ctx, syncStats)
userGroupCache := memsto.NewUserGroupCache(ctx, syncStats)
promClients := prom.NewPromClient(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)
r := httpx.GinEngine(config.Global.RunMode, config.HTTP)
rt := router.New(config.HTTP, config.Alert, alertMuteCache, targetCache, busiGroupCache, alertStats, ctx, externalProcessors)
rt.Config(r)
dumper.ConfigRouter(r)
httpClean := httpx.Init(config.HTTP, r)
return func() {
logxClean()
httpClean()
}, nil
}
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) {
alertSubscribeCache := memsto.NewAlertSubscribeCache(ctx, syncStats)
recordingRuleCache := memsto.NewRecordingRuleCache(ctx, syncStats)
go models.InitNotifyConfig(ctx, alertc.Alerting.TemplatesDir)
naming := naming.NewNaming(ctx, alertc.Heartbeat)
writers := writer.NewWriters(pushgwc)
record.NewScheduler(alertc, recordingRuleCache, promClients, writers, alertStats)
eval.NewScheduler(alertc, externalProcessors, alertRuleCache, targetCache, busiGroupCache, alertMuteCache, datasourceCache, promClients, naming, ctx, alertStats)
dp := dispatch.NewDispatch(alertRuleCache, userCache, userGroupCache, alertSubscribeCache, targetCache, notifyConfigCache, alertc.Alerting, ctx)
consumer := dispatch.NewConsumer(alertc.Alerting, ctx, dp)
go dp.ReloadTpls()
go consumer.LoopConsume()
go queue.ReportQueueSize(alertStats)
go sender.StartEmailSender(notifyConfigCache.GetSMTP()) // todo
}

93
alert/astats/stats.go Normal file
View File

@@ -0,0 +1,93 @@
package astats
import (
"github.com/prometheus/client_golang/prometheus"
)
const (
namespace = "n9e"
subsystem = "alert"
)
type Stats struct {
CounterSampleTotal *prometheus.CounterVec
CounterAlertsTotal *prometheus.CounterVec
GaugeAlertQueueSize prometheus.Gauge
GaugeSampleQueueSize *prometheus.GaugeVec
RequestDuration *prometheus.HistogramVec
ForwardDuration *prometheus.HistogramVec
}
func NewSyncStats() *Stats {
// 从各个接收接口接收到的监控数据总量
CounterSampleTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "samples_received_total",
Help: "Total number samples received.",
}, []string{"cluster", "channel"})
// 产生的告警总量
CounterAlertsTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "alerts_total",
Help: "Total number alert events.",
}, []string{"cluster"})
// 内存中的告警事件队列的长度
GaugeAlertQueueSize := prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "alert_queue_size",
Help: "The size of alert queue.",
})
// 数据转发队列,各个队列的长度
GaugeSampleQueueSize := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "sample_queue_size",
Help: "The size of sample queue.",
}, []string{"cluster", "channel_number"})
// 一些重要的请求,比如接收数据的请求,应该统计一下延迟情况
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"},
)
// 发往后端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"},
)
prometheus.MustRegister(
CounterSampleTotal,
CounterAlertsTotal,
GaugeAlertQueueSize,
GaugeSampleQueueSize,
RequestDuration,
ForwardDuration,
)
return &Stats{
CounterSampleTotal: CounterSampleTotal,
CounterAlertsTotal: CounterAlertsTotal,
GaugeAlertQueueSize: GaugeAlertQueueSize,
GaugeSampleQueueSize: GaugeSampleQueueSize,
RequestDuration: RequestDuration,
ForwardDuration: ForwardDuration,
}
}

111
alert/common/conv.go Normal file
View File

@@ -0,0 +1,111 @@
package common
import (
"fmt"
"math"
"strings"
"github.com/prometheus/common/model"
)
type AnomalyPoint struct {
Key string `json:"key"`
Labels model.Metric `json:"labels"`
Timestamp int64 `json:"timestamp"`
Value float64 `json:"value"`
Severity int `json:"severity"`
Triggered bool `json:"triggered"`
Query string `json:"query"`
}
func NewAnomalyPoint(key string, labels map[string]string, ts int64, value float64, severity int) AnomalyPoint {
anomalyPointLabels := make(model.Metric)
for k, v := range labels {
anomalyPointLabels[model.LabelName(k)] = model.LabelValue(v)
}
anomalyPointLabels[model.MetricNameLabel] = model.LabelValue(key)
return AnomalyPoint{
Key: key,
Labels: anomalyPointLabels,
Timestamp: ts,
Value: value,
Severity: severity,
}
}
func (v *AnomalyPoint) ReadableValue() string {
ret := fmt.Sprintf("%.5f", v.Value)
ret = strings.TrimRight(ret, "0")
return strings.TrimRight(ret, ".")
}
func ConvertAnomalyPoints(value model.Value) (lst []AnomalyPoint) {
if value == nil {
return
}
switch value.Type() {
case model.ValVector:
items, ok := value.(model.Vector)
if !ok {
return
}
for _, item := range items {
if math.IsNaN(float64(item.Value)) {
continue
}
lst = append(lst, AnomalyPoint{
Key: item.Metric.String(),
Timestamp: item.Timestamp.Unix(),
Value: float64(item.Value),
Labels: item.Metric,
})
}
case model.ValMatrix:
items, ok := value.(model.Matrix)
if !ok {
return
}
for _, item := range items {
if len(item.Values) == 0 {
return
}
last := item.Values[len(item.Values)-1]
if math.IsNaN(float64(last.Value)) {
continue
}
lst = append(lst, AnomalyPoint{
Key: item.Metric.String(),
Labels: item.Metric,
Timestamp: last.Timestamp.Unix(),
Value: float64(last.Value),
})
}
case model.ValScalar:
item, ok := value.(*model.Scalar)
if !ok {
return
}
if math.IsNaN(float64(item.Value)) {
return
}
lst = append(lst, AnomalyPoint{
Key: "{}",
Timestamp: item.Timestamp.Unix(),
Value: float64(item.Value),
Labels: model.Metric{},
})
default:
return
}
return
}

45
alert/common/key.go Normal file
View File

@@ -0,0 +1,45 @@
package common
import (
"fmt"
"github.com/ccfos/nightingale/v6/models"
)
func RuleKey(datasourceId, id int64) string {
return fmt.Sprintf("alert-%d-%d", datasourceId, id)
}
func MatchTags(eventTagsMap map[string]string, itags []models.TagFilter) bool {
for _, filter := range itags {
value, has := eventTagsMap[filter.Key]
if !has {
return false
}
if !matchTag(value, filter) {
return false
}
}
return true
}
func matchTag(value string, filter models.TagFilter) bool {
switch filter.Func {
case "==":
return filter.Value == value
case "!=":
return filter.Value != value
case "in":
_, has := filter.Vset[value]
return has
case "not in":
_, has := filter.Vset[value]
return !has
case "=~":
return filter.Regexp.MatchString(value)
case "!~":
return !filter.Regexp.MatchString(value)
}
// unexpect func
return false
}

103
alert/dispatch/consume.go Normal file
View File

@@ -0,0 +1,103 @@
package dispatch
import (
"fmt"
"time"
"github.com/ccfos/nightingale/v6/alert/aconf"
"github.com/ccfos/nightingale/v6/alert/queue"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/poster"
"github.com/toolkits/pkg/concurrent/semaphore"
"github.com/toolkits/pkg/logger"
)
type Consumer struct {
alerting aconf.Alerting
ctx *ctx.Context
dispatch *Dispatch
}
// 创建一个 Consumer 实例
func NewConsumer(alerting aconf.Alerting, ctx *ctx.Context, dispatch *Dispatch) *Consumer {
return &Consumer{
alerting: alerting,
ctx: ctx,
dispatch: dispatch,
}
}
func (e *Consumer) LoopConsume() {
sema := semaphore.NewSemaphore(e.alerting.NotifyConcurrency)
duration := time.Duration(100) * time.Millisecond
for {
events := queue.EventQueue.PopBackBy(100)
if len(events) == 0 {
time.Sleep(duration)
continue
}
e.consume(events, sema)
}
}
func (e *Consumer) consume(events []interface{}, sema *semaphore.Semaphore) {
for i := range events {
if events[i] == nil {
continue
}
event := events[i].(*models.AlertCurEvent)
sema.Acquire()
go func(event *models.AlertCurEvent) {
defer sema.Release()
e.consumeOne(event)
}(event)
}
}
func (e *Consumer) consumeOne(event *models.AlertCurEvent) {
LogEvent(event, "consume")
if err := event.ParseRule("rule_name"); err != nil {
event.RuleName = fmt.Sprintf("failed to parse rule name: %v", err)
}
if err := event.ParseRule("rule_note"); err != nil {
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)
}
e.persist(event)
if event.IsRecovered && event.NotifyRecovered == 0 {
return
}
e.dispatch.HandleEventNotify(event, false)
}
func (e *Consumer) persist(event *models.AlertCurEvent) {
if event.Status != 0 {
return
}
if !e.ctx.IsCenter {
event.DB2FE()
err := poster.PostByUrls(e.ctx, "/v1/n9e/event-persist", event)
if err != nil {
logger.Errorf("event%+v persist err:%v", event, err)
}
return
}
err := models.EventPersist(e.ctx, event)
if err != nil {
logger.Errorf("event%+v persist err:%v", event, err)
}
}

289
alert/dispatch/dispatch.go Normal file
View File

@@ -0,0 +1,289 @@
package dispatch
import (
"bytes"
"encoding/json"
"html/template"
"strconv"
"sync"
"time"
"github.com/ccfos/nightingale/v6/alert/aconf"
"github.com/ccfos/nightingale/v6/alert/common"
"github.com/ccfos/nightingale/v6/alert/sender"
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/toolkits/pkg/logger"
)
type Dispatch struct {
alertRuleCache *memsto.AlertRuleCacheType
userCache *memsto.UserCacheType
userGroupCache *memsto.UserGroupCacheType
alertSubscribeCache *memsto.AlertSubscribeCacheType
targetCache *memsto.TargetCacheType
notifyConfigCache *memsto.NotifyConfigCacheType
alerting aconf.Alerting
Senders map[string]sender.Sender
tpls map[string]*template.Template
ExtraSenders map[string]sender.Sender
BeforeSenderHook func(*models.AlertCurEvent) bool
ctx *ctx.Context
RwLock sync.RWMutex
}
// 创建一个 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 {
notify := &Dispatch{
alertRuleCache: alertRuleCache,
userCache: userCache,
userGroupCache: userGroupCache,
alertSubscribeCache: alertSubscribeCache,
targetCache: targetCache,
notifyConfigCache: notifyConfigCache,
alerting: alerting,
Senders: make(map[string]sender.Sender),
tpls: make(map[string]*template.Template),
ExtraSenders: make(map[string]sender.Sender),
BeforeSenderHook: func(*models.AlertCurEvent) bool { return true },
ctx: ctx,
}
return notify
}
func (e *Dispatch) ReloadTpls() error {
err := e.relaodTpls()
if err != nil {
logger.Errorf("failed to reload tpls: %v", err)
}
duration := time.Duration(9000) * time.Millisecond
for {
time.Sleep(duration)
if err := e.relaodTpls(); err != nil {
logger.Warning("failed to reload tpls:", err)
}
}
}
func (e *Dispatch) relaodTpls() error {
tmpTpls, err := models.ListTpls(e.ctx)
if err != nil {
return err
}
smtp := e.notifyConfigCache.GetSMTP()
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),
}
e.RwLock.RLock()
for channel, sender := range e.ExtraSenders {
senders[channel] = sender
}
e.RwLock.RUnlock()
e.RwLock.Lock()
e.tpls = tmpTpls
e.Senders = senders
e.RwLock.Unlock()
return nil
}
// HandleEventNotify 处理event事件的主逻辑
// event: 告警/恢复事件
// isSubscribe: 告警事件是否由subscribe的配置产生
func (e *Dispatch) HandleEventNotify(event *models.AlertCurEvent, isSubscribe bool) {
rule := e.alertRuleCache.Get(event.RuleId)
if rule == nil {
return
}
fillUsers(event, e.userCache, e.userGroupCache)
var (
// 处理事件到 notifyTarget 关系,处理的notifyTarget用OrMerge进行合并
handlers []NotifyTargetDispatch
// 额外去掉一些订阅,处理的notifyTarget用AndMerge进行合并, 如设置 channel=false,合并后不通过这个channel发送
// 如果实现了相关 Dispatch,可以添加到interceptors中
interceptorHandlers []NotifyTargetDispatch
)
if isSubscribe {
handlers = []NotifyTargetDispatch{NotifyGroupDispatch, EventCallbacksDispatch}
} else {
handlers = []NotifyTargetDispatch{NotifyGroupDispatch, GlobalWebhookDispatch, EventCallbacksDispatch}
}
notifyTarget := NewNotifyTarget()
// 处理订阅关系使用OrMerge
for _, handler := range handlers {
notifyTarget.OrMerge(handler(rule, event, notifyTarget, e))
}
// 处理移除订阅关系的逻辑,比如员工离职,临时静默某个通道的策略等
for _, handler := range interceptorHandlers {
notifyTarget.AndMerge(handler(rule, event, notifyTarget, e))
}
// 处理事件发送,这里用一个goroutine处理一个event的所有发送事件
go e.Send(rule, event, notifyTarget)
// 如果是不是订阅规则出现的event, 则需要处理订阅规则的event
if !isSubscribe {
e.handleSubs(event)
}
}
func (e *Dispatch) handleSubs(event *models.AlertCurEvent) {
// handle alert subscribes
subscribes := make([]*models.AlertSubscribe, 0)
// rule specific subscribes
if subs, has := e.alertSubscribeCache.Get(event.RuleId); has {
subscribes = append(subscribes, subs...)
}
// global subscribes
if subs, has := e.alertSubscribeCache.Get(0); has {
subscribes = append(subscribes, subs...)
}
for _, sub := range subscribes {
e.handleSub(sub, *event)
}
}
// handleSub 处理订阅规则的event,注意这里event要使用值传递,因为后面会修改event的状态
func (e *Dispatch) handleSub(sub *models.AlertSubscribe, event models.AlertCurEvent) {
if sub.IsDisabled() || !sub.MatchCluster(event.DatasourceId) {
return
}
if !common.MatchTags(event.TagsMap, sub.ITags) {
return
}
if sub.ForDuration > (event.TriggerTime - event.FirstTriggerTime) {
return
}
if len(sub.SeveritiesJson) != 0 {
match := false
for _, s := range sub.SeveritiesJson {
if s == event.Severity || s == 0 {
match = true
break
}
}
if !match {
return
}
}
sub.ModifyEvent(&event)
LogEvent(&event, "subscribe")
event.SubRuleId = sub.Id
e.HandleEventNotify(&event, true)
}
func (e *Dispatch) Send(rule *models.AlertRule, event *models.AlertCurEvent, notifyTarget *NotifyTarget) {
needSend := e.BeforeSenderHook(event)
if needSend {
for channel, uids := range notifyTarget.ToChannelUserMap() {
ctx := sender.BuildMessageContext(rule, []*models.AlertCurEvent{event}, uids, e.userCache)
e.RwLock.RLock()
s := e.Senders[channel]
e.RwLock.RUnlock()
if s == nil {
logger.Debugf("no sender for channel: %s", channel)
continue
}
s.Send(ctx)
}
}
// handle event callbacks
sender.SendCallbacks(e.ctx, notifyTarget.ToCallbackList(), event, e.targetCache, e.userCache, e.notifyConfigCache.GetIbex())
// handle global webhooks
sender.SendWebhooks(notifyTarget.ToWebhookList(), event)
// handle plugin call
go sender.MayPluginNotify(e.genNoticeBytes(event), e.notifyConfigCache.GetNotifyScript())
}
type Notice struct {
Event *models.AlertCurEvent `json:"event"`
Tpls map[string]string `json:"tpls"`
}
func (e *Dispatch) genNoticeBytes(event *models.AlertCurEvent) []byte {
// build notice body with templates
ntpls := make(map[string]string)
e.RwLock.RLock()
defer e.RwLock.RUnlock()
for filename, tpl := range e.tpls {
var body bytes.Buffer
if err := tpl.Execute(&body, event); err != nil {
ntpls[filename] = err.Error()
} else {
ntpls[filename] = body.String()
}
}
notice := Notice{Event: event, Tpls: ntpls}
stdinBytes, err := json.Marshal(notice)
if err != nil {
logger.Errorf("event_notify: failed to marshal notice: %v", err)
return nil
}
return stdinBytes
}
// for alerting
func fillUsers(ce *models.AlertCurEvent, uc *memsto.UserCacheType, ugc *memsto.UserGroupCacheType) {
gids := make([]int64, 0, len(ce.NotifyGroupsJSON))
for i := 0; i < len(ce.NotifyGroupsJSON); i++ {
gid, err := strconv.ParseInt(ce.NotifyGroupsJSON[i], 10, 64)
if err != nil {
continue
}
gids = append(gids, gid)
}
ce.NotifyGroupsObj = ugc.GetByUserGroupIds(gids)
uids := make(map[int64]struct{})
for i := 0; i < len(ce.NotifyGroupsObj); i++ {
ug := ce.NotifyGroupsObj[i]
for j := 0; j < len(ug.UserIds); j++ {
uids[ug.UserIds[j]] = struct{}{}
}
}
ce.NotifyUsersObj = uc.GetByUserIds(mapKeys(uids))
}
func mapKeys(m map[int64]struct{}) []int64 {
lst := make([]int64, 0, len(m))
for k := range m {
lst = append(lst, k)
}
return lst
}

32
alert/dispatch/log.go Normal file
View File

@@ -0,0 +1,32 @@
package dispatch
import (
"github.com/ccfos/nightingale/v6/models"
"github.com/toolkits/pkg/logger"
)
func LogEvent(event *models.AlertCurEvent, location string, err ...error) {
status := "triggered"
if event.IsRecovered {
status = "recovered"
}
message := ""
if len(err) > 0 && err[0] != nil {
message = "error_message: " + err[0].Error()
}
logger.Infof(
"event(%s %s) %s: rule_id=%d cluster:%s %v%s@%d %s",
event.Hash,
status,
location,
event.RuleId,
event.Cluster,
event.TagsJSON,
event.TriggerValue,
event.TriggerTime,
message,
)
}

View File

@@ -0,0 +1,33 @@
package dispatch
// NotifyChannels channelKey -> bool
type NotifyChannels map[string]bool
func NewNotifyChannels(channels []string) NotifyChannels {
nc := make(NotifyChannels)
for _, ch := range channels {
nc[ch] = true
}
return nc
}
func (nc NotifyChannels) OrMerge(other NotifyChannels) {
nc.merge(other, func(a, b bool) bool { return a || b })
}
func (nc NotifyChannels) AndMerge(other NotifyChannels) {
nc.merge(other, func(a, b bool) bool { return a && b })
}
func (nc NotifyChannels) merge(other NotifyChannels, f func(bool, bool) bool) {
if other == nil {
return
}
for k, v := range other {
if curV, has := nc[k]; has {
nc[k] = f(curV, v)
} else {
nc[k] = v
}
}
}

View File

@@ -0,0 +1,134 @@
package dispatch
import (
"strconv"
"github.com/ccfos/nightingale/v6/models"
)
// NotifyTarget 维护所有需要发送的目标 用户-通道/回调/钩子信息,用map维护的数据结构具有去重功能
type NotifyTarget struct {
userMap map[int64]NotifyChannels
webhooks map[string]*models.Webhook
callbacks map[string]struct{}
}
func NewNotifyTarget() *NotifyTarget {
return &NotifyTarget{
userMap: make(map[int64]NotifyChannels),
webhooks: make(map[string]*models.Webhook),
callbacks: make(map[string]struct{}),
}
}
// OrMerge 将 channelMap 按照 or 的方式合并,方便实现多种组合的策略,比如根据某个 tag 进行路由等
func (s *NotifyTarget) OrMerge(other *NotifyTarget) {
s.merge(other, NotifyChannels.OrMerge)
}
// AndMerge 将 channelMap 中的 bool 值按照 and 的逻辑进行合并,可以单独将人/通道维度的通知移除
// 常用的场景有:
// 1. 人员离职了不需要发送告警了
// 2. 某个告警通道进行维护,暂时不需要发送告警了
// 3. 业务值班的重定向逻辑,将高等级的告警额外发送给应急人员等
// 可以结合业务需求自己实现router
func (s *NotifyTarget) AndMerge(other *NotifyTarget) {
s.merge(other, NotifyChannels.AndMerge)
}
func (s *NotifyTarget) merge(other *NotifyTarget, f func(NotifyChannels, NotifyChannels)) {
if other == nil {
return
}
for k, v := range other.userMap {
if curV, has := s.userMap[k]; has {
f(curV, v)
} else {
s.userMap[k] = v
}
}
for k, v := range other.webhooks {
s.webhooks[k] = v
}
for k, v := range other.callbacks {
s.callbacks[k] = v
}
}
// ToChannelUserMap userMap(map[uid][channel]bool) 转换为 map[channel][]uid 的结构
func (s *NotifyTarget) ToChannelUserMap() map[string][]int64 {
m := make(map[string][]int64)
for uid, nc := range s.userMap {
for ch, send := range nc {
if send {
m[ch] = append(m[ch], uid)
}
}
}
return m
}
func (s *NotifyTarget) ToCallbackList() []string {
callbacks := make([]string, 0, len(s.callbacks))
for cb := range s.callbacks {
callbacks = append(callbacks, cb)
}
return callbacks
}
func (s *NotifyTarget) ToWebhookList() []*models.Webhook {
webhooks := make([]*models.Webhook, 0, len(s.webhooks))
for _, wh := range s.webhooks {
webhooks = append(webhooks, wh)
}
return webhooks
}
// Dispatch 抽象由告警事件到信息接收者的路由策略
// rule: 告警规则
// event: 告警事件
// prev: 前一次路由结果, Dispatch 的实现可以直接修改 prev, 也可以返回一个新的 NotifyTarget 用于 AndMerge/OrMerge
type NotifyTargetDispatch func(rule *models.AlertRule, event *models.AlertCurEvent, prev *NotifyTarget, dispatch *Dispatch) *NotifyTarget
// GroupDispatch 处理告警规则的组订阅关系
func NotifyGroupDispatch(rule *models.AlertRule, event *models.AlertCurEvent, prev *NotifyTarget, dispatch *Dispatch) *NotifyTarget {
groupIds := make([]int64, 0, len(event.NotifyGroupsJSON))
for _, groupId := range event.NotifyGroupsJSON {
gid, err := strconv.ParseInt(groupId, 10, 64)
if err != nil {
continue
}
groupIds = append(groupIds, gid)
}
groups := dispatch.userGroupCache.GetByUserGroupIds(groupIds)
NotifyTarget := NewNotifyTarget()
for _, group := range groups {
for _, userId := range group.UserIds {
NotifyTarget.userMap[userId] = NewNotifyChannels(event.NotifyChannelsJSON)
}
}
return NotifyTarget
}
func GlobalWebhookDispatch(rule *models.AlertRule, event *models.AlertCurEvent, prev *NotifyTarget, dispatch *Dispatch) *NotifyTarget {
webhooks := dispatch.notifyConfigCache.GetWebhooks()
NotifyTarget := NewNotifyTarget()
for _, webhook := range webhooks {
if !webhook.Enable {
continue
}
NotifyTarget.webhooks[webhook.Url] = webhook
}
return NotifyTarget
}
func EventCallbacksDispatch(rule *models.AlertRule, event *models.AlertCurEvent, prev *NotifyTarget, dispatch *Dispatch) *NotifyTarget {
for _, c := range event.CallbacksJSON {
if c == "" {
continue
}
prev.callbacks[c] = struct{}{}
}
return nil
}

172
alert/eval/alert_rule.go Normal file
View File

@@ -0,0 +1,172 @@
package eval
import (
"context"
"fmt"
"time"
"github.com/ccfos/nightingale/v6/alert/aconf"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/alert/naming"
"github.com/ccfos/nightingale/v6/alert/process"
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/prom"
"github.com/toolkits/pkg/logger"
)
type Scheduler struct {
// key: hash
alertRules map[string]*AlertRuleWorker
ExternalProcessors *process.ExternalProcessorsType
aconf aconf.Alert
alertRuleCache *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
}
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 {
scheduler := &Scheduler{
aconf: aconf,
alertRules: make(map[string]*AlertRuleWorker),
ExternalProcessors: externalProcessors,
alertRuleCache: arc,
targetCache: targetCache,
busiGroupCache: busiGroupCache,
alertMuteCache: alertMuteCache,
datasourceCache: datasourceCache,
promClients: promClients,
naming: naming,
ctx: ctx,
stats: stats,
}
go scheduler.LoopSyncRules(context.Background())
return scheduler
}
func (s *Scheduler) LoopSyncRules(ctx context.Context) {
time.Sleep(time.Duration(s.aconf.EngineDelay) * time.Second)
duration := 9000 * time.Millisecond
for {
select {
case <-ctx.Done():
return
case <-time.After(duration):
s.syncAlertRules()
}
}
}
func (s *Scheduler) syncAlertRules() {
ids := s.alertRuleCache.GetRuleIds()
alertRuleWorkers := make(map[string]*AlertRuleWorker)
externalRuleWorkers := make(map[string]*process.Processor)
for _, id := range ids {
rule := s.alertRuleCache.Get(id)
if rule == nil {
continue
}
if rule.IsPrometheusRule() {
datasourceIds := s.promClients.Hit(rule.DatasourceIdsJson)
for _, dsId := range datasourceIds {
if !naming.DatasourceHashRing.IsHit(dsId, fmt.Sprintf("%d", rule.Id), s.aconf.Heartbeat.Endpoint) {
continue
}
ds := s.datasourceCache.GetById(dsId)
if ds == nil {
logger.Debugf("datasource %d not found", dsId)
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)
alertRule := NewAlertRuleWorker(rule, dsId, processor, s.promClients, s.ctx)
alertRuleWorkers[alertRule.Hash()] = alertRule
}
} else if rule.IsHostRule() && s.ctx.IsCenter {
// all host rule will be processed by center instance
if !naming.DatasourceHashRing.IsHit(naming.HostDatasource, fmt.Sprintf("%d", rule.Id), 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)
alertRuleWorkers[alertRule.Hash()] = alertRule
} else {
// 如果 rule 不是通过 prometheus engine 来告警的,则创建为 externalRule
// if rule is not processed by prometheus engine, create it as externalRule
for _, dsId := range rule.DatasourceIdsJson {
ds := s.datasourceCache.GetById(dsId)
if ds == nil {
logger.Debugf("datasource %d not found", dsId)
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)
externalRuleWorkers[processor.Key()] = processor
}
}
}
for hash, rule := range alertRuleWorkers {
if _, has := s.alertRules[hash]; !has {
rule.Prepare()
rule.Start()
s.alertRules[hash] = rule
}
}
for hash, rule := range s.alertRules {
if _, has := alertRuleWorkers[hash]; !has {
rule.Stop()
delete(s.alertRules, hash)
}
}
s.ExternalProcessors.ExternalLock.Lock()
for key, processor := range externalRuleWorkers {
if curProcessor, has := s.ExternalProcessors.Processors[key]; has {
// rule存在,且hash一致,认为没有变更,这里可以根据需求单独实现一个关联数据更多的hash函数
if processor.Hash() == curProcessor.Hash() {
continue
}
}
// 现有规则中没有rule以及有rule但hash不一致的场景需要触发rule的update
processor.RecoverAlertCurEventFromDb()
s.ExternalProcessors.Processors[key] = processor
}
for key := range s.ExternalProcessors.Processors {
if _, has := externalRuleWorkers[key]; !has {
delete(s.ExternalProcessors.Processors, key)
}
}
s.ExternalProcessors.ExternalLock.Unlock()
}

271
alert/eval/eval.go Normal file
View File

@@ -0,0 +1,271 @@
package eval
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/ccfos/nightingale/v6/alert/common"
"github.com/ccfos/nightingale/v6/alert/process"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
promsdk "github.com/ccfos/nightingale/v6/pkg/prom"
"github.com/ccfos/nightingale/v6/prom"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/str"
)
type AlertRuleWorker struct {
datasourceId int64
quit chan struct{}
inhibit bool
severity int
rule *models.AlertRule
processor *process.Processor
promClients *prom.PromClientMap
ctx *ctx.Context
}
func NewAlertRuleWorker(rule *models.AlertRule, datasourceId int64, processor *process.Processor, promClients *prom.PromClientMap, ctx *ctx.Context) *AlertRuleWorker {
arw := &AlertRuleWorker{
datasourceId: datasourceId,
quit: make(chan struct{}),
rule: rule,
processor: processor,
promClients: promClients,
ctx: ctx,
}
return arw
}
func (arw *AlertRuleWorker) Key() string {
return common.RuleKey(arw.datasourceId, arw.rule.Id)
}
func (arw *AlertRuleWorker) Hash() string {
return str.MD5(fmt.Sprintf("%d_%d_%s_%d",
arw.rule.Id,
arw.rule.PromEvalInterval,
arw.rule.RuleConfig,
arw.datasourceId,
))
}
func (arw *AlertRuleWorker) Prepare() {
arw.processor.RecoverAlertCurEventFromDb()
}
func (arw *AlertRuleWorker) Start() {
logger.Infof("eval:%s started", arw.Key())
interval := arw.rule.PromEvalInterval
if interval <= 0 {
interval = 10
}
ticker := time.NewTicker(time.Duration(interval) * time.Second)
go func() {
defer ticker.Stop()
for {
select {
case <-arw.quit:
return
case <-ticker.C:
arw.Eval()
}
}
}()
}
func (arw *AlertRuleWorker) Eval() {
cachedRule := arw.rule
if cachedRule == nil {
//logger.Errorf("rule_eval:%s rule not found", arw.Key())
return
}
typ := cachedRule.GetRuleType()
var lst []common.AnomalyPoint
switch typ {
case models.PROMETHEUS:
lst = arw.GetPromAnomalyPoint(cachedRule.RuleConfig)
case models.HOST:
lst = arw.GetHostAnomalyPoint(cachedRule.RuleConfig)
default:
return
}
if arw.processor == nil {
logger.Warningf("rule_eval:%s processor is nil", arw.Key())
return
}
arw.processor.Handle(lst, "inner", arw.inhibit)
}
func (arw *AlertRuleWorker) Stop() {
logger.Infof("rule_eval %s stopped", arw.Key())
close(arw.quit)
}
func (arw *AlertRuleWorker) GetPromAnomalyPoint(ruleConfig string) []common.AnomalyPoint {
var lst []common.AnomalyPoint
var severity int
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)
return lst
}
if rule == nil {
logger.Errorf("rule_eval:%s rule_config:%s, error:rule is nil", arw.Key(), ruleConfig)
return lst
}
arw.inhibit = rule.Inhibit
for _, query := range rule.Queries {
if query.Severity < severity {
arw.severity = query.Severity
}
promql := strings.TrimSpace(query.PromQl)
if promql == "" {
logger.Errorf("rule_eval:%s promql is blank", arw.Key())
continue
}
if arw.promClients.IsNil(arw.datasourceId) {
logger.Errorf("rule_eval:%s error reader client is nil", arw.Key())
continue
}
readerClient := arw.promClients.GetCli(arw.datasourceId)
var warnings promsdk.Warnings
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)
continue
}
if len(warnings) > 0 {
logger.Errorf("rule_eval:%s promql:%s, warnings:%v", arw.Key(), promql, warnings)
continue
}
logger.Debugf("rule_eval:%s query:%+v, value:%v", arw.Key(), query, value)
points := common.ConvertAnomalyPoints(value)
for i := 0; i < len(points); i++ {
points[i].Severity = query.Severity
points[i].Query = promql
}
lst = append(lst, points...)
}
return lst
}
func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) []common.AnomalyPoint {
var lst []common.AnomalyPoint
var severity int
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)
return lst
}
if rule == nil {
logger.Errorf("rule_eval:%s rule_config:%s, error:rule is nil", arw.Key(), ruleConfig)
return lst
}
arw.inhibit = rule.Inhibit
now := time.Now().Unix()
for _, trigger := range rule.Triggers {
if trigger.Severity < severity {
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)
continue
}
for _, target := range targets {
m := make(map[string]string)
target.FillTagsMap()
for k, v := range target.TagsMap {
m[k] = v
}
m["ident"] = target.Ident
bg := arw.processor.BusiGroupCache.GetByBusiGroupId(target.GroupId)
if bg != nil && bg.LabelEnable == 1 {
m["busigroup"] = bg.LabelValue
}
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)
continue
}
var 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 {
m := make(map[string]string)
target, exists := targetMap[host]
if exists {
target.FillTagsMap()
for k, v := range target.TagsMap {
m[k] = v
}
}
m["ident"] = host
bg := arw.processor.BusiGroupCache.GetByBusiGroupId(target.GroupId)
if bg != nil && bg.LabelEnable == 1 {
m["busigroup"] = bg.LabelValue
}
lst = append(lst, common.NewAnomalyPoint(trigger.Type, m, now, float64(offset), trigger.Severity))
}
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)
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
}
pct := float64(count) / float64(total) * 100
if pct >= float64(trigger.Percent) {
lst = append(lst, common.NewAnomalyPoint(trigger.Type, nil, now, pct, trigger.Severity))
}
}
}
return lst
}

213
alert/mute/mute.go Normal file
View File

@@ -0,0 +1,213 @@
package mute
import (
"strconv"
"strings"
"time"
"github.com/ccfos/nightingale/v6/alert/common"
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/models"
"github.com/toolkits/pkg/logger"
)
func IsMuted(rule *models.AlertRule, event *models.AlertCurEvent, targetCache *memsto.TargetCacheType, alertMuteCache *memsto.AlertMuteCacheType) bool {
if rule.Disabled == 1 {
return true
}
if TimeSpanMuteStrategy(rule, event) {
return true
}
if IdentNotExistsMuteStrategy(rule, event, targetCache) {
return true
}
if BgNotMatchMuteStrategy(rule, event, targetCache) {
return true
}
if EventMuteStrategy(event, alertMuteCache) {
return true
}
return false
}
// TimeSpanMuteStrategy 根据规则配置的告警生效时间段过滤,如果产生的告警不在规则配置的告警生效时间段内,则不告警,即被mute
// 时间范围左闭右开默认范围00:00-24:00
func TimeSpanMuteStrategy(rule *models.AlertRule, event *models.AlertCurEvent) bool {
tm := time.Unix(event.TriggerTime, 0)
triggerTime := tm.Format("15:04")
triggerWeek := strconv.Itoa(int(tm.Weekday()))
enableStime := strings.Fields(rule.EnableStime)
enableEtime := strings.Fields(rule.EnableEtime)
enableDaysOfWeek := strings.Split(rule.EnableDaysOfWeek, ";")
length := len(enableDaysOfWeek)
// enableStime,enableEtime,enableDaysOfWeek三者长度肯定相同这里循环一个即可
for i := 0; i < length; i++ {
enableDaysOfWeek[i] = strings.Replace(enableDaysOfWeek[i], "7", "0", 1)
if !strings.Contains(enableDaysOfWeek[i], triggerWeek) {
continue
}
if enableStime[i] < enableEtime[i] {
if enableEtime[i] == "23:59" {
// 02:00-23:59这种情况做个特殊处理相当于左闭右闭区间了
if triggerTime < enableStime[i] {
// mute, 即没生效
continue
}
} else {
// 02:00-04:00 或者 02:00-24:00
if triggerTime < enableStime[i] || triggerTime >= enableEtime[i] {
// mute, 即没生效
continue
}
}
} else if enableStime[i] > enableEtime[i] {
// 21:00-09:00
if triggerTime < enableStime[i] && triggerTime >= enableEtime[i] {
// mute, 即没生效
continue
}
}
// 到这里说明当前时刻在告警规则的某组生效时间范围内,即没有 mute直接返回 false
return false
}
return true
}
// IdentNotExistsMuteStrategy 根据ident是否存在过滤,如果ident不存在,则target_up的告警直接过滤掉
func IdentNotExistsMuteStrategy(rule *models.AlertRule, event *models.AlertCurEvent, targetCache *memsto.TargetCacheType) bool {
ident, has := event.TagsMap["ident"]
if !has {
return false
}
_, exists := targetCache.Get(ident)
// 如果是target_up的告警,且ident已经不存在了,直接过滤掉
// 这里的判断有点太粗暴了,但是目前没有更好的办法
if !exists && strings.Contains(rule.PromQl, "target_up") {
logger.Debugf("[%s] mute: rule_eval:%d cluster:%s ident:%s", "IdentNotExistsMuteStrategy", rule.Id, event.Cluster, ident)
return true
}
return false
}
// BgNotMatchMuteStrategy 当规则开启只在bg内部告警时,对于非bg内部的机器过滤
func BgNotMatchMuteStrategy(rule *models.AlertRule, event *models.AlertCurEvent, targetCache *memsto.TargetCacheType) bool {
// 没有开启BG内部告警,直接不过滤
if rule.EnableInBG == 0 {
return false
}
ident, has := event.TagsMap["ident"]
if !has {
return false
}
target, exists := targetCache.Get(ident)
// 对于包含ident的告警事件check一下ident所属bg和rule所属bg是否相同
// 如果告警规则选择了只在本BG生效那其他BG的机器就不能因此规则产生告警
if exists && target.GroupId != rule.GroupId {
logger.Debugf("[%s] mute: rule_eval:%d cluster:%s", "BgNotMatchMuteStrategy", rule.Id, event.Cluster)
return true
}
return false
}
func EventMuteStrategy(event *models.AlertCurEvent, alertMuteCache *memsto.AlertMuteCacheType) bool {
mutes, has := alertMuteCache.Gets(event.GroupId)
if !has || len(mutes) == 0 {
return false
}
for i := 0; i < len(mutes); i++ {
if matchMute(event, mutes[i]) {
return true
}
}
return false
}
// matchMute 如果传入了clock这个可选参数就表示使用这个clock表示的时间否则就从event的字段中取TriggerTime
func matchMute(event *models.AlertCurEvent, mute *models.AlertMute, clock ...int64) bool {
if mute.Disabled == 1 {
return false
}
ts := event.TriggerTime
if len(clock) > 0 {
ts = clock[0]
}
// 如果不是全局的,判断 匹配的 datasource id
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{}{}
}
// 判断 event.datasourceId 是否包含在 idm 中
if _, has := idm[event.DatasourceId]; !has {
return false
}
}
var matchTime bool
if mute.MuteTimeType == models.TimeRange {
if ts < mute.Btime || ts > mute.Etime {
return false
}
matchTime = true
} else if mute.MuteTimeType == models.Periodic {
tm := time.Unix(event.TriggerTime, 0)
triggerTime := tm.Format("15:04")
triggerWeek := strconv.Itoa(int(tm.Weekday()))
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 {
matchTime = true
break
} else if mute.PeriodicMutesJson[i].EnableStime < mute.PeriodicMutesJson[i].EnableEtime {
if triggerTime >= mute.PeriodicMutesJson[i].EnableStime && triggerTime < mute.PeriodicMutesJson[i].EnableEtime {
matchTime = true
break
}
} else {
if triggerTime >= mute.PeriodicMutesJson[i].EnableStime || triggerTime < mute.PeriodicMutesJson[i].EnableEtime {
matchTime = true
break
}
}
}
}
}
if !matchTime {
return false
}
var matchSeverity bool
if len(mute.SeveritiesJson) > 0 {
for _, s := range mute.SeveritiesJson {
if event.Severity == s || s == 0 {
matchSeverity = true
break
}
}
} else {
matchSeverity = true
}
if !matchSeverity {
return false
}
return common.MatchTags(event.TagsMap, mute.ITags)
}

79
alert/naming/hashring.go Normal file
View File

@@ -0,0 +1,79 @@
package naming
import (
"errors"
"sync"
"github.com/toolkits/pkg/consistent"
"github.com/toolkits/pkg/logger"
)
const NodeReplicas = 500
type DatasourceHashRingType struct {
sync.RWMutex
Rings map[int64]*consistent.Consistent
}
// for alert_rule sharding
var HostDatasource int64 = 99999999
var DatasourceHashRing = DatasourceHashRingType{Rings: make(map[int64]*consistent.Consistent)}
func NewConsistentHashRing(replicas int32, nodes []string) *consistent.Consistent {
ret := consistent.New()
ret.NumberOfReplicas = int(replicas)
for i := 0; i < len(nodes); i++ {
ret.Add(nodes[i])
}
return ret
}
func RebuildConsistentHashRing(datasourceId int64, nodes []string) {
r := consistent.New()
r.NumberOfReplicas = NodeReplicas
for i := 0; i < len(nodes); i++ {
r.Add(nodes[i])
}
DatasourceHashRing.Set(datasourceId, r)
logger.Infof("hash ring %d rebuild %+v", datasourceId, r.Members())
}
func (chr *DatasourceHashRingType) GetNode(datasourceId int64, pk string) (string, error) {
chr.Lock()
defer chr.Unlock()
_, exists := chr.Rings[datasourceId]
if !exists {
chr.Rings[datasourceId] = NewConsistentHashRing(int32(NodeReplicas), []string{})
}
return chr.Rings[datasourceId].Get(pk)
}
func (chr *DatasourceHashRingType) IsHit(datasourceId int64, 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)
}
return false
}
return node == currentNode
}
func (chr *DatasourceHashRingType) Set(datasourceId int64, r *consistent.Consistent) {
chr.Lock()
defer chr.Unlock()
chr.Rings[datasourceId] = r
}
func (chr *DatasourceHashRingType) Clear() {
chr.Lock()
defer chr.Unlock()
for id := range chr.Rings {
if id == HostDatasource {
continue
}
delete(chr.Rings, id)
}
}

179
alert/naming/heartbeat.go Normal file
View File

@@ -0,0 +1,179 @@
package naming
import (
"fmt"
"sort"
"strings"
"time"
"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/poster"
"github.com/toolkits/pkg/logger"
)
type Naming struct {
ctx *ctx.Context
heartbeatConfig aconf.HeartbeatConfig
}
func NewNaming(ctx *ctx.Context, heartbeat aconf.HeartbeatConfig) *Naming {
naming := &Naming{
ctx: ctx,
heartbeatConfig: heartbeat,
}
naming.Heartbeats()
return naming
}
// local servers
var localss map[int64]string
func (n *Naming) Heartbeats() error {
localss = make(map[int64]string)
if err := n.heartbeat(); err != nil {
fmt.Println("failed to heartbeat:", err)
return err
}
go n.loopHeartbeat()
go n.loopDeleteInactiveInstances()
return nil
}
func (n *Naming) loopDeleteInactiveInstances() {
if !n.ctx.IsCenter {
return
}
interval := time.Duration(10) * time.Minute
for {
time.Sleep(interval)
n.DeleteInactiveInstances()
}
}
func (n *Naming) DeleteInactiveInstances() {
err := models.DB(n.ctx).Where("clock < ?", time.Now().Unix()-600).Delete(new(models.AlertingEngines)).Error
if err != nil {
logger.Errorf("delete inactive instances err:%v", err)
}
}
func (n *Naming) loopHeartbeat() {
interval := time.Duration(n.heartbeatConfig.Interval) * time.Millisecond
for {
time.Sleep(interval)
if err := n.heartbeat(); err != nil {
logger.Warning(err)
}
}
}
func (n *Naming) heartbeat() error {
var datasourceIds []int64
var err error
// 在页面上维护实例和集群的对应关系
datasourceIds, err = models.GetDatasourceIdsByEngineName(n.ctx, n.heartbeatConfig.EngineName)
if err != nil {
return err
}
if len(datasourceIds) == 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)
}
} else {
for i := 0; i < len(datasourceIds); i++ {
err := models.AlertingEngineHeartbeatWithCluster(n.ctx, n.heartbeatConfig.Endpoint, n.heartbeatConfig.EngineName, datasourceIds[i])
if err != nil {
logger.Warningf("heartbeat with cluster %d err:%v", datasourceIds[i], err)
}
}
}
if len(datasourceIds) == 0 {
DatasourceHashRing.Clear()
for dsId := range localss {
if dsId == HostDatasource {
continue
}
delete(localss, dsId)
}
}
for i := 0; i < len(datasourceIds); i++ {
servers, err := n.ActiveServers(datasourceIds[i])
if err != nil {
logger.Warningf("hearbeat %d get active server err:%v", datasourceIds[i], err)
continue
}
sort.Strings(servers)
newss := strings.Join(servers, " ")
oldss, exists := localss[datasourceIds[i]]
if exists && oldss == newss {
continue
}
RebuildConsistentHashRing(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)
}
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
}
return nil
}
func (n *Naming) ActiveServers(datasourceId int64) ([]string, error) {
if datasourceId == -1 {
return nil, fmt.Errorf("cluster is empty")
}
if !n.ctx.IsCenter {
lst, err := poster.GetByUrls[[]string](n.ctx, "/v1/n9e/servers-active?dsid="+fmt.Sprintf("%d", datasourceId))
return lst, err
}
// 30秒内有心跳就认为是活的
return models.AlertingEngineGetsInstances(n.ctx, "datasource_id = ? and clock > ?", datasourceId, time.Now().Unix()-30)
}
func (n *Naming) ActiveServersByEngineName() ([]string, error) {
if !n.ctx.IsCenter {
lst, err := poster.GetByUrls[[]string](n.ctx, "/v1/n9e/servers-active?engine_name="+n.heartbeatConfig.EngineName)
return lst, err
}
// 30秒内有心跳就认为是活的
return models.AlertingEngineGetsInstances(n.ctx, "engine_cluster = ? and clock > ?", n.heartbeatConfig.EngineName, time.Now().Unix()-30)
}

View File

@@ -0,0 +1,74 @@
package process
import (
"sync"
"github.com/ccfos/nightingale/v6/models"
)
type AlertCurEventMap struct {
sync.RWMutex
Data map[string]*models.AlertCurEvent
}
func NewAlertCurEventMap(data map[string]*models.AlertCurEvent) *AlertCurEventMap {
if data == nil {
return &AlertCurEventMap{
Data: make(map[string]*models.AlertCurEvent),
}
}
return &AlertCurEventMap{
Data: data,
}
}
func (a *AlertCurEventMap) SetAll(data map[string]*models.AlertCurEvent) {
a.Lock()
defer a.Unlock()
a.Data = data
}
func (a *AlertCurEventMap) Set(key string, value *models.AlertCurEvent) {
a.Lock()
defer a.Unlock()
a.Data[key] = value
}
func (a *AlertCurEventMap) Get(key string) (*models.AlertCurEvent, bool) {
a.RLock()
defer a.RUnlock()
event, exists := a.Data[key]
return event, exists
}
func (a *AlertCurEventMap) UpdateLastEvalTime(key string, lastEvalTime int64) {
a.Lock()
defer a.Unlock()
event, exists := a.Data[key]
if !exists {
return
}
event.LastEvalTime = lastEvalTime
}
func (a *AlertCurEventMap) Delete(key string) {
a.Lock()
defer a.Unlock()
delete(a.Data, key)
}
func (a *AlertCurEventMap) Keys() []string {
a.RLock()
defer a.RUnlock()
keys := make([]string, 0, len(a.Data))
for k := range a.Data {
keys = append(keys, k)
}
return keys
}
func (a *AlertCurEventMap) GetAll() map[string]*models.AlertCurEvent {
a.RLock()
defer a.RUnlock()
return a.Data
}

463
alert/process/process.go Normal file
View File

@@ -0,0 +1,463 @@
package process
import (
"bytes"
"fmt"
"html/template"
"sort"
"strings"
"sync"
"time"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/alert/common"
"github.com/ccfos/nightingale/v6/alert/dispatch"
"github.com/ccfos/nightingale/v6/alert/mute"
"github.com/ccfos/nightingale/v6/alert/queue"
"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/tplx"
"github.com/ccfos/nightingale/v6/prom"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/str"
)
type EventMuteHookFunc func(event *models.AlertCurEvent) bool
type ExternalProcessorsType struct {
ExternalLock sync.RWMutex
Processors map[string]*Processor
}
var ExternalProcessors ExternalProcessorsType
func NewExternalProcessors() *ExternalProcessorsType {
return &ExternalProcessorsType{
Processors: make(map[string]*Processor),
}
}
func (e *ExternalProcessorsType) GetExternalAlertRule(datasourceId, id int64) (*Processor, bool) {
e.ExternalLock.RLock()
defer e.ExternalLock.RUnlock()
processor, has := e.Processors[common.RuleKey(datasourceId, id)]
return processor, has
}
type HandleEventFunc func(event *models.AlertCurEvent)
type Processor struct {
datasourceId int64
rule *models.AlertRule
fires *AlertCurEventMap
pendings *AlertCurEventMap
inhibit bool
tagsMap map[string]string
tagsArr []string
target string
targetNote string
groupName string
atertRuleCache *memsto.AlertRuleCacheType
TargetCache *memsto.TargetCacheType
BusiGroupCache *memsto.BusiGroupCacheType
alertMuteCache *memsto.AlertMuteCacheType
datasourceCache *memsto.DatasourceCacheType
promClients *prom.PromClientMap
ctx *ctx.Context
stats *astats.Stats
HandleFireEventHook HandleEventFunc
HandleRecoverEventHook HandleEventFunc
EventMuteHook EventMuteHookFunc
}
func (p *Processor) Key() string {
return common.RuleKey(p.datasourceId, p.rule.Id)
}
func (p *Processor) DatasourceId() int64 {
return p.datasourceId
}
func (p *Processor) Hash() string {
return str.MD5(fmt.Sprintf("%d_%d_%s_%d",
p.rule.Id,
p.rule.PromEvalInterval,
p.rule.RuleConfig,
p.datasourceId,
))
}
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,
stats *astats.Stats) *Processor {
p := &Processor{
datasourceId: datasourceId,
rule: rule,
TargetCache: targetCache,
BusiGroupCache: busiGroupCache,
alertMuteCache: alertMuteCache,
atertRuleCache: atertRuleCache,
datasourceCache: datasourceCache,
promClients: promClients,
ctx: ctx,
stats: stats,
HandleFireEventHook: func(event *models.AlertCurEvent) {},
HandleRecoverEventHook: func(event *models.AlertCurEvent) {},
EventMuteHook: func(event *models.AlertCurEvent) bool { return false },
}
p.mayHandleGroup()
return p
}
func (p *Processor) Handle(anomalyPoints []common.AnomalyPoint, from string, inhibit bool) {
// 有可能rule的一些配置已经发生变化比如告警接收人、callbacks等
// 这些信息的修改是不会引起worker restart的但是确实会影响告警处理逻辑
// 所以这里直接从memsto.AlertRuleCache中获取并覆盖
p.inhibit = inhibit
cachedRule := p.atertRuleCache.Get(p.rule.Id)
if cachedRule == nil {
logger.Errorf("rule not found %+v", anomalyPoints)
return
}
p.rule = cachedRule
now := time.Now().Unix()
alertingKeys := map[string]struct{}{}
// 根据 event 的 tag 将 events 分组,处理告警抑制的情况
eventsMap := make(map[string][]*models.AlertCurEvent)
for _, anomalyPoint := range anomalyPoints {
event := p.BuildEvent(anomalyPoint, from, now)
// 如果 event 被 mute 了,本质也是 fire 的状态,这里无论如何都添加到 alertingKeys 中,防止 fire 的事件自动恢复了
hash := event.Hash
alertingKeys[hash] = struct{}{}
if mute.IsMuted(cachedRule, event, p.TargetCache, p.alertMuteCache) {
logger.Debugf("rule_eval:%s event:%v is muted", p.Key(), event)
continue
}
if p.EventMuteHook(event) {
continue
}
tagHash := TagHash(anomalyPoint)
eventsMap[tagHash] = append(eventsMap[tagHash], event)
}
for _, events := range eventsMap {
p.handleEvent(events)
}
p.HandleRecover(alertingKeys, now)
}
func (p *Processor) BuildEvent(anomalyPoint common.AnomalyPoint, from string, now int64) *models.AlertCurEvent {
p.fillTags(anomalyPoint)
p.mayHandleIdent()
hash := Hash(p.rule.Id, p.datasourceId, anomalyPoint)
ds := p.datasourceCache.GetById(p.datasourceId)
var dsName string
if ds != nil {
dsName = ds.Name
}
event := p.rule.GenerateNewEvent(p.ctx)
event.TriggerTime = anomalyPoint.Timestamp
event.TagsMap = p.tagsMap
event.DatasourceId = p.datasourceId
event.Cluster = dsName
event.Hash = hash
event.TargetIdent = p.target
event.TargetNote = p.targetNote
event.TriggerValue = anomalyPoint.ReadableValue()
event.TagsJSON = p.tagsArr
event.GroupName = p.groupName
event.Tags = strings.Join(p.tagsArr, ",,")
event.IsRecovered = false
event.Callbacks = p.rule.Callbacks
event.CallbacksJSON = p.rule.CallbacksJSON
event.Annotations = p.rule.Annotations
event.AnnotationsJSON = make(map[string]string)
event.RuleConfig = p.rule.RuleConfig
event.RuleConfigJson = p.rule.RuleConfigJson
event.Severity = anomalyPoint.Severity
event.ExtraConfig = p.rule.ExtraConfigJSON
event.PromQl = anomalyPoint.Query
if from == "inner" {
event.LastEvalTime = now
} else {
event.LastEvalTime = event.TriggerTime
}
return event
}
func (p *Processor) HandleRecover(alertingKeys map[string]struct{}, now int64) {
for _, hash := range p.pendings.Keys() {
if _, has := alertingKeys[hash]; has {
continue
}
p.pendings.Delete(hash)
}
for hash := range p.fires.GetAll() {
if _, has := alertingKeys[hash]; has {
continue
}
p.RecoverSingle(hash, now, nil)
}
}
func (p *Processor) RecoverSingle(hash string, now int64, value *string) {
cachedRule := p.rule
if cachedRule == nil {
return
}
event, has := p.fires.Get(hash)
if !has {
return
}
// 如果配置了留观时长,就不能立马恢复了
if cachedRule.RecoverDuration > 0 && now-event.LastEvalTime < cachedRule.RecoverDuration {
logger.Debugf("rule_eval:%s event:%v not recover", p.Key(), event)
return
}
if value != nil {
event.TriggerValue = *value
}
// 没查到触发阈值的vector姑且就认为这个vector的值恢复了
// 我确实无法分辨是prom中有值但是未满足阈值所以没返回还是prom中确实丢了一些点导致没有数据可以返回尴尬
p.fires.Delete(hash)
p.pendings.Delete(hash)
// 可能是因为调整了promql才恢复的所以事件里边要体现最新的promql否则用户会比较困惑
// 当然其实rule的各个字段都可能发生变化了都更新一下吧
cachedRule.UpdateEvent(event)
event.IsRecovered = true
event.LastEvalTime = now
p.HandleRecoverEventHook(event)
p.pushEventToQueue(event)
}
func (p *Processor) handleEvent(events []*models.AlertCurEvent) {
var fireEvents []*models.AlertCurEvent
// severity 初始为 4, 一定为遇到比自己优先级高的事件
severity := 4
for _, event := range events {
if event == nil {
continue
}
if p.rule.PromForDuration == 0 {
fireEvents = append(fireEvents, event)
if severity > event.Severity {
severity = event.Severity
}
continue
}
var preTriggerTime int64
preEvent, has := p.pendings.Get(event.Hash)
if has {
p.pendings.UpdateLastEvalTime(event.Hash, event.LastEvalTime)
preTriggerTime = preEvent.TriggerTime
} else {
p.pendings.Set(event.Hash, event)
preTriggerTime = event.TriggerTime
}
if event.LastEvalTime-preTriggerTime+int64(event.PromEvalInterval) >= int64(p.rule.PromForDuration) {
fireEvents = append(fireEvents, event)
if severity > event.Severity {
severity = event.Severity
}
continue
}
}
p.inhibitEvent(fireEvents, severity)
}
func (p *Processor) inhibitEvent(events []*models.AlertCurEvent, highSeverity int) {
for _, event := range events {
if p.inhibit && event.Severity > highSeverity {
logger.Debugf("rule_eval:%s event:%+v inhibit highSeverity:%d", p.Key(), event, highSeverity)
continue
}
p.fireEvent(event)
}
}
func (p *Processor) fireEvent(event *models.AlertCurEvent) {
// As p.rule maybe outdated, use rule from cache
cachedRule := p.rule
if cachedRule == nil {
return
}
logger.Debugf("rule_eval:%s event:%+v fire", p.Key(), event)
if fired, has := p.fires.Get(event.Hash); has {
p.fires.UpdateLastEvalTime(event.Hash, event.LastEvalTime)
event.FirstTriggerTime = fired.FirstTriggerTime
p.HandleFireEventHook(event)
if cachedRule.NotifyRepeatStep == 0 {
logger.Debugf("rule_eval:%s event:%+v repeat is zero nothing to do", p.Key(), event)
// 说明不想重复通知那就直接返回了nothing to do
// do not need to send alert again
return
}
// 之前发送过告警了,这次是否要继续发送,要看是否过了通道静默时间
if event.LastEvalTime >= fired.LastSentTime+int64(cachedRule.NotifyRepeatStep)*60 {
if cachedRule.NotifyMaxNumber == 0 {
// 最大可以发送次数如果是0表示不想限制最大发送次数一直发即可
event.NotifyCurNumber = fired.NotifyCurNumber + 1
p.pushEventToQueue(event)
} else {
// 有最大发送次数的限制,就要看已经发了几次了,是否达到了最大发送次数
if fired.NotifyCurNumber >= cachedRule.NotifyMaxNumber {
logger.Debugf("rule_eval:%s event:%+v reach max number", p.Key(), event)
return
} else {
event.NotifyCurNumber = fired.NotifyCurNumber + 1
p.pushEventToQueue(event)
}
}
}
} else {
event.NotifyCurNumber = 1
event.FirstTriggerTime = event.TriggerTime
p.HandleFireEventHook(event)
p.pushEventToQueue(event)
}
}
func (p *Processor) pushEventToQueue(e *models.AlertCurEvent) {
if !e.IsRecovered {
e.LastSentTime = e.LastEvalTime
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)
}
}
func (p *Processor) RecoverAlertCurEventFromDb() {
p.pendings = NewAlertCurEventMap(nil)
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.fires = NewAlertCurEventMap(nil)
return
}
fireMap := make(map[string]*models.AlertCurEvent)
for _, event := range curEvents {
event.DB2Mem()
fireMap[event.Hash] = event
}
p.fires = NewAlertCurEventMap(fireMap)
}
func (p *Processor) fillTags(anomalyPoint common.AnomalyPoint) {
// handle series tags
tagsMap := make(map[string]string)
for label, value := range anomalyPoint.Labels {
tagsMap[string(label)] = string(value)
}
var e = &models.AlertCurEvent{
TagsMap: tagsMap,
}
// handle rule tags
for _, tag := range p.rule.AppendTagsJSON {
arr := strings.SplitN(tag, "=", 2)
var defs = []string{
"{{$labels := .TagsMap}}",
"{{$value := .TriggerValue}}",
}
tagValue := arr[1]
text := strings.Join(append(defs, tagValue), "")
t, err := template.New(fmt.Sprint(p.rule.Id)).Funcs(template.FuncMap(tplx.TemplateFuncMap)).Parse(text)
if err != nil {
tagValue = fmt.Sprintf("parse tag value failed, err:%s", err)
tagsMap[arr[0]] = tagValue
continue
}
var body bytes.Buffer
err = t.Execute(&body, e)
if err != nil {
tagValue = fmt.Sprintf("parse tag value failed, err:%s", err)
tagsMap[arr[0]] = tagValue
continue
}
tagsMap[arr[0]] = body.String()
}
tagsMap["rulename"] = p.rule.Name
p.tagsMap = tagsMap
// handle tagsArr
p.tagsArr = labelMapToArr(tagsMap)
}
func (p *Processor) mayHandleIdent() {
// handle ident
if ident, has := p.tagsMap["ident"]; has {
if target, exists := p.TargetCache.Get(ident); exists {
p.target = target.Ident
p.targetNote = target.Note
}
}
}
func (p *Processor) mayHandleGroup() {
// handle bg
bg := p.BusiGroupCache.GetByBusiGroupId(p.rule.GroupId)
if bg != nil {
p.groupName = bg.Name
}
}
func labelMapToArr(m map[string]string) []string {
numLabels := len(m)
labelStrings := make([]string, 0, numLabels)
for label, value := range m {
labelStrings = append(labelStrings, fmt.Sprintf("%s=%s", label, value))
}
if numLabels > 1 {
sort.Strings(labelStrings)
}
return labelStrings
}
func Hash(ruleId, datasourceId int64, vector common.AnomalyPoint) string {
return str.MD5(fmt.Sprintf("%d_%s_%d_%d_%s", ruleId, vector.Labels.String(), datasourceId, vector.Severity, vector.Query))
}
func TagHash(vector common.AnomalyPoint) string {
return str.MD5(vector.Labels.String())
}

18
alert/queue/queue.go Normal file
View File

@@ -0,0 +1,18 @@
package queue
import (
"time"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/toolkits/pkg/container/list"
)
var EventQueue = list.NewSafeListLimited(10000000)
func ReportQueueSize(stats *astats.Stats) {
for {
time.Sleep(time.Second)
stats.GaugeAlertQueueSize.Set(float64(EventQueue.Len()))
}
}

104
alert/record/prom_rule.go Normal file
View File

@@ -0,0 +1,104 @@
package record
import (
"context"
"fmt"
"strings"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/prom"
"github.com/ccfos/nightingale/v6/pushgw/writer"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/str"
)
type RecordRuleContext struct {
datasourceId int64
quit chan struct{}
rule *models.RecordingRule
// writers *writer.WritersType
promClients *prom.PromClientMap
}
func NewRecordRuleContext(rule *models.RecordingRule, datasourceId int64, promClients *prom.PromClientMap, writers *writer.WritersType) *RecordRuleContext {
return &RecordRuleContext{
datasourceId: datasourceId,
quit: make(chan struct{}),
rule: rule,
promClients: promClients,
//writers: writers,
}
}
func (rrc *RecordRuleContext) Key() string {
return fmt.Sprintf("record-%d-%d", rrc.datasourceId, rrc.rule.Id)
}
func (rrc *RecordRuleContext) Hash() string {
return str.MD5(fmt.Sprintf("%d_%d_%s_%d",
rrc.rule.Id,
rrc.rule.PromEvalInterval,
rrc.rule.PromQl,
rrc.datasourceId,
))
}
func (rrc *RecordRuleContext) Prepare() {}
func (rrc *RecordRuleContext) Start() {
logger.Infof("eval:%s started", rrc.Key())
interval := rrc.rule.PromEvalInterval
if interval <= 0 {
interval = 10
}
ticker := time.NewTicker(time.Duration(interval) * time.Second)
go func() {
defer ticker.Stop()
for {
select {
case <-rrc.quit:
return
case <-ticker.C:
rrc.Eval()
}
}
}()
}
func (rrc *RecordRuleContext) Eval() {
promql := strings.TrimSpace(rrc.rule.PromQl)
if promql == "" {
logger.Errorf("eval:%s promql is blank", rrc.Key())
return
}
if rrc.promClients.IsNil(rrc.datasourceId) {
logger.Errorf("eval:%s reader client is nil", rrc.Key())
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)
return
}
if len(warnings) > 0 {
logger.Errorf("eval:%s promql:%s, warnings:%v", rrc.Key(), promql, warnings)
return
}
ts := ConvertToTimeSeries(value, rrc.rule)
if len(ts) != 0 {
rrc.promClients.GetWriterCli(rrc.datasourceId).Write(ts)
}
}
func (rrc *RecordRuleContext) Stop() {
logger.Infof("%s stopped", rrc.Key())
close(rrc.quit)
}

122
alert/record/sample.go Normal file
View File

@@ -0,0 +1,122 @@
package record
import (
"math"
"strings"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/prompb"
)
const (
LabelName = "__name__"
)
func ConvertToTimeSeries(value model.Value, rule *models.RecordingRule) (lst []*prompb.TimeSeries) {
switch value.Type() {
case model.ValVector:
items, ok := value.(model.Vector)
if !ok {
return
}
for _, item := range items {
if math.IsNaN(float64(item.Value)) {
continue
}
s := prompb.Sample{}
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{
Labels: l,
Samples: []prompb.Sample{s},
})
}
case model.ValMatrix:
items, ok := value.(model.Matrix)
if !ok {
return
}
for _, item := range items {
if len(item.Values) == 0 {
return
}
last := item.Values[len(item.Values)-1]
if math.IsNaN(float64(last.Value)) {
continue
}
l := labelsToLabelsProto(item.Metric, rule)
var slst []prompb.Sample
for _, v := range item.Values {
if math.IsNaN(float64(v.Value)) {
continue
}
slst = append(slst, prompb.Sample{
Timestamp: time.Unix(v.Timestamp.Unix(), 0).UnixNano() / 1e6,
Value: float64(v.Value),
})
}
lst = append(lst, &prompb.TimeSeries{
Labels: l,
Samples: slst,
})
}
case model.ValScalar:
item, ok := value.(*model.Scalar)
if !ok {
return
}
if math.IsNaN(float64(item.Value)) {
return
}
lst = append(lst, &prompb.TimeSeries{
Labels: nil,
Samples: []prompb.Sample{{Value: float64(item.Value), Timestamp: time.Unix(item.Timestamp.Unix(), 0).UnixNano() / 1e6}},
})
default:
return
}
return
}
func labelsToLabelsProto(labels model.Metric, rule *models.RecordingRule) (result []*prompb.Label) {
//name
nameLs := &prompb.Label{
Name: LabelName,
Value: rule.Name,
}
result = append(result, nameLs)
for k, v := range labels {
if k == LabelName {
continue
}
if model.LabelNameRE.MatchString(string(k)) {
result = append(result, &prompb.Label{
Name: string(k),
Value: string(v),
})
}
}
if len(rule.AppendTagsJSON) != 0 {
for _, v := range rule.AppendTagsJSON {
index := strings.Index(v, "=")
if model.LabelNameRE.MatchString(v[:index]) {
result = append(result, &prompb.Label{
Name: v[:index],
Value: v[index+1:],
})
}
}
}
return result
}

94
alert/record/scheduler.go Normal file
View File

@@ -0,0 +1,94 @@
package record
import (
"context"
"fmt"
"time"
"github.com/ccfos/nightingale/v6/alert/aconf"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/alert/naming"
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/prom"
"github.com/ccfos/nightingale/v6/pushgw/writer"
)
type Scheduler struct {
// key: hash
recordRules map[string]*RecordRuleContext
aconf aconf.Alert
recordingRuleCache *memsto.RecordingRuleCacheType
promClients *prom.PromClientMap
writers *writer.WritersType
stats *astats.Stats
}
func NewScheduler(aconf aconf.Alert, rrc *memsto.RecordingRuleCacheType, promClients *prom.PromClientMap, writers *writer.WritersType, stats *astats.Stats) *Scheduler {
scheduler := &Scheduler{
aconf: aconf,
recordRules: make(map[string]*RecordRuleContext),
recordingRuleCache: rrc,
promClients: promClients,
writers: writers,
stats: stats,
}
go scheduler.LoopSyncRules(context.Background())
return scheduler
}
func (s *Scheduler) LoopSyncRules(ctx context.Context) {
time.Sleep(time.Duration(s.aconf.EngineDelay) * time.Second)
duration := 9000 * time.Millisecond
for {
select {
case <-ctx.Done():
return
case <-time.After(duration):
s.syncRecordRules()
}
}
}
func (s *Scheduler) syncRecordRules() {
ids := s.recordingRuleCache.GetRuleIds()
recordRules := make(map[string]*RecordRuleContext)
for _, id := range ids {
rule := s.recordingRuleCache.Get(id)
if rule == nil {
continue
}
datasourceIds := s.promClients.Hit(rule.DatasourceIdsJson)
for _, dsId := range datasourceIds {
if !naming.DatasourceHashRing.IsHit(dsId, fmt.Sprintf("%d", rule.Id), s.aconf.Heartbeat.Endpoint) {
continue
}
recordRule := NewRecordRuleContext(rule, dsId, s.promClients, s.writers)
recordRules[recordRule.Hash()] = recordRule
}
}
for hash, rule := range recordRules {
if _, has := s.recordRules[hash]; !has {
rule.Prepare()
rule.Start()
s.recordRules[hash] = rule
}
}
for hash, rule := range s.recordRules {
if _, has := recordRules[hash]; !has {
rule.Stop()
delete(s.recordRules, hash)
}
}
}

79
alert/router/router.go Normal file
View File

@@ -0,0 +1,79 @@
package router
import (
"net/http"
"github.com/ccfos/nightingale/v6/alert/aconf"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/alert/process"
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/httpx"
"github.com/gin-gonic/gin"
)
type Router struct {
HTTP httpx.Config
Alert aconf.Alert
AlertMuteCache *memsto.AlertMuteCacheType
TargetCache *memsto.TargetCacheType
BusiGroupCache *memsto.BusiGroupCacheType
AlertStats *astats.Stats
Ctx *ctx.Context
ExternalProcessors *process.ExternalProcessorsType
}
func New(httpConfig httpx.Config, alert aconf.Alert, amc *memsto.AlertMuteCacheType, tc *memsto.TargetCacheType, bgc *memsto.BusiGroupCacheType,
astats *astats.Stats, ctx *ctx.Context, externalProcessors *process.ExternalProcessorsType) *Router {
return &Router{
HTTP: httpConfig,
Alert: alert,
AlertMuteCache: amc,
TargetCache: tc,
BusiGroupCache: bgc,
AlertStats: astats,
Ctx: ctx,
ExternalProcessors: externalProcessors,
}
}
func (rt *Router) Config(r *gin.Engine) {
if !rt.HTTP.APIForService.Enable {
return
}
service := r.Group("/v1/n9e")
if len(rt.HTTP.APIForService.BasicAuth) > 0 {
service.Use(gin.BasicAuth(rt.HTTP.APIForService.BasicAuth))
}
service.POST("/event", rt.pushEventToQueue)
service.POST("/event-persist", rt.eventPersist)
service.POST("/make-event", rt.makeEvent)
}
func Render(c *gin.Context, data, msg interface{}) {
if msg == nil {
if data == nil {
data = struct{}{}
}
c.JSON(http.StatusOK, gin.H{"data": data, "error": ""})
} else {
c.JSON(http.StatusOK, gin.H{"error": gin.H{"message": msg}})
}
}
func Dangerous(c *gin.Context, v interface{}, code ...int) {
if v == nil {
return
}
switch t := v.(type) {
case string:
if t != "" {
c.JSON(http.StatusOK, gin.H{"error": gin.H{"message": v}})
}
case error:
c.JSON(http.StatusOK, gin.H{"error": gin.H{"message": t.Error()}})
}
}

View File

@@ -0,0 +1,148 @@
package router
import (
"fmt"
"strings"
"time"
"github.com/ccfos/nightingale/v6/alert/common"
"github.com/ccfos/nightingale/v6/alert/dispatch"
"github.com/ccfos/nightingale/v6/alert/mute"
"github.com/ccfos/nightingale/v6/alert/naming"
"github.com/ccfos/nightingale/v6/alert/process"
"github.com/ccfos/nightingale/v6/alert/queue"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/poster"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)
func (rt *Router) pushEventToQueue(c *gin.Context) {
var event *models.AlertCurEvent
ginx.BindJSON(c, &event)
if event.RuleId == 0 {
ginx.Bomb(200, "event is illegal")
}
event.TagsMap = make(map[string]string)
for i := 0; i < len(event.TagsJSON); i++ {
pair := strings.TrimSpace(event.TagsJSON[i])
if pair == "" {
continue
}
arr := strings.Split(pair, "=")
if len(arr) != 2 {
continue
}
event.TagsMap[arr[0]] = arr[1]
}
if mute.EventMuteStrategy(event, rt.AlertMuteCache) {
logger.Infof("event_muted: rule_id=%d %s", event.RuleId, event.Hash)
ginx.NewRender(c).Message(nil)
return
}
if err := event.ParseRule("rule_name"); err != nil {
event.RuleName = fmt.Sprintf("failed to parse rule name: %v", err)
}
if err := event.ParseRule("rule_note"); err != nil {
event.RuleNote = fmt.Sprintf("failed to parse rule note: %v", err)
}
if err := event.ParseRule("annotations"); err != nil {
event.RuleNote = fmt.Sprintf("failed to parse rule note: %v", err)
}
// 如果 rule_note 中有 ; 前缀,则使用 rule_note 替换 tags 中的内容
if strings.HasPrefix(event.RuleNote, ";") {
event.RuleNote = strings.TrimPrefix(event.RuleNote, ";")
event.Tags = strings.ReplaceAll(event.RuleNote, " ", ",,")
event.TagsJSON = strings.Split(event.Tags, ",,")
} else {
event.Tags = strings.Join(event.TagsJSON, ",,")
}
event.Callbacks = strings.Join(event.CallbacksJSON, " ")
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)
ginx.Bomb(200, msg)
logger.Warningf(msg)
}
ginx.NewRender(c).Message(nil)
}
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))
}
type eventForm struct {
Alert bool `json:"alert"`
AnomalyPoints []common.AnomalyPoint `json:"vectors"`
RuleId int64 `json:"rule_id"`
DatasourceId int64 `json:"datasource_id"`
Inhibit bool `json:"inhibit"`
}
func (rt *Router) makeEvent(c *gin.Context) {
var events []*eventForm
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))
if err != nil {
logger.Warningf("event:%+v get node err:%v", events[i], err)
ginx.Bomb(200, "event node not exists")
}
if node != rt.Alert.Heartbeat.Endpoint {
err := forwardEvent(events[i], node)
if err != nil {
logger.Warningf("event:%+v forward err:%v", events[i], err)
ginx.Bomb(200, "event forward error")
}
continue
}
ruleWorker, exists := rt.ExternalProcessors.GetExternalAlertRule(events[i].DatasourceId, events[i].RuleId)
logger.Debugf("handle event:%+v exists:%v", events[i], exists)
if !exists {
ginx.Bomb(200, "rule not exists")
}
if events[i].Alert {
go ruleWorker.Handle(events[i].AnomalyPoints, "http", events[i].Inhibit)
} else {
for _, vector := range events[i].AnomalyPoints {
readableString := vector.ReadableValue()
go ruleWorker.RecoverSingle(process.Hash(events[i].RuleId, events[i].DatasourceId, vector), vector.Timestamp, &readableString)
}
}
}
ginx.NewRender(c).Message(nil)
}
// event 不归本实例处理,转发给对应的实例
func forwardEvent(event *eventForm, instance string) error {
ur := fmt.Sprintf("http://%s/v1/n9e/make-event", instance)
res, code, err := poster.PostJSON(ur, time.Second*5, []*eventForm{event}, 3)
if err != nil {
return err
}
logger.Infof("forward event: result=succ url=%s code=%d event:%v response=%s", ur, code, event, string(res))
return nil
}

218
alert/sender/callback.go Normal file
View File

@@ -0,0 +1,218 @@
package sender
import (
"encoding/json"
"strconv"
"strings"
"time"
"github.com/ccfos/nightingale/v6/alert/aconf"
"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"
"github.com/toolkits/pkg/logger"
)
func SendCallbacks(ctx *ctx.Context, urls []string, event *models.AlertCurEvent, targetCache *memsto.TargetCacheType, userCache *memsto.UserCacheType, ibexConf aconf.Ibex) {
for _, url := range urls {
if url == "" {
continue
}
if strings.HasPrefix(url, "${ibex}") {
if !event.IsRecovered {
handleIbex(ctx, url, event, targetCache, userCache, ibexConf)
}
continue
}
if !(strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://")) {
url = "http://" + url
}
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)
} 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) {
arr := strings.Split(url, "/")
var idstr string
var host string
if len(arr) > 1 {
idstr = arr[1]
}
if len(arr) > 2 {
host = arr[2]
}
id, err := strconv.ParseInt(idstr, 10, 64)
if err != nil {
logger.Errorf("event_callback_ibex: failed to parse url: %s", url)
return
}
if host == "" {
// 用户在callback url中没有传入host就从event中解析
host = event.TargetIdent
}
if host == "" {
logger.Error("event_callback_ibex: failed to get host")
return
}
tpl, err := models.TaskTplGet(ctx, "id = ?", id)
if err != nil {
logger.Errorf("event_callback_ibex: failed to get tpl: %v", err)
return
}
if tpl == nil {
logger.Errorf("event_callback_ibex: no such tpl(%d)", id)
return
}
// check perm
// tpl.GroupId - host - account 三元组校验权限
can, err := canDoIbex(ctx, tpl.UpdateBy, tpl, host, targetCache, userCache)
if err != nil {
logger.Errorf("event_callback_ibex: check perm fail: %v", err)
return
}
if !can {
logger.Errorf("event_callback_ibex: user(%s) no permission", tpl.UpdateBy)
return
}
tagsMap := make(map[string]string)
for i := 0; i < len(event.TagsJSON); i++ {
pair := strings.TrimSpace(event.TagsJSON[i])
if pair == "" {
continue
}
arr := strings.Split(pair, "=")
if len(arr) != 2 {
continue
}
tagsMap[arr[0]] = arr[1]
}
// 附加告警级别 告警触发值标签
tagsMap["alert_severity"] = strconv.Itoa(event.Severity)
tagsMap["alert_trigger_value"] = event.TriggerValue
tags, err := json.Marshal(tagsMap)
if err != nil {
logger.Errorf("event_callback_ibex: failed to marshal tags to json: %v", tagsMap)
return
}
// 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},
}
var res TaskCreateReply
err = ibex.New(
ibexConf.Address,
ibexConf.BasicAuthUser,
ibexConf.BasicAuthPass,
ibexConf.Timeout,
).
Path("/ibex/v1/tasks").
In(in).
Out(&res).
POST()
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,
}
if err = record.Add(ctx); err != nil {
logger.Errorf("event_callback_ibex: persist task_record fail: %v", err)
}
}
func canDoIbex(ctx *ctx.Context, 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
}
target, has := targetCache.Get(host)
if !has {
return false, nil
}
return target.GroupId == tpl.GroupId, nil
}

100
alert/sender/dingtalk.go Normal file
View File

@@ -0,0 +1,100 @@
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 dingtalkMarkdown struct {
Title string `json:"title"`
Text string `json:"text"`
}
type dingtalkAt struct {
AtMobiles []string `json:"atMobiles"`
IsAtAll bool `json:"isAtAll"`
}
type dingtalk struct {
Msgtype string `json:"msgtype"`
Markdown dingtalkMarkdown `json:"markdown"`
At dingtalkAt `json:"at"`
}
type DingtalkSender struct {
tpl *template.Template
}
func (ds *DingtalkSender) Send(ctx MessageContext) {
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
return
}
urls, ats := ds.extract(ctx.Users)
if len(urls) == 0 {
return
}
message := BuildTplMessage(ds.tpl, ctx.Events)
for _, url := range urls {
var body dingtalk
// NoAt in url
if strings.Contains(url, "noat=1") {
body = dingtalk{
Msgtype: "markdown",
Markdown: dingtalkMarkdown{
Title: ctx.Events[0].RuleName,
Text: message,
},
}
} else {
body = dingtalk{
Msgtype: "markdown",
Markdown: dingtalkMarkdown{
Title: ctx.Events[0].RuleName,
Text: message + "\n" + strings.Join(ats, " "),
},
At: dingtalkAt{
AtMobiles: ats,
IsAtAll: false,
},
}
}
ds.doSend(url, body)
}
}
// extract urls and ats from Users
func (ds *DingtalkSender) extract(users []*models.User) ([]string, []string) {
urls := make([]string, 0, len(users))
ats := make([]string, 0, len(users))
for _, user := range users {
if user.Phone != "" {
ats = append(ats, "@"+user.Phone)
}
if token, has := user.ExtractToken(models.Dingtalk); has {
url := token
if !strings.HasPrefix(token, "https://") {
url = "https://oapi.dingtalk.com/robot/send?access_token=" + token
}
urls = append(urls, url)
}
}
return urls, ats
}
func (ds *DingtalkSender) doSend(url string, body dingtalk) {
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))
} else {
logger.Infof("dingtalk_sender: result=succ url=%s code=%d response=%s", url, code, string(res))
}
}

173
alert/sender/email.go Normal file
View File

@@ -0,0 +1,173 @@
package sender
import (
"crypto/tls"
"html/template"
"time"
"github.com/ccfos/nightingale/v6/alert/aconf"
"github.com/ccfos/nightingale/v6/models"
"github.com/toolkits/pkg/logger"
"gopkg.in/gomail.v2"
)
var mailch chan *gomail.Message
type EmailSender struct {
subjectTpl *template.Template
contentTpl *template.Template
smtp aconf.SMTPConfig
}
func (es *EmailSender) Send(ctx MessageContext) {
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
return
}
tos := extract(ctx.Users)
var subject string
if es.subjectTpl != nil {
subject = BuildTplMessage(es.subjectTpl, []*models.AlertCurEvent{ctx.Events[0]})
} else {
subject = ctx.Events[0].RuleName
}
content := BuildTplMessage(es.contentTpl, ctx.Events)
es.WriteEmail(subject, content, tos)
}
func extract(users []*models.User) []string {
tos := make([]string, 0, len(users))
for _, u := range users {
if u.Email != "" {
tos = append(tos, u.Email)
}
}
return tos
}
func (es *EmailSender) SendEmail(subject, content string, tos []string, stmp aconf.SMTPConfig) {
conf := stmp
d := gomail.NewDialer(conf.Host, conf.Port, conf.User, conf.Pass)
if conf.InsecureSkipVerify {
d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
}
m := gomail.NewMessage()
m.SetHeader("From", stmp.From)
m.SetHeader("To", tos...)
m.SetHeader("Subject", subject)
m.SetBody("text/html", content)
err := d.DialAndSend(m)
if err != nil {
logger.Errorf("email_sender: failed to send: %v", err)
}
}
func (es *EmailSender) WriteEmail(subject, content string, tos []string) {
m := gomail.NewMessage()
m.SetHeader("From", es.smtp.From)
m.SetHeader("To", tos...)
m.SetHeader("Subject", subject)
m.SetBody("text/html", content)
mailch <- m
}
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)
time.Sleep(time.Second)
continue
} else {
return s
}
}
}
var mailQuit = make(chan struct{})
func RestartEmailSender(smtp aconf.SMTPConfig) {
close(mailQuit)
mailQuit = make(chan struct{})
StartEmailSender(smtp)
}
func StartEmailSender(smtp aconf.SMTPConfig) {
mailch = make(chan *gomail.Message, 100000)
conf := smtp
if conf.Host == "" || conf.Port == 0 {
logger.Warning("SMTP configurations invalid")
return
}
logger.Infof("start email sender... %+v", conf)
d := gomail.NewDialer(conf.Host, conf.Port, conf.User, conf.Pass)
if conf.InsecureSkipVerify {
d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
}
var s gomail.SendCloser
var open bool
var size int
for {
select {
case <-mailQuit:
return
case m, ok := <-mailch:
if !ok {
return
}
if !open {
s = dialSmtp(d)
open = true
}
if err := gomail.Send(s, m); err != nil {
logger.Errorf("email_sender: failed to send: %s", err)
// close and retry
if err := s.Close(); err != nil {
logger.Warningf("email_sender: failed to close smtp connection: %s", err)
}
s = dialSmtp(d)
open = true
if err := gomail.Send(s, m); err != nil {
logger.Errorf("email_sender: failed to retry send: %s", err)
}
} else {
logger.Infof("email_sender: result=succ subject=%v to=%v", m.GetHeader("Subject"), m.GetHeader("To"))
}
size++
if size >= conf.Batch {
if err := s.Close(); err != nil {
logger.Warningf("email_sender: failed to close smtp connection: %s", err)
}
open = false
size = 0
}
// Close the connection to the SMTP server if no email was sent in
// the last 30 seconds.
case <-time.After(30 * time.Second):
if open {
if err := s.Close(); err != nil {
logger.Warningf("email_sender: failed to close smtp connection: %s", err)
}
open = false
}
}
}
}

82
alert/sender/feishu.go Normal file
View File

@@ -0,0 +1,82 @@
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 {
Text string `json:"text"`
}
type feishuAt struct {
AtMobiles []string `json:"atMobiles"`
IsAtAll bool `json:"isAtAll"`
}
type feishu struct {
Msgtype string `json:"msg_type"`
Content feishuContent `json:"content"`
At feishuAt `json:"at"`
}
type FeishuSender struct {
tpl *template.Template
}
func (fs *FeishuSender) Send(ctx MessageContext) {
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
return
}
urls, ats := fs.extract(ctx.Users)
message := BuildTplMessage(fs.tpl, ctx.Events)
for _, url := range urls {
body := feishu{
Msgtype: "text",
Content: feishuContent{
Text: message,
},
}
if !strings.Contains(url, "noat=1") {
body.At = feishuAt{
AtMobiles: ats,
IsAtAll: false,
}
}
fs.doSend(url, body)
}
}
func (fs *FeishuSender) extract(users []*models.User) ([]string, []string) {
urls := make([]string, 0, len(users))
ats := make([]string, 0, len(users))
for _, user := range users {
if user.Phone != "" {
ats = append(ats, user.Phone)
}
if token, has := user.ExtractToken(models.Feishu); has {
url := token
if !strings.HasPrefix(token, "https://") {
url = "https://open.feishu.cn/open-apis/bot/v2/hook/" + token
}
urls = append(urls, url)
}
}
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))
}
}

144
alert/sender/feishucard.go Normal file
View File

@@ -0,0 +1,144 @@
package sender
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 {
WideScreenMode bool `json:"wide_screen_mode"`
EnableForward bool `json:"enable_forward"`
}
type Te struct {
Content string `json:"content"`
Tag string `json:"tag"`
}
type Element struct {
Tag string `json:"tag"`
Text Te `json:"text"`
Content string `json:"content"`
Elements []Element `json:"elements"`
}
type Titles struct {
Content string `json:"content"`
Tag string `json:"tag"`
}
type Headers struct {
Title Titles `json:"title"`
Template string `json:"template"`
}
type Cards struct {
Config Conf `json:"config"`
Elements []Element `json:"elements"`
Header Headers `json:"header"`
}
type feishuCard struct {
feishu
Card Cards `json:"card"`
}
type FeishuCardSender struct {
tpl *template.Template
}
const (
Recovered = "recovered"
Triggered = "triggered"
)
var (
body = feishuCard{
feishu: feishu{Msgtype: "interactive"},
Card: Cards{
Config: Conf{
WideScreenMode: true,
EnableForward: true,
},
Header: Headers{
Title: Titles{
Tag: "plain_text",
},
},
Elements: []Element{
{
Tag: "div",
Text: Te{
Tag: "lark_md",
},
},
{
Tag: "hr",
},
{
Tag: "note",
Elements: []Element{
{
Tag: "lark_md",
},
},
},
},
},
}
)
func (fs *FeishuCardSender) Send(ctx MessageContext) {
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
return
}
urls, _ := fs.extract(ctx.Users)
message := BuildTplMessage(fs.tpl, ctx.Events)
color := "red"
lowerUnicode := strings.ToLower(message)
if strings.Count(lowerUnicode, Recovered) > 0 && strings.Count(lowerUnicode, Triggered) > 0 {
color = "orange"
} else if strings.Count(lowerUnicode, Recovered) > 0 {
color = "green"
}
SendTitle := fmt.Sprintf("🔔 %s", ctx.Events[0].RuleName)
body.Card.Header.Title.Content = SendTitle
body.Card.Header.Template = color
body.Card.Elements[0].Text.Content = message
body.Card.Elements[2].Elements[0].Content = SendTitle
for _, url := range urls {
fs.doSend(url, body)
}
}
func (fs *FeishuCardSender) extract(users []*models.User) ([]string, []string) {
urls := make([]string, 0, len(users))
ats := make([]string, 0)
for i := range users {
if token, has := users[i].ExtractToken(models.FeishuCard); has {
url := token
if !strings.HasPrefix(token, "https://") {
url = "https://open.feishu.cn/open-apis/bot/v2/hook/" + strings.TrimSpace(token)
}
urls = append(urls, url)
}
}
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))
}
}

107
alert/sender/mm.go Normal file
View File

@@ -0,0 +1,107 @@
package sender
import (
"html/template"
"net/url"
"strings"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/poster"
"github.com/toolkits/pkg/logger"
)
type MatterMostMessage struct {
Text string
Tokens []string
}
type mm struct {
Channel string `json:"channel"`
Username string `json:"username"`
Text string `json:"text"`
}
type MmSender struct {
tpl *template.Template
}
func (ms *MmSender) Send(ctx MessageContext) {
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
return
}
urls := ms.extract(ctx.Users)
if len(urls) == 0 {
return
}
message := BuildTplMessage(ms.tpl, ctx.Events)
SendMM(MatterMostMessage{
Text: message,
Tokens: urls,
})
}
func (ms *MmSender) extract(users []*models.User) []string {
tokens := make([]string, 0, len(users))
for _, user := range users {
if token, has := user.ExtractToken(models.Mm); has {
tokens = append(tokens, token)
}
}
return tokens
}
func SendMM(message MatterMostMessage) {
for i := 0; i < len(message.Tokens); i++ {
u, err := url.Parse(message.Tokens[i])
if err != nil {
logger.Errorf("mm_sender: failed to parse error=%v", err)
continue
}
v, err := url.ParseQuery(u.RawQuery)
if err != nil {
logger.Errorf("mm_sender: failed to parse query error=%v", err)
}
channels := v["channel"] // do not get
txt := ""
atuser := v["atuser"]
if len(atuser) != 0 {
txt = strings.Join(MapStrToStr(atuser, func(u string) string {
return "@" + u
}), ",") + "\n"
}
username := v.Get("username")
if err != nil {
logger.Errorf("mm_sender: failed to parse error=%v", err)
}
// simple concatenating
ur := u.Scheme + "://" + u.Host + u.Path
for _, channel := range channels {
body := mm{
Channel: channel,
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))
}
}
}
}
func MapStrToStr(arr []string, fn func(s string) string) []string {
var newArray = []string{}
for _, it := range arr {
newArray = append(newArray, fn(it))
}
return newArray
}

97
alert/sender/plugin.go Normal file
View File

@@ -0,0 +1,97 @@
package sender
import (
"bytes"
"os"
"os/exec"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/toolkits/pkg/file"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/sys"
)
func MayPluginNotify(noticeBytes []byte, notifyScript models.NotifyScript) {
if len(noticeBytes) == 0 {
return
}
alertingCallScript(noticeBytes, notifyScript)
}
func alertingCallScript(stdinBytes []byte, notifyScript models.NotifyScript) {
// not enable or no notify.py? do nothing
config := notifyScript
if !config.Enable || config.Content == "" {
return
}
fpath := ".notify_scriptt"
if config.Type == 1 {
fpath = config.Content
} else {
rewrite := true
if file.IsExist(fpath) {
oldContent, err := file.ToString(fpath)
if err != nil {
logger.Errorf("event_script_notify_fail: read script file err: %v", err)
return
}
if oldContent == config.Content {
rewrite = false
}
}
if rewrite {
_, err := file.WriteString(fpath, config.Content)
if err != nil {
logger.Errorf("event_script_notify_fail: write script file err: %v", err)
return
}
err = os.Chmod(fpath, 0777)
if err != nil {
logger.Errorf("event_script_notify_fail: chmod script file err: %v", err)
return
}
}
fpath = "./" + fpath
}
cmd := exec.Command(fpath)
cmd.Stdin = bytes.NewReader(stdinBytes)
// combine stdout and stderr
var buf bytes.Buffer
cmd.Stdout = &buf
cmd.Stderr = &buf
err := startCmd(cmd)
if err != nil {
logger.Errorf("event_script_notify_fail: run cmd err: %v", err)
return
}
err, isTimeout := sys.WrapTimeout(cmd, time.Duration(config.Timeout)*time.Second)
if isTimeout {
if err == nil {
logger.Errorf("event_script_notify_fail: timeout and killed process %s", fpath)
}
if err != nil {
logger.Errorf("event_script_notify_fail: kill process %s occur error %v", fpath, err)
}
return
}
if err != nil {
logger.Errorf("event_script_notify_fail: exec script %s occur error: %v, output: %s", fpath, err, buf.String())
return
}
logger.Infof("event_script_notify_ok: exec %s output: %s", fpath, buf.String())
}

View File

@@ -0,0 +1,14 @@
//go:build !windows
// +build !windows
package sender
import (
"os/exec"
"syscall"
)
func startCmd(c *exec.Cmd) error {
c.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
return c.Start()
}

View File

@@ -0,0 +1,7 @@
package sender
import "os/exec"
func startCmd(c *exec.Cmd) error {
return c.Start()
}

74
alert/sender/sender.go Normal file
View File

@@ -0,0 +1,74 @@
package sender
import (
"bytes"
"html/template"
"github.com/ccfos/nightingale/v6/alert/aconf"
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/models"
)
type (
// Sender 发送消息通知的接口
Sender interface {
Send(ctx MessageContext)
}
// MessageContext 一个event所生成的告警通知的上下文
MessageContext struct {
Users []*models.User
Rule *models.AlertRule
Events []*models.AlertCurEvent
}
)
func NewSender(key string, tpls map[string]*template.Template, smtp aconf.SMTPConfig) Sender {
switch key {
case models.Dingtalk:
return &DingtalkSender{tpl: tpls[models.Dingtalk]}
case models.Wecom:
return &WecomSender{tpl: tpls[models.Wecom]}
case models.Feishu:
return &FeishuSender{tpl: tpls[models.Feishu]}
case models.FeishuCard:
return &FeishuCardSender{tpl: tpls[models.FeishuCard]}
case models.Email:
return &EmailSender{subjectTpl: tpls["mailsubject"], contentTpl: tpls[models.Email], smtp: smtp}
case models.Mm:
return &MmSender{tpl: tpls[models.Mm]}
case models.Telegram:
return &TelegramSender{tpl: tpls[models.Telegram]}
}
return nil
}
func BuildMessageContext(rule *models.AlertRule, events []*models.AlertCurEvent, uids []int64, userCache *memsto.UserCacheType) MessageContext {
users := userCache.GetByUserIds(uids)
return MessageContext{
Rule: rule,
Events: events,
Users: users,
}
}
type BuildTplMessageFunc func(tpl *template.Template, events []*models.AlertCurEvent) string
var BuildTplMessage BuildTplMessageFunc = buildTplMessage
func buildTplMessage(tpl *template.Template, events []*models.AlertCurEvent) string {
if tpl == nil {
return "tpl for current sender not found, please check configuration"
}
var content string
for _, event := range events {
var body bytes.Buffer
if err := tpl.Execute(&body, event); err != nil {
return err.Error()
}
content += body.String() + "\n\n"
}
return content
}

82
alert/sender/telegram.go Normal file
View File

@@ -0,0 +1,82 @@
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 TelegramMessage struct {
Text string
Tokens []string
}
type telegram struct {
ParseMode string `json:"parse_mode"`
Text string `json:"text"`
}
type TelegramSender struct {
tpl *template.Template
}
func (ts *TelegramSender) Send(ctx MessageContext) {
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
return
}
tokens := ts.extract(ctx.Users)
message := BuildTplMessage(ts.tpl, ctx.Events)
SendTelegram(TelegramMessage{
Text: message,
Tokens: tokens,
})
}
func (ts *TelegramSender) extract(users []*models.User) []string {
tokens := make([]string, 0, len(users))
for _, user := range users {
if token, has := user.ExtractToken(models.Telegram); has {
tokens = append(tokens, token)
}
}
return tokens
}
func SendTelegram(message TelegramMessage) {
for i := 0; i < len(message.Tokens); i++ {
if !strings.Contains(message.Tokens[i], "/") && !strings.HasPrefix(message.Tokens[i], "https://") {
logger.Errorf("telegram_sender: result=fail invalid token=%s", message.Tokens[i])
continue
}
var url string
if strings.HasPrefix(message.Tokens[i], "https://") {
url = message.Tokens[i]
} else {
array := strings.Split(message.Tokens[i], "/")
if len(array) != 2 {
logger.Errorf("telegram_sender: result=fail invalid token=%s", message.Tokens[i])
continue
}
botToken := array[0]
chatId := array[1]
url = "https://api.telegram.org/bot" + botToken + "/sendMessage?chat_id=" + chatId
}
body := telegram{
ParseMode: "markdown",
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))
}
}
}

68
alert/sender/webhook.go Normal file
View File

@@ -0,0 +1,68 @@
package sender
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/toolkits/pkg/logger"
)
func SendWebhooks(webhooks []*models.Webhook, event *models.AlertCurEvent) {
for _, conf := range webhooks {
if conf.Url == "" || !conf.Enable {
continue
}
bs, err := json.Marshal(event)
if err != nil {
continue
}
bf := bytes.NewBuffer(bs)
req, err := http.NewRequest("POST", conf.Url, bf)
if err != nil {
logger.Warning("alertingWebhook failed to new request", err)
continue
}
req.Header.Set("Content-Type", "application/json")
if conf.BasicAuthUser != "" && conf.BasicAuthPass != "" {
req.SetBasicAuth(conf.BasicAuthUser, conf.BasicAuthPass)
}
if len(conf.Headers) > 0 && len(conf.Headers)%2 == 0 {
for i := 0; i < len(conf.Headers); i += 2 {
if conf.Headers[i] == "host" {
req.Host = conf.Headers[i+1]
continue
}
req.Header.Set(conf.Headers[i], conf.Headers[i+1])
}
}
// todo add skip verify
client := http.Client{
Timeout: time.Duration(conf.Timeout) * time.Second,
}
var resp *http.Response
resp, err = client.Do(req)
if err != nil {
logger.Errorf("event_webhook_fail, ruleId: [%d], eventId: [%d], url: [%s], error: [%s]", event.RuleId, event.Id, conf.Url, err)
continue
}
var body []byte
if resp.Body != nil {
defer resp.Body.Close()
body, _ = ioutil.ReadAll(resp.Body)
}
logger.Debugf("event_webhook_succ, url: %s, response code: %d, body: %s", conf.Url, resp.StatusCode, string(body))
}
}

65
alert/sender/wecom.go Normal file
View File

@@ -0,0 +1,65 @@
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 {
Content string `json:"content"`
}
type wecom struct {
Msgtype string `json:"msgtype"`
Markdown wecomMarkdown `json:"markdown"`
}
type WecomSender struct {
tpl *template.Template
}
func (ws *WecomSender) Send(ctx MessageContext) {
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
return
}
urls := ws.extract(ctx.Users)
message := BuildTplMessage(ws.tpl, ctx.Events)
for _, url := range urls {
body := wecom{
Msgtype: "markdown",
Markdown: wecomMarkdown{
Content: message,
},
}
ws.doSend(url, body)
}
}
func (ws *WecomSender) extract(users []*models.User) []string {
urls := make([]string, 0, len(users))
for _, user := range users {
if token, has := user.ExtractToken(models.Wecom); has {
url := token
if !strings.HasPrefix(token, "https://") {
url = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=" + token
}
urls = append(urls, url)
}
}
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))
}
}

30
center/cconf/conf.go Normal file
View File

@@ -0,0 +1,30 @@
package cconf
type Center struct {
Plugins []Plugin
MetricsYamlFile string
OpsYamlFile string
BuiltinIntegrationsDir string
I18NHeaderKey string
MetricDesc MetricDescType
AnonymousAccess AnonymousAccess
UseFileAssets bool
}
type Plugin struct {
Id int64 `json:"id"`
Category string `json:"category"`
Type string `json:"plugin_type"`
TypeName string `json:"plugin_type_name"`
}
type AnonymousAccess struct {
PromQuerier bool
AlertDetail bool
}
func (c *Center) PreCheck() {
if len(c.Plugins) == 0 {
c.Plugins = Plugins
}
}

View File

@@ -0,0 +1,60 @@
package cconf
const EVENT_EXAMPLE = `
{
"id": 1000000,
"cate": "prometheus",
"datasource_id": 1,
"group_id": 1,
"group_name": "Default Busi Group",
"hash": "2cb966f9ba1cdc7af94c3796e855955a",
"rule_id": 23,
"rule_name": "测试告警",
"rule_note": "测试告警",
"rule_prod": "metric",
"rule_config": {
"queries": [
{
"key": "all_hosts",
"op": "==",
"values": []
}
],
"triggers": [
{
"duration": 3,
"percent": 10,
"severity": 3,
"type": "pct_target_miss"
}
]
},
"prom_for_duration": 60,
"prom_eval_interval": 30,
"callbacks": ["https://n9e.github.io"],
"notify_recovered": 1,
"notify_channels": ["dingtalk"],
"notify_groups": [],
"notify_groups_obj": null,
"target_ident": "host01",
"target_note": "机器备注",
"trigger_time": 1677229517,
"trigger_value": "2273533952",
"tags": [
"__name__=disk_free",
"dc=qcloud-dev",
"device=vda1",
"fstype=ext4",
"ident=tt-fc-dev00.nj"
],
"is_recovered": false,
"notify_users_obj": null,
"last_eval_time": 1677229517,
"last_sent_time": 1677229517,
"notify_cur_number": 1,
"first_trigger_time": 1677229517,
"annotations": {
"summary": "测试告警"
}
}
`

44
center/cconf/metric.go Normal file
View File

@@ -0,0 +1,44 @@
package cconf
import (
"path"
"github.com/toolkits/pkg/file"
)
// metricDesc , As load map happens before read map, there is no necessary to use concurrent map for metric desc store
type MetricDescType struct {
CommonDesc map[string]string `yaml:",inline" json:"common"`
Zh map[string]string `yaml:"zh" json:"zh"`
En map[string]string `yaml:"en" json:"en"`
}
var MetricDesc MetricDescType
// GetMetricDesc , if metric is not registered, empty string will be returned
func GetMetricDesc(lang, metric string) string {
var m map[string]string
if lang == "zh" {
m = MetricDesc.Zh
} else {
m = MetricDesc.En
}
if m != nil {
if desc, has := m[metric]; has {
return desc
}
}
return MetricDesc.CommonDesc[metric]
}
func LoadMetricsYaml(configDir, metricsYamlFile string) error {
fp := metricsYamlFile
if fp == "" {
fp = path.Join(configDir, "metrics.yaml")
}
if !file.IsExist(fp) {
return nil
}
return file.ReadYaml(fp, &MetricDesc)
}

38
center/cconf/ops.go Normal file
View File

@@ -0,0 +1,38 @@
package cconf
import (
"path"
"github.com/toolkits/pkg/file"
)
var Operations = Operation{}
type Operation struct {
Ops []Ops `yaml:"ops"`
}
type Ops struct {
Name string `yaml:"name" json:"name"`
Cname string `yaml:"cname" json:"cname"`
Ops []string `yaml:"ops" json:"ops"`
}
func LoadOpsYaml(configDir string, opsYamlFile string) error {
fp := opsYamlFile
if fp == "" {
fp = path.Join(configDir, "ops.yaml")
}
if !file.IsExist(fp) {
return nil
}
return file.ReadYaml(fp, &Operations)
}
func GetAllOps(ops []Ops) []string {
var ret []string
for _, op := range ops {
ret = append(ret, op.Ops...)
}
return ret
}

22
center/cconf/plugin.go Normal file
View File

@@ -0,0 +1,22 @@
package cconf
var Plugins = []Plugin{
{
Id: 1,
Category: "timeseries",
Type: "prometheus",
TypeName: "Prometheus Like",
},
{
Id: 2,
Category: "logging",
Type: "elasticsearch",
TypeName: "Elasticsearch",
},
{
Id: 3,
Category: "logging",
Type: "jaeger",
TypeName: "Jaeger",
},
}

104
center/center.go Normal file
View File

@@ -0,0 +1,104 @@
package center
import (
"context"
"fmt"
"github.com/ccfos/nightingale/v6/alert"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/alert/process"
"github.com/ccfos/nightingale/v6/center/cconf"
"github.com/ccfos/nightingale/v6/center/metas"
"github.com/ccfos/nightingale/v6/center/sso"
"github.com/ccfos/nightingale/v6/conf"
"github.com/ccfos/nightingale/v6/dumper"
"github.com/ccfos/nightingale/v6/memsto"
"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/httpx"
"github.com/ccfos/nightingale/v6/pkg/i18nx"
"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"
"github.com/ccfos/nightingale/v6/storage"
alertrt "github.com/ccfos/nightingale/v6/alert/router"
centerrt "github.com/ccfos/nightingale/v6/center/router"
pushgwrt "github.com/ccfos/nightingale/v6/pushgw/router"
)
func Initialize(configDir string, cryptoKey string) (func(), error) {
config, err := conf.InitConfig(configDir, cryptoKey)
if err != nil {
return nil, fmt.Errorf("failed to init config: %v", err)
}
cconf.LoadMetricsYaml(configDir, config.Center.MetricsYamlFile)
cconf.LoadOpsYaml(configDir, config.Center.OpsYamlFile)
logxClean, err := logx.Init(config.Log)
if err != nil {
return nil, err
}
i18nx.Init()
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)
redis, err := storage.NewRedis(config.Redis)
if err != nil {
return nil, err
}
metas := metas.New(redis)
idents := idents.New(ctx)
syncStats := memsto.NewSyncStats()
alertStats := astats.NewSyncStats()
sso := sso.Init(config.Center, ctx)
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)
userCache := memsto.NewUserCache(ctx, syncStats)
userGroupCache := memsto.NewUserGroupCache(ctx, syncStats)
promClients := prom.NewPromClient(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)
writers := writer.NewWriters(config.Pushgw)
httpx.InitRSAConfig(&config.HTTP.RSA)
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)
r := httpx.GinEngine(config.Global.RunMode, config.HTTP)
centerRouter.Config(r)
alertrtRouter.Config(r)
pushgwRouter.Config(r)
dumper.ConfigRouter(r)
httpClean := httpx.Init(config.HTTP, r)
return func() {
logxClean()
httpClean()
}, nil
}

53
center/cstats/stats.go Normal file
View File

@@ -0,0 +1,53 @@
package cstats
import (
"time"
"github.com/prometheus/client_golang/prometheus"
)
const Service = "n9e-center"
var (
labels = []string{"service", "code", "path", "method"}
uptime = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "uptime",
Help: "HTTP service uptime.",
}, []string{"service"},
)
RequestCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_request_count_total",
Help: "Total number of HTTP requests made.",
}, labels,
)
RequestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Buckets: []float64{.01, .1, 1, 10},
Name: "http_request_duration_seconds",
Help: "HTTP request latencies in seconds.",
}, labels,
)
)
func Init() {
// Register the summary and the histogram with Prometheus's default registry.
prometheus.MustRegister(
uptime,
RequestCounter,
RequestDuration,
)
go recordUptime()
}
// recordUptime increases service uptime per second.
func recordUptime() {
for range time.Tick(time.Second) {
uptime.WithLabelValues(Service).Inc()
}
}

104
center/metas/metas.go Normal file
View File

@@ -0,0 +1,104 @@
package metas
import (
"context"
"sync"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/storage"
"github.com/toolkits/pkg/logger"
)
type Set struct {
sync.RWMutex
items map[string]models.HostMeta
redis storage.Redis
}
func New(redis storage.Redis) *Set {
set := &Set{
items: make(map[string]models.HostMeta),
redis: redis,
}
set.Init()
return set
}
func (s *Set) Init() {
go s.LoopPersist()
}
func (s *Set) MSet(items map[string]models.HostMeta) {
s.Lock()
defer s.Unlock()
for ident, meta := range items {
s.items[ident] = meta
}
}
func (s *Set) Set(ident string, meta models.HostMeta) {
s.Lock()
defer s.Unlock()
s.items[ident] = meta
}
func (s *Set) LoopPersist() {
for {
time.Sleep(time.Second)
s.persist()
}
}
func (s *Set) persist() {
var items map[string]models.HostMeta
s.Lock()
if len(s.items) == 0 {
s.Unlock()
return
}
items = s.items
s.items = make(map[string]models.HostMeta)
s.Unlock()
s.updateMeta(items)
}
func (s *Set) updateMeta(items map[string]models.HostMeta) {
m := make(map[string]models.HostMeta, 100)
num := 0
for _, meta := range items {
m[meta.Hostname] = meta
num++
if num == 100 {
if err := s.updateTargets(m); err != nil {
logger.Errorf("failed to update targets: %v", err)
}
m = make(map[string]models.HostMeta, 100)
num = 0
}
}
if err := s.updateTargets(m); err != nil {
logger.Errorf("failed to update targets: %v", err)
}
}
func (s *Set) updateTargets(m map[string]models.HostMeta) error {
count := int64(len(m))
if count == 0 {
return nil
}
newMap := make(map[string]interface{}, count)
for ident, meta := range m {
newMap[models.WrapIdent(ident)] = meta
}
err := storage.MSet(context.Background(), s.redis, newMap)
return err
}

477
center/router/router.go Normal file
View File

@@ -0,0 +1,477 @@
package router
import (
"fmt"
"net/http"
"path"
"runtime"
"strings"
"time"
"github.com/ccfos/nightingale/v6/center/cconf"
"github.com/ccfos/nightingale/v6/center/cstats"
"github.com/ccfos/nightingale/v6/center/metas"
"github.com/ccfos/nightingale/v6/center/sso"
_ "github.com/ccfos/nightingale/v6/front/statik"
"github.com/ccfos/nightingale/v6/memsto"
"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/prom"
"github.com/ccfos/nightingale/v6/pushgw/idents"
"github.com/ccfos/nightingale/v6/storage"
"github.com/gin-gonic/gin"
"github.com/rakyll/statik/fs"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/runner"
)
type Router struct {
HTTP httpx.Config
Center cconf.Center
Operations cconf.Operation
DatasourceCache *memsto.DatasourceCacheType
NotifyConfigCache *memsto.NotifyConfigCacheType
PromClients *prom.PromClientMap
Redis storage.Redis
MetaSet *metas.Set
IdentSet *idents.Set
TargetCache *memsto.TargetCacheType
Sso *sso.SsoClient
UserCache *memsto.UserCacheType
UserGroupCache *memsto.UserGroupCacheType
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 {
return &Router{
HTTP: httpConfig,
Center: center,
Operations: operations,
DatasourceCache: ds,
NotifyConfigCache: ncc,
PromClients: pc,
Redis: redis,
MetaSet: metaSet,
IdentSet: idents,
TargetCache: tc,
Sso: sso,
UserCache: uc,
UserGroupCache: ugc,
Ctx: ctx,
}
}
func stat() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
code := fmt.Sprintf("%d", c.Writer.Status())
method := c.Request.Method
labels := []string{cstats.Service, code, c.FullPath(), method}
cstats.RequestCounter.WithLabelValues(labels...).Inc()
cstats.RequestDuration.WithLabelValues(labels...).Observe(float64(time.Since(start).Seconds()))
}
}
func languageDetector(i18NHeaderKey string) gin.HandlerFunc {
headerKey := i18NHeaderKey
return func(c *gin.Context) {
if headerKey != "" {
lang := c.GetHeader(headerKey)
if lang != "" {
if strings.HasPrefix(lang, "zh") {
c.Request.Header.Set("X-Language", "zh")
} 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.Next()
}
}
func (rt *Router) configNoRoute(r *gin.Engine, fs *http.FileSystem) {
r.NoRoute(func(c *gin.Context) {
arr := strings.Split(c.Request.URL.Path, ".")
suffix := arr[len(arr)-1]
switch suffix {
case "png", "jpeg", "jpg", "svg", "ico", "gif", "css", "js", "html", "htm", "gz", "zip", "map", "ttf":
if !rt.Center.UseFileAssets {
c.FileFromFS(c.Request.URL.Path, *fs)
} else {
cwdarr := []string{"/"}
if runtime.GOOS == "windows" {
cwdarr[0] = ""
}
cwdarr = append(cwdarr, strings.Split(runner.Cwd, "/")...)
cwdarr = append(cwdarr, "pub")
cwdarr = append(cwdarr, strings.Split(c.Request.URL.Path, "/")...)
c.File(path.Join(cwdarr...))
}
default:
if !rt.Center.UseFileAssets {
c.FileFromFS("/", *fs)
} else {
cwdarr := []string{"/"}
if runtime.GOOS == "windows" {
cwdarr[0] = ""
}
cwdarr = append(cwdarr, strings.Split(runner.Cwd, "/")...)
cwdarr = append(cwdarr, "pub")
cwdarr = append(cwdarr, "index.html")
c.File(path.Join(cwdarr...))
}
}
})
}
func (rt *Router) Config(r *gin.Engine) {
r.Use(stat())
r.Use(languageDetector(rt.Center.I18NHeaderKey))
r.Use(aop.Recovery())
statikFS, err := fs.New()
if err != nil {
logger.Errorf("cannot create statik fs: %v", err)
}
if !rt.Center.UseFileAssets {
r.StaticFS("/pub", statikFS)
}
pagesPrefix := "/api/n9e"
pages := r.Group(pagesPrefix)
{
if rt.Center.AnonymousAccess.PromQuerier {
pages.Any("/proxy/:id/*url", rt.dsProxy)
pages.POST("/query-range-batch", rt.promBatchQueryRange)
pages.POST("/query-instant-batch", rt.promBatchQueryInstant)
pages.GET("/datasource/brief", rt.datasourceBriefs)
} 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.POST("/auth/login", rt.jwtMock(), rt.loginPost)
pages.POST("/auth/logout", rt.jwtMock(), rt.auth(), 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)
pages.GET("/auth/ifshowcaptcha", rt.ifShowCaptcha)
pages.GET("/auth/sso-config", rt.ssoConfigNameGet)
pages.GET("/auth/rsa-config", rt.rsaConfigGet)
pages.GET("/auth/redirect", rt.loginRedirect)
pages.GET("/auth/redirect/cas", rt.loginRedirectCas)
pages.GET("/auth/redirect/oauth", rt.loginRedirectOAuth)
pages.GET("/auth/callback", rt.loginCallback)
pages.GET("/auth/callback/cas", rt.loginCallbackCas)
pages.GET("/auth/callback/oauth", rt.loginCallbackOAuth)
pages.GET("/auth/perms", rt.allPerms)
pages.GET("/metrics/desc", rt.metricsDescGetFile)
pages.POST("/metrics/desc", rt.metricsDescGetMap)
pages.GET("/notify-channels", rt.notifyChannelsGets)
pages.GET("/contact-keys", rt.contactKeysGets)
pages.GET("/self/perms", rt.auth(), rt.user(), rt.permsGets)
pages.GET("/self/profile", rt.auth(), rt.user(), rt.selfProfileGet)
pages.PUT("/self/profile", rt.auth(), rt.user(), rt.selfProfilePut)
pages.PUT("/self/password", rt.auth(), rt.user(), rt.selfPasswordPut)
pages.GET("/users", rt.auth(), rt.user(), rt.perm("/users"), rt.userGets)
pages.POST("/users", rt.auth(), rt.admin(), rt.userAddPost)
pages.GET("/user/:id/profile", rt.auth(), rt.userProfileGet)
pages.PUT("/user/:id/profile", rt.auth(), rt.admin(), rt.userProfilePut)
pages.PUT("/user/:id/password", rt.auth(), rt.admin(), rt.userPasswordPut)
pages.DELETE("/user/:id", rt.auth(), rt.admin(), rt.userDel)
pages.GET("/metric-views", rt.auth(), rt.metricViewGets)
pages.DELETE("/metric-views", rt.auth(), rt.user(), rt.metricViewDel)
pages.POST("/metric-views", rt.auth(), rt.user(), rt.metricViewAdd)
pages.PUT("/metric-views", rt.auth(), rt.user(), rt.metricViewPut)
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)
pages.PUT("/user-group/:id", rt.auth(), rt.user(), rt.perm("/user-groups/put"), rt.userGroupWrite(), rt.userGroupPut)
pages.DELETE("/user-group/:id", rt.auth(), rt.user(), rt.perm("/user-groups/del"), rt.userGroupWrite(), rt.userGroupDel)
pages.POST("/user-group/:id/members", rt.auth(), rt.user(), rt.perm("/user-groups/put"), rt.userGroupWrite(), rt.userGroupMemberAdd)
pages.DELETE("/user-group/:id/members", rt.auth(), rt.user(), rt.perm("/user-groups/put"), rt.userGroupWrite(), rt.userGroupMemberDel)
pages.GET("/busi-groups", rt.auth(), rt.user(), rt.busiGroupGets)
pages.POST("/busi-groups", rt.auth(), rt.user(), rt.perm("/busi-groups/add"), rt.busiGroupAdd)
pages.GET("/busi-groups/alertings", rt.auth(), rt.busiGroupAlertingsGets)
pages.GET("/busi-group/:id", rt.auth(), rt.user(), rt.bgro(), rt.busiGroupGet)
pages.PUT("/busi-group/:id", rt.auth(), rt.user(), rt.perm("/busi-groups/put"), rt.bgrw(), rt.busiGroupPut)
pages.POST("/busi-group/:id/members", rt.auth(), rt.user(), rt.perm("/busi-groups/put"), rt.bgrw(), rt.busiGroupMemberAdd)
pages.DELETE("/busi-group/:id/members", rt.auth(), rt.user(), rt.perm("/busi-groups/put"), rt.bgrw(), rt.busiGroupMemberDel)
pages.DELETE("/busi-group/:id", rt.auth(), rt.user(), rt.perm("/busi-groups/del"), rt.bgrw(), rt.busiGroupDel)
pages.GET("/busi-group/:id/perm/:perm", rt.auth(), rt.user(), rt.checkBusiGroupPerm)
pages.GET("/targets", rt.auth(), rt.user(), rt.targetGets)
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)
pages.POST("/targets/tags", rt.auth(), rt.user(), rt.perm("/targets/put"), rt.targetBindTagsByFE)
pages.DELETE("/targets/tags", rt.auth(), rt.user(), rt.perm("/targets/put"), rt.targetUnbindTagsByFE)
pages.PUT("/targets/note", rt.auth(), rt.user(), rt.perm("/targets/put"), rt.targetUpdateNote)
pages.PUT("/targets/bgid", rt.auth(), rt.user(), rt.perm("/targets/put"), rt.targetUpdateBgid)
pages.POST("/builtin-cate-favorite", rt.auth(), rt.user(), rt.builtinCateFavoriteAdd)
pages.DELETE("/builtin-cate-favorite/:name", rt.auth(), rt.user(), rt.builtinCateFavoriteDel)
pages.GET("/builtin-boards", rt.builtinBoardGets)
pages.GET("/builtin-board/:name", rt.builtinBoardGet)
pages.GET("/dashboards/builtin/list", rt.builtinBoardGets)
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("/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.GET("/board/:bid", rt.boardGet)
pages.GET("/board/:bid/pure", rt.boardPureGet)
pages.PUT("/board/:bid", rt.auth(), rt.user(), rt.perm("/dashboards/put"), rt.boardPut)
pages.PUT("/board/:bid/configs", rt.auth(), rt.user(), rt.perm("/dashboards/put"), rt.boardPutConfigs)
pages.PUT("/board/:bid/public", rt.auth(), rt.user(), rt.perm("/dashboards/put"), rt.boardPutPublic)
pages.DELETE("/boards", rt.auth(), rt.user(), rt.perm("/dashboards/del"), rt.boardDel)
pages.GET("/share-charts", rt.chartShareGets)
pages.POST("/share-charts", rt.auth(), rt.chartShareAdd)
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("/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)
pages.DELETE("/busi-group/:id/alert-rules", rt.auth(), rt.user(), rt.perm("/alert-rules/del"), rt.bgrw(), rt.alertRuleDel)
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.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)
pages.PUT("/busi-group/:id/recording-rule/:rrid", rt.auth(), rt.user(), rt.perm("/recording-rules/put"), rt.bgrw(), rt.recordingRulePutByFE)
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-group/:id/alert-mutes", rt.auth(), rt.user(), rt.perm("/alert-mutes"), rt.bgro(), rt.alertMuteGetsByBG)
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.PUT("/busi-group/:id/alert-mutes/fields", rt.auth(), rt.user(), rt.perm("/alert-mutes/put"), rt.bgrw(), rt.alertMutePutFields)
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)
pages.PUT("/busi-group/:id/alert-subscribes", rt.auth(), rt.user(), rt.perm("/alert-subscribes/put"), rt.bgrw(), rt.alertSubscribePut)
pages.DELETE("/busi-group/:id/alert-subscribes", rt.auth(), rt.user(), rt.perm("/alert-subscribes/del"), rt.bgrw(), rt.alertSubscribeDel)
if rt.Center.AnonymousAccess.AlertDetail {
pages.GET("/alert-cur-event/:eid", rt.alertCurEventGet)
pages.GET("/alert-his-event/:eid", rt.alertHisEventGet)
} else {
pages.GET("/alert-cur-event/:eid", rt.auth(), rt.alertCurEventGet)
pages.GET("/alert-his-event/:eid", rt.auth(), rt.alertHisEventGet)
}
// card logic
pages.GET("/alert-cur-events/list", rt.auth(), rt.alertCurEventsList)
pages.GET("/alert-cur-events/card", rt.auth(), rt.alertCurEventsCard)
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-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-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)
pages.POST("/busi-group/:id/task-tpls/tags", rt.auth(), rt.user(), rt.perm("/job-tpls/put"), rt.bgrw(), rt.taskTplBindTags)
pages.DELETE("/busi-group/:id/task-tpls/tags", rt.auth(), rt.user(), rt.perm("/job-tpls/put"), rt.bgrw(), rt.taskTplUnbindTags)
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-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.POST("/datasource/list", rt.auth(), 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)
pages.POST("/datasource/status/update", rt.auth(), rt.admin(), rt.datasourceUpdataStatus)
pages.DELETE("/datasource/", rt.auth(), rt.admin(), rt.datasourceDel)
pages.GET("/roles", rt.auth(), rt.admin(), rt.roleGets)
pages.POST("/roles", rt.auth(), rt.admin(), rt.roleAdd)
pages.PUT("/roles", rt.auth(), rt.admin(), rt.rolePut)
pages.DELETE("/role/:id", rt.auth(), rt.admin(), rt.roleDel)
pages.GET("/role/:id/ops", rt.auth(), rt.admin(), rt.operationOfRole)
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("/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.PUT("/webhooks", rt.auth(), rt.admin(), rt.webhookPuts)
pages.GET("/notify-script", rt.auth(), rt.admin(), rt.notifyScriptGet)
pages.PUT("/notify-script", rt.auth(), rt.admin(), rt.notifyScriptPut)
pages.GET("/notify-channel", rt.auth(), rt.admin(), rt.notifyChannelGets)
pages.PUT("/notify-channel", rt.auth(), rt.admin(), rt.notifyChannelPuts)
pages.GET("/notify-contact", rt.auth(), rt.admin(), rt.notifyContactGets)
pages.PUT("/notify-contact", rt.auth(), rt.admin(), rt.notifyContactPuts)
pages.GET("/notify-config", rt.auth(), rt.admin(), rt.notifyConfigGet)
pages.PUT("/notify-config", rt.auth(), rt.admin(), rt.notifyConfigPut)
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)
}
if rt.HTTP.APIForService.Enable {
service := r.Group("/v1/n9e")
if len(rt.HTTP.APIForService.BasicAuth) > 0 {
service.Use(gin.BasicAuth(rt.HTTP.APIForService.BasicAuth))
}
{
service.Any("/prometheus/*url", rt.dsProxy)
service.POST("/users", rt.userAddPost)
service.GET("/users", rt.userFindAll)
service.GET("/user-groups", rt.userGroupGetsByService)
service.GET("/user-group-members", rt.userGroupMemberGetsByService)
service.GET("/targets", rt.targetGetsByService)
service.GET("/targets/tags", rt.targetGetTags)
service.POST("/targets/tags", rt.targetBindTagsByService)
service.DELETE("/targets/tags", rt.targetUnbindTagsByService)
service.PUT("/targets/note", rt.targetUpdateNoteByService)
service.POST("/alert-rules", rt.alertRuleAddByService)
service.DELETE("/alert-rules", rt.alertRuleDelByService)
service.PUT("/alert-rule/:arid", rt.alertRulePutByService)
service.GET("/alert-rule/:arid", rt.alertRuleGet)
service.GET("/alert-rules", rt.alertRulesGetByService)
service.GET("/alert-subscribes", rt.alertSubscribeGetsByService)
service.GET("/busi-groups", rt.busiGroupGetsByService)
service.GET("/datasources", rt.datasourceGetsByService)
service.GET("/datasource-ids", rt.getDatasourceIds)
service.POST("/server-heartbeat", rt.serverHeartbeat)
service.GET("/servers-active", rt.serversActive)
service.GET("/recording-rules", rt.recordingRuleGetsByService)
service.GET("/alert-mutes", rt.alertMuteGets)
service.POST("/alert-mutes", rt.alertMuteAddByService)
service.DELETE("/alert-mutes", rt.alertMuteDel)
service.GET("/alert-cur-events", rt.alertCurEventsList)
service.GET("/alert-cur-events-get-by-rid", rt.alertCurEventsGetByRid)
service.GET("/alert-his-events", rt.alertHisEventsList)
service.GET("/alert-his-event/:eid", rt.alertHisEventGet)
service.GET("/config/:id", rt.configGet)
service.GET("/configs", rt.configsGet)
service.GET("/config", rt.configGetByKey)
service.PUT("/configs", rt.configsPut)
service.POST("/configs", rt.configsPost)
service.DELETE("/configs", rt.configsDel)
service.POST("/conf-prop/encrypt", rt.confPropEncrypt)
service.POST("/conf-prop/decrypt", rt.confPropDecrypt)
service.GET("/statistic", rt.statistic)
service.GET("/notify-tpls", rt.notifyTplGets)
service.POST("/task-record-add", rt.taskRecordAdd)
}
}
if rt.HTTP.APIForAgent.Enable {
heartbeat := r.Group("/v1/n9e")
{
if len(rt.HTTP.APIForAgent.BasicAuth) > 0 {
heartbeat.Use(gin.BasicAuth(rt.HTTP.APIForAgent.BasicAuth))
}
heartbeat.POST("/heartbeat", rt.heartbeat)
}
}
rt.configNoRoute(r, &statikFS)
}
func Render(c *gin.Context, data, msg interface{}) {
if msg == nil {
if data == nil {
data = struct{}{}
}
c.JSON(http.StatusOK, gin.H{"data": data, "error": ""})
} else {
c.JSON(http.StatusOK, gin.H{"error": gin.H{"message": msg}})
}
}
func Dangerous(c *gin.Context, v interface{}, code ...int) {
if v == nil {
return
}
switch t := v.(type) {
case string:
if t != "" {
c.JSON(http.StatusOK, gin.H{"error": v})
}
case error:
c.JSON(http.StatusOK, gin.H{"error": t.Error()})
}
}

View File

@@ -0,0 +1,79 @@
package router
import (
"net/http"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
// no param
func (rt *Router) alertAggrViewGets(c *gin.Context) {
lst, err := models.AlertAggrViewGets(rt.Ctx, c.MustGet("userid"))
ginx.NewRender(c).Data(lst, err)
}
// body: name, rule, cate
func (rt *Router) alertAggrViewAdd(c *gin.Context) {
var f models.AlertAggrView
ginx.BindJSON(c, &f)
me := c.MustGet("user").(*models.User)
if !me.IsAdmin() {
// 管理员可以选择当前这个视图是公开呢,还是私有,普通用户的话就只能是私有的
f.Cate = 1
}
f.Id = 0
f.CreateBy = me.Id
ginx.Dangerous(f.Add(rt.Ctx))
ginx.NewRender(c).Data(f, nil)
}
// body: ids
func (rt *Router) alertAggrViewDel(c *gin.Context) {
var f idsForm
ginx.BindJSON(c, &f)
f.Verify()
me := c.MustGet("user").(*models.User)
if me.IsAdmin() {
ginx.NewRender(c).Message(models.AlertAggrViewDel(rt.Ctx, f.Ids))
} else {
ginx.NewRender(c).Message(models.AlertAggrViewDel(rt.Ctx, f.Ids, me.Id))
}
}
// body: id, name, rule, cate
func (rt *Router) alertAggrViewPut(c *gin.Context) {
var f models.AlertAggrView
ginx.BindJSON(c, &f)
view, err := models.AlertAggrViewGet(rt.Ctx, "id = ?", f.Id)
ginx.Dangerous(err)
if view == nil {
ginx.NewRender(c).Message("no such item(id: %d)", f.Id)
return
}
me := c.MustGet("user").(*models.User)
if !me.IsAdmin() {
f.Cate = 1
if view.CreateBy != me.Id {
ginx.NewRender(c, http.StatusForbidden).Message("forbidden")
return
}
}
view.Name = f.Name
view.Rule = f.Rule
view.Cate = f.Cate
if view.CreateBy == 0 {
view.CreateBy = me.Id
}
ginx.NewRender(c).Message(view.Update(rt.Ctx))
}

View File

@@ -0,0 +1,210 @@
package router
import (
"net/http"
"sort"
"strings"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func parseAggrRules(c *gin.Context) []*models.AggrRule {
aggrRules := strings.Split(ginx.QueryStr(c, "rule", ""), "::") // e.g. field:group_name::field:severity::tagkey:ident
if len(aggrRules) == 0 {
ginx.Bomb(http.StatusBadRequest, "rule empty")
}
rules := make([]*models.AggrRule, len(aggrRules))
for i := 0; i < len(aggrRules); i++ {
pair := strings.Split(aggrRules[i], ":")
if len(pair) != 2 {
ginx.Bomb(http.StatusBadRequest, "rule invalid")
}
if !(pair[0] == "field" || pair[0] == "tagkey") {
ginx.Bomb(http.StatusBadRequest, "rule invalid")
}
rules[i] = &models.AggrRule{
Type: pair[0],
Value: pair[1],
}
}
return rules
}
func (rt *Router) alertCurEventsCard(c *gin.Context) {
stime, etime := getTimeRange(c)
severity := ginx.QueryInt(c, "severity", -1)
query := ginx.QueryStr(c, "query", "")
busiGroupId := ginx.QueryInt64(c, "bgid", 0)
dsIds := queryDatasourceIds(c)
rules := parseAggrRules(c)
prod := ginx.QueryStr(c, "prods", "")
if prod == "" {
prod = ginx.QueryStr(c, "rule_prods", "")
}
prods := []string{}
if prod != "" {
prods = strings.Split(prod, ",")
}
cate := ginx.QueryStr(c, "cate", "$all")
cates := []string{}
if cate != "$all" {
cates = strings.Split(cate, ",")
}
// 最多获取50000个获取太多也没啥意义
list, err := models.AlertCurEventGets(rt.Ctx, prods, busiGroupId, stime, etime, severity, dsIds, cates, query, 50000, 0)
ginx.Dangerous(err)
cardmap := make(map[string]*AlertCard)
for _, event := range list {
title := event.GenCardTitle(rules)
if _, has := cardmap[title]; has {
cardmap[title].Total++
cardmap[title].EventIds = append(cardmap[title].EventIds, event.Id)
if event.Severity < cardmap[title].Severity {
cardmap[title].Severity = event.Severity
}
} else {
cardmap[title] = &AlertCard{
Total: 1,
EventIds: []int64{event.Id},
Title: title,
Severity: event.Severity,
}
}
}
titles := make([]string, 0, len(cardmap))
for title := range cardmap {
titles = append(titles, title)
}
sort.Strings(titles)
cards := make([]*AlertCard, len(titles))
for i := 0; i < len(titles); i++ {
cards[i] = cardmap[titles[i]]
}
sort.SliceStable(cards, func(i, j int) bool {
if cards[i].Severity != cards[j].Severity {
return cards[i].Severity < cards[j].Severity
}
return cards[i].Total > cards[j].Total
})
ginx.NewRender(c).Data(cards, nil)
}
type AlertCard struct {
Title string `json:"title"`
Total int `json:"total"`
EventIds []int64 `json:"event_ids"`
Severity int `json:"severity"`
}
func (rt *Router) alertCurEventsCardDetails(c *gin.Context) {
var f idsForm
ginx.BindJSON(c, &f)
list, err := models.AlertCurEventGetByIds(rt.Ctx, f.Ids)
if err == nil {
cache := make(map[int64]*models.UserGroup)
for i := 0; i < len(list); i++ {
list[i].FillNotifyGroups(rt.Ctx, cache)
}
}
ginx.NewRender(c).Data(list, err)
}
// alertCurEventsGetByRid
func (rt *Router) alertCurEventsGetByRid(c *gin.Context) {
rid := ginx.QueryInt64(c, "rid")
dsId := ginx.QueryInt64(c, "dsid")
ginx.NewRender(c).Data(models.AlertCurEventGetByRuleIdAndDsId(rt.Ctx, rid, dsId))
}
// 列表方式,拉取活跃告警
func (rt *Router) alertCurEventsList(c *gin.Context) {
stime, etime := getTimeRange(c)
severity := ginx.QueryInt(c, "severity", -1)
query := ginx.QueryStr(c, "query", "")
limit := ginx.QueryInt(c, "limit", 20)
busiGroupId := ginx.QueryInt64(c, "bgid", 0)
dsIds := queryDatasourceIds(c)
prod := ginx.QueryStr(c, "prods", "")
if prod == "" {
prod = ginx.QueryStr(c, "rule_prods", "")
}
prods := []string{}
if prod != "" {
prods = strings.Split(prod, ",")
}
cate := ginx.QueryStr(c, "cate", "$all")
cates := []string{}
if cate != "$all" {
cates = strings.Split(cate, ",")
}
total, err := models.AlertCurEventTotal(rt.Ctx, prods, busiGroupId, stime, etime, severity, dsIds, cates, query)
ginx.Dangerous(err)
list, err := models.AlertCurEventGets(rt.Ctx, prods, busiGroupId, stime, etime, severity, dsIds, cates, query, limit, ginx.Offset(c, limit))
ginx.Dangerous(err)
cache := make(map[int64]*models.UserGroup)
for i := 0; i < len(list); i++ {
list[i].FillNotifyGroups(rt.Ctx, cache)
}
ginx.NewRender(c).Data(gin.H{
"list": list,
"total": total,
}, nil)
}
func (rt *Router) alertCurEventDel(c *gin.Context) {
var f idsForm
ginx.BindJSON(c, &f)
f.Verify()
set := make(map[int64]struct{})
for i := 0; i < len(f.Ids); i++ {
event, err := models.AlertCurEventGetById(rt.Ctx, f.Ids[i])
ginx.Dangerous(err)
if _, has := set[event.GroupId]; !has {
rt.bgrwCheck(c, event.GroupId)
set[event.GroupId] = struct{}{}
}
}
ginx.NewRender(c).Message(models.AlertCurEventDel(rt.Ctx, f.Ids))
}
func (rt *Router) alertCurEventGet(c *gin.Context) {
eid := ginx.UrlParamInt64(c, "eid")
event, err := models.AlertCurEventGetById(rt.Ctx, eid)
ginx.Dangerous(err)
if event == nil {
ginx.Bomb(404, "No such active event")
}
ginx.NewRender(c).Data(event, nil)
}

View File

@@ -0,0 +1,82 @@
package router
import (
"strings"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func getTimeRange(c *gin.Context) (stime, etime int64) {
stime = ginx.QueryInt64(c, "stime", 0)
etime = ginx.QueryInt64(c, "etime", 0)
hours := ginx.QueryInt64(c, "hours", 0)
now := time.Now().Unix()
if hours != 0 {
stime = now - 3600*hours
etime = now + 3600*24
}
if stime != 0 && etime == 0 {
etime = now + 3600*24
}
return
}
func (rt *Router) alertHisEventsList(c *gin.Context) {
stime, etime := getTimeRange(c)
severity := ginx.QueryInt(c, "severity", -1)
recovered := ginx.QueryInt(c, "is_recovered", -1)
query := ginx.QueryStr(c, "query", "")
limit := ginx.QueryInt(c, "limit", 20)
busiGroupId := ginx.QueryInt64(c, "bgid", 0)
dsIds := queryDatasourceIds(c)
prod := ginx.QueryStr(c, "prods", "")
if prod == "" {
prod = ginx.QueryStr(c, "rule_prods", "")
}
prods := []string{}
if prod != "" {
prods = strings.Split(prod, ",")
}
cate := ginx.QueryStr(c, "cate", "$all")
cates := []string{}
if cate != "$all" {
cates = strings.Split(cate, ",")
}
total, err := models.AlertHisEventTotal(rt.Ctx, prods, busiGroupId, stime, etime, severity, recovered, dsIds, cates, query)
ginx.Dangerous(err)
list, err := models.AlertHisEventGets(rt.Ctx, prods, busiGroupId, stime, etime, severity, recovered, dsIds, cates, query, limit, ginx.Offset(c, limit))
ginx.Dangerous(err)
cache := make(map[int64]*models.UserGroup)
for i := 0; i < len(list); i++ {
list[i].FillNotifyGroups(rt.Ctx, cache)
}
ginx.NewRender(c).Data(gin.H{
"list": list,
"total": total,
}, nil)
}
func (rt *Router) alertHisEventGet(c *gin.Context) {
eid := ginx.UrlParamInt64(c, "eid")
event, err := models.AlertHisEventGetById(rt.Ctx, eid)
ginx.Dangerous(err)
if event == nil {
ginx.Bomb(404, "No such alert event")
}
ginx.NewRender(c).Data(event, err)
}

View File

@@ -0,0 +1,327 @@
package router
import (
"net/http"
"strconv"
"strings"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/i18n"
)
// Return all, front-end search and paging
func (rt *Router) alertRuleGets(c *gin.Context) {
busiGroupId := ginx.UrlParamInt64(c, "id")
ars, err := models.AlertRuleGets(rt.Ctx, busiGroupId)
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", "")
if prodStr != "" {
prods = strings.Split(ginx.QueryStr(c, "prods", ""), ",")
}
query := ginx.QueryStr(c, "query", "")
algorithm := ginx.QueryStr(c, "algorithm", "")
cluster := ginx.QueryStr(c, "cluster", "")
cate := ginx.QueryStr(c, "cate", "$all")
cates := []string{}
if cate != "$all" {
cates = strings.Split(cate, ",")
}
disabled := ginx.QueryInt(c, "disabled", -1)
ars, err := models.AlertRulesGetsBy(rt.Ctx, prods, query, algorithm, cluster, cates, disabled)
if err == nil {
cache := make(map[int64]*models.UserGroup)
for i := 0; i < len(ars); i++ {
ars[i].FillNotifyGroups(rt.Ctx, cache)
}
}
ginx.NewRender(c).Data(ars, err)
}
// single or import
func (rt *Router) alertRuleAddByFE(c *gin.Context) {
username := c.MustGet("username").(string)
var lst []models.AlertRule
ginx.BindJSON(c, &lst)
count := len(lst)
if count == 0 {
ginx.Bomb(http.StatusBadRequest, "input json is empty")
}
bgid := ginx.UrlParamInt64(c, "id")
reterr := rt.alertRuleAdd(lst, username, bgid, c.GetHeader("X-Language"))
ginx.NewRender(c).Data(reterr, nil)
}
func (rt *Router) alertRuleAddByImport(c *gin.Context) {
username := c.MustGet("username").(string)
var lst []models.AlertRule
ginx.BindJSON(c, &lst)
count := len(lst)
if count == 0 {
ginx.Bomb(http.StatusBadRequest, "input json is empty")
}
bgid := ginx.UrlParamInt64(c, "id")
reterr := rt.alertRuleAdd(lst, username, bgid, c.GetHeader("X-Language"))
ginx.NewRender(c).Data(reterr, nil)
}
func (rt *Router) alertRuleAddByService(c *gin.Context) {
var lst []models.AlertRule
ginx.BindJSON(c, &lst)
count := len(lst)
if count == 0 {
ginx.Bomb(http.StatusBadRequest, "input json is empty")
}
reterr := rt.alertRuleAddForService(lst, "")
ginx.NewRender(c).Data(reterr, nil)
}
func (rt *Router) alertRuleAddForService(lst []models.AlertRule, username string) map[string]string {
count := len(lst)
// alert rule name -> error string
reterr := make(map[string]string)
for i := 0; i < count; i++ {
lst[i].Id = 0
if username != "" {
lst[i].CreateBy = username
lst[i].UpdateBy = username
}
if err := lst[i].FE2DB(); err != nil {
reterr[lst[i].Name] = err.Error()
continue
}
if err := lst[i].Add(rt.Ctx); err != nil {
reterr[lst[i].Name] = err.Error()
} else {
reterr[lst[i].Name] = ""
}
}
return reterr
}
func (rt *Router) alertRuleAdd(lst []models.AlertRule, username string, bgid int64, lang string) map[string]string {
count := len(lst)
// alert rule name -> error string
reterr := make(map[string]string)
for i := 0; i < count; i++ {
lst[i].Id = 0
lst[i].GroupId = bgid
if username != "" {
lst[i].CreateBy = username
lst[i].UpdateBy = username
}
if err := lst[i].FE2DB(); err != nil {
reterr[lst[i].Name] = i18n.Sprintf(lang, err.Error())
continue
}
if err := lst[i].Add(rt.Ctx); err != nil {
reterr[lst[i].Name] = i18n.Sprintf(lang, err.Error())
} else {
reterr[lst[i].Name] = ""
}
}
return reterr
}
func (rt *Router) alertRuleDel(c *gin.Context) {
var f idsForm
ginx.BindJSON(c, &f)
f.Verify()
// param(busiGroupId) for protect
ginx.NewRender(c).Message(models.AlertRuleDels(rt.Ctx, f.Ids, ginx.UrlParamInt64(c, "id")))
}
func (rt *Router) alertRuleDelByService(c *gin.Context) {
var f idsForm
ginx.BindJSON(c, &f)
f.Verify()
ginx.NewRender(c).Message(models.AlertRuleDels(rt.Ctx, f.Ids))
}
func (rt *Router) alertRulePutByFE(c *gin.Context) {
var f models.AlertRule
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)
f.UpdateBy = c.MustGet("username").(string)
ginx.NewRender(c).Message(ar.Update(rt.Ctx, f))
}
func (rt *Router) alertRulePutByService(c *gin.Context) {
var f models.AlertRule
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
}
ginx.NewRender(c).Message(ar.Update(rt.Ctx, f))
}
type alertRuleFieldForm struct {
Ids []int64 `json:"ids"`
Fields map[string]interface{} `json:"fields"`
Action string `json:"action"`
}
// update one field: cluster note severity disabled prom_eval_interval prom_for_duration notify_channels notify_groups notify_recovered notify_repeat_step callbacks runbook_url append_tags
func (rt *Router) alertRulePutFields(c *gin.Context) {
var f alertRuleFieldForm
ginx.BindJSON(c, &f)
if len(f.Fields) == 0 {
ginx.Bomb(http.StatusBadRequest, "fields empty")
}
f.Fields["update_by"] = c.MustGet("username").(string)
f.Fields["update_at"] = time.Now().Unix()
for i := 0; i < len(f.Ids); i++ {
ar, err := models.AlertRuleGetById(rt.Ctx, f.Ids[i])
ginx.Dangerous(err)
if ar == nil {
continue
}
if f.Action == "callback_add" {
// 增加一个 callback 地址
if callbacks, has := f.Fields["callbacks"]; has {
callback := callbacks.(string)
if !strings.Contains(ar.Callbacks, callback) {
ginx.Dangerous(ar.UpdateFieldsMap(rt.Ctx, map[string]interface{}{"callbacks": ar.Callbacks + " " + callback}))
continue
}
}
}
if f.Action == "callback_del" {
// 删除一个 callback 地址
if callbacks, has := f.Fields["callbacks"]; has {
callback := callbacks.(string)
ginx.Dangerous(ar.UpdateFieldsMap(rt.Ctx, map[string]interface{}{"callbacks": strings.ReplaceAll(ar.Callbacks, callback, "")}))
continue
}
}
for k, v := range f.Fields {
ginx.Dangerous(ar.UpdateColumn(rt.Ctx, k, v))
}
}
ginx.NewRender(c).Message(nil)
}
func (rt *Router) alertRuleGet(c *gin.Context) {
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
}
err = ar.FillNotifyGroups(rt.Ctx, make(map[int64]*models.UserGroup))
ginx.Dangerous(err)
ginx.NewRender(c).Data(ar, err)
}
//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 {
id, _ := strconv.ParseInt(f.NotifyGroupsJSON[i], 10, 64)
ngids = append(ngids, id)
}
userGroups := rt.UserGroupCache.GetByUserGroupIds(ngids)
uids := make([]int64, 0)
for i := range userGroups {
uids = append(uids, userGroups[i].UserIds...)
}
users := rt.UserCache.GetByUserIds(uids)
//If any users have a certain notify channel's token, it will be okay. Otherwise, this notify channel is absent of tokens.
ancs := make([]string, 0, len(f.NotifyChannelsJSON)) //absent Notify Channels
for i := range f.NotifyChannelsJSON {
flag := true
for ui := range users {
if _, b := users[ui].ExtractToken(f.NotifyChannelsJSON[i]); b {
flag = false
break
}
}
if flag {
ancs = append(ancs, f.NotifyChannelsJSON[i])
}
}
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))
return
}
}
ginx.NewRender(c).Message("")
}

View File

@@ -0,0 +1,121 @@
package router
import (
"net/http"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
// 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))
}
rulecache := make(map[int64]string)
for i := 0; i < len(lst); i++ {
ginx.Dangerous(lst[i].FillRuleName(rt.Ctx, rulecache))
}
for i := 0; i < len(lst); i++ {
ginx.Dangerous(lst[i].FillDatasourceIds(rt.Ctx))
}
}
ginx.NewRender(c).Data(lst, err)
}
func (rt *Router) alertSubscribeGet(c *gin.Context) {
subid := ginx.UrlParamInt64(c, "sid")
sub, err := models.AlertSubscribeGet(rt.Ctx, "id=?", subid)
ginx.Dangerous(err)
if sub == nil {
ginx.NewRender(c, 404).Message("No such alert subscribe")
return
}
ugcache := make(map[int64]*models.UserGroup)
ginx.Dangerous(sub.FillUserGroups(rt.Ctx, ugcache))
rulecache := make(map[int64]string)
ginx.Dangerous(sub.FillRuleName(rt.Ctx, rulecache))
ginx.Dangerous(sub.FillDatasourceIds(rt.Ctx))
ginx.Dangerous(sub.DB2FE())
ginx.NewRender(c).Data(sub, nil)
}
func (rt *Router) alertSubscribeAdd(c *gin.Context) {
var f models.AlertSubscribe
ginx.BindJSON(c, &f)
username := c.MustGet("username").(string)
f.CreateBy = username
f.UpdateBy = username
f.GroupId = ginx.UrlParamInt64(c, "id")
if f.GroupId <= 0 {
ginx.Bomb(http.StatusBadRequest, "group_id invalid")
}
ginx.NewRender(c).Message(f.Add(rt.Ctx))
}
func (rt *Router) alertSubscribePut(c *gin.Context) {
var fs []models.AlertSubscribe
ginx.BindJSON(c, &fs)
timestamp := time.Now().Unix()
username := c.MustGet("username").(string)
for i := 0; i < len(fs); i++ {
fs[i].UpdateBy = username
fs[i].UpdateAt = timestamp
ginx.Dangerous(fs[i].Update(
rt.Ctx,
"name",
"disabled",
"prod",
"cate",
"datasource_ids",
"cluster",
"rule_id",
"tags",
"redefine_severity",
"new_severity",
"redefine_channels",
"new_channels",
"user_group_ids",
"update_at",
"update_by",
"webhooks",
"for_duration",
"redefine_webhooks",
"severities",
"extra_config",
))
}
ginx.NewRender(c).Message(nil)
}
func (rt *Router) alertSubscribeDel(c *gin.Context) {
var f idsForm
ginx.BindJSON(c, &f)
f.Verify()
ginx.NewRender(c).Message(models.AlertSubscribeDel(rt.Ctx, f.Ids))
}
func (rt *Router) alertSubscribeGetsByService(c *gin.Context) {
lst, err := models.AlertSubscribeGetsByService(rt.Ctx)
ginx.NewRender(c).Data(lst, err)
}

View File

@@ -0,0 +1,237 @@
package router
import (
"net/http"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/toolkits/pkg/ginx"
)
type boardForm struct {
Name string `json:"name"`
Ident string `json:"ident"`
Tags string `json:"tags"`
Configs string `json:"configs"`
Public int `json:"public"`
}
func (rt *Router) boardAdd(c *gin.Context) {
var f boardForm
ginx.BindJSON(c, &f)
me := c.MustGet("user").(*models.User)
board := &models.Board{
GroupId: ginx.UrlParamInt64(c, "id"),
Name: f.Name,
Ident: f.Ident,
Tags: f.Tags,
Configs: f.Configs,
CreateBy: me.Username,
UpdateBy: me.Username,
}
err := board.Add(rt.Ctx)
ginx.Dangerous(err)
if f.Configs != "" {
ginx.Dangerous(models.BoardPayloadSave(rt.Ctx, board.Id, f.Configs))
}
ginx.NewRender(c).Data(board, nil)
}
func (rt *Router) boardGet(c *gin.Context) {
bid := ginx.UrlParamStr(c, "bid")
board, err := models.BoardGet(rt.Ctx, "id = ? or ident = ?", bid, bid)
ginx.Dangerous(err)
if board == nil {
ginx.Bomb(http.StatusNotFound, "No such dashboard")
}
if board.Public == 0 {
rt.auth()(c)
rt.user()(c)
me := c.MustGet("user").(*models.User)
if !me.IsAdmin() {
// check permission
rt.bgroCheck(c, board.GroupId)
}
}
ginx.NewRender(c).Data(board, nil)
}
func (rt *Router) boardPureGet(c *gin.Context) {
board, err := models.BoardGetByID(rt.Ctx, ginx.UrlParamInt64(c, "bid"))
ginx.Dangerous(err)
if board == nil {
ginx.Bomb(http.StatusNotFound, "No such dashboard")
}
ginx.NewRender(c).Data(board, nil)
}
// bgrwCheck
func (rt *Router) boardDel(c *gin.Context) {
var f idsForm
ginx.BindJSON(c, &f)
f.Verify()
for i := 0; i < len(f.Ids); i++ {
bid := f.Ids[i]
board, err := models.BoardGet(rt.Ctx, "id = ?", bid)
ginx.Dangerous(err)
if board == nil {
continue
}
me := c.MustGet("user").(*models.User)
if !me.IsAdmin() {
// check permission
rt.bgrwCheck(c, board.GroupId)
}
ginx.Dangerous(board.Del(rt.Ctx))
}
ginx.NewRender(c).Message(nil)
}
func (rt *Router) Board(id int64) *models.Board {
obj, err := models.BoardGet(rt.Ctx, "id=?", id)
ginx.Dangerous(err)
if obj == nil {
ginx.Bomb(http.StatusNotFound, "No such dashboard")
}
return obj
}
// bgrwCheck
func (rt *Router) boardPut(c *gin.Context) {
var f boardForm
ginx.BindJSON(c, &f)
me := c.MustGet("user").(*models.User)
bo := rt.Board(ginx.UrlParamInt64(c, "bid"))
if !me.IsAdmin() {
// check permission
rt.bgrwCheck(c, bo.GroupId)
}
can, err := bo.CanRenameIdent(rt.Ctx, f.Ident)
ginx.Dangerous(err)
if !can {
ginx.Bomb(http.StatusOK, "Ident duplicate")
}
bo.Name = f.Name
bo.Ident = f.Ident
bo.Tags = f.Tags
bo.UpdateBy = me.Username
bo.UpdateAt = time.Now().Unix()
err = bo.Update(rt.Ctx, "name", "ident", "tags", "update_by", "update_at")
ginx.NewRender(c).Data(bo, err)
}
// bgrwCheck
func (rt *Router) boardPutConfigs(c *gin.Context) {
var f boardForm
ginx.BindJSON(c, &f)
me := c.MustGet("user").(*models.User)
bid := ginx.UrlParamStr(c, "bid")
bo, err := models.BoardGet(rt.Ctx, "id = ? or ident = ?", bid, bid)
ginx.Dangerous(err)
if bo == nil {
ginx.Bomb(http.StatusNotFound, "No such dashboard")
}
// check permission
if !me.IsAdmin() {
rt.bgrwCheck(c, bo.GroupId)
}
bo.UpdateBy = me.Username
bo.UpdateAt = time.Now().Unix()
ginx.Dangerous(bo.Update(rt.Ctx, "update_by", "update_at"))
bo.Configs = f.Configs
ginx.Dangerous(models.BoardPayloadSave(rt.Ctx, bo.Id, f.Configs))
ginx.NewRender(c).Data(bo, nil)
}
// bgrwCheck
func (rt *Router) boardPutPublic(c *gin.Context) {
var f boardForm
ginx.BindJSON(c, &f)
me := c.MustGet("user").(*models.User)
bo := rt.Board(ginx.UrlParamInt64(c, "bid"))
// check permission
if !me.IsAdmin() {
rt.bgrwCheck(c, bo.GroupId)
}
bo.Public = f.Public
bo.UpdateBy = me.Username
bo.UpdateAt = time.Now().Unix()
err := bo.Update(rt.Ctx, "public", "update_by", "update_at")
ginx.NewRender(c).Data(bo, err)
}
func (rt *Router) boardGets(c *gin.Context) {
bgid := ginx.UrlParamInt64(c, "id")
query := ginx.QueryStr(c, "query", "")
boards, err := models.BoardGetsByGroupId(rt.Ctx, bgid, query)
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()
}
ginx.Dangerous(newBoard.Add(rt.Ctx))
// clone payload
payload, err := models.BoardPayloadGet(rt.Ctx, bo.Id)
ginx.Dangerous(err)
if payload != "" {
ginx.Dangerous(models.BoardPayloadSave(rt.Ctx, newBoard.Id, payload))
}
ginx.NewRender(c).Message(nil)
}

View File

@@ -0,0 +1,317 @@
package router
import (
"encoding/json"
"fmt"
"net/http"
"path"
"strings"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/file"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/runner"
)
// 创建 builtin_cate
func (rt *Router) builtinCateFavoriteAdd(c *gin.Context) {
var f models.BuiltinCate
ginx.BindJSON(c, &f)
if f.Name == "" {
ginx.Bomb(http.StatusBadRequest, "name is empty")
}
me := c.MustGet("user").(*models.User)
f.UserId = me.Id
ginx.NewRender(c).Message(f.Create(rt.Ctx))
}
// 删除 builtin_cate
func (rt *Router) builtinCateFavoriteDel(c *gin.Context) {
name := ginx.UrlParamStr(c, "name")
me := c.MustGet("user").(*models.User)
ginx.NewRender(c).Message(models.BuiltinCateDelete(rt.Ctx, name, me.Id))
}
type Payload struct {
Cate string `json:"cate"`
Fname string `json:"fname"`
Name string `json:"name"`
Configs interface{} `json:"configs"`
Tags string `json:"tags"`
}
type BoardCate struct {
Name string `json:"name"`
IconUrl string `json:"icon_url"`
Boards []Payload `json:"boards"`
Favorite bool `json:"favorite"`
}
func (rt *Router) builtinBoardDetailGets(c *gin.Context) {
var payload Payload
ginx.BindJSON(c, &payload)
fp := rt.Center.BuiltinIntegrationsDir
if fp == "" {
fp = path.Join(runner.Cwd, "integrations")
}
fn := fp + "/" + payload.Cate + "/dashboards/" + payload.Fname
content, err := file.ReadBytes(fn)
ginx.Dangerous(err)
err = json.Unmarshal(content, &payload)
ginx.NewRender(c).Data(payload, err)
}
func (rt *Router) builtinBoardCateGets(c *gin.Context) {
fp := rt.Center.BuiltinIntegrationsDir
if fp == "" {
fp = path.Join(runner.Cwd, "integrations")
}
me := c.MustGet("user").(*models.User)
buildinFavoritesMap, err := models.BuiltinCateGetByUserId(rt.Ctx, me.Id)
if err != nil {
logger.Warningf("get builtin favorites fail: %v", err)
}
var boardCates []BoardCate
dirList, err := file.DirsUnder(fp)
ginx.Dangerous(err)
for _, dir := range dirList {
var boardCate BoardCate
boardCate.Name = dir
files, err := file.FilesUnder(fp + "/" + dir + "/dashboards")
ginx.Dangerous(err)
if len(files) == 0 {
continue
}
var boards []Payload
for _, f := range files {
fn := fp + "/" + dir + "/dashboards/" + f
content, err := file.ReadBytes(fn)
if err != nil {
logger.Warningf("add board fail: %v", err)
continue
}
var payload Payload
err = json.Unmarshal(content, &payload)
if err != nil {
logger.Warningf("add board:%s fail: %v", fn, err)
continue
}
payload.Cate = dir
payload.Fname = f
payload.Configs = ""
boards = append(boards, payload)
}
boardCate.Boards = boards
if _, ok := buildinFavoritesMap[dir]; ok {
boardCate.Favorite = true
}
iconFiles, _ := file.FilesUnder(fp + "/" + dir + "/icon")
if len(iconFiles) > 0 {
boardCate.IconUrl = fmt.Sprintf("/api/n9e/integrations/icon/%s/%s", dir, iconFiles[0])
}
boardCates = append(boardCates, boardCate)
}
ginx.NewRender(c).Data(boardCates, nil)
}
func (rt *Router) builtinBoardGets(c *gin.Context) {
fp := rt.Center.BuiltinIntegrationsDir
if fp == "" {
fp = path.Join(runner.Cwd, "integrations")
}
var fileList []string
dirList, err := file.DirsUnder(fp)
ginx.Dangerous(err)
for _, dir := range dirList {
files, err := file.FilesUnder(fp + "/" + dir + "/dashboards")
ginx.Dangerous(err)
fileList = append(fileList, files...)
}
names := make([]string, 0, len(fileList))
for _, f := range fileList {
if !strings.HasSuffix(f, ".json") {
continue
}
name := strings.TrimSuffix(f, ".json")
names = append(names, name)
}
ginx.NewRender(c).Data(names, nil)
}
type AlertCate struct {
Name string `json:"name"`
IconUrl string `json:"icon_url"`
AlertRules []models.AlertRule `json:"alert_rules"`
Favorite bool `json:"favorite"`
}
func (rt *Router) builtinAlertCateGets(c *gin.Context) {
fp := rt.Center.BuiltinIntegrationsDir
if fp == "" {
fp = path.Join(runner.Cwd, "integrations")
}
me := c.MustGet("user").(*models.User)
buildinFavoritesMap, err := models.BuiltinCateGetByUserId(rt.Ctx, me.Id)
if err != nil {
logger.Warningf("get builtin favorites fail: %v", err)
}
var alertCates []AlertCate
dirList, err := file.DirsUnder(fp)
ginx.Dangerous(err)
for _, dir := range dirList {
var alertCate AlertCate
alertCate.Name = dir
files, err := file.FilesUnder(fp + "/" + dir + "/alerts")
ginx.Dangerous(err)
var alertRules []models.AlertRule
for _, f := range files {
fn := fp + "/" + dir + "/alerts/" + f
content, err := file.ReadBytes(fn)
if err != nil {
logger.Warningf("add board fail: %v", err)
continue
}
var ars []models.AlertRule
err = json.Unmarshal(content, &ars)
if err != nil {
logger.Warningf("add board:%s fail: %v", fn, err)
continue
}
alertRules = append(alertRules, ars...)
}
alertCate.AlertRules = alertRules
iconFiles, _ := file.FilesUnder(fp + "/" + dir + "/icon")
if len(iconFiles) > 0 {
alertCate.IconUrl = fmt.Sprintf("/api/n9e/integrations/icon/%s/%s", dir, iconFiles[0])
}
if _, ok := buildinFavoritesMap[dir]; ok {
alertCate.Favorite = true
}
alertCates = append(alertCates, alertCate)
}
ginx.NewRender(c).Data(alertCates, nil)
}
type builtinAlertRulesList struct {
Name string `json:"name"`
IconUrl string `json:"icon_url"`
AlertRules map[string][]models.AlertRule `json:"alert_rules"`
Favorite bool `json:"favorite"`
}
func (rt *Router) builtinAlertRules(c *gin.Context) {
fp := rt.Center.BuiltinIntegrationsDir
if fp == "" {
fp = path.Join(runner.Cwd, "integrations")
}
me := c.MustGet("user").(*models.User)
buildinFavoritesMap, err := models.BuiltinCateGetByUserId(rt.Ctx, me.Id)
if err != nil {
logger.Warningf("get builtin favorites fail: %v", err)
}
var alertCates []builtinAlertRulesList
dirList, err := file.DirsUnder(fp)
ginx.Dangerous(err)
for _, dir := range dirList {
var alertCate builtinAlertRulesList
alertCate.Name = dir
files, err := file.FilesUnder(fp + "/" + dir + "/alerts")
ginx.Dangerous(err)
if len(files) == 0 {
continue
}
alertRules := make(map[string][]models.AlertRule)
for _, f := range files {
fn := fp + "/" + dir + "/alerts/" + f
content, err := file.ReadBytes(fn)
if err != nil {
logger.Warningf("add board fail: %v", err)
continue
}
var ars []models.AlertRule
err = json.Unmarshal(content, &ars)
if err != nil {
logger.Warningf("add board:%s fail: %v", fn, err)
continue
}
alertRules[strings.TrimSuffix(f, ".json")] = ars
}
alertCate.AlertRules = alertRules
iconFiles, _ := file.FilesUnder(fp + "/" + dir + "/icon")
if len(iconFiles) > 0 {
alertCate.IconUrl = fmt.Sprintf("/api/n9e/integrations/icon/%s/%s", dir, iconFiles[0])
}
if _, ok := buildinFavoritesMap[dir]; ok {
alertCate.Favorite = true
}
alertCates = append(alertCates, alertCate)
}
ginx.NewRender(c).Data(alertCates, nil)
}
// read the json file content
func (rt *Router) builtinBoardGet(c *gin.Context) {
name := ginx.UrlParamStr(c, "name")
dirpath := rt.Center.BuiltinIntegrationsDir
if dirpath == "" {
dirpath = path.Join(runner.Cwd, "integrations")
}
dirList, err := file.DirsUnder(dirpath)
ginx.Dangerous(err)
for _, dir := range dirList {
jsonFile := dirpath + "/" + dir + "/dashboards/" + name + ".json"
if file.IsExist(jsonFile) {
body, err := file.ReadString(jsonFile)
ginx.NewRender(c).Data(body, err)
return
}
}
ginx.Bomb(http.StatusBadRequest, "%s not found", name)
}
func (rt *Router) builtinIcon(c *gin.Context) {
fp := rt.Center.BuiltinIntegrationsDir
if fp == "" {
fp = path.Join(runner.Cwd, "integrations")
}
cate := ginx.UrlParamStr(c, "cate")
iconPath := fp + "/" + cate + "/icon/" + ginx.UrlParamStr(c, "name")
c.File(path.Join(iconPath))
}

View File

@@ -0,0 +1,142 @@
package router
import (
"net/http"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/str"
)
type busiGroupForm struct {
Name string `json:"name" binding:"required"`
LabelEnable int `json:"label_enable"`
LabelValue string `json:"label_value"`
Members []models.BusiGroupMember `json:"members"`
}
func (rt *Router) busiGroupAdd(c *gin.Context) {
var f busiGroupForm
ginx.BindJSON(c, &f)
if len(f.Members) == 0 {
ginx.Bomb(http.StatusBadRequest, "members empty")
}
rwhas := false
for i := 0; i < len(f.Members); i++ {
if f.Members[i].PermFlag == "rw" {
rwhas = true
break
}
}
if !rwhas {
ginx.Bomb(http.StatusBadRequest, "At least one team have rw permission")
}
username := c.MustGet("username").(string)
ginx.Dangerous(models.BusiGroupAdd(rt.Ctx, f.Name, f.LabelEnable, f.LabelValue, f.Members, username))
// 如果创建成功拿着name去查应该可以查到
newbg, err := models.BusiGroupGet(rt.Ctx, "name=?", f.Name)
ginx.Dangerous(err)
if newbg == nil {
ginx.NewRender(c).Message("Failed to create BusiGroup(%s)", f.Name)
return
}
ginx.NewRender(c).Data(newbg.Id, nil)
}
func (rt *Router) busiGroupPut(c *gin.Context) {
var f busiGroupForm
ginx.BindJSON(c, &f)
username := c.MustGet("username").(string)
targetbg := c.MustGet("busi_group").(*models.BusiGroup)
ginx.NewRender(c).Message(targetbg.Update(rt.Ctx, f.Name, f.LabelEnable, f.LabelValue, username))
}
func (rt *Router) busiGroupMemberAdd(c *gin.Context) {
var members []models.BusiGroupMember
ginx.BindJSON(c, &members)
username := c.MustGet("username").(string)
targetbg := c.MustGet("busi_group").(*models.BusiGroup)
for i := 0; i < len(members); i++ {
if members[i].BusiGroupId != targetbg.Id {
ginx.Bomb(http.StatusBadRequest, "business group id invalid")
}
}
ginx.NewRender(c).Message(targetbg.AddMembers(rt.Ctx, members, username))
}
func (rt *Router) busiGroupMemberDel(c *gin.Context) {
var members []models.BusiGroupMember
ginx.BindJSON(c, &members)
username := c.MustGet("username").(string)
targetbg := c.MustGet("busi_group").(*models.BusiGroup)
for i := 0; i < len(members); i++ {
if members[i].BusiGroupId != targetbg.Id {
ginx.Bomb(http.StatusBadRequest, "business group id invalid")
}
}
ginx.NewRender(c).Message(targetbg.DelMembers(rt.Ctx, members, username))
}
func (rt *Router) busiGroupDel(c *gin.Context) {
username := c.MustGet("username").(string)
targetbg := c.MustGet("busi_group").(*models.BusiGroup)
err := targetbg.Del(rt.Ctx)
if err != nil {
logger.Infof("busi_group_delete fail: operator=%s, group_name=%s error=%v", username, targetbg.Name, err)
} else {
logger.Infof("busi_group_delete succ: operator=%s, group_name=%s", username, targetbg.Name)
}
ginx.NewRender(c).Message(err)
}
// 我是超管、或者我是业务组成员
func (rt *Router) busiGroupGets(c *gin.Context) {
limit := ginx.QueryInt(c, "limit", defaultLimit)
query := ginx.QueryStr(c, "query", "")
all := ginx.QueryBool(c, "all", false)
me := c.MustGet("user").(*models.User)
lst, err := me.BusiGroups(rt.Ctx, limit, query, all)
if len(lst) == 0 {
lst = []models.BusiGroup{}
}
ginx.NewRender(c).Data(lst, err)
}
func (rt *Router) busiGroupGetsByService(c *gin.Context) {
lst, err := models.BusiGroupGetAll(rt.Ctx)
ginx.NewRender(c).Data(lst, err)
}
// 这个接口只有在活跃告警页面才调用获取各个BG的活跃告警数量
func (rt *Router) busiGroupAlertingsGets(c *gin.Context) {
ids := ginx.QueryStr(c, "ids", "")
ret, err := models.AlertNumbers(rt.Ctx, str.IdsInt64(ids))
ginx.NewRender(c).Data(ret, err)
}
func (rt *Router) busiGroupGet(c *gin.Context) {
bg := BusiGroup(rt.Ctx, ginx.UrlParamInt64(c, "id"))
ginx.Dangerous(bg.FillUserGroups(rt.Ctx))
ginx.NewRender(c).Data(bg, nil)
}

View File

@@ -0,0 +1,114 @@
package router
import (
"context"
"time"
"github.com/ccfos/nightingale/v6/storage"
"github.com/gin-gonic/gin"
captcha "github.com/mojocn/base64Captcha"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)
type CaptchaRedisStore struct {
redis storage.Redis
}
func (s *CaptchaRedisStore) Set(id string, value string) error {
ctx := context.Background()
err := s.redis.Set(ctx, id, value, time.Duration(300*time.Second)).Err()
if err != nil {
logger.Errorf("captcha id set to redis error : %s", err.Error())
return err
}
return nil
}
func (s *CaptchaRedisStore) Get(id string, clear bool) string {
ctx := context.Background()
val, err := s.redis.Get(ctx, id).Result()
if err != nil {
logger.Errorf("captcha id get from redis error : %s", err.Error())
return ""
}
if clear {
s.redis.Del(ctx, id)
}
return val
}
func (s *CaptchaRedisStore) Verify(id, answer string, clear bool) bool {
old := s.Get(id, clear)
return old == answer
}
func (rt *Router) newCaptchaRedisStore() *CaptchaRedisStore {
if captchaStore == nil {
captchaStore = &CaptchaRedisStore{redis: rt.Redis}
}
return captchaStore
}
var captchaStore *CaptchaRedisStore
type CaptchaReqBody struct {
Id string
VerifyValue string
}
// 生成图形验证码
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()
if err != nil {
ginx.NewRender(c).Message(err)
return
}
ginx.NewRender(c).Data(gin.H{
"imgdata": b64s,
"captchaid": id,
}, nil)
}
// 验证
func (rt *Router) captchaVerify(c *gin.Context) {
var param CaptchaReqBody
ginx.BindJSON(c, &param)
//verify the captcha
if captchaStore.Verify(param.Id, param.VerifyValue, true) {
ginx.NewRender(c).Message("")
return
}
ginx.NewRender(c).Message("incorrect verification code")
}
// 验证码开关
func (rt *Router) ifShowCaptcha(c *gin.Context) {
if rt.HTTP.ShowCaptcha.Enable {
ginx.NewRender(c).Data(gin.H{
"show": true,
}, nil)
return
}
ginx.NewRender(c).Data(gin.H{
"show": false,
}, nil)
}
// 验证
func CaptchaVerify(id string, value string) bool {
//verify the captcha
return captchaStore.Verify(id, value, true)
}

View File

@@ -0,0 +1,45 @@
package router
import (
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/str"
)
func (rt *Router) chartShareGets(c *gin.Context) {
ids := ginx.QueryStr(c, "ids", "")
lst, err := models.ChartShareGetsByIds(rt.Ctx, str.IdsInt64(ids, ","))
ginx.NewRender(c).Data(lst, err)
}
type chartShareForm struct {
DatasourceId int64 `json:"datasource_id"`
Configs string `json:"configs"`
}
func (rt *Router) chartShareAdd(c *gin.Context) {
username := c.MustGet("username").(string)
var forms []chartShareForm
ginx.BindJSON(c, &forms)
ids := []int64{}
now := time.Now().Unix()
for _, f := range forms {
chart := models.ChartShare{
DatasourceId: f.DatasourceId,
Configs: f.Configs,
CreateBy: username,
CreateAt: now,
}
ginx.Dangerous(chart.Add(rt.Ctx))
ids = append(ids, chart.Id)
}
ginx.NewRender(c).Data(ids, nil)
}

View File

@@ -0,0 +1,64 @@
package router
import (
"encoding/json"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) notifyChannelsGets(c *gin.Context) {
var labelAndKeys []models.LabelAndKey
cval, err := models.ConfigsGet(rt.Ctx, models.NOTIFYCHANNEL)
ginx.Dangerous(err)
if cval == "" {
ginx.NewRender(c).Data(labelAndKeys, nil)
return
}
var notifyChannels []models.NotifyChannel
err = json.Unmarshal([]byte(cval), &notifyChannels)
ginx.Dangerous(err)
for _, v := range notifyChannels {
if v.Hide {
continue
}
var labelAndKey models.LabelAndKey
labelAndKey.Label = v.Name
labelAndKey.Key = v.Ident
labelAndKeys = append(labelAndKeys, labelAndKey)
}
ginx.NewRender(c).Data(labelAndKeys, nil)
}
func (rt *Router) contactKeysGets(c *gin.Context) {
var labelAndKeys []models.LabelAndKey
cval, err := models.ConfigsGet(rt.Ctx, models.NOTIFYCONTACT)
ginx.Dangerous(err)
if cval == "" {
ginx.NewRender(c).Data(labelAndKeys, nil)
return
}
var notifyContacts []models.NotifyContact
err = json.Unmarshal([]byte(cval), &notifyContacts)
ginx.Dangerous(err)
for _, v := range notifyContacts {
if v.Hide {
continue
}
var labelAndKey models.LabelAndKey
labelAndKey.Label = v.Name
labelAndKey.Key = v.Ident
labelAndKeys = append(labelAndKeys, labelAndKey)
}
ginx.NewRender(c).Data(labelAndKeys, nil)
}

View File

@@ -0,0 +1,54 @@
package router
import (
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) configsGet(c *gin.Context) {
prefix := ginx.QueryStr(c, "prefix", "")
limit := ginx.QueryInt(c, "limit", 10)
configs, err := models.ConfigsGets(rt.Ctx, prefix, limit, ginx.Offset(c, limit))
ginx.NewRender(c).Data(configs, err)
}
func (rt *Router) configGet(c *gin.Context) {
id := ginx.UrlParamInt64(c, "id")
configs, err := models.ConfigGet(rt.Ctx, id)
ginx.NewRender(c).Data(configs, err)
}
func (rt *Router) configGetByKey(c *gin.Context) {
config, err := models.ConfigsGet(rt.Ctx, ginx.QueryStr(c, "key"))
ginx.NewRender(c).Data(config, err)
}
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) {
var arr []models.Configs
ginx.BindJSON(c, &arr)
for i := 0; i < len(arr); i++ {
ginx.Dangerous(arr[i].Update(rt.Ctx))
}
ginx.NewRender(c).Message(nil)
}
func (rt *Router) configsPost(c *gin.Context) {
var arr []models.Configs
ginx.BindJSON(c, &arr)
for i := 0; i < len(arr); i++ {
ginx.Dangerous(arr[i].Add(rt.Ctx))
}
ginx.NewRender(c).Message(nil)
}

View File

@@ -0,0 +1,63 @@
package router
import (
"github.com/ccfos/nightingale/v6/pkg/secu"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
type confPropCrypto struct {
Data string `json:"data" binding:"required"`
Key string `json:"key" binding:"required"`
}
func (rt *Router) confPropEncrypt(c *gin.Context) {
var f confPropCrypto
ginx.BindJSON(c, &f)
k := len(f.Key)
switch k {
default:
c.String(400, "The key length should be 16, 24 or 32")
return
case 16, 24, 32:
break
}
s, err := secu.DealWithEncrypt(f.Data, f.Key)
if err != nil {
c.String(500, err.Error())
}
c.JSON(200, gin.H{
"src": f.Data,
"key": f.Key,
"encrypt": s,
})
}
func (rt *Router) confPropDecrypt(c *gin.Context) {
var f confPropCrypto
ginx.BindJSON(c, &f)
k := len(f.Key)
switch k {
default:
c.String(400, "The key length should be 16, 24 or 32")
return
case 16, 24, 32:
break
}
s, err := secu.DealWithDecrypt(f.Data, f.Key)
if err != nil {
c.String(500, err.Error())
}
c.JSON(200, gin.H{
"src": f.Data,
"key": f.Key,
"decrypt": s,
})
}

View File

@@ -0,0 +1,19 @@
package router
type ChartPure struct {
Configs string `json:"configs"`
Weight int `json:"weight"`
}
type ChartGroupPure struct {
Name string `json:"name"`
Weight int `json:"weight"`
Charts []ChartPure `json:"charts"`
}
type DashboardPure struct {
Name string `json:"name"`
Tags string `json:"tags"`
Configs string `json:"configs"`
ChartGroups []ChartGroupPure `json:"chart_groups"`
}

View File

@@ -0,0 +1,200 @@
package router
import (
"crypto/tls"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)
func (rt *Router) pluginList(c *gin.Context) {
Render(c, rt.Center.Plugins, nil)
}
type listReq struct {
Name string `json:"name"`
Type string `json:"plugin_type"`
Category string `json:"category"`
}
func (rt *Router) datasourceList(c *gin.Context) {
var req listReq
ginx.BindJSON(c, &req)
typ := req.Type
category := req.Category
name := req.Name
list, err := models.GetDatasourcesGetsBy(rt.Ctx, typ, category, name, "")
Render(c, list, err)
}
func (rt *Router) datasourceGetsByService(c *gin.Context) {
typ := ginx.QueryStr(c, "typ", "")
lst, err := models.GetDatasourcesGetsBy(rt.Ctx, typ, "", "", "")
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
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,
})
}
ginx.NewRender(c).Data(dss, err)
}
func (rt *Router) datasourceUpsert(c *gin.Context) {
var req models.Datasource
ginx.BindJSON(c, &req)
username := Username(c)
req.UpdatedBy = username
var err error
var count int64
err = DatasourceCheck(req)
if err != nil {
Dangerous(c, err)
return
}
if req.Id == 0 {
req.CreatedBy = username
req.Status = "enabled"
count, err = models.GetDatasourcesCountBy(rt.Ctx, "", "", req.Name)
if err != nil {
Render(c, nil, err)
return
}
if count > 0 {
Render(c, nil, "name already exists")
return
}
err = req.Add(rt.Ctx)
} else {
err = req.Update(rt.Ctx, "name", "description", "cluster_name", "settings", "http", "auth", "updated_by", "updated_at")
}
Render(c, nil, err)
}
func DatasourceCheck(ds models.Datasource) error {
if ds.HTTPJson.Url == "" {
return fmt.Errorf("url is empty")
}
if !strings.HasPrefix(ds.HTTPJson.Url, "http") {
return fmt.Errorf("url must start with http or https")
}
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: ds.HTTPJson.TLS.SkipTlsVerify,
},
},
}
fullURL := ds.HTTPJson.Url
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)
}
if ds.PluginType == models.PROMETHEUS {
subPath := "/api/v1/query"
query := url.Values{}
if strings.Contains(fullURL, "loki") {
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)
if err != nil {
logger.Errorf("Error creating request: %v", err)
return fmt.Errorf("request url:%s failed", fullURL)
}
}
if ds.AuthJson.BasicAuthUser != "" {
req.SetBasicAuth(ds.AuthJson.BasicAuthUser, ds.AuthJson.BasicAuthPassword)
}
for k, v := range ds.HTTPJson.Headers {
req.Header.Set(k, v)
}
resp, err := client.Do(req)
if err != nil {
logger.Errorf("Error making request: %v\n", err)
return fmt.Errorf("request url:%s failed", fullURL)
}
defer resp.Body.Close()
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)
}
return nil
}
func (rt *Router) datasourceGet(c *gin.Context) {
var req models.Datasource
ginx.BindJSON(c, &req)
err := req.Get(rt.Ctx)
Render(c, req, err)
}
func (rt *Router) datasourceUpdataStatus(c *gin.Context) {
var req models.Datasource
ginx.BindJSON(c, &req)
username := Username(c)
req.UpdatedBy = username
err := req.Update(rt.Ctx, "status", "updated_by", "updated_at")
Render(c, req, err)
}
func (rt *Router) datasourceDel(c *gin.Context) {
var ids []int64
ginx.BindJSON(c, &ids)
err := models.DatasourceDel(rt.Ctx, ids)
Render(c, nil, err)
}
func (rt *Router) getDatasourceIds(c *gin.Context) {
name := ginx.QueryStr(c, "name")
datasourceIds, err := models.GetDatasourceIdsByEngineName(rt.Ctx, name)
ginx.NewRender(c).Data(datasourceIds, err)
}
func Username(c *gin.Context) string {
return c.MustGet("username").(string)
}

View File

@@ -0,0 +1,80 @@
package router
import (
"net/http"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
// 创建 ES Index Pattern
func (rt *Router) esIndexPatternAdd(c *gin.Context) {
var f models.EsIndexPattern
ginx.BindJSON(c, &f)
username := c.MustGet("username").(string)
now := time.Now().Unix()
f.CreateAt = now
f.CreateBy = username
f.UpdateAt = now
f.UpdateBy = username
err := f.Add(rt.Ctx)
ginx.NewRender(c).Message(err)
}
// 更新 ES Index Pattern
func (rt *Router) esIndexPatternPut(c *gin.Context) {
var f models.EsIndexPattern
ginx.BindJSON(c, &f)
id := ginx.QueryInt64(c, "id")
esIndexPattern, err := models.EsIndexPatternGetById(rt.Ctx, id)
ginx.Dangerous(err)
if esIndexPattern == nil {
ginx.NewRender(c, http.StatusNotFound).Message("No such EsIndexPattern")
return
}
f.UpdateBy = c.MustGet("username").(string)
ginx.NewRender(c).Message(esIndexPattern.Update(rt.Ctx, f))
}
// 删除 ES Index Pattern
func (rt *Router) esIndexPatternDel(c *gin.Context) {
var f idsForm
ginx.BindJSON(c, &f)
if len(f.Ids) == 0 {
ginx.Bomb(http.StatusBadRequest, "ids empty")
}
ginx.NewRender(c).Message(models.EsIndexPatternDel(rt.Ctx, f.Ids))
}
// ES Index Pattern列表
func (rt *Router) esIndexPatternGetList(c *gin.Context) {
datasourceId := ginx.QueryInt64(c, "datasource_id", 0)
var lst []*models.EsIndexPattern
var err error
if datasourceId != 0 {
lst, err = models.EsIndexPatternGets(rt.Ctx, "datasource_id = ?", datasourceId)
} else {
lst, err = models.EsIndexPatternGets(rt.Ctx, "")
}
ginx.NewRender(c).Data(lst, err)
}
// ES Index Pattern 单个数据
func (rt *Router) esIndexPatternGet(c *gin.Context) {
id := ginx.QueryInt64(c, "id")
item, err := models.EsIndexPatternGet(rt.Ctx, "id=?", id)
ginx.NewRender(c).Data(item, err)
}

View File

@@ -0,0 +1,156 @@
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/toolkits/pkg/ginx"
)
const defaultLimit = 300
func (rt *Router) statistic(c *gin.Context) {
name := ginx.QueryStr(c, "name")
var model interface{}
var err error
var statistics *models.Statistics
switch name {
case "alert_mute":
model = models.AlertMute{}
case "alert_rule":
model = models.AlertRule{}
case "alert_subscribe":
model = models.AlertSubscribe{}
case "busi_group":
model = models.BusiGroup{}
case "recording_rule":
model = models.RecordingRule{}
case "target":
model = models.Target{}
case "user":
model = models.User{}
case "user_group":
model = models.UserGroup{}
case "datasource":
// datasource update_at is different from others
statistics, err = models.DatasourceStatistics(rt.Ctx)
ginx.NewRender(c).Data(statistics, err)
return
default:
ginx.Bomb(http.StatusBadRequest, "invalid name")
}
statistics, err = models.StatisticsGet(rt.Ctx, model)
ginx.NewRender(c).Data(statistics, err)
}
func queryDatasourceIds(c *gin.Context) []int64 {
datasourceIds := ginx.QueryStr(c, "datasource_ids", "")
datasourceIds = strings.ReplaceAll(datasourceIds, ",", " ")
idsStr := strings.Fields(datasourceIds)
ids := make([]int64, len(idsStr))
for i, idStr := range idsStr {
id, _ := strconv.ParseInt(idStr, 10, 64)
ids[i] = id
}
return ids
}
type idsForm struct {
Ids []int64 `json:"ids"`
}
func (f idsForm) Verify() {
if len(f.Ids) == 0 {
ginx.Bomb(http.StatusBadRequest, "ids empty")
}
}
func User(ctx *ctx.Context, id int64) *models.User {
obj, err := models.UserGetById(ctx, id)
ginx.Dangerous(err)
if obj == nil {
ginx.Bomb(http.StatusNotFound, "No such user")
}
return obj
}
func UserGroup(ctx *ctx.Context, id int64) *models.UserGroup {
obj, err := models.UserGroupGetById(ctx, id)
ginx.Dangerous(err)
if obj == nil {
ginx.Bomb(http.StatusNotFound, "No such UserGroup")
}
return obj
}
func BusiGroup(ctx *ctx.Context, id int64) *models.BusiGroup {
obj, err := models.BusiGroupGetById(ctx, id)
ginx.Dangerous(err)
if obj == nil {
ginx.Bomb(http.StatusNotFound, "No such BusiGroup")
}
return obj
}
func Dashboard(ctx *ctx.Context, id int64) *models.Dashboard {
obj, err := models.DashboardGet(ctx, "id=?", id)
ginx.Dangerous(err)
if obj == nil {
ginx.Bomb(http.StatusNotFound, "No such dashboard")
}
return obj
}
type DoneIdsReply struct {
Err string `json:"err"`
Dat struct {
List []int64 `json:"list"`
} `json:"dat"`
}
type TaskCreateReply struct {
Err string `json:"err"`
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
}
if res.Err != "" {
return 0, fmt.Errorf("response.err: %v", res.Err)
}
return res.Dat, nil
}

View File

@@ -0,0 +1,62 @@
package router
import (
"compress/gzip"
"encoding/json"
"io/ioutil"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) heartbeat(c *gin.Context) {
var bs []byte
var err error
var r *gzip.Reader
var req models.HostMeta
if c.GetHeader("Content-Encoding") == "gzip" {
r, err = gzip.NewReader(c.Request.Body)
if err != nil {
c.String(400, err.Error())
return
}
defer r.Close()
bs, err = ioutil.ReadAll(r)
ginx.Dangerous(err)
} else {
defer c.Request.Body.Close()
bs, err = ioutil.ReadAll(c.Request.Body)
ginx.Dangerous(err)
}
err = json.Unmarshal(bs, &req)
ginx.Dangerous(err)
// maybe from pushgw
if req.Offset == 0 {
req.Offset = (time.Now().UnixMilli() - req.UnixTime)
}
if req.RemoteAddr == "" {
req.RemoteAddr = c.ClientIP()
}
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 gid != 0 {
target, has := rt.TargetCache.Get(req.Hostname)
if has && target.GroupId != gid {
err = models.TargetUpdateBgid(rt.Ctx, []string{req.Hostname}, gid, false)
}
}
ginx.NewRender(c).Message(err)
}

View File

@@ -0,0 +1,578 @@
package router
import (
"encoding/base64"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/cas"
"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/secu"
"github.com/pelletier/go-toml/v2"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)
type loginForm struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
Captchaid string `json:"captchaid"`
Verifyvalue string `json:"verifyvalue"`
}
func (rt *Router) loginPost(c *gin.Context) {
var f loginForm
ginx.BindJSON(c, &f)
logger.Infof("username:%s login from:%s", f.Username, c.ClientIP())
if rt.HTTP.ShowCaptcha.Enable {
if !CaptchaVerify(f.Captchaid, f.Verifyvalue) {
ginx.NewRender(c).Message("incorrect verification code")
return
}
}
authPassWord := f.Password
// need decode
if rt.HTTP.RSA.OpenRSA {
decPassWord, err := secu.Decrypt(f.Password, rt.HTTP.RSA.RSAPrivateKey, rt.HTTP.RSA.RSAPassWord)
if err != nil {
logger.Errorf("RSA Decrypt failed: %v username: %s", err, f.Username)
ginx.NewRender(c).Message(err)
return
}
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)
return
}
user.RolesLst = strings.Fields(user.Roles)
} else {
ginx.NewRender(c).Message(err)
return
}
}
if user == nil {
// Theoretically impossible
ginx.NewRender(c).Message("Username or password invalid")
return
}
userIdentity := fmt.Sprintf("%d-%s", user.Id, user.Username)
ts, err := rt.createTokens(rt.HTTP.JWTAuth.SigningKey, userIdentity)
ginx.Dangerous(err)
ginx.Dangerous(rt.createAuth(c.Request.Context(), userIdentity, ts))
ginx.NewRender(c).Data(gin.H{
"user": user,
"access_token": ts.AccessToken,
"refresh_token": ts.RefreshToken,
}, nil)
}
func (rt *Router) logoutPost(c *gin.Context) {
logger.Infof("username:%s login 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")
return
}
delErr := rt.deleteTokens(c.Request.Context(), metadata)
if delErr != nil {
ginx.NewRender(c).Message(http.StatusText(http.StatusInternalServerError))
return
}
ginx.NewRender(c).Message("")
}
type refreshForm struct {
RefreshToken string `json:"refresh_token" binding:"required"`
}
func (rt *Router) refreshPost(c *gin.Context) {
var f refreshForm
ginx.BindJSON(c, &f)
// verify the token
token, err := jwt.Parse(f.RefreshToken, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected jwt signing method: %v", token.Header["alg"])
}
return []byte(rt.HTTP.JWTAuth.SigningKey), nil
})
// if there is an error, the token must have expired
if err != nil {
// redirect to login page
ginx.NewRender(c, http.StatusUnauthorized).Message("refresh token expired")
return
}
// Since token is valid, get the uuid:
claims, ok := token.Claims.(jwt.MapClaims) //the token claims should conform to MapClaims
if ok && token.Valid {
refreshUuid, ok := claims["refresh_uuid"].(string) //convert the interface to string
if !ok {
// Theoretically impossible
ginx.NewRender(c, http.StatusUnauthorized).Message("failed to parse refresh_uuid from jwt")
return
}
userIdentity, ok := claims["user_identity"].(string)
if !ok {
// Theoretically impossible
ginx.NewRender(c, http.StatusUnauthorized).Message("failed to parse user_identity from jwt")
return
}
userid, err := strconv.ParseInt(strings.Split(userIdentity, "-")[0], 10, 64)
if err != nil {
ginx.NewRender(c, http.StatusUnauthorized).Message("failed to parse user_identity from jwt")
return
}
u, err := models.UserGetById(rt.Ctx, userid)
if err != nil {
ginx.NewRender(c, http.StatusInternalServerError).Message("failed to query user by id")
return
}
if u == nil {
// user already deleted
ginx.NewRender(c, http.StatusUnauthorized).Message("user already deleted")
return
}
// Delete the previous Refresh Token
err = rt.deleteAuth(c.Request.Context(), refreshUuid)
if err != nil {
ginx.NewRender(c, http.StatusUnauthorized).Message(http.StatusText(http.StatusInternalServerError))
return
}
// Delete previous Access Token
rt.deleteAuth(c.Request.Context(), strings.Split(refreshUuid, "++")[0])
// Create new pairs of refresh and access tokens
ts, err := rt.createTokens(rt.HTTP.JWTAuth.SigningKey, userIdentity)
ginx.Dangerous(err)
ginx.Dangerous(rt.createAuth(c.Request.Context(), userIdentity, ts))
ginx.NewRender(c).Data(gin.H{
"access_token": ts.AccessToken,
"refresh_token": ts.RefreshToken,
}, nil)
} else {
// redirect to login page
ginx.NewRender(c, http.StatusUnauthorized).Message("refresh token expired")
}
}
func (rt *Router) loginRedirect(c *gin.Context) {
redirect := ginx.QueryStr(c, "redirect", "/")
v, exists := c.Get("userid")
if exists {
userid := v.(int64)
user, err := models.UserGetById(rt.Ctx, userid)
ginx.Dangerous(err)
if user == nil {
ginx.Bomb(200, "user not found")
}
if user.Username != "" { // already login
ginx.NewRender(c).Data(redirect, nil)
return
}
}
if !rt.Sso.OIDC.Enable {
ginx.NewRender(c).Data("", nil)
return
}
redirect, err := rt.Sso.OIDC.Authorize(rt.Redis, redirect)
ginx.Dangerous(err)
ginx.NewRender(c).Data(redirect, err)
}
type CallbackOutput struct {
Redirect string `json:"redirect"`
User *models.User `json:"user"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
func (rt *Router) loginCallback(c *gin.Context) {
code := ginx.QueryStr(c, "code", "")
state := ginx.QueryStr(c, "state", "")
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)
ginx.NewRender(c).Data(CallbackOutput{}, err)
return
}
user, err := models.UserGet(rt.Ctx, "username=?", ret.Username)
ginx.Dangerous(err)
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")
}
} 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",
}
// create user from oidc
ginx.Dangerous(user.Add(rt.Ctx))
}
// set user login state
userIdentity := fmt.Sprintf("%d-%s", user.Id, user.Username)
ts, err := rt.createTokens(rt.HTTP.JWTAuth.SigningKey, userIdentity)
ginx.Dangerous(err)
ginx.Dangerous(rt.createAuth(c.Request.Context(), userIdentity, ts))
redirect := "/"
if ret.Redirect != "/login" {
redirect = ret.Redirect
}
ginx.NewRender(c).Data(CallbackOutput{
Redirect: redirect,
User: user,
AccessToken: ts.AccessToken,
RefreshToken: ts.RefreshToken,
}, nil)
}
type RedirectOutput struct {
Redirect string `json:"redirect"`
State string `json:"state"`
}
func (rt *Router) loginRedirectCas(c *gin.Context) {
redirect := ginx.QueryStr(c, "redirect", "/")
v, exists := c.Get("userid")
if exists {
userid := v.(int64)
user, err := models.UserGetById(rt.Ctx, userid)
ginx.Dangerous(err)
if user == nil {
ginx.Bomb(200, "user not found")
}
if user.Username != "" { // already login
ginx.NewRender(c).Data(redirect, nil)
return
}
}
if !rt.Sso.CAS.Enable {
logger.Error("cas is not enable")
ginx.NewRender(c).Data("", nil)
return
}
redirect, state, err := rt.Sso.CAS.Authorize(rt.Redis, redirect)
ginx.Dangerous(err)
ginx.NewRender(c).Data(RedirectOutput{
Redirect: redirect,
State: state,
}, err)
}
func (rt *Router) loginCallbackCas(c *gin.Context) {
ticket := ginx.QueryStr(c, "ticket", "")
state := ginx.QueryStr(c, "state", "")
ret, err := rt.Sso.CAS.ValidateServiceTicket(c.Request.Context(), ticket, state, rt.Redis)
if err != nil {
logger.Errorf("ValidateServiceTicket: %s", err)
ginx.NewRender(c).Data("", err)
return
}
user, err := models.UserGet(rt.Ctx, "username=?", ret.Username)
if err != nil {
logger.Errorf("UserGet: %s", err)
}
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"))
}
} 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",
}
// create user from cas
ginx.Dangerous(user.Add(rt.Ctx))
}
// set user login state
userIdentity := fmt.Sprintf("%d-%s", user.Id, user.Username)
ts, err := rt.createTokens(rt.HTTP.JWTAuth.SigningKey, userIdentity)
if err != nil {
logger.Errorf("createTokens: %s", err)
}
ginx.Dangerous(err)
ginx.Dangerous(rt.createAuth(c.Request.Context(), userIdentity, ts))
redirect := "/"
if ret.Redirect != "/login" {
redirect = ret.Redirect
}
ginx.NewRender(c).Data(CallbackOutput{
Redirect: redirect,
User: user,
AccessToken: ts.AccessToken,
RefreshToken: ts.RefreshToken,
}, nil)
}
func (rt *Router) loginRedirectOAuth(c *gin.Context) {
redirect := ginx.QueryStr(c, "redirect", "/")
v, exists := c.Get("userid")
if exists {
userid := v.(int64)
user, err := models.UserGetById(rt.Ctx, userid)
ginx.Dangerous(err)
if user == nil {
ginx.Bomb(200, "user not found")
}
if user.Username != "" { // already login
ginx.NewRender(c).Data(redirect, nil)
return
}
}
if !rt.Sso.OAuth2.Enable {
ginx.NewRender(c).Data("", nil)
return
}
redirect, err := rt.Sso.OAuth2.Authorize(rt.Redis, redirect)
ginx.Dangerous(err)
ginx.NewRender(c).Data(redirect, err)
}
func (rt *Router) loginCallbackOAuth(c *gin.Context) {
code := ginx.QueryStr(c, "code", "")
state := ginx.QueryStr(c, "state", "")
ret, err := rt.Sso.OAuth2.Callback(rt.Redis, c.Request.Context(), code, state)
if err != nil {
logger.Debugf("sso.callback() get ret %+v error %v", ret, err)
ginx.NewRender(c).Data(CallbackOutput{}, err)
return
}
user, err := models.UserGet(rt.Ctx, "username=?", ret.Username)
ginx.Dangerous(err)
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")
}
} 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",
}
// create user from oidc
ginx.Dangerous(user.Add(rt.Ctx))
}
// set user login state
userIdentity := fmt.Sprintf("%d-%s", user.Id, user.Username)
ts, err := rt.createTokens(rt.HTTP.JWTAuth.SigningKey, userIdentity)
ginx.Dangerous(err)
ginx.Dangerous(rt.createAuth(c.Request.Context(), userIdentity, ts))
redirect := "/"
if ret.Redirect != "/login" {
redirect = ret.Redirect
}
ginx.NewRender(c).Data(CallbackOutput{
Redirect: redirect,
User: user,
AccessToken: ts.AccessToken,
RefreshToken: ts.RefreshToken,
}, nil)
}
type SsoConfigOutput struct {
OidcDisplayName string `json:"oidcDisplayName"`
CasDisplayName string `json:"casDisplayName"`
OauthDisplayName string `json:"oauthDisplayName"`
}
func (rt *Router) ssoConfigNameGet(c *gin.Context) {
ginx.NewRender(c).Data(SsoConfigOutput{
OidcDisplayName: rt.Sso.OIDC.GetDisplayName(),
CasDisplayName: rt.Sso.CAS.GetDisplayName(),
OauthDisplayName: rt.Sso.OAuth2.GetDisplayName(),
}, nil)
}
func (rt *Router) ssoConfigGets(c *gin.Context) {
ginx.NewRender(c).Data(models.SsoConfigGets(rt.Ctx))
}
func (rt *Router) ssoConfigUpdate(c *gin.Context) {
var f models.SsoConfig
ginx.BindJSON(c, &f)
err := f.Update(rt.Ctx)
ginx.Dangerous(err)
switch f.Name {
case "LDAP":
var config ldapx.Config
err := toml.Unmarshal([]byte(f.Content), &config)
ginx.Dangerous(err)
rt.Sso.LDAP.Reload(config)
case "OIDC":
var config oidcx.Config
err := toml.Unmarshal([]byte(f.Content), &config)
ginx.Dangerous(err)
err = rt.Sso.OIDC.Reload(config)
ginx.Dangerous(err)
case "CAS":
var config cas.Config
err := toml.Unmarshal([]byte(f.Content), &config)
ginx.Dangerous(err)
rt.Sso.CAS.Reload(config)
case "OAuth2":
var config oauth2x.Config
err := toml.Unmarshal([]byte(f.Content), &config)
ginx.Dangerous(err)
rt.Sso.OAuth2.Reload(config)
}
ginx.NewRender(c).Message(nil)
}
type RSAConfigOutput struct {
OpenRSA bool
RSAPublicKey string
}
func (rt *Router) rsaConfigGet(c *gin.Context) {
publicKey := ""
if rt.HTTP.RSA.OpenRSA {
publicKey = base64.StdEncoding.EncodeToString(rt.HTTP.RSA.RSAPublicKey)
}
ginx.NewRender(c).Data(RSAConfigOutput{
OpenRSA: rt.HTTP.RSA.OpenRSA,
RSAPublicKey: publicKey,
}, nil)
}

View File

@@ -0,0 +1,97 @@
package router
import (
"github.com/ccfos/nightingale/v6/center/cconf"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) metricsDescGetFile(c *gin.Context) {
c.JSON(200, rt.Center.MetricDesc)
}
// 前端传过来一个metric数组后端去查询有没有对应的释义返回map
func (rt *Router) metricsDescGetMap(c *gin.Context) {
var arr []string
ginx.BindJSON(c, &arr)
ret := make(map[string]string)
for _, key := range arr {
ret[key] = cconf.GetMetricDesc(c.GetHeader("X-Language"), key)
}
ginx.NewRender(c).Data(ret, nil)
}
// 页面功能暂时先不要了,直接通过配置文件来维护
// func metricDescriptionGets(c *gin.Context) {
// limit := ginx.QueryInt(c, "limit", 20)
// query := ginx.QueryStr(c, "query", "")
// total, err := models.MetricDescriptionTotal(query)
// ginx.Dangerous(err)
// list, err := models.MetricDescriptionGets(query, limit, ginx.Offset(c, limit))
// ginx.Dangerous(err)
// ginx.NewRender(c).Data(gin.H{
// "list": list,
// "total": total,
// }, nil)
// }
// type metricDescriptionAddForm struct {
// Data string `json:"data"`
// }
// func metricDescriptionAdd(c *gin.Context) {
// var f metricDescriptionAddForm
// ginx.BindJSON(c, &f)
// var metricDescriptions []models.MetricDescription
// lines := strings.Split(f.Data, "\n")
// for _, md := range lines {
// arr := strings.SplitN(md, ":", 2)
// if len(arr) != 2 {
// ginx.Bomb(200, "metric description %s is illegal", md)
// }
// m := models.MetricDescription{
// Metric: arr[0],
// Description: arr[1],
// }
// metricDescriptions = append(metricDescriptions, m)
// }
// if len(metricDescriptions) == 0 {
// ginx.Bomb(http.StatusBadRequest, "Decoded metric description empty")
// }
// ginx.NewRender(c).Message(models.MetricDescriptionUpdate(metricDescriptions))
// }
// func metricDescriptionDel(c *gin.Context) {
// var f idsForm
// ginx.BindJSON(c, &f)
// f.Verify()
// ginx.NewRender(c).Message(models.MetricDescriptionDel(f.Ids))
// }
// type metricDescriptionForm struct {
// Description string `json:"description"`
// }
// func metricDescriptionPut(c *gin.Context) {
// var f metricDescriptionForm
// ginx.BindJSON(c, &f)
// md, err := models.MetricDescriptionGet("id=?", ginx.UrlParamInt64(c, "id"))
// ginx.Dangerous(err)
// if md == nil {
// ginx.Bomb(200, "No such metric description")
// }
// ginx.NewRender(c).Message(md.Update(f.Description, time.Now().Unix()))
// }

View File

@@ -0,0 +1,76 @@
package router
import (
"net/http"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
// no param
func (rt *Router) metricViewGets(c *gin.Context) {
lst, err := models.MetricViewGets(rt.Ctx, c.MustGet("userid"))
ginx.NewRender(c).Data(lst, err)
}
// body: name, configs, cate
func (rt *Router) metricViewAdd(c *gin.Context) {
var f models.MetricView
ginx.BindJSON(c, &f)
me := c.MustGet("user").(*models.User)
if !me.IsAdmin() {
// 管理员可以选择当前这个视图是公开呢,还是私有,普通用户的话就只能是私有的
f.Cate = 1
}
f.Id = 0
f.CreateBy = me.Id
ginx.Dangerous(f.Add(rt.Ctx))
ginx.NewRender(c).Data(f, nil)
}
// body: ids
func (rt *Router) metricViewDel(c *gin.Context) {
var f idsForm
ginx.BindJSON(c, &f)
f.Verify()
me := c.MustGet("user").(*models.User)
if me.IsAdmin() {
ginx.NewRender(c).Message(models.MetricViewDel(rt.Ctx, f.Ids))
} else {
ginx.NewRender(c).Message(models.MetricViewDel(rt.Ctx, f.Ids, me.Id))
}
}
// body: id, name, configs, cate
func (rt *Router) metricViewPut(c *gin.Context) {
var f models.MetricView
ginx.BindJSON(c, &f)
view, err := models.MetricViewGet(rt.Ctx, "id = ?", f.Id)
ginx.Dangerous(err)
if view == nil {
ginx.NewRender(c).Message("no such item(id: %d)", f.Id)
return
}
me := c.MustGet("user").(*models.User)
if !me.IsAdmin() {
f.Cate = 1
// 如果是普通用户,只能修改自己的
if view.CreateBy != me.Id {
ginx.NewRender(c, http.StatusForbidden).Message("forbidden")
return
}
}
ginx.NewRender(c).Message(view.Update(rt.Ctx, f.Name, f.Configs, f.Cate, me.Id))
}

View File

@@ -0,0 +1,105 @@
package router
import (
"net/http"
"strings"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
// Return all, front-end search and paging
func (rt *Router) alertMuteGetsByBG(c *gin.Context) {
bgid := ginx.UrlParamInt64(c, "id")
lst, err := models.AlertMuteGetsByBG(rt.Ctx, bgid)
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)
query := ginx.QueryStr(c, "query", "")
lst, err := models.AlertMuteGets(rt.Ctx, prods, bgid, query)
ginx.NewRender(c).Data(lst, err)
}
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))
}
func (rt *Router) alertMuteAddByService(c *gin.Context) {
var f models.AlertMute
ginx.BindJSON(c, &f)
ginx.NewRender(c).Message(f.Add(rt.Ctx))
}
func (rt *Router) alertMuteDel(c *gin.Context) {
var f idsForm
ginx.BindJSON(c, &f)
f.Verify()
ginx.NewRender(c).Message(models.AlertMuteDel(rt.Ctx, f.Ids))
}
func (rt *Router) alertMutePutByFE(c *gin.Context) {
var f models.AlertMute
ginx.BindJSON(c, &f)
amid := ginx.UrlParamInt64(c, "amid")
am, err := models.AlertMuteGetById(rt.Ctx, amid)
ginx.Dangerous(err)
if am == nil {
ginx.NewRender(c, http.StatusNotFound).Message("No such AlertMute")
return
}
rt.bgrwCheck(c, am.GroupId)
f.UpdateBy = c.MustGet("username").(string)
ginx.NewRender(c).Message(am.Update(rt.Ctx, f))
}
type alertMuteFieldForm struct {
Ids []int64 `json:"ids"`
Fields map[string]interface{} `json:"fields"`
}
func (rt *Router) alertMutePutFields(c *gin.Context) {
var f alertMuteFieldForm
ginx.BindJSON(c, &f)
if len(f.Fields) == 0 {
ginx.Bomb(http.StatusBadRequest, "fields empty")
}
f.Fields["update_by"] = c.MustGet("username").(string)
f.Fields["update_at"] = time.Now().Unix()
for i := 0; i < len(f.Ids); i++ {
am, err := models.AlertMuteGetById(rt.Ctx, f.Ids[i])
ginx.Dangerous(err)
if am == nil {
continue
}
am.FE2DB()
ginx.Dangerous(am.UpdateFieldsMap(rt.Ctx, f.Fields))
}
ginx.NewRender(c).Message(nil)
}

422
center/router/router_mw.go Normal file
View File

@@ -0,0 +1,422 @@
package router
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
"github.com/google/uuid"
"github.com/toolkits/pkg/ginx"
)
type AccessDetails struct {
AccessUuid string
UserIdentity string
}
func (rt *Router) handleProxyUser(c *gin.Context) *models.User {
headerUserNameKey := rt.HTTP.ProxyAuth.HeaderUserNameKey
username := c.GetHeader(headerUserNameKey)
if username == "" {
ginx.Bomb(http.StatusUnauthorized, "unauthorized")
}
user, err := models.UserGetByUsername(rt.Ctx, username)
if err != nil {
ginx.Bomb(http.StatusInternalServerError, err.Error())
}
if user == nil {
now := time.Now().Unix()
user = &models.User{
Username: username,
Nickname: username,
Roles: strings.Join(rt.HTTP.ProxyAuth.DefaultRoles, " "),
CreateAt: now,
UpdateAt: now,
CreateBy: "system",
UpdateBy: "system",
}
err = user.Add(rt.Ctx)
if err != nil {
ginx.Bomb(http.StatusInternalServerError, err.Error())
}
}
return user
}
func (rt *Router) proxyAuth() gin.HandlerFunc {
return func(c *gin.Context) {
user := rt.handleProxyUser(c)
c.Set("userid", user.Id)
c.Set("username", user.Username)
c.Next()
}
}
func (rt *Router) jwtAuth() gin.HandlerFunc {
return func(c *gin.Context) {
metadata, err := rt.extractTokenMetadata(c.Request)
if err != nil {
ginx.Bomb(http.StatusUnauthorized, "unauthorized")
}
userIdentity, err := rt.fetchAuth(c.Request.Context(), metadata.AccessUuid)
if err != nil {
ginx.Bomb(http.StatusUnauthorized, "unauthorized")
}
// ${userid}-${username}
arr := strings.SplitN(userIdentity, "-", 2)
if len(arr) != 2 {
ginx.Bomb(http.StatusUnauthorized, "unauthorized")
}
userid, err := strconv.ParseInt(arr[0], 10, 64)
if err != nil {
ginx.Bomb(http.StatusUnauthorized, "unauthorized")
}
c.Set("userid", userid)
c.Set("username", arr[1])
c.Next()
}
}
func (rt *Router) auth() gin.HandlerFunc {
if rt.HTTP.ProxyAuth.Enable {
return rt.proxyAuth()
} else {
return rt.jwtAuth()
}
}
// if proxy auth is enabled, mock jwt login/logout/refresh request
func (rt *Router) jwtMock() gin.HandlerFunc {
return func(c *gin.Context) {
if !rt.HTTP.ProxyAuth.Enable {
c.Next()
return
}
if strings.Contains(c.FullPath(), "logout") {
ginx.Bomb(http.StatusBadRequest, "logout is not supported when proxy auth is enabled")
}
user := rt.handleProxyUser(c)
ginx.NewRender(c).Data(gin.H{
"user": user,
"access_token": "",
"refresh_token": "",
}, nil)
c.Abort()
}
}
func (rt *Router) user() gin.HandlerFunc {
return func(c *gin.Context) {
userid := c.MustGet("userid").(int64)
user, err := models.UserGetById(rt.Ctx, userid)
if err != nil {
ginx.Bomb(http.StatusUnauthorized, "unauthorized")
}
if user == nil {
ginx.Bomb(http.StatusUnauthorized, "unauthorized")
}
c.Set("user", user)
c.Set("isadmin", user.IsAdmin())
c.Next()
}
}
func (rt *Router) userGroupWrite() gin.HandlerFunc {
return func(c *gin.Context) {
me := c.MustGet("user").(*models.User)
ug := UserGroup(rt.Ctx, ginx.UrlParamInt64(c, "id"))
can, err := me.CanModifyUserGroup(rt.Ctx, ug)
ginx.Dangerous(err)
if !can {
ginx.Bomb(http.StatusForbidden, "forbidden")
}
c.Set("user_group", ug)
c.Next()
}
}
func (rt *Router) bgro() gin.HandlerFunc {
return func(c *gin.Context) {
me := c.MustGet("user").(*models.User)
bg := BusiGroup(rt.Ctx, ginx.UrlParamInt64(c, "id"))
can, err := me.CanDoBusiGroup(rt.Ctx, bg)
ginx.Dangerous(err)
if !can {
ginx.Bomb(http.StatusForbidden, "forbidden")
}
c.Set("busi_group", bg)
c.Next()
}
}
// bgrw 逐步要被干掉,不安全
func (rt *Router) bgrw() gin.HandlerFunc {
return func(c *gin.Context) {
me := c.MustGet("user").(*models.User)
bg := BusiGroup(rt.Ctx, ginx.UrlParamInt64(c, "id"))
can, err := me.CanDoBusiGroup(rt.Ctx, bg, "rw")
ginx.Dangerous(err)
if !can {
ginx.Bomb(http.StatusForbidden, "forbidden")
}
c.Set("busi_group", bg)
c.Next()
}
}
// bgrwCheck 要逐渐替换掉bgrw方法更安全
func (rt *Router) bgrwCheck(c *gin.Context, bgid int64) {
me := c.MustGet("user").(*models.User)
bg := BusiGroup(rt.Ctx, bgid)
can, err := me.CanDoBusiGroup(rt.Ctx, bg, "rw")
ginx.Dangerous(err)
if !can {
ginx.Bomb(http.StatusForbidden, "forbidden")
}
c.Set("busi_group", bg)
}
func (rt *Router) bgrwChecks(c *gin.Context, bgids []int64) {
set := make(map[int64]struct{})
for i := 0; i < len(bgids); i++ {
if _, has := set[bgids[i]]; has {
continue
}
rt.bgrwCheck(c, bgids[i])
set[bgids[i]] = struct{}{}
}
}
func (rt *Router) bgroCheck(c *gin.Context, bgid int64) {
me := c.MustGet("user").(*models.User)
bg := BusiGroup(rt.Ctx, bgid)
can, err := me.CanDoBusiGroup(rt.Ctx, bg)
ginx.Dangerous(err)
if !can {
ginx.Bomb(http.StatusForbidden, "forbidden")
}
c.Set("busi_group", bg)
}
func (rt *Router) perm(operation string) gin.HandlerFunc {
return func(c *gin.Context) {
me := c.MustGet("user").(*models.User)
can, err := me.CheckPerm(rt.Ctx, operation)
ginx.Dangerous(err)
if !can {
ginx.Bomb(http.StatusForbidden, "forbidden")
}
c.Next()
}
}
func (rt *Router) admin() gin.HandlerFunc {
return func(c *gin.Context) {
userid := c.MustGet("userid").(int64)
user, err := models.UserGetById(rt.Ctx, userid)
if err != nil {
ginx.Bomb(http.StatusUnauthorized, "unauthorized")
}
if user == nil {
ginx.Bomb(http.StatusUnauthorized, "unauthorized")
}
roles := strings.Fields(user.Roles)
found := false
for i := 0; i < len(roles); i++ {
if roles[i] == models.AdminRole {
found = true
break
}
}
if !found {
ginx.Bomb(http.StatusForbidden, "forbidden")
}
c.Set("user", user)
c.Next()
}
}
func (rt *Router) extractTokenMetadata(r *http.Request) (*AccessDetails, error) {
token, err := rt.verifyToken(rt.HTTP.JWTAuth.SigningKey, rt.extractToken(r))
if err != nil {
return nil, err
}
claims, ok := token.Claims.(jwt.MapClaims)
if ok && token.Valid {
accessUuid, ok := claims["access_uuid"].(string)
if !ok {
return nil, errors.New("failed to parse access_uuid from jwt")
}
return &AccessDetails{
AccessUuid: accessUuid,
UserIdentity: claims["user_identity"].(string),
}, nil
}
return nil, err
}
func (rt *Router) extractToken(r *http.Request) string {
tok := r.Header.Get("Authorization")
if len(tok) > 6 && strings.ToUpper(tok[0:7]) == "BEARER " {
return tok[7:]
}
return ""
}
func (rt *Router) createAuth(ctx context.Context, userIdentity string, td *TokenDetails) error {
at := time.Unix(td.AtExpires, 0)
rte := time.Unix(td.RtExpires, 0)
now := time.Now()
errAccess := rt.Redis.Set(ctx, rt.wrapJwtKey(td.AccessUuid), userIdentity, at.Sub(now)).Err()
if errAccess != nil {
return errAccess
}
errRefresh := rt.Redis.Set(ctx, rt.wrapJwtKey(td.RefreshUuid), userIdentity, rte.Sub(now)).Err()
if errRefresh != nil {
return errRefresh
}
return nil
}
func (rt *Router) fetchAuth(ctx context.Context, givenUuid string) (string, error) {
return rt.Redis.Get(ctx, rt.wrapJwtKey(givenUuid)).Result()
}
func (rt *Router) deleteAuth(ctx context.Context, givenUuid string) error {
return rt.Redis.Del(ctx, rt.wrapJwtKey(givenUuid)).Err()
}
func (rt *Router) deleteTokens(ctx context.Context, authD *AccessDetails) error {
// get the refresh uuid
refreshUuid := authD.AccessUuid + "++" + authD.UserIdentity
// delete access token
err := rt.Redis.Del(ctx, rt.wrapJwtKey(authD.AccessUuid)).Err()
if err != nil {
return err
}
// delete refresh token
err = rt.Redis.Del(ctx, rt.wrapJwtKey(refreshUuid)).Err()
if err != nil {
return err
}
return nil
}
func (rt *Router) wrapJwtKey(key string) string {
return rt.HTTP.JWTAuth.RedisKeyPrefix + key
}
type TokenDetails struct {
AccessToken string
RefreshToken string
AccessUuid string
RefreshUuid string
AtExpires int64
RtExpires int64
}
func (rt *Router) createTokens(signingKey, userIdentity string) (*TokenDetails, error) {
td := &TokenDetails{}
td.AtExpires = time.Now().Add(time.Minute * time.Duration(rt.HTTP.JWTAuth.AccessExpired)).Unix()
td.AccessUuid = uuid.NewString()
td.RtExpires = time.Now().Add(time.Minute * time.Duration(rt.HTTP.JWTAuth.RefreshExpired)).Unix()
td.RefreshUuid = td.AccessUuid + "++" + userIdentity
var err error
// Creating Access Token
atClaims := jwt.MapClaims{}
atClaims["authorized"] = true
atClaims["access_uuid"] = td.AccessUuid
atClaims["user_identity"] = userIdentity
atClaims["exp"] = td.AtExpires
at := jwt.NewWithClaims(jwt.SigningMethodHS256, atClaims)
td.AccessToken, err = at.SignedString([]byte(signingKey))
if err != nil {
return nil, err
}
// Creating Refresh Token
rtClaims := jwt.MapClaims{}
rtClaims["refresh_uuid"] = td.RefreshUuid
rtClaims["user_identity"] = userIdentity
rtClaims["exp"] = td.RtExpires
jrt := jwt.NewWithClaims(jwt.SigningMethodHS256, rtClaims)
td.RefreshToken, err = jrt.SignedString([]byte(signingKey))
if err != nil {
return nil, err
}
return td, nil
}
func (rt *Router) verifyToken(signingKey, tokenString string) (*jwt.Token, error) {
if tokenString == "" {
return nil, fmt.Errorf("bearer token not found")
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected jwt signing method: %v", token.Header["alg"])
}
return []byte(signingKey), nil
})
if err != nil {
return nil, err
}
return token, nil
}

View File

@@ -0,0 +1,193 @@
package router
import (
"encoding/json"
"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/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) webhookGets(c *gin.Context) {
var webhooks []models.Webhook
cval, err := models.ConfigsGet(rt.Ctx, models.WEBHOOKKEY)
ginx.Dangerous(err)
if cval == "" {
ginx.NewRender(c).Data(webhooks, nil)
return
}
err = json.Unmarshal([]byte(cval), &webhooks)
ginx.NewRender(c).Data(webhooks, err)
}
func (rt *Router) webhookPuts(c *gin.Context) {
var webhooks []models.Webhook
ginx.BindJSON(c, &webhooks)
for i := 0; i < len(webhooks); i++ {
webhooks[i].Headers = []string{}
if len(webhooks[i].HeaderMap) > 0 {
for k, v := range webhooks[i].HeaderMap {
webhooks[i].Headers = append(webhooks[i].Headers, k)
webhooks[i].Headers = append(webhooks[i].Headers, v)
}
}
}
data, err := json.Marshal(webhooks)
ginx.Dangerous(err)
ginx.NewRender(c).Message(models.ConfigsSet(rt.Ctx, models.WEBHOOKKEY, string(data)))
}
func (rt *Router) notifyScriptGet(c *gin.Context) {
var notifyScript models.NotifyScript
cval, err := models.ConfigsGet(rt.Ctx, models.NOTIFYSCRIPT)
ginx.Dangerous(err)
if cval == "" {
ginx.NewRender(c).Data(notifyScript, nil)
return
}
err = json.Unmarshal([]byte(cval), &notifyScript)
ginx.NewRender(c).Data(notifyScript, err)
}
func (rt *Router) notifyScriptPut(c *gin.Context) {
var notifyScript models.NotifyScript
ginx.BindJSON(c, &notifyScript)
data, err := json.Marshal(notifyScript)
ginx.Dangerous(err)
ginx.NewRender(c).Message(models.ConfigsSet(rt.Ctx, models.NOTIFYSCRIPT, string(data)))
}
func (rt *Router) notifyChannelGets(c *gin.Context) {
var notifyChannels []models.NotifyChannel
cval, err := models.ConfigsGet(rt.Ctx, models.NOTIFYCHANNEL)
ginx.Dangerous(err)
if cval == "" {
ginx.NewRender(c).Data(notifyChannels, nil)
return
}
err = json.Unmarshal([]byte(cval), &notifyChannels)
ginx.NewRender(c).Data(notifyChannels, err)
}
func (rt *Router) notifyChannelPuts(c *gin.Context) {
var notifyChannels []models.NotifyChannel
ginx.BindJSON(c, &notifyChannels)
channels := []string{models.Dingtalk, models.Wecom, models.Feishu, models.Mm, models.Telegram, models.Email}
m := make(map[string]struct{})
for _, v := range notifyChannels {
m[v.Ident] = struct{}{}
}
for _, v := range channels {
if _, ok := m[v]; !ok {
ginx.Bomb(200, "channel %s ident can not modify", v)
}
}
data, err := json.Marshal(notifyChannels)
ginx.Dangerous(err)
ginx.NewRender(c).Message(models.ConfigsSet(rt.Ctx, models.NOTIFYCHANNEL, string(data)))
}
func (rt *Router) notifyContactGets(c *gin.Context) {
var notifyContacts []models.NotifyContact
cval, err := models.ConfigsGet(rt.Ctx, models.NOTIFYCONTACT)
ginx.Dangerous(err)
if cval == "" {
ginx.NewRender(c).Data(notifyContacts, nil)
return
}
err = json.Unmarshal([]byte(cval), &notifyContacts)
ginx.NewRender(c).Data(notifyContacts, err)
}
func (rt *Router) notifyContactPuts(c *gin.Context) {
var notifyContacts []models.NotifyContact
ginx.BindJSON(c, &notifyContacts)
keys := []string{models.DingtalkKey, models.WecomKey, models.FeishuKey, models.MmKey, models.TelegramKey}
m := make(map[string]struct{})
for _, v := range notifyContacts {
m[v.Ident] = struct{}{}
}
for _, v := range keys {
if _, ok := m[v]; !ok {
ginx.Bomb(200, "contact %s ident can not modify", v)
}
}
data, err := json.Marshal(notifyContacts)
ginx.Dangerous(err)
ginx.NewRender(c).Message(models.ConfigsSet(rt.Ctx, models.NOTIFYCONTACT, string(data)))
}
func (rt *Router) notifyConfigGet(c *gin.Context) {
key := ginx.QueryStr(c, "ckey")
cval, err := models.ConfigsGet(rt.Ctx, key)
if cval == "" {
switch key {
case models.IBEX:
cval = memsto.DefaultIbex
case models.SMTP:
cval = memsto.DefaultSMTP
}
}
ginx.NewRender(c).Data(cval, err)
}
func (rt *Router) notifyConfigPut(c *gin.Context) {
var f models.Configs
ginx.BindJSON(c, &f)
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)
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())
}
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")
}
go sender.RestartEmailSender(smtp)
}
ginx.NewRender(c).Message(nil)
}

View File

@@ -0,0 +1,148 @@
package router
import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"strings"
"github.com/ccfos/nightingale/v6/center/cconf"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/tplx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/str"
)
func (rt *Router) notifyTplGets(c *gin.Context) {
m := make(map[string]struct{})
for _, channel := range models.DefaultChannels {
m[channel] = struct{}{}
}
m["mailsubject"] = struct{}{}
lst, err := models.NotifyTplGets(rt.Ctx)
for i := 0; i < len(lst); i++ {
if _, exists := m[lst[i].Channel]; exists {
lst[i].BuiltIn = true
}
}
ginx.NewRender(c).Data(lst, err)
}
func (rt *Router) notifyTplUpdateContent(c *gin.Context) {
var f models.NotifyTpl
ginx.BindJSON(c, &f)
ginx.Dangerous(templateValidate(f))
ginx.NewRender(c).Message(f.UpdateContent(rt.Ctx))
}
func (rt *Router) notifyTplUpdate(c *gin.Context) {
var f models.NotifyTpl
ginx.BindJSON(c, &f)
ginx.Dangerous(templateValidate(f))
ginx.NewRender(c).Message(f.Update(rt.Ctx))
}
func templateValidate(f models.NotifyTpl) error {
if len(f.Channel) > 32 {
return fmt.Errorf("channel length should not exceed 32")
}
if str.Dangerous(f.Channel) {
return fmt.Errorf("channel should not contain dangerous characters")
}
if len(f.Name) > 255 {
return fmt.Errorf("name length should not exceed 255")
}
if str.Dangerous(f.Name) {
return fmt.Errorf("name should not contain dangerous characters")
}
if f.Content == "" {
return nil
}
var defs = []string{
"{{$labels := .TagsMap}}",
"{{$value := .TriggerValue}}",
}
text := strings.Join(append(defs, f.Content), "")
if _, err := template.New(f.Channel).Funcs(tplx.TemplateFuncMap).Parse(text); err != nil {
return fmt.Errorf("notify template verify illegal:%s", err.Error())
}
return nil
}
func (rt *Router) notifyTplPreview(c *gin.Context) {
var event models.AlertCurEvent
err := json.Unmarshal([]byte(cconf.EVENT_EXAMPLE), &event)
ginx.Dangerous(err)
var f models.NotifyTpl
ginx.BindJSON(c, &f)
var defs = []string{
"{{$labels := .TagsMap}}",
"{{$value := .TriggerValue}}",
}
text := strings.Join(append(defs, f.Content), "")
tpl, err := template.New(f.Channel).Funcs(tplx.TemplateFuncMap).Parse(text)
ginx.Dangerous(err)
event.TagsMap = make(map[string]string)
for i := 0; i < len(event.TagsJSON); i++ {
pair := strings.TrimSpace(event.TagsJSON[i])
if pair == "" {
continue
}
arr := strings.Split(pair, "=")
if len(arr) != 2 {
continue
}
event.TagsMap[arr[0]] = arr[1]
}
var body bytes.Buffer
var ret string
if err := tpl.Execute(&body, event); err != nil {
ret = err.Error()
} else {
ret = body.String()
}
ginx.NewRender(c).Data(ret, nil)
}
// add new notify template
func (rt *Router) notifyTplAdd(c *gin.Context) {
var f models.NotifyTpl
ginx.BindJSON(c, &f)
f.Channel = strings.TrimSpace(f.Channel)
ginx.Dangerous(templateValidate(f))
count, err := models.NotifyTplCountByChannel(rt.Ctx, f.Channel)
ginx.Dangerous(err)
if count != 0 {
ginx.Bomb(200, "Refuse to create duplicate channel(unique)")
}
ginx.NewRender(c).Message(f.Create(rt.Ctx))
}
// delete notify template, not allowed to delete the system defaults(models.DefaultChannels)
func (rt *Router) notifyTplDel(c *gin.Context) {
f := new(models.NotifyTpl)
id := ginx.UrlParamInt64(c, "id")
ginx.NewRender(c).Message(f.NotifyTplDelete(rt.Ctx, id))
}

View File

@@ -0,0 +1,215 @@
package router
import (
"context"
"crypto/tls"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"sync"
"time"
pkgprom "github.com/ccfos/nightingale/v6/pkg/prom"
"github.com/gin-gonic/gin"
"github.com/prometheus/common/model"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)
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 {
DatasourceId int64 `json:"datasource_id" binding:"required"`
Queries []queryFormItem `json:"queries" binding:"required"`
}
func (rt *Router) promBatchQueryRange(c *gin.Context) {
var f batchQueryForm
ginx.Dangerous(c.BindJSON(&f))
var lst []model.Value
cli := rt.PromClients.GetCli(f.DatasourceId)
if cli == nil {
logger.Warningf("no such datasource id: %d", f.DatasourceId)
ginx.NewRender(c).Data(lst, nil)
return
}
for _, item := range f.Queries {
r := pkgprom.Range{
Start: time.Unix(item.Start, 0),
End: time.Unix(item.End, 0),
Step: time.Duration(item.Step) * time.Second,
}
resp, _, err := cli.QueryRange(context.Background(), item.Query, r)
ginx.Dangerous(err)
lst = append(lst, resp)
}
ginx.NewRender(c).Data(lst, nil)
}
type batchInstantForm struct {
DatasourceId int64 `json:"datasource_id" binding:"required"`
Queries []InstantFormItem `json:"queries" binding:"required"`
}
type InstantFormItem struct {
Time int64 `json:"time" binding:"required"`
Query string `json:"query" binding:"required"`
}
func (rt *Router) promBatchQueryInstant(c *gin.Context) {
var f batchInstantForm
ginx.Dangerous(c.BindJSON(&f))
var lst []model.Value
cli := rt.PromClients.GetCli(f.DatasourceId)
if cli == nil {
logger.Warningf("no such datasource id: %d", f.DatasourceId)
ginx.NewRender(c).Data(lst, nil)
return
}
for _, item := range f.Queries {
resp, _, err := cli.Query(context.Background(), item.Query, time.Unix(item.Time, 0))
ginx.Dangerous(err)
lst = append(lst, resp)
}
ginx.NewRender(c).Data(lst, nil)
}
func (rt *Router) dsProxy(c *gin.Context) {
dsId := ginx.UrlParamInt64(c, "id")
ds := rt.DatasourceCache.GetById(dsId)
if ds == nil {
c.String(http.StatusBadRequest, "no such datasource")
return
}
target, err := url.Parse(ds.HTTPJson.Url)
if err != nil {
c.String(http.StatusInternalServerError, "invalid url: %s", ds.HTTPJson.Url)
return
}
director := func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.Host = target.Host
req.Header.Set("Host", target.Host)
// fe request e.g. /api/n9e/proxy/:id/*
arr := strings.Split(req.URL.Path, "/")
if len(arr) < 6 {
c.String(http.StatusBadRequest, "invalid url path")
return
}
req.URL.Path = strings.TrimRight(target.Path, "/") + "/" + strings.Join(arr[5:], "/")
if target.RawQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = target.RawQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = target.RawQuery + "&" + req.URL.RawQuery
}
if _, ok := req.Header["User-Agent"]; !ok {
req.Header.Set("User-Agent", "")
}
if ds.AuthJson.BasicAuthUser != "" {
req.SetBasicAuth(ds.AuthJson.BasicAuthUser, ds.AuthJson.BasicAuthPassword)
}
headerCount := len(ds.HTTPJson.Headers)
if headerCount > 0 {
for key, value := range ds.HTTPJson.Headers {
req.Header.Set(key, value)
if key == "Host" {
req.Host = value
}
}
}
}
errFunc := func(w http.ResponseWriter, r *http.Request, err error) {
http.Error(w, err.Error(), http.StatusBadGateway)
}
transport, has := transportGet(dsId, ds.UpdatedAt)
if !has {
transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: ds.HTTPJson.TLS.SkipTlsVerify},
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: time.Duration(ds.HTTPJson.DialTimeout) * time.Millisecond,
}).DialContext,
ResponseHeaderTimeout: time.Duration(ds.HTTPJson.Timeout) * time.Millisecond,
MaxIdleConnsPerHost: ds.HTTPJson.MaxIdleConnsPerHost,
}
transportPut(dsId, ds.UpdatedAt, transport)
}
proxy := &httputil.ReverseProxy{
Director: director,
Transport: transport,
ErrorHandler: errFunc,
}
proxy.ServeHTTP(c.Writer, c.Request)
}
var (
transports = map[int64]http.RoundTripper{}
updatedAts = map[int64]int64{}
transportsLock = &sync.Mutex{}
)
func transportGet(dsid, newUpdatedAt int64) (http.RoundTripper, bool) {
transportsLock.Lock()
defer transportsLock.Unlock()
tran, has := transports[dsid]
if !has {
return nil, false
}
oldUpdateAt, has := updatedAts[dsid]
if !has {
oldtran := tran.(*http.Transport)
oldtran.CloseIdleConnections()
delete(transports, dsid)
return nil, false
}
if oldUpdateAt != newUpdatedAt {
oldtran := tran.(*http.Transport)
oldtran.CloseIdleConnections()
delete(transports, dsid)
delete(updatedAts, dsid)
return nil, false
}
return tran, has
}
func transportPut(dsid, updatedat int64, tran http.RoundTripper) {
transportsLock.Lock()
transports[dsid] = tran
updatedAts[dsid] = updatedat
transportsLock.Unlock()
}

View File

@@ -0,0 +1,146 @@
package router
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) recordingRuleGets(c *gin.Context) {
busiGroupId := ginx.UrlParamInt64(c, "id")
ars, err := models.RecordingRuleGets(rt.Ctx, busiGroupId)
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)
}
func (rt *Router) recordingRuleGet(c *gin.Context) {
rrid := ginx.UrlParamInt64(c, "rrid")
ar, err := models.RecordingRuleGetById(rt.Ctx, rrid)
ginx.Dangerous(err)
if ar == nil {
ginx.NewRender(c, http.StatusNotFound).Message("No such recording rule")
return
}
ginx.NewRender(c).Data(ar, err)
}
func (rt *Router) recordingRuleAddByFE(c *gin.Context) {
username := c.MustGet("username").(string)
var lst []models.RecordingRule
ginx.BindJSON(c, &lst)
count := len(lst)
if count == 0 {
ginx.Bomb(http.StatusBadRequest, "input json is empty")
}
bgid := ginx.UrlParamInt64(c, "id")
reterr := make(map[string]string)
for i := 0; i < count; i++ {
lst[i].Id = 0
lst[i].GroupId = bgid
lst[i].CreateBy = username
lst[i].UpdateBy = username
lst[i].FE2DB()
if err := lst[i].Add(rt.Ctx); err != nil {
reterr[lst[i].Name] = err.Error()
} else {
reterr[lst[i].Name] = ""
}
}
ginx.NewRender(c).Data(reterr, nil)
}
func (rt *Router) recordingRulePutByFE(c *gin.Context) {
var f models.RecordingRule
ginx.BindJSON(c, &f)
rrid := ginx.UrlParamInt64(c, "rrid")
ar, err := models.RecordingRuleGetById(rt.Ctx, rrid)
ginx.Dangerous(err)
if ar == nil {
ginx.NewRender(c, http.StatusNotFound).Message("No such recording rule")
return
}
rt.bgrwCheck(c, ar.GroupId)
f.UpdateBy = c.MustGet("username").(string)
ginx.NewRender(c).Message(ar.Update(rt.Ctx, f))
}
func (rt *Router) recordingRuleDel(c *gin.Context) {
var f idsForm
ginx.BindJSON(c, &f)
f.Verify()
ginx.NewRender(c).Message(models.RecordingRuleDels(rt.Ctx, f.Ids, ginx.UrlParamInt64(c, "id")))
}
type recordRuleFieldForm struct {
Ids []int64 `json:"ids"`
Fields map[string]interface{} `json:"fields"`
}
func (rt *Router) recordingRulePutFields(c *gin.Context) {
var f recordRuleFieldForm
ginx.BindJSON(c, &f)
if len(f.Fields) == 0 {
ginx.Bomb(http.StatusBadRequest, "fields empty")
}
f.Fields["update_by"] = c.MustGet("username").(string)
f.Fields["update_at"] = time.Now().Unix()
if _, ok := f.Fields["datasource_ids"]; ok {
// datasource_ids = "1 2 3"
idsStr := strings.Fields(f.Fields["datasource_ids"].(string))
ids := make([]int64, 0)
for _, idStr := range idsStr {
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
ginx.Bomb(http.StatusBadRequest, "datasource_ids error")
}
ids = append(ids, id)
}
bs, err := json.Marshal(ids)
if err != nil {
ginx.Bomb(http.StatusBadRequest, "datasource_ids error")
}
f.Fields["datasource_ids"] = string(bs)
}
for i := 0; i < len(f.Ids); i++ {
ar, err := models.RecordingRuleGetById(rt.Ctx, f.Ids[i])
ginx.Dangerous(err)
if ar == nil {
continue
}
ginx.Dangerous(ar.UpdateFieldsMap(rt.Ctx, f.Fields))
}
ginx.NewRender(c).Message(nil)
}

View File

@@ -0,0 +1,100 @@
package router
import (
"net/http"
"strings"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) rolesGets(c *gin.Context) {
lst, err := models.RoleGetsAll(rt.Ctx)
ginx.NewRender(c).Data(lst, err)
}
func (rt *Router) permsGets(c *gin.Context) {
user := c.MustGet("user").(*models.User)
lst, err := models.OperationsOfRole(rt.Ctx, strings.Fields(user.Roles))
ginx.NewRender(c).Data(lst, err)
}
// 创建角色
func (rt *Router) roleAdd(c *gin.Context) {
var f models.Role
ginx.BindJSON(c, &f)
err := f.Add(rt.Ctx)
ginx.NewRender(c).Message(err)
}
// 更新角色
func (rt *Router) rolePut(c *gin.Context) {
var f models.Role
ginx.BindJSON(c, &f)
oldRule, err := models.RoleGet(rt.Ctx, "id=?", f.Id)
ginx.Dangerous(err)
if oldRule == nil {
ginx.Bomb(http.StatusOK, "role not found")
}
if oldRule.Name == "Admin" {
ginx.Bomb(http.StatusOK, "admin role can not be modified")
}
if oldRule.Name != f.Name {
// name changed, check duplication
num, err := models.RoleCount(rt.Ctx, "name=? and id<>?", f.Name, oldRule.Id)
ginx.Dangerous(err)
if num > 0 {
ginx.Bomb(http.StatusOK, "role name already exists")
}
}
oldRule.Name = f.Name
oldRule.Note = f.Note
ginx.NewRender(c).Message(oldRule.Update(rt.Ctx, "name", "note"))
}
func (rt *Router) roleDel(c *gin.Context) {
id := ginx.UrlParamInt64(c, "id")
target, err := models.RoleGet(rt.Ctx, "id=?", id)
ginx.Dangerous(err)
if target.Name == "Admin" {
ginx.Bomb(http.StatusOK, "admin role can not be modified")
}
if target == nil {
ginx.NewRender(c).Message(nil)
return
}
ginx.NewRender(c).Message(target.Del(rt.Ctx))
}
// 角色列表
func (rt *Router) roleGets(c *gin.Context) {
lst, err := models.RoleGetsAll(rt.Ctx)
ginx.NewRender(c).Data(lst, err)
}
func (rt *Router) allPerms(c *gin.Context) {
roles, err := models.RoleGetsAll(rt.Ctx)
ginx.Dangerous(err)
m := make(map[string][]string)
for _, r := range roles {
lst, err := models.OperationsOfRole(rt.Ctx, strings.Fields(r.Name))
if err != nil {
continue
}
m[r.Name] = lst
}
ginx.NewRender(c).Data(m, err)
}

View File

@@ -0,0 +1,43 @@
package router
import (
"net/http"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) operationOfRole(c *gin.Context) {
id := ginx.UrlParamInt64(c, "id")
role, err := models.RoleGet(rt.Ctx, "id=?", id)
ginx.Dangerous(err)
if role == nil {
ginx.Bomb(http.StatusOK, "role not found")
}
ops, err := models.OperationsOfRole(rt.Ctx, []string{role.Name})
ginx.NewRender(c).Data(ops, err)
}
func (rt *Router) roleBindOperation(c *gin.Context) {
id := ginx.UrlParamInt64(c, "id")
role, err := models.RoleGet(rt.Ctx, "id=?", id)
ginx.Dangerous(err)
if role == nil {
ginx.Bomb(http.StatusOK, "role not found")
}
if role.Name == "Admin" {
ginx.Bomb(http.StatusOK, "admin role can not be modified")
}
var ops []string
ginx.BindJSON(c, &ops)
ginx.NewRender(c).Message(models.RoleOperationBind(rt.Ctx, role.Name, ops))
}
func (rt *Router) operations(c *gin.Context) {
ginx.NewRender(c).Data(rt.Operations.Ops, nil)
}

View File

@@ -0,0 +1,52 @@
package router
import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ormx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) selfProfileGet(c *gin.Context) {
user := c.MustGet("user").(*models.User)
if user.IsAdmin() {
user.Admin = true
}
ginx.NewRender(c).Data(user, nil)
}
type selfProfileForm struct {
Nickname string `json:"nickname"`
Phone string `json:"phone"`
Email string `json:"email"`
Portrait string `json:"portrait"`
Contacts ormx.JSONObj `json:"contacts"`
}
func (rt *Router) selfProfilePut(c *gin.Context) {
var f selfProfileForm
ginx.BindJSON(c, &f)
user := c.MustGet("user").(*models.User)
user.Nickname = f.Nickname
user.Phone = f.Phone
user.Email = f.Email
user.Portrait = f.Portrait
user.Contacts = f.Contacts
user.UpdateBy = user.Username
ginx.NewRender(c).Message(user.UpdateAllFields(rt.Ctx))
}
type selfPasswordForm struct {
OldPass string `json:"oldpass" binding:"required"`
NewPass string `json:"newpass" binding:"required"`
}
func (rt *Router) selfPasswordPut(c *gin.Context) {
var f selfPasswordForm
ginx.BindJSON(c, &f)
user := c.MustGet("user").(*models.User)
ginx.NewRender(c).Message(user.ChangePassword(rt.Ctx, f.OldPass, f.NewPass))
}

View File

@@ -0,0 +1,40 @@
package router
import (
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) serversGet(c *gin.Context) {
list, err := models.AlertingEngineGets(rt.Ctx, "")
ginx.NewRender(c).Data(list, err)
}
func (rt *Router) serverClustersGet(c *gin.Context) {
list, err := models.AlertingEngineGetsClusters(rt.Ctx, "")
ginx.NewRender(c).Data(list, err)
}
func (rt *Router) serverHeartbeat(c *gin.Context) {
var req models.HeartbeatInfo
ginx.BindJSON(c, &req)
err := models.AlertingEngineHeartbeatWithCluster(rt.Ctx, req.Instance, req.EngineCluster, req.DatasourceId)
ginx.NewRender(c).Message(err)
}
func (rt *Router) serversActive(c *gin.Context) {
datasourceId := ginx.QueryInt64(c, "dsid")
engineName := ginx.QueryStr(c, "engine_name", "")
if engineName != "" {
servers, err := models.AlertingEngineGetsInstances(rt.Ctx, "engine_cluster = ? and clock > ?", engineName, time.Now().Unix()-30)
ginx.NewRender(c).Data(servers, err)
return
}
servers, err := models.AlertingEngineGetsInstances(rt.Ctx, "datasource_id = ? and clock > ?", datasourceId, time.Now().Unix()-30)
ginx.NewRender(c).Data(servers, err)
}

View File

@@ -0,0 +1,368 @@
package router
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/storage"
"github.com/gin-gonic/gin"
"github.com/prometheus/common/model"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)
type TargetQuery struct {
Filters []models.HostQuery `json:"queries"`
P int `json:"p"`
Limit int `json:"limit"`
}
func (rt *Router) targetGetsByHostFilter(c *gin.Context) {
var f TargetQuery
ginx.BindJSON(c, &f)
query := models.GetHostsQuery(f.Filters)
hosts, err := models.TargetGetsByFilter(rt.Ctx, query, f.Limit, (f.P-1)*f.Limit)
ginx.Dangerous(err)
total, err := models.TargetCountByFilter(rt.Ctx, query)
ginx.Dangerous(err)
ginx.NewRender(c).Data(gin.H{
"list": hosts,
"total": total,
}, nil)
}
func (rt *Router) targetGets(c *gin.Context) {
bgid := ginx.QueryInt64(c, "bgid", -1)
query := ginx.QueryStr(c, "query", "")
limit := ginx.QueryInt(c, "limit", 30)
dsIds := queryDatasourceIds(c)
var bgids []int64
var err error
if bgid == -1 {
// 全部对象的情况,找到用户有权限的业务组
user := c.MustGet("user").(*models.User)
userGroupIds, err := models.MyGroupIds(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)
}
total, err := models.TargetTotal(rt.Ctx, bgids, dsIds, query)
ginx.Dangerous(err)
list, err := models.TargetGets(rt.Ctx, bgids, dsIds, query, limit, ginx.Offset(c, limit))
ginx.Dangerous(err)
if err == nil {
now := time.Now()
cache := make(map[int64]*models.BusiGroup)
var keys []string
for i := 0; i < len(list); i++ {
ginx.Dangerous(list[i].FillGroup(rt.Ctx, cache))
keys = append(keys, models.WrapIdent(list[i].Ident))
}
if len(keys) > 0 {
metaMap := make(map[string]*models.HostMeta)
vals := storage.MGet(context.Background(), rt.Redis, keys)
for _, value := range vals {
var meta models.HostMeta
if value == nil {
continue
}
err := json.Unmarshal(value, &meta)
if err != nil {
logger.Warningf("unmarshal %v host meta failed: %v", value, err)
continue
}
metaMap[meta.Hostname] = &meta
}
for i := 0; i < len(list); i++ {
if meta, ok := metaMap[list[i].Ident]; ok {
list[i].FillMeta(meta)
} else {
// 未上报过元数据的主机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
}
}
}
}
ginx.NewRender(c).Data(gin.H{
"list": list,
"total": total,
}, nil)
}
func (rt *Router) targetGetsByService(c *gin.Context) {
lst, err := models.TargetGetsAll(rt.Ctx)
ginx.NewRender(c).Data(lst, err)
}
func (rt *Router) targetGetTags(c *gin.Context) {
idents := ginx.QueryStr(c, "idents", "")
idents = strings.ReplaceAll(idents, ",", " ")
lst, err := models.TargetGetTags(rt.Ctx, strings.Fields(idents))
ginx.NewRender(c).Data(lst, err)
}
type targetTagsForm struct {
Idents []string `json:"idents" binding:"required"`
Tags []string `json:"tags" binding:"required"`
}
func (rt *Router) targetBindTagsByFE(c *gin.Context) {
var f targetTagsForm
ginx.BindJSON(c, &f)
if len(f.Idents) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents empty")
}
rt.checkTargetPerm(c, f.Idents)
ginx.NewRender(c).Message(rt.targetBindTags(f))
}
func (rt *Router) targetBindTagsByService(c *gin.Context) {
var f targetTagsForm
ginx.BindJSON(c, &f)
if len(f.Idents) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents empty")
}
ginx.NewRender(c).Message(rt.targetBindTags(f))
}
func (rt *Router) targetBindTags(f targetTagsForm) error {
for i := 0; i < len(f.Tags); i++ {
arr := strings.Split(f.Tags[i], "=")
if len(arr) != 2 {
return fmt.Errorf("invalid tag(%s)", f.Tags[i])
}
if strings.TrimSpace(arr[0]) == "" || strings.TrimSpace(arr[1]) == "" {
return fmt.Errorf("invalid tag(%s)", f.Tags[i])
}
if strings.IndexByte(arr[0], '.') != -1 {
return fmt.Errorf("invalid tagkey(%s): cannot contains . ", arr[0])
}
if strings.IndexByte(arr[0], '-') != -1 {
return fmt.Errorf("invalid tagkey(%s): cannot contains -", arr[0])
}
if !model.LabelNameRE.MatchString(arr[0]) {
return fmt.Errorf("invalid tagkey(%s)", arr[0])
}
}
for i := 0; i < len(f.Idents); i++ {
target, err := models.TargetGetByIdent(rt.Ctx, f.Idents[i])
if err != nil {
return err
}
if target == nil {
continue
}
// 不能有同key的标签否则附到时序数据上会产生覆盖让人困惑
for j := 0; j < len(f.Tags); j++ {
tagkey := strings.Split(f.Tags[j], "=")[0]
tagkeyPrefix := tagkey + "="
if strings.HasPrefix(target.Tags, tagkeyPrefix) {
return fmt.Errorf("duplicate tagkey(%s)", tagkey)
}
}
err = target.AddTags(rt.Ctx, f.Tags)
if err != nil {
return err
}
}
return nil
}
func (rt *Router) targetUnbindTagsByFE(c *gin.Context) {
var f targetTagsForm
ginx.BindJSON(c, &f)
if len(f.Idents) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents empty")
}
rt.checkTargetPerm(c, f.Idents)
ginx.NewRender(c).Message(rt.targetUnbindTags(f))
}
func (rt *Router) targetUnbindTagsByService(c *gin.Context) {
var f targetTagsForm
ginx.BindJSON(c, &f)
if len(f.Idents) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents empty")
}
ginx.NewRender(c).Message(rt.targetUnbindTags(f))
}
func (rt *Router) targetUnbindTags(f targetTagsForm) error {
for i := 0; i < len(f.Idents); i++ {
target, err := models.TargetGetByIdent(rt.Ctx, f.Idents[i])
if err != nil {
return err
}
if target == nil {
continue
}
err = target.DelTags(rt.Ctx, f.Tags)
if err != nil {
return err
}
}
return nil
}
type targetNoteForm struct {
Idents []string `json:"idents" binding:"required"`
Note string `json:"note"`
}
func (rt *Router) targetUpdateNote(c *gin.Context) {
var f targetNoteForm
ginx.BindJSON(c, &f)
if len(f.Idents) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents empty")
}
rt.checkTargetPerm(c, f.Idents)
ginx.NewRender(c).Message(models.TargetUpdateNote(rt.Ctx, f.Idents, f.Note))
}
func (rt *Router) targetUpdateNoteByService(c *gin.Context) {
var f targetNoteForm
ginx.BindJSON(c, &f)
if len(f.Idents) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents empty")
}
ginx.NewRender(c).Message(models.TargetUpdateNote(rt.Ctx, f.Idents, f.Note))
}
type targetBgidForm struct {
Idents []string `json:"idents" binding:"required"`
Bgid int64 `json:"bgid"`
}
func (rt *Router) targetUpdateBgid(c *gin.Context) {
var f targetBgidForm
ginx.BindJSON(c, &f)
if len(f.Idents) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents empty")
}
user := c.MustGet("user").(*models.User)
if user.IsAdmin() {
ginx.NewRender(c).Message(models.TargetUpdateBgid(rt.Ctx, f.Idents, f.Bgid, false))
return
}
if f.Bgid > 0 {
// 把要操作的机器分成两部分一部分是bgid为0需要管理员分配另一部分bgid>0说明是业务组内部想调整
// 比如原来分配给didiyun的机器didiyun的管理员想把部分机器调整到didiyun-ceph下
// 对于调整的这种情况当前登录用户要对这批机器有操作权限同时还要对目标BG有操作权限
orphans, err := models.IdentsFilter(rt.Ctx, f.Idents, "group_id = ?", 0)
ginx.Dangerous(err)
// 机器里边存在未归组的登录用户就需要是admin
if len(orphans) > 0 && !user.IsAdmin() {
ginx.Bomb(http.StatusForbidden, "No permission. Only admin can assign BG")
}
reBelongs, err := models.IdentsFilter(rt.Ctx, f.Idents, "group_id > ?", 0)
ginx.Dangerous(err)
if len(reBelongs) > 0 {
// 对于这些要重新分配的机器操作者要对这些机器本身有权限同时要对目标bgid有权限
rt.checkTargetPerm(c, f.Idents)
bg := BusiGroup(rt.Ctx, f.Bgid)
can, err := user.CanDoBusiGroup(rt.Ctx, bg, "rw")
ginx.Dangerous(err)
if !can {
ginx.Bomb(http.StatusForbidden, "No permission. You are not admin of BG(%s)", bg.Name)
}
}
} else if f.Bgid == 0 {
// 退还机器
rt.checkTargetPerm(c, f.Idents)
} else {
ginx.Bomb(http.StatusBadRequest, "invalid bgid")
}
ginx.NewRender(c).Message(models.TargetUpdateBgid(rt.Ctx, f.Idents, f.Bgid, false))
}
type identsForm struct {
Idents []string `json:"idents" binding:"required"`
}
func (rt *Router) targetDel(c *gin.Context) {
var f identsForm
ginx.BindJSON(c, &f)
if len(f.Idents) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents empty")
}
rt.checkTargetPerm(c, f.Idents)
ginx.NewRender(c).Message(models.TargetDel(rt.Ctx, f.Idents))
}
func (rt *Router) checkTargetPerm(c *gin.Context, idents []string) {
user := c.MustGet("user").(*models.User)
nopri, err := user.NopriIdents(rt.Ctx, idents)
ginx.Dangerous(err)
if len(nopri) > 0 {
ginx.Bomb(http.StatusForbidden, "No permission to operate the targets: %s", strings.Join(nopri, ", "))
}
}

View File

@@ -0,0 +1,216 @@
package router
import (
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/str"
)
func (rt *Router) taskGets(c *gin.Context) {
bgid := ginx.UrlParamInt64(c, "id")
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, bgid, beginTime, creator, query)
ginx.Dangerous(err)
list, err := models.TaskRecordGets(rt.Ctx, bgid, beginTime, creator, query, limit, ginx.Offset(c, limit))
ginx.Dangerous(err)
ginx.NewRender(c).Data(gin.H{
"total": total,
"list": list,
}, nil)
}
type taskForm struct {
Title string `json:"title" binding:"required"`
Account string `json:"account" binding:"required"`
Batch int `json:"batch"`
Tolerance int `json:"tolerance"`
Timeout int `json:"timeout"`
Pause string `json:"pause"`
Script string `json:"script" binding:"required"`
Args string `json:"args"`
Action string `json:"action" binding:"required"`
Creator string `json:"creator"`
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)
ginx.NewRender(c).Message(f.Add(rt.Ctx))
}
func (rt *Router) taskAdd(c *gin.Context) {
var f taskForm
ginx.BindJSON(c, &f)
bgid := ginx.UrlParamInt64(c, "id")
user := c.MustGet("user").(*models.User)
f.Creator = user.Username
err := f.Verify()
ginx.Dangerous(err)
f.HandleFH(f.Hosts[0])
// check permission
rt.checkTargetPerm(c, f.Hosts)
// call ibex
taskId, err := TaskCreate(f, rt.NotifyConfigCache.GetIbex())
ginx.Dangerous(err)
if taskId <= 0 {
ginx.Dangerous("created task.id is zero")
}
// 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,
}
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)
}

View File

@@ -0,0 +1,214 @@
package router
import (
"net/http"
"sort"
"strings"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/str"
)
func (rt *Router) taskTplGets(c *gin.Context) {
query := ginx.QueryStr(c, "query", "")
limit := ginx.QueryInt(c, "limit", 20)
groupId := ginx.UrlParamInt64(c, "id")
total, err := models.TaskTplTotal(rt.Ctx, groupId, query)
ginx.Dangerous(err)
list, err := models.TaskTplGets(rt.Ctx, groupId, query, limit, ginx.Offset(c, limit))
ginx.Dangerous(err)
ginx.NewRender(c).Data(gin.H{
"total": total,
"list": list,
}, nil)
}
func (rt *Router) taskTplGet(c *gin.Context) {
tid := ginx.UrlParamInt64(c, "tid")
tpl, err := models.TaskTplGet(rt.Ctx, "id = ?", tid)
ginx.Dangerous(err)
if tpl == nil {
ginx.Bomb(404, "no such task template")
}
hosts, err := tpl.Hosts(rt.Ctx)
ginx.NewRender(c).Data(gin.H{
"tpl": tpl,
"hosts": hosts,
}, err)
}
type taskTplForm struct {
Title string `json:"title" binding:"required"`
Batch int `json:"batch"`
Tolerance int `json:"tolerance"`
Timeout int `json:"timeout"`
Pause string `json:"pause"`
Script string `json:"script"`
Args string `json:"args"`
Tags []string `json:"tags"`
Account string `json:"account"`
Hosts []string `json:"hosts"`
}
func (rt *Router) taskTplAdd(c *gin.Context) {
var f taskTplForm
ginx.BindJSON(c, &f)
user := c.MustGet("user").(*models.User)
now := time.Now().Unix()
sort.Strings(f.Tags)
tpl := &models.TaskTpl{
GroupId: ginx.UrlParamInt64(c, "id"),
Title: f.Title,
Batch: f.Batch,
Tolerance: f.Tolerance,
Timeout: f.Timeout,
Pause: f.Pause,
Script: f.Script,
Args: f.Args,
Tags: strings.Join(f.Tags, " ") + " ",
Account: f.Account,
CreateBy: user.Username,
UpdateBy: user.Username,
CreateAt: now,
UpdateAt: now,
}
ginx.NewRender(c).Message(tpl.Save(rt.Ctx, f.Hosts))
}
func (rt *Router) taskTplPut(c *gin.Context) {
tid := ginx.UrlParamInt64(c, "tid")
tpl, err := models.TaskTplGet(rt.Ctx, "id = ?", tid)
ginx.Dangerous(err)
if tpl == nil {
ginx.NewRender(c).Message("no such task template")
return
}
user := c.MustGet("user").(*models.User)
var f taskTplForm
ginx.BindJSON(c, &f)
sort.Strings(f.Tags)
tpl.Title = f.Title
tpl.Batch = f.Batch
tpl.Tolerance = f.Tolerance
tpl.Timeout = f.Timeout
tpl.Pause = f.Pause
tpl.Script = f.Script
tpl.Args = f.Args
tpl.Tags = strings.Join(f.Tags, " ") + " "
tpl.Account = f.Account
tpl.UpdateBy = user.Username
tpl.UpdateAt = time.Now().Unix()
ginx.NewRender(c).Message(tpl.Update(rt.Ctx, f.Hosts))
}
func (rt *Router) taskTplDel(c *gin.Context) {
tid := ginx.UrlParamInt64(c, "tid")
tpl, err := models.TaskTplGet(rt.Ctx, "id = ?", tid)
ginx.Dangerous(err)
if tpl == nil {
ginx.NewRender(c).Message(nil)
return
}
ginx.NewRender(c).Message(tpl.Del(rt.Ctx))
}
type tplTagsForm struct {
Ids []int64 `json:"ids" binding:"required"`
Tags []string `json:"tags" binding:"required"`
}
func (f *tplTagsForm) Verify() {
if len(f.Ids) == 0 {
ginx.Bomb(http.StatusBadRequest, "arg(ids) empty")
}
if len(f.Tags) == 0 {
ginx.Bomb(http.StatusBadRequest, "arg(tags) empty")
}
newTags := make([]string, 0, len(f.Tags))
for i := 0; i < len(f.Tags); i++ {
tag := strings.TrimSpace(f.Tags[i])
if tag == "" {
continue
}
if str.Dangerous(tag) {
ginx.Bomb(http.StatusBadRequest, "arg(tags) invalid")
}
newTags = append(newTags, tag)
}
f.Tags = newTags
if len(f.Tags) == 0 {
ginx.Bomb(http.StatusBadRequest, "arg(tags) empty")
}
}
func (rt *Router) taskTplBindTags(c *gin.Context) {
var f tplTagsForm
ginx.BindJSON(c, &f)
f.Verify()
username := c.MustGet("username").(string)
for i := 0; i < len(f.Ids); i++ {
tpl, err := models.TaskTplGet(rt.Ctx, "id = ?", f.Ids[i])
ginx.Dangerous(err)
if tpl == nil {
continue
}
ginx.Dangerous(tpl.AddTags(rt.Ctx, f.Tags, username))
}
ginx.NewRender(c).Message(nil)
}
func (rt *Router) taskTplUnbindTags(c *gin.Context) {
var f tplTagsForm
ginx.BindJSON(c, &f)
f.Verify()
username := c.MustGet("username").(string)
for i := 0; i < len(f.Ids); i++ {
tpl, err := models.TaskTplGet(rt.Ctx, "id = ?", f.Ids[i])
ginx.Dangerous(err)
if tpl == nil {
continue
}
ginx.Dangerous(tpl.DelTags(rt.Ctx, f.Tags, username))
}
ginx.NewRender(c).Message(nil)
}

View File

@@ -0,0 +1,137 @@
package router
import (
"net/http"
"strings"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ormx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) userFindAll(c *gin.Context) {
list, err := models.UserGetAll(rt.Ctx)
ginx.NewRender(c).Data(list, err)
}
func (rt *Router) userGets(c *gin.Context) {
limit := ginx.QueryInt(c, "limit", 20)
query := ginx.QueryStr(c, "query", "")
total, err := models.UserTotal(rt.Ctx, query)
ginx.Dangerous(err)
list, err := models.UserGets(rt.Ctx, query, limit, ginx.Offset(c, limit))
ginx.Dangerous(err)
user := c.MustGet("user").(*models.User)
ginx.NewRender(c).Data(gin.H{
"list": list,
"total": total,
"admin": user.IsAdmin(),
}, nil)
}
type userAddForm struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
Nickname string `json:"nickname"`
Phone string `json:"phone"`
Email string `json:"email"`
Portrait string `json:"portrait"`
Roles []string `json:"roles" binding:"required"`
Contacts ormx.JSONObj `json:"contacts"`
}
func (rt *Router) userAddPost(c *gin.Context) {
var f userAddForm
ginx.BindJSON(c, &f)
password, err := models.CryptoPass(rt.Ctx, f.Password)
ginx.Dangerous(err)
if len(f.Roles) == 0 {
ginx.Bomb(http.StatusBadRequest, "roles empty")
}
user := c.MustGet("user").(*models.User)
u := models.User{
Username: f.Username,
Password: password,
Nickname: f.Nickname,
Phone: f.Phone,
Email: f.Email,
Portrait: f.Portrait,
Roles: strings.Join(f.Roles, " "),
Contacts: f.Contacts,
CreateBy: user.Username,
UpdateBy: user.Username,
}
ginx.NewRender(c).Message(u.Add(rt.Ctx))
}
func (rt *Router) userProfileGet(c *gin.Context) {
user := User(rt.Ctx, ginx.UrlParamInt64(c, "id"))
ginx.NewRender(c).Data(user, nil)
}
type userProfileForm struct {
Nickname string `json:"nickname"`
Phone string `json:"phone"`
Email string `json:"email"`
Roles []string `json:"roles"`
Contacts ormx.JSONObj `json:"contacts"`
}
func (rt *Router) userProfilePut(c *gin.Context) {
var f userProfileForm
ginx.BindJSON(c, &f)
if len(f.Roles) == 0 {
ginx.Bomb(http.StatusBadRequest, "roles empty")
}
target := User(rt.Ctx, ginx.UrlParamInt64(c, "id"))
target.Nickname = f.Nickname
target.Phone = f.Phone
target.Email = f.Email
target.Roles = strings.Join(f.Roles, " ")
target.Contacts = f.Contacts
target.UpdateBy = c.MustGet("username").(string)
ginx.NewRender(c).Message(target.UpdateAllFields(rt.Ctx))
}
type userPasswordForm struct {
Password string `json:"password" binding:"required"`
}
func (rt *Router) userPasswordPut(c *gin.Context) {
var f userPasswordForm
ginx.BindJSON(c, &f)
target := User(rt.Ctx, ginx.UrlParamInt64(c, "id"))
cryptoPass, err := models.CryptoPass(rt.Ctx, f.Password)
ginx.Dangerous(err)
ginx.NewRender(c).Message(target.UpdatePassword(rt.Ctx, cryptoPass, c.MustGet("username").(string)))
}
func (rt *Router) userDel(c *gin.Context) {
id := ginx.UrlParamInt64(c, "id")
target, err := models.UserGetById(rt.Ctx, id)
ginx.Dangerous(err)
if target == nil {
ginx.NewRender(c).Message(nil)
return
}
ginx.NewRender(c).Message(target.Del(rt.Ctx))
}

View File

@@ -0,0 +1,150 @@
package router
import (
"net/http"
"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) checkBusiGroupPerm(c *gin.Context) {
me := c.MustGet("user").(*models.User)
bg := BusiGroup(rt.Ctx, ginx.UrlParamInt64(c, "id"))
can, err := me.CanDoBusiGroup(rt.Ctx, bg, ginx.UrlParamStr(c, "perm"))
ginx.NewRender(c).Data(can, err)
}
func (rt *Router) userGroupGets(c *gin.Context) {
limit := ginx.QueryInt(c, "limit", 1500)
query := ginx.QueryStr(c, "query", "")
me := c.MustGet("user").(*models.User)
lst, err := me.UserGroups(rt.Ctx, limit, query)
ginx.NewRender(c).Data(lst, err)
}
func (rt *Router) userGroupGetsByService(c *gin.Context) {
lst, err := models.UserGroupGetAll(rt.Ctx)
ginx.NewRender(c).Data(lst, err)
}
// user group member get by service
func (rt *Router) userGroupMemberGetsByService(c *gin.Context) {
members, err := models.UserGroupMemberGetAll(rt.Ctx)
ginx.NewRender(c).Data(members, err)
}
type userGroupForm struct {
Name string `json:"name" binding:"required"`
Note string `json:"note"`
}
func (rt *Router) userGroupAdd(c *gin.Context) {
var f userGroupForm
ginx.BindJSON(c, &f)
me := c.MustGet("user").(*models.User)
ug := models.UserGroup{
Name: f.Name,
Note: f.Note,
CreateBy: me.Username,
UpdateBy: me.Username,
}
err := ug.Add(rt.Ctx)
if err == nil {
// Even failure is not a big deal
models.UserGroupMemberAdd(rt.Ctx, ug.Id, me.Id)
}
ginx.NewRender(c).Data(ug.Id, err)
}
func (rt *Router) userGroupPut(c *gin.Context) {
var f userGroupForm
ginx.BindJSON(c, &f)
me := c.MustGet("user").(*models.User)
ug := c.MustGet("user_group").(*models.UserGroup)
if ug.Name != f.Name {
// name changed, check duplication
num, err := models.UserGroupCount(rt.Ctx, "name=? and id<>?", f.Name, ug.Id)
ginx.Dangerous(err)
if num > 0 {
ginx.Bomb(http.StatusOK, "UserGroup already exists")
}
}
ug.Name = f.Name
ug.Note = f.Note
ug.UpdateBy = me.Username
ug.UpdateAt = time.Now().Unix()
ginx.NewRender(c).Message(ug.Update(rt.Ctx, "Name", "Note", "UpdateAt", "UpdateBy"))
}
// Return all members, front-end search and paging
func (rt *Router) userGroupGet(c *gin.Context) {
ug := UserGroup(rt.Ctx, ginx.UrlParamInt64(c, "id"))
ids, err := models.MemberIds(rt.Ctx, ug.Id)
ginx.Dangerous(err)
logger.Info("userGroupGet", ids)
users, err := models.UserGetsByIds(rt.Ctx, ids)
ginx.NewRender(c).Data(gin.H{
"users": users,
"user_group": ug,
}, err)
}
func (rt *Router) userGroupDel(c *gin.Context) {
ug := c.MustGet("user_group").(*models.UserGroup)
ginx.NewRender(c).Message(ug.Del(rt.Ctx))
}
func (rt *Router) userGroupMemberAdd(c *gin.Context) {
var f idsForm
ginx.BindJSON(c, &f)
f.Verify()
me := c.MustGet("user").(*models.User)
ug := c.MustGet("user_group").(*models.UserGroup)
err := ug.AddMembers(rt.Ctx, f.Ids)
if err == nil {
ug.UpdateAt = time.Now().Unix()
ug.UpdateBy = me.Username
ug.Update(rt.Ctx, "UpdateAt", "UpdateBy")
}
ginx.NewRender(c).Message(err)
}
func (rt *Router) userGroupMemberDel(c *gin.Context) {
var f idsForm
ginx.BindJSON(c, &f)
f.Verify()
me := c.MustGet("user").(*models.User)
ug := c.MustGet("user_group").(*models.UserGroup)
err := ug.DelMembers(rt.Ctx, f.Ids)
if err == nil {
ug.UpdateAt = time.Now().Unix()
ug.UpdateBy = me.Username
ug.Update(rt.Ctx, "UpdateAt", "UpdateBy")
}
ginx.NewRender(c).Message(err)
}

169
center/sso/init.go Normal file
View File

@@ -0,0 +1,169 @@
package sso
import (
"log"
"github.com/BurntSushi/toml"
"github.com/ccfos/nightingale/v6/center/cconf"
"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/toolkits/pkg/logger"
)
type SsoClient struct {
OIDC *oidcx.SsoClient
LDAP *ldapx.SsoClient
CAS *cas.SsoClient
OAuth2 *oauth2x.SsoClient
}
const LDAP = `
Enable = false
Host = 'ldap.example.org'
Port = 389
BaseDn = 'dc=example,dc=org'
BindUser = 'cn=manager,dc=example,dc=org'
BindPass = '*******'
AuthFilter = '(&(uid=%s))'
CoverAttributes = true
TLS = false
StartTLS = true
DefaultRoles = ['Standard']
[Attributes]
Nickname = 'cn'
Phone = 'mobile'
Email = 'mail'
`
const OAuth2 = `
Enable = false
DisplayName = 'OAuth2登录'
RedirectURL = 'http://127.0.0.1:18000/callback/oauth'
SsoAddr = 'https://sso.example.com/oauth2/authorize'
TokenAddr = 'https://sso.example.com/oauth2/token'
UserInfoAddr = 'https://api.example.com/api/v1/user/info'
TranTokenMethod = 'header'
ClientId = ''
ClientSecret = ''
CoverAttributes = true
DefaultRoles = ['Standard']
UserinfoIsArray = false
UserinfoPrefix = 'data'
Scopes = ['profile', 'email', 'phone']
[Attributes]
Username = 'username'
Nickname = 'nickname'
Phone = 'phone_number'
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
DefaultRoles = ['Standard']
[Attributes]
Nickname = 'nickname'
Phone = 'phone_number'
Email = 'email'
`
const OIDC = `
Enable = false
DisplayName = 'OIDC登录'
RedirectURL = 'http://n9e.com/callback'
SsoAddr = 'http://sso.example.org'
ClientId = ''
ClientSecret = ''
CoverAttributes = true
DefaultRoles = ['Standard']
[Attributes]
Nickname = 'nickname'
Phone = 'phone_number'
Email = 'email'
`
func Init(center cconf.Center, ctx *ctx.Context) *SsoClient {
ssoClient := new(SsoClient)
m := make(map[string]string)
m["LDAP"] = LDAP
m["CAS"] = CAS
m["OIDC"] = OIDC
m["OAuth2"] = OAuth2
for name, config := range m {
count, err := models.SsoConfigCountByName(ctx, name)
if err != nil {
logger.Error(err)
continue
}
if count > 0 {
continue
}
ssoConfig := models.SsoConfig{
Name: name,
Content: config,
}
err = ssoConfig.Create(ctx)
if err != nil {
log.Fatalln(err)
}
}
configs, err := models.SsoConfigGets(ctx)
if err != nil {
log.Fatalln(err)
}
for _, cfg := range configs {
switch cfg.Name {
case "LDAP":
var config ldapx.Config
err := toml.Unmarshal([]byte(cfg.Content), &config)
if err != nil {
log.Fatalln("init ldap failed", err)
}
ssoClient.LDAP = ldapx.New(config)
case "OIDC":
var config oidcx.Config
err := toml.Unmarshal([]byte(cfg.Content), &config)
if err != nil {
log.Fatalln("init oidc failed:", err)
}
oidcClient, err := oidcx.New(config)
if err != nil {
logger.Error("init oidc failed:", err)
} else {
ssoClient.OIDC = oidcClient
}
case "CAS":
var config cas.Config
err := toml.Unmarshal([]byte(cfg.Content), &config)
if err != nil {
log.Fatalln("init cas failed:", err)
}
ssoClient.CAS = cas.New(config)
case "OAuth2":
var config oauth2x.Config
err := toml.Unmarshal([]byte(cfg.Content), &config)
if err != nil {
log.Fatalln("init oauth2 failed:", err)
}
ssoClient.OAuth2 = oauth2x.New(config)
}
}
return ssoClient
}

9
cli/cli.go Normal file
View File

@@ -0,0 +1,9 @@
package cli
import (
"github.com/ccfos/nightingale/v6/cli/upgrade"
)
func Upgrade(configFile string) error {
return upgrade.Upgrade(configFile)
}

63
cli/upgrade/config.go Normal file
View File

@@ -0,0 +1,63 @@
package upgrade
import (
"bytes"
"path"
"github.com/ccfos/nightingale/v6/pkg/cfg"
"github.com/ccfos/nightingale/v6/pkg/ormx"
"github.com/ccfos/nightingale/v6/pkg/tlsx"
"github.com/koding/multiconfig"
)
type Config struct {
DB ormx.DBConfig
Clusters []ClusterOptions
}
type ClusterOptions struct {
Name string
Prom string
BasicAuthUser string
BasicAuthPass string
Headers []string
Timeout int64
DialTimeout int64
UseTLS bool
tlsx.ClientConfig
MaxIdleConnsPerHost int
}
func Parse(fpath string, configPtr interface{}) error {
var (
tBuf []byte
)
loaders := []multiconfig.Loader{
&multiconfig.TagLoader{},
&multiconfig.EnvironmentLoader{},
}
s := cfg.NewFileScanner()
s.Read(path.Join(fpath))
tBuf = append(tBuf, s.Data()...)
tBuf = append(tBuf, []byte("\n")...)
if s.Err() != nil {
return s.Err()
}
if len(tBuf) != 0 {
loaders = append(loaders, &multiconfig.TOMLLoader{Reader: bytes.NewReader(tBuf)})
}
m := multiconfig.DefaultLoader{
Loader: multiconfig.MultiLoader(loaders...),
Validator: multiconfig.MultiValidator(&multiconfig.RequiredValidator{}),
}
return m.Load(configPtr)
}

21
cli/upgrade/readme.md Normal file
View File

@@ -0,0 +1,21 @@
# v5 升级 v6 手册
0. 操作之前,记得备注下数据库!
1. 需要先将你正在使用的夜莺数据源表结构更新到和 v5.15.0 一致,[release](https://github.com/ccfos/nightingale/releases) 页面有每个版本表结构的更新说明,可以根据你正在使用的版本,按照说明,逐个执行的更新表结构的语句
2. 解压 n9e 安装包,导入 upgrade.sql 到 n9e_v5 数据库
```
mysql -h 127.0.0.1 -u root -p1234 < cli/upgrade/upgrade.sql
```
3. 执行 n9e-cli 完成数据库表结构升级, webapi.conf 为 v5 版本 n9e-webapi 正在使用的配置文件
```
./n9e-cli --upgrade --config webapi.conf
```
4. 修改 n9e 配置文件中的数据库为 n9e_v5启动 n9e 进程
```
nohup ./n9e &> n9e.log &
```
5. n9e 监听的端口为 17000需要将之前的 web 端口和数据上报的端口,都调整为 17000

117
cli/upgrade/upgrade.go Normal file
View File

@@ -0,0 +1,117 @@
package upgrade
import (
"context"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/storage"
"github.com/toolkits/pkg/logger"
)
func Upgrade(configFile string) error {
var config Config
Parse(configFile, &config)
db, err := storage.New(config.DB)
if err != nil {
return err
}
ctx := ctx.NewContext(context.Background(), db, true)
for _, cluster := range config.Clusters {
count, err := models.GetDatasourcesCountByName(ctx, cluster.Name)
if err != nil {
logger.Errorf("get datasource %s count error: %v", cluster.Name, err)
continue
}
if count > 0 {
continue
}
header := make(map[string]string)
headerCount := len(cluster.Headers)
if headerCount > 0 && headerCount%2 == 0 {
for i := 0; i < len(cluster.Headers); i += 2 {
header[cluster.Headers[i]] = cluster.Headers[i+1]
}
}
authJosn := models.Auth{
BasicAuthUser: cluster.BasicAuthUser,
BasicAuthPassword: cluster.BasicAuthPass,
}
httpJson := models.HTTP{
Timeout: cluster.Timeout,
DialTimeout: cluster.DialTimeout,
TLS: models.TLS{
SkipTlsVerify: cluster.UseTLS,
},
MaxIdleConnsPerHost: cluster.MaxIdleConnsPerHost,
Url: cluster.Prom,
Headers: header,
}
datasrouce := models.Datasource{
PluginId: 1,
PluginType: "prometheus",
PluginTypeName: "Prometheus Like",
Name: cluster.Name,
HTTPJson: httpJson,
AuthJson: authJosn,
ClusterName: "default",
Status: "enabled",
}
err = datasrouce.Add(ctx)
if err != nil {
logger.Errorf("add datasource %s error: %v", cluster.Name, err)
}
}
datasources, err := models.GetDatasources(ctx)
if err != nil {
return err
}
m := make(map[string]models.Datasource)
for i := 0; i < len(datasources); i++ {
m[datasources[i].Name] = datasources[i]
}
err = models.AlertRuleUpgradeToV6(ctx, m)
if err != nil {
return err
}
// alert mute
err = models.AlertMuteUpgradeToV6(ctx, m)
if err != nil {
return err
}
// alert subscribe
err = models.AlertSubscribeUpgradeToV6(ctx, m)
if err != nil {
return err
}
// recoding rule
err = models.RecordingRuleUpgradeToV6(ctx, m)
if err != nil {
return err
}
// alert cur event
err = models.AlertCurEventUpgradeToV6(ctx, m)
if err != nil {
return err
}
// alert his event
err = models.AlertHisEventUpgradeToV6(ctx, m)
if err != nil {
return err
}
return nil
}

98
cli/upgrade/upgrade.sql Normal file
View File

@@ -0,0 +1,98 @@
use n9e_v5;
insert into `role_operation`(role_name, operation) values('Guest', '/log/explorer');
insert into `role_operation`(role_name, operation) values('Guest', '/trace/explorer');
insert into `role_operation`(role_name, operation) values('Standard', '/log/explorer');
insert into `role_operation`(role_name, operation) values('Standard', '/trace/explorer');
insert into `role_operation`(role_name, operation) values('Standard', '/alert-rules-built-in');
insert into `role_operation`(role_name, operation) values('Standard', '/dashboards-built-in');
insert into `role_operation`(role_name, operation) values('Standard', '/trace/dependencies');
insert into `role_operation`(role_name, operation) values('Standard', '/help/servers');
insert into `role_operation`(role_name, operation) values('Standard', '/help/migrate');
insert into `role_operation`(role_name, operation) values('Admin', '/help/source');
insert into `role_operation`(role_name, operation) values('Admin', '/help/sso');
insert into `role_operation`(role_name, operation) values('Admin', '/help/notification-tpls');
insert into `role_operation`(role_name, operation) values('Admin', '/help/notification-settings');
alter table `board` add built_in tinyint(1) not null default 0 comment '0:false 1:true';
alter table `board` add hide tinyint(1) not null default 0 comment '0:false 1:true';
alter table `chart_share` add datasource_id bigint unsigned not null default 0;
alter table `alert_rule` add datasource_ids varchar(255) not null default '';
alter table `alert_rule` add rule_config text not null comment 'rule_config';
alter table `alert_rule` add annotations text not null comment 'annotations';
alter table `alert_mute` add datasource_ids varchar(255) not null default '';
alter table `alert_mute` add periodic_mutes varchar(4096) not null default '[]';
alter table `alert_mute` add mute_time_type tinyint(1) not null default 0;
alter table `alert_subscribe` add datasource_ids varchar(255) not null default '';
alter table `alert_subscribe` add prod varchar(255) not null default '';
alter table `alert_subscribe` add webhooks text;
alter table `alert_subscribe` add redefine_webhooks tinyint(1) default 0;
alter table `alert_subscribe` add for_duration bigint not null default 0;
alter table `recording_rule` add datasource_ids varchar(255) default '';
alter table `target` modify cluster varchar(128) not null default '';
alter table `alert_cur_event` add datasource_id bigint unsigned not null default 0;
alter table `alert_cur_event` add annotations text not null comment 'annotations';
alter table `alert_cur_event` add rule_config text not null comment 'rule_config';
alter table `alert_his_event` add datasource_id bigint unsigned not null default 0;
alter table `alert_his_event` add annotations text not null comment 'annotations';
alter table `alert_his_event` add rule_config text not null comment 'rule_config';
alter table `alerting_engines` add datasource_id bigint unsigned not null default 0;
alter table `alerting_engines` change cluster engine_cluster varchar(128) not null default '' comment 'n9e engine cluster';
alter table `task_record` add event_id bigint not null comment 'event id' default 0;
CREATE TABLE `datasource`
(
`id` int unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) not null default '',
`description` varchar(255) not null default '',
`category` varchar(255) not null default '',
`plugin_id` int unsigned not null default 0,
`plugin_type` varchar(255) not null default '',
`plugin_type_name` varchar(255) not null default '',
`cluster_name` varchar(255) not null default '',
`settings` text not null,
`status` varchar(255) not null default '',
`http` varchar(4096) not null default '',
`auth` varchar(8192) not null default '',
`created_at` bigint not null default 0,
`created_by` varchar(64) not null default '',
`updated_at` bigint not null default 0,
`updated_by` varchar(64) not null default '',
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
CREATE TABLE `builtin_cate` (
`id` bigint unsigned not null auto_increment,
`name` varchar(191) not null,
`user_id` bigint not null default 0,
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
CREATE TABLE `notify_tpl` (
`id` bigint unsigned not null auto_increment,
`channel` varchar(32) not null,
`name` varchar(255) not null,
`content` text not null,
PRIMARY KEY (`id`),
UNIQUE KEY (`channel`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
CREATE TABLE `sso_config` (
`id` bigint unsigned not null auto_increment,
`name` varchar(191) not null,
`content` text not null,
PRIMARY KEY (`id`),
UNIQUE KEY (`name`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

Some files were not shown because too many files have changed in this diff Show More