mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 18:13:09 +08:00
Working on collaborators
This commit is contained in:
2
grafana
2
grafana
Submodule grafana updated: e5fd35db34...c65b7d1591
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
"github.com/torkelo/grafana-pro/pkg/components"
|
"github.com/torkelo/grafana-pro/pkg/components"
|
||||||
|
"github.com/torkelo/grafana-pro/pkg/models"
|
||||||
"github.com/torkelo/grafana-pro/pkg/stores"
|
"github.com/torkelo/grafana-pro/pkg/stores"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -61,9 +62,9 @@ func (self *HttpServer) ListenAndServe() {
|
|||||||
|
|
||||||
func (self *HttpServer) index(c *gin.Context) {
|
func (self *HttpServer) index(c *gin.Context) {
|
||||||
viewModel := &IndexDto{}
|
viewModel := &IndexDto{}
|
||||||
login, _ := c.Get("login")
|
userAccount, _ := c.Get("userAccount")
|
||||||
if login != nil {
|
if userAccount != nil {
|
||||||
viewModel.User.Login = login.(string)
|
viewModel.User.Login = userAccount.(*models.UserAccount).Login
|
||||||
}
|
}
|
||||||
|
|
||||||
c.HTML(200, "index.html", viewModel)
|
c.HTML(200, "index.html", viewModel)
|
||||||
@ -74,12 +75,3 @@ func CacheHeadersMiddleware() gin.HandlerFunc {
|
|||||||
c.Writer.Header().Add("Cache-Control", "max-age=0, public, must-revalidate, proxy-revalidate")
|
c.Writer.Header().Add("Cache-Control", "max-age=0, public, must-revalidate, proxy-revalidate")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Api Handler Registration
|
|
||||||
var routeHandlers = make([]routeHandlerRegisterFn, 0)
|
|
||||||
|
|
||||||
type routeHandlerRegisterFn func(self *HttpServer)
|
|
||||||
|
|
||||||
func addRoutes(fn routeHandlerRegisterFn) {
|
|
||||||
routeHandlers = append(routeHandlers, fn)
|
|
||||||
}
|
|
||||||
|
@ -4,7 +4,7 @@ import "github.com/gin-gonic/gin"
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
addRoutes(func(self *HttpServer) {
|
addRoutes(func(self *HttpServer) {
|
||||||
self.router.POST("/api/account/collaborators/add", self.auth(), self.addCollaborator)
|
self.addRoute("POST", "/api/account/collaborators/add", self.addCollaborator)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -12,28 +12,34 @@ type addCollaboratorDto struct {
|
|||||||
Email string `json:"email" binding:"required"`
|
Email string `json:"email" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *HttpServer) addCollaborator(c *gin.Context) {
|
func (self *HttpServer) addCollaborator(c *gin.Context, auth *authContext) {
|
||||||
var model addCollaboratorDto
|
var model addCollaboratorDto
|
||||||
|
|
||||||
if !c.EnsureBody(&model) {
|
if !c.EnsureBody(&model) {
|
||||||
c.JSON(400, gin.H{"status": "bad request"})
|
c.JSON(400, gin.H{"status": "Collaborator not found"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
accountId, _ := c.Get("accountId")
|
|
||||||
account, err := self.store.GetAccount(accountId.(int))
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(401, gin.H{"status": "Authentication error"})
|
|
||||||
}
|
|
||||||
|
|
||||||
collaborator, err := self.store.GetUserAccountLogin(model.Email)
|
collaborator, err := self.store.GetUserAccountLogin(model.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(404, gin.H{"status": "Collaborator not found"})
|
c.JSON(404, gin.H{"status": "Collaborator not found"})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
account.AddCollaborator(collaborator.Id)
|
userAccount := auth.userAccount
|
||||||
|
|
||||||
self.store.SaveUserAccount(account)
|
if collaborator.Id == userAccount.Id {
|
||||||
|
c.JSON(400, gin.H{"status": "Cannot add yourself as collaborator"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = userAccount.AddCollaborator(collaborator.Id)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"status": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.store.SaveUserAccount(userAccount)
|
||||||
|
|
||||||
c.JSON(200, gin.H{"status": "Collaborator added"})
|
c.JSON(200, gin.H{"status": "Collaborator added"})
|
||||||
}
|
}
|
||||||
|
48
pkg/api/api_auth.go
Normal file
48
pkg/api/api_auth.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/torkelo/grafana-pro/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type authContext struct {
|
||||||
|
account *models.UserAccount
|
||||||
|
userAccount *models.UserAccount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *authContext) getAccountId() int {
|
||||||
|
return auth.account.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *HttpServer) authDenied(c *gin.Context) {
|
||||||
|
c.Writer.Header().Set("Location", "/login")
|
||||||
|
c.Abort(302)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *HttpServer) auth() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
session, _ := sessionStore.Get(c.Request, "grafana-session")
|
||||||
|
|
||||||
|
if c.Request.URL.Path != "/login" && session.Values["userAccountId"] == nil {
|
||||||
|
self.authDenied(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
account, err := self.store.GetAccount(session.Values["userAccountId"].(int))
|
||||||
|
if err != nil {
|
||||||
|
self.authDenied(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
usingAccount, err := self.store.GetAccount(session.Values["usingAccountId"].(int))
|
||||||
|
if err != nil {
|
||||||
|
self.authDenied(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("userAccount", account)
|
||||||
|
c.Set("usingAccount", usingAccount)
|
||||||
|
|
||||||
|
session.Save(c.Request, c.Writer)
|
||||||
|
}
|
||||||
|
}
|
@ -8,18 +8,17 @@ import (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
addRoutes(func(self *HttpServer) {
|
addRoutes(func(self *HttpServer) {
|
||||||
self.router.GET("/api/dashboards/:slug", self.auth(), self.getDashboard)
|
self.addRoute("GET", "/api/dashboards/:slug", self.getDashboard)
|
||||||
self.router.GET("/api/search/", self.auth(), self.search)
|
self.addRoute("GET", "/api/search/", self.search)
|
||||||
self.router.POST("/api/dashboard", self.auth(), self.postDashboard)
|
self.addRoute("POST", "/api/dashboard/", self.postDashboard)
|
||||||
self.router.DELETE("/api/dashboard/:slug", self.auth(), self.deleteDashboard)
|
self.addRoute("DELETE", "/api/dashboard/:slug", self.deleteDashboard)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *HttpServer) getDashboard(c *gin.Context) {
|
func (self *HttpServer) getDashboard(c *gin.Context, auth *authContext) {
|
||||||
slug := c.Params.ByName("slug")
|
slug := c.Params.ByName("slug")
|
||||||
accountId, err := c.Get("accountId")
|
|
||||||
|
|
||||||
dash, err := self.store.GetDashboard(slug, accountId.(int))
|
dash, err := self.store.GetDashboard(slug, auth.getAccountId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(404, newErrorResponse("Dashboard not found"))
|
c.JSON(404, newErrorResponse("Dashboard not found"))
|
||||||
return
|
return
|
||||||
@ -30,17 +29,16 @@ func (self *HttpServer) getDashboard(c *gin.Context) {
|
|||||||
c.JSON(200, dash.Data)
|
c.JSON(200, dash.Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *HttpServer) deleteDashboard(c *gin.Context) {
|
func (self *HttpServer) deleteDashboard(c *gin.Context, auth *authContext) {
|
||||||
slug := c.Params.ByName("slug")
|
slug := c.Params.ByName("slug")
|
||||||
accountId, err := c.Get("accountId")
|
|
||||||
|
|
||||||
dash, err := self.store.GetDashboard(slug, accountId.(int))
|
dash, err := self.store.GetDashboard(slug, auth.getAccountId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(404, newErrorResponse("Dashboard not found"))
|
c.JSON(404, newErrorResponse("Dashboard not found"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = self.store.DeleteDashboard(slug, accountId.(int))
|
err = self.store.DeleteDashboard(slug, auth.getAccountId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(500, newErrorResponse("Failed to delete dashboard: "+err.Error()))
|
c.JSON(500, newErrorResponse("Failed to delete dashboard: "+err.Error()))
|
||||||
return
|
return
|
||||||
@ -51,11 +49,10 @@ func (self *HttpServer) deleteDashboard(c *gin.Context) {
|
|||||||
c.JSON(200, resp)
|
c.JSON(200, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *HttpServer) search(c *gin.Context) {
|
func (self *HttpServer) search(c *gin.Context, auth *authContext) {
|
||||||
query := c.Params.ByName("q")
|
query := c.Params.ByName("q")
|
||||||
accountId, err := c.Get("accountId")
|
|
||||||
|
|
||||||
results, err := self.store.Query(query, accountId.(int))
|
results, err := self.store.Query(query, auth.getAccountId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Store query error: %v", err)
|
log.Error("Store query error: %v", err)
|
||||||
c.JSON(500, newErrorResponse("Failed"))
|
c.JSON(500, newErrorResponse("Failed"))
|
||||||
@ -65,15 +62,14 @@ func (self *HttpServer) search(c *gin.Context) {
|
|||||||
c.JSON(200, results)
|
c.JSON(200, results)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *HttpServer) postDashboard(c *gin.Context) {
|
func (self *HttpServer) postDashboard(c *gin.Context, auth *authContext) {
|
||||||
var command saveDashboardCommand
|
var command saveDashboardCommand
|
||||||
accountId, _ := c.Get("accountId")
|
|
||||||
|
|
||||||
if c.EnsureBody(&command) {
|
if c.EnsureBody(&command) {
|
||||||
dashboard := models.NewDashboard("test")
|
dashboard := models.NewDashboard("test")
|
||||||
dashboard.Data = command.Dashboard
|
dashboard.Data = command.Dashboard
|
||||||
dashboard.Title = dashboard.Data["title"].(string)
|
dashboard.Title = dashboard.Data["title"].(string)
|
||||||
dashboard.AccountId = accountId.(int)
|
dashboard.AccountId = auth.getAccountId()
|
||||||
dashboard.UpdateSlug()
|
dashboard.UpdateSlug()
|
||||||
|
|
||||||
if dashboard.Data["id"] != nil {
|
if dashboard.Data["id"] != nil {
|
||||||
|
@ -35,8 +35,8 @@ func (self *HttpServer) loginPost(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
session, _ := sessionStore.Get(c.Request, "grafana-session")
|
session, _ := sessionStore.Get(c.Request, "grafana-session")
|
||||||
session.Values["login"] = loginModel.Email
|
session.Values["userAccountId"] = account.Id
|
||||||
session.Values["accountId"] = account.Id
|
session.Values["usingAccountId"] = account.UsingAccountId
|
||||||
session.Save(c.Request, c.Writer)
|
session.Save(c.Request, c.Writer)
|
||||||
|
|
||||||
var resp = &LoginResultDto{}
|
var resp = &LoginResultDto{}
|
||||||
@ -48,25 +48,8 @@ func (self *HttpServer) loginPost(c *gin.Context) {
|
|||||||
|
|
||||||
func (self *HttpServer) logoutPost(c *gin.Context) {
|
func (self *HttpServer) logoutPost(c *gin.Context) {
|
||||||
session, _ := sessionStore.Get(c.Request, "grafana-session")
|
session, _ := sessionStore.Get(c.Request, "grafana-session")
|
||||||
session.Values["login"] = nil
|
session.Values = nil
|
||||||
session.Save(c.Request, c.Writer)
|
session.Save(c.Request, c.Writer)
|
||||||
|
|
||||||
c.JSON(200, gin.H{"status": "logged out"})
|
c.JSON(200, gin.H{"status": "logged out"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *HttpServer) auth() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
session, _ := sessionStore.Get(c.Request, "grafana-session")
|
|
||||||
|
|
||||||
if c.Request.URL.Path != "/login" && session.Values["login"] == nil {
|
|
||||||
c.Writer.Header().Set("Location", "/login")
|
|
||||||
c.Abort(302)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Set("accountId", session.Values["accountId"])
|
|
||||||
c.Set("login", session.Values["login"])
|
|
||||||
|
|
||||||
session.Save(c.Request, c.Writer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
36
pkg/api/api_routing.go
Normal file
36
pkg/api/api_routing.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/torkelo/grafana-pro/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type routeHandlerRegisterFn func(self *HttpServer)
|
||||||
|
type routeHandlerFn func(c *gin.Context, auth *authContext)
|
||||||
|
|
||||||
|
var routeHandlers = make([]routeHandlerRegisterFn, 0)
|
||||||
|
|
||||||
|
func getRouteHandlerWrapper(handler routeHandlerFn) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
authContext := authContext{
|
||||||
|
account: c.MustGet("usingAccount").(*models.UserAccount),
|
||||||
|
userAccount: c.MustGet("userAccount").(*models.UserAccount),
|
||||||
|
}
|
||||||
|
handler(c, &authContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *HttpServer) addRoute(method string, path string, handler routeHandlerFn) {
|
||||||
|
switch method {
|
||||||
|
case "GET":
|
||||||
|
self.router.GET(path, self.auth(), getRouteHandlerWrapper(handler))
|
||||||
|
case "POST":
|
||||||
|
self.router.POST(path, self.auth(), getRouteHandlerWrapper(handler))
|
||||||
|
case "DELETE":
|
||||||
|
self.router.DELETE(path, self.auth(), getRouteHandlerWrapper(handler))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRoutes(fn routeHandlerRegisterFn) {
|
||||||
|
routeHandlers = append(routeHandlers, fn)
|
||||||
|
}
|
43
pkg/models/account.go
Normal file
43
pkg/models/account.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CollaboratorLink struct {
|
||||||
|
AccountId int
|
||||||
|
Role string
|
||||||
|
ModifiedOn time.Time
|
||||||
|
CreatedOn time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserAccount struct {
|
||||||
|
Id int `gorethink:"id"`
|
||||||
|
UserName string
|
||||||
|
Login string
|
||||||
|
Email string
|
||||||
|
Password string
|
||||||
|
NextDashboardId int
|
||||||
|
UsingAccountId int
|
||||||
|
Collaborators []CollaboratorLink
|
||||||
|
CreatedOn time.Time
|
||||||
|
ModifiedOn time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (account *UserAccount) AddCollaborator(accountId int) error {
|
||||||
|
for _, collaborator := range account.Collaborators {
|
||||||
|
if collaborator.AccountId == accountId {
|
||||||
|
return errors.New("Collaborator already exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
account.Collaborators = append(account.Collaborators, CollaboratorLink{
|
||||||
|
AccountId: accountId,
|
||||||
|
Role: "admin",
|
||||||
|
CreatedOn: time.Now(),
|
||||||
|
ModifiedOn: time.Now(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -21,31 +21,6 @@ type Dashboard struct {
|
|||||||
Data map[string]interface{}
|
Data map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type CollaboratorLink struct {
|
|
||||||
AccountId int
|
|
||||||
Role string
|
|
||||||
ModifiedOn time.Time
|
|
||||||
CreatedOn time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserAccount struct {
|
|
||||||
Id int `gorethink:"id"`
|
|
||||||
UserName string
|
|
||||||
Login string
|
|
||||||
Email string
|
|
||||||
Password string
|
|
||||||
NextDashboardId int
|
|
||||||
UsingAccountId int
|
|
||||||
Collaborators []CollaboratorLink
|
|
||||||
CreatedOn time.Time
|
|
||||||
ModifiedOn time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserContext struct {
|
|
||||||
UserId string
|
|
||||||
AccountId string
|
|
||||||
}
|
|
||||||
|
|
||||||
type SearchResult struct {
|
type SearchResult struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
@ -87,12 +62,3 @@ func (dash *Dashboard) UpdateSlug() {
|
|||||||
re2 := regexp.MustCompile("\\s")
|
re2 := regexp.MustCompile("\\s")
|
||||||
dash.Slug = re2.ReplaceAllString(re.ReplaceAllString(title, ""), "-")
|
dash.Slug = re2.ReplaceAllString(re.ReplaceAllString(title, ""), "-")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (account *UserAccount) AddCollaborator(accountId int) {
|
|
||||||
account.Collaborators = append(account.Collaborators, CollaboratorLink{
|
|
||||||
AccountId: accountId,
|
|
||||||
Role: "admin",
|
|
||||||
CreatedOn: time.Now(),
|
|
||||||
ModifiedOn: time.Now(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
@ -32,6 +32,7 @@ func (self *rethinkStore) SaveUserAccount(account *models.UserAccount) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
account.Id = accountId
|
account.Id = accountId
|
||||||
|
account.UsingAccountId = accountId
|
||||||
|
|
||||||
resp, err := r.Table("accounts").Insert(account).RunWrite(self.session)
|
resp, err := r.Table("accounts").Insert(account).RunWrite(self.session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -28,9 +28,14 @@
|
|||||||
<div ng-include="'app/partials/pro/sidemenu.html'"></div>
|
<div ng-include="'app/partials/pro/sidemenu.html'"></div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div ng-repeat='alert in dashAlerts.list' class="alert-{{alert.severity}} dashboard-notice" ng-show="$last">
|
<div class="page-alert-list">
|
||||||
<button type="button" class="close" ng-click="dashAlerts.clear(alert)" style="padding-right:50px">×</button>
|
<div ng-repeat='alert in dashAlerts.list' class="alert-{{alert.severity}} alert">
|
||||||
<strong>{{alert.title}}</strong> <span ng-bind-html='alert.text'></span> <div style="padding-right:10px" class='pull-right small'> {{$index + 1}} alert(s) </div>
|
<button type="button" class="alert-close" ng-click="dashAlerts.clear(alert)">
|
||||||
|
<i class="icon-remove-sign"></i>
|
||||||
|
</button>
|
||||||
|
<div class="alert-title">{{alert.title}}</div>
|
||||||
|
<div ng-bind-html='alert.text'></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ng-view class="pro-main-view" ng-class="{'dashboard-fullscreen': fullscreen}"></div>
|
<div ng-view class="pro-main-view" ng-class="{'dashboard-fullscreen': fullscreen}"></div>
|
||||||
|
Reference in New Issue
Block a user