diff --git a/.bra.toml b/.bra.toml index 1fb047534e5..0b3ce160c2f 100644 --- a/.bra.toml +++ b/.bra.toml @@ -4,6 +4,7 @@ watch_all = true watch_dirs = [ "$WORKDIR/pkg", "$WORKDIR/views", + "$WORKDIR/conf", ] watch_exts = [".go", ".ini"] build_delay = 1500 diff --git a/conf/grafana.ini b/conf/grafana.ini index 322fc720698..54777e91a11 100644 --- a/conf/grafana.ini +++ b/conf/grafana.ini @@ -8,7 +8,31 @@ root_url = %(protocol)s://%(domain)s:%(http_port)s/ http_addr = http_port = 3000 ssh_port = 22 -route_log = true +router_logging = false + +[session] +; Either "memory", "file", default is "memory" +provider = file +; Provider config options +; memory: not have any config yet +; file: session file path, e.g. `data/sessions` +; redis: config like redis server addr, poolSize, password, e.g. `127.0.0.1:6379,100,gogs` +; mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table` +provider_config = data/sessions +; Session cookie name +cookie_name = grafana_pro_sess +; If you use session in https only, default is false +cookie_secure = false +; Enable set cookie, default is true +enable_set_cookie = true +; Session GC time interval, default is 86400 +gc_time_interval = 86400 +; Session life time, default is 86400 +session_life_time = 86400 +; session id hash func, Either "sha1", "sha256" or "md5" default is sha1 +session_id_hashfunc = sha1 +; Session hash key, default is use random string +session_id_hashkey = [log] root_path = diff --git a/grafana-pro b/grafana-pro index e0bb72cb5d5..e9ecefd55f9 100755 Binary files a/grafana-pro and b/grafana-pro differ diff --git a/pkg/cmd/web.go b/pkg/cmd/web.go index a5bfc902f81..5f11cce9efa 100644 --- a/pkg/cmd/web.go +++ b/pkg/cmd/web.go @@ -1,12 +1,19 @@ +// Copyright 2014 Unknwon +// Copyright 2014 Torkel Ödegaard + package cmd import ( - "time" + "fmt" + "net/http" + "path" + "github.com/Unknwon/macaron" "github.com/codegangsta/cli" - "github.com/siddontang/go-log/log" - "github.com/torkelo/grafana-pro/pkg/configuration" - "github.com/torkelo/grafana-pro/pkg/server" + + "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/setting" ) @@ -18,23 +25,66 @@ var CmdWeb = cli.Command{ Flags: []cli.Flag{}, } +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", + }, + )) + + m.Use(macaron.Renderer(macaron.RenderOptions{ + Directory: path.Join(setting.StaticRootPath, "views"), + IndentJSON: macaron.Env != macaron.PROD, + Delims: macaron.Delims{Left: "[[", Right: "]]"}, + })) + + m.Use(middleware.GetContextHandler()) + return m +} + func runWeb(*cli.Context) { + setting.NewConfigContext() + setting.InitServices() + log.Info("Starting Grafana-Pro v.1-alpha") - setting.NewConfigContext() + m := newMacaron() - cfg := configuration.NewCfg(setting.HttpPort) - server, err := server.NewServer(cfg) - if err != nil { - time.Sleep(time.Second) - panic(err) + // index + m.Get("/", routes.Index) + + var err error + listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort) + log.Info("Listen: %v://%s%s", setting.Protocol, listenAddr, setting.AppSubUrl) + switch setting.Protocol { + case setting.HTTP: + err = http.ListenAndServe(listenAddr, m) + case setting.HTTPS: + err = http.ListenAndServeTLS(listenAddr, setting.CertFile, setting.KeyFile, m) + default: + log.Fatal(4, "Invalid protocol: %s", setting.Protocol) } - err = server.ListenAndServe() if err != nil { - log.Error("ListenAndServe failed: ", err) + log.Fatal(4, "Fail to start server: %v", err) } - - time.Sleep(time.Millisecond * 2000) - } diff --git a/pkg/middleware/logger.go b/pkg/middleware/logger.go new file mode 100644 index 00000000000..26e7d096c90 --- /dev/null +++ b/pkg/middleware/logger.go @@ -0,0 +1,58 @@ +// Copyright 2013 Martini Authors +// Copyright 2014 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package middleware + +import ( + "fmt" + "net/http" + "runtime" + "time" + + "github.com/Unknwon/macaron" + "github.com/torkelo/grafana-pro/pkg/log" +) + +var isWindows bool + +func init() { + isWindows = runtime.GOOS == "windows" +} + +// Logger returns a middleware handler that logs the request as it goes in and the response as it goes out. +func Logger() macaron.Handler { + return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) { + start := time.Now() + + rw := res.(macaron.ResponseWriter) + c.Next() + + content := fmt.Sprintf("Completed %s %v %s in %v", req.URL.Path, rw.Status(), http.StatusText(rw.Status()), time.Since(start)) + if !isWindows { + switch rw.Status() { + case 200: + content = fmt.Sprintf("\033[1;32m%s\033[0m", content) + case 304: + return + content = fmt.Sprintf("\033[1;33m%s\033[0m", content) + case 404: + content = fmt.Sprintf("\033[1;31m%s\033[0m", content) + case 500: + content = fmt.Sprintf("\033[1;36m%s\033[0m", content) + } + } + log.Info(content) + } +} diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go new file mode 100644 index 00000000000..28d39ee5a14 --- /dev/null +++ b/pkg/middleware/middleware.go @@ -0,0 +1,52 @@ +package middleware + +import ( + "time" + + "github.com/Unknwon/macaron" + "github.com/macaron-contrib/session" + + "github.com/torkelo/grafana-pro/pkg/log" + "github.com/torkelo/grafana-pro/pkg/models" +) + +type Context struct { + *macaron.Context + Session session.Store + + Account *models.Account + UserAccount *models.Account + + IsSigned bool +} + +func GetContextHandler() macaron.Handler { + return func(c *macaron.Context) { + ctx := &Context{ + Context: c, + } + + ctx.Data["PageStartTime"] = time.Now() + + c.Map(ctx) + } +} + +// Handle handles and logs error by given status. +func (ctx *Context) Handle(status int, title string, err error) { + if err != nil { + log.Error(4, "%s: %v", title, err) + if macaron.Env != macaron.PROD { + ctx.Data["ErrorMsg"] = err + } + } + + switch status { + case 404: + ctx.Data["Title"] = "Page Not Found" + case 500: + ctx.Data["Title"] = "Internal Server Error" + } + + ctx.HTML(status, "index") +} diff --git a/pkg/routes/index.go b/pkg/routes/index.go new file mode 100644 index 00000000000..50fde3fafd9 --- /dev/null +++ b/pkg/routes/index.go @@ -0,0 +1,12 @@ +package routes + +import "github.com/torkelo/grafana-pro/pkg/middleware" + +func Index(ctx *middleware.Context) { + ctx.HTML(200, "index") +} + +func NotFound(ctx *middleware.Context) { + ctx.Data["Title"] = "Page Not Found" + ctx.Handle(404, "index", nil) +} diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go deleted file mode 100644 index db9eb4de0e4..00000000000 --- a/pkg/routes/routes.go +++ /dev/null @@ -1,7 +0,0 @@ -package routes - -import "github.com/torkelo/grafana-pro/pkg/setting" - -func GlobalInit() { - setting.NewConfigContext() -} diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index cfee65428b7..6b7ce09625e 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -1,3 +1,6 @@ +// Copyright 2014 Unknwon +// Copyright 2014 Torkel Ödegaard + package setting import ( @@ -11,6 +14,8 @@ import ( "github.com/Unknwon/com" "github.com/Unknwon/goconfig" + "github.com/macaron-contrib/session" + "github.com/torkelo/grafana-pro/pkg/log" ) @@ -39,7 +44,12 @@ var ( HttpAddr, HttpPort string SshPort int CertFile, KeyFile string - DisableRouterLog bool + RouterLogging bool + StaticRootPath string + + // Session settings. + SessionProvider string + SessionConfig *session.Config // Global setting objects. Cfg *goconfig.ConfigFile @@ -127,4 +137,33 @@ func NewConfigContext() { if port != "" { HttpPort = port } + + StaticRootPath = Cfg.MustValue("server", "static_root_path", workDir) + RouterLogging = Cfg.MustBool("server", "router_logging", false) +} + +func initSessionService() { + SessionProvider = Cfg.MustValueRange("session", "provider", "memory", []string{"memory", "file"}) + + SessionConfig = new(session.Config) + SessionConfig.ProviderConfig = strings.Trim(Cfg.MustValue("session", "provider_config"), "\" ") + SessionConfig.CookieName = Cfg.MustValue("session", "cookie_name", "grafana_pro_sess") + SessionConfig.CookiePath = AppSubUrl + SessionConfig.Secure = Cfg.MustBool("session", "cookie_secure") + SessionConfig.EnableSetCookie = Cfg.MustBool("session", "enable_set_cookie", true) + SessionConfig.Gclifetime = Cfg.MustInt64("session", "gc_interval_time", 86400) + SessionConfig.Maxlifetime = Cfg.MustInt64("session", "session_life_time", 86400) + SessionConfig.SessionIDHashFunc = Cfg.MustValueRange("session", "session_id_hashfunc", + "sha1", []string{"sha1", "sha256", "md5"}) + SessionConfig.SessionIDHashKey = Cfg.MustValue("session", "session_id_hashkey", string(com.RandomCreateBytes(16))) + + if SessionProvider == "file" { + os.MkdirAll(path.Dir(SessionConfig.ProviderConfig), os.ModePerm) + } + + log.Info("Session Service Enabled") +} + +func InitServices() { + initSessionService() }