Commit a9a06ad5 by Torkel Ödegaard

Working on collaborators

parent 17057344
Subproject commit e5fd35db343109feec09d339d5d770dd1de1808a Subproject commit c65b7d159189f81b0d87ecc5b64be3ffbe332393
...@@ -7,6 +7,7 @@ import ( ...@@ -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() { ...@@ -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 { ...@@ -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" ...@@ -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 { ...@@ -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
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"}) c.JSON(200, gin.H{"status": "Collaborator added"})
} }
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 ( ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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)
}
}
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)
}
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 { ...@@ -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() { ...@@ -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 { ...@@ -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 @@ ...@@ -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">&times;</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>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment