mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
macaron transition progress
This commit is contained in:
parent
201e1d3e6d
commit
222319d924
BIN
data/sessions/5/e/5e40ff05d87ac75cba0634e7350c263c4c45f202
Normal file
BIN
data/sessions/5/e/5e40ff05d87ac75cba0634e7350c263c4c45f202
Normal file
Binary file not shown.
BIN
grafana-pro
BIN
grafana-pro
Binary file not shown.
@ -10,11 +10,14 @@ import (
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/macaron-contrib/session"
|
||||
|
||||
"github.com/torkelo/grafana-pro/pkg/log"
|
||||
"github.com/torkelo/grafana-pro/pkg/middleware"
|
||||
"github.com/torkelo/grafana-pro/pkg/routes"
|
||||
"github.com/torkelo/grafana-pro/pkg/routes/login"
|
||||
"github.com/torkelo/grafana-pro/pkg/setting"
|
||||
"github.com/torkelo/grafana-pro/pkg/stores/rethink"
|
||||
)
|
||||
|
||||
var CmdWeb = cli.Command{
|
||||
@ -29,27 +32,15 @@ func newMacaron() *macaron.Macaron {
|
||||
m := macaron.New()
|
||||
m.Use(middleware.Logger())
|
||||
m.Use(macaron.Recovery())
|
||||
m.Use(macaron.Static(
|
||||
path.Join(setting.StaticRootPath, "public"),
|
||||
macaron.StaticOptions{
|
||||
SkipLogging: true,
|
||||
Prefix: "public",
|
||||
},
|
||||
))
|
||||
m.Use(macaron.Static(
|
||||
path.Join(setting.StaticRootPath, "public/app"),
|
||||
macaron.StaticOptions{
|
||||
SkipLogging: true,
|
||||
Prefix: "app",
|
||||
},
|
||||
))
|
||||
m.Use(macaron.Static(
|
||||
path.Join(setting.StaticRootPath, "public/img"),
|
||||
macaron.StaticOptions{
|
||||
SkipLogging: true,
|
||||
Prefix: "img",
|
||||
},
|
||||
))
|
||||
|
||||
mapStatic(m, "public", "public")
|
||||
mapStatic(m, "public/app", "app")
|
||||
mapStatic(m, "public/img", "img")
|
||||
|
||||
m.Use(session.Sessioner(session.Options{
|
||||
Provider: setting.SessionProvider,
|
||||
Config: *setting.SessionConfig,
|
||||
}))
|
||||
|
||||
m.Use(macaron.Renderer(macaron.RenderOptions{
|
||||
Directory: path.Join(setting.StaticRootPath, "views"),
|
||||
@ -61,16 +52,31 @@ func newMacaron() *macaron.Macaron {
|
||||
return m
|
||||
}
|
||||
|
||||
func mapStatic(m *macaron.Macaron, dir string, prefix string) {
|
||||
m.Use(macaron.Static(
|
||||
path.Join(setting.StaticRootPath, dir),
|
||||
macaron.StaticOptions{
|
||||
SkipLogging: true,
|
||||
Prefix: prefix,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
func runWeb(*cli.Context) {
|
||||
setting.NewConfigContext()
|
||||
setting.InitServices()
|
||||
rethink.Init()
|
||||
|
||||
log.Info("Starting Grafana-Pro v.1-alpha")
|
||||
|
||||
m := newMacaron()
|
||||
|
||||
auth := middleware.Auth()
|
||||
|
||||
// index
|
||||
m.Get("/", routes.Index)
|
||||
m.Get("/", auth, routes.Index)
|
||||
m.Get("/login", routes.Index)
|
||||
m.Post("/login", login.LoginPost)
|
||||
|
||||
var err error
|
||||
listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort)
|
||||
|
57
pkg/middleware/auth.go
Normal file
57
pkg/middleware/auth.go
Normal file
@ -0,0 +1,57 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/session"
|
||||
"github.com/torkelo/grafana-pro/pkg/models"
|
||||
)
|
||||
|
||||
func authGetRequestAccountId(c *Context, sess session.Store) (int, error) {
|
||||
accountId := sess.Get("accountId")
|
||||
|
||||
urlQuery := c.Req.URL.Query()
|
||||
if len(urlQuery["render"]) > 0 {
|
||||
accId, _ := strconv.Atoi(urlQuery["accountId"][0])
|
||||
sess.Set("accountId", accId)
|
||||
accountId = accId
|
||||
}
|
||||
|
||||
if accountId == nil {
|
||||
return -1, errors.New("Auth: session account id not found")
|
||||
}
|
||||
|
||||
return accountId.(int), nil
|
||||
}
|
||||
|
||||
func authDenied(c *Context) {
|
||||
c.Redirect("/login")
|
||||
}
|
||||
|
||||
func Auth() macaron.Handler {
|
||||
return func(c *Context, sess session.Store) {
|
||||
accountId, err := authGetRequestAccountId(c, sess)
|
||||
|
||||
if err != nil && c.Req.URL.Path != "/login" {
|
||||
authDenied(c)
|
||||
return
|
||||
}
|
||||
|
||||
account, err := models.GetAccount(accountId)
|
||||
if err != nil {
|
||||
authDenied(c)
|
||||
return
|
||||
}
|
||||
|
||||
usingAccount, err := models.GetAccount(account.UsingAccountId)
|
||||
if err != nil {
|
||||
authDenied(c)
|
||||
return
|
||||
}
|
||||
|
||||
c.UserAccount = account
|
||||
c.Account = usingAccount
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"time"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/session"
|
||||
@ -21,13 +22,12 @@ type Context struct {
|
||||
}
|
||||
|
||||
func GetContextHandler() macaron.Handler {
|
||||
return func(c *macaron.Context) {
|
||||
return func(c *macaron.Context, sess session.Store) {
|
||||
ctx := &Context{
|
||||
Context: c,
|
||||
Session: sess,
|
||||
}
|
||||
|
||||
ctx.Data["PageStartTime"] = time.Now()
|
||||
|
||||
c.Map(ctx)
|
||||
}
|
||||
}
|
||||
@ -50,3 +50,9 @@ func (ctx *Context) Handle(status int, title string, err error) {
|
||||
|
||||
ctx.HTML(status, "index")
|
||||
}
|
||||
|
||||
func (ctx *Context) JsonBody(model interface{}) bool {
|
||||
b, _ := ioutil.ReadAll(ctx.Req.Body)
|
||||
err := json.Unmarshal(b, &model)
|
||||
return err == nil
|
||||
}
|
||||
|
@ -5,6 +5,19 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
CreateAccount func(acccount *Account) error
|
||||
UpdateAccount func(acccount *Account) error
|
||||
GetAccountByLogin func(emailOrName string) (*Account, error)
|
||||
GetAccount func(accountId int) (*Account, error)
|
||||
GetOtherAccountsFor func(accountId int) ([]*OtherAccount, error)
|
||||
)
|
||||
|
||||
// Typed errors
|
||||
var (
|
||||
ErrAccountNotFound = errors.New("Account not found")
|
||||
)
|
||||
|
||||
type CollaboratorLink struct {
|
||||
AccountId int
|
||||
Role string
|
||||
|
36
pkg/routes/apimodel/models.go
Normal file
36
pkg/routes/apimodel/models.go
Normal file
@ -0,0 +1,36 @@
|
||||
package apimodel
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/torkelo/grafana-pro/pkg/models"
|
||||
)
|
||||
|
||||
type LoginResultDto struct {
|
||||
Status string `json:"status"`
|
||||
User CurrentUserDto `json:"user"`
|
||||
}
|
||||
|
||||
type CurrentUserDto struct {
|
||||
Login string `json:"login"`
|
||||
Email string `json:"email"`
|
||||
GravatarUrl string `json:"gravatarUrl"`
|
||||
}
|
||||
|
||||
func NewCurrentUserDto(account *models.Account) *CurrentUserDto {
|
||||
model := &CurrentUserDto{}
|
||||
if account != nil {
|
||||
model.Login = account.Login
|
||||
model.Email = account.Email
|
||||
model.GravatarUrl = getGravatarUrl(account.Email)
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
func getGravatarUrl(text string) string {
|
||||
hasher := md5.New()
|
||||
hasher.Write([]byte(strings.ToLower(text)))
|
||||
return fmt.Sprintf("https://secure.gravatar.com/avatar/%x?s=90&default=mm", hasher.Sum(nil))
|
||||
}
|
@ -1,12 +1,15 @@
|
||||
package routes
|
||||
|
||||
import "github.com/torkelo/grafana-pro/pkg/middleware"
|
||||
import (
|
||||
"github.com/torkelo/grafana-pro/pkg/middleware"
|
||||
"github.com/torkelo/grafana-pro/pkg/routes/apimodel"
|
||||
)
|
||||
|
||||
func Index(ctx *middleware.Context) {
|
||||
ctx.Data["User"] = apimodel.NewCurrentUserDto(ctx.UserAccount)
|
||||
ctx.HTML(200, "index")
|
||||
}
|
||||
|
||||
func NotFound(ctx *middleware.Context) {
|
||||
ctx.Data["Title"] = "Page Not Found"
|
||||
ctx.Handle(404, "index", nil)
|
||||
}
|
||||
|
56
pkg/routes/login/login.go
Normal file
56
pkg/routes/login/login.go
Normal file
@ -0,0 +1,56 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/torkelo/grafana-pro/pkg/log"
|
||||
"github.com/torkelo/grafana-pro/pkg/middleware"
|
||||
"github.com/torkelo/grafana-pro/pkg/models"
|
||||
"github.com/torkelo/grafana-pro/pkg/routes/apimodel"
|
||||
)
|
||||
|
||||
type loginJsonModel struct {
|
||||
Email string `json:"email" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
Remember bool `json:"remember"`
|
||||
}
|
||||
|
||||
func LoginPost(c *middleware.Context) {
|
||||
var loginModel loginJsonModel
|
||||
|
||||
if !c.JsonBody(&loginModel) {
|
||||
c.JSON(400, gin.H{"status": "bad request"})
|
||||
return
|
||||
}
|
||||
|
||||
account, err := models.GetAccountByLogin(loginModel.Email)
|
||||
if err != nil {
|
||||
c.JSON(401, gin.H{"status": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
if loginModel.Password != account.Password {
|
||||
c.JSON(401, gin.H{"status": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
loginUserWithAccount(account, c)
|
||||
|
||||
var resp = &apimodel.LoginResultDto{}
|
||||
resp.Status = "Logged in"
|
||||
resp.User.Login = account.Login
|
||||
|
||||
c.JSON(200, resp)
|
||||
}
|
||||
|
||||
func loginUserWithAccount(account *models.Account, c *middleware.Context) {
|
||||
if account == nil {
|
||||
log.Error(3, "Account login with nil account")
|
||||
}
|
||||
|
||||
c.Session.Set("accountId", account.Id)
|
||||
}
|
||||
|
||||
func LogoutPost(c *middleware.Context) {
|
||||
c.Session.Delete("accountId")
|
||||
c.JSON(200, gin.H{"status": "logged out"})
|
||||
}
|
197
pkg/stores/rethink/rethink.go
Normal file
197
pkg/stores/rethink/rethink.go
Normal file
@ -0,0 +1,197 @@
|
||||
package rethink
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
r "github.com/dancannon/gorethink"
|
||||
|
||||
"github.com/torkelo/grafana-pro/pkg/log"
|
||||
"github.com/torkelo/grafana-pro/pkg/models"
|
||||
)
|
||||
|
||||
var (
|
||||
session *r.Session
|
||||
dbName string = "grafana"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
log.Info("Initializing rethink storage")
|
||||
|
||||
var err error
|
||||
session, err = r.Connect(r.ConnectOpts{
|
||||
Address: "localhost:28015",
|
||||
Database: dbName,
|
||||
MaxIdle: 10,
|
||||
IdleTimeout: time.Second * 10,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Error(3, "Failed to connect to rethink database %v", err)
|
||||
}
|
||||
|
||||
createRethinkDBTablesAndIndices()
|
||||
|
||||
models.GetAccount = GetAccount
|
||||
models.GetAccountByLogin = GetAccountByLogin
|
||||
}
|
||||
|
||||
func createRethinkDBTablesAndIndices() {
|
||||
|
||||
r.DbCreate(dbName).Exec(session)
|
||||
|
||||
// create tables
|
||||
r.Db(dbName).TableCreate("dashboards").Exec(session)
|
||||
r.Db(dbName).TableCreate("accounts").Exec(session)
|
||||
r.Db(dbName).TableCreate("master").Exec(session)
|
||||
|
||||
// create dashboard accountId + slug index
|
||||
r.Db(dbName).Table("dashboards").IndexCreateFunc("AccountIdSlug", func(row r.Term) interface{} {
|
||||
return []interface{}{row.Field("AccountId"), row.Field("Slug")}
|
||||
}).Exec(session)
|
||||
|
||||
r.Db(dbName).Table("dashboards").IndexCreate("AccountId").Exec(session)
|
||||
r.Db(dbName).Table("accounts").IndexCreate("Login").Exec(session)
|
||||
|
||||
// create account collaborator index
|
||||
r.Db(dbName).Table("accounts").
|
||||
IndexCreateFunc("CollaboratorAccountId", func(row r.Term) interface{} {
|
||||
return row.Field("Collaborators").Map(func(row r.Term) interface{} {
|
||||
return row.Field("AccountId")
|
||||
})
|
||||
}, r.IndexCreateOpts{Multi: true}).Exec(session)
|
||||
|
||||
// make sure master ids row exists
|
||||
_, err := r.Table("master").Insert(map[string]interface{}{"id": "ids", "NextAccountId": 0}).RunWrite(session)
|
||||
if err != nil {
|
||||
log.Error(3, "Failed to insert master ids row", err)
|
||||
}
|
||||
}
|
||||
|
||||
func getNextAccountId() (int, error) {
|
||||
resp, err := r.Table("master").Get("ids").Update(map[string]interface{}{
|
||||
"NextAccountId": r.Row.Field("NextAccountId").Add(1),
|
||||
}, r.UpdateOpts{ReturnChanges: true}).RunWrite(session)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
change := resp.Changes[0]
|
||||
|
||||
if change.NewValue == nil {
|
||||
return 0, errors.New("Failed to get new value after incrementing account id")
|
||||
}
|
||||
|
||||
return int(change.NewValue.(map[string]interface{})["NextAccountId"].(float64)), nil
|
||||
}
|
||||
|
||||
func CreateAccount(account *models.Account) error {
|
||||
accountId, err := getNextAccountId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account.Id = accountId
|
||||
account.UsingAccountId = accountId
|
||||
|
||||
resp, err := r.Table("accounts").Insert(account).RunWrite(session)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.Inserted == 0 {
|
||||
return errors.New("Failed to insert acccount")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetAccountByLogin(emailOrName string) (*models.Account, error) {
|
||||
resp, err := r.Table("accounts").GetAllByIndex("Login", emailOrName).Run(session)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var account models.Account
|
||||
err = resp.One(&account)
|
||||
if err != nil {
|
||||
return nil, models.ErrAccountNotFound
|
||||
}
|
||||
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
func GetAccount(id int) (*models.Account, error) {
|
||||
resp, err := r.Table("accounts").Get(id).Run(session)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var account models.Account
|
||||
err = resp.One(&account)
|
||||
if err != nil {
|
||||
return nil, errors.New("Not found")
|
||||
}
|
||||
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
func UpdateAccount(account *models.Account) error {
|
||||
resp, err := r.Table("accounts").Update(account).RunWrite(session)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.Replaced == 0 && resp.Unchanged == 0 {
|
||||
return errors.New("Could not find account to update")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getNextDashboardNumber(accountId int) (int, error) {
|
||||
resp, err := r.Table("accounts").Get(accountId).Update(map[string]interface{}{
|
||||
"NextDashboardId": r.Row.Field("NextDashboardId").Add(1),
|
||||
}, r.UpdateOpts{ReturnChanges: true}).RunWrite(session)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
change := resp.Changes[0]
|
||||
|
||||
if change.NewValue == nil {
|
||||
return 0, errors.New("Failed to get next dashboard id, no new value after update")
|
||||
}
|
||||
|
||||
return int(change.NewValue.(map[string]interface{})["NextDashboardId"].(float64)), nil
|
||||
}
|
||||
|
||||
func GetOtherAccountsFor(accountId int) ([]*models.OtherAccount, error) {
|
||||
resp, err := r.Table("accounts").
|
||||
GetAllByIndex("CollaboratorAccountId", accountId).
|
||||
Map(func(row r.Term) interface{} {
|
||||
return map[string]interface{}{
|
||||
"id": row.Field("id"),
|
||||
"Name": row.Field("Email"),
|
||||
"Role": row.Field("Collaborators").Filter(map[string]interface{}{
|
||||
"AccountId": accountId,
|
||||
}).Nth(0).Field("Role"),
|
||||
}
|
||||
}).Run(session)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var list []*models.OtherAccount
|
||||
err = resp.All(&list)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to read available accounts")
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
@ -1,10 +1,6 @@
|
||||
package stores
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/torkelo/grafana-pro/pkg/models"
|
||||
)
|
||||
import "github.com/torkelo/grafana-pro/pkg/models"
|
||||
|
||||
type Store interface {
|
||||
GetDashboard(slug string, accountId int) (*models.Dashboard, error)
|
||||
@ -19,11 +15,6 @@ type Store interface {
|
||||
Close()
|
||||
}
|
||||
|
||||
// Typed errors
|
||||
var (
|
||||
ErrAccountNotFound = errors.New("Account not found")
|
||||
)
|
||||
|
||||
func New() Store {
|
||||
return NewRethinkStore(&RethinkCfg{DatabaseName: "grafana"})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user