mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 18:02:18 +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/gorilla/sessions"
|
||||
"github.com/torkelo/grafana-pro/pkg/components"
|
||||
"github.com/torkelo/grafana-pro/pkg/models"
|
||||
"github.com/torkelo/grafana-pro/pkg/stores"
|
||||
)
|
||||
|
||||
@ -61,9 +62,9 @@ func (self *HttpServer) ListenAndServe() {
|
||||
|
||||
func (self *HttpServer) index(c *gin.Context) {
|
||||
viewModel := &IndexDto{}
|
||||
login, _ := c.Get("login")
|
||||
if login != nil {
|
||||
viewModel.User.Login = login.(string)
|
||||
userAccount, _ := c.Get("userAccount")
|
||||
if userAccount != nil {
|
||||
viewModel.User.Login = userAccount.(*models.UserAccount).Login
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
// 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() {
|
||||
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"`
|
||||
}
|
||||
|
||||
func (self *HttpServer) addCollaborator(c *gin.Context) {
|
||||
func (self *HttpServer) addCollaborator(c *gin.Context, auth *authContext) {
|
||||
var model addCollaboratorDto
|
||||
|
||||
if !c.EnsureBody(&model) {
|
||||
c.JSON(400, gin.H{"status": "bad request"})
|
||||
c.JSON(400, gin.H{"status": "Collaborator not found"})
|
||||
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)
|
||||
if err != nil {
|
||||
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"})
|
||||
}
|
||||
|
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() {
|
||||
addRoutes(func(self *HttpServer) {
|
||||
self.router.GET("/api/dashboards/:slug", self.auth(), self.getDashboard)
|
||||
self.router.GET("/api/search/", self.auth(), self.search)
|
||||
self.router.POST("/api/dashboard", self.auth(), self.postDashboard)
|
||||
self.router.DELETE("/api/dashboard/:slug", self.auth(), self.deleteDashboard)
|
||||
self.addRoute("GET", "/api/dashboards/:slug", self.getDashboard)
|
||||
self.addRoute("GET", "/api/search/", self.search)
|
||||
self.addRoute("POST", "/api/dashboard/", self.postDashboard)
|
||||
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")
|
||||
accountId, err := c.Get("accountId")
|
||||
|
||||
dash, err := self.store.GetDashboard(slug, accountId.(int))
|
||||
dash, err := self.store.GetDashboard(slug, auth.getAccountId())
|
||||
if err != nil {
|
||||
c.JSON(404, newErrorResponse("Dashboard not found"))
|
||||
return
|
||||
@ -30,17 +29,16 @@ func (self *HttpServer) getDashboard(c *gin.Context) {
|
||||
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")
|
||||
accountId, err := c.Get("accountId")
|
||||
|
||||
dash, err := self.store.GetDashboard(slug, accountId.(int))
|
||||
dash, err := self.store.GetDashboard(slug, auth.getAccountId())
|
||||
if err != nil {
|
||||
c.JSON(404, newErrorResponse("Dashboard not found"))
|
||||
return
|
||||
}
|
||||
|
||||
err = self.store.DeleteDashboard(slug, accountId.(int))
|
||||
err = self.store.DeleteDashboard(slug, auth.getAccountId())
|
||||
if err != nil {
|
||||
c.JSON(500, newErrorResponse("Failed to delete dashboard: "+err.Error()))
|
||||
return
|
||||
@ -51,11 +49,10 @@ func (self *HttpServer) deleteDashboard(c *gin.Context) {
|
||||
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")
|
||||
accountId, err := c.Get("accountId")
|
||||
|
||||
results, err := self.store.Query(query, accountId.(int))
|
||||
results, err := self.store.Query(query, auth.getAccountId())
|
||||
if err != nil {
|
||||
log.Error("Store query error: %v", err)
|
||||
c.JSON(500, newErrorResponse("Failed"))
|
||||
@ -65,15 +62,14 @@ func (self *HttpServer) search(c *gin.Context) {
|
||||
c.JSON(200, results)
|
||||
}
|
||||
|
||||
func (self *HttpServer) postDashboard(c *gin.Context) {
|
||||
func (self *HttpServer) postDashboard(c *gin.Context, auth *authContext) {
|
||||
var command saveDashboardCommand
|
||||
accountId, _ := c.Get("accountId")
|
||||
|
||||
if c.EnsureBody(&command) {
|
||||
dashboard := models.NewDashboard("test")
|
||||
dashboard.Data = command.Dashboard
|
||||
dashboard.Title = dashboard.Data["title"].(string)
|
||||
dashboard.AccountId = accountId.(int)
|
||||
dashboard.AccountId = auth.getAccountId()
|
||||
dashboard.UpdateSlug()
|
||||
|
||||
if dashboard.Data["id"] != nil {
|
||||
|
@ -35,8 +35,8 @@ func (self *HttpServer) loginPost(c *gin.Context) {
|
||||
}
|
||||
|
||||
session, _ := sessionStore.Get(c.Request, "grafana-session")
|
||||
session.Values["login"] = loginModel.Email
|
||||
session.Values["accountId"] = account.Id
|
||||
session.Values["userAccountId"] = account.Id
|
||||
session.Values["usingAccountId"] = account.UsingAccountId
|
||||
session.Save(c.Request, c.Writer)
|
||||
|
||||
var resp = &LoginResultDto{}
|
||||
@ -48,25 +48,8 @@ func (self *HttpServer) loginPost(c *gin.Context) {
|
||||
|
||||
func (self *HttpServer) logoutPost(c *gin.Context) {
|
||||
session, _ := sessionStore.Get(c.Request, "grafana-session")
|
||||
session.Values["login"] = nil
|
||||
session.Values = nil
|
||||
session.Save(c.Request, c.Writer)
|
||||
|
||||
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{}
|
||||
}
|
||||
|
||||
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 {
|
||||
Id string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
@ -87,12 +62,3 @@ func (dash *Dashboard) UpdateSlug() {
|
||||
re2 := regexp.MustCompile("\\s")
|
||||
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.UsingAccountId = accountId
|
||||
|
||||
resp, err := r.Table("accounts").Insert(account).RunWrite(self.session)
|
||||
if err != nil {
|
||||
|
@ -28,9 +28,14 @@
|
||||
<div ng-include="'app/partials/pro/sidemenu.html'"></div>
|
||||
</aside>
|
||||
|
||||
<div ng-repeat='alert in dashAlerts.list' class="alert-{{alert.severity}} dashboard-notice" ng-show="$last">
|
||||
<button type="button" class="close" ng-click="dashAlerts.clear(alert)" style="padding-right:50px">×</button>
|
||||
<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>
|
||||
<div class="page-alert-list">
|
||||
<div ng-repeat='alert in dashAlerts.list' class="alert-{{alert.severity}} alert">
|
||||
<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 ng-view class="pro-main-view" ng-class="{'dashboard-fullscreen': fullscreen}"></div>
|
||||
|
Reference in New Issue
Block a user