more macaroon stuff

This commit is contained in:
Torkel Ödegaard 2014-10-06 15:31:54 -04:00
parent 222319d924
commit e84f06b503
17 changed files with 343 additions and 11 deletions

Binary file not shown.

View File

@ -15,7 +15,6 @@ import (
"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"
)
@ -70,13 +69,7 @@ func runWeb(*cli.Context) {
log.Info("Starting Grafana-Pro v.1-alpha")
m := newMacaron()
auth := middleware.Auth()
// index
m.Get("/", auth, routes.Index)
m.Get("/login", routes.Index)
m.Post("/login", login.LoginPost)
routes.Register(m)
var err error
listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort)

View File

@ -0,0 +1,69 @@
package renderer
import (
"crypto/md5"
"encoding/hex"
"io"
"os"
"os/exec"
"path/filepath"
"time"
"github.com/torkelo/grafana-pro/pkg/log"
"github.com/torkelo/grafana-pro/pkg/setting"
)
type RenderOpts struct {
Url string
Width string
Height string
}
func RenderToPng(params *RenderOpts) (string, error) {
log.Info("PhantomRenderer::renderToPng url %v", params.Url)
binPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, "phantomjs"))
scriptPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, "render.js"))
pngPath, _ := filepath.Abs(filepath.Join(setting.ImagesDir, getHash(params.Url)))
pngPath = pngPath + ".png"
cmd := exec.Command(binPath, scriptPath, "url="+params.Url, "width="+params.Width, "height="+params.Height, "png="+pngPath)
stdout, err := cmd.StdoutPipe()
if err != nil {
return "", err
}
stderr, err := cmd.StderrPipe()
if err != nil {
return "", err
}
err = cmd.Start()
if err != nil {
return "", err
}
go io.Copy(os.Stdout, stdout)
go io.Copy(os.Stdout, stderr)
done := make(chan error)
go func() {
cmd.Wait()
close(done)
}()
select {
case <-time.After(10 * time.Second):
if err := cmd.Process.Kill(); err != nil {
log.Error(4, "failed to kill: %v", err)
}
case <-done:
}
return pngPath, nil
}
func getHash(text string) string {
hasher := md5.New()
hasher.Write([]byte(text))
return hex.EncodeToString(hasher.Sum(nil))
}

View File

