2016-10-20 14:38:23 +00:00
|
|
|
package chronograf
|
2016-09-12 19:43:01 +00:00
|
|
|
|
|
|
|
import (
|
2017-06-07 15:16:09 +00:00
|
|
|
"bytes"
|
2016-10-25 15:20:06 +00:00
|
|
|
"context"
|
2017-06-06 22:26:08 +00:00
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2017-03-06 21:16:45 +00:00
|
|
|
"io"
|
2016-10-25 15:20:06 +00:00
|
|
|
"net/http"
|
2017-08-08 18:05:50 +00:00
|
|
|
"regexp"
|
2017-06-06 22:26:08 +00:00
|
|
|
"strconv"
|
2017-06-08 15:31:35 +00:00
|
|
|
"strings"
|
2017-06-06 22:26:08 +00:00
|
|
|
"time"
|
2017-06-08 15:31:35 +00:00
|
|
|
"unicode"
|
|
|
|
"unicode/utf8"
|
2017-06-13 18:49:28 +00:00
|
|
|
|
|
|
|
"github.com/influxdata/influxdb/influxql"
|
2016-10-25 15:20:06 +00:00
|
|
|
)
|
2016-09-12 19:43:01 +00:00
|
|
|
|
2016-10-25 15:20:06 +00:00
|
|
|
// General errors.
|
|
|
|
const (
|
2017-02-01 21:05:06 +00:00
|
|
|
ErrUpstreamTimeout = Error("request to backend timed out")
|
|
|
|
ErrSourceNotFound = Error("source not found")
|
|
|
|
ErrServerNotFound = Error("server not found")
|
|
|
|
ErrLayoutNotFound = Error("layout not found")
|
|
|
|
ErrDashboardNotFound = Error("dashboard not found")
|
|
|
|
ErrUserNotFound = Error("user not found")
|
|
|
|
ErrLayoutInvalid = Error("layout is invalid")
|
|
|
|
ErrAlertNotFound = Error("alert not found")
|
|
|
|
ErrAuthentication = Error("user not authenticated")
|
2017-02-17 16:11:55 +00:00
|
|
|
ErrUninitialized = Error("client uninitialized. Call Open() method")
|
2017-07-21 16:09:49 +00:00
|
|
|
ErrInvalidAxis = Error("Unexpected axis in cell. Valid axes are 'x', 'y', and 'y2'")
|
2016-09-12 19:43:01 +00:00
|
|
|
)
|
|
|
|
|
2016-10-25 15:20:06 +00:00
|
|
|
// Error is a domain error encountered while processing chronograf requests
|
|
|
|
type Error string
|
|
|
|
|
|
|
|
func (e Error) Error() string {
|
|
|
|
return string(e)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Logger represents an abstracted structured logging implementation. It
|
|
|
|
// provides methods to trigger log messages at various alert levels and a
|
|
|
|
// WithField method to set keys for a structured log message.
|
|
|
|
type Logger interface {
|
2016-10-24 17:08:36 +00:00
|
|
|
Debug(...interface{})
|
2016-10-25 15:20:06 +00:00
|
|
|
Info(...interface{})
|
|
|
|
Error(...interface{})
|
|
|
|
|
|
|
|
WithField(string, interface{}) Logger
|
2017-03-06 21:16:45 +00:00
|
|
|
|
|
|
|
// Logger can be transformed into an io.Writer.
|
|
|
|
// That writer is the end of an io.Pipe and it is your responsibility to close it.
|
|
|
|
Writer() *io.PipeWriter
|
2016-10-25 15:20:06 +00:00
|
|
|
}
|
|
|
|
|
2017-03-31 15:20:44 +00:00
|
|
|
// Router is an abstracted Router based on the API provided by the
|
|
|
|
// julienschmidt/httprouter package.
|
|
|
|
type Router interface {
|
|
|
|
http.Handler
|
|
|
|
GET(string, http.HandlerFunc)
|
|
|
|
PATCH(string, http.HandlerFunc)
|
|
|
|
POST(string, http.HandlerFunc)
|
|
|
|
DELETE(string, http.HandlerFunc)
|
|
|
|
PUT(string, http.HandlerFunc)
|
|
|
|
|
|
|
|
Handler(string, string, http.Handler)
|
|
|
|
}
|
|
|
|
|
2016-10-25 15:20:06 +00:00
|
|
|
// Assets returns a handler to serve the website.
|
|
|
|
type Assets interface {
|
|
|
|
Handler() http.Handler
|
|
|
|
}
|
|
|
|
|
2017-03-13 21:45:05 +00:00
|
|
|
// Supported time-series databases
|
|
|
|
const (
|
|
|
|
// InfluxDB is the open-source time-series database
|
|
|
|
InfluxDB = "influx"
|
|
|
|
// InfluxEnteprise is the clustered HA time-series database
|
|
|
|
InfluxEnterprise = "influx-enterprise"
|
|
|
|
// InfluxRelay is the basic HA layer over InfluxDB
|
|
|
|
InfluxRelay = "influx-relay"
|
|
|
|
)
|
|
|
|
|
|
|
|
// TSDBStatus represents the current status of a time series database
|
|
|
|
type TSDBStatus interface {
|
|
|
|
// Connect will connect to the time series using the information in `Source`.
|
|
|
|
Connect(ctx context.Context, src *Source) error
|
|
|
|
// Ping returns version and TSDB type of time series database if reachable.
|
|
|
|
Ping(context.Context) error
|
|
|
|
// Version returns the version of the TSDB database
|
|
|
|
Version(context.Context) (string, error)
|
|
|
|
// Type returns the type of the TSDB database
|
|
|
|
Type(context.Context) (string, error)
|
|
|
|
}
|
|
|
|
|
2016-10-25 15:20:06 +00:00
|
|
|
// TimeSeries represents a queryable time series database.
|
|
|
|
type TimeSeries interface {
|
|
|
|
// Query retrieves time series data from the database.
|
|
|
|
Query(context.Context, Query) (Response, error)
|
|
|
|
// Connect will connect to the time series using the information in `Source`.
|
|
|
|
Connect(context.Context, *Source) error
|
2017-02-17 19:37:00 +00:00
|
|
|
// UsersStore represents the user accounts within the TimeSeries database
|
|
|
|
Users(context.Context) UsersStore
|
2017-02-27 19:31:38 +00:00
|
|
|
// Permissions returns all valid names permissions in this database
|
|
|
|
Permissions(context.Context) Permissions
|
2017-02-23 22:02:53 +00:00
|
|
|
// Roles represents the roles associated with this TimesSeriesDatabase
|
|
|
|
Roles(context.Context) (RolesStore, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Role is a restricted set of permissions assigned to a set of users.
|
|
|
|
type Role struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Permissions Permissions `json:"permissions,omitempty"`
|
|
|
|
Users []User `json:"users,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// RolesStore is the Storage and retrieval of authentication information
|
|
|
|
type RolesStore interface {
|
|
|
|
// All lists all roles from the RolesStore
|
|
|
|
All(context.Context) ([]Role, error)
|
|
|
|
// Create a new Role in the RolesStore
|
|
|
|
Add(context.Context, *Role) (*Role, error)
|
|
|
|
// Delete the Role from the RolesStore
|
|
|
|
Delete(context.Context, *Role) error
|
|
|
|
// Get retrieves a role if name exists.
|
|
|
|
Get(ctx context.Context, name string) (*Role, error)
|
|
|
|
// Update the roles' users or permissions
|
|
|
|
Update(context.Context, *Role) error
|
2016-10-25 15:20:06 +00:00
|
|
|
}
|
|
|
|
|
2016-11-29 21:04:54 +00:00
|
|
|
// Range represents an upper and lower bound for data
|
|
|
|
type Range struct {
|
2016-11-30 21:22:35 +00:00
|
|
|
Upper int64 `json:"upper"` // Upper is the upper bound
|
|
|
|
Lower int64 `json:"lower"` // Lower is the lower bound
|
2016-11-29 21:04:54 +00:00
|
|
|
}
|
|
|
|
|
2017-06-06 22:26:08 +00:00
|
|
|
type TemplateVariable interface {
|
|
|
|
fmt.Stringer
|
2017-06-13 18:42:52 +00:00
|
|
|
Name() string // returns the variable name
|
|
|
|
Precedence() uint // ordinal indicating precedence level for replacement
|
2017-06-06 22:26:08 +00:00
|
|
|
}
|
|
|
|
|
2017-06-08 15:31:35 +00:00
|
|
|
type ExecutableVar interface {
|
|
|
|
Exec(string)
|
|
|
|
}
|
|
|
|
|
2017-04-19 16:18:23 +00:00
|
|
|
// TemplateValue is a value use to replace a template in an InfluxQL query
|
2017-06-06 22:26:08 +00:00
|
|
|
type BasicTemplateValue struct {
|
2017-04-20 15:33:47 +00:00
|
|
|
Value string `json:"value"` // Value is the specific value used to replace a template in an InfluxQL query
|
2017-04-20 19:30:17 +00:00
|
|
|
Type string `json:"type"` // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant
|
2017-04-20 15:33:47 +00:00
|
|
|
Selected bool `json:"selected"` // Selected states that this variable has been picked to use for replacement
|
2017-04-19 16:18:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TemplateVar is a named variable within an InfluxQL query to be replaced with Values
|
2017-06-06 22:26:08 +00:00
|
|
|
type BasicTemplateVar struct {
|
|
|
|
Var string `json:"tempVar"` // Var is the string to replace within InfluxQL
|
|
|
|
Values []BasicTemplateValue `json:"values"` // Values are the replacement values within InfluxQL
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t BasicTemplateVar) Name() string {
|
|
|
|
return t.Var
|
2017-04-19 16:18:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// String converts the template variable into a correct InfluxQL string based
|
|
|
|
// on its type
|
2017-06-06 22:26:08 +00:00
|
|
|
func (t BasicTemplateVar) String() string {
|
2017-04-19 16:18:23 +00:00
|
|
|
if len(t.Values) == 0 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
switch t.Values[0].Type {
|
2017-04-20 19:30:17 +00:00
|
|
|
case "tagKey", "fieldKey", "measurement", "database":
|
2017-04-19 16:18:23 +00:00
|
|
|
return `"` + t.Values[0].Value + `"`
|
2017-07-10 21:33:48 +00:00
|
|
|
case "tagValue", "timeStamp":
|
2017-04-19 16:18:23 +00:00
|
|
|
return `'` + t.Values[0].Value + `'`
|
2017-04-20 19:30:17 +00:00
|
|
|
case "csv", "constant":
|
2017-04-19 16:18:23 +00:00
|
|
|
return t.Values[0].Value
|
|
|
|
default:
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-13 18:42:52 +00:00
|
|
|
func (t BasicTemplateVar) Precedence() uint {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2017-06-06 22:26:08 +00:00
|
|
|
type GroupByVar struct {
|
2017-06-09 19:53:55 +00:00
|
|
|
Var string `json:"tempVar"` // the name of the variable as present in the query
|
|
|
|
Duration time.Duration `json:"duration,omitempty"` // the Duration supplied by the query
|
|
|
|
Resolution uint `json:"resolution"` // the available screen resolution to render the results of this query
|
|
|
|
ReportingInterval time.Duration `json:"reportingInterval,omitempty"` // the interval at which data is reported to this series
|
2017-06-06 22:26:08 +00:00
|
|
|
}
|
|
|
|
|
2017-06-08 15:31:35 +00:00
|
|
|
// Exec is responsible for extracting the Duration from the query
|
|
|
|
func (g *GroupByVar) Exec(query string) {
|
2017-08-08 18:05:50 +00:00
|
|
|
whereClause := "WHERE"
|
2017-06-08 15:31:35 +00:00
|
|
|
start := strings.Index(query, whereClause)
|
|
|
|
if start == -1 {
|
|
|
|
// no where clause
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-08-08 18:05:50 +00:00
|
|
|
// reposition start to after the 'where' keyword
|
2017-06-08 15:31:35 +00:00
|
|
|
durStr := query[start+len(whereClause):]
|
|
|
|
|
2017-08-08 18:05:50 +00:00
|
|
|
// attempt to parse out a relative time range
|
|
|
|
dur, err := g.parseRelative(durStr)
|
|
|
|
if err == nil {
|
|
|
|
// we parsed relative duration successfully
|
|
|
|
g.Duration = dur
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
dur, err = g.parseAbsolute(durStr)
|
|
|
|
if err == nil {
|
|
|
|
// we found an absolute time range
|
|
|
|
g.Duration = dur
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseRelative locates and extracts a duration value from a fragment of an
|
|
|
|
// InfluxQL query following the "where" keyword. For example, in the fragment
|
|
|
|
// "time > now() - 180d GROUP BY :interval:", parseRelative would return a
|
|
|
|
// duration equal to 180d
|
|
|
|
func (g *GroupByVar) parseRelative(fragment string) (time.Duration, error) {
|
|
|
|
// locate duration literal start
|
|
|
|
prefix := "time > now() - "
|
|
|
|
start := strings.Index(fragment, prefix)
|
|
|
|
if start == -1 {
|
|
|
|
return time.Duration(0), errors.New("not a relative duration")
|
|
|
|
}
|
|
|
|
|
|
|
|
// reposition to duration literal
|
|
|
|
durFragment := fragment[start+len(prefix):]
|
|
|
|
|
|
|
|
// init counters
|
2017-06-08 15:31:35 +00:00
|
|
|
pos := 0
|
2017-08-08 18:05:50 +00:00
|
|
|
|
|
|
|
// locate end of duration literal
|
|
|
|
for pos < len(durFragment) {
|
|
|
|
rn, _ := utf8.DecodeRuneInString(durFragment[pos:])
|
2017-06-08 15:31:35 +00:00
|
|
|
if unicode.IsSpace(rn) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
pos++
|
|
|
|
}
|
|
|
|
|
2017-08-08 18:05:50 +00:00
|
|
|
// attempt to parse what we suspect is a duration literal
|
|
|
|
dur, err := influxql.ParseDuration(durFragment[:pos])
|
2017-06-08 15:31:35 +00:00
|
|
|
if err != nil {
|
2017-08-08 18:05:50 +00:00
|
|
|
return dur, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return dur, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseAbsolute will determine the duration between two absolute timestamps
|
|
|
|
// found within an InfluxQL fragment following the "where" keyword. For
|
|
|
|
// example, the fragement "time > '1985-10-25T00:01:21-0800 and time <
|
|
|
|
// '1985-10-25T00:01:22-0800'" would yield a duration of 1m'
|
|
|
|
func (g *GroupByVar) parseAbsolute(fragment string) (time.Duration, error) {
|
2017-08-24 15:37:25 +00:00
|
|
|
timePtn := `time\s[>|<]\s'([0-9\-T\:\.Z]+)'` // Playground: http://gobular.com/x/208f66bd-1889-4269-ab47-1efdfeeb63f0
|
2017-08-08 18:05:50 +00:00
|
|
|
re, err := regexp.Compile(timePtn)
|
|
|
|
if err != nil {
|
|
|
|
// this is a developer error and should complain loudly
|
|
|
|
panic("Bad Regex: err:" + err.Error())
|
2017-06-08 15:31:35 +00:00
|
|
|
}
|
|
|
|
|
2017-08-08 18:05:50 +00:00
|
|
|
if !re.Match([]byte(fragment)) {
|
|
|
|
return time.Duration(0), errors.New("absolute duration not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
// extract at most two times
|
|
|
|
matches := re.FindAll([]byte(fragment), 2)
|
|
|
|
|
|
|
|
// parse out absolute times
|
|
|
|
durs := make([]time.Time, 0, 2)
|
|
|
|
for _, match := range matches {
|
|
|
|
durStr := re.FindSubmatch(match)
|
|
|
|
if tm, err := time.Parse(time.RFC3339Nano, string(durStr[1])); err == nil {
|
|
|
|
durs = append(durs, tm)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// reject more than 2 times found
|
|
|
|
if len(durs) != 2 {
|
|
|
|
return time.Duration(0), errors.New("must provide exactly two absolute times")
|
|
|
|
}
|
|
|
|
|
|
|
|
dur := durs[1].Sub(durs[0])
|
|
|
|
|
|
|
|
return dur, nil
|
2017-06-08 15:31:35 +00:00
|
|
|
}
|
|
|
|
|
2017-06-06 22:26:08 +00:00
|
|
|
func (g *GroupByVar) String() string {
|
2017-06-16 22:54:15 +00:00
|
|
|
duration := g.Duration.Nanoseconds() / (g.ReportingInterval.Nanoseconds() * int64(g.Resolution))
|
|
|
|
if duration == 0 {
|
|
|
|
duration = 1
|
2017-06-09 19:53:55 +00:00
|
|
|
}
|
2017-06-16 22:54:15 +00:00
|
|
|
return "time(" + strconv.Itoa(int(duration)) + "s)"
|
2017-06-06 22:26:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g *GroupByVar) Name() string {
|
|
|
|
return g.Var
|
|
|
|
}
|
|
|
|
|
2017-06-13 18:42:52 +00:00
|
|
|
func (g *GroupByVar) Precedence() uint {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2017-04-20 15:33:47 +00:00
|
|
|
// TemplateID is the unique ID used to identify a template
|
|
|
|
type TemplateID string
|
|
|
|
|
|
|
|
// Template represents a series of choices to replace TemplateVars within InfluxQL
|
|
|
|
type Template struct {
|
2017-06-06 22:26:08 +00:00
|
|
|
BasicTemplateVar
|
2017-04-20 15:33:47 +00:00
|
|
|
ID TemplateID `json:"id"` // ID is the unique ID associated with this template
|
2017-04-20 19:30:17 +00:00
|
|
|
Type string `json:"type"` // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, query, measurements, databases
|
2017-04-20 15:33:47 +00:00
|
|
|
Label string `json:"label"` // Label is a user-facing description of the Template
|
|
|
|
Query *TemplateQuery `json:"query,omitempty"` // Query is used to generate the choices for a template
|
|
|
|
}
|
|
|
|
|
2016-10-25 15:20:06 +00:00
|
|
|
// Query retrieves a Response from a TimeSeries.
|
|
|
|
type Query struct {
|
2017-06-16 00:42:46 +00:00
|
|
|
Command string `json:"query"` // Command is the query itself
|
|
|
|
DB string `json:"db,omitempty"` // DB is optional and if empty will not be used.
|
|
|
|
RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used.
|
|
|
|
TemplateVars TemplateVars `json:"tempVars,omitempty"` // TemplateVars are template variables to replace within an InfluxQL query
|
|
|
|
Wheres []string `json:"wheres,omitempty"` // Wheres restricts the query to certain attributes
|
|
|
|
GroupBys []string `json:"groupbys,omitempty"` // GroupBys collate the query by these tags
|
|
|
|
Resolution uint `json:"resolution,omitempty"` // Resolution is the available screen resolution to render query results
|
|
|
|
Label string `json:"label,omitempty"` // Label is the Y-Axis label for the data
|
|
|
|
Range *Range `json:"range,omitempty"` // Range is the default Y-Axis range for the data
|
2017-06-06 22:26:08 +00:00
|
|
|
}
|
|
|
|
|
2017-06-16 00:42:46 +00:00
|
|
|
// TemplateVars are a heterogeneous collection of different TemplateVariables
|
2017-06-06 22:26:08 +00:00
|
|
|
// with the capability to decode arbitrary JSON into the appropriate template
|
|
|
|
// variable type
|
|
|
|
type TemplateVars []TemplateVariable
|
|
|
|
|
|
|
|
func (t *TemplateVars) UnmarshalJSON(text []byte) error {
|
2017-06-16 22:54:15 +00:00
|
|
|
// TODO: Need to test that server throws an error when :interval:'s Resolution or ReportingInterval or zero-value
|
2017-06-07 15:16:09 +00:00
|
|
|
rawVars := bytes.NewReader(text)
|
|
|
|
dec := json.NewDecoder(rawVars)
|
|
|
|
|
|
|
|
// read open bracket
|
|
|
|
rawTok, err := dec.Token()
|
2017-06-06 22:26:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-06-07 15:16:09 +00:00
|
|
|
|
|
|
|
tok, isDelim := rawTok.(json.Delim)
|
|
|
|
if !isDelim || tok != '[' {
|
|
|
|
return errors.New("Expected JSON array, but found " + tok.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
for dec.More() {
|
|
|
|
var halfBakedVar json.RawMessage
|
|
|
|
err := dec.Decode(&halfBakedVar)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var agb GroupByVar
|
|
|
|
err = json.Unmarshal(halfBakedVar, &agb)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// ensure that we really have a GroupByVar
|
|
|
|
if agb.Resolution != 0 {
|
|
|
|
(*t) = append(*t, &agb)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var tvar BasicTemplateVar
|
|
|
|
err = json.Unmarshal(halfBakedVar, &tvar)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2017-06-06 22:26:08 +00:00
|
|
|
}
|
|
|
|
|
2017-06-07 15:16:09 +00:00
|
|
|
// ensure that we really have a BasicTemplateVar
|
|
|
|
if len(tvar.Values) != 0 {
|
|
|
|
(*t) = append(*t, tvar)
|
2017-06-06 22:26:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2016-10-25 15:20:06 +00:00
|
|
|
}
|
|
|
|
|
Introduce ability to edit a dashboard cell
* Correct documentation for dashboards
* Exclude .git and use 'make run-dev' in 'make continuous'
* Fix dashboard deletion bug where id serialization was wrong
* Commence creation of overlay technology, add autoRefresh props to DashboardPage
* Enhance overlay magnitude of overlay technology
* Add confirm buttons to overlay technology
* Refactor ResizeContainer to accommodate arbitrary containers
* Refactor ResizeContainer to require explicit ResizeTop and ResizeBottom for clarity
* Add markup and styles for OverlayControls
* CellEditorOverlay needs a larger minimum bottom height to accommodate more things
* Revert Visualization to not use ResizeTop or flex-box
* Remove TODO and move to issue
* Refactor CellEditorOverlay to allow selection of graph type
* Style Overlay controls, move confirm buttons to own stylesheet
* Fix toggle buttons in overlay so active is actually active
* Block user-select on a few UI items
* Update cell query shape to support Visualization and LayoutRenderer
* Code cleanup
* Repair fixture schema; update props for affected components
* Wired up selectedGraphType and activeQueryID in CellEditorOverlay
* Wire up chooseMeasurements in QueryBuilder
Pass queryActions into QueryBuilder so that DataExplorer can provide
actionCreators and CellEditorOverlay can provide functions that
modify its component state
* semicolon cleanup
* Bind all queryModifier actions to component state with a stateReducer
* Overlay Technologies™ can add and delete a query from a cell
* Semicolon cleanup
* Add conversion of InfluxQL to QueryConfig for dashboards
* Update go deps to add influxdb at af72d9b0e4ebe95be30e89b160f43eabaf0529ed
* Updated docs for dashboard query config
* Update CHANGELOG to mention InfluxQL to QueryConfig
* Make reducer’s name more specific for clarity
* Remove 'table' as graphType
* Make graph renaming prettier
* Remove duplicate DashboardQuery in swagger.json
* Fix swagger to include name and links for Cell
* Refactor CellEditorOverlay to enable graph type selection
* Add link.self to all Dashboard cells; add bolt migrations
* Make dash graph names only hover on contents
* Consolidate timeRange format patterns, clean up
* Add cell endpoints to dashboards
* Include Line + Stat in Visualization Type list
* Add cell link to dashboards
* Enable step plot and stacked graph in Visualization
* Overlay Technologies are summonable and dismissable
* OverlayTechnologies saves changes to a cell
* Convert NameableGraph to createClass for state
This was converted from a pure function to encapsulate the state of the
buttons. An attempt was made previously to store this state in Redux,
but it proved too convoluted with the current state of the reducers for
cells and dashboards. Another effort must take place to separate a cell
reducer to manage the state of an individual cell in Redux in order for
this state to be sanely kept in Redux as well.
For the time being, this state is being kept in the component for the
sake of expeditiousness, since this is needed for Dashboards to be
released. A refactor of this will occur later.
* Cells should contain a links key in server response
* Clean up console logs
* Use live data instead of a cellQuery fixture
* Update docs for dashboard creation
* DB and RP are already present in the Command field
* Fix LayoutRenderer’s understanding of query schema
* Return a new object, rather that mutate in place
* Visualization doesn’t use activeQueryID
* Selected is an object, not a string
* QueryBuilder refactored to use query index instead of query id
* CellEditorOverlay refactored to use query index instead of query id
* ConfirmButtons doesn’t need to act on an item
* Rename functions to follow convention
* Queries are no longer guaranteed to have ids
* Omit WHERE and GROUP BY clauses when saving query
* Select new query on add in OverlayTechnologies
* Add click outside to dash graph menu, style menu also
* Change context menu from ... to a caret
More consistent with the rest of the UI, better affordance
* Hide graph context menu in presentation mode
Don’t want people editing a dashboard from presentation mode
* Move graph refreshing spinner so it does not overlap with context menu
* Wire up Cell Menu to Overlay Technologies
* Correct empty dashboard type
* Refactor dashboard spec fixtures
* Test syncDashboardCell reducer
* Remove Delete button from graph dropdown menu (for now)
* Update changelog
2017-03-24 00:12:33 +00:00
|
|
|
// DashboardQuery includes state for the query builder. This is a transition
|
|
|
|
// struct while we move to the full InfluxQL AST
|
|
|
|
type DashboardQuery struct {
|
|
|
|
Command string `json:"query"` // Command is the query itself
|
|
|
|
Label string `json:"label,omitempty"` // Label is the Y-Axis label for the data
|
|
|
|
Range *Range `json:"range,omitempty"` // Range is the default Y-Axis range for the data
|
|
|
|
QueryConfig QueryConfig `json:"queryConfig,omitempty"` // QueryConfig represents the query state that is understood by the data explorer
|
|
|
|
}
|
|
|
|
|
2017-04-20 15:33:47 +00:00
|
|
|
// TemplateQuery is used to retrieve choices for template replacement
|
|
|
|
type TemplateQuery struct {
|
2017-04-20 23:23:59 +00:00
|
|
|
Command string `json:"influxql"` // Command is the query itself
|
2017-04-20 19:30:17 +00:00
|
|
|
DB string `json:"db,omitempty"` // DB is optional and if empty will not be used.
|
|
|
|
RP string `json:"rp,omitempty"` // RP is a retention policy and optional; if empty will not be used.
|
|
|
|
Measurement string `json:"measurement"` // Measurement is the optinally selected measurement for the query
|
|
|
|
TagKey string `json:"tagKey"` // TagKey is the optionally selected tag key for the query
|
2017-04-22 02:06:24 +00:00
|
|
|
FieldKey string `json:"fieldKey"` // FieldKey is the optionally selected field key for the query
|
2017-04-20 15:33:47 +00:00
|
|
|
}
|
|
|
|
|
2016-10-25 15:20:06 +00:00
|
|
|
// Response is the result of a query against a TimeSeries
|
|
|
|
type Response interface {
|
|
|
|
MarshalJSON() ([]byte, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Source is connection information to a time-series data store.
|
|
|
|
type Source struct {
|
2017-03-30 16:48:04 +00:00
|
|
|
ID int `json:"id,string"` // ID is the unique ID of the source
|
2017-01-05 01:35:07 +00:00
|
|
|
Name string `json:"name"` // Name is the user-defined name for the source
|
|
|
|
Type string `json:"type,omitempty"` // Type specifies which kinds of source (enterprise vs oss)
|
|
|
|
Username string `json:"username,omitempty"` // Username is the username to connect to the source
|
|
|
|
Password string `json:"password,omitempty"` // Password is in CLEARTEXT
|
2017-07-15 01:02:13 +00:00
|
|
|
SharedSecret string `json:"sharedSecret,omitempty"` // ShareSecret is the optional signing secret for Influx JWT authorization
|
2017-01-05 01:35:07 +00:00
|
|
|
URL string `json:"url"` // URL are the connections to the source
|
2017-02-07 23:57:51 +00:00
|
|
|
MetaURL string `json:"metaUrl,omitempty"` // MetaURL is the url for the meta node
|
2017-01-05 01:35:07 +00:00
|
|
|
InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"` // InsecureSkipVerify as true means any certificate presented by the source is accepted.
|
|
|
|
Default bool `json:"default"` // Default specifies the default source for the application
|
|
|
|
Telegraf string `json:"telegraf"` // Telegraf is the db telegraf is written to. By default it is "telegraf"
|
2016-10-25 15:20:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SourcesStore stores connection information for a `TimeSeries`
|
|
|
|
type SourcesStore interface {
|
|
|
|
// All returns all sources in the store
|
|
|
|
All(context.Context) ([]Source, error)
|
|
|
|
// Add creates a new source in the SourcesStore and returns Source with ID
|
|
|
|
Add(context.Context, Source) (Source, error)
|
|
|
|
// Delete the Source from the store
|
|
|
|
Delete(context.Context, Source) error
|
|
|
|
// Get retrieves Source if `ID` exists
|
|
|
|
Get(ctx context.Context, ID int) (Source, error)
|
|
|
|
// Update the Source in the store.
|
|
|
|
Update(context.Context, Source) error
|
|
|
|
}
|
|
|
|
|
2016-11-03 00:59:25 +00:00
|
|
|
// AlertRule represents rules for building a tickscript alerting task
|
|
|
|
type AlertRule struct {
|
2017-02-09 06:10:23 +00:00
|
|
|
ID string `json:"id,omitempty"` // ID is the unique ID of the alert
|
2017-04-06 01:04:42 +00:00
|
|
|
TICKScript TICKScript `json:"tickscript"` // TICKScript is the raw tickscript associated with this Alert
|
2017-05-05 19:30:20 +00:00
|
|
|
Query *QueryConfig `json:"query"` // Query is the filter of data for the alert.
|
2017-02-09 06:10:23 +00:00
|
|
|
Every string `json:"every"` // Every how often to check for the alerting criteria
|
|
|
|
Alerts []string `json:"alerts"` // Alerts name all the services to notify (e.g. pagerduty)
|
|
|
|
AlertNodes []KapacitorNode `json:"alertNodes,omitempty"` // AlertNodes define additional arguments to alerts
|
|
|
|
Message string `json:"message"` // Message included with alert
|
|
|
|
Details string `json:"details"` // Details is generally used for the Email alert. If empty will not be added.
|
|
|
|
Trigger string `json:"trigger"` // Trigger is a type that defines when to trigger the alert
|
|
|
|
TriggerValues TriggerValues `json:"values"` // Defines the values that cause the alert to trigger
|
|
|
|
Name string `json:"name"` // Name is the user-defined name for the alert
|
2016-11-03 00:59:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TICKScript task to be used by kapacitor
|
|
|
|
type TICKScript string
|
2016-11-01 00:19:32 +00:00
|
|
|
|
2016-11-03 00:59:25 +00:00
|
|
|
// Ticker generates tickscript tasks for kapacitor
|
|
|
|
type Ticker interface {
|
|
|
|
// Generate will create the tickscript to be used as a kapacitor task
|
|
|
|
Generate(AlertRule) (TICKScript, error)
|
2016-11-01 00:19:32 +00:00
|
|
|
}
|
|
|
|
|
2016-11-04 00:44:28 +00:00
|
|
|
// TriggerValues specifies the alerting logic for a specific trigger type
|
|
|
|
type TriggerValues struct {
|
2017-05-05 22:00:04 +00:00
|
|
|
Change string `json:"change,omitempty"` // Change specifies if the change is a percent or absolute
|
|
|
|
Period string `json:"period,omitempty"` // Period length of time before deadman is alerted
|
|
|
|
Shift string `json:"shift,omitempty"` // Shift is the amount of time to look into the past for the alert to compare to the present
|
|
|
|
Operator string `json:"operator,omitempty"` // Operator for alert comparison
|
|
|
|
Value string `json:"value,omitempty"` // Value is the boundary value when alert goes critical
|
|
|
|
RangeValue string `json:"rangeValue"` // RangeValue is an optional value for range comparisons
|
2016-11-10 17:27:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Field represent influxql fields and functions from the UI
|
|
|
|
type Field struct {
|
|
|
|
Field string `json:"field"`
|
|
|
|
Funcs []string `json:"funcs"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// GroupBy represents influxql group by tags from the UI
|
|
|
|
type GroupBy struct {
|
|
|
|
Time string `json:"time"`
|
|
|
|
Tags []string `json:"tags"`
|
2016-11-03 22:27:58 +00:00
|
|
|
}
|
|
|
|
|
2017-05-02 20:08:51 +00:00
|
|
|
// DurationRange represents the lower and upper durations of the query config
|
|
|
|
type DurationRange struct {
|
|
|
|
Upper string `json:"upper"`
|
|
|
|
Lower string `json:"lower"`
|
|
|
|
}
|
|
|
|
|
2016-11-03 22:27:58 +00:00
|
|
|
// QueryConfig represents UI query from the data explorer
|
|
|
|
type QueryConfig struct {
|
2016-11-10 17:27:42 +00:00
|
|
|
ID string `json:"id,omitempty"`
|
|
|
|
Database string `json:"database"`
|
|
|
|
Measurement string `json:"measurement"`
|
|
|
|
RetentionPolicy string `json:"retentionPolicy"`
|
|
|
|
Fields []Field `json:"fields"`
|
|
|
|
Tags map[string][]string `json:"tags"`
|
|
|
|
GroupBy GroupBy `json:"groupBy"`
|
|
|
|
AreTagsAccepted bool `json:"areTagsAccepted"`
|
2017-04-07 22:32:10 +00:00
|
|
|
RawText *string `json:"rawText"`
|
2017-05-02 20:08:51 +00:00
|
|
|
Range *DurationRange `json:"range"`
|
2016-11-03 22:27:58 +00:00
|
|
|
}
|
|
|
|
|
2017-02-09 04:18:23 +00:00
|
|
|
// KapacitorNode adds arguments and properties to an alert
|
|
|
|
type KapacitorNode struct {
|
2017-02-09 06:10:23 +00:00
|
|
|
Name string `json:"name"`
|
|
|
|
Args []string `json:"args"`
|
|
|
|
Properties []KapacitorProperty `json:"properties"`
|
2017-02-09 04:18:23 +00:00
|
|
|
// In the future we could add chaining methods here.
|
|
|
|
}
|
|
|
|
|
|
|
|
// KapacitorProperty modifies the node they are called on
|
|
|
|
type KapacitorProperty struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Args []string `json:"args"`
|
|
|
|
}
|
|
|
|
|
2016-10-25 15:20:06 +00:00
|
|
|
// Server represents a proxy connection to an HTTP server
|
|
|
|
type Server struct {
|
|
|
|
ID int // ID is the unique ID of the server
|
|
|
|
SrcID int // SrcID of the data source
|
|
|
|
Name string // Name is the user-defined name for the server
|
|
|
|
Username string // Username is the username to connect to the server
|
2016-11-10 18:09:14 +00:00
|
|
|
Password string // Password is in CLEARTEXT
|
2016-10-25 15:20:06 +00:00
|
|
|
URL string // URL are the connections to the server
|
2017-04-20 23:15:37 +00:00
|
|
|
Active bool // Is this the active server for the source?
|
2016-10-25 15:20:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ServersStore stores connection information for a `Server`
|
|
|
|
type ServersStore interface {
|
|
|
|
// All returns all servers in the store
|
|
|
|
All(context.Context) ([]Server, error)
|
|
|
|
// Add creates a new source in the ServersStore and returns Server with ID
|
|
|
|
Add(context.Context, Server) (Server, error)
|
|
|
|
// Delete the Server from the store
|
|
|
|
Delete(context.Context, Server) error
|
|
|
|
// Get retrieves Server if `ID` exists
|
|
|
|
Get(ctx context.Context, ID int) (Server, error)
|
|
|
|
// Update the Server in the store.
|
|
|
|
Update(context.Context, Server) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// ID creates uniq ID string
|
|
|
|
type ID interface {
|
|
|
|
// Generate creates a unique ID string
|
|
|
|
Generate() (string, error)
|
|
|
|
}
|
|
|
|
|
2017-02-17 22:35:56 +00:00
|
|
|
const (
|
2017-02-19 06:54:52 +00:00
|
|
|
// AllScope grants permission for all databases.
|
|
|
|
AllScope Scope = "all"
|
|
|
|
// DBScope grants permissions for a specific database
|
|
|
|
DBScope Scope = "database"
|
2017-02-17 22:35:56 +00:00
|
|
|
)
|
|
|
|
|
2017-02-17 22:03:49 +00:00
|
|
|
// Permission is a specific allowance for User or Role bound to a
|
|
|
|
// scope of the data source
|
|
|
|
type Permission struct {
|
2017-02-19 06:54:52 +00:00
|
|
|
Scope Scope `json:"scope"`
|
|
|
|
Name string `json:"name,omitempty"`
|
|
|
|
Allowed Allowances `json:"allowed"`
|
2017-02-17 22:03:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Permissions represent the entire set of permissions a User or Role may have
|
|
|
|
type Permissions []Permission
|
|
|
|
|
2017-02-19 06:54:52 +00:00
|
|
|
// Allowances defines what actions a user can have on a scoped permission
|
|
|
|
type Allowances []string
|
|
|
|
|
|
|
|
// Scope defines the location of access of a permission
|
|
|
|
type Scope string
|
|
|
|
|
2016-10-25 15:20:06 +00:00
|
|
|
// User represents an authenticated user.
|
2016-09-12 19:43:01 +00:00
|
|
|
type User struct {
|
2017-02-23 22:02:53 +00:00
|
|
|
Name string `json:"name"`
|
2017-02-17 22:03:49 +00:00
|
|
|
Passwd string `json:"password"`
|
|
|
|
Permissions Permissions `json:"permissions,omitempty"`
|
2017-03-02 01:13:44 +00:00
|
|
|
Roles []Role `json:"roles,omitempty"`
|
2016-11-17 23:57:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// UsersStore is the Storage and retrieval of authentication information
|
|
|
|
type UsersStore interface {
|
2017-02-17 21:13:51 +00:00
|
|
|
// All lists all users from the UsersStore
|
|
|
|
All(context.Context) ([]User, error)
|
2016-11-17 23:57:46 +00:00
|
|
|
// Create a new User in the UsersStore
|
|
|
|
Add(context.Context, *User) (*User, error)
|
|
|
|
// Delete the User from the UsersStore
|
|
|
|
Delete(context.Context, *User) error
|
2017-02-17 19:37:00 +00:00
|
|
|
// Get retrieves a user if name exists.
|
|
|
|
Get(ctx context.Context, name string) (*User, error)
|
2016-11-17 23:57:46 +00:00
|
|
|
// Update the user's permissions or roles
|
|
|
|
Update(context.Context, *User) error
|
2016-09-12 19:43:01 +00:00
|
|
|
}
|
|
|
|
|
2017-03-24 16:58:57 +00:00
|
|
|
// Database represents a database in a time series source
|
2017-03-22 09:53:19 +00:00
|
|
|
type Database struct {
|
2017-03-23 11:56:36 +00:00
|
|
|
Name string `json:"name"` // a unique string identifier for the database
|
|
|
|
Duration string `json:"duration,omitempty"` // the duration (when creating a default retention policy)
|
|
|
|
Replication int32 `json:"replication,omitempty"` // the replication factor (when creating a default retention policy)
|
|
|
|
ShardDuration string `json:"shardDuration,omitempty"` // the shard duration (when creating a default retention policy)
|
2017-03-23 10:06:59 +00:00
|
|
|
}
|
|
|
|
|
2017-03-24 16:58:57 +00:00
|
|
|
// RetentionPolicy represents a retention policy in a time series source
|
2017-03-23 10:06:59 +00:00
|
|
|
type RetentionPolicy struct {
|
2017-03-23 11:56:36 +00:00
|
|
|
Name string `json:"name"` // a unique string identifier for the retention policy
|
|
|
|
Duration string `json:"duration,omitempty"` // the duration
|
|
|
|
Replication int32 `json:"replication,omitempty"` // the replication factor
|
|
|
|
ShardDuration string `json:"shardDuration,omitempty"` // the shard duration
|
2017-03-24 17:03:38 +00:00
|
|
|
Default bool `json:"isDefault,omitempty"` // whether the RP should be the default
|
2017-03-22 09:53:19 +00:00
|
|
|
}
|
|
|
|
|
2017-03-24 16:58:57 +00:00
|
|
|
// Databases represents a databases in a time series source
|
2017-03-22 09:53:19 +00:00
|
|
|
type Databases interface {
|
2017-03-23 11:56:36 +00:00
|
|
|
// All lists all databases
|
|
|
|
AllDB(context.Context) ([]Database, error)
|
2017-03-22 20:27:36 +00:00
|
|
|
Connect(context.Context, *Source) error
|
2017-03-23 06:21:21 +00:00
|
|
|
CreateDB(context.Context, *Database) (*Database, error)
|
2017-03-23 08:04:35 +00:00
|
|
|
DropDB(context.Context, string) error
|
2017-03-23 10:06:59 +00:00
|
|
|
AllRP(context.Context, string) ([]RetentionPolicy, error)
|
2017-03-23 11:27:53 +00:00
|
|
|
CreateRP(context.Context, string, *RetentionPolicy) (*RetentionPolicy, error)
|
2017-03-23 13:13:41 +00:00
|
|
|
UpdateRP(context.Context, string, string, *RetentionPolicy) (*RetentionPolicy, error)
|
2017-03-23 11:51:08 +00:00
|
|
|
DropRP(context.Context, string, string) error
|
2017-03-22 09:53:19 +00:00
|
|
|
}
|
2017-03-22 08:40:30 +00:00
|
|
|
|
2016-12-08 00:31:22 +00:00
|
|
|
// DashboardID is the dashboard ID
|
|
|
|
type DashboardID int
|
|
|
|
|
|
|
|
// Dashboard represents all visual and query data for a dashboard
|
|
|
|
type Dashboard struct {
|
2017-04-20 15:33:47 +00:00
|
|
|
ID DashboardID `json:"id"`
|
|
|
|
Cells []DashboardCell `json:"cells"`
|
|
|
|
Templates []Template `json:"templates"`
|
|
|
|
Name string `json:"name"`
|
2016-12-08 00:31:22 +00:00
|
|
|
}
|
|
|
|
|
2017-07-20 18:39:19 +00:00
|
|
|
// Axis represents the visible extents of a visualization
|
|
|
|
type Axis struct {
|
2017-08-02 17:07:27 +00:00
|
|
|
Bounds []string `json:"bounds"` // bounds are an arbitrary list of client-defined strings that specify the viewport for a cell
|
|
|
|
LegacyBounds [2]int64 `json:"-"` // legacy bounds are for testing a migration from an earlier version of axis
|
|
|
|
Label string `json:"label"` // label is a description of this Axis
|
2017-08-09 20:06:34 +00:00
|
|
|
Prefix string `json:"prefix"` // specifies a prefix for axis values
|
|
|
|
Suffix string `json:"suffix"` // specifies a suffix for axis values
|
|
|
|
Base string `json:"base"` // defines the base for axis values
|
2017-08-18 17:48:41 +00:00
|
|
|
Scale string `json:"scale"` // the magnitude of the numbers for this axis
|
2017-07-19 14:27:21 +00:00
|
|
|
}
|
|
|
|
|
2016-12-08 00:31:22 +00:00
|
|
|
// DashboardCell holds visual and query information for a cell
|
|
|
|
type DashboardCell struct {
|
2017-07-20 18:39:19 +00:00
|
|
|
ID string `json:"i"`
|
|
|
|
X int32 `json:"x"`
|
|
|
|
Y int32 `json:"y"`
|
|
|
|
W int32 `json:"w"`
|
|
|
|
H int32 `json:"h"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Queries []DashboardQuery `json:"queries"`
|
|
|
|
Axes map[string]Axis `json:"axes"`
|
|
|
|
Type string `json:"type"`
|
2016-12-08 00:31:22 +00:00
|
|
|
}
|
|
|
|
|
2016-12-09 03:28:40 +00:00
|
|
|
// DashboardsStore is the storage and retrieval of dashboards
|
|
|
|
type DashboardsStore interface {
|
2016-12-20 20:22:53 +00:00
|
|
|
// All lists all dashboards from the DashboardStore
|
2016-12-14 07:56:26 +00:00
|
|
|
All(context.Context) ([]Dashboard, error)
|
2016-12-08 00:31:22 +00:00
|
|
|
// Create a new Dashboard in the DashboardStore
|
2016-12-15 21:37:11 +00:00
|
|
|
Add(context.Context, Dashboard) (Dashboard, error)
|
2016-12-14 06:57:52 +00:00
|
|
|
// Delete the Dashboard from the DashboardStore if `ID` exists.
|
2016-12-15 21:37:11 +00:00
|
|
|
Delete(context.Context, Dashboard) error
|
2016-12-08 00:31:22 +00:00
|
|
|
// Get retrieves a dashboard if `ID` exists.
|
2016-12-15 21:37:11 +00:00
|
|
|
Get(ctx context.Context, id DashboardID) (Dashboard, error)
|
2016-12-08 00:31:22 +00:00
|
|
|
// Update replaces the dashboard information
|
2016-12-15 21:37:11 +00:00
|
|
|
Update(context.Context, Dashboard) error
|
2016-12-08 00:31:22 +00:00
|
|
|
}
|
|
|
|
|
2016-09-12 19:43:01 +00:00
|
|
|
// Cell is a rectangle and multiple time series queries to visualize.
|
|
|
|
type Cell struct {
|
2017-07-24 22:30:53 +00:00
|
|
|
X int32 `json:"x"`
|
|
|
|
Y int32 `json:"y"`
|
|
|
|
W int32 `json:"w"`
|
|
|
|
H int32 `json:"h"`
|
|
|
|
I string `json:"i"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Queries []Query `json:"queries"`
|
|
|
|
Axes map[string]Axis `json:"axes"`
|
|
|
|
Type string `json:"type"`
|
2016-09-12 19:43:01 +00:00
|
|
|
}
|
|
|
|
|
2016-10-06 04:26:39 +00:00
|
|
|
// Layout is a collection of Cells for visualization
|
|
|
|
type Layout struct {
|
2016-10-28 23:08:05 +00:00
|
|
|
ID string `json:"id"`
|
2016-10-10 15:45:35 +00:00
|
|
|
Application string `json:"app"`
|
2016-10-11 17:48:32 +00:00
|
|
|
Measurement string `json:"measurement"`
|
2016-11-15 18:11:32 +00:00
|
|
|
Autoflow bool `json:"autoflow"`
|
2016-10-10 15:45:35 +00:00
|
|
|
Cells []Cell `json:"cells"`
|
2016-09-12 19:43:01 +00:00
|
|
|
}
|
|
|
|
|
2016-10-06 04:26:39 +00:00
|
|
|
// LayoutStore stores dashboards and associated Cells
|
|
|
|
type LayoutStore interface {
|
2016-09-29 22:07:35 +00:00
|
|
|
// All returns all dashboards in the store
|
2016-10-06 04:26:39 +00:00
|
|
|
All(context.Context) ([]Layout, error)
|
|
|
|
// Add creates a new dashboard in the LayoutStore
|
|
|
|
Add(context.Context, Layout) (Layout, error)
|
2016-09-12 19:43:01 +00:00
|
|
|
// Delete the dashboard from the store
|
2016-10-06 04:26:39 +00:00
|
|
|
Delete(context.Context, Layout) error
|
|
|
|
// Get retrieves Layout if `ID` exists
|
2016-10-10 22:00:27 +00:00
|
|
|
Get(ctx context.Context, ID string) (Layout, error)
|
2016-09-12 19:43:01 +00:00
|
|
|
// Update the dashboard in the store.
|
2016-10-06 04:26:39 +00:00
|
|
|
Update(context.Context, Layout) error
|
2016-09-29 22:07:35 +00:00
|
|
|
}
|