Merge branch 'master' of github.com:grafana/grafana into preferences
This commit is contained in:
+11
-1
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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"},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user