Compare commits

..

645 Commits

Author SHA1 Message Date
ning
6743b125a3 add some sql 2024-05-30 15:40:06 +08:00
ning
928388f637 code refactor 2024-05-29 20:49:21 +08:00
ning
664c98e6a6 code refactor 2024-05-29 20:30:26 +08:00
ning
cd1be178e7 code refactor 2024-05-29 19:56:59 +08:00
ning
a7034917b5 code refactor 2024-05-29 17:17:04 +08:00
ning
b872329508 code refactor 2024-05-29 17:06:11 +08:00
ning
b5ede2f2d8 code refactor 2024-05-29 16:58:46 +08:00
ning
209e82787f code refactor 2024-05-29 16:48:28 +08:00
ning
dd94719952 change integration 2024-05-28 18:18:47 +08:00
ning
0991967c61 Merge branch 'main' of github.com:ccfos/nightingale into integration-tpl 2024-05-28 17:24:56 +08:00
ning
07c1ef6bd4 docs: add some sql 2024-05-28 15:37:04 +08:00
ning
bfa7059098 docs: add some sql 2024-05-28 15:31:23 +08:00
ning
ee649d0b4b modity import alert rule 2024-05-28 11:37:56 +08:00
ning
ca1094b263 code refactor 2024-05-24 17:45:14 +08:00
ning
e2b6b1f9ba code refactor 2024-05-24 17:21:13 +08:00
ning
be1b20685e code refactor 2024-05-24 17:16:28 +08:00
ning
335d551bd9 code refactor 2024-05-24 16:48:04 +08:00
ning
5b3e2e9764 code refactor 2024-05-24 16:09:54 +08:00
ning
cc67908ac3 code refactor 2024-05-24 16:02:01 +08:00
ning
8b297d7df1 code refactor 2024-05-24 14:57:22 +08:00
ning
0d1114b988 code refactor 2024-05-24 14:45:27 +08:00
ning
b800fcd47f code refactor 2024-05-24 14:25:43 +08:00
laiwei
096a2d3675 add nvidia gpu metrics dashboard 2024-05-24 14:02:14 +08:00
ning
2232733922 fix: delete target service api 2024-05-23 17:03:52 +08:00
ning
b15f638688 refactor: code format 2024-05-23 16:43:25 +08:00
ning
4f818e3642 Merge branch 'main' of github.com:ccfos/nightingale 2024-05-23 11:06:50 +08:00
ning
638c62da2f refactor: automatically generate jwt signing key 2024-05-23 11:06:33 +08:00
shardingHe
e1a9c995c2 docs: merge the metric data from metric.toml into oracle.toml (#1962)
Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2024-05-23 11:01:58 +08:00
Yang Zhiyan
1898675075 optimize: Optimize targets-related interfaces (#1961) 2024-05-23 10:27:09 +08:00
ning
3f865e2d19 code refactor 2024-05-22 10:47:37 +08:00
ning
e3ea65d098 code refactor 2024-05-22 10:34:34 +08:00
Resurgence72
ce7f0272d8 对 prometheus 2.50.0 版本引入的 NewPossibleNonCounterInfo warnings 做适配 (#1939) 2024-05-21 17:29:33 +08:00
赵尚
93159f07fd refactor: change the task time limit from 1 day to 5 days. (#1959) 2024-05-21 15:32:45 +08:00
Yening Qin
7d410baa2d refactor: recovery event support inhibit (#1958) 2024-05-20 20:35:35 +08:00
Ulric Qin
20b30c3e2c update ping metrics 2024-05-20 10:26:18 +08:00
Ulric Qin
8805bf6598 fix typo of logout router 2024-05-20 10:23:08 +08:00
Ulric Qin
fe6a64dae8 Merge branch 'main' of github.com:ccfos/nightingale 2024-05-20 10:22:39 +08:00
Ulric Qin
2c564a2c58 add cdn metrics doc 2024-05-20 10:22:16 +08:00
ulricqin
ae3c13224d Update host_generic_categraf.json 2024-05-17 10:52:22 +08:00
ulricqin
9a4015f13f Update host_generic_categraf.json 2024-05-16 17:46:48 +08:00
Yening Qin
274ca09551 Update switch branch.json 2024-05-16 13:44:08 +08:00
ulricqin
3d9b4fc14e Update categraf-procstat.json 2024-05-16 11:38:53 +08:00
ning
07436a5e0d refactor: change event order 2024-05-16 11:15:18 +08:00
ning
f7b2f1acb9 refactor: change event order 2024-05-16 11:08:35 +08:00
ning
4f4287030a docs: update snmp board tpl 2024-05-15 22:19:14 +08:00
ning
e25e712c48 refactor: change boards clone api 2024-05-15 22:16:16 +08:00
ning
66951d7e77 refactor: change boards clone api 2024-05-14 14:24:32 +08:00
ulricqin
f5ff27cd18 Create host_generic_categraf.json 2024-05-13 18:03:54 +08:00
ning
9e3f6e6285 refactor: add create user verify 2024-05-13 17:25:08 +08:00
ning
48e3df2cb4 refactor: new ldap conn 2024-05-13 17:07:18 +08:00
Yening Qin
ac5d69dba4 feat: ldap support role mapping (#1948)
* feature: LDAP implements role mapping capabilities (#1932)

* feature: Implement the team mapping function (#1934)

* refactor: ldap login add timeout

---------

Co-authored-by: Ciusyan <101268302+Yziyan@users.noreply.github.com>
Co-authored-by: ciusyan <yangzhiyan_i@didiglobal.com>
2024-05-13 16:56:19 +08:00
Ulric Qin
597351c424 code refactor 2024-05-13 10:39:27 +08:00
Ulric Qin
1f6b2e341a update README 2024-05-13 10:28:19 +08:00
ulricqin
035752ace2 Update README.md 2024-05-13 10:15:55 +08:00
ulricqin
60a1437207 Update README.md 2024-05-13 10:12:58 +08:00
ulricqin
e31414bc8c Update README.md 2024-05-13 10:12:08 +08:00
ning
785a294845 refactor: update event.TriggerValue 2024-05-11 11:17:38 +08:00
ning
98933eee34 docs: update sql 2024-05-10 16:32:05 +08:00
ulricqin
20905810d7 Delete integrations/Netstat/metrics directory 2024-05-10 15:30:08 +08:00
ulricqin
c1bde83639 Delete integrations/Kernel_Vmstat/metrics directory 2024-05-10 15:29:30 +08:00
ulricqin
782a0e9616 Delete integrations/Processes/metrics directory 2024-05-10 15:28:50 +08:00
ning
6a3720bc8b docs: update ops 2024-05-10 14:19:49 +08:00
ning
de252359d6 Merge branch 'main' of github.com:ccfos/nightingale 2024-05-10 14:11:54 +08:00
ning
deb313ca3d refactor: change server and server clusters routes to include permission check 2024-05-10 14:11:32 +08:00
yang xiaokai
d119de56be docs: creating SNMP monitoring for Cisco like switches (#1945)
* Add files via upload

* Add files via upload

* Update and rename DCN.toml to Cisco.toml

---------

Co-authored-by: Yening Qin <710leo@gmail.com>
2024-05-10 13:27:24 +08:00
tuogege
f05417fa23 docs: fix wrong table name about 'WriteRelabels' (#1942) 2024-05-10 11:56:28 +08:00
ning
9ab2eb591f docs: update integration 2024-05-10 10:55:22 +08:00
Yening Qin
3f476d770f feat: add builtin metrics (#1944) 2024-05-10 10:41:51 +08:00
ning
ced6759686 Merge branch 'main' of github.com:ccfos/nightingale 2024-05-09 16:48:43 +08:00
ning
eba3014c59 fix: alert engine rebuild hash 2024-05-09 16:48:29 +08:00
ulricqin
3aeb4e16e9 Update webhook.go. Refactor Host header settings 2024-05-09 15:54:37 +08:00
ning
3b62722251 refactor: change server and server clusters routes to include permission check 2024-05-09 11:42:09 +08:00
shardingHe
fb1cc4868e feat: add user variable for sso(decrypted). (#1936)
* add user variable for sso(decrypted).
---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2024-05-08 14:10:25 +08:00
ning
4a0dcf0dbf refactor: add forbidden status check for empty group id list 2024-05-08 11:59:13 +08:00
ning
4f913f146e Remove table prefix from all config files for consistency 2024-05-07 21:04:12 +08:00
Ulric Qin
533560f432 Merge branch 'main' of github.com:ccfos/nightingale 2024-05-07 20:03:52 +08:00
Ulric Qin
cf7b479a1b update integration metrics 2024-05-07 20:01:41 +08:00
Yening Qin
2e4c29a0de docs: delete integrations/Ping/collect/ping2.toml 2024-05-07 12:21:43 +08:00
Ulric Qin
6f0ceb94c6 Merge branch 'main' of github.com:ccfos/nightingale 2024-04-29 16:57:08 +08:00
Ulric Qin
800d7ba04b update integrations metrics 2024-04-29 16:56:56 +08:00
Thomas Zhao
fb6a6d2b93 Resolve a problem of pushgw WriteRrelabel not working actually (#1928)
* fix: timeSeries is not updated after relabeled

* fix: GaugeSampleQueueSize defined but not registered

---------

Co-authored-by: zhaotuo <zhaotuo@mail.jj.cn>
2024-04-29 10:22:17 +08:00
ning
cf2b19ae90 Update the size of callbacks and runbook_url columns to varchar(4096) in the alert_rule table 2024-04-28 11:56:52 +08:00
Ulric Qin
fb1cc93613 Merge branch 'main' of github.com:ccfos/nightingale 2024-04-26 19:27:07 +08:00
Ulric Qin
c2bba796c2 add some integrations metrics 2024-04-26 19:26:53 +08:00
ning
a02bf83842 fix: query busigroup error by checking if t is not nil before accessing its GroupId property 2024-04-25 17:18:50 +08:00
ning
cd9f129e2d docs: remove memory metric reference from dashboard descriptions 2024-04-22 17:30:03 +08:00
dependabot[bot]
e85c80bdcf build(deps): bump golang.org/x/net from 0.17.0 to 0.23.0 (#1921)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.17.0...v0.23.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>
2024-04-22 10:52:44 +08:00
Ciusyan
7e83e0c482 Add fields to the users interface and fix email sending bugs (#1919)
* fix: SMTP configuration error may not exit the current sending email goroutine

* optimize: Add user information fields

---------

Co-authored-by: ciusyan <yangzhiyan_i@didiglobal.com>
2024-04-22 10:45:23 +08:00
ning
92ac3125f3 refactor: change ibex version 2024-04-19 00:05:20 +08:00
ning
a61feca369 Merge branch 'main' of github.com:ccfos/nightingale 2024-04-17 17:32:06 +08:00
ning
8b0b811919 Refactor redundant code for initializing Redis storage in alert/alert.go and cmd/edge/edge.go 2024-04-17 17:31:53 +08:00
Ulric Qin
8742526c7f check configDir exists 2024-04-17 17:29:49 +08:00
ning
ee757cfd92 Enable PProf for profiling and monitoring in config.toml 2024-04-17 16:54:00 +08:00
ulricqin
b12cfea379 Update edge.toml 2024-04-17 16:34:27 +08:00
kongfei605
45365e3e03 chore: update dashboards for ipmi (#1913) 2024-04-16 19:44:08 +08:00
kongfei605
1b676eefd2 Merge pull request #1912 from shardingHe/sync_dashboard_impi_and_sqlserver
docs: sync ipmi & sqlserver dashboards
2024-04-16 13:36:32 +08:00
shardingHe
0092dc44fd sync ipmi & sqlserver dashboards
update ipmi collect config
2024-04-16 13:05:12 +08:00
shardingHe
4941b376f3 feat: update buildIn dashboard custom (#1911) 2024-04-12 15:06:31 +08:00
ulricqin
e46813cd17 Update docker-compose.yaml 2024-04-12 10:02:48 +08:00
ning
58ebd224c2 refactor: change datasource api 2024-04-09 15:06:56 +08:00
ning
95ece6e16f refactor: update endpoint for deleting a datasource to remove unnecessary trailing slash 2024-04-09 15:04:38 +08:00
ning
b82cbd06fa merge main 2024-04-09 14:23:10 +08:00
ning
16210892da docs: change dockerfile 2024-04-09 14:10:52 +08:00
Ulric Qin
a452d63a56 Merge branch 'main' of github.com:ccfos/nightingale 2024-04-09 13:53:14 +08:00
Ulric Qin
51c7abedd3 Delete Dockerfile 2024-04-09 13:52:59 +08:00
ning
6d0a2420a8 Merge branch 'main' of github.com:ccfos/nightingale 2024-04-09 10:25:26 +08:00
ning
9cf687b73d fix: ldap user login info sync 2024-04-09 10:25:10 +08:00
ulricqin
49c9e41df5 Update host_table_view_demo.json 2024-04-08 16:01:31 +08:00
ning
2ec2e64213 refactor: Remove redundant DB2FE function from models 2024-04-08 15:29:19 +08:00
ning
867a61c8dc docs: change docker compose config 2024-04-07 19:36:36 +08:00
HongKuang
12263d1453 chore: fix function name in comment (#1905)
Signed-off-by: hongkuang <liurenhong@outlook.com>
2024-04-07 18:50:06 +08:00
Yening Qin
c0cacb2e64 refactor: change docker compose (#1906)
* update init sql

* change compose config
2024-04-07 18:49:32 +08:00
ning
0637b343b1 refactor: update ibex version 2024-04-06 23:21:10 +08:00
ning
2473e144ef refactor: update ibex version 2024-04-06 23:19:38 +08:00
Yening Qin
00a37d6de7 feat: Integration ibex (#1904)
* Ibex integrate (#1876)

---------

Co-authored-by: Deke Wang <94156972+wdkcc@users.noreply.github.com>
2024-04-06 22:02:07 +08:00
ning
50c664e6bf code refactor 2024-04-03 15:37:31 +08:00
Yening Qin
22b7d20455 refactor: sync user info to duty (#1903) 2024-04-02 21:49:32 +08:00
ning
141262e5a5 refactor: datasource update 2024-03-31 15:16:40 +08:00
ning
4717abfa77 update built-in rule 2024-03-26 15:43:18 +08:00
ning
1bf1a01c32 Merge branch 'main' of github.com:ccfos/nightingale 2024-03-21 16:38:35 +08:00
ning
05b714de38 fix: cas user login 2024-03-21 16:38:22 +08:00
xtan
11377d4e5f docs: docker remove ops.yaml (#1894) 2024-03-20 10:43:08 +08:00
ning
46ea46fdfe docs: update edge.toml 2024-03-19 17:46:32 +08:00
Yening Qin
d4f0483238 feat: ldap support sycn user and oidc suport logout (#1893)
* ldap user sync (#1858)

---------

Co-authored-by: Deke Wang <94156972+wdkcc@users.noreply.github.com>
2024-03-19 17:19:08 +08:00
ning
a79610f5ea fix: go mod deps 2024-03-19 15:29:27 +08:00
dependabot[bot]
d9fb71b9a0 build(deps): bump google.golang.org/protobuf from 1.31.0 to 1.33.0 (#1885)
Bumps google.golang.org/protobuf from 1.31.0 to 1.33.0.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-19 15:25:35 +08:00
dependabot[bot]
37057fa0cf build(deps): bump github.com/jackc/pgproto3/v2 from 2.3.1 to 2.3.3 (#1886)
Bumps [github.com/jackc/pgproto3/v2](https://github.com/jackc/pgproto3) from 2.3.1 to 2.3.3.
- [Commits](https://github.com/jackc/pgproto3/compare/v2.3.1...v2.3.3)

---
updated-dependencies:
- dependency-name: github.com/jackc/pgproto3/v2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-19 15:25:26 +08:00
yingjun
b234128a45 fix: typo error (#1891) 2024-03-19 15:25:06 +08:00
Yening Qin
67a2d57966 refactor: optimize alert mute and some config (#1892)
* Refactor alert mute and recording rule retrieval to handle cases where group IDs are empty in the current session

* Update `tags` column default value to '[]' in `alert_mute` table and `AlertMute` struct

* Update gids variable to handle empty query parameter in alertMuteGetsByGids and recordingRuleGetsByGids functions

* Refactor alertMuteGet function to include database to frontend transformation

* change mute datasource

* Refactor AlertSubscribeGetsByBGIds function to handle empty bgids parameter gracefully

* Refactor taskTplTotal and taskTplGets functions to allow filtering by groupIds when provided, enhancing flexibility and optimization

* Refactor taskGetsByGids to handle empty gids and non-admin user cases more efficiently
2024-03-19 15:24:24 +08:00
Ulric Qin
3a1516877e code refactor 2024-03-19 09:55:11 +08:00
Ulric Qin
53f31d175f code refactor 2024-03-19 09:54:24 +08:00
kongfei605
25323e9ce2 update dashboards for springboot (#1867)
* update dashboards for springboot

* update dashboard for springboot
2024-03-13 23:58:23 +08:00
shardingHe
3136596add docs: update aliyun dashboards (#1884)
* remove invalid dashboard for aliyun

* update dashboard for aliyun

* update dashboard for aliyun

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2024-03-13 23:57:45 +08:00
ning
e7200b0b23 refactor: compatible mute rule time period 00:00-23:59 2024-03-12 14:35:01 +08:00
shardingHe
dfb19c1dde docs: remove invalid dashboard for aliyun (#1881)
Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2024-03-08 10:23:36 +08:00
ning
2363b35263 docs: remove rsa config 2024-03-08 10:18:25 +08:00
shardingHe
99367aaf88 fix: sync smtp config (#1879)
Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2024-03-07 10:26:07 +08:00
xtan
ad17ef328f docs: fix pg docker config (#1874) 2024-03-05 21:16:53 +08:00
ning
5f149f6a38 Merge branch 'main' of github.com:ccfos/nightingale 2024-03-03 21:50:27 +08:00
ning
73ed57301b add iamleader 2024-03-03 21:49:45 +08:00
Ulric Qin
138b929db4 Merge branch 'main' of github.com:ccfos/nightingale 2024-03-01 18:27:10 +08:00
Ulric Qin
4585e94cd1 set DatasourceIdsJson to []int64{} if it is nil 2024-03-01 18:26:56 +08:00
ning
69ad6344f5 refactor: add ldap login log 2024-02-29 18:13:05 +08:00
Ulric Qin
a55665bd14 fix dash 2024-02-28 12:16:24 +08:00
Ulric Qin
b5e2053b0c Merge branch 'main' of github.com:ccfos/nightingale 2024-02-28 10:34:15 +08:00
Ulric Qin
94265eab9f add rsa configurations 2024-02-28 10:33:46 +08:00
ning
eb79d473b0 fix sync sso config 2024-02-27 12:00:53 +08:00
ning
c4e0a9962f fix sync sso config 2024-02-27 11:50:53 +08:00
shardingHe
ee613616ca docs: add a new IPMI dashboard for version v0.3.44-pre and subsequent versions. (#1869)
Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2024-02-23 22:36:19 +08:00
shardingHe
6bbf00c371 docs: update sqlserver config (#1868)
Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2024-02-23 22:35:26 +08:00
ulricqin
f9f45d315d Update edge.toml 2024-02-22 10:31:57 +08:00
Yening Qin
84f215b7f1 refactor: handle event (#1866) 2024-02-22 09:46:37 +08:00
ning
016220bb2a refactor: change notify debug log 2024-02-21 19:07:53 +08:00
ning
ba1eb73ace refactor: add more notify debug log 2024-02-21 18:44:54 +08:00
Yening Qin
b304091fb3 fix: handle host recovery event (#1865) 2024-02-21 16:14:57 +08:00
Ulric Qin
840eaea667 Merge branch 'main' of github.com:ccfos/nightingale 2024-02-20 15:18:00 +08:00
Ulric Qin
956cc9fd68 update integrations icon 2024-02-20 15:17:46 +08:00
ning
e78e212f83 replace parser pkg 2024-02-20 11:11:44 +08:00
Ulric Qin
cdc2d4c039 update integrations icon 2024-02-18 21:02:19 +08:00
Ulric Qin
cd4b0c4f94 update integrations icon 2024-02-18 20:43:16 +08:00
Deke Wang
53ada6cc40 feat: board batch clone (#1861)
* feat: sync user in ldap to mysql

* feat:board batch clone

* Revert "feat: sync user in ldap to mysql"

This reverts commit 6063c34f0e.

* fix: use transactions to store board and payload

* fix: the busigroup is incorrectly specified

* chore: adjust import order of pkg

* refactor: batch clone the board codes

* fix: set value='' in reterr if err==nil

* refactor: move AtomicAdd to board.go

* chore: adjust import order of pkg

* chore: adjust import order of pkg
2024-02-18 10:03:08 +08:00
ning
2e6cb0f21d update windows_by_categraf dashboards 2024-02-07 16:41:23 +08:00
laiwei
4287591a6b update n9e readme 2024-02-06 18:57:13 +08:00
laiwei
2fe0c21e36 update n9e readme 2024-02-06 18:55:46 +08:00
ning
bfa043aeba refactor: optimize trigger values 2024-02-01 23:43:03 +08:00
ning
f4336ca5e9 refactor alert process 2024-02-01 22:47:48 +08:00
ning
8125cb7090 refactor alert process 2024-02-01 22:25:53 +08:00
ning
0ae1e7fbc4 refactor: auto remove ops.yaml 2024-01-31 18:57:42 +08:00
Yening Qin
88f8111a56 refactor: alert eval (#1855)
* refactor
2024-01-30 18:08:37 +08:00
ning
dbfaa519ba fix: edge query db panic 2024-01-30 11:13:12 +08:00
ning
402e803146 Merge branch 'main' of github.com:ccfos/nightingale 2024-01-30 11:09:57 +08:00
Ulric Qin
5eae14a3c9 add default settings: ForceUseServerTS = true 2024-01-30 09:54:25 +08:00
Ulric Qin
e0bfc45f5a add default configurations: ForceUseServerTS 2024-01-30 09:53:19 +08:00
Ulric Qin
7d8fb7aab7 use fasttime instead of time.Now.Unix 2024-01-30 09:50:24 +08:00
Deke Wang
846ef00aed fix: Compose host network metric log (#1852)
* fix: The status of the log is all info in compose-host-network-metric-log

* fix: set KAFKA_CFG_MESSAGE_MAX_BYTES to avoid null value error

* chore: keep the same span

---------
Co-authored-by: wdk <wdk_cc@163.com>
2024-01-29 19:01:55 +08:00
ulricqin
f2f730e88c Update linux_by_categraf.json 2024-01-29 12:09:36 +08:00
ulricqin
311a9405e4 Update bug_report.yml 2024-01-29 07:33:01 +08:00
Yening Qin
6c53981883 refactor: optimize sync user to duty (#1850)
* fix: usergroup del

* refactor user to duty

* code refactor
2024-01-26 17:01:41 +08:00
ning
f23f960368 fix: usergroup del 2024-01-26 12:59:43 +08:00
ulricqin
f593c6d310 fix migrate sql: task_records 2024-01-25 09:17:42 +08:00
ning
3fb5ea96bc chage IdentDropThreshold 2024-01-24 19:28:01 +08:00
ning
30c697a3df refactor: flashtudy sycn team 2024-01-24 14:59:26 +08:00
ning
1d50d05329 fix: sync user to flashtudy 2024-01-19 20:35:36 +08:00
Yening Qin
840221d9ec feat: auto drop metrics by ident threshold (#1845)
* auto drop data by ident

* refactor drop ident
2024-01-19 19:01:13 +08:00
Deke Wang
e52a76921f feat: sync user and user_group to flashduty (#1842)
* fix build event

* fix:  append labels

* Add the function of batch subscription alert rules (#1825)

* add: docker-compose files for logs processing

* update: set restart:always

* fix: compose-host-network-metric-log

* update: regularize

* add: batch subscription

* add: sql columns for rule_ids and rule_names

* add: add migrate of AlertSubscribe

* update: Remove redundant codes

* fix: The question of 1821

* fix: Optimized for getting rule_ids and rule_names

* fix: error handle

* fix: add rule_ids for update api

* fix: Clear the rule_id to zero when updating

* refactor: Compatible with old rule_id

* refactor: rename

* fix: set rule_id=0 when updating subscription rules

---------

Co-authored-by: wdk <wdk_cc@163.com>

* feat: sync user and team to flashduty

* fix: sync to flashduty

* fix: failed to update team change to flashduty

* fix: sync default user when create team

* chore: delete the generated binary file

* refactor: user_group refact

* fix: func AddUsers(fdConf *cconf.FlashDuty, appKey string, users []User) error {

* fix: remove sync for user in router

* fix: user_grroup no change in n9e when put user_group

* chore: set default api_url=https://api.flashcat.cloud

* chore: refactor user_group

* chore: refact codes

* chore: set api=https://jira.flashcat.cloud/api for test

* chore: set api=https://api.flashcat.cloud

* chore: adjust the import order

* chore: remove excess code

* chore: refact codes

* chore: remove excess codes

* chore: adjust import order

* chore: adjust import order

* chore: adjust import order

* chore: refact code

* chore: optimized codes

* code refactor

* chore: remove excess code

---------

Co-authored-by: ning <710leo@gmail.com>
Co-authored-by: wdk <wdk_cc@163.com>
2024-01-19 18:20:00 +08:00
ning
80fdb37129 code refactor 2024-01-18 18:52:52 +08:00
ning
bbef4aa8d9 refactor datasource api 2024-01-18 12:01:34 +08:00
ning
35eba3b1e1 refactor: datasource api 2024-01-17 17:20:11 +08:00
Yening Qin
28a1230d26 refactor:host alert in edge (#1838)
* refactor update ident

* target add engine name

* refactor: heartbeat hash ring

* target update_ts to redis

* add log

* refactor GetHostUpdateTime

* update heartbeat

* add targets-of-alert-rule

* fix recovery
2024-01-17 16:04:37 +08:00
Yening Qin
86dd6a9608 feat: support subscribe multi alert rules (#1834)
* add: batch subscription

* add: sql columns for rule_ids and rule_names

* add: add migrate of AlertSubscribe

* update: Remove redundant codes

* fix: The question of 1821

* fix: Optimized for getting rule_ids and rule_names

* fix: error handle

* fix: add rule_ids for update api

* fix: Clear the rule_id to zero when updating

* refactor: Compatible with old rule_id

* refactor: rename

* fix: set rule_id=0 when updating subscription rules

---------

Co-authored-by: wdk <wdk_cc@163.com>

* refactor: remove prod and cate in alert mute (#1828)

* add: remove monitor type in alter mute

* chore: remove prod in alter mute

* chore: remove cate in alter mute

---------

Co-authored-by: wdk <wdk_cc@163.com>

---------

Co-authored-by: Deke Wang <94156972+wdkcc@users.noreply.github.com>
Co-authored-by: wdk <wdk_cc@163.com>
2024-01-12 20:01:57 +08:00
真小明同学
f7a40b7324 fix: remove stats code that could cause panic (#1831)
Co-authored-by: xem <xemxx@qq.com>
2024-01-11 10:49:49 +08:00
laiwei
e2e8eb837d make readme zh as default 2024-01-10 11:37:20 +08:00
Yening Qin
020f7ae07e fix: extractIdentFromTimeSeries append labels (#1824)
* fix:  append labels
2024-01-08 19:56:44 +08:00
Yening Qin
8311667930 refactor: share board (#1822)
* support board-share
2024-01-08 13:07:08 +08:00
Deke Wang
741ab94150 docs: add docker-compose files for logs processing (#1819)
* add: docker-compose files for logs processing

* update: set restart:always

* fix: compose-host-network-metric-log

* update: regularize

* fix: categraf will panic when it starts

* fix: add WAIT_HOSTS of kafka for categraf and set es index=n9e-y.m.d

---------

Co-authored-by: wdk <wdk_cc@163.com>
2024-01-04 19:23:05 +08:00
Yening Qin
5d6ca183be fix build event (#1817) 2024-01-04 16:49:07 +08:00
ning
0f937ad6d0 refactor: event trigger 2023-12-28 17:48:33 +08:00
ning
ab38f220f7 feat:add my alert rule callbacks api 2023-12-27 17:45:29 +08:00
Yening Qin
a8c0b3bfd5 refactor: optimize oidc get user info (#1811)
* update oidc
2023-12-27 16:28:27 +08:00
shardingHe
5d1629bf0b docs: add oracle alert rule template (#1806)
Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-12-27 16:13:21 +08:00
ning
da7fa40c70 refactor: extract ident 2023-12-27 10:59:49 +08:00
ning
f1f0ee193f refactor: update var config api 2023-12-26 19:34:52 +08:00
ning
deccccead0 refactor: add target GetTagsMap() 2023-12-25 11:59:58 +08:00
ning
47b4464ad8 update migrate.sql 2023-12-22 17:31:05 +08:00
liooooo
3cf4a2edc1 fix:error variable incorrect invocation,it needs decryptErr but got err,it will cause error message loss and while err == nil & decryptErr != nil the program will panic (#1802) 2023-12-21 23:17:12 +08:00
Yening Qin
350f3a66dd feat: host support extra meta (#1804) 2023-12-21 23:14:30 +08:00
dependabot[bot]
f8edcabb05 build(deps): bump golang.org/x/crypto from 0.14.0 to 0.17.0 (#1798)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.17.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-21 14:18:58 +08:00
ning
00cafc613d add user service api 2023-12-20 21:01:38 +08:00
Yening Qin
8c614dc8a1 add /targets/bind perm & fix panic (#1801)
* fix panic

* add /targets/bind
2023-12-19 20:50:30 +08:00
ning
216c9d8852 feat: add single alert rule service api 2023-12-14 20:15:38 +08:00
ning
741e3eb89b fix: build 2023-12-14 15:18:04 +08:00
dependabot[bot]
bc06684694 build(deps): bump github.com/mojocn/base64Captcha from 1.3.5 to 1.3.6 (#1793)
Bumps [github.com/mojocn/base64Captcha](https://github.com/mojocn/base64Captcha) from 1.3.5 to 1.3.6.
- [Release notes](https://github.com/mojocn/base64Captcha/releases)
- [Commits](https://github.com/mojocn/base64Captcha/compare/v1.3.5...v1.3.6)

---
updated-dependencies:
- dependency-name: github.com/mojocn/base64Captcha
  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-12-14 12:53:06 +08:00
shardingHe
2539cb9c1a update canal dashboards (#1794)
Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-12-14 12:52:36 +08:00
ning
32dd3d5098 refactor: add event log 2023-12-08 16:21:58 +08:00
Yening Qin
b6cf382b86 feat: optimize tplx (#1789)
* optimize-tplx
2023-12-08 14:40:48 +08:00
ning
03d19a797c fix refresh event group name 2023-12-08 11:30:40 +08:00
ning
98cbc14039 code refactor 2023-12-08 11:21:48 +08:00
ning
248bb50b3e Merge branch 'main' of github.com:ccfos/nightingale 2023-12-08 11:13:25 +08:00
ning
01f1dcf93e optimize show annotations 2023-12-08 11:13:10 +08:00
Yening Qin
fdac82b8dc refactor: add more self metrics (#1788)
*  add stats metrics
2023-12-08 00:32:18 +08:00
shardingHe
0f926cb218 feat: Dashboards for SQL Server (#1781)
* remove defaultValue for datasource.

* add sqlserver dashboards

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-12-08 00:11:21 +08:00
ning
de35b61b52 refactor: aop log 2023-12-05 20:32:50 +08:00
ning
341aa3f070 update migrate 2023-12-05 11:08:11 +08:00
ning
f47254e72d fix: notify tpl put 2023-12-05 10:59:29 +08:00
Yening Qin
0b75d4d2ed feat: support drop timeseries by labels (#1784)
* feat: drop sample
2023-12-02 11:44:52 +08:00
Yening Qin
d204aa0cd4 refactor build noitfy tpl (#1783)
* refactor build tpl

* change notify api
2023-12-02 11:19:34 +08:00
Yening Qin
4f6584a41d refactor: extract Ident from timeseries (#1782) 2023-12-01 16:37:03 +08:00
ning
8f8f24ccfe refactor: change sql-template api 2023-11-30 16:57:42 +08:00
ning
0f2257b8bb Merge branch 'main' of github.com:ccfos/nightingale 2023-11-30 12:06:59 +08:00
ning
8bd99f13c1 docs: change i18n 2023-11-30 12:06:41 +08:00
shardingHe
f8deb89592 feat: add gorm logger (#1768)
* add gorm logger

* add gorm logger implement, slow log save to warning-level file

* optimize gorm logger

* optimize code

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-11-30 12:02:23 +08:00
Yening Qin
701407581b feat: support multi resources get (#1780)
* get alert rules by gids

* add perm check

* code refactor

* add tasks api

* code refactor

* add site-info api

* update targets api

* add /site-settings
2023-11-28 15:37:40 +08:00
ning
ba2ee05bc0 docs: rename clone board name 2023-11-23 20:38:12 +08:00
ning
c6e649129e refactor: role ops api 2023-11-22 14:49:10 +08:00
Yening Qin
329249ea99 refactor: ops i18n (#1778)
* ops i18n

* update gomod

* code refactor
2023-11-22 13:57:30 +08:00
Yening Qin
65d8a30396 refactor: datasource api (#1777)
* ds refactor
2023-11-20 23:28:15 +08:00
ning
e29a45c4a3 refactor: update role_operation 2023-11-17 17:13:44 +08:00
Yening Qin
438078cdc5 target add agent version (#1776)
* add agent-version
2023-11-17 16:57:56 +08:00
ning
ae07ba7523 docs: update default cas config 2023-11-15 12:14:06 +08:00
ning
f201b12dd8 Merge branch 'main' of github.com:ccfos/nightingale 2023-11-15 11:58:36 +08:00
ning
ee5322f406 change alert_rule callbacks length 2023-11-15 11:58:21 +08:00
Yening Qin
60a2e0c963 feat: writer support mult addrs (#1775)
* support mult write
2023-11-15 10:56:13 +08:00
Ulric Qin
2b55ed9b46 code refactor 2023-11-15 10:33:38 +08:00
ning
68eb7cb57e docs: update migrate sql 2023-11-13 17:54:33 +08:00
ning
6387b601b1 docs: add auto migrate sql 2023-11-13 17:52:10 +08:00
ning
af58fa8802 code refactor 2023-11-13 17:40:31 +08:00
ning
80daea5744 Merge branch 'main' of github.com:ccfos/nightingale 2023-11-13 17:33:38 +08:00
ning
bf9a471484 docs: add auto migrate sql 2023-11-13 17:31:56 +08:00
shardingHe
195ed9761c docs: built-in dashboards remove defaultValue for datasource (#1773)
Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-11-13 14:31:13 +08:00
Ulric Qin
fdc0123681 update logo 2023-11-11 22:21:15 +08:00
ning
6fd75ae552 Merge branch 'main' of github.com:ccfos/nightingale 2023-11-10 14:42:31 +08:00
ning
10c462a477 fix: insert ibex-settings perm point 2023-11-10 14:42:18 +08:00
Ulric Qin
694c43292a add api: /api/n9e/user/busi-groups 2023-11-09 20:07:30 +08:00
ning
cfa78dc9e2 feat: alert_subscribe support note and disabled 2023-11-09 00:15:36 +08:00
ning
cc80f5b685 fix: add ibex-settings perm point 2023-11-08 19:08:58 +08:00
ning
58f4a11669 refactor: datasource update 2023-11-08 18:17:22 +08:00
Yening Qin
4f57624a67 refactor: timeteries handle hook (#1772)
* refactor:  timeteries handle hook
2023-11-08 15:11:39 +08:00
Yening Qin
9558520dcd feat: host filter support * (#1769) 2023-11-07 11:19:18 +08:00
dependabot[bot]
8ded3623a4 build(deps): bump golang.org/x/image from 0.5.0 to 0.10.0 (#1767)
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.5.0 to 0.10.0.
- [Commits](https://github.com/golang/image/compare/v0.5.0...v0.10.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-11-07 11:12:54 +08:00
ning
12fcca2faf docs: change integrations 2023-11-03 15:32:27 +08:00
xtan
9dc20fc674 fix: fix windows dashboard (#1762) 2023-10-27 19:32:23 +08:00
Lars Lehtonen
16430550d1 models: fix clobbered error (#1764) 2023-10-27 19:32:07 +08:00
ning
b34b66785d change notify api perm 2023-10-27 19:22:13 +08:00
ning
2cf38b6027 docs: update built-in linux dashboard 2023-10-27 19:08:46 +08:00
ning
e1b4edaa68 refactor: user-variable-configs api 2023-10-27 18:58:22 +08:00
ning
97f3f70d57 refactor perm api 2023-10-27 18:13:04 +08:00
ning
cee0ce6620 code refactor 2023-10-27 17:40:11 +08:00
ning
89b659695f add ops 2023-10-27 17:35:00 +08:00
Yening Qin
d52848ab1b feat: http support print body log (#1757)
* add log
2023-10-24 11:37:34 +08:00
shardingHe
314a8d71ef Fix:use text template to replace data (#1756)
* add text/template func, avoid escape issues caused by special characters

* rename the function of template

* change use of template. ReplaceTemplateUseHtml replaced to ReplaceTemplateUseText

* change use of template. ReplaceTemplateUseHtml replaced to ReplaceTemplateUseText

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-10-23 17:22:34 +08:00
shardingHe
bfa85cd8f1 fix: add func for tplx, avoid escape issues caused by special characters (#1755)
* add text/template func, avoid escape issues caused by special characters

* rename the function of template

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-10-23 16:35:08 +08:00
shardingHe
2254cb1f87 fix: move dashboards to the right place (#1753)
* move dashboards to the right place

* move dashboards to the right place

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-10-21 11:49:48 +08:00
ning
17cbfb8453 change alert_rule extra_config type 2023-10-20 11:26:30 +08:00
ning
6b89b7b4a5 change configs cval type 2023-10-20 10:20:08 +08:00
shardingHe
4fe5828d8d feat: macro variable in Configs (#1725)
* configs form user crud

* configs form user crud

* code refactor

* code refactor

* configs for user variable update & add InitRSAConfig for alert.go

* configs for user variable update

* remove InitRSAConfig for alert.go

* add config of Center.Encryption

* migrate for config and set default value

* add annotation for Center.Encryption

* code refactor

* code refactor

* code refactor again

* code refactor

* remove userVariableCheck bool return

* remove userVariableCheck bool return

* optimize InitRSAConfig

* optimize InitRSAConfig

* optimize InitRSAConfig

* macro variable

* optimize config

* remove test function user-variable-ras

* code refactor config_cache

* code refactor again

* code refactor again

* ReplaceMacroVariables return string value & optimize code

* add user variable check on ckey, it is not recommended to use the period "."

* change configs ckey+external=uniqueness

* configs add add external value

* configs remove isInternal check(ckey is no longer unique field)

* update userVariableCheck

* migrate drop unique filed limit

* refactor rsa generate logic. read key pairs from config.toml(HTTP.RSA) if file is not exist generate rsa key paris and saving to database(configs).

* Improve the RSA key generation logic. In the first step, attempt to read key pairs from the database or the 'config.toml' file. If they don't exist, generate a new set of key pairs and store them in the database.

* optimize code

* ckey use C-style naming convention and optimize smtp validations

* change email logger. print host & port

* update init rsa config logically

* update migrate about DropUniqueFiled

* update migrate about DropUniqueFiled

* make migrating at the first step

* move config of rsa. generate in db.

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
Co-authored-by: Yening Qin <710leo@gmail.com>
2023-10-19 15:02:59 +08:00
Yening Qin
98422d696e refactor: heartbeat api (#1747) 2023-10-17 17:19:37 +08:00
shardingHe
e3103faeae refactor: Integrations sync from categraf (#1739)
* sync fist step

* integrations sync 14

* integrations sync 20

* integrations sync

* integrations sync 1 & add aliyun alerts

* update v2 dashboard to v3, and move dashboard to cloudwatch, cAdvisor, nginx_upstream_check. make sure all dashboards's name is unique.

* update readme

* delete nsq readme file

* rename switch_legacy,delete sockstat

* Rename the directory to match the lowercase plugin name

* add AMD_ROCm_SMI

* add ipvs

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-10-17 11:05:52 +08:00
Ulric Qin
0b23ddffb2 ignore rsa 2023-10-16 16:23:50 +08:00
Ulric Qin
37fa12e214 remove rust wait tool 2023-10-16 13:29:14 +08:00
Ulric Qin
328f8ac125 Merge branch 'main' of github.com:ccfos/nightingale 2023-10-16 12:25:21 +08:00
Ulric Qin
744749d22b remove rust wait tool 2023-10-16 12:25:09 +08:00
Yening Qin
a56fd039b4 fix eval query err (#1743) 2023-10-16 12:18:27 +08:00
Ulric Qin
e16867b72a code refactor 2023-10-16 12:12:09 +08:00
Ulric Qin
ff6756447b refactor docker compose configurations 2023-10-15 17:05:35 +08:00
Ulric Qin
546980a906 add tcpx.WaitHosts 2023-10-15 14:26:54 +08:00
ning
f93e2ad4b6 fix: push sample 2023-10-13 11:54:31 +08:00
ning
68732d6b31 refactor: rsa generate file 2023-10-12 20:33:42 +08:00
shardingHe
c3b8146e7f fix: rsa decrypt & generate method (#1740)
* Maintain consistency between encryption and decryption methods

* Maintain consistency between encryption and decryption methods

* Maintain consistency between encryption and decryption methods

* optimize code

* optimize code

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-10-12 19:34:55 +08:00
ning
271b7ca8a5 refactor get rule type 2023-10-12 19:33:05 +08:00
ning
2fa87cc428 fix: tdengine alert inhibit 2023-10-12 16:44:39 +08:00
ning
ffa7c4ee79 docs: delete ops.yaml 2023-10-12 15:50:33 +08:00
ning
86c4374238 refactor: unified heartbeat api 2023-10-12 15:27:02 +08:00
dependabot[bot]
4ee8f1b9ad build(deps): bump golang.org/x/net from 0.10.0 to 0.17.0 (#1738)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.10.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.10.0...v0.17.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-10-12 14:47:43 +08:00
ning
47dcd2b054 refactor: update go.mod 2023-10-12 14:44:45 +08:00
yuweizzz
d099e6b85c refactor: convert task script line endings (#1717) 2023-10-09 10:30:39 +08:00
shardingHe
320401e8f3 fix: sync event ID for edge model (#1710)
* sync event id for edge model

* update PostByUrlsWithResp

* update test file

* update test file

* update mock struct

* update mock struct

* update mock struct

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-10-09 10:29:04 +08:00
Ulric Qin
6d75244f8f Merge branch 'main' of github.com:ccfos/nightingale 2023-10-09 07:01:27 +08:00
Ulric Qin
05ac5d51b5 forward metrics one by one 2023-10-09 07:01:13 +08:00
shardingHe
2b7c9a9673 feat: merge operation permission points from built-in (#1729)
* merge operation permission points from build-in

* optimizing spelling

* optimizing ops

* remove duplicate role operation

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-10-07 11:49:52 +08:00
Yening Qin
f76bfdf6b3 Datasource add is_default column (#1728)
* ds add is_default

* code refactor
2023-09-28 15:05:14 +08:00
Lars Lehtonen
7ebb70b896 prom: fix dropped errors (#1727) 2023-09-27 20:27:18 +08:00
Ulric Qin
960ed6bf70 Merge branch 'main' of github.com:ccfos/nightingale 2023-09-27 10:00:30 +08:00
Ulric Qin
b4fd4f1087 code refactor 2023-09-27 09:59:49 +08:00
shardingHe
109d6db1fc fix: Optimize user config (#1724)
* optimize initRSAFile

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-09-26 10:18:38 +08:00
shardingHe
07e2d9ed10 feat: configs from user crud (#1714)
* configs form user crud

* configs for user variable update & add InitRSAConfig for alert.go

* configs for user variable update

* remove InitRSAConfig for alert.go

* add config of Center.Encryption

* migrate for config and set default value

* add annotation for Center.Encryption

* remove userVariableCheck bool return

* optimize InitRSAConfig

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
Co-authored-by: Yening Qin <710leo@gmail.com>
2023-09-25 20:44:07 +08:00
ning
c5cd6c0337 docs: update integrations 2023-09-25 13:27:35 +08:00
Yening Qin
fe1d566326 feat: add tdengine datasource (#1722)
* refactor: add alert stats

* add tdengine

* add sql tpl api
2023-09-25 11:40:20 +08:00
Lars Lehtonen
cedc918a09 pkg/secu: fix dropped error (#1698) 2023-09-25 11:03:00 +08:00
shardingHe
1e6c0865dd feat: merge i18n configuration (#1701)
* merge expand config and build-in, prioritize the settings within the expand config options in case of conflicts

* code refactor

* code refactor

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-09-22 17:49:22 +08:00
shardingHe
7649986b55 fix: oauth2 add debug log (#1706)
* oauth2 add CallbackOutput debug log

* code refactor

* code refactor

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-09-22 17:48:14 +08:00
yanli
86a82b409a Update user_group.go (#1718)
Fix :修复同步用户组接口BUG
2023-09-21 17:15:11 +08:00
710leo
f6ad9bdf82 add oidc log 2023-09-19 17:27:50 +08:00
Yening Qin
a647526084 config api (#1716) 2023-09-19 16:29:12 +08:00
qifenggang
44ed90e181 set target_ident when cache is not sync (#1708) 2023-09-18 17:51:59 +08:00
shardingHe
3e7273701d feat: add host ip for target (#1711)
* add host ip for target

* update host ip from heartbeat

* update host ip from heartbeat

* batch update host ip from heartbeat

* batch update host ip from heartbeat

* refactor code

* update target when either gid or host_ip has a new value

* rollback

* add debug log

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-09-14 14:45:16 +08:00
Ulric Qin
d77ed30940 add some sso logs 2023-09-11 18:30:30 +08:00
yuweizzz
5ae80e67a3 Update http_response_by_categraf.json (#1704) 2023-09-08 08:34:46 +08:00
idcdog
184389be33 1、增加victoriametrics单级版本视图(视图项定义参考官方grafana版本);2、针对集群版本视图调整datasource命名以便适配最新版本n9e (#1702) 2023-09-07 08:42:02 +08:00
ning
c1f022001f refactor: loki datasource 2023-09-01 23:14:08 +08:00
Tripitakav
616d56d515 feat: support loki datasources (#1700) 2023-09-01 23:07:27 +08:00
ning
10a0b5099e add debug log 2023-08-30 17:38:01 +08:00
chixianliangGithub
0815605298 Update reader.go (#1689)
remove  invalid code
2023-08-30 10:24:26 +08:00
ning
2df3216b32 refactor: event stats api 2023-08-29 15:33:24 +08:00
shardingHe
74491c666d Compute statistics for alert_cur_events (#1697)
* add statistics of alert_cur_events

* code refactor

* code refactor

* code refactor

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-08-29 15:15:32 +08:00
Ulric Qin
29a2eb6f2f code refactor 2023-08-28 11:13:29 +08:00
Ulric Qin
baf56746ce Merge branch 'main' of github.com:ccfos/nightingale 2023-08-28 10:33:48 +08:00
Ulric Qin
5867c5af8f update dashboard table demo 2023-08-28 10:33:34 +08:00
shardingHe
4a358f5cff fix: Automatically migrate recent table structure changes. (#1696)
* auto migrate alerting_engines[engine_cluster],task_record[event_id],chart_share[datasource_id],recording_rule[datasource_ids]

* auto migrate code refactor

* auto migrate drop chart_share

* auto migrate drop dashboard_id

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-08-25 18:46:22 +08:00
Ulric Qin
13f2b008fd code refactor 2023-08-25 17:52:09 +08:00
Ulric Qin
84400cd657 code refactor 2023-08-25 10:20:41 +08:00
Ulric Qin
f2a3a6933e Merge branch 'main' of github.com:ccfos/nightingale 2023-08-25 10:15:04 +08:00
Ulric Qin
0a4d1cad4c add linux_by_categraf_zh.json 2023-08-25 10:14:52 +08:00
李明
08f472f9ee fix: PostgreSQL bool int convert error (#1695)
* fix: postgres bool int

* fix: code optimize

* fix: updateby

* optimize

* Update es_index_pattern.go

---------

Co-authored-by: Yening Qin <710leo@gmail.com>
2023-08-24 20:53:14 +08:00
ning
7f73945c8d Merge branch 'main' of github.com:ccfos/nightingale 2023-08-24 16:09:43 +08:00
ning
56a7860b5a docs: update integrations 2023-08-24 16:07:55 +08:00
shardingHe
25dab86b8e feat: add mute preview and an option to delete the active alerts (#1692)
* add mute preview and an option to delete these active alerts(del_alert_cur:true)


---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
Co-authored-by: Yening Qin <710leo@gmail.com>
2023-08-24 15:56:42 +08:00
Ulric Qin
35b90ca162 upgrade mysql version in docker-compose 2023-08-23 16:43:09 +08:00
Ulric Qin
5babee6de3 Merge branch 'main' of github.com:ccfos/nightingale 2023-08-22 20:43:38 +08:00
Ulric Qin
7567d440a9 update metrics.yaml 2023-08-22 20:43:17 +08:00
shardingHe
2ecd799dab fix: attempt to send an email (#1691)
* After configuring the SMTP, attempt to send the email.

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
Co-authored-by: Yening Qin <710leo@gmail.com>
2023-08-22 17:43:12 +08:00
shardingHe
5b3561f983 fix: alert pre-save validation (#1690)
* add interface of validation rule
---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
Co-authored-by: Yening Qin <710leo@gmail.com>
2023-08-22 17:42:05 +08:00
shardingHe
cce3711c02 fix: Edge config check (#1686)
* check the configuration for the CenterApi in the edge model

* set default timeout 5000 ms

* Update edge.go

* Update post.go

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
Co-authored-by: Yening Qin <710leo@gmail.com>
2023-08-21 16:46:55 +08:00
ning
9cdbda0828 refactor: alert rule mute strategy 2023-08-19 20:17:24 +08:00
ning
9c4775fd38 refactor: alert subscribe gets api 2023-08-18 11:20:13 +08:00
ning
212e0aa4c3 refactor: alert subscribe verify 2023-08-18 10:20:33 +08:00
xtan
05300ec0e9 fix: compatible with pg (#1683)
* fix: fix pg migrate

* fix: compatible with pg

* fix: compatible with pg
2023-08-18 10:00:00 +08:00
shardingHe
67fb49e54e After configuring the SMTP, attempt to send the email. (#1684)
* After configuring the SMTP, attempt to send the email.

* refactor code

* refactor code

* Update router_notify_config.go

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
Co-authored-by: Yening Qin <710leo@gmail.com>
2023-08-17 20:30:53 +08:00
shardingHe
7164b696b1 Change validate rule ignore non-default channels (#1680)
* change validate rule ignore non-default channels

* refactor code

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
Co-authored-by: Yening Qin <710leo@gmail.com>
2023-08-17 18:18:20 +08:00
Yening Qin
8728167733 feat: sso support skip tls verify (#1685)
* refactor: sso support skip tls verify

* fix: update oidc

* fix: cas init enable
2023-08-17 18:03:26 +08:00
ning
6e80a63b68 refactor: cur event del 2023-08-17 14:20:03 +08:00
kongfei605
9e43a22ec3 use redis.Cmdable instead of Redis (#1681) 2023-08-16 18:37:57 +08:00
Ulric Qin
49d8ed4a6f Merge branch 'main' of github.com:ccfos/nightingale 2023-08-16 17:44:03 +08:00
Ulric Qin
c7b537e6c7 expose tags_map 2023-08-16 17:43:48 +08:00
shardingHe
f1cdd2fa46 refactor: modify the alert_subscribe to make the datasource optional (#1679)
* subscribe change 'pord','datasource_ids' to optional item

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-08-16 14:23:16 +08:00
ning
3d5ad02274 feat: notification proxy supports http 2023-08-14 18:00:51 +08:00
Ulric Qin
1cb9f4becf code refactor 2023-08-14 15:01:36 +08:00
Ulric Qin
0d0dafbe49 code refactor 2023-08-14 15:00:05 +08:00
Ulric Qin
048d1df2d1 code refactor 2023-08-14 14:59:28 +08:00
ning
4fb4154e30 feat: add FormatDecimal 2023-08-10 23:15:09 +08:00
ning
0be69bbccd feat: add FormatDecimal 2023-08-10 22:58:36 +08:00
shardingHe
7015a40256 refactor: alert subscribe verify check (#1666)
* add BusiGroupFilter for alert_subscribe ,copy from TagFiler

* refactor BusiGroupFilter

* refactor BusiGroupFilter

* refactor BusiGroupFilter

* AlertSubscribe verify check

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-08-09 13:26:00 +08:00
Ulric Qin
03cca642e9 modify email words 2023-08-08 16:30:51 +08:00
ulricqin
579fd3780b Update community-governance.md 2023-08-08 10:55:14 +08:00
Ulric Qin
a85d91c10e Merge branch 'main' of github.com:ccfos/nightingale 2023-08-08 07:55:40 +08:00
Ulric Qin
af31c496a1 datasource checker for loki 2023-08-08 07:55:27 +08:00
shardingHe
f9efbaa954 refactor: use config arguments (#1665)
Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-08-07 19:03:15 +08:00
Ulric Qin
d541ec7f20 Merge branch 'main' of github.com:ccfos/nightingale 2023-08-07 09:16:05 +08:00
Ulric Qin
1d847e2c6f refactor datasource check of loki 2023-08-07 09:15:52 +08:00
xtan
2fedf4f075 docs: pg init sql (#1663) 2023-08-07 08:33:11 +08:00
Tripitakav
e9a02c4c80 refactor: sync rule to scheduler (#1657) 2023-08-07 08:29:50 +08:00
ning
8beaccdded refactor: GetTagFilters 2023-08-05 12:39:10 +08:00
shardingHe
af6003da6d feat: Add BusiGroupFilter for alert_subscribe (#1660)
* add BusiGroupFilter for alert_subscribe ,copy from TagFiler

* refactor BusiGroupFilter

* refactor BusiGroupFilter

---------

Co-authored-by: shardingHe <wangzihe@flashcat.cloud>
2023-08-04 18:30:00 +08:00
ning
76ac2cd013 refactor version api 2023-08-03 18:06:09 +08:00
ning
859876e3f8 change version api 2023-08-03 16:34:42 +08:00
ning
7d49e7fb34 feat: add github version api 2023-08-03 15:46:18 +08:00
ning
6c42ae9077 fix: query-range skip tls verify 2023-08-03 14:42:45 +08:00
Yening Qin
15dcc60407 refactor: proxy api (#1656)
* refactor: proxy api
2023-08-02 17:20:06 +08:00
Ulric Qin
5b811b7003 Merge branch 'main' of github.com:ccfos/nightingale 2023-08-02 16:22:09 +08:00
Ulric Qin
55d670fe3c code refactor 2023-08-02 16:21:57 +08:00
ning
ac3a5e52c7 docs: update ldap config 2023-08-02 14:16:20 +08:00
李明
2abe00e251 fix: post err process (#1653) 2023-08-02 13:33:44 +08:00
热心网友吴溢豪
1bd3c29e39 fix: open cstats init (#1654)
Co-authored-by: wuyh_1 <wuyh_1@chinatelecom.cn>
2023-08-02 13:28:36 +08:00
Ulric Qin
1a8087bda7 update zookeeper markdown 2023-08-02 09:36:24 +08:00
Ulric Qin
72b4c2b1ec update markdown of vmware 2023-08-02 09:20:09 +08:00
Ulric Qin
38e6820d7b update markdown of VictoriaMetrics 2023-08-02 09:10:14 +08:00
Ulric Qin
765b3a57fe update markdown of tomcat 2023-08-02 09:03:59 +08:00
Ulric Qin
1c4a32f8fa code refactor 2023-08-02 09:01:39 +08:00
Ulric Qin
3f258fcebf update markdown of springboot 2023-08-02 08:57:39 +08:00
Ulric Qin
140f2cbfa8 update markdown if snmp 2023-08-02 08:44:45 +08:00
Ulric Qin
6aacd77492 update markdown of redis 2023-08-02 08:34:51 +08:00
Ulric Qin
ef3f46f8b7 update markdown of integration RabbitMQ 2023-08-02 08:26:06 +08:00
Ulric Qin
0cdd25d2cf update markdown of integration Processes 2023-08-02 08:20:51 +08:00
Ulric Qin
5d02ce0636 update markdown of integration procstat 2023-08-02 08:17:41 +08:00
Ulric Qin
0cd1228ba7 update postgres markdown 2023-08-02 07:38:46 +08:00
Ulric Qin
0595401d14 update oracle markdown 2023-08-02 07:30:03 +08:00
Yening Qin
d724f8cc8e fix get tpl (#1655) 2023-08-02 00:48:02 +08:00
Ulric Qin
a3f5d458d7 add nginx markdown 2023-08-01 18:21:17 +08:00
Ulric Qin
76bfb130b0 code refactor 2023-08-01 18:05:50 +08:00
Ulric Qin
184bb78e3b add markdown of integration n9e 2023-08-01 17:49:49 +08:00
Ulric Qin
6a41af2cb2 update markdown of integration mysql 2023-08-01 17:42:30 +08:00
Ulric Qin
faa149cc87 code refactor 2023-08-01 17:26:10 +08:00
Ulric Qin
24592fe480 code refactor 2023-08-01 17:18:12 +08:00
Ulric Qin
4be53082e0 code refactor 2023-08-01 17:04:36 +08:00
Ulric Qin
ae8c9c668c code refactor 2023-08-01 16:52:21 +08:00
Ulric Qin
b0c15af04f code refactor 2023-08-01 16:39:46 +08:00
Ulric Qin
c05b710aff update markdown of kafka integration 2023-08-01 16:21:45 +08:00
Ulric Qin
4299c48aef update markdown of IPMI integration 2023-08-01 15:58:27 +08:00
Ulric Qin
ae0523dec0 code refactor 2023-08-01 15:50:26 +08:00
Ulric Qin
e18a6bda7b update markdown of integration http_response 2023-08-01 15:47:45 +08:00
Ulric Qin
e64be95f1c code refactor 2023-08-01 15:25:14 +08:00
Ulric Qin
a1aa0150f8 update markdown of integration elasticsearch 2023-08-01 15:14:27 +08:00
Ulric Qin
32f9cb5996 update markdown of ceph integration 2023-08-01 14:59:10 +08:00
Ulric Qin
3b7e692b01 update markdown of aliyun integration 2023-08-01 14:54:52 +08:00
Yening Qin
6491eba1da check datasource (#1651) 2023-07-31 15:32:03 +08:00
ning
bb7ea7e809 code refactor 2023-07-27 16:36:04 +08:00
ning
169930e3b8 docs: add markdown 2023-07-27 16:33:38 +08:00
ning
8e14047f36 docs: add markdown 2023-07-27 16:25:07 +08:00
ning
fd29a96f7b docs: remove jaeger 2023-07-27 15:24:58 +08:00
ning
820c12f230 docs: update markdown 2023-07-27 15:13:54 +08:00
ning
ff3550e7b3 docs: update markdown 2023-07-27 15:09:32 +08:00
ning
b65e43351d Merge branch 'main' of github.com:ccfos/nightingale 2023-07-27 15:02:58 +08:00
ning
3fb74b632b docs: update markdown 2023-07-27 15:02:35 +08:00
xtan
253e54344d docs: fix docker-compose for pg-vm (#1649) 2023-07-27 10:50:38 +08:00
ning
f1ee7d24a6 fix: sub rule filter 2023-07-26 18:14:45 +08:00
ning
475673b3e7 fix: admin role get targets 2023-07-26 16:49:38 +08:00
Yening Qin
dd49afef01 support markdown api and downtime select (#1645) 2023-07-25 17:06:25 +08:00
kongfei605
d0c842fe87 Merge pull request #1644 from ccfos/docker_update
install requests lib for python3
2023-07-25 11:19:14 +08:00
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
736 changed files with 139645 additions and 15579 deletions

View File

@@ -11,7 +11,7 @@ body:
- type: textarea
id: config
attributes:
label: Relevant server.conf | webapi.conf
label: Your config.toml
description: Place config in the toml code section. This will be automatically formatted into toml, so no need for backticks.
render: toml
validations:
@@ -20,7 +20,7 @@ body:
id: logs
attributes:
label: Relevant logs
description: categraf | telegraf | server | webapi | prometheus | chrome request/response ...
description: categraf | telegraf | n9e | prometheus | chrome request/response ...
render: text
validations:
required: true

14
.gitignore vendored
View File

@@ -30,7 +30,11 @@ _test
/dist
/etc/*.local.yml
/etc/*.local.conf
/etc/rsa/*
/etc/plugins/*.local.yml
/etc/script/rules.yaml
/etc/script/alert-rules.json
/etc/script/record-rules.json
/data*
/tarball
/run
@@ -40,8 +44,14 @@ _test
/n9e
/docker/pub
/docker/n9e
/docker/mysqldata
/etc.local
/docker/compose-bridge/mysqldata
/docker/compose-host-network/mysqldata
/docker/compose-host-network-metric-log/mysqldata
/docker/compose-host-network-metric-log/n9e-logs
/docker/compose-postgres/pgdata
/etc.local*
/front/statik/statik.go
/docker/compose-bridge/etc-nightingale/rsa/
.alerts
.idea

View File

@@ -2,6 +2,7 @@ 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 }}'
@@ -14,7 +15,8 @@ builds:
- id: build
hooks:
pre:
- ./fe.sh
- cmd: sh -x ./fe.sh
output: true
main: ./cmd/center/
binary: n9e
env:
@@ -40,22 +42,9 @@ builds:
ldflags:
- -s -w
- -X github.com/ccfos/nightingale/v6/pkg/version.Version={{ .Tag }}-{{.Commit}}
- id: build-alert
main: ./cmd/alert/
binary: n9e-alert
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-pushgw
main: ./cmd/pushgw/
binary: n9e-pushgw
- id: build-edge
main: ./cmd/edge/
binary: n9e-edge
env:
- CGO_ENABLED=0
goos:
@@ -72,8 +61,7 @@ archives:
builds:
- build
- build-cli
- build-alert
- build-pushgw
- build-edge
format: tar.gz
format_overrides:
- goos: windows
@@ -83,7 +71,6 @@ archives:
files:
- docker/*
- etc/*
- pub/*
- integrations/*
- cli/*
- n9e.sql
@@ -103,7 +90,6 @@ dockers:
- build
dockerfile: docker/Dockerfile.goreleaser
extra_files:
- pub
- etc
- integrations
use: buildx
@@ -117,7 +103,6 @@ dockers:
- build
dockerfile: docker/Dockerfile.goreleaser.arm64
extra_files:
- pub
- etc
- integrations
use: buildx

View File

@@ -1,4 +1,4 @@
.PHONY: start build
.PHONY: prebuild build
ROOT:=$(shell pwd -P)
GIT_COMMIT:=$(shell git --work-tree ${ROOT} rev-parse 'HEAD^{commit}')
@@ -6,27 +6,35 @@ _GIT_VERSION:=$(shell git --work-tree ${ROOT} describe --tags --abbrev=14 "${GIT
TAG=$(shell echo "${_GIT_VERSION}" | awk -F"-" '{print $$1}')
RELEASE_VERSION:="$(TAG)-$(GIT_COMMIT)"
all: build
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:
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:
run-alert:
nohup ./n9e-alert > n9e-alert.log 2>&1 &
run_pushgw:
run-pushgw:
nohup ./n9e-pushgw > n9e-pushgw.log 2>&1 &
release:

187
README.md
View File

@@ -1,148 +1,99 @@
<p align="center">
<a href="https://github.com/ccfos/nightingale">
<img src="doc/img/nightingale_logo_h.png" alt="nightingale - cloud native monitoring" width="240" /></a>
<img src="doc/img/Nightingale_L_V.png" alt="nightingale - cloud native monitoring" width="100" /></a>
</p>
<p align="center">
<b>开源告警管理专家 一体化的可观测平台</b>
</p>
<p align="center">
<img alt="GitHub latest release" src="https://img.shields.io/github/v/release/ccfos/nightingale"/>
<a href="https://n9e.github.io">
<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>
<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>
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/ccfos/nightingale">
<img alt="GitHub forks" src="https://img.shields.io/github/forks/ccfos/nightingale">
<br/><img alt="GitHub Repo issues" src="https://img.shields.io/github/issues/ccfos/nightingale">
<img alt="GitHub Repo issues closed" src="https://img.shields.io/github/issues-closed/ccfos/nightingale">
<img alt="GitHub 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>
<img alt="License" src="https://img.shields.io/badge/license-Apache--2.0-blue"/>
</p>
<p align="center">
<b>All-in-one</b> 的开源观测平台 <br/>
<b>开箱即用</b>,集数据采集、可视化、监控告警于一体 <br/>
推荐升级您的 <b>Prometheus + AlertManager + Grafana + ELK + Jaeger</b> 组合方案到夜莺!
</p>
[English](./README_en.md) | [中文](./README.md)
## 夜莺 Nightingale 是什么
夜莺监控是一款开源云原生观测分析工具,采用 All-in-One 的设计理念,集数据采集、可视化、监控告警、数据分析于一体,与云原生生态紧密集成,提供开箱即用的企业级监控分析和告警能力。夜莺于 2020 年 3 月 20 日,在 github 上发布 v1 版本,已累计迭代 100 多个版本。
夜莺最初由滴滴开发和开源,并于 2022 年 5 月 11 日捐赠予中国计算机学会开源发展委员会CCF ODC为 CCF ODC 成立后接受捐赠的第一个开源项目。夜莺的核心研发团队,也是 Open-Falcon 项目原核心研发人员,从 2014 年Open-Falcon 是 2014 年开源)算起来,也有 10 年了,只为把监控这个事情做好。
## 功能和特点
## 快速开始
- 👉[文档中心](https://flashcat.cloud/docs/) | [下载中心](https://flashcat.cloud/download/nightingale/)
- ❤️[报告 Bug](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Fbug&projects=&template=bug_report.yml)
- ℹ️为了提供更快速的访问体验,上述文档和下载站点托管于 [FlashcatCloud](https://flashcat.cloud)
- **开箱即用**
- 支持 Docker、Helm Chart、云服务等多种部署方式集数据采集、监控告警、可视化为一体内置多种监控仪表盘、快捷视图、告警规则模板导入即可快速使用**大幅降低云原生监控系统的建设成本、学习成本、使用成本**
- **专业告警**
- 可视化的告警配置和管理,支持丰富的告警规则,提供屏蔽规则、订阅规则的配置能力,支持告警多种送达渠道,支持告警自愈、告警事件管理等;
- **推荐您使用夜莺的同时,无缝搭配[FlashDuty](https://flashcat.cloud/product/flashcat-duty/),实现告警聚合收敛、认领、升级、排班、协同,让告警的触达既高效,又确保告警处理不遗漏、做到件件有回响**。
- **云原生**
- 以交钥匙的方式快速构建企业级的云原生监控体系,支持 [Categraf](https://github.com/flashcatcloud/categraf)、Telegraf、Grafana-agent 等多种采集器,支持 Prometheus、VictoriaMetrics、M3DB、ElasticSearch、Jaeger 等多种数据源,兼容支持导入 Grafana 仪表盘,**与云原生生态无缝集成**
- **高性能 高可用**
- 得益于夜莺的多数据源管理引擎,和夜莺引擎侧优秀的架构设计,借助于高性能时序库,可以满足数亿时间线的采集、存储、告警分析场景,节省大量成本;
- 夜莺监控组件均可水平扩展,无单点,已在上千家企业部署落地,经受了严苛的生产实践检验。众多互联网头部公司,夜莺集群机器达百台,处理数亿级时间线,重度使用夜莺监控;
- **灵活扩展 中心化管理**
- 夜莺监控,可部署在 1 核 1G 的云主机,可在上百台机器集群化部署,可运行在 K8s 中;也可将时序库、告警引擎等组件下沉到各机房、各 Region兼顾边缘部署和中心化统一管理**解决数据割裂,缺乏统一视图的难题**
- **开放社区**
- 托管于[中国计算机学会开源发展委员会](https://www.ccf.org.cn/kyfzwyh/),有[快猫星云](https://flashcat.cloud)和众多公司的持续投入,和数千名社区用户的积极参与,以及夜莺监控项目清晰明确的定位,都保证了夜莺开源社区健康、长久的发展。活跃、专业的社区用户也在持续迭代和沉淀更多的最佳实践于产品中;
## 功能特点
## 使用场景
1. **如果您希望在一个平台中,统一管理和查看 Metrics、Logging、Tracing 数据,推荐你使用夜莺**
- 请参考阅读:[不止于监控,夜莺 V6 全新升级为开源观测平台](http://flashcat.cloud/blog/nightingale-v6-release/)
2. **如果您在使用 Prometheus 过程中,有以下的一个或者多个需求场景,推荐您无缝升级到夜莺**
- Prometheus、Alertmanager、Grafana 等多个系统较为割裂,缺乏统一视图,无法开箱即用;
- 通过修改配置文件来管理 Prometheus、Alertmanager 的方式,学习曲线大,协同有难度;
- 数据量过大而无法扩展您的 Prometheus 集群;
- 生产环境运行多套 Prometheus 集群,面临管理和使用成本高的问题;
3. **如果您在使用 Zabbix有以下的场景推荐您升级到夜莺**
- 监控的数据量太大,希望有更好的扩展解决方案;
- 学习曲线高,多人多团队模式下,希望有更好的协同使用效率;
- 微服务和云原生架构下监控数据的生命周期多变、监控数据维度基数高Zabbix 数据模型不易适配;
- 了解更多Zabbix和夜莺监控的对比推荐您进一步阅读[Zabbix 和夜莺监控选型对比](https://flashcat.cloud/blog/zabbx-vs-nightingale/)
4. **如果您在使用 [Open-Falcon](https://github.com/open-falcon/falcon-plus),我们推荐您升级到夜莺:**
- 关于 Open-Falcon 和夜莺的详细介绍,请参考阅读:[云原生监控的十个特点和趋势](http://flashcat.cloud/blog/10-trends-of-cloudnative-monitoring/)
- 监控系统和可观测平台的区别,请参考阅读:[从监控系统到可观测平台Gap有多大
](https://flashcat.cloud/blog/gap-of-monitoring-to-o11y/)
5. **我们推荐您使用 [Categraf](https://github.com/flashcatcloud/categraf) 作为首选的监控数据采集器**
- [Categraf](https://github.com/flashcatcloud/categraf) 是夜莺监控的默认采集器,采用开放插件机制和 All-in-one 的设计理念,同时支持 metric、log、trace、event 的采集。Categraf 不仅可以采集 CPU、内存、网络等系统层面的指标也集成了众多开源组件的采集能力支持K8s生态。Categraf 内置了对应的仪表盘和告警规则,开箱即用。
## 文档
[English Doc](https://n9e.github.io/) | [中文文档](https://flashcat.cloud/docs/)
## 产品示意图
https://user-images.githubusercontent.com/792850/216888712-2565fcea-9df5-47bd-a49e-d60af9bd76e8.mp4
## 夜莺架构
夜莺监控可以接收各种采集器上报的监控数据(比如 [Categraf](https://github.com/flashcatcloud/categraf)、telegraf、grafana-agent、Prometheus并写入多种流行的时序数据库中可以支持Prometheus、M3DB、VictoriaMetrics、Thanos、TDEngine等提供告警规则、屏蔽规则、订阅规则的配置能力提供监控数据的查看能力提供告警自愈机制告警触发之后自动回调某个webhook地址或者执行某个脚本提供历史告警事件的存储管理、分组查看的能力。
### 中心汇聚式部署方案
![中心汇聚式部署方案](https://download.flashcat.cloud/ulric/20230327133406.png)
夜莺只有一个模块,就是 n9e可以部署多个 n9e 实例组成集群n9e 依赖 2 个存储数据库、Redis数据库可以使用 MySQL 或 Postgres自己按需选用。
n9e 提供的是 HTTP 接口,前面负载均衡可以是 4 层的,也可以是 7 层的。一般就选用 Nginx 就可以了。
n9e 这个模块接收到数据之后,需要转发给后端的时序库,相关配置是:
```toml
[Pushgw]
LabelRewrite = true
[[Pushgw.Writers]]
Url = "http://127.0.0.1:9090/api/v1/write"
```
> 注意:虽然数据源可以在页面配置了,但是上报转发链路,还是需要在配置文件指定。
所有机房的 agent 比如 Categraf、Telegraf、 Grafana-agent、Datadog-agent ),都直接推数据给 n9e这个架构最为简单维护成本最低。当然前提是要求机房之间网络链路比较好一般有专线。如果网络链路不好则要使用下面的部署方式了。
### 边缘下沉式混杂部署方案
![边缘下沉式混杂部署方案](https://download.flashcat.cloud/ulric/20230327135615.png)
这个图尝试解释 3 种不同的情形,比如 A 机房和中心网络链路很好Categraf 可以直接汇报数据给中心 n9e 模块,另一个机房网络链路不好,就需要把时序库下沉部署,时序库下沉了,对应的告警引擎和转发网关也都要跟随下沉,这样数据不会跨机房传输,比较稳定。但是心跳还是需要往中心心跳,要不然在对象列表里看不到机器的 CPU、内存使用率。还有的时候可能是接入的一个已有的 Prometheus数据采集没有走 Categraf那此时只需要把 Prometheus 作为数据源接入夜莺即可,可以在夜莺里看图、配告警规则,但是就是在对象列表里看不到,也不能使用告警自愈的功能,问题也不大,核心功能都不受影响。
边缘机房下沉部署时序库、告警引擎、转发网关的时候要注意告警引擎需要依赖数据库因为要同步告警规则转发网关也要依赖数据库因为要注册对象到数据库里去需要打通相关网络告警引擎和转发网关都不用Redis所以无需为 Redis 打通网络。
### VictoriaMetrics 集群架构
<img src="doc/img/install-vm.png" width="600">
如果单机版本的时序数据库(比如 Prometheus 性能有瓶颈或容灾较差,我们推荐使用 [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)VictoriaMetrics 架构较为简单性能优异易于部署和运维架构图如上。VictoriaMetrics 更详尽的文档,还请参考其[官网](https://victoriametrics.com/)。
## 夜莺社区
开源项目要更有生命力,离不开开放的治理架构和源源不断的开发者和用户共同参与,我们致力于建立开放、中立的开源治理架构,吸纳更多来自企业、高校等各方面对云原生监控感兴趣、有热情的开发者,一起打造有活力的夜莺开源社区。关于《夜莺开源项目和社区治理架构(草案)》,请查阅 [COMMUNITY GOVERNANCE](./doc/community-governance.md).
**我们欢迎您以各种方式参与到夜莺开源项目和开源社区中来,工作包括不限于**
- 补充和完善文档 => [n9e.github.io](https://n9e.github.io/)
- 分享您在使用夜莺监控过程中的最佳实践和经验心得 => [文章分享](https://flashcat.cloud/docs/content/flashcat-monitor/nightingale/share/)
- 提交产品建议 =》 [github issue](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Ffeature&template=enhancement.md)
- 提交代码,让夜莺监控更快、更稳、更好用 => [github pull request](https://github.com/didi/nightingale/pulls)
**尊重、认可和记录每一位贡献者的工作**是夜莺开源社区的第一指导原则,我们提倡**高效的提问**,这既是对开发者时间的尊重,也是对整个社区知识沉淀的贡献:
- 提问之前请先查阅 [FAQ](https://www.gitlink.org.cn/ccfos/nightingale/wiki/faq)
- 我们使用[论坛](https://answer.flashcat.cloud/)进行交流,有问题可以到这里搜索、提问
- 我们也推荐你加入微信群,和其他夜莺用户交流经验 (请先加好友:[picobyte](https://www.gitlink.org.cn/UlricQin/gist/tree/master/self.jpeg) 备注:夜莺加群+姓名+公司)
- 对接多种时序库:支持对接 Prometheus、VictoriaMetrics、Thanos、Mimir、M3DB、TDengine 等多种时序库,实现统一告警管理。
- 专业告警能力:内置支持多种告警规则,可以扩展支持常见通知媒介,支持告警屏蔽/抑制/订阅/自愈、告警事件管理。
- 高性能可视化引擎:支持多种图表样式,内置众多 Dashboard 模版,也可导入 Grafana 模版,开箱即用,开源协议商业友好。
- 支持常见采集器:支持 [Categraf](https://flashcat.cloud/product/categraf)、Telegraf、Grafana-agent、Datadog-agent、各种 Exporter 作为采集器,没有什么数据是不能监控的。
- 👀无缝搭配 [Flashduty](https://flashcat.cloud/product/flashcat-duty/)实现告警聚合收敛、认领、升级、排班、IM集成确保告警处理不遗漏减少打扰高效协同。
## Who is using Nightingale
## 截图演示
您可以通过在 **[Who is Using Nightingale](https://github.com/ccfos/nightingale/issues/897)** 登记您的使用情况,分享您的使用经验
即时查询,类似 Prometheus 内置的查询分析页面,做 ad-hoc 查询,夜莺做了一些 UI 优化,同时提供了一些内置 promql 指标,让不太了解 promql 的用户也可以快速查询
## Stargazers over time
[![Stargazers over time](https://starchart.cc/ccfos/nightingale.svg)](https://starchart.cc/ccfos/nightingale)
![即时查询](https://download.flashcat.cloud/ulric/20240513103305.png)
## Contributors
当然,也可以直接通过指标视图查看,有了指标视图,即时查询基本可以不用了,或者只有高端玩家使用即时查询,普通用户直接通过指标视图查询即可。
![指标视图](https://download.flashcat.cloud/ulric/20240513103530.png)
夜莺内置了常用仪表盘,可以直接导入使用。也可以导入 Grafana 仪表盘,不过只能兼容 Grafana 基本图表,如果已经习惯了 Grafana 建议继续使用 Grafana 看图,把夜莺作为一个告警引擎使用。
![内置仪表盘](https://download.flashcat.cloud/ulric/20240513103628.png)
除了内置的仪表盘,也内置了很多告警规则,开箱即用。
![内置告警规则](https://download.flashcat.cloud/ulric/20240513103825.png)
## 产品架构
社区使用夜莺最多的场景就是使用夜莺做告警引擎,对接多套时序库,统一告警规则管理。绘图仍然使用 Grafana 居多。作为一个告警引擎,夜莺的产品架构如下:
![产品架构](https://download.flashcat.cloud/ulric/20240221152601.png)
对于个别边缘机房,如果和中心夜莺服务端网络链路不好,希望提升告警可用性,我们也提供边缘机房告警引擎下沉部署模式,这个模式下,即便网络割裂,告警功能也不受影响。
![边缘部署模式](https://download.flashcat.cloud/ulric/20240222102119.png)
## 交流渠道
- 报告Bug优先推荐提交[夜莺GitHub Issue](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Fbug&projects=&template=bug_report.yml)
- 推荐完整浏览[夜莺文档站点](https://flashcat.cloud/docs/content/flashcat-monitor/nightingale-v7/introduction/),了解更多信息
- 推荐搜索关注夜莺公众号,第一时间获取社区动态:`夜莺监控Nightingale`
- 日常答疑、技术分享、用户之间的交流,统一使用知识星球,大伙可以免费加入交流,[入口在这里](https://download.flashcat.cloud/ulric/20240319095409.png)
## 广受关注
[![Stargazers over time](https://api.star-history.com/svg?repos=ccfos/nightingale&type=Date)](https://star-history.com/#ccfos/nightingale&Date)
## 社区共建
- ❇️请阅读浏览[夜莺开源项目和社区治理架构草案](./doc/community-governance.md),真诚欢迎每一位用户、开发者、公司以及组织,使用夜莺监控、积极反馈 Bug、提交功能需求、分享最佳实践共建专业、活跃的夜莺开源社区。
- 夜莺贡献者❤️
<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)
## 加入交流群
<img src="doc/img/wecom.png" width="120">
- [Apache License V2.0](https://github.com/didi/nightingale/blob/main/LICENSE)

View File

@@ -1,6 +1,6 @@
<p align="center">
<a href="https://github.com/ccfos/nightingale">
<img src="doc/img/nightingale_logo_h.png" alt="nightingale - cloud native monitoring" width="240" /></a>
<img src="doc/img/Nightingale_L_V.png" alt="nightingale - cloud native monitoring" width="240" /></a>
</p>
<p align="center">
@@ -25,7 +25,7 @@
We recommend upgrading your <b>Prometheus + AlertManager + Grafana</b> combination to Nightingale!
</p>
[English](./README.md) | [中文](./README_ZH.md)
[English](./README_en.md) | [中文](./README.md)
## Highlighted Features
@@ -62,7 +62,7 @@
## Getting Started
[English Doc](https://n9e.github.io/) | [中文文档](http://n9e.flashcat.cloud/)
[https://n9e.github.io/](https://n9e.github.io/)
## Screenshots

View File

@@ -2,11 +2,10 @@ package aconf
import (
"path"
"github.com/toolkits/pkg/runner"
)
type Alert struct {
Disable bool
EngineDelay int64
Heartbeat HeartbeatConfig
Alerting Alerting
@@ -47,16 +46,9 @@ type RedisPub struct {
ChannelKey string
}
type Ibex struct {
Address string
BasicAuthUser string
BasicAuthPass string
Timeout int64
}
func (a *Alert) PreCheck() {
func (a *Alert) PreCheck(configDir string) {
if a.Alerting.TemplatesDir == "" {
a.Alerting.TemplatesDir = path.Join(runner.Cwd, "etc", "template")
a.Alerting.TemplatesDir = path.Join(configDir, "template")
}
if a.Alerting.NotifyConcurrency == 0 {
@@ -70,4 +62,8 @@ func (a *Alert) PreCheck() {
if a.Heartbeat.EngineName == "" {
a.Heartbeat.EngineName = "default"
}
if a.EngineDelay == 0 {
a.EngineDelay = 30
}
}

View File

@@ -15,6 +15,7 @@ import (
"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"
@@ -24,6 +25,9 @@ import (
"github.com/ccfos/nightingale/v6/pushgw/pconf"
"github.com/ccfos/nightingale/v6/pushgw/writer"
"github.com/ccfos/nightingale/v6/storage"
"github.com/ccfos/nightingale/v6/tdengine"
"github.com/flashcatcloud/ibex/src/cmd/ibex"
)
func Initialize(configDir string, cryptoKey string) (func(), error) {
@@ -37,13 +41,10 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
return nil, err
}
db, err := storage.New(config.DB)
if err != nil {
return nil, err
}
ctx := ctx.NewContext(context.Background(), db)
ctx := ctx.NewContext(context.Background(), nil, false, config.CenterApi)
redis, err := storage.NewRedis(config.Redis)
var redis storage.Redis
redis, err = storage.NewRedis(config.Redis)
if err != nil {
return nil, err
}
@@ -51,22 +52,33 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
syncStats := memsto.NewSyncStats()
alertStats := astats.NewSyncStats()
targetCache := memsto.NewTargetCache(ctx, syncStats, redis)
configCache := memsto.NewConfigCache(ctx, syncStats, nil, "")
targetCache := memsto.NewTargetCache(ctx, syncStats, nil)
busiGroupCache := memsto.NewBusiGroupCache(ctx, syncStats)
alertMuteCache := memsto.NewAlertMuteCache(ctx, syncStats)
alertRuleCache := memsto.NewAlertRuleCache(ctx, syncStats)
notifyConfigCache := memsto.NewNotifyConfigCache(ctx)
notifyConfigCache := memsto.NewNotifyConfigCache(ctx, configCache)
dsCache := memsto.NewDatasourceCache(ctx, syncStats)
userCache := memsto.NewUserCache(ctx, syncStats)
userGroupCache := memsto.NewUserGroupCache(ctx, syncStats)
taskTplsCache := memsto.NewTaskTplCache(ctx)
promClients := prom.NewPromClient(ctx, config.Alert.Heartbeat)
promClients := prom.NewPromClient(ctx)
tdengineClients := tdengine.NewTdengineClient(ctx, config.Alert.Heartbeat)
externalProcessors := process.NewExternalProcessors()
Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, dsCache, ctx, promClients, false)
Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, taskTplsCache, dsCache, ctx, promClients, tdengineClients, userCache, userGroupCache)
r := httpx.GinEngine(config.Global.RunMode, config.HTTP)
rt := router.New(config.HTTP, config.Alert, alertMuteCache, targetCache, busiGroupCache, alertStats, ctx, externalProcessors)
if config.Ibex.Enable {
ibex.ServerStart(false, nil, redis, config.HTTP.APIForService.BasicAuth, config.Alert.Heartbeat, &config.CenterApi, r, nil, config.Ibex, config.HTTP.Port)
}
rt.Config(r)
dumper.ConfigRouter(r)
httpClean := httpx.Init(config.HTTP, r)
@@ -77,27 +89,28 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
}
func Start(alertc aconf.Alert, pushgwc pconf.Pushgw, syncStats *memsto.Stats, alertStats *astats.Stats, externalProcessors *process.ExternalProcessorsType, targetCache *memsto.TargetCacheType, busiGroupCache *memsto.BusiGroupCacheType,
alertMuteCache *memsto.AlertMuteCacheType, alertRuleCache *memsto.AlertRuleCacheType, notifyConfigCache *memsto.NotifyConfigCacheType, datasourceCache *memsto.DatasourceCacheType, ctx *ctx.Context, promClients *prom.PromClientMap, isCenter bool) {
userCache := memsto.NewUserCache(ctx, syncStats)
userGroupCache := memsto.NewUserGroupCache(ctx, syncStats)
alertMuteCache *memsto.AlertMuteCacheType, alertRuleCache *memsto.AlertRuleCacheType, notifyConfigCache *memsto.NotifyConfigCacheType, taskTplsCache *memsto.TaskTplCache, datasourceCache *memsto.DatasourceCacheType, ctx *ctx.Context,
promClients *prom.PromClientMap, tdendgineClients *tdengine.TdengineClientMap, userCache *memsto.UserCacheType, userGroupCache *memsto.UserGroupCacheType) {
alertSubscribeCache := memsto.NewAlertSubscribeCache(ctx, syncStats)
recordingRuleCache := memsto.NewRecordingRuleCache(ctx, syncStats)
targetsOfAlertRulesCache := memsto.NewTargetOfAlertRuleCache(ctx, alertc.Heartbeat.EngineName, syncStats)
go models.InitNotifyConfig(ctx, alertc.Alerting.TemplatesDir)
naming := naming.NewNaming(ctx, alertc.Heartbeat, isCenter)
naming := naming.NewNaming(ctx, alertc.Heartbeat, alertStats)
writers := writer.NewWriters(pushgwc)
record.NewScheduler(alertc, recordingRuleCache, promClients, writers, alertStats)
eval.NewScheduler(isCenter, alertc, externalProcessors, alertRuleCache, targetCache, busiGroupCache, alertMuteCache, datasourceCache, promClients, naming, ctx, alertStats)
eval.NewScheduler(alertc, externalProcessors, alertRuleCache, targetCache, targetsOfAlertRulesCache,
busiGroupCache, alertMuteCache, datasourceCache, promClients, tdendgineClients, naming, ctx, alertStats)
dp := dispatch.NewDispatch(alertRuleCache, userCache, userGroupCache, alertSubscribeCache, targetCache, notifyConfigCache, alertc.Alerting, ctx)
dp := dispatch.NewDispatch(alertRuleCache, userCache, userGroupCache, alertSubscribeCache, targetCache, notifyConfigCache, taskTplsCache, alertc.Alerting, ctx, alertStats)
consumer := dispatch.NewConsumer(alertc.Alerting, ctx, dp)
go dp.ReloadTpls()
go consumer.LoopConsume()
go queue.ReportQueueSize(alertStats)
go sender.StartEmailSender(notifyConfigCache.GetSMTP()) // todo
go sender.InitEmailSender(notifyConfigCache)
}

View File

@@ -10,22 +10,77 @@ const (
)
type Stats struct {
CounterSampleTotal *prometheus.CounterVec
CounterAlertsTotal *prometheus.CounterVec
GaugeAlertQueueSize prometheus.Gauge
GaugeSampleQueueSize *prometheus.GaugeVec
RequestDuration *prometheus.HistogramVec
ForwardDuration *prometheus.HistogramVec
AlertNotifyTotal *prometheus.CounterVec
AlertNotifyErrorTotal *prometheus.CounterVec
CounterAlertsTotal *prometheus.CounterVec
GaugeAlertQueueSize prometheus.Gauge
CounterRuleEval *prometheus.CounterVec
CounterQueryDataErrorTotal *prometheus.CounterVec
CounterQueryDataTotal *prometheus.CounterVec
CounterRecordEval *prometheus.CounterVec
CounterRecordEvalErrorTotal *prometheus.CounterVec
CounterMuteTotal *prometheus.CounterVec
CounterRuleEvalErrorTotal *prometheus.CounterVec
CounterHeartbeatErrorTotal *prometheus.CounterVec
CounterSubEventTotal *prometheus.CounterVec
}
func NewSyncStats() *Stats {
// 从各个接收接口接收到的监控数据总量
CounterSampleTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
CounterRuleEval := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "samples_received_total",
Help: "Total number samples received.",
}, []string{"cluster", "channel"})
Name: "rule_eval_total",
Help: "Number of rule eval.",
}, []string{})
CounterRuleEvalErrorTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "rule_eval_error_total",
Help: "Number of rule eval error.",
}, []string{"datasource", "stage"})
CounterQueryDataErrorTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "query_data_error_total",
Help: "Number of rule eval query data error.",
}, []string{"datasource"})
CounterQueryDataTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "query_data_total",
Help: "Number of rule eval query data.",
}, []string{"datasource"})
CounterRecordEval := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "record_eval_total",
Help: "Number of record eval.",
}, []string{"datasource"})
CounterRecordEvalErrorTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "record_eval_error_total",
Help: "Number of record eval error.",
}, []string{"datasource"})
AlertNotifyTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "alert_notify_total",
Help: "Number of send msg.",
}, []string{"channel"})
AlertNotifyErrorTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "alert_notify_error_total",
Help: "Number of send msg.",
}, []string{"channel"})
// 产生的告警总量
CounterAlertsTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
@@ -33,7 +88,7 @@ func NewSyncStats() *Stats {
Subsystem: subsystem,
Name: "alerts_total",
Help: "Total number alert events.",
}, []string{"cluster"})
}, []string{"cluster", "type", "busi_group"})
// 内存中的告警事件队列的长度
GaugeAlertQueueSize := prometheus.NewGauge(prometheus.GaugeOpts{
@@ -43,51 +98,56 @@ func NewSyncStats() *Stats {
Help: "The size of alert queue.",
})
// 数据转发队列,各个队列的长度
GaugeSampleQueueSize := prometheus.NewGaugeVec(prometheus.GaugeOpts{
CounterMuteTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "sample_queue_size",
Help: "The size of sample queue.",
}, []string{"cluster", "channel_number"})
Name: "mute_total",
Help: "Number of mute.",
}, []string{"group"})
// 一些重要的请求,比如接收数据的请求,应该统计一下延迟情况
RequestDuration := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Buckets: []float64{.01, .1, 1},
Name: "http_request_duration_seconds",
Help: "HTTP request latencies in seconds.",
}, []string{"code", "path", "method"},
)
CounterSubEventTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "sub_event_total",
Help: "Number of sub event.",
}, []string{"group"})
// 发往后端TSDB延迟如何
ForwardDuration := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Buckets: []float64{.1, 1, 10},
Name: "forward_duration_seconds",
Help: "Forward samples to TSDB. latencies in seconds.",
}, []string{"cluster", "channel_number"},
)
CounterHeartbeatErrorTotal := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "heartbeat_error_count",
Help: "Number of heartbeat error.",
}, []string{})
prometheus.MustRegister(
CounterSampleTotal,
CounterAlertsTotal,
GaugeAlertQueueSize,
GaugeSampleQueueSize,
RequestDuration,
ForwardDuration,
AlertNotifyTotal,
AlertNotifyErrorTotal,
CounterRuleEval,
CounterQueryDataTotal,
CounterQueryDataErrorTotal,
CounterRecordEval,
CounterRecordEvalErrorTotal,
CounterMuteTotal,
CounterRuleEvalErrorTotal,
CounterHeartbeatErrorTotal,
CounterSubEventTotal,
)
return &Stats{
CounterSampleTotal: CounterSampleTotal,
CounterAlertsTotal: CounterAlertsTotal,
GaugeAlertQueueSize: GaugeAlertQueueSize,
GaugeSampleQueueSize: GaugeSampleQueueSize,
RequestDuration: RequestDuration,
ForwardDuration: ForwardDuration,
CounterAlertsTotal: CounterAlertsTotal,
GaugeAlertQueueSize: GaugeAlertQueueSize,
AlertNotifyTotal: AlertNotifyTotal,
AlertNotifyErrorTotal: AlertNotifyErrorTotal,
CounterRuleEval: CounterRuleEval,
CounterQueryDataTotal: CounterQueryDataTotal,
CounterQueryDataErrorTotal: CounterQueryDataErrorTotal,
CounterRecordEval: CounterRecordEval,
CounterRecordEvalErrorTotal: CounterRecordEvalErrorTotal,
CounterMuteTotal: CounterMuteTotal,
CounterRuleEvalErrorTotal: CounterRuleEvalErrorTotal,
CounterHeartbeatErrorTotal: CounterHeartbeatErrorTotal,
CounterSubEventTotal: CounterSubEventTotal,
}
}

View File

@@ -15,6 +15,8 @@ type AnomalyPoint struct {
Value float64 `json:"value"`
Severity int `json:"severity"`
Triggered bool `json:"triggered"`
Query string `json:"query"`
Values string `json:"values"`
}
func NewAnomalyPoint(key string, labels map[string]string, ts int64, value float64, severity int) AnomalyPoint {

View File

@@ -22,6 +22,14 @@ func MatchTags(eventTagsMap map[string]string, itags []models.TagFilter) bool {
}
return true
}
func MatchGroupsName(groupName string, groupFilter []models.TagFilter) bool {
for _, filter := range groupFilter {
if !matchTag(groupName, filter) {
return false
}
}
return true
}
func matchTag(value string, filter models.TagFilter) bool {
switch filter.Func {

View File

@@ -8,6 +8,7 @@ import (
"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"
@@ -60,16 +61,27 @@ func (e *Consumer) consume(events []interface{}, sema *semaphore.Semaphore) {
func (e *Consumer) consumeOne(event *models.AlertCurEvent) {
LogEvent(event, "consume")
eventType := "alert"
if event.IsRecovered {
eventType = "recovery"
}
e.dispatch.Astats.CounterAlertsTotal.WithLabelValues(event.Cluster, eventType, event.GroupName).Inc()
if err := event.ParseRule("rule_name"); err != nil {
logger.Warningf("ruleid:%d failed to parse rule name: %v", event.RuleId, err)
event.RuleName = fmt.Sprintf("failed to parse rule name: %v", err)
}
if err := event.ParseRule("rule_note"); err != nil {
logger.Warningf("ruleid:%d failed to parse rule note: %v", event.RuleId, err)
event.RuleNote = fmt.Sprintf("failed to parse rule note: %v", err)
}
if err := event.ParseRule("annotations"); err != nil {
event.Annotations = fmt.Sprintf("failed to parse rule note: %v", err)
logger.Warningf("ruleid:%d failed to parse annotations: %v", event.RuleId, err)
event.Annotations = fmt.Sprintf("failed to parse annotations: %v", err)
event.AnnotationsJSON["error"] = event.Annotations
}
e.persist(event)
@@ -82,78 +94,24 @@ func (e *Consumer) consumeOne(event *models.AlertCurEvent) {
}
func (e *Consumer) persist(event *models.AlertCurEvent) {
has, err := models.AlertCurEventExists(e.ctx, "hash=?", event.Hash)
if err != nil {
logger.Errorf("event_persist_check_exists_fail: %v rule_id=%d hash=%s", err, event.RuleId, event.Hash)
if event.Status != 0 {
return
}
his := event.ToHis(e.ctx)
// 不管是告警还是恢复,全量告警里都要记录
if err := his.Add(e.ctx); err != nil {
logger.Errorf(
"event_persist_his_fail: %v rule_id=%d cluster:%s hash=%s tags=%v timestamp=%d value=%s",
err,
event.RuleId,
event.Cluster,
event.Hash,
event.TagsJSON,
event.TriggerTime,
event.TriggerValue,
)
}
if has {
// 活跃告警表中有记录,删之
err = models.AlertCurEventDelByHash(e.ctx, event.Hash)
if !e.ctx.IsCenter {
event.DB2FE()
var err error
event.Id, err = poster.PostByUrlsWithResp[int64](e.ctx, "/v1/n9e/event-persist", event)
if err != nil {
logger.Errorf("event_del_cur_fail: %v hash=%s", err, event.Hash)
return
logger.Errorf("event:%+v persist err:%v", event, err)
e.dispatch.Astats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", event.DatasourceId), "persist_event").Inc()
}
if !event.IsRecovered {
// 恢复事件从活跃告警列表彻底删掉告警事件要重新加进来新的event
// use his id as cur id
event.Id = his.Id
if event.Id > 0 {
if err := event.Add(e.ctx); err != nil {
logger.Errorf(
"event_persist_cur_fail: %v rule_id=%d cluster:%s hash=%s tags=%v timestamp=%d value=%s",
err,
event.RuleId,
event.Cluster,
event.Hash,
event.TagsJSON,
event.TriggerTime,
event.TriggerValue,
)
}
}
}
return
}
if event.IsRecovered {
// alert_cur_event表里没有数据表示之前没告警结果现在报了恢复神奇....理论上不应该出现的
return
}
// use his id as cur id
event.Id = his.Id
if event.Id > 0 {
if err := event.Add(e.ctx); err != nil {
logger.Errorf(
"event_persist_cur_fail: %v rule_id=%d cluster:%s hash=%s tags=%v timestamp=%d value=%s",
err,
event.RuleId,
event.Cluster,
event.Hash,
event.TagsJSON,
event.TriggerTime,
event.TriggerValue,
)
}
err := models.EventPersist(e.ctx, event)
if err != nil {
logger.Errorf("event%+v persist err:%v", event, err)
e.dispatch.Astats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", event.DatasourceId), "persist_event").Inc()
}
}

View File

@@ -9,6 +9,7 @@ import (
"time"
"github.com/ccfos/nightingale/v6/alert/aconf"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/alert/common"
"github.com/ccfos/nightingale/v6/alert/sender"
"github.com/ccfos/nightingale/v6/memsto"
@@ -25,13 +26,17 @@ type Dispatch struct {
alertSubscribeCache *memsto.AlertSubscribeCacheType
targetCache *memsto.TargetCacheType
notifyConfigCache *memsto.NotifyConfigCacheType
taskTplsCache *memsto.TaskTplCache
alerting aconf.Alerting
senders map[string]sender.Sender
tpls map[string]*template.Template
Senders map[string]sender.Sender
tpls map[string]*template.Template
ExtraSenders map[string]sender.Sender
BeforeSenderHook func(*models.AlertCurEvent) bool
ctx *ctx.Context
ctx *ctx.Context
Astats *astats.Stats
RwLock sync.RWMutex
}
@@ -39,7 +44,7 @@ type Dispatch struct {
// 创建一个 Notify 实例
func NewDispatch(alertRuleCache *memsto.AlertRuleCacheType, userCache *memsto.UserCacheType, userGroupCache *memsto.UserGroupCacheType,
alertSubscribeCache *memsto.AlertSubscribeCacheType, targetCache *memsto.TargetCacheType, notifyConfigCache *memsto.NotifyConfigCacheType,
alerting aconf.Alerting, ctx *ctx.Context) *Dispatch {
taskTplsCache *memsto.TaskTplCache, alerting aconf.Alerting, ctx *ctx.Context, astats *astats.Stats) *Dispatch {
notify := &Dispatch{
alertRuleCache: alertRuleCache,
userCache: userCache,
@@ -47,13 +52,17 @@ func NewDispatch(alertRuleCache *memsto.AlertRuleCacheType, userCache *memsto.Us
alertSubscribeCache: alertSubscribeCache,
targetCache: targetCache,
notifyConfigCache: notifyConfigCache,
taskTplsCache: taskTplsCache,
alerting: alerting,
senders: make(map[string]sender.Sender),
tpls: make(map[string]*template.Template),
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,
ctx: ctx,
Astats: astats,
}
return notify
}
@@ -61,7 +70,7 @@ func NewDispatch(alertRuleCache *memsto.AlertRuleCacheType, userCache *memsto.Us
func (e *Dispatch) ReloadTpls() error {
err := e.relaodTpls()
if err != nil {
logger.Error("failed to reload tpls: %v", err)
logger.Errorf("failed to reload tpls: %v", err)
}
duration := time.Duration(9000) * time.Millisecond
@@ -81,17 +90,24 @@ func (e *Dispatch) relaodTpls() error {
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.Email: sender.NewSender(models.Email, tmpTpls, smtp),
models.Dingtalk: sender.NewSender(models.Dingtalk, tmpTpls),
models.Wecom: sender.NewSender(models.Wecom, tmpTpls),
models.Feishu: sender.NewSender(models.Feishu, tmpTpls),
models.Mm: sender.NewSender(models.Mm, tmpTpls),
models.Telegram: sender.NewSender(models.Telegram, tmpTpls),
models.FeishuCard: sender.NewSender(models.FeishuCard, tmpTpls),
}
e.RwLock.RLock()
for channelName, extraSender := range e.ExtraSenders {
senders[channelName] = extraSender
}
e.RwLock.RUnlock()
e.RwLock.Lock()
e.tpls = tmpTpls
e.senders = senders
e.Senders = senders
e.RwLock.Unlock()
return nil
}
@@ -132,7 +148,7 @@ func (e *Dispatch) HandleEventNotify(event *models.AlertCurEvent, isSubscribe bo
}
// 处理事件发送,这里用一个goroutine处理一个event的所有发送事件
go e.Send(rule, event, notifyTarget, isSubscribe)
go e.Send(rule, event, notifyTarget)
// 如果是不是订阅规则出现的event, 则需要处理订阅规则的event
if !isSubscribe {
@@ -159,45 +175,81 @@ func (e *Dispatch) handleSubs(event *models.AlertCurEvent) {
// handleSub 处理订阅规则的event,注意这里event要使用值传递,因为后面会修改event的状态
func (e *Dispatch) handleSub(sub *models.AlertSubscribe, event models.AlertCurEvent) {
if sub.IsDisabled() || !sub.MatchCluster(event.DatasourceId) {
if sub.IsDisabled() {
return
}
if !sub.MatchCluster(event.DatasourceId) {
return
}
if !sub.MatchProd(event.RuleProd) {
return
}
if !common.MatchTags(event.TagsMap, sub.ITags) {
return
}
// event BusiGroups filter
if !common.MatchGroupsName(event.GroupName, sub.IBusiGroups) {
return
}
if sub.ForDuration > (event.TriggerTime - event.FirstTriggerTime) {
return
}
if len(sub.SeveritiesJson) != 0 {
match := false
for _, s := range sub.SeveritiesJson {
if s == event.Severity || s == 0 {
match = true
break
}
}
if !match {
return
}
}
e.Astats.CounterSubEventTotal.WithLabelValues(event.GroupName).Inc()
sub.ModifyEvent(&event)
event.SubRuleId = sub.Id
LogEvent(&event, "subscribe")
e.HandleEventNotify(&event, true)
}
func (e *Dispatch) Send(rule *models.AlertRule, event *models.AlertCurEvent, notifyTarget *NotifyTarget, isSubscribe bool) {
for channel, uids := range notifyTarget.ToChannelUserMap() {
ctx := sender.BuildMessageContext(rule, event, uids, e.userCache)
e.RwLock.RLock()
s := e.senders[channel]
e.RwLock.RUnlock()
if s == nil {
logger.Warningf("no sender for channel: %s", channel)
continue
func (e *Dispatch) Send(rule *models.AlertRule, event *models.AlertCurEvent, notifyTarget *NotifyTarget) {
needSend := e.BeforeSenderHook(event)
if needSend {
for channel, uids := range notifyTarget.ToChannelUserMap() {
msgCtx := sender.BuildMessageContext(rule, []*models.AlertCurEvent{event}, uids, e.userCache, e.Astats)
e.RwLock.RLock()
s := e.Senders[channel]
e.RwLock.RUnlock()
if s == nil {
logger.Debugf("no sender for channel: %s", channel)
continue
}
var event *models.AlertCurEvent
if len(msgCtx.Events) > 0 {
event = msgCtx.Events[0]
}
logger.Debugf("send to channel:%s event:%+v users:%+v", channel, event, msgCtx.Users)
s.Send(msgCtx)
}
logger.Debugf("send event: %s, channel: %s", event.Hash, channel)
for i := 0; i < len(ctx.Users); i++ {
logger.Debug("send event to user: ", ctx.Users[i])
}
s.Send(ctx)
}
// handle event callbacks
sender.SendCallbacks(e.ctx, notifyTarget.ToCallbackList(), event, e.targetCache, e.notifyConfigCache.GetIbex())
sender.SendCallbacks(e.ctx, notifyTarget.ToCallbackList(), event, e.targetCache, e.userCache, e.taskTplsCache, e.Astats)
// handle global webhooks
sender.SendWebhooks(notifyTarget.ToWebhookList(), event)
sender.SendWebhooks(notifyTarget.ToWebhookList(), event, e.Astats)
// handle plugin call
go sender.MayPluginNotify(e.genNoticeBytes(event), e.notifyConfigCache.GetNotifyScript())
go sender.MayPluginNotify(e.genNoticeBytes(event), e.notifyConfigCache.GetNotifyScript(), e.Astats)
}
type Notice struct {

View File

@@ -18,11 +18,12 @@ func LogEvent(event *models.AlertCurEvent, location string, err ...error) {
}
logger.Infof(
"event(%s %s) %s: rule_id=%d cluster:%s %v%s@%d %s",
"event(%s %s) %s: rule_id=%d sub_id:%d cluster:%s %v%s@%d %s",
event.Hash,
status,
location,
event.RuleId,
event.SubRuleId,
event.Cluster,
event.TagsJSON,
event.TriggerValue,

View File

@@ -3,6 +3,7 @@ package eval
import (
"context"
"fmt"
"strconv"
"time"
"github.com/ccfos/nightingale/v6/alert/aconf"
@@ -12,11 +13,12 @@ import (
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/prom"
"github.com/ccfos/nightingale/v6/tdengine"
"github.com/toolkits/pkg/logger"
)
type Scheduler struct {
isCenter bool
// key: hash
alertRules map[string]*AlertRuleWorker
@@ -24,13 +26,15 @@ type Scheduler struct {
aconf aconf.Alert
alertRuleCache *memsto.AlertRuleCacheType
targetCache *memsto.TargetCacheType
busiGroupCache *memsto.BusiGroupCacheType
alertMuteCache *memsto.AlertMuteCacheType
datasourceCache *memsto.DatasourceCacheType
alertRuleCache *memsto.AlertRuleCacheType
targetCache *memsto.TargetCacheType
targetsOfAlertRuleCache *memsto.TargetsOfAlertRuleCacheType
busiGroupCache *memsto.BusiGroupCacheType
alertMuteCache *memsto.AlertMuteCacheType
datasourceCache *memsto.DatasourceCacheType
promClients *prom.PromClientMap
promClients *prom.PromClientMap
tdengineClients *tdengine.TdengineClientMap
naming *naming.Naming
@@ -38,24 +42,26 @@ type Scheduler struct {
stats *astats.Stats
}
func NewScheduler(isCenter bool, aconf aconf.Alert, externalProcessors *process.ExternalProcessorsType, arc *memsto.AlertRuleCacheType, targetCache *memsto.TargetCacheType,
busiGroupCache *memsto.BusiGroupCacheType, alertMuteCache *memsto.AlertMuteCacheType, datasourceCache *memsto.DatasourceCacheType, promClients *prom.PromClientMap, naming *naming.Naming,
ctx *ctx.Context, stats *astats.Stats) *Scheduler {
func NewScheduler(aconf aconf.Alert, externalProcessors *process.ExternalProcessorsType, arc *memsto.AlertRuleCacheType,
targetCache *memsto.TargetCacheType, toarc *memsto.TargetsOfAlertRuleCacheType,
busiGroupCache *memsto.BusiGroupCacheType, alertMuteCache *memsto.AlertMuteCacheType, datasourceCache *memsto.DatasourceCacheType,
promClients *prom.PromClientMap, tdengineClients *tdengine.TdengineClientMap, naming *naming.Naming, ctx *ctx.Context, stats *astats.Stats) *Scheduler {
scheduler := &Scheduler{
isCenter: isCenter,
aconf: aconf,
alertRules: make(map[string]*AlertRuleWorker),
ExternalProcessors: externalProcessors,
alertRuleCache: arc,
targetCache: targetCache,
busiGroupCache: busiGroupCache,
alertMuteCache: alertMuteCache,
datasourceCache: datasourceCache,
alertRuleCache: arc,
targetCache: targetCache,
targetsOfAlertRuleCache: toarc,
busiGroupCache: busiGroupCache,
alertMuteCache: alertMuteCache,
datasourceCache: datasourceCache,
promClients: promClients,
naming: naming,
promClients: promClients,
tdengineClients: tdengineClients,
naming: naming,
ctx: ctx,
stats: stats,
@@ -87,10 +93,13 @@ func (s *Scheduler) syncAlertRules() {
if rule == nil {
continue
}
if rule.IsPrometheusRule() {
ruleType := rule.GetRuleType()
if rule.IsPrometheusRule() || rule.IsLokiRule() || rule.IsTdengineRule() {
datasourceIds := s.promClients.Hit(rule.DatasourceIdsJson)
datasourceIds = append(datasourceIds, s.tdengineClients.Hit(rule.DatasourceIdsJson)...)
for _, dsId := range datasourceIds {
if !naming.DatasourceHashRing.IsHit(dsId, fmt.Sprintf("%d", rule.Id), s.aconf.Heartbeat.Endpoint) {
if !naming.DatasourceHashRing.IsHit(strconv.FormatInt(dsId, 10), fmt.Sprintf("%d", rule.Id), s.aconf.Heartbeat.Endpoint) {
continue
}
ds := s.datasourceCache.GetById(dsId)
@@ -99,22 +108,27 @@ func (s *Scheduler) syncAlertRules() {
continue
}
if ds.PluginType != ruleType {
logger.Debugf("datasource %d category is %s not %s", dsId, ds.PluginType, ruleType)
continue
}
if ds.Status != "enabled" {
logger.Debugf("datasource %d status is %s", dsId, ds.Status)
continue
}
processor := process.NewProcessor(rule, dsId, s.alertRuleCache, s.targetCache, s.busiGroupCache, s.alertMuteCache, s.datasourceCache, s.promClients, s.ctx, s.stats)
processor := process.NewProcessor(s.aconf.Heartbeat.EngineName, rule, dsId, s.alertRuleCache, s.targetCache, s.targetsOfAlertRuleCache, s.busiGroupCache, s.alertMuteCache, s.datasourceCache, s.ctx, s.stats)
alertRule := NewAlertRuleWorker(rule, dsId, processor, s.promClients, s.ctx)
alertRule := NewAlertRuleWorker(rule, dsId, processor, s.promClients, s.tdengineClients, s.ctx)
alertRuleWorkers[alertRule.Hash()] = alertRule
}
} else if rule.IsHostRule() && s.isCenter {
} else if rule.IsHostRule() {
// all host rule will be processed by center instance
if !naming.DatasourceHashRing.IsHit(naming.HostDatasource, fmt.Sprintf("%d", rule.Id), s.aconf.Heartbeat.Endpoint) {
if !naming.DatasourceHashRing.IsHit(s.aconf.Heartbeat.EngineName, strconv.FormatInt(rule.Id, 10), s.aconf.Heartbeat.Endpoint) {
continue
}
processor := process.NewProcessor(rule, 0, s.alertRuleCache, s.targetCache, s.busiGroupCache, s.alertMuteCache, s.datasourceCache, s.promClients, s.ctx, s.stats)
alertRule := NewAlertRuleWorker(rule, 0, processor, s.promClients, s.ctx)
processor := process.NewProcessor(s.aconf.Heartbeat.EngineName, rule, 0, s.alertRuleCache, s.targetCache, s.targetsOfAlertRuleCache, s.busiGroupCache, s.alertMuteCache, s.datasourceCache, s.ctx, s.stats)
alertRule := NewAlertRuleWorker(rule, 0, processor, s.promClients, s.tdengineClients, s.ctx)
alertRuleWorkers[alertRule.Hash()] = alertRule
} else {
// 如果 rule 不是通过 prometheus engine 来告警的,则创建为 externalRule
@@ -130,7 +144,7 @@ func (s *Scheduler) syncAlertRules() {
logger.Debugf("datasource %d status is %s", dsId, ds.Status)
continue
}
processor := process.NewProcessor(rule, dsId, s.alertRuleCache, s.targetCache, s.busiGroupCache, s.alertMuteCache, s.datasourceCache, s.promClients, s.ctx, s.stats)
processor := process.NewProcessor(s.aconf.Heartbeat.EngineName, rule, dsId, s.alertRuleCache, s.targetCache, s.targetsOfAlertRuleCache, s.busiGroupCache, s.alertMuteCache, s.datasourceCache, s.ctx, s.stats)
externalRuleWorkers[processor.Key()] = processor
}
}

View File

@@ -4,6 +4,8 @@ import (
"context"
"encoding/json"
"fmt"
"math"
"sort"
"strings"
"time"
@@ -11,8 +13,11 @@ import (
"github.com/ccfos/nightingale/v6/alert/process"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/hash"
"github.com/ccfos/nightingale/v6/pkg/parser"
promsdk "github.com/ccfos/nightingale/v6/pkg/prom"
"github.com/ccfos/nightingale/v6/prom"
"github.com/ccfos/nightingale/v6/tdengine"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/str"
@@ -28,19 +33,29 @@ type AlertRuleWorker struct {
processor *process.Processor
promClients *prom.PromClientMap
ctx *ctx.Context
promClients *prom.PromClientMap
tdengineClients *tdengine.TdengineClientMap
ctx *ctx.Context
}
func NewAlertRuleWorker(rule *models.AlertRule, datasourceId int64, processor *process.Processor, promClients *prom.PromClientMap, ctx *ctx.Context) *AlertRuleWorker {
const (
GET_RULE_CONFIG = "get_rule_config"
GET_PROCESSOR = "get_processor"
CHECK_QUERY = "check_query_config"
GET_CLIENT = "get_client"
QUERY_DATA = "query_data"
)
func NewAlertRuleWorker(rule *models.AlertRule, datasourceId int64, processor *process.Processor, promClients *prom.PromClientMap, tdengineClients *tdengine.TdengineClientMap, ctx *ctx.Context) *AlertRuleWorker {
arw := &AlertRuleWorker{
datasourceId: datasourceId,
quit: make(chan struct{}),
rule: rule,
processor: processor,
promClients: promClients,
ctx: ctx,
promClients: promClients,
tdengineClients: tdengineClients,
ctx: ctx,
}
return arw
@@ -69,14 +84,16 @@ func (arw *AlertRuleWorker) Start() {
if interval <= 0 {
interval = 10
}
ticker := time.NewTicker(time.Duration(interval) * time.Second)
go func() {
defer ticker.Stop()
for {
select {
case <-arw.quit:
return
default:
case <-ticker.C:
arw.Eval()
time.Sleep(time.Duration(interval) * time.Second)
}
}
}()
@@ -85,17 +102,23 @@ func (arw *AlertRuleWorker) Start() {
func (arw *AlertRuleWorker) Eval() {
cachedRule := arw.rule
if cachedRule == nil {
//logger.Errorf("rule_eval:%s rule not found", arw.Key())
// logger.Errorf("rule_eval:%s rule not found", arw.Key())
return
}
arw.processor.Stats.CounterRuleEval.WithLabelValues().Inc()
typ := cachedRule.GetRuleType()
var lst []common.AnomalyPoint
var anomalyPoints []common.AnomalyPoint
var recoverPoints []common.AnomalyPoint
switch typ {
case models.PROMETHEUS:
lst = arw.GetPromAnomalyPoint(cachedRule.RuleConfig)
anomalyPoints = arw.GetPromAnomalyPoint(cachedRule.RuleConfig)
case models.HOST:
lst = arw.GetHostAnomalyPoint(cachedRule.RuleConfig)
anomalyPoints = arw.GetHostAnomalyPoint(cachedRule.RuleConfig)
case models.TDENGINE:
anomalyPoints, recoverPoints = arw.GetTdengineAnomalyPoint(cachedRule, arw.processor.DatasourceId())
case models.LOKI:
anomalyPoints = arw.GetPromAnomalyPoint(cachedRule.RuleConfig)
default:
return
}
@@ -105,11 +128,42 @@ func (arw *AlertRuleWorker) Eval() {
return
}
arw.processor.Handle(lst, "inner", arw.inhibit)
if arw.inhibit {
pointsMap := make(map[string]common.AnomalyPoint)
for _, point := range recoverPoints {
// 对于恢复的事件,合并处理
tagHash := process.TagHash(point)
p, exists := pointsMap[tagHash]
if !exists {
pointsMap[tagHash] = point
continue
}
if p.Severity > point.Severity {
hash := process.Hash(cachedRule.Id, arw.processor.DatasourceId(), p)
arw.processor.DeleteProcessEvent(hash)
pointsMap[tagHash] = point
}
}
for _, point := range pointsMap {
str := fmt.Sprintf("%v", point.Value)
arw.processor.RecoverSingle(process.Hash(cachedRule.Id, arw.processor.DatasourceId(), point), point.Timestamp, &str)
}
} else {
for _, point := range recoverPoints {
str := fmt.Sprintf("%v", point.Value)
arw.processor.RecoverSingle(process.Hash(cachedRule.Id, arw.processor.DatasourceId(), point), point.Timestamp, &str)
}
}
arw.processor.Handle(anomalyPoints, "inner", arw.inhibit)
}
func (arw *AlertRuleWorker) Stop() {
logger.Infof("%s stopped", arw.Key())
logger.Infof("rule_eval %s stopped", arw.Key())
close(arw.quit)
}
@@ -120,11 +174,13 @@ func (arw *AlertRuleWorker) GetPromAnomalyPoint(ruleConfig string) []common.Anom
var rule *models.PromRuleConfig
if err := json.Unmarshal([]byte(ruleConfig), &rule); err != nil {
logger.Errorf("rule_eval:%s rule_config:%s, error:%v", arw.Key(), ruleConfig, err)
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
return lst
}
if rule == nil {
logger.Errorf("rule_eval:%s rule_config:%s, error:rule is nil", arw.Key(), ruleConfig)
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
return lst
}
@@ -136,39 +192,101 @@ func (arw *AlertRuleWorker) GetPromAnomalyPoint(ruleConfig string) []common.Anom
promql := strings.TrimSpace(query.PromQl)
if promql == "" {
logger.Errorf("rule_eval:%s promql is blank", arw.Key())
logger.Warningf("rule_eval:%s promql is blank", arw.Key())
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), CHECK_QUERY).Inc()
continue
}
if arw.promClients.IsNil(arw.datasourceId) {
logger.Errorf("rule_eval:%s error reader client is nil", arw.Key())
logger.Warningf("rule_eval:%s error reader client is nil", arw.Key())
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_CLIENT).Inc()
continue
}
readerClient := arw.promClients.GetCli(arw.datasourceId)
var warnings promsdk.Warnings
arw.processor.Stats.CounterQueryDataTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
value, warnings, err := readerClient.Query(context.Background(), promql, time.Now())
if err != nil {
logger.Errorf("rule_eval:%s promql:%s, error:%v", arw.Key(), promql, err)
arw.processor.Stats.CounterQueryDataErrorTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
continue
}
if len(warnings) > 0 {
logger.Errorf("rule_eval:%s promql:%s, warnings:%v", arw.Key(), promql, warnings)
continue
arw.processor.Stats.CounterQueryDataErrorTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
}
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) GetTdengineAnomalyPoint(rule *models.AlertRule, dsId int64) ([]common.AnomalyPoint, []common.AnomalyPoint) {
// 获取查询和规则判断条件
points := []common.AnomalyPoint{}
recoverPoints := []common.AnomalyPoint{}
ruleConfig := strings.TrimSpace(rule.RuleConfig)
if ruleConfig == "" {
logger.Warningf("rule_eval:%d promql is blank", rule.Id)
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
return points, recoverPoints
}
var ruleQuery models.RuleQuery
err := json.Unmarshal([]byte(ruleConfig), &ruleQuery)
if err != nil {
logger.Warningf("rule_eval:%d promql parse error:%s", rule.Id, err.Error())
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId())).Inc()
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
return points, recoverPoints
}
arw.inhibit = ruleQuery.Inhibit
if len(ruleQuery.Queries) > 0 {
seriesStore := make(map[uint64]models.DataResp)
seriesTagIndex := make(map[uint64][]uint64)
for _, query := range ruleQuery.Queries {
arw.processor.Stats.CounterQueryDataTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
cli := arw.tdengineClients.GetCli(dsId)
if cli == nil {
logger.Warningf("rule_eval:%d tdengine client is nil", rule.Id)
arw.processor.Stats.CounterQueryDataErrorTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_CLIENT).Inc()
continue
}
series, err := cli.Query(query)
arw.processor.Stats.CounterQueryDataTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
if err != nil {
logger.Warningf("rule_eval rid:%d query data error: %v", rule.Id, err)
arw.processor.Stats.CounterQueryDataErrorTotal.WithLabelValues(fmt.Sprintf("%d", arw.datasourceId)).Inc()
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
continue
}
// 此条日志很重要,是告警判断的现场值
logger.Debugf("rule_eval rid:%d req:%+v resp:%+v", rule.Id, query, series)
MakeSeriesMap(series, seriesTagIndex, seriesStore)
}
points, recoverPoints = GetAnomalyPoint(rule.Id, ruleQuery, seriesTagIndex, seriesStore)
}
return points, recoverPoints
}
func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) []common.AnomalyPoint {
var lst []common.AnomalyPoint
var severity int
@@ -176,11 +294,13 @@ func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) []common.Anom
var rule *models.HostRuleConfig
if err := json.Unmarshal([]byte(ruleConfig), &rule); err != nil {
logger.Errorf("rule_eval:%s rule_config:%s, error:%v", arw.Key(), ruleConfig, err)
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
return lst
}
if rule == nil {
logger.Errorf("rule_eval:%s rule_config:%s, error:rule is nil", arw.Key(), ruleConfig)
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), GET_RULE_CONFIG).Inc()
return lst
}
@@ -191,15 +311,42 @@ func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) []common.Anom
arw.severity = trigger.Severity
}
query := models.GetHostsQuery(rule.Queries)
switch trigger.Type {
case "target_miss":
t := now - int64(trigger.Duration)
targets, err := models.MissTargetGetsByFilter(arw.ctx, query, t)
if err != nil {
logger.Errorf("rule_eval:%s query:%v, error:%v", arw.Key(), query, err)
var idents, engineIdents, missEngineIdents []string
var exists bool
if arw.ctx.IsCenter {
// 如果是中心节点, 将不再上报数据的主机 engineName 为空的机器,也加入到 targets 中
missEngineIdents, exists = arw.processor.TargetsOfAlertRuleCache.Get("", arw.rule.Id)
if !exists {
logger.Debugf("rule_eval:%s targets not found engineName:%s", arw.Key(), arw.processor.EngineName)
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
}
}
idents = append(idents, missEngineIdents...)
engineIdents, exists = arw.processor.TargetsOfAlertRuleCache.Get(arw.processor.EngineName, arw.rule.Id)
if !exists {
logger.Warningf("rule_eval:%s targets not found engineName:%s", arw.Key(), arw.processor.EngineName)
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
}
idents = append(idents, engineIdents...)
if len(idents) == 0 {
continue
}
var missTargets []string
targetUpdateTimeMap := arw.processor.TargetCache.GetHostUpdateTime(idents)
for ident, updateTime := range targetUpdateTimeMap {
if updateTime < t {
missTargets = append(missTargets, ident)
}
}
logger.Debugf("rule_eval:%s missTargets:%v", arw.Key(), missTargets)
targets := arw.processor.TargetCache.Gets(missTargets)
for _, target := range targets {
m := make(map[string]string)
target.FillTagsMap()
@@ -216,20 +363,43 @@ func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) []common.Anom
lst = append(lst, common.NewAnomalyPoint(trigger.Type, m, now, float64(now-target.UpdateAt), trigger.Severity))
}
case "offset":
targets, err := models.TargetGetsByFilter(arw.ctx, query, 0, 0)
if err != nil {
logger.Errorf("rule_eval:%s query:%v, error:%v", arw.Key(), query, err)
idents, exists := arw.processor.TargetsOfAlertRuleCache.Get(arw.processor.EngineName, arw.rule.Id)
if !exists {
logger.Warningf("rule_eval:%s targets not found", arw.Key())
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
continue
}
var targetMap = make(map[string]*models.Target)
targets := arw.processor.TargetCache.Gets(idents)
targetMap := make(map[string]*models.Target)
for _, target := range targets {
targetMap[target.Ident] = target
}
hostOffsetMap := arw.processor.TargetCache.GetOffsetHost(targets, now, int64(trigger.Duration))
for host, offset := range hostOffsetMap {
offsetIdents := make(map[string]int64)
targetsMeta := arw.processor.TargetCache.GetHostMetas(targets)
for ident, meta := range targetsMeta {
if meta.CpuNum <= 0 {
// means this target is not collect by categraf, do not check offset
continue
}
if target, exists := targetMap[ident]; exists {
if now-target.UpdateAt > 120 {
// means this target is not a active host, do not check offset
continue
}
}
offset := meta.Offset
if math.Abs(float64(offset)) > float64(trigger.Duration) {
offsetIdents[ident] = offset
}
}
logger.Debugf("rule_eval:%s offsetIdents:%v", arw.Key(), offsetIdents)
for host, offset := range offsetIdents {
m := make(map[string]string)
target, exists := targetMap[host]
target, exists := arw.processor.TargetCache.Get(host)
if exists {
target.FillTagsMap()
for k, v := range target.TagsMap {
@@ -247,18 +417,22 @@ func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) []common.Anom
}
case "pct_target_miss":
t := now - int64(trigger.Duration)
count, err := models.MissTargetCountByFilter(arw.ctx, query, t)
if err != nil {
logger.Errorf("rule_eval:%s query:%v, error:%v", arw.Key(), query, err)
idents, exists := arw.processor.TargetsOfAlertRuleCache.Get(arw.processor.EngineName, arw.rule.Id)
if !exists {
logger.Warningf("rule_eval:%s targets not found", arw.Key())
arw.processor.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", arw.processor.DatasourceId()), QUERY_DATA).Inc()
continue
}
total, err := models.TargetCountByFilter(arw.ctx, query)
if err != nil {
logger.Errorf("rule_eval:%s query:%v, error:%v", arw.Key(), query, err)
continue
var missTargets []string
targetUpdateTimeMap := arw.processor.TargetCache.GetHostUpdateTime(idents)
for ident, updateTime := range targetUpdateTimeMap {
if updateTime < t {
missTargets = append(missTargets, ident)
}
}
pct := float64(count) / float64(total) * 100
logger.Debugf("rule_eval:%s missTargets:%v", arw.Key(), missTargets)
pct := float64(len(missTargets)) / float64(len(idents)) * 100
if pct >= float64(trigger.Percent) {
lst = append(lst, common.NewAnomalyPoint(trigger.Type, nil, now, pct, trigger.Severity))
}
@@ -266,3 +440,92 @@ func (arw *AlertRuleWorker) GetHostAnomalyPoint(ruleConfig string) []common.Anom
}
return lst
}
func GetAnomalyPoint(ruleId int64, ruleQuery models.RuleQuery, seriesTagIndex map[uint64][]uint64, seriesStore map[uint64]models.DataResp) ([]common.AnomalyPoint, []common.AnomalyPoint) {
points := []common.AnomalyPoint{}
recoverPoints := []common.AnomalyPoint{}
for _, trigger := range ruleQuery.Triggers {
for _, seriesHash := range seriesTagIndex {
sort.Slice(seriesHash, func(i, j int) bool {
return seriesHash[i] < seriesHash[j]
})
m := make(map[string]float64)
var ts int64
var sample models.DataResp
var value float64
for _, serieHash := range seriesHash {
series, exists := seriesStore[serieHash]
if !exists {
logger.Warningf("rule_eval rid:%d series:%+v not found", ruleId, series)
continue
}
t, v, exists := series.Last()
if !exists {
logger.Warningf("rule_eval rid:%d series:%+v value not found", ruleId, series)
continue
}
if !strings.Contains(trigger.Exp, "$"+series.Ref) {
// 表达式中不包含该变量
continue
}
m["$"+series.Ref] = v
m["$"+series.Ref+"."+series.MetricName()] = v
ts = int64(t)
sample = series
value = v
}
isTriggered := parser.Calc(trigger.Exp, m)
// 此条日志很重要,是告警判断的现场值
logger.Infof("rule_eval rid:%d trigger:%+v exp:%s res:%v m:%v", ruleId, trigger, trigger.Exp, isTriggered, m)
var values string
for k, v := range m {
if !strings.Contains(k, ".") {
continue
}
values += fmt.Sprintf("%s:%v ", k, v)
}
point := common.AnomalyPoint{
Key: sample.MetricName(),
Labels: sample.Metric,
Timestamp: int64(ts),
Value: value,
Values: values,
Severity: trigger.Severity,
Triggered: isTriggered,
Query: fmt.Sprintf("query:%+v trigger:%+v", ruleQuery.Queries, trigger),
}
if sample.Query != "" {
point.Query = sample.Query
}
if isTriggered {
points = append(points, point)
} else {
recoverPoints = append(recoverPoints, point)
}
}
}
return points, recoverPoints
}
func MakeSeriesMap(series []models.DataResp, seriesTagIndex map[uint64][]uint64, seriesStore map[uint64]models.DataResp) {
for i := 0; i < len(series); i++ {
serieHash := hash.GetHash(series[i].Metric, series[i].Ref)
tagHash := hash.GetTagHash(series[i].Metric)
seriesStore[serieHash] = series[i]
// 将曲线按照相同的 tag 分组
if _, exists := seriesTagIndex[tagHash]; !exists {
seriesTagIndex[tagHash] = make([]uint64, 0)
}
seriesTagIndex[tagHash] = append(seriesTagIndex[tagHash], serieHash)
}
}

View File

@@ -13,7 +13,11 @@ import (
)
func IsMuted(rule *models.AlertRule, event *models.AlertCurEvent, targetCache *memsto.TargetCacheType, alertMuteCache *memsto.AlertMuteCacheType) bool {
if TimeNonEffectiveMuteStrategy(rule, event) {
if rule.Disabled == 1 {
return true
}
if TimeSpanMuteStrategy(rule, event) {
return true
}
@@ -32,12 +36,9 @@ func IsMuted(rule *models.AlertRule, event *models.AlertCurEvent, targetCache *m
return false
}
// TimeNonEffectiveMuteStrategy 根据规则配置的告警时间过滤,如果产生的告警不在规则配置的告警时间内,则不告警
func TimeNonEffectiveMuteStrategy(rule *models.AlertRule, event *models.AlertCurEvent) bool {
if rule.Disabled == 1 {
return true
}
// 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()))
@@ -52,18 +53,33 @@ func TimeNonEffectiveMuteStrategy(rule *models.AlertRule, event *models.AlertCur
if !strings.Contains(enableDaysOfWeek[i], triggerWeek) {
continue
}
if enableStime[i] <= enableEtime[i] {
if triggerTime < enableStime[i] || triggerTime > enableEtime[i] {
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 triggerTime < enableStime[i] && triggerTime > enableEtime[i] {
} else if enableStime[i] > enableEtime[i] {
// 21:00-09:00
if triggerTime < enableStime[i] && triggerTime >= enableEtime[i] {
// mute, 即没生效
continue
}
}
// 到这里说明当前时刻在告警规则的某组生效时间范围内,直接返回 false
// 到这里说明当前时刻在告警规则的某组生效时间范围内,即没有 mute直接返回 false
return false
}
return true
}
@@ -131,7 +147,7 @@ func matchMute(event *models.AlertCurEvent, mute *models.AlertMute, clock ...int
}
// 如果不是全局的,判断 匹配的 datasource id
if !(len(mute.DatasourceIdsJson) != 0 && mute.DatasourceIdsJson[0] == 0) && event.DatasourceId != 0 {
if len(mute.DatasourceIdsJson) != 0 && mute.DatasourceIdsJson[0] != 0 && event.DatasourceId != 0 {
idm := make(map[int64]struct{}, len(mute.DatasourceIdsJson))
for i := 0; i < len(mute.DatasourceIdsJson); i++ {
idm[mute.DatasourceIdsJson[i]] = struct{}{}
@@ -156,13 +172,16 @@ func matchMute(event *models.AlertCurEvent, mute *models.AlertMute, clock ...int
for i := 0; i < len(mute.PeriodicMutesJson); i++ {
if strings.Contains(mute.PeriodicMutesJson[i].EnableDaysOfWeek, triggerWeek) {
if mute.PeriodicMutesJson[i].EnableStime <= mute.PeriodicMutesJson[i].EnableEtime {
if mute.PeriodicMutesJson[i].EnableStime == mute.PeriodicMutesJson[i].EnableEtime || (mute.PeriodicMutesJson[i].EnableStime == "00:00" && mute.PeriodicMutesJson[i].EnableEtime == "23:59") {
matchTime = true
break
} else if mute.PeriodicMutesJson[i].EnableStime < mute.PeriodicMutesJson[i].EnableEtime {
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 {
if triggerTime >= mute.PeriodicMutesJson[i].EnableStime || triggerTime < mute.PeriodicMutesJson[i].EnableEtime {
matchTime = true
break
}
@@ -174,5 +193,25 @@ func matchMute(event *models.AlertCurEvent, mute *models.AlertMute, clock ...int
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
}
if mute.ITags == nil || len(mute.ITags) == 0 {
return true
}
return common.MatchTags(event.TagsMap, mute.ITags)
}

View File

@@ -1,6 +1,7 @@
package naming
import (
"errors"
"sync"
"github.com/toolkits/pkg/consistent"
@@ -11,12 +12,12 @@ const NodeReplicas = 500
type DatasourceHashRingType struct {
sync.RWMutex
Rings map[int64]*consistent.Consistent
Rings map[string]*consistent.Consistent
}
// for alert_rule sharding
var HostDatasource int64 = 100000
var DatasourceHashRing = DatasourceHashRingType{Rings: make(map[int64]*consistent.Consistent)}
var HostDatasource int64 = 99999999
var DatasourceHashRing = DatasourceHashRingType{Rings: make(map[string]*consistent.Consistent)}
func NewConsistentHashRing(replicas int32, nodes []string) *consistent.Consistent {
ret := consistent.New()
@@ -27,7 +28,7 @@ func NewConsistentHashRing(replicas int32, nodes []string) *consistent.Consisten
return ret
}
func RebuildConsistentHashRing(datasourceId int64, nodes []string) {
func RebuildConsistentHashRing(datasourceId string, nodes []string) {
r := consistent.New()
r.NumberOfReplicas = NodeReplicas
for i := 0; i < len(nodes); i++ {
@@ -35,12 +36,12 @@ func RebuildConsistentHashRing(datasourceId int64, nodes []string) {
}
DatasourceHashRing.Set(datasourceId, r)
logger.Infof("hash ring %d rebuild %+v", datasourceId, r.Members())
logger.Infof("hash ring %s rebuild %+v", datasourceId, r.Members())
}
func (chr *DatasourceHashRingType) GetNode(datasourceId int64, pk string) (string, error) {
chr.RLock()
defer chr.RUnlock()
func (chr *DatasourceHashRingType) GetNode(datasourceId string, pk string) (string, error) {
chr.Lock()
defer chr.Unlock()
_, exists := chr.Rings[datasourceId]
if !exists {
chr.Rings[datasourceId] = NewConsistentHashRing(int32(NodeReplicas), []string{})
@@ -49,17 +50,36 @@ func (chr *DatasourceHashRingType) GetNode(datasourceId int64, pk string) (strin
return chr.Rings[datasourceId].Get(pk)
}
func (chr *DatasourceHashRingType) IsHit(datasourceId int64, pk string, currentNode string) bool {
func (chr *DatasourceHashRingType) IsHit(datasourceId string, pk string, currentNode string) bool {
node, err := chr.GetNode(datasourceId, pk)
if err != nil {
logger.Debugf("datasource id:%d pk:%s failed to get node from hashring:%v", datasourceId, pk, err)
if !errors.Is(err, consistent.ErrEmptyCircle) {
logger.Errorf("rule id:%s is not work, datasource id:%s failed to get node from hashring:%v", pk, datasourceId, err)
}
return false
}
return node == currentNode
}
func (chr *DatasourceHashRingType) Set(datasourceId int64, r *consistent.Consistent) {
chr.RLock()
defer chr.RUnlock()
func (chr *DatasourceHashRingType) Set(datasourceId string, r *consistent.Consistent) {
chr.Lock()
defer chr.Unlock()
chr.Rings[datasourceId] = r
}
func (chr *DatasourceHashRingType) Del(datasourceId string) {
chr.Lock()
defer chr.Unlock()
delete(chr.Rings, datasourceId)
}
func (chr *DatasourceHashRingType) Clear(engineName string) {
chr.Lock()
defer chr.Unlock()
for id := range chr.Rings {
if id == engineName {
continue
}
delete(chr.Rings, id)
}
}

View File

@@ -7,8 +7,10 @@ import (
"time"
"github.com/ccfos/nightingale/v6/alert/aconf"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/poster"
"github.com/toolkits/pkg/logger"
)
@@ -16,14 +18,14 @@ import (
type Naming struct {
ctx *ctx.Context
heartbeatConfig aconf.HeartbeatConfig
isCenter bool
astats *astats.Stats
}
func NewNaming(ctx *ctx.Context, heartbeat aconf.HeartbeatConfig, isCenter bool) *Naming {
func NewNaming(ctx *ctx.Context, heartbeat aconf.HeartbeatConfig, alertStats *astats.Stats) *Naming {
naming := &Naming{
ctx: ctx,
heartbeatConfig: heartbeat,
isCenter: isCenter,
astats: alertStats,
}
naming.Heartbeats()
return naming
@@ -31,9 +33,11 @@ func NewNaming(ctx *ctx.Context, heartbeat aconf.HeartbeatConfig, isCenter bool)
// local servers
var localss map[int64]string
var localHostServers map[string]string
func (n *Naming) Heartbeats() error {
localss = make(map[int64]string)
localHostServers = make(map[string]string)
if err := n.heartbeat(); err != nil {
fmt.Println("failed to heartbeat:", err)
return err
@@ -45,6 +49,10 @@ func (n *Naming) Heartbeats() error {
}
func (n *Naming) loopDeleteInactiveInstances() {
if !n.ctx.IsCenter {
return
}
interval := time.Duration(10) * time.Minute
for {
time.Sleep(interval)
@@ -74,7 +82,7 @@ func (n *Naming) heartbeat() error {
var err error
// 在页面上维护实例和集群的对应关系
datasourceIds, err = models.GetDatasourceIdsByClusterName(n.ctx, n.heartbeatConfig.EngineName)
datasourceIds, err = models.GetDatasourceIdsByEngineName(n.ctx, n.heartbeatConfig.EngineName)
if err != nil {
return err
}
@@ -83,20 +91,32 @@ func (n *Naming) heartbeat() error {
err := models.AlertingEngineHeartbeatWithCluster(n.ctx, n.heartbeatConfig.Endpoint, n.heartbeatConfig.EngineName, 0)
if err != nil {
logger.Warningf("heartbeat with cluster %s err:%v", "", err)
n.astats.CounterHeartbeatErrorTotal.WithLabelValues().Inc()
}
} else {
for i := 0; i < len(datasourceIds); i++ {
err := models.AlertingEngineHeartbeatWithCluster(n.ctx, n.heartbeatConfig.Endpoint, n.heartbeatConfig.EngineName, datasourceIds[i])
if err != nil {
logger.Warningf("heartbeat with cluster %d err:%v", datasourceIds[i], err)
n.astats.CounterHeartbeatErrorTotal.WithLabelValues().Inc()
}
}
}
if len(datasourceIds) == 0 {
DatasourceHashRing.Clear(n.heartbeatConfig.EngineName)
for dsId := range localss {
delete(localss, dsId)
}
}
newDatasource := make(map[int64]struct{})
for i := 0; i < len(datasourceIds); i++ {
newDatasource[datasourceIds[i]] = struct{}{}
servers, err := n.ActiveServers(datasourceIds[i])
if err != nil {
logger.Warningf("hearbeat %d get active server err:%v", datasourceIds[i], err)
n.astats.CounterHeartbeatErrorTotal.WithLabelValues().Inc()
continue
}
@@ -108,36 +128,42 @@ func (n *Naming) heartbeat() error {
continue
}
RebuildConsistentHashRing(datasourceIds[i], servers)
RebuildConsistentHashRing(fmt.Sprintf("%d", datasourceIds[i]), servers)
localss[datasourceIds[i]] = newss
}
if n.isCenter {
// 如果是中心节点,还需要处理 host 类型的告警规则host 类型告警规则,和数据源无关,想复用下数据源的 hash ring想用一个虚假的数据源 id 来处理
// if is center node, we need to handle host type alerting rules, host type alerting rules are not related to datasource, we want to reuse the hash ring of datasource, we want to use a fake datasource id to handle it
err := models.AlertingEngineHeartbeatWithCluster(n.ctx, n.heartbeatConfig.Endpoint, n.heartbeatConfig.EngineName, HostDatasource)
if err != nil {
logger.Warningf("heartbeat with cluster %s err:%v", "", err)
for dsId := range localss {
if _, exists := newDatasource[dsId]; !exists {
delete(localss, dsId)
DatasourceHashRing.Del(fmt.Sprintf("%d", dsId))
}
servers, err := n.ActiveServers(HostDatasource)
if err != nil {
logger.Warningf("hearbeat %d get active server err:%v", HostDatasource, err)
return nil
}
sort.Strings(servers)
newss := strings.Join(servers, " ")
oldss, exists := localss[HostDatasource]
if exists && oldss == newss {
return nil
}
RebuildConsistentHashRing(HostDatasource, servers)
localss[HostDatasource] = newss
}
// host 告警使用的是 hash ring
err = models.AlertingEngineHeartbeatWithCluster(n.ctx, n.heartbeatConfig.Endpoint, n.heartbeatConfig.EngineName, HostDatasource)
if err != nil {
logger.Warningf("heartbeat with cluster %s err:%v", "", err)
n.astats.CounterHeartbeatErrorTotal.WithLabelValues().Inc()
}
servers, err := n.ActiveServersByEngineName()
if err != nil {
logger.Warningf("hearbeat %d get active server err:%v", HostDatasource, err)
n.astats.CounterHeartbeatErrorTotal.WithLabelValues().Inc()
return nil
}
sort.Strings(servers)
newss := strings.Join(servers, " ")
oldss, exists := localHostServers[n.heartbeatConfig.EngineName]
if exists && oldss == newss {
return nil
}
RebuildConsistentHashRing(n.heartbeatConfig.EngineName, servers)
localHostServers[n.heartbeatConfig.EngineName] = newss
return nil
}
@@ -146,6 +172,21 @@ func (n *Naming) ActiveServers(datasourceId int64) ([]string, error) {
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)
}

28
alert/naming/leader.go Normal file
View File

@@ -0,0 +1,28 @@
package naming
import (
"sort"
"github.com/toolkits/pkg/logger"
)
func (n *Naming) IamLeader() bool {
if !n.ctx.IsCenter {
return false
}
servers, err := n.ActiveServersByEngineName()
if err != nil {
logger.Errorf("failed to get active servers: %v", err)
return false
}
if len(servers) == 0 {
logger.Errorf("active servers empty")
return false
}
sort.Strings(servers)
return n.heartbeatConfig.Endpoint == servers[0]
}

View File

@@ -18,11 +18,12 @@ import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/tplx"
"github.com/ccfos/nightingale/v6/prom"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/str"
)
type EventMuteHookFunc func(event *models.AlertCurEvent) bool
type ExternalProcessorsType struct {
ExternalLock sync.RWMutex
Processors map[string]*Processor
@@ -43,8 +44,11 @@ func (e *ExternalProcessorsType) GetExternalAlertRule(datasourceId, id int64) (*
return processor, has
}
type HandleEventFunc func(event *models.AlertCurEvent)
type Processor struct {
datasourceId int64
EngineName string
rule *models.AlertRule
fires *AlertCurEventMap
@@ -57,15 +61,19 @@ type Processor struct {
targetNote string
groupName string
atertRuleCache *memsto.AlertRuleCacheType
TargetCache *memsto.TargetCacheType
BusiGroupCache *memsto.BusiGroupCacheType
alertMuteCache *memsto.AlertMuteCacheType
datasourceCache *memsto.DatasourceCacheType
alertRuleCache *memsto.AlertRuleCacheType
TargetCache *memsto.TargetCacheType
TargetsOfAlertRuleCache *memsto.TargetsOfAlertRuleCacheType
BusiGroupCache *memsto.BusiGroupCacheType
alertMuteCache *memsto.AlertMuteCacheType
datasourceCache *memsto.DatasourceCacheType
promClients *prom.PromClientMap
ctx *ctx.Context
stats *astats.Stats
ctx *ctx.Context
Stats *astats.Stats
HandleFireEventHook HandleEventFunc
HandleRecoverEventHook HandleEventFunc
EventMuteHook EventMuteHookFunc
}
func (p *Processor) Key() string {
@@ -85,23 +93,29 @@ func (p *Processor) Hash() string {
))
}
func NewProcessor(rule *models.AlertRule, datasourceId int64, atertRuleCache *memsto.AlertRuleCacheType, targetCache *memsto.TargetCacheType,
busiGroupCache *memsto.BusiGroupCacheType, alertMuteCache *memsto.AlertMuteCacheType, datasourceCache *memsto.DatasourceCacheType, promClients *prom.PromClientMap, ctx *ctx.Context,
func NewProcessor(engineName string, rule *models.AlertRule, datasourceId int64, alertRuleCache *memsto.AlertRuleCacheType,
targetCache *memsto.TargetCacheType, targetsOfAlertRuleCache *memsto.TargetsOfAlertRuleCacheType,
busiGroupCache *memsto.BusiGroupCacheType, alertMuteCache *memsto.AlertMuteCacheType, datasourceCache *memsto.DatasourceCacheType, ctx *ctx.Context,
stats *astats.Stats) *Processor {
p := &Processor{
EngineName: engineName,
datasourceId: datasourceId,
rule: rule,
TargetCache: targetCache,
BusiGroupCache: busiGroupCache,
alertMuteCache: alertMuteCache,
atertRuleCache: atertRuleCache,
datasourceCache: datasourceCache,
TargetCache: targetCache,
TargetsOfAlertRuleCache: targetsOfAlertRuleCache,
BusiGroupCache: busiGroupCache,
alertMuteCache: alertMuteCache,
alertRuleCache: alertRuleCache,
datasourceCache: datasourceCache,
promClients: promClients,
ctx: ctx,
stats: stats,
ctx: ctx,
Stats: stats,
HandleFireEventHook: func(event *models.AlertCurEvent) {},
HandleRecoverEventHook: func(event *models.AlertCurEvent) {},
EventMuteHook: func(event *models.AlertCurEvent) bool { return false },
}
p.mayHandleGroup()
@@ -113,13 +127,14 @@ func (p *Processor) Handle(anomalyPoints []common.AnomalyPoint, from string, inh
// 这些信息的修改是不会引起worker restart的但是确实会影响告警处理逻辑
// 所以这里直接从memsto.AlertRuleCache中获取并覆盖
p.inhibit = inhibit
p.rule = p.atertRuleCache.Get(p.rule.Id)
cachedRule := p.rule
cachedRule := p.alertRuleCache.Get(p.rule.Id)
if cachedRule == nil {
logger.Errorf("rule not found %+v", anomalyPoints)
p.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", p.DatasourceId()), "handle_event").Inc()
return
}
p.rule = cachedRule
now := time.Now().Unix()
alertingKeys := map[string]struct{}{}
@@ -131,9 +146,17 @@ func (p *Processor) Handle(anomalyPoints []common.AnomalyPoint, from string, inh
hash := event.Hash
alertingKeys[hash] = struct{}{}
if mute.IsMuted(cachedRule, event, p.TargetCache, p.alertMuteCache) {
p.Stats.CounterMuteTotal.WithLabelValues(event.GroupName).Inc()
logger.Debugf("rule_eval:%s event:%v is muted", p.Key(), event)
continue
}
if p.EventMuteHook(event) {
p.Stats.CounterMuteTotal.WithLabelValues(event.GroupName).Inc()
logger.Debugf("rule_eval:%s event:%v is muted by hook", p.Key(), event)
continue
}
tagHash := TagHash(anomalyPoint)
eventsMap[tagHash] = append(eventsMap[tagHash], event)
}
@@ -142,7 +165,7 @@ func (p *Processor) Handle(anomalyPoints []common.AnomalyPoint, from string, inh
p.handleEvent(events)
}
p.HandleRecover(alertingKeys, now)
p.HandleRecover(alertingKeys, now, inhibit)
}
func (p *Processor) BuildEvent(anomalyPoint common.AnomalyPoint, from string, now int64) *models.AlertCurEvent {
@@ -156,6 +179,12 @@ func (p *Processor) BuildEvent(anomalyPoint common.AnomalyPoint, from string, no
}
event := p.rule.GenerateNewEvent(p.ctx)
bg := p.BusiGroupCache.GetByBusiGroupId(p.rule.GroupId)
if bg != nil {
event.GroupName = bg.Name
}
event.TriggerTime = anomalyPoint.Timestamp
event.TagsMap = p.tagsMap
event.DatasourceId = p.datasourceId
@@ -164,8 +193,8 @@ func (p *Processor) BuildEvent(anomalyPoint common.AnomalyPoint, from string, no
event.TargetIdent = p.target
event.TargetNote = p.targetNote
event.TriggerValue = anomalyPoint.ReadableValue()
event.TriggerValues = anomalyPoint.Values
event.TagsJSON = p.tagsArr
event.GroupName = p.groupName
event.Tags = strings.Join(p.tagsArr, ",,")
event.IsRecovered = false
event.Callbacks = p.rule.Callbacks
@@ -175,6 +204,13 @@ func (p *Processor) BuildEvent(anomalyPoint common.AnomalyPoint, from string, no
event.RuleConfig = p.rule.RuleConfig
event.RuleConfigJson = p.rule.RuleConfigJson
event.Severity = anomalyPoint.Severity
event.ExtraConfig = p.rule.ExtraConfigJSON
event.PromQl = anomalyPoint.Query
if event.TriggerValues != "" && strings.Count(event.TriggerValues, "$") > 1 {
// TriggerValues 有多个变量,将多个变量都放到 TriggerValue 中
event.TriggerValue = event.TriggerValues
}
if from == "inner" {
event.LastEvalTime = now
@@ -184,7 +220,7 @@ func (p *Processor) BuildEvent(anomalyPoint common.AnomalyPoint, from string, no
return event
}
func (p *Processor) HandleRecover(alertingKeys map[string]struct{}, now int64) {
func (p *Processor) HandleRecover(alertingKeys map[string]struct{}, now int64, inhibit bool) {
for _, hash := range p.pendings.Keys() {
if _, has := alertingKeys[hash]; has {
continue
@@ -192,19 +228,63 @@ func (p *Processor) HandleRecover(alertingKeys map[string]struct{}, now int64) {
p.pendings.Delete(hash)
}
hashArr := make([]string, 0, len(alertingKeys))
for hash := range p.fires.GetAll() {
if _, has := alertingKeys[hash]; has {
continue
}
p.RecoverSingle(hash, now, nil)
hashArr = append(hashArr, hash)
}
p.HandleRecoverEvent(hashArr, now, inhibit)
}
func (p *Processor) RecoverSingle(hash string, now int64, value *string) {
func (p *Processor) HandleRecoverEvent(hashArr []string, now int64, inhibit bool) {
cachedRule := p.rule
if cachedRule == nil {
return
}
if !inhibit {
for _, hash := range hashArr {
p.RecoverSingle(hash, now, nil)
}
return
}
eventMap := make(map[string]models.AlertCurEvent)
for _, hash := range hashArr {
event, has := p.fires.Get(hash)
if !has {
continue
}
e, exists := eventMap[event.Tags]
if !exists {
eventMap[event.Tags] = *event
continue
}
if e.Severity > event.Severity {
// hash 对应的恢复事件的被抑制了,把之前的事件删除
p.fires.Delete(e.Hash)
p.pendings.Delete(e.Hash)
eventMap[event.Tags] = *event
}
}
for _, event := range eventMap {
p.RecoverSingle(event.Hash, now, nil)
}
}
func (p *Processor) RecoverSingle(hash string, now int64, value *string, values ...string) {
cachedRule := p.rule
if cachedRule == nil {
return
}
event, has := p.fires.Get(hash)
if !has {
return
@@ -216,6 +296,9 @@ func (p *Processor) RecoverSingle(hash string, now int64, value *string) {
}
if value != nil {
event.TriggerValue = *value
if len(values) > 0 {
event.TriggerValues = values[0]
}
}
// 没查到触发阈值的vector姑且就认为这个vector的值恢复了
@@ -228,6 +311,8 @@ func (p *Processor) RecoverSingle(hash string, now int64, value *string) {
cachedRule.UpdateEvent(event)
event.IsRecovered = true
event.LastEvalTime = now
p.HandleRecoverEventHook(event)
p.pushEventToQueue(event)
}
@@ -285,9 +370,12 @@ func (p *Processor) fireEvent(event *models.AlertCurEvent) {
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)
@@ -297,11 +385,10 @@ func (p *Processor) fireEvent(event *models.AlertCurEvent) {
}
// 之前发送过告警了,这次是否要继续发送,要看是否过了通道静默时间
if event.LastEvalTime > fired.LastSentTime+int64(cachedRule.NotifyRepeatStep)*60 {
if event.LastEvalTime >= fired.LastSentTime+int64(cachedRule.NotifyRepeatStep)*60 {
if cachedRule.NotifyMaxNumber == 0 {
// 最大可以发送次数如果是0表示不想限制最大发送次数一直发即可
event.NotifyCurNumber = fired.NotifyCurNumber + 1
event.FirstTriggerTime = fired.FirstTriggerTime
p.pushEventToQueue(event)
} else {
// 有最大发送次数的限制,就要看已经发了几次了,是否达到了最大发送次数
@@ -310,7 +397,6 @@ func (p *Processor) fireEvent(event *models.AlertCurEvent) {
return
} else {
event.NotifyCurNumber = fired.NotifyCurNumber + 1
event.FirstTriggerTime = fired.FirstTriggerTime
p.pushEventToQueue(event)
}
}
@@ -318,6 +404,7 @@ func (p *Processor) fireEvent(event *models.AlertCurEvent) {
} else {
event.NotifyCurNumber = 1
event.FirstTriggerTime = event.TriggerTime
p.HandleFireEventHook(event)
p.pushEventToQueue(event)
}
}
@@ -328,25 +415,34 @@ func (p *Processor) pushEventToQueue(e *models.AlertCurEvent) {
p.fires.Set(e.Hash, e)
}
p.stats.CounterAlertsTotal.WithLabelValues(fmt.Sprintf("%d", e.DatasourceId)).Inc()
dispatch.LogEvent(e, "push_queue")
if !queue.EventQueue.PushFront(e) {
logger.Warningf("event_push_queue: queue is full, event:%+v", e)
p.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", p.DatasourceId()), "push_event_queue").Inc()
}
}
func (p *Processor) RecoverAlertCurEventFromDb() {
p.pendings = NewAlertCurEventMap(nil)
curEvents, err := models.AlertCurEventGetByRuleIdAndCluster(p.ctx, p.rule.Id, p.datasourceId)
curEvents, err := models.AlertCurEventGetByRuleIdAndDsId(p.ctx, p.rule.Id, p.datasourceId)
if err != nil {
logger.Errorf("recover event from db for rule:%s failed, err:%s", p.Key(), err)
p.Stats.CounterRuleEvalErrorTotal.WithLabelValues(fmt.Sprintf("%v", p.DatasourceId()), "get_recover_event").Inc()
p.fires = NewAlertCurEventMap(nil)
return
}
fireMap := make(map[string]*models.AlertCurEvent)
for _, event := range curEvents {
if event.Cate == models.HOST {
target, exists := p.TargetCache.Get(event.TargetIdent)
if exists && target.EngineName != p.EngineName && !(p.ctx.IsCenter && target.EngineName == "") {
// 如果是 host rule且 target 的 engineName 不是当前的 engineName 或者是中心机房 target EngineName 为空,就跳过
continue
}
}
event.DB2Mem()
fireMap[event.Hash] = event
}
@@ -406,7 +502,13 @@ func (p *Processor) mayHandleIdent() {
if target, exists := p.TargetCache.Get(ident); exists {
p.target = target.Ident
p.targetNote = target.Note
} else {
p.target = ident
p.targetNote = ""
}
} else {
p.target = ""
p.targetNote = ""
}
}
@@ -418,6 +520,11 @@ func (p *Processor) mayHandleGroup() {
}
}
func (p *Processor) DeleteProcessEvent(hash string) {
p.fires.Delete(hash)
p.pendings.Delete(hash)
}
func labelMapToArr(m map[string]string) []string {
numLabels := len(m)
@@ -433,7 +540,7 @@ func labelMapToArr(m map[string]string) []string {
}
func Hash(ruleId, datasourceId int64, vector common.AnomalyPoint) string {
return str.MD5(fmt.Sprintf("%d_%s_%d_%d", ruleId, vector.Labels.String(), datasourceId, vector.Severity))
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 {

View File

@@ -6,6 +6,7 @@ import (
"strings"
"time"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/prom"
"github.com/ccfos/nightingale/v6/pushgw/writer"
@@ -18,18 +19,18 @@ type RecordRuleContext struct {
datasourceId int64
quit chan struct{}
rule *models.RecordingRule
// writers *writer.WritersType
rule *models.RecordingRule
promClients *prom.PromClientMap
stats *astats.Stats
}
func NewRecordRuleContext(rule *models.RecordingRule, datasourceId int64, promClients *prom.PromClientMap, writers *writer.WritersType) *RecordRuleContext {
func NewRecordRuleContext(rule *models.RecordingRule, datasourceId int64, promClients *prom.PromClientMap, writers *writer.WritersType, stats *astats.Stats) *RecordRuleContext {
return &RecordRuleContext{
datasourceId: datasourceId,
quit: make(chan struct{}),
rule: rule,
promClients: promClients,
//writers: writers,
stats: stats,
}
}
@@ -54,20 +55,23 @@ func (rrc *RecordRuleContext) Start() {
if interval <= 0 {
interval = 10
}
ticker := time.NewTicker(time.Duration(interval) * time.Second)
go func() {
defer ticker.Stop()
for {
select {
case <-rrc.quit:
return
default:
case <-ticker.C:
rrc.Eval()
time.Sleep(time.Duration(interval) * time.Second)
}
}
}()
}
func (rrc *RecordRuleContext) Eval() {
rrc.stats.CounterRecordEval.WithLabelValues(fmt.Sprintf("%d", rrc.datasourceId)).Inc()
promql := strings.TrimSpace(rrc.rule.PromQl)
if promql == "" {
logger.Errorf("eval:%s promql is blank", rrc.Key())
@@ -76,23 +80,30 @@ func (rrc *RecordRuleContext) Eval() {
if rrc.promClients.IsNil(rrc.datasourceId) {
logger.Errorf("eval:%s reader client is nil", rrc.Key())
rrc.stats.CounterRecordEvalErrorTotal.WithLabelValues(fmt.Sprintf("%d", rrc.datasourceId)).Inc()
return
}
value, warnings, err := rrc.promClients.GetCli(rrc.datasourceId).Query(context.Background(), promql, time.Now())
if err != nil {
logger.Errorf("eval:%s promql:%s, error:%v", rrc.Key(), promql, err)
rrc.stats.CounterRecordEvalErrorTotal.WithLabelValues(fmt.Sprintf("%d", rrc.datasourceId)).Inc()
return
}
if len(warnings) > 0 {
logger.Errorf("eval:%s promql:%s, warnings:%v", rrc.Key(), promql, warnings)
rrc.stats.CounterRecordEvalErrorTotal.WithLabelValues(fmt.Sprintf("%d", rrc.datasourceId)).Inc()
return
}
ts := ConvertToTimeSeries(value, rrc.rule)
if len(ts) != 0 {
rrc.promClients.GetWriterCli(rrc.datasourceId).Write(ts)
err := rrc.promClients.GetWriterCli(rrc.datasourceId).Write(ts)
if err != nil {
logger.Errorf("eval:%s promql:%s, error:%v", rrc.Key(), promql, err)
rrc.stats.CounterRecordEvalErrorTotal.WithLabelValues(fmt.Sprintf("%d", rrc.datasourceId)).Inc()
}
}
}

View File

@@ -15,7 +15,7 @@ const (
LabelName = "__name__"
)
func ConvertToTimeSeries(value model.Value, rule *models.RecordingRule) (lst []*prompb.TimeSeries) {
func ConvertToTimeSeries(value model.Value, rule *models.RecordingRule) (lst []prompb.TimeSeries) {
switch value.Type() {
case model.ValVector:
items, ok := value.(model.Vector)
@@ -31,7 +31,7 @@ func ConvertToTimeSeries(value model.Value, rule *models.RecordingRule) (lst []*
s.Timestamp = time.Unix(item.Timestamp.Unix(), 0).UnixNano() / 1e6
s.Value = float64(item.Value)
l := labelsToLabelsProto(item.Metric, rule)
lst = append(lst, &prompb.TimeSeries{
lst = append(lst, prompb.TimeSeries{
Labels: l,
Samples: []prompb.Sample{s},
})
@@ -63,7 +63,7 @@ func ConvertToTimeSeries(value model.Value, rule *models.RecordingRule) (lst []*
Value: float64(v.Value),
})
}
lst = append(lst, &prompb.TimeSeries{
lst = append(lst, prompb.TimeSeries{
Labels: l,
Samples: slst,
})
@@ -78,7 +78,7 @@ func ConvertToTimeSeries(value model.Value, rule *models.RecordingRule) (lst []*
return
}
lst = append(lst, &prompb.TimeSeries{
lst = append(lst, prompb.TimeSeries{
Labels: nil,
Samples: []prompb.Sample{{Value: float64(item.Value), Timestamp: time.Unix(item.Timestamp.Unix(), 0).UnixNano() / 1e6}},
})
@@ -89,9 +89,9 @@ func ConvertToTimeSeries(value model.Value, rule *models.RecordingRule) (lst []*
return
}
func labelsToLabelsProto(labels model.Metric, rule *models.RecordingRule) (result []*prompb.Label) {
func labelsToLabelsProto(labels model.Metric, rule *models.RecordingRule) (result []prompb.Label) {
//name
nameLs := &prompb.Label{
nameLs := prompb.Label{
Name: LabelName,
Value: rule.Name,
}
@@ -101,7 +101,7 @@ func labelsToLabelsProto(labels model.Metric, rule *models.RecordingRule) (resul
continue
}
if model.LabelNameRE.MatchString(string(k)) {
result = append(result, &prompb.Label{
result = append(result, prompb.Label{
Name: string(k),
Value: string(v),
})
@@ -111,7 +111,7 @@ func labelsToLabelsProto(labels model.Metric, rule *models.RecordingRule) (resul
for _, v := range rule.AppendTagsJSON {
index := strings.Index(v, "=")
if model.LabelNameRE.MatchString(v[:index]) {
result = append(result, &prompb.Label{
result = append(result, prompb.Label{
Name: v[:index],
Value: v[index+1:],
})

View File

@@ -3,6 +3,7 @@ package record
import (
"context"
"fmt"
"strconv"
"time"
"github.com/ccfos/nightingale/v6/alert/aconf"
@@ -68,11 +69,11 @@ func (s *Scheduler) syncRecordRules() {
datasourceIds := s.promClients.Hit(rule.DatasourceIdsJson)
for _, dsId := range datasourceIds {
if !naming.DatasourceHashRing.IsHit(dsId, fmt.Sprintf("%d", rule.Id), s.aconf.Heartbeat.Endpoint) {
if !naming.DatasourceHashRing.IsHit(strconv.FormatInt(dsId, 10), fmt.Sprintf("%d", rule.Id), s.aconf.Heartbeat.Endpoint) {
continue
}
recordRule := NewRecordRuleContext(rule, dsId, s.promClients, s.writers)
recordRule := NewRecordRuleContext(rule, dsId, s.promClients, s.writers, s.stats)
recordRules[recordRule.Hash()] = recordRule
}
}

View File

@@ -39,15 +39,16 @@ func New(httpConfig httpx.Config, alert aconf.Alert, amc *memsto.AlertMuteCacheT
}
func (rt *Router) Config(r *gin.Engine) {
if !rt.HTTP.Alert.Enable {
if !rt.HTTP.APIForService.Enable {
return
}
service := r.Group("/v1/n9e")
if len(rt.HTTP.Alert.BasicAuth) > 0 {
service.Use(gin.BasicAuth(rt.HTTP.Alert.BasicAuth))
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)
}

View File

@@ -2,6 +2,7 @@ package router
import (
"fmt"
"strconv"
"strings"
"time"
@@ -72,8 +73,6 @@ func (rt *Router) pushEventToQueue(c *gin.Context) {
event.NotifyChannels = strings.Join(event.NotifyChannelsJSON, " ")
event.NotifyGroups = strings.Join(event.NotifyGroupsJSON, " ")
rt.AlertStats.CounterAlertsTotal.WithLabelValues(event.Cluster).Inc()
dispatch.LogEvent(event, "http_push_queue")
if !queue.EventQueue.PushFront(event) {
msg := fmt.Sprintf("event:%+v push_queue err: queue is full", event)
@@ -83,6 +82,14 @@ func (rt *Router) pushEventToQueue(c *gin.Context) {
ginx.NewRender(c).Message(nil)
}
func (rt *Router) eventPersist(c *gin.Context) {
var event *models.AlertCurEvent
ginx.BindJSON(c, &event)
event.FE2DB()
err := models.EventPersist(rt.Ctx, event)
ginx.NewRender(c).Data(event.Id, err)
}
type eventForm struct {
Alert bool `json:"alert"`
AnomalyPoints []common.AnomalyPoint `json:"vectors"`
@@ -96,7 +103,7 @@ func (rt *Router) makeEvent(c *gin.Context) {
ginx.BindJSON(c, &events)
//now := time.Now().Unix()
for i := 0; i < len(events); i++ {
node, err := naming.DatasourceHashRing.GetNode(events[i].DatasourceId, fmt.Sprintf("%d", events[i].RuleId))
node, err := naming.DatasourceHashRing.GetNode(strconv.FormatInt(events[i].DatasourceId, 10), fmt.Sprintf("%d", events[i].RuleId))
if err != nil {
logger.Warningf("event:%+v get node err:%v", events[i], err)
ginx.Bomb(200, "event node not exists")

View File

@@ -1,21 +1,25 @@
package sender
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"github.com/ccfos/nightingale/v6/alert/aconf"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/ibex"
"github.com/ccfos/nightingale/v6/pkg/poster"
imodels "github.com/flashcatcloud/ibex/src/models"
"github.com/flashcatcloud/ibex/src/storage"
"github.com/toolkits/pkg/logger"
)
func SendCallbacks(ctx *ctx.Context, urls []string, event *models.AlertCurEvent, targetCache *memsto.TargetCacheType, ibexConf aconf.Ibex) {
func SendCallbacks(ctx *ctx.Context, urls []string, event *models.AlertCurEvent, targetCache *memsto.TargetCacheType, userCache *memsto.UserCacheType,
taskTplCache *memsto.TaskTplCache, stats *astats.Stats) {
for _, url := range urls {
if url == "" {
continue
@@ -23,7 +27,7 @@ func SendCallbacks(ctx *ctx.Context, urls []string, event *models.AlertCurEvent,
if strings.HasPrefix(url, "${ibex}") {
if !event.IsRecovered {
handleIbex(ctx, url, event, targetCache, ibexConf)
handleIbex(ctx, url, event, targetCache, userCache, taskTplCache)
}
continue
}
@@ -32,35 +36,29 @@ func SendCallbacks(ctx *ctx.Context, urls []string, event *models.AlertCurEvent,
url = "http://" + url
}
stats.AlertNotifyTotal.WithLabelValues("rule_callback").Inc()
resp, code, err := poster.PostJSON(url, 5*time.Second, event, 3)
if err != nil {
logger.Errorf("event_callback(rule_id=%d url=%s) fail, resp: %s, err: %v, code: %d", event.RuleId, url, string(resp), err, code)
logger.Errorf("event_callback_fail(rule_id=%d url=%s), resp: %s, err: %v, code: %d", event.RuleId, url, string(resp), err, code)
stats.AlertNotifyErrorTotal.WithLabelValues("rule_callback").Inc()
} else {
logger.Infof("event_callback(rule_id=%d url=%s) succ, resp: %s, code: %d", event.RuleId, url, string(resp), code)
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"`
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, ibexConf aconf.Ibex) {
func handleIbex(ctx *ctx.Context, url string, event *models.AlertCurEvent, targetCache *memsto.TargetCacheType, userCache *memsto.UserCacheType,
taskTplCache *memsto.TaskTplCache) {
if imodels.DB() == nil {
logger.Warning("event_callback_ibex: db is nil")
return
}
arr := strings.Split(url, "/")
var idstr string
@@ -90,12 +88,7 @@ func handleIbex(ctx *ctx.Context, url string, event *models.AlertCurEvent, targe
return
}
tpl, err := models.TaskTplGet(ctx, "id = ?", id)
if err != nil {
logger.Errorf("event_callback_ibex: failed to get tpl: %v", err)
return
}
tpl := taskTplCache.Get(id)
if tpl == nil {
logger.Errorf("event_callback_ibex: no such tpl(%d)", id)
return
@@ -103,7 +96,7 @@ func handleIbex(ctx *ctx.Context, url string, event *models.AlertCurEvent, targe
// check perm
// tpl.GroupId - host - account 三元组校验权限
can, err := canDoIbex(ctx, tpl.UpdateBy, tpl, host, targetCache)
can, err := canDoIbex(tpl.UpdateBy, tpl, host, targetCache, userCache)
if err != nil {
logger.Errorf("event_callback_ibex: check perm fail: %v", err)
return
@@ -114,61 +107,68 @@ func handleIbex(ctx *ctx.Context, url string, event *models.AlertCurEvent, targe
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,
Action: "start",
Creator: tpl.UpdateBy,
Hosts: []string{host},
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
}
var res TaskCreateReply
err = ibex.New(
ibexConf.Address,
ibexConf.BasicAuthUser,
ibexConf.BasicAuthPass,
ibexConf.Timeout,
).
Path("/ibex/v1/tasks").
In(in).
Out(&res).
POST()
// call ibex
in := models.TaskForm{
Title: tpl.Title + " FH: " + host,
Account: tpl.Account,
Batch: tpl.Batch,
Tolerance: tpl.Tolerance,
Timeout: tpl.Timeout,
Pause: tpl.Pause,
Script: tpl.Script,
Args: tpl.Args,
Stdin: string(tags),
Action: "start",
Creator: tpl.UpdateBy,
Hosts: []string{host},
AlertTriggered: true,
}
id, err = TaskAdd(in, tpl.UpdateBy, ctx.IsCenter)
if err != nil {
logger.Errorf("event_callback_ibex: call ibex fail: %v", err)
return
}
if res.Err != "" {
logger.Errorf("event_callback_ibex: call ibex response error: %v", res.Err)
return
}
// write db
record := models.TaskRecord{
Id: res.Dat,
EventId: event.Id,
GroupId: tpl.GroupId,
IbexAddress: ibexConf.Address,
IbexAuthUser: ibexConf.BasicAuthUser,
IbexAuthPass: ibexConf.BasicAuthPass,
Title: in.Title,
Account: in.Account,
Batch: in.Batch,
Tolerance: in.Tolerance,
Timeout: in.Timeout,
Pause: in.Pause,
Script: in.Script,
Args: in.Args,
CreateAt: time.Now().Unix(),
CreateBy: in.Creator,
Id: id,
EventId: event.Id,
GroupId: tpl.GroupId,
Title: in.Title,
Account: in.Account,
Batch: in.Batch,
Tolerance: in.Tolerance,
Timeout: in.Timeout,
Pause: in.Pause,
Script: in.Script,
Args: in.Args,
CreateAt: time.Now().Unix(),
CreateBy: in.Creator,
}
if err = record.Add(ctx); err != nil {
@@ -176,12 +176,8 @@ func handleIbex(ctx *ctx.Context, url string, event *models.AlertCurEvent, targe
}
}
func canDoIbex(ctx *ctx.Context, username string, tpl *models.TaskTpl, host string, targetCache *memsto.TargetCacheType) (bool, error) {
user, err := models.UserGetByUsername(ctx, username)
if err != nil {
return false, err
}
func canDoIbex(username string, tpl *models.TaskTpl, host string, targetCache *memsto.TargetCacheType, userCache *memsto.UserCacheType) (bool, error) {
user := userCache.GetByUsername(username)
if user != nil && user.IsAdmin() {
return true, nil
}
@@ -193,3 +189,88 @@ func canDoIbex(ctx *ctx.Context, username string, tpl *models.TaskTpl, host stri
return target.GroupId == tpl.GroupId, nil
}
func TaskAdd(f models.TaskForm, authUser string, isCenter bool) (int64, error) {
hosts := cleanHosts(f.Hosts)
if len(hosts) == 0 {
return 0, fmt.Errorf("arg(hosts) empty")
}
taskMeta := &imodels.TaskMeta{
Title: f.Title,
Account: f.Account,
Batch: f.Batch,
Tolerance: f.Tolerance,
Timeout: f.Timeout,
Pause: f.Pause,
Script: f.Script,
Args: f.Args,
Stdin: f.Stdin,
Creator: f.Creator,
}
err := taskMeta.CleanFields()
if err != nil {
return 0, err
}
taskMeta.HandleFH(hosts[0])
// 任务类型分为"告警规则触发"和"n9e center用户下发"两种;
// 边缘机房"告警规则触发"的任务不需要规划并且它可能是失联的无法使用db资源所以放入redis缓存中直接下发给agentd执行
if !isCenter && f.AlertTriggered {
if err := taskMeta.Create(); err != nil {
// 当网络不连通时生成唯一的id防止边缘机房中不同任务的id相同
// 方法是redis自增id去防止同一个机房的不同n9e edge生成的id相同
// 但没法防止不同边缘机房生成同样的id所以生成id的数据不会上报存入数据库只用于闭环执行。
taskMeta.Id, err = storage.IdGet()
if err != nil {
return 0, err
}
}
taskHost := imodels.TaskHost{
Id: taskMeta.Id,
Host: hosts[0],
Status: "running",
}
if err = taskHost.Create(); err != nil {
logger.Warningf("task_add_fail: authUser=%s title=%s err=%s", authUser, taskMeta.Title, err.Error())
}
// 缓存任务元信息和待下发的任务
err = taskMeta.Cache(hosts[0])
if err != nil {
return 0, err
}
} else {
// 如果是中心机房,还是保持之前的逻辑
err = taskMeta.Save(hosts, f.Action)
if err != nil {
return 0, err
}
}
logger.Infof("task_add_succ: authUser=%s title=%s", authUser, taskMeta.Title)
return taskMeta.Id, nil
}
func cleanHosts(formHosts []string) []string {
cnt := len(formHosts)
arr := make([]string, 0, cnt)
for i := 0; i < cnt; i++ {
item := strings.TrimSpace(formHosts[i])
if item == "" {
continue
}
if strings.HasPrefix(item, "#") {
continue
}
arr = append(arr, item)
}
return arr
}

View File

@@ -5,6 +5,7 @@ import (
"strings"
"time"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/poster"
@@ -32,7 +33,7 @@ type DingtalkSender struct {
}
func (ds *DingtalkSender) Send(ctx MessageContext) {
if len(ctx.Users) == 0 || ctx.Rule == nil || ctx.Event == nil {
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
return
}
@@ -40,7 +41,7 @@ func (ds *DingtalkSender) Send(ctx MessageContext) {
if len(urls) == 0 {
return
}
message := BuildTplMessage(ds.tpl, ctx.Event)
message := BuildTplMessage(models.Dingtalk, ds.tpl, ctx.Events)
for _, url := range urls {
var body dingtalk
@@ -49,7 +50,7 @@ func (ds *DingtalkSender) Send(ctx MessageContext) {
body = dingtalk{
Msgtype: "markdown",
Markdown: dingtalkMarkdown{
Title: ctx.Event.RuleName,
Title: ctx.Events[0].RuleName,
Text: message,
},
}
@@ -57,7 +58,7 @@ func (ds *DingtalkSender) Send(ctx MessageContext) {
body = dingtalk{
Msgtype: "markdown",
Markdown: dingtalkMarkdown{
Title: ctx.Event.RuleName,
Title: ctx.Events[0].RuleName,
Text: message + "\n" + strings.Join(ats, " "),
},
At: dingtalkAt{
@@ -66,7 +67,8 @@ func (ds *DingtalkSender) Send(ctx MessageContext) {
},
}
}
ds.doSend(url, body)
doSend(url, body, models.Dingtalk, ctx.Stats)
}
}
@@ -81,7 +83,7 @@ func (ds *DingtalkSender) extract(users []*models.User) ([]string, []string) {
}
if token, has := user.ExtractToken(models.Dingtalk); has {
url := token
if !strings.HasPrefix(token, "https://") {
if !strings.HasPrefix(token, "https://") && !strings.HasPrefix(token, "http://") {
url = "https://oapi.dingtalk.com/robot/send?access_token=" + token
}
urls = append(urls, url)
@@ -90,11 +92,14 @@ func (ds *DingtalkSender) extract(users []*models.User) ([]string, []string) {
return urls, ats
}
func (ds *DingtalkSender) doSend(url string, body dingtalk) {
func doSend(url string, body interface{}, channel string, stats *astats.Stats) {
stats.AlertNotifyTotal.WithLabelValues(channel).Inc()
res, code, err := poster.PostJSON(url, time.Second*5, body, 3)
if err != nil {
logger.Errorf("dingtalk_sender: result=fail url=%s code=%d error=%v response=%s", url, code, err, string(res))
logger.Errorf("%s_sender: result=fail url=%s code=%d error=%v req:%v response=%s", channel, url, code, err, body, string(res))
stats.AlertNotifyErrorTotal.WithLabelValues(channel).Inc()
} else {
logger.Infof("dingtalk_sender: result=succ url=%s code=%d response=%s", url, code, string(res))
logger.Infof("%s_sender: result=succ url=%s code=%d req:%v response=%s", channel, url, code, body, string(res))
}
}

View File

@@ -2,10 +2,12 @@ package sender
import (
"crypto/tls"
"errors"
"html/template"
"time"
"github.com/ccfos/nightingale/v6/alert/aconf"
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/models"
"github.com/toolkits/pkg/logger"
@@ -22,19 +24,21 @@ type EmailSender struct {
}
func (es *EmailSender) Send(ctx MessageContext) {
if len(ctx.Users) == 0 || ctx.Rule == nil || ctx.Event == nil {
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, ctx.Event)
subject = BuildTplMessage(models.Email, es.subjectTpl, []*models.AlertCurEvent{ctx.Events[0]})
} else {
subject = ctx.Event.RuleName
subject = ctx.Events[0].RuleName
}
content := BuildTplMessage(es.contentTpl, ctx.Event)
content := BuildTplMessage(models.Email, es.contentTpl, ctx.Events)
es.WriteEmail(subject, content, tos)
ctx.Stats.AlertNotifyTotal.WithLabelValues(models.Email).Add(float64(len(tos)))
}
func extract(users []*models.User) []string {
@@ -47,7 +51,7 @@ func extract(users []*models.User) []string {
return tos
}
func (es *EmailSender) SendEmail(subject, content string, tos []string, stmp aconf.SMTPConfig) {
func SendEmail(subject, content string, tos []string, stmp aconf.SMTPConfig) error {
conf := stmp
d := gomail.NewDialer(conf.Host, conf.Port, conf.User, conf.Pass)
@@ -64,8 +68,9 @@ func (es *EmailSender) SendEmail(subject, content string, tos []string, stmp aco
err := d.DialAndSend(m)
if err != nil {
logger.Errorf("email_sender: failed to send: %v", err)
return errors.New("email_sender: failed to send: " + err.Error())
}
return nil
}
func (es *EmailSender) WriteEmail(subject, content string, tos []string) {
@@ -81,12 +86,18 @@ func (es *EmailSender) WriteEmail(subject, content string, tos []string) {
func dialSmtp(d *gomail.Dialer) gomail.SendCloser {
for {
if s, err := d.Dial(); err != nil {
logger.Errorf("email_sender: failed to dial smtp: %s", err)
select {
case <-mailQuit:
// Note that Sendcloser is not obtained below,
// and the outgoing signal (with configuration changes) exits the current dial
return nil
default:
if s, err := d.Dial(); err != nil {
logger.Errorf("email_sender: failed to dial smtp: %s", err)
} else {
return s
}
time.Sleep(time.Second)
continue
} else {
return s
}
}
}
@@ -94,21 +105,40 @@ func dialSmtp(d *gomail.Dialer) gomail.SendCloser {
var mailQuit = make(chan struct{})
func RestartEmailSender(smtp aconf.SMTPConfig) {
close(mailQuit)
mailQuit = make(chan struct{})
StartEmailSender(smtp)
// Notify internal start exit
mailQuit <- struct{}{}
startEmailSender(smtp)
}
func StartEmailSender(smtp aconf.SMTPConfig) {
var smtpConfig aconf.SMTPConfig
func InitEmailSender(ncc *memsto.NotifyConfigCacheType) {
mailch = make(chan *gomail.Message, 100000)
go updateSmtp(ncc)
smtpConfig = ncc.GetSMTP()
startEmailSender(smtpConfig)
}
func updateSmtp(ncc *memsto.NotifyConfigCacheType) {
for {
time.Sleep(1 * time.Minute)
smtp := ncc.GetSMTP()
if smtpConfig.Host != smtp.Host || smtpConfig.Batch != smtp.Batch || smtpConfig.From != smtp.From ||
smtpConfig.Pass != smtp.Pass || smtpConfig.User != smtp.User || smtpConfig.Port != smtp.Port ||
smtpConfig.InsecureSkipVerify != smtp.InsecureSkipVerify { //diff
smtpConfig = smtp
RestartEmailSender(smtp)
}
}
}
func startEmailSender(smtp aconf.SMTPConfig) {
conf := smtp
if conf.Host == "" || conf.Port == 0 {
logger.Warning("SMTP configurations invalid")
return
}
logger.Infof("start email sender... %+v", conf)
logger.Infof("start email sender... conf.Host:%+v,conf.Port:%+v", conf.Host, conf.Port)
d := gomail.NewDialer(conf.Host, conf.Port, conf.User, conf.Pass)
if conf.InsecureSkipVerify {
@@ -129,6 +159,12 @@ func StartEmailSender(smtp aconf.SMTPConfig) {
if !open {
s = dialSmtp(d)
if s == nil {
// Indicates that the dialing failed and exited the current goroutine directly,
// but put the Message back in the mailch
mailch <- m
return
}
open = true
}
if err := gomail.Send(s, m); err != nil {
@@ -140,6 +176,12 @@ func StartEmailSender(smtp aconf.SMTPConfig) {
}
s = dialSmtp(d)
if s == nil {
// Indicates that the dialing failed and exited the current goroutine directly,
// but put the Message back in the mailch
mailch <- m
return
}
open = true
if err := gomail.Send(s, m); err != nil {

View File

@@ -3,12 +3,8 @@ package sender
import (
"html/template"
"strings"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/poster"
"github.com/toolkits/pkg/logger"
)
type feishuContent struct {
@@ -31,11 +27,11 @@ type FeishuSender struct {
}
func (fs *FeishuSender) Send(ctx MessageContext) {
if len(ctx.Users) == 0 || ctx.Rule == nil || ctx.Event == nil {
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
return
}
urls, ats := fs.extract(ctx.Users)
message := BuildTplMessage(fs.tpl, ctx.Event)
message := BuildTplMessage(models.Feishu, fs.tpl, ctx.Events)
for _, url := range urls {
body := feishu{
Msgtype: "text",
@@ -49,7 +45,7 @@ func (fs *FeishuSender) Send(ctx MessageContext) {
IsAtAll: false,
}
}
fs.doSend(url, body)
doSend(url, body, models.Feishu, ctx.Stats)
}
}
@@ -63,7 +59,7 @@ func (fs *FeishuSender) extract(users []*models.User) ([]string, []string) {
}
if token, has := user.ExtractToken(models.Feishu); has {
url := token
if !strings.HasPrefix(token, "https://") {
if !strings.HasPrefix(token, "https://") && !strings.HasPrefix(token, "http://") {
url = "https://open.feishu.cn/open-apis/bot/v2/hook/" + token
}
urls = append(urls, url)
@@ -71,12 +67,3 @@ func (fs *FeishuSender) extract(users []*models.User) ([]string, []string) {
}
return urls, ats
}
func (fs *FeishuSender) doSend(url string, body feishu) {
res, code, err := poster.PostJSON(url, time.Second*5, body, 3)
if err != nil {
logger.Errorf("feishu_sender: result=fail url=%s code=%d error=%v response=%s", url, code, err, string(res))
} else {
logger.Infof("feishu_sender: result=succ url=%s code=%d response=%s", url, code, string(res))
}
}

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

@@ -0,0 +1,131 @@
package sender
import (
"fmt"
"html/template"
"strings"
"github.com/ccfos/nightingale/v6/models"
)
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(models.FeishuCard, 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 {
doSend(url, body, models.FeishuCard, ctx.Stats)
}
}
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://") && !strings.HasPrefix(token, "http://") {
url = "https://open.feishu.cn/open-apis/bot/v2/hook/" + strings.TrimSpace(token)
}
urls = append(urls, url)
}
}
return urls, ats
}

View File

@@ -4,10 +4,9 @@ import (
"html/template"
"net/url"
"strings"
"time"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/poster"
"github.com/toolkits/pkg/logger"
)
@@ -15,6 +14,7 @@ import (
type MatterMostMessage struct {
Text string
Tokens []string
Stats *astats.Stats
}
type mm struct {
@@ -28,7 +28,7 @@ type MmSender struct {
}
func (ms *MmSender) Send(ctx MessageContext) {
if len(ctx.Users) == 0 || ctx.Rule == nil || ctx.Event == nil {
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
return
}
@@ -36,11 +36,12 @@ func (ms *MmSender) Send(ctx MessageContext) {
if len(urls) == 0 {
return
}
message := BuildTplMessage(ms.tpl, ctx.Event)
message := BuildTplMessage(models.Mm, ms.tpl, ctx.Events)
SendMM(MatterMostMessage{
Text: message,
Tokens: urls,
Stats: ctx.Stats,
})
}
@@ -87,13 +88,7 @@ func SendMM(message MatterMostMessage) {
Username: username,
Text: txt + message.Text,
}
res, code, err := poster.PostJSON(ur, time.Second*5, body, 3)
if err != nil {
logger.Errorf("mm_sender: result=fail url=%s code=%d error=%v response=%s", ur, code, err, string(res))
} else {
logger.Infof("mm_sender: result=succ url=%s code=%d response=%s", ur, code, string(res))
}
doSend(ur, body, models.Mm, message.Stats)
}
}
}

View File

@@ -6,6 +6,7 @@ import (
"os/exec"
"time"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/models"
"github.com/toolkits/pkg/file"
@@ -13,20 +14,22 @@ import (
"github.com/toolkits/pkg/sys"
)
func MayPluginNotify(noticeBytes []byte, notifyScript models.NotifyScript) {
func MayPluginNotify(noticeBytes []byte, notifyScript models.NotifyScript, stats *astats.Stats) {
if len(noticeBytes) == 0 {
return
}
alertingCallScript(noticeBytes, notifyScript)
alertingCallScript(noticeBytes, notifyScript, stats)
}
func alertingCallScript(stdinBytes []byte, notifyScript models.NotifyScript) {
func alertingCallScript(stdinBytes []byte, notifyScript models.NotifyScript, stats *astats.Stats) {
// not enable or no notify.py? do nothing
config := notifyScript
if !config.Enable || config.Content == "" {
return
}
channel := "script"
stats.AlertNotifyTotal.WithLabelValues(channel).Inc()
fpath := ".notify_scriptt"
if config.Type == 1 {
fpath = config.Content
@@ -35,7 +38,8 @@ func alertingCallScript(stdinBytes []byte, notifyScript models.NotifyScript) {
if file.IsExist(fpath) {
oldContent, err := file.ToString(fpath)
if err != nil {
logger.Errorf("event_notify: read script file err: %v", err)
logger.Errorf("event_script_notify_fail: read script file err: %v", err)
stats.AlertNotifyErrorTotal.WithLabelValues(channel).Inc()
return
}
@@ -47,13 +51,15 @@ func alertingCallScript(stdinBytes []byte, notifyScript models.NotifyScript) {
if rewrite {
_, err := file.WriteString(fpath, config.Content)
if err != nil {
logger.Errorf("event_notify: write script file err: %v", err)
logger.Errorf("event_script_notify_fail: write script file err: %v", err)
stats.AlertNotifyErrorTotal.WithLabelValues(channel).Inc()
return
}
err = os.Chmod(fpath, 0777)
if err != nil {
logger.Errorf("event_notify: chmod script file err: %v", err)
logger.Errorf("event_script_notify_fail: chmod script file err: %v", err)
stats.AlertNotifyErrorTotal.WithLabelValues(channel).Inc()
return
}
}
@@ -70,7 +76,7 @@ func alertingCallScript(stdinBytes []byte, notifyScript models.NotifyScript) {
err := startCmd(cmd)
if err != nil {
logger.Errorf("event_notify: run cmd err: %v", err)
logger.Errorf("event_script_notify_fail: run cmd err: %v", err)
return
}
@@ -78,20 +84,21 @@ func alertingCallScript(stdinBytes []byte, notifyScript models.NotifyScript) {
if isTimeout {
if err == nil {
logger.Errorf("event_notify: timeout and killed process %s", fpath)
logger.Errorf("event_script_notify_fail: timeout and killed process %s", fpath)
}
if err != nil {
logger.Errorf("event_notify: kill process %s occur error %v", fpath, err)
logger.Errorf("event_script_notify_fail: kill process %s occur error %v", fpath, err)
stats.AlertNotifyErrorTotal.WithLabelValues(channel).Inc()
}
return
}
if err != nil {
logger.Errorf("event_notify: exec script %s occur error: %v, output: %s", fpath, err, buf.String())
logger.Errorf("event_script_notify_fail: exec script %s occur error: %v, output: %s", fpath, err, buf.String())
stats.AlertNotifyErrorTotal.WithLabelValues(channel).Inc()
return
}
logger.Infof("event_notify: exec %s output: %s", fpath, buf.String())
logger.Infof("event_script_notify_ok: exec %s output: %s", fpath, buf.String())
}

View File

@@ -5,6 +5,7 @@ import (
"html/template"
"github.com/ccfos/nightingale/v6/alert/aconf"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/models"
)
@@ -17,13 +18,14 @@ type (
// MessageContext 一个event所生成的告警通知的上下文
MessageContext struct {
Users []*models.User
Rule *models.AlertRule
Event *models.AlertCurEvent
Users []*models.User
Rule *models.AlertRule
Events []*models.AlertCurEvent
Stats *astats.Stats
}
)
func NewSender(key string, tpls map[string]*template.Template, smtp aconf.SMTPConfig) Sender {
func NewSender(key string, tpls map[string]*template.Template, smtp ...aconf.SMTPConfig) Sender {
switch key {
case models.Dingtalk:
return &DingtalkSender{tpl: tpls[models.Dingtalk]}
@@ -31,8 +33,10 @@ func NewSender(key string, tpls map[string]*template.Template, smtp aconf.SMTPCo
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}
return &EmailSender{subjectTpl: tpls[models.EmailSubject], contentTpl: tpls[models.Email], smtp: smtp[0]}
case models.Mm:
return &MmSender{tpl: tpls[models.Mm]}
case models.Telegram:
@@ -41,22 +45,33 @@ func NewSender(key string, tpls map[string]*template.Template, smtp aconf.SMTPCo
return nil
}
func BuildMessageContext(rule *models.AlertRule, event *models.AlertCurEvent, uids []int64, userCache *memsto.UserCacheType) MessageContext {
func BuildMessageContext(rule *models.AlertRule, events []*models.AlertCurEvent, uids []int64, userCache *memsto.UserCacheType, stats *astats.Stats) MessageContext {
users := userCache.GetByUserIds(uids)
return MessageContext{
Rule: rule,
Event: event,
Users: users,
Rule: rule,
Events: events,
Users: users,
Stats: stats,
}
}
func BuildTplMessage(tpl *template.Template, event *models.AlertCurEvent) string {
type BuildTplMessageFunc func(channel string, tpl *template.Template, events []*models.AlertCurEvent) string
var BuildTplMessage BuildTplMessageFunc = buildTplMessage
func buildTplMessage(channel string, tpl *template.Template, events []*models.AlertCurEvent) string {
if tpl == nil {
return "tpl for current sender not found, please check configuration"
}
var body bytes.Buffer
if err := tpl.Execute(&body, event); err != nil {
return err.Error()
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 body.String()
return content
}

View File

@@ -3,10 +3,9 @@ package sender
import (
"html/template"
"strings"
"time"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/poster"
"github.com/toolkits/pkg/logger"
)
@@ -14,6 +13,7 @@ import (
type TelegramMessage struct {
Text string
Tokens []string
Stats *astats.Stats
}
type telegram struct {
@@ -26,15 +26,16 @@ type TelegramSender struct {
}
func (ts *TelegramSender) Send(ctx MessageContext) {
if len(ctx.Users) == 0 || ctx.Rule == nil || ctx.Event == nil {
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
return
}
tokens := ts.extract(ctx.Users)
message := BuildTplMessage(ts.tpl, ctx.Event)
message := BuildTplMessage(models.Telegram, ts.tpl, ctx.Events)
SendTelegram(TelegramMessage{
Text: message,
Tokens: tokens,
Stats: ctx.Stats,
})
}
@@ -55,7 +56,7 @@ func SendTelegram(message TelegramMessage) {
continue
}
var url string
if strings.HasPrefix(message.Tokens[i], "https://") {
if strings.HasPrefix(message.Tokens[i], "https://") || strings.HasPrefix(message.Tokens[i], "http://") {
url = message.Tokens[i]
} else {
array := strings.Split(message.Tokens[i], "/")
@@ -72,11 +73,6 @@ func SendTelegram(message TelegramMessage) {
Text: message.Text,
}
res, code, err := poster.PostJSON(url, time.Second*5, body, 3)
if err != nil {
logger.Errorf("telegram_sender: result=fail url=%s code=%d error=%v response=%s", url, code, err, string(res))
} else {
logger.Infof("telegram_sender: result=succ url=%s code=%d response=%s", url, code, string(res))
}
doSend(url, body, models.Telegram, message.Stats)
}
}

View File

@@ -3,16 +3,17 @@ package sender
import (
"bytes"
"encoding/json"
"io/ioutil"
"io"
"net/http"
"time"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/models"
"github.com/toolkits/pkg/logger"
)
func SendWebhooks(webhooks []*models.Webhook, event *models.AlertCurEvent) {
func SendWebhooks(webhooks []*models.Webhook, event *models.AlertCurEvent, stats *astats.Stats) {
for _, conf := range webhooks {
if conf.Url == "" || !conf.Enable {
continue
@@ -37,7 +38,7 @@ func SendWebhooks(webhooks []*models.Webhook, event *models.AlertCurEvent) {
if len(conf.Headers) > 0 && len(conf.Headers)%2 == 0 {
for i := 0; i < len(conf.Headers); i += 2 {
if conf.Headers[i] == "host" {
if conf.Headers[i] == "host" || conf.Headers[i] == "Host" {
req.Host = conf.Headers[i+1]
continue
}
@@ -50,19 +51,21 @@ func SendWebhooks(webhooks []*models.Webhook, event *models.AlertCurEvent) {
Timeout: time.Duration(conf.Timeout) * time.Second,
}
stats.AlertNotifyTotal.WithLabelValues("webhook").Inc()
var resp *http.Response
resp, err = client.Do(req)
if err != nil {
logger.Warningf("WebhookCallError, ruleId: [%d], eventId: [%d], url: [%s], error: [%s]", event.RuleId, event.Id, conf.Url, err)
stats.AlertNotifyErrorTotal.WithLabelValues("webhook").Inc()
logger.Errorf("event_webhook_fail, ruleId: [%d], eventId: [%d], url: [%s], error: [%s]", event.RuleId, event.Id, conf.Url, err)
continue
}
var body []byte
if resp.Body != nil {
defer resp.Body.Close()
body, _ = ioutil.ReadAll(resp.Body)
body, _ = io.ReadAll(resp.Body)
}
logger.Debugf("alertingWebhook done, url: %s, response code: %d, body: %s", conf.Url, resp.StatusCode, string(body))
logger.Debugf("event_webhook_succ, url: %s, response code: %d, body: %s event:%+v", conf.Url, resp.StatusCode, string(body), event)
}
}

View File

@@ -3,12 +3,8 @@ package sender
import (
"html/template"
"strings"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/poster"
"github.com/toolkits/pkg/logger"
)
type wecomMarkdown struct {
@@ -25,11 +21,11 @@ type WecomSender struct {
}
func (ws *WecomSender) Send(ctx MessageContext) {
if len(ctx.Users) == 0 || ctx.Rule == nil || ctx.Event == nil {
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
return
}
urls := ws.extract(ctx.Users)
message := BuildTplMessage(ws.tpl, ctx.Event)
message := BuildTplMessage(models.Wecom, ws.tpl, ctx.Events)
for _, url := range urls {
body := wecom{
Msgtype: "markdown",
@@ -37,7 +33,7 @@ func (ws *WecomSender) Send(ctx MessageContext) {
Content: message,
},
}
ws.doSend(url, body)
doSend(url, body, models.Wecom, ctx.Stats)
}
}
@@ -46,7 +42,7 @@ func (ws *WecomSender) extract(users []*models.User) []string {
for _, user := range users {
if token, has := user.ExtractToken(models.Wecom); has {
url := token
if !strings.HasPrefix(token, "https://") {
if !strings.HasPrefix(token, "https://") && !strings.HasPrefix(token, "http://") {
url = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=" + token
}
urls = append(urls, url)
@@ -54,12 +50,3 @@ func (ws *WecomSender) extract(users []*models.User) []string {
}
return urls
}
func (ws *WecomSender) doSend(url string, body wecom) {
res, code, err := poster.PostJSON(url, time.Second*5, body, 3)
if err != nil {
logger.Errorf("wecom_sender: result=fail url=%s code=%d error=%v response=%s", url, code, err, string(res))
} else {
logger.Infof("wecom_sender: result=succ url=%s code=%d response=%s", url, code, string(res))
}
}

View File

@@ -1,19 +1,17 @@
package cconf
import (
"github.com/gin-gonic/gin"
)
import "time"
type Center struct {
Plugins []Plugin
BasicAuth gin.Accounts
MetricsYamlFile string
OpsYamlFile string
BuiltinIntegrationsDir string
I18NHeaderKey string
MetricDesc MetricDescType
TargetMetrics map[string]string
AnonymousAccess AnonymousAccess
UseFileAssets bool
FlashDuty FlashDuty
}
type Plugin struct {
@@ -23,6 +21,12 @@ type Plugin struct {
TypeName string `json:"plugin_type_name"`
}
type FlashDuty struct {
Api string
Headers map[string]string
Timeout time.Duration
}
type AnonymousAccess struct {
PromQuerier bool
AlertDetail bool

View File

@@ -4,7 +4,6 @@ import (
"path"
"github.com/toolkits/pkg/file"
"github.com/toolkits/pkg/runner"
)
// metricDesc , As load map happens before read map, there is no necessary to use concurrent map for metric desc store
@@ -33,10 +32,10 @@ func GetMetricDesc(lang, metric string) string {
return MetricDesc.CommonDesc[metric]
}
func LoadMetricsYaml(metricsYamlFile string) error {
func LoadMetricsYaml(configDir, metricsYamlFile string) error {
fp := metricsYamlFile
if fp == "" {
fp = path.Join(runner.Cwd, "etc", "metrics.yaml")
fp = path.Join(configDir, "metrics.yaml")
}
if !file.IsExist(fp) {
return nil

View File

@@ -1,10 +1,11 @@
package cconf
import (
"fmt"
"path"
"github.com/toolkits/pkg/file"
"github.com/toolkits/pkg/runner"
"gopkg.in/yaml.v2"
)
var Operations = Operation{}
@@ -19,14 +20,22 @@ type Ops struct {
Ops []string `yaml:"ops" json:"ops"`
}
func LoadOpsYaml(opsYamlFile string) error {
func LoadOpsYaml(configDir string, opsYamlFile string) error {
fp := opsYamlFile
if fp == "" {
fp = path.Join(runner.Cwd, "etc", "ops.yaml")
fp = path.Join(configDir, "ops.yaml")
}
if !file.IsExist(fp) {
return nil
}
hash, _ := file.MD5(fp)
if hash == "2f91a9ed265cf2024e266dc1d538ee77" {
// ops.yaml 是老的默认文件,删除
file.Remove(fp)
return nil
}
return file.ReadYaml(fp, &Operations)
}
@@ -37,3 +46,158 @@ func GetAllOps(ops []Ops) []string {
}
return ret
}
func MergeOperationConf() error {
opsBuiltIn := Operation{}
err := yaml.Unmarshal([]byte(builtInOps), &opsBuiltIn)
if err != nil {
return fmt.Errorf("cannot parse builtInOps: %s", err.Error())
}
configOpsMap := make(map[string]struct{})
for _, op := range Operations.Ops {
configOpsMap[op.Name] = struct{}{}
}
//If the opBu.Name is not a constant in the target (Operations.Ops), add Ops from the built-in options
for _, opBu := range opsBuiltIn.Ops {
if _, has := configOpsMap[opBu.Name]; !has {
Operations.Ops = append(Operations.Ops, opBu)
}
}
return nil
}
const (
builtInOps = `
ops:
- name: dashboards
cname: 仪表盘
ops:
- "/dashboards"
- "/dashboards/add"
- "/dashboards/put"
- "/dashboards/del"
- name: alert
cname: 告警规则
ops:
- "/alert-rules"
- "/alert-rules/add"
- "/alert-rules/put"
- "/alert-rules/del"
- name: alert-mutes
cname: 告警静默管理
ops:
- "/alert-mutes"
- "/alert-mutes/add"
- "/alert-mutes/put"
- "/alert-mutes/del"
- name: alert-subscribes
cname: 告警订阅管理
ops:
- "/alert-subscribes"
- "/alert-subscribes/add"
- "/alert-subscribes/put"
- "/alert-subscribes/del"
- name: alert-events
cname: 告警事件管理
ops:
- "/alert-cur-events"
- "/alert-cur-events/del"
- "/alert-his-events"
- name: recording-rules
cname: 记录规则管理
ops:
- "/recording-rules"
- "/recording-rules/add"
- "/recording-rules/put"
- "/recording-rules/del"
- name: metric
cname: 时序指标
ops:
- "/metric/explorer"
- "/object/explorer"
- name: log
cname: 日志分析
ops:
- "/log/explorer"
- "/log/index-patterns"
- name: targets
cname: 基础设施
ops:
- "/targets"
- "/targets/add"
- "/targets/put"
- "/targets/del"
- "/targets/bind"
- name: job
cname: 任务管理
ops:
- "/job-tpls"
- "/job-tpls/add"
- "/job-tpls/put"
- "/job-tpls/del"
- "/job-tasks"
- "/job-tasks/add"
- "/job-tasks/put"
- "/ibex-settings"
- name: user
cname: 用户管理
ops:
- "/users"
- "/user-groups"
- "/user-groups/add"
- "/user-groups/put"
- "/user-groups/del"
- name: permissions
cname: 权限管理
ops:
- "/permissions"
- name: busi-groups
cname: 业务分组管理
ops:
- "/busi-groups"
- "/busi-groups/add"
- "/busi-groups/put"
- "/busi-groups/del"
- name: builtin-metrics
cname: 指标视图
ops:
- "/metrics-built-in"
- "/builtin-metrics/add"
- "/builtin-metrics/put"
- "/builtin-metrics/del"
- name: built-in-components
cname: 模版中心
ops:
- "/built-in-components"
- "/built-in-components/add"
- "/built-in-components/put"
- "/built-in-components/del"
- name: system
cname: 系统信息
ops:
- "/help/variable-configs"
- "/help/version"
- "/help/servers"
- "/help/source"
- "/help/sso"
- "/help/notification-tpls"
- "/help/notification-settings"
- "/help/migrate"
- "/site-settings"
`
)

View File

@@ -15,8 +15,14 @@ var Plugins = []Plugin{
},
{
Id: 3,
Category: "logging",
Type: "jaeger",
TypeName: "Jaeger",
Category: "loki",
Type: "loki",
TypeName: "Loki",
},
{
Id: 4,
Category: "timeseries",
Type: "tdengine",
TypeName: "TDengine",
},
}

View File

@@ -0,0 +1,105 @@
package rsa
import (
"os"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/httpx"
"github.com/ccfos/nightingale/v6/pkg/secu"
"github.com/pkg/errors"
"github.com/toolkits/pkg/file"
"github.com/toolkits/pkg/logger"
)
func InitRSAConfig(ctx *ctx.Context, rsaConfig *httpx.RSAConfig) error {
// 1.Load RSA keys from Database
rsaPassWord, err := models.ConfigsGet(ctx, models.RSA_PASSWORD)
if err != nil {
return errors.WithMessagef(err, "cannot query config(%s)", models.RSA_PASSWORD)
}
privateKeyVal, err := models.ConfigsGet(ctx, models.RSA_PRIVATE_KEY)
if err != nil {
return errors.WithMessagef(err, "cannot query config(%s)", models.RSA_PRIVATE_KEY)
}
publicKeyVal, err := models.ConfigsGet(ctx, models.RSA_PUBLIC_KEY)
if err != nil {
return errors.WithMessagef(err, "cannot query config(%s)", models.RSA_PUBLIC_KEY)
}
if rsaPassWord != "" && privateKeyVal != "" && publicKeyVal != "" {
rsaConfig.RSAPassWord = rsaPassWord
rsaConfig.RSAPrivateKey = []byte(privateKeyVal)
rsaConfig.RSAPublicKey = []byte(publicKeyVal)
return nil
}
// 2.Read RSA configuration from file if exists
if file.IsExist(rsaConfig.RSAPrivateKeyPath) && file.IsExist(rsaConfig.RSAPublicKeyPath) {
//password already read from config
rsaConfig.RSAPrivateKey, rsaConfig.RSAPublicKey, err = readConfigFile(rsaConfig)
if err != nil {
return errors.WithMessage(err, "failed to read rsa config from file")
}
return nil
}
// 3.Generate RSA keys if not exist
rsaConfig.RSAPassWord, rsaConfig.RSAPrivateKey, rsaConfig.RSAPublicKey, err = initRSAKeyPairs(ctx, rsaConfig.RSAPassWord)
if err != nil {
return errors.WithMessage(err, "failed to generate rsa key pair")
}
return nil
}
func initRSAKeyPairs(ctx *ctx.Context, rsaPassWord string) (password string, privateByte, publicByte []byte, err error) {
// Generate RSA keys
// Generate RSA password
if rsaPassWord != "" {
logger.Debug("Using existing RSA password")
password = rsaPassWord
err = models.ConfigsSet(ctx, models.RSA_PASSWORD, password)
if err != nil {
err = errors.WithMessagef(err, "failed to set config(%s)", models.RSA_PASSWORD)
return
}
} else {
password, err = models.InitRSAPassWord(ctx)
if err != nil {
err = errors.WithMessage(err, "failed to generate rsa password")
return
}
}
privateByte, publicByte, err = secu.GenerateRsaKeyPair(password)
if err != nil {
err = errors.WithMessage(err, "failed to generate rsa key pair")
return
}
// Save generated RSA keys
err = models.ConfigsSet(ctx, models.RSA_PRIVATE_KEY, string(privateByte))
if err != nil {
err = errors.WithMessagef(err, "failed to set config(%s)", models.RSA_PRIVATE_KEY)
return
}
err = models.ConfigsSet(ctx, models.RSA_PUBLIC_KEY, string(publicByte))
if err != nil {
err = errors.WithMessagef(err, "failed to set config(%s)", models.RSA_PUBLIC_KEY)
return
}
return
}
func readConfigFile(rsaConfig *httpx.RSAConfig) (privateBuf, publicBuf []byte, err error) {
publicBuf, err = os.ReadFile(rsaConfig.RSAPublicKeyPath)
if err != nil {
err = errors.WithMessagef(err, "could not read RSAPublicKeyPath %q", rsaConfig.RSAPublicKeyPath)
return
}
privateBuf, err = os.ReadFile(rsaConfig.RSAPrivateKeyPath)
if err != nil {
err = errors.WithMessagef(err, "could not read RSAPrivateKeyPath %q", rsaConfig.RSAPrivateKeyPath)
}
return
}

15
center/cconf/sql_tpl.go Normal file
View File

@@ -0,0 +1,15 @@
package cconf
var TDengineSQLTpl = map[string]string{
"load5": "SELECT _wstart as ts, last(load5) FROM $database.system WHERE host = '$server' and _ts >= $from and _ts <= $to interval($interval) fill(null)",
"process_total": "SELECT _wstart as ts, last(total) FROM $database.processes WHERE host = '$server' and _ts >= $from and _ts <= $to interval($interval) fill(null)",
"thread_total": "SELECT _wstart as ts, last(total) FROM $database.threads WHERE host = '$server' and _ts >= $from and _ts <= $to interval($interval) fill(null)",
"cpu_idle": "SELECT _wstart as ts, last(usage_idle) * -1 + 100 FROM $database.cpu WHERE (host = '$server' and cpu = 'cpu-total') and _ts >= $from and _ts <= $to interval($interval) fill(null)",
"mem_used_percent": "SELECT _wstart as ts, last(used_percent) FROM $database.mem WHERE (host = '$server') and _ts >= $from and _ts <= $to interval($interval) fill(null)",
"disk_used_percent": "SELECT _wstart as ts, last(used_percent) FROM $database.disk WHERE (host = '$server' and path = '/') and _ts >= $from and _ts <= $to interval($interval) fill(null)",
"cpu_context_switches": "select ts, derivative(context_switches, 1s, 0) as context FROM (SELECT _wstart as ts, avg(context_switches) as context_switches FROM $database.kernel WHERE host = '$server' and _ts >= $from and _ts <= $to interval($interval) )",
"tcp": "SELECT _wstart as ts, avg(tcp_close) as CLOSED, avg(tcp_close_wait) as CLOSE_WAIT, avg(tcp_closing) as CLOSING, avg(tcp_established) as ESTABLISHED, avg(tcp_fin_wait1) as FIN_WAIT1, avg(tcp_fin_wait2) as FIN_WAIT2, avg(tcp_last_ack) as LAST_ACK, avg(tcp_syn_recv) as SYN_RECV, avg(tcp_syn_sent) as SYN_SENT, avg(tcp_time_wait) as TIME_WAIT FROM $database.netstat WHERE host = '$server' and _ts >= $from and _ts <= $to interval($interval)",
"net_bytes_recv": "SELECT _wstart as ts, derivative(bytes_recv,1s, 1) as bytes_in FROM $database.net WHERE host = '$server' and interface = '$netif' and _ts >= $from and _ts <= $to group by tbname",
"net_bytes_sent": "SELECT _wstart as ts, derivative(bytes_sent,1s, 1) as bytes_out FROM $database.net WHERE host = '$server' and interface = '$netif' and _ts >= $from and _ts <= $to group by tbname",
"disk_total": "SELECT _wstart as ts, avg(total) AS total, avg(used) as used FROM $database.disk WHERE path = '$mountpoint' and _ts >= $from and _ts <= $to interval($interval) group by host",
}

View File

@@ -7,24 +7,33 @@ import (
"github.com/ccfos/nightingale/v6/alert"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/alert/process"
alertrt "github.com/ccfos/nightingale/v6/alert/router"
"github.com/ccfos/nightingale/v6/center/cconf"
"github.com/ccfos/nightingale/v6/center/cconf/rsa"
"github.com/ccfos/nightingale/v6/center/cstats"
"github.com/ccfos/nightingale/v6/center/integration"
"github.com/ccfos/nightingale/v6/center/metas"
centerrt "github.com/ccfos/nightingale/v6/center/router"
"github.com/ccfos/nightingale/v6/center/sso"
"github.com/ccfos/nightingale/v6/conf"
"github.com/ccfos/nightingale/v6/dumper"
"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/flashduty"
"github.com/ccfos/nightingale/v6/pkg/httpx"
"github.com/ccfos/nightingale/v6/pkg/i18nx"
"github.com/ccfos/nightingale/v6/pkg/logx"
"github.com/ccfos/nightingale/v6/pkg/version"
"github.com/ccfos/nightingale/v6/prom"
"github.com/ccfos/nightingale/v6/pushgw/idents"
pushgwrt "github.com/ccfos/nightingale/v6/pushgw/router"
"github.com/ccfos/nightingale/v6/pushgw/writer"
"github.com/ccfos/nightingale/v6/storage"
"github.com/ccfos/nightingale/v6/tdengine"
alertrt "github.com/ccfos/nightingale/v6/alert/router"
centerrt "github.com/ccfos/nightingale/v6/center/router"
pushgwrt "github.com/ccfos/nightingale/v6/pushgw/router"
"github.com/flashcatcloud/ibex/src/cmd/ibex"
)
func Initialize(configDir string, cryptoKey string) (func(), error) {
@@ -33,59 +42,86 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
return nil, fmt.Errorf("failed to init config: %v", err)
}
cconf.LoadMetricsYaml(config.Center.MetricsYamlFile)
cconf.LoadOpsYaml(config.Center.OpsYamlFile)
cconf.LoadMetricsYaml(configDir, config.Center.MetricsYamlFile)
cconf.LoadOpsYaml(configDir, config.Center.OpsYamlFile)
cconf.MergeOperationConf()
logxClean, err := logx.Init(config.Log)
if err != nil {
return nil, err
}
i18nx.Init()
i18nx.Init(configDir)
cstats.Init()
flashduty.Init(config.Center.FlashDuty)
db, err := storage.New(config.DB)
if err != nil {
return nil, err
}
ctx := ctx.NewContext(context.Background(), db)
ctx := ctx.NewContext(context.Background(), db, true)
migrate.Migrate(db)
models.InitRoot(ctx)
redis, err := storage.NewRedis(config.Redis)
config.HTTP.JWTAuth.SigningKey = models.InitJWTSigningKey(ctx)
err = rsa.InitRSAConfig(ctx, &config.HTTP.RSA)
if err != nil {
return nil, err
}
integration.Init(ctx, config.Center.BuiltinIntegrationsDir)
var redis storage.Redis
redis, err = storage.NewRedis(config.Redis)
if err != nil {
return nil, err
}
metas := metas.New(redis)
idents := idents.New(db)
idents := idents.New(ctx, redis)
syncStats := memsto.NewSyncStats()
alertStats := astats.NewSyncStats()
sso := sso.Init(config.Center, ctx)
configCache := memsto.NewConfigCache(ctx, syncStats, config.HTTP.RSA.RSAPrivateKey, config.HTTP.RSA.RSAPassWord)
busiGroupCache := memsto.NewBusiGroupCache(ctx, syncStats)
targetCache := memsto.NewTargetCache(ctx, syncStats, redis)
dsCache := memsto.NewDatasourceCache(ctx, syncStats)
alertMuteCache := memsto.NewAlertMuteCache(ctx, syncStats)
alertRuleCache := memsto.NewAlertRuleCache(ctx, syncStats)
notifyConfigCache := memsto.NewNotifyConfigCache(ctx)
notifyConfigCache := memsto.NewNotifyConfigCache(ctx, configCache)
userCache := memsto.NewUserCache(ctx, syncStats)
userGroupCache := memsto.NewUserGroupCache(ctx, syncStats)
taskTplCache := memsto.NewTaskTplCache(ctx)
promClients := prom.NewPromClient(ctx, config.Alert.Heartbeat)
sso := sso.Init(config.Center, ctx, configCache)
promClients := prom.NewPromClient(ctx)
tdengineClients := tdengine.NewTdengineClient(ctx, config.Alert.Heartbeat)
externalProcessors := process.NewExternalProcessors()
alert.Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, dsCache, ctx, promClients, true)
alert.Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, taskTplCache, dsCache, ctx, promClients, tdengineClients, userCache, userGroupCache)
writers := writer.NewWriters(config.Pushgw)
go version.GetGithubVersion()
alertrtRouter := alertrt.New(config.HTTP, config.Alert, alertMuteCache, targetCache, busiGroupCache, alertStats, ctx, externalProcessors)
centerRouter := centerrt.New(config.HTTP, config.Center, cconf.Operations, dsCache, notifyConfigCache, promClients, redis, sso, ctx, metas, targetCache)
pushgwRouter := pushgwrt.New(config.HTTP, config.Pushgw, targetCache, busiGroupCache, idents, writers, ctx)
centerRouter := centerrt.New(config.HTTP, config.Center, config.Alert, cconf.Operations, dsCache, notifyConfigCache, promClients, tdengineClients,
redis, sso, ctx, metas, idents, targetCache, userCache, userGroupCache)
pushgwRouter := pushgwrt.New(config.HTTP, config.Pushgw, config.Alert, targetCache, busiGroupCache, idents, metas, writers, ctx)
r := httpx.GinEngine(config.Global.RunMode, config.HTTP)
centerRouter.Config(r)
alertrtRouter.Config(r)
pushgwRouter.Config(r)
dumper.ConfigRouter(r)
if config.Ibex.Enable {
migrate.MigrateIbexTables(db)
ibex.ServerStart(true, db, redis, config.HTTP.APIForService.BasicAuth, config.Alert.Heartbeat, &config.CenterApi, r, centerRouter, config.Ibex, config.HTTP.Port)
}
httpClean := httpx.Init(config.HTTP, r)

231
center/integration/init.go Normal file
View File

@@ -0,0 +1,231 @@
package integration
import (
"encoding/json"
"path"
"strings"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/toolkits/pkg/file"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/runner"
)
func Init(ctx *ctx.Context, builtinIntegrationsDir string) {
fp := builtinIntegrationsDir
if fp == "" {
fp = path.Join(runner.Cwd, "integrations")
}
// var fileList []string
dirList, err := file.DirsUnder(fp)
ginx.Dangerous(err)
for _, dir := range dirList {
// components icon
componentDir := fp + "/" + dir
component := models.BuiltinComponent{
Ident: dir,
}
// get logo name
// /api/n9e/integrations/icon/AliYun/aliyun.png
files, err := file.FilesUnder(componentDir + "/icon")
if err == nil && len(files) > 0 {
component.Logo = "/api/n9e/integrations/icon/" + component.Ident + "/" + files[0]
} else if err != nil {
logger.Warningf("read builtin component icon dir fail %s %v", component.Ident, err)
}
// get description
files, err = file.FilesUnder(componentDir + "/markdown")
if err == nil && len(files) > 0 {
var readmeFile string
for _, file := range files {
if strings.HasSuffix(strings.ToLower(file), "md") {
readmeFile = componentDir + "/markdown/" + file
break
}
}
if readmeFile != "" {
component.Readme, _ = file.ReadString(readmeFile)
}
} else if err != nil {
logger.Warningf("read builtin component markdown dir fail %s %v", component.Ident, err)
}
exists, _ := models.BuiltinComponentExists(ctx, &component)
if !exists {
err = component.Add(ctx, "system")
if err != nil {
logger.Warning("add builtin component fail ", component, err)
continue
}
}
// alerts
files, err = file.FilesUnder(componentDir + "/alerts")
if err == nil && len(files) > 0 {
for _, f := range files {
fp := componentDir + "/alerts/" + f
bs, err := file.ReadBytes(fp)
if err != nil {
logger.Warning("read builtin component alerts file fail ", f, err)
continue
}
alerts := []models.AlertRule{}
err = json.Unmarshal(bs, &alerts)
if err != nil {
logger.Warning("parse builtin component alerts file fail ", f, err)
continue
}
for _, alert := range alerts {
content, err := json.Marshal(alert)
if err != nil {
logger.Warning("marshal builtin alert fail ", alert, err)
continue
}
cate := strings.Replace(f, ".json", "", -1)
builtinAlert := models.BuiltinPayload{
Component: component.Ident,
Type: "alert",
Cate: cate,
Name: alert.Name,
Tags: alert.AppendTags,
Content: string(content),
}
exists, err := models.BuiltinPayloadExists(ctx, &builtinAlert)
if err != nil {
logger.Warning("check builtin alert exists fail ", builtinAlert, err)
continue
}
if exists {
continue
}
err = builtinAlert.Add(ctx, "system")
if err != nil {
logger.Warningf("add builtin alert:%+v fail %v", builtinAlert, err)
continue
}
}
}
}
// dashboards
files, err = file.FilesUnder(componentDir + "/dashboards")
if err == nil && len(files) > 0 {
for _, f := range files {
fp := componentDir + "/dashboards/" + f
bs, err := file.ReadBytes(fp)
if err != nil {
logger.Warning("read builtin component dashboards file fail ", f, err)
continue
}
dashboard := BuiltinBoard{}
err = json.Unmarshal(bs, &dashboard)
if err != nil {
logger.Warning("parse builtin component dashboards file fail ", f, err)
continue
}
content, err := json.Marshal(dashboard)
if err != nil {
logger.Warning("marshal builtin dashboard fail ", dashboard, err)
continue
}
builtinDashboard := models.BuiltinPayload{
Component: component.Ident,
Type: "dashboard",
Cate: "",
Name: dashboard.Name,
Tags: dashboard.Tags,
Content: string(content),
}
exists, err := models.BuiltinPayloadExists(ctx, &builtinDashboard)
if err != nil {
logger.Warning("check builtin dashboard exists fail ", builtinDashboard, err)
continue
}
if exists {
continue
}
err = builtinDashboard.Add(ctx, "system")
if err != nil {
logger.Warning("add builtin dashboard fail ", builtinDashboard, err)
continue
}
}
} else if err != nil {
logger.Warningf("read builtin component dash dir fail %s %v", component.Ident, err)
}
// metrics
files, err = file.FilesUnder(componentDir + "/metrics")
if err == nil && len(files) > 0 {
for _, f := range files {
fp := componentDir + "/metrics/" + f
bs, err := file.ReadBytes(fp)
if err != nil {
logger.Warning("read builtin component metrics file fail", f, err)
continue
}
metrics := []models.BuiltinMetric{}
err = json.Unmarshal(bs, &metrics)
if err != nil {
logger.Warning("parse builtin component metrics file fail", f, err)
continue
}
for _, metric := range metrics {
exists, err := models.BuiltinMetricExists(ctx, &metric)
if err != nil {
logger.Warning("check builtin metric exists fail", metric, err)
continue
}
if exists {
continue
}
err = metric.Add(ctx, "system")
if err != nil {
logger.Warning("add builtin metric fail", metric, err)
continue
}
}
}
} else if err != nil {
logger.Warningf("read builtin component metrics dir fail %s %v", component.Ident, err)
}
}
}
type BuiltinBoard struct {
Id int64 `json:"id" gorm:"primaryKey"`
GroupId int64 `json:"group_id"`
Name string `json:"name"`
Ident string `json:"ident"`
Tags string `json:"tags"`
CreateAt int64 `json:"create_at"`
CreateBy string `json:"create_by"`
UpdateAt int64 `json:"update_at"`
UpdateBy string `json:"update_by"`
Configs interface{} `json:"configs" gorm:"-"`
Public int `json:"public"` // 0: false, 1: true
PublicCate int `json:"public_cate"` // 0: anonymous, 1: login, 2: busi
Bgids []int64 `json:"bgids" gorm:"-"`
BuiltIn int `json:"built_in"` // 0: false, 1: true
Hide int `json:"hide"` // 0: false, 1: true
}

View File

@@ -2,6 +2,7 @@ package metas
import (
"context"
"encoding/json"
"sync"
"time"
@@ -90,15 +91,41 @@ func (s *Set) updateMeta(items map[string]models.HostMeta) {
}
func (s *Set) updateTargets(m map[string]models.HostMeta) error {
if s.redis == nil {
logger.Warningf("redis is nil")
return nil
}
count := int64(len(m))
if count == 0 {
return nil
}
newMap := make(map[string]interface{}, count)
extendMap := make(map[string]interface{})
for ident, meta := range m {
if meta.ExtendInfo != nil {
extendMeta := meta.ExtendInfo
meta.ExtendInfo = make(map[string]interface{})
extendMetaStr, err := json.Marshal(extendMeta)
if err != nil {
return err
}
extendMap[models.WrapExtendIdent(ident)] = extendMetaStr
}
newMap[models.WrapIdent(ident)] = meta
}
err := storage.MSet(context.Background(), s.redis, newMap)
if err != nil {
return err
}
if len(extendMap) > 0 {
err = storage.MSet(context.Background(), s.redis, extendMap)
if err != nil {
return err
}
}
return err
}

View File

@@ -8,48 +8,65 @@ import (
"strings"
"time"
"github.com/ccfos/nightingale/v6/alert/aconf"
"github.com/ccfos/nightingale/v6/center/cconf"
"github.com/ccfos/nightingale/v6/center/cstats"
"github.com/ccfos/nightingale/v6/center/metas"
"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/pkg/version"
"github.com/ccfos/nightingale/v6/prom"
"github.com/ccfos/nightingale/v6/pushgw/idents"
"github.com/ccfos/nightingale/v6/storage"
"github.com/toolkits/pkg/runner"
"github.com/ccfos/nightingale/v6/tdengine"
"github.com/gin-gonic/gin"
"github.com/rakyll/statik/fs"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/runner"
)
type Router struct {
HTTP httpx.Config
Center cconf.Center
Alert aconf.Alert
Operations cconf.Operation
DatasourceCache *memsto.DatasourceCacheType
NotifyConfigCache *memsto.NotifyConfigCacheType
PromClients *prom.PromClientMap
TdendgineClients *tdengine.TdengineClientMap
Redis storage.Redis
MetaSet *metas.Set
IdentSet *idents.Set
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, tc *memsto.TargetCacheType) *Router {
func New(httpConfig httpx.Config, center cconf.Center, alert aconf.Alert, operations cconf.Operation, ds *memsto.DatasourceCacheType, ncc *memsto.NotifyConfigCacheType, pc *prom.PromClientMap, tdendgineClients *tdengine.TdengineClientMap, redis storage.Redis, sso *sso.SsoClient, ctx *ctx.Context, metaSet *metas.Set, idents *idents.Set, tc *memsto.TargetCacheType, uc *memsto.UserCacheType, ugc *memsto.UserGroupCacheType) *Router {
return &Router{
HTTP: httpConfig,
Center: center,
Alert: alert,
Operations: operations,
DatasourceCache: ds,
NotifyConfigCache: ncc,
PromClients: pc,
TdendgineClients: tdendgineClients,
Redis: redis,
MetaSet: metaSet,
IdentSet: idents,
TargetCache: tc,
Sso: sso,
UserCache: uc,
UserGroupCache: ugc,
Ctx: ctx,
}
}
@@ -75,52 +92,71 @@ func languageDetector(i18NHeaderKey string) gin.HandlerFunc {
lang := c.GetHeader(headerKey)
if lang != "" {
if strings.HasPrefix(lang, "zh") {
c.Request.Header.Set("X-Language", "zh")
c.Request.Header.Set("X-Language", "zh_CN")
} else if strings.HasPrefix(lang, "en") {
c.Request.Header.Set("X-Language", "en")
} else {
c.Request.Header.Set("X-Language", lang)
}
} else {
c.Request.Header.Set("X-Language", "en")
c.Request.Header.Set("X-Language", "zh_CN")
}
}
c.Next()
}
}
func (rt *Router) configNoRoute(r *gin.Engine) {
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":
cwdarr := []string{"/"}
if runtime.GOOS == "windows" {
cwdarr[0] = ""
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...))
}
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:
cwdarr := []string{"/"}
if runtime.GOOS == "windows" {
cwdarr[0] = ""
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...))
}
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)
{
@@ -130,24 +166,45 @@ func (rt *Router) Config(r *gin.Engine) {
pages.POST("/query-range-batch", rt.promBatchQueryRange)
pages.POST("/query-instant-batch", rt.promBatchQueryInstant)
pages.GET("/datasource/brief", rt.datasourceBriefs)
pages.POST("/ds-query", rt.QueryData)
pages.POST("/logs-query", rt.QueryLog)
pages.POST("/tdengine-databases", rt.tdengineDatabases)
pages.POST("/tdengine-tables", rt.tdengineTables)
pages.POST("/tdengine-columns", rt.tdengineColumns)
} else {
pages.Any("/proxy/:id/*url", rt.auth(), rt.dsProxy)
pages.POST("/query-range-batch", rt.auth(), rt.promBatchQueryRange)
pages.POST("/query-instant-batch", rt.auth(), rt.promBatchQueryInstant)
pages.GET("/datasource/brief", rt.auth(), rt.datasourceBriefs)
pages.GET("/datasource/brief", rt.auth(), rt.user(), rt.datasourceBriefs)
pages.POST("/ds-query", rt.auth(), rt.QueryData)
pages.POST("/logs-query", rt.auth(), rt.QueryLog)
pages.POST("/tdengine-databases", rt.auth(), rt.tdengineDatabases)
pages.POST("/tdengine-tables", rt.auth(), rt.tdengineTables)
pages.POST("/tdengine-columns", rt.auth(), rt.tdengineColumns)
}
pages.GET("/sql-template", rt.QuerySqlTemplate)
pages.POST("/auth/login", rt.jwtMock(), rt.loginPost)
pages.POST("/auth/logout", rt.jwtMock(), rt.logoutPost)
pages.POST("/auth/logout", rt.jwtMock(), rt.auth(), rt.user(), rt.logoutPost)
pages.POST("/auth/refresh", rt.jwtMock(), rt.refreshPost)
pages.POST("/auth/captcha", rt.jwtMock(), rt.generateCaptcha)
pages.POST("/auth/captcha-verify", rt.jwtMock(), rt.captchaVerify)
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)
@@ -172,6 +229,20 @@ func (rt *Router) Config(r *gin.Engine) {
pages.POST("/metric-views", rt.auth(), rt.user(), rt.metricViewAdd)
pages.PUT("/metric-views", rt.auth(), rt.user(), rt.metricViewPut)
pages.GET("/builtin-metric-filters", rt.auth(), rt.user(), rt.metricFilterGets)
pages.DELETE("/builtin-metric-filters", rt.auth(), rt.user(), rt.metricFilterDel)
pages.POST("/builtin-metric-filters", rt.auth(), rt.user(), rt.metricFilterAdd)
pages.PUT("/builtin-metric-filters", rt.auth(), rt.user(), rt.metricFilterPut)
pages.POST("/builtin-metric-promql", rt.auth(), rt.user(), rt.getMetricPromql)
pages.POST("/builtin-metrics", rt.auth(), rt.user(), rt.perm("/builtin-metrics/add"), rt.builtinMetricsAdd)
pages.PUT("/builtin-metrics", rt.auth(), rt.user(), rt.perm("/builtin-metrics/put"), rt.builtinMetricsPut)
pages.DELETE("/builtin-metrics", rt.auth(), rt.user(), rt.perm("/builtin-metrics/del"), rt.builtinMetricsDel)
pages.GET("/builtin-metrics", rt.auth(), rt.user(), rt.builtinMetricsGets)
pages.GET("/builtin-metrics/types", rt.auth(), rt.user(), rt.builtinMetricsTypes)
pages.GET("/builtin-metrics/types/default", rt.auth(), rt.user(), rt.builtinMetricsDefaultTypes)
pages.GET("/builtin-metrics/collectors", rt.auth(), rt.user(), rt.builtinMetricsCollectors)
pages.GET("/user-groups", rt.auth(), rt.user(), rt.userGroupGets)
pages.POST("/user-groups", rt.auth(), rt.user(), rt.perm("/user-groups/add"), rt.userGroupAdd)
pages.GET("/user-group/:id", rt.auth(), rt.user(), rt.userGroupGet)
@@ -191,6 +262,7 @@ func (rt *Router) Config(r *gin.Engine) {
pages.GET("/busi-group/:id/perm/:perm", rt.auth(), rt.user(), rt.checkBusiGroupPerm)
pages.GET("/targets", rt.auth(), rt.user(), rt.targetGets)
pages.GET("/target/extra-meta", rt.auth(), rt.user(), rt.targetExtendInfoByIdent)
pages.POST("/target/list", rt.auth(), rt.user(), rt.targetGetsByHostFilter)
pages.DELETE("/targets", rt.auth(), rt.user(), rt.perm("/targets/del"), rt.targetDel)
pages.GET("/targets/tags", rt.auth(), rt.user(), rt.targetGetTags)
@@ -202,16 +274,21 @@ func (rt *Router) Config(r *gin.Engine) {
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("/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/makedown/:cate", rt.builtinMarkdown)
pages.GET("/busi-groups/public-boards", rt.auth(), rt.user(), rt.perm("/dashboards"), rt.publicBoardGets)
pages.GET("/busi-groups/boards", rt.auth(), rt.user(), rt.perm("/dashboards"), rt.boardGetsByGids)
pages.GET("/busi-group/:id/boards", rt.auth(), rt.user(), rt.perm("/dashboards"), rt.bgro(), rt.boardGets)
pages.POST("/busi-group/:id/boards", rt.auth(), rt.user(), rt.perm("/dashboards/add"), rt.bgrw(), rt.boardAdd)
pages.POST("/busi-group/:id/board/:bid/clone", rt.auth(), rt.user(), rt.perm("/dashboards/add"), rt.bgrw(), rt.boardClone)
pages.POST("/busi-groups/boards/clones", rt.auth(), rt.user(), rt.perm("/dashboards/add"), rt.boardBatchClone)
pages.GET("/board/:bid", rt.boardGet)
pages.GET("/board/:bid/pure", rt.boardPureGet)
@@ -223,9 +300,11 @@ func (rt *Router) Config(r *gin.Engine) {
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("/alert-rules/builtin/alerts-cates", rt.auth(), rt.user(), rt.builtinAlertCateGets)
// pages.GET("/alert-rules/builtin/list", rt.auth(), rt.user(), rt.builtinAlertRules)
pages.GET("/alert-rules/callbacks", rt.auth(), rt.user(), rt.alertRuleCallbacks)
pages.GET("/busi-groups/alert-rules", rt.auth(), rt.user(), rt.perm("/alert-rules"), rt.alertRuleGetsByGids)
pages.GET("/busi-group/:id/alert-rules", rt.auth(), rt.user(), rt.perm("/alert-rules"), rt.alertRuleGets)
pages.POST("/busi-group/:id/alert-rules", rt.auth(), rt.user(), rt.perm("/alert-rules/add"), rt.bgrw(), rt.alertRuleAddByFE)
pages.POST("/busi-group/:id/alert-rules/import", rt.auth(), rt.user(), rt.perm("/alert-rules/add"), rt.bgrw(), rt.alertRuleAddByImport)
@@ -233,7 +312,9 @@ func (rt *Router) Config(r *gin.Engine) {
pages.PUT("/busi-group/:id/alert-rules/fields", rt.auth(), rt.user(), rt.perm("/alert-rules/put"), rt.bgrw(), rt.alertRulePutFields)
pages.PUT("/busi-group/:id/alert-rule/:arid", rt.auth(), rt.user(), rt.perm("/alert-rules/put"), rt.alertRulePutByFE)
pages.GET("/alert-rule/:arid", rt.auth(), rt.user(), rt.perm("/alert-rules"), rt.alertRuleGet)
pages.PUT("/busi-group/alert-rule/validate", rt.auth(), rt.user(), rt.perm("/alert-rules/put"), rt.alertRuleValidation)
pages.GET("/busi-groups/recording-rules", rt.auth(), rt.user(), rt.perm("/recording-rules"), rt.recordingRuleGetsByGids)
pages.GET("/busi-group/:id/recording-rules", rt.auth(), rt.user(), rt.perm("/recording-rules"), rt.recordingRuleGets)
pages.POST("/busi-group/:id/recording-rules", rt.auth(), rt.user(), rt.perm("/recording-rules/add"), rt.bgrw(), rt.recordingRuleAddByFE)
pages.DELETE("/busi-group/:id/recording-rules", rt.auth(), rt.user(), rt.perm("/recording-rules/del"), rt.bgrw(), rt.recordingRuleDel)
@@ -241,12 +322,16 @@ func (rt *Router) Config(r *gin.Engine) {
pages.GET("/recording-rule/:rrid", rt.auth(), rt.user(), rt.perm("/recording-rules"), rt.recordingRuleGet)
pages.PUT("/busi-group/:id/recording-rules/fields", rt.auth(), rt.user(), rt.perm("/recording-rules/put"), rt.recordingRulePutFields)
pages.GET("/busi-groups/alert-mutes", rt.auth(), rt.user(), rt.perm("/alert-mutes"), rt.alertMuteGetsByGids)
pages.GET("/busi-group/:id/alert-mutes", rt.auth(), rt.user(), rt.perm("/alert-mutes"), rt.bgro(), rt.alertMuteGetsByBG)
pages.POST("/busi-group/:id/alert-mutes/preview", rt.auth(), rt.user(), rt.perm("/alert-mutes/add"), rt.bgrw(), rt.alertMutePreview)
pages.POST("/busi-group/:id/alert-mutes", rt.auth(), rt.user(), rt.perm("/alert-mutes/add"), rt.bgrw(), rt.alertMuteAdd)
pages.DELETE("/busi-group/:id/alert-mutes", rt.auth(), rt.user(), rt.perm("/alert-mutes/del"), rt.bgrw(), rt.alertMuteDel)
pages.PUT("/busi-group/:id/alert-mute/:amid", rt.auth(), rt.user(), rt.perm("/alert-mutes/put"), rt.alertMutePutByFE)
pages.GET("/busi-group/:id/alert-mute/:amid", rt.auth(), rt.user(), rt.perm("/alert-mutes"), rt.alertMuteGet)
pages.PUT("/busi-group/:id/alert-mutes/fields", rt.auth(), rt.user(), rt.perm("/alert-mutes/put"), rt.bgrw(), rt.alertMutePutFields)
pages.GET("/busi-groups/alert-subscribes", rt.auth(), rt.user(), rt.perm("/alert-subscribes"), rt.alertSubscribeGetsByGids)
pages.GET("/busi-group/:id/alert-subscribes", rt.auth(), rt.user(), rt.perm("/alert-subscribes"), rt.bgro(), rt.alertSubscribeGets)
pages.GET("/alert-subscribe/:sid", rt.auth(), rt.user(), rt.perm("/alert-subscribes"), rt.alertSubscribeGet)
pages.POST("/busi-group/:id/alert-subscribes", rt.auth(), rt.user(), rt.perm("/alert-subscribes/add"), rt.bgrw(), rt.alertSubscribeAdd)
@@ -267,12 +352,14 @@ func (rt *Router) Config(r *gin.Engine) {
pages.POST("/alert-cur-events/card/details", rt.auth(), rt.alertCurEventsCardDetails)
pages.GET("/alert-his-events/list", rt.auth(), rt.alertHisEventsList)
pages.DELETE("/alert-cur-events", rt.auth(), rt.user(), rt.perm("/alert-cur-events/del"), rt.alertCurEventDel)
pages.GET("/alert-cur-events/stats", rt.auth(), rt.alertCurEventsStatistics)
pages.GET("/alert-aggr-views", rt.auth(), rt.alertAggrViewGets)
pages.DELETE("/alert-aggr-views", rt.auth(), rt.user(), rt.alertAggrViewDel)
pages.POST("/alert-aggr-views", rt.auth(), rt.user(), rt.alertAggrViewAdd)
pages.PUT("/alert-aggr-views", rt.auth(), rt.user(), rt.alertAggrViewPut)
pages.GET("/busi-groups/task-tpls", rt.auth(), rt.user(), rt.perm("/job-tpls"), rt.taskTplGetsByGids)
pages.GET("/busi-group/:id/task-tpls", rt.auth(), rt.user(), rt.perm("/job-tpls"), rt.bgro(), rt.taskTplGets)
pages.POST("/busi-group/:id/task-tpls", rt.auth(), rt.user(), rt.perm("/job-tpls/add"), rt.bgrw(), rt.taskTplAdd)
pages.DELETE("/busi-group/:id/task-tpl/:tid", rt.auth(), rt.user(), rt.perm("/job-tpls/del"), rt.bgrw(), rt.taskTplDel)
@@ -281,15 +368,14 @@ func (rt *Router) Config(r *gin.Engine) {
pages.GET("/busi-group/:id/task-tpl/:tid", rt.auth(), rt.user(), rt.perm("/job-tpls"), rt.bgro(), rt.taskTplGet)
pages.PUT("/busi-group/:id/task-tpl/:tid", rt.auth(), rt.user(), rt.perm("/job-tpls/put"), rt.bgrw(), rt.taskTplPut)
pages.GET("/busi-groups/tasks", rt.auth(), rt.user(), rt.perm("/job-tasks"), rt.taskGetsByGids)
pages.GET("/busi-group/:id/tasks", rt.auth(), rt.user(), rt.perm("/job-tasks"), rt.bgro(), rt.taskGets)
pages.POST("/busi-group/:id/tasks", rt.auth(), rt.user(), rt.perm("/job-tasks/add"), rt.bgrw(), rt.taskAdd)
pages.GET("/busi-group/:id/task/*url", rt.auth(), rt.user(), rt.perm("/job-tasks"), rt.taskProxy)
pages.PUT("/busi-group/:id/task/*url", rt.auth(), rt.user(), rt.perm("/job-tasks/put"), rt.bgrw(), rt.taskProxy)
pages.GET("/servers", rt.auth(), rt.admin(), rt.serversGet)
pages.GET("/server-clusters", rt.auth(), rt.admin(), rt.serverClustersGet)
pages.GET("/servers", rt.auth(), rt.user(), rt.perm("/help/servers"), rt.serversGet)
pages.GET("/server-clusters", rt.auth(), rt.user(), rt.perm("/help/servers"), rt.serverClustersGet)
pages.POST("/datasource/list", rt.auth(), rt.datasourceList)
pages.POST("/datasource/list", rt.auth(), rt.user(), rt.datasourceList)
pages.POST("/datasource/plugin/list", rt.auth(), rt.pluginList)
pages.POST("/datasource/upsert", rt.auth(), rt.admin(), rt.datasourceUpsert)
pages.POST("/datasource/desc", rt.auth(), rt.admin(), rt.datasourceGet)
@@ -303,84 +389,171 @@ func (rt *Router) Config(r *gin.Engine) {
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("/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/preview", rt.auth(), rt.admin(), rt.notifyTplPreview)
pages.GET("/notify-tpls", rt.auth(), rt.user(), rt.notifyTplGets)
pages.PUT("/notify-tpl/content", rt.auth(), rt.user(), rt.notifyTplUpdateContent)
pages.PUT("/notify-tpl", rt.auth(), rt.user(), rt.notifyTplUpdate)
pages.POST("/notify-tpl", rt.auth(), rt.user(), rt.notifyTplAdd)
pages.DELETE("/notify-tpl/:id", rt.auth(), rt.user(), rt.notifyTplDel)
pages.POST("/notify-tpl/preview", rt.auth(), rt.user(), rt.notifyTplPreview)
pages.GET("/sso-configs", rt.auth(), rt.admin(), rt.ssoConfigGets)
pages.PUT("/sso-config", rt.auth(), rt.admin(), rt.ssoConfigUpdate)
pages.GET("/webhooks", rt.auth(), rt.admin(), rt.webhookGets)
pages.GET("/webhooks", rt.auth(), rt.user(), rt.webhookGets)
pages.PUT("/webhooks", rt.auth(), rt.admin(), rt.webhookPuts)
pages.GET("/notify-script", rt.auth(), rt.admin(), rt.notifyScriptGet)
pages.GET("/notify-script", rt.auth(), rt.user(), rt.perm("/help/notification-settings"), rt.notifyScriptGet)
pages.PUT("/notify-script", rt.auth(), rt.admin(), rt.notifyScriptPut)
pages.GET("/notify-channel", rt.auth(), rt.admin(), rt.notifyChannelGets)
pages.GET("/notify-channel", rt.auth(), rt.user(), rt.perm("/help/notification-settings"), rt.notifyChannelGets)
pages.PUT("/notify-channel", rt.auth(), rt.admin(), rt.notifyChannelPuts)
pages.GET("/notify-contact", rt.auth(), rt.admin(), rt.notifyContactGets)
pages.GET("/notify-contact", rt.auth(), rt.user(), rt.perm("/help/notification-settings"), rt.notifyContactGets)
pages.PUT("/notify-contact", rt.auth(), rt.admin(), rt.notifyContactPuts)
pages.GET("/notify-config", rt.auth(), rt.admin(), rt.notifyConfigGet)
pages.GET("/notify-config", rt.auth(), rt.user(), rt.perm("/help/notification-settings"), rt.notifyConfigGet)
pages.PUT("/notify-config", rt.auth(), rt.admin(), rt.notifyConfigPut)
pages.PUT("/smtp-config-test", rt.auth(), rt.admin(), rt.attemptSendEmail)
pages.GET("/es-index-pattern", rt.auth(), rt.esIndexPatternGet)
pages.GET("/es-index-pattern-list", rt.auth(), rt.esIndexPatternGetList)
pages.POST("/es-index-pattern", rt.auth(), rt.admin(), rt.esIndexPatternAdd)
pages.PUT("/es-index-pattern", rt.auth(), rt.admin(), rt.esIndexPatternPut)
pages.DELETE("/es-index-pattern", rt.auth(), rt.admin(), rt.esIndexPatternDel)
pages.GET("/user-variable-configs", rt.auth(), rt.user(), rt.perm("/help/variable-configs"), rt.userVariableConfigGets)
pages.POST("/user-variable-config", rt.auth(), rt.user(), rt.perm("/help/variable-configs"), rt.userVariableConfigAdd)
pages.PUT("/user-variable-config/:id", rt.auth(), rt.user(), rt.perm("/help/variable-configs"), rt.userVariableConfigPut)
pages.DELETE("/user-variable-config/:id", rt.auth(), rt.user(), rt.perm("/help/variable-configs"), rt.userVariableConfigDel)
pages.GET("/config", rt.auth(), rt.admin(), rt.configGetByKey)
pages.PUT("/config", rt.auth(), rt.admin(), rt.configPutByKey)
pages.GET("/site-info", rt.siteInfo)
// for admin api
pages.GET("/user/busi-groups", rt.auth(), rt.admin(), rt.userBusiGroupsGets)
pages.GET("/builtin-components", rt.auth(), rt.user(), rt.builtinComponentsGets)
pages.POST("/builtin-components", rt.auth(), rt.user(), rt.perm("/built-in-components/add"), rt.builtinComponentsAdd)
pages.PUT("/builtin-components", rt.auth(), rt.user(), rt.perm("/built-in-components/put"), rt.builtinComponentsPut)
pages.DELETE("/builtin-components", rt.auth(), rt.user(), rt.perm("/built-in-components/del"), rt.builtinComponentsDel)
pages.GET("/builtin-payloads", rt.auth(), rt.user(), rt.builtinPayloadsGets)
pages.GET("/builtin-payloads/cates", rt.auth(), rt.user(), rt.builtinPayloadcatesGet)
pages.POST("/builtin-payloads", rt.auth(), rt.user(), rt.perm("/built-in-components/add"), rt.builtinPayloadsAdd)
pages.GET("/builtin-payload/:id", rt.auth(), rt.user(), rt.perm("/built-in-components"), rt.builtinPayloadGet)
pages.PUT("/builtin-payloads", rt.auth(), rt.user(), rt.perm("/built-in-components/put"), rt.builtinPayloadsPut)
pages.DELETE("/builtin-payloads", rt.auth(), rt.user(), rt.perm("/built-in-components/del"), rt.builtinPayloadsDel)
}
if rt.HTTP.Service.Enable {
r.GET("/api/n9e/versions", func(c *gin.Context) {
v := version.Version
lastIndex := strings.LastIndex(version.Version, "-")
if lastIndex != -1 {
v = version.Version[:lastIndex]
}
gv := version.GithubVersion.Load()
if gv != nil {
ginx.NewRender(c).Data(gin.H{"version": v, "github_verison": gv.(string)}, nil)
} else {
ginx.NewRender(c).Data(gin.H{"version": v, "github_verison": ""}, nil)
}
})
if rt.HTTP.APIForService.Enable {
service := r.Group("/v1/n9e")
if len(rt.HTTP.Service.BasicAuth) > 0 {
service.Use(gin.BasicAuth(rt.HTTP.Service.BasicAuth))
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.PUT("/user/:id", rt.userProfilePutByService)
service.DELETE("/user/:id", rt.userDel)
service.GET("/users", rt.userFindAll)
service.GET("/targets", rt.targetGets)
service.GET("/user-groups", rt.userGroupGetsByService)
service.GET("/user-group-members", rt.userGroupMemberGetsByService)
service.GET("/targets", rt.targetGetsByService)
service.GET("/target/extra-meta", rt.targetExtendInfoByIdent)
service.POST("/target/list", rt.targetGetsByHostFilter)
service.DELETE("/targets", rt.targetDelByService)
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.PUT("/targets/bgid", rt.targetUpdateBgidByService)
service.POST("/alert-rules", rt.alertRuleAddByService)
service.POST("/alert-rule-add", rt.alertRuleAddOneByService)
service.DELETE("/alert-rules", rt.alertRuleDelByService)
service.PUT("/alert-rule/:arid", rt.alertRulePutByService)
service.GET("/alert-rule/:arid", rt.alertRuleGet)
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("/task-tpl/:tid", rt.taskTplGetByService)
service.GET("/task-tpls", rt.taskTplGetsByService)
service.GET("/task-tpl/statistics", rt.taskTplStatistics)
service.GET("/config/:id", rt.configGet)
service.GET("/configs", rt.configsGet)
service.GET("/config", rt.configGetByKey)
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)
service.GET("/user-variable/decrypt", rt.userVariableGetDecryptByService)
service.GET("/targets-of-alert-rule", rt.targetsOfAlertRule)
}
}
if rt.HTTP.Heartbeat.Enable {
if rt.HTTP.APIForAgent.Enable {
heartbeat := r.Group("/v1/n9e")
{
if len(rt.HTTP.Heartbeat.BasicAuth) > 0 {
heartbeat.Use(gin.BasicAuth(rt.HTTP.Heartbeat.BasicAuth))
if len(rt.HTTP.APIForAgent.BasicAuth) > 0 {
heartbeat.Use(gin.BasicAuth(rt.HTTP.APIForAgent.BasicAuth))
}
heartbeat.POST("/heartbeat", rt.heartbeat)
}
}
rt.configNoRoute(r)
rt.configNoRoute(r, &statikFS)
}
func Render(c *gin.Context, data, msg interface{}) {

View File

@@ -69,6 +69,11 @@ func (rt *Router) alertAggrViewPut(c *gin.Context) {
return
}
}
ginx.NewRender(c).Message(view.Update(rt.Ctx, f.Name, f.Rule, f.Cate, me.Id))
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

@@ -4,6 +4,7 @@ import (
"net/http"
"sort"
"strings"
"time"
"github.com/ccfos/nightingale/v6/models"
@@ -128,6 +129,13 @@ func (rt *Router) alertCurEventsCardDetails(c *gin.Context) {
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)
@@ -175,10 +183,19 @@ func (rt *Router) alertCurEventDel(c *gin.Context) {
ginx.BindJSON(c, &f)
f.Verify()
rt.checkCurEventBusiGroupRWPermission(c, f.Ids)
ginx.NewRender(c).Message(models.AlertCurEventDel(rt.Ctx, f.Ids))
}
func (rt *Router) checkCurEventBusiGroupRWPermission(c *gin.Context, ids []int64) {
set := make(map[int64]struct{})
for i := 0; i < len(f.Ids); i++ {
event, err := models.AlertCurEventGetById(rt.Ctx, f.Ids[i])
// event group id is 0, ignore perm check
set[0] = struct{}{}
for i := 0; i < len(ids); i++ {
event, err := models.AlertCurEventGetById(rt.Ctx, ids[i])
ginx.Dangerous(err)
if _, has := set[event.GroupId]; !has {
@@ -186,8 +203,6 @@ func (rt *Router) alertCurEventDel(c *gin.Context) {
set[event.GroupId] = struct{}{}
}
}
ginx.NewRender(c).Message(models.AlertCurEventDel(rt.Ctx, f.Ids))
}
func (rt *Router) alertCurEventGet(c *gin.Context) {
@@ -201,3 +216,8 @@ func (rt *Router) alertCurEventGet(c *gin.Context) {
ginx.NewRender(c).Data(event, nil)
}
func (rt *Router) alertCurEventsStatistics(c *gin.Context) {
ginx.NewRender(c).Data(models.AlertCurEventStatistics(rt.Ctx, time.Now()), nil)
}

View File

@@ -2,6 +2,7 @@ package router
import (
"net/http"
"strconv"
"strings"
"time"
@@ -10,6 +11,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/i18n"
"github.com/toolkits/pkg/str"
)
// Return all, front-end search and paging
@@ -26,8 +28,44 @@ func (rt *Router) alertRuleGets(c *gin.Context) {
ginx.NewRender(c).Data(ars, err)
}
func (rt *Router) alertRuleGetsByGids(c *gin.Context) {
gids := str.IdsInt64(ginx.QueryStr(c, "gids", ""), ",")
if len(gids) > 0 {
for _, gid := range gids {
rt.bgroCheck(c, gid)
}
} else {
me := c.MustGet("user").(*models.User)
if !me.IsAdmin() {
var err error
gids, err = models.MyBusiGroupIds(rt.Ctx, me.Id)
ginx.Dangerous(err)
if len(gids) == 0 {
ginx.Bomb(http.StatusForbidden, "forbidden")
return
}
}
}
ars, err := models.AlertRuleGetsByBGIds(rt.Ctx, gids)
if err == nil {
cache := make(map[int64]*models.UserGroup)
for i := 0; i < len(ars); i++ {
ars[i].FillNotifyGroups(rt.Ctx, cache)
ars[i].FillSeverities()
}
}
ginx.NewRender(c).Data(ars, err)
}
func (rt *Router) alertRulesGetByService(c *gin.Context) {
prods := strings.Split(ginx.QueryStr(c, "prods", ""), ",")
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", "")
@@ -95,6 +133,17 @@ func (rt *Router) alertRuleAddByService(c *gin.Context) {
ginx.NewRender(c).Data(reterr, nil)
}
func (rt *Router) alertRuleAddOneByService(c *gin.Context) {
var f models.AlertRule
ginx.BindJSON(c, &f)
err := f.FE2DB()
ginx.Dangerous(err)
err = f.Add(rt.Ctx)
ginx.NewRender(c).Data(f.Id, err)
}
func (rt *Router) alertRuleAddForService(lst []models.AlertRule, username string) map[string]string {
count := len(lst)
// alert rule name -> error string
@@ -266,3 +315,76 @@ func (rt *Router) alertRuleGet(c *gin.Context) {
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)
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
//ignore non-default channels
switch f.NotifyChannelsJSON[i] {
case models.Dingtalk, models.Wecom, models.Feishu, models.Mm,
models.Telegram, models.Email, models.FeishuCard:
// do nothing
default:
continue
}
//default channels
for ui := range users {
if _, b := users[ui].ExtractToken(f.NotifyChannelsJSON[i]); b {
flag = false
break
}
}
if flag {
ancs = append(ancs, f.NotifyChannelsJSON[i])
}
}
if len(ancs) > 0 {
ginx.NewRender(c).Message("All users are missing notify channel configurations. Please check for missing tokens (each channel should be configured with at least one user). %s", ancs)
return
}
}
ginx.NewRender(c).Message("")
}
func (rt *Router) alertRuleCallbacks(c *gin.Context) {
user := c.MustGet("user").(*models.User)
bussGroupIds, err := models.MyBusiGroupIds(rt.Ctx, user.Id)
ginx.Dangerous(err)
ars, err := models.AlertRuleGetsByBGIds(rt.Ctx, bussGroupIds)
ginx.Dangerous(err)
var callbacks []string
callbackFilter := make(map[string]struct{})
for i := range ars {
for _, callback := range ars[i].CallbacksJSON {
if _, ok := callbackFilter[callback]; !ok {
callbackFilter[callback] = struct{}{}
callbacks = append(callbacks, callback)
}
}
}
ginx.NewRender(c).Data(callbacks, nil)
}

View File

@@ -8,27 +8,61 @@ import (
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/str"
)
// Return all, front-end search and paging
func (rt *Router) alertSubscribeGets(c *gin.Context) {
bgid := ginx.UrlParamInt64(c, "id")
lst, err := models.AlertSubscribeGets(rt.Ctx, bgid)
if err == nil {
ugcache := make(map[int64]*models.UserGroup)
for i := 0; i < len(lst); i++ {
ginx.Dangerous(lst[i].FillUserGroups(rt.Ctx, ugcache))
}
ginx.Dangerous(err)
rulecache := make(map[int64]string)
for i := 0; i < len(lst); i++ {
ginx.Dangerous(lst[i].FillRuleName(rt.Ctx, rulecache))
}
ugcache := make(map[int64]*models.UserGroup)
rulecache := make(map[int64]string)
for i := 0; i < len(lst); i++ {
ginx.Dangerous(lst[i].FillDatasourceIds(rt.Ctx))
for i := 0; i < len(lst); i++ {
ginx.Dangerous(lst[i].FillUserGroups(rt.Ctx, ugcache))
ginx.Dangerous(lst[i].FillRuleNames(rt.Ctx, rulecache))
ginx.Dangerous(lst[i].FillDatasourceIds(rt.Ctx))
ginx.Dangerous(lst[i].DB2FE())
}
ginx.NewRender(c).Data(lst, err)
}
func (rt *Router) alertSubscribeGetsByGids(c *gin.Context) {
gids := str.IdsInt64(ginx.QueryStr(c, "gids", ""), ",")
if len(gids) > 0 {
for _, gid := range gids {
rt.bgroCheck(c, gid)
}
} else {
me := c.MustGet("user").(*models.User)
if !me.IsAdmin() {
var err error
gids, err = models.MyBusiGroupIds(rt.Ctx, me.Id)
ginx.Dangerous(err)
if len(gids) == 0 {
ginx.Bomb(http.StatusForbidden, "forbidden")
return
}
}
}
lst, err := models.AlertSubscribeGetsByBGIds(rt.Ctx, gids)
ginx.Dangerous(err)
ugcache := make(map[int64]*models.UserGroup)
rulecache := make(map[int64]string)
for i := 0; i < len(lst); i++ {
ginx.Dangerous(lst[i].FillUserGroups(rt.Ctx, ugcache))
ginx.Dangerous(lst[i].FillRuleNames(rt.Ctx, rulecache))
ginx.Dangerous(lst[i].FillDatasourceIds(rt.Ctx))
ginx.Dangerous(lst[i].DB2FE())
}
ginx.NewRender(c).Data(lst, err)
}
@@ -47,7 +81,7 @@ func (rt *Router) alertSubscribeGet(c *gin.Context) {
ginx.Dangerous(sub.FillUserGroups(rt.Ctx, ugcache))
rulecache := make(map[int64]string)
ginx.Dangerous(sub.FillRuleName(rt.Ctx, rulecache))
ginx.Dangerous(sub.FillRuleNames(rt.Ctx, rulecache))
ginx.Dangerous(sub.FillDatasourceIds(rt.Ctx))
ginx.Dangerous(sub.DB2FE())
@@ -79,12 +113,19 @@ func (rt *Router) alertSubscribePut(c *gin.Context) {
for i := 0; i < len(fs); i++ {
fs[i].UpdateBy = username
fs[i].UpdateAt = timestamp
//After adding the function of batch subscription alert rules, rule_ids is used instead of rule_id.
//When the subscription rules are updated, set rule_id=0 to prevent the wrong subscription caused by the old rule_id.
fs[i].RuleId = 0
ginx.Dangerous(fs[i].Update(
rt.Ctx,
"name",
"disabled",
"prod",
"cate",
"datasource_ids",
"cluster",
"rule_id",
"rule_ids",
"tags",
"redefine_severity",
"new_severity",
@@ -96,7 +137,10 @@ func (rt *Router) alertSubscribePut(c *gin.Context) {
"webhooks",
"for_duration",
"redefine_webhooks",
"datasource_ids",
"severities",
"extra_config",
"busi_groups",
"note",
))
}
@@ -110,3 +154,8 @@ func (rt *Router) alertSubscribeDel(c *gin.Context) {
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

@@ -1,22 +1,26 @@
package router
import (
"fmt"
"net/http"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/i18n"
"github.com/toolkits/pkg/str"
)
type boardForm struct {
Name string `json:"name"`
Ident string `json:"ident"`
Tags string `json:"tags"`
Configs string `json:"configs"`
Public int `json:"public"`
Name string `json:"name"`
Ident string `json:"ident"`
Tags string `json:"tags"`
Configs string `json:"configs"`
Public int `json:"public"`
PublicCate int `json:"public_cate"`
Bgids []int64 `json:"bgids"`
}
func (rt *Router) boardAdd(c *gin.Context) {
@@ -65,6 +69,28 @@ func (rt *Router) boardGet(c *gin.Context) {
}
}
if board.PublicCate == models.PublicLogin {
rt.auth()(c)
} else if board.PublicCate == models.PublicBusi {
rt.auth()(c)
rt.user()(c)
me := c.MustGet("user").(*models.User)
if !me.IsAdmin() {
bgids, err := models.MyBusiGroupIds(rt.Ctx, me.Id)
ginx.Dangerous(err)
if len(bgids) == 0 {
ginx.Bomb(http.StatusForbidden, "forbidden")
}
ok, err := models.BoardBusigroupCheck(rt.Ctx, board.Id, bgids)
ginx.Dangerous(err)
if !ok {
ginx.Bomb(http.StatusForbidden, "forbidden")
}
}
}
ginx.NewRender(c).Data(board, nil)
}
@@ -192,10 +218,20 @@ func (rt *Router) boardPutPublic(c *gin.Context) {
}
bo.Public = f.Public
bo.PublicCate = f.PublicCate
if bo.PublicCate == models.PublicBusi {
err := models.BoardBusigroupUpdate(rt.Ctx, bo.Id, f.Bgids)
ginx.Dangerous(err)
} else {
err := models.BoardBusigroupDelByBoardId(rt.Ctx, bo.Id)
ginx.Dangerous(err)
}
bo.UpdateBy = me.Username
bo.UpdateAt = time.Now().Unix()
err := bo.Update(rt.Ctx, "public", "update_by", "update_at")
err := bo.Update(rt.Ctx, "public", "public_cate", "update_by", "update_at")
ginx.NewRender(c).Data(bo, err)
}
@@ -207,21 +243,64 @@ func (rt *Router) boardGets(c *gin.Context) {
ginx.NewRender(c).Data(boards, err)
}
func (rt *Router) publicBoardGets(c *gin.Context) {
me := c.MustGet("user").(*models.User)
bgids, err := models.MyBusiGroupIds(rt.Ctx, me.Id)
ginx.Dangerous(err)
boardIds, err := models.BoardIdsByBusiGroupIds(rt.Ctx, bgids)
ginx.Dangerous(err)
boards, err := models.BoardGets(rt.Ctx, "", "public=1 and (public_cate in (?) or id in (?))", []int64{0, 1}, boardIds)
ginx.NewRender(c).Data(boards, err)
}
func (rt *Router) boardGetsByGids(c *gin.Context) {
gids := str.IdsInt64(ginx.QueryStr(c, "gids", ""), ",")
query := ginx.QueryStr(c, "query", "")
if len(gids) > 0 {
for _, gid := range gids {
rt.bgroCheck(c, gid)
}
} else {
me := c.MustGet("user").(*models.User)
if !me.IsAdmin() {
var err error
gids, err = models.MyBusiGroupIds(rt.Ctx, me.Id)
ginx.Dangerous(err)
if len(gids) == 0 {
ginx.Bomb(http.StatusForbidden, "forbidden")
return
}
}
}
boardBusigroups, err := models.BoardBusigroupGets(rt.Ctx)
ginx.Dangerous(err)
m := make(map[int64][]int64)
for _, boardBusigroup := range boardBusigroups {
m[boardBusigroup.BoardId] = append(m[boardBusigroup.BoardId], boardBusigroup.BusiGroupId)
}
boards, err := models.BoardGetsByBGIds(rt.Ctx, gids, query)
ginx.Dangerous(err)
for i := 0; i < len(boards); i++ {
if ids, ok := m[boards[i].Id]; ok {
boards[i].Bgids = ids
}
}
ginx.NewRender(c).Data(boards, err)
}
func (rt *Router) boardClone(c *gin.Context) {
me := c.MustGet("user").(*models.User)
bo := rt.Board(ginx.UrlParamInt64(c, "bid"))
newBoard := &models.Board{
Name: bo.Name + " Copy",
Tags: bo.Tags,
GroupId: bo.GroupId,
CreateBy: me.Username,
UpdateBy: me.Username,
}
if bo.Ident != "" {
newBoard.Ident = uuid.NewString()
}
newBoard := bo.Clone(me.Username, bo.GroupId, " Cloned")
ginx.Dangerous(newBoard.Add(rt.Ctx))
@@ -235,3 +314,39 @@ func (rt *Router) boardClone(c *gin.Context) {
ginx.NewRender(c).Message(nil)
}
type boardsForm struct {
BoardIds []int64 `json:"board_ids"`
Bgids []int64 `json:"bgids"`
}
func (rt *Router) boardBatchClone(c *gin.Context) {
me := c.MustGet("user").(*models.User)
var f boardsForm
ginx.BindJSON(c, &f)
for _, bgid := range f.Bgids {
rt.bgrwCheck(c, bgid)
}
reterr := make(map[string]string, len(f.BoardIds))
lang := c.GetHeader("X-Language")
for _, bgid := range f.Bgids {
for _, bid := range f.BoardIds {
bo := rt.Board(bid)
newBoard := bo.Clone(me.Username, bgid, "")
payload, err := models.BoardPayloadGet(rt.Ctx, bo.Id)
if err != nil {
reterr[fmt.Sprintf("%s-%d", newBoard.Name, bgid)] = i18n.Sprintf(lang, err.Error())
continue
}
if err = newBoard.AtomicAdd(rt.Ctx, payload); err != nil {
reterr[fmt.Sprintf("%s-%d", newBoard.Name, bgid)] = i18n.Sprintf(lang, err.Error())
}
}
}
ginx.NewRender(c).Data(reterr, nil)
}

View File

@@ -78,7 +78,7 @@ func (rt *Router) builtinBoardCateGets(c *gin.Context) {
}
me := c.MustGet("user").(*models.User)
buildinFavoritesMap, err := models.BuiltinCateGetByUserId(rt.Ctx, me.Id)
builtinFavoritesMap, err := models.BuiltinCateGetByUserId(rt.Ctx, me.Id)
if err != nil {
logger.Warningf("get builtin favorites fail: %v", err)
}
@@ -91,6 +91,9 @@ func (rt *Router) builtinBoardCateGets(c *gin.Context) {
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 {
@@ -114,7 +117,7 @@ func (rt *Router) builtinBoardCateGets(c *gin.Context) {
}
boardCate.Boards = boards
if _, ok := buildinFavoritesMap[dir]; ok {
if _, ok := builtinFavoritesMap[dir]; ok {
boardCate.Favorite = true
}
@@ -170,7 +173,7 @@ func (rt *Router) builtinAlertCateGets(c *gin.Context) {
}
me := c.MustGet("user").(*models.User)
buildinFavoritesMap, err := models.BuiltinCateGetByUserId(rt.Ctx, me.Id)
builtinFavoritesMap, err := models.BuiltinCateGetByUserId(rt.Ctx, me.Id)
if err != nil {
logger.Warningf("get builtin favorites fail: %v", err)
}
@@ -207,7 +210,7 @@ func (rt *Router) builtinAlertCateGets(c *gin.Context) {
alertCate.IconUrl = fmt.Sprintf("/api/n9e/integrations/icon/%s/%s", dir, iconFiles[0])
}
if _, ok := buildinFavoritesMap[dir]; ok {
if _, ok := builtinFavoritesMap[dir]; ok {
alertCate.Favorite = true
}
@@ -230,7 +233,7 @@ func (rt *Router) builtinAlertRules(c *gin.Context) {
}
me := c.MustGet("user").(*models.User)
buildinFavoritesMap, err := models.BuiltinCateGetByUserId(rt.Ctx, me.Id)
builtinFavoritesMap, err := models.BuiltinCateGetByUserId(rt.Ctx, me.Id)
if err != nil {
logger.Warningf("get builtin favorites fail: %v", err)
}
@@ -243,6 +246,9 @@ func (rt *Router) builtinAlertRules(c *gin.Context) {
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 {
@@ -268,7 +274,7 @@ func (rt *Router) builtinAlertRules(c *gin.Context) {
alertCate.IconUrl = fmt.Sprintf("/api/n9e/integrations/icon/%s/%s", dir, iconFiles[0])
}
if _, ok := buildinFavoritesMap[dir]; ok {
if _, ok := builtinFavoritesMap[dir]; ok {
alertCate.Favorite = true
}
@@ -309,3 +315,26 @@ func (rt *Router) builtinIcon(c *gin.Context) {
iconPath := fp + "/" + cate + "/icon/" + ginx.UrlParamStr(c, "name")
c.File(path.Join(iconPath))
}
func (rt *Router) builtinMarkdown(c *gin.Context) {
fp := rt.Center.BuiltinIntegrationsDir
if fp == "" {
fp = path.Join(runner.Cwd, "integrations")
}
cate := ginx.UrlParamStr(c, "cate")
var markdown []byte
markdownDir := fp + "/" + cate + "/markdown"
markdownFiles, err := file.FilesUnder(markdownDir)
if err != nil {
logger.Warningf("get markdown fail: %v", err)
} else if len(markdownFiles) > 0 {
f := markdownFiles[0]
fn := markdownDir + "/" + f
markdown, err = file.ReadBytes(fn)
if err != nil {
logger.Warningf("get collect fail: %v", err)
}
}
ginx.NewRender(c).Data(string(markdown), nil)
}

View File

@@ -0,0 +1,66 @@
package router
import (
"net/http"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) builtinComponentsAdd(c *gin.Context) {
var lst []models.BuiltinComponent
ginx.BindJSON(c, &lst)
username := Username(c)
count := len(lst)
if count == 0 {
ginx.Bomb(http.StatusBadRequest, "input json is empty")
}
reterr := make(map[string]string)
for i := 0; i < count; i++ {
if err := lst[i].Add(rt.Ctx, username); err != nil {
reterr[lst[i].Ident] = err.Error()
}
}
ginx.NewRender(c).Data(reterr, nil)
}
func (rt *Router) builtinComponentsGets(c *gin.Context) {
query := ginx.QueryStr(c, "query", "")
bc, err := models.BuiltinComponentGets(rt.Ctx, query)
ginx.Dangerous(err)
ginx.NewRender(c).Data(bc, nil)
}
func (rt *Router) builtinComponentsPut(c *gin.Context) {
var req models.BuiltinComponent
ginx.BindJSON(c, &req)
bc, err := models.BuiltinComponentGet(rt.Ctx, "id = ?", req.ID)
ginx.Dangerous(err)
if bc == nil {
ginx.NewRender(c, http.StatusNotFound).Message("No such builtin component")
return
}
username := Username(c)
req.UpdatedBy = username
ginx.NewRender(c).Message(bc.Update(rt.Ctx, req))
}
func (rt *Router) builtinComponentsDel(c *gin.Context) {
var req idsForm
ginx.BindJSON(c, &req)
req.Verify()
ginx.NewRender(c).Message(models.BuiltinComponentDels(rt.Ctx, req.Ids))
}

View File

@@ -0,0 +1,120 @@
package router
import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/prom"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) metricFilterGets(c *gin.Context) {
lst, err := models.MetricFilterGets(rt.Ctx, "")
ginx.Dangerous(err)
me := c.MustGet("user").(*models.User)
gids, err := models.MyGroupIds(rt.Ctx, me.Id)
ginx.Dangerous(err)
arr := make([]models.MetricFilter, 0)
for _, f := range lst {
if me.Username == f.CreateBy {
arr = append(arr, f)
continue
}
if HasPerm(gids, f.GroupsPerm, false) {
arr = append(arr, f)
}
}
ginx.NewRender(c).Data(arr, err)
}
func (rt *Router) metricFilterAdd(c *gin.Context) {
var f models.MetricFilter
ginx.BindJSON(c, &f)
me := c.MustGet("user").(*models.User)
f.CreateBy = me.Username
f.UpdateBy = me.Username
ginx.Dangerous(f.Add(rt.Ctx))
ginx.NewRender(c).Data(f, nil)
}
func (rt *Router) metricFilterDel(c *gin.Context) {
var f idsForm
ginx.BindJSON(c, &f)
f.Verify()
me := c.MustGet("user").(*models.User)
for _, id := range f.Ids {
old, err := models.MetricFilterGet(rt.Ctx, id)
ginx.Dangerous(err)
if me.Username != old.CreateBy {
gids, err := models.MyGroupIds(rt.Ctx, me.Id)
ginx.Dangerous(err)
if !HasPerm(gids, old.GroupsPerm, true) {
ginx.NewRender(c).Message("no permission")
return
}
}
}
ginx.NewRender(c).Message(models.MetricFilterDel(rt.Ctx, f.Ids))
}
func (rt *Router) metricFilterPut(c *gin.Context) {
var f models.MetricFilter
ginx.BindJSON(c, &f)
me := c.MustGet("user").(*models.User)
old, err := models.MetricFilterGet(rt.Ctx, f.ID)
ginx.Dangerous(err)
if me.Username != old.CreateBy {
gids, err := models.MyGroupIds(rt.Ctx, me.Id)
ginx.Dangerous(err)
if !HasPerm(gids, old.GroupsPerm, true) {
ginx.NewRender(c).Message("no permission")
return
}
}
f.UpdateBy = me.Username
ginx.NewRender(c).Message(f.Update(rt.Ctx))
}
type metricPromqlReq struct {
LabelFilter string `json:"label_filter"`
Promql string `json:"promql"`
}
func (rt *Router) getMetricPromql(c *gin.Context) {
var req metricPromqlReq
ginx.BindJSON(c, &req)
promql := prom.AddLabelToPromQL(req.LabelFilter, req.Promql)
ginx.NewRender(c).Data(promql, nil)
}
func HasPerm(gids []int64, gps []models.GroupPerm, checkWrite bool) bool {
gmap := make(map[int64]struct{})
for _, gp := range gps {
if checkWrite && !gp.Write {
continue
}
gmap[gp.Gid] = struct{}{}
}
for _, gid := range gids {
if _, ok := gmap[gid]; ok {
return true
}
}
return false
}

View File

@@ -0,0 +1,113 @@
package router
import (
"net/http"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
// single or import
func (rt *Router) builtinMetricsAdd(c *gin.Context) {
var lst []models.BuiltinMetric
ginx.BindJSON(c, &lst)
username := Username(c)
count := len(lst)
if count == 0 {
ginx.Bomb(http.StatusBadRequest, "input json is empty")
}
lang := c.GetHeader("X-Language")
if lang == "" {
lang = "zh_CN"
}
reterr := make(map[string]string)
for i := 0; i < count; i++ {
lst[i].Lang = lang
if err := lst[i].Add(rt.Ctx, username); err != nil {
reterr[lst[i].Name] = err.Error()
}
}
ginx.NewRender(c).Data(reterr, nil)
}
func (rt *Router) builtinMetricsGets(c *gin.Context) {
collector := ginx.QueryStr(c, "collector", "")
typ := ginx.QueryStr(c, "typ", "")
query := ginx.QueryStr(c, "query", "")
limit := ginx.QueryInt(c, "limit", 20)
lang := c.GetHeader("X-Language")
unit := ginx.QueryStr(c, "unit", "")
if lang == "" {
lang = "zh_CN"
}
bm, err := models.BuiltinMetricGets(rt.Ctx, lang, collector, typ, query, unit, limit, ginx.Offset(c, limit))
ginx.Dangerous(err)
total, err := models.BuiltinMetricCount(rt.Ctx, lang, collector, typ, query, unit)
ginx.Dangerous(err)
ginx.NewRender(c).Data(gin.H{
"list": bm,
"total": total,
}, nil)
}
func (rt *Router) builtinMetricsPut(c *gin.Context) {
var req models.BuiltinMetric
ginx.BindJSON(c, &req)
bm, err := models.BuiltinMetricGet(rt.Ctx, "id = ?", req.ID)
ginx.Dangerous(err)
if bm == nil {
ginx.NewRender(c, http.StatusNotFound).Message("No such builtin metric")
return
}
username := Username(c)
req.UpdatedBy = username
ginx.NewRender(c).Message(bm.Update(rt.Ctx, req))
}
func (rt *Router) builtinMetricsDel(c *gin.Context) {
var req idsForm
ginx.BindJSON(c, &req)
req.Verify()
ginx.NewRender(c).Message(models.BuiltinMetricDels(rt.Ctx, req.Ids))
}
func (rt *Router) builtinMetricsDefaultTypes(c *gin.Context) {
lst := []string{
"Linux",
"cAdvisor",
"Ping",
"MySQL",
"Redis",
"Kafka",
"Elasticsearch",
"PostgreSQL",
"MongoDB",
"Memcached",
}
ginx.NewRender(c).Data(lst, nil)
}
func (rt *Router) builtinMetricsTypes(c *gin.Context) {
collector := ginx.QueryStr(c, "collector", "")
query := ginx.QueryStr(c, "query", "")
lang := c.GetHeader("X-Language")
ginx.NewRender(c).Data(models.BuiltinMetricTypes(rt.Ctx, lang, collector, query))
}
func (rt *Router) builtinMetricsCollectors(c *gin.Context) {
typ := ginx.QueryStr(c, "typ", "")
query := ginx.QueryStr(c, "query", "")
lang := c.GetHeader("X-Language")
ginx.NewRender(c).Data(models.BuiltinMetricCollectors(rt.Ctx, lang, typ, query))
}

View File

@@ -0,0 +1,224 @@
package router
import (
"encoding/json"
"net/http"
"strings"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
type Board struct {
Name string `json:"name"`
Tags string `json:"tags"`
Configs interface{} `json:"configs"`
}
func (rt *Router) builtinPayloadsAdd(c *gin.Context) {
var lst []models.BuiltinPayload
ginx.BindJSON(c, &lst)
username := Username(c)
count := len(lst)
if count == 0 {
ginx.Bomb(http.StatusBadRequest, "input json is empty")
}
reterr := make(map[string]string)
for i := 0; i < count; i++ {
if lst[i].Type == "alert" {
if strings.HasPrefix(strings.TrimSpace(lst[i].Content), "[") {
// 处理多个告警规则模板的情况
alertRules := []models.AlertRule{}
if err := json.Unmarshal([]byte(lst[i].Content), &alertRules); err != nil {
reterr[lst[i].Name] = err.Error()
}
for _, rule := range alertRules {
contentBytes, err := json.Marshal(rule)
if err != nil {
reterr[rule.Name] = err.Error()
continue
}
bp := models.BuiltinPayload{
Type: lst[i].Type,
Component: lst[i].Component,
Cate: lst[i].Cate,
Name: rule.Name,
Tags: rule.AppendTags,
Content: string(contentBytes),
CreatedBy: username,
UpdatedBy: username,
}
if err := bp.Add(rt.Ctx, username); err != nil {
reterr[bp.Name] = err.Error()
}
}
continue
}
alertRule := models.AlertRule{}
if err := json.Unmarshal([]byte(lst[i].Content), &alertRule); err != nil {
reterr[lst[i].Name] = err.Error()
continue
}
bp := models.BuiltinPayload{
Type: lst[i].Type,
Component: lst[i].Component,
Cate: lst[i].Cate,
Name: alertRule.Name,
Tags: alertRule.AppendTags,
Content: lst[i].Content,
CreatedBy: username,
UpdatedBy: username,
}
if err := bp.Add(rt.Ctx, username); err != nil {
reterr[bp.Name] = err.Error()
}
} else if lst[i].Type == "dashboard" {
if strings.HasPrefix(strings.TrimSpace(lst[i].Content), "[") {
// 处理多个告警规则模板的情况
dashboards := []Board{}
if err := json.Unmarshal([]byte(lst[i].Content), &dashboards); err != nil {
reterr[lst[i].Name] = err.Error()
}
for _, dashboard := range dashboards {
contentBytes, err := json.Marshal(dashboard)
if err != nil {
reterr[dashboard.Name] = err.Error()
continue
}
bp := models.BuiltinPayload{
Type: lst[i].Type,
Component: lst[i].Component,
Cate: lst[i].Cate,
Name: dashboard.Name,
Tags: dashboard.Tags,
Content: string(contentBytes),
CreatedBy: username,
UpdatedBy: username,
}
if err := bp.Add(rt.Ctx, username); err != nil {
reterr[bp.Name] = err.Error()
}
}
continue
}
dashboard := Board{}
if err := json.Unmarshal([]byte(lst[i].Content), &dashboard); err != nil {
reterr[lst[i].Name] = err.Error()
continue
}
bp := models.BuiltinPayload{
Type: lst[i].Type,
Component: lst[i].Component,
Cate: lst[i].Cate,
Name: dashboard.Name,
Tags: dashboard.Tags,
Content: lst[i].Content,
CreatedBy: username,
UpdatedBy: username,
}
if err := bp.Add(rt.Ctx, username); err != nil {
reterr[bp.Name] = err.Error()
}
} else {
if err := lst[i].Add(rt.Ctx, username); err != nil {
reterr[lst[i].Name] = err.Error()
}
}
}
ginx.NewRender(c).Data(reterr, nil)
}
func (rt *Router) builtinPayloadsGets(c *gin.Context) {
typ := ginx.QueryStr(c, "type", "")
component := ginx.QueryStr(c, "component", "")
cate := ginx.QueryStr(c, "cate", "")
query := ginx.QueryStr(c, "query", "")
lst, err := models.BuiltinPayloadGets(rt.Ctx, typ, component, cate, query)
ginx.NewRender(c).Data(lst, err)
}
func (rt *Router) builtinPayloadcatesGet(c *gin.Context) {
typ := ginx.QueryStr(c, "type", "")
component := ginx.QueryStr(c, "component", "")
cates, err := models.BuiltinPayloadCates(rt.Ctx, typ, component)
ginx.NewRender(c).Data(cates, err)
}
func (rt *Router) builtinPayloadGet(c *gin.Context) {
id := ginx.UrlParamInt64(c, "id")
bp, err := models.BuiltinPayloadGet(rt.Ctx, "id = ?", id)
if err != nil {
ginx.Bomb(http.StatusInternalServerError, err.Error())
}
if bp == nil {
ginx.Bomb(http.StatusNotFound, "builtin payload not found")
}
ginx.NewRender(c).Data(bp, nil)
}
func (rt *Router) builtinPayloadsPut(c *gin.Context) {
var req models.BuiltinPayload
ginx.BindJSON(c, &req)
bp, err := models.BuiltinPayloadGet(rt.Ctx, "id = ?", req.ID)
ginx.Dangerous(err)
if bp == nil {
ginx.NewRender(c, http.StatusNotFound).Message("No such builtin payload")
return
}
if req.Type == "alert" {
alertRule := models.AlertRule{}
if err := json.Unmarshal([]byte(req.Content), &alertRule); err != nil {
ginx.Bomb(http.StatusBadRequest, err.Error())
}
req.Name = alertRule.Name
req.Tags = alertRule.AppendTags
} else if req.Type == "dashboard" {
dashboard := Board{}
if err := json.Unmarshal([]byte(req.Content), &dashboard); err != nil {
ginx.Bomb(http.StatusBadRequest, err.Error())
}
req.Name = dashboard.Name
req.Tags = dashboard.Tags
}
username := Username(c)
req.UpdatedBy = username
ginx.NewRender(c).Message(bp.Update(rt.Ctx, req))
}
func (rt *Router) builtinPayloadsDel(c *gin.Context) {
var req idsForm
ginx.BindJSON(c, &req)
req.Verify()
ginx.NewRender(c).Message(models.BuiltinPayloadDels(rt.Ctx, req.Ids))
}

View File

@@ -123,6 +123,11 @@ func (rt *Router) busiGroupGets(c *gin.Context) {
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", "")

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

@@ -62,3 +62,8 @@ func (rt *Router) contactKeysGets(c *gin.Context) {
ginx.NewRender(c).Data(labelAndKeys, nil)
}
func (rt *Router) siteInfo(c *gin.Context) {
config, err := models.ConfigsGet(rt.Ctx, "site_info")
ginx.NewRender(c).Data(config, err)
}

View File

@@ -2,6 +2,7 @@ package router
import (
"github.com/ccfos/nightingale/v6/models"
"time"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
@@ -20,28 +21,54 @@ func (rt *Router) configGet(c *gin.Context) {
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) configPutByKey(c *gin.Context) {
var f models.Configs
ginx.BindJSON(c, &f)
username := c.MustGet("username").(string)
ginx.NewRender(c).Message(models.ConfigsSetWithUname(rt.Ctx, f.Ckey, f.Cval, username))
}
func (rt *Router) configsDel(c *gin.Context) {
var f idsForm
ginx.BindJSON(c, &f)
ginx.NewRender(c).Message(models.ConfigsDel(rt.Ctx, f.Ids))
}
func (rt *Router) configsPut(c *gin.Context) {
func (rt *Router) configsPut(c *gin.Context) { //for APIForService
var arr []models.Configs
ginx.BindJSON(c, &arr)
username := c.GetString("user")
if username == "" {
username = "default"
}
now := time.Now().Unix()
for i := 0; i < len(arr); i++ {
arr[i].UpdateBy = username
arr[i].UpdateAt = now
ginx.Dangerous(arr[i].Update(rt.Ctx))
}
ginx.NewRender(c).Message(nil)
}
func (rt *Router) configsPost(c *gin.Context) {
func (rt *Router) configsPost(c *gin.Context) { //for APIForService
var arr []models.Configs
ginx.BindJSON(c, &arr)
username := c.GetString("user")
if username == "" {
username = "default"
}
now := time.Now().Unix()
for i := 0; i < len(arr); i++ {
arr[i].CreateBy = username
arr[i].UpdateBy = username
arr[i].CreateAt = now
arr[i].UpdateAt = now
ginx.Dangerous(arr[i].Add(rt.Ctx))
}

View File

@@ -3,8 +3,10 @@ package router
import (
"crypto/tls"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"github.com/ccfos/nightingale/v6/models"
@@ -24,6 +26,11 @@ type listReq struct {
}
func (rt *Router) datasourceList(c *gin.Context) {
if rt.DatasourceCache.DatasourceCheckHook(c) {
Render(c, []int{}, nil)
return
}
var req listReq
ginx.BindJSON(c, &req)
@@ -31,33 +38,52 @@ func (rt *Router) datasourceList(c *gin.Context) {
category := req.Category
name := req.Name
user := c.MustGet("user").(*models.User)
list, err := models.GetDatasourcesGetsBy(rt.Ctx, typ, category, name, "")
Render(c, list, err)
Render(c, rt.DatasourceCache.DatasourceFilter(list, user), err)
}
type datasourceBrief struct {
Id int64 `json:"id"`
Name string `json:"name"`
PluginType string `json:"plugin_type"`
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)
}
func (rt *Router) datasourceBriefs(c *gin.Context) {
var dss []datasourceBrief
var dss []*models.Datasource
list, err := models.GetDatasourcesGetsBy(rt.Ctx, "", "", "", "")
ginx.Dangerous(err)
for i := range list {
dss = append(dss, datasourceBrief{
Id: list[i].Id,
Name: list[i].Name,
PluginType: list[i].PluginType,
})
for _, item := range list {
item.AuthJson.BasicAuthPassword = ""
if item.PluginType != models.PROMETHEUS {
item.SettingsJson = nil
} else {
for k, v := range item.SettingsJson {
if strings.HasPrefix(k, "prometheus.") {
item.SettingsJson[strings.TrimPrefix(k, "prometheus.")] = v
delete(item.SettingsJson, k)
}
}
}
dss = append(dss, item)
}
if !rt.Center.AnonymousAccess.PromQuerier {
user := c.MustGet("user").(*models.User)
dss = rt.DatasourceCache.DatasourceFilter(dss, user)
}
ginx.NewRender(c).Data(dss, err)
}
func (rt *Router) datasourceUpsert(c *gin.Context) {
if rt.DatasourceCache.DatasourceCheckHook(c) {
Render(c, []int{}, nil)
return
}
var req models.Datasource
ginx.BindJSON(c, &req)
username := Username(c)
@@ -87,7 +113,7 @@ func (rt *Router) datasourceUpsert(c *gin.Context) {
}
err = req.Add(rt.Ctx)
} else {
err = req.Update(rt.Ctx, "name", "description", "cluster_name", "settings", "http", "auth", "updated_by", "updated_at")
err = req.Update(rt.Ctx, "name", "description", "cluster_name", "settings", "http", "auth", "updated_by", "updated_at", "is_default")
}
Render(c, nil, err)
@@ -98,6 +124,10 @@ func DatasourceCheck(ds models.Datasource) error {
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{
@@ -116,10 +146,33 @@ func DatasourceCheck(ds models.Datasource) error {
if ds.PluginType == models.PROMETHEUS {
subPath := "/api/v1/query"
query := url.Values{}
query.Add("query", "1+1")
if ds.HTTPJson.IsLoki() {
subPath = "/api/v1/labels"
} else {
query.Add("query", "1+1")
}
fullURL = fmt.Sprintf("%s%s?%s", ds.HTTPJson.Url, subPath, query.Encode())
req, err = http.NewRequest("POST", fullURL, nil)
req, err = http.NewRequest("GET", fullURL, nil)
if err != nil {
logger.Errorf("Error creating request: %v", err)
return fmt.Errorf("request url:%s failed", fullURL)
}
} else if ds.PluginType == models.TDENGINE {
fullURL = fmt.Sprintf("%s/rest/sql", ds.HTTPJson.Url)
req, err = http.NewRequest("POST", fullURL, strings.NewReader("show databases"))
if err != nil {
logger.Errorf("Error creating request: %v", err)
return fmt.Errorf("request url:%s failed", fullURL)
}
}
if ds.PluginType == models.LOKI {
subPath := "/api/v1/labels"
fullURL = fmt.Sprintf("%s%s", ds.HTTPJson.Url, subPath)
req, err = http.NewRequest("GET", fullURL, nil)
if err != nil {
logger.Errorf("Error creating request: %v", err)
return fmt.Errorf("request url:%s failed", fullURL)
@@ -143,13 +196,19 @@ func DatasourceCheck(ds models.Datasource) error {
if resp.StatusCode != 200 {
logger.Errorf("Error making request: %v\n", resp.StatusCode)
return fmt.Errorf("request url:%s failed code:%d", fullURL, resp.StatusCode)
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("request url:%s failed code:%d body:%s", fullURL, resp.StatusCode, string(body))
}
return nil
}
func (rt *Router) datasourceGet(c *gin.Context) {
if rt.DatasourceCache.DatasourceCheckHook(c) {
Render(c, []int{}, nil)
return
}
var req models.Datasource
ginx.BindJSON(c, &req)
err := req.Get(rt.Ctx)
@@ -157,6 +216,11 @@ func (rt *Router) datasourceGet(c *gin.Context) {
}
func (rt *Router) datasourceUpdataStatus(c *gin.Context) {
if rt.DatasourceCache.DatasourceCheckHook(c) {
Render(c, []int{}, nil)
return
}
var req models.Datasource
ginx.BindJSON(c, &req)
username := Username(c)
@@ -166,13 +230,20 @@ func (rt *Router) datasourceUpdataStatus(c *gin.Context) {
}
func (rt *Router) datasourceDel(c *gin.Context) {
if rt.DatasourceCache.DatasourceCheckHook(c) {
Render(c, []int{}, nil)
return
}
var ids []int64
ginx.BindJSON(c, &ids)
err := models.DatasourceDel(rt.Ctx, ids)
Render(c, nil, err)
}
func Username(c *gin.Context) string {
func (rt *Router) getDatasourceIds(c *gin.Context) {
name := ginx.QueryStr(c, "name")
datasourceIds, err := models.GetDatasourceIdsByEngineName(rt.Ctx, name)
return c.MustGet("username").(string)
ginx.NewRender(c).Data(datasourceIds, err)
}

View File

@@ -0,0 +1,81 @@
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

@@ -1,22 +1,58 @@
package router
import (
"fmt"
"net/http"
"strconv"
"strings"
"github.com/ccfos/nightingale/v6/alert/aconf"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/ibex"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
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
case "user_variable":
statistics, err = models.ConfigsUserVariableStatistics(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, ",", " ")
@@ -30,7 +66,8 @@ func queryDatasourceIds(c *gin.Context) []int64 {
}
type idsForm struct {
Ids []int64 `json:"ids"`
Ids []int64 `json:"ids"`
IsSyncToFlashDuty bool `json:"is_sync_to_flashduty"`
}
func (f idsForm) Verify() {
@@ -95,27 +132,11 @@ type TaskCreateReply struct {
Dat int64 `json:"dat"` // task.id
}
// return task.id, error
func TaskCreate(v interface{}, ibexc aconf.Ibex) (int64, error) {
var res TaskCreateReply
err := ibex.New(
ibexc.Address,
ibexc.BasicAuthUser,
ibexc.BasicAuthPass,
ibexc.Timeout,
).
Path("/ibex/v1/tasks").
In(v).
Out(&res).
POST()
if err != nil {
return 0, err
func Username(c *gin.Context) string {
username := c.GetString(gin.AuthUserKey)
if username == "" {
user := c.MustGet("user").(*models.User)
username = user.Username
}
if res.Err != "" {
return 0, fmt.Errorf("response.err: %v", res.Err)
}
return res.Dat, nil
return username
}

View File

@@ -4,12 +4,15 @@ import (
"compress/gzip"
"encoding/json"
"io/ioutil"
"sort"
"strings"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)
func (rt *Router) heartbeat(c *gin.Context) {
@@ -35,17 +38,60 @@ func (rt *Router) heartbeat(c *gin.Context) {
err = json.Unmarshal(bs, &req)
ginx.Dangerous(err)
req.Offset = (time.Now().UnixMilli() - req.UnixTime)
req.RemoteAddr = c.ClientIP()
// maybe from pushgw
if req.Offset == 0 {
req.Offset = (time.Now().UnixMilli() - req.UnixTime)
}
if req.RemoteAddr == "" {
req.RemoteAddr = c.ClientIP()
}
if req.EngineName == "" {
req.EngineName = rt.Alert.Heartbeat.EngineName
}
rt.MetaSet.Set(req.Hostname, req)
var items = make(map[string]struct{})
items[req.Hostname] = struct{}{}
rt.IdentSet.MSet(items)
gid := ginx.QueryInt64(c, "gid", 0)
if target, has := rt.TargetCache.Get(req.Hostname); has && target != nil {
gid := ginx.QueryInt64(c, "gid", 0)
hostIp := strings.TrimSpace(req.HostIp)
if gid != 0 {
target, has := rt.TargetCache.Get(req.Hostname)
if has && target.GroupId != gid {
err = models.TargetUpdateBgid(rt.Ctx, []string{req.Hostname}, gid, false)
filed := make(map[string]interface{})
if gid != 0 && gid != target.GroupId {
filed["group_id"] = gid
}
if hostIp != "" && hostIp != target.HostIp {
filed["host_ip"] = hostIp
}
if len(req.GlobalLabels) > 0 {
lst := []string{}
for k, v := range req.GlobalLabels {
lst = append(lst, k+"="+v)
}
sort.Strings(lst)
labels := strings.Join(lst, " ")
if target.Tags != labels {
filed["tags"] = labels
}
}
if req.EngineName != "" && req.EngineName != target.EngineName {
filed["engine_name"] = req.EngineName
}
if len(filed) > 0 {
err := target.UpdateFieldsMap(rt.Ctx, filed)
if err != nil {
logger.Errorf("update target fields failed, err: %v", err)
}
}
logger.Debugf("heartbeat field:%+v target: %v", filed, *target)
}
ginx.NewRender(c).Message(err)

View File

@@ -1,50 +1,75 @@
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/pelletier/go-toml/v2"
"github.com/ccfos/nightingale/v6/pkg/secu"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"github.com/pelletier/go-toml/v2"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)
type loginForm struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
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())
user, err := models.PassLogin(rt.Ctx, f.Username, f.Password)
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, f.Password, 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 {
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
}
var user *models.User
var err error
lc := rt.Sso.LDAP.Copy()
if lc.Enable {
user, err = ldapx.LdapLogin(rt.Ctx, f.Username, authPassWord, lc.DefaultRoles, lc)
if err != nil {
logger.Debugf("ldap login failed: %v username: %s", err, f.Username)
var errLoginInN9e error
// to use n9e as the minimum guarantee for login
if user, errLoginInN9e = models.PassLogin(rt.Ctx, f.Username, authPassWord); errLoginInN9e != nil {
ginx.NewRender(c).Message("ldap login failed: %v; n9e login failed: %v", err, errLoginInN9e)
return
}
} else {
user.RolesLst = strings.Fields(user.Roles)
}
} else {
user, err = models.PassLogin(rt.Ctx, f.Username, authPassWord)
ginx.Dangerous(err)
}
if user == nil {
@@ -67,6 +92,7 @@ func (rt *Router) loginPost(c *gin.Context) {
}
func (rt *Router) logoutPost(c *gin.Context) {
logger.Infof("username:%s logout from:%s", c.GetString("username"), c.ClientIP())
metadata, err := rt.extractTokenMetadata(c.Request)
if err != nil {
ginx.NewRender(c, http.StatusBadRequest).Message("failed to parse jwt token")
@@ -79,7 +105,18 @@ func (rt *Router) logoutPost(c *gin.Context) {
return
}
ginx.NewRender(c).Message("")
var logoutAddr string
user := c.MustGet("user").(*models.User)
switch user.Belong {
case "oidc":
logoutAddr = rt.Sso.OIDC.GetSsoLogoutAddr()
case "cas":
logoutAddr = rt.Sso.CAS.GetSsoLogoutAddr()
case "oauth2":
logoutAddr = rt.Sso.OAuth2.GetSsoLogoutAddr()
}
ginx.NewRender(c).Data(logoutAddr, nil)
}
type refreshForm struct {
@@ -207,7 +244,7 @@ func (rt *Router) loginCallback(c *gin.Context) {
ret, err := rt.Sso.OIDC.Callback(rt.Redis, c.Request.Context(), code, state)
if err != nil {
logger.Debugf("sso.callback() get ret %+v error %v", ret, err)
logger.Errorf("sso_callback fail. code:%s, state:%s, get ret: %+v. error: %v", code, state, ret, err)
ginx.NewRender(c).Data(CallbackOutput{}, err)
return
}
@@ -217,39 +254,12 @@ func (rt *Router) loginCallback(c *gin.Context) {
if user != nil {
if rt.Sso.OIDC.CoverAttributes {
if ret.Nickname != "" {
user.Nickname = ret.Nickname
}
if ret.Email != "" {
user.Email = ret.Email
}
if ret.Phone != "" {
user.Phone = ret.Phone
}
user.UpdateAt = time.Now().Unix()
user.Update(rt.Ctx, "email", "nickname", "phone", "update_at")
updatedFields := user.UpdateSsoFields("oidc", ret.Nickname, ret.Phone, ret.Email)
ginx.Dangerous(user.Update(rt.Ctx, "update_at", updatedFields...))
}
} else {
now := time.Now().Unix()
user = &models.User{
Username: ret.Username,
Password: "******",
Nickname: ret.Nickname,
Phone: ret.Phone,
Email: ret.Email,
Portrait: "",
Roles: strings.Join(rt.Sso.OIDC.DefaultRoles, " "),
RolesLst: rt.Sso.OIDC.DefaultRoles,
Contacts: []byte("{}"),
CreateAt: now,
UpdateAt: now,
CreateBy: "oidc",
UpdateBy: "oidc",
}
user = new(models.User)
user.FullSsoFields("oidc", ret.Username, ret.Nickname, ret.Phone, ret.Email, rt.Sso.OIDC.DefaultRoles)
// create user from oidc
ginx.Dangerous(user.Add(rt.Ctx))
}
@@ -327,38 +337,12 @@ func (rt *Router) loginCallbackCas(c *gin.Context) {
ginx.Dangerous(err)
if user != nil {
if rt.Sso.CAS.CoverAttributes {
if ret.Nickname != "" {
user.Nickname = ret.Nickname
}
if ret.Email != "" {
user.Email = ret.Email
}
if ret.Phone != "" {
user.Phone = ret.Phone
}
user.UpdateAt = time.Now().Unix()
ginx.Dangerous(user.Update(rt.Ctx, "email", "nickname", "phone", "update_at"))
updatedFields := user.UpdateSsoFields("cas", ret.Nickname, ret.Phone, ret.Email)
ginx.Dangerous(user.Update(rt.Ctx, "update_at", updatedFields...))
}
} else {
now := time.Now().Unix()
user = &models.User{
Username: ret.Username,
Password: "******",
Nickname: ret.Nickname,
Portrait: "",
Roles: strings.Join(rt.Sso.CAS.DefaultRoles, " "),
RolesLst: rt.Sso.CAS.DefaultRoles,
Contacts: []byte("{}"),
Phone: ret.Phone,
Email: ret.Email,
CreateAt: now,
UpdateAt: now,
CreateBy: "CAS",
UpdateBy: "CAS",
}
user = new(models.User)
user.FullSsoFields("cas", ret.Username, ret.Nickname, ret.Phone, ret.Email, rt.Sso.CAS.DefaultRoles)
// create user from cas
ginx.Dangerous(user.Add(rt.Ctx))
}
@@ -429,39 +413,12 @@ func (rt *Router) loginCallbackOAuth(c *gin.Context) {
if user != nil {
if rt.Sso.OAuth2.CoverAttributes {
if ret.Nickname != "" {
user.Nickname = ret.Nickname
}
if ret.Email != "" {
user.Email = ret.Email
}
if ret.Phone != "" {
user.Phone = ret.Phone
}
user.UpdateAt = time.Now().Unix()
user.Update(rt.Ctx, "email", "nickname", "phone", "update_at")
updatedFields := user.UpdateSsoFields("oauth2", ret.Nickname, ret.Phone, ret.Email)
ginx.Dangerous(user.Update(rt.Ctx, "update_at", updatedFields...))
}
} else {
now := time.Now().Unix()
user = &models.User{
Username: ret.Username,
Password: "******",
Nickname: ret.Nickname,
Phone: ret.Phone,
Email: ret.Email,
Portrait: "",
Roles: strings.Join(rt.Sso.OAuth2.DefaultRoles, " "),
RolesLst: rt.Sso.OAuth2.DefaultRoles,
Contacts: []byte("{}"),
CreateAt: now,
UpdateAt: now,
CreateBy: "oauth2",
UpdateBy: "oauth2",
}
user = new(models.User)
user.FullSsoFields("oauth2", ret.Username, ret.Nickname, ret.Phone, ret.Email, rt.Sso.OAuth2.DefaultRoles)
// create user from oidc
ginx.Dangerous(user.Add(rt.Ctx))
}
@@ -492,10 +449,23 @@ type SsoConfigOutput struct {
}
func (rt *Router) ssoConfigNameGet(c *gin.Context) {
var oidcDisplayName, casDisplayName, oauthDisplayName string
if rt.Sso.OIDC != nil {
oidcDisplayName = rt.Sso.OIDC.GetDisplayName()
}
if rt.Sso.CAS != nil {
casDisplayName = rt.Sso.CAS.GetDisplayName()
}
if rt.Sso.OAuth2 != nil {
oauthDisplayName = rt.Sso.OAuth2.GetDisplayName()
}
ginx.NewRender(c).Data(SsoConfigOutput{
OidcDisplayName: rt.Sso.OIDC.GetDisplayName(),
CasDisplayName: rt.Sso.CAS.GetDisplayName(),
OauthDisplayName: rt.Sso.OAuth2.GetDisplayName(),
OidcDisplayName: oidcDisplayName,
CasDisplayName: casDisplayName,
OauthDisplayName: oauthDisplayName,
}, nil)
}
@@ -520,8 +490,7 @@ func (rt *Router) ssoConfigUpdate(c *gin.Context) {
var config oidcx.Config
err := toml.Unmarshal([]byte(f.Content), &config)
ginx.Dangerous(err)
err = rt.Sso.OIDC.Reload(config)
rt.Sso.OIDC, err = oidcx.New(config)
ginx.Dangerous(err)
case "CAS":
var config cas.Config
@@ -537,3 +506,19 @@ func (rt *Router) ssoConfigUpdate(c *gin.Context) {
ginx.NewRender(c).Message(nil)
}
type RSAConfigOutput struct {
OpenRSA bool
RSAPublicKey string
}
func (rt *Router) rsaConfigGet(c *gin.Context) {
publicKey := ""
if len(rt.HTTP.RSA.RSAPublicKey) > 0 {
publicKey = base64.StdEncoding.EncodeToString(rt.HTTP.RSA.RSAPublicKey)
}
ginx.NewRender(c).Data(RSAConfigOutput{
OpenRSA: rt.HTTP.RSA.OpenRSA,
RSAPublicKey: publicKey,
}, nil)
}

View File

@@ -5,10 +5,12 @@ import (
"strings"
"time"
"github.com/ccfos/nightingale/v6/alert/common"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/str"
)
// Return all, front-end search and paging
@@ -19,9 +21,34 @@ func (rt *Router) alertMuteGetsByBG(c *gin.Context) {
ginx.NewRender(c).Data(lst, err)
}
func (rt *Router) alertMuteGetsByGids(c *gin.Context) {
gids := str.IdsInt64(ginx.QueryStr(c, "gids", ""), ",")
if len(gids) > 0 {
for _, gid := range gids {
rt.bgroCheck(c, gid)
}
} else {
me := c.MustGet("user").(*models.User)
if !me.IsAdmin() {
var err error
gids, err = models.MyBusiGroupIds(rt.Ctx, me.Id)
ginx.Dangerous(err)
if len(gids) == 0 {
ginx.Bomb(http.StatusForbidden, "forbidden")
return
}
}
}
lst, err := models.AlertMuteGetsByBGIds(rt.Ctx, gids)
ginx.NewRender(c).Data(lst, err)
}
func (rt *Router) alertMuteGets(c *gin.Context) {
prods := strings.Fields(ginx.QueryStr(c, "prods", ""))
bgid := ginx.QueryInt64(c, "bgid", 0)
bgid := ginx.QueryInt64(c, "bgid", -1)
query := ginx.QueryStr(c, "query", "")
lst, err := models.AlertMuteGets(rt.Ctx, prods, bgid, query)
@@ -29,16 +56,41 @@ func (rt *Router) alertMuteGets(c *gin.Context) {
}
func (rt *Router) alertMuteAdd(c *gin.Context) {
var f models.AlertMute
ginx.BindJSON(c, &f)
username := c.MustGet("username").(string)
f.CreateBy = username
f.GroupId = ginx.UrlParamInt64(c, "id")
ginx.NewRender(c).Message(f.Add(rt.Ctx))
}
// Preview events (alert_cur_event) that match the mute strategy based on the following criteria:
// business group ID (group_id, group_id), product (prod, rule_prod),
// alert event severity (severities, severity), and event tags (tags, tags).
// For products of type not 'host', also consider the category (cate, cate) and datasource ID (datasource_ids, datasource_id).
func (rt *Router) alertMutePreview(c *gin.Context) {
//Generally the match of events would be less.
var f models.AlertMute
ginx.BindJSON(c, &f)
f.GroupId = ginx.UrlParamInt64(c, "id")
ginx.Dangerous(f.Verify()) //verify and parse tags json to ITags
events, err := models.AlertCurEventGetsFromAlertMute(rt.Ctx, &f)
ginx.Dangerous(err)
matchEvents := make([]*models.AlertCurEvent, 0, len(events))
for i := 0; i < len(events); i++ {
events[i].DB2Mem()
if common.MatchTags(events[i].TagsMap, f.ITags) {
matchEvents = append(matchEvents, events[i])
}
}
ginx.NewRender(c).Data(matchEvents, err)
}
func (rt *Router) alertMuteAddByService(c *gin.Context) {
var f models.AlertMute
ginx.BindJSON(c, &f)
@@ -54,6 +106,14 @@ func (rt *Router) alertMuteDel(c *gin.Context) {
ginx.NewRender(c).Message(models.AlertMuteDel(rt.Ctx, f.Ids))
}
// alertMuteGet returns the alert mute by ID
func (rt *Router) alertMuteGet(c *gin.Context) {
amid := ginx.UrlParamInt64(c, "amid")
am, err := models.AlertMuteGetById(rt.Ctx, amid)
am.DB2FE()
ginx.NewRender(c).Data(am, err)
}
func (rt *Router) alertMutePutByFE(c *gin.Context) {
var f models.AlertMute
ginx.BindJSON(c, &f)

View File

@@ -92,6 +92,10 @@ func (rt *Router) jwtAuth() gin.HandlerFunc {
}
}
func (rt *Router) Auth() gin.HandlerFunc {
return rt.auth()
}
func (rt *Router) auth() gin.HandlerFunc {
if rt.HTTP.ProxyAuth.Enable {
return rt.proxyAuth()
@@ -120,6 +124,10 @@ func (rt *Router) jwtMock() gin.HandlerFunc {
}
}
func (rt *Router) User() gin.HandlerFunc {
return rt.user()
}
func (rt *Router) user() gin.HandlerFunc {
return func(c *gin.Context) {
userid := c.MustGet("userid").(int64)
@@ -174,6 +182,10 @@ func (rt *Router) bgro() gin.HandlerFunc {
}
// bgrw 逐步要被干掉,不安全
func (rt *Router) Bgrw() gin.HandlerFunc {
return rt.bgrw()
}
func (rt *Router) bgrw() gin.HandlerFunc {
return func(c *gin.Context) {
me := c.MustGet("user").(*models.User)
@@ -233,6 +245,10 @@ func (rt *Router) bgroCheck(c *gin.Context, bgid int64) {
c.Set("busi_group", bg)
}
func (rt *Router) Perm(operation string) gin.HandlerFunc {
return rt.perm(operation)
}
func (rt *Router) perm(operation string) gin.HandlerFunc {
return func(c *gin.Context) {
me := c.MustGet("user").(*models.User)

View File

@@ -2,15 +2,19 @@ package router
import (
"encoding/json"
"fmt"
"strings"
"github.com/ccfos/nightingale/v6/alert/aconf"
"github.com/ccfos/nightingale/v6/alert/sender"
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/models"
"github.com/pelletier/go-toml/v2"
"github.com/ccfos/nightingale/v6/pkg/tplx"
"github.com/gin-gonic/gin"
"github.com/pelletier/go-toml/v2"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/str"
)
func (rt *Router) webhookGets(c *gin.Context) {
@@ -30,16 +34,19 @@ func (rt *Router) webhookPuts(c *gin.Context) {
var webhooks []models.Webhook
ginx.BindJSON(c, &webhooks)
for i := 0; i < len(webhooks); i++ {
for k, v := range webhooks[i].HeaderMap {
webhooks[i].Headers = append(webhooks[i].Headers, k)
webhooks[i].Headers = append(webhooks[i].Headers, v)
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)))
username := c.MustGet("username").(string)
ginx.NewRender(c).Message(models.ConfigsSetWithUname(rt.Ctx, models.WEBHOOKKEY, string(data), username))
}
func (rt *Router) notifyScriptGet(c *gin.Context) {
@@ -62,8 +69,8 @@ func (rt *Router) notifyScriptPut(c *gin.Context) {
data, err := json.Marshal(notifyScript)
ginx.Dangerous(err)
ginx.NewRender(c).Message(models.ConfigsSet(rt.Ctx, models.NOTIFYSCRIPT, string(data)))
username := c.MustGet("username").(string)
ginx.NewRender(c).Message(models.ConfigsSetWithUname(rt.Ctx, models.NOTIFYSCRIPT, string(data), username))
}
func (rt *Router) notifyChannelGets(c *gin.Context) {
@@ -98,8 +105,8 @@ func (rt *Router) notifyChannelPuts(c *gin.Context) {
data, err := json.Marshal(notifyChannels)
ginx.Dangerous(err)
ginx.NewRender(c).Message(models.ConfigsSet(rt.Ctx, models.NOTIFYCHANNEL, string(data)))
username := c.MustGet("username").(string)
ginx.NewRender(c).Message(models.ConfigsSetWithUname(rt.Ctx, models.NOTIFYCHANNEL, string(data), username))
}
func (rt *Router) notifyContactGets(c *gin.Context) {
@@ -134,8 +141,8 @@ func (rt *Router) notifyContactPuts(c *gin.Context) {
data, err := json.Marshal(notifyContacts)
ginx.Dangerous(err)
ginx.NewRender(c).Message(models.ConfigsSet(rt.Ctx, models.NOTIFYCONTACT, string(data)))
username := c.MustGet("username").(string)
ginx.NewRender(c).Message(models.ConfigsSetWithUname(rt.Ctx, models.NOTIFYCONTACT, string(data), username))
}
func (rt *Router) notifyConfigGet(c *gin.Context) {
@@ -155,36 +162,65 @@ func (rt *Router) notifyConfigGet(c *gin.Context) {
func (rt *Router) notifyConfigPut(c *gin.Context) {
var f models.Configs
ginx.BindJSON(c, &f)
userVariableMap := rt.NotifyConfigCache.ConfigCache.Get()
text := tplx.ReplaceTemplateUseText(f.Ckey, f.Cval, userVariableMap)
switch f.Ckey {
case models.SMTP:
var smtp aconf.SMTPConfig
err := toml.Unmarshal([]byte(f.Cval), &smtp)
ginx.Dangerous(err)
case models.IBEX:
var ibex aconf.Ibex
err := toml.Unmarshal([]byte(f.Cval), &ibex)
err := toml.Unmarshal([]byte(text), &smtp)
ginx.Dangerous(err)
default:
ginx.Bomb(200, "key %s can not modify", f.Ckey)
}
err := models.ConfigsSet(rt.Ctx, f.Ckey, f.Cval)
if err != nil {
ginx.Bomb(200, err.Error())
}
username := c.MustGet("username").(string)
//insert or update build-in config
ginx.Dangerous(models.ConfigsSetWithUname(rt.Ctx, f.Ckey, f.Cval, username))
if f.Ckey == models.SMTP {
// 重置邮件发送器
var smtp aconf.SMTPConfig
err := toml.Unmarshal([]byte(f.Cval), &smtp)
ginx.Dangerous(err)
if smtp.Host == "" || smtp.Port == 0 {
ginx.Bomb(200, "smtp host or port can not be empty")
}
smtp, errSmtp := SmtpValidate(text)
ginx.Dangerous(errSmtp)
go sender.RestartEmailSender(smtp)
}
ginx.NewRender(c).Message(nil)
}
func SmtpValidate(text string) (aconf.SMTPConfig, error) {
var smtp aconf.SMTPConfig
var err error
err = toml.Unmarshal([]byte(text), &smtp)
if err != nil {
return smtp, err
}
if smtp.Host == "" || smtp.Port == 0 {
return smtp, fmt.Errorf("smtp host or port can not be empty")
}
return smtp, err
}
type form struct {
models.Configs
Email string `json:"email"`
}
// After configuring the aconf.SMTPConfig, users can choose to perform a test. In this test, the function attempts to send an email
func (rt *Router) attemptSendEmail(c *gin.Context) {
var f form
ginx.BindJSON(c, &f)
if f.Email = strings.TrimSpace(f.Email); f.Email == "" || !str.IsMail(f.Email) {
ginx.Bomb(200, "email(%s) invalid", f.Email)
}
if f.Ckey != models.SMTP {
ginx.Bomb(200, "config(%v) invalid", f)
}
userVariableMap := rt.NotifyConfigCache.ConfigCache.Get()
text := tplx.ReplaceTemplateUseText(f.Ckey, f.Cval, userVariableMap)
smtp, err := SmtpValidate(text)
ginx.Dangerous(err)
ginx.NewRender(c).Message(sender.SendEmail("Email test", "email content", []string{f.Email}, smtp))
}

View File

@@ -5,49 +5,110 @@ import (
"encoding/json"
"fmt"
"html/template"
"strings"
"time"
"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[models.EmailSubject] = 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) {
user := c.MustGet("user").(*models.User)
var f models.NotifyTpl
ginx.BindJSON(c, &f)
ginx.Dangerous(templateValidate(f))
if err := templateValidate(f); err != nil {
ginx.NewRender(c).Message(err.Error())
return
notifyTpl, err := models.NotifyTplGet(rt.Ctx, f.Id)
ginx.Dangerous(err)
if notifyTpl.CreateBy != user.Username && !user.IsAdmin() {
ginx.Bomb(403, "no permission")
}
f.UpdateAt = time.Now().Unix()
f.UpdateBy = user.Username
ginx.NewRender(c).Message(f.UpdateContent(rt.Ctx))
}
func (rt *Router) notifyTplUpdate(c *gin.Context) {
var f models.NotifyTpl
ginx.BindJSON(c, &f)
ginx.Dangerous(templateValidate(f))
user := c.MustGet("user").(*models.User)
if err := templateValidate(f); err != nil {
ginx.NewRender(c).Message(err.Error())
return
notifyTpl, err := models.NotifyTplGet(rt.Ctx, f.Id)
ginx.Dangerous(err)
if notifyTpl.CreateBy != user.Username && !user.IsAdmin() {
ginx.Bomb(403, "no permission")
}
ginx.NewRender(c).Message(f.Update(rt.Ctx))
// get the count of the same channel and name but different id
count, err := models.Count(models.DB(rt.Ctx).Model(&models.NotifyTpl{}).Where("channel = ? or name = ? and id <> ?", f.Channel, f.Name, f.Id))
ginx.Dangerous(err)
if count != 0 {
ginx.Bomb(200, "Refuse to create duplicate channel or name")
}
notifyTpl.UpdateAt = time.Now().Unix()
notifyTpl.UpdateBy = user.Username
notifyTpl.Name = f.Name
ginx.NewRender(c).Message(notifyTpl.Update(rt.Ctx))
}
func templateValidate(f models.NotifyTpl) error {
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
}
if _, err := template.New(f.Channel).Funcs(tplx.TemplateFuncMap).Parse(f.Content); err != 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())
}
@@ -57,17 +118,34 @@ func templateValidate(f models.NotifyTpl) error {
func (rt *Router) notifyTplPreview(c *gin.Context) {
var event models.AlertCurEvent
err := json.Unmarshal([]byte(cconf.EVENT_EXAMPLE), &event)
if err != nil {
ginx.NewRender(c).Message(err.Error())
return
}
ginx.Dangerous(err)
var f models.NotifyTpl
ginx.BindJSON(c, &f)
tpl, err := template.New(f.Channel).Funcs(tplx.TemplateFuncMap).Parse(f.Content)
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 {
@@ -78,3 +156,34 @@ func (rt *Router) notifyTplPreview(c *gin.Context) {
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.Count(models.DB(rt.Ctx).Model(&models.NotifyTpl{}).Where("channel = ? or name = ?", f.Channel, f.Name))
ginx.Dangerous(err)
if count != 0 {
ginx.Bomb(200, "Refuse to create duplicate channel(unique)")
}
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")
user := c.MustGet("user").(*models.User)
notifyTpl, err := models.NotifyTplGet(rt.Ctx, id)
ginx.Dangerous(err)
if notifyTpl.CreateBy != user.Username && !user.IsAdmin() {
ginx.Bomb(403, "no permission")
}
ginx.NewRender(c).Message(f.NotifyTplDelete(rt.Ctx, id))
}

View File

@@ -3,39 +3,51 @@ package router
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"sync"
"time"
pkgprom "github.com/ccfos/nightingale/v6/pkg/prom"
"github.com/ccfos/nightingale/v6/prom"
"github.com/gin-gonic/gin"
"github.com/prometheus/common/model"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)
type queryFormItem struct {
type QueryFormItem struct {
Start int64 `json:"start" binding:"required"`
End int64 `json:"end" binding:"required"`
Step int64 `json:"step" binding:"required"`
Query string `json:"query" binding:"required"`
}
type batchQueryForm struct {
type BatchQueryForm struct {
DatasourceId int64 `json:"datasource_id" binding:"required"`
Queries []queryFormItem `json:"queries" binding:"required"`
Queries []QueryFormItem `json:"queries" binding:"required"`
}
func (rt *Router) promBatchQueryRange(c *gin.Context) {
var f batchQueryForm
var f BatchQueryForm
ginx.Dangerous(c.BindJSON(&f))
cli := rt.PromClients.GetCli(f.DatasourceId)
lst, err := PromBatchQueryRange(rt.PromClients, f)
ginx.NewRender(c).Data(lst, err)
}
func PromBatchQueryRange(pc *prom.PromClientMap, f BatchQueryForm) ([]model.Value, error) {
var lst []model.Value
cli := pc.GetCli(f.DatasourceId)
if cli == nil {
return lst, fmt.Errorf("no such datasource id: %d", f.DatasourceId)
}
for _, item := range f.Queries {
r := pkgprom.Range{
Start: time.Unix(item.Start, 0),
@@ -44,15 +56,16 @@ func (rt *Router) promBatchQueryRange(c *gin.Context) {
}
resp, _, err := cli.QueryRange(context.Background(), item.Query, r)
ginx.Dangerous(err)
if err != nil {
return lst, err
}
lst = append(lst, resp)
}
ginx.NewRender(c).Data(lst, nil)
return lst, nil
}
type batchInstantForm struct {
type BatchInstantForm struct {
DatasourceId int64 `json:"datasource_id" binding:"required"`
Queries []InstantFormItem `json:"queries" binding:"required"`
}
@@ -63,21 +76,31 @@ type InstantFormItem struct {
}
func (rt *Router) promBatchQueryInstant(c *gin.Context) {
var f batchInstantForm
var f BatchInstantForm
ginx.Dangerous(c.BindJSON(&f))
cli := rt.PromClients.GetCli(f.DatasourceId)
lst, err := PromBatchQueryInstant(rt.PromClients, f)
ginx.NewRender(c).Data(lst, err)
}
func PromBatchQueryInstant(pc *prom.PromClientMap, f BatchInstantForm) ([]model.Value, error) {
var lst []model.Value
cli := pc.GetCli(f.DatasourceId)
if cli == nil {
logger.Warningf("no such datasource id: %d", f.DatasourceId)
return lst, fmt.Errorf("no such datasource id: %d", f.DatasourceId)
}
for _, item := range f.Queries {
resp, _, err := cli.Query(context.Background(), item.Query, time.Unix(item.Time, 0))
ginx.Dangerous(err)
if err != nil {
return lst, err
}
lst = append(lst, resp)
}
ginx.NewRender(c).Data(lst, nil)
return lst, nil
}
func (rt *Router) dsProxy(c *gin.Context) {
@@ -139,21 +162,77 @@ func (rt *Router) dsProxy(c *gin.Context) {
http.Error(w, err.Error(), http.StatusBadGateway)
}
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,
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)
}
modifyResponse := func(r *http.Response) error {
if r.StatusCode == http.StatusUnauthorized {
logger.Warningf("proxy path:%s unauthorized access ", c.Request.URL.Path)
return fmt.Errorf("unauthorized access")
}
return nil
}
proxy := &httputil.ReverseProxy{
Director: director,
Transport: transport,
ErrorHandler: errFunc,
Director: director,
Transport: transport,
ErrorHandler: errFunc,
ModifyResponse: modifyResponse,
}
proxy.ServeHTTP(c.Writer, c.Request)
}
var (
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

@@ -11,6 +11,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/str"
)
func (rt *Router) recordingRuleGets(c *gin.Context) {
@@ -19,6 +20,35 @@ func (rt *Router) recordingRuleGets(c *gin.Context) {
ginx.NewRender(c).Data(ars, err)
}
func (rt *Router) recordingRuleGetsByGids(c *gin.Context) {
gids := str.IdsInt64(ginx.QueryStr(c, "gids", ""), ",")
if len(gids) > 0 {
for _, gid := range gids {
rt.bgroCheck(c, gid)
}
} else {
me := c.MustGet("user").(*models.User)
if !me.IsAdmin() {
var err error
gids, err = models.MyBusiGroupIds(rt.Ctx, me.Id)
ginx.Dangerous(err)
if len(gids) == 0 {
ginx.Bomb(http.StatusForbidden, "forbidden")
return
}
}
}
ars, err := models.RecordingRuleGetsByBGIds(rt.Ctx, gids)
ginx.NewRender(c).Data(ars, err)
}
func (rt *Router) recordingRuleGetsByService(c *gin.Context) {
ars, err := models.RecordingRuleEnabledGets(rt.Ctx)
ginx.NewRender(c).Data(ars, err)
}
func (rt *Router) recordingRuleGet(c *gin.Context) {
rrid := ginx.UrlParamInt64(c, "rrid")

View File

@@ -4,6 +4,7 @@ import (
"net/http"
"strings"
"github.com/ccfos/nightingale/v6/center/cconf"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
@@ -17,6 +18,15 @@ func (rt *Router) rolesGets(c *gin.Context) {
func (rt *Router) permsGets(c *gin.Context) {
user := c.MustGet("user").(*models.User)
if user.IsAdmin() {
var lst []string
for _, ops := range cconf.Operations.Ops {
lst = append(lst, ops.Ops...)
}
ginx.NewRender(c).Data(lst, nil)
return
}
lst, err := models.OperationsOfRole(rt.Ctx, strings.Fields(user.Roles))
ginx.NewRender(c).Data(lst, err)
}
@@ -83,3 +93,18 @@ 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

@@ -3,9 +3,11 @@ package router
import (
"net/http"
"github.com/ccfos/nightingale/v6/center/cconf"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/i18n"
)
func (rt *Router) operationOfRole(c *gin.Context) {
@@ -16,6 +18,15 @@ func (rt *Router) operationOfRole(c *gin.Context) {
ginx.Bomb(http.StatusOK, "role not found")
}
if role.Name == "Admin" {
var lst []string
for _, ops := range cconf.Operations.Ops {
lst = append(lst, ops.Ops...)
}
ginx.NewRender(c).Data(lst, nil)
return
}
ops, err := models.OperationsOfRole(rt.Ctx, []string{role.Name})
ginx.NewRender(c).Data(ops, err)
}
@@ -39,5 +50,11 @@ func (rt *Router) roleBindOperation(c *gin.Context) {
}
func (rt *Router) operations(c *gin.Context) {
ginx.NewRender(c).Data(rt.Operations.Ops, nil)
var ops []cconf.Ops
for _, v := range rt.Operations.Ops {
v.Cname = i18n.Sprintf(c.GetHeader("X-Language"), v.Cname)
ops = append(ops, v)
}
ginx.NewRender(c).Data(ops, nil)
}

View File

@@ -2,6 +2,7 @@ package router
import (
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/flashduty"
"github.com/ccfos/nightingale/v6/pkg/ormx"
"github.com/gin-gonic/gin"
@@ -29,6 +30,11 @@ func (rt *Router) selfProfilePut(c *gin.Context) {
ginx.BindJSON(c, &f)
user := c.MustGet("user").(*models.User)
oldInfo := models.User{
Username: user.Username,
Phone: user.Phone,
Email: user.Email,
}
user.Nickname = f.Nickname
user.Phone = f.Phone
user.Email = f.Email
@@ -36,6 +42,10 @@ func (rt *Router) selfProfilePut(c *gin.Context) {
user.Contacts = f.Contacts
user.UpdateBy = user.Username
if flashduty.NeedSyncUser(rt.Ctx) {
flashduty.UpdateUser(rt.Ctx, oldInfo, f.Email, f.Phone)
}
ginx.NewRender(c).Message(user.UpdateAllFields(rt.Ctx))
}

View File

@@ -1,6 +1,8 @@
package router
import (
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
@@ -16,3 +18,27 @@ 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", 0)
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
}
if datasourceId == 0 {
ginx.NewRender(c).Message("dsid is required")
return
}
servers, err := models.AlertingEngineGetsInstances(rt.Ctx, "datasource_id = ? and clock > ?", datasourceId, time.Now().Unix()-30)
ginx.NewRender(c).Data(servers, err)
}

View File

@@ -15,6 +15,7 @@ import (
"github.com/prometheus/common/model"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/str"
)
type TargetQuery struct {
@@ -42,15 +43,30 @@ func (rt *Router) targetGetsByHostFilter(c *gin.Context) {
}
func (rt *Router) targetGets(c *gin.Context) {
bgid := ginx.QueryInt64(c, "bgid", -1)
bgids := str.IdsInt64(ginx.QueryStr(c, "gids", ""), ",")
query := ginx.QueryStr(c, "query", "")
limit := ginx.QueryInt(c, "limit", 30)
downtime := ginx.QueryInt64(c, "downtime", 0)
dsIds := queryDatasourceIds(c)
total, err := models.TargetTotal(rt.Ctx, bgid, dsIds, query)
var err error
if len(bgids) == 0 {
user := c.MustGet("user").(*models.User)
if !user.IsAdmin() {
// 如果是非 admin 用户,全部对象的情况,找到用户有权限的业务组
var err error
bgids, err = models.MyBusiGroupIds(rt.Ctx, user.Id)
ginx.Dangerous(err)
// 将未分配业务组的对象也加入到列表中
bgids = append(bgids, 0)
}
}
total, err := models.TargetTotal(rt.Ctx, bgids, dsIds, query, downtime)
ginx.Dangerous(err)
list, err := models.TargetGets(rt.Ctx, bgid, dsIds, query, limit, ginx.Offset(c, limit))
list, err := models.TargetGets(rt.Ctx, bgids, dsIds, query, downtime, limit, ginx.Offset(c, limit))
ginx.Dangerous(err)
if err == nil {
@@ -61,6 +77,12 @@ func (rt *Router) targetGets(c *gin.Context) {
for i := 0; i < len(list); i++ {
ginx.Dangerous(list[i].FillGroup(rt.Ctx, cache))
keys = append(keys, models.WrapIdent(list[i].Ident))
if now.Unix()-list[i].UpdateAt < 60 {
list[i].TargetUp = 2
} else if now.Unix()-list[i].UpdateAt < 180 {
list[i].TargetUp = 1
}
}
if len(keys) > 0 {
@@ -80,10 +102,6 @@ func (rt *Router) targetGets(c *gin.Context) {
}
for i := 0; i < len(list); i++ {
if now.Unix()-list[i].UpdateAt < 120 {
list[i].TargetUp = 1
}
if meta, ok := metaMap[list[i].Ident]; ok {
list[i].FillMeta(meta)
} else {
@@ -101,6 +119,32 @@ func (rt *Router) targetGets(c *gin.Context) {
}, nil)
}
func (rt *Router) targetExtendInfoByIdent(c *gin.Context) {
ident := ginx.QueryStr(c, "ident", "")
key := models.WrapExtendIdent(ident)
vals := storage.MGet(context.Background(), rt.Redis, []string{key})
if len(vals) > 0 {
extInfo := string(vals[0])
if extInfo == "null" {
extInfo = ""
}
ginx.NewRender(c).Data(gin.H{
"extend_info": extInfo,
"ident": ident,
}, nil)
return
}
ginx.NewRender(c).Data(gin.H{
"extend_info": "",
"ident": ident,
}, nil)
}
func (rt *Router) targetGetsByService(c *gin.Context) {
lst, err := models.TargetGetsAll(rt.Ctx)
ginx.NewRender(c).Data(lst, err)
}
func (rt *Router) targetGetTags(c *gin.Context) {
idents := ginx.QueryStr(c, "idents", "")
idents = strings.ReplaceAll(idents, ",", " ")
@@ -109,173 +153,242 @@ func (rt *Router) targetGetTags(c *gin.Context) {
}
type targetTagsForm struct {
Idents []string `json:"idents" binding:"required"`
Tags []string `json:"tags" binding:"required"`
Idents []string `json:"idents" binding:"required_without=HostIps"`
HostIps []string `json:"host_ips" binding:"required_without=Idents"`
Tags []string `json:"tags" binding:"required"`
}
func (rt *Router) targetBindTagsByFE(c *gin.Context) {
var f targetTagsForm
var err error
var failedResults = make(map[string]string)
ginx.BindJSON(c, &f)
if len(f.Idents) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents empty")
if len(f.Idents) == 0 && len(f.HostIps) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents or host_ips must be provided")
}
// Acquire idents by idents and hostIps
failedResults, f.Idents, err = models.TargetsGetIdentsByIdentsAndHostIps(rt.Ctx, f.Idents, f.HostIps)
if err != nil {
ginx.Bomb(http.StatusBadRequest, err.Error())
}
rt.checkTargetPerm(c, f.Idents)
ginx.NewRender(c).Message(rt.targetBindTags(f))
ginx.NewRender(c).Data(rt.targetBindTags(f, failedResults))
}
func (rt *Router) targetBindTagsByService(c *gin.Context) {
var f targetTagsForm
var err error
var failedResults = make(map[string]string)
ginx.BindJSON(c, &f)
if len(f.Idents) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents empty")
if len(f.Idents) == 0 && len(f.HostIps) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents or host_ips must be provided")
}
// Acquire idents by idents and hostIps
failedResults, f.Idents, err = models.TargetsGetIdentsByIdentsAndHostIps(rt.Ctx, f.Idents, f.HostIps)
if err != nil {
ginx.Bomb(http.StatusBadRequest, err.Error())
}
ginx.NewRender(c).Message(rt.targetBindTags(f))
ginx.NewRender(c).Data(rt.targetBindTags(f, failedResults))
}
func (rt *Router) targetBindTags(f targetTagsForm) error {
for i := 0; i < len(f.Tags); i++ {
arr := strings.Split(f.Tags[i], "=")
func (rt *Router) targetBindTags(f targetTagsForm, failedIdents map[string]string) (map[string]string, error) {
// 1. Check tags
if err := rt.validateTags(f.Tags); err != nil {
return nil, err
}
// 2. Acquire targets by idents
targets, err := models.TargetsGetByIdents(rt.Ctx, f.Idents)
if err != nil {
return nil, err
}
// 3. Add tags to targets
for _, target := range targets {
if err = rt.addTagsToTarget(target, f.Tags); err != nil {
failedIdents[target.Ident] = err.Error()
}
}
return failedIdents, nil
}
func (rt *Router) validateTags(tags []string) error {
for _, tag := range tags {
arr := strings.Split(tag, "=")
if len(arr) != 2 {
return fmt.Errorf("invalid tag(%s)", f.Tags[i])
return fmt.Errorf("invalid tag format: %s (expected format: key=value)", tag)
}
if strings.TrimSpace(arr[0]) == "" || strings.TrimSpace(arr[1]) == "" {
return fmt.Errorf("invalid tag(%s)", f.Tags[i])
key, value := strings.TrimSpace(arr[0]), strings.TrimSpace(arr[1])
if key == "" {
return fmt.Errorf("invalid tag: key is empty in tag %s", tag)
}
if value == "" {
return fmt.Errorf("invalid tag: value is empty in tag %s", tag)
}
if strings.IndexByte(arr[0], '.') != -1 {
return fmt.Errorf("invalid tagkey(%s): cannot contains . ", arr[0])
if strings.Contains(key, ".") {
return fmt.Errorf("invalid tag key: %s (key cannot contain '.')", key)
}
if strings.IndexByte(arr[0], '-') != -1 {
return fmt.Errorf("invalid tagkey(%s): cannot contains -", arr[0])
if strings.Contains(key, "-") {
return fmt.Errorf("invalid tag key: %s (key cannot contain '-')", key)
}
if !model.LabelNameRE.MatchString(arr[0]) {
return fmt.Errorf("invalid tagkey(%s)", arr[0])
if !model.LabelNameRE.MatchString(key) {
return fmt.Errorf("invalid tag key: %s "+
"(key must start with a letter or underscore, followed by letters, digits, or underscores)", key)
}
}
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) addTagsToTarget(target *models.Target, tags []string) error {
for _, tag := range tags {
tagKey := strings.Split(tag, "=")[0]
if strings.Contains(target.Tags, tagKey+"=") {
return fmt.Errorf("duplicate tagkey(%s)", tagKey)
}
}
return target.AddTags(rt.Ctx, tags)
}
func (rt *Router) targetUnbindTagsByFE(c *gin.Context) {
var f targetTagsForm
var err error
var failedResults = make(map[string]string)
ginx.BindJSON(c, &f)
if len(f.Idents) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents empty")
if len(f.Idents) == 0 && len(f.HostIps) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents or host_ips must be provided")
}
// Acquire idents by idents and hostIps
failedResults, f.Idents, err = models.TargetsGetIdentsByIdentsAndHostIps(rt.Ctx, f.Idents, f.HostIps)
if err != nil {
ginx.Bomb(http.StatusBadRequest, err.Error())
}
rt.checkTargetPerm(c, f.Idents)
ginx.NewRender(c).Message(rt.targetUnbindTags(f))
ginx.NewRender(c).Data(rt.targetUnbindTags(f, failedResults))
}
func (rt *Router) targetUnbindTagsByService(c *gin.Context) {
var f targetTagsForm
var err error
var failedResults = make(map[string]string)
ginx.BindJSON(c, &f)
if len(f.Idents) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents empty")
if len(f.Idents) == 0 && len(f.HostIps) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents or host_ips must be provided")
}
// Acquire idents by idents and hostIps
failedResults, f.Idents, err = models.TargetsGetIdentsByIdentsAndHostIps(rt.Ctx, f.Idents, f.HostIps)
if err != nil {
ginx.Bomb(http.StatusBadRequest, err.Error())
}
ginx.NewRender(c).Message(rt.targetUnbindTags(f))
ginx.NewRender(c).Data(rt.targetUnbindTags(f, failedResults))
}
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
}
func (rt *Router) targetUnbindTags(f targetTagsForm, failedIdents map[string]string) (map[string]string, error) {
// 1. Acquire targets by idents
targets, err := models.TargetsGetByIdents(rt.Ctx, f.Idents)
if err != nil {
return nil, err
}
// 2. Remove tags from targets
for _, target := range targets {
err = target.DelTags(rt.Ctx, f.Tags)
if err != nil {
return err
failedIdents[target.Ident] = err.Error()
continue
}
}
return nil
return failedIdents, nil
}
type targetNoteForm struct {
Idents []string `json:"idents" binding:"required"`
Note string `json:"note"`
Idents []string `json:"idents" binding:"required_without=HostIps"`
HostIps []string `json:"host_ips" binding:"required_without=Idents"`
Note string `json:"note"`
}
func (rt *Router) targetUpdateNote(c *gin.Context) {
var f targetNoteForm
var err error
var failedResults = make(map[string]string)
ginx.BindJSON(c, &f)
if len(f.Idents) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents empty")
if len(f.Idents) == 0 && len(f.HostIps) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents or host_ips must be provided")
}
// Acquire idents by idents and hostIps
failedResults, f.Idents, err = models.TargetsGetIdentsByIdentsAndHostIps(rt.Ctx, f.Idents, f.HostIps)
if err != nil {
ginx.Bomb(http.StatusBadRequest, err.Error())
}
rt.checkTargetPerm(c, f.Idents)
ginx.NewRender(c).Message(models.TargetUpdateNote(rt.Ctx, f.Idents, f.Note))
ginx.NewRender(c).Data(failedResults, models.TargetUpdateNote(rt.Ctx, f.Idents, f.Note))
}
func (rt *Router) targetUpdateNoteByService(c *gin.Context) {
var f targetNoteForm
var err error
var failedResults = make(map[string]string)
ginx.BindJSON(c, &f)
if len(f.Idents) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents empty")
if len(f.Idents) == 0 && len(f.HostIps) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents or host_ips must be provided")
}
ginx.NewRender(c).Message(models.TargetUpdateNote(rt.Ctx, f.Idents, f.Note))
// Acquire idents by idents and hostIps
failedResults, f.Idents, err = models.TargetsGetIdentsByIdentsAndHostIps(rt.Ctx, f.Idents, f.HostIps)
if err != nil {
ginx.Bomb(http.StatusBadRequest, err.Error())
}
ginx.NewRender(c).Data(failedResults, models.TargetUpdateNote(rt.Ctx, f.Idents, f.Note))
}
type targetBgidForm struct {
Idents []string `json:"idents" binding:"required"`
Bgid int64 `json:"bgid"`
Idents []string `json:"idents" binding:"required_without=HostIps"`
HostIps []string `json:"host_ips" binding:"required_without=Idents"`
Bgid int64 `json:"bgid"`
}
func (rt *Router) targetUpdateBgid(c *gin.Context) {
var f targetBgidForm
var err error
var failedResults = make(map[string]string)
ginx.BindJSON(c, &f)
if len(f.Idents) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents empty")
if len(f.Idents) == 0 && len(f.HostIps) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents or host_ips must be provided")
}
// Acquire idents by idents and hostIps
failedResults, f.Idents, err = models.TargetsGetIdentsByIdentsAndHostIps(rt.Ctx, f.Idents, f.HostIps)
if err != nil {
ginx.Bomb(http.StatusBadRequest, err.Error())
}
user := c.MustGet("user").(*models.User)
if user.IsAdmin() {
ginx.NewRender(c).Message(models.TargetUpdateBgid(rt.Ctx, f.Idents, f.Bgid, false))
ginx.NewRender(c).Data(failedResults, models.TargetUpdateBgid(rt.Ctx, f.Idents, f.Bgid, false))
return
}
@@ -288,7 +401,11 @@ func (rt *Router) targetUpdateBgid(c *gin.Context) {
// 机器里边存在未归组的登录用户就需要是admin
if len(orphans) > 0 && !user.IsAdmin() {
ginx.Bomb(http.StatusForbidden, "No permission. Only admin can assign BG")
can, err := user.CheckPerm(rt.Ctx, "/targets/bind")
ginx.Dangerous(err)
if !can {
ginx.Bomb(http.StatusForbidden, "No permission. Only admin can assign BG")
}
}
reBelongs, err := models.IdentsFilter(rt.Ctx, f.Idents, "group_id > ?", 0)
@@ -313,24 +430,69 @@ func (rt *Router) targetUpdateBgid(c *gin.Context) {
ginx.Bomb(http.StatusBadRequest, "invalid bgid")
}
ginx.NewRender(c).Message(models.TargetUpdateBgid(rt.Ctx, f.Idents, f.Bgid, false))
ginx.NewRender(c).Data(failedResults, models.TargetUpdateBgid(rt.Ctx, f.Idents, f.Bgid, false))
}
func (rt *Router) targetUpdateBgidByService(c *gin.Context) {
var f targetBgidForm
var err error
var failedResults = make(map[string]string)
ginx.BindJSON(c, &f)
if len(f.Idents) == 0 && len(f.HostIps) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents or host_ips must be provided")
}
// Acquire idents by idents and hostIps
failedResults, f.Idents, err = models.TargetsGetIdentsByIdentsAndHostIps(rt.Ctx, f.Idents, f.HostIps)
if err != nil {
ginx.Bomb(http.StatusBadRequest, err.Error())
}
ginx.NewRender(c).Data(failedResults, models.TargetUpdateBgid(rt.Ctx, f.Idents, f.Bgid, false))
}
type identsForm struct {
Idents []string `json:"idents" binding:"required"`
Idents []string `json:"idents" binding:"required_without=HostIps"`
HostIps []string `json:"host_ips" binding:"required_without=Idents"`
}
func (rt *Router) targetDel(c *gin.Context) {
var f identsForm
var err error
var failedResults = make(map[string]string)
ginx.BindJSON(c, &f)
if len(f.Idents) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents empty")
if len(f.Idents) == 0 && len(f.HostIps) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents or host_ips must be provided")
}
rt.checkTargetPerm(c, f.Idents)
// Acquire idents by idents and hostIps
failedResults, f.Idents, err = models.TargetsGetIdentsByIdentsAndHostIps(rt.Ctx, f.Idents, f.HostIps)
if err != nil {
ginx.Bomb(http.StatusBadRequest, err.Error())
}
ginx.NewRender(c).Message(models.TargetDel(rt.Ctx, f.Idents))
ginx.NewRender(c).Data(failedResults, models.TargetDel(rt.Ctx, f.Idents))
}
func (rt *Router) targetDelByService(c *gin.Context) {
var f identsForm
var err error
var failedResults = make(map[string]string)
ginx.BindJSON(c, &f)
if len(f.Idents) == 0 && len(f.HostIps) == 0 {
ginx.Bomb(http.StatusBadRequest, "idents or host_ips must be provided")
}
// Acquire idents by idents and hostIps
failedResults, f.Idents, err = models.TargetsGetIdentsByIdentsAndHostIps(rt.Ctx, f.Idents, f.HostIps)
if err != nil {
ginx.Bomb(http.StatusBadRequest, err.Error())
}
ginx.NewRender(c).Data(failedResults, models.TargetDel(rt.Ctx, f.Idents))
}
func (rt *Router) checkTargetPerm(c *gin.Context, idents []string) {
@@ -342,3 +504,21 @@ func (rt *Router) checkTargetPerm(c *gin.Context, idents []string) {
ginx.Bomb(http.StatusForbidden, "No permission to operate the targets: %s", strings.Join(nopri, ", "))
}
}
func (rt *Router) targetsOfAlertRule(c *gin.Context) {
engineName := ginx.QueryStr(c, "engine_name", "")
m, err := models.GetTargetsOfHostAlertRule(rt.Ctx, engineName)
ret := make(map[string]map[int64][]string)
for en, v := range m {
if en != engineName {
continue
}
ret[en] = make(map[int64][]string)
for rid, idents := range v {
ret[en][rid] = idents
}
}
ginx.NewRender(c).Data(ret, err)
}

View File

@@ -1,13 +1,10 @@
package router
import (
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"time"
"github.com/ccfos/nightingale/v6/alert/sender"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
@@ -30,10 +27,55 @@ func (rt *Router) taskGets(c *gin.Context) {
beginTime := time.Now().Unix() - days*24*3600
total, err := models.TaskRecordTotal(rt.Ctx, bgid, beginTime, creator, query)
total, err := models.TaskRecordTotal(rt.Ctx, []int64{bgid}, beginTime, creator, query)
ginx.Dangerous(err)
list, err := models.TaskRecordGets(rt.Ctx, bgid, beginTime, creator, query, limit, ginx.Offset(c, limit))
list, err := models.TaskRecordGets(rt.Ctx, []int64{bgid}, beginTime, creator, query, limit, ginx.Offset(c, limit))
ginx.Dangerous(err)
ginx.NewRender(c).Data(gin.H{
"total": total,
"list": list,
}, nil)
}
func (rt *Router) taskGetsByGids(c *gin.Context) {
gids := str.IdsInt64(ginx.QueryStr(c, "gids", ""), ",")
if len(gids) > 0 {
for _, gid := range gids {
rt.bgroCheck(c, gid)
}
} else {
me := c.MustGet("user").(*models.User)
if !me.IsAdmin() {
var err error
gids, err = models.MyBusiGroupIds(rt.Ctx, me.Id)
ginx.Dangerous(err)
if len(gids) == 0 {
ginx.Bomb(http.StatusForbidden, "forbidden")
return
}
}
}
mine := ginx.QueryBool(c, "mine", false)
days := ginx.QueryInt64(c, "days", 7)
limit := ginx.QueryInt(c, "limit", 20)
query := ginx.QueryStr(c, "query", "")
user := c.MustGet("user").(*models.User)
creator := ""
if mine {
creator = user.Username
}
beginTime := time.Now().Unix() - days*24*3600
total, err := models.TaskRecordTotal(rt.Ctx, gids, beginTime, creator, query)
ginx.Dangerous(err)
list, err := models.TaskRecordGets(rt.Ctx, gids, beginTime, creator, query, limit, ginx.Offset(c, limit))
ginx.Dangerous(err)
ginx.NewRender(c).Data(gin.H{
@@ -56,72 +98,14 @@ type taskForm struct {
Hosts []string `json:"hosts" binding:"required"`
}
func (f *taskForm) Verify() error {
if f.Batch < 0 {
return fmt.Errorf("arg(batch) should be nonnegative")
}
if f.Tolerance < 0 {
return fmt.Errorf("arg(tolerance) should be nonnegative")
}
if f.Timeout < 0 {
return fmt.Errorf("arg(timeout) should be nonnegative")
}
if f.Timeout > 3600*24 {
return fmt.Errorf("arg(timeout) longer than one day")
}
if f.Timeout == 0 {
f.Timeout = 30
}
f.Pause = strings.Replace(f.Pause, "", ",", -1)
f.Pause = strings.Replace(f.Pause, " ", "", -1)
f.Args = strings.Replace(f.Args, "", ",", -1)
if f.Title == "" {
return fmt.Errorf("arg(title) is required")
}
if str.Dangerous(f.Title) {
return fmt.Errorf("arg(title) is dangerous")
}
if f.Script == "" {
return fmt.Errorf("arg(script) is required")
}
if str.Dangerous(f.Args) {
return fmt.Errorf("arg(args) is dangerous")
}
if str.Dangerous(f.Pause) {
return fmt.Errorf("arg(pause) is dangerous")
}
if len(f.Hosts) == 0 {
return fmt.Errorf("arg(hosts) empty")
}
if f.Action != "start" && f.Action != "pause" {
return fmt.Errorf("arg(action) invalid")
}
return nil
}
func (f *taskForm) HandleFH(fh string) {
i := strings.Index(f.Title, " FH: ")
if i > 0 {
f.Title = f.Title[:i]
}
f.Title = f.Title + " FH: " + fh
func (rt *Router) taskRecordAdd(c *gin.Context) {
var f *models.TaskRecord
ginx.BindJSON(c, &f)
ginx.NewRender(c).Message(f.Add(rt.Ctx))
}
func (rt *Router) taskAdd(c *gin.Context) {
var f taskForm
var f models.TaskForm
ginx.BindJSON(c, &f)
bgid := ginx.UrlParamInt64(c, "id")
@@ -137,7 +121,7 @@ func (rt *Router) taskAdd(c *gin.Context) {
rt.checkTargetPerm(c, f.Hosts)
// call ibex
taskId, err := TaskCreate(f, rt.NotifyConfigCache.GetIbex())
taskId, err := sender.TaskAdd(f, user.Username, rt.Ctx.IsCenter)
ginx.Dangerous(err)
if taskId <= 0 {
@@ -146,65 +130,20 @@ func (rt *Router) taskAdd(c *gin.Context) {
// write db
record := models.TaskRecord{
Id: taskId,
GroupId: bgid,
IbexAddress: rt.NotifyConfigCache.GetIbex().Address,
IbexAuthUser: rt.NotifyConfigCache.GetIbex().BasicAuthUser,
IbexAuthPass: rt.NotifyConfigCache.GetIbex().BasicAuthPass,
Title: f.Title,
Account: f.Account,
Batch: f.Batch,
Tolerance: f.Tolerance,
Timeout: f.Timeout,
Pause: f.Pause,
Script: f.Script,
Args: f.Args,
CreateAt: time.Now().Unix(),
CreateBy: f.Creator,
Id: taskId,
GroupId: bgid,
Title: f.Title,
Account: f.Account,
Batch: f.Batch,
Tolerance: f.Tolerance,
Timeout: f.Timeout,
Pause: f.Pause,
Script: f.Script,
Args: f.Args,
CreateAt: time.Now().Unix(),
CreateBy: f.Creator,
}
err = record.Add(rt.Ctx)
ginx.NewRender(c).Data(taskId, err)
}
func (rt *Router) taskProxy(c *gin.Context) {
target, err := url.Parse(rt.NotifyConfigCache.GetIbex().Address)
if err != nil {
ginx.NewRender(c).Message("invalid ibex address: %s", rt.NotifyConfigCache.GetIbex().Address)
return
}
director := func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
// fe request e.g. /api/n9e/busi-group/:id/task/*url
index := strings.Index(req.URL.Path, "/task/")
if index == -1 {
panic("url path invalid")
}
req.URL.Path = "/ibex/v1" + req.URL.Path[index:]
if target.RawQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = target.RawQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = target.RawQuery + "&" + req.URL.RawQuery
}
if rt.NotifyConfigCache.GetIbex().BasicAuthUser != "" {
req.SetBasicAuth(rt.NotifyConfigCache.GetIbex().BasicAuthUser, rt.NotifyConfigCache.GetIbex().BasicAuthPass)
}
}
errFunc := func(w http.ResponseWriter, r *http.Request, err error) {
ginx.NewRender(c, http.StatusBadGateway).Message(err)
}
proxy := &httputil.ReverseProxy{
Director: director,
ErrorHandler: errFunc,
}
proxy.ServeHTTP(c.Writer, c.Request)
}

View File

@@ -18,10 +18,45 @@ func (rt *Router) taskTplGets(c *gin.Context) {
limit := ginx.QueryInt(c, "limit", 20)
groupId := ginx.UrlParamInt64(c, "id")
total, err := models.TaskTplTotal(rt.Ctx, groupId, query)
total, err := models.TaskTplTotal(rt.Ctx, []int64{groupId}, query)
ginx.Dangerous(err)
list, err := models.TaskTplGets(rt.Ctx, groupId, query, limit, ginx.Offset(c, limit))
list, err := models.TaskTplGets(rt.Ctx, []int64{groupId}, query, limit, ginx.Offset(c, limit))
ginx.Dangerous(err)
ginx.NewRender(c).Data(gin.H{
"total": total,
"list": list,
}, nil)
}
func (rt *Router) taskTplGetsByGids(c *gin.Context) {
query := ginx.QueryStr(c, "query", "")
limit := ginx.QueryInt(c, "limit", 20)
gids := str.IdsInt64(ginx.QueryStr(c, "gids", ""), ",")
if len(gids) > 0 {
for _, gid := range gids {
rt.bgroCheck(c, gid)
}
} else {
me := c.MustGet("user").(*models.User)
if !me.IsAdmin() {
var err error
gids, err = models.MyBusiGroupIds(rt.Ctx, me.Id)
ginx.Dangerous(err)
if len(gids) == 0 {
ginx.Bomb(http.StatusForbidden, "forbidden")
return
}
}
}
total, err := models.TaskTplTotal(rt.Ctx, gids, query)
ginx.Dangerous(err)
list, err := models.TaskTplGets(rt.Ctx, gids, query, limit, ginx.Offset(c, limit))
ginx.Dangerous(err)
ginx.NewRender(c).Data(gin.H{
@@ -48,6 +83,27 @@ func (rt *Router) taskTplGet(c *gin.Context) {
}, err)
}
func (rt *Router) taskTplGetByService(c *gin.Context) {
tid := ginx.UrlParamInt64(c, "tid")
tpl, err := models.TaskTplGetById(rt.Ctx, tid)
ginx.Dangerous(err)
if tpl == nil {
ginx.Bomb(404, "no such task template")
}
ginx.NewRender(c).Data(tpl, err)
}
func (rt *Router) taskTplGetsByService(c *gin.Context) {
ginx.NewRender(c).Data(models.TaskTplGetAll(rt.Ctx))
}
func (rt *Router) taskTplStatistics(c *gin.Context) {
ginx.NewRender(c).Data(models.TaskTplStatistics(rt.Ctx))
}
type taskTplForm struct {
Title string `json:"title" binding:"required"`
Batch int `json:"batch"`

View File

@@ -0,0 +1,117 @@
package router
import (
"net/http"
"github.com/ccfos/nightingale/v6/center/cconf"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
"github.com/toolkits/pkg/logger"
)
type databasesQueryForm struct {
Cate string `json:"cate" form:"cate"`
DatasourceId int64 `json:"datasource_id" form:"datasource_id"`
}
func (rt *Router) tdengineDatabases(c *gin.Context) {
var f databasesQueryForm
ginx.BindJSON(c, &f)
tdClient := rt.TdendgineClients.GetCli(f.DatasourceId)
if tdClient == nil {
ginx.NewRender(c, http.StatusNotFound).Message("No such datasource")
return
}
databases, err := tdClient.GetDatabases()
ginx.NewRender(c).Data(databases, err)
}
type tablesQueryForm struct {
Cate string `json:"cate"`
DatasourceId int64 `json:"datasource_id" `
Database string `json:"db"`
IsStable bool `json:"is_stable"`
}
// get tdengine tables
func (rt *Router) tdengineTables(c *gin.Context) {
var f tablesQueryForm
ginx.BindJSON(c, &f)
tdClient := rt.TdendgineClients.GetCli(f.DatasourceId)
if tdClient == nil {
ginx.NewRender(c, http.StatusNotFound).Message("No such datasource")
return
}
tables, err := tdClient.GetTables(f.Database, f.IsStable)
ginx.NewRender(c).Data(tables, err)
}
type columnsQueryForm struct {
Cate string `json:"cate"`
DatasourceId int64 `json:"datasource_id" `
Database string `json:"db"`
Table string `json:"table"`
}
// get tdengine columns
func (rt *Router) tdengineColumns(c *gin.Context) {
var f columnsQueryForm
ginx.BindJSON(c, &f)
tdClient := rt.TdendgineClients.GetCli(f.DatasourceId)
if tdClient == nil {
ginx.NewRender(c, http.StatusNotFound).Message("No such datasource")
return
}
columns, err := tdClient.GetColumns(f.Database, f.Table)
ginx.NewRender(c).Data(columns, err)
}
func (rt *Router) QueryData(c *gin.Context) {
var f models.QueryParam
ginx.BindJSON(c, &f)
var resp []models.DataResp
var err error
tdClient := rt.TdendgineClients.GetCli(f.DatasourceId)
for _, q := range f.Querys {
datas, err := tdClient.Query(q)
ginx.Dangerous(err)
resp = append(resp, datas...)
}
ginx.NewRender(c).Data(resp, err)
}
func (rt *Router) QueryLog(c *gin.Context) {
var f models.QueryParam
ginx.BindJSON(c, &f)
tdClient := rt.TdendgineClients.GetCli(f.DatasourceId)
if len(f.Querys) == 0 {
ginx.Bomb(200, "querys is empty")
return
}
data, err := tdClient.QueryLog(f.Querys[0])
logger.Debugf("tdengine query:%s result: %+v", f.Querys[0], data)
ginx.NewRender(c).Data(data, err)
}
// query sql template
func (rt *Router) QuerySqlTemplate(c *gin.Context) {
cate := ginx.QueryStr(c, "cate")
m := make(map[string]string)
switch cate {
case models.TDENGINE:
m = cconf.TDengineSQLTpl
}
ginx.NewRender(c).Data(m, nil)
}

View File

@@ -5,26 +5,38 @@ import (
"strings"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/flashduty"
"github.com/ccfos/nightingale/v6/pkg/ormx"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) userBusiGroupsGets(c *gin.Context) {
userid := ginx.QueryInt64(c, "userid", 0)
username := ginx.QueryStr(c, "username", "")
if userid == 0 && username == "" {
ginx.Bomb(http.StatusBadRequest, "userid or username required")
}
var user *models.User
var err error
if userid > 0 {
user, err = models.UserGetById(rt.Ctx, userid)
} else {
user, err = models.UserGetByUsername(rt.Ctx, username)
}
ginx.Dangerous(err)
groups, err := user.BusiGroups(rt.Ctx, 10000, "")
ginx.NewRender(c).Data(groups, err)
}
func (rt *Router) userFindAll(c *gin.Context) {
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)
ginx.NewRender(c).Data(gin.H{
"list": list,
"total": total,
}, nil)
list, err := models.UserGetAll(rt.Ctx)
ginx.NewRender(c).Data(list, err)
}
func (rt *Router) userGets(c *gin.Context) {
@@ -68,7 +80,7 @@ func (rt *Router) userAddPost(c *gin.Context) {
ginx.Bomb(http.StatusBadRequest, "roles empty")
}
user := c.MustGet("user").(*models.User)
username := Username(c)
u := models.User{
Username: f.Username,
@@ -79,10 +91,11 @@ func (rt *Router) userAddPost(c *gin.Context) {
Portrait: f.Portrait,
Roles: strings.Join(f.Roles, " "),
Contacts: f.Contacts,
CreateBy: user.Username,
UpdateBy: user.Username,
CreateBy: username,
UpdateBy: username,
}
ginx.Dangerous(u.Verify())
ginx.NewRender(c).Message(u.Add(rt.Ctx))
}
@@ -99,6 +112,30 @@ type userProfileForm struct {
Contacts ormx.JSONObj `json:"contacts"`
}
func (rt *Router) userProfilePutByService(c *gin.Context) {
var f models.User
ginx.BindJSON(c, &f)
if len(f.RolesLst) == 0 {
ginx.Bomb(http.StatusBadRequest, "roles empty")
}
password, err := models.CryptoPass(rt.Ctx, f.Password)
ginx.Dangerous(err)
target := User(rt.Ctx, ginx.UrlParamInt64(c, "id"))
target.Nickname = f.Nickname
target.Password = password
target.Phone = f.Phone
target.Email = f.Email
target.Portrait = f.Portrait
target.Roles = strings.Join(f.RolesLst, " ")
target.Contacts = f.Contacts
target.UpdateBy = Username(c)
ginx.NewRender(c).Message(target.UpdateAllFields(rt.Ctx))
}
func (rt *Router) userProfilePut(c *gin.Context) {
var f userProfileForm
ginx.BindJSON(c, &f)
@@ -108,6 +145,11 @@ func (rt *Router) userProfilePut(c *gin.Context) {
}
target := User(rt.Ctx, ginx.UrlParamInt64(c, "id"))
oldInfo := models.User{
Username: target.Username,
Phone: target.Phone,
Email: target.Email,
}
target.Nickname = f.Nickname
target.Phone = f.Phone
target.Email = f.Email
@@ -115,6 +157,10 @@ func (rt *Router) userProfilePut(c *gin.Context) {
target.Contacts = f.Contacts
target.UpdateBy = c.MustGet("username").(string)
if flashduty.NeedSyncUser(rt.Ctx) {
flashduty.UpdateUser(rt.Ctx, oldInfo, f.Email, f.Phone)
}
ginx.NewRender(c).Message(target.UpdateAllFields(rt.Ctx))
}

View File

@@ -5,6 +5,7 @@ import (
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/flashduty"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
@@ -29,9 +30,21 @@ func (rt *Router) userGroupGets(c *gin.Context) {
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"`
Name string `json:"name" binding:"required"`
Note string `json:"note"`
IsSyncToFlashDuty bool `json:"is_sync_to_flashduty"`
}
func (rt *Router) userGroupAdd(c *gin.Context) {
@@ -48,12 +61,19 @@ func (rt *Router) userGroupAdd(c *gin.Context) {
}
err := ug.Add(rt.Ctx)
if err == nil {
// Even failure is not a big deal
models.UserGroupMemberAdd(rt.Ctx, ug.Id, me.Id)
}
ginx.Dangerous(err)
// Even failure is not a big deal
models.UserGroupMemberAdd(rt.Ctx, ug.Id, me.Id)
if f.IsSyncToFlashDuty || flashduty.NeedSyncTeam(rt.Ctx) {
ugs, err := flashduty.NewUserGroupSyncer(rt.Ctx, &ug)
ginx.Dangerous(err)
err = ugs.SyncUGAdd()
ginx.Dangerous(err)
}
ginx.NewRender(c).Data(ug.Id, err)
}
func (rt *Router) userGroupPut(c *gin.Context) {
@@ -62,6 +82,7 @@ func (rt *Router) userGroupPut(c *gin.Context) {
me := c.MustGet("user").(*models.User)
ug := c.MustGet("user_group").(*models.UserGroup)
oldUGName := ug.Name
if ug.Name != f.Name {
// name changed, check duplication
@@ -77,8 +98,14 @@ func (rt *Router) userGroupPut(c *gin.Context) {
ug.Note = f.Note
ug.UpdateBy = me.Username
ug.UpdateAt = time.Now().Unix()
if f.IsSyncToFlashDuty || flashduty.NeedSyncTeam(rt.Ctx) {
ugs, err := flashduty.NewUserGroupSyncer(rt.Ctx, ug)
ginx.Dangerous(err)
err = ugs.SyncUGPut(oldUGName)
ginx.Dangerous(err)
}
ginx.NewRender(c).Message(ug.Update(rt.Ctx, "Name", "Note", "UpdateAt", "UpdateBy"))
}
// Return all members, front-end search and paging
@@ -98,8 +125,16 @@ func (rt *Router) userGroupGet(c *gin.Context) {
}
func (rt *Router) userGroupDel(c *gin.Context) {
isSyncToFlashDuty := ginx.QueryBool(c, "is_sync_to_flashduty", false)
ug := c.MustGet("user_group").(*models.UserGroup)
if isSyncToFlashDuty || flashduty.NeedSyncTeam(rt.Ctx) {
ugs, err := flashduty.NewUserGroupSyncer(rt.Ctx, ug)
ginx.Dangerous(err)
err = ugs.SyncUGDel(ug.Name)
ginx.Dangerous(err)
}
ginx.NewRender(c).Message(ug.Del(rt.Ctx))
}
func (rt *Router) userGroupMemberAdd(c *gin.Context) {
@@ -111,13 +146,21 @@ func (rt *Router) userGroupMemberAdd(c *gin.Context) {
ug := c.MustGet("user_group").(*models.UserGroup)
err := ug.AddMembers(rt.Ctx, f.Ids)
ginx.Dangerous(err)
if err == nil {
ug.UpdateAt = time.Now().Unix()
ug.UpdateBy = me.Username
ug.Update(rt.Ctx, "UpdateAt", "UpdateBy")
}
if f.IsSyncToFlashDuty || flashduty.NeedSyncTeam(rt.Ctx) {
ugs, err := flashduty.NewUserGroupSyncer(rt.Ctx, ug)
ginx.Dangerous(err)
err = ugs.SyncMembersAdd()
ginx.Dangerous(err)
}
ginx.NewRender(c).Message(err)
}
func (rt *Router) userGroupMemberDel(c *gin.Context) {
@@ -134,6 +177,12 @@ func (rt *Router) userGroupMemberDel(c *gin.Context) {
ug.UpdateBy = me.Username
ug.Update(rt.Ctx, "UpdateAt", "UpdateBy")
}
if f.IsSyncToFlashDuty || flashduty.NeedSyncTeam(rt.Ctx) {
ugs, err := flashduty.NewUserGroupSyncer(rt.Ctx, ug)
ginx.Dangerous(err)
err = ugs.SyncMembersDel()
ginx.Dangerous(err)
}
ginx.NewRender(c).Message(err)
}

View File

@@ -0,0 +1,70 @@
package router
import (
"strings"
"time"
"github.com/ccfos/nightingale/v6/models"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/ginx"
)
func (rt *Router) userVariableConfigGets(context *gin.Context) {
userVariables, err := models.ConfigsGetUserVariable(rt.Ctx)
ginx.NewRender(context).Data(userVariables, err)
}
func (rt *Router) userVariableConfigAdd(context *gin.Context) {
var f models.Configs
ginx.BindJSON(context, &f)
f.Ckey = strings.TrimSpace(f.Ckey)
//insert external config. needs to make sure not plaintext for an encrypted type config
username := context.MustGet("username").(string)
now := time.Now().Unix()
f.CreateBy = username
f.UpdateBy = username
f.CreateAt = now
f.UpdateAt = now
ginx.NewRender(context).Message(models.ConfigsUserVariableInsert(rt.Ctx, f))
}
func (rt *Router) userVariableConfigPut(context *gin.Context) {
var f models.Configs
ginx.BindJSON(context, &f)
f.Id = ginx.UrlParamInt64(context, "id")
f.Ckey = strings.TrimSpace(f.Ckey)
f.UpdateBy = context.MustGet("username").(string)
f.UpdateAt = time.Now().Unix()
user := context.MustGet("user").(*models.User)
if !user.IsAdmin() && f.CreateBy != user.Username {
// only admin or creator can update
ginx.Bomb(403, "no permission")
}
ginx.NewRender(context).Message(models.ConfigsUserVariableUpdate(rt.Ctx, f))
}
func (rt *Router) userVariableConfigDel(context *gin.Context) {
id := ginx.UrlParamInt64(context, "id")
configs, err := models.ConfigGet(rt.Ctx, id)
ginx.Dangerous(err)
user := context.MustGet("user").(*models.User)
if !user.IsAdmin() && configs.CreateBy != user.Username {
// only admin or creator can delete
ginx.Bomb(403, "no permission")
}
if configs != nil && configs.External == models.ConfigExternal {
ginx.NewRender(context).Message(models.ConfigsDel(rt.Ctx, []int64{id}))
} else {
ginx.NewRender(context).Message(nil)
}
}
func (rt *Router) userVariableGetDecryptByService(context *gin.Context) {
decryptMap, decryptErr := models.ConfigUserVariableGetDecryptMap(rt.Ctx, rt.HTTP.RSA.RSAPrivateKey, rt.HTTP.RSA.RSAPassWord)
ginx.NewRender(context).Data(decryptMap, decryptErr)
}

View File

@@ -2,24 +2,30 @@ package sso
import (
"log"
"time"
"github.com/BurntSushi/toml"
"github.com/ccfos/nightingale/v6/center/cconf"
"github.com/ccfos/nightingale/v6/memsto"
"github.com/ccfos/nightingale/v6/models"
"github.com/ccfos/nightingale/v6/pkg/cas"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/ccfos/nightingale/v6/pkg/ldapx"
"github.com/ccfos/nightingale/v6/pkg/oauth2x"
"github.com/ccfos/nightingale/v6/pkg/oidcx"
"github.com/ccfos/nightingale/v6/pkg/tplx"
"github.com/BurntSushi/toml"
"github.com/toolkits/pkg/logger"
)
type SsoClient struct {
OIDC *oidcx.SsoClient
LDAP *ldapx.SsoClient
CAS *cas.SsoClient
OAuth2 *oauth2x.SsoClient
OIDC *oidcx.SsoClient
LDAP *ldapx.SsoClient
CAS *cas.SsoClient
OAuth2 *oauth2x.SsoClient
LastUpdateTime int64
configCache *memsto.ConfigCache
configLastUpdateTime int64
}
const LDAP = `
@@ -29,13 +35,21 @@ Port = 389
BaseDn = 'dc=example,dc=org'
BindUser = 'cn=manager,dc=example,dc=org'
BindPass = '*******'
SyncAddUsers = false
SyncDelUsers = false
# unit: s
SyncInterval = 86400
# openldap format e.g. (&(uid=%s))
# AD format e.g. (&(sAMAccountName=%s))
AuthFilter = '(&(uid=%s))'
UserFilter = '(&(uid=*))'
CoverAttributes = true
TLS = false
StartTLS = true
DefaultRoles = ['Standard']
[Attributes]
Username = 'uid'
Nickname = 'cn'
Phone = 'mobile'
Email = 'mail'
@@ -44,8 +58,9 @@ Email = 'mail'
const OAuth2 = `
Enable = false
DisplayName = 'OAuth2登录'
RedirectURL = 'http://127.0.0.1:18000/callback/oauth'
RedirectURL = 'http://n9e.com/callback/oauth'
SsoAddr = 'https://sso.example.com/oauth2/authorize'
SsoLogoutAddr = 'https://sso.example.com/oauth2/authorize/session/end'
TokenAddr = 'https://sso.example.com/oauth2/token'
UserInfoAddr = 'https://api.example.com/api/v1/user/info'
TranTokenMethod = 'header'
@@ -58,7 +73,7 @@ UserinfoPrefix = 'data'
Scopes = ['profile', 'email', 'phone']
[Attributes]
Username = 'username'
Username = 'sub'
Nickname = 'nickname'
Phone = 'phone_number'
Email = 'email'
@@ -66,34 +81,41 @@ Email = 'email'
const CAS = `
Enable = false
SsoAddr = 'https://cas.example.com/cas/'
RedirectURL = 'http://127.0.0.1:18000/callback/cas'
DisplayName = 'CAS登录'
CoverAttributes = false
RedirectURL = 'http://n9e.com/callback/cas'
SsoAddr = 'https://cas.example.com/cas/'
SsoLogoutAddr = 'https://cas.example.com/cas/session/end'
# LoginPath = ''
CoverAttributes = true
DefaultRoles = ['Standard']
[Attributes]
Username = 'sub'
Nickname = 'nickname'
Phone = 'phone_number'
Email = 'email'
`
const OIDC = `
Enable = false
DisplayName = 'OIDC登录'
RedirectURL = 'http://n9e.com/callback'
SsoAddr = 'http://sso.example.org'
SsoLogoutAddr = 'http://sso.example.org/session/end'
ClientId = ''
ClientSecret = ''
CoverAttributes = true
DefaultRoles = ['Standard']
Scopes = ['openid', 'profile', 'email', 'phone']
[Attributes]
Username = 'sub'
Nickname = 'nickname'
Phone = 'phone_number'
Email = 'email'
`
func Init(center cconf.Center, ctx *ctx.Context) *SsoClient {
func Init(center cconf.Center, ctx *ctx.Context, configCache *memsto.ConfigCache) *SsoClient {
ssoClient := new(SsoClient)
m := make(map[string]string)
m["LDAP"] = LDAP
@@ -122,6 +144,11 @@ func Init(center cconf.Center, ctx *ctx.Context) *SsoClient {
log.Fatalln(err)
}
}
if configCache == nil {
logger.Error("configCache is nil, sso initialization failed")
}
ssoClient.configCache = configCache
userVariableMap := configCache.Get()
configs, err := models.SsoConfigGets(ctx)
if err != nil {
@@ -129,6 +156,7 @@ func Init(center cconf.Center, ctx *ctx.Context) *SsoClient {
}
for _, cfg := range configs {
cfg.Content = tplx.ReplaceTemplateUseText(cfg.Name, cfg.Content, userVariableMap)
switch cfg.Name {
case "LDAP":
var config ldapx.Config
@@ -143,6 +171,7 @@ func Init(center cconf.Center, ctx *ctx.Context) *SsoClient {
if err != nil {
log.Fatalln("init oidc failed:", err)
}
logger.Info("init oidc..")
oidcClient, err := oidcx.New(config)
if err != nil {
logger.Error("init oidc failed:", err)
@@ -165,5 +194,84 @@ func Init(center cconf.Center, ctx *ctx.Context) *SsoClient {
ssoClient.OAuth2 = oauth2x.New(config)
}
}
go ssoClient.SyncSsoUsers(ctx)
go ssoClient.Reload(ctx)
return ssoClient
}
// 定期更新sso配置
func (s *SsoClient) reload(ctx *ctx.Context) error {
lastUpdateTime, err := models.SsoConfigLastUpdateTime(ctx)
if err != nil {
return err
}
lastCacheUpdateTime := s.configCache.GetLastUpdateTime()
if lastUpdateTime == s.LastUpdateTime && lastCacheUpdateTime == s.configLastUpdateTime {
return nil
}
configs, err := models.SsoConfigGets(ctx)
if err != nil {
return err
}
userVariableMap := s.configCache.Get()
for _, cfg := range configs {
cfg.Content = tplx.ReplaceTemplateUseText(cfg.Name, cfg.Content, userVariableMap)
switch cfg.Name {
case "LDAP":
var config ldapx.Config
err := toml.Unmarshal([]byte(cfg.Content), &config)
if err != nil {
logger.Warning("reload ldap failed", err)
continue
}
s.LDAP.Reload(config)
case "OIDC":
var config oidcx.Config
err := toml.Unmarshal([]byte(cfg.Content), &config)
if err != nil {
logger.Warning("reload oidc failed:", err)
continue
}
logger.Info("reload oidc..")
err = s.OIDC.Reload(config)
if err != nil {
logger.Error("reload oidc failed:", err)
continue
}
case "CAS":
var config cas.Config
err := toml.Unmarshal([]byte(cfg.Content), &config)
if err != nil {
logger.Warning("reload cas failed:", err)
continue
}
s.CAS.Reload(config)
case "OAuth2":
var config oauth2x.Config
err := toml.Unmarshal([]byte(cfg.Content), &config)
if err != nil {
logger.Warning("reload oauth2 failed:", err)
continue
}
s.OAuth2.Reload(config)
}
}
s.LastUpdateTime = lastUpdateTime
s.configLastUpdateTime = lastCacheUpdateTime
return nil
}
func (s *SsoClient) Reload(ctx *ctx.Context) {
duration := time.Duration(9000) * time.Millisecond
for {
time.Sleep(duration)
if err := s.reload(ctx); err != nil {
logger.Warning("reload sso client err:", err)
}
}
}

37
center/sso/sync.go Normal file
View File

@@ -0,0 +1,37 @@
package sso
import (
"fmt"
"github.com/ccfos/nightingale/v6/pkg/ctx"
"github.com/toolkits/pkg/logger"
)
func (s *SsoClient) SyncSsoUsers(ctx *ctx.Context) {
if err := s.LDAP.SyncAddAndDelUsers(ctx); err != nil {
fmt.Println("failed to sync the addition and deletion of ldap users:", err)
}
if err := s.LDAP.SyncDelUsers(ctx); err != nil {
fmt.Println("failed to sync deletion of ldap users:", err)
}
go s.loopSyncSsoUsers(ctx)
}
func (s *SsoClient) loopSyncSsoUsers(ctx *ctx.Context) {
for {
select {
case <-s.LDAP.Ticker.C:
lc := s.LDAP.Copy()
if err := lc.SyncAddAndDelUsers(ctx); err != nil {
logger.Warningf("failed to sync the addition and deletion of ldap users: %v", err)
}
if err := lc.SyncDelUsers(ctx); err != nil {
logger.Warningf("failed to sync deletion of ldap users: %v", err)
}
}
}
}

View File

@@ -18,9 +18,9 @@ func Upgrade(configFile string) error {
return err
}
ctx := ctx.NewContext(context.Background(), db)
ctx := ctx.NewContext(context.Background(), db, true)
for _, cluster := range config.Clusters {
count, err := models.GetDatasourcesCountBy(ctx, "", "", cluster.Name)
count, err := models.GetDatasourcesCountByName(ctx, cluster.Name)
if err != nil {
logger.Errorf("get datasource %s count error: %v", cluster.Name, err)
continue

View File

@@ -8,12 +8,18 @@ insert into `role_operation`(role_name, operation) values('Standard', '/trace/ex
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 `chart_share` drop dashboard_id;
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';

View File

@@ -17,7 +17,7 @@ import (
var (
showVersion = flag.Bool("version", false, "Show version.")
configDir = flag.String("configs", osx.GetEnv("N9E_CONFIGS", "etc"), "Specify configuration directory.(env:N9E_CONFIGS)")
configDir = flag.String("configs", osx.GetEnv("N9E_ALERT_CONFIGS", "etc"), "Specify configuration directory.(env:N9E_ALERT_CONFIGS)")
cryptoKey = flag.String("crypto-key", "", "Specify the secret key for configuration file field encryption.")
)

View File

@@ -12,6 +12,7 @@ import (
"github.com/ccfos/nightingale/v6/pkg/osx"
"github.com/ccfos/nightingale/v6/pkg/version"
"github.com/toolkits/pkg/net/tcpx"
"github.com/toolkits/pkg/runner"
)
@@ -31,6 +32,8 @@ func main() {
printEnv()
tcpx.WaitHosts()
cleanFunc, err := center.Initialize(*configDir, *cryptoKey)
if err != nil {
log.Fatalln("failed to initialize:", err)

96
cmd/edge/edge.go Normal file
View File

@@ -0,0 +1,96 @@
package main
import (
"context"
"errors"
"fmt"
"github.com/ccfos/nightingale/v6/alert"
"github.com/ccfos/nightingale/v6/alert/astats"
"github.com/ccfos/nightingale/v6/alert/process"
alertrt "github.com/ccfos/nightingale/v6/alert/router"
"github.com/ccfos/nightingale/v6/center/metas"
"github.com/ccfos/nightingale/v6/conf"
"github.com/ccfos/nightingale/v6/dumper"
"github.com/ccfos/nightingale/v6/memsto"
"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/idents"
pushgwrt "github.com/ccfos/nightingale/v6/pushgw/router"
"github.com/ccfos/nightingale/v6/pushgw/writer"
"github.com/ccfos/nightingale/v6/storage"
"github.com/ccfos/nightingale/v6/tdengine"
"github.com/flashcatcloud/ibex/src/cmd/ibex"
)
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
}
//check CenterApi is default value
if len(config.CenterApi.Addrs) < 1 {
return nil, errors.New("failed to init config: the CenterApi configuration is missing")
}
ctx := ctx.NewContext(context.Background(), nil, false, config.CenterApi)
var redis storage.Redis
redis, err = storage.NewRedis(config.Redis)
if err != nil {
return nil, err
}
syncStats := memsto.NewSyncStats()
targetCache := memsto.NewTargetCache(ctx, syncStats, nil)
busiGroupCache := memsto.NewBusiGroupCache(ctx, syncStats)
idents := idents.New(ctx, redis)
metas := metas.New(redis)
writers := writer.NewWriters(config.Pushgw)
pushgwRouter := pushgwrt.New(config.HTTP, config.Pushgw, config.Alert, targetCache, busiGroupCache, idents, metas, writers, ctx)
r := httpx.GinEngine(config.Global.RunMode, config.HTTP)
pushgwRouter.Config(r)
if !config.Alert.Disable {
configCache := memsto.NewConfigCache(ctx, syncStats, nil, "")
alertStats := astats.NewSyncStats()
dsCache := memsto.NewDatasourceCache(ctx, syncStats)
alertMuteCache := memsto.NewAlertMuteCache(ctx, syncStats)
alertRuleCache := memsto.NewAlertRuleCache(ctx, syncStats)
notifyConfigCache := memsto.NewNotifyConfigCache(ctx, configCache)
userCache := memsto.NewUserCache(ctx, syncStats)
userGroupCache := memsto.NewUserGroupCache(ctx, syncStats)
taskTplsCache := memsto.NewTaskTplCache(ctx)
promClients := prom.NewPromClient(ctx)
tdengineClients := tdengine.NewTdengineClient(ctx, config.Alert.Heartbeat)
externalProcessors := process.NewExternalProcessors()
alert.Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache,
alertRuleCache, notifyConfigCache, taskTplsCache, dsCache, ctx, promClients, tdengineClients, userCache, userGroupCache)
alertrtRouter := alertrt.New(config.HTTP, config.Alert, alertMuteCache, targetCache, busiGroupCache, alertStats, ctx, externalProcessors)
alertrtRouter.Config(r)
if config.Ibex.Enable {
ibex.ServerStart(false, nil, redis, config.HTTP.APIForService.BasicAuth, config.Alert.Heartbeat, &config.CenterApi, r, nil, config.Ibex, config.HTTP.Port)
}
}
dumper.ConfigRouter(r)
httpClean := httpx.Init(config.HTTP, r)
return func() {
logxClean()
httpClean()
}, nil
}

68
cmd/edge/main.go Normal file
View File

@@ -0,0 +1,68 @@
package main
import (
"flag"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"github.com/ccfos/nightingale/v6/pkg/osx"
"github.com/ccfos/nightingale/v6/pkg/version"
"github.com/toolkits/pkg/runner"
)
var (
showVersion = flag.Bool("version", false, "Show version.")
configDir = flag.String("configs", osx.GetEnv("N9E_EDGE_CONFIGS", "etc"), "Specify configuration directory.(env:N9E_EDGE_CONFIGS)")
cryptoKey = flag.String("crypto-key", "", "Specify the secret key for configuration file field encryption.")
)
func main() {
flag.Parse()
if *showVersion {
fmt.Println(version.Version)
os.Exit(0)
}
printEnv()
cleanFunc, err := Initialize(*configDir, *cryptoKey)
if err != nil {
log.Fatalln("failed to initialize:", err)
}
code := 1
sc := make(chan os.Signal, 1)
signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
EXIT:
for {
sig := <-sc
fmt.Println("received signal:", sig.String())
switch sig {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
code = 0
break EXIT
case syscall.SIGHUP:
// reload configuration?
default:
break EXIT
}
}
cleanFunc()
fmt.Println("process exited")
os.Exit(code)
}
func printEnv() {
runner.Init()
fmt.Println("runner.cwd:", runner.Cwd)
fmt.Println("runner.hostname:", runner.Hostname)
fmt.Println("runner.fd_limits:", runner.FdLimits())
fmt.Println("runner.vm_limits:", runner.VMLimits())
}

View File

@@ -17,7 +17,7 @@ import (
var (
showVersion = flag.Bool("version", false, "Show version.")
configDir = flag.String("configs", osx.GetEnv("N9E_CONFIGS", "etc"), "Specify configuration directory.(env:N9E_CONFIGS)")
configDir = flag.String("configs", osx.GetEnv("N9E_PUSHGW_CONFIGS", "etc"), "Specify configuration directory.(env:N9E_PUSHGW_CONFIGS)")
cryptoKey = flag.String("crypto-key", "", "Specify the secret key for configuration file field encryption.")
)

View File

@@ -2,6 +2,7 @@ package conf
import (
"fmt"
"net"
"os"
"strings"
@@ -16,21 +17,41 @@ import (
)
type ConfigType struct {
Global GlobalConfig
Log logx.Config
HTTP httpx.Config
DB ormx.DBConfig
Redis storage.RedisConfig
Global GlobalConfig
Log logx.Config
HTTP httpx.Config
DB ormx.DBConfig
Redis storage.RedisConfig
CenterApi CenterApi
Pushgw pconf.Pushgw
Alert aconf.Alert
Center cconf.Center
Ibex Ibex
}
type CenterApi struct {
Addrs []string
BasicAuthUser string
BasicAuthPass string
Timeout int64
}
type GlobalConfig struct {
RunMode string
}
type Ibex struct {
Enable bool
RPCListen string
Output Output
}
type Output struct {
ComeFrom string
AgtdPort int
}
func InitConfig(configDir, cryptoKey string) (*ConfigType, error) {
var config = new(ConfigType)
@@ -39,7 +60,7 @@ func InitConfig(configDir, cryptoKey string) (*ConfigType, error) {
}
config.Pushgw.PreCheck()
config.Alert.PreCheck()
config.Alert.PreCheck(configDir)
config.Center.PreCheck()
err := decryptConfig(config, cryptoKey)
@@ -49,28 +70,36 @@ func InitConfig(configDir, cryptoKey string) (*ConfigType, error) {
if config.Alert.Heartbeat.IP == "" {
// auto detect
// config.Alert.Heartbeat.IP = fmt.Sprint(GetOutboundIP())
// 自动获取IP在有些环境下容易出错这里用hostname+pid来作唯一标识
config.Alert.Heartbeat.IP = fmt.Sprint(GetOutboundIP())
if config.Alert.Heartbeat.IP == "" {
hostname, err := os.Hostname()
if err != nil {
fmt.Println("failed to get hostname:", err)
os.Exit(1)
}
hostname, err := os.Hostname()
if err != nil {
fmt.Println("failed to get hostname:", err)
os.Exit(1)
if strings.Contains(hostname, "localhost") {
fmt.Println("Warning! hostname contains substring localhost, setting a more unique hostname is recommended")
}
config.Alert.Heartbeat.IP = hostname
}
if strings.Contains(hostname, "localhost") {
fmt.Println("Warning! hostname contains substring localhost, setting a more unique hostname is recommended")
}
config.Alert.Heartbeat.IP = hostname
// if config.Alert.Heartbeat.IP == "" {
// fmt.Println("heartbeat ip auto got is blank")
// os.Exit(1)
// }
}
config.Alert.Heartbeat.Endpoint = fmt.Sprintf("%s:%d", config.Alert.Heartbeat.IP, config.HTTP.Port)
return config, nil
}
func GetOutboundIP() net.IP {
conn, err := net.Dial("udp", "223.5.5.5:80")
if err != nil {
fmt.Println("auto get outbound ip fail:", err)
return []byte{}
}
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP
}

View File

@@ -14,39 +14,22 @@ func decryptConfig(config *ConfigType, cryptoKey string) error {
config.DB.DSN = decryptDsn
for k := range config.HTTP.Alert.BasicAuth {
decryptPwd, err := secu.DealWithDecrypt(config.HTTP.Alert.BasicAuth[k], cryptoKey)
for k := range config.HTTP.APIForService.BasicAuth {
decryptPwd, err := secu.DealWithDecrypt(config.HTTP.APIForService.BasicAuth[k], cryptoKey)
if err != nil {
return fmt.Errorf("failed to decrypt http basic auth password: %s", err)
}
config.HTTP.Alert.BasicAuth[k] = decryptPwd
config.HTTP.APIForService.BasicAuth[k] = decryptPwd
}
for k := range config.HTTP.Pushgw.BasicAuth {
decryptPwd, err := secu.DealWithDecrypt(config.HTTP.Pushgw.BasicAuth[k], cryptoKey)
for k := range config.HTTP.APIForAgent.BasicAuth {
decryptPwd, err := secu.DealWithDecrypt(config.HTTP.APIForAgent.BasicAuth[k], cryptoKey)
if err != nil {
return fmt.Errorf("failed to decrypt http basic auth password: %s", err)
}
config.HTTP.Pushgw.BasicAuth[k] = decryptPwd
}
for k := range config.HTTP.Heartbeat.BasicAuth {
decryptPwd, err := secu.DealWithDecrypt(config.HTTP.Heartbeat.BasicAuth[k], cryptoKey)
if err != nil {
return fmt.Errorf("failed to decrypt http basic auth password: %s", err)
}
config.HTTP.Heartbeat.BasicAuth[k] = decryptPwd
}
for k := range config.HTTP.Service.BasicAuth {
decryptPwd, err := secu.DealWithDecrypt(config.HTTP.Service.BasicAuth[k], cryptoKey)
if err != nil {
return fmt.Errorf("failed to decrypt http basic auth password: %s", err)
}
config.HTTP.Service.BasicAuth[k] = decryptPwd
config.HTTP.APIForAgent.BasicAuth[k] = decryptPwd
}
for i, v := range config.Pushgw.Writers {

147
doc/README.bak.md Normal file
View File

@@ -0,0 +1,147 @@
<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">
<b>All-in-one</b> 的开源观测平台 <br/>
<b>开箱即用</b>,集数据采集、可视化、监控告警于一体 <br/>
推荐升级您的 <b>Prometheus + AlertManager + Grafana + ELK + Jaeger</b> 组合方案到夜莺!
</p>
[English](./README_en.md) | [中文](./README.md)
## 功能和特点
- **开箱即用**
- 支持 Docker、Helm Chart、云服务等多种部署方式集数据采集、监控告警、可视化为一体内置多种监控仪表盘、快捷视图、告警规则模板导入即可快速使用**大幅降低云原生监控系统的建设成本、学习成本、使用成本**
- **专业告警**
- 可视化的告警配置和管理,支持丰富的告警规则,提供屏蔽规则、订阅规则的配置能力,支持告警多种送达渠道,支持告警自愈、告警事件管理等;
- **推荐您使用夜莺的同时,无缝搭配[FlashDuty](https://flashcat.cloud/product/flashcat-duty/),实现告警聚合收敛、认领、升级、排班、协同,让告警的触达既高效,又确保告警处理不遗漏、做到件件有回响**。
- **云原生**
- 以交钥匙的方式快速构建企业级的云原生监控体系,支持 [Categraf](https://github.com/flashcatcloud/categraf)、Telegraf、Grafana-agent 等多种采集器,支持 Prometheus、VictoriaMetrics、M3DB、ElasticSearch、Jaeger 等多种数据源,兼容支持导入 Grafana 仪表盘,**与云原生生态无缝集成**
- **高性能 高可用**
- 得益于夜莺的多数据源管理引擎,和夜莺引擎侧优秀的架构设计,借助于高性能时序库,可以满足数亿时间线的采集、存储、告警分析场景,节省大量成本;
- 夜莺监控组件均可水平扩展,无单点,已在上千家企业部署落地,经受了严苛的生产实践检验。众多互联网头部公司,夜莺集群机器达百台,处理数亿级时间线,重度使用夜莺监控;
- **灵活扩展 中心化管理**
- 夜莺监控,可部署在 1 核 1G 的云主机,可在上百台机器集群化部署,可运行在 K8s 中;也可将时序库、告警引擎等组件下沉到各机房、各 Region兼顾边缘部署和中心化统一管理**解决数据割裂,缺乏统一视图的难题**
- **开放社区**
- 托管于[中国计算机学会开源发展委员会](https://www.ccf.org.cn/kyfzwyh/),有[快猫星云](https://flashcat.cloud)和众多公司的持续投入,和数千名社区用户的积极参与,以及夜莺监控项目清晰明确的定位,都保证了夜莺开源社区健康、长久的发展。活跃、专业的社区用户也在持续迭代和沉淀更多的最佳实践于产品中;
## 使用场景
1. **如果您希望在一个平台中,统一管理和查看 Metrics、Logging、Tracing 数据,推荐你使用夜莺**
- 请参考阅读:[不止于监控,夜莺 V6 全新升级为开源观测平台](http://flashcat.cloud/blog/nightingale-v6-release/)
2. **如果您在使用 Prometheus 过程中,有以下的一个或者多个需求场景,推荐您无缝升级到夜莺**
- Prometheus、Alertmanager、Grafana 等多个系统较为割裂,缺乏统一视图,无法开箱即用;
- 通过修改配置文件来管理 Prometheus、Alertmanager 的方式,学习曲线大,协同有难度;
- 数据量过大而无法扩展您的 Prometheus 集群;
- 生产环境运行多套 Prometheus 集群,面临管理和使用成本高的问题;
3. **如果您在使用 Zabbix有以下的场景推荐您升级到夜莺**
- 监控的数据量太大,希望有更好的扩展解决方案;
- 学习曲线高,多人多团队模式下,希望有更好的协同使用效率;
- 微服务和云原生架构下监控数据的生命周期多变、监控数据维度基数高Zabbix 数据模型不易适配;
- 了解更多Zabbix和夜莺监控的对比推荐您进一步阅读[Zabbix 和夜莺监控选型对比](https://flashcat.cloud/blog/zabbx-vs-nightingale/)
4. **如果您在使用 [Open-Falcon](https://github.com/open-falcon/falcon-plus),我们推荐您升级到夜莺:**
- 关于 Open-Falcon 和夜莺的详细介绍,请参考阅读:[云原生监控的十个特点和趋势](http://flashcat.cloud/blog/10-trends-of-cloudnative-monitoring/)
- 监控系统和可观测平台的区别,请参考阅读:[从监控系统到可观测平台Gap有多大
](https://flashcat.cloud/blog/gap-of-monitoring-to-o11y/)
5. **我们推荐您使用 [Categraf](https://github.com/flashcatcloud/categraf) 作为首选的监控数据采集器**
- [Categraf](https://github.com/flashcatcloud/categraf) 是夜莺监控的默认采集器,采用开放插件机制和 All-in-one 的设计理念,同时支持 metric、log、trace、event 的采集。Categraf 不仅可以采集 CPU、内存、网络等系统层面的指标也集成了众多开源组件的采集能力支持K8s生态。Categraf 内置了对应的仪表盘和告警规则,开箱即用。
## 文档
[English Doc](https://n9e.github.io/) | [中文文档](https://flashcat.cloud/docs/)
## 产品示意图
https://user-images.githubusercontent.com/792850/216888712-2565fcea-9df5-47bd-a49e-d60af9bd76e8.mp4
## 夜莺架构
夜莺监控可以接收各种采集器上报的监控数据(比如 [Categraf](https://github.com/flashcatcloud/categraf)、telegraf、grafana-agent、Prometheus并写入多种流行的时序数据库中可以支持Prometheus、M3DB、VictoriaMetrics、Thanos、TDEngine等提供告警规则、屏蔽规则、订阅规则的配置能力提供监控数据的查看能力提供告警自愈机制告警触发之后自动回调某个webhook地址或者执行某个脚本提供历史告警事件的存储管理、分组查看的能力。
### 中心汇聚式部署方案
![中心汇聚式部署方案](https://download.flashcat.cloud/ulric/20230327133406.png)
夜莺只有一个模块,就是 n9e可以部署多个 n9e 实例组成集群n9e 依赖 2 个存储数据库、Redis数据库可以使用 MySQL 或 Postgres自己按需选用。
n9e 提供的是 HTTP 接口,前面负载均衡可以是 4 层的,也可以是 7 层的。一般就选用 Nginx 就可以了。
n9e 这个模块接收到数据之后,需要转发给后端的时序库,相关配置是:
```toml
[Pushgw]
LabelRewrite = true
[[Pushgw.Writers]]
Url = "http://127.0.0.1:9090/api/v1/write"
```
> 注意:虽然数据源可以在页面配置了,但是上报转发链路,还是需要在配置文件指定。
所有机房的 agent 比如 Categraf、Telegraf、 Grafana-agent、Datadog-agent ),都直接推数据给 n9e这个架构最为简单维护成本最低。当然前提是要求机房之间网络链路比较好一般有专线。如果网络链路不好则要使用下面的部署方式了。
### 边缘下沉式混杂部署方案
![边缘下沉式混杂部署方案](https://download.flashcat.cloud/ulric/20230327135615.png)
这个图尝试解释 3 种不同的情形,比如 A 机房和中心网络链路很好Categraf 可以直接汇报数据给中心 n9e 模块,另一个机房网络链路不好,就需要把时序库下沉部署,时序库下沉了,对应的告警引擎和转发网关也都要跟随下沉,这样数据不会跨机房传输,比较稳定。但是心跳还是需要往中心心跳,要不然在对象列表里看不到机器的 CPU、内存使用率。还有的时候可能是接入的一个已有的 Prometheus数据采集没有走 Categraf那此时只需要把 Prometheus 作为数据源接入夜莺即可,可以在夜莺里看图、配告警规则,但是就是在对象列表里看不到,也不能使用告警自愈的功能,问题也不大,核心功能都不受影响。
边缘机房下沉部署时序库、告警引擎、转发网关的时候要注意告警引擎需要依赖数据库因为要同步告警规则转发网关也要依赖数据库因为要注册对象到数据库里去需要打通相关网络告警引擎和转发网关都不用Redis所以无需为 Redis 打通网络。
### VictoriaMetrics 集群架构
<img src="doc/img/install-vm.png" width="600">
如果单机版本的时序数据库(比如 Prometheus 性能有瓶颈或容灾较差,我们推荐使用 [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)VictoriaMetrics 架构较为简单性能优异易于部署和运维架构图如上。VictoriaMetrics 更详尽的文档,还请参考其[官网](https://victoriametrics.com/)。
## 夜莺社区
开源项目要更有生命力,离不开开放的治理架构和源源不断的开发者和用户共同参与,我们致力于建立开放、中立的开源治理架构,吸纳更多来自企业、高校等各方面对云原生监控感兴趣、有热情的开发者,一起打造有活力的夜莺开源社区。关于《夜莺开源项目和社区治理架构(草案)》,请查阅 [COMMUNITY GOVERNANCE](./doc/community-governance.md).
**我们欢迎您以各种方式参与到夜莺开源项目和开源社区中来,工作包括不限于**
- 补充和完善文档 => [n9e.github.io](https://n9e.github.io/)
- 分享您在使用夜莺监控过程中的最佳实践和经验心得 => [文章分享](https://flashcat.cloud/docs/content/flashcat-monitor/nightingale/share/)
- 提交产品建议 =》 [github issue](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Ffeature&template=enhancement.md)
- 提交代码,让夜莺监控更快、更稳、更好用 => [github pull request](https://github.com/didi/nightingale/pulls)
**尊重、认可和记录每一位贡献者的工作**是夜莺开源社区的第一指导原则,我们提倡**高效的提问**,这既是对开发者时间的尊重,也是对整个社区知识沉淀的贡献:
- 提问之前请先查阅 [FAQ](https://www.gitlink.org.cn/ccfos/nightingale/wiki/faq)
- 我们使用[论坛](https://answer.flashcat.cloud/)进行交流,有问题可以到这里搜索、提问
## Who is using Nightingale
您可以通过在 **[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)
## 加入交流群
<img src="doc/img/wecom.png" width="120">

View File

@@ -77,4 +77,3 @@ Committer 记录并公示于 **[COMMITTERS](https://github.com/ccfos/nightingale
2. 提问之前请先搜索 [Github Issues](https://github.com/ccfos/nightingale/issues "Github Issue")
3. 我们优先推荐通过提交 [Github Issue](https://github.com/ccfos/nightingale/issues "Github Issue") 来提问,如果[有问题点击这里](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Fbug&template=bug_report.yml "有问题点击这里") | [有需求建议点击这里](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Ffeature&template=enhancement.md "有需求建议点击这里")
最后,我们推荐你加入微信群,针对相关开放式问题,相互交流咨询 (请先加好友:[UlricGO](https://www.gitlink.org.cn/UlricQin/gist/tree/master/self.jpeg "UlricGO") 备注:夜莺加群+姓名+公司,交流群里会有开发者团队和专业、热心的群友回答问题)。

BIN
doc/img/Nightingale_L_V.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

BIN
doc/img/n9e-arch-latest.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 KiB

View File

@@ -1,7 +1,5 @@
ibexetc
compose-host-network
compose-postgres
compose-bridge
initsql
mysqletc
n9eetc
prometc
build.sh
docker-compose.yaml

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