chore: dashboard locking ee query-service (#3856)

* chore: dashboard locking ee query-service

* chore: remove print statements

* chore: remove unused imports

* chore: no one is allowed to edit/delete the locked dashboard

---------

Co-authored-by: Srikanth Chekuri <srikanth@Srikanths-MacBook-Pro.local>
This commit is contained in:
Srikanth Chekuri 2023-11-02 22:52:50 +05:30 committed by GitHub
parent b5654c8bfa
commit 5e349d8294
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 93 additions and 7 deletions

View File

@ -160,6 +160,9 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew
router.HandleFunc("/api/v1/billing", am.AdminAccess(ah.getBilling)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/portal", am.AdminAccess(ah.portalSession)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/dashboards/{uuid}/lock", am.EditAccess(ah.lockDashboard)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/dashboards/{uuid}/unlock", am.EditAccess(ah.unlockDashboard)).Methods(http.MethodPut)
router.HandleFunc("/api/v2/licenses",
am.ViewAccess(ah.listLicensesV2)).
Methods(http.MethodGet)

View File

@ -0,0 +1,51 @@
package api
import (
"github.com/gorilla/mux"
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
"go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/common"
"go.signoz.io/signoz/pkg/query-service/model"
"net/http"
)
func (ah *APIHandler) lockDashboard(w http.ResponseWriter, r *http.Request) {
ah.lockUnlockDashboard(w, r, true)
}
func (ah *APIHandler) unlockDashboard(w http.ResponseWriter, r *http.Request) {
ah.lockUnlockDashboard(w, r, false)
}
func (ah *APIHandler) lockUnlockDashboard(w http.ResponseWriter, r *http.Request, lock bool) {
// Locking can only be done by the owner of the dashboard
// or an admin
// - Fetch the dashboard
// - Check if the user is the owner or an admin
// - If yes, lock/unlock the dashboard
// - If no, return 403
// Get the dashboard UUID from the request
uuid := mux.Vars(r)["uuid"]
dashboard, err := dashboards.GetDashboard(r.Context(), uuid)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, err.Error())
return
}
user := common.GetUserFromContext(r.Context())
if !auth.IsAdmin(user) || (dashboard.CreateBy != nil && *dashboard.CreateBy != user.Email) {
RespondError(w, &model.ApiError{Typ: model.ErrorForbidden, Err: err}, "You are not authorized to lock/unlock this dashboard")
return
}
// Lock/Unlock the dashboard
err = dashboards.LockUnlockDashboard(r.Context(), uuid, lock)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, err.Error())
return
}
ah.Respond(w, "Dashboard updated successfully")
}

View File

@ -128,6 +128,12 @@ func InitDB(dataSourceName string) (*sqlx.DB, error) {
return nil, fmt.Errorf("error in adding column updated_by to dashboards table: %s", err.Error())
}
locked := `ALTER TABLE dashboards ADD COLUMN locked INTEGER DEFAULT 0;`
_, err = db.Exec(locked)
if err != nil && !strings.Contains(err.Error(), "duplicate column name") {
return nil, fmt.Errorf("error in adding column locked to dashboards table: %s", err.Error())
}
return db, nil
}
@ -141,6 +147,7 @@ type Dashboard struct {
UpdateBy *string `json:"updated_by" db:"updated_by"`
Title string `json:"-" db:"-"`
Data Data `json:"data" db:"data"`
Locked *int `json:"isLocked" db:"locked"`
}
type Data map[string]interface{}
@ -239,6 +246,12 @@ func DeleteDashboard(ctx context.Context, uuid string, fm interfaces.FeatureLook
return dErr
}
if user := common.GetUserFromContext(ctx); user != nil {
if dashboard.Locked != nil && *dashboard.Locked == 1 {
return model.BadRequest(fmt.Errorf("dashboard is locked, please unlock the dashboard to be able to delete it"))
}
}
query := `DELETE FROM dashboards WHERE uuid=?`
result, err := db.Exec(query, uuid)
@ -289,6 +302,14 @@ func UpdateDashboard(ctx context.Context, uuid string, data map[string]interface
return nil, apiErr
}
var userEmail string
if user := common.GetUserFromContext(ctx); user != nil {
userEmail = user.Email
if dashboard.Locked != nil && *dashboard.Locked == 1 {
return nil, model.BadRequest(fmt.Errorf("dashboard is locked, please unlock the dashboard to be able to edit it"))
}
}
// check if the count of trace and logs QB panel has changed, if yes, then check feature flag count
existingCount, existingTotal := countTraceAndLogsPanel(dashboard.Data)
newCount, newTotal := countTraceAndLogsPanel(data)
@ -306,10 +327,6 @@ func UpdateDashboard(ctx context.Context, uuid string, data map[string]interface
}
dashboard.UpdatedAt = time.Now()
var userEmail string
if user := common.GetUserFromContext(ctx); user != nil {
userEmail = user.Email
}
dashboard.UpdateBy = &userEmail
dashboard.Data = data
@ -327,6 +344,24 @@ func UpdateDashboard(ctx context.Context, uuid string, data map[string]interface
return dashboard, nil
}
func LockUnlockDashboard(ctx context.Context, uuid string, lock bool) *model.ApiError {
var query string
if lock {
query = `UPDATE dashboards SET locked=1 WHERE uuid=?;`
} else {
query = `UPDATE dashboards SET locked=0 WHERE uuid=?;`
}
_, err := db.Exec(query, uuid)
if err != nil {
zap.S().Errorf("Error in updating dashboard: ", uuid, err)
return &model.ApiError{Typ: model.ErrorExec, Err: err}
}
return nil
}
func updateFeatureUsage(fm interfaces.FeatureLookup, usage int64) *model.ApiError {
feature, err := fm.GetFeatureFlag(model.QueryBuilderPanels)
if err != nil {

View File

@ -91,7 +91,6 @@ func ParseMetricAutocompleteTagParams(r *http.Request) (*model.MetricAutocomplet
}
tagsStr := r.URL.Query().Get("tags")
// fmt.Println(tagsStr)
// parsing tags
var tags map[string]string

View File

@ -2,7 +2,6 @@ package sqlite
import (
"context"
"fmt"
"github.com/jmoiron/sqlx"
"go.signoz.io/signoz/pkg/query-service/model"
@ -51,7 +50,6 @@ func (mds *ModelDaoSqlite) GetApdexSettings(ctx context.Context, services []stri
func (mds *ModelDaoSqlite) SetApdexSettings(ctx context.Context, apdexSettings *model.ApdexSettings) *model.ApiError {
fmt.Println("apdexSettings:", apdexSettings)
_, err := mds.db.NamedExec(`
INSERT OR REPLACE INTO apdex_settings (
service_name,