| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | package legacy | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"database/sql" | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2024-08-27 17:12:22 +08:00
										 |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-12 14:26:53 +08:00
										 |  |  | 	"github.com/grafana/authlib/claims" | 
					
						
							| 
									
										
										
										
											2024-09-24 14:03:48 +08:00
										 |  |  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 
					
						
							|  |  |  | 	"k8s.io/utils/ptr" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-27 17:12:22 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/apimachinery/identity" | 
					
						
							| 
									
										
										
										
											2024-06-12 19:39:37 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/apimachinery/utils" | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	dashboardsV0 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/components/simplejson" | 
					
						
							| 
									
										
										
										
											2024-02-02 06:27:30 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" | 
					
						
							| 
									
										
										
										
											2024-06-12 19:39:37 +08:00
										 |  |  | 	gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils" | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/dashboards" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/services/provisioning" | 
					
						
							| 
									
										
										
										
											2024-08-15 19:38:43 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/storage/legacysql" | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/storage/unified/resource" | 
					
						
							| 
									
										
										
										
											2024-08-07 18:43:13 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	_ DashboardAccess = (*dashboardSqlAccess)(nil) | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type dashboardRow struct { | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 	// The numeric version for this dashboard
 | 
					
						
							|  |  |  | 	RV int64 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	// Dashboard resource
 | 
					
						
							|  |  |  | 	Dash *dashboardsV0.Dashboard | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// The folder UID (needed for access control checks)
 | 
					
						
							|  |  |  | 	FolderUID string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// The token we can use that will start a new connection that includes
 | 
					
						
							|  |  |  | 	// this same dashboard
 | 
					
						
							|  |  |  | 	token *continueToken | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type dashboardSqlAccess struct { | 
					
						
							| 
									
										
										
										
											2024-08-19 17:15:43 +08:00
										 |  |  | 	sql          legacysql.LegacyDatabaseProvider | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	namespacer   request.NamespaceMapper | 
					
						
							|  |  |  | 	provisioning provisioning.ProvisioningService | 
					
						
							| 
									
										
										
										
											2024-08-19 17:15:43 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Use for writing (not reading)
 | 
					
						
							| 
									
										
										
										
											2024-08-27 17:12:22 +08:00
										 |  |  | 	dashStore  dashboards.Store | 
					
						
							|  |  |  | 	softDelete bool | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Typically one... the server wrapper
 | 
					
						
							|  |  |  | 	subscribers []chan *resource.WrittenEvent | 
					
						
							|  |  |  | 	mutex       sync.Mutex | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-19 17:15:43 +08:00
										 |  |  | func NewDashboardAccess(sql legacysql.LegacyDatabaseProvider, | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 	namespacer request.NamespaceMapper, | 
					
						
							|  |  |  | 	dashStore dashboards.Store, | 
					
						
							|  |  |  | 	provisioning provisioning.ProvisioningService, | 
					
						
							| 
									
										
										
										
											2024-08-27 17:12:22 +08:00
										 |  |  | 	softDelete bool, | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | ) DashboardAccess { | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	return &dashboardSqlAccess{ | 
					
						
							| 
									
										
										
										
											2024-08-19 17:15:43 +08:00
										 |  |  | 		sql:          sql, | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 		namespacer:   namespacer, | 
					
						
							|  |  |  | 		dashStore:    dashStore, | 
					
						
							|  |  |  | 		provisioning: provisioning, | 
					
						
							| 
									
										
										
										
											2024-08-27 17:12:22 +08:00
										 |  |  | 		softDelete:   softDelete, | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-19 17:15:43 +08:00
										 |  |  | func (a *dashboardSqlAccess) getRows(ctx context.Context, sql *legacysql.LegacyDatabaseHelper, query *DashboardQuery) (*rowsWrapper, error) { | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 	if len(query.Labels) > 0 { | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | 		return nil, fmt.Errorf("labels not yet supported") | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 		// if query.Requirements.Folder != nil {
 | 
					
						
							|  |  |  | 		// 	args = append(args, *query.Requirements.Folder)
 | 
					
						
							| 
									
										
										
										
											2024-08-07 18:43:13 +08:00
										 |  |  | 		// 	sqlcmd = fmt.Sprintf("%s AND dashboard.folder_uid=?$%d", sqlcmd, len(args))
 | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 		// }
 | 
					
						
							| 
									
										
										
										
											2024-06-05 16:21:51 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-19 17:15:43 +08:00
										 |  |  | 	req := newQueryReq(sql, query) | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-07 18:43:13 +08:00
										 |  |  | 	tmpl := sqlQueryDashboards | 
					
						
							|  |  |  | 	if query.UseHistoryTable() && query.GetTrash { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("trash not included in history table") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-06-05 16:21:51 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-07 18:43:13 +08:00
										 |  |  | 	rawQuery, err := sqltemplate.Execute(tmpl, req) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("execute template %q: %w", tmpl.Name(), err) | 
					
						
							| 
									
										
										
										
											2024-06-05 16:21:51 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-08-07 18:43:13 +08:00
										 |  |  | 	q := rawQuery | 
					
						
							|  |  |  | 	// q = sqltemplate.RemoveEmptyLines(rawQuery)
 | 
					
						
							|  |  |  | 	// fmt.Printf(">>%s [%+v]", q, req.GetArgs())
 | 
					
						
							| 
									
										
										
										
											2024-06-05 16:21:51 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-19 17:15:43 +08:00
										 |  |  | 	rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) | 
					
						
							| 
									
										
										
										
											2024-06-05 16:21:51 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		if rows != nil { | 
					
						
							|  |  |  | 			_ = rows.Close() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		rows = nil | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	return &rowsWrapper{ | 
					
						
							|  |  |  | 		rows: rows, | 
					
						
							|  |  |  | 		a:    a, | 
					
						
							|  |  |  | 		// This looks up rules from the permissions on a user
 | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 		canReadDashboard: func(scopes ...string) bool { | 
					
						
							|  |  |  | 			return true // ???
 | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		// accesscontrol.Checker(user, dashboards.ActionDashboardsRead),
 | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	}, err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | var _ resource.ListIterator = (*rowsWrapper)(nil) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | type rowsWrapper struct { | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | 	a    *dashboardSqlAccess | 
					
						
							|  |  |  | 	rows *sql.Rows | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	canReadDashboard func(scopes ...string) bool | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Current
 | 
					
						
							|  |  |  | 	row *dashboardRow | 
					
						
							|  |  |  | 	err error | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (r *rowsWrapper) Close() error { | 
					
						
							| 
									
										
										
										
											2024-08-07 18:43:13 +08:00
										 |  |  | 	if r.rows == nil { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	return r.rows.Close() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | func (r *rowsWrapper) Next() bool { | 
					
						
							|  |  |  | 	if r.err != nil { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	var err error | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	// breaks after first readable value
 | 
					
						
							|  |  |  | 	for r.rows.Next() { | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | 		r.row, err = r.a.scanRow(r.rows) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			r.err = err | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if r.row != nil { | 
					
						
							|  |  |  | 			d := r.row | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 			// Access control checker
 | 
					
						
							|  |  |  | 			scopes := []string{dashboards.ScopeDashboardsProvider.GetResourceScopeUID(d.Dash.Name)} | 
					
						
							|  |  |  | 			if d.FolderUID != "" { // Copied from searchV2... not sure the logic is right
 | 
					
						
							|  |  |  | 				scopes = append(scopes, dashboards.ScopeFoldersProvider.GetResourceScopeUID(d.FolderUID)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if !r.canReadDashboard(scopes...) { | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | 			// returns the first folder it can
 | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ContinueToken implements resource.ListIterator.
 | 
					
						
							|  |  |  | func (r *rowsWrapper) ContinueToken() string { | 
					
						
							|  |  |  | 	return r.row.token.String() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Error implements resource.ListIterator.
 | 
					
						
							|  |  |  | func (r *rowsWrapper) Error() error { | 
					
						
							|  |  |  | 	return r.err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Name implements resource.ListIterator.
 | 
					
						
							|  |  |  | func (r *rowsWrapper) Name() string { | 
					
						
							|  |  |  | 	return r.row.Dash.Name | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Namespace implements resource.ListIterator.
 | 
					
						
							|  |  |  | func (r *rowsWrapper) Namespace() string { | 
					
						
							|  |  |  | 	return r.row.Dash.Namespace | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ResourceVersion implements resource.ListIterator.
 | 
					
						
							|  |  |  | func (r *rowsWrapper) ResourceVersion() int64 { | 
					
						
							|  |  |  | 	return r.row.RV | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Value implements resource.ListIterator.
 | 
					
						
							|  |  |  | func (r *rowsWrapper) Value() []byte { | 
					
						
							|  |  |  | 	b, err := json.Marshal(r.row.Dash) | 
					
						
							|  |  |  | 	r.err = err | 
					
						
							|  |  |  | 	return b | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (a *dashboardSqlAccess) scanRow(rows *sql.Rows) (*dashboardRow, error) { | 
					
						
							|  |  |  | 	dash := &dashboardsV0.Dashboard{ | 
					
						
							|  |  |  | 		TypeMeta:   dashboardsV0.DashboardResourceInfo.TypeMeta(), | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 		ObjectMeta: metav1.ObjectMeta{Annotations: make(map[string]string)}, | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	row := &dashboardRow{Dash: dash} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var dashboard_id int64 | 
					
						
							|  |  |  | 	var orgId int64 | 
					
						
							|  |  |  | 	var folder_uid sql.NullString | 
					
						
							|  |  |  | 	var updated time.Time | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 	var updatedBy sql.NullString | 
					
						
							| 
									
										
										
										
											2024-08-07 18:43:13 +08:00
										 |  |  | 	var updatedByID sql.NullInt64 | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 	var deleted sql.NullTime | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	var created time.Time | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 	var createdBy sql.NullString | 
					
						
							| 
									
										
										
										
											2024-08-07 18:43:13 +08:00
										 |  |  | 	var createdByID sql.NullInt64 | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 	var message sql.NullString | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	var plugin_id string | 
					
						
							|  |  |  | 	var origin_name sql.NullString | 
					
						
							|  |  |  | 	var origin_path sql.NullString | 
					
						
							|  |  |  | 	var origin_ts sql.NullInt64 | 
					
						
							| 
									
										
										
										
											2024-06-19 03:27:16 +08:00
										 |  |  | 	var origin_hash sql.NullString | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	var data []byte // the dashboard JSON
 | 
					
						
							|  |  |  | 	var version int64 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 	err := rows.Scan(&orgId, &dashboard_id, &dash.Name, &folder_uid, | 
					
						
							|  |  |  | 		&deleted, &plugin_id, | 
					
						
							| 
									
										
										
										
											2024-06-19 03:27:16 +08:00
										 |  |  | 		&origin_name, &origin_path, &origin_hash, &origin_ts, | 
					
						
							| 
									
										
										
										
											2024-08-07 18:43:13 +08:00
										 |  |  | 		&created, &createdBy, &createdByID, | 
					
						
							|  |  |  | 		&updated, &updatedBy, &updatedByID, | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 		&version, &message, &data, | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	row.token = &continueToken{orgId: orgId, id: dashboard_id} | 
					
						
							|  |  |  | 	if err == nil { | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 		row.RV = getResourceVersion(dashboard_id, version) | 
					
						
							|  |  |  | 		dash.ResourceVersion = fmt.Sprintf("%d", row.RV) | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 		dash.Namespace = a.namespacer(orgId) | 
					
						
							| 
									
										
										
										
											2024-06-12 19:39:37 +08:00
										 |  |  | 		dash.UID = gapiutil.CalculateClusterWideUID(dash) | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 		dash.SetCreationTimestamp(metav1.NewTime(created)) | 
					
						
							| 
									
										
										
										
											2024-01-13 05:18:14 +08:00
										 |  |  | 		meta, err := utils.MetaAccessor(dash) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 		meta.SetUpdatedTimestamp(&updated) | 
					
						
							| 
									
										
										
										
											2024-08-07 18:43:13 +08:00
										 |  |  | 		meta.SetCreatedBy(getUserID(createdBy, createdByID)) | 
					
						
							|  |  |  | 		meta.SetUpdatedBy(getUserID(updatedBy, updatedByID)) | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		if deleted.Valid { | 
					
						
							|  |  |  | 			meta.SetDeletionTimestamp(ptr.To(metav1.NewTime(deleted.Time))) | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		if message.String != "" { | 
					
						
							|  |  |  | 			meta.SetMessage(message.String) | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 		if folder_uid.String != "" { | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 			meta.SetFolder(folder_uid.String) | 
					
						
							|  |  |  | 			row.FolderUID = folder_uid.String | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 		if origin_name.String != "" { | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 			ts := time.Unix(origin_ts.Int64, 0) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-26 18:32:56 +08:00
										 |  |  | 			resolvedPath := a.provisioning.GetDashboardProvisionerResolvedPath(origin_name.String) | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 			originPath, err := filepath.Rel( | 
					
						
							| 
									
										
										
										
											2024-03-26 18:32:56 +08:00
										 |  |  | 				resolvedPath, | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 				origin_path.String, | 
					
						
							|  |  |  | 			) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return nil, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-13 05:18:14 +08:00
										 |  |  | 			meta.SetOriginInfo(&utils.ResourceOriginInfo{ | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 				Name:      origin_name.String, | 
					
						
							|  |  |  | 				Path:      originPath, | 
					
						
							| 
									
										
										
										
											2024-06-19 03:27:16 +08:00
										 |  |  | 				Hash:      origin_hash.String, | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 				Timestamp: &ts, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		} else if plugin_id != "" { | 
					
						
							| 
									
										
										
										
											2024-01-13 05:18:14 +08:00
										 |  |  | 			meta.SetOriginInfo(&utils.ResourceOriginInfo{ | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 				Name: "plugin", | 
					
						
							|  |  |  | 				Path: plugin_id, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | 		if len(data) > 0 { | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 			err = dash.Spec.UnmarshalJSON(data) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return row, err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | 		// add it so we can get it from the body later
 | 
					
						
							|  |  |  | 		dash.Spec.Set("id", dashboard_id) | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return row, err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-07 18:43:13 +08:00
										 |  |  | func getUserID(v sql.NullString, id sql.NullInt64) string { | 
					
						
							|  |  |  | 	if v.Valid && v.String != "" { | 
					
						
							| 
									
										
										
										
											2024-08-13 16:18:28 +08:00
										 |  |  | 		return identity.NewTypedIDString(claims.TypeUser, v.String) | 
					
						
							| 
									
										
										
										
											2024-08-07 18:43:13 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	if id.Valid && id.Int64 == -1 { | 
					
						
							| 
									
										
										
										
											2024-08-13 16:18:28 +08:00
										 |  |  | 		return identity.NewTypedIDString(claims.TypeProvisioning, "") | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-08-07 18:43:13 +08:00
										 |  |  | 	return "" | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | // DeleteDashboard implements DashboardAccess.
 | 
					
						
							|  |  |  | func (a *dashboardSqlAccess) DeleteDashboard(ctx context.Context, orgId int64, uid string) (*dashboardsV0.Dashboard, bool, error) { | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 	dash, _, err := a.GetDashboard(ctx, orgId, uid, 0) | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, false, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-27 17:12:22 +08:00
										 |  |  | 	if a.softDelete { | 
					
						
							|  |  |  | 		err = a.dashStore.SoftDeleteDashboard(ctx, orgId, uid) | 
					
						
							|  |  |  | 		if err == nil && dash != nil { | 
					
						
							|  |  |  | 			now := metav1.NewTime(time.Now()) | 
					
						
							|  |  |  | 			dash.DeletionTimestamp = &now | 
					
						
							|  |  |  | 			return dash, true, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return dash, false, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	id := dash.Spec.GetNestedInt64("id") | 
					
						
							|  |  |  | 	if id == 0 { | 
					
						
							|  |  |  | 		return nil, false, fmt.Errorf("could not find id in saved body") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = a.dashStore.DeleteDashboard(ctx, &dashboards.DeleteDashboardCommand{ | 
					
						
							|  |  |  | 		OrgID: orgId, | 
					
						
							|  |  |  | 		ID:    id, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, false, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return dash, true, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // SaveDashboard implements DashboardAccess.
 | 
					
						
							|  |  |  | func (a *dashboardSqlAccess) SaveDashboard(ctx context.Context, orgId int64, dash *dashboardsV0.Dashboard) (*dashboardsV0.Dashboard, bool, error) { | 
					
						
							|  |  |  | 	created := false | 
					
						
							| 
									
										
										
										
											2024-09-24 14:03:48 +08:00
										 |  |  | 	user, ok := claims.From(ctx) | 
					
						
							|  |  |  | 	if !ok || user == nil { | 
					
						
							|  |  |  | 		return nil, created, fmt.Errorf("no user found in context") | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-09-24 14:03:48 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	if dash.Name != "" { | 
					
						
							|  |  |  | 		dash.Spec.Set("uid", dash.Name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Get the previous version to set the internal ID
 | 
					
						
							|  |  |  | 		old, _ := a.dashStore.GetDashboard(ctx, &dashboards.GetDashboardQuery{ | 
					
						
							|  |  |  | 			OrgID: orgId, | 
					
						
							|  |  |  | 			UID:   dash.Name, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		if old != nil { | 
					
						
							|  |  |  | 			dash.Spec.Set("id", old.ID) | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			dash.Spec.Remove("id") // existing of "id" makes it an update
 | 
					
						
							|  |  |  | 			created = true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		dash.Spec.Remove("id") | 
					
						
							|  |  |  | 		dash.Spec.Remove("uid") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-13 16:18:28 +08:00
										 |  |  | 	var userID int64 | 
					
						
							| 
									
										
										
										
											2024-09-24 14:03:48 +08:00
										 |  |  | 	idClaims := user.GetIdentity() | 
					
						
							|  |  |  | 	if claims.IsIdentityType(idClaims.IdentityType(), claims.TypeUser) { | 
					
						
							|  |  |  | 		var err error | 
					
						
							|  |  |  | 		userID, err = identity.UserIdentifier(idClaims.Subject()) | 
					
						
							| 
									
										
										
										
											2024-08-13 16:18:28 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, false, err | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-07-26 21:39:23 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-13 05:18:14 +08:00
										 |  |  | 	meta, err := utils.MetaAccessor(dash) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, false, err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	out, err := a.dashStore.SaveDashboard(ctx, dashboards.SaveDashboardCommand{ | 
					
						
							|  |  |  | 		OrgID:     orgId, | 
					
						
							|  |  |  | 		Dashboard: simplejson.NewFromAny(dash.Spec.UnstructuredContent()), | 
					
						
							|  |  |  | 		FolderUID: meta.GetFolder(), | 
					
						
							|  |  |  | 		Overwrite: true, // already passed the revisionVersion checks!
 | 
					
						
							| 
									
										
										
										
											2024-07-26 21:39:23 +08:00
										 |  |  | 		UserID:    userID, | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, false, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if out != nil { | 
					
						
							|  |  |  | 		created = (out.Created.Unix() == out.Updated.Unix()) // and now?
 | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-07-18 01:49:23 +08:00
										 |  |  | 	dash, _, err = a.GetDashboard(ctx, orgId, out.UID, 0) | 
					
						
							| 
									
										
										
										
											2024-01-11 07:20:30 +08:00
										 |  |  | 	return dash, created, err | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-08-27 17:12:22 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func (a *dashboardSqlAccess) GetLibraryPanels(ctx context.Context, query LibraryPanelQuery) (*dashboardsV0.LibraryPanelList, error) { | 
					
						
							|  |  |  | 	limit := int(query.Limit) | 
					
						
							|  |  |  | 	query.Limit += 1 // for continue
 | 
					
						
							|  |  |  | 	if query.OrgID == 0 { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("expected non zero orgID") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sql, err := a.sql(ctx) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	req := newLibraryQueryReq(sql, &query) | 
					
						
							|  |  |  | 	rawQuery, err := sqltemplate.Execute(sqlQueryPanels, req) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("execute template %q: %w", sqlQueryPanels.Name(), err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	q := rawQuery | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	res := &dashboardsV0.LibraryPanelList{} | 
					
						
							|  |  |  | 	rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) | 
					
						
							|  |  |  | 	defer func() { | 
					
						
							|  |  |  | 		if rows != nil { | 
					
						
							|  |  |  | 			_ = rows.Close() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	type panel struct { | 
					
						
							|  |  |  | 		ID        int64 | 
					
						
							|  |  |  | 		UID       string | 
					
						
							|  |  |  | 		FolderUID string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		Created   time.Time | 
					
						
							|  |  |  | 		CreatedBy string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		Updated   time.Time | 
					
						
							|  |  |  | 		UpdatedBy string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		Name        string | 
					
						
							|  |  |  | 		Type        string | 
					
						
							|  |  |  | 		Description string | 
					
						
							|  |  |  | 		Model       []byte | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var lastID int64 | 
					
						
							|  |  |  | 	for rows.Next() { | 
					
						
							|  |  |  | 		p := panel{} | 
					
						
							|  |  |  | 		err = rows.Scan(&p.ID, &p.UID, &p.FolderUID, | 
					
						
							|  |  |  | 			&p.Created, &p.CreatedBy, | 
					
						
							|  |  |  | 			&p.Updated, &p.UpdatedBy, | 
					
						
							|  |  |  | 			&p.Name, &p.Type, &p.Description, &p.Model, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return res, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		lastID = p.ID | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		item := dashboardsV0.LibraryPanel{ | 
					
						
							|  |  |  | 			ObjectMeta: metav1.ObjectMeta{ | 
					
						
							|  |  |  | 				Name:              p.UID, | 
					
						
							|  |  |  | 				CreationTimestamp: metav1.NewTime(p.Created), | 
					
						
							|  |  |  | 				ResourceVersion:   strconv.FormatInt(p.Updated.UnixMilli(), 10), | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			Spec: dashboardsV0.LibraryPanelSpec{}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		status := &dashboardsV0.LibraryPanelStatus{ | 
					
						
							|  |  |  | 			Missing: v0alpha1.Unstructured{}, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		err = json.Unmarshal(p.Model, &item.Spec) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		err = json.Unmarshal(p.Model, &status.Missing.Object) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if item.Spec.Title != p.Name { | 
					
						
							|  |  |  | 			status.Warnings = append(item.Status.Warnings, fmt.Sprintf("title mismatch (expected: %s)", p.Name)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if item.Spec.Description != p.Description { | 
					
						
							|  |  |  | 			status.Warnings = append(item.Status.Warnings, fmt.Sprintf("description mismatch (expected: %s)", p.Description)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if item.Spec.Type != p.Type { | 
					
						
							|  |  |  | 			status.Warnings = append(item.Status.Warnings, fmt.Sprintf("type mismatch (expected: %s)", p.Type)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		item.Status = status | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Remove the properties we are already showing
 | 
					
						
							|  |  |  | 		for _, k := range []string{"type", "pluginVersion", "title", "description", "options", "fieldConfig", "datasource", "targets", "libraryPanel"} { | 
					
						
							|  |  |  | 			delete(status.Missing.Object, k) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		meta, err := utils.MetaAccessor(&item) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		meta.SetFolder(p.FolderUID) | 
					
						
							|  |  |  | 		meta.SetCreatedBy(p.CreatedBy) | 
					
						
							|  |  |  | 		meta.SetUpdatedBy(p.UpdatedBy) | 
					
						
							|  |  |  | 		meta.SetUpdatedTimestamp(&p.Updated) | 
					
						
							|  |  |  | 		meta.SetOriginInfo(&utils.ResourceOriginInfo{ | 
					
						
							|  |  |  | 			Name: "SQL", | 
					
						
							|  |  |  | 			Path: strconv.FormatInt(p.ID, 10), | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		res.Items = append(res.Items, item) | 
					
						
							|  |  |  | 		if len(res.Items) > limit { | 
					
						
							|  |  |  | 			res.Continue = strconv.FormatInt(lastID, 10) | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if query.UID == "" { | 
					
						
							|  |  |  | 		rv, err := sql.GetResourceVersion(ctx, "library_element", "updated") | 
					
						
							|  |  |  | 		if err == nil { | 
					
						
							|  |  |  | 			res.ResourceVersion = strconv.FormatInt(rv, 10) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return res, err | 
					
						
							|  |  |  | } |