2023-01-12 07:06:07 +00:00
|
|
|
package vmselect
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2024-01-23 02:11:19 +00:00
|
|
|
"time"
|
2023-01-12 07:06:07 +00:00
|
|
|
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2023-01-12 07:25:31 +00:00
|
|
|
vmuiCustomDashboardsPath = flag.String("vmui.customDashboardsPath", "", "Optional path to vmui dashboards. "+
|
|
|
|
"See https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui/packages/vmui/public/dashboards")
|
2024-02-01 12:47:41 +00:00
|
|
|
vmuiDefaultTimezone = flag.String("vmui.defaultTimezone", "", "The default timezone to be used in vmui. "+
|
2024-01-23 02:11:19 +00:00
|
|
|
"Timezone must be a valid IANA Time Zone. For example: America/New_York, Europe/Berlin, Etc/GMT+3 or Local")
|
2023-01-12 07:06:07 +00:00
|
|
|
)
|
|
|
|
|
2023-01-12 07:25:31 +00:00
|
|
|
// dashboardSettings represents dashboard settings file struct.
|
|
|
|
//
|
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui/packages/vmui/public/dashboards
|
|
|
|
type dashboardSettings struct {
|
2023-01-12 07:06:07 +00:00
|
|
|
Title string `json:"title,omitempty"`
|
|
|
|
Filename string `json:"filename,omitempty"`
|
|
|
|
Rows []dashboardRow `json:"rows"`
|
|
|
|
}
|
|
|
|
|
2023-01-12 07:25:31 +00:00
|
|
|
// panelSettings represents fields which used to show graph.
|
|
|
|
//
|
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui/packages/vmui/public/dashboards
|
2023-01-12 07:06:07 +00:00
|
|
|
type panelSettings struct {
|
|
|
|
Title string `json:"title,omitempty"`
|
|
|
|
Description string `json:"description,omitempty"`
|
|
|
|
Unit string `json:"unit,omitempty"`
|
|
|
|
Expr []string `json:"expr"`
|
|
|
|
Alias []string `json:"alias,omitempty"`
|
2024-11-28 12:47:37 +00:00
|
|
|
ShowLegend *bool `json:"showLegend"`
|
2023-01-12 07:06:07 +00:00
|
|
|
Width int `json:"width,omitempty"`
|
|
|
|
}
|
|
|
|
|
2023-01-12 07:25:31 +00:00
|
|
|
// dashboardRow represents panels on dashboard.
|
|
|
|
//
|
|
|
|
// See https://github.com/VictoriaMetrics/VictoriaMetrics/tree/master/app/vmui/packages/vmui/public/dashboards
|
2023-01-12 07:06:07 +00:00
|
|
|
type dashboardRow struct {
|
|
|
|
Title string `json:"title,omitempty"`
|
|
|
|
Panels []panelSettings `json:"panels"`
|
|
|
|
}
|
|
|
|
|
2023-01-12 07:25:31 +00:00
|
|
|
// dashboardsData represents all the dashboards settings.
|
2023-01-12 07:06:07 +00:00
|
|
|
type dashboardsData struct {
|
2023-01-12 07:25:31 +00:00
|
|
|
DashboardsSettings []dashboardSettings `json:"dashboardsSettings"`
|
2023-01-12 07:06:07 +00:00
|
|
|
}
|
|
|
|
|
2023-01-12 07:25:31 +00:00
|
|
|
func handleVMUICustomDashboards(w http.ResponseWriter) error {
|
2023-01-12 07:06:07 +00:00
|
|
|
path := *vmuiCustomDashboardsPath
|
|
|
|
if path == "" {
|
|
|
|
writeSuccessResponse(w, []byte(`{"dashboardsSettings": []}`))
|
2023-01-12 07:25:31 +00:00
|
|
|
return nil
|
2023-01-12 07:06:07 +00:00
|
|
|
}
|
|
|
|
settings, err := collectDashboardsSettings(path)
|
|
|
|
if err != nil {
|
2023-01-12 07:25:31 +00:00
|
|
|
return fmt.Errorf("cannot collect dashboards settings by -vmui.customDashboardsPath=%q: %w", path, err)
|
2023-01-12 07:06:07 +00:00
|
|
|
}
|
|
|
|
writeSuccessResponse(w, settings)
|
2023-01-12 07:25:31 +00:00
|
|
|
return nil
|
2023-01-12 07:06:07 +00:00
|
|
|
}
|
|
|
|
|
2024-01-23 02:11:19 +00:00
|
|
|
func handleVMUITimezone(w http.ResponseWriter) error {
|
|
|
|
tz, err := time.LoadLocation(*vmuiDefaultTimezone)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot load timezone %q: %w", *vmuiDefaultTimezone, err)
|
|
|
|
}
|
|
|
|
response := fmt.Sprintf(`{"timezone": %q}`, tz)
|
|
|
|
writeSuccessResponse(w, []byte(response))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-12 07:06:07 +00:00
|
|
|
func writeSuccessResponse(w http.ResponseWriter, data []byte) {
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
w.Write(data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func collectDashboardsSettings(path string) ([]byte, error) {
|
|
|
|
if !fs.IsPathExist(path) {
|
2023-01-12 07:25:31 +00:00
|
|
|
return nil, fmt.Errorf("cannot find folder %q", path)
|
2023-01-12 07:06:07 +00:00
|
|
|
}
|
2023-04-15 05:08:43 +00:00
|
|
|
files := fs.MustReadDir(path)
|
2023-01-12 07:06:07 +00:00
|
|
|
|
2023-01-12 07:25:31 +00:00
|
|
|
var dss []dashboardSettings
|
2023-01-12 07:06:07 +00:00
|
|
|
for _, file := range files {
|
2023-01-12 07:25:31 +00:00
|
|
|
filename := file.Name()
|
|
|
|
if filepath.Ext(filename) != ".json" {
|
2023-01-12 07:06:07 +00:00
|
|
|
continue
|
|
|
|
}
|
2023-01-12 07:25:31 +00:00
|
|
|
filePath := filepath.Join(path, filename)
|
|
|
|
f, err := os.ReadFile(filePath)
|
|
|
|
if err != nil {
|
|
|
|
// There is no need to add more context to the returned error, since os.ReadFile() adds enough context.
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var ds dashboardSettings
|
|
|
|
err = json.Unmarshal(f, &ds)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot parse file %s: %w", filePath, err)
|
|
|
|
}
|
2024-11-28 12:47:37 +00:00
|
|
|
|
|
|
|
for i := range ds.Rows {
|
|
|
|
for j := range ds.Rows[i].Panels {
|
|
|
|
// Set default value for ShowLegend = true if it is not specified
|
|
|
|
if ds.Rows[i].Panels[j].ShowLegend == nil {
|
|
|
|
defaultValue := true
|
|
|
|
ds.Rows[i].Panels[j].ShowLegend = &defaultValue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-12 07:25:31 +00:00
|
|
|
if len(ds.Rows) > 0 {
|
|
|
|
dss = append(dss, ds)
|
2023-01-12 07:06:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-12 07:25:31 +00:00
|
|
|
dd := dashboardsData{DashboardsSettings: dss}
|
2023-01-12 07:06:07 +00:00
|
|
|
return json.Marshal(dd)
|
|
|
|
}
|