package main import ( "database/sql" "flag" "fmt" "log" "net/http" "os" "strings" "text/template" "time" "github.com/go-echarts/go-echarts/v2/charts" "github.com/go-echarts/go-echarts/v2/opts" _ "github.com/go-sql-driver/mysql" ini "gopkg.in/ini.v1" ) var verb bool = false var file string = "" var cfg *ini.File type DataSet struct { Alias string Item string Result string UpdateTime string } func main() { flag.BoolVar(&verb, "v", false, "Show Verbose Debug Messages") flag.StringVar(&file, "f", "", "Config File Path") flag.Parse() log.SetOutput(os.Stdout) log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds) if file == "" { file = strings.TrimSuffix(os.Args[0], ".exe") + ".ini" } var err error cfg, err = ini.Load(file) if err != nil { log.Printf("[ERR] %v\r\n", err) os.Exit(1) } addr := cfg.Section("HTTP").Key("ListenIP").String() port := cfg.Section("HTTP").Key("ListenPort").String() http.HandleFunc("/", handleIndex) http.HandleFunc("/echart/", eChart) http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("./assets")))) http.ListenAndServe(addr+":"+port, nil) } func handleIndex(w http.ResponseWriter, r *http.Request) { if r.RequestURI == "/favicon.ico" { return } log.Printf("[MSG] Received Request: %s, %s\r\n", r.RemoteAddr, r.RequestURI) defer r.Body.Close() type Data struct { Title string Host string } data := Data{ Title: cfg.Section("HTTP").Key("Title").String(), Host: cfg.Section("HTTP").Key("Host").String(), } tpl, err := template.ParseFiles("root.html") if err != nil { log.Printf("[ERR] %v\r\n", err) return } err = tpl.Execute(w, data) if err != nil { log.Printf("[ERR] %v\r\n", err) return } } func eChart(w http.ResponseWriter, r *http.Request) { if r.RequestURI == "/favicon.ico" { return } fmt.Println() log.Printf("[MSG] Received Request: %s, %s\r\n", r.RemoteAddr, r.RequestURI) defer r.Body.Close() if err := r.ParseForm(); err != nil { log.Printf("[ERR] %v\r\n", err) } type Params struct { Range string "" Alias string "" Class string "" Scope string "" } var params Params params.Range = r.Form.Get("range") params.Scope = r.Form.Get("scope") params.Alias = r.Form.Get("alias") params.Class = r.Form.Get("class") log.Printf("[MSG] Request Params: %#v\r\n", params) timeAxis := make([]string, 0) endTime := time.Now() beginTime := endTime.AddDate(0, 0, -30) bt := beginTime for i := 1; i <= 60*24*30; i++ { bt = bt.Add(time.Minute) timeAxis = append(timeAxis, bt.Format("01-02 15:04")) } // Parsing Config - Database cfg.Reload() dbHost := cfg.Section("Database").Key("Host").MustString("127.0.0.1") dbPort := cfg.Section("Database").Key("Port").MustString("3306") dbSchema := cfg.Section("Database").Key("Database").MustString("monitoring") dbTable := cfg.Section("Database").Key("Table").MustString("server") dbColumns := cfg.Section("Database").Key("Columns").MustString("alias,item,result,update_time") dbUsername := cfg.Section("Database").Key("Username").String() dbPassword := cfg.Section("Database").Key("Password").String() if dbUsername == "" || dbPassword == "" { log.Println("[ERR] Invalid Database Configuration.") return } qry := fmt.Sprintf( "SELECT %s FROM %s WHERE alias='%s' AND item='%s' AND update_stmp>=%d ORDER BY seqid ASC;", dbColumns, dbTable, params.Alias, params.Class, beginTime.Unix(), ) log.Printf("[MSG] Query SQL: \r\n%s\r\n", qry) dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", dbUsername, dbPassword, dbHost, dbPort, dbSchema) conn, err := sql.Open("mysql", dsn) if err != nil { log.Printf("[ERR] %v\r\n", err) return } defer conn.Close() if err := conn.Ping(); err != nil { log.Printf("[ERR] %v\r\n", err) return } defer conn.Close() rows, err := conn.Query(qry) if err != nil { log.Printf("[ERR] %v\r\n", err) return } defer rows.Close() log.Println("[MSG] Query Successed.") baseData := make(map[string]string, 0) for rows.Next() { var data DataSet err := rows.Scan(&data.Alias, &data.Item, &data.Result, &data.UpdateTime) if err != nil { log.Printf("[ERR] %v\r\n", err) return } baseData[data.UpdateTime] = data.Result } log.Printf("[MSG] Total Scanned: %d\r\n", len(baseData)) var dots []opts.LineData = make([]opts.LineData, 0) for _, t := range timeAxis { tmp, ok := baseData[t] if ok { dots = append(dots, opts.LineData{Value: tmp}) } else { dots = append(dots, opts.LineData{Value: "null"}) } } log.Println("[MSG] Finished Sort Metadata.") // Parsing Config - Chart Titles chartTitle := cfg.Section("Chart").Key("Title").String() chartSubtitle := cfg.Section("Chart").Key("Subtitle").String() chartSeries := cfg.Section("Chart").Key("Series").String() assetsURL := cfg.Section("Chart").Key("AssetsURL").String() var xPercent float32 = 0 if params.Range == "all" { xPercent = 0 } if params.Range == "7days" { xPercent = 76.66 } if params.Range == "3days" { xPercent = 90 } if params.Range == "2days" { xPercent = 93.33 } if params.Range == "1day" { xPercent = 96.66 } if params.Range == "12hrs" { xPercent = 98.33 } if params.Range == "6hrs" { xPercent = 99.16 } if params.Alias != "" { chartSubtitle = params.Alias } if params.Class != "" { chartTitle = params.Class } chart := charts.NewLine() // lang := make([]string, 0) // lang = append(lang, "data view") // lang = append(lang, "turn off") // lang = append(lang, "refresh") chart.SetGlobalOptions( charts.WithTitleOpts( opts.Title{ Left: "center", Top: "0", Title: chartTitle, TitleStyle: &opts.TextStyle{ Color: "#000000", FontSize: 18, FontFamily: "Calibri", }, Subtitle: chartSubtitle, SubtitleStyle: &opts.TextStyle{ Color: "#222222", FontSize: 16, FontFamily: "Calibri", }, }, ), charts.WithInitializationOpts( opts.Initialization{ PageTitle: "eChart", BackgroundColor: "#FFFFFF", Width: "800px", Height: "600px", AssetsHost: assetsURL, }, ), charts.WithDataZoomOpts( opts.DataZoom{ Type: "inside", Start: xPercent, End: 100, }, ), charts.WithXAxisOpts( opts.XAxis{ Min: "dataMin", Max: "dataMax", AxisLabel: &opts.AxisLabel{ Show: true, ShowMinLabel: true, ShowMaxLabel: true, Rotate: 45, FontSize: "14", FontWeight: "600", FontFamily: "Calibri", LineHeight: "250", }, }, ), charts.WithTooltipOpts( opts.Tooltip{ Show: true, Trigger: "axis", }, ), charts.WithToolboxOpts( opts.Toolbox{ Show: true, Feature: &opts.ToolBoxFeature{ SaveAsImage: &opts.ToolBoxFeatureSaveAsImage{ Show: true, Type: "png", }, DataView: &opts.ToolBoxFeatureDataView{ Show: true, }, Restore: &opts.ToolBoxFeatureRestore{ Show: true, }, }, }, ), charts.WithLegendOpts( opts.Legend{ Show: false, }, ), ) chart.SetXAxis(timeAxis).AddSeries(chartSeries, dots). SetSeriesOptions( charts.WithLineChartOpts( opts.LineChart{ ConnectNulls: true, }, ), charts.WithMarkPointNameTypeItemOpts( opts.MarkPointNameTypeItem{ Name: "Max", Type: "max", }, opts.MarkPointNameTypeItem{ Name: "Min", Type: "min", }, ), charts.WithMarkPointStyleOpts( opts.MarkPointStyle{ SymbolSize: 50, Label: &opts.Label{ Show: true, Position: "inside", }, }, ), charts.WithMarkLineNameTypeItemOpts( opts.MarkLineNameTypeItem{ Name: "Avrg", Type: "average", }, ), // charts.WithLabelOpts( // opts.Label{ // Show: true, // Position: "top", // }, // ), ) chart.Render(w) log.Println("[MSG] Finished Render Web Page.") }