Compare commits
231 Commits
refactor_h
...
v6.0.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c4775fd38 | ||
|
|
212e0aa4c3 | ||
|
|
05300ec0e9 | ||
|
|
67fb49e54e | ||
|
|
7164b696b1 | ||
|
|
8728167733 | ||
|
|
6e80a63b68 | ||
|
|
9e43a22ec3 | ||
|
|
49d8ed4a6f | ||
|
|
c7b537e6c7 | ||
|
|
f1cdd2fa46 | ||
|
|
3d5ad02274 | ||
|
|
1cb9f4becf | ||
|
|
0d0dafbe49 | ||
|
|
048d1df2d1 | ||
|
|
4fb4154e30 | ||
|
|
0be69bbccd | ||
|
|
7015a40256 | ||
|
|
03cca642e9 | ||
|
|
579fd3780b | ||
|
|
a85d91c10e | ||
|
|
af31c496a1 | ||
|
|
f9efbaa954 | ||
|
|
d541ec7f20 | ||
|
|
1d847e2c6f | ||
|
|
2fedf4f075 | ||
|
|
e9a02c4c80 | ||
|
|
8beaccdded | ||
|
|
af6003da6d | ||
|
|
76ac2cd013 | ||
|
|
859876e3f8 | ||
|
|
7d49e7fb34 | ||
|
|
6c42ae9077 | ||
|
|
15dcc60407 | ||
|
|
5b811b7003 | ||
|
|
55d670fe3c | ||
|
|
ac3a5e52c7 | ||
|
|
2abe00e251 | ||
|
|
1bd3c29e39 | ||
|
|
1a8087bda7 | ||
|
|
72b4c2b1ec | ||
|
|
38e6820d7b | ||
|
|
765b3a57fe | ||
|
|
1c4a32f8fa | ||
|
|
3f258fcebf | ||
|
|
140f2cbfa8 | ||
|
|
6aacd77492 | ||
|
|
ef3f46f8b7 | ||
|
|
0cdd25d2cf | ||
|
|
5d02ce0636 | ||
|
|
0cd1228ba7 | ||
|
|
0595401d14 | ||
|
|
d724f8cc8e | ||
|
|
a3f5d458d7 | ||
|
|
76bfb130b0 | ||
|
|
184bb78e3b | ||
|
|
6a41af2cb2 | ||
|
|
faa149cc87 | ||
|
|
24592fe480 | ||
|
|
4be53082e0 | ||
|
|
ae8c9c668c | ||
|
|
b0c15af04f | ||
|
|
c05b710aff | ||
|
|
4299c48aef | ||
|
|
ae0523dec0 | ||
|
|
e18a6bda7b | ||
|
|
e64be95f1c | ||
|
|
a1aa0150f8 | ||
|
|
32f9cb5996 | ||
|
|
3b7e692b01 | ||
|
|
6491eba1da | ||
|
|
bb7ea7e809 | ||
|
|
169930e3b8 | ||
|
|
8e14047f36 | ||
|
|
fd29a96f7b | ||
|
|
820c12f230 | ||
|
|
ff3550e7b3 | ||
|
|
b65e43351d | ||
|
|
3fb74b632b | ||
|
|
253e54344d | ||
|
|
f1ee7d24a6 | ||
|
|
475673b3e7 | ||
|
|
dd49afef01 | ||
|
|
d0c842fe87 | ||
|
|
b873bd161e | ||
|
|
60b76b9ccc | ||
|
|
ef39ee2f66 | ||
|
|
6c83c2ef9b | ||
|
|
9495ec67ab | ||
|
|
bb5680f6c4 | ||
|
|
acbe49f518 | ||
|
|
9dd55938c2 | ||
|
|
5433e6e27e | ||
|
|
2dd6eb5f0f | ||
|
|
1731713dbb | ||
|
|
327ddb7bad | ||
|
|
9e4adc1fa2 | ||
|
|
bce7fdb470 | ||
|
|
b79422962c | ||
|
|
e5989ae5c2 | ||
|
|
64feafa3a6 | ||
|
|
52e4fa4d0d | ||
|
|
6462c02861 | ||
|
|
c657182659 | ||
|
|
04d93eff34 | ||
|
|
40d60aeb4a | ||
|
|
ac875fa1b9 | ||
|
|
b7c3e8a4f5 | ||
|
|
2524e15947 | ||
|
|
995c579403 | ||
|
|
848b7ac1ae | ||
|
|
9476b5ba7c | ||
|
|
7b58696bdc | ||
|
|
6159178d99 | ||
|
|
99e5e0c117 | ||
|
|
be1a3c1d8b | ||
|
|
f6378b055c | ||
|
|
2574bb19cd | ||
|
|
aa9d43cc69 | ||
|
|
d7f18ebec1 | ||
|
|
b40f6976bb | ||
|
|
cd1db57b7c | ||
|
|
5a6ca42c75 | ||
|
|
80874a743c | ||
|
|
6cc612564f | ||
|
|
909bbb5e66 | ||
|
|
ff3ea7de58 | ||
|
|
dd316e6ce1 | ||
|
|
ba893e77cd | ||
|
|
21904f1e39 | ||
|
|
b5d5ecbab2 | ||
|
|
ee612908ac | ||
|
|
2ee04dffac | ||
|
|
be25adf990 | ||
|
|
ab72b6e1ba | ||
|
|
a4718e7a45 | ||
|
|
f948d50d8b | ||
|
|
cb797d5913 | ||
|
|
8941c192de | ||
|
|
5b726c1e61 | ||
|
|
03871a0bf0 | ||
|
|
e002e9cb8f | ||
|
|
d414831c79 | ||
|
|
89807ada94 | ||
|
|
351a31b079 | ||
|
|
af0127c905 | ||
|
|
95612e7140 | ||
|
|
a338b5233c | ||
|
|
ad26225f63 | ||
|
|
16db570f18 | ||
|
|
97c68360a1 | ||
|
|
00192b9d0f | ||
|
|
e745253d08 | ||
|
|
76905c55d5 | ||
|
|
d4bce5456b | ||
|
|
58136d30e6 | ||
|
|
563fb0330a | ||
|
|
c2ab3b4240 | ||
|
|
f5dde6e4d6 | ||
|
|
a9779703dd | ||
|
|
9f4a9e77ae | ||
|
|
df37071c3d | ||
|
|
fa164ac5d2 | ||
|
|
f5de4c3f22 | ||
|
|
dd9099af0a | ||
|
|
5bdb63a818 | ||
|
|
8a4c709e87 | ||
|
|
75f6e07c40 | ||
|
|
de9b11a049 | ||
|
|
067b3f91a7 | ||
|
|
5d215a89b6 | ||
|
|
63679c15dd | ||
|
|
38229a43dc | ||
|
|
1d1ae238d4 | ||
|
|
c2d300c0f1 | ||
|
|
bcb89017a0 | ||
|
|
e04a3eed5f | ||
|
|
e77cf40938 | ||
|
|
cb66b19d70 | ||
|
|
9edf05c19a | ||
|
|
6a6b4a2283 | ||
|
|
0473bb3925 | ||
|
|
4afc3a60a4 | ||
|
|
e9c9a3ac58 | ||
|
|
98260e239e | ||
|
|
f751b2034d | ||
|
|
9ce22a33f0 | ||
|
|
3da64ca0fe | ||
|
|
9a883dc02c | ||
|
|
5ab6fe7e56 | ||
|
|
c730eaa860 | ||
|
|
5ba2d6bc8e | ||
|
|
64feee79ff | ||
|
|
c490ab09ad | ||
|
|
61762e894c | ||
|
|
ac4ff33dff | ||
|
|
72abeea51f | ||
|
|
6ec2b42669 | ||
|
|
a93e967d30 | ||
|
|
b5984b7871 | ||
|
|
70ccbbc929 | ||
|
|
79d4fc508c | ||
|
|
794f0f874f | ||
|
|
aff53e8be3 | ||
|
|
2de6847323 | ||
|
|
eed037a3a1 | ||
|
|
4099c467bb | ||
|
|
6b51adbc9a | ||
|
|
307be1dda2 | ||
|
|
7da6145ec6 | ||
|
|
0e4298a592 | ||
|
|
037fab74eb | ||
|
|
fb849928c9 | ||
|
|
7833aae0a1 | ||
|
|
6edd71b1f0 | ||
|
|
2f2f310a40 | ||
|
|
14bfdaa2ee | ||
|
|
ffd0a69e43 | ||
|
|
5b79d0ef46 | ||
|
|
8f2a885a7d | ||
|
|
31f6300c16 | ||
|
|
54710c22f0 | ||
|
|
352aa2b6b1 | ||
|
|
624e5b5e62 | ||
|
|
65e3b5c8f1 | ||
|
|
750732f203 | ||
|
|
9957711643 | ||
|
|
8f4fb0d28b | ||
|
|
5d63f23cfc | ||
|
|
c0fb8d22db | ||
|
|
1732b297b1 |
4
.gitignore
vendored
@@ -31,6 +31,9 @@ _test
|
||||
/etc/*.local.yml
|
||||
/etc/*.local.conf
|
||||
/etc/plugins/*.local.yml
|
||||
/etc/script/rules.yaml
|
||||
/etc/script/alert-rules.json
|
||||
/etc/script/record-rules.json
|
||||
/data*
|
||||
/tarball
|
||||
/run
|
||||
@@ -43,6 +46,7 @@ _test
|
||||
/docker/mysqldata
|
||||
/docker/experience_pg_vm/pgdata
|
||||
/etc.local*
|
||||
/front/statik/statik.go
|
||||
|
||||
.alerts
|
||||
.idea
|
||||
|
||||
@@ -15,7 +15,8 @@ builds:
|
||||
- id: build
|
||||
hooks:
|
||||
pre:
|
||||
- ./fe.sh
|
||||
- cmd: sh -x ./fe.sh
|
||||
output: true
|
||||
main: ./cmd/center/
|
||||
binary: n9e
|
||||
env:
|
||||
@@ -41,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:
|
||||
@@ -73,8 +61,7 @@ archives:
|
||||
builds:
|
||||
- build
|
||||
- build-cli
|
||||
- build-alert
|
||||
- build-pushgw
|
||||
- build-edge
|
||||
format: tar.gz
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
@@ -84,7 +71,6 @@ archives:
|
||||
files:
|
||||
- docker/*
|
||||
- etc/*
|
||||
- pub/*
|
||||
- integrations/*
|
||||
- cli/*
|
||||
- n9e.sql
|
||||
@@ -104,7 +90,6 @@ dockers:
|
||||
- build
|
||||
dockerfile: docker/Dockerfile.goreleaser
|
||||
extra_files:
|
||||
- pub
|
||||
- etc
|
||||
- integrations
|
||||
use: buildx
|
||||
@@ -118,7 +103,6 @@ dockers:
|
||||
- build
|
||||
dockerfile: docker/Dockerfile.goreleaser.arm64
|
||||
extra_files:
|
||||
- pub
|
||||
- etc
|
||||
- integrations
|
||||
use: buildx
|
||||
|
||||
13
Makefile
@@ -1,4 +1,4 @@
|
||||
.PHONY: prebuild start build
|
||||
.PHONY: prebuild build
|
||||
|
||||
ROOT:=$(shell pwd -P)
|
||||
GIT_COMMIT:=$(shell git --work-tree ${ROOT} rev-parse 'HEAD^{commit}')
|
||||
@@ -6,16 +6,19 @@ _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: prebuild build
|
||||
|
||||
prebuild:
|
||||
echo "begin download and embed the front-end file..."
|
||||
sh fe.sh
|
||||
echo "front-end file download and embedding completed."
|
||||
|
||||
all: build
|
||||
|
||||
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
|
||||
|
||||
@@ -28,10 +31,10 @@ build-cli:
|
||||
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:
|
||||
|
||||
82
README.md
@@ -20,36 +20,77 @@
|
||||
<img alt="License" src="https://img.shields.io/badge/license-Apache--2.0-blue"/>
|
||||
</p>
|
||||
<p align="center">
|
||||
告警管理专家,一体化开源观测平台!
|
||||
An open-source cloud-native monitoring system that is <b>all-in-one</b> <br/>
|
||||
<b>Out-of-the-box</b>, it integrates data collection, visualization, and monitoring alert <br/>
|
||||
We recommend upgrading your <b>Prometheus + AlertManager + Grafana</b> combination to Nightingale!
|
||||
</p>
|
||||
|
||||
[English](./README_en.md) | [中文](./README.md)
|
||||
[English](./README.md) | [中文](./README_zh.md)
|
||||
|
||||
## 资料
|
||||
|
||||
- 文档:[https://flashcat.cloud/docs/](https://flashcat.cloud/docs/)
|
||||
- 论坛提问:[https://answer.flashcat.cloud/](https://answer.flashcat.cloud/)
|
||||
- 报Bug:[https://github.com/ccfos/nightingale/issues](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Fbug&projects=&template=bug_report.yml)
|
||||
- 商业版本:[企业版](https://mp.weixin.qq.com/s/FOwnnGPkRao2ZDV574EHrw) | [专业版](https://mp.weixin.qq.com/s/uM2a8QUDJEYwdBpjkbQDxA) 感兴趣请 [联系我们交流试用](https://flashcat.cloud/contact/)
|
||||
## Highlighted Features
|
||||
|
||||
## 功能和特点
|
||||
- **Out-of-the-box**
|
||||
- Supports multiple deployment methods such as **Docker, Helm Chart, and cloud services**, integrates data collection, monitoring, and alerting into one system, and comes with various monitoring dashboards, quick views, and alert rule templates. **It greatly reduces the construction cost, learning cost, and usage cost of cloud-native monitoring systems**.
|
||||
- **Professional Alerting**
|
||||
- Provides visual alert configuration and management, supports various alert rules, offers the ability to configure silence and subscription rules, supports multiple alert delivery channels, and has features such as alert self-healing and event management.
|
||||
- **Cloud-Native**
|
||||
- Quickly builds an enterprise-level cloud-native monitoring system through a turnkey approach, supports multiple collectors such as [Categraf](https://github.com/flashcatcloud/categraf), Telegraf, and Grafana-agent, supports multiple data sources such as Prometheus, VictoriaMetrics, M3DB, ElasticSearch, and Jaeger, and is compatible with importing Grafana dashboards. **It seamlessly integrates with the cloud-native ecosystem**.
|
||||
- **High Performance and High Availability**
|
||||
- Due to the multi-data-source management engine of Nightingale and its excellent architecture design, and utilizing a high-performance time-series database, it can handle data collection, storage, and alert analysis scenarios with billions of time-series data, saving a lot of costs.
|
||||
- Nightingale components can be horizontally scaled with no single point of failure. It has been deployed in thousands of enterprises and tested in harsh production practices. Many leading Internet companies have used Nightingale for cluster machines with hundreds of nodes, processing billions of time-series data.
|
||||
- **Flexible Extension and Centralized Management**
|
||||
- Nightingale can be deployed on a 1-core 1G cloud host, deployed in a cluster of hundreds of machines, or run in Kubernetes. Time-series databases, alert engines, and other components can also be decentralized to various data centers and regions, balancing edge deployment with centralized management. **It solves the problem of data fragmentation and lack of unified views**.
|
||||
|
||||
- **统一接入各种时序库**:支持对接 Prometheus、VictoriaMetrics、Thanos、Mimir、M3DB 等多种时序库,实现统一告警管理
|
||||
- **专业告警能力**:内置支持多种告警规则,可以扩展支持所有通知媒介,支持告警屏蔽、告警抑制、告警自愈、告警事件管理
|
||||
- **无缝搭配 [FlashDuty](https://flashcat.cloud/product/flashcat-duty/)**:实现告警聚合收敛、认领、升级、排班、IM集成,确保告警处理不遗漏,减少打扰,更好协同
|
||||
- **支持所有常见采集器**:支持 categraf、telegraf、grafana-agent、datadog-agent、给类 exporter 作为采集器,没有什么数据是不能监控的
|
||||
- **统一的观测平台**:从 v6 版本开始,支持接入 ElasticSearch、Jaeger 数据源,逐步实现日志、链路、指标的一体化观测
|
||||
|
||||
## 产品示意图
|
||||
#### If you are using Prometheus and have one or more of the following requirement scenarios, it is recommended that you upgrade to Nightingale:
|
||||
|
||||
- Multiple systems such as Prometheus, Alertmanager, Grafana, etc. are fragmented and lack a unified view and cannot be used out of the box;
|
||||
- The way to manage Prometheus and Alertmanager by modifying configuration files has a big learning curve and is difficult to collaborate;
|
||||
- Too much data to scale-up your Prometheus cluster;
|
||||
- Multiple Prometheus clusters running in production environments, which faced high management and usage costs;
|
||||
|
||||
#### If you are using Zabbix and have the following scenarios, it is recommended that you upgrade to Nightingale:
|
||||
|
||||
- Monitoring too much data and wanting a better scalable solution;
|
||||
- A high learning curve and a desire for better efficiency of collaborative use in a multi-person, multi-team model;
|
||||
- Microservice and cloud-native architectures with variable monitoring data lifecycles and high monitoring data dimension bases, which are not easily adaptable to the Zabbix data model;
|
||||
|
||||
|
||||
#### If you are using [open-falcon](https://github.com/open-falcon/falcon-plus), we recommend you to upgrade to Nightingale:
|
||||
- For more information about open-falcon and Nightingale, please refer to read [Ten features and trends of cloud-native monitoring](https://mp.weixin.qq.com/s?__biz=MzkzNjI5OTM5Nw==&mid=2247483738&idx=1&sn=e8bdbb974a2cd003c1abcc2b5405dd18&chksm=c2a19fb0f5d616a63185cd79277a79a6b80118ef2185890d0683d2bb20451bd9303c78d083c5#rd)。
|
||||
|
||||
## Getting Started
|
||||
|
||||
[https://n9e.github.io/](https://n9e.github.io/)
|
||||
|
||||
## Screenshots
|
||||
|
||||
https://user-images.githubusercontent.com/792850/216888712-2565fcea-9df5-47bd-a49e-d60af9bd76e8.mp4
|
||||
|
||||
## Architecture
|
||||
|
||||
## 加入交流群
|
||||
<img src="doc/img/arch-product.png" width="600">
|
||||
|
||||
欢迎加入 QQ 交流群,群号:479290895,也可以扫下方二维码加入微信交流群:
|
||||
Nightingale monitoring can receive monitoring data reported by various collectors (such as [Categraf](https://github.com/flashcatcloud/categraf) , telegraf, grafana-agent, Prometheus, etc.) and write them to various popular time-series databases (such as Prometheus, M3DB, VictoriaMetrics, Thanos, TDEngine, etc.). It provides configuration capabilities for alert rules, silence rules, and subscription rules, as well as the ability to view monitoring data. It also provides automatic alarm self-healing mechanisms (such as automatically calling back to a webhook address or executing a script after an alarm is triggered), and the ability to store and manage historical alarm events and view them in groups.
|
||||
|
||||
<img src="doc/img/wecom.png" width="240">
|
||||
If the performance of a standalone time-series database (such as Prometheus) has bottlenecks or poor disaster recovery, we recommend using [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics). The VictoriaMetrics architecture is relatively simple, has excellent performance, and is easy to deploy and maintain. The architecture diagram is as shown above. For more detailed documentation on VictoriaMetrics, please refer to its [official website](https://victoriametrics.com/).
|
||||
|
||||
**We welcome you to participate in the Nightingale open-source project and community in various ways, including but not limited to**:
|
||||
- Adding and improving documentation => [n9e.github.io](https://n9e.github.io/)
|
||||
- Sharing your best practices and experience in using Nightingale monitoring => [Article sharing]((https://n9e.github.io/docs/prologue/share/))
|
||||
- Submitting product suggestions => [github issue](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Ffeature&template=enhancement.md)
|
||||
- Submitting code to make Nightingale monitoring faster, more stable, and easier to use => [github pull request](https://github.com/didi/nightingale/pulls)
|
||||
|
||||
|
||||
**Respecting, recognizing, and recording the work of every contributor** is the first guiding principle of the Nightingale open-source community. We advocate effective questioning, which not only respects the developer's time but also contributes to the accumulation of knowledge in the entire community
|
||||
- Before asking a question, please first refer to the [FAQ](https://www.gitlink.org.cn/ccfos/nightingale/wiki/faq)
|
||||
- We use [GitHub Discussions](https://github.com/ccfos/nightingale/discussions) as the communication forum. You can search and ask questions here.
|
||||
- We also recommend that you join ours [Slack channel](https://n9e-talk.slack.com/) to exchange experiences with other Nightingale users.
|
||||
|
||||
|
||||
## Who is using Nightingale
|
||||
You can register your usage and share your experience by posting on **[Who is Using Nightingale](https://github.com/ccfos/nightingale/issues/897)**.
|
||||
|
||||
## Stargazers over time
|
||||
[](https://starchart.cc/ccfos/nightingale)
|
||||
@@ -60,9 +101,4 @@ https://user-images.githubusercontent.com/792850/216888712-2565fcea-9df5-47bd-a4
|
||||
</a>
|
||||
|
||||
## License
|
||||
[Apache License V2.0](https://github.com/didi/nightingale/blob/main/LICENSE)
|
||||
|
||||
## 社区管理
|
||||
|
||||
[夜莺开源项目和社区治理架构(草案)](./doc/community-governance.md)
|
||||
|
||||
[Apache License V2.0](https://github.com/didi/nightingale/blob/main/LICENSE)
|
||||
104
README_en.md
@@ -1,104 +0,0 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/ccfos/nightingale">
|
||||
<img src="doc/img/nightingale_logo_h.png" alt="nightingale - cloud native monitoring" width="240" /></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img alt="GitHub latest release" src="https://img.shields.io/github/v/release/ccfos/nightingale"/>
|
||||
<a href="https://n9e.github.io">
|
||||
<img alt="Docs" src="https://img.shields.io/badge/docs-get%20started-brightgreen"/></a>
|
||||
<a href="https://hub.docker.com/u/flashcatcloud">
|
||||
<img alt="Docker pulls" src="https://img.shields.io/docker/pulls/flashcatcloud/nightingale"/></a>
|
||||
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/ccfos/nightingale">
|
||||
<img alt="GitHub Repo issues" src="https://img.shields.io/github/issues/ccfos/nightingale">
|
||||
<img alt="GitHub Repo issues closed" src="https://img.shields.io/github/issues-closed/ccfos/nightingale">
|
||||
<img alt="GitHub forks" src="https://img.shields.io/github/forks/ccfos/nightingale">
|
||||
<a href="https://github.com/ccfos/nightingale/graphs/contributors">
|
||||
<img alt="GitHub contributors" src="https://img.shields.io/github/contributors-anon/ccfos/nightingale"/></a>
|
||||
<a href="https://n9e-talk.slack.com/">
|
||||
<img alt="GitHub contributors" src="https://img.shields.io/badge/join%20slack-%23n9e-brightgreen.svg"/></a>
|
||||
<img alt="License" src="https://img.shields.io/badge/license-Apache--2.0-blue"/>
|
||||
</p>
|
||||
<p align="center">
|
||||
An open-source cloud-native monitoring system that is <b>all-in-one</b> <br/>
|
||||
<b>Out-of-the-box</b>, it integrates data collection, visualization, and monitoring alert <br/>
|
||||
We recommend upgrading your <b>Prometheus + AlertManager + Grafana</b> combination to Nightingale!
|
||||
</p>
|
||||
|
||||
[English](./README.md) | [中文](./README_ZH.md)
|
||||
|
||||
|
||||
## Highlighted Features
|
||||
|
||||
- **Out-of-the-box**
|
||||
- Supports multiple deployment methods such as **Docker, Helm Chart, and cloud services**, integrates data collection, monitoring, and alerting into one system, and comes with various monitoring dashboards, quick views, and alert rule templates. **It greatly reduces the construction cost, learning cost, and usage cost of cloud-native monitoring systems**.
|
||||
- **Professional Alerting**
|
||||
- Provides visual alert configuration and management, supports various alert rules, offers the ability to configure silence and subscription rules, supports multiple alert delivery channels, and has features such as alert self-healing and event management.
|
||||
- **Cloud-Native**
|
||||
- Quickly builds an enterprise-level cloud-native monitoring system through a turnkey approach, supports multiple collectors such as [Categraf](https://github.com/flashcatcloud/categraf), Telegraf, and Grafana-agent, supports multiple data sources such as Prometheus, VictoriaMetrics, M3DB, ElasticSearch, and Jaeger, and is compatible with importing Grafana dashboards. **It seamlessly integrates with the cloud-native ecosystem**.
|
||||
- **High Performance and High Availability**
|
||||
- Due to the multi-data-source management engine of Nightingale and its excellent architecture design, and utilizing a high-performance time-series database, it can handle data collection, storage, and alert analysis scenarios with billions of time-series data, saving a lot of costs.
|
||||
- Nightingale components can be horizontally scaled with no single point of failure. It has been deployed in thousands of enterprises and tested in harsh production practices. Many leading Internet companies have used Nightingale for cluster machines with hundreds of nodes, processing billions of time-series data.
|
||||
- **Flexible Extension and Centralized Management**
|
||||
- Nightingale can be deployed on a 1-core 1G cloud host, deployed in a cluster of hundreds of machines, or run in Kubernetes. Time-series databases, alert engines, and other components can also be decentralized to various data centers and regions, balancing edge deployment with centralized management. **It solves the problem of data fragmentation and lack of unified views**.
|
||||
|
||||
|
||||
#### If you are using Prometheus and have one or more of the following requirement scenarios, it is recommended that you upgrade to Nightingale:
|
||||
|
||||
- Multiple systems such as Prometheus, Alertmanager, Grafana, etc. are fragmented and lack a unified view and cannot be used out of the box;
|
||||
- The way to manage Prometheus and Alertmanager by modifying configuration files has a big learning curve and is difficult to collaborate;
|
||||
- Too much data to scale-up your Prometheus cluster;
|
||||
- Multiple Prometheus clusters running in production environments, which faced high management and usage costs;
|
||||
|
||||
#### If you are using Zabbix and have the following scenarios, it is recommended that you upgrade to Nightingale:
|
||||
|
||||
- Monitoring too much data and wanting a better scalable solution;
|
||||
- A high learning curve and a desire for better efficiency of collaborative use in a multi-person, multi-team model;
|
||||
- Microservice and cloud-native architectures with variable monitoring data lifecycles and high monitoring data dimension bases, which are not easily adaptable to the Zabbix data model;
|
||||
|
||||
|
||||
#### If you are using [open-falcon](https://github.com/open-falcon/falcon-plus), we recommend you to upgrade to Nightingale:
|
||||
- For more information about open-falcon and Nightingale, please refer to read [Ten features and trends of cloud-native monitoring](https://mp.weixin.qq.com/s?__biz=MzkzNjI5OTM5Nw==&mid=2247483738&idx=1&sn=e8bdbb974a2cd003c1abcc2b5405dd18&chksm=c2a19fb0f5d616a63185cd79277a79a6b80118ef2185890d0683d2bb20451bd9303c78d083c5#rd)。
|
||||
|
||||
## Getting Started
|
||||
|
||||
[English Doc](https://n9e.github.io/) | [中文文档](http://n9e.flashcat.cloud/)
|
||||
|
||||
## Screenshots
|
||||
|
||||
https://user-images.githubusercontent.com/792850/216888712-2565fcea-9df5-47bd-a49e-d60af9bd76e8.mp4
|
||||
|
||||
## Architecture
|
||||
|
||||
<img src="doc/img/arch-product.png" width="600">
|
||||
|
||||
Nightingale monitoring can receive monitoring data reported by various collectors (such as [Categraf](https://github.com/flashcatcloud/categraf) , telegraf, grafana-agent, Prometheus, etc.) and write them to various popular time-series databases (such as Prometheus, M3DB, VictoriaMetrics, Thanos, TDEngine, etc.). It provides configuration capabilities for alert rules, silence rules, and subscription rules, as well as the ability to view monitoring data. It also provides automatic alarm self-healing mechanisms (such as automatically calling back to a webhook address or executing a script after an alarm is triggered), and the ability to store and manage historical alarm events and view them in groups.
|
||||
|
||||
If the performance of a standalone time-series database (such as Prometheus) has bottlenecks or poor disaster recovery, we recommend using [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics). The VictoriaMetrics architecture is relatively simple, has excellent performance, and is easy to deploy and maintain. The architecture diagram is as shown above. For more detailed documentation on VictoriaMetrics, please refer to its [official website](https://victoriametrics.com/).
|
||||
|
||||
**We welcome you to participate in the Nightingale open-source project and community in various ways, including but not limited to**:
|
||||
- Adding and improving documentation => [n9e.github.io](https://n9e.github.io/)
|
||||
- Sharing your best practices and experience in using Nightingale monitoring => [Article sharing]((https://n9e.github.io/docs/prologue/share/))
|
||||
- Submitting product suggestions => [github issue](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Ffeature&template=enhancement.md)
|
||||
- Submitting code to make Nightingale monitoring faster, more stable, and easier to use => [github pull request](https://github.com/didi/nightingale/pulls)
|
||||
|
||||
|
||||
**Respecting, recognizing, and recording the work of every contributor** is the first guiding principle of the Nightingale open-source community. We advocate effective questioning, which not only respects the developer's time but also contributes to the accumulation of knowledge in the entire community
|
||||
- Before asking a question, please first refer to the [FAQ](https://www.gitlink.org.cn/ccfos/nightingale/wiki/faq)
|
||||
- We use [GitHub Discussions](https://github.com/ccfos/nightingale/discussions) as the communication forum. You can search and ask questions here.
|
||||
- We also recommend that you join ours [Slack channel](https://n9e-talk.slack.com/) to exchange experiences with other Nightingale users.
|
||||
|
||||
|
||||
## Who is using Nightingale
|
||||
You can register your usage and share your experience by posting on **[Who is Using Nightingale](https://github.com/ccfos/nightingale/issues/897)**.
|
||||
|
||||
## Stargazers over time
|
||||
[](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)
|
||||
74
README_zh.md
Normal file
@@ -0,0 +1,74 @@
|
||||
<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">
|
||||
<a href="https://flashcat.cloud/docs/">
|
||||
<img alt="Docs" src="https://img.shields.io/badge/docs-get%20started-brightgreen"/></a>
|
||||
<a href="https://hub.docker.com/u/flashcatcloud">
|
||||
<img alt="Docker pulls" src="https://img.shields.io/docker/pulls/flashcatcloud/nightingale"/></a>
|
||||
<a href="https://github.com/ccfos/nightingale/graphs/contributors">
|
||||
<img alt="GitHub contributors" src="https://img.shields.io/github/contributors-anon/ccfos/nightingale"/></a>
|
||||
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/ccfos/nightingale">
|
||||
<br/><img alt="GitHub Repo issues" src="https://img.shields.io/github/issues/ccfos/nightingale">
|
||||
<img alt="GitHub Repo issues closed" src="https://img.shields.io/github/issues-closed/ccfos/nightingale">
|
||||
<img alt="GitHub forks" src="https://img.shields.io/github/forks/ccfos/nightingale">
|
||||
<img alt="GitHub latest release" src="https://img.shields.io/github/v/release/ccfos/nightingale"/>
|
||||
<img alt="License" src="https://img.shields.io/badge/license-Apache--2.0-blue"/>
|
||||
<a href="https://n9e-talk.slack.com/">
|
||||
<img alt="GitHub contributors" src="https://img.shields.io/badge/join%20slack-%23n9e-brightgreen.svg"/></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
告警管理专家,一体化的开源可观测平台
|
||||
</p>
|
||||
|
||||
[English](./README.md) | [中文](./README_zh.md)
|
||||
|
||||
夜莺Nightingale是中国计算机学会托管的开源云原生可观测工具,最早由滴滴于 2020 年孵化并开源,并于 2022 年正式捐赠予中国计算机学会。夜莺采用 All-in-One 的设计理念,集数据采集、可视化、监控告警、数据分析于一体,与云原生生态紧密集成,融入了顶级互联网公司可观测性最佳实践,沉淀了众多社区专家经验,开箱即用。
|
||||
|
||||
## 资料
|
||||
|
||||
- 文档:[flashcat.cloud/docs](https://flashcat.cloud/docs/)
|
||||
- 提问:[answer.flashcat.cloud](https://answer.flashcat.cloud/)
|
||||
- 报Bug:[github.com/ccfos/nightingale/issues](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Fbug&projects=&template=bug_report.yml)
|
||||
|
||||
|
||||
## 功能和特点
|
||||
|
||||
- 统一接入各种时序库:支持对接 Prometheus、VictoriaMetrics、Thanos、Mimir、M3DB 等多种时序库,实现统一告警管理
|
||||
- 专业告警能力:内置支持多种告警规则,可以扩展支持所有通知媒介,支持告警屏蔽、告警抑制、告警自愈、告警事件管理
|
||||
- 高性能可视化引擎:支持多种图表样式,内置众多Dashboard模版,也可导入Grafana模版,开箱即用,开源协议商业友好
|
||||
- 无缝搭配 [Flashduty](https://flashcat.cloud/product/flashcat-duty/):实现告警聚合收敛、认领、升级、排班、IM集成,确保告警处理不遗漏,减少打扰,更好协同
|
||||
- 支持所有常见采集器:支持 [Categraf](https://flashcat.cloud/product/categraf)、telegraf、grafana-agent、datadog-agent、各种 exporter 作为采集器,没有什么数据是不能监控的
|
||||
- 一体化观测平台:从 v6 版本开始,支持接入 ElasticSearch、Jaeger 数据源,实现日志、链路、指标多维度的统一可观测
|
||||
|
||||
|
||||
## 产品演示
|
||||
|
||||

|
||||
|
||||
## 部署架构
|
||||
|
||||

|
||||
|
||||
## 加入交流群
|
||||
|
||||
欢迎加入 QQ 交流群,群号:479290895,QQ 群适合群友互助,夜莺研发人员通常不在群里。如果要报 bug 请到[这里](https://github.com/ccfos/nightingale/issues/new?assignees=&labels=kind%2Fbug&projects=&template=bug_report.yml),提问到[这里](https://answer.flashcat.cloud/)。
|
||||
|
||||
## Stargazers over time
|
||||
|
||||
[](https://star-history.com/#ccfos/nightingale&Date)
|
||||
|
||||
|
||||
## Contributors
|
||||
<a href="https://github.com/ccfos/nightingale/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=ccfos/nightingale" />
|
||||
</a>
|
||||
|
||||
## 社区治理
|
||||
[夜莺开源项目和社区治理架构(草案)](./doc/community-governance.md)
|
||||
|
||||
## License
|
||||
[Apache License V2.0](https://github.com/didi/nightingale/blob/main/LICENSE)
|
||||
@@ -2,11 +2,10 @@ package aconf
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/toolkits/pkg/runner"
|
||||
)
|
||||
|
||||
type Alert struct {
|
||||
Disable bool
|
||||
EngineDelay int64
|
||||
Heartbeat HeartbeatConfig
|
||||
Alerting Alerting
|
||||
@@ -54,9 +53,9 @@ type Ibex struct {
|
||||
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 +69,8 @@ func (a *Alert) PreCheck() {
|
||||
if a.Heartbeat.EngineName == "" {
|
||||
a.Heartbeat.EngineName = "default"
|
||||
}
|
||||
|
||||
if a.EngineDelay == 0 {
|
||||
a.EngineDelay = 30
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -47,16 +48,19 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
alertRuleCache := memsto.NewAlertRuleCache(ctx, syncStats)
|
||||
notifyConfigCache := memsto.NewNotifyConfigCache(ctx)
|
||||
dsCache := memsto.NewDatasourceCache(ctx, syncStats)
|
||||
userCache := memsto.NewUserCache(ctx, syncStats)
|
||||
userGroupCache := memsto.NewUserGroupCache(ctx, syncStats)
|
||||
|
||||
promClients := prom.NewPromClient(ctx, config.Alert.Heartbeat)
|
||||
|
||||
externalProcessors := process.NewExternalProcessors()
|
||||
|
||||
Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, dsCache, ctx, promClients)
|
||||
Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, dsCache, ctx, promClients, userCache, userGroupCache)
|
||||
|
||||
r := httpx.GinEngine(config.Global.RunMode, config.HTTP)
|
||||
rt := router.New(config.HTTP, config.Alert, alertMuteCache, targetCache, busiGroupCache, alertStats, ctx, externalProcessors)
|
||||
rt.Config(r)
|
||||
dumper.ConfigRouter(r)
|
||||
|
||||
httpClean := httpx.Init(config.HTTP, r)
|
||||
|
||||
@@ -67,9 +71,7 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
}
|
||||
|
||||
func Start(alertc aconf.Alert, pushgwc pconf.Pushgw, syncStats *memsto.Stats, alertStats *astats.Stats, externalProcessors *process.ExternalProcessorsType, targetCache *memsto.TargetCacheType, busiGroupCache *memsto.BusiGroupCacheType,
|
||||
alertMuteCache *memsto.AlertMuteCacheType, alertRuleCache *memsto.AlertRuleCacheType, notifyConfigCache *memsto.NotifyConfigCacheType, datasourceCache *memsto.DatasourceCacheType, ctx *ctx.Context, promClients *prom.PromClientMap) {
|
||||
userCache := memsto.NewUserCache(ctx, syncStats)
|
||||
userGroupCache := memsto.NewUserGroupCache(ctx, syncStats)
|
||||
alertMuteCache *memsto.AlertMuteCacheType, alertRuleCache *memsto.AlertRuleCacheType, notifyConfigCache *memsto.NotifyConfigCacheType, datasourceCache *memsto.DatasourceCacheType, ctx *ctx.Context, promClients *prom.PromClientMap, userCache *memsto.UserCacheType, userGroupCache *memsto.UserGroupCacheType) {
|
||||
alertSubscribeCache := memsto.NewAlertSubscribeCache(ctx, syncStats)
|
||||
recordingRuleCache := memsto.NewRecordingRuleCache(ctx, syncStats)
|
||||
|
||||
@@ -89,5 +91,5 @@ func Start(alertc aconf.Alert, pushgwc pconf.Pushgw, syncStats *memsto.Stats, al
|
||||
go consumer.LoopConsume()
|
||||
|
||||
go queue.ReportQueueSize(alertStats)
|
||||
go sender.StartEmailSender(notifyConfigCache.GetSMTP()) // todo
|
||||
go sender.InitEmailSender(notifyConfigCache.GetSMTP())
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ type AnomalyPoint struct {
|
||||
Value float64 `json:"value"`
|
||||
Severity int `json:"severity"`
|
||||
Triggered bool `json:"triggered"`
|
||||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
func NewAnomalyPoint(key string, labels map[string]string, ts int64, value float64, severity int) AnomalyPoint {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -83,6 +83,10 @@ func (e *Consumer) consumeOne(event *models.AlertCurEvent) {
|
||||
}
|
||||
|
||||
func (e *Consumer) persist(event *models.AlertCurEvent) {
|
||||
if event.Status != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if !e.ctx.IsCenter {
|
||||
event.DB2FE()
|
||||
err := poster.PostByUrls(e.ctx, "/v1/n9e/event-persist", event)
|
||||
|
||||
@@ -28,9 +28,10 @@ type Dispatch struct {
|
||||
|
||||
alerting aconf.Alerting
|
||||
|
||||
senders map[string]sender.Sender
|
||||
tpls map[string]*template.Template
|
||||
ExtraSenders map[string]sender.Sender
|
||||
Senders map[string]sender.Sender
|
||||
tpls map[string]*template.Template
|
||||
ExtraSenders map[string]sender.Sender
|
||||
BeforeSenderHook func(*models.AlertCurEvent) bool
|
||||
|
||||
ctx *ctx.Context
|
||||
|
||||
@@ -51,9 +52,10 @@ func NewDispatch(alertRuleCache *memsto.AlertRuleCacheType, userCache *memsto.Us
|
||||
|
||||
alerting: alerting,
|
||||
|
||||
senders: make(map[string]sender.Sender),
|
||||
tpls: make(map[string]*template.Template),
|
||||
ExtraSenders: make(map[string]sender.Sender),
|
||||
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,
|
||||
}
|
||||
@@ -63,7 +65,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
|
||||
@@ -83,23 +85,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 channel, sender := range e.ExtraSenders {
|
||||
senders[channel] = sender
|
||||
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
|
||||
}
|
||||
@@ -140,7 +143,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 {
|
||||
@@ -167,35 +170,63 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
sub.ModifyEvent(&event)
|
||||
LogEvent(&event, "subscribe")
|
||||
|
||||
event.SubRuleId = sub.Id
|
||||
e.HandleEventNotify(&event, true)
|
||||
}
|
||||
|
||||
func (e *Dispatch) Send(rule *models.AlertRule, event *models.AlertCurEvent, notifyTarget *NotifyTarget, 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.Debugf("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.RwLock.RLock()
|
||||
s := e.Senders[channel]
|
||||
e.RwLock.RUnlock()
|
||||
if s == nil {
|
||||
logger.Debugf("no sender for channel: %s", channel)
|
||||
continue
|
||||
}
|
||||
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
|
||||
|
||||
@@ -85,6 +85,10 @@ func (s *Scheduler) syncAlertRules() {
|
||||
if rule == nil {
|
||||
continue
|
||||
}
|
||||
// 同步rule到Scheduler时,未生效的rule应该直接drop,减少对时序数据库查询次数
|
||||
if rule.TimeSpanMuteStrategy() {
|
||||
continue
|
||||
}
|
||||
if rule.IsPrometheusRule() {
|
||||
datasourceIds := s.promClients.Hit(rule.DatasourceIdsJson)
|
||||
for _, dsId := range datasourceIds {
|
||||
|
||||
@@ -69,14 +69,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)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -163,6 +165,7 @@ func (arw *AlertRuleWorker) GetPromAnomalyPoint(ruleConfig string) []common.Anom
|
||||
points := common.ConvertAnomalyPoints(value)
|
||||
for i := 0; i < len(points); i++ {
|
||||
points[i].Severity = query.Severity
|
||||
points[i].Query = promql
|
||||
}
|
||||
lst = append(lst, points...)
|
||||
}
|
||||
|
||||
@@ -13,10 +13,15 @@ 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
|
||||
}
|
||||
|
||||
// 移到Sync Rule之前就判断
|
||||
// if TimeSpanMuteStrategy(rule, event) {
|
||||
// return true
|
||||
// }
|
||||
|
||||
if IdentNotExistsMuteStrategy(rule, event, targetCache) {
|
||||
return true
|
||||
}
|
||||
@@ -32,41 +37,6 @@ 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
|
||||
}
|
||||
|
||||
tm := time.Unix(event.TriggerTime, 0)
|
||||
triggerTime := tm.Format("15:04")
|
||||
triggerWeek := strconv.Itoa(int(tm.Weekday()))
|
||||
|
||||
enableStime := strings.Fields(rule.EnableStime)
|
||||
enableEtime := strings.Fields(rule.EnableEtime)
|
||||
enableDaysOfWeek := strings.Split(rule.EnableDaysOfWeek, ";")
|
||||
length := len(enableDaysOfWeek)
|
||||
// enableStime,enableEtime,enableDaysOfWeek三者长度肯定相同,这里循环一个即可
|
||||
for i := 0; i < length; i++ {
|
||||
enableDaysOfWeek[i] = strings.Replace(enableDaysOfWeek[i], "7", "0", 1)
|
||||
if !strings.Contains(enableDaysOfWeek[i], triggerWeek) {
|
||||
continue
|
||||
}
|
||||
if enableStime[i] <= enableEtime[i] {
|
||||
if triggerTime < enableStime[i] || triggerTime > enableEtime[i] {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if triggerTime < enableStime[i] && triggerTime > enableEtime[i] {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// 到这里说明当前时刻在告警规则的某组生效时间范围内,直接返回 false
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IdentNotExistsMuteStrategy 根据ident是否存在过滤,如果ident不存在,则target_up的告警直接过滤掉
|
||||
func IdentNotExistsMuteStrategy(rule *models.AlertRule, event *models.AlertCurEvent, targetCache *memsto.TargetCacheType) bool {
|
||||
ident, has := event.TagsMap["ident"]
|
||||
@@ -156,13 +126,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 {
|
||||
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 +147,21 @@ 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
|
||||
}
|
||||
|
||||
return common.MatchTags(event.TagsMap, mute.ITags)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ type DatasourceHashRingType struct {
|
||||
}
|
||||
|
||||
// for alert_rule sharding
|
||||
var HostDatasource int64 = 100000
|
||||
var HostDatasource int64 = 99999999
|
||||
var DatasourceHashRing = DatasourceHashRingType{Rings: make(map[int64]*consistent.Consistent)}
|
||||
|
||||
func NewConsistentHashRing(replicas int32, nodes []string) *consistent.Consistent {
|
||||
@@ -53,9 +53,7 @@ func (chr *DatasourceHashRingType) GetNode(datasourceId int64, pk string) (strin
|
||||
func (chr *DatasourceHashRingType) IsHit(datasourceId int64, pk string, currentNode string) bool {
|
||||
node, err := chr.GetNode(datasourceId, pk)
|
||||
if err != nil {
|
||||
if errors.Is(err, consistent.ErrEmptyCircle) {
|
||||
logger.Debugf("rule id:%s is not work, datasource id:%d is not assigned to active alert engine", pk, datasourceId)
|
||||
} else {
|
||||
if !errors.Is(err, consistent.ErrEmptyCircle) {
|
||||
logger.Debugf("rule id:%s is not work, datasource id:%d failed to get node from hashring:%v", pk, datasourceId, err)
|
||||
}
|
||||
return false
|
||||
@@ -68,3 +66,14 @@ func (chr *DatasourceHashRingType) Set(datasourceId int64, r *consistent.Consist
|
||||
defer chr.Unlock()
|
||||
chr.Rings[datasourceId] = r
|
||||
}
|
||||
|
||||
func (chr *DatasourceHashRingType) Clear() {
|
||||
chr.Lock()
|
||||
defer chr.Unlock()
|
||||
for id := range chr.Rings {
|
||||
if id == HostDatasource {
|
||||
continue
|
||||
}
|
||||
delete(chr.Rings, id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,16 @@ func (n *Naming) heartbeat() error {
|
||||
}
|
||||
}
|
||||
|
||||
if len(datasourceIds) == 0 {
|
||||
DatasourceHashRing.Clear()
|
||||
for dsId := range localss {
|
||||
if dsId == HostDatasource {
|
||||
continue
|
||||
}
|
||||
delete(localss, dsId)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(datasourceIds); i++ {
|
||||
servers, err := n.ActiveServers(datasourceIds[i])
|
||||
if err != nil {
|
||||
@@ -157,3 +167,13 @@ func (n *Naming) ActiveServers(datasourceId int64) ([]string, error) {
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import (
|
||||
"github.com/toolkits/pkg/str"
|
||||
)
|
||||
|
||||
type EventMuteHookFunc func(event *models.AlertCurEvent) bool
|
||||
|
||||
type ExternalProcessorsType struct {
|
||||
ExternalLock sync.RWMutex
|
||||
Processors map[string]*Processor
|
||||
@@ -43,6 +45,8 @@ func (e *ExternalProcessorsType) GetExternalAlertRule(datasourceId, id int64) (*
|
||||
return processor, has
|
||||
}
|
||||
|
||||
type HandleEventFunc func(event *models.AlertCurEvent)
|
||||
|
||||
type Processor struct {
|
||||
datasourceId int64
|
||||
|
||||
@@ -66,6 +70,10 @@ type Processor struct {
|
||||
promClients *prom.PromClientMap
|
||||
ctx *ctx.Context
|
||||
stats *astats.Stats
|
||||
|
||||
HandleFireEventHook HandleEventFunc
|
||||
HandleRecoverEventHook HandleEventFunc
|
||||
EventMuteHook EventMuteHookFunc
|
||||
}
|
||||
|
||||
func (p *Processor) Key() string {
|
||||
@@ -102,6 +110,10 @@ func NewProcessor(rule *models.AlertRule, datasourceId int64, atertRuleCache *me
|
||||
promClients: promClients,
|
||||
ctx: ctx,
|
||||
stats: stats,
|
||||
|
||||
HandleFireEventHook: func(event *models.AlertCurEvent) {},
|
||||
HandleRecoverEventHook: func(event *models.AlertCurEvent) {},
|
||||
EventMuteHook: func(event *models.AlertCurEvent) bool { return false },
|
||||
}
|
||||
|
||||
p.mayHandleGroup()
|
||||
@@ -133,6 +145,11 @@ func (p *Processor) Handle(anomalyPoints []common.AnomalyPoint, from string, inh
|
||||
logger.Debugf("rule_eval:%s event:%v is muted", p.Key(), event)
|
||||
continue
|
||||
}
|
||||
|
||||
if p.EventMuteHook(event) {
|
||||
continue
|
||||
}
|
||||
|
||||
tagHash := TagHash(anomalyPoint)
|
||||
eventsMap[tagHash] = append(eventsMap[tagHash], event)
|
||||
}
|
||||
@@ -174,6 +191,8 @@ 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 from == "inner" {
|
||||
event.LastEvalTime = now
|
||||
@@ -227,6 +246,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)
|
||||
}
|
||||
|
||||
@@ -284,9 +305,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)
|
||||
@@ -296,11 +320,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 {
|
||||
// 有最大发送次数的限制,就要看已经发了几次了,是否达到了最大发送次数
|
||||
@@ -309,7 +332,6 @@ func (p *Processor) fireEvent(event *models.AlertCurEvent) {
|
||||
return
|
||||
} else {
|
||||
event.NotifyCurNumber = fired.NotifyCurNumber + 1
|
||||
event.FirstTriggerTime = fired.FirstTriggerTime
|
||||
p.pushEventToQueue(event)
|
||||
}
|
||||
}
|
||||
@@ -317,6 +339,7 @@ func (p *Processor) fireEvent(event *models.AlertCurEvent) {
|
||||
} else {
|
||||
event.NotifyCurNumber = 1
|
||||
event.FirstTriggerTime = event.TriggerTime
|
||||
p.HandleFireEventHook(event)
|
||||
p.pushEventToQueue(event)
|
||||
}
|
||||
}
|
||||
@@ -432,7 +455,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 {
|
||||
|
||||
@@ -54,14 +54,16 @@ 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)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -39,13 +39,13 @@ 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)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sender
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -50,6 +51,7 @@ type TaskForm struct {
|
||||
Pause string `json:"pause"`
|
||||
Script string `json:"script"`
|
||||
Args string `json:"args"`
|
||||
Stdin string `json:"stdin"`
|
||||
Action string `json:"action"`
|
||||
Creator string `json:"creator"`
|
||||
Hosts []string `json:"hosts"`
|
||||
@@ -90,7 +92,7 @@ func handleIbex(ctx *ctx.Context, url string, event *models.AlertCurEvent, targe
|
||||
return
|
||||
}
|
||||
|
||||
tpl, err := models.TaskTplGet(ctx, "id = ?", id)
|
||||
tpl, err := models.TaskTplGetById(ctx, id)
|
||||
if err != nil {
|
||||
logger.Errorf("event_callback_ibex: failed to get tpl: %v", err)
|
||||
return
|
||||
@@ -114,6 +116,30 @@ func handleIbex(ctx *ctx.Context, url string, event *models.AlertCurEvent, targe
|
||||
return
|
||||
}
|
||||
|
||||
tagsMap := make(map[string]string)
|
||||
for i := 0; i < len(event.TagsJSON); i++ {
|
||||
pair := strings.TrimSpace(event.TagsJSON[i])
|
||||
if pair == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
arr := strings.Split(pair, "=")
|
||||
if len(arr) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
tagsMap[arr[0]] = arr[1]
|
||||
}
|
||||
// 附加告警级别 告警触发值标签
|
||||
tagsMap["alert_severity"] = strconv.Itoa(event.Severity)
|
||||
tagsMap["alert_trigger_value"] = event.TriggerValue
|
||||
|
||||
tags, err := json.Marshal(tagsMap)
|
||||
if err != nil {
|
||||
logger.Errorf("event_callback_ibex: failed to marshal tags to json: %v", tagsMap)
|
||||
return
|
||||
}
|
||||
|
||||
// call ibex
|
||||
in := TaskForm{
|
||||
Title: tpl.Title + " FH: " + host,
|
||||
@@ -124,6 +150,7 @@ func handleIbex(ctx *ctx.Context, url string, event *models.AlertCurEvent, targe
|
||||
Pause: tpl.Pause,
|
||||
Script: tpl.Script,
|
||||
Args: tpl.Args,
|
||||
Stdin: string(tags),
|
||||
Action: "start",
|
||||
Creator: tpl.UpdateBy,
|
||||
Hosts: []string{host},
|
||||
|
||||
@@ -32,7 +32,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 +40,7 @@ func (ds *DingtalkSender) Send(ctx MessageContext) {
|
||||
if len(urls) == 0 {
|
||||
return
|
||||
}
|
||||
message := BuildTplMessage(ds.tpl, ctx.Event)
|
||||
message := BuildTplMessage(ds.tpl, ctx.Events)
|
||||
|
||||
for _, url := range urls {
|
||||
var body dingtalk
|
||||
@@ -49,7 +49,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 +57,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{
|
||||
@@ -81,7 +81,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)
|
||||
|
||||
@@ -2,6 +2,7 @@ package sender
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"html/template"
|
||||
"time"
|
||||
|
||||
@@ -22,18 +23,18 @@ 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(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(es.contentTpl, ctx.Events)
|
||||
es.WriteEmail(subject, content, tos)
|
||||
}
|
||||
|
||||
@@ -47,7 +48,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 +65,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) {
|
||||
@@ -96,14 +98,16 @@ var mailQuit = make(chan struct{})
|
||||
func RestartEmailSender(smtp aconf.SMTPConfig) {
|
||||
close(mailQuit)
|
||||
mailQuit = make(chan struct{})
|
||||
StartEmailSender(smtp)
|
||||
startEmailSender(smtp)
|
||||
}
|
||||
|
||||
func StartEmailSender(smtp aconf.SMTPConfig) {
|
||||
func InitEmailSender(smtp aconf.SMTPConfig) {
|
||||
mailch = make(chan *gomail.Message, 100000)
|
||||
startEmailSender(smtp)
|
||||
}
|
||||
|
||||
func startEmailSender(smtp aconf.SMTPConfig) {
|
||||
conf := smtp
|
||||
|
||||
if conf.Host == "" || conf.Port == 0 {
|
||||
logger.Warning("SMTP configurations invalid")
|
||||
return
|
||||
|
||||
@@ -31,11 +31,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(fs.tpl, ctx.Events)
|
||||
for _, url := range urls {
|
||||
body := feishu{
|
||||
Msgtype: "text",
|
||||
@@ -63,7 +63,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)
|
||||
|
||||
144
alert/sender/feishucard.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package sender
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/pkg/poster"
|
||||
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
type Conf struct {
|
||||
WideScreenMode bool `json:"wide_screen_mode"`
|
||||
EnableForward bool `json:"enable_forward"`
|
||||
}
|
||||
|
||||
type Te struct {
|
||||
Content string `json:"content"`
|
||||
Tag string `json:"tag"`
|
||||
}
|
||||
|
||||
type Element struct {
|
||||
Tag string `json:"tag"`
|
||||
Text Te `json:"text"`
|
||||
Content string `json:"content"`
|
||||
Elements []Element `json:"elements"`
|
||||
}
|
||||
|
||||
type Titles struct {
|
||||
Content string `json:"content"`
|
||||
Tag string `json:"tag"`
|
||||
}
|
||||
|
||||
type Headers struct {
|
||||
Title Titles `json:"title"`
|
||||
Template string `json:"template"`
|
||||
}
|
||||
|
||||
type Cards struct {
|
||||
Config Conf `json:"config"`
|
||||
Elements []Element `json:"elements"`
|
||||
Header Headers `json:"header"`
|
||||
}
|
||||
|
||||
type feishuCard struct {
|
||||
feishu
|
||||
Card Cards `json:"card"`
|
||||
}
|
||||
|
||||
type FeishuCardSender struct {
|
||||
tpl *template.Template
|
||||
}
|
||||
|
||||
const (
|
||||
Recovered = "recovered"
|
||||
Triggered = "triggered"
|
||||
)
|
||||
|
||||
var (
|
||||
body = feishuCard{
|
||||
feishu: feishu{Msgtype: "interactive"},
|
||||
Card: Cards{
|
||||
Config: Conf{
|
||||
WideScreenMode: true,
|
||||
EnableForward: true,
|
||||
},
|
||||
Header: Headers{
|
||||
Title: Titles{
|
||||
Tag: "plain_text",
|
||||
},
|
||||
},
|
||||
Elements: []Element{
|
||||
{
|
||||
Tag: "div",
|
||||
Text: Te{
|
||||
Tag: "lark_md",
|
||||
},
|
||||
},
|
||||
{
|
||||
Tag: "hr",
|
||||
},
|
||||
{
|
||||
Tag: "note",
|
||||
Elements: []Element{
|
||||
{
|
||||
Tag: "lark_md",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func (fs *FeishuCardSender) Send(ctx MessageContext) {
|
||||
if len(ctx.Users) == 0 || len(ctx.Events) == 0 {
|
||||
return
|
||||
}
|
||||
urls, _ := fs.extract(ctx.Users)
|
||||
message := BuildTplMessage(fs.tpl, ctx.Events)
|
||||
color := "red"
|
||||
lowerUnicode := strings.ToLower(message)
|
||||
if strings.Count(lowerUnicode, Recovered) > 0 && strings.Count(lowerUnicode, Triggered) > 0 {
|
||||
color = "orange"
|
||||
} else if strings.Count(lowerUnicode, Recovered) > 0 {
|
||||
color = "green"
|
||||
}
|
||||
|
||||
SendTitle := fmt.Sprintf("🔔 %s", ctx.Events[0].RuleName)
|
||||
body.Card.Header.Title.Content = SendTitle
|
||||
body.Card.Header.Template = color
|
||||
body.Card.Elements[0].Text.Content = message
|
||||
body.Card.Elements[2].Elements[0].Content = SendTitle
|
||||
for _, url := range urls {
|
||||
fs.doSend(url, body)
|
||||
}
|
||||
}
|
||||
|
||||
func (fs *FeishuCardSender) extract(users []*models.User) ([]string, []string) {
|
||||
urls := make([]string, 0, len(users))
|
||||
ats := make([]string, 0)
|
||||
for i := range users {
|
||||
if token, has := users[i].ExtractToken(models.FeishuCard); has {
|
||||
url := token
|
||||
if !strings.HasPrefix(token, "https://") && !strings.HasPrefix(token, "http://") {
|
||||
url = "https://open.feishu.cn/open-apis/bot/v2/hook/" + strings.TrimSpace(token)
|
||||
}
|
||||
urls = append(urls, url)
|
||||
}
|
||||
}
|
||||
return urls, ats
|
||||
}
|
||||
|
||||
func (fs *FeishuCardSender) doSend(url string, body feishuCard) {
|
||||
res, code, err := poster.PostJSON(url, time.Second*5, body, 3)
|
||||
if err != nil {
|
||||
logger.Errorf("feishucard_sender: result=fail url=%s code=%d error=%v response=%s", url, code, err, string(res))
|
||||
} else {
|
||||
logger.Debugf("feishucard_sender: result=succ url=%s code=%d response=%s", url, code, string(res))
|
||||
}
|
||||
}
|
||||
@@ -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,7 +36,7 @@ func (ms *MmSender) Send(ctx MessageContext) {
|
||||
if len(urls) == 0 {
|
||||
return
|
||||
}
|
||||
message := BuildTplMessage(ms.tpl, ctx.Event)
|
||||
message := BuildTplMessage(ms.tpl, ctx.Events)
|
||||
|
||||
SendMM(MatterMostMessage{
|
||||
Text: message,
|
||||
|
||||
@@ -17,13 +17,13 @@ type (
|
||||
|
||||
// MessageContext 一个event所生成的告警通知的上下文
|
||||
MessageContext struct {
|
||||
Users []*models.User
|
||||
Rule *models.AlertRule
|
||||
Event *models.AlertCurEvent
|
||||
Users []*models.User
|
||||
Rule *models.AlertRule
|
||||
Events []*models.AlertCurEvent
|
||||
}
|
||||
)
|
||||
|
||||
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 +31,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,23 +43,32 @@ 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) MessageContext {
|
||||
users := userCache.GetByUserIds(uids)
|
||||
return MessageContext{
|
||||
Rule: rule,
|
||||
Event: event,
|
||||
Users: users,
|
||||
Rule: rule,
|
||||
Events: events,
|
||||
Users: users,
|
||||
}
|
||||
}
|
||||
|
||||
func BuildTplMessage(tpl *template.Template, event *models.AlertCurEvent) string {
|
||||
type BuildTplMessageFunc func(tpl *template.Template, events []*models.AlertCurEvent) string
|
||||
|
||||
var BuildTplMessage BuildTplMessageFunc = buildTplMessage
|
||||
|
||||
func buildTplMessage(tpl *template.Template, events []*models.AlertCurEvent) string {
|
||||
if tpl == nil {
|
||||
return "tpl for current sender not found, please check configuration"
|
||||
}
|
||||
|
||||
var 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
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@ 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(ts.tpl, ctx.Events)
|
||||
|
||||
SendTelegram(TelegramMessage{
|
||||
Text: message,
|
||||
@@ -55,7 +55,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], "/")
|
||||
|
||||
@@ -25,11 +25,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(ws.tpl, ctx.Events)
|
||||
for _, url := range urls {
|
||||
body := wecom{
|
||||
Msgtype: "markdown",
|
||||
@@ -46,7 +46,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)
|
||||
|
||||
@@ -8,6 +8,7 @@ type Center struct {
|
||||
I18NHeaderKey string
|
||||
MetricDesc MetricDescType
|
||||
AnonymousAccess AnonymousAccess
|
||||
UseFileAssets bool
|
||||
}
|
||||
|
||||
type Plugin struct {
|
||||
|
||||
@@ -13,10 +13,4 @@ var Plugins = []Plugin{
|
||||
Type: "elasticsearch",
|
||||
TypeName: "Elasticsearch",
|
||||
},
|
||||
{
|
||||
Id: 3,
|
||||
Category: "logging",
|
||||
Type: "jaeger",
|
||||
TypeName: "Jaeger",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8,15 +8,19 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/alert/astats"
|
||||
"github.com/ccfos/nightingale/v6/alert/process"
|
||||
"github.com/ccfos/nightingale/v6/center/cconf"
|
||||
"github.com/ccfos/nightingale/v6/center/cstats"
|
||||
"github.com/ccfos/nightingale/v6/center/metas"
|
||||
"github.com/ccfos/nightingale/v6/center/sso"
|
||||
"github.com/ccfos/nightingale/v6/conf"
|
||||
"github.com/ccfos/nightingale/v6/dumper"
|
||||
"github.com/ccfos/nightingale/v6/memsto"
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/ccfos/nightingale/v6/models/migrate"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/httpx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/i18nx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/logx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/version"
|
||||
"github.com/ccfos/nightingale/v6/prom"
|
||||
"github.com/ccfos/nightingale/v6/pushgw/idents"
|
||||
"github.com/ccfos/nightingale/v6/pushgw/writer"
|
||||
@@ -41,7 +45,8 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i18nx.Init()
|
||||
i18nx.Init(configDir)
|
||||
cstats.Init()
|
||||
|
||||
db, err := storage.New(config.DB)
|
||||
if err != nil {
|
||||
@@ -49,6 +54,7 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
}
|
||||
ctx := ctx.NewContext(context.Background(), db, true)
|
||||
models.InitRoot(ctx)
|
||||
migrate.Migrate(db)
|
||||
|
||||
redis, err := storage.NewRedis(config.Redis)
|
||||
if err != nil {
|
||||
@@ -69,16 +75,21 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
alertMuteCache := memsto.NewAlertMuteCache(ctx, syncStats)
|
||||
alertRuleCache := memsto.NewAlertRuleCache(ctx, syncStats)
|
||||
notifyConfigCache := memsto.NewNotifyConfigCache(ctx)
|
||||
userCache := memsto.NewUserCache(ctx, syncStats)
|
||||
userGroupCache := memsto.NewUserGroupCache(ctx, syncStats)
|
||||
|
||||
promClients := prom.NewPromClient(ctx, config.Alert.Heartbeat)
|
||||
|
||||
externalProcessors := process.NewExternalProcessors()
|
||||
alert.Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, dsCache, ctx, promClients)
|
||||
alert.Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, dsCache, ctx, promClients, userCache, userGroupCache)
|
||||
|
||||
writers := writer.NewWriters(config.Pushgw)
|
||||
|
||||
httpx.InitRSAConfig(&config.HTTP.RSA)
|
||||
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)
|
||||
centerRouter := centerrt.New(config.HTTP, config.Center, cconf.Operations, dsCache, notifyConfigCache, promClients, redis, sso, ctx, metas, idents, targetCache, userCache, userGroupCache)
|
||||
pushgwRouter := pushgwrt.New(config.HTTP, config.Pushgw, targetCache, busiGroupCache, idents, writers, ctx)
|
||||
|
||||
r := httpx.GinEngine(config.Global.RunMode, config.HTTP)
|
||||
@@ -86,6 +97,7 @@ func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
centerRouter.Config(r)
|
||||
alertrtRouter.Config(r)
|
||||
pushgwRouter.Config(r)
|
||||
dumper.ConfigRouter(r)
|
||||
|
||||
httpClean := httpx.Init(config.HTTP, r)
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ package router
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -15,12 +17,16 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/pkg/aop"
|
||||
"github.com/ccfos/nightingale/v6/pkg/ctx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/httpx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/version"
|
||||
"github.com/ccfos/nightingale/v6/prom"
|
||||
"github.com/ccfos/nightingale/v6/pushgw/idents"
|
||||
"github.com/ccfos/nightingale/v6/storage"
|
||||
|
||||
"github.com/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 {
|
||||
@@ -32,13 +38,19 @@ type Router struct {
|
||||
PromClients *prom.PromClientMap
|
||||
Redis storage.Redis
|
||||
MetaSet *metas.Set
|
||||
IdentSet *idents.Set
|
||||
TargetCache *memsto.TargetCacheType
|
||||
Sso *sso.SsoClient
|
||||
UserCache *memsto.UserCacheType
|
||||
UserGroupCache *memsto.UserGroupCacheType
|
||||
Ctx *ctx.Context
|
||||
|
||||
DatasourceCheckHook func(*gin.Context) bool
|
||||
}
|
||||
|
||||
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 {
|
||||
pc *prom.PromClientMap, redis storage.Redis, sso *sso.SsoClient, ctx *ctx.Context, metaSet *metas.Set, idents *idents.Set, tc *memsto.TargetCacheType,
|
||||
uc *memsto.UserCacheType, ugc *memsto.UserGroupCacheType) *Router {
|
||||
return &Router{
|
||||
HTTP: httpConfig,
|
||||
Center: center,
|
||||
@@ -48,9 +60,14 @@ func New(httpConfig httpx.Config, center cconf.Center, operations cconf.Operatio
|
||||
PromClients: pc,
|
||||
Redis: redis,
|
||||
MetaSet: metaSet,
|
||||
IdentSet: idents,
|
||||
TargetCache: tc,
|
||||
Sso: sso,
|
||||
UserCache: uc,
|
||||
UserGroupCache: ugc,
|
||||
Ctx: ctx,
|
||||
|
||||
DatasourceCheckHook: func(ctx *gin.Context) bool { return false },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,10 +112,32 @@ func (rt *Router) configNoRoute(r *gin.Engine, fs *http.FileSystem) {
|
||||
suffix := arr[len(arr)-1]
|
||||
|
||||
switch suffix {
|
||||
case "png", "jpeg", "jpg", "svg", "ico", "gif", "css", "js", "html", "htm", "gz", "zip", "map":
|
||||
c.FileFromFS(c.Request.URL.Path, *fs)
|
||||
case "png", "jpeg", "jpg", "svg", "ico", "gif", "css", "js", "html", "htm", "gz", "zip", "map", "ttf":
|
||||
if !rt.Center.UseFileAssets {
|
||||
c.FileFromFS(c.Request.URL.Path, *fs)
|
||||
} else {
|
||||
cwdarr := []string{"/"}
|
||||
if runtime.GOOS == "windows" {
|
||||
cwdarr[0] = ""
|
||||
}
|
||||
cwdarr = append(cwdarr, strings.Split(runner.Cwd, "/")...)
|
||||
cwdarr = append(cwdarr, "pub")
|
||||
cwdarr = append(cwdarr, strings.Split(c.Request.URL.Path, "/")...)
|
||||
c.File(path.Join(cwdarr...))
|
||||
}
|
||||
default:
|
||||
c.FileFromFS("/", *fs)
|
||||
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...))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -113,7 +152,10 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
if err != nil {
|
||||
logger.Errorf("cannot create statik fs: %v", err)
|
||||
}
|
||||
r.StaticFS("/pub", statikFS)
|
||||
|
||||
if !rt.Center.UseFileAssets {
|
||||
r.StaticFS("/pub", statikFS)
|
||||
}
|
||||
|
||||
pagesPrefix := "/api/n9e"
|
||||
pages := r.Group(pagesPrefix)
|
||||
@@ -132,10 +174,14 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
}
|
||||
|
||||
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.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)
|
||||
@@ -203,6 +249,7 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.GET("/builtin-boards-cates", rt.auth(), rt.user(), rt.builtinBoardCateGets)
|
||||
pages.POST("/builtin-boards-detail", rt.auth(), rt.user(), rt.builtinBoardDetailGets)
|
||||
pages.GET("/integrations/icon/:cate/:name", rt.builtinIcon)
|
||||
pages.GET("/integrations/makedown/:cate", rt.builtinMarkdown)
|
||||
|
||||
pages.GET("/busi-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)
|
||||
@@ -228,6 +275,7 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.PUT("/busi-group/:id/alert-rules/fields", rt.auth(), rt.user(), rt.perm("/alert-rules/put"), rt.bgrw(), rt.alertRulePutFields)
|
||||
pages.PUT("/busi-group/:id/alert-rule/:arid", rt.auth(), rt.user(), rt.perm("/alert-rules/put"), rt.alertRulePutByFE)
|
||||
pages.GET("/alert-rule/:arid", rt.auth(), rt.user(), rt.perm("/alert-rules"), rt.alertRuleGet)
|
||||
pages.PUT("/busi-group/:id/alert-rule/:arid/validate", rt.auth(), rt.user(), rt.perm("/alert-rules/put"), rt.alertRuleValidation)
|
||||
|
||||
pages.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)
|
||||
@@ -303,6 +351,8 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
pages.GET("/notify-tpls", rt.auth(), rt.admin(), rt.notifyTplGets)
|
||||
pages.PUT("/notify-tpl/content", rt.auth(), rt.admin(), rt.notifyTplUpdateContent)
|
||||
pages.PUT("/notify-tpl", rt.auth(), rt.admin(), rt.notifyTplUpdate)
|
||||
pages.POST("/notify-tpl", rt.auth(), rt.admin(), rt.notifyTplAdd)
|
||||
pages.DELETE("/notify-tpl/:id", rt.auth(), rt.admin(), rt.notifyTplDel)
|
||||
pages.POST("/notify-tpl/preview", rt.auth(), rt.admin(), rt.notifyTplPreview)
|
||||
|
||||
pages.GET("/sso-configs", rt.auth(), rt.admin(), rt.ssoConfigGets)
|
||||
@@ -322,12 +372,29 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
|
||||
pages.GET("/notify-config", rt.auth(), rt.admin(), rt.notifyConfigGet)
|
||||
pages.PUT("/notify-config", rt.auth(), rt.admin(), rt.notifyConfigPut)
|
||||
pages.GET("/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)
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(gin.H{"version": v, "github_verison": version.GithubVersion.Load().(string)}, 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)
|
||||
@@ -369,6 +436,8 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
service.GET("/alert-his-events", rt.alertHisEventsList)
|
||||
service.GET("/alert-his-event/:eid", rt.alertHisEventGet)
|
||||
|
||||
service.GET("/task-tpl/:tid", rt.taskTplGetByService)
|
||||
|
||||
service.GET("/config/:id", rt.configGet)
|
||||
service.GET("/configs", rt.configsGet)
|
||||
service.GET("/config", rt.configGetByKey)
|
||||
@@ -387,11 +456,11 @@ func (rt *Router) Config(r *gin.Engine) {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -184,6 +184,9 @@ func (rt *Router) alertCurEventDel(c *gin.Context) {
|
||||
|
||||
set := make(map[int64]struct{})
|
||||
|
||||
// event group id is 0, ignore perm check
|
||||
set[0] = struct{}{}
|
||||
|
||||
for i := 0; i < len(f.Ids); i++ {
|
||||
event, err := models.AlertCurEventGetById(rt.Ctx, f.Ids[i])
|
||||
ginx.Dangerous(err)
|
||||
|
||||
@@ -2,6 +2,7 @@ package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -271,3 +272,65 @@ 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)
|
||||
|
||||
arid := ginx.UrlParamInt64(c, "arid")
|
||||
ar, err := models.AlertRuleGetById(rt.Ctx, arid)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if ar == nil {
|
||||
ginx.NewRender(c, http.StatusNotFound).Message("No such AlertRule")
|
||||
return
|
||||
}
|
||||
|
||||
rt.bgrwCheck(c, ar.GroupId)
|
||||
|
||||
if len(f.NotifyChannelsJSON) > 0 && len(f.NotifyGroupsJSON) > 0 { //Validation NotifyChannels
|
||||
ngids := make([]int64, 0, len(f.NotifyChannelsJSON))
|
||||
for i := range f.NotifyGroupsJSON {
|
||||
id, _ := strconv.ParseInt(f.NotifyGroupsJSON[i], 10, 64)
|
||||
ngids = append(ngids, id)
|
||||
}
|
||||
userGroups := rt.UserGroupCache.GetByUserGroupIds(ngids)
|
||||
uids := make([]int64, 0)
|
||||
for i := range userGroups {
|
||||
uids = append(uids, userGroups[i].UserIds...)
|
||||
}
|
||||
users := rt.UserCache.GetByUserIds(uids)
|
||||
//If any users have a certain notify channel's token, it will be okay. Otherwise, this notify channel is absent of tokens.
|
||||
ancs := make([]string, 0, len(f.NotifyChannelsJSON)) //absent Notify Channels
|
||||
for i := range f.NotifyChannelsJSON {
|
||||
flag := true
|
||||
//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("")
|
||||
}
|
||||
|
||||
@@ -14,21 +14,18 @@ import (
|
||||
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].FillRuleName(rt.Ctx, rulecache))
|
||||
ginx.Dangerous(lst[i].FillDatasourceIds(rt.Ctx))
|
||||
ginx.Dangerous(lst[i].DB2FE())
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(lst, err)
|
||||
}
|
||||
|
||||
@@ -83,6 +80,9 @@ func (rt *Router) alertSubscribePut(c *gin.Context) {
|
||||
rt.Ctx,
|
||||
"name",
|
||||
"disabled",
|
||||
"prod",
|
||||
"cate",
|
||||
"datasource_ids",
|
||||
"cluster",
|
||||
"rule_id",
|
||||
"tags",
|
||||
@@ -96,7 +96,9 @@ func (rt *Router) alertSubscribePut(c *gin.Context) {
|
||||
"webhooks",
|
||||
"for_duration",
|
||||
"redefine_webhooks",
|
||||
"datasource_ids",
|
||||
"severities",
|
||||
"extra_config",
|
||||
"busi_groups",
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
@@ -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 {
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
114
center/router/router_captcha.go
Normal 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, ¶m)
|
||||
|
||||
//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)
|
||||
}
|
||||
@@ -25,6 +25,11 @@ type listReq struct {
|
||||
}
|
||||
|
||||
func (rt *Router) datasourceList(c *gin.Context) {
|
||||
if rt.DatasourceCheckHook(c) {
|
||||
Render(c, []int{}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var req listReq
|
||||
ginx.BindJSON(c, &req)
|
||||
|
||||
@@ -65,6 +70,11 @@ func (rt *Router) datasourceBriefs(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (rt *Router) datasourceUpsert(c *gin.Context) {
|
||||
if rt.DatasourceCheckHook(c) {
|
||||
Render(c, []int{}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var req models.Datasource
|
||||
ginx.BindJSON(c, &req)
|
||||
username := Username(c)
|
||||
@@ -105,6 +115,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{
|
||||
@@ -123,16 +137,14 @@ func DatasourceCheck(ds models.Datasource) error {
|
||||
if ds.PluginType == models.PROMETHEUS {
|
||||
subPath := "/api/v1/query"
|
||||
query := url.Values{}
|
||||
if strings.Contains(fullURL, "loki") {
|
||||
if ds.HTTPJson.IsLoki() {
|
||||
subPath = "/api/v1/labels"
|
||||
query.Add("start", "1")
|
||||
query.Add("end", "2")
|
||||
} 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)
|
||||
@@ -163,6 +175,11 @@ func DatasourceCheck(ds models.Datasource) error {
|
||||
}
|
||||
|
||||
func (rt *Router) datasourceGet(c *gin.Context) {
|
||||
if rt.DatasourceCheckHook(c) {
|
||||
Render(c, []int{}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var req models.Datasource
|
||||
ginx.BindJSON(c, &req)
|
||||
err := req.Get(rt.Ctx)
|
||||
@@ -170,6 +187,11 @@ func (rt *Router) datasourceGet(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (rt *Router) datasourceUpdataStatus(c *gin.Context) {
|
||||
if rt.DatasourceCheckHook(c) {
|
||||
Render(c, []int{}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var req models.Datasource
|
||||
ginx.BindJSON(c, &req)
|
||||
username := Username(c)
|
||||
@@ -179,6 +201,11 @@ func (rt *Router) datasourceUpdataStatus(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (rt *Router) datasourceDel(c *gin.Context) {
|
||||
if rt.DatasourceCheckHook(c) {
|
||||
Render(c, []int{}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var ids []int64
|
||||
ginx.BindJSON(c, &ids)
|
||||
err := models.DatasourceDel(rt.Ctx, ids)
|
||||
|
||||
80
center/router/router_es_index_pattern.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
)
|
||||
|
||||
// 创建 ES Index Pattern
|
||||
func (rt *Router) esIndexPatternAdd(c *gin.Context) {
|
||||
var f models.EsIndexPattern
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
username := c.MustGet("username").(string)
|
||||
now := time.Now().Unix()
|
||||
f.CreateAt = now
|
||||
f.CreateBy = username
|
||||
f.UpdateAt = now
|
||||
f.UpdateBy = username
|
||||
|
||||
err := f.Add(rt.Ctx)
|
||||
ginx.NewRender(c).Message(err)
|
||||
}
|
||||
|
||||
// 更新 ES Index Pattern
|
||||
func (rt *Router) esIndexPatternPut(c *gin.Context) {
|
||||
var f models.EsIndexPattern
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
id := ginx.QueryInt64(c, "id")
|
||||
|
||||
esIndexPattern, err := models.EsIndexPatternGetById(rt.Ctx, id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
if esIndexPattern == nil {
|
||||
ginx.NewRender(c, http.StatusNotFound).Message("No such EsIndexPattern")
|
||||
return
|
||||
}
|
||||
|
||||
f.UpdateBy = c.MustGet("username").(string)
|
||||
ginx.NewRender(c).Message(esIndexPattern.Update(rt.Ctx, f))
|
||||
}
|
||||
|
||||
// 删除 ES Index Pattern
|
||||
func (rt *Router) esIndexPatternDel(c *gin.Context) {
|
||||
var f idsForm
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
if len(f.Ids) == 0 {
|
||||
ginx.Bomb(http.StatusBadRequest, "ids empty")
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Message(models.EsIndexPatternDel(rt.Ctx, f.Ids))
|
||||
}
|
||||
|
||||
// ES Index Pattern列表
|
||||
func (rt *Router) esIndexPatternGetList(c *gin.Context) {
|
||||
datasourceId := ginx.QueryInt64(c, "datasource_id", 0)
|
||||
|
||||
var lst []*models.EsIndexPattern
|
||||
var err error
|
||||
if datasourceId != 0 {
|
||||
lst, err = models.EsIndexPatternGets(rt.Ctx, "datasource_id = ?", datasourceId)
|
||||
} else {
|
||||
lst, err = models.EsIndexPatternGets(rt.Ctx, "")
|
||||
}
|
||||
|
||||
ginx.NewRender(c).Data(lst, err)
|
||||
}
|
||||
|
||||
// ES Index Pattern 单个数据
|
||||
func (rt *Router) esIndexPatternGet(c *gin.Context) {
|
||||
id := ginx.QueryInt64(c, "id")
|
||||
|
||||
item, err := models.EsIndexPatternGet(rt.Ctx, "id=?", id)
|
||||
ginx.NewRender(c).Data(item, err)
|
||||
}
|
||||
@@ -35,9 +35,19 @@ 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()
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/pkg/ldapx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/oauth2x"
|
||||
"github.com/ccfos/nightingale/v6/pkg/oidcx"
|
||||
"github.com/ccfos/nightingale/v6/pkg/secu"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
@@ -21,20 +23,40 @@ import (
|
||||
)
|
||||
|
||||
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 rt.HTTP.ShowCaptcha.Enable {
|
||||
if !CaptchaVerify(f.Captchaid, f.Verifyvalue) {
|
||||
ginx.NewRender(c).Message("incorrect verification code")
|
||||
return
|
||||
}
|
||||
}
|
||||
authPassWord := f.Password
|
||||
// need decode
|
||||
if rt.HTTP.RSA.OpenRSA {
|
||||
decPassWord, err := secu.Decrypt(f.Password, rt.HTTP.RSA.RSAPrivateKey, rt.HTTP.RSA.RSAPassWord)
|
||||
if err != nil {
|
||||
logger.Errorf("RSA Decrypt failed: %v username: %s", err, f.Username)
|
||||
ginx.NewRender(c).Message(err)
|
||||
return
|
||||
}
|
||||
authPassWord = decPassWord
|
||||
}
|
||||
user, err := models.PassLogin(rt.Ctx, f.Username, authPassWord)
|
||||
if err != nil {
|
||||
// pass validate fail, try ldap
|
||||
if rt.Sso.LDAP.Enable {
|
||||
roles := strings.Join(rt.Sso.LDAP.DefaultRoles, " ")
|
||||
user, err = models.LdapLogin(rt.Ctx, f.Username, f.Password, roles, rt.Sso.LDAP)
|
||||
user, err = models.LdapLogin(rt.Ctx, f.Username, authPassWord, roles, rt.Sso.LDAP)
|
||||
if err != nil {
|
||||
logger.Debugf("ldap login failed: %v username: %s", err, f.Username)
|
||||
ginx.NewRender(c).Message(err)
|
||||
@@ -67,6 +89,7 @@ func (rt *Router) loginPost(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (rt *Router) logoutPost(c *gin.Context) {
|
||||
logger.Infof("username:%s login from:%s", c.GetString("username"), c.ClientIP())
|
||||
metadata, err := rt.extractTokenMetadata(c.Request)
|
||||
if err != nil {
|
||||
ginx.NewRender(c, http.StatusBadRequest).Message("failed to parse jwt token")
|
||||
@@ -492,10 +515,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 +556,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 +572,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 rt.HTTP.RSA.OpenRSA {
|
||||
publicKey = base64.StdEncoding.EncodeToString(rt.HTTP.RSA.RSAPublicKey)
|
||||
}
|
||||
ginx.NewRender(c).Data(RSAConfigOutput{
|
||||
OpenRSA: rt.HTTP.RSA.OpenRSA,
|
||||
RSAPublicKey: publicKey,
|
||||
}, nil)
|
||||
}
|
||||
|
||||
@@ -2,15 +2,17 @@ package router
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"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/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) {
|
||||
@@ -191,3 +193,15 @@ func (rt *Router) notifyConfigPut(c *gin.Context) {
|
||||
|
||||
ginx.NewRender(c).Message(nil)
|
||||
}
|
||||
|
||||
//After configuring the aconf.SMTPConfig, attempt to send the email.
|
||||
func (rt *Router) attemptSendEmail(c *gin.Context) {
|
||||
//input is an email address
|
||||
email := ginx.QueryStr(c, "email")
|
||||
if email = strings.TrimSpace(email); email != "" && !str.IsMail(email) {
|
||||
ginx.Bomb(200, "email(%s) invalid", email)
|
||||
}
|
||||
smtp := rt.NotifyConfigCache.GetSMTP()
|
||||
|
||||
ginx.NewRender(c).Message(sender.SendEmail("Email test", "email content", []string{email}, smtp))
|
||||
}
|
||||
|
||||
@@ -10,12 +10,25 @@ import (
|
||||
"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)
|
||||
}
|
||||
@@ -23,11 +36,7 @@ func (rt *Router) notifyTplGets(c *gin.Context) {
|
||||
func (rt *Router) notifyTplUpdateContent(c *gin.Context) {
|
||||
var f models.NotifyTpl
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
if err := templateValidate(f); err != nil {
|
||||
ginx.NewRender(c).Message(err.Error())
|
||||
return
|
||||
}
|
||||
ginx.Dangerous(templateValidate(f))
|
||||
|
||||
ginx.NewRender(c).Message(f.UpdateContent(rt.Ctx))
|
||||
}
|
||||
@@ -35,16 +44,28 @@ func (rt *Router) notifyTplUpdateContent(c *gin.Context) {
|
||||
func (rt *Router) notifyTplUpdate(c *gin.Context) {
|
||||
var f models.NotifyTpl
|
||||
ginx.BindJSON(c, &f)
|
||||
|
||||
if err := templateValidate(f); err != nil {
|
||||
ginx.NewRender(c).Message(err.Error())
|
||||
return
|
||||
}
|
||||
ginx.Dangerous(templateValidate(f))
|
||||
|
||||
ginx.NewRender(c).Message(f.Update(rt.Ctx))
|
||||
}
|
||||
|
||||
func templateValidate(f models.NotifyTpl) error {
|
||||
if len(f.Channel) > 32 {
|
||||
return fmt.Errorf("channel length should not exceed 32")
|
||||
}
|
||||
|
||||
if str.Dangerous(f.Channel) {
|
||||
return fmt.Errorf("channel should not contain dangerous characters")
|
||||
}
|
||||
|
||||
if len(f.Name) > 255 {
|
||||
return fmt.Errorf("name length should not exceed 255")
|
||||
}
|
||||
|
||||
if str.Dangerous(f.Name) {
|
||||
return fmt.Errorf("name should not contain dangerous characters")
|
||||
}
|
||||
|
||||
if f.Content == "" {
|
||||
return nil
|
||||
}
|
||||
@@ -65,10 +86,7 @@ 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)
|
||||
@@ -106,3 +124,25 @@ 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.NotifyTplCountByChannel(rt.Ctx, f.Channel)
|
||||
ginx.Dangerous(err)
|
||||
if count != 0 {
|
||||
ginx.Bomb(200, "Refuse to create duplicate channel(unique)")
|
||||
}
|
||||
ginx.NewRender(c).Message(f.Create(rt.Ctx))
|
||||
}
|
||||
|
||||
// delete notify template, not allowed to delete the system defaults(models.DefaultChannels)
|
||||
func (rt *Router) notifyTplDel(c *gin.Context) {
|
||||
f := new(models.NotifyTpl)
|
||||
id := ginx.UrlParamInt64(c, "id")
|
||||
ginx.NewRender(c).Message(f.NotifyTplDelete(rt.Ctx, id))
|
||||
}
|
||||
|
||||
@@ -3,17 +3,20 @@ 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/gin-gonic/gin"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/toolkits/pkg/ginx"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
type queryFormItem struct {
|
||||
@@ -31,10 +34,14 @@ type batchQueryForm struct {
|
||||
func (rt *Router) promBatchQueryRange(c *gin.Context) {
|
||||
var f batchQueryForm
|
||||
ginx.Dangerous(c.BindJSON(&f))
|
||||
var lst []model.Value
|
||||
|
||||
cli := rt.PromClients.GetCli(f.DatasourceId)
|
||||
|
||||
var lst []model.Value
|
||||
if cli == nil {
|
||||
logger.Warningf("no such datasource id: %d", f.DatasourceId)
|
||||
ginx.NewRender(c).Data(lst, nil)
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range f.Queries {
|
||||
r := pkgprom.Range{
|
||||
@@ -66,10 +73,15 @@ func (rt *Router) promBatchQueryInstant(c *gin.Context) {
|
||||
var f batchInstantForm
|
||||
ginx.Dangerous(c.BindJSON(&f))
|
||||
|
||||
cli := rt.PromClients.GetCli(f.DatasourceId)
|
||||
|
||||
var lst []model.Value
|
||||
|
||||
cli := rt.PromClients.GetCli(f.DatasourceId)
|
||||
if cli == nil {
|
||||
logger.Warningf("no such datasource id: %d", f.DatasourceId)
|
||||
ginx.NewRender(c).Data(lst, nil)
|
||||
return
|
||||
}
|
||||
|
||||
for _, item := range f.Queries {
|
||||
resp, _, err := cli.Query(context.Background(), item.Query, time.Unix(item.Time, 0))
|
||||
ginx.Dangerous(err)
|
||||
@@ -139,21 +151,74 @@ 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 {
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -28,6 +28,12 @@ func (rt *Router) serverHeartbeat(c *gin.Context) {
|
||||
|
||||
func (rt *Router) serversActive(c *gin.Context) {
|
||||
datasourceId := ginx.QueryInt64(c, "dsid")
|
||||
engineName := ginx.QueryStr(c, "engine_name", "")
|
||||
if engineName != "" {
|
||||
servers, err := models.AlertingEngineGetsInstances(rt.Ctx, "engine_cluster = ? and clock > ?", engineName, time.Now().Unix()-30)
|
||||
ginx.NewRender(c).Data(servers, err)
|
||||
return
|
||||
}
|
||||
|
||||
servers, err := models.AlertingEngineGetsInstances(rt.Ctx, "datasource_id = ? and clock > ?", datasourceId, time.Now().Unix()-30)
|
||||
ginx.NewRender(c).Data(servers, err)
|
||||
|
||||
@@ -45,12 +45,32 @@ func (rt *Router) targetGets(c *gin.Context) {
|
||||
bgid := ginx.QueryInt64(c, "bgid", -1)
|
||||
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 bgids []int64
|
||||
var err error
|
||||
if bgid == -1 {
|
||||
user := c.MustGet("user").(*models.User)
|
||||
if !user.IsAdmin() {
|
||||
// 如果是非 admin 用户,全部对象的情况,找到用户有权限的业务组
|
||||
userGroupIds, err := models.MyGroupIds(rt.Ctx, user.Id)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
bgids, err = models.BusiGroupIds(rt.Ctx, userGroupIds)
|
||||
ginx.Dangerous(err)
|
||||
|
||||
// 将未分配业务组的对象也加入到列表中
|
||||
bgids = append(bgids, 0)
|
||||
}
|
||||
} else {
|
||||
bgids = append(bgids, bgid)
|
||||
}
|
||||
|
||||
total, err := models.TargetTotal(rt.Ctx, bgids, dsIds, query, 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 +81,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 +106,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 {
|
||||
|
||||
@@ -48,6 +48,19 @@ 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)
|
||||
}
|
||||
|
||||
type taskTplForm struct {
|
||||
Title string `json:"title" binding:"required"`
|
||||
Batch int `json:"batch"`
|
||||
|
||||
@@ -29,6 +29,8 @@ Port = 389
|
||||
BaseDn = 'dc=example,dc=org'
|
||||
BindUser = 'cn=manager,dc=example,dc=org'
|
||||
BindPass = '*******'
|
||||
# openldap format e.g. (&(uid=%s))
|
||||
# AD format e.g. (&(sAMAccountName=%s))
|
||||
AuthFilter = '(&(uid=%s))'
|
||||
CoverAttributes = true
|
||||
TLS = false
|
||||
|
||||
@@ -18,9 +18,9 @@ func Upgrade(configFile string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := ctx.NewContext(context.Background(), db, false)
|
||||
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
|
||||
|
||||
@@ -8,6 +8,13 @@ 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';
|
||||
|
||||
@@ -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.")
|
||||
)
|
||||
|
||||
|
||||
73
cmd/edge/edge.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/ccfos/nightingale/v6/alert"
|
||||
"github.com/ccfos/nightingale/v6/alert/astats"
|
||||
"github.com/ccfos/nightingale/v6/alert/process"
|
||||
"github.com/ccfos/nightingale/v6/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"
|
||||
"github.com/ccfos/nightingale/v6/pushgw/writer"
|
||||
|
||||
alertrt "github.com/ccfos/nightingale/v6/alert/router"
|
||||
pushgwrt "github.com/ccfos/nightingale/v6/pushgw/router"
|
||||
)
|
||||
|
||||
func Initialize(configDir string, cryptoKey string) (func(), error) {
|
||||
config, err := conf.InitConfig(configDir, cryptoKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to init config: %v", err)
|
||||
}
|
||||
|
||||
logxClean, err := logx.Init(config.Log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx := ctx.NewContext(context.Background(), nil, false, config.CenterApi)
|
||||
|
||||
syncStats := memsto.NewSyncStats()
|
||||
|
||||
targetCache := memsto.NewTargetCache(ctx, syncStats, nil)
|
||||
busiGroupCache := memsto.NewBusiGroupCache(ctx, syncStats)
|
||||
idents := idents.New(ctx)
|
||||
writers := writer.NewWriters(config.Pushgw)
|
||||
pushgwRouter := pushgwrt.New(config.HTTP, config.Pushgw, targetCache, busiGroupCache, idents, writers, ctx)
|
||||
r := httpx.GinEngine(config.Global.RunMode, config.HTTP)
|
||||
pushgwRouter.Config(r)
|
||||
|
||||
if !config.Alert.Disable {
|
||||
alertStats := astats.NewSyncStats()
|
||||
dsCache := memsto.NewDatasourceCache(ctx, syncStats)
|
||||
alertMuteCache := memsto.NewAlertMuteCache(ctx, syncStats)
|
||||
alertRuleCache := memsto.NewAlertRuleCache(ctx, syncStats)
|
||||
notifyConfigCache := memsto.NewNotifyConfigCache(ctx)
|
||||
userCache := memsto.NewUserCache(ctx, syncStats)
|
||||
userGroupCache := memsto.NewUserGroupCache(ctx, syncStats)
|
||||
|
||||
promClients := prom.NewPromClient(ctx, config.Alert.Heartbeat)
|
||||
externalProcessors := process.NewExternalProcessors()
|
||||
|
||||
alert.Start(config.Alert, config.Pushgw, syncStats, alertStats, externalProcessors, targetCache, busiGroupCache, alertMuteCache, alertRuleCache, notifyConfigCache, dsCache, ctx, promClients, userCache, userGroupCache)
|
||||
|
||||
alertrtRouter := alertrt.New(config.HTTP, config.Alert, alertMuteCache, targetCache, busiGroupCache, alertStats, ctx, externalProcessors)
|
||||
|
||||
alertrtRouter.Config(r)
|
||||
}
|
||||
|
||||
dumper.ConfigRouter(r)
|
||||
httpClean := httpx.Init(config.HTTP, r)
|
||||
|
||||
return func() {
|
||||
logxClean()
|
||||
httpClean()
|
||||
}, nil
|
||||
}
|
||||
68
cmd/edge/main.go
Normal 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())
|
||||
}
|
||||
@@ -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.")
|
||||
)
|
||||
|
||||
|
||||
10
conf/conf.go
@@ -14,8 +14,6 @@ import (
|
||||
"github.com/ccfos/nightingale/v6/pkg/ormx"
|
||||
"github.com/ccfos/nightingale/v6/pushgw/pconf"
|
||||
"github.com/ccfos/nightingale/v6/storage"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ConfigType struct {
|
||||
@@ -32,8 +30,10 @@ type ConfigType struct {
|
||||
}
|
||||
|
||||
type CenterApi struct {
|
||||
Addrs []string
|
||||
BasicAuth gin.Accounts
|
||||
Addrs []string
|
||||
BasicAuthUser string
|
||||
BasicAuthPass string
|
||||
Timeout int64
|
||||
}
|
||||
|
||||
type GlobalConfig struct {
|
||||
@@ -48,7 +48,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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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/n9e-arch-latest.png
Normal file
|
After Width: | Height: | Size: 215 KiB |
BIN
doc/img/n9e-screenshot-gif-v6.gif
Normal file
|
After Width: | Height: | Size: 877 KiB |
@@ -6,7 +6,7 @@ ADD n9e /app/
|
||||
ADD etc /app/
|
||||
ADD integrations /app/integrations/
|
||||
ADD --chmod=755 https://github.com/ufoscout/docker-compose-wait/releases/download/2.11.0/wait_x86_64 /wait
|
||||
RUN chmod +x /wait
|
||||
RUN chmod +x /wait && pip install requests
|
||||
|
||||
EXPOSE 17000
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ insert into user_group_member(group_id, user_id) values(1, 1);
|
||||
CREATE TABLE configs (
|
||||
id bigserial,
|
||||
ckey varchar(191) not null,
|
||||
cval varchar(4096) not null default '',
|
||||
cval text not null default '',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE (ckey)
|
||||
) ;
|
||||
@@ -94,10 +94,18 @@ insert into role_operation(role_name, operation) values('Standard', '/log/explor
|
||||
insert into role_operation(role_name, operation) values('Standard', '/trace/explorer');
|
||||
insert into role_operation(role_name, operation) values('Standard', '/help/version');
|
||||
insert into role_operation(role_name, operation) values('Standard', '/help/contact');
|
||||
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('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('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');
|
||||
|
||||
insert into role_operation(role_name, operation) values('Standard', '/users');
|
||||
insert into role_operation(role_name, operation) values('Standard', '/user-groups');
|
||||
insert into role_operation(role_name, operation) values('Standard', '/user-groups/add');
|
||||
@@ -292,6 +300,7 @@ CREATE TABLE alert_rule (
|
||||
runbook_url varchar(255),
|
||||
append_tags varchar(255) not null default '' ,
|
||||
annotations text not null ,
|
||||
extra_config text not null ,
|
||||
create_at bigint not null default 0,
|
||||
create_by varchar(64) not null default '',
|
||||
update_at bigint not null default 0,
|
||||
@@ -320,7 +329,7 @@ COMMENT ON COLUMN alert_rule.recover_duration IS 'unit: s';
|
||||
COMMENT ON COLUMN alert_rule.callbacks IS 'split by space: http://a.com/api/x http://a.com/api/y';
|
||||
COMMENT ON COLUMN alert_rule.append_tags IS 'split by space: service=n9e mod=api';
|
||||
COMMENT ON COLUMN alert_rule.annotations IS 'annotations';
|
||||
|
||||
COMMENT ON COLUMN alert_rule.extra_config IS 'extra_config';
|
||||
|
||||
CREATE TABLE alert_mute (
|
||||
id bigserial,
|
||||
@@ -337,6 +346,7 @@ CREATE TABLE alert_mute (
|
||||
disabled smallint not null default 0 ,
|
||||
mute_time_type smallint not null default 0,
|
||||
periodic_mutes varchar(4096) not null default '',
|
||||
severities varchar(32) not null default '',
|
||||
create_at bigint not null default 0,
|
||||
create_by varchar(64) not null default '',
|
||||
update_at bigint not null default 0,
|
||||
@@ -363,13 +373,15 @@ CREATE TABLE alert_subscribe (
|
||||
datasource_ids varchar(255) not null default '' ,
|
||||
cluster varchar(128) not null,
|
||||
rule_id bigint not null default 0,
|
||||
tags varchar(4096) not null default '' ,
|
||||
severities varchar(32) not null default '',
|
||||
tags varchar(4096) not null default '[]',
|
||||
redefine_severity smallint default 0 ,
|
||||
new_severity smallint not null ,
|
||||
redefine_channels smallint default 0 ,
|
||||
new_channels varchar(255) not null default '' ,
|
||||
user_group_ids varchar(250) not null ,
|
||||
webhooks text not null,
|
||||
extra_config text not null,
|
||||
redefine_webhooks smallint default 0,
|
||||
for_duration bigint not null default 0,
|
||||
create_at bigint not null default 0,
|
||||
@@ -389,8 +401,9 @@ COMMENT ON COLUMN alert_subscribe.new_severity IS '0:Emergency 1:Warning 2:Notic
|
||||
COMMENT ON COLUMN alert_subscribe.redefine_channels IS 'is redefine channels?';
|
||||
COMMENT ON COLUMN alert_subscribe.new_channels IS 'split by space: sms voice email dingtalk wecom';
|
||||
COMMENT ON COLUMN alert_subscribe.user_group_ids IS 'split by space 1 34 5, notify cc to user_group_ids';
|
||||
COMMENT ON COLUMN alert_subscribe.extra_config IS 'extra_config';
|
||||
|
||||
|
||||
|
||||
CREATE TABLE target (
|
||||
id bigserial,
|
||||
group_id bigint not null default 0 ,
|
||||
@@ -456,6 +469,7 @@ CREATE TABLE recording_rule (
|
||||
prom_ql varchar(8192) not null ,
|
||||
prom_eval_interval int not null ,
|
||||
append_tags varchar(255) default '' ,
|
||||
query_configs text not null ,
|
||||
create_at bigint default '0',
|
||||
create_by varchar(64) default '',
|
||||
update_at bigint default '0',
|
||||
@@ -472,6 +486,7 @@ COMMENT ON COLUMN recording_rule.disabled IS '0:enabled 1:disabled';
|
||||
COMMENT ON COLUMN recording_rule.prom_ql IS 'promql';
|
||||
COMMENT ON COLUMN recording_rule.prom_eval_interval IS 'evaluate interval';
|
||||
COMMENT ON COLUMN recording_rule.append_tags IS 'split by space: service=n9e mod=api';
|
||||
COMMENT ON COLUMN recording_rule.query_configs IS 'query configs';
|
||||
|
||||
|
||||
CREATE TABLE alert_aggr_view (
|
||||
@@ -732,4 +747,21 @@ CREATE TABLE sso_config (
|
||||
content text not null,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE (name)
|
||||
) ;
|
||||
) ;
|
||||
|
||||
|
||||
CREATE TABLE es_index_pattern (
|
||||
id bigserial,
|
||||
datasource_id bigint not null default 0,
|
||||
name varchar(191) not null,
|
||||
time_field varchar(128) not null default '@timestamp',
|
||||
allow_hide_system_indices smallint not null default 0,
|
||||
fields_format varchar(4096) not null default '',
|
||||
create_at bigint default '0',
|
||||
create_by varchar(64) default '',
|
||||
update_at bigint default '0',
|
||||
update_by varchar(64) default '',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE (datasource_id, name)
|
||||
) ;
|
||||
COMMENT ON COLUMN es_index_pattern.datasource_id IS 'datasource id';
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
CREATE TABLE task_meta
|
||||
(
|
||||
id bigserial,
|
||||
title varchar(255) not null default '',
|
||||
account varchar(64) not null,
|
||||
batch int not null default 0,
|
||||
tolerance int not null default 0,
|
||||
timeout int not null default 0,
|
||||
pause varchar(255) not null default '',
|
||||
script text not null,
|
||||
args varchar(512) not null default '',
|
||||
creator varchar(64) not null default '',
|
||||
created timestamp not null default CURRENT_TIMESTAMP,
|
||||
id bigserial,
|
||||
title varchar(255) not null default '',
|
||||
account varchar(64) not null,
|
||||
batch int not null default 0,
|
||||
tolerance int not null default 0,
|
||||
timeout int not null default 0,
|
||||
pause varchar(255) not null default '',
|
||||
script text not null,
|
||||
args varchar(512) not null default '',
|
||||
stdin varchar(1024) not null default '' ,
|
||||
creator varchar(64) not null default '',
|
||||
created timestamp not null default CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id)
|
||||
) ;
|
||||
CREATE INDEX task_meta_creator_idx ON task_meta (creator);
|
||||
|
||||
@@ -9,7 +9,7 @@ Level = "DEBUG"
|
||||
# stdout, stderr, file
|
||||
Output = "stdout"
|
||||
# # rotate by time
|
||||
# KeepHours: 4
|
||||
# KeepHours = 4
|
||||
# # rotate by size
|
||||
# RotateNum = 3
|
||||
# # unit: MB
|
||||
@@ -41,24 +41,17 @@ WriteTimeout = 40
|
||||
# http server idle timeout, unit: s
|
||||
IdleTimeout = 120
|
||||
|
||||
[HTTP.Pushgw]
|
||||
[HTTP.ShowCaptcha]
|
||||
Enable = false
|
||||
|
||||
[HTTP.APIForAgent]
|
||||
Enable = true
|
||||
# [HTTP.Pushgw.BasicAuth]
|
||||
# [HTTP.APIForAgent.BasicAuth]
|
||||
# user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
[HTTP.Alert]
|
||||
[HTTP.APIForService]
|
||||
Enable = true
|
||||
[HTTP.Alert.BasicAuth]
|
||||
user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
[HTTP.Heartbeat]
|
||||
Enable = true
|
||||
# [HTTP.Heartbeat.BasicAuth]
|
||||
# user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
[HTTP.Service]
|
||||
Enable = true
|
||||
[HTTP.Service.BasicAuth]
|
||||
[HTTP.APIForService.BasicAuth]
|
||||
user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
[HTTP.JWTAuth]
|
||||
@@ -77,6 +70,16 @@ Enable = false
|
||||
HeaderUserNameKey = "X-User-Name"
|
||||
DefaultRoles = ["Standard"]
|
||||
|
||||
[HTTP.RSA]
|
||||
# open RSA
|
||||
OpenRSA = false
|
||||
# RSA public key
|
||||
RSAPublicKeyPath = "/etc/n9e/public.pem"
|
||||
# RSA private key
|
||||
RSAPrivateKeyPath = "/etc/n9e/private.pem"
|
||||
# RSA private key password
|
||||
RSAPassWord = ""
|
||||
|
||||
[DB]
|
||||
DSN="host=postgres port=5432 user=root dbname=n9e_v6 password=1234 sslmode=disable"
|
||||
# enable debug mode or not
|
||||
@@ -115,7 +118,7 @@ RedisType = "standalone"
|
||||
IP = ""
|
||||
# unit ms
|
||||
Interval = 1000
|
||||
ClusterName = "default"
|
||||
EngineName = "default"
|
||||
|
||||
# [Alert.Alerting]
|
||||
# NotifyConcurrency = 10
|
||||
@@ -128,16 +131,49 @@ I18NHeaderKey = "X-Language"
|
||||
PromQuerier = true
|
||||
AlertDetail = true
|
||||
|
||||
[Center.Ibex]
|
||||
Address = "http://ibex:10090"
|
||||
# basic auth
|
||||
BasicAuthUser = "ibex"
|
||||
BasicAuthPass = "ibex"
|
||||
# unit: ms
|
||||
Timeout = 3000
|
||||
|
||||
[Pushgw]
|
||||
# use target labels in database instead of in series
|
||||
LabelRewrite = true
|
||||
# # default busigroup key name
|
||||
# BusiGroupLabelKey = "busigroup"
|
||||
# ForceUseServerTS = false
|
||||
|
||||
[[Pushgw.Writers]]
|
||||
Url = "http://victoriametrics:8428/api/v1/write"
|
||||
# [Pushgw.DebugSample]
|
||||
# ident = "xx"
|
||||
# __name__ = "xx"
|
||||
|
||||
# [Pushgw.WriterOpt]
|
||||
# QueueMaxSize = 1000000
|
||||
# QueuePopSize = 1000
|
||||
|
||||
[[Pushgw.Writers]]
|
||||
# Url = "http://127.0.0.1:8480/insert/0/prometheus/api/v1/write"
|
||||
Url = "http://victoriametrics:8428/api/v1/write"
|
||||
# Basic auth username
|
||||
BasicAuthUser = ""
|
||||
# Basic auth password
|
||||
BasicAuthPass = ""
|
||||
# timeout settings, unit: ms
|
||||
Headers = ["X-From", "n9e"]
|
||||
Timeout = 10000
|
||||
DialTimeout = 3000
|
||||
TLSHandshakeTimeout = 30000
|
||||
ExpectContinueTimeout = 1000
|
||||
IdleConnTimeout = 90000
|
||||
# time duration, unit: ms
|
||||
KeepAlive = 30000
|
||||
MaxConnsPerHost = 0
|
||||
MaxIdleConns = 100
|
||||
MaxIdleConnsPerHost = 100
|
||||
## Optional TLS Config
|
||||
# UseTLS = false
|
||||
# TLSCA = "/etc/n9e/ca.pem"
|
||||
# TLSCert = "/etc/n9e/cert.pem"
|
||||
# TLSKey = "/etc/n9e/key.pem"
|
||||
# InsecureSkipVerify = false
|
||||
# [[Writers.WriteRelabels]]
|
||||
# Action = "replace"
|
||||
# SourceLabels = ["__address__"]
|
||||
# Regex = "([^:]+)(?::\\d+)?"
|
||||
# Replacement = "$1:80"
|
||||
# TargetLabel = "__address__"
|
||||
|
||||
@@ -214,7 +214,7 @@
|
||||
|
||||
<footer>
|
||||
<div class="copyright" style="font-style: italic">
|
||||
我们希望与您一起,将监控这个事情,做到极致!
|
||||
报警太多?使用 <a href="https://flashcat.cloud/product/flashduty/" target="_blank">FlashDuty</a> 做告警聚合降噪、排班OnCall!
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@@ -54,7 +54,7 @@ insert into user_group_member(group_id, user_id) values(1, 1);
|
||||
CREATE TABLE `configs` (
|
||||
`id` bigint unsigned not null auto_increment,
|
||||
`ckey` varchar(191) not null,
|
||||
`cval` varchar(4096) not null default '',
|
||||
`cval` text not null,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY (`ckey`)
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
||||
@@ -94,10 +94,17 @@ insert into `role_operation`(role_name, operation) values('Standard', '/log/expl
|
||||
insert into `role_operation`(role_name, operation) values('Standard', '/trace/explorer');
|
||||
insert into `role_operation`(role_name, operation) values('Standard', '/help/version');
|
||||
insert into `role_operation`(role_name, operation) values('Standard', '/help/contact');
|
||||
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('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('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');
|
||||
|
||||
insert into `role_operation`(role_name, operation) values('Standard', '/users');
|
||||
insert into `role_operation`(role_name, operation) values('Standard', '/user-groups');
|
||||
insert into `role_operation`(role_name, operation) values('Standard', '/user-groups/add');
|
||||
@@ -274,6 +281,7 @@ CREATE TABLE `alert_rule` (
|
||||
`runbook_url` varchar(255),
|
||||
`append_tags` varchar(255) not null default '' comment 'split by space: service=n9e mod=api',
|
||||
`annotations` text not null comment 'annotations',
|
||||
`extra_config` text not null comment 'extra_config',
|
||||
`create_at` bigint not null default 0,
|
||||
`create_by` varchar(64) not null default '',
|
||||
`update_at` bigint not null default 0,
|
||||
@@ -298,6 +306,7 @@ CREATE TABLE `alert_mute` (
|
||||
`disabled` tinyint(1) not null default 0 comment '0:enabled 1:disabled',
|
||||
`mute_time_type` tinyint(1) not null default 0,
|
||||
`periodic_mutes` varchar(4096) not null default '',
|
||||
`severities` varchar(32) not null default '',
|
||||
`create_at` bigint not null default 0,
|
||||
`create_by` varchar(64) not null default '',
|
||||
`update_at` bigint not null default 0,
|
||||
@@ -317,6 +326,7 @@ CREATE TABLE `alert_subscribe` (
|
||||
`datasource_ids` varchar(255) not null default '' comment 'datasource ids',
|
||||
`cluster` varchar(128) not null,
|
||||
`rule_id` bigint not null default 0,
|
||||
`severities` varchar(32) not null default '',
|
||||
`tags` varchar(4096) not null default '' comment 'json,map,tagkey->regexp|value',
|
||||
`redefine_severity` tinyint(1) default 0 comment 'is redefine severity?',
|
||||
`new_severity` tinyint(1) not null comment '0:Emergency 1:Warning 2:Notice',
|
||||
@@ -324,6 +334,7 @@ CREATE TABLE `alert_subscribe` (
|
||||
`new_channels` varchar(255) not null default '' comment 'split by space: sms voice email dingtalk wecom',
|
||||
`user_group_ids` varchar(250) not null comment 'split by space 1 34 5, notify cc to user_group_ids',
|
||||
`webhooks` text not null,
|
||||
`extra_config` text not null comment 'extra_config',
|
||||
`redefine_webhooks` tinyint(1) default 0,
|
||||
`for_duration` bigint not null default 0,
|
||||
`create_at` bigint not null default 0,
|
||||
@@ -395,6 +406,7 @@ CREATE TABLE `recording_rule` (
|
||||
`prom_ql` varchar(8192) not null comment 'promql',
|
||||
`prom_eval_interval` int not null comment 'evaluate interval',
|
||||
`append_tags` varchar(255) default '' comment 'split by space: service=n9e mod=api',
|
||||
`query_configs` text not null comment 'query configs',
|
||||
`create_at` bigint default '0',
|
||||
`create_by` varchar(64) default '',
|
||||
`update_at` bigint default '0',
|
||||
@@ -606,4 +618,19 @@ CREATE TABLE `sso_config` (
|
||||
`content` text not null,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY (`name`)
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
||||
|
||||
CREATE TABLE `es_index_pattern` (
|
||||
`id` bigint unsigned not null auto_increment,
|
||||
`datasource_id` bigint not null default 0 comment 'datasource id',
|
||||
`name` varchar(191) not null,
|
||||
`time_field` varchar(128) not null default '@timestamp',
|
||||
`allow_hide_system_indices` tinyint(1) not null default 0,
|
||||
`fields_format` varchar(4096) not null default '',
|
||||
`create_at` bigint default '0',
|
||||
`create_by` varchar(64) default '',
|
||||
`update_at` bigint default '0',
|
||||
`update_by` varchar(64) default '',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY (`datasource_id`, `name`)
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
||||
@@ -6,17 +6,18 @@ use ibex;
|
||||
|
||||
CREATE TABLE `task_meta`
|
||||
(
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`title` varchar(255) not null default '',
|
||||
`account` varchar(64) not null,
|
||||
`batch` int unsigned not null default 0,
|
||||
`tolerance` int unsigned not null default 0,
|
||||
`timeout` int unsigned not null default 0,
|
||||
`pause` varchar(255) not null default '',
|
||||
`script` text not null,
|
||||
`args` varchar(512) not null default '',
|
||||
`creator` varchar(64) not null default '',
|
||||
`created` timestamp not null default CURRENT_TIMESTAMP,
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||
`title` varchar(255) not null default '',
|
||||
`account` varchar(64) not null,
|
||||
`batch` int unsigned not null default 0,
|
||||
`tolerance` int unsigned not null default 0,
|
||||
`timeout` int unsigned not null default 0,
|
||||
`pause` varchar(255) not null default '',
|
||||
`script` text not null,
|
||||
`args` varchar(512) not null default '',
|
||||
`stdin` varchar(1024) not null default '',
|
||||
`creator` varchar(64) not null default '',
|
||||
`created` timestamp not null default CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY (`creator`),
|
||||
KEY (`created`)
|
||||
|
||||
8
dumper/dumper.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package dumper
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
// package level functions
|
||||
func ConfigRouter(r *gin.Engine) {
|
||||
syncDumper.ConfigRouter(r)
|
||||
}
|
||||
110
dumper/sync.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package dumper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/toolkits/pkg/time"
|
||||
)
|
||||
|
||||
type SyncRecord struct {
|
||||
Timestamp int64
|
||||
Mills int64
|
||||
Count int
|
||||
Message string
|
||||
}
|
||||
|
||||
func (sr *SyncRecord) String() string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("timestamp: ")
|
||||
sb.WriteString(time.Format(sr.Timestamp))
|
||||
sb.WriteString(", mills: ")
|
||||
sb.WriteString(fmt.Sprint(sr.Mills, "ms"))
|
||||
sb.WriteString(", count: ")
|
||||
sb.WriteString(fmt.Sprint(sr.Count))
|
||||
sb.WriteString(", message: ")
|
||||
sb.WriteString(sr.Message)
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
type SyncRecords struct {
|
||||
Current *SyncRecord
|
||||
Last *SyncRecord
|
||||
}
|
||||
|
||||
type SyncDumper struct {
|
||||
sync.RWMutex
|
||||
records map[string]*SyncRecords
|
||||
}
|
||||
|
||||
func NewSyncDumper() *SyncDumper {
|
||||
return &SyncDumper{
|
||||
records: make(map[string]*SyncRecords),
|
||||
}
|
||||
}
|
||||
|
||||
var syncDumper = NewSyncDumper()
|
||||
|
||||
func (sd *SyncDumper) Put(key string, timestamp, mills int64, count int, message string) {
|
||||
sr := &SyncRecord{
|
||||
Timestamp: timestamp,
|
||||
Mills: mills,
|
||||
Count: count,
|
||||
Message: message,
|
||||
}
|
||||
|
||||
sd.Lock()
|
||||
defer sd.Unlock()
|
||||
|
||||
if _, ok := sd.records[key]; !ok {
|
||||
sd.records[key] = &SyncRecords{Current: sr}
|
||||
return
|
||||
}
|
||||
|
||||
sd.records[key].Last = sd.records[key].Current
|
||||
sd.records[key].Current = sr
|
||||
}
|
||||
|
||||
// busi_groups:
|
||||
// last: timestamp, mills, count
|
||||
// curr: timestamp, mills, count
|
||||
func (sd *SyncDumper) Sprint() string {
|
||||
sd.RLock()
|
||||
defer sd.RUnlock()
|
||||
|
||||
var sb strings.Builder
|
||||
sb.WriteString("\n")
|
||||
|
||||
for k, v := range sd.records {
|
||||
sb.WriteString(k)
|
||||
sb.WriteString(":\n")
|
||||
if v.Last != nil {
|
||||
sb.WriteString("last: ")
|
||||
sb.WriteString(v.Last.String())
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
sb.WriteString("curr: ")
|
||||
sb.WriteString(v.Current.String())
|
||||
sb.WriteString("\n\n")
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (sd *SyncDumper) ConfigRouter(r *gin.Engine) {
|
||||
r.GET("/dumper/sync", func(c *gin.Context) {
|
||||
clientIP := c.ClientIP()
|
||||
if clientIP != "127.0.0.1" && clientIP != "::1" {
|
||||
c.String(403, "forbidden")
|
||||
return
|
||||
}
|
||||
c.String(200, sd.Sprint())
|
||||
})
|
||||
}
|
||||
|
||||
func PutSyncRecord(key string, timestamp, mills int64, count int, message string) {
|
||||
syncDumper.Put(key, timestamp, mills, count, message)
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
[Global]
|
||||
RunMode = "release"
|
||||
|
||||
[CenterApi]
|
||||
Addrs = ["http://127.0.0.1:17000"]
|
||||
[CenterApi.BasicAuth]
|
||||
user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
[Alert]
|
||||
[Alert.Heartbeat]
|
||||
# auto detect if blank
|
||||
IP = ""
|
||||
# unit ms
|
||||
Interval = 1000
|
||||
EngineName = "default02"
|
||||
|
||||
[Log]
|
||||
# log write dir
|
||||
Dir = "logs"
|
||||
# log level: DEBUG INFO WARNING ERROR
|
||||
Level = "DEBUG"
|
||||
# stdout, stderr, file
|
||||
Output = "stdout"
|
||||
# # rotate by time
|
||||
# KeepHours = 4
|
||||
# # rotate by size
|
||||
# RotateNum = 3
|
||||
# # unit: MB
|
||||
# RotateSize = 256
|
||||
|
||||
[HTTP]
|
||||
# http listening address
|
||||
Host = "0.0.0.0"
|
||||
# http listening port
|
||||
Port = 17001
|
||||
# https cert file path
|
||||
CertFile = ""
|
||||
# https key file path
|
||||
KeyFile = ""
|
||||
# whether print access log
|
||||
PrintAccessLog = false
|
||||
# whether enable pprof
|
||||
PProf = false
|
||||
# expose prometheus /metrics?
|
||||
ExposeMetrics = true
|
||||
# http graceful shutdown timeout, unit: s
|
||||
ShutdownTimeout = 30
|
||||
# max content length: 64M
|
||||
MaxContentLength = 67108864
|
||||
# http server read timeout, unit: s
|
||||
ReadTimeout = 20
|
||||
# http server write timeout, unit: s
|
||||
WriteTimeout = 40
|
||||
# http server idle timeout, unit: s
|
||||
IdleTimeout = 120
|
||||
|
||||
[HTTP.Alert]
|
||||
Enable = true
|
||||
[HTTP.Alert.BasicAuth]
|
||||
user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
@@ -41,24 +41,17 @@ WriteTimeout = 40
|
||||
# http server idle timeout, unit: s
|
||||
IdleTimeout = 120
|
||||
|
||||
[HTTP.Pushgw]
|
||||
[HTTP.ShowCaptcha]
|
||||
Enable = false
|
||||
|
||||
[HTTP.APIForAgent]
|
||||
Enable = true
|
||||
# [HTTP.Pushgw.BasicAuth]
|
||||
# [HTTP.APIForAgent.BasicAuth]
|
||||
# user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
[HTTP.Alert]
|
||||
[HTTP.APIForService]
|
||||
Enable = true
|
||||
[HTTP.Alert.BasicAuth]
|
||||
user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
[HTTP.Heartbeat]
|
||||
Enable = true
|
||||
# [HTTP.Heartbeat.BasicAuth]
|
||||
# user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
[HTTP.Service]
|
||||
Enable = true
|
||||
[HTTP.Service.BasicAuth]
|
||||
[HTTP.APIForService.BasicAuth]
|
||||
user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
[HTTP.JWTAuth]
|
||||
@@ -77,8 +70,19 @@ Enable = false
|
||||
HeaderUserNameKey = "X-User-Name"
|
||||
DefaultRoles = ["Standard"]
|
||||
|
||||
[HTTP.RSA]
|
||||
# open RSA
|
||||
OpenRSA = false
|
||||
# RSA public key
|
||||
RSAPublicKeyPath = "/etc/n9e/public.pem"
|
||||
# RSA private key
|
||||
RSAPrivateKeyPath = "/etc/n9e/private.pem"
|
||||
# RSA private key password
|
||||
RSAPassWord = ""
|
||||
|
||||
[DB]
|
||||
# postgres: host=%s port=%s user=%s dbname=%s password=%s sslmode=%s
|
||||
# postgres: DSN="host=127.0.0.1 port=5432 user=root dbname=n9e_v6 password=1234 sslmode=disable"
|
||||
DSN="root:1234@tcp(127.0.0.1:3306)/n9e_v6?charset=utf8mb4&parseTime=True&loc=Local&allowNativePasswords=true"
|
||||
# enable debug mode or not
|
||||
Debug = false
|
||||
@@ -141,12 +145,8 @@ LabelRewrite = true
|
||||
# __name__ = "xx"
|
||||
|
||||
# [Pushgw.WriterOpt]
|
||||
# # Writer Options
|
||||
# QueueCount = 1000
|
||||
# QueueMaxSize = 1000000
|
||||
# QueuePopSize = 1000
|
||||
# # ident or metric
|
||||
# ShardingKey = "ident"
|
||||
|
||||
[[Pushgw.Writers]]
|
||||
# Url = "http://127.0.0.1:8480/insert/0/prometheus/api/v1/write"
|
||||
|
||||
@@ -3,9 +3,72 @@ RunMode = "release"
|
||||
|
||||
[CenterApi]
|
||||
Addrs = ["http://127.0.0.1:17000"]
|
||||
[CenterApi.BasicAuth]
|
||||
BasicAuthUser = "user001"
|
||||
BasicAuthPass = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
# unit: ms
|
||||
Timeout = 9000
|
||||
|
||||
[Log]
|
||||
# log write dir
|
||||
Dir = "logs"
|
||||
# log level: DEBUG INFO WARNING ERROR
|
||||
Level = "DEBUG"
|
||||
# stdout, stderr, file
|
||||
Output = "stdout"
|
||||
# # rotate by time
|
||||
# KeepHours = 4
|
||||
# # rotate by size
|
||||
# RotateNum = 3
|
||||
# # unit: MB
|
||||
# RotateSize = 256
|
||||
|
||||
[HTTP]
|
||||
# http listening address
|
||||
Host = "0.0.0.0"
|
||||
# http listening port
|
||||
Port = 19000
|
||||
# https cert file path
|
||||
CertFile = ""
|
||||
# https key file path
|
||||
KeyFile = ""
|
||||
# whether print access log
|
||||
PrintAccessLog = false
|
||||
# whether enable pprof
|
||||
PProf = false
|
||||
# expose prometheus /metrics?
|
||||
ExposeMetrics = true
|
||||
# http graceful shutdown timeout, unit: s
|
||||
ShutdownTimeout = 30
|
||||
# max content length: 64M
|
||||
MaxContentLength = 67108864
|
||||
# http server read timeout, unit: s
|
||||
ReadTimeout = 20
|
||||
# http server write timeout, unit: s
|
||||
WriteTimeout = 40
|
||||
# http server idle timeout, unit: s
|
||||
IdleTimeout = 120
|
||||
|
||||
[HTTP.APIForAgent]
|
||||
Enable = true
|
||||
# [HTTP.APIForAgent.BasicAuth]
|
||||
# user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
[HTTP.APIForService]
|
||||
Enable = true
|
||||
[HTTP.APIForService.BasicAuth]
|
||||
user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
|
||||
[Alert]
|
||||
[Alert.Heartbeat]
|
||||
# auto detect if blank
|
||||
IP = ""
|
||||
# unit ms
|
||||
Interval = 1000
|
||||
EngineName = "edge"
|
||||
|
||||
# [Alert.Alerting]
|
||||
# NotifyConcurrency = 10
|
||||
|
||||
[Pushgw]
|
||||
# use target labels in database instead of in series
|
||||
LabelRewrite = true
|
||||
@@ -18,12 +81,8 @@ LabelRewrite = true
|
||||
# __name__ = "xx"
|
||||
|
||||
# [Pushgw.WriterOpt]
|
||||
# # Writer Options
|
||||
# QueueCount = 1000
|
||||
# QueueMaxSize = 1000000
|
||||
# QueuePopSize = 1000
|
||||
# # ident or metric
|
||||
# ShardingKey = "ident"
|
||||
|
||||
[[Pushgw.Writers]]
|
||||
# Url = "http://127.0.0.1:8480/insert/0/prometheus/api/v1/write"
|
||||
@@ -56,48 +115,3 @@ MaxIdleConnsPerHost = 100
|
||||
# Regex = "([^:]+)(?::\\d+)?"
|
||||
# Replacement = "$1:80"
|
||||
# TargetLabel = "__address__"
|
||||
|
||||
[Log]
|
||||
# log write dir
|
||||
Dir = "logs"
|
||||
# log level: DEBUG INFO WARNING ERROR
|
||||
Level = "DEBUG"
|
||||
# stdout, stderr, file
|
||||
Output = "stdout"
|
||||
# # rotate by time
|
||||
# KeepHours = 4
|
||||
# # rotate by size
|
||||
# RotateNum = 3
|
||||
# # unit: MB
|
||||
# RotateSize = 256
|
||||
|
||||
[HTTP]
|
||||
# http listening address
|
||||
Host = "0.0.0.0"
|
||||
# http listening port
|
||||
Port = 17000
|
||||
# https cert file path
|
||||
CertFile = ""
|
||||
# https key file path
|
||||
KeyFile = ""
|
||||
# whether print access log
|
||||
PrintAccessLog = false
|
||||
# whether enable pprof
|
||||
PProf = false
|
||||
# expose prometheus /metrics?
|
||||
ExposeMetrics = true
|
||||
# http graceful shutdown timeout, unit: s
|
||||
ShutdownTimeout = 30
|
||||
# max content length: 64M
|
||||
MaxContentLength = 67108864
|
||||
# http server read timeout, unit: s
|
||||
ReadTimeout = 20
|
||||
# http server write timeout, unit: s
|
||||
WriteTimeout = 40
|
||||
# http server idle timeout, unit: s
|
||||
IdleTimeout = 120
|
||||
|
||||
[HTTP.Pushgw]
|
||||
Enable = true
|
||||
# [HTTP.Pushgw.BasicAuth]
|
||||
# user001 = "ccc26da7b9aba533cbb263a36c07dcc5"
|
||||
@@ -40,14 +40,14 @@ zh:
|
||||
kernel_processes_forked: fork的进程数
|
||||
|
||||
mem_active: 活跃使用的内存总数(包括cache和buffer内存)
|
||||
mem_available: 应用程序可用内存数
|
||||
mem_available: 可用内存大小(bytes)
|
||||
mem_available_percent: 内存剩余百分比(0~100)
|
||||
mem_buffered: 用来给文件做缓冲大小
|
||||
mem_cached: 被高速缓冲存储器(cache memory)用的内存的大小(等于 diskcache minus SwapCache )
|
||||
mem_commit_limit: 根据超额分配比率('vm.overcommit_ratio'),这是当前在系统上分配可用的内存总量,这个限制只是在模式2('vm.overcommit_memory')时启用
|
||||
mem_committed_as: 目前在系统上分配的内存量。是所有进程申请的内存的总和
|
||||
mem_dirty: 等待被写回到磁盘的内存大小
|
||||
mem_free: 空闲内存数
|
||||
mem_free: 空闲内存大小(bytes)
|
||||
mem_high_free: 未被使用的高位内存大小
|
||||
mem_high_total: 高位内存总大小(Highmem是指所有内存高于860MB的物理内存,Highmem区域供用户程序使用,或用于页面缓存。该区域不是直接映射到内核空间。内核必须使用不同的手法使用该段内存)
|
||||
mem_huge_page_size: 每个大页的大小
|
||||
|
||||
@@ -107,4 +107,5 @@ ops:
|
||||
- "/help/source"
|
||||
- "/help/sso"
|
||||
- "/help/notification-tpls"
|
||||
- "/help/notification-settings"
|
||||
- "/help/notification-settings"
|
||||
- "/help/migrate"
|
||||
@@ -1,26 +0,0 @@
|
||||
# 告警消息模版文件
|
||||
|
||||
模版中可以使用的变量参考`AlertCurEvent`对象
|
||||
模版语法如何使用可以参考[html/template](https://pkg.go.dev/html/template)
|
||||
|
||||
## 如何在告警模版中添加监控详情url
|
||||
|
||||
假设web的地址是http://127.0.0.1:18000/, 实际使用时用web地址替换该地址
|
||||
|
||||
在监控模版中添加以下行:
|
||||
|
||||
* dingtalk / wecom / feishu
|
||||
```markdown
|
||||
[监控详情](http://127.0.0.1:18000/metric/explorer?promql={{ .PromQl | escape }})
|
||||
```
|
||||
|
||||
* mailbody
|
||||
|
||||
```html
|
||||
<tr>
|
||||
<th>监控详情:</th>
|
||||
<td>
|
||||
<a href="http://127.0.0.1:18000/metric/explorer?promql={{ .PromQl | escape }}" target="_blank">点击查看</a>
|
||||
</td>
|
||||
</tr>
|
||||
```
|
||||
@@ -1,11 +0,0 @@
|
||||
#### {{if .IsRecovered}}<font color="#008800">S{{.Severity}} - Recovered - {{.RuleName}}</font>{{else}}<font color="#FF0000">S{{.Severity}} - Triggered - {{.RuleName}}</font>{{end}}
|
||||
|
||||
---
|
||||
|
||||
- **规则标题**: {{.RuleName}}{{if .RuleNote}}
|
||||
- **规则备注**: {{.RuleNote}}{{end}}
|
||||
{{if not .IsRecovered}}- **触发时值**: {{.TriggerValue}}{{end}}
|
||||
{{if .TargetIdent}}- **监控对象**: {{.TargetIdent}}{{end}}
|
||||
- **监控指标**: {{.TagsJSON}}
|
||||
- {{if .IsRecovered}}**恢复时间**: {{timeformat .LastEvalTime}}{{else}}**触发时间**: {{timeformat .TriggerTime}}{{end}}
|
||||
- **发送时间**: {{timestamp}}
|
||||
@@ -1,217 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>夜莺告警通知</title>
|
||||
<style type="text/css">
|
||||
.wrapper {
|
||||
background-color: #f8f8f8;
|
||||
padding: 15px;
|
||||
height: 100%;
|
||||
}
|
||||
.main {
|
||||
width: 600px;
|
||||
padding: 30px;
|
||||
margin: 0 auto;
|
||||
background-color: #fff;
|
||||
font-size: 12px;
|
||||
font-family: verdana,'Microsoft YaHei',Consolas,'Deja Vu Sans Mono','Bitstream Vera Sans Mono';
|
||||
}
|
||||
header {
|
||||
border-radius: 2px 2px 0 0;
|
||||
}
|
||||
header .title {
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
margin: 0;
|
||||
}
|
||||
header .sub-desc {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
margin-top: 6px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
hr {
|
||||
margin: 20px 0;
|
||||
height: 0;
|
||||
border: none;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
}
|
||||
em {
|
||||
font-weight: 600;
|
||||
}
|
||||
table {
|
||||
margin: 20px 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table tbody tr{
|
||||
font-weight: 200;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.succ {
|
||||
background-color: green;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.fail {
|
||||
background-color: red;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.succ th, .succ td, .fail th, .fail td {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
table tbody tr th {
|
||||
width: 80px;
|
||||
text-align: right;
|
||||
}
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
.body {
|
||||
margin-top: 24px;
|
||||
}
|
||||
.body-text {
|
||||
color: #666666;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
.body-extra {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
.body-extra.text-right a {
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
}
|
||||
.body-extra.text-right a:hover {
|
||||
color: #666;
|
||||
}
|
||||
.button {
|
||||
width: 200px;
|
||||
height: 50px;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
border-radius: 2px;
|
||||
background: #2D77EE;
|
||||
line-height: 50px;
|
||||
font-size: 20px;
|
||||
color: #FFFFFF;
|
||||
cursor: pointer;
|
||||
}
|
||||
.button:hover {
|
||||
background: rgb(25, 115, 255);
|
||||
border-color: rgb(25, 115, 255);
|
||||
color: #fff;
|
||||
}
|
||||
footer {
|
||||
margin-top: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
.footer-logo {
|
||||
text-align: right;
|
||||
}
|
||||
.footer-logo-image {
|
||||
width: 108px;
|
||||
height: 27px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.copyright {
|
||||
margin-top: 10px;
|
||||
font-size: 12px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<div class="main">
|
||||
<header>
|
||||
<h3 class="title">{{.RuleName}}</h3>
|
||||
<p class="sub-desc"></p>
|
||||
</header>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="body">
|
||||
<table cellspacing="0" cellpadding="0" border="0">
|
||||
<tbody>
|
||||
{{if .IsRecovered}}
|
||||
<tr class="succ">
|
||||
<th>级别状态:</th>
|
||||
<td>S{{.Severity}} Recovered</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr class="fail">
|
||||
<th>级别状态:</th>
|
||||
<td>S{{.Severity}} Triggered</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
|
||||
<tr>
|
||||
<th>策略备注:</th>
|
||||
<td>{{.RuleNote}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>设备备注:</th>
|
||||
<td>{{.TargetNote}}</td>
|
||||
</tr>
|
||||
{{if not .IsRecovered}}
|
||||
<tr>
|
||||
<th>触发时值:</th>
|
||||
<td>{{.TriggerValue}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
|
||||
{{if .TargetIdent}}
|
||||
<tr>
|
||||
<th>监控对象:</th>
|
||||
<td>{{.TargetIdent}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
<tr>
|
||||
<th>监控指标:</th>
|
||||
<td>{{.TagsJSON}}</td>
|
||||
</tr>
|
||||
|
||||
{{if .IsRecovered}}
|
||||
<tr>
|
||||
<th>恢复时间:</th>
|
||||
<td>{{timeformat .LastEvalTime}}</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr>
|
||||
<th>触发时间:</th>
|
||||
<td>
|
||||
{{timeformat .TriggerTime}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
|
||||
<tr>
|
||||
<th>发送时间:</th>
|
||||
<td>
|
||||
{{timestamp}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<footer>
|
||||
<div class="copyright" style="font-style: italic">
|
||||
我们希望与您一起,将监控这个事情,做到极致!
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,7 +0,0 @@
|
||||
级别状态: S{{.Severity}} {{if .IsRecovered}}Recovered{{else}}Triggered{{end}}
|
||||
规则名称: {{.RuleName}}{{if .RuleNote}}
|
||||
规则备注: {{.RuleNote}}{{end}}
|
||||
监控指标: {{.TagsJSON}}
|
||||
{{if .IsRecovered}}恢复时间:{{timeformat .LastEvalTime}}{{else}}触发时间: {{timeformat .TriggerTime}}
|
||||
触发时值: {{.TriggerValue}}{{end}}
|
||||
发送时间: {{timestamp}}
|
||||
@@ -1 +0,0 @@
|
||||
{{if .IsRecovered}}Recovered{{else}}Triggered{{end}}: {{.RuleName}} {{.TagsJSON}}
|
||||
@@ -1,7 +0,0 @@
|
||||
级别状态: S{{.Severity}} {{if .IsRecovered}}Recovered{{else}}Triggered{{end}}
|
||||
规则名称: {{.RuleName}}{{if .RuleNote}}
|
||||
规则备注: {{.RuleNote}}{{end}}
|
||||
监控指标: {{.TagsJSON}}
|
||||
{{if .IsRecovered}}恢复时间:{{timeformat .LastEvalTime}}{{else}}触发时间: {{timeformat .TriggerTime}}
|
||||
触发时值: {{.TriggerValue}}{{end}}
|
||||
发送时间: {{timestamp}}
|
||||
@@ -1,9 +0,0 @@
|
||||
**级别状态**: {{if .IsRecovered}}<font color="info">S{{.Severity}} Recovered</font>{{else}}<font color="warning">S{{.Severity}} Triggered</font>{{end}}
|
||||
**规则标题**: {{.RuleName}}{{if .RuleNote}}
|
||||
**规则备注**: {{.RuleNote}}{{end}}{{if .TargetIdent}}
|
||||
**监控对象**: {{.TargetIdent}}{{end}}
|
||||
**监控指标**: {{.TagsJSON}}{{if not .IsRecovered}}
|
||||
**触发时值**: {{.TriggerValue}}{{end}}
|
||||
{{if .IsRecovered}}**恢复时间**: {{timeformat .LastEvalTime}}{{else}}**首次触发时间**: {{timeformat .FirstTriggerTime}}{{end}}
|
||||
{{$time_duration := sub now.Unix .FirstTriggerTime }}{{if .IsRecovered}}{{$time_duration = sub .LastEvalTime .FirstTriggerTime }}{{end}}**持续时长**: {{humanizeDurationInterface $time_duration}}
|
||||
**发送时间**: {{timestamp}}
|
||||
@@ -1,9 +0,0 @@
|
||||
**级别状态**: {{if .IsRecovered}}<font color="info">S{{.Severity}} Recovered</font>{{else}}<font color="warning">S{{.Severity}} Triggered</font>{{end}}
|
||||
**规则标题**: {{.RuleName}}{{if .RuleNote}}
|
||||
**规则备注**: {{.RuleNote}}{{end}}{{if .TargetIdent}}
|
||||
**监控对象**: {{.TargetIdent}}{{end}}
|
||||
**监控指标**: {{.TagsJSON}}{{if not .IsRecovered}}
|
||||
**触发时值**: {{.TriggerValue}}{{end}}
|
||||
{{if .IsRecovered}}**恢复时间**: {{timeformat .LastEvalTime}}{{else}}**首次触发时间**: {{timeformat .FirstTriggerTime}}{{end}}
|
||||
{{$time_duration := sub now.Unix .FirstTriggerTime }}{{if .IsRecovered}}{{$time_duration = sub .LastEvalTime .FirstTriggerTime }}{{end}}**持续时长**: {{humanizeDurationInterface $time_duration}}
|
||||
**发送时间**: {{timestamp}}
|
||||
29
fe.sh
@@ -1,17 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
TAG=$(curl -sX GET https://api.github.com/repos/n9e/fe/releases/latest | awk '/tag_name/{print $4;exit}' FS='[""]')
|
||||
VERSION=$(echo $TAG)
|
||||
cp -f ./docker/initsql/a-n9e.sql n9e.sql
|
||||
|
||||
curl -o n9e-fe-${VERSION}.tar.gz -L https://github.com/n9e/fe/releases/download/${TAG}/n9e-fe-${VERSION}.tar.gz
|
||||
if [ ! -d "./pub" ]; then
|
||||
TAG=$(curl -sX GET https://api.github.com/repos/n9e/fe/releases/latest | awk '/tag_name/{print $4;exit}' FS='[""]')
|
||||
|
||||
tar zxvf n9e-fe-${VERSION}.tar.gz
|
||||
if ! curl -o n9e-fe-${TAG}.tar.gz -L https://github.com/n9e/fe/releases/download/${TAG}/n9e-fe-${TAG}.tar.gz; then
|
||||
echo "failed to download n9e-fe-${TAG}.tar.gz!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp ./docker/initsql/a-n9e.sql n9e.sql
|
||||
if ! tar zxf n9e-fe-${TAG}.tar.gz; then
|
||||
echo "failed to untar n9e-fe-${TAG}.tar.gz!"
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
# Embed files into a Go executable
|
||||
statik -src=./pub -dest=./front
|
||||
GOPATH=$(go env GOPATH)
|
||||
GOPATH=${GOPATH:-/home/runner/go}
|
||||
|
||||
# rm the fe file
|
||||
rm n9e-fe-${VERSION}.tar.gz
|
||||
rm -r ./pub
|
||||
# Embed files into a go binary
|
||||
if ! $GOPATH/bin/statik -src=./pub -dest=./front; then
|
||||
echo "failed to embed files into a go binary!"
|
||||
exit 4
|
||||
fi
|
||||
|
||||
41
go.mod
@@ -7,18 +7,20 @@ require (
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/gin-contrib/pprof v1.4.0
|
||||
github.com/gin-gonic/gin v1.9.0
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/go-ldap/ldap/v3 v3.4.4
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/golang/snappy v0.0.4
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/koding/multiconfig v0.0.0-20171124222453-69c27309b2d7
|
||||
github.com/mailru/easyjson v0.7.7
|
||||
github.com/mattn/go-isatty v0.0.17
|
||||
github.com/pelletier/go-toml/v2 v2.0.6
|
||||
github.com/mattn/go-isatty v0.0.19
|
||||
github.com/mojocn/base64Captcha v1.3.5
|
||||
github.com/pelletier/go-toml/v2 v2.0.8
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/prometheus/common v0.39.0
|
||||
@@ -27,7 +29,7 @@ require (
|
||||
github.com/redis/go-redis/v9 v9.0.2
|
||||
github.com/tidwall/gjson v1.14.0
|
||||
github.com/toolkits/pkg v1.3.3
|
||||
golang.org/x/oauth2 v0.3.0
|
||||
golang.org/x/oauth2 v0.4.0
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gorm.io/driver/mysql v1.4.4
|
||||
gorm.io/driver/postgres v1.4.5
|
||||
@@ -37,19 +39,21 @@ require (
|
||||
require (
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.8.0 // indirect
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fatih/camelcase v1.0.0 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.11.2 // indirect
|
||||
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||
github.com/goccy/go-json v0.10.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.13.0 // indirect
|
||||
@@ -62,8 +66,8 @@ require (
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
@@ -74,17 +78,18 @@ require (
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.9 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
go.uber.org/automaxprocs v1.4.0 // indirect
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||
golang.org/x/crypto v0.5.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.9.0 // indirect
|
||||
golang.org/x/image v0.5.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect
|
||||
google.golang.org/grpc v1.51.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
|
||||
google.golang.org/grpc v1.53.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
||||
88
go.sum
@@ -12,8 +12,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
|
||||
github.com/bsm/ginkgo/v2 v2.5.0 h1:aOAnND1T40wEdAtkGSkvSICWeQ8L3UASX7YVCqQx+eQ=
|
||||
github.com/bsm/gomega v1.20.0 h1:JhAwLmtRzXFTx2AkALSLa8ijZafntmhSoU63Ok18Uq8=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
|
||||
github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
@@ -45,6 +45,8 @@ github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8
|
||||
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/pprof v1.4.0 h1:XxiBSf5jWZ5i16lNOPbMTVdgHBdhfGRD5PZ1LWazzvg=
|
||||
@@ -53,8 +55,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
|
||||
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
@@ -73,20 +75,22 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
|
||||
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
|
||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
||||
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -102,7 +106,7 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -110,6 +114,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||
@@ -169,8 +175,9 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/koding/multiconfig v0.0.0-20171124222453-69c27309b2d7 h1:SWlt7BoQNASbhTUD0Oy5yysI2seJ7vWuGUp///OM4TM=
|
||||
github.com/koding/multiconfig v0.0.0-20171124222453-69c27309b2d7/go.mod h1:Y2SaZf2Rzd0pXkLVhLlCiAXFCLSXAIbTKDivVgff/AM=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -185,8 +192,9 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
@@ -200,8 +208,8 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -210,9 +218,11 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0=
|
||||
github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -267,8 +277,10 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w=
|
||||
github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
@@ -283,8 +295,8 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
|
||||
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
@@ -302,8 +314,9 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@@ -316,9 +329,12 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
|
||||
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
@@ -343,12 +359,12 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8=
|
||||
golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
|
||||
golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
|
||||
golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -375,10 +391,11 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -389,8 +406,9 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@@ -419,20 +437,20 @@ google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY=
|
||||
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
|
||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U=
|
||||
google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=
|
||||
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
|
||||
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
313
integrations/AliYun/dashboards/ecs.json
Normal file
@@ -0,0 +1,313 @@
|
||||
{
|
||||
"name": "阿里云ECS",
|
||||
"tags": "",
|
||||
"ident": "",
|
||||
"configs": {
|
||||
"var": [
|
||||
{
|
||||
"name": "ident",
|
||||
"type": "query",
|
||||
"datasource": {
|
||||
"cate": "prometheus"
|
||||
},
|
||||
"definition": "label_values(aliyun_acs_ecs_dashboard_cpu_utilization_average,ident)",
|
||||
"multi": true,
|
||||
"allOption": true
|
||||
}
|
||||
],
|
||||
"panels": [
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "8606d5ad-c3c7-4b1d-86bf-474d3302ee17",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"i": "8606d5ad-c3c7-4b1d-86bf-474d3302ee17",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceName": "Default",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_ecs_dashboard_cpu_utilization_average{ident=~\"$ident\"}) by (ident,instance_id)"
|
||||
}
|
||||
],
|
||||
"name": "CPU平均使用率",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "c7034fe3-5521-4867-a8bd-429767cc03a2",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0,
|
||||
"i": "55404296-0bd9-409d-aeaf-e9c7cceea0dd",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceName": "Default",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_ecs_dashboard_memory_usedutilization_average{ident=~\"$ident\"}) by (ident,instance_id)"
|
||||
}
|
||||
],
|
||||
"name": "内存平均使用率",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "e4c11925-b359-4edb-9269-4bdd4d230224",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 4,
|
||||
"i": "0c7b3a5a-ef12-4349-be9b-7a245bf01418",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceName": "Default",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(aliyun_acs_ecs_dashboard_load_5m_average{ident=~\"$ident\"}) by (ident,instance_id)",
|
||||
"refId": "B",
|
||||
"legend": "{{ident}} {{instance_id}} 5分钟负载"
|
||||
}
|
||||
],
|
||||
"name": "系统负载[5m]",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {
|
||||
"util": "percent"
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "388d4da6-eb1f-48f1-955d-37579809dfec",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 5,
|
||||
"i": "5abea3d2-ea82-4bdb-a4f0-4dd1316c0377",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceName": "Default",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_ecs_dashboard_diskusage_utilization_average{ident=~\"$ident\"}) by (ident,instance_id)"
|
||||
}
|
||||
],
|
||||
"name": "磁盘平均使用率",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {
|
||||
"util": "percent"
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "f8d19cc9-0168-4c13-b9a9-c7980eced974",
|
||||
"layout": {
|
||||
"x": 0,
|
||||
"y": 9,
|
||||
"w": 12,
|
||||
"h": 4,
|
||||
"i": "f8d19cc9-0168-4c13-b9a9-c7980eced974"
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_ecs_dashboard_intranet_in_average{ident=~\"$ident\"}) by (ident,instance_id)",
|
||||
"legend": "主机:{{ident}} 实例ID: {{instance_id}} 入流量",
|
||||
"step": 300,
|
||||
"time": {
|
||||
"start": "now-5m",
|
||||
"end": "now"
|
||||
}
|
||||
},
|
||||
{
|
||||
"expr": "sum(aliyun_acs_ecs_dashboard_intranet_out_average{ident=~\"$ident\"}) by (ident,instance_id)",
|
||||
"refId": "B",
|
||||
"legend": "主机:{{ident}} 实例ID: {{instance_id}} 出流量",
|
||||
"step": 300,
|
||||
"time": {
|
||||
"start": "now-5m",
|
||||
"end": "now"
|
||||
}
|
||||
}
|
||||
],
|
||||
"name": "网络流量",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {
|
||||
"util": "bitsSI"
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"version": "2.0.0"
|
||||
}
|
||||
}
|
||||
313
integrations/AliYun/dashboards/rds.json
Normal file
@@ -0,0 +1,313 @@
|
||||
{
|
||||
"name": "阿里云RDS",
|
||||
"tags": "",
|
||||
"ident": "",
|
||||
"configs": {
|
||||
"var": [
|
||||
{
|
||||
"name": "instance_id",
|
||||
"type": "query",
|
||||
"datasource": {
|
||||
"cate": "prometheus"
|
||||
},
|
||||
"definition": "label_values(aliyun_acs_rds_dashboard_cpu_usage_average,instance_id)",
|
||||
"multi": false
|
||||
}
|
||||
],
|
||||
"panels": [
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "01f4d444-aa2d-466d-9615-c76baf60a40c",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"i": "01f4d444-aa2d-466d-9615-c76baf60a40c",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_rds_dashboard_cpu_usage_average{instance_id=\"$instance_id\"}) by (instance_id)",
|
||||
"legend": ""
|
||||
}
|
||||
],
|
||||
"name": "CPU平均使用率",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "c43eb882-915f-4c38-a0b5-8f33c21ab44a",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0,
|
||||
"i": "09903231-6557-42be-9cf3-2873878e9bf2",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_rds_dashboard_memory_usage_average{instance_id=\"$instance_id\"}) by (instance_id)",
|
||||
"legend": ""
|
||||
}
|
||||
],
|
||||
"name": "内存平均使用率",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "37447883-ad79-46bc-888a-1be2835c1c64",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 4,
|
||||
"i": "378a5a26-c28e-4612-af09-f82ec2e11d80",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_rds_dashboard_disk_usage_average{instance_id=\"$instance_id\"}) by (instance_id)",
|
||||
"legend": ""
|
||||
}
|
||||
],
|
||||
"name": "磁盘平均使用率",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "c9735607-3f24-44a7-bbf1-3ad39441c5c9",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 4,
|
||||
"i": "93a4c8a6-ac23-4e26-8a38-781ec1668820",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_rds_dashboard_iops_usage_average{instance_id=\"$instance_id\"}) by (instance_id)",
|
||||
"legend": ""
|
||||
}
|
||||
],
|
||||
"name": "IOPS平均使用率",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "b516e7dc-8022-409d-b907-18c4143df891",
|
||||
"layout": {
|
||||
"h": 5,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 8,
|
||||
"i": "b516e7dc-8022-409d-b907-18c4143df891",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_rds_dashboard_my_sql_com_delete_average{instance_id=\"$instance_id\"}) by (instance_id)",
|
||||
"legend": "delete"
|
||||
},
|
||||
{
|
||||
"expr": "sum(aliyun_acs_rds_dashboard_my_sql_com_insert_average{instance_id=\"$instance_id\"}) by (instance_id)",
|
||||
"refId": "B",
|
||||
"legend": "insert"
|
||||
},
|
||||
{
|
||||
"expr": "sum(aliyun_acs_rds_dashboard_my_sql_com_insert_select_average{instance_id=\"$instance_id\"}) by (instance_id)",
|
||||
"refId": "C",
|
||||
"legend": "insert_select"
|
||||
},
|
||||
{
|
||||
"expr": "sum(aliyun_acs_rds_dashboard_my_sql_com_update_average{instance_id=\"$instance_id\"}) by (instance_id)",
|
||||
"refId": "D",
|
||||
"legend": "update"
|
||||
},
|
||||
{
|
||||
"expr": "sum(aliyun_acs_rds_dashboard_my_sql_com_select_average{instance_id=\"$instance_id\"}) by (instance_id)",
|
||||
"refId": "E",
|
||||
"legend": "select"
|
||||
}
|
||||
],
|
||||
"name": "SQL执行量",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {
|
||||
"util": "percent"
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"version": "2.0.0"
|
||||
}
|
||||
}
|
||||
298
integrations/AliYun/dashboards/redis.json
Normal file
@@ -0,0 +1,298 @@
|
||||
{
|
||||
"name": "阿里云REDIS",
|
||||
"tags": "",
|
||||
"ident": "",
|
||||
"configs": {
|
||||
"var": [
|
||||
{
|
||||
"name": "instance_id",
|
||||
"type": "query",
|
||||
"datasource": {
|
||||
"cate": "prometheus"
|
||||
},
|
||||
"definition": "label_values(aliyun_acs_kvstore_cpu_usage_average,instance_id)",
|
||||
"multi": false
|
||||
}
|
||||
],
|
||||
"panels": [
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "01f4d444-aa2d-466d-9615-c76baf60a40c",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"i": "01f4d444-aa2d-466d-9615-c76baf60a40c",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_kvstore_cpu_usage_average{instance_id=\"$instance_id\"}) by (instance_id)",
|
||||
"legend": ""
|
||||
}
|
||||
],
|
||||
"name": "CPU平均使用率",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "c43eb882-915f-4c38-a0b5-8f33c21ab44a",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0,
|
||||
"i": "09903231-6557-42be-9cf3-2873878e9bf2",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_kvstore_memory_usage_average{instance_id=\"$instance_id\"}) by (instance_id)",
|
||||
"legend": ""
|
||||
}
|
||||
],
|
||||
"name": "内存平均使用率",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "37447883-ad79-46bc-888a-1be2835c1c64",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 4,
|
||||
"i": "378a5a26-c28e-4612-af09-f82ec2e11d80",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_kvstore_connection_usage_average{instance_id=\"$instance_id\"}) by (instance_id)",
|
||||
"legend": ""
|
||||
}
|
||||
],
|
||||
"name": "连接数平均使用率",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "c9735607-3f24-44a7-bbf1-3ad39441c5c9",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 4,
|
||||
"i": "93a4c8a6-ac23-4e26-8a38-781ec1668820",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_kvstore_failed_count_average{instance_id=\"$instance_id\"}) by (instance_id)",
|
||||
"legend": ""
|
||||
}
|
||||
],
|
||||
"name": "失败统计平均使用率",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "b516e7dc-8022-409d-b907-18c4143df891",
|
||||
"layout": {
|
||||
"h": 5,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 8,
|
||||
"i": "b516e7dc-8022-409d-b907-18c4143df891",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_kvstore_intranet_in_average{instance_id=\"$instance_id\"}) by (instance_id)",
|
||||
"legend": "in"
|
||||
},
|
||||
{
|
||||
"expr": "sum(aliyun_acs_kvstore_intranet_out_average{instance_id=\"$instance_id\"}) by (instance_id)",
|
||||
"refId": "B",
|
||||
"legend": "out"
|
||||
}
|
||||
],
|
||||
"name": "网络流量",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {
|
||||
"util": "bitsSI"
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"version": "2.0.0"
|
||||
}
|
||||
}
|
||||
376
integrations/AliYun/dashboards/slb.json
Normal file
@@ -0,0 +1,376 @@
|
||||
{
|
||||
"name": "阿里云SLB",
|
||||
"tags": "",
|
||||
"ident": "",
|
||||
"configs": {
|
||||
"var": [
|
||||
{
|
||||
"name": "instance_id",
|
||||
"type": "query",
|
||||
"datasource": {
|
||||
"cate": "prometheus"
|
||||
},
|
||||
"definition": "label_values(aliyun_acs_slb_dashboard_active_connection_average ,instance_id)",
|
||||
"multi": true,
|
||||
"allOption": true
|
||||
}
|
||||
],
|
||||
"panels": [
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "aa8b2623-1e14-43cd-a3c4-33944a61fcc5",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"i": "aa8b2623-1e14-43cd-a3c4-33944a61fcc5",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"datasourceName": "Default",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_slb_dashboard_instance_qps_utilization_average{instance_id=~\"$instance_id\"}) by (instance_id)",
|
||||
"legend": "{{instance_id}} QPS使用率 "
|
||||
}
|
||||
],
|
||||
"name": "七层实例QPS使用率(%)",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "b2002c63-8f0b-436c-b765-5bb65191f3c2",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0,
|
||||
"i": "b2002c63-8f0b-436c-b765-5bb65191f3c2",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_slb_dashboard_upstream_code4xx_average{instance_id=~\"$instance_id\"}) by (instance_id)",
|
||||
"legend": "{{instance_id}} 状态码 4xx "
|
||||
},
|
||||
{
|
||||
"expr": "sum(aliyun_acs_slb_dashboard_upstream_code5xx_average{instance_id=~\"$instance_id\"}) by (instance_id)",
|
||||
"refId": "B",
|
||||
"legend": "{{instance_id}} 状态码 5xx "
|
||||
}
|
||||
],
|
||||
"name": "7层协议实例Upstream状态码分布",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "71028d82-4804-468f-92f4-3444953b22cc",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 4,
|
||||
"i": "71028d82-4804-468f-92f4-3444953b22cc",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_slb_dashboard_instance_new_connection_average{instance_id=~\"$instance_id\"}) by (instance_id)",
|
||||
"legend": "{{instance_id}} 新建连接数"
|
||||
}
|
||||
],
|
||||
"name": "新建连接数",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "25f90635-ff68-4dc2-bfb0-c6634f0e6867",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 4,
|
||||
"i": "25f90635-ff68-4dc2-bfb0-c6634f0e6867",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_slb_dashboard_active_connection_average{instance_id=~\"$instance_id\"}) by (instance_id)",
|
||||
"legend": "{{instance_id}} 活跃连接数"
|
||||
},
|
||||
{
|
||||
"expr": "sum(aliyun_acs_slb_dashboard_inactive_connection_average{instance_id=~\"$instance_id\"}) by (instance_id)",
|
||||
"refId": "B",
|
||||
"legend": "{{instance_id}} 非活跃连接数"
|
||||
},
|
||||
{
|
||||
"expr": "sum(aliyun_acs_slb_dashboard_max_connection_average{instance_id=~\"$instance_id\"}) by (instance_id)",
|
||||
"refId": "C",
|
||||
"legend": "{{instance_id}} 最大活跃连接数"
|
||||
}
|
||||
],
|
||||
"name": "并发连接数",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "fde27e57-bdd6-4fd6-b3c0-75222f736d3b",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 8,
|
||||
"i": "fde27e57-bdd6-4fd6-b3c0-75222f736d3b",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_slb_dashboard_packet_rx_average{instance_id=~\"$instance_id\"}) by (instance_id)",
|
||||
"legend": "{{instance_id}} 接受数据包数"
|
||||
},
|
||||
{
|
||||
"expr": "sum(aliyun_acs_slb_dashboard_packet_tx_average{instance_id=~\"$instance_id\"}) by (instance_id)",
|
||||
"refId": "B",
|
||||
"legend": "{{instance_id}} 发送数据包数"
|
||||
}
|
||||
],
|
||||
"name": "数据包数",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {
|
||||
"util": "none"
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "a0fd47db-0b49-4b71-ae16-b4108324e35a",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 8,
|
||||
"i": "a0fd47db-0b49-4b71-ae16-b4108324e35a",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_acs_slb_dashboard_instance_traffic_rx_average{instance_id=~\"$instance_id\"}) by (instance_id)",
|
||||
"legend": "{{instance_id}} 出流量"
|
||||
},
|
||||
{
|
||||
"expr": "sum(aliyun_acs_slb_dashboard_instance_traffic_tx_average{instance_id=~\"$instance_id\"}) by (instance_id)",
|
||||
"refId": "B",
|
||||
"legend": "{{instance_id}} 入流量"
|
||||
}
|
||||
],
|
||||
"name": "流量",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {
|
||||
"util": "bitsSI"
|
||||
},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"version": "2.0.0"
|
||||
}
|
||||
}
|
||||
346
integrations/AliYun/dashboards/waf.json
Normal file
@@ -0,0 +1,346 @@
|
||||
{
|
||||
"name": "阿里云WAF",
|
||||
"tags": "",
|
||||
"ident": "",
|
||||
"configs": {
|
||||
"var": [
|
||||
{
|
||||
"name": "instance_id",
|
||||
"type": "query",
|
||||
"datasource": {
|
||||
"cate": "prometheus"
|
||||
},
|
||||
"definition": "label_values(aliyun_waf_qps_ratio_maximum,instance_id)",
|
||||
"multi": true,
|
||||
"allOption": true
|
||||
}
|
||||
],
|
||||
"panels": [
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "ec46b990-faf5-4ed7-a791-bbac5df91636",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"i": "ec46b990-faf5-4ed7-a791-bbac5df91636",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_waf_4xx_ratio_wafv3_maximum{instance_id=~\"$instance_id\"}) by (instance_id)",
|
||||
"legend": "4xx 环比率V3"
|
||||
}
|
||||
],
|
||||
"name": "4xx 环比率V3",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "efc75e62-5e75-470d-b12b-a98ca44b268a",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0,
|
||||
"i": "584b5a3c-2b7a-4e11-bee5-c2ed8661933e",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_waf_5xx_ratio_wafv3_maximum{instance_id=~\"$instance_id\"}) by (instance_id)",
|
||||
"legend": "5xx 环比率V3"
|
||||
}
|
||||
],
|
||||
"name": "5xx 环比率V3",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "79aefa1b-5e50-4c0c-980d-e5523b859509",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 4,
|
||||
"i": "fc875397-c1a4-4713-b564-09abf852bcf3",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_waf_4xx_ratio_maximum{instance_id=~\"$instance_id\"}) by (instance_id)",
|
||||
"legend": "4xx 环比率"
|
||||
}
|
||||
],
|
||||
"name": "4xx 环比率",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "60c211d4-d51a-4681-b23b-ec8cc5dce7fe",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 4,
|
||||
"i": "946be0db-32a3-48ea-9473-88fdfa77201d",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_waf_5xx_ratio_maximum{instance_id=~\"$instance_id\"}) by (instance_id)",
|
||||
"legend": "5xx 环比率"
|
||||
}
|
||||
],
|
||||
"name": "5xx 环比率",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "0de9271c-7b19-4003-ae56-2e273b4b99c4",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 8,
|
||||
"i": "0de9271c-7b19-4003-ae56-2e273b4b99c4",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_waf_qps_ratio_maximum{instance_id=~\"$instance_id\"}) by (instance_id)",
|
||||
"legend": "{{instance_id}}QPS 环比增长率"
|
||||
}
|
||||
],
|
||||
"name": "QPS环比增长率",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"id": "ccf2ffc1-6f22-4a13-b795-68072c077e1f",
|
||||
"layout": {
|
||||
"h": 4,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 8,
|
||||
"i": "d0320716-f704-4b6e-8671-b58fb77a5d7c",
|
||||
"isResizable": true
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"datasourceCate": "prometheus",
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"expr": "sum(aliyun_waf_qps_ratio_down_maximum{instance_id=~\"$instance_id\"}) by (instance_id)",
|
||||
"legend": "{{instance_id}}QPS 环比下降率"
|
||||
}
|
||||
],
|
||||
"name": "QPS环比下降率",
|
||||
"maxPerRow": 4,
|
||||
"options": {
|
||||
"tooltip": {
|
||||
"mode": "all",
|
||||
"sort": "none"
|
||||
},
|
||||
"legend": {
|
||||
"displayMode": "hidden"
|
||||
},
|
||||
"standardOptions": {},
|
||||
"thresholds": {
|
||||
"steps": [
|
||||
{
|
||||
"color": "#634CD9",
|
||||
"value": null,
|
||||
"type": "base"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"custom": {
|
||||
"drawStyle": "lines",
|
||||
"lineInterpolation": "smooth",
|
||||
"spanNulls": false,
|
||||
"lineWidth": 1,
|
||||
"fillOpacity": 0.5,
|
||||
"gradientMode": "none",
|
||||
"stack": "off",
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"version": "2.0.0"
|
||||
}
|
||||
}
|
||||
BIN
integrations/AliYun/icon/aliyun.png
Normal file
|
After Width: | Height: | Size: 532 B |
70
integrations/AliYun/markdown/README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# aliyun plugin
|
||||
|
||||
## 简介
|
||||
|
||||
使用[categraf](https://github.com/flashcatcloud/categraf)中[aliyun](https://github.com/flashcatcloud/categraf/tree/main/inputs/aliyun)插件拉取阿里云云监控的数据(通过 OpenAPI)。
|
||||
|
||||
## 授权
|
||||
|
||||
获取凭证 [https://usercenter.console.aliyun.com/#/manage/ak](https://usercenter.console.aliyun.com/#/manage/ak)
|
||||
RAM 用户授权。RAM 用户调用云监控 API 前,需要所属的阿里云账号将权限策略授予对应的 RAM 用户,参见 [RAM 用户权限](https://help.aliyun.com/document_detail/43170.html?spm=a2c4g.11186623.0.0.30c841feqsoAAn)。
|
||||
可以在 [授权页面](https://ram.console.aliyun.com/permissions) 新增授权,选择对应的用户,授予云监控只读权限 `AliyunCloudMonitorReadOnlyAccess`, 并为授予权限的用户创建accessKey 即可。
|
||||
|
||||
## Categraf中conf/input.aliyun/cloud.toml配置文件:
|
||||
|
||||
```toml
|
||||
# # categraf采集周期,阿里云指标的粒度一般是60秒,建议设置不要少于60秒
|
||||
interval = 120
|
||||
[[instances]]
|
||||
## 阿里云资源所处的region
|
||||
## endpoint region 参考 https://help.aliyun.com/document_detail/28616.html#section-72p-xhs-6qt
|
||||
region="cn-beijing"
|
||||
endpoint="metrics.cn-hangzhou.aliyuncs.com"
|
||||
## 填入你的access_key_id
|
||||
access_key_id=""
|
||||
## 填入你的access_key_secret
|
||||
access_key_secret=""
|
||||
|
||||
## 可能无法获取当前最新指标,这个指标是指监控指标的截止时间距离现在多久
|
||||
delay="50m"
|
||||
## 阿里云指标的最小粒度,60s 是推荐值,再小了部分指标不支持
|
||||
period="60s"
|
||||
## 指标所属的namespace ,为空,则表示所有空间指标都要采集
|
||||
## namespace 参考 https://help.aliyun.com/document_detail/163515.htm?spm=a2c4g.11186623.0.0.44d65c58mhgNw3
|
||||
namespaces=["acs_ecs_dashboard"]
|
||||
## 过滤某个namespace下的一个或多个指标
|
||||
## metric name 参考 https://help.aliyun.com/document_detail/163515.htm?spm=a2c4g.11186623.0.0.401d15c73Z0dZh
|
||||
## 参考页面中的Metric Id 填入下面的metricName ,页面中包含中文的Metric Name对应接口中的Description
|
||||
[[instances.metric_filters]]
|
||||
namespace=""
|
||||
metric_names=["cpu_cores","vm.TcpCount", "cpu_idle"]
|
||||
|
||||
# 阿里云查询指标接口的QPS是50, 这里默认设置为一半
|
||||
ratelimit=25
|
||||
# 查询指定namesapce指标后, namespace/metric_name等meta信息会缓存起来,catch_ttl 是指标的缓存时间
|
||||
catch_ttl="1h"
|
||||
# 每次请求阿里云endpoint的超时时间
|
||||
timeout="5s"
|
||||
```
|
||||
|
||||
## 效果图
|
||||
|
||||
### aliyun ecs
|
||||
|
||||

|
||||
|
||||
### aliyun rds
|
||||
|
||||

|
||||
|
||||
### aliyun redis
|
||||
|
||||

|
||||
|
||||
### aliyun slb
|
||||
|
||||

|
||||
|
||||
### aliyun waf
|
||||
|
||||

|
||||
BIN
integrations/AliYun/markdown/ecs.png
Normal file
|
After Width: | Height: | Size: 155 KiB |
BIN
integrations/AliYun/markdown/rds.png
Normal file
|
After Width: | Height: | Size: 180 KiB |
BIN
integrations/AliYun/markdown/redis.png
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
integrations/AliYun/markdown/slb.png
Normal file
|
After Width: | Height: | Size: 193 KiB |
BIN
integrations/AliYun/markdown/waf.png
Normal file
|
After Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |