alerting modify

This commit is contained in:
liugq 2021-09-29 17:49:42 +08:00
parent 1efca99090
commit 879f529514
64 changed files with 707 additions and 411 deletions

View File

@ -38,21 +38,22 @@ func Init(cfg *config.AppConfig) {
ui.HandleUIMethod(api.POST, path.Join(pathPrefix, "index/:index"), handler.HandleCreateIndexAction)
//new api
ui.HandleUIMethod(api.GET, "/elasticsearch/:id/alerting/overview", alerting.GetAlertOverview)
ui.HandleUIMethod(api.POST, "/elasticsearch/:id/alerting/destinations/email_accounts", alerting.CreateEmailAccount)
ui.HandleUIMethod(api.PUT, "/elasticsearch/:id/alerting/email_accounts/:emailAccountId", alerting.UpdateEmailAccount)
ui.HandleUIMethod(api.DELETE, "/elasticsearch/:id/alerting/email_accounts/:emailAccountId", alerting.DeleteEmailAccount)
ui.HandleUIMethod(api.GET, "/elasticsearch/:id/alerting/destinations/email_accounts", alerting.GetEmailAccounts)
ui.HandleUIMethod(api.GET, "/elasticsearch/:id/alerting/email_accounts/:emailAccountId", alerting.GetEmailAccount)
ui.HandleUIMethod(api.POST, "/elasticsearch/:id/alerting/destinations/email_groups", alerting.CreateEmailGroup)
ui.HandleUIMethod(api.GET, "/elasticsearch/:id/alerting/destinations/email_groups", alerting.GetEmailGroups)
ui.HandleUIMethod(api.DELETE, "/elasticsearch/:id/alerting/email_groups/:emailGroupId", alerting.DeleteEmailGroup)
ui.HandleUIMethod(api.PUT, "/elasticsearch/:id/alerting/email_groups/:emailGroupId", alerting.UpdateEmailGroup)
ui.HandleUIMethod(api.GET, "/elasticsearch/:id/alerting/email_groups/:emailGroupId", alerting.GetEmailGroup)
ui.HandleUIMethod(api.GET, "/elasticsearch/:id/alerting/destinations", alerting.GetDestinations)
ui.HandleUIMethod(api.POST, "/elasticsearch/:id/alerting/destinations", alerting.CreateDestination)
ui.HandleUIMethod(api.PUT, "/elasticsearch/:id/alerting/destinations/:destinationId", alerting.UpdateDestination)
ui.HandleUIMethod(api.DELETE, "/elasticsearch/:id/alerting/destinations/:destinationId", alerting.DeleteDestination)
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "alerting/overview"), alerting.GetAlertOverview)
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "alerting/overview/alerts"), alerting.GetAlerts)
ui.HandleUIMethod(api.POST, path.Join(pathPrefix,"alerting/destinations/email_accounts"), alerting.CreateEmailAccount)
ui.HandleUIMethod(api.PUT, path.Join(pathPrefix, "alerting/email_accounts/:emailAccountId"), alerting.UpdateEmailAccount)
ui.HandleUIMethod(api.DELETE, path.Join(pathPrefix,"alerting/email_accounts/:emailAccountId"), alerting.DeleteEmailAccount)
ui.HandleUIMethod(api.GET, path.Join(pathPrefix,"alerting/destinations/email_accounts"), alerting.GetEmailAccounts)
ui.HandleUIMethod(api.GET, path.Join(pathPrefix,"alerting/email_accounts/:emailAccountId"), alerting.GetEmailAccount)
ui.HandleUIMethod(api.POST, path.Join(pathPrefix,"alerting/destinations/email_groups"), alerting.CreateEmailGroup)
ui.HandleUIMethod(api.GET, path.Join(pathPrefix,"alerting/destinations/email_groups"), alerting.GetEmailGroups)
ui.HandleUIMethod(api.DELETE, path.Join(pathPrefix,"alerting/email_groups/:emailGroupId"), alerting.DeleteEmailGroup)
ui.HandleUIMethod(api.PUT, path.Join(pathPrefix,"alerting/email_groups/:emailGroupId"), alerting.UpdateEmailGroup)
ui.HandleUIMethod(api.GET, path.Join(pathPrefix,"alerting/email_groups/:emailGroupId"), alerting.GetEmailGroup)
ui.HandleUIMethod(api.GET, path.Join(pathPrefix, "alerting/destinations"), alerting.GetDestinations)
ui.HandleUIMethod(api.POST, path.Join(pathPrefix,"alerting/destinations"), alerting.CreateDestination)
ui.HandleUIMethod(api.PUT, path.Join(pathPrefix,"alerting/destinations/:destinationId"), alerting.UpdateDestination)
ui.HandleUIMethod(api.DELETE, path.Join(pathPrefix, "alerting/destinations/:destinationId"), alerting.DeleteDestination)
ui.HandleUIMethod(api.GET, "/elasticsearch/:id/alerting/monitors/:monitorID", alerting.GetMonitor)
ui.HandleUIMethod(api.PUT, "/elasticsearch/:id/alerting/monitors/:monitorID", alerting.UpdateMonitor)
ui.HandleUIMethod(api.GET, "/elasticsearch/:id/alerting/monitors", alerting.GetMonitors)
@ -60,11 +61,11 @@ func Init(cfg *config.AppConfig) {
ui.HandleUIMethod(api.POST, "/elasticsearch/:id/alerting/monitors/_execute", alerting.ExecuteMonitor)
ui.HandleUIMethod(api.DELETE, "/elasticsearch/:id/alerting/monitors/:monitorID", alerting.DeleteMonitor)
ui.HandleUIMethod(api.GET, "/elasticsearch/:id/alerting/_settings", alerting.GetSettings)
ui.HandleUIMethod(api.GET, path.Join(pathPrefix,"alerting/_settings"), alerting.GetSettings)
ui.HandleUIMethod(api.POST, "/elasticsearch/:id/alerting/_indices", alerting.GetIndices)
ui.HandleUIMethod(api.POST, "/elasticsearch/:id/alerting/_aliases", alerting.GetAliases)
ui.HandleUIMethod(api.POST, "/elasticsearch/:id/alerting/_mappings", alerting.GetMappings)
ui.HandleUIMethod(api.POST, "/elasticsearch/:id/alerting/monitors/_search", alerting.Search)
ui.HandleUIMethod(api.POST, path.Join(pathPrefix, "alerting/_search"), alerting.Search)
ui.HandleUIMethod(api.GET, "/elasticsearch/:id/alerting/alerts", alerting.GetAlerts)
ui.HandleUIMethod(api.POST, "/elasticsearch/:id/alerting/_monitors/:monitorID/_acknowledge/alerts", alerting.AcknowledgeAlerts)

View File

@ -18,7 +18,6 @@ type EmailAction struct {
func (act *EmailAction) Execute() ([]byte, error){
from := act.Sender.Email
//Todo add password input in frontend?
act.Sender.Host = strings.TrimSpace(act.Sender.Host)
addr := fmt.Sprintf("%s:%d", act.Sender.Host, act.Sender.Port)
msg := fmt.Sprintf("To: %s\r\nSubject: %s\r\n%s\r\n", strings.Join(act.Receiver, ","), act.Subject, act.Message)
@ -41,6 +40,10 @@ func SendEmailOverTLS(addr string, auth smtp.Auth, from string, to []string, msg
}
conn, err := tls.Dial("tcp", addr, tlsConfig)
//dialer := &net.Dialer{
// Timeout: time.Second * 15,
//}
//conn, err := tls.DialWithDialer(dialer, "tcp", addr, tlsConfig)
if err != nil {
return err
}

View File

@ -75,7 +75,7 @@ func GetAlerts (w http.ResponseWriter, req *http.Request, ps httprouter.Params){
}
must := []IfaceMap{
}
if id != "_all" {
if id != "" {
must = append(must, IfaceMap{
"match": IfaceMap{
"cluster_id": id,

View File

@ -9,19 +9,12 @@ import (
"infini.sh/framework/core/util"
"infini.sh/search-center/model/alerting"
"net/http"
"runtime/debug"
"strings"
"time"
)
func GetDestination(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
id := ps.ByName("id")
conf := elastic.GetConfig(id)
if conf == nil {
writeError(w, errors.New("cluster not found"))
return
}
conf := getDefaultConfig()
dstID := ps.ByName("destID")
reqUrl := fmt.Sprintf("%s/%s/_doc/%s", conf.Endpoint, orm.GetIndexName(alerting.Config{}), dstID)
res, err := doRequest(reqUrl, http.MethodGet, nil, nil)
@ -45,12 +38,6 @@ func GetDestination(w http.ResponseWriter, req *http.Request, ps httprouter.Para
}
func GetDestinations(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
id := ps.ByName("id")
conf := elastic.GetConfig(id)
if conf == nil {
writeError(w, errors.New("cluster not found"))
return
}
var (
from = getQueryParam(req, "from", "0")
size = getQueryParam(req, "size", "20")
@ -153,13 +140,6 @@ func GetDestinations(w http.ResponseWriter, req *http.Request, ps httprouter.Par
}
func CreateDestination(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
id := ps.ByName("id")
conf := elastic.GetConfig(id)
if conf == nil {
writeError(w, errors.New("cluster not found"))
return
}
config := getDefaultConfig()
destId := util.GetUUID()
reqUrl := fmt.Sprintf("%s/%s/_doc/%s", config.Endpoint, orm.GetIndexName(alerting.Config{}), destId)
@ -189,7 +169,6 @@ func CreateDestination(w http.ResponseWriter, req *http.Request, ps httprouter.P
res, err := doRequest(reqUrl, http.MethodPost, map[string]string{
"refresh": "wait_for",
}, IfaceMap{
"cluster_id": id,
DESTINATION_FIELD: toSaveDest,
})
if err != nil {
@ -217,13 +196,6 @@ func CreateDestination(w http.ResponseWriter, req *http.Request, ps httprouter.P
}
func UpdateDestination(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
id := ps.ByName("id")
conf := elastic.GetConfig(id)
if conf == nil {
writeError(w, errors.New("cluster not found"))
return
}
destinationId := ps.ByName("destinationId")
config := getDefaultConfig()
@ -254,7 +226,6 @@ func UpdateDestination(w http.ResponseWriter, req *http.Request, ps httprouter.P
res, err := doRequest(reqUrl, http.MethodPut, map[string]string{
"refresh": "wait_for",
}, IfaceMap{
"cluster_id": id,
DESTINATION_FIELD: toSaveDest,
})
if err != nil {
@ -278,13 +249,6 @@ func UpdateDestination(w http.ResponseWriter, req *http.Request, ps httprouter.P
}
func DeleteDestination(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
id := ps.ByName("id")
conf := elastic.GetConfig(id)
if conf == nil {
writeError(w, errors.New("cluster not found"))
return
}
destinationId := ps.ByName("destinationId")
config := getDefaultConfig()
@ -337,17 +301,6 @@ func getDefaultConfig() *elastic.ElasticsearchConfig {
//}
func CreateEmailAccount(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
defer func() {
if err := recover(); err != nil {
debug.PrintStack()
}
}()
id := ps.ByName("id")
conf := elastic.GetConfig(id)
if conf == nil {
writeError(w, errors.New("cluster not found"))
return
}
config := getDefaultConfig()
reqUrl := fmt.Sprintf("%s/%s/_doc", config.Endpoint, orm.GetIndexName(alerting.Config{}))
@ -363,7 +316,6 @@ func CreateEmailAccount(w http.ResponseWriter, req *http.Request, ps httprouter.
"refresh": "wait_for",
}, IfaceMap{
EMAIL_ACCOUNT_FIELD: emailAccount,
"cluster_id": id,
})
if err != nil {
writeError(w, err)
@ -403,13 +355,6 @@ func CreateEmailAccount(w http.ResponseWriter, req *http.Request, ps httprouter.
}
func UpdateEmailAccount(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
id := ps.ByName("id")
conf := elastic.GetConfig(id)
if conf == nil {
writeError(w, errors.New("cluster not found"))
return
}
emailAccountId := ps.ByName("emailAccountId")
config := getDefaultConfig()
@ -424,7 +369,6 @@ func UpdateEmailAccount(w http.ResponseWriter, req *http.Request, ps httprouter.
res, err := doRequest(reqUrl, http.MethodPut, map[string]string{
"refresh": "wait_for",
}, IfaceMap{
"cluster_id": id,
EMAIL_ACCOUNT_FIELD: emailAccount,
})
if err != nil {
@ -448,13 +392,6 @@ func UpdateEmailAccount(w http.ResponseWriter, req *http.Request, ps httprouter.
}
func DeleteEmailAccount(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
id := ps.ByName("id")
conf := elastic.GetConfig(id)
if conf == nil {
writeError(w, errors.New("cluster not found"))
return
}
emailAccountId := ps.ByName("emailAccountId")
config := getDefaultConfig()
@ -487,12 +424,6 @@ func DeleteEmailAccount(w http.ResponseWriter, req *http.Request, ps httprouter.
}
func GetEmailAccounts(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
id := ps.ByName("id")
conf := elastic.GetConfig(id)
if conf == nil {
writeError(w, errors.New("cluster not found"))
return
}
var (
from = getQueryParam(req, "from", "0")
size = getQueryParam(req, "size", "20")
@ -577,13 +508,6 @@ func GetEmailAccounts(w http.ResponseWriter, req *http.Request, ps httprouter.Pa
}
func GetEmailAccount(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
id := ps.ByName("id")
conf := elastic.GetConfig(id)
if conf == nil {
writeError(w, errors.New("cluster not found"))
return
}
emailAccountId := ps.ByName("emailAccountId")
config := getDefaultConfig()
@ -621,13 +545,6 @@ func GetEmailAccount(w http.ResponseWriter, req *http.Request, ps httprouter.Par
// --- email group
func CreateEmailGroup(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
id := ps.ByName("id")
conf := elastic.GetConfig(id)
if conf == nil {
writeError(w, errors.New("cluster not found"))
return
}
config := getDefaultConfig()
reqUrl := fmt.Sprintf("%s/%s/_doc", config.Endpoint, orm.GetIndexName(alerting.Config{}))
var emailGroup = &alerting.EmailGroup{}
@ -639,7 +556,6 @@ func CreateEmailGroup(w http.ResponseWriter, req *http.Request, ps httprouter.Pa
res, err := doRequest(reqUrl, http.MethodPost, map[string]string{
"refresh": "wait_for",
}, IfaceMap{
"cluster_id": id,
EMAIL_GROUP_FIELD: emailGroup,
})
if err != nil {
@ -670,13 +586,6 @@ func CreateEmailGroup(w http.ResponseWriter, req *http.Request, ps httprouter.Pa
}
func UpdateEmailGroup(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
id := ps.ByName("id")
conf := elastic.GetConfig(id)
if conf == nil {
writeError(w, errors.New("cluster not found"))
return
}
emailGroupId := ps.ByName("emailGroupId")
config := getDefaultConfig()
reqUrl := fmt.Sprintf("%s/%s/_doc/%s", config.Endpoint, orm.GetIndexName(alerting.Config{}), emailGroupId)
@ -689,7 +598,6 @@ func UpdateEmailGroup(w http.ResponseWriter, req *http.Request, ps httprouter.Pa
res, err := doRequest(reqUrl, http.MethodPut, map[string]string{
"refresh": "wait_for",
}, IfaceMap{
"cluster_id": id,
EMAIL_GROUP_FIELD: emailGroup,
})
if err != nil {
@ -712,13 +620,6 @@ func UpdateEmailGroup(w http.ResponseWriter, req *http.Request, ps httprouter.Pa
}
func DeleteEmailGroup(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
id := ps.ByName("id")
conf := elastic.GetConfig(id)
if conf == nil {
writeError(w, errors.New("cluster not found"))
return
}
emailGroupId := ps.ByName("emailGroupId")
config := getDefaultConfig()
reqUrl := fmt.Sprintf("%s/%s/_doc/%s", config.Endpoint, orm.GetIndexName(alerting.Config{}), emailGroupId)
@ -749,12 +650,6 @@ func DeleteEmailGroup(w http.ResponseWriter, req *http.Request, ps httprouter.Pa
}
func GetEmailGroups(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
id := ps.ByName("id")
conf := elastic.GetConfig(id)
if conf == nil {
writeError(w, errors.New("cluster not found"))
return
}
var (
from = getQueryParam(req, "from", "0")
size = getQueryParam(req, "size", "20")
@ -838,13 +733,6 @@ func GetEmailGroups(w http.ResponseWriter, req *http.Request, ps httprouter.Para
}
func GetEmailGroup(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
id := ps.ByName("id")
conf := elastic.GetConfig(id)
if conf == nil {
writeError(w, errors.New("cluster not found"))
return
}
emailGroupId := ps.ByName("emailGroupId")
config := getDefaultConfig()

View File

@ -17,12 +17,6 @@ type SearchBody struct {
}
func Search(w http.ResponseWriter, req *http.Request, ps httprouter.Params){
id := ps.ByName("id")
meta := elastic.GetMetadata(id)
if meta == nil {
writeError(w, errors.New("cluster not found"))
return
}
var body = SearchBody{}
err := decodeJSON(req.Body, &body)
if err != nil {
@ -175,15 +169,10 @@ func GetMappings(w http.ResponseWriter, req *http.Request, ps httprouter.Params)
}
func GetSettings(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
id := ps.ByName("id")
meta := elastic.GetMetadata(id)
if meta == nil {
writeError(w, errors.New("cluster not found"))
return
}
// /_cluster/settings?include_defaults=true
reqUrl := fmt.Sprintf("%s/_cluster/settings", meta.GetActiveEndpoint())
config := getDefaultConfig()
reqUrl := fmt.Sprintf("%s/_cluster/settings", config.Endpoint)
res, err := doRequest(reqUrl, http.MethodGet, map[string]string{
"include_defaults": "true",
}, nil)

View File

@ -695,7 +695,7 @@ func ExecuteMonitor(w http.ResponseWriter, req *http.Request, ps httprouter.Para
}
}
period := alertUtil.GetMonitorPeriod(&periodStart, &monitor.Schedule)
period := alertUtil.GetMonitorPeriod(periodStart, &monitor.Schedule)
writeJSON(w, IfaceMap{
"ok": true,
"resp": IfaceMap{

View File

@ -3,6 +3,8 @@ package alerting
import (
"fmt"
httprouter "infini.sh/framework/core/api/router"
"infini.sh/framework/core/elastic"
"infini.sh/framework/core/orm"
"net/http"
)
@ -127,13 +129,35 @@ func getTopTenAlertCluster()(interface{}, error){
}
}
}
//reqBody = IfaceMap{
// "query": IfaceMap{
// "terms": IfaceMap{
// "_id": clusterIDs,
// },
// },
//}
reqBody = IfaceMap{
"_source": "name",
"query": IfaceMap{
"terms": IfaceMap{
"_id": clusterIDs,
},
},
}
config := getDefaultConfig()
reqUrl := fmt.Sprintf("%s/%s/_search", config.Endpoint, orm.GetIndexName(elastic.ElasticsearchConfig{}))
res, err := doRequest(reqUrl, http.MethodGet, nil, reqBody)
if err != nil {
return nil, err
}
var resBody = &elastic.SearchResponse{}
err = decodeJSON(res.Body, resBody)
if err != nil {
return nil, err
}
res.Body.Close()
clusterMap := IfaceMap{}
for _, hit := range resBody.Hits.Hits {
clusterMap[hit.ID.(string)] = hit.Source["name"]
}
for _, d := range metricData {
if name, ok := clusterMap[d["x"].(string)]; ok {
d["x"] = name
}
}
return metricData, nil
}

View File

@ -15,6 +15,7 @@ import (
"infini.sh/framework/core/orm"
"infini.sh/search-center/model/alerting"
"infini.sh/search-center/service/alerting/action"
"infini.sh/search-center/service/alerting/util"
"io"
"net/http"
"strings"
@ -148,12 +149,17 @@ type MonitorJob func()
func generateMonitorJob(smt *ScheduleMonitor) MonitorJob{
sm := *smt
return func() {
startTime := time.Now()
queryResult, err := getQueryResult(sm.ClusterID, &sm.Monitor.Inputs[0])
if err != nil {
log.Error(err)
}
periods := util.GetMonitorPeriod(startTime, &sm.Monitor.Schedule)
for _, trigger := range sm.Monitor.Triggers {
monitorCtx, err := createMonitorContext(&trigger, queryResult, &sm, IfaceMap{})
monitorCtx, err := createMonitorContext(&trigger, queryResult, &sm, IfaceMap{
"periodStart": periods.Start,
"periodEnd": periods.End,
})
if err != nil {
log.Error(err)
continue
@ -496,17 +502,17 @@ func resolveMessage(messageTemplate IfaceMap, monitorCtx []byte ) ([]byte, error
}
func createMonitorContext(trigger *alerting.Trigger, result IfaceMap, smt *ScheduleMonitor, extra IfaceMap) ([]byte, error){
ctx := IfaceMap{
"_ctx": IfaceMap{
"results": []interface{}{
result,
},
"trigger": trigger,
"monitor": smt.Monitor,
"cluster_id": smt.ClusterID,
"periodStart": "",
"periodEnd":"",
params := IfaceMap{
"results": []interface{}{
result,
},
"trigger": trigger,
"monitor": smt.Monitor,
"cluster_id": smt.ClusterID,
}
assignTo(params, extra)
ctx := IfaceMap{
"_ctx": params,
}
return json.Marshal(ctx)
}

View File

@ -8,11 +8,11 @@ import (
)
type MonitorPeriod struct {
Start int64
End int64
Start time.Time
End time.Time
}
func GetMonitorPeriod(currentTime *time.Time, schedule *alerting.Schedule) *MonitorPeriod{
func GetMonitorPeriod(currentTime time.Time, schedule *alerting.Schedule) *MonitorPeriod{
if schedule.Period != nil {
return transformPeriod(currentTime, schedule.Period)
}
@ -23,7 +23,7 @@ func GetMonitorPeriod(currentTime *time.Time, schedule *alerting.Schedule) *Moni
}
func transformCron(currentTime *time.Time, cron *alerting.Cron) *MonitorPeriod {
func transformCron(currentTime time.Time, cron *alerting.Cron) *MonitorPeriod {
timezone := ""
if cron.Timezone != "" {
timezone = fmt.Sprintf("CRON_TZ=%s ", cron.Timezone)
@ -39,7 +39,7 @@ func transformCron(currentTime *time.Time, cron *alerting.Cron) *MonitorPeriod {
if ssd.Hour == 1 {
duration = time.Hour
}
tempTime := *currentTime
tempTime := currentTime
nextTime := sd.Next(tempTime)
var preTime = tempTime
for {
@ -49,18 +49,18 @@ func transformCron(currentTime *time.Time, cron *alerting.Cron) *MonitorPeriod {
}
}
mp := &MonitorPeriod{
Start: preTime.UnixNano()/1e6,
End: currentTime.UnixNano()/1e6,
Start: preTime,
End: currentTime,
}
return mp
}
func transformPeriod(currentTime *time.Time, period *alerting.Period) *MonitorPeriod {
func transformPeriod(currentTime time.Time, period *alerting.Period) *MonitorPeriod {
if period == nil {
return nil
}
mp := &MonitorPeriod{
End: currentTime.UnixNano()/1e6,
End: currentTime,
}
var duration time.Duration
switch period.Unit {
@ -73,6 +73,6 @@ func transformPeriod(currentTime *time.Time, period *alerting.Period) *MonitorPe
default:
return nil
}
mp.Start = currentTime.Add(-duration * time.Duration(period.Interval)).UnixNano()/1e6
mp.Start = currentTime.Add(-duration * time.Duration(period.Interval))
return mp
}

View File

@ -73,6 +73,11 @@ export default {
changeOrigin: true,
// pathRewrite: { '^/server': '' },
},
'/_search-center/': {
target: 'http://localhost:9000',
changeOrigin: true,
// pathRewrite: { '^/server': '' },
},
},
ignoreMomentLocale: true,
lessLoaderOptions: {

View File

@ -106,13 +106,31 @@ export default [
//alerting
{
routes:[
{ path: '/', redirect: '/' },
],
path: '/alerting',
name: 'alerting',
icon: 'alert',
component: './Alerting/index',
routes: [{
routes:[
{ path: '/', redirect: '/' },
],
path: '/alerting/overview',
component: './Alerting/pages/Overview/Overview',
name: 'overview'
},{
routes:[
{ path: '/', redirect: '/' },
],
path: '/alerting/monitor',
component: './Alerting/index',
name: 'monitor'
},{
routes:[
{ path: '/', redirect: '/' },
],
path: '/alerting/destination',
component: './Alerting/destination',
name: 'destination'
}]
},
//data

View File

@ -68,6 +68,7 @@ export default class GlobalHeader extends PureComponent {
payload:{
history,
pathname: history.location.pathname,
isChangedState: true,
}
})
});

View File

@ -68,6 +68,7 @@ export default {
'form.button.add': 'Add',
'form.button.edit': 'Edit',
'form.button.update': 'Update',
'form.button.save': 'Save',
'form.button.delete': 'Delete',
'form.button.cancel': 'Cancel',
'form.button.collapse': 'Collapse',
@ -91,6 +92,9 @@ export default {
'menu.home': 'Home',
'menu.devtool': 'CONSOLE',
'menu.alerting': 'AERTING',
'menu.alerting.overview': 'OVERVIEW',
'menu.alerting.monitor': 'MONITORS',
'menu.alerting.destination': 'DESTINATIONS',
'menu.cluster': 'CLUSTER',
'menu.cluster.overview': 'OVERVIEW',

View File

@ -1,4 +1,10 @@
export default {
'alert.overview.metric.active': 'ACTIVE',
'alert.overview.metric.acknowledged': 'ACKNOWLEDGED',
'alert.overview.metric.error': 'ERROR',
'alert.overview.alertlist.title': 'Open Alerts',
'alert.overview.alertlist-history.title': 'History Alerts',
'alert.button.acknowledge': 'Acknowledge',
'alert.button.create-monitor': 'Create monitor',
'alert.button.update-monitor': 'Update monitor',
@ -79,6 +85,20 @@ export default {
'alert.monitor.create.schedule.when-text': 'When do you want this monitor to run?',
'alert.monitor.create.schedule.advise-text': 'Define how often the monitor collects data and determines how often you may receive alerts. We recommend two times of detector interval to avoid missing anomaly results due to any potential delay of processing time.',
'alert.monitor.overview.title': 'Overview',
'alert.monitor.overview.header.state': 'State',
'alert.monitor.overview.header.definition_type': 'Monitor definition type',
'alert.monitor.overview.header.total_active_alerts': 'Total active alerts',
'alert.monitor.overview.header.schedule': 'Schedule',
'alert.monitor.overview.header.last_updated': 'Last updated',
'alert.monitor.overview.header.monitor_id': 'Monitor ID',
'alert.monitor.overview.header.version_number': 'Monitor version number',
'alert.monitor.triggers.title': 'Triggers',
'alert.monitor.triggers.table.header.name': 'Name',
'alert.monitor.triggers.table.header.number_of_actions': 'Number of actions',
'alert.monitor.triggers.table.header.severity': 'Severity',
'alert.monitor.history.title': 'History',
'alert.destination': 'Destinations',
'alert.destination.self': 'destination',
'alert.destination.self-upper': 'Destination',
@ -107,4 +127,43 @@ export default {
'alert.trigger': 'Triggers',
'alert.trigger.self': 'trigger',
'alert.trigger.create.title': 'Create trigger',
'alert.trigger.edit.title': 'Edit trigger',
'alert.trigger.edit.define.title': 'Define trigger',
'alert.trigger.edit.define.field.name': 'Trigger name',
'alert.trigger.edit.define.field.severity': 'Severity level',
'alert.trigger.edit.define.field.query_response': 'Extraction query response',
'alert.trigger.edit.define.field.condition': 'Trigger condition',
'alert.trigger.edit.define.field.condition.info.paragraph1': 'You have access to a "_ctx" variable in your yaml scripts',
'alert.trigger.edit.define.field.condition.info.paragraph2': `Below shows a quick JSON example of what's available under the "_ctx" variable along withthe actual results (where possible) for you to reference.`,
'alert.trigger.edit.define.field.condition_response': 'Trigger condition response',
'alert.trigger.edit.define.field.name.help': 'Trigger names must be unique. Names can only contain letters, numbers, and special characters.',
'alert.trigger.edit.define.field.severity.help': 'Severity levels help you organize your triggers and actions. A trigger with a high severity level might page a specific individual, whereas a trigger with a low severity level might email a list.',
'alert.trigger.edit.define.button.run': 'Run',
'alert.trigger.edit.action.title': 'Configure actions',
'alert.trigger.edit.action.button.add_action': 'Add action',
'alert.trigger.edit.action.field.name': 'Action name',
'alert.trigger.edit.action.field.destination': 'Destination',
'alert.trigger.edit.action.field.message': 'Message',
'alert.trigger.edit.action.field.message_subject': 'Message subject',
'alert.trigger.edit.action.field.message_preview': 'Message preview',
'alert.trigger.edit.action.send_test_message': 'Send test message',
'alert.trigger.edit.action.default_name': 'Notification',
'alert.email.manage.title': 'Manage email senders',
'alert.email.manage.button.remove': 'Remove sender',
'alert.email.manage.button.add': 'Add sender',
'alert.email.manage.field.name': 'Sender name',
'alert.email.manage.field.name.help': 'A unique and descriptive name that is easy to search. Valid characters are upper and lowercase a-z, 0-9, and _ (underscore).',
'alert.email.manage.field.address': 'Email address',
'alert.email.manage.field.password': 'Email password',
'alert.email.manage.field.host': 'Host',
'alert.email.manage.field.port': 'Port',
'alert.email.manage.field.method': 'Encryption method',
'alert.emailgroup.manage.title': 'Manage email groups',
'alert.emailgroup.manage.button.remove': 'Remove email group',
'alert.emailgroup.manage.button.add': 'Add email group',
'alert.emailgroup.manage.field.name': 'Email group name',
'alert.emailgroup.manage.field.name.help': 'A unique and descriptive name that is easy to search. Valid characters are upper and lowercase a-z, 0-9, and _ (underscore).',
'alert.emailgroup.manage.field.emails': 'Emails',
'alert.emailgroup.manage.field.emails.help': 'Search for previously used email addresses or type in new ones.',
};

View File

@ -74,6 +74,7 @@ export default {
'form.button.add': '添加',
'form.button.edit': '编辑',
'form.button.update': '更新',
'form.button.save': '保存',
'form.button.delete': '删除',
'form.button.cancel': '取消',
'form.button.collapse': '收起',
@ -98,6 +99,9 @@ export default {
'menu.home': '首页',
'menu.devtool': '开发工具',
'menu.alerting': '告警管理',
'menu.alerting.overview': '概览',
'menu.alerting.monitor': '监控管理',
'menu.alerting.destination': '渠道管理',
'menu.cluster': '集群管理',
'menu.cluster.overview': '概览',

View File

@ -1,4 +1,10 @@
export default {
'alert.overview.metric.active': '激活告警',
'alert.overview.metric.acknowledged': '已响应告警',
'alert.overview.metric.error': '错误告警',
'alert.overview.alertlist.title': '当前告警',
'alert.overview.alertlist-history.title': '历史告警',
'alert.button.acknowledge': '确认',
'alert.button.create-monitor': '创建监控项',
'alert.button.update-monitor': '更新监控项',
@ -79,6 +85,19 @@ export default {
'alert.monitor.create.schedule.when-text': '您希望此监视器何时运行?',
'alert.monitor.create.schedule.advise-text': '定义监视器收集数据的频率并确定您接收警报的频率。 我们建议两倍检测器间隔,以避免由于处理时间的任何潜在延迟而遗漏异常结果。',
'alert.monitor.overview.title': '概览',
'alert.monitor.overview.header.state': '状态',
'alert.monitor.overview.header.definition_type': '监控定义类型',
'alert.monitor.overview.header.total_active_alerts': '激活告警数',
'alert.monitor.overview.header.schedule': '监控计划',
'alert.monitor.overview.header.last_updated': '最近更新时间',
'alert.monitor.overview.header.monitor_id': '监控编号',
'alert.monitor.overview.header.version_number': '版本',
'alert.monitor.triggers.title': '触发器',
'alert.monitor.triggers.table.header.name': '名称',
'alert.monitor.triggers.table.header.number_of_actions': '通知渠道',
'alert.monitor.triggers.table.header.severity': '告警等级',
'alert.monitor.history.title': '告警历史',
'alert.destination': '通知渠道',
'alert.destination.self': '通知渠道',
@ -107,4 +126,43 @@ export default {
'alert.destination.create.settings.attributes.label': '通过自定义属性 URL 定义端点',
'alert.trigger': '触发器',
'alert.trigger.create.title': '创建触发器',
'alert.trigger.edit.title': '修改触发器',
'alert.trigger.edit.define.title': '定义触发器',
'alert.trigger.edit.define.field.name': '名称',
'alert.trigger.edit.define.field.severity': '告警等级',
'alert.trigger.edit.define.field.query_response': '查询响应',
'alert.trigger.edit.define.field.condition': '触发条件',
'alert.trigger.edit.define.field.condition.info.paragraph1': '您可以在 yaml 脚本中访问 "_ctx" 变量',
'alert.trigger.edit.define.field.condition.info.paragraph2': `以下是一个快速 JSON 示例,示例中列举了 "_ctx" 下的可访问变量 。`,
'alert.trigger.edit.define.field.condition_response': '触发响应',
'alert.trigger.edit.define.field.name.help': '名称必须唯一,只能包含字母,数字和特殊字符。',
'alert.trigger.edit.define.field.severity.help':'告警等级帮助您管理触发器',
'alert.trigger.edit.define.button.run': '运行',
'alert.trigger.edit.action.title': '触发通知配置',
'alert.trigger.edit.action.button.add_action': '添加通知',
'alert.trigger.edit.action.field.name': '通知名称',
'alert.trigger.edit.action.field.destination': '渠道选择',
'alert.trigger.edit.action.field.message': '消息配置',
'alert.trigger.edit.action.field.message_subject': '消息标题',
'alert.trigger.edit.action.field.message_preview': '消息预览',
'alert.trigger.edit.action.send_test_message': '发送测试消息',
'alert.trigger.edit.action.default_name': '通知',
'alert.email.manage.title': '管理邮件发送者',
'alert.email.manage.button.remove': '删除',
'alert.email.manage.button.add': '添加',
'alert.email.manage.field.name': '名称',
'alert.email.manage.field.name.help': '唯一的名称利于搜索,名称中只能包含大小写字母、数字和下划线',
'alert.email.manage.field.address': '邮件地址',
'alert.email.manage.field.password': '邮件密码',
'alert.email.manage.field.host': '主机',
'alert.email.manage.field.port': '端口',
'alert.email.manage.field.method': '加密方式',
'alert.emailgroup.manage.title': '管理邮件组',
'alert.emailgroup.manage.button.remove': '删除',
'alert.emailgroup.manage.button.add': '添加',
'alert.emailgroup.manage.field.name': '名称',
'alert.emailgroup.manage.field.name.help': '唯一的名称利于搜索,名称中只能包含大小写字母、数字和下划线',
'alert.emailgroup.manage.field.emails': '邮件列表',
'alert.emailgroup.manage.field.emails.help': '搜索先前用过的邮件或者输入新的邮件地址',
};

View File

@ -7,7 +7,6 @@ import router from "umi/router";
const MENU_COLLAPSED_KEY = "search-center:menu:collapsed";
console.log(localStorage.getItem(MENU_COLLAPSED_KEY))
export default {
namespace: 'global',
@ -22,7 +21,7 @@ export default {
search:{
cluster: {
}
}
},
},
effects: {
@ -130,7 +129,7 @@ export default {
});
},
*rewriteURL({payload}, {select, put}){
const {pathname, history, search} = payload;
const {pathname, history, search, isChangedState} = payload;
const global = yield select(state=>state.global);
if(pathname && global.selectedClusterID){
const newPart = `/elasticsearch/${global.selectedClusterID}/`;
@ -139,7 +138,11 @@ export default {
}else{
const ms = pathname.match(/\/elasticsearch\/(\w+)\/?/);
if(ms && ms.length>1 && ms[1] != global.selectedClusterID){
console.log(ms[1])
if(isChangedState){
const newPath = pathname.replace(/\/elasticsearch\/(\w+)\/?/, newPart);
history.replace(newPath+(search || ''))
return
}
yield put({
type: 'changeClusterById',
payload:{
@ -248,12 +251,21 @@ export default {
// Subscribe history(url) change, trigger `load` action if pathname is `/`
return history.listen(({ pathname, search }) => {
let clusterVisible = true;
if(pathname.startsWith("/system")){
clusterVisible = false;
}else if(pathname.startsWith("/cluster/overview")){
const clusterHiddenPath = ["/system", "/cluster/overview", "/alerting/overview", "/alerting/monitor/monitors/", "/alerting/destination"];
if(clusterHiddenPath.some(p=>pathname.startsWith(p))){
clusterVisible = false;
if(pathname.includes("elasticsearch")){
dispatch({
type: 'rewriteURL',
payload: {
pathname,
history,
search,
}
});
}
}else{
if(!pathname.startsWith("/exception")){
if(!pathname.startsWith("/exception") && pathname !="/alerting/monitor"){
dispatch({
type: 'rewriteURL',
payload: {

View File

@ -34,16 +34,16 @@ const message = () => ({
body: (
<EuiText style={{ fontSize: '14px' }}>
<p>
{`You have access to a "ctx" variable in your painless scripts and action mustache templates.`}
{`You have access to a "_ctx" variable in your yaml scripts and action quicktemplate templates.`}
</p>
<h3>Learn More</h3>
{/* <h3>Learn More</h3>
<ul>
<li>
<EuiLink target="_blank" href={URL.MUSTACHE}>
HTML Templates with Mustache.js
</EuiLink>
</li>
</ul>
</ul> */}
</EuiText>
),
});

View File

@ -15,6 +15,7 @@
import React from 'react';
import { EuiCodeBlock, EuiCodeEditor, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
import {formatMessage} from 'umi/locale';
const CONTEXT_VARIABLES = JSON.stringify(
{
@ -40,17 +41,16 @@ const triggerCondition = context => ({
header: (
<EuiTitle size="m" style={{ fontSize: '25px' }}>
<h2>
<strong>Trigger condition</strong>
<strong>{formatMessage({id:'alert.trigger.edit.define.field.condition'})}</strong>
</h2>
</EuiTitle>
),
body: (
<div>
<EuiText style={{ fontSize: '14px' }}>
<p>You have access to a "ctx" variable in your painless scripts</p>
<p>{formatMessage({id:'alert.trigger.edit.define.field.condition.info.paragraph1'})}</p>
<p>
Below shows a quick JSON example of what's available under the "ctx" variable along with
the actual results (where possible) for you to reference.
{formatMessage({id:'alert.trigger.edit.define.field.condition.info.paragraph2'})}
</p>
</EuiText>

View File

@ -0,0 +1,90 @@
import React, { Component, useMemo } from 'react';
import { Switch, Route, Router } from 'react-router-dom';
import CreateDestination from './pages/Destinations/containers/CreateDestination';
import DestinationsList from './pages/Destinations/containers/DestinationsList';
import {Fetch} from '../../components/kibana/core/public/http/fetch';
import {ScopedHistory} from '../../components/kibana/core/public/application/scoped_history';
import {notification} from 'antd';
const Destination = ({httpClient, notifications, history})=> {
return (
<div style={{ padding: '15px', background:'#fff' }}>
<Router history={history}>
<Switch>
<Route
path='/create-destination'
render={(props) => (
<CreateDestination
httpClient={httpClient}
notifications={notifications}
{...props}
/>
)}
/>
<Route
path="/destinations/:destinationId"
render={(props) => (
<CreateDestination
httpClient={httpClient}
notifications={notifications}
{...props}
edit
/>
)}
/>
<Route
// exact
// path="/destinations"
render={(props) => {
return (
<DestinationsList
{...props}
httpClient={httpClient}
notifications={notifications}
/>
)}}
/>
</Switch>
</Router>
</div>
);
}
const httpClient = new Fetch({
basePath:{
get: () => '',
prepend: (url) => url,
remove: (url) => url,
serverBasePath: '',
}
});
const notifications = {
toasts: {
addDanger: ({title, text, toastLifeTimeMs})=>{
notification.warning({
message: title,
description: text,
duration: toastLifeTimeMs/1000,
})
},
addSuccess: (message) => {
notification.success({
description: message,
})
}
}
}
export default (props)=>{
const isDarkMode = false;
const history = useMemo(()=>{
return new ScopedHistory(props.history, '/alerting/destination');
}, [props.history])
return (
<Destination httpClient={httpClient} notifications={notifications} history={history} />
)
}

View File

@ -45,7 +45,7 @@ const AlertingUI = (props)=>{
}, [props.selectedCluster]);
const isDarkMode = false;
const history = useMemo(()=>{
return new ScopedHistory(props.history, '/alerting');
return new ScopedHistory(props.history, '/alerting/monitor');
}, [props.history])
return (

View File

@ -21,6 +21,7 @@ import { isInvalid, hasError, validateActionName } from '../../../../utils/valid
import { ActionsMap } from './utils/constants';
import { validateDestination } from './utils/validate';
import { DEFAULT_ACTION_TYPE } from '../../utils/constants';
import {formatMessage} from 'umi/locale';
const Action = ({
action,
@ -44,12 +45,12 @@ const Action = ({
className="accordion-action"
buttonContent={
!_.get(selectedDestination, '0.type', undefined)
? 'Notification'
? formatMessage({id:'alert.trigger.edit.action.default_name'})
: `${actionLabel}: ${name}`
}
extraAction={
<div style={{ paddingRight: '10px' }}>
<EuiButton onClick={onDelete}>Delete</EuiButton>
<EuiButton onClick={onDelete}>{formatMessage({id:'form.button.delete'})}</EuiButton>
</div>
}
>
@ -58,9 +59,9 @@ const Action = ({
<FormikFieldText
name={`actions.${index}.name`}
formRow
fieldProps={{ validate: validateActionName(context.ctx.trigger) }}
fieldProps={{ validate: validateActionName(context._ctx.trigger) }}
rowProps={{
label: 'Action name',
label: formatMessage({id:'alert.trigger.edit.action.field.name'}),
helpText: 'Names can only contain letters, numbers, and special characters',
isInvalid,
error: hasError,
@ -72,7 +73,7 @@ const Action = ({
formRow
fieldProps={{ validate: validateDestination(destinations) }}
rowProps={{
label: 'Destination',
label: formatMessage({id:'alert.trigger.edit.action.field.destination'}),
isInvalid,
error: hasError,
}}

View File

@ -40,14 +40,15 @@ import {
required,
} from '../../../../../utils/validate';
import { URL, MAX_THROTTLE_VALUE, WRONG_THROTTLE_WARNING } from '../../../../../utils/constants';
import {formatMessage} from 'umi/locale';
const messageHelpText = (index, sendTestMessage) => (
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem>
<EuiText size="xs">
{/* <EuiText size="xs">
Embed variables in your message using Mustache templates.{' '}
<a href={URL.MUSTACHE}>Learn more about Mustache</a>
</EuiText>
</EuiText> */}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs">
@ -56,7 +57,7 @@ const messageHelpText = (index, sendTestMessage) => (
sendTestMessage(index);
}}
>
Send test message
{formatMessage({id:'alert.trigger.edit.action.send_test_message'})}
</EuiButtonEmpty>
</EuiText>
</EuiFlexItem>
@ -76,7 +77,7 @@ const Message = ({
preview = Mustache.render(action.message_template.source, context);
} catch (err) {
preview = err.message;
console.error('There was an error rendering mustache template', err);
console.error('There was an error rendering template', err);
}
return (
<div>
@ -86,7 +87,7 @@ const Message = ({
formRow
fieldProps={{ validate: required }}
rowProps={{
label: 'Message subject',
label: formatMessage({id:'alert.trigger.edit.action.field.message_subject'}),
isInvalid,
error: hasError,
}}
@ -100,7 +101,7 @@ const Message = ({
rowProps={{
label: (
<div>
<span>Message</span>
<span>{formatMessage({id:'alert.trigger.edit.action.field.message'})}</span>
<EuiButtonEmpty
size="s"
onClick={() => {
@ -117,15 +118,15 @@ const Message = ({
error: hasError,
}}
inputProps={{
placeholder: 'Can use mustache templates',
placeholder: 'Can use templates',
fullWidth: true,
isInvalid,
}}
/>
<EuiFormRow label="Message preview" style={{ maxWidth: '100%' }}>
<EuiFormRow label={formatMessage({id:'alert.trigger.edit.action.field.message_preview'})} style={{ maxWidth: '100%' }}>
<EuiTextArea
placeholder="Preview of mustache template"
placeholder="Preview of template"
fullWidth
value={preview}
readOnly
@ -133,7 +134,7 @@ const Message = ({
/>
</EuiFormRow>
<EuiSpacer size="s" />
{/* <EuiSpacer size="s" />
<EuiFormRow
label={
@ -201,7 +202,7 @@ const Message = ({
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexGroup>
</EuiFormRow>
</EuiFormRow> */}
</div>
);
};

View File

@ -22,7 +22,7 @@ const actionEmptyText = 'Add an action to perform when this trigger is triggered
const destinationEmptyText =
'There are no existing destinations. Add a destinations to create an action';
const createDestinationButton = (
<EuiButton fill href={`#/${PLUGIN_NAME}/create-destination`}>
<EuiButton fill href={`#/alerting/destination/create-destination`}>
Add destination
</EuiButton>
);

View File

@ -18,10 +18,11 @@ import _ from 'lodash';
import { EuiButton } from '@elastic/eui';
import { FORMIK_INITIAL_ACTION_VALUES } from '../../utils/constants';
import {formatMessage} from 'umi/locale';
const AddActionButton = ({ arrayHelpers, type = 'slack' }) => (
<EuiButton fill onClick={() => arrayHelpers.unshift(_.cloneDeep(FORMIK_INITIAL_ACTION_VALUES))}>
Add action
{formatMessage({id:'alert.trigger.edit.action.button.add_action'})}
</EuiButton>
);

View File

@ -30,6 +30,7 @@ import 'brace/mode/plain_text';
import 'brace/snippets/javascript';
import 'brace/ext/language_tools';
import { formikToTrigger } from '../../containers/CreateTrigger/utils/formikToTrigger';
import {formatMessage} from 'umi/locale';
export const getExecuteMessage = (response) => {
if (!response) return 'No response';
@ -59,7 +60,7 @@ const TriggerQuery = ({
<div style={{ padding: '0px 10px', marginTop: '0px' }}>
<EuiFlexGroup direction="column">
<EuiFlexItem>
<EuiFormRow label="Extraction query response" fullWidth>
<EuiFormRow label={formatMessage({id:'alert.trigger.edit.define.field.query_response'})} fullWidth>
<EuiCodeEditor
mode="json"
theme={isDarkMode ? 'sense-dark' : 'github'}
@ -75,7 +76,7 @@ const TriggerQuery = ({
<EuiFormRow
label={
<div>
<span>Trigger condition</span>
<span>{formatMessage({id:'alert.trigger.edit.define.field.condition'})}</span>
<EuiButtonEmpty
size="s"
onClick={() => {
@ -108,7 +109,7 @@ const TriggerQuery = ({
</Field>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFormRow label="Trigger condition response:" fullWidth>
<EuiFormRow label={formatMessage({id:'alert.trigger.edit.define.field.condition_response'})} fullWidth>
<EuiCodeEditor
mode="plain_text"
theme={isDarkMode ? 'sense-dark' : 'github'}
@ -125,7 +126,7 @@ const TriggerQuery = ({
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem grow={false}>
<EuiButton onClick={() => onRun([trigger])}>Run</EuiButton>
<EuiButton onClick={() => onRun([trigger])}>{formatMessage({id:'alert.trigger.edit.define.button.run'})}</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</div>

View File

@ -24,9 +24,11 @@ import { DESTINATION_OPTIONS } from '../../../Destinations/utils/constants';
import { getAllowList } from '../../../Destinations/utils/helpers';
import { MAX_QUERY_RESULT_SIZE } from '../../../../utils/constants';
import { backendErrorNotification } from '../../../../utils/helpers';
import {pathPrefix} from '@/services/common'
import {formatMessage} from 'umi/locale';
const createActionContext = (context, action) => ({
ctx: {
_ctx: {
...context,
action,
},
@ -62,8 +64,9 @@ class ConfigureActions extends React.Component {
return destination.type;
};
try {
const response = await httpClient.get('/alerting/destinations', {
const response = await httpClient.get(pathPrefix +'/alerting/destinations', {
query: { search: searchText, size: MAX_QUERY_RESULT_SIZE },
prependBasePath: false,
});
if (response.ok) {
const destinations = response.destinations
@ -143,7 +146,7 @@ class ConfigureActions extends React.Component {
//TODO:: Handle loading Destinations inside the Action which will be more intuitive for customers.
return (
<ContentPanel
title="Configure actions"
title={formatMessage({id:'alert.trigger.edit.action.title'})}
titleSize="s"
panelStyles={{ paddingBottom: '0px' }}
bodyStyles={{ padding: '10px' }}

View File

@ -42,6 +42,7 @@ import { FORMIK_INITIAL_VALUES } from './utils/constants';
import { SEARCH_TYPE } from '../../../../utils/constants';
import { SubmitErrorHandler } from '../../../../utils/SubmitErrorHandler';
import { backendErrorNotification } from '../../../../utils/helpers';
import {formatMessage} from 'umi/locale';
export default class CreateTrigger extends Component {
constructor(props) {
@ -201,7 +202,7 @@ export default class CreateTrigger extends Component {
{({ values, handleSubmit, isSubmitting, errors, isValid }) => (
<Fragment>
<EuiTitle size="l">
<h1>{edit ? 'Edit' : 'Create'} trigger</h1>
<h1>{edit ? formatMessage({id:'alert.trigger.edit.title'}):formatMessage({id:'alert.trigger.create.title'})}</h1>
</EuiTitle>
<EuiSpacer />
<DefineTrigger
@ -230,11 +231,11 @@ export default class CreateTrigger extends Component {
<EuiSpacer />
<EuiFlexGroup alignItems="center" justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<EuiButtonEmpty onClick={onCloseTrigger}>Cancel</EuiButtonEmpty>
<EuiButtonEmpty onClick={onCloseTrigger}>{formatMessage({id:'form.button.cancel'})}</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton onClick={handleSubmit} isLoading={isSubmitting} fill>
{edit ? 'Update' : 'Create'}
{edit ? formatMessage({id:'form.button.update'}) : formatMessage({id:'form.button.create'})}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>

View File

@ -26,10 +26,11 @@ import { validateTriggerName } from './utils/validation';
import { SEARCH_TYPE } from '../../../../utils/constants';
import { AnomalyDetectorTrigger } from './AnomalyDetectorTrigger';
import { TRIGGER_TYPE } from '../CreateTrigger/utils/constants';
import {formatMessage} from 'umi/locale';
const defaultRowProps = {
label: 'Trigger name',
helpText: `Trigger names must be unique. Names can only contain letters, numbers, and special characters.`,
label: formatMessage({id:"alert.trigger.edit.define.field.name"}),
helpText: formatMessage({id:"alert.trigger.edit.define.field.name.help"}),
style: { paddingLeft: '10px' },
isInvalid,
error: hasError,
@ -38,8 +39,8 @@ const defaultInputProps = { isInvalid };
const selectFieldProps = { validate: () => {} };
const selectRowProps = {
label: 'Severity level',
helpText: `Severity levels help you organize your triggers and actions. A trigger with a high severity level might page a specific individual, whereas a trigger with a low severity level might email a list.`,
label: formatMessage({id:"alert.trigger.edit.define.field.severity"}),
helpText: formatMessage({id:'alert.trigger.edit.define.field.severity.help'}),
style: { paddingLeft: '10px', marginTop: '0px' },
isInvalid,
error: hasError,
@ -119,7 +120,7 @@ const DefineTrigger = ({
}
return (
<ContentPanel title="Define trigger" titleSize="s" bodyStyles={{ padding: 'initial' }}>
<ContentPanel title={formatMessage({id:"alert.trigger.edit.define.title"})} titleSize="s" bodyStyles={{ padding: 'initial' }}>
<FormikFieldText
name="name"
fieldProps={{ validate: validateTriggerName(triggers, triggerValues) }}

View File

@ -14,22 +14,22 @@
*/
const DEFAULT_MESSAGE_SOURCE = `
Monitor {{ctx.monitor.name}} just entered alert status. Please investigate the issue.
- Trigger: {{ctx.trigger.name}}
- Severity: {{ctx.trigger.severity}}
- Period start: {{ctx.periodStart}}
- Period end: {{ctx.periodEnd}}
Monitor {{_ctx.monitor.name}} just entered alert status. Please investigate the issue.
- Trigger: {{_ctx.trigger.name}}
- Severity: {{_ctx.trigger.severity}}
- Period start: {{_ctx.periodStart}}
- Period end: {{_ctx.periodEnd}}
`.trim();
export const FORMIK_INITIAL_ACTION_VALUES = {
name: '',
destination_id: '',
subject_template: {
lang: 'mustache',
lang: 'quicktemplate',
source: '',
},
message_template: {
lang: 'mustache',
lang: 'quicktemplate',
source: DEFAULT_MESSAGE_SOURCE,
},
throttle_enabled: false,
@ -39,4 +39,4 @@ export const FORMIK_INITIAL_ACTION_VALUES = {
},
};
export const DEFAULT_ACTION_TYPE = 'slack';
export const DEFAULT_ACTION_TYPE = 'custom_webhook';

View File

@ -1,12 +1,14 @@
import {List} from 'antd';
import {AlertItem, AlertRecord} from './AlertItem';
import './alertlist.scss';
import {Legend, LegendItem} from './Legend';
interface AlertListProps {
dataSource: AlertRecord[];
pagination?: any;
title: string;
onItemClick: (item: AlertRecord)=>void
onItemClick: (item: AlertRecord)=>void;
legendItems?: LegendItem[];
}
export const AlertList = ({
@ -14,13 +16,22 @@ export const AlertList = ({
pagination,
title,
onItemClick,
legendItems
}: AlertListProps)=>{
return (
<div className="alert-list">
<div className="title">
{title}
<span className="total">({pagination?.total})</span>
</div>
<div className="header">
<div className="title">
{title}
<span className="total">({pagination?.total})</span>
</div>
{
legendItems ? ( <div className="legend">
<Legend items={legendItems}/>
</div>):null
}
</div>
<List
itemLayout="vertical"
size="large"

View File

@ -0,0 +1,25 @@
import './legend.scss';
export interface LegendItem {
color: string;
title: string;
}
interface LegendProps{
items: LegendItem[];
}
export const Legend = ({
items
}:LegendProps)=>{
return (
<div className="legend-list">
{(items || []).map(item=>{
return <div className="legend-item">
<span className="shape" style={{backgroundColor:item.color}}></span>
<span className="text">{item.title}</span>
</div>
})}
</div>
)
}

View File

@ -1,14 +1,21 @@
.alert-list{
background: #f0f2f5;
padding: 10px 5px;
.title{
color: #333;
font-weight:600;
padding-bottom: 6px;
.total{
color: #999;
margin-left: 15px;
font-size: 12px;
.header{
display: flex;
.title{
color: #333;
font-weight:600;
padding-bottom: 6px;
.total{
color: #999;
margin-left: 15px;
font-size: 12px;
}
}
.legend{
margin-left: auto;
}
}
}

View File

@ -0,0 +1,18 @@
.legend-list{
display: flex;
align-items: center;
justify-content: center;
>.legend-item{
margin-left: 10px;
.shape{
width: 10px;
height: 10px;
display: inline-block;
}
.text{
font-size: 12px;
font-weight: 500;
margin-left: 3px;
}
}
}

View File

@ -2,6 +2,7 @@ import {AlertList} from '../components/AlertList/AlertList';
import _ from 'lodash';
import {useState, useEffect} from 'react';
import './alertoverview.scss';
import { formatMessage } from 'umi/locale';
export const AlertOverview = (props: any)=>{
const {httpClient, history} = props;
@ -10,8 +11,13 @@ export const AlertOverview = (props: any)=>{
totalAlerts: 0,
});
const [historyData, setHistoryData] = useState({
alerts: [],
totalAlerts: 0,
});
const getAlerts = _.debounce(
(from, size, search, sortField, sortDirection, severityLevel, alertState, monitorIds) => {
(from, size, search, sortField, sortDirection, severityLevel, alertState, monitorIds, type) => {
let params = {
from,
size,
@ -20,15 +26,21 @@ export const AlertOverview = (props: any)=>{
sortDirection,
severityLevel,
alertState,
type,
};
if(monitorIds){
params["monitorIds"]= monitorIds;
}
// const queryParamsString = queryString.stringify(params);
// history.replace({ ...this.props.location, search: queryParamsString });
httpClient.get('/alerting/alerts', { query: params }).then((resp:any) => {
if (resp.ok) {
const { alerts, totalAlerts } = resp;
if(type == 'ALERT_HISTORY'){
setHistoryData({
alerts,
totalAlerts,
});
return;
}
setData({
alerts,
totalAlerts,
@ -44,13 +56,17 @@ export const AlertOverview = (props: any)=>{
const pageSize = 10;
useEffect(()=>{
getAlerts(0, pageSize, "", "start_time", "desc", "ALL", "ALL","")
getAlerts(0, pageSize, "", "start_time", "desc", "ALL", "ALL","", "ALERT");
getAlerts(0, pageSize, "", "start_time", "desc", "ALL", "ALL","", "ALERT_HISTORY")
},[])
const onPageChange = (pageIndex: number)=>{
const from = (pageIndex - 1) * pageSize;
getAlerts(from, pageSize, "", "start_time", "desc", "ALL", "ALL","")
const onPageChangeGen = (type:string) => {
return (pageIndex: number)=>{
const from = (pageIndex - 1) * pageSize;
getAlerts(from, pageSize, "", "start_time", "desc", "ALL", "ALL","", type)
}
}
const onItemClick = (item: any)=>{
history.push(`/monitors/${item.monitor_id}`)
@ -60,17 +76,25 @@ export const AlertOverview = (props: any)=>{
<div className="alert-overview">
<div className="left">
<AlertList dataSource={data.alerts}
title="Open Alerts"
title={formatMessage({id:'alert.overview.alertlist.title'})}
onItemClick={onItemClick}
pagination={{
pageSize,
total: data.totalAlerts,
onChange: onPageChange,
onChange: onPageChangeGen('ALERT'),
}}/>
<AlertList dataSource={historyData.alerts}
title={formatMessage({id:'alert.overview.alertlist-history.title'})}
onItemClick={onItemClick}
pagination={{
pageSize,
total: historyData.totalAlerts,
onChange: onPageChangeGen('ALERT_HISTORY'),
}}/>
</div>
<div className="right">
{/* <div className="right">
<div></div>
</div>
</div> */}
</div>
)
}

View File

@ -87,7 +87,7 @@ export default class DestinationsActions extends Component {
</EuiFlexItem>
) : null}
<EuiFlexItem grow={false}>
<EuiButton fill href={`#/${PLUGIN_NAME}${APP_PATH.CREATE_DESTINATION}`}>
<EuiButton fill href={`#/alerting/destination${APP_PATH.CREATE_DESTINATION}`}>
{formatMessage({ id: 'alert.button.add-destination' })}
</EuiButton>
</EuiFlexItem>

View File

@ -18,10 +18,11 @@ import _ from 'lodash';
import { EuiButton } from '@elastic/eui';
import { FORMIK_INITIAL_EMAIL_GROUP_VALUES } from '../Email/utils/constants';
import {formatMessage} from 'umi/locale';
const AddEmailGroupButton = ({ arrayHelpers }) => (
<EuiButton onClick={() => arrayHelpers.unshift(_.cloneDeep(FORMIK_INITIAL_EMAIL_GROUP_VALUES))}>
Add email group
{formatMessage({id:'alert.emailgroup.manage.button.add'})}
</EuiButton>
);

View File

@ -18,10 +18,11 @@ import _ from 'lodash';
import { EuiButton } from '@elastic/eui';
import { FORMIK_INITIAL_SENDER_VALUES } from '../Email/utils/constants';
import {formatMessage} from 'umi/locale';
const AddSenderButton = ({ arrayHelpers }) => (
<EuiButton onClick={() => arrayHelpers.unshift(_.cloneDeep(FORMIK_INITIAL_SENDER_VALUES))}>
Add sender
{formatMessage({id:'alert.email.manage.button.add'})}
</EuiButton>
);

View File

@ -19,6 +19,7 @@ import { FormikComboBox, FormikFieldText } from '../../../../../components/FormC
import { isInvalid, hasError } from '../../../../../utils/validate';
import { validateEmailGroupEmails, validateEmailGroupName } from './utils/validate';
import { STATE } from './utils/constants';
import {formatMessage} from 'umi/locale';
const onEmailGroupChange = (index, emailGroup, arrayHelpers) => {
// Checking for id here since new email groups should not be marked as updated
@ -49,7 +50,7 @@ const EmailGroup = ({ emailGroup, emailOptions, arrayHelpers, context, index, on
paddingSize="l"
extraAction={
<EuiButton color="danger" size="s" onClick={onDelete}>
Remove email group
{formatMessage({id:'alert.emailgroup.manage.button.remove'})}
</EuiButton>
}
>
@ -58,10 +59,8 @@ const EmailGroup = ({ emailGroup, emailOptions, arrayHelpers, context, index, on
formRow
fieldProps={{ validate: validateEmailGroupName(context.ctx.emailGroups) }}
rowProps={{
label: 'Email group name',
helpText:
'A unique and descriptive name that is easy to search. ' +
'Valid characters are upper and lowercase a-z, 0-9, _ (underscore) and - (hyphen).',
label: formatMessage({id:'alert.emailgroup.manage.field.name'}),
helpText:formatMessage({id:'alert.emailgroup.manage.field.name.help'}),
style: { padding: '10px 0px' },
isInvalid,
error: hasError,
@ -79,8 +78,8 @@ const EmailGroup = ({ emailGroup, emailOptions, arrayHelpers, context, index, on
formRow
fieldProps={{ validate: validateEmailGroupEmails }}
rowProps={{
label: 'Emails',
helpText: 'Search for previously used email addresses or type in new ones.',
label: formatMessage({id:'alert.emailgroup.manage.field.emails'}),
helpText: formatMessage({id:'alert.emailgroup.manage.field.emails.help'}),
isInvalid,
error: hasError,
}}

View File

@ -24,6 +24,7 @@ import {
import { isInvalid, hasError } from '../../../../../utils/validate';
import { validateEmail, validateHost, validatePort, validateSenderName } from './utils/validate';
import { METHOD_TYPE, STATE } from './utils/constants';
import {formatMessage} from 'umi/locale';
const methodOptions = [
{ value: METHOD_TYPE.NONE, text: 'None' },
@ -51,7 +52,7 @@ const Sender = ({ sender, arrayHelpers, context, index, onDelete }) => {
paddingSize="l"
extraAction={
<EuiButton color="danger" size="s" onClick={onDelete}>
Remove sender
{formatMessage({id:'alert.email.manage.button.remove'})}
</EuiButton>
}
>
@ -60,10 +61,8 @@ const Sender = ({ sender, arrayHelpers, context, index, onDelete }) => {
formRow
fieldProps={{ validate: validateSenderName(context.ctx.senders) }}
rowProps={{
label: 'Sender name',
helpText:
'A unique and descriptive name that is easy to search. ' +
'Valid characters are upper and lowercase a-z, 0-9, and _ (underscore).',
label: formatMessage({id:'alert.email.manage.field.name'}),
helpText:formatMessage({id:'alert.email.manage.field.name.help'}),
style: { padding: '10px 0px' },
isInvalid,
error: hasError,
@ -88,7 +87,7 @@ const Sender = ({ sender, arrayHelpers, context, index, onDelete }) => {
formRow
fieldProps={{ validate: validateEmail }}
rowProps={{
label: 'Email address',
label: formatMessage({id:'alert.email.manage.field.address'}),
isInvalid,
error: hasError,
}}
@ -108,7 +107,7 @@ const Sender = ({ sender, arrayHelpers, context, index, onDelete }) => {
formRow
// fieldProps={{ validate: validateEmail }}
rowProps={{
label: 'Email password',
label: formatMessage({id:'alert.email.manage.field.password'}),
isInvalid,
error: hasError,
}}
@ -130,7 +129,7 @@ const Sender = ({ sender, arrayHelpers, context, index, onDelete }) => {
formRow
fieldProps={{ validate: validateHost }}
rowProps={{
label: 'Host',
label: formatMessage({id:'alert.email.manage.field.host'}),
isInvalid,
error: hasError,
}}
@ -150,7 +149,7 @@ const Sender = ({ sender, arrayHelpers, context, index, onDelete }) => {
formRow
fieldProps={{ validate: validatePort }}
rowProps={{
label: 'Port',
label: formatMessage({id:'alert.email.manage.field.port'}),
isInvalid,
error: hasError,
}}
@ -169,11 +168,11 @@ const Sender = ({ sender, arrayHelpers, context, index, onDelete }) => {
name={`senders.${index}.method`}
formRow
rowProps={{
label: 'Encryption method',
helpText: `SSL or TLS is recommended for security.
SSL and TLS requires validation by adding the following fields to the Elasticsearch keystore:
opendistro.alerting.destination.email.${!name ? '[sender name]' : name}.username
opendistro.alerting.destination.email.${!name ? '[sender name]' : name}.password`,
label: formatMessage({id:'alert.email.manage.field.method'}),
// helpText: `SSL or TLS is recommended for security.
// SSL and TLS requires validation by adding the following fields to the Elasticsearch keystore:
// opendistro.alerting.destination.email.${!name ? '[sender name]' : name}.username
// opendistro.alerting.destination.email.${!name ? '[sender name]' : name}.password`,
style: { padding: '10px 0px' },
}}
inputProps={{

View File

@ -39,6 +39,7 @@ import { SubmitErrorHandler } from '../../../../utils/SubmitErrorHandler';
import { getAllowList } from '../../utils/helpers';
import { backendErrorNotification } from '../../../../utils/helpers';
import { formatMessage } from 'umi/locale';
import {pathPrefix} from '@/services/common';
const destinationType = {
[DESTINATION_TYPE.SLACK]: (props) => <Webhook {...props} />,
@ -59,7 +60,7 @@ class CreateDestination extends React.Component {
async componentDidMount() {
const { httpClient, location, edit, history } = this.props;
const allowList = await getAllowList(httpClient);
const allowList = [DESTINATION_TYPE.EMAIL,DESTINATION_TYPE.CUSTOM_HOOK];//await getAllowList(httpClient);
this.setState({ allowList });
let ifSeqNo, ifPrimaryTerm;
@ -90,7 +91,7 @@ class CreateDestination extends React.Component {
getDestination = async (destinationId) => {
const { httpClient, history, notifications } = this.props;
try {
const resp = await httpClient.get(`/alerting/destinations/${destinationId}`);
const resp = await httpClient.get(pathPrefix + `/alerting/destinations/${destinationId}`);
if (resp.ok) {
const ifSeqNo = _.get(resp, 'ifSeqNo');
const ifPrimaryTerm = _.get(resp, 'ifPrimaryTerm');
@ -101,7 +102,7 @@ class CreateDestination extends React.Component {
} else {
// Handle error, show message in case of 404
backendErrorNotification(notifications, 'get', 'destination', resp.resp);
history.push(`/destinations`);
history.push(`alerting/destination`);
}
} catch (e) {
console.log('Unable to get the data');
@ -119,7 +120,7 @@ class CreateDestination extends React.Component {
} = this.props;
const { ifSeqNo, ifPrimaryTerm } = this.state;
try {
const resp = await httpClient.put(`/alerting/destinations/${destinationId}`, {
const resp = await httpClient.put(pathPrefix+`/alerting/destinations/${destinationId}`, {
query: { ifSeqNo, ifPrimaryTerm },
body: JSON.stringify(requestData),
});
@ -140,7 +141,7 @@ class CreateDestination extends React.Component {
handleCreate = async (requestData, { setSubmitting }) => {
const { httpClient, history, notifications } = this.props;
try {
const resp = await httpClient.post('/alerting/destinations', {
const resp = await httpClient.post(pathPrefix+'/alerting/destinations', {
body: JSON.stringify(requestData),
});
setSubmitting(false);

View File

@ -44,7 +44,7 @@ export default class EmailRecipients extends React.Component {
async componentDidMount() {
const { httpClient } = this.props;
const allowList = await getAllowList(httpClient);
const allowList = [DESTINATION_TYPE.EMAIL, DESTINATION_TYPE.CUSTOM_HOOK];//await getAllowList(httpClient);
this.setState({ allowList });
this.loadData();

View File

@ -14,10 +14,11 @@
*/
import { MAX_QUERY_RESULT_SIZE } from '../../../../../../utils/constants';
import {pathPrefix} from '@/services/common';
export default async function getEmailGroups(httpClient, searchText = '') {
try {
const response = await httpClient.get('/alerting/destinations/email_groups', {
const response = await httpClient.get(pathPrefix+'/alerting/destinations/email_groups', {
query: { search: searchText, size: MAX_QUERY_RESULT_SIZE },
});
if (response.ok) {

View File

@ -43,7 +43,7 @@ export default class EmailSender extends React.Component {
async componentDidMount() {
const { httpClient } = this.props;
const allowList = await getAllowList(httpClient);
const allowList = [DESTINATION_TYPE.CUSTOM_HOOK, DESTINATION_TYPE.EMAIL];//await getAllowList(httpClient);
this.setState({ allowList });
this.loadSenders();

View File

@ -14,10 +14,11 @@
*/
import { MAX_QUERY_RESULT_SIZE } from '../../../../../../utils/constants';
import {pathPrefix} from '@/services/common';
export default async function getSenders(httpClient, searchText = '') {
try {
const response = await httpClient.get('/alerting/destinations/email_accounts', {
const response = await httpClient.get(pathPrefix+'/alerting/destinations/email_accounts', {
query: { search: searchText, size: MAX_QUERY_RESULT_SIZE },
});
if (response.ok) {

View File

@ -39,6 +39,8 @@ import { emailGroupToFormik } from './utils/helpers';
import getEmailGroups from '../EmailRecipients/utils/helpers';
import { STATE } from '../../../components/createDestinations/Email/utils/constants';
import { ignoreEscape } from '../../../../../utils/helpers';
import {pathPrefix} from '@/services/common';
import {formatMessage} from 'umi/locale';
const createEmailGroupContext = (emailGroups) => ({
ctx: {
@ -119,7 +121,7 @@ export default class ManageEmailGroups extends React.Component {
emails: emailGroup.emails.map((email) => ({ email: email.label })),
};
try {
const response = await httpClient.post(`/alerting/destinations/email_groups`, {
const response = await httpClient.post(pathPrefix+`/alerting/destinations/email_groups`, {
body: JSON.stringify(body),
});
if (!response.ok) {
@ -147,7 +149,7 @@ export default class ManageEmailGroups extends React.Component {
emails: updatedEmailGroup.emails.map((email) => ({ email: email.label })),
};
try {
const response = await httpClient.put(`/alerting/email_groups/${id}`, {
const response = await httpClient.put(pathPrefix + `/alerting/email_groups/${id}`, {
query: { ifSeqNo, ifPrimaryTerm },
body: JSON.stringify(body),
});
@ -172,7 +174,7 @@ export default class ManageEmailGroups extends React.Component {
const { httpClient, notifications } = this.props;
const { id } = emailGroup;
try {
const response = await httpClient.delete(`/alerting/email_groups/${id}`);
const response = await httpClient.delete(pathPrefix + `/alerting/email_groups/${id}`);
if (!response.ok) {
this.setState({ failedEmailGroups: true });
notifications.toasts.addDanger({
@ -276,7 +278,7 @@ export default class ManageEmailGroups extends React.Component {
onClose={ignoreEscape(onClickCancel)}
>
<EuiModalHeader>
<EuiModalHeaderTitle>Manage email groups</EuiModalHeaderTitle>
<EuiModalHeaderTitle> {formatMessage({id:'alert.emailgroup.manage.title'})}</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiHorizontalRule margin="s" />
@ -301,11 +303,11 @@ export default class ManageEmailGroups extends React.Component {
<EuiModalFooter>
<EuiFlexGroup alignItems="center" justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<EuiButtonEmpty onClick={onClickCancel}>Cancel</EuiButtonEmpty>
<EuiButtonEmpty onClick={onClickCancel}>{formatMessage({id:'form.button.cancel'})}</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton onClick={handleSubmit} isLoading={isSubmitting} fill>
Save
{formatMessage({id:'form.button.save'})}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>

View File

@ -39,6 +39,8 @@ import { senderToFormik } from './utils/helpers';
import getSenders from '../EmailSender/utils/helpers';
import { STATE } from '../../../components/createDestinations/Email/utils/constants';
import { ignoreEscape } from '../../../../../utils/helpers';
import {pathPrefix} from '@/services/common';
import {formatMessage} from 'umi/locale';
const createSenderContext = (senders) => ({
ctx: {
@ -108,7 +110,7 @@ export default class ManageSenders extends React.Component {
password: sender.password,
};
try {
const response = await httpClient.post(`/alerting/destinations/email_accounts`, {
const response = await httpClient.post(pathPrefix+`/alerting/destinations/email_accounts`, {
body: JSON.stringify(body),
});
if (!response.ok) {
@ -140,7 +142,7 @@ export default class ManageSenders extends React.Component {
password: updatedSender.password,
};
try {
const response = await httpClient.put(`/alerting/email_accounts/${id}`, {
const response = await httpClient.put(pathPrefix+`/alerting/email_accounts/${id}`, {
query: { ifSeqNo, ifPrimaryTerm },
body: JSON.stringify(body),
});
@ -165,7 +167,7 @@ export default class ManageSenders extends React.Component {
const { httpClient, notifications } = this.props;
const { id } = sender;
try {
const response = await httpClient.delete(`/alerting/email_accounts/${id}`);
const response = await httpClient.delete(pathPrefix+`/alerting/email_accounts/${id}`);
if (!response.ok) {
this.setState({ failedSenders: true });
notifications.toasts.addDanger({
@ -266,7 +268,7 @@ export default class ManageSenders extends React.Component {
onClose={ignoreEscape(onClickCancel)}
>
<EuiModalHeader>
<EuiModalHeaderTitle>Manage email senders</EuiModalHeaderTitle>
<EuiModalHeaderTitle>{formatMessage({id:'alert.email.manage.title'})}</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiHorizontalRule margin="s" />
@ -291,11 +293,11 @@ export default class ManageSenders extends React.Component {
<EuiModalFooter>
<EuiFlexGroup alignItems="center" justifyContent="flexEnd">
<EuiFlexItem grow={false}>
<EuiButtonEmpty onClick={onClickCancel}>Cancel</EuiButtonEmpty>
<EuiButtonEmpty onClick={onClickCancel}>{formatMessage({id:'form.button.cancel'})}</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton onClick={handleSubmit} isLoading={isSubmitting} fill>
Save
{formatMessage({id:'form.button.save'})}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>

View File

@ -27,7 +27,7 @@ const DEFAULT_CONTENT_VALUE = 'application/json';
export const formikInitialValues = {
urlType: 'url',
name: '',
type: 'slack',
type: 'custom_webhook',
[DESTINATION_TYPE.SLACK]: {
url: '',
},

View File

@ -17,6 +17,7 @@ import _ from 'lodash';
import { URL_TYPE, CONTENT_TYPE_KEY } from './constants';
import { DESTINATION_TYPE } from '../../../utils/constants';
import { RECIPIENT_TYPE } from '../EmailRecipients/utils/constants';
import {pathPrefix} from '@/services/common';
const getAttributeArrayFromValues = (attributes) =>
Object.keys(attributes).map((currentKey) => ({
@ -49,7 +50,7 @@ const customWebhookToFormik = ({
const getSender = async (httpClient, id) => {
try {
const response = await httpClient.get(`/alerting/email_accounts/${id}`);
const response = await httpClient.get(pathPrefix+`/alerting/email_accounts/${id}`);
if (response.ok) {
return response.resp;
}
@ -62,7 +63,7 @@ const getSender = async (httpClient, id) => {
const getEmailGroup = async (httpClient, id) => {
try {
const response = await httpClient.get(`/alerting/email_groups/${id}`);
const response = await httpClient.get(pathPrefix+`/alerting/email_groups/${id}`);
if (response.ok) {
return response.resp;
}

View File

@ -16,6 +16,7 @@
import _ from 'lodash';
import { INDEX } from '../../../../../utils/constants';
import { getAllowList } from '../../../utils/helpers';
import {pathPrefix} from '@/services/common';
export const validateDestinationName = (httpClient, destinationToEdit) => async (value) => {
try {
@ -24,8 +25,9 @@ export const validateDestinationName = (httpClient, destinationToEdit) => async
index: INDEX.SCHEDULED_JOBS,
query: { query: { term: { 'destination.name.keyword': value } } },
};
const response = await httpClient.post('/alerting/monitors/_search', {
const response = await httpClient.post(pathPrefix+'/alerting/_search', {
body: JSON.stringify(options),
prependBasePath: false,
});
if (_.get(response, 'resp.hits.total.value', 0)) {
if (!destinationToEdit) return 'Destination name is already used';

View File

@ -36,6 +36,7 @@ import { getAllowList } from '../../utils/helpers';
import { DESTINATION_TYPE } from '../../utils/constants';
import { backendErrorNotification } from '../../../../utils/helpers';
import { formatMessage } from 'umi/locale';
import {pathPrefix} from '@/services/common';
class DestinationsList extends React.Component {
constructor(props) {
@ -101,7 +102,7 @@ class DestinationsList extends React.Component {
async componentDidMount() {
const { httpClient } = this.props;
const allowList = await getAllowList(httpClient);
const allowList = [DESTINATION_TYPE.EMAIL, DESTINATION_TYPE.CUSTOM_HOOK];//await getAllowList(httpClient);
this.setState({ allowList });
const { page, queryParams } = this.state;
@ -120,8 +121,9 @@ class DestinationsList extends React.Component {
query: isDeleteAllowedQuery(type, id),
index: INDEX.SCHEDULED_JOBS,
};
const resp = await httpClient.post('/alerting/monitors/_search', {
const resp = await httpClient.post(pathPrefix+'/alerting/_search', {
body: JSON.stringify(requestBody),
prependBasePath: false,
});
const total = _.get(resp, 'resp.hits.total.value');
return total === 0;
@ -152,7 +154,7 @@ class DestinationsList extends React.Component {
const { id: destinationId } = this.state.destinationToDelete;
const { httpClient, notifications } = this.props;
try {
const resp = await httpClient.delete(`/alerting/destinations/${destinationId}`);
const resp = await httpClient.delete(pathPrefix+`/alerting/destinations/${destinationId}`);
if (resp.ok) {
await this.getDestinations();
} else {
@ -215,7 +217,7 @@ class DestinationsList extends React.Component {
// search: queryParms,
// });
try {
const resp = await httpClient.get('/alerting/destinations', {
const resp = await httpClient.get(pathPrefix+ '/alerting/destinations', {
query: { from, ...params },
});
if (resp.ok) {

View File

@ -16,10 +16,13 @@
import _ from 'lodash';
import { ALLOW_LIST_SETTING_PATH } from './constants';
import { backendErrorNotification } from '../../../utils/helpers';
import {pathPrefix} from '@/services/common';
export async function getAllowList(httpClient) {
try {
const response = await httpClient.get('/alerting/_settings');
const response = await httpClient.get(pathPrefix+'/alerting/_settings', {
prependBasePath: false,
});
if (response.ok) {
// Attempt to resolve the value of allow_list in the order of 'persistent, 'transient' and 'defaults' settings
const { defaults, transient, persistent } = response.resp;

View File

@ -49,11 +49,11 @@ export default class Home extends Component {
name: formatMessage({ id: 'alert.monitor' }),
route: 'monitors',
},
{
id: 'destinations',
name: formatMessage({ id: 'alert.destination' }),
route: 'destinations',
},
// {
// id: 'destinations',
// name: formatMessage({ id: 'alert.destination' }),
// route: 'destinations',
// },
];
}
@ -100,10 +100,11 @@ export default class Home extends Component {
<Route
// exact
path="/dashboard"
render={(props) => (
render={(props) => {
return (
// <Dashboard {...props} httpClient={httpClient} notifications={notifications} />
<AlertOverview {...props} httpClient={httpClient} notifications={notifications} />
)}
)}}
/>
<Route
exact
@ -112,7 +113,7 @@ export default class Home extends Component {
<Monitors {...props} httpClient={httpClient} notifications={notifications} />
)}
/>
<Route
{/* <Route
exact
path="/destinations"
render={(props) => (
@ -122,7 +123,7 @@ export default class Home extends Component {
notifications={notifications}
/>
)}
/>
/> */}
<Route
exact
path="/"

View File

@ -56,12 +56,6 @@ class Main extends Component {
}}
/>
<Switch>
<Route
path="/overview"
render={(props) => (
<Overview {...props} httpClient={core.http} notifications={core.notifications} />
)}
/>
<Route
path={APP_PATH.CREATE_MONITOR}
render={(props) => (
@ -74,7 +68,7 @@ class Main extends Component {
/>
)}
/>
<Route
{/* <Route
path={APP_PATH.CREATE_DESTINATION}
render={(props) => (
<CreateDestination
@ -96,7 +90,7 @@ class Main extends Component {
edit
/>
)}
/>
/> */}
<Route
path="/monitors/:monitorId"
render={(props) => (

View File

@ -19,11 +19,12 @@ import { EuiFlexGrid } from '@elastic/eui';
import ContentPanel from '../../../../components/ContentPanel/index';
import OverviewStat from '../OverviewStat/index';
import getOverviewStats from './utils/getOverviewStats';
import {formatMessage} from 'umi/locale';
const MonitorOverview = ({ monitor, monitorId, monitorVersion, activeCount }) => {
const items = getOverviewStats(monitor, monitorId, monitorVersion, activeCount);
return (
<ContentPanel title="Overview" titleSize="s">
<ContentPanel title={formatMessage({id:'alert.monitor.overview.title'})} titleSize="s">
<EuiFlexGrid columns={4}>
{items.map(props => (
<OverviewStat key={props.header} {...props} />

View File

@ -18,6 +18,7 @@ import moment from 'moment-timezone';
import getScheduleFromMonitor from './getScheduleFromMonitor';
import { DEFAULT_EMPTY_DATA, SEARCH_TYPE } from '../../../../../utils/constants';
import {formatMessage} from 'umi/locale';
// TODO: used in multiple places, move into helper
function getTime(time) {
@ -31,42 +32,42 @@ export default function getOverviewStats(monitor, monitorId, monitorVersion, act
const searchType = _.get(monitor, 'ui_metadata.search.searchType', 'query');
return [
{
header: 'State',
header: formatMessage({id:"alert.monitor.overview.header.state"}),
value: monitor.enabled ? 'Enabled' : 'Disabled',
},
{
header: 'Monitor definition type',
header: formatMessage({id:"alert.monitor.overview.header.definition_type"}),
value: searchType === SEARCH_TYPE.QUERY ? 'Extraction Query' : 'Visual graph',
},
{
header: 'Total active alerts',
header: formatMessage({id:"alert.monitor.overview.header.total_active_alerts"}),
value: activeCount,
},
{
header: 'Schedule',
header: formatMessage({id:"alert.monitor.overview.header.schedule"}),
value: getScheduleFromMonitor(monitor),
},
{
header: 'Last updated',
header: formatMessage({id:"alert.monitor.overview.header.last_updated"}),
value: getTime(monitor.last_update_time),
},
{
header: 'Monitor ID',
header: formatMessage({id:"alert.monitor.overview.header.monitor_id"}),
value: monitorId,
},
{
header: 'Monitor version number',
header: formatMessage({id:"alert.monitor.overview.header.version_number"}),
value: monitorVersion,
},
{
/* There are 3 cases:
1. Monitors created by older versions and never updated.
These monitors wont have User details in the monitor object. `monitor.user` will be null.
2. Monitors are created when security plugin is disabled, these will have empty User object.
(`monitor.user.name`, `monitor.user.roles` are empty )
3. Monitors are created when security plugin is enabled, these will have an User object. */
header: 'Last updated by',
value: monitor.user && monitor.user.name ? monitor.user.name : 'N/A',
},
// {
// /* There are 3 cases:
// 1. Monitors created by older versions and never updated.
// These monitors wont have User details in the monitor object. `monitor.user` will be null.
// 2. Monitors are created when security plugin is disabled, these will have empty User object.
// (`monitor.user.name`, `monitor.user.roles` are empty )
// 3. Monitors are created when security plugin is enabled, these will have an User object. */
// header: 'Last updated by',
// value: monitor.user && monitor.user.name ? monitor.user.name : 'N/A',
// },
];
}

View File

@ -43,6 +43,7 @@ import {
} from '../../../utils/constants';
import { migrateTriggerMetadata } from './utils/helpers';
import { backendErrorNotification } from '../../../utils/helpers';
import {formatMessage} from 'umi/locale';
export default class MonitorDetails extends Component {
constructor(props) {
@ -323,7 +324,7 @@ export default class MonitorDetails extends Component {
});
}}
>
Edit
{formatMessage({ id: 'form.button.edit' })}
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
@ -331,7 +332,7 @@ export default class MonitorDetails extends Component {
isLoading={updating}
onClick={() => this.updateMonitor({ enabled: !monitor.enabled })}
>
{monitor.enabled ? 'Disable' : 'Enable'}
{monitor.enabled ? formatMessage({id:'alert.monitor.actions.disable'}) : formatMessage({id:'alert.monitor.actions.enable'})}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>

View File

@ -37,6 +37,8 @@ import {
import * as HistoryConstants from './utils/constants';
import { INDEX } from '../../../../utils/constants';
import { backendErrorNotification } from '../../../../utils/helpers';
import {pathPrefix} from '@/services/common';
import {formatMessage} from 'umi/locale';
class MonitorHistory extends PureComponent {
constructor(props) {
@ -200,8 +202,9 @@ class MonitorHistory extends PureComponent {
),
index: INDEX.ALL_ALERTS,
};
const resp = await httpClient.post('/alerting/monitors/_search', {
const resp = await httpClient.post(pathPrefix + '/alerting/_search', {
body: JSON.stringify(requestBody),
prependBasePath: false,
});
if (resp.ok) {
const poiData = get(resp, 'resp.aggregations.alerts_over_time.buckets', []).map((item) => ({
@ -309,7 +312,7 @@ class MonitorHistory extends PureComponent {
const { triggers, onShowTrigger } = this.props;
return (
<ContentPanel
title="History"
title={formatMessage({id:"alert.monitor.history.title"})}
titleSize="s"
bodyStyles={{ minHeight: 200, padding: 0 }}
actions={[

View File

@ -19,6 +19,7 @@ import uuid from 'uuid-v4';
import { EuiButton, EuiInMemoryTable } from '@elastic/eui';
import ContentPanel from '../../../../components/ContentPanel';
import {formatMessage} from 'umi/locale';
const MAX_TRIGGERS = 10;
@ -80,20 +81,20 @@ export default class Triggers extends Component {
const columns = [
{
field: 'name',
name: 'Name',
name: formatMessage({ id: 'alert.monitor.triggers.table.header.name' }),
sortable: true,
truncateText: true,
},
{
field: 'actions',
name: 'Number of actions',
name: formatMessage({ id: 'alert.monitor.triggers.table.header.number_of_actions' }),
sortable: true,
truncateText: false,
render: actions => actions.length,
},
{
field: 'severity',
name: 'Severity',
name: formatMessage({ id: 'alert.monitor.triggers.table.header.severity' }),
sortable: true,
truncateText: false,
},
@ -105,22 +106,22 @@ export default class Triggers extends Component {
return (
<ContentPanel
title="Triggers"
title={formatMessage({ id: 'alert.monitor.triggers.title' })}
titleSize="s"
bodyStyles={{ padding: 'initial' }}
actions={[
<EuiButton onClick={this.onEdit} disabled={selectedItems.length !== 1}>
Edit
{formatMessage({ id: 'form.button.edit' })}
</EuiButton>,
<EuiButton onClick={this.onDelete} disabled={!selectedItems.length}>
Delete
{formatMessage({ id: 'form.button.delete' })}
</EuiButton>,
<EuiButton
onClick={onCreateTrigger}
disabled={monitor.triggers.length >= MAX_TRIGGERS}
fill
>
Create
{formatMessage({ id: 'form.button.create' })}
</EuiButton>,
]}
>

View File

@ -1,5 +1,6 @@
import React, {useEffect, useState} from "react";
import React, {useEffect, useState, useMemo} from "react";
import {Spin, Card} from 'antd';
import {Fetch} from '../../../../components/kibana/core/public/http/fetch';
import './overview.scss';
import {
Axis,
@ -13,9 +14,11 @@ import {
timeFormatter,
BarSeries,
} from "@elastic/charts";
import {pathPrefix} from '@/services/common';
import {useAlertData, useAlertHsitoryData} from './hooks/use_alert_data';
import {AlertList} from '../Dashboard/components/AlertList/AlertList';
import { formatMessage } from 'umi/locale';
const theme = {
legend: {
@ -70,7 +73,7 @@ const theme = {
export default (props)=>{
const {httpClient, history} = props;
const {history} = props;
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState({
metrics: {
@ -78,8 +81,18 @@ export default (props)=>{
top_ten_cluster:{},
},
});
const httpClient = useMemo(()=>{
return new Fetch({
basePath:{
get: () => '',
prepend: (url) => url,
remove: (url) => url,
serverBasePath: '',
}
});
})
useEffect(()=>{
httpClient.get('/alerting/overview', {}).then((resp) => {
httpClient.get(pathPrefix + '/alerting/overview', {}).then((resp) => {
if (resp.ok) {
const { metrics, state_count } = resp;
setData({
@ -97,9 +110,18 @@ export default (props)=>{
const [historyAlerts, onAlertHistoryPageChange] = useAlertHsitoryData(pageSize);
const onItemClick = (item)=>{
history.push(`/monitors/${item.monitor_id}/elasticsearch/${item.cluster_id}`)
history.push(`/alerting/monitor/monitors/${item.monitor_id}/elasticsearch/${item.cluster_id}`)
}
const pickLegendItems = (items)=>{
return [{title:'ACKNOWLEDGED',color:'pink'}, {title:'ACTIVE',color:' rgb(208, 2, 27)'},
{title:'ERROR', color:'lightgrey'}, {color:'rgb(208, 2, 27)', title:'COMPLETED'}, {title:'DELETED', color:'gray'}]
.filter(legend=> items.includes(legend.title)).map(legend=>{
return {
...legend,
title: formatMessage({id: `alert.dashboard.state-options.${legend.title.toLowerCase()}`})
}
})
}
return (
<div className="overview-wrapper">
@ -107,19 +129,29 @@ export default (props)=>{
<div className="layout">
<div className="left">
<div className="state-count">
<Card className="item" title="激活告警">
{data.state_count?.ACTIVE || 0}
<Card className="item" bodyStyle={{ paddingBottom: 20 }}>
<Card.Meta title={formatMessage({id:'alert.overview.metric.active'})} className="title" />
<div>
<span className="total">{data.state_count?.ACTIVE || 0}</span>
</div>
</Card>
<Card className="item" title="已响应告警" >
{data.state_count?.ACKNOWLEDGED || 0}
<Card className="item" bodyStyle={{ paddingBottom: 20 }}>
<Card.Meta title={formatMessage({id:'alert.overview.metric.acknowledged'})} className="title" />
<div>
<span className="total">{data.state_count?.ACKNOWLEDGED || 0}</span>
</div>
</Card>
<Card className="item" title="错误告警">
{data.state_count?.ERROR || 0}
<Card className="item" bodyStyle={{ paddingBottom: 20 }}>
<Card.Meta title={formatMessage({id:'alert.overview.metric.error'})} className="title" />
<div>
<span className="total">{data.state_count?.ERROR || 0}</span>
</div>
</Card>
</div>
<div>
<AlertList dataSource={alerts.data}
title="Open Alerts"
title={formatMessage({id:'alert.overview.alertlist.title'})}
legendItems={pickLegendItems(['ACTIVE','ERROR','ACKNOWLEDGED'])}
onItemClick={onItemClick}
pagination={{
pageSize: 10,
@ -127,10 +159,11 @@ export default (props)=>{
onChange: onAlertPageChange,
}}/>
</div>
<div>
<div style={{marginTop:10}}>
<AlertList dataSource={historyAlerts.data}
title="History Alerts"
title={formatMessage({id:'alert.overview.alertlist-history.title'})}
onItemClick={onItemClick}
legendItems={pickLegendItems(["ACKNOWLEDGED", "ACTIVE", "ERROR", "COMPLETED", "DELETED"])}
pagination={{
pageSize: 10,
total: historyAlerts.total,

View File

@ -1,5 +1,6 @@
import {useState, useEffect} from 'react';
import _ from 'lodash';
import {pathPrefix} from '@/services/common';
const getAlerts =
async (from, size,type) => {
@ -15,14 +16,11 @@ const getAlerts =
if(qstr){
qstr = `?${qstr.slice(1)}`
}
const resp = await fetch('/elasticsearch/_all/alerting/alerts'+qstr);
const resp = await fetch(pathPrefix + '/alerting/overview/alerts'+qstr);
return resp.json();
// if (resp.ok) {
// const { alerts, totalAlerts } = resp;
}
export const useAlertData = (pageSize, page)=>{
const useData = (pageSize, page, type) => {
const [size, setSize] = useState(pageSize || 10);
const [pageIndex, setPageIndex] = useState(page || 1);
const [alertData, setAlertData] = useState({
@ -32,7 +30,7 @@ export const useAlertData = (pageSize, page)=>{
useEffect(()=>{
const from = (pageIndex - 1) * size;
const fetchAlerts = async (from, size)=>{
const resp = await getAlerts(from, size, 'ALERT');
const resp = await getAlerts(from, size, type);
if(resp.ok){
const { alerts, totalAlerts } = resp;
setAlertData({
@ -43,7 +41,7 @@ export const useAlertData = (pageSize, page)=>{
}
}
fetchAlerts(from,size);
}, [pageIndex, size]);
}, [pageIndex, size, type]);
const changePage = (pageIndex) => {
setPageIndex(pageIndex);
}
@ -51,32 +49,10 @@ export const useAlertData = (pageSize, page)=>{
return [alertData, changePage];
}
export const useAlertHsitoryData = (pageSize, page)=>{
const [size, setSize] = useState(pageSize || 10);
const [pageIndex, setPageIndex] = useState(page || 1);
const [alertHisotryData, setAlertHisotryData] = useState({
data: [],
total: 0,
});
useEffect(()=>{
const from = (pageIndex - 1) * size;
const fetchHistoryAlerts = async (from, size)=>{
const resp = await getAlerts(from, size, 'ALERT_HISTORY');
if(resp.ok){
const { alerts, totalAlerts } = resp;
setAlertHisotryData({
...alertHisotryData,
data: alerts,
total: totalAlerts,
})
}
}
fetchHistoryAlerts(from, size);
}, [pageIndex, size])
export const useAlertData = (pageSize, page)=>{
return useData(pageSize, page, 'ALERT');
}
const changePage = (pageIndex) => {
setPageIndex(pageIndex);
}
return [alertHisotryData, changePage];
export const useAlertHsitoryData = (pageSize, page)=>{
return useData(pageSize, page, 'ALERT_HISTORY');
}

View File

@ -6,11 +6,22 @@
flex-direction: column;
.state-count{
display: flex;
text-align: center;
justify-content: space-between;
.item{
min-width: 30%;
}
.title {
padding-bottom: 10px;
border-bottom: 1px solid #eef1f4;
}
.total {
font-size: 40px;
}
.unit {
color: #767676;
font-size: 12px;
font-weight: normal;
}
}
margin-bottom: 10px;
}
}
@ -21,4 +32,5 @@
.overview-wrapper {
padding: 10px;
background-color: #fff;
}

View File

@ -68,7 +68,7 @@ export const MAX_QUERY_RESULT_SIZE = 200;
export const OPEN_DISTRO_PREFIX = 'infini-search-center';
export const PLUGIN_NAME = `alerting`;
export const PLUGIN_NAME = `alerting/monitor`;
export const INDEX_PREFIX = `${OPEN_DISTRO_PREFIX}_alerting`;
export const INDEX = {
SCHEDULED_JOBS: `.${INDEX_PREFIX}-config`,

View File

@ -15,6 +15,7 @@
import _ from 'lodash';
import { INDEX, MAX_THROTTLE_VALUE, WRONG_THROTTLE_WARNING } from '../utils/constants';
import {pathPrefix} from '@/services/common'
// TODO: Use a validation framework to clean all of this up or create own.
@ -56,8 +57,9 @@ export const validateMonitorName = (httpClient, monitorToEdit) => async (value)
index: INDEX.SCHEDULED_JOBS,
query: { query: { term: { 'monitor.name.keyword': value } } },
};
const response = await httpClient.post('/alerting/monitors/_search', {
const response = await httpClient.post(pathPrefix + '/alerting/_search', {
body: JSON.stringify(options),
prependBasePath: false,
});
if (_.get(response, 'resp.hits.total.value', 0)) {
if (!monitorToEdit) return 'Monitor name is already used';