124 lines
4.1 KiB
Go
124 lines
4.1 KiB
Go
package features
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/grafana/authlib/types"
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
|
"github.com/grafana/grafana/pkg/services/live/model"
|
|
)
|
|
|
|
type actionType string
|
|
|
|
const (
|
|
ActionSaved actionType = "saved"
|
|
ActionDeleted actionType = "deleted"
|
|
EditingStarted actionType = "editing-started"
|
|
//EditingFinished actionType = "editing-finished"
|
|
)
|
|
|
|
// DashboardEvent events related to dashboards
|
|
type dashboardEvent struct {
|
|
UID string `json:"uid"`
|
|
Action actionType `json:"action"` // saved, editing, deleted
|
|
SessionID string `json:"sessionId,omitempty"`
|
|
ResourceVersion string `json:"rv,omitempty"`
|
|
}
|
|
|
|
// DashboardHandler manages all the `grafana/dashboard/*` channels
|
|
type DashboardHandler struct {
|
|
Publisher model.ChannelPublisher
|
|
ClientCount model.ChannelClientCount
|
|
AccessControl dashboards.DashboardAccessService
|
|
}
|
|
|
|
// GetHandlerForPath called on init
|
|
func (h *DashboardHandler) GetHandlerForPath(_ string) (model.ChannelHandler, error) {
|
|
return h, nil // all dashboards share the same handler
|
|
}
|
|
|
|
// OnSubscribe for now allows anyone to subscribe to any dashboard
|
|
func (h *DashboardHandler) OnSubscribe(ctx context.Context, user identity.Requester, e model.SubscribeEvent) (model.SubscribeReply, backend.SubscribeStreamStatus, error) {
|
|
parts := strings.Split(e.Path, "/")
|
|
|
|
// make sure can view this dashboard
|
|
if len(parts) == 2 && parts[0] == "uid" {
|
|
ns := types.OrgNamespaceFormatter(user.GetOrgID())
|
|
ok, err := h.AccessControl.HasDashboardAccess(ctx, user, utils.VerbGet, ns, parts[1])
|
|
if ok && err == nil {
|
|
return model.SubscribeReply{
|
|
Presence: true,
|
|
JoinLeave: true,
|
|
}, backend.SubscribeStreamStatusOK, nil
|
|
}
|
|
return model.SubscribeReply{}, backend.SubscribeStreamStatusPermissionDenied, err
|
|
}
|
|
|
|
// Unknown path
|
|
logger.Error("Unknown dashboard channel", "path", e.Path)
|
|
return model.SubscribeReply{}, backend.SubscribeStreamStatusNotFound, nil
|
|
}
|
|
|
|
// OnPublish is called when someone begins to edit a dashboard
|
|
func (h *DashboardHandler) OnPublish(ctx context.Context, requester identity.Requester, e model.PublishEvent) (model.PublishReply, backend.PublishStreamStatus, error) {
|
|
parts := strings.Split(e.Path, "/")
|
|
|
|
// make sure can view this dashboard
|
|
if len(parts) == 2 && parts[0] == "uid" {
|
|
event := dashboardEvent{}
|
|
err := json.Unmarshal(e.Data, &event)
|
|
if err != nil || event.UID != parts[1] {
|
|
return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("bad request")
|
|
}
|
|
if event.Action != EditingStarted {
|
|
// just ignore the event
|
|
return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("ignore???")
|
|
}
|
|
|
|
ns := types.OrgNamespaceFormatter(requester.GetOrgID())
|
|
ok, err := h.AccessControl.HasDashboardAccess(ctx, requester, utils.VerbUpdate, ns, parts[1])
|
|
if ok && err == nil {
|
|
msg, err := json.Marshal(event)
|
|
if err != nil {
|
|
return model.PublishReply{}, backend.PublishStreamStatusNotFound, fmt.Errorf("internal error")
|
|
}
|
|
return model.PublishReply{Data: msg}, backend.PublishStreamStatusOK, nil
|
|
}
|
|
}
|
|
|
|
return model.PublishReply{}, backend.PublishStreamStatusNotFound, nil
|
|
}
|
|
|
|
// DashboardSaved should broadcast to the appropriate stream
|
|
func (h *DashboardHandler) publish(orgID int64, event dashboardEvent) error {
|
|
msg, err := json.Marshal(event)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return h.Publisher(orgID, "grafana/dashboard/uid/"+event.UID, msg)
|
|
}
|
|
|
|
// DashboardSaved will broadcast to all connected dashboards
|
|
func (h *DashboardHandler) DashboardSaved(orgID int64, uid string, rv string) error {
|
|
return h.publish(orgID, dashboardEvent{
|
|
UID: uid,
|
|
Action: ActionSaved,
|
|
ResourceVersion: rv,
|
|
})
|
|
}
|
|
|
|
// DashboardDeleted will broadcast to all connected dashboards
|
|
func (h *DashboardHandler) DashboardDeleted(orgID int64, uid string) error {
|
|
return h.publish(orgID, dashboardEvent{
|
|
UID: uid,
|
|
Action: ActionDeleted,
|
|
})
|
|
}
|