@ -1,4 +1,4 @@
package components
package renderer
import (
"io/ioutil"
@ -12,8 +12,7 @@ func TestPhantomRender(t *testing.T) {
Convey("Can render url", t, func() {
tempDir, _ := ioutil.TempDir("", "img")
renderer := &PhantomRenderer{ImagesDir: tempDir, PhantomDir: "../../_vendor/phantomjs/"}
png, err := renderer.RenderToPng("http://www.google.com")
png, err := RenderToPng("http://www.google.com")
So(err, ShouldBeNil)
So(exists(png), ShouldEqual, true)

View File

@ -21,6 +21,10 @@ type Context struct {
IsSigned bool
}
func (c *Context) GetAccountId() int {
return c.Account.Id
}
func GetContextHandler() macaron.Handler {
return func(c *macaron.Context, sess session.Store) {
ctx := &Context{
@ -51,6 +55,30 @@ func (ctx *Context) Handle(status int, title string, err error) {
ctx.HTML(status, "index")
}
func (ctx *Context) ApiError(status int, message string, err error) {
resp := make(map[string]interface{})
if err != nil {
log.Error(4, "%s: %v", message, err)
if macaron.Env != macaron.PROD {
resp["error"] = err
}
}
switch status {
case 404:
resp["message"] = "Not Found"
case 500:
resp["message"] = "Internal Server Error"
}
if message != "" {
resp["message"] = message
}
ctx.HTML(status, "index")
}
func (ctx *Context) JsonBody(model interface{}) bool {
b, _ := ioutil.ReadAll(ctx.Req.Body)
err := json.Unmarshal(b, &model)

View File

@ -8,6 +8,13 @@ import (
"time"
)
var (
GetDashboard func(slug string, accountId int) (*Dashboard, error)
SaveDashboard func(dash *Dashboard) error
DeleteDashboard func(slug string, accountId int) error
SearchQuery func(query string, acccountId int) ([]*SearchResult, error)
)
type Dashboard struct {
Id string `gorethink:"id,omitempty"`
Slug string

View File

@ -0,0 +1,82 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/torkelo/grafana-pro/pkg/middleware"
"github.com/torkelo/grafana-pro/pkg/models"
"github.com/torkelo/grafana-pro/pkg/routes/apimodel"
)
func GetDashboard(c *middleware.Context) {
slug := c.Params(":slug")
dash, err := models.GetDashboard(slug, c.GetAccountId())
if err != nil {
c.ApiError(404, "Dashboard not found", nil)
return
}
dash.Data["id"] = dash.Id
c.JSON(200, dash.Data)
}
func DeleteDashboard(c *middleware.Context) {
slug := c.Params(":slug")
dash, err := models.GetDashboard(slug, c.GetAccountId())
if err != nil {
c.ApiError(404, "Dashboard not found", nil)
return
}
err = models.DeleteDashboard(slug, c.GetAccountId())
if err != nil {
c.ApiError(500, "Failed to delete dashboard", err)
return
}
var resp = map[string]interface{}{"title": dash.Title}
c.JSON(200, resp)
}
func Search(c *middleware.Context) {
query := c.Query("q")
results, err := models.SearchQuery(query, c.GetAccountId())
if err != nil {
c.ApiError(500, "Search failed", err)
return
}
c.JSON(200, results)
}
func PostDashboard(c *middleware.Context) {
var command apimodel.SaveDashboardCommand
if !c.JsonBody(&command) {
c.ApiError(400, "bad request", nil)
return
}
dashboard := models.NewDashboard("test")
dashboard.Data = command.Dashboard
dashboard.Title = dashboard.Data["title"].(string)
dashboard.AccountId = c.GetAccountId()
dashboard.UpdateSlug()
if dashboard.Data["id"] != nil {
dashboard.Id = dashboard.Data["id"].(string)
}
err := models.SaveDashboard(dashboard)
if err != nil {
c.ApiError(500, "Failed to save dashboard", err)
return
}
c.JSON(200, gin.H{"status": "success", "slug": dashboard.Slug})
}

View File

@ -0,0 +1,30 @@
package api
import (
"strconv"
"github.com/torkelo/grafana-pro/pkg/components/renderer"
"github.com/torkelo/grafana-pro/pkg/middleware"
"github.com/torkelo/grafana-pro/pkg/utils"
)
func RenderToPng(c *middleware.Context) {
accountId := c.GetAccountId()
queryReader := utils.NewUrlQueryReader(c.Req.URL)
queryParams := "?render&accountId=" + strconv.Itoa(accountId) + "&" + c.Req.URL.RawQuery
renderOpts := &renderer.RenderOpts{
Url: c.Params("url") + queryParams,
Width: queryReader.Get("width", "800"),
Height: queryReader.Get("height", "400"),
}
renderOpts.Url = "http://localhost:3000" + renderOpts.Url
pngPath, err := renderer.RenderToPng(renderOpts)
if err != nil {
c.HTML(500, "error.html", nil)
}
c.ServeFile(pngPath)
}

View File

@ -34,3 +34,9 @@ func getGravatarUrl(text string) string {
hasher.Write([]byte(strings.ToLower(text)))
return fmt.Sprintf("https://secure.gravatar.com/avatar/%x?s=90&default=mm", hasher.Sum(nil))
}
type SaveDashboardCommand struct {
Id string `json:"id"`
Title string `json:"title"`
Dashboard map[string]interface{} `json:"dashboard"`
}

View File

@ -1,10 +1,35 @@
package routes
import (
"github.com/Unknwon/macaron"
"github.com/torkelo/grafana-pro/pkg/middleware"
"github.com/torkelo/grafana-pro/pkg/routes/api"
"github.com/torkelo/grafana-pro/pkg/routes/apimodel"
"github.com/torkelo/grafana-pro/pkg/routes/login"
)
func Register(m *macaron.Macaron) {
auth := middleware.Auth()
// index
m.Get("/", auth, Index)
m.Post("/logout", login.LogoutPost)
m.Post("/login", login.LoginPost)
// no auth
m.Get("/login", Index)
// dashboards
m.Get("/dashboard/*", auth, Index)
m.Get("/api/dashboards/:slug", auth, api.GetDashboard)
m.Get("/api/search/", auth, api.Search)
m.Post("/api/dashboard/", auth, api.PostDashboard)
m.Delete("/api/dashboard/:slug", auth, api.DeleteDashboard)
// rendering
m.Get("/render/*url", auth, api.RenderToPng)
}
func Index(ctx *middleware.Context) {
ctx.Data["User"] = apimodel.NewCurrentUserDto(ctx.UserAccount)
ctx.HTML(200, "index")

View File

@ -58,6 +58,10 @@ var (
ProdMode bool
RunUser string
IsWindows bool
// PhantomJs Rendering
ImagesDir string
PhantomDir string
)
func init() {
@ -140,6 +144,10 @@ func NewConfigContext() {
StaticRootPath = Cfg.MustValue("server", "static_root_path", workDir)
RouterLogging = Cfg.MustBool("server", "router_logging", false)
// PhantomJS rendering
ImagesDir = "data/png"
PhantomDir = "_vendor/phantomjs"
}
func initSessionService() {

View File

@ -34,6 +34,11 @@ func Init() {
models.GetAccount = GetAccount
models.GetAccountByLogin = GetAccountByLogin
models.GetDashboard = GetDashboard
models.SearchQuery = SearchQuery
models.DeleteDashboard = DeleteDashboard
models.SaveDashboard = SaveDashboard
}
func createRethinkDBTablesAndIndices() {

View File

@ -0,0 +1,80 @@
package rethink
import (
"errors"
r "github.com/dancannon/gorethink"
"github.com/torkelo/grafana-pro/pkg/log"
"github.com/torkelo/grafana-pro/pkg/models"
)
func SaveDashboard(dash *models.Dashboard) error {
resp, err := r.Table("dashboards").Insert(dash, r.InsertOpts{Conflict: "update"}).RunWrite(session)
if err != nil {
return err
}
log.Info("Inserted: %v, Errors: %v, Updated: %v", resp.Inserted, resp.Errors, resp.Updated)
log.Info("First error:", resp.FirstError)
if len(resp.GeneratedKeys) > 0 {
dash.Id = resp.GeneratedKeys[0]
}
return nil
}
func GetDashboard(slug string, accountId int) (*models.Dashboard, error) {
resp, err := r.Table("dashboards").
GetAllByIndex("AccountIdSlug", []interface{}{accountId, slug}).
Run(session)
if err != nil {
return nil, err
}
var dashboard models.Dashboard
err = resp.One(&dashboard)
if err != nil {
return nil, err
}
return &dashboard, nil
}
func DeleteDashboard(slug string, accountId int) error {
resp, err := r.Table("dashboards").
GetAllByIndex("AccountIdSlug", []interface{}{accountId, slug}).
Delete().RunWrite(session)
if err != nil {
return err
}
if resp.Deleted != 1 {
return errors.New("Did not find dashboard to delete")
}
return nil
}
func SearchQuery(query string, accountId int) ([]*models.SearchResult, error) {
docs, err := r.Table("dashboards").
GetAllByIndex("AccountId", []interface{}{accountId}).
Filter(r.Row.Field("Title").Match(".*")).Run(session)
if err != nil {
return nil, err
}
results := make([]*models.SearchResult, 0, 50)
var dashboard models.Dashboard
for docs.Next(&dashboard) {
results = append(results, &models.SearchResult{
Title: dashboard.Title,
Id: dashboard.Slug,
})
}
return results, nil
}