瀏覽代碼

Working on collaborators

Torkel Ödegaard 11 年之前
父節點
當前提交
a9a06ad51d
共有 11 個文件被更改,包括 174 次插入98 次删除
  1. 1 1
      grafana
  2. 4 12
      pkg/api/api.go
  3. 17 11
      pkg/api/api_account.go
  4. 48 0
      pkg/api/api_auth.go
  5. 13 17
      pkg/api/api_dashboard.go
  6. 3 20
      pkg/api/api_login.go
  7. 36 0
      pkg/api/api_routing.go
  8. 43 0
      pkg/models/account.go
  9. 0 34
      pkg/models/dashboards.go
  10. 1 0
      pkg/stores/rethinkdb_accounts.go
  11. 8 3
      views/index.html

+ 1 - 1
grafana

@@ -1 +1 @@
-Subproject commit e5fd35db343109feec09d339d5d770dd1de1808a
+Subproject commit c65b7d159189f81b0d87ecc5b64be3ffbe332393

+ 4 - 12
pkg/api/api.go

@@ -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)
-}

+ 17 - 11
pkg/api/api_account.go

@@ -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
+
+	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(account)
+	self.store.SaveUserAccount(userAccount)
 
 	c.JSON(200, gin.H{"status": "Collaborator added"})
 }

+ 48 - 0
pkg/api/api_auth.go

@@ -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)
+	}
+}

+ 13 - 17
pkg/api/api_dashboard.go

@@ -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 {

+ 3 - 20
pkg/api/api_login.go

@@ -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 - 0
pkg/api/api_routing.go

@@ -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 - 0
pkg/models/account.go

@@ -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
+}

+ 0 - 34
pkg/models/dashboards.go

@@ -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(),
-	})
-}

+ 1 - 0
pkg/stores/rethinkdb_accounts.go

@@ -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 {

+ 8 - 3
views/index.html

@@ -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">&times;</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>