2022-06-02 18:27:23 -08:00
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2022-10-19 17:24:00 -03:00
|
|
|
"errors"
|
2022-06-22 13:58:52 -08:00
|
|
|
"time"
|
2022-06-02 18:27:23 -08:00
|
|
|
|
2022-08-29 18:13:06 -03:00
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
2022-06-13 17:23:56 -06:00
|
|
|
"github.com/grafana/grafana/pkg/api/dtos"
|
2022-07-06 15:51:44 -08:00
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
2022-06-02 18:27:23 -08:00
|
|
|
"github.com/grafana/grafana/pkg/models"
|
2022-10-18 19:48:20 -06:00
|
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/annotations"
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
2022-07-06 12:42:39 -06:00
|
|
|
"github.com/grafana/grafana/pkg/services/datasources"
|
2022-07-06 15:51:44 -08:00
|
|
|
"github.com/grafana/grafana/pkg/services/publicdashboards"
|
|
|
|
|
. "github.com/grafana/grafana/pkg/services/publicdashboards/models"
|
2022-09-14 09:49:10 -06:00
|
|
|
"github.com/grafana/grafana/pkg/services/publicdashboards/queries"
|
2022-07-21 13:56:20 -06:00
|
|
|
"github.com/grafana/grafana/pkg/services/publicdashboards/validation"
|
2022-08-29 18:13:06 -03:00
|
|
|
"github.com/grafana/grafana/pkg/services/query"
|
2022-08-10 11:56:48 +02:00
|
|
|
"github.com/grafana/grafana/pkg/services/user"
|
2022-07-06 15:51:44 -08:00
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
2022-10-18 19:48:20 -06:00
|
|
|
"github.com/grafana/grafana/pkg/tsdb/grafanads"
|
2022-08-29 18:13:06 -03:00
|
|
|
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
|
|
|
|
|
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
2022-06-02 18:27:23 -08:00
|
|
|
)
|
|
|
|
|
|
2022-07-06 15:51:44 -08:00
|
|
|
// Define the Service Implementation. We're generating mock implementation
|
|
|
|
|
// automatically
|
|
|
|
|
type PublicDashboardServiceImpl struct {
|
2022-08-29 18:13:06 -03:00
|
|
|
log log.Logger
|
|
|
|
|
cfg *setting.Cfg
|
|
|
|
|
store publicdashboards.Store
|
|
|
|
|
intervalCalculator intervalv2.Calculator
|
|
|
|
|
QueryDataService *query.Service
|
2022-10-18 19:48:20 -06:00
|
|
|
AnnotationsRepo annotations.Repository
|
2022-10-19 17:24:00 -03:00
|
|
|
ac accesscontrol.AccessControl
|
2022-07-06 15:51:44 -08:00
|
|
|
}
|
|
|
|
|
|
2022-08-01 14:46:48 -08:00
|
|
|
var LogPrefix = "publicdashboards.service"
|
|
|
|
|
|
2022-07-06 15:51:44 -08:00
|
|
|
// Gives us compile time error if the service does not adhere to the contract of
|
|
|
|
|
// the interface
|
|
|
|
|
var _ publicdashboards.Service = (*PublicDashboardServiceImpl)(nil)
|
|
|
|
|
|
|
|
|
|
// Factory for method used by wire to inject dependencies.
|
|
|
|
|
// builds the service, and api, and configures routes
|
|
|
|
|
func ProvideService(
|
|
|
|
|
cfg *setting.Cfg,
|
|
|
|
|
store publicdashboards.Store,
|
2022-08-29 18:13:06 -03:00
|
|
|
qds *query.Service,
|
2022-10-18 19:48:20 -06:00
|
|
|
anno annotations.Repository,
|
2022-10-19 17:24:00 -03:00
|
|
|
ac accesscontrol.AccessControl,
|
2022-07-06 15:51:44 -08:00
|
|
|
) *PublicDashboardServiceImpl {
|
|
|
|
|
return &PublicDashboardServiceImpl{
|
2022-08-29 18:13:06 -03:00
|
|
|
log: log.New(LogPrefix),
|
|
|
|
|
cfg: cfg,
|
|
|
|
|
store: store,
|
|
|
|
|
intervalCalculator: intervalv2.NewCalculator(),
|
|
|
|
|
QueryDataService: qds,
|
2022-10-18 19:48:20 -06:00
|
|
|
AnnotationsRepo: anno,
|
2022-10-19 17:24:00 -03:00
|
|
|
ac: ac,
|
2022-07-06 15:51:44 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-12 21:36:05 -08:00
|
|
|
// Gets a dashboard by Uid
|
2022-07-21 13:56:20 -06:00
|
|
|
func (pd *PublicDashboardServiceImpl) GetDashboard(ctx context.Context, dashboardUid string) (*models.Dashboard, error) {
|
|
|
|
|
dashboard, err := pd.store.GetDashboard(ctx, dashboardUid)
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dashboard, err
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-22 13:58:52 -08:00
|
|
|
// Gets public dashboard via access token
|
2022-08-26 01:21:52 -08:00
|
|
|
func (pd *PublicDashboardServiceImpl) GetPublicDashboard(ctx context.Context, accessToken string) (*PublicDashboard, *models.Dashboard, error) {
|
|
|
|
|
pubdash, dash, err := pd.store.GetPublicDashboard(ctx, accessToken)
|
2022-10-17 13:17:24 -08:00
|
|
|
ctxLogger := pd.log.FromContext(ctx)
|
2022-06-02 18:27:23 -08:00
|
|
|
|
|
|
|
|
if err != nil {
|
2022-08-26 01:21:52 -08:00
|
|
|
return nil, nil, err
|
2022-06-02 18:27:23 -08:00
|
|
|
}
|
|
|
|
|
|
2022-10-17 13:17:24 -08:00
|
|
|
if pubdash == nil {
|
|
|
|
|
ctxLogger.Error("GetPublicDashboard: Public dashboard not found", "accessToken", accessToken)
|
|
|
|
|
return nil, nil, ErrPublicDashboardNotFound
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if dash == nil {
|
|
|
|
|
ctxLogger.Error("GetPublicDashboard: Dashboard not found", "accessToken", accessToken)
|
2022-08-26 01:21:52 -08:00
|
|
|
return nil, nil, ErrPublicDashboardNotFound
|
2022-06-02 18:27:23 -08:00
|
|
|
}
|
|
|
|
|
|
2022-06-22 13:58:52 -08:00
|
|
|
if !pubdash.IsEnabled {
|
2022-10-17 13:17:24 -08:00
|
|
|
ctxLogger.Error("GetPublicDashboard: Public dashboard is disabled", "accessToken", accessToken)
|
2022-08-26 01:21:52 -08:00
|
|
|
return nil, nil, ErrPublicDashboardNotFound
|
2022-06-02 18:27:23 -08:00
|
|
|
}
|
|
|
|
|
|
2022-08-26 01:21:52 -08:00
|
|
|
return pubdash, dash, nil
|
2022-06-02 18:27:23 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetPublicDashboardConfig is a helper method to retrieve the public dashboard configuration for a given dashboard from the database
|
2022-07-06 15:51:44 -08:00
|
|
|
func (pd *PublicDashboardServiceImpl) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error) {
|
|
|
|
|
pdc, err := pd.store.GetPublicDashboardConfig(ctx, orgId, dashboardUid)
|
2022-06-02 18:27:23 -08:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pdc, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SavePublicDashboardConfig is a helper method to persist the sharing config
|
|
|
|
|
// to the database. It handles validations for sharing config and persistence
|
2022-08-26 11:28:54 -08:00
|
|
|
func (pd *PublicDashboardServiceImpl) SavePublicDashboardConfig(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardConfigDTO) (*PublicDashboard, error) {
|
2022-09-28 15:34:53 -03:00
|
|
|
// validate if the dashboard exists
|
2022-07-21 13:56:20 -06:00
|
|
|
dashboard, err := pd.GetDashboard(ctx, dto.DashboardUid)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2022-08-26 11:28:54 -08:00
|
|
|
|
2022-06-22 13:58:52 -08:00
|
|
|
// set default value for time settings
|
|
|
|
|
if dto.PublicDashboard.TimeSettings == nil {
|
2022-09-13 13:33:41 -03:00
|
|
|
dto.PublicDashboard.TimeSettings = &TimeSettings{}
|
2022-06-02 18:27:23 -08:00
|
|
|
}
|
|
|
|
|
|
2022-08-26 11:28:54 -08:00
|
|
|
// get existing public dashboard if exists
|
|
|
|
|
existingPubdash, err := pd.store.GetPublicDashboardByUid(ctx, dto.PublicDashboard.Uid)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
2022-06-22 13:58:52 -08:00
|
|
|
}
|
2022-06-02 18:27:23 -08:00
|
|
|
|
2022-08-26 11:28:54 -08:00
|
|
|
// save changes
|
|
|
|
|
var pubdashUid string
|
|
|
|
|
if existingPubdash == nil {
|
2022-09-28 15:34:53 -03:00
|
|
|
err = validation.ValidateSavePublicDashboard(dto, dashboard)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2022-08-26 11:28:54 -08:00
|
|
|
pubdashUid, err = pd.savePublicDashboardConfig(ctx, dto)
|
|
|
|
|
} else {
|
|
|
|
|
pubdashUid, err = pd.updatePublicDashboardConfig(ctx, dto)
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Get latest public dashboard to return
|
|
|
|
|
newPubdash, err := pd.store.GetPublicDashboardByUid(ctx, pubdashUid)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pd.logIsEnabledChanged(existingPubdash, newPubdash, u)
|
|
|
|
|
|
|
|
|
|
return newPubdash, err
|
2022-06-22 13:58:52 -08:00
|
|
|
}
|
|
|
|
|
|
2022-08-26 11:28:54 -08:00
|
|
|
// Called by SavePublicDashboardConfig this handles business logic
|
|
|
|
|
// to generate token and calls create at the database layer
|
|
|
|
|
func (pd *PublicDashboardServiceImpl) savePublicDashboardConfig(ctx context.Context, dto *SavePublicDashboardConfigDTO) (string, error) {
|
2022-07-06 15:51:44 -08:00
|
|
|
uid, err := pd.store.GenerateNewPublicDashboardUid(ctx)
|
2022-06-02 18:27:23 -08:00
|
|
|
if err != nil {
|
2022-08-26 11:28:54 -08:00
|
|
|
return "", err
|
2022-06-02 18:27:23 -08:00
|
|
|
}
|
|
|
|
|
|
2022-10-13 11:32:32 -03:00
|
|
|
accessToken, err := pd.store.GenerateNewPublicDashboardAccessToken(ctx)
|
2022-06-13 17:23:56 -06:00
|
|
|
if err != nil {
|
2022-08-26 11:28:54 -08:00
|
|
|
return "", err
|
2022-06-13 17:23:56 -06:00
|
|
|
}
|
|
|
|
|
|
2022-07-06 15:51:44 -08:00
|
|
|
cmd := SavePublicDashboardConfigCommand{
|
|
|
|
|
PublicDashboard: PublicDashboard{
|
2022-06-22 13:58:52 -08:00
|
|
|
Uid: uid,
|
|
|
|
|
DashboardUid: dto.DashboardUid,
|
|
|
|
|
OrgId: dto.OrgId,
|
|
|
|
|
IsEnabled: dto.PublicDashboard.IsEnabled,
|
|
|
|
|
TimeSettings: dto.PublicDashboard.TimeSettings,
|
|
|
|
|
CreatedBy: dto.UserId,
|
|
|
|
|
CreatedAt: time.Now(),
|
|
|
|
|
AccessToken: accessToken,
|
|
|
|
|
},
|
2022-06-13 17:23:56 -06:00
|
|
|
}
|
|
|
|
|
|
2022-08-26 11:28:54 -08:00
|
|
|
err = pd.store.SavePublicDashboardConfig(ctx, cmd)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return uid, nil
|
2022-06-22 13:58:52 -08:00
|
|
|
}
|
|
|
|
|
|
2022-08-26 11:28:54 -08:00
|
|
|
// Called by SavePublicDashboard this handles business logic for updating a
|
|
|
|
|
// dashboard and calls update at the database layer
|
|
|
|
|
func (pd *PublicDashboardServiceImpl) updatePublicDashboardConfig(ctx context.Context, dto *SavePublicDashboardConfigDTO) (string, error) {
|
2022-07-06 15:51:44 -08:00
|
|
|
cmd := SavePublicDashboardConfigCommand{
|
|
|
|
|
PublicDashboard: PublicDashboard{
|
2022-06-22 13:58:52 -08:00
|
|
|
Uid: dto.PublicDashboard.Uid,
|
|
|
|
|
IsEnabled: dto.PublicDashboard.IsEnabled,
|
|
|
|
|
TimeSettings: dto.PublicDashboard.TimeSettings,
|
|
|
|
|
UpdatedBy: dto.UserId,
|
|
|
|
|
UpdatedAt: time.Now(),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-26 11:28:54 -08:00
|
|
|
return dto.PublicDashboard.Uid, pd.store.UpdatePublicDashboardConfig(ctx, cmd)
|
2022-06-22 13:58:52 -08:00
|
|
|
}
|
|
|
|
|
|
2022-09-07 12:08:52 -06:00
|
|
|
func (pd *PublicDashboardServiceImpl) GetQueryDataResponse(ctx context.Context, skipCache bool, queryDto PublicDashboardQueryDTO, panelId int64, accessToken string) (*backend.QueryDataResponse, error) {
|
2022-08-29 18:13:06 -03:00
|
|
|
publicDashboard, dashboard, err := pd.GetPublicDashboard(ctx, accessToken)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-07 12:08:52 -06:00
|
|
|
metricReq, err := pd.GetMetricRequest(ctx, dashboard, publicDashboard, panelId, queryDto)
|
2022-08-29 18:13:06 -03:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-18 10:47:24 -03:00
|
|
|
if len(metricReq.Queries) == 0 {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-17 13:17:24 -08:00
|
|
|
anonymousUser := pd.BuildAnonymousUser(ctx, dashboard)
|
2022-10-14 10:27:06 -04:00
|
|
|
res, err := pd.QueryDataService.QueryData(ctx, anonymousUser, skipCache, metricReq)
|
2022-09-14 13:19:21 -06:00
|
|
|
|
|
|
|
|
reqDatasources := metricReq.GetUniqueDatasourceTypes()
|
|
|
|
|
if err != nil {
|
2022-09-27 12:25:56 -06:00
|
|
|
LogQueryFailure(reqDatasources, pd.log, err)
|
2022-09-14 13:19:21 -06:00
|
|
|
return nil, err
|
|
|
|
|
}
|
2022-09-27 12:25:56 -06:00
|
|
|
LogQuerySuccess(reqDatasources, pd.log)
|
2022-09-14 13:19:21 -06:00
|
|
|
|
2022-09-19 12:44:29 -03:00
|
|
|
queries.SanitizeMetadataFromQueryData(res)
|
2022-09-27 12:25:56 -06:00
|
|
|
|
2022-09-14 13:19:21 -06:00
|
|
|
return res, nil
|
2022-09-07 12:08:52 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (pd *PublicDashboardServiceImpl) GetMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *PublicDashboard, panelId int64, queryDto PublicDashboardQueryDTO) (dtos.MetricRequest, error) {
|
2022-10-17 13:17:24 -08:00
|
|
|
err := validation.ValidateQueryPublicDashboardRequest(queryDto)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return dtos.MetricRequest{}, err
|
2022-09-07 12:08:52 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
metricReqDTO, err := pd.buildMetricRequest(
|
|
|
|
|
ctx,
|
|
|
|
|
dashboard,
|
|
|
|
|
publicDashboard,
|
|
|
|
|
panelId,
|
|
|
|
|
queryDto,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return dtos.MetricRequest{}, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return metricReqDTO, nil
|
2022-08-29 18:13:06 -03:00
|
|
|
}
|
|
|
|
|
|
2022-10-18 19:48:20 -06:00
|
|
|
func (pd *PublicDashboardServiceImpl) GetAnnotations(ctx context.Context, reqDTO AnnotationsQueryDTO, accessToken string) ([]AnnotationEvent, error) {
|
|
|
|
|
_, dash, err := pd.GetPublicDashboard(ctx, accessToken)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
annoDto, err := UnmarshalDashboardAnnotations(dash.Data)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
anonymousUser := pd.BuildAnonymousUser(ctx, dash)
|
|
|
|
|
|
|
|
|
|
uniqueEvents := make(map[int64]AnnotationEvent, 0)
|
|
|
|
|
for _, anno := range annoDto.Annotations.List {
|
|
|
|
|
// skip annotations that are not enabled or are not a grafana datasource
|
|
|
|
|
if !anno.Enable || (*anno.Datasource.Uid != grafanads.DatasourceUID && *anno.Datasource.Uid != grafanads.DatasourceName) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
annoQuery := &annotations.ItemQuery{
|
|
|
|
|
From: reqDTO.From,
|
|
|
|
|
To: reqDTO.To,
|
|
|
|
|
OrgId: dash.OrgId,
|
|
|
|
|
DashboardId: dash.Id,
|
|
|
|
|
DashboardUid: dash.Uid,
|
|
|
|
|
Limit: anno.Target.Limit,
|
|
|
|
|
MatchAny: anno.Target.MatchAny,
|
|
|
|
|
SignedInUser: anonymousUser,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if anno.Target.Type == "tags" {
|
|
|
|
|
annoQuery.DashboardId = 0
|
|
|
|
|
annoQuery.Tags = anno.Target.Tags
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
annotationItems, err := pd.AnnotationsRepo.Find(ctx, annoQuery)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, item := range annotationItems {
|
|
|
|
|
event := AnnotationEvent{
|
|
|
|
|
Id: item.Id,
|
|
|
|
|
DashboardId: item.DashboardId,
|
|
|
|
|
Tags: item.Tags,
|
|
|
|
|
IsRegion: item.TimeEnd > 0 && item.Time != item.TimeEnd,
|
|
|
|
|
Text: item.Text,
|
|
|
|
|
Color: *anno.IconColor,
|
|
|
|
|
Time: item.Time,
|
|
|
|
|
TimeEnd: item.TimeEnd,
|
|
|
|
|
Source: anno,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We want dashboard annotations to reference the panel they're for. If no panelId is provided, they'll show up on all panels
|
|
|
|
|
// which is only intended for tag and org annotations.
|
|
|
|
|
if anno.Type == "dashboard" {
|
|
|
|
|
event.PanelId = item.PanelId
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We want events from tag queries to overwrite existing events
|
|
|
|
|
_, has := uniqueEvents[event.Id]
|
|
|
|
|
if !has || (has && anno.Target.Type == "tags") {
|
|
|
|
|
uniqueEvents[event.Id] = event
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var results []AnnotationEvent
|
|
|
|
|
for _, result := range uniqueEvents {
|
|
|
|
|
results = append(results, result)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return results, nil
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-07 12:08:52 -06:00
|
|
|
// buildMetricRequest merges public dashboard parameters with
|
2022-08-29 18:13:06 -03:00
|
|
|
// dashboard and returns a metrics request to be sent to query backend
|
2022-09-07 12:08:52 -06:00
|
|
|
func (pd *PublicDashboardServiceImpl) buildMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *PublicDashboard, panelId int64, reqDTO PublicDashboardQueryDTO) (dtos.MetricRequest, error) {
|
2022-08-29 18:13:06 -03:00
|
|
|
// group queries by panel
|
2022-09-14 09:49:10 -06:00
|
|
|
queriesByPanel := queries.GroupQueriesByPanelId(dashboard.Data)
|
2022-08-29 18:13:06 -03:00
|
|
|
queries, ok := queriesByPanel[panelId]
|
|
|
|
|
if !ok {
|
2022-07-06 15:51:44 -08:00
|
|
|
return dtos.MetricRequest{}, ErrPublicDashboardPanelNotFound
|
2022-06-13 17:23:56 -06:00
|
|
|
}
|
|
|
|
|
|
2022-06-22 13:58:52 -08:00
|
|
|
ts := publicDashboard.BuildTimeSettings(dashboard)
|
|
|
|
|
|
2022-08-29 18:13:06 -03:00
|
|
|
// determine safe resolution to query data at
|
|
|
|
|
safeInterval, safeResolution := pd.getSafeIntervalAndMaxDataPoints(reqDTO, ts)
|
|
|
|
|
for i := range queries {
|
|
|
|
|
queries[i].Set("intervalMs", safeInterval)
|
|
|
|
|
queries[i].Set("maxDataPoints", safeResolution)
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-13 17:23:56 -06:00
|
|
|
return dtos.MetricRequest{
|
2022-06-22 13:58:52 -08:00
|
|
|
From: ts.From,
|
|
|
|
|
To: ts.To,
|
2022-08-29 18:13:06 -03:00
|
|
|
Queries: queries,
|
2022-06-13 17:23:56 -06:00
|
|
|
}, nil
|
|
|
|
|
}
|
2022-06-22 13:58:52 -08:00
|
|
|
|
2022-07-06 12:42:39 -06:00
|
|
|
// BuildAnonymousUser creates a user with permissions to read from all datasources used in the dashboard
|
2022-10-17 13:17:24 -08:00
|
|
|
func (pd *PublicDashboardServiceImpl) BuildAnonymousUser(ctx context.Context, dashboard *models.Dashboard) *user.SignedInUser {
|
2022-09-14 09:49:10 -06:00
|
|
|
datasourceUids := queries.GetUniqueDashboardDatasourceUids(dashboard.Data)
|
2022-07-06 12:42:39 -06:00
|
|
|
|
2022-10-18 19:48:20 -06:00
|
|
|
// Create a user with blank permissions
|
2022-08-11 13:28:55 +02:00
|
|
|
anonymousUser := &user.SignedInUser{OrgID: dashboard.OrgId, Permissions: make(map[int64]map[string][]string)}
|
2022-10-18 19:48:20 -06:00
|
|
|
|
|
|
|
|
// Scopes needed for Annotation queries
|
|
|
|
|
annotationScopes := []string{accesscontrol.ScopeAnnotationsTypeDashboard}
|
|
|
|
|
// Need to access all dashboards since tags annotations span across all dashboards
|
|
|
|
|
dashboardScopes := []string{dashboards.ScopeDashboardsProvider.GetResourceAllScope()}
|
|
|
|
|
|
|
|
|
|
// Scopes needed for datasource queries
|
2022-07-06 12:42:39 -06:00
|
|
|
queryScopes := make([]string, 0)
|
|
|
|
|
readScopes := make([]string, 0)
|
|
|
|
|
for _, uid := range datasourceUids {
|
2022-10-18 19:48:20 -06:00
|
|
|
scope := datasources.ScopeProvider.GetResourceScopeUID(uid)
|
|
|
|
|
queryScopes = append(queryScopes, scope)
|
|
|
|
|
readScopes = append(readScopes, scope)
|
2022-07-06 12:42:39 -06:00
|
|
|
}
|
2022-10-18 19:48:20 -06:00
|
|
|
|
|
|
|
|
// Apply all scopes to the actions we need the user to be able to perform
|
|
|
|
|
permissions := make(map[string][]string)
|
2022-07-06 12:42:39 -06:00
|
|
|
permissions[datasources.ActionQuery] = queryScopes
|
|
|
|
|
permissions[datasources.ActionRead] = readScopes
|
2022-10-18 19:48:20 -06:00
|
|
|
permissions[accesscontrol.ActionAnnotationsRead] = annotationScopes
|
|
|
|
|
permissions[dashboards.ActionDashboardsRead] = dashboardScopes
|
|
|
|
|
|
2022-07-06 12:42:39 -06:00
|
|
|
anonymousUser.Permissions[dashboard.OrgId] = permissions
|
|
|
|
|
|
2022-10-17 13:17:24 -08:00
|
|
|
return anonymousUser
|
2022-07-06 12:42:39 -06:00
|
|
|
}
|
|
|
|
|
|
2022-10-19 17:24:00 -03:00
|
|
|
// Gets a list of public dashboards by orgId
|
|
|
|
|
func (pd *PublicDashboardServiceImpl) ListPublicDashboards(ctx context.Context, u *user.SignedInUser, orgId int64) ([]PublicDashboardListResponse, error) {
|
|
|
|
|
publicDashboards, err := pd.store.ListPublicDashboards(ctx, orgId)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pd.filterDashboardsByPermissions(ctx, u, publicDashboards)
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-19 17:44:41 -06:00
|
|
|
func (pd *PublicDashboardServiceImpl) PublicDashboardEnabled(ctx context.Context, dashboardUid string) (bool, error) {
|
|
|
|
|
return pd.store.PublicDashboardEnabled(ctx, dashboardUid)
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-10 11:14:48 -06:00
|
|
|
func (pd *PublicDashboardServiceImpl) AccessTokenExists(ctx context.Context, accessToken string) (bool, error) {
|
|
|
|
|
return pd.store.AccessTokenExists(ctx, accessToken)
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-06 12:35:19 -08:00
|
|
|
func (pd *PublicDashboardServiceImpl) GetPublicDashboardOrgId(ctx context.Context, accessToken string) (int64, error) {
|
|
|
|
|
return pd.store.GetPublicDashboardOrgId(ctx, accessToken)
|
2022-06-22 13:58:52 -08:00
|
|
|
}
|
2022-08-26 11:28:54 -08:00
|
|
|
|
2022-08-29 18:13:06 -03:00
|
|
|
// intervalMS and maxQueryData values are being calculated on the frontend for regular dashboards
|
|
|
|
|
// we are doing the same for public dashboards but because this access would be public, we need a way to keep this
|
|
|
|
|
// values inside reasonable bounds to avoid an attack that could hit data sources with a small interval and a big
|
|
|
|
|
// time range and perform big calculations
|
|
|
|
|
// this is an additional validation, all data sources implements QueryData interface and should have proper validations
|
|
|
|
|
// of these limits
|
|
|
|
|
// for the maxDataPoints we took a hard limit from prometheus which is 11000
|
2022-09-07 12:08:52 -06:00
|
|
|
func (pd *PublicDashboardServiceImpl) getSafeIntervalAndMaxDataPoints(reqDTO PublicDashboardQueryDTO, ts TimeSettings) (int64, int64) {
|
2022-08-29 18:13:06 -03:00
|
|
|
// arbitrary max value for all data sources, it is actually a hard limit defined in prometheus
|
|
|
|
|
safeResolution := int64(11000)
|
|
|
|
|
|
|
|
|
|
// interval calculated on the frontend
|
|
|
|
|
interval := time.Duration(reqDTO.IntervalMs) * time.Millisecond
|
|
|
|
|
|
|
|
|
|
// calculate a safe interval with time range from dashboard and safeResolution
|
|
|
|
|
dataTimeRange := legacydata.NewDataTimeRange(ts.From, ts.To)
|
|
|
|
|
tr := backend.TimeRange{
|
|
|
|
|
From: dataTimeRange.GetFromAsTimeUTC(),
|
|
|
|
|
To: dataTimeRange.GetToAsTimeUTC(),
|
|
|
|
|
}
|
|
|
|
|
safeInterval := pd.intervalCalculator.CalculateSafeInterval(tr, safeResolution)
|
|
|
|
|
|
|
|
|
|
if interval > safeInterval.Value {
|
|
|
|
|
return reqDTO.IntervalMs, reqDTO.MaxDataPoints
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return safeInterval.Value.Milliseconds(), safeResolution
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-26 11:28:54 -08:00
|
|
|
// Log when PublicDashboard.IsEnabled changed
|
|
|
|
|
func (pd *PublicDashboardServiceImpl) logIsEnabledChanged(existingPubdash *PublicDashboard, newPubdash *PublicDashboard, u *user.SignedInUser) {
|
|
|
|
|
if publicDashboardIsEnabledChanged(existingPubdash, newPubdash) {
|
|
|
|
|
verb := "disabled"
|
|
|
|
|
if newPubdash.IsEnabled {
|
|
|
|
|
verb = "enabled"
|
|
|
|
|
}
|
2022-10-17 13:17:24 -08:00
|
|
|
pd.log.Info("Public dashboard "+verb, "publicDashboardUid", newPubdash.Uid, "dashboardUid", newPubdash.DashboardUid, "user", u.Login)
|
2022-08-26 11:28:54 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-19 17:24:00 -03:00
|
|
|
// Filter out dashboards that user does not have read access to
|
|
|
|
|
func (pd *PublicDashboardServiceImpl) filterDashboardsByPermissions(ctx context.Context, u *user.SignedInUser, publicDashboards []PublicDashboardListResponse) ([]PublicDashboardListResponse, error) {
|
|
|
|
|
result := make([]PublicDashboardListResponse, 0)
|
|
|
|
|
|
|
|
|
|
for i := range publicDashboards {
|
|
|
|
|
hasAccess, err := pd.ac.Evaluate(ctx, u, accesscontrol.EvalPermission(dashboards.ActionDashboardsRead, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(publicDashboards[i].DashboardUid)))
|
|
|
|
|
// If original dashboard does not exist, the public dashboard is an orphan. We want to list it anyway
|
|
|
|
|
if err != nil && !errors.Is(err, dashboards.ErrDashboardNotFound) {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If user has access to the original dashboard or the dashboard does not exist, add the pubdash to the result
|
|
|
|
|
if hasAccess || errors.Is(err, dashboards.ErrDashboardNotFound) {
|
|
|
|
|
result = append(result, publicDashboards[i])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Checks to see if PublicDashboard.IsEnabled is true on create or changed on update
|
2022-08-26 11:28:54 -08:00
|
|
|
func publicDashboardIsEnabledChanged(existingPubdash *PublicDashboard, newPubdash *PublicDashboard) bool {
|
|
|
|
|
// creating dashboard, enabled true
|
|
|
|
|
newDashCreated := existingPubdash == nil && newPubdash.IsEnabled
|
|
|
|
|
// updating dashboard, enabled changed
|
|
|
|
|
isEnabledChanged := existingPubdash != nil && newPubdash.IsEnabled != existingPubdash.IsEnabled
|
|
|
|
|
return newDashCreated || isEnabledChanged
|
|
|
|
|
}
|