Merge branch 'master' of github.com:grafana/grafana into preferences

This commit is contained in:
Torkel Ödegaard
2016-03-30 13:35:17 -07:00
205 changed files with 7158 additions and 2402 deletions
+11 -1
View File
@@ -4,6 +4,7 @@ import (
"github.com/go-macaron/binding"
"github.com/grafana/grafana/pkg/api/avatar"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/live"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"gopkg.in/macaron.v1"
@@ -35,6 +36,7 @@ func Register(r *macaron.Macaron) {
r.Get("/org/users/", reqSignedIn, Index)
r.Get("/org/apikeys/", reqSignedIn, Index)
r.Get("/dashboard/import/", reqSignedIn, Index)
r.Get("/admin", reqGrafanaAdmin, Index)
r.Get("/admin/settings", reqGrafanaAdmin, Index)
r.Get("/admin/users", reqGrafanaAdmin, Index)
r.Get("/admin/users/create", reqGrafanaAdmin, Index)
@@ -43,6 +45,8 @@ func Register(r *macaron.Macaron) {
r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index)
r.Get("/admin/stats", reqGrafanaAdmin, Index)
r.Get("/styleguide", reqSignedIn, Index)
r.Get("/plugins", reqSignedIn, Index)
r.Get("/plugins/:id/edit", reqSignedIn, Index)
r.Get("/plugins/:id/page/:page", reqSignedIn, Index)
@@ -243,7 +247,13 @@ func Register(r *macaron.Macaron) {
avt := avatar.CacheServer()
r.Get("/avatar/:hash", avt.ServeHTTP)
// Websocket
liveConn := live.New()
r.Any("/ws", liveConn.Serve)
// streams
r.Post("/api/streams/push", reqSignedIn, bind(dtos.StreamMessage{}), liveConn.PushToStream)
InitAppPluginRoutes(r)
r.NotFound(NotFoundHandler)
}
+1 -1
View File
@@ -61,7 +61,7 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string, targetUrl *url.URL) *ht
req.Header.Del("Set-Cookie")
}
return &httputil.ReverseProxy{Director: director}
return &httputil.ReverseProxy{Director: director, FlushInterval: time.Millisecond * 200}
}
func getDatasource(id int64, orgId int64) (*m.DataSource, error) {
+1 -1
View File
@@ -40,7 +40,7 @@ func GetDataSources(c *middleware.Context) {
if plugin, exists := plugins.DataSources[ds.Type]; exists {
dsItem.TypeLogoUrl = plugin.Info.Logos.Small
} else {
dsItem.TypeLogoUrl = "public/img/icn-datasources.svg"
dsItem.TypeLogoUrl = "public/img/icn-datasource.svg"
}
result = append(result, dsItem)
+12 -12
View File
@@ -3,18 +3,18 @@ package dtos
import "github.com/grafana/grafana/pkg/plugins"
type PluginSetting struct {
Name string `json:"name"`
Type string `json:"type"`
Id string `json:"id"`
Enabled bool `json:"enabled"`
Pinned bool `json:"pinned"`
Module string `json:"module"`
BaseUrl string `json:"baseUrl"`
Info *plugins.PluginInfo `json:"info"`
Pages []*plugins.AppPluginPage `json:"pages"`
Includes []*plugins.PluginInclude `json:"includes"`
Dependencies *plugins.PluginDependencies `json:"dependencies"`
JsonData map[string]interface{} `json:"jsonData"`
Name string `json:"name"`
Type string `json:"type"`
Id string `json:"id"`
Enabled bool `json:"enabled"`
Pinned bool `json:"pinned"`
Module string `json:"module"`
BaseUrl string `json:"baseUrl"`
Info *plugins.PluginInfo `json:"info"`
Includes []*plugins.PluginInclude `json:"includes"`
Dependencies *plugins.PluginDependencies `json:"dependencies"`
JsonData map[string]interface{} `json:"jsonData"`
DefaultNavUrl string `json:"defaultNavUrl"`
}
type PluginListItem struct {
+13
View File
@@ -0,0 +1,13 @@
package dtos
import "encoding/json"
type StreamMessage struct {
Stream string `json:"stream"`
Series []StreamMessageSeries `json:"series"`
}
type StreamMessageSeries struct {
Name string `json:"name"`
Datapoints [][]json.Number `json:"datapoints"`
}
+28 -15
View File
@@ -56,8 +56,8 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
if c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR {
dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{Divider: true})
dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{Text: "New", Url: setting.AppSubUrl + "/dashboard/new"})
dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{Text: "Import", Url: setting.AppSubUrl + "/import/dashboard"})
dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{Text: "New", Icon: "fa fa-plus", Url: setting.AppSubUrl + "/dashboard/new"})
dashboardChildNavs = append(dashboardChildNavs, &dtos.NavLink{Text: "Import", Icon: "fa fa-download", Url: setting.AppSubUrl + "/import/dashboard"})
}
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
@@ -88,22 +88,35 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
for _, plugin := range enabledPlugins.Apps {
if plugin.Pinned {
pageLink := &dtos.NavLink{
appLink := &dtos.NavLink{
Text: plugin.Name,
Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/edit",
Url: plugin.DefaultNavUrl,
Img: plugin.Info.Logos.Small,
}
for _, page := range plugin.Pages {
if !page.SuppressNav {
pageLink.Children = append(pageLink.Children, &dtos.NavLink{
Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/page/" + page.Slug,
Text: page.Name,
})
for _, include := range plugin.Includes {
if include.Type == "page" && include.AddToNav {
link := &dtos.NavLink{
Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/page/" + include.Slug,
Text: include.Name,
}
appLink.Children = append(appLink.Children, link)
}
if include.Type == "dashboard" && include.AddToNav {
link := &dtos.NavLink{
Url: setting.AppSubUrl + "/dashboard/db/" + include.Slug,
Text: include.Name,
}
appLink.Children = append(appLink.Children, link)
}
}
data.MainNavLinks = append(data.MainNavLinks, pageLink)
if c.OrgRole == m.ROLE_ADMIN {
appLink.Children = append(appLink.Children, &dtos.NavLink{Divider: true})
appLink.Children = append(appLink.Children, &dtos.NavLink{Text: "Plugin Config", Icon: "fa fa-cog", Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/edit"})
}
data.MainNavLinks = append(data.MainNavLinks, appLink)
}
}
@@ -113,10 +126,10 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
Icon: "fa fa-fw fa-cogs",
Url: setting.AppSubUrl + "/admin",
Children: []*dtos.NavLink{
{Text: "Global Users", Icon: "fa fa-fw fa-cogs", Url: setting.AppSubUrl + "/admin/users"},
{Text: "Global Orgs", Icon: "fa fa-fw fa-cogs", Url: setting.AppSubUrl + "/admin/orgs"},
{Text: "Server Settings", Icon: "fa fa-fw fa-cogs", Url: setting.AppSubUrl + "/admin/settings"},
{Text: "Server Stats", Icon: "fa-fw fa-cogs", Url: setting.AppSubUrl + "/admin/stats"},
{Text: "Global Users", Url: setting.AppSubUrl + "/admin/users"},
{Text: "Global Orgs", Url: setting.AppSubUrl + "/admin/orgs"},
{Text: "Server Settings", Url: setting.AppSubUrl + "/admin/settings"},
{Text: "Server Stats", Url: setting.AppSubUrl + "/admin/stats"},
},
})
}
+118
View File
@@ -0,0 +1,118 @@
package live
import (
"net/http"
"time"
"github.com/gorilla/websocket"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/log"
)
const (
// Time allowed to write a message to the peer.
writeWait = 10 * time.Second
// Time allowed to read the next pong message from the peer.
pongWait = 60 * time.Second
// Send pings to peer with this period. Must be less than pongWait.
pingPeriod = (pongWait * 9) / 10
// Maximum message size allowed from peer.
maxMessageSize = 512
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
type connection struct {
ws *websocket.Conn
send chan []byte
}
func newConnection(ws *websocket.Conn) *connection {
return &connection{
send: make(chan []byte, 256),
ws: ws,
}
}
func (c *connection) readPump() {
defer func() {
h.unregister <- c
c.ws.Close()
}()
c.ws.SetReadLimit(maxMessageSize)
c.ws.SetReadDeadline(time.Now().Add(pongWait))
c.ws.SetPongHandler(func(string) error { c.ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
for {
_, message, err := c.ws.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
log.Info("error: %v", err)
}
break
}
c.handleMessage(message)
}
}
func (c *connection) handleMessage(message []byte) {
json, err := simplejson.NewJson(message)
if err != nil {
log.Error(3, "Unreadable message on websocket channel:", err)
}
msgType := json.Get("action").MustString()
streamName := json.Get("stream").MustString()
if len(streamName) == 0 {
log.Error(3, "Not allowed to subscribe to empty stream name")
return
}
switch msgType {
case "subscribe":
h.subChannel <- &streamSubscription{name: streamName, conn: c}
case "unsubscribe":
h.subChannel <- &streamSubscription{name: streamName, conn: c, remove: true}
}
}
func (c *connection) write(mt int, payload []byte) error {
c.ws.SetWriteDeadline(time.Now().Add(writeWait))
return c.ws.WriteMessage(mt, payload)
}
// writePump pumps messages from the hub to the websocket connection.
func (c *connection) writePump() {
ticker := time.NewTicker(pingPeriod)
defer func() {
ticker.Stop()
c.ws.Close()
}()
for {
select {
case message, ok := <-c.send:
if !ok {
c.write(websocket.CloseMessage, []byte{})
return
}
if err := c.write(websocket.TextMessage, message); err != nil {
return
}
case <-ticker.C:
if err := c.write(websocket.PingMessage, []byte{}); err != nil {
return
}
}
}
}
+94
View File
@@ -0,0 +1,94 @@
package live
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/log"
)
type hub struct {
connections map[*connection]bool
streams map[string]map[*connection]bool
register chan *connection
unregister chan *connection
streamChannel chan *dtos.StreamMessage
subChannel chan *streamSubscription
}
type streamSubscription struct {
conn *connection
name string
remove bool
}
var h = hub{
connections: make(map[*connection]bool),
streams: make(map[string]map[*connection]bool),
register: make(chan *connection),
unregister: make(chan *connection),
streamChannel: make(chan *dtos.StreamMessage),
subChannel: make(chan *streamSubscription),
}
func (h *hub) removeConnection() {
}
func (h *hub) run() {
for {
select {
case c := <-h.register:
h.connections[c] = true
log.Info("Live: New connection (Total count: %v)", len(h.connections))
case c := <-h.unregister:
if _, ok := h.connections[c]; ok {
log.Info("Live: Closing Connection (Total count: %v)", len(h.connections))
delete(h.connections, c)
close(c.send)
}
// hand stream subscriptions
case sub := <-h.subChannel:
log.Info("Live: Subscribing to: %v, remove: %v", sub.name, sub.remove)
subscribers, exists := h.streams[sub.name]
// handle unsubscribe
if exists && sub.remove {
delete(subscribers, sub.conn)
continue
}
if !exists {
subscribers = make(map[*connection]bool)
h.streams[sub.name] = subscribers
}
subscribers[sub.conn] = true
// handle stream messages
case message := <-h.streamChannel:
subscribers, exists := h.streams[message.Stream]
if !exists || len(subscribers) == 0 {
log.Info("Live: Message to stream without subscribers: %v", message.Stream)
continue
}
messageBytes, _ := simplejson.NewFromAny(message).Encode()
for sub := range subscribers {
// check if channel is open
if _, ok := h.connections[sub]; !ok {
delete(subscribers, sub)
continue
}
select {
case sub.send <- messageBytes:
default:
close(sub.send)
delete(h.connections, sub)
delete(subscribers, sub)
}
}
}
}
}
+36
View File
@@ -0,0 +1,36 @@
package live
import (
"net/http"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/middleware"
)
type LiveConn struct {
}
func New() *LiveConn {
go h.run()
return &LiveConn{}
}
func (lc *LiveConn) Serve(w http.ResponseWriter, r *http.Request) {
log.Info("Live: Upgrading to WebSocket")
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Error(3, "Live: Failed to upgrade connection to WebSocket", err)
return
}
c := newConnection(ws)
h.register <- c
go c.writePump()
c.readPump()
}
func (lc *LiveConn) PushToStream(c *middleware.Context, message dtos.StreamMessage) {
h.streamChannel <- &message
c.JsonOK("Message recevived")
}
+9 -12
View File
@@ -72,18 +72,15 @@ func GetPluginSettingById(c *middleware.Context) Response {
} else {
dto := &dtos.PluginSetting{
Type: def.Type,
Id: def.Id,
Name: def.Name,
Info: &def.Info,
Dependencies: &def.Dependencies,
Includes: def.Includes,
BaseUrl: def.BaseUrl,
Module: def.Module,
}
if app, exists := plugins.Apps[pluginId]; exists {
dto.Pages = app.Pages
Type: def.Type,
Id: def.Id,
Name: def.Name,
Info: &def.Info,
Dependencies: &def.Dependencies,
Includes: def.Includes,
BaseUrl: def.BaseUrl,
Module: def.Module,
DefaultNavUrl: def.DefaultNavUrl,
}
query := m.GetPluginSettingByIdQuery{PluginId: pluginId, OrgId: c.OrgId}
+2 -1
View File
@@ -1,10 +1,11 @@
package api
import (
"strconv"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/services/search"
"strconv"
)
func Search(c *middleware.Context) {