1// Copyright 2016 Circonus, Inc. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Dashboard API support - Fetch, Create, Update, Delete, and Search
6// See: https://login.circonus.com/resources/api/calls/dashboard
7
8package api
9
10import (
11	"encoding/json"
12	"fmt"
13	"net/url"
14	"regexp"
15
16	"github.com/circonus-labs/circonus-gometrics/api/config"
17)
18
19// DashboardGridLayout defines layout
20type DashboardGridLayout struct {
21	Height uint `json:"height"`
22	Width  uint `json:"width"`
23}
24
25// DashboardAccessConfig defines access config
26type DashboardAccessConfig struct {
27	BlackDash           bool   `json:"black_dash"`
28	Enabled             bool   `json:"enabled"`
29	Fullscreen          bool   `json:"fullscreen"`
30	FullscreenHideTitle bool   `json:"fullscreen_hide_title"`
31	Nickname            string `json:"nickname"`
32	ScaleText           bool   `json:"scale_text"`
33	SharedID            string `json:"shared_id"`
34	TextSize            uint   `json:"text_size"`
35}
36
37// DashboardOptions defines options
38type DashboardOptions struct {
39	AccessConfigs       []DashboardAccessConfig `json:"access_configs"`
40	FullscreenHideTitle bool                    `json:"fullscreen_hide_title"`
41	HideGrid            bool                    `json:"hide_grid"`
42	Linkages            [][]string              `json:"linkages"`
43	ScaleText           bool                    `json:"scale_text"`
44	TextSize            uint                    `json:"text_size"`
45}
46
47// ChartTextWidgetDatapoint defines datapoints for charts
48type ChartTextWidgetDatapoint struct {
49	AccountID    string `json:"account_id,omitempty"`     // metric cluster, metric
50	CheckID      uint   `json:"_check_id,omitempty"`      // metric
51	ClusterID    uint   `json:"cluster_id,omitempty"`     // metric cluster
52	ClusterTitle string `json:"_cluster_title,omitempty"` // metric cluster
53	Label        string `json:"label,omitempty"`          // metric
54	Label2       string `json:"_label,omitempty"`         // metric cluster
55	Metric       string `json:"metric,omitempty"`         // metric
56	MetricType   string `json:"_metric_type,omitempty"`   // metric
57	NumericOnly  bool   `json:"numeric_only,omitempty"`   // metric cluster
58}
59
60// ChartWidgetDefinitionLegend defines chart widget definition legend
61type ChartWidgetDefinitionLegend struct {
62	Show bool   `json:"show,omitempty"`
63	Type string `json:"type,omitempty"`
64}
65
66// ChartWidgetWedgeLabels defines chart widget wedge labels
67type ChartWidgetWedgeLabels struct {
68	OnChart  bool `json:"on_chart,omitempty"`
69	ToolTips bool `json:"tooltips,omitempty"`
70}
71
72// ChartWidgetWedgeValues defines chart widget wedge values
73type ChartWidgetWedgeValues struct {
74	Angle string `json:"angle,omitempty"`
75	Color string `json:"color,omitempty"`
76	Show  bool   `json:"show,omitempty"`
77}
78
79// ChartWidgtDefinition defines chart widget definition
80type ChartWidgtDefinition struct {
81	Datasource        string                      `json:"datasource,omitempty"`
82	Derive            string                      `json:"derive,omitempty"`
83	DisableAutoformat bool                        `json:"disable_autoformat,omitempty"`
84	Formula           string                      `json:"formula,omitempty"`
85	Legend            ChartWidgetDefinitionLegend `json:"legend,omitempty"`
86	Period            uint                        `json:"period,omitempty"`
87	PopOnHover        bool                        `json:"pop_onhover,omitempty"`
88	WedgeLabels       ChartWidgetWedgeLabels      `json:"wedge_labels,omitempty"`
89	WedgeValues       ChartWidgetWedgeValues      `json:"wedge_values,omitempty"`
90}
91
92// ForecastGaugeWidgetThresholds defines forecast widget thresholds
93type ForecastGaugeWidgetThresholds struct {
94	Colors []string `json:"colors,omitempty"` // forecasts, gauges
95	Flip   bool     `json:"flip,omitempty"`   // gauges
96	Values []string `json:"values,omitempty"` // forecasts, gauges
97}
98
99// StatusWidgetAgentStatusSettings defines agent status settings
100type StatusWidgetAgentStatusSettings struct {
101	Search         string `json:"search,omitempty"`
102	ShowAgentTypes string `json:"show_agent_types,omitempty"`
103	ShowContact    bool   `json:"show_contact,omitempty"`
104	ShowFeeds      bool   `json:"show_feeds,omitempty"`
105	ShowSetup      bool   `json:"show_setup,omitempty"`
106	ShowSkew       bool   `json:"show_skew,omitempty"`
107	ShowUpdates    bool   `json:"show_updates,omitempty"`
108}
109
110// StatusWidgetHostStatusSettings defines host status settings
111type StatusWidgetHostStatusSettings struct {
112	LayoutStyle  string   `json:"layout_style,omitempty"`
113	Search       string   `json:"search,omitempty"`
114	SortBy       string   `json:"sort_by,omitempty"`
115	TagFilterSet []string `json:"tag_filter_set,omitempty"`
116}
117
118// DashboardWidgetSettings defines settings specific to widget
119// Note: optional attributes which are structs need to be pointers so they will be omitted
120type DashboardWidgetSettings struct {
121	AccountID           string                           `json:"account_id,omitempty"`            // alerts, clusters, gauges, graphs, lists, status
122	Acknowledged        string                           `json:"acknowledged,omitempty"`          // alerts
123	AgentStatusSettings *StatusWidgetAgentStatusSettings `json:"agent_status_settings,omitempty"` // status
124	Algorithm           string                           `json:"algorithm,omitempty"`             // clusters
125	Autoformat          bool                             `json:"autoformat,omitempty"`            // text
126	BodyFormat          string                           `json:"body_format,omitempty"`           // text
127	ChartType           string                           `json:"chart_type,omitempty"`            // charts
128	CheckUUID           string                           `json:"check_uuid,omitempty"`            // gauges
129	Cleared             string                           `json:"cleared,omitempty"`               // alerts
130	ClusterID           uint                             `json:"cluster_id,omitempty"`            // clusters
131	ClusterName         string                           `json:"cluster_name,omitempty"`          // clusters
132	ContactGroups       []uint                           `json:"contact_groups,omitempty"`        // alerts
133	ContentType         string                           `json:"content_type,omitempty"`          // status
134	Datapoints          []ChartTextWidgetDatapoint       `json:"datapoints,omitempty"`            // charts, text
135	DateWindow          string                           `json:"date_window,omitempty"`           // graphs
136	Definition          *ChartWidgtDefinition            `json:"definition,omitempty"`            // charts
137	Dependents          string                           `json:"dependents,omitempty"`            // alerts
138	DisableAutoformat   bool                             `json:"disable_autoformat,omitempty"`    // gauges
139	Display             string                           `json:"display,omitempty"`               // alerts
140	Format              string                           `json:"format,omitempty"`                // forecasts
141	Formula             string                           `json:"formula,omitempty"`               // gauges
142	GraphUUID           string                           `json:"graph_id,omitempty"`              // graphs
143	HideXAxis           bool                             `json:"hide_xaxis,omitempty"`            // graphs
144	HideYAxis           bool                             `json:"hide_yaxis,omitempty"`            // graphs
145	HostStatusSettings  *StatusWidgetHostStatusSettings  `json:"host_status_settings,omitempty"`  // status
146	KeyInline           bool                             `json:"key_inline,omitempty"`            // graphs
147	KeyLoc              string                           `json:"key_loc,omitempty"`               // graphs
148	KeySize             uint                             `json:"key_size,omitempty"`              // graphs
149	KeyWrap             bool                             `json:"key_wrap,omitempty"`              // graphs
150	Label               string                           `json:"label,omitempty"`                 // graphs
151	Layout              string                           `json:"layout,omitempty"`                // clusters
152	Limit               uint                             `json:"limit,omitempty"`                 // lists
153	Maintenance         string                           `json:"maintenance,omitempty"`           // alerts
154	Markup              string                           `json:"markup,omitempty"`                // html
155	MetricDisplayName   string                           `json:"metric_display_name,omitempty"`   // gauges
156	MetricName          string                           `json:"metric_name,omitempty"`           // gauges
157	MinAge              string                           `json:"min_age,omitempty"`               // alerts
158	OffHours            []uint                           `json:"off_hours,omitempty"`             // alerts
159	OverlaySetID        string                           `json:"overlay_set_id,omitempty"`        // graphs
160	Period              uint                             `json:"period,omitempty"`                // gauges, text, graphs
161	RangeHigh           int                              `json:"range_high,omitempty"`            // gauges
162	RangeLow            int                              `json:"range_low,omitempty"`             // gauges
163	Realtime            bool                             `json:"realtime,omitempty"`              // graphs
164	ResourceLimit       string                           `json:"resource_limit,omitempty"`        // forecasts
165	ResourceUsage       string                           `json:"resource_usage,omitempty"`        // forecasts
166	Search              string                           `json:"search,omitempty"`                // alerts, lists
167	Severity            string                           `json:"severity,omitempty"`              // alerts
168	ShowFlags           bool                             `json:"show_flags,omitempty"`            // graphs
169	Size                string                           `json:"size,omitempty"`                  // clusters
170	TagFilterSet        []string                         `json:"tag_filter_set,omitempty"`        // alerts
171	Threshold           float32                          `json:"threshold,omitempty"`             // clusters
172	Thresholds          *ForecastGaugeWidgetThresholds   `json:"thresholds,omitempty"`            // forecasts, gauges
173	TimeWindow          string                           `json:"time_window,omitempty"`           // alerts
174	Title               string                           `json:"title,omitempty"`                 // alerts, charts, forecasts, gauges, html
175	TitleFormat         string                           `json:"title_format,omitempty"`          // text
176	Trend               string                           `json:"trend,omitempty"`                 // forecasts
177	Type                string                           `json:"type,omitempty"`                  // gauges, lists
178	UseDefault          bool                             `json:"use_default,omitempty"`           // text
179	ValueType           string                           `json:"value_type,omitempty"`            // gauges, text
180	WeekDays            []string                         `json:"weekdays,omitempty"`              // alerts
181}
182
183// DashboardWidget defines widget
184type DashboardWidget struct {
185	Active   bool                    `json:"active"`
186	Height   uint                    `json:"height"`
187	Name     string                  `json:"name"`
188	Origin   string                  `json:"origin"`
189	Settings DashboardWidgetSettings `json:"settings"`
190	Type     string                  `json:"type"`
191	WidgetID string                  `json:"widget_id"`
192	Width    uint                    `json:"width"`
193}
194
195// Dashboard defines a dashboard. See https://login.circonus.com/resources/api/calls/dashboard for more information.
196type Dashboard struct {
197	AccountDefault bool                `json:"account_default"`
198	Active         bool                `json:"_active,omitempty"`
199	CID            string              `json:"_cid,omitempty"`
200	Created        uint                `json:"_created,omitempty"`
201	CreatedBy      string              `json:"_created_by,omitempty"`
202	GridLayout     DashboardGridLayout `json:"grid_layout"`
203	LastModified   uint                `json:"_last_modified,omitempty"`
204	Options        DashboardOptions    `json:"options"`
205	Shared         bool                `json:"shared"`
206	Title          string              `json:"title"`
207	UUID           string              `json:"_dashboard_uuid,omitempty"`
208	Widgets        []DashboardWidget   `json:"widgets"`
209}
210
211// NewDashboard returns a new Dashboard (with defaults, if applicable)
212func NewDashboard() *Dashboard {
213	return &Dashboard{}
214}
215
216// FetchDashboard retrieves dashboard with passed cid.
217func (a *API) FetchDashboard(cid CIDType) (*Dashboard, error) {
218	if cid == nil || *cid == "" {
219		return nil, fmt.Errorf("Invalid dashboard CID [none]")
220	}
221
222	dashboardCID := string(*cid)
223
224	matched, err := regexp.MatchString(config.DashboardCIDRegex, dashboardCID)
225	if err != nil {
226		return nil, err
227	}
228	if !matched {
229		return nil, fmt.Errorf("Invalid dashboard CID [%s]", dashboardCID)
230	}
231
232	result, err := a.Get(string(*cid))
233	if err != nil {
234		return nil, err
235	}
236
237	if a.Debug {
238		a.Log.Printf("[DEBUG] fetch dashboard, received JSON: %s", string(result))
239	}
240
241	dashboard := new(Dashboard)
242	if err := json.Unmarshal(result, dashboard); err != nil {
243		return nil, err
244	}
245
246	return dashboard, nil
247}
248
249// FetchDashboards retrieves all dashboards available to the API Token.
250func (a *API) FetchDashboards() (*[]Dashboard, error) {
251	result, err := a.Get(config.DashboardPrefix)
252	if err != nil {
253		return nil, err
254	}
255
256	var dashboards []Dashboard
257	if err := json.Unmarshal(result, &dashboards); err != nil {
258		return nil, err
259	}
260
261	return &dashboards, nil
262}
263
264// UpdateDashboard updates passed dashboard.
265func (a *API) UpdateDashboard(cfg *Dashboard) (*Dashboard, error) {
266	if cfg == nil {
267		return nil, fmt.Errorf("Invalid dashboard config [nil]")
268	}
269
270	dashboardCID := string(cfg.CID)
271
272	matched, err := regexp.MatchString(config.DashboardCIDRegex, dashboardCID)
273	if err != nil {
274		return nil, err
275	}
276	if !matched {
277		return nil, fmt.Errorf("Invalid dashboard CID [%s]", dashboardCID)
278	}
279
280	jsonCfg, err := json.Marshal(cfg)
281	if err != nil {
282		return nil, err
283	}
284
285	if a.Debug {
286		a.Log.Printf("[DEBUG] update dashboard, sending JSON: %s", string(jsonCfg))
287	}
288
289	result, err := a.Put(dashboardCID, jsonCfg)
290	if err != nil {
291		return nil, err
292	}
293
294	dashboard := &Dashboard{}
295	if err := json.Unmarshal(result, dashboard); err != nil {
296		return nil, err
297	}
298
299	return dashboard, nil
300}
301
302// CreateDashboard creates a new dashboard.
303func (a *API) CreateDashboard(cfg *Dashboard) (*Dashboard, error) {
304	if cfg == nil {
305		return nil, fmt.Errorf("Invalid dashboard config [nil]")
306	}
307
308	jsonCfg, err := json.Marshal(cfg)
309	if err != nil {
310		return nil, err
311	}
312
313	if a.Debug {
314		a.Log.Printf("[DEBUG] create dashboard, sending JSON: %s", string(jsonCfg))
315	}
316
317	result, err := a.Post(config.DashboardPrefix, jsonCfg)
318	if err != nil {
319		return nil, err
320	}
321
322	dashboard := &Dashboard{}
323	if err := json.Unmarshal(result, dashboard); err != nil {
324		return nil, err
325	}
326
327	return dashboard, nil
328}
329
330// DeleteDashboard deletes passed dashboard.
331func (a *API) DeleteDashboard(cfg *Dashboard) (bool, error) {
332	if cfg == nil {
333		return false, fmt.Errorf("Invalid dashboard config [nil]")
334	}
335	return a.DeleteDashboardByCID(CIDType(&cfg.CID))
336}
337
338// DeleteDashboardByCID deletes dashboard with passed cid.
339func (a *API) DeleteDashboardByCID(cid CIDType) (bool, error) {
340	if cid == nil || *cid == "" {
341		return false, fmt.Errorf("Invalid dashboard CID [none]")
342	}
343
344	dashboardCID := string(*cid)
345
346	matched, err := regexp.MatchString(config.DashboardCIDRegex, dashboardCID)
347	if err != nil {
348		return false, err
349	}
350	if !matched {
351		return false, fmt.Errorf("Invalid dashboard CID [%s]", dashboardCID)
352	}
353
354	_, err = a.Delete(dashboardCID)
355	if err != nil {
356		return false, err
357	}
358
359	return true, nil
360}
361
362// SearchDashboards returns dashboards matching the specified
363// search query and/or filter. If nil is passed for both parameters
364// all dashboards will be returned.
365func (a *API) SearchDashboards(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Dashboard, error) {
366	q := url.Values{}
367
368	if searchCriteria != nil && *searchCriteria != "" {
369		q.Set("search", string(*searchCriteria))
370	}
371
372	if filterCriteria != nil && len(*filterCriteria) > 0 {
373		for filter, criteria := range *filterCriteria {
374			for _, val := range criteria {
375				q.Add(filter, val)
376			}
377		}
378	}
379
380	if q.Encode() == "" {
381		return a.FetchDashboards()
382	}
383
384	reqURL := url.URL{
385		Path:     config.DashboardPrefix,
386		RawQuery: q.Encode(),
387	}
388
389	result, err := a.Get(reqURL.String())
390	if err != nil {
391		return nil, fmt.Errorf("[ERROR] API call error %+v", err)
392	}
393
394	var dashboards []Dashboard
395	if err := json.Unmarshal(result, &dashboards); err != nil {
396		return nil, err
397	}
398
399	return &dashboards, nil
400}
401