diff --git a/handlers/proxy.go b/handlers/proxy.go index 730b846085..0ceb625739 100644 --- a/handlers/proxy.go +++ b/handlers/proxy.go @@ -1,6 +1,9 @@ package handlers import ( + "fmt" + "strconv" + "github.com/go-openapi/runtime/middleware" "github.com/influxdata/mrfusion" "github.com/influxdata/mrfusion/models" @@ -10,11 +13,28 @@ import ( ) type InfluxProxy struct { + Srcs mrfusion.SourcesStore TimeSeries mrfusion.TimeSeries } func (h *InfluxProxy) Proxy(ctx context.Context, params op.PostSourcesIDProxyParams) middleware.Responder { - // TODO: Add support for multiple TimeSeries with lookup based on params.ID + id, err := strconv.Atoi(params.ID) + if err != nil { + errMsg := &models.Error{Code: 500, Message: fmt.Sprintf("Error converting ID %s", params.ID)} + return op.NewPostSourcesIDProxyDefault(500).WithPayload(errMsg) + } + + src, err := h.Srcs.Get(ctx, id) + if err != nil { + errMsg := &models.Error{Code: 404, Message: fmt.Sprintf("Unknown ID %s", params.ID)} + return op.NewPostSourcesIDProxyNotFound().WithPayload(errMsg) + } + + if err = h.TimeSeries.Connect(ctx, &src); err != nil { + errMsg := &models.Error{Code: 400, Message: fmt.Sprintf("Unable to connect to source %s", params.ID)} + return op.NewPostSourcesIDProxyNotFound().WithPayload(errMsg) + } + query := mrfusion.Query{ Command: *params.Query.Query, DB: params.Query.Db, diff --git a/influx/influx.go b/influx/influx.go index b93a79c825..90dba90083 100644 --- a/influx/influx.go +++ b/influx/influx.go @@ -59,6 +59,9 @@ func query(u *url.URL, q mrfusion.Query) (mrfusion.Response, error) { return nil, err } defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("received status code %d from server", resp.StatusCode) + } var response Response dec := json.NewDecoder(resp.Body) @@ -73,6 +76,7 @@ func query(u *url.URL, q mrfusion.Query) (mrfusion.Response, error) { if decErr != nil { return nil, fmt.Errorf("unable to decode json: received status code %d err: %s", resp.StatusCode, decErr) } + // If we don't have an error in our json response, and didn't get statusOK // then send back an error if resp.StatusCode != http.StatusOK && response.Err != "" { @@ -111,3 +115,13 @@ func (c *Client) Query(ctx context.Context, q mrfusion.Query) (mrfusion.Response func (c *Client) MonitoredServices(ctx context.Context) ([]mrfusion.MonitoredService, error) { return []mrfusion.MonitoredService{}, nil } + +func (c *Client) Connect(ctx context.Context, src *mrfusion.Source) error { + u, err := url.Parse(src.URL[0]) + if err != nil { + return err + } + u.User = url.UserPassword(src.Username, src.Password) + c.URL = u + return nil +} diff --git a/mock/handlers.go b/mock/handlers.go index 94ffa16dcf..cb4a6ff35c 100644 --- a/mock/handlers.go +++ b/mock/handlers.go @@ -16,70 +16,154 @@ import ( type Handler struct { Store mrfusion.ExplorationStore + Srcs mrfusion.SourcesStore TimeSeries mrfusion.TimeSeries } func NewHandler() Handler { - return Handler{ - DefaultExplorationStore, - DefaultTimeSeries, + h := Handler{ + Store: DefaultExplorationStore, + Srcs: DefaultSourcesStore, + TimeSeries: DefaultTimeSeries, } + return h } -func sampleSource() *models.Source { - name := "muh name" - url := "http://localhost:8086" - - return &models.Source{ - ID: "1", - Links: &models.SourceLinks{ - Self: "/chronograf/v1/sources/1", - Proxy: "/chronograf/v1/sources/1/proxy", - }, - Name: &name, - Type: "influx-enterprise", - Username: "HOWDY!", - Password: "changeme", - URL: &url, +func (m *Handler) AllRoutes(ctx context.Context, params op.GetParams) middleware.Responder { + routes := &models.Routes{ + Sources: "/chronograf/v1/sources", + Dashboards: "/chronograf/v1/dashboards", + Apps: "/chronograf/v1/apps", + Users: "/chronograf/v1/users", } + return op.NewGetOK().WithPayload(routes) } func (m *Handler) NewSource(ctx context.Context, params op.PostSourcesParams) middleware.Responder { - return op.NewPostSourcesCreated() + src := mrfusion.Source{ + Name: *params.Source.Name, + Type: params.Source.Type, + Username: params.Source.Username, + Password: params.Source.Password, + URL: []string{*params.Source.URL}, + Default: params.Source.Default, + } + var err error + if src, err = m.Srcs.Add(ctx, src); err != nil { + errMsg := &models.Error{Code: 500, Message: fmt.Sprintf("Error storing source %v: %v", params.Source, err)} + return op.NewPostSourcesDefault(500).WithPayload(errMsg) + } + mSrc := mrToModel(src) + return op.NewPostSourcesCreated().WithPayload(mSrc).WithLocation(mSrc.Links.Self) +} + +func srcLinks(id int) *models.SourceLinks { + return &models.SourceLinks{ + Self: fmt.Sprintf("/chronograf/v1/sources/%d", id), + Proxy: fmt.Sprintf("/chronograf/v1/sources/%d/proxy", id), + Users: fmt.Sprintf("/chronograf/v1/sources/%d/users", id), + Roles: fmt.Sprintf("/chronograf/v1/sources/%d/roles", id), + Permissions: fmt.Sprintf("/chronograf/v1/sources/%d/permissions", id), + } +} + +func mrToModel(src mrfusion.Source) *models.Source { + return &models.Source{ + ID: strconv.Itoa(src.ID), + Links: srcLinks(src.ID), + Name: &src.Name, + Type: src.Type, + Username: src.Username, + Password: src.Password, + URL: &src.URL[0], + Default: src.Default, + } } func (m *Handler) Sources(ctx context.Context, params op.GetSourcesParams) middleware.Responder { + mrSrcs, err := m.Srcs.All(ctx) + if err != nil { + errMsg := &models.Error{Code: 500, Message: "Error loading sources"} + return op.NewGetSourcesDefault(500).WithPayload(errMsg) + } + + srcs := make([]*models.Source, len(mrSrcs)) + for i, src := range mrSrcs { + srcs[i] = mrToModel(src) + } + res := &models.Sources{ - Sources: []*models.Source{ - sampleSource(), - }, + Sources: srcs, } return op.NewGetSourcesOK().WithPayload(res) } func (m *Handler) SourcesID(ctx context.Context, params op.GetSourcesIDParams) middleware.Responder { - if params.ID != "1" { - return op.NewGetSourcesIDNotFound() + id, err := strconv.Atoi(params.ID) + if err != nil { + errMsg := &models.Error{Code: 500, Message: fmt.Sprintf("Error converting ID %s", params.ID)} + return op.NewGetSourcesIDDefault(500).WithPayload(errMsg) } - return op.NewGetSourcesIDOK().WithPayload(sampleSource()) + + src, err := m.Srcs.Get(ctx, id) + if err != nil { + errMsg := &models.Error{Code: 404, Message: fmt.Sprintf("Unknown ID %s", params.ID)} + return op.NewGetSourcesIDNotFound().WithPayload(errMsg) + } + + return op.NewGetSourcesIDOK().WithPayload(mrToModel(src)) } -func (m *Handler) Proxy(ctx context.Context, params op.PostSourcesIDProxyParams) middleware.Responder { - query := mrfusion.Query{ - Command: *params.Query.Query, - DB: params.Query.Db, - RP: params.Query.Rp, - } - response, err := m.TimeSeries.Query(ctx, mrfusion.Query(query)) +func (m *Handler) RemoveSource(ctx context.Context, params op.DeleteSourcesIDParams) middleware.Responder { + id, err := strconv.Atoi(params.ID) if err != nil { - return op.NewPostSourcesIDProxyDefault(500) + errMsg := &models.Error{Code: 500, Message: fmt.Sprintf("Error converting ID %s", params.ID)} + return op.NewDeleteSourcesIDDefault(500).WithPayload(errMsg) + } + src := mrfusion.Source{ + ID: id, + } + if err = m.Srcs.Delete(ctx, src); err != nil { + errMsg := &models.Error{Code: 500, Message: fmt.Sprintf("Unknown error deleting source %s", params.ID)} + return op.NewDeleteSourcesIDDefault(500).WithPayload(errMsg) } - res := &models.ProxyResponse{ - Results: response, + return op.NewDeleteSourcesIDNoContent() +} + +func (m *Handler) UpdateSource(ctx context.Context, params op.PatchSourcesIDParams) middleware.Responder { + id, err := strconv.Atoi(params.ID) + if err != nil { + errMsg := &models.Error{Code: 500, Message: fmt.Sprintf("Error converting ID %s", params.ID)} + return op.NewPatchSourcesIDDefault(500).WithPayload(errMsg) } - return op.NewPostSourcesIDProxyOK().WithPayload(res) + src, err := m.Srcs.Get(ctx, id) + if err != nil { + errMsg := &models.Error{Code: 404, Message: fmt.Sprintf("Unknown ID %s", params.ID)} + return op.NewPatchSourcesIDNotFound().WithPayload(errMsg) + } + src.Default = params.Config.Default + if params.Config.Name != nil { + src.Name = *params.Config.Name + } + if params.Config.Password != "" { + src.Password = params.Config.Password + } + if params.Config.Username != "" { + src.Username = params.Config.Username + } + if params.Config.URL != nil { + src.URL = []string{*params.Config.URL} + } + if params.Config.Type != "" { + src.Type = params.Config.Type + } + if err := m.Srcs.Update(ctx, src); err != nil { + errMsg := &models.Error{Code: 500, Message: fmt.Sprintf("Error updating source ID %s", params.ID)} + return op.NewPatchSourcesIDDefault(500).WithPayload(errMsg) + } + return op.NewPatchSourcesIDNoContent() } func (m *Handler) MonitoredServices(ctx context.Context, params op.GetSourcesIDMonitoredParams) middleware.Responder { @@ -98,6 +182,39 @@ func (m *Handler) MonitoredServices(ctx context.Context, params op.GetSourcesIDM return op.NewGetSourcesIDMonitoredOK().WithPayload(res) } +func (m *Handler) Proxy(ctx context.Context, params op.PostSourcesIDProxyParams) middleware.Responder { + id, err := strconv.Atoi(params.ID) + if err != nil { + errMsg := &models.Error{Code: 500, Message: fmt.Sprintf("Error converting ID %s", params.ID)} + return op.NewPostSourcesIDProxyDefault(500).WithPayload(errMsg) + } + + src, err := m.Srcs.Get(ctx, id) + if err != nil { + errMsg := &models.Error{Code: 404, Message: fmt.Sprintf("Unknown ID %s", params.ID)} + return op.NewPostSourcesIDProxyNotFound().WithPayload(errMsg) + } + + if err = m.TimeSeries.Connect(ctx, &src); err != nil { + errMsg := &models.Error{Code: 400, Message: fmt.Sprintf("Unable to connect to source %s", params.ID)} + return op.NewPostSourcesIDProxyNotFound().WithPayload(errMsg) + } + query := mrfusion.Query{ + Command: *params.Query.Query, + DB: params.Query.Db, + RP: params.Query.Rp, + } + response, err := m.TimeSeries.Query(ctx, mrfusion.Query(query)) + if err != nil { + return op.NewPostSourcesIDProxyDefault(500) + } + + res := &models.ProxyResponse{ + Results: response, + } + return op.NewPostSourcesIDProxyOK().WithPayload(res) +} + func (m *Handler) Explorations(ctx context.Context, params op.GetSourcesIDUsersUserIDExplorationsParams) middleware.Responder { id, err := strconv.Atoi(params.UserID) if err != nil { diff --git a/mock/mock.go b/mock/mock.go index c5445b85ac..75f62f02ef 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -8,6 +8,62 @@ import ( "golang.org/x/net/context" ) +type SourcesStore struct { + srcs map[int]mrfusion.Source +} + +func NewSourcesStore() mrfusion.SourcesStore { + return &SourcesStore{ + srcs: map[int]mrfusion.Source{}, + } +} + +func (s *SourcesStore) All(ctx context.Context) ([]mrfusion.Source, error) { + all := []mrfusion.Source{} + for _, src := range s.srcs { + all = append(all, src) + } + return all, nil +} + +func (s *SourcesStore) Add(ctx context.Context, src mrfusion.Source) (mrfusion.Source, error) { + id := len(s.srcs) + 1 + for k, _ := range s.srcs { + if k >= id { + id = k + 1 + break + } + } + src.ID = id + s.srcs[id] = src + return src, nil +} + +func (s *SourcesStore) Delete(ctx context.Context, src mrfusion.Source) error { + if _, ok := s.srcs[src.ID]; !ok { + return fmt.Errorf("Error unknown id %d", src.ID) + } + delete(s.srcs, src.ID) + return nil +} + +func (s *SourcesStore) Get(ctx context.Context, ID int) (mrfusion.Source, error) { + if src, ok := s.srcs[ID]; ok { + return src, nil + } + return mrfusion.Source{}, fmt.Errorf("Error no such source %d", ID) +} + +func (s *SourcesStore) Update(ctx context.Context, src mrfusion.Source) error { + if _, ok := s.srcs[src.ID]; !ok { + return fmt.Errorf("Error unknown ID %d", src.ID) + } + s.srcs[src.ID] = src + return nil +} + +var DefaultSourcesStore mrfusion.SourcesStore = NewSourcesStore() + type ExplorationStore struct { db map[int]*mrfusion.Exploration NowFunc func() time.Time @@ -99,6 +155,10 @@ func (t *TimeSeries) Query(context.Context, mrfusion.Query) (mrfusion.Response, return t.Response, nil } +func (t *TimeSeries) Connect(ctx context.Context, src *mrfusion.Source) error { + return nil +} + func (t *TimeSeries) MonitoredServices(context.Context) ([]mrfusion.MonitoredService, error) { hosts := make([]mrfusion.MonitoredService, len(t.Hosts)) for i, name := range t.Hosts { diff --git a/models/routes.go b/models/routes.go new file mode 100644 index 0000000000..0eca292379 --- /dev/null +++ b/models/routes.go @@ -0,0 +1,43 @@ +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + strfmt "github.com/go-openapi/strfmt" + + "github.com/go-openapi/errors" +) + +/*Routes routes + +swagger:model Routes +*/ +type Routes struct { + + /* Location of the apps endpoint + */ + Apps string `json:"apps,omitempty"` + + /* Location of the dashboards endpoint + */ + Dashboards string `json:"dashboards,omitempty"` + + /* Location of the sources endpoint + */ + Sources string `json:"sources,omitempty"` + + /* Location of the users endpoint + */ + Users string `json:"users,omitempty"` +} + +// Validate validates this routes +func (m *Routes) Validate(formats strfmt.Registry) error { + var res []error + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/models/sources.go b/models/sources.go index ae725aaf60..09c526bf1b 100644 --- a/models/sources.go +++ b/models/sources.go @@ -8,6 +8,7 @@ import ( "github.com/go-openapi/swag" "github.com/go-openapi/errors" + "github.com/go-openapi/validate" ) /*Sources sources @@ -17,8 +18,10 @@ swagger:model Sources type Sources struct { /* sources - */ - Sources []*Source `json:"sources,omitempty"` + + Required: true + */ + Sources []*Source `json:"sources"` } // Validate validates this sources @@ -38,8 +41,8 @@ func (m *Sources) Validate(formats strfmt.Registry) error { func (m *Sources) validateSources(formats strfmt.Registry) error { - if swag.IsZero(m.Sources) { // not required - return nil + if err := validate.Required("sources", "body", m.Sources); err != nil { + return err } for i := 0; i < len(m.Sources); i++ { diff --git a/restapi/configure_mr_fusion.go b/restapi/configure_mr_fusion.go index 33afbcdcb5..f50fd2f881 100644 --- a/restapi/configure_mr_fusion.go +++ b/restapi/configure_mr_fusion.go @@ -86,6 +86,8 @@ func configureAPI(api *operations.MrFusionAPI) http.Handler { mockHandler := mock.NewHandler() + api.GetHandler = operations.GetHandlerFunc(mockHandler.AllRoutes) + if len(storeFlags.BoltPath) > 0 { c := bolt.NewClient() c.Path = storeFlags.BoltPath @@ -108,12 +110,28 @@ func configureAPI(api *operations.MrFusionAPI) http.Handler { api.PostSourcesIDUsersUserIDExplorationsHandler = operations.PostSourcesIDUsersUserIDExplorationsHandlerFunc(mockHandler.NewExploration) } - api.DeleteDashboardsIDHandler = operations.DeleteDashboardsIDHandlerFunc(func(ctx context.Context, params operations.DeleteDashboardsIDParams) middleware.Responder { - return middleware.NotImplemented("operation .DeleteDashboardsID has not yet been implemented") - }) - api.DeleteSourcesIDHandler = operations.DeleteSourcesIDHandlerFunc(func(ctx context.Context, params operations.DeleteSourcesIDParams) middleware.Responder { - return middleware.NotImplemented("operation .DeleteSourcesID has not yet been implemented") - }) + api.DeleteSourcesIDHandler = operations.DeleteSourcesIDHandlerFunc(mockHandler.RemoveSource) + api.PatchSourcesIDHandler = operations.PatchSourcesIDHandlerFunc(mockHandler.UpdateSource) + + api.GetSourcesHandler = operations.GetSourcesHandlerFunc(mockHandler.Sources) + api.GetSourcesIDHandler = operations.GetSourcesIDHandlerFunc(mockHandler.SourcesID) + api.PostSourcesHandler = operations.PostSourcesHandlerFunc(mockHandler.NewSource) + + if len(influxFlags.Server) > 0 { + c, err := influx.NewClient(influxFlags.Server) + if err != nil { + panic(err) + } + // TODO: Change to bolt when finished + h := handlers.InfluxProxy{ + Srcs: mock.DefaultSourcesStore, + TimeSeries: c, + } + api.PostSourcesIDProxyHandler = operations.PostSourcesIDProxyHandlerFunc(h.Proxy) + } else { + api.PostSourcesIDProxyHandler = operations.PostSourcesIDProxyHandlerFunc(mockHandler.Proxy) + } + api.DeleteSourcesIDRolesRoleIDHandler = operations.DeleteSourcesIDRolesRoleIDHandlerFunc(func(ctx context.Context, params operations.DeleteSourcesIDRolesRoleIDParams) middleware.Responder { return middleware.NotImplemented("operation .DeleteSourcesIDRolesRoleID has not yet been implemented") }) @@ -121,8 +139,9 @@ func configureAPI(api *operations.MrFusionAPI) http.Handler { api.DeleteSourcesIDUsersUserIDHandler = operations.DeleteSourcesIDUsersUserIDHandlerFunc(func(ctx context.Context, params operations.DeleteSourcesIDUsersUserIDParams) middleware.Responder { return middleware.NotImplemented("operation .DeleteSourcesIDUsersUserID has not yet been implemented") }) - api.GetHandler = operations.GetHandlerFunc(func(ctx context.Context, params operations.GetParams) middleware.Responder { - return middleware.NotImplemented("operation .Get has not yet been implemented") + + api.DeleteDashboardsIDHandler = operations.DeleteDashboardsIDHandlerFunc(func(ctx context.Context, params operations.DeleteDashboardsIDParams) middleware.Responder { + return middleware.NotImplemented("operation .DeleteDashboardsID has not yet been implemented") }) api.GetDashboardsHandler = operations.GetDashboardsHandlerFunc(func(ctx context.Context, params operations.GetDashboardsParams) middleware.Responder { return middleware.NotImplemented("operation .GetDashboards has not yet been implemented") @@ -131,9 +150,6 @@ func configureAPI(api *operations.MrFusionAPI) http.Handler { return middleware.NotImplemented("operation .GetDashboardsID has not yet been implemented") }) - api.GetSourcesHandler = operations.GetSourcesHandlerFunc(mockHandler.Sources) - api.GetSourcesIDHandler = operations.GetSourcesIDHandlerFunc(mockHandler.SourcesID) - api.GetSourcesIDPermissionsHandler = operations.GetSourcesIDPermissionsHandlerFunc(func(ctx context.Context, params operations.GetSourcesIDPermissionsParams) middleware.Responder { return middleware.NotImplemented("operation .GetSourcesIDPermissions has not yet been implemented") }) @@ -151,9 +167,6 @@ func configureAPI(api *operations.MrFusionAPI) http.Handler { return middleware.NotImplemented("operation .GetSourcesIDUsersUserID has not yet been implemented") }) - api.PatchSourcesIDHandler = operations.PatchSourcesIDHandlerFunc(func(ctx context.Context, params operations.PatchSourcesIDParams) middleware.Responder { - return middleware.NotImplemented("operation .PatchSourcesID has not yet been implemented") - }) api.PatchSourcesIDRolesRoleIDHandler = operations.PatchSourcesIDRolesRoleIDHandlerFunc(func(ctx context.Context, params operations.PatchSourcesIDRolesRoleIDParams) middleware.Responder { return middleware.NotImplemented("operation .PatchSourcesIDRolesRoleID has not yet been implemented") }) @@ -164,20 +177,6 @@ func configureAPI(api *operations.MrFusionAPI) http.Handler { api.PostDashboardsHandler = operations.PostDashboardsHandlerFunc(func(ctx context.Context, params operations.PostDashboardsParams) middleware.Responder { return middleware.NotImplemented("operation .PostDashboards has not yet been implemented") }) - api.PostSourcesHandler = operations.PostSourcesHandlerFunc(mockHandler.NewSource) - - if len(influxFlags.Server) > 0 { - c, err := influx.NewClient(influxFlags.Server) - if err != nil { - panic(err) - } - h := handlers.InfluxProxy{ - TimeSeries: c, - } - api.PostSourcesIDProxyHandler = operations.PostSourcesIDProxyHandlerFunc(h.Proxy) - } else { - api.PostSourcesIDProxyHandler = operations.PostSourcesIDProxyHandlerFunc(mockHandler.Proxy) - } api.PostSourcesIDRolesHandler = operations.PostSourcesIDRolesHandlerFunc(func(ctx context.Context, params operations.PostSourcesIDRolesParams) middleware.Responder { return middleware.NotImplemented("operation .PostSourcesIDRoles has not yet been implemented") diff --git a/restapi/operations/get_responses.go b/restapi/operations/get_responses.go index ece03b9d3d..3321655aed 100644 --- a/restapi/operations/get_responses.go +++ b/restapi/operations/get_responses.go @@ -18,7 +18,7 @@ swagger:response getOK type GetOK struct { // In: body - Payload *models.Links `json:"body,omitempty"` + Payload *models.Routes `json:"body,omitempty"` } // NewGetOK creates GetOK with default headers values @@ -27,13 +27,13 @@ func NewGetOK() *GetOK { } // WithPayload adds the payload to the get o k response -func (o *GetOK) WithPayload(payload *models.Links) *GetOK { +func (o *GetOK) WithPayload(payload *models.Routes) *GetOK { o.Payload = payload return o } // SetPayload sets the payload to the get o k response -func (o *GetOK) SetPayload(payload *models.Links) { +func (o *GetOK) SetPayload(payload *models.Routes) { o.Payload = payload } diff --git a/restapi/server.go b/restapi/server.go index 6a91d22684..e3c883b410 100644 --- a/restapi/server.go +++ b/restapi/server.go @@ -2,11 +2,11 @@ package restapi import ( "crypto/tls" - "fmt" "log" "net" "net/http" "os" + "strconv" "sync" "time" @@ -213,6 +213,11 @@ func (s *Server) Listen() error { if s.TLSCertificateKey == "" { s.Fatalf("the required flag `--tls-key` was not specified") } + + // Use http host if https host wasn't defined + if s.TLSHost == "" { + s.TLSHost = s.Host + } } if s.hasScheme(schemeUnix) { @@ -224,7 +229,7 @@ func (s *Server) Listen() error { } if s.hasScheme(schemeHTTP) { - listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Host, s.Port)) + listener, err := net.Listen("tcp", net.JoinHostPort(s.Host, strconv.Itoa(s.Port))) if err != nil { return err } @@ -239,10 +244,7 @@ func (s *Server) Listen() error { } if s.hasScheme(schemeHTTPS) { - if s.TLSHost == "" { - s.TLSHost = s.Host - } - tlsListener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.TLSHost, s.TLSPort)) + tlsListener, err := net.Listen("tcp", net.JoinHostPort(s.TLSHost, strconv.Itoa(s.TLSPort))) if err != nil { return err } diff --git a/stores.go b/stores.go index f68d45ecd3..d62506949e 100644 --- a/stores.go +++ b/stores.go @@ -105,12 +105,38 @@ type Dashboard struct { // DashboardStore stores dashboards and associated Cells type DashboardStore interface { + // All returns all dashboards in the store + All(context.Context) ([]*Dashboard, error) // Add creates a new dashboard in the DashboardStore - Add(context.Context, Dashboard) error + Add(context.Context, *Dashboard) error // Delete the dashboard from the store - Delete(context.Context, Dashboard) error + Delete(context.Context, *Dashboard) error // Get retrieves Dashboard if `ID` exists - Get(ctx context.Context, ID int) error + Get(ctx context.Context, ID int) (*Dashboard, error) // Update the dashboard in the store. - Update(context.Context, Dashboard) error + Update(context.Context, *Dashboard) error +} + +type Source struct { + ID int // ID is the unique ID of the source + Name string // Name is the user-defined name for the source + Type string // Type specifies which kinds of source (enterprise vs oss) + Username string // Username is the username to connect to the source + Password string // Password is in CLEARTEXT FIXME + URL []string // URL are the connections to the source + Default bool // Default specifies the default source for the application +} + +// 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 } diff --git a/swagger.yaml b/swagger.yaml index e4ff3c7ba8..7b1e3b5c7f 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -20,7 +20,7 @@ paths: 200: description: Returns the links to the top level endpoints. schema: - $ref: '#/definitions/Links' + $ref: '#/definitions/Routes' default: description: Unexpected internal service error schema: @@ -730,6 +730,8 @@ paths: definitions: Sources: type: object + required: + - sources properties: sources: type: array @@ -958,6 +960,25 @@ definitions: type: array items: type: string + Routes: + type: object + properties: + users: + description: Location of the users endpoint + type: string + format: url + dashboards: + description: Location of the dashboards endpoint + type: string + format: url + sources: + description: Location of the sources endpoint + type: string + format: url + apps: + description: Location of the apps endpoint + type: string + format: url Services: type: object properties: diff --git a/timeseries.go b/timeseries.go index e691a5a9a0..05c88dede2 100644 --- a/timeseries.go +++ b/timeseries.go @@ -27,4 +27,6 @@ type TimeSeries interface { Query(context.Context, Query) (Response, error) // MonitoredServices retrieves all services sending monitoring data to this `TimeSeries` MonitoredServices(context.Context) ([]MonitoredService, error) + // Connect will connect to the time series using the information in `Source`. + Connect(context.Context, *Source) error } diff --git a/ui/src/select_source/containers/SelectSourcePage.js b/ui/src/select_source/containers/SelectSourcePage.js index 6bb56c2ff2..2946ac352e 100644 --- a/ui/src/select_source/containers/SelectSourcePage.js +++ b/ui/src/select_source/containers/SelectSourcePage.js @@ -43,8 +43,8 @@ export const SelectSourcePage = React.createClass({ username: this.sourceUser.value, password: this.sourcePassword.value, }; - createSource(source).then(() => { - // this.redirectToApp(sourceFromServer) + createSource(source).then(({data: sourceFromServer}) => { + this.redirectToApp(sourceFromServer); }); },