未验证 提交 0fd6176a 编写于 作者: S sole 提交者: GitHub

Merge pull request #1 from didi/master

update
web
\ No newline at end of file
FROM golang AS builder
# RUN apk add --no-cache git gcc
WORKDIR /app
# comment this if using vendor
# ENV GOPROXY=https://mod.gokit.info
# COPY go.mod go.sum ./
# RUN go mod download
COPY . .
ENV GOPROXY=https://mod.gokit.info
RUN ./control build docker
FROM buildpack-deps:buster-curl
LABEL maintainer="llitfkitfk@gmail.com"
WORKDIR /app
COPY --from=builder /app/docker/scripts /app/scripts
COPY --from=builder /app/etc /app/etc
# Change default address (hard code)
RUN ./scripts/sed.sh
COPY --from=builder /app/bin /usr/local/bin
# ENTRYPOINT []
# CMD []
\ No newline at end of file
......@@ -15,7 +15,17 @@ Nightingale user manual: [https://n9e.didiyun.com/](https://n9e.didiyun.com/)
mkdir -p $GOPATH/src/github.com/didi
cd $GOPATH/src/github.com/didi
git clone https://github.com/didi/nightingale.git
cd nightingale && ./control build
cd nightingale
# export env[GOPROXY] if your network is not good
# export GOPROXY=https://mirrors.aliyun.com/goproxy/
./control build
```
## Quick Start (need install docker for [mac](https://docs.docker.com/docker-for-mac/install/)/[win](https://docs.docker.com/docker-for-windows/install/))
```bash
docker-compose up -d
# open http://localhost in web browser
```
## Team
......
......@@ -15,7 +15,10 @@ Nightingale是一套衍生自Open-Falcon的互联网监控解决方案,融入
mkdir -p $GOPATH/src/github.com/didi
cd $GOPATH/src/github.com/didi
git clone https://github.com/didi/nightingale.git
cd nightingale && ./control build
cd nightingale
# 如果网络环境不好可以尝试aliyun的mirror
# export GOPROXY=https://mirrors.aliyun.com/goproxy/
./control build
```
## 团队
......
......@@ -139,8 +139,16 @@ build_one()
go build -o n9e-${mod} --tags "md5" src/modules/${mod}/${mod}.go
}
build_docker()
{
mod=$1
go build -o bin/n9e-${mod} --tags "md5" src/modules/${mod}/${mod}.go
}
build()
{
export GO111MODULE=on
mod=$1
if [ "x${mod}" = "x" ]; then
build_one monapi
......@@ -151,6 +159,16 @@ build()
build_one tsdb
return
fi
if [ "x${mod}" = "xdocker" ]; then
build_docker monapi
build_docker transfer
build_docker index
build_docker judge
build_docker collector
build_docker tsdb
return
fi
build_one $mod
}
......
version: "3"
volumes:
mysql-data:
services:
nginx:
image: nginx:stable-alpine
ports:
- 80:80
volumes:
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
- ./docker/nginx/conf.d:/etc/nginx/conf.d
- ./pub:/home/n9e/pub
api:
build: .
image: api
monapi:
image: api
restart: always
command: n9e-monapi
ports:
- 5800:5800
transfer:
image: api
restart: always
command: n9e-transfer
ports:
- 5810:5810
- 5811:5811
tsdb:
image: api
restart: always
command: n9e-tsdb
ports:
- 5820:5820
- 5821:5821
index:
image: api
restart: always
command: n9e-index
ports:
- 5830:5830
- 5831:5831
judge:
image: api
restart: always
command: n9e-judge
ports:
- 5840:5840
- 5841:5841
collector:
image: api
restart: always
command: n9e-collector
ports:
- 2058:2058
# web:
# build:
# context: web
# restart: always
# command: npm run dev
# ports:
# - 8010:8010
redis:
image: redis
restart: always
ports:
- 6379:6379
mysql:
image: mysql:5.7
restart: always
environment:
- MYSQL_ROOT_PASSWORD=1234
ports:
- 3306:3306
volumes:
- ./sql:/docker-entrypoint-initdb.d
- mysql-data:/var/lib/mysql
\ No newline at end of file
user root;
worker_processes auto;
worker_cpu_affinity auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
use epoll;
worker_connections 204800;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
proxy_connect_timeout 500ms;
proxy_send_timeout 1000ms;
proxy_read_timeout 3000ms;
proxy_buffers 64 8k;
proxy_busy_buffers_size 128k;
proxy_temp_file_write_size 64k;
proxy_redirect off;
proxy_next_upstream error invalid_header timeout http_502 http_504;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-Port $remote_port;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
upstream n9e.monapi {
server monapi:5800;
keepalive 10;
}
upstream n9e.index {
server index:5830;
keepalive 10;
}
upstream n9e.transfer {
server transfer:5810;
keepalive 10;
}
server {
listen 80 default_server;
server_name _;
root /usr/share/nginx/html;
# Load configuration files for the default server block.
include /etc/nginx/conf.d/*.conf;
location / {
root /home/n9e/pub;
}
location /api/portal {
proxy_pass http://n9e.monapi;
}
location /api/index {
proxy_pass http://n9e.index;
}
location /api/transfer {
proxy_pass http://n9e.transfer;
}
}
}
\ No newline at end of file
#!/bin/bash
set -xe
sed -i 's/127.0.0.1:6379/redis:6379/g' /app/etc/judge.yml
sed -i 's/127.0.0.1:6379/redis:6379/g' /app/etc/monapi.yml
sed -i 's/127.0.0.1:3306/mysql:3306/g' /app/etc/mysql.yml
sed -i 's/127.0.0.1:5821/tsdb:5821/g' /app/etc/transfer.yml
\ No newline at end of file
# use shell if specify is blank
logger:
dir: logs/collector
level: WARNING
keepHours: 2
identity:
specify: ""
shell: /usr/sbin/ifconfig `/usr/sbin/route|grep '^default'|awk '{print $NF}'`|grep inet|awk '{print $2}'|head -n 1
shell: ifconfig `route|grep '^default'|awk '{print $NF}'`|grep inet|awk '{print $2}'|awk -F ':' '{print $NF}'|head -n 1
sys:
# timeout in ms
# interval in second
......@@ -28,4 +27,4 @@ sys:
- cpu.core.irq
- cpu.core.softirq
- cpu.core.iowait
- cpu.core.steal
\ No newline at end of file
- cpu.core.steal
......@@ -4,4 +4,4 @@ logger:
keepHours: 2
identity:
specify: ""
shell: /usr/sbin/ifconfig `/usr/sbin/route|grep '^default'|awk '{print $NF}'`|grep inet|awk '{print $2}'|head -n 1
shell: ifconfig `route|grep '^default'|awk '{print $NF}'`|grep inet|awk '{print $2}'|awk -F ':' '{print $NF}'|head -n 1
......@@ -6,6 +6,7 @@ query:
redis:
addrs:
- 127.0.0.1:6379
db: 0
pass: ""
# timeout:
# conn: 500
......@@ -14,9 +15,9 @@ redis:
identity:
specify: ""
shell: /usr/sbin/ifconfig `/usr/sbin/route|grep '^default'|awk '{print $NF}'`|grep inet|awk '{print $2}'|head -n 1
shell: ifconfig `route|grep '^default'|awk '{print $NF}'`|grep inet|awk '{print $2}'|awk -F ':' '{print $NF}'|head -n 1
logger:
dir: logs/judge
level: WARNING
keepHours: 2
\ No newline at end of file
keepHours: 2
......@@ -14,11 +14,19 @@ ldap:
host: "ldap.example.org"
port: 389
baseDn: "dc=example,dc=org"
# AD: manange@example.org
bindUser: "cn=manager,dc=example,dc=org"
bindPass: "*******"
# openldap: (&(uid=%s))
# AD: (&(sAMAccountName=%s))
authFilter: "(&(uid=%s))"
attributes:
dispname: "cn"
email: "mail"
phone: "mobile"
im: ""
coverAttributes: false
autoRegist: false
tls: false
startTLS: false
......@@ -44,6 +52,7 @@ link:
# for alarm event and message queue
redis:
addr: "127.0.0.1:6379"
db: 0
pass: ""
# in ms
# timeout:
......
user nginx;
user root;
worker_processes auto;
worker_cpu_affinity auto;
......
sshd
\ No newline at end of file
n9e-transfer
\ No newline at end of file
n9e-judge
\ No newline at end of file
{"version":3,"file":"index-3eb7c9ca4d88e3ad5c58.js","sources":["webpack:///index-3eb7c9ca4d88e3ad5c58.js"],"mappings":"AAAA;;;;;;;AAmsYA","sourceRoot":""}
\ No newline at end of file
{"version":3,"file":"index-5c8cbf958683e20086f5.js","sources":["webpack:///index-5c8cbf958683e20086f5.js"],"mappings":"AAAA;;;;;;;AAmsYA","sourceRoot":""}
\ No newline at end of file
<!doctype html><html><head><meta charset="UTF-8"><title>Nightingale</title><link rel="shortcut icon" href="/favicon.ico"><link href="/index-3eb7c9ca4d88e3ad5c58.css" rel="stylesheet"></head><body><div id="react-content"></div><script src="/lib-033bee8514de110e36ef.dll.js"></script><script src="/index-3eb7c9ca4d88e3ad5c58.js"></script></body></html>
\ No newline at end of file
<!doctype html><html><head><meta charset="UTF-8"><title>Nightingale</title><link rel="shortcut icon" href="/favicon.ico"><link href="/index-5c8cbf958683e20086f5.css" rel="stylesheet"></head><body><div id="react-content"></div><script src="/lib-033bee8514de110e36ef.dll.js"></script><script src="/index-5c8cbf958683e20086f5.js"></script></body></html>
\ No newline at end of file
......@@ -91,8 +91,8 @@ create table `tmp_chart` (
create table `event_cur` (
`id` bigint(20) unsigned not null AUTO_INCREMENT comment 'id',
`sid` bigint(20) unsigned not null default 0 comment 'sid',
`sname` varchar(256) not null default '' comment 'name, 报警通知名称',
`node_path` varchar(256) not null default '' comment 'node path',
`sname` varchar(255) not null default '' comment 'name, 报警通知名称',
`node_path` varchar(255) not null default '' comment 'node path',
`nid` int unsigned not null default '0' comment 'node id',
`endpoint` varchar(255) not null default '' comment 'endpoint',
`endpoint_alias` varchar(255) not null default '' comment 'endpoint alias',
......@@ -103,7 +103,7 @@ create table `event_cur` (
`detail` text comment 'counter points pred_points 详情',
`hashid` varchar(128) not null default '' comment 'sid+counter hash',
`etime` bigint(20) not null default 0 comment 'event ts',
`value` varchar(256) not null default '' comment '当前值',
`value` varchar(255) not null default '' comment '当前值',
`users` varchar(512) not null default '[]' comment 'notify users',
`groups` varchar(512) not null default '[]' comment 'notify groups',
`info` varchar(512) not null default '' comment 'strategy info',
......@@ -122,8 +122,8 @@ create table `event_cur` (
create table `event` (
`id` bigint(20) unsigned not null AUTO_INCREMENT comment 'id',
`sid` bigint(20) unsigned not null default 0 comment 'sid',
`sname` varchar(256) not null default '' comment 'name, 报警通知名称',
`node_path` varchar(256) not null default '' comment 'node path',
`sname` varchar(255) not null default '' comment 'name, 报警通知名称',
`node_path` varchar(255) not null default '' comment 'node path',
`nid` int unsigned not null default '0' comment 'node id',
`endpoint` varchar(255) not null default '' comment 'endpoint',
`endpoint_alias` varchar(255) not null default '' comment 'endpoint alias',
......@@ -134,7 +134,7 @@ create table `event` (
`detail` text comment 'counter points pred_points 详情',
`hashid` varchar(128) not null default '' comment 'sid+counter hash',
`etime` bigint(20) not null default 0 comment 'event ts',
`value` varchar(256) not null default '' comment '当前值',
`value` varchar(255) not null default '' comment '当前值',
`users` varchar(512) not null default '[]' comment 'notify users',
`groups` varchar(512) not null default '[]' comment 'notify groups',
`info` varchar(512) not null default '' comment 'strategy info',
......@@ -167,8 +167,8 @@ CREATE TABLE `stra` (
`converge` varchar(45) NOT NULL DEFAULT '' COMMENT 'n秒最多报m次警',
`recovery_notify` int(1) NOT NULL DEFAULT 1 COMMENT '1 发送恢复通知 0不发送恢复通知',
`priority` int(1) NOT NULL DEFAULT 3 COMMENT '告警等级',
`notify_group` varchar(256) NOT NULL DEFAULT '' COMMENT '告警通知组',
`notify_user` varchar(256) NOT NULL DEFAULT '' COMMENT '告警通知人',
`notify_group` varchar(255) NOT NULL DEFAULT '' COMMENT '告警通知组',
`notify_user` varchar(255) NOT NULL DEFAULT '' COMMENT '告警通知人',
`callback` varchar(1024) NOT NULL DEFAULT '' COMMENT 'callback url',
`creator` varchar(64) NOT NULL COMMENT '创建者',
`created` timestamp NOT NULL DEFAULT '1971-01-01 00:00:00' COMMENT 'created',
......
......@@ -63,17 +63,14 @@ func (m *MetricValue) CheckValidity() (err error) {
return
}
//将保留字替换
var illegal bool
m.Metric, illegal = ReplaceReservedWords(m.Metric)
if illegal {
err = fmt.Errorf("Metric contains reserved word")
//检测保留字
if HasReservedWords(m.Metric) {
err = fmt.Errorf("metric:%s contains reserved words:[\\t] [\\r] [\\n] [,] [ ] [=]", m.Metric)
return
}
m.Endpoint, illegal = ReplaceReservedWords(m.Endpoint)
if illegal {
err = fmt.Errorf("Endpoint contains reserved word:%s", m.Endpoint)
if HasReservedWords(m.Endpoint) {
err = fmt.Errorf("endpoint:%s contains reserved words:[\\t] [\\r] [\\n] [,] [ ] [=]", m.Endpoint)
return
}
......@@ -151,7 +148,7 @@ func (m *MetricValue) CheckValidity() (err error) {
return
}
func ReplaceReservedWords(str string) (string, bool) {
func HasReservedWords(str string) bool {
if -1 == strings.IndexFunc(str,
func(r rune) bool {
return r == '\t' ||
......@@ -159,27 +156,13 @@ func ReplaceReservedWords(str string) (string, bool) {
r == '\n' ||
r == ',' ||
r == ' ' ||
r == ':' ||
r == '='
}) {
return str, false
return false
}
return strings.Map(func(r rune) rune {
if r == '\t' ||
r == '\r' ||
r == '\n' ||
r == ',' ||
r == ' ' ||
r == ':' ||
r == '=' {
return '_'
}
return r
}, str), true
return str, false
return true
}
func SortedTags(tags map[string]string) string {
......
......@@ -24,6 +24,17 @@ type QueryDataForUI struct {
Comparisons []int64 `json:"comparisons"` //环比多少时间
}
type QueryDataForUIResp struct {
Start int64 `json:"start"`
End int64 `json:"end"`
Endpoint string `json:"endpoint"`
Counter string `json:"counter"`
DsType string `json:"dstype"`
Step int `json:"step"`
Values []*RRDData `json:"values"`
Comparison int64 `json:"comparison"`
}
type QueryDataResp struct {
Data []*TsdbQueryResponse
Msg string
......
package model
import (
"crypto/tls"
"fmt"
"github.com/didi/nightingale/src/modules/monapi/config"
"gopkg.in/ldap.v3"
)
func genLdapAttributeSearchList() []string {
ldapAttributes := []string{}
attrs := config.Get().LDAP.Attributes
if attrs.Dispname != "" {
ldapAttributes = append(ldapAttributes, attrs.Dispname)
}
if attrs.Email != "" {
ldapAttributes = append(ldapAttributes, attrs.Email)
}
if attrs.Phone != "" {
ldapAttributes = append(ldapAttributes, attrs.Phone)
}
if attrs.Im != "" {
ldapAttributes = append(ldapAttributes, attrs.Im)
}
return ldapAttributes
}
func ldapReq(user, pass string) (*ldap.SearchResult, error) {
var conn *ldap.Conn
var err error
lc := config.Get().LDAP
addr := fmt.Sprintf("%s:%d", lc.Host, lc.Port)
if lc.TLS {
conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: true})
} else {
conn, err = ldap.Dial("tcp", addr)
}
if err != nil {
return nil, fmt.Errorf("cannot dial ldap: %v", err)
}
defer conn.Close()
if !lc.TLS && lc.StartTLS {
if err := conn.StartTLS(&tls.Config{InsecureSkipVerify: true}); err != nil {
return nil, fmt.Errorf("ldap.conn startTLS fail: %v", err)
}
}
//if bindUser is empty, anonymousSearch mode
if lc.BindUser != "" {
//BindSearch mode
if err := conn.Bind(lc.BindUser, lc.BindPass); err != nil {
return nil, fmt.Errorf("bind ldap fail: %v, use %s", err, lc.BindUser)
}
}
searchRequest := ldap.NewSearchRequest(
lc.BaseDn, // The base dn to search
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf(lc.AuthFilter, user), // The filter to apply
genLdapAttributeSearchList(), // A list attributes to retrieve
nil,
)
sr, err := conn.Search(searchRequest)
if err != nil {
return nil, fmt.Errorf("ldap search fail: %v", err)
}
if len(sr.Entries) == 0 {
return nil, fmt.Errorf("cannot find such user: %v", user)
}
if len(sr.Entries) > 1 {
return nil, fmt.Errorf("multi users is search, query user: %v", user)
}
if err := conn.Bind(sr.Entries[0].DN, pass); err != nil {
return nil, fmt.Errorf("password error")
}
return sr, nil
}
......@@ -79,7 +79,7 @@ func NodeQueryPath(query string, limit int) (nodes []Node, err error) {
func TreeSearchByPath(query string) (nodes []Node, err error) {
session := DB["mon"].NewSession()
defer session.Clone()
defer session.Close()
if strings.Contains(query, " ") {
arr := strings.Fields(query)
......
......@@ -20,7 +20,7 @@ type Team struct {
func (t *Team) Del() error {
session := DB["uic"].NewSession()
defer session.Clone()
defer session.Close()
if err := session.Begin(); err != nil {
return err
......
package model
import (
"crypto/tls"
"fmt"
"log"
"strings"
......@@ -118,6 +117,23 @@ func (u *User) CanModifyTeam(t *Team) (bool, error) {
return cnt > 0, err
}
func (u *User) CopyLdapAttr(sr *ldap.SearchResult) {
attrs := config.Get().LDAP.Attributes
if attrs.Dispname != "" {
u.Dispname = sr.Entries[0].GetAttributeValue(attrs.Dispname)
}
if attrs.Email != "" {
u.Email = sr.Entries[0].GetAttributeValue(attrs.Email)
}
if attrs.Phone != "" {
u.Phone = sr.Entries[0].GetAttributeValue(attrs.Phone)
}
if attrs.Im != "" {
u.Im = sr.Entries[0].GetAttributeValue(attrs.Im)
}
return
}
func InitRoot() {
var u User
has, err := DB["uic"].Where("username=?", "root").Get(&u)
......@@ -147,78 +163,31 @@ func InitRoot() {
}
func LdapLogin(user, pass string) error {
var conn *ldap.Conn
var err error
lc := config.Get().LDAP
addr := fmt.Sprintf("%s:%d", lc.Host, lc.Port)
if lc.TLS {
conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: true})
} else {
conn, err = ldap.Dial("tcp", addr)
}
if err != nil {
return fmt.Errorf("cannot dial ldap: %v", err)
}
defer conn.Close()
if !lc.TLS && lc.StartTLS {
err = conn.StartTLS(&tls.Config{InsecureSkipVerify: true})
if err != nil {
return fmt.Errorf("ldap.conn startTLS fail: %v", err)
}
}
err = conn.Bind(lc.BindUser, lc.BindPass)
if err != nil {
return fmt.Errorf("bind ldap fail: %v, use %s", err, lc.BindUser)
}
searchRequest := ldap.NewSearchRequest(
lc.BaseDn, // The base dn to search
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf(lc.AuthFilter, user), // The filter to apply
[]string{}, // A list attributes to retrieve
nil,
)
sr, err := conn.Search(searchRequest)
if err != nil {
return fmt.Errorf("ldap search fail: %v", err)
}
if len(sr.Entries) == 0 {
return fmt.Errorf("cannot find such user: %v", user)
}
if len(sr.Entries) > 1 {
return fmt.Errorf("multi users is search, query user: %v", user)
}
err = conn.Bind(sr.Entries[0].DN, pass)
sr, err := ldapReq(user, pass)
if err != nil {
return fmt.Errorf("password error")
return err
}
cnt, err := DB["uic"].Where("username=?", user).Count(new(User))
var u User
has, err := DB["uic"].Where("username=?", user).Get(&u)
if err != nil {
return err
}
if cnt > 0 {
return nil
u.CopyLdapAttr(sr)
if has {
if config.Get().LDAP.CoverAttributes {
_, err := DB["uic"].Where("id=?", u.Id).Update(u)
return err
} else {
return nil
}
}
u := &User{
Username: user,
Password: "******",
Dispname: "",
Email: "",
if !config.Get().LDAP.AutoRegist {
return fmt.Errorf("user has not be created, may be you should enable auto regist: %v", user)
}
u.Username = user
u.Password = "******"
_, err = DB["uic"].Insert(u)
return err
}
......
......@@ -63,6 +63,8 @@ func main() {
identity.Init(cfg.Identity)
if identity.Identity == "127.0.0.1" {
log.Fatalln("endpoint: 127.0.0.1, cannot work")
} else {
log.Println("endpoint:", identity.Identity)
}
sys.Init(cfg.Sys)
......@@ -70,7 +72,6 @@ func main() {
funcs.BuildMappers()
funcs.Collect()
stra.GetCollects()
//插件采集
plugins.Detect()
......@@ -137,6 +138,5 @@ func ending() {
func start() {
runner.Init()
fmt.Println("collector start, use configuration file:", *conf)
fmt.Println("runner.Cwd:", runner.Cwd)
fmt.Println("runner.Endpoint:", runner.Hostname)
fmt.Println("runner.cwd:", runner.Cwd)
}
......@@ -62,16 +62,16 @@ func Parse(conf string) error {
"enable": true,
"timeout": 1000,
"interval": 10, //采集策略更新时间
"portPath": "/home/n9e/etc/port",
"procPath": "/home/n9e/etc/proc",
"logPath": "/home/n9e/etc/log",
"portPath": "./etc/port",
"procPath": "./etc/proc",
"logPath": "./etc/log",
"api": "/api/portal/collects/",
})
viper.SetDefault("sys", map[string]interface{}{
"timeout": 1000, //请求超时时间
"interval": 10, //基础指标上报周期
"plugin": "/home/n9e/plugin",
"plugin": "./plugin",
})
err = viper.Unmarshal(&Config)
......
......@@ -18,9 +18,9 @@ func init() {
}
func Update() error {
strategys := stra.GetLogCollects()
strategies := stra.GetLogCollects()
err := UpdateGlobalStrategy(strategys)
err := UpdateGlobalStrategy(strategies)
if err != nil {
logger.Errorf("Update Strategy cache error ! [msg:%v]", err)
return err
......
......@@ -74,7 +74,7 @@ func getCollects() (CollectResp, error) {
url := fmt.Sprintf("http://%s%s%s", addr, StraConfig.Api, identity.Identity)
err = httplib.Get(url).SetTimeout(time.Duration(StraConfig.Timeout) * time.Millisecond).ToJSON(&res)
if err != nil {
err = fmt.Errorf("get collects from remote failed, error:%v", err)
err = fmt.Errorf("get collects from remote:%s failed, error:%v", url, err)
}
return res, err
......
......@@ -146,8 +146,8 @@ func DeepCopyStringMap(p map[string]string) map[string]string {
const PATTERN_EXCLUDE_PARTITION = "```EXCLUDE```"
func parsePattern(strategys []*Strategy) {
for _, st := range strategys {
func parsePattern(strategies []*Strategy) {
for _, st := range strategies {
patList := strings.Split(st.Pattern, PATTERN_EXCLUDE_PARTITION)
if len(patList) == 1 {
......@@ -163,8 +163,8 @@ func parsePattern(strategys []*Strategy) {
}
}
func updateRegs(strategys []*Strategy) {
for _, st := range strategys {
func updateRegs(strategies []*Strategy) {
for _, st := range strategies {
st.TagRegs = make(map[string]*regexp.Regexp, 0)
st.ParseSucc = false
......
......@@ -48,7 +48,9 @@ func Push(items []*dataobj.MetricValue) {
logger.Error(err)
continue
} else {
logger.Info("push succ, reply: ", reply)
if reply.Msg != "ok" {
logger.Error("some item push err", reply)
}
return
}
}
......
......@@ -32,6 +32,7 @@ func ListPlugins(dir string) map[string]*Plugin {
filename := f.Name()
arr := strings.Split(filename, "_")
if len(arr) < 2 {
logger.Warningf("plugin:%s name illegal, should be: $cycle_$xx", filename)
continue
}
......@@ -39,10 +40,16 @@ func ListPlugins(dir string) map[string]*Plugin {
var cycle int
cycle, err = strconv.Atoi(arr[0])
if err != nil {
logger.Warningf("plugin:%s name illegal, should be: $cycle_$xx %v", filename, err)
continue
}
fpath, err := filepath.Abs(filepath.Join(dir, filename))
if err != nil {
logger.Warningf("plugin:%s absolute path get err:%v", filename, err)
continue
}
fpath := filepath.Join(dir, filename)
plugin := &Plugin{FilePath: fpath, MTime: f.ModTime().Unix(), Cycle: cycle}
ret[fpath] = plugin
}
......
......@@ -50,8 +50,7 @@ func PluginRun(plugin *Plugin) {
timeout := plugin.Cycle*1000 - 500 //比运行周期少500毫秒
fpath := filepath.Join(plugin.FilePath)
fpath := plugin.FilePath
if !file.IsExist(fpath) {
logger.Error("no such plugin:", fpath)
return
......
......@@ -20,6 +20,6 @@ func loopDetect() {
func detect() {
ps := stra.GetProcCollects()
DelNoPorcCollect(ps)
AddNewPorcCollect(ps)
DelNoProcCollect(ps)
AddNewProcCollect(ps)
}
......@@ -9,7 +9,7 @@ var (
ProcsWithScheduler = make(map[string]*ProcScheduler)
)
func DelNoPorcCollect(newCollect map[string]*model.ProcCollect) {
func DelNoProcCollect(newCollect map[string]*model.ProcCollect) {
for currKey, currProc := range Procs {
newProc, ok := newCollect[currKey]
if !ok || currProc.LastUpdated != newProc.LastUpdated {
......@@ -18,7 +18,7 @@ func DelNoPorcCollect(newCollect map[string]*model.ProcCollect) {
}
}
func AddNewPorcCollect(newCollect map[string]*model.ProcCollect) {
func AddNewProcCollect(newCollect map[string]*model.ProcCollect) {
for target, newProc := range newCollect {
if _, ok := Procs[target]; ok && newProc.LastUpdated == Procs[target].LastUpdated {
continue
......
......@@ -6,6 +6,7 @@ import (
"time"
"github.com/didi/nightingale/src/toolkits/address"
"github.com/didi/nightingale/src/toolkits/stats"
"github.com/toolkits/pkg/concurrent/semaphore"
"github.com/toolkits/pkg/logger"
......@@ -49,11 +50,12 @@ func reportEndpoint(endpoints []interface{}) {
err := httplib.Post(url).JSONBodyQuiet(m).SetTimeout(3*time.Second).Header("x-srv-token", "monapi-builtin-token").ToJSON(&body)
if err != nil {
logger.Warningf("curl %s fail: %v. retry", url, err)
stats.Counter.Set("report.endpoint.err", 1)
continue
}
if body.Err != "" {
if body.Err != "" { //数据库连接出错会出现此情况
logger.Warningf("curl %s fail: %s. retry", url, body.Err)
stats.Counter.Set("report.endpoint.err", 1)
continue
}
......
......@@ -19,6 +19,7 @@ import (
"github.com/didi/nightingale/src/toolkits/compress"
"github.com/didi/nightingale/src/toolkits/identity"
"github.com/didi/nightingale/src/toolkits/report"
"github.com/didi/nightingale/src/toolkits/stats"
)
type CacheSection struct {
......@@ -72,8 +73,8 @@ func StartPersist(interval int) {
err := Persist("normal")
if err != nil {
logger.Error("Persist err:", err)
stats.Counter.Set("persist.err", 1)
}
//logger.Infof("clean %+v, took %.2f ms\n", cleanRet, float64(time.Since(start).Nanoseconds())*1e-6)
}
}
......
......@@ -30,8 +30,9 @@ func (t *TagkvIndex) GetTagkv() []*TagPair {
t.RLock()
defer t.RUnlock()
tagkvs := []*TagPair{}
var vs []string
for k, vm := range t.Tagkv {
var vs []string
for v, _ := range vm {
vs = append(vs, v)
}
......@@ -50,8 +51,8 @@ func (t *TagkvIndex) GetTagkvMap() map[string][]string {
defer t.RUnlock()
tagkvs := make(map[string][]string)
var vs []string
for k, vm := range t.Tagkv {
var vs []string
for v, _ := range vm {
vs = append(vs, v)
}
......
......@@ -93,9 +93,8 @@ func GetTagPairs(c *gin.Context) {
resp := []*IndexTagkvResp{}
tagkvFilter := make(map[string]map[string]struct{})
for _, metric := range recv.Metrics {
tagkvFilter := make(map[string]map[string]struct{})
tagkvs := []*cache.TagPair{}
for _, endpoint := range recv.Endpoints {
......@@ -107,6 +106,7 @@ func GetTagPairs(c *gin.Context) {
}
tagkvMap := metricIndex.TagkvMap.GetTagkvMap()
for tagk, tagvs := range tagkvMap {
tagvFilter, exists := tagkvFilter[tagk]
if !exists {
......
......@@ -42,6 +42,5 @@ func Push(event *dataobj.Event) error {
return nil
}
stats.Counter.Set("redis.failed", 1)
return fmt.Errorf("redis publish failed finally:%v", err)
}
......@@ -4,6 +4,7 @@ import (
"log"
"time"
"github.com/didi/nightingale/src/toolkits/stats"
"github.com/garyburd/redigo/redis"
"github.com/toolkits/pkg/logger"
)
......@@ -14,6 +15,7 @@ var Config RedisSection
type RedisSection struct {
Addrs []string `yaml:"addrs"`
Pass string `yaml:"pass"`
DB int `yaml:"db"`
Idle int `yaml:"idle"`
Timeout TimeoutSection `yaml:"timeout"`
Prefix string `yaml:"prefix"`
......@@ -30,6 +32,7 @@ func Init(cfg RedisSection) {
addrs := cfg.Addrs
pass := cfg.Pass
db := cfg.DB
maxIdle := cfg.Idle
idleTimeout := 240 * time.Second
......@@ -44,6 +47,7 @@ func Init(cfg RedisSection) {
c, err := redis.Dial("tcp", addr, redis.DialConnectTimeout(connTimeout), redis.DialReadTimeout(readTimeout), redis.DialWriteTimeout(writeTimeout))
if err != nil {
logger.Errorf("conn redis err:%v", err)
stats.Counter.Set("redis.conn.failed", 1)
return nil, err
}
......@@ -51,6 +55,17 @@ func Init(cfg RedisSection) {
if _, err := c.Do("AUTH", pass); err != nil {
c.Close()
logger.Errorf("ERR: redis auth fail:%v", err)
stats.Counter.Set("redis.conn.failed", 1)
return nil, err
}
}
if db != 0 {
if _, err := c.Do("SELECT", db); err != nil {
c.Close()
logger.Error("redis select db fail, db: ", db)
stats.Counter.Set("redis.conn.failed", 1)
return nil, err
}
}
......
......@@ -65,7 +65,7 @@ func Judge(stra *model.Stra, exps []model.Exp, historyData []*dataobj.RRDData, f
stats.Counter.Set("running", 1)
if len(exps) < 1 {
stats.Counter.Set("stra.err", 1)
stats.Counter.Set("stra.illegal", 1)
logger.Warningf("stra:%v exp is null", stra)
return
}
......@@ -105,21 +105,21 @@ func Judge(stra *model.Stra, exps []model.Exp, historyData []*dataobj.RRDData, f
Hashid: getHashId(stra.Id, firstItem),
}
sendEventIfNeed(historyData, isTriggered, now, event)
sendEventIfNeed(historyData, isTriggered, event)
}
}()
leftValue, isTriggered = judgeItemWithStrategy(stra, historyData, exps[0], firstItem, now)
if !isTriggered {
return
}
if value == "" {
value = fmt.Sprintf("%s: %v", exp.Metric, leftValue)
} else {
value += fmt.Sprintf("; %s: %v", exp.Metric, leftValue)
}
if !isTriggered {
return
}
//与条件情况下执行
if len(exps) > 1 {
if exps[1].Func == "nodata" { //nodata重新查询索引来进行告警判断
......@@ -387,7 +387,7 @@ func GetReqs(stra *model.Stra, metric string, endpoints []string, now int64) ([]
return reqs, nil
}
func sendEventIfNeed(historyData []*dataobj.RRDData, isTriggered bool, now int64, event *dataobj.Event) {
func sendEventIfNeed(historyData []*dataobj.RRDData, isTriggered bool, event *dataobj.Event) {
lastEvent, exists := cache.LastEvents.Get(event.ID)
if isTriggered {
event.EventType = EVENT_ALERT
......@@ -421,6 +421,7 @@ func sendEvent(event *dataobj.Event) {
err := redi.Push(event)
if err != nil {
stats.Counter.Set("redis.push.failed", 1)
logger.Errorf("push event:%v err:%v", event, err)
}
}
......
......@@ -56,11 +56,13 @@ func getStrategy(opts StrategySection) {
if err != nil {
logger.Warningf("get strategy from remote failed, error:%v", err)
stats.Counter.Set("stra.get.err", 1)
continue
}
if resp.Err != "" {
logger.Warningf("get strategy from remote failed, error:%v", resp.Err)
stats.Counter.Set("stra.get.err", 1)
continue
}
......
......@@ -43,6 +43,7 @@ type cleanerSection struct {
type redisSection struct {
Addr string `yaml:"addr"`
Pass string `yaml:"pass"`
DB int `yaml:"db"`
Idle int `yaml:"idle"`
Timeout timeoutSection `yaml:"timeout"`
}
......@@ -64,14 +65,24 @@ type httpSection struct {
}
type ldapSection struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
BaseDn string `yaml:"baseDn"`
BindUser string `yaml:"bindUser"`
BindPass string `yaml:"bindPass"`
AuthFilter string `yaml:"authFilter"`
TLS bool `yaml:"tls"`
StartTLS bool `yaml:"startTLS"`
Host string `yaml:"host"`
Port int `yaml:"port"`
BaseDn string `yaml:"baseDn"`
BindUser string `yaml:"bindUser"`
BindPass string `yaml:"bindPass"`
AuthFilter string `yaml:"authFilter"`
Attributes ldapAttributes `yaml:"attributes"`
CoverAttributes bool `yaml:"coverAttributes"`
AutoRegist bool `yaml:"autoRegist"`
TLS bool `yaml:"tls"`
StartTLS bool `yaml:"startTLS"`
}
type ldapAttributes struct {
Dispname string `yaml:"dispname"`
Phone string `yaml:"phone"`
Email string `yaml:"email"`
Im string `yaml:"im"`
}
var (
......
......@@ -11,6 +11,7 @@ import (
"github.com/didi/nightingale/src/model"
"github.com/didi/nightingale/src/modules/monapi/config"
"github.com/didi/nightingale/src/modules/monapi/scache"
"github.com/didi/nightingale/src/toolkits/stats"
)
func CheckJudgeLoop() {
......@@ -19,6 +20,7 @@ func CheckJudgeLoop() {
time.Sleep(duration)
err := CheckJudge()
if err != nil {
stats.Counter.Set("get.judge.err", 1)
logger.Error("check judge fail: ", err)
}
}
......
......@@ -58,7 +58,7 @@ func consume(event *model.Event) {
SetEventStatus(event, model.STATUS_UPGRADE)
if needNotify {
if event.EventType == config.ALERT && NeedCallback(event.Sid) {
if NeedCallback(event.Sid) {
if err := PushCallbackEvent(event); err != nil {
logger.Errorf("push event to callback queue failed, callbackEvent: %+v", event)
}
......@@ -82,7 +82,7 @@ func consume(event *model.Event) {
return
}
if event.EventType == config.ALERT && NeedCallback(event.Sid) {
if NeedCallback(event.Sid) {
if err := PushCallbackEvent(event); err != nil {
logger.Errorf("push event to callback queue failed, callbackEvent: %+v", event)
}
......
......@@ -7,6 +7,7 @@ import (
"github.com/toolkits/pkg/logger"
"github.com/didi/nightingale/src/modules/monapi/config"
"github.com/didi/nightingale/src/toolkits/stats"
)
var RedisConnPool *redis.Pool
......@@ -16,6 +17,7 @@ func InitRedis() {
addr := cfg.Redis.Addr
pass := cfg.Redis.Pass
db := cfg.Redis.DB
maxIdle := cfg.Redis.Idle
idleTimeout := 240 * time.Second
......@@ -29,6 +31,8 @@ func InitRedis() {
Dial: func() (redis.Conn, error) {
c, err := redis.Dial("tcp", addr, redis.DialConnectTimeout(connTimeout), redis.DialReadTimeout(readTimeout), redis.DialWriteTimeout(writeTimeout))
if err != nil {
logger.Errorf("conn redis err:%v", err)
stats.Counter.Set("redis.conn.failed", 1)
return nil, err
}
......@@ -36,6 +40,16 @@ func InitRedis() {
if _, err := c.Do("AUTH", pass); err != nil {
c.Close()
logger.Error("redis auth fail, pass: ", pass)
stats.Counter.Set("redis.conn.failed", 1)
return nil, err
}
}
if db != 0 {
if _, err := c.Do("SELECT", db); err != nil {
c.Close()
logger.Error("redis select db fail, db: ", db)
stats.Counter.Set("redis.select.failed", 1)
return nil, err
}
}
......
......@@ -12,6 +12,7 @@ import (
"github.com/didi/nightingale/src/dataobj"
"github.com/didi/nightingale/src/modules/transfer/calc"
"github.com/didi/nightingale/src/toolkits/address"
"github.com/didi/nightingale/src/toolkits/stats"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/net/httplib"
......@@ -95,15 +96,15 @@ func FetchDataForUI(input dataobj.QueryDataForUI) []*dataobj.TsdbQueryResponse {
//进行数据计算
aggrDatas := []*dataobj.TsdbQueryResponse{}
if input.AggrFunc != "" && len(resp) > 1 {
aggrData := &dataobj.TsdbQueryResponse{
Start: input.Start,
End: input.End,
}
aggrCounter := make(map[string][]*dataobj.TsdbQueryResponse)
if len(input.GroupKey) == 0 || getTags(resp[0].Counter) == "" {
aggrData := &dataobj.TsdbQueryResponse{
Start: input.Start,
End: input.End,
Values: calc.Compute(input.AggrFunc, resp),
}
//没有聚合 tag, 或者曲线没有其他 tags, 直接所有曲线进行计算
aggrData.Values = calc.Compute(input.AggrFunc, resp)
aggrDatas = append(aggrDatas, aggrData)
} else {
for _, data := range resp {
......@@ -132,9 +133,12 @@ func FetchDataForUI(input dataobj.QueryDataForUI) []*dataobj.TsdbQueryResponse {
}
for counter, datas := range aggrCounter {
aggrData.Counter = counter
aggrData.Values = calc.Compute(input.AggrFunc, datas)
aggrData := &dataobj.TsdbQueryResponse{
Start: input.Start,
End: input.End,
Counter: counter,
Values: calc.Compute(input.AggrFunc, datas),
}
aggrDatas = append(aggrDatas, aggrData)
}
}
......@@ -161,10 +165,12 @@ func fetchDataSync(start, end int64, consolFun, endpoint, counter string, step i
defer func() {
<-worker
}()
stats.Counter.Set("query.tsdb", 1)
data, err := fetchData(start, end, consolFun, endpoint, counter, step)
if err != nil {
logger.Warning(err)
stats.Counter.Set("query.data.err", 1)
}
dataChan <- data
return
......
......@@ -107,6 +107,7 @@ func Send2TsdbTask(Q *list.SafeListLimited, node string, addr string, concurrent
// 将数据 打入 某个Tsdb的发送缓存队列, 具体是哪一个Tsdb 由一致性哈希 决定
func Push2TsdbSendQueue(items []*dataobj.MetricValue) {
errCnt := 0
for _, item := range items {
tsdbItem := convert2TsdbItem(item)
stats.Counter.Set("tsdb.queue.push", 1)
......@@ -118,19 +119,18 @@ func Push2TsdbSendQueue(items []*dataobj.MetricValue) {
}
cnode := Config.ClusterList[node]
errCnt := 0
for _, addr := range cnode.Addrs {
Q := TsdbQueues[node+addr]
if !Q.PushFront(tsdbItem) {
errCnt += 1
}
}
}
// statistics
if errCnt > 0 {
stats.Counter.Set("tsdb.queue.err", errCnt)
logger.Error("Push2TsdbSendQueue err num: ", errCnt)
}
// statistics
if errCnt > 0 {
stats.Counter.Set("tsdb.queue.err", errCnt)
logger.Error("Push2TsdbSendQueue err num: ", errCnt)
}
}
......@@ -172,7 +172,7 @@ func Send2JudgeTask(Q *list.SafeListLimited, addr string, concurrent int) {
if !sendOk {
stats.Counter.Set("points.out.judge.err", 1)
logger.Errorf("send judge %s fail: %v", addr, err)
logger.Errorf("send %v to judge %s fail: %v", judgeItems, addr, err)
}
}(addr, judgeItems, count)
......@@ -180,6 +180,7 @@ func Send2JudgeTask(Q *list.SafeListLimited, addr string, concurrent int) {
}
func Push2JudgeSendQueue(items []*dataobj.MetricValue) {
errCnt := 0
for _, item := range items {
key := str.PK(item.Metric, item.Endpoint)
stras := cache.StraMap.GetByKey(key)
......@@ -203,11 +204,13 @@ func Push2JudgeSendQueue(items []*dataobj.MetricValue) {
q, exists := JudgeQueues.Get(stra.JudgeInstance)
if exists {
q.PushFront(judgeItem)
if !q.PushFront(judgeItem) {
errCnt += 1
}
}
}
}
stats.Counter.Set("judge.queue.err", errCnt)
}
// 打到Tsdb的数据,要根据rrdtool的特定 来限制 step、counterType、timestamp
......
......@@ -46,18 +46,50 @@ func QueryData(c *gin.Context) {
func QueryDataForUI(c *gin.Context) {
stats.Counter.Set("data.ui.qp10s", 1)
var input dataobj.QueryDataForUI
var respData []*dataobj.QueryDataForUIResp
errors.Dangerous(c.ShouldBindJSON(&input))
start := input.Start
end := input.End
resp := backend.FetchDataForUI(input)
for _, d := range resp {
data := &dataobj.QueryDataForUIResp{
Start: d.Start,
End: d.End,
Endpoint: d.Endpoint,
Counter: d.Counter,
DsType: d.DsType,
Step: d.Step,
Values: d.Values,
}
respData = append(respData, data)
}
if len(input.Comparisons) > 1 {
for i := 1; i < len(input.Comparisons); i++ {
input.Start = input.Start - input.Comparisons[i]
input.End = input.End - input.Comparisons[i]
comparison := input.Comparisons[i]
input.Start = start - comparison
input.End = end - comparison
res := backend.FetchDataForUI(input)
resp = append(resp, res...)
for _, d := range res {
for j := range d.Values {
d.Values[j].Timestamp += comparison
}
data := &dataobj.QueryDataForUIResp{
Start: d.Start,
End: d.End,
Endpoint: d.Endpoint,
Counter: d.Counter,
DsType: d.DsType,
Step: d.Step,
Values: d.Values,
Comparison: comparison,
}
respData = append(respData, data)
}
}
}
render.Data(c, resp, nil)
render.Data(c, respData, nil)
}
......@@ -18,7 +18,7 @@ func Config(r *gin.Engine) {
sys.GET("/alive-judges", judges)
sys.POST("/push", PushData)
sys.POST("/data", QueryDataForJudge)
sys.POST("/data", QueryData)
sys.POST("/data/ui", QueryDataForUI)
}
......
......@@ -27,9 +27,10 @@ func (t *Transfer) Push(args []*dataobj.MetricValue, reply *dataobj.TransferResp
err := v.CheckValidity()
if err != nil {
stats.Counter.Set("points.in.err", 1)
logger.Warningf("item is illegal item:%s err:%v", v, err)
msg := fmt.Sprintf("item is illegal item:%s err:%v", v, err)
logger.Warningf(msg)
reply.Invalid += 1
reply.Msg += fmt.Sprintf("%v\n", err)
reply.Msg += msg
continue
}
......
......@@ -47,7 +47,7 @@ func GetIndexLoop() {
func GetIndex() {
instances, err := report.GetAlive("index", Config.HbsMod)
if err != nil {
stats.Counter.Set("index.get.err", 1)
stats.Counter.Set("get.index.err", 1)
logger.Warningf("get index list err:%v", err)
return
}
......
......@@ -58,7 +58,6 @@ func handleItems(items []*dataobj.TsdbItem) {
//todo hash冲突问题需要解决
if err := cache.Caches.Push(item.Key, item.Timestamp, item.Value); err != nil {
stats.Counter.Set("points.in.err", 1)
logger.Warningf("push obj error, obj: %v, error: %v\n", items[i], err)
fail++
}
......
......@@ -231,6 +231,7 @@ func FlushRRD(flushChunks map[interface{}][]*cache.Chunk) {
err := FlushFile(seriesID, items)
if err != nil {
stats.Counter.Set("flush.rrd.err", 1)
logger.Errorf("flush %v data to rrd err:%v", seriesID, err)
continue
}
......
......@@ -18,11 +18,12 @@ type IdentitySection struct {
func Init(identity IdentitySection) {
if identity.Specify != "" {
Identity = identity.Specify
return
}
var err error
Identity, err = sys.CmdOutTrim("bash", "-c", identity.Shell)
if err != nil {
log.Fatalln("[F] cannot get hostname")
log.Fatalln("[F] cannot get identity")
}
}
......@@ -3,13 +3,17 @@ package stats
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"path"
"time"
"github.com/didi/nightingale/src/dataobj"
"github.com/didi/nightingale/src/toolkits/identity"
"github.com/didi/nightingale/src/toolkits/address"
"github.com/toolkits/pkg/file"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/runner"
)
var (
......@@ -17,8 +21,14 @@ var (
)
func Init(prefix string, addr ...string) {
if len(addr) > 0 {
if len(addr) > 0 && addr[0] != "" {
//如果配置了 addr,使用 addr 参数
PushUrl = addr[0]
} else if file.IsExist(path.Join(runner.Cwd, "etc", "address.yml")) {
//address.yml 存在,则使用配置文件的地址
port := address.GetHTTPPort("collector")
PushUrl = fmt.Sprintf("http://127.0.0.1:%d/api/collector/push", port)
}
Counter = NewCounter(prefix)
......@@ -42,7 +52,6 @@ func Push() {
func NewMetricValue(metric string, value int64) *dataobj.MetricValue {
item := &dataobj.MetricValue{
Metric: metric,
Endpoint: identity.Identity,
Timestamp: time.Now().Unix(),
ValueUntyped: value,
CounterType: "GAUGE",
......
node_modules
\ No newline at end of file
FROM node:lts-alpine AS builder
WORKDIR /app
COPY . .
RUN npm install
\ No newline at end of file
......@@ -76,7 +76,10 @@ class index extends Component<Props, State> {
processData = async (endpoints: Endpoint[]) => {
if (this.state.displayBindNode && endpoints) {
const idents = _.map(endpoints, item => item.ident);
const endpointNodes = await request(`${api.endpoint}s/bindings?idents=${_.join(idents, ',')}`);
let endpointNodes: any[] = [];
if (idents.length) {
endpointNodes = await request(`${api.endpoint}s/bindings?idents=${_.join(idents, ',')}`);
}
const newEndpoints = _.map(endpoints, (item) => {
const current = _.find(endpointNodes, { ident: item.ident });
const nodes = _.get(current, 'nodes', []);
......
......@@ -184,7 +184,7 @@ class index extends Component<null, State> {
<div className="mt10 mb10" style={{ wordBreak: 'break-word' }}>
{_.get(selectedNode, 'path')}
</div>
<Popconfirm title="确定要删除这个节点吗?" onConfirm={this.handleDelNode}>
<Popconfirm disabled={isPdlNode} title="确定要删除这个节点吗?" onConfirm={this.handleDelNode}>
<Button disabled={isPdlNode}>删除</Button>
</Popconfirm>
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册