mirror of
https://github.com/grafana/grafana.git
synced 2025-08-06 20:59:35 +08:00
more macaroon stuff
This commit is contained in:
BIN
data/sessions/5/a/5a7a6d798450f878d373110c50ed07ae3bc99d63
Normal file
BIN
data/sessions/5/a/5a7a6d798450f878d373110c50ed07ae3bc99d63
Normal file
Binary file not shown.
BIN
data/sessions/7/a/7ad60c89b1bc7a310c66e59570df698fc75d28b3
Normal file
BIN
data/sessions/7/a/7ad60c89b1bc7a310c66e59570df698fc75d28b3
Normal file
Binary file not shown.
BIN
data/sessions/7/b/7b786a2d47bb26f2fce2d9aa874615c6428c55a3
Normal file
BIN
data/sessions/7/b/7b786a2d47bb26f2fce2d9aa874615c6428c55a3
Normal file
Binary file not shown.
BIN
data/sessions/b/7/b724e1a2a6d52de49c11d1d62d6e0d83cba2911a
Normal file
BIN
data/sessions/b/7/b724e1a2a6d52de49c11d1d62d6e0d83cba2911a
Normal file
Binary file not shown.
BIN
grafana-pro
BIN
grafana-pro
Binary file not shown.
@ -15,7 +15,6 @@ import (
|
|||||||
"github.com/torkelo/grafana-pro/pkg/log"
|
"github.com/torkelo/grafana-pro/pkg/log"
|
||||||
"github.com/torkelo/grafana-pro/pkg/middleware"
|
"github.com/torkelo/grafana-pro/pkg/middleware"
|
||||||
"github.com/torkelo/grafana-pro/pkg/routes"
|
"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/setting"
|
||||||
"github.com/torkelo/grafana-pro/pkg/stores/rethink"
|
"github.com/torkelo/grafana-pro/pkg/stores/rethink"
|
||||||
)
|
)
|
||||||
@ -70,13 +69,7 @@ func runWeb(*cli.Context) {
|
|||||||
log.Info("Starting Grafana-Pro v.1-alpha")
|
log.Info("Starting Grafana-Pro v.1-alpha")
|
||||||
|
|
||||||
m := newMacaron()
|
m := newMacaron()
|
||||||
|
routes.Register(m)
|
||||||
auth := middleware.Auth()
|
|
||||||
|
|
||||||
// index
|
|
||||||
m.Get("/", auth, routes.Index)
|
|
||||||
m.Get("/login", routes.Index)
|
|
||||||
m.Post("/login", login.LoginPost)
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort)
|
listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort)
|
||||||
|
69
pkg/components/renderer/renderer.go
Normal file
69
pkg/components/renderer/renderer.go
Normal 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))
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package components
|
package renderer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -12,8 +12,7 @@ func TestPhantomRender(t *testing.T) {
|
|||||||
|
|
||||||
Convey("Can render url", t, func() {
|
Convey("Can render url", t, func() {
|
||||||
tempDir, _ := ioutil.TempDir("", "img")
|
tempDir, _ := ioutil.TempDir("", "img")
|
||||||
renderer := &PhantomRenderer{ImagesDir: tempDir, PhantomDir: "../../_vendor/phantomjs/"}
|
png, err := RenderToPng("http://www.google.com")
|
||||||
png, err := renderer.RenderToPng("http://www.google.com")
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(exists(png), ShouldEqual, true)
|
So(exists(png), ShouldEqual, true)
|
||||||
|
|
@ -21,6 +21,10 @@ type Context struct {
|
|||||||
IsSigned bool
|
IsSigned bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Context) GetAccountId() int {
|
||||||
|
return c.Account.Id
|
||||||
|
}
|
||||||
|
|
||||||
func GetContextHandler() macaron.Handler {
|
func GetContextHandler() macaron.Handler {
|
||||||
return func(c *macaron.Context, sess session.Store) {
|
return func(c *macaron.Context, sess session.Store) {
|
||||||
ctx := &Context{
|
ctx := &Context{
|
||||||
@ -51,6 +55,30 @@ func (ctx *Context) Handle(status int, title string, err error) {
|
|||||||
ctx.HTML(status, "index")
|
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 {
|
func (ctx *Context) JsonBody(model interface{}) bool {
|
||||||
b, _ := ioutil.ReadAll(ctx.Req.Body)
|
b, _ := ioutil.ReadAll(ctx.Req.Body)
|
||||||
err := json.Unmarshal(b, &model)
|
err := json.Unmarshal(b, &model)
|
||||||
|
@ -8,6 +8,13 @@ import (
|
|||||||
"time"
|
"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 {
|
type Dashboard struct {
|
||||||
Id string `gorethink:"id,omitempty"`
|
Id string `gorethink:"id,omitempty"`
|
||||||
Slug string
|
Slug string
|
||||||
|
82
pkg/routes/api/api_dashboard.go
Normal file
82
pkg/routes/api/api_dashboard.go
Normal 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})
|
||||||
|
}
|
30
pkg/routes/api/api_render.go
Normal file
30
pkg/routes/api/api_render.go
Normal 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)
|
||||||
|
}
|
@ -34,3 +34,9 @@ func getGravatarUrl(text string) string {
|
|||||||
hasher.Write([]byte(strings.ToLower(text)))
|
hasher.Write([]byte(strings.ToLower(text)))
|
||||||
return fmt.Sprintf("https://secure.gravatar.com/avatar/%x?s=90&default=mm", hasher.Sum(nil))
|
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"`
|
||||||
|
}
|
||||||
|
@ -1,10 +1,35 @@
|
|||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/Unknwon/macaron"
|
||||||
"github.com/torkelo/grafana-pro/pkg/middleware"
|
"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/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) {
|
func Index(ctx *middleware.Context) {
|
||||||
ctx.Data["User"] = apimodel.NewCurrentUserDto(ctx.UserAccount)
|
ctx.Data["User"] = apimodel.NewCurrentUserDto(ctx.UserAccount)
|
||||||
ctx.HTML(200, "index")
|
ctx.HTML(200, "index")
|
||||||
|
@ -58,6 +58,10 @@ var (
|
|||||||
ProdMode bool
|
ProdMode bool
|
||||||
RunUser string
|
RunUser string
|
||||||
IsWindows bool
|
IsWindows bool
|
||||||
|
|
||||||
|
// PhantomJs Rendering
|
||||||
|
ImagesDir string
|
||||||
|
PhantomDir string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -140,6 +144,10 @@ func NewConfigContext() {
|
|||||||
|
|
||||||
StaticRootPath = Cfg.MustValue("server", "static_root_path", workDir)
|
StaticRootPath = Cfg.MustValue("server", "static_root_path", workDir)
|
||||||
RouterLogging = Cfg.MustBool("server", "router_logging", false)
|
RouterLogging = Cfg.MustBool("server", "router_logging", false)
|
||||||
|
|
||||||
|
// PhantomJS rendering
|
||||||
|
ImagesDir = "data/png"
|
||||||
|
PhantomDir = "_vendor/phantomjs"
|
||||||
}
|
}
|
||||||
|
|
||||||
func initSessionService() {
|
func initSessionService() {
|
||||||
|
@ -34,6 +34,11 @@ func Init() {
|
|||||||
|
|
||||||
models.GetAccount = GetAccount
|
models.GetAccount = GetAccount
|
||||||
models.GetAccountByLogin = GetAccountByLogin
|
models.GetAccountByLogin = GetAccountByLogin
|
||||||
|
|
||||||
|
models.GetDashboard = GetDashboard
|
||||||
|
models.SearchQuery = SearchQuery
|
||||||
|
models.DeleteDashboard = DeleteDashboard
|
||||||
|
models.SaveDashboard = SaveDashboard
|
||||||
}
|
}
|
||||||
|
|
||||||
func createRethinkDBTablesAndIndices() {
|
func createRethinkDBTablesAndIndices() {
|
||||||
|
80
pkg/stores/rethink/rethink_dashboards.go
Normal file
80
pkg/stores/rethink/rethink_dashboards.go
Normal 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
|
||||||
|
}
|
Reference in New Issue
Block a user