From 235bbc9c7ed8fa28294a23e33143a6b288d9a7ad Mon Sep 17 00:00:00 2001 From: "Haneysmith, Nathan" Date: Thu, 20 Aug 2015 11:15:36 -0700 Subject: [PATCH 001/166] custom login hints via config file --- conf/sample.ini | 3 +++ pkg/api/login.go | 1 + pkg/setting/setting.go | 2 ++ public/app/controllers/loginCtrl.js | 1 + public/app/partials/login.html | 2 +- 5 files changed, 8 insertions(+), 1 deletion(-) diff --git a/conf/sample.ini b/conf/sample.ini index 9a8d9aa3908..e8122766e9f 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -130,6 +130,9 @@ # Default role new users will be automatically assigned (if disabled above is set to true) ;auto_assign_org_role = Viewer +# Background text for the user field on the login page +;login_hint = email or username + #################################### Anonymous Auth ########################## [auth.anonymous] # enable anonymous access diff --git a/pkg/api/login.go b/pkg/api/login.go index 8863e1b10c1..d691270ad72 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -28,6 +28,7 @@ func LoginView(c *middleware.Context) { settings["googleAuthEnabled"] = setting.OAuthService.Google settings["githubAuthEnabled"] = setting.OAuthService.GitHub settings["disableUserSignUp"] = !setting.AllowUserSignUp + settings["loginHint"] = setting.LoginHint if !tryLoginUsingRememberCookie(c) { c.HTML(200, VIEW_INDEX) diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 3ac4b16db64..907d12479d8 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -79,6 +79,7 @@ var ( AllowUserOrgCreate bool AutoAssignOrg bool AutoAssignOrgRole string + LoginHint string // Http auth AdminUser string @@ -392,6 +393,7 @@ func NewConfigContext(args *CommandLineArgs) { AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true) AutoAssignOrg = users.Key("auto_assign_org").MustBool(true) AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Read Only Editor", "Viewer"}) + LoginHint = users.Key("login_hint").String() // anonymous access AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false) diff --git a/public/app/controllers/loginCtrl.js b/public/app/controllers/loginCtrl.js index 40e8009b399..9d27687c5ba 100644 --- a/public/app/controllers/loginCtrl.js +++ b/public/app/controllers/loginCtrl.js @@ -19,6 +19,7 @@ function (angular, config) { $scope.googleAuthEnabled = config.googleAuthEnabled; $scope.githubAuthEnabled = config.githubAuthEnabled; $scope.disableUserSignUp = config.disableUserSignUp; + $scope.loginHint = config.loginHint; $scope.loginMode = true; $scope.submitBtnText = 'Log in'; diff --git a/public/app/partials/login.html b/public/app/partials/login.html index f311c929b66..aaff2f6fd53 100644 --- a/public/app/partials/login.html +++ b/public/app/partials/login.html @@ -26,7 +26,7 @@ User
  • - +
  • From 74ea26615734776f091e42433204ddb1ff52fc69 Mon Sep 17 00:00:00 2001 From: "Haneysmith, Nathan" Date: Thu, 20 Aug 2015 11:20:40 -0700 Subject: [PATCH 002/166] add login hint to defaults.ini --- conf/defaults.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/conf/defaults.ini b/conf/defaults.ini index 7ca5191c4e7..39899efb9ff 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -131,6 +131,9 @@ auto_assign_org = true # Default role new users will be automatically assigned (if auto_assign_org above is set to true) auto_assign_org_role = Viewer +# Background text for the user field on the login page +login_hint = email or username + #################################### Anonymous Auth ########################## [auth.anonymous] # enable anonymous access From 4bb656b7043cdf156bdf47ded10efafcf87f6237 Mon Sep 17 00:00:00 2001 From: Julien Maitrehenry Date: Thu, 8 Oct 2015 00:22:09 -0400 Subject: [PATCH 003/166] #2834 - follow symlink --- pkg/plugins/plugins.go | 4 +- pkg/util/filepath.go | 98 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 pkg/util/filepath.go diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 2f7e5264e53..a5767c7a70b 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -5,10 +5,10 @@ import ( "errors" "os" "path" - "path/filepath" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" ) type PluginMeta struct { @@ -36,7 +36,7 @@ func scan(pluginDir string) error { pluginPath: pluginDir, } - if err := filepath.Walk(pluginDir, scanner.walker); err != nil { + if err := util.Walk(pluginDir, true, true, scanner.walker); err != nil { return err } diff --git a/pkg/util/filepath.go b/pkg/util/filepath.go new file mode 100644 index 00000000000..d0e27926956 --- /dev/null +++ b/pkg/util/filepath.go @@ -0,0 +1,98 @@ +package util + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" +) + +//WalkSkipDir is the Error returned when we want to skip descending into a directory +var WalkSkipDir = errors.New("skip this directory") + +//WalkFunc is a callback function called for each path as a directory is walked +//If resolvedPath != "", then we are following symbolic links. +type WalkFunc func(resolvedPath string, info os.FileInfo, err error) error + +//Walk walks a path, optionally following symbolic links, and for each path, +//it calls the walkFn passed. +// +//It is similar to filepath.Walk, except that it supports symbolic links and +//can detect infinite loops while following sym links. +//It solves the issue where your WalkFunc needs a path relative to the symbolic link +//(resolving links within walkfunc loses the path to the symbolic link for each traversal). +func Walk(path string, followSymlinks bool, detectSymlinkInfiniteLoop bool, walkFn WalkFunc) error { + info, err := os.Lstat(path) + if err != nil { + return err + } + var symlinkPathsFollowed map[string]bool + var resolvedPath string + if followSymlinks { + resolvedPath = path + if detectSymlinkInfiniteLoop { + symlinkPathsFollowed = make(map[string]bool, 8) + } + } + return walk(path, info, resolvedPath, symlinkPathsFollowed, walkFn) +} + +//walk walks the path. It is a helper/sibling function to Walk. +//It takes a resolvedPath into consideration. This way, paths being walked are +//always relative to the path argument, even if symbolic links were resolved). +// +//If resolvedPath is "", then we are not following symbolic links. +//If symlinkPathsFollowed is not nil, then we need to detect infinite loop. +func walk(path string, info os.FileInfo, resolvedPath string, + symlinkPathsFollowed map[string]bool, walkFn WalkFunc) error { + if info == nil { + return errors.New("Walk: Nil FileInfo passed") + } + err := walkFn(resolvedPath, info, nil) + if err != nil { + if info.IsDir() && err == WalkSkipDir { + err = nil + } + return err + } + if resolvedPath != "" && info.Mode()&os.ModeSymlink == os.ModeSymlink { + path2, err := os.Readlink(resolvedPath) + if err != nil { + return err + } + //vout("SymLink Path: %v, links to: %v", resolvedPath, path2) + if symlinkPathsFollowed != nil { + if _, ok := symlinkPathsFollowed[path2]; ok { + errMsg := "Potential SymLink Infinite Loop. Path: %v, Link To: %v" + return fmt.Errorf(errMsg, resolvedPath, path2) + } else { + symlinkPathsFollowed[path2] = true + } + } + info2, err := os.Lstat(path2) + if err != nil { + return err + } + return walk(path, info2, path2, symlinkPathsFollowed, walkFn) + } + if info.IsDir() { + list, err := ioutil.ReadDir(path) + if err != nil { + return walkFn(resolvedPath, info, err) + } + for _, fileInfo := range list { + path2 := filepath.Join(path, fileInfo.Name()) + var resolvedPath2 string + if resolvedPath != "" { + resolvedPath2 = filepath.Join(resolvedPath, fileInfo.Name()) + } + err = walk(path2, fileInfo, resolvedPath2, symlinkPathsFollowed, walkFn) + if err != nil { + return err + } + } + return nil + } + return nil +} From 7e2f653bc7131d09b91d620caa2fed8aa30809dd Mon Sep 17 00:00:00 2001 From: Nick Christus Date: Sat, 10 Oct 2015 14:17:07 -0400 Subject: [PATCH 004/166] added alerting tab stub and styles --- public/app/features/panel/partials/panel.html | 5 + public/app/panels/graph/alerting.html | 233 ++++++++++++++++++ public/app/panels/graph/module.js | 1 + public/img/CopyQuery.png | Bin 0 -> 144 bytes public/img/critical.svg | 15 ++ public/img/envelope.png | Bin 0 -> 187 bytes public/img/online.svg | 12 + public/img/warn-tiny.svg | 16 ++ public/img/warn.svg | 22 ++ public/less/alerting.less | 42 ++++ public/less/gfbox.less | 7 + public/less/grafana.less | 1 + public/less/overrides.less | 9 + public/less/tightform.less | 6 + 14 files changed, 369 insertions(+) create mode 100644 public/app/panels/graph/alerting.html create mode 100644 public/img/CopyQuery.png create mode 100644 public/img/critical.svg create mode 100644 public/img/envelope.png create mode 100644 public/img/online.svg create mode 100644 public/img/warn-tiny.svg create mode 100644 public/img/warn.svg create mode 100644 public/less/alerting.less diff --git a/public/app/features/panel/partials/panel.html b/public/app/features/panel/partials/panel.html index a7fef0f5d67..65f482161f8 100644 --- a/public/app/features/panel/partials/panel.html +++ b/public/app/features/panel/partials/panel.html @@ -35,6 +35,11 @@ + +
    + There are unsaved changes. + +
    diff --git a/public/app/panels/graph/alerting.html b/public/app/panels/graph/alerting.html new file mode 100644 index 00000000000..7230c6ef488 --- /dev/null +++ b/public/app/panels/graph/alerting.html @@ -0,0 +1,233 @@ +
    + Last updated by Grafana October 4, 2015 12:15:04 by $username +
    +
    General Alerting Options
    +
    +
      +
    • + Alert Title +
    • +
    • + +
    • +
    • + Alerting Backend +
    • +
    • + +
    • +
    • + + + +
    • +
    +
    +
    +
    +
    +
    +
    Choose your query:
    +

    Select an exising query to alert on:

    +
    +
    +
      +
    • +
    • None
    • +
    +
    +
    +
    +
    +
    +
    +
      +
    • +
    • A
    • +
    • apps
    • +
    • +
    • fakesite
    • +
    • counters
    • +
    • requests
    • +
    • count
    • +
    • scaleToSeconds(1)
    • +
    • aliasByNode(2)
    • +
    • +
    +
    +
    +
    +
    +
    +
      +
    • +
    • B
    • +
    • Metric: us-west-2 AWS/EC2 CPUUtilization Stats: Minimum Maximum Dimensions InstanceIS = i-b0e8a447 Alias {{stat}} Period 60
    • +
    • +
    +
    +
    +
    +
    +
    +
      +
    • +
    • C
    • +
    • Query: avg(counters_logins) by(server) Legend Format: {{app}} - {{server}} Step: 1s Resolution: 1/2
    • +
    • +
    +
    +
    +
    +
    +
    +
      +
    • +
    • D
    • +
    • SELECT mean(value) FROM logins.count WHERE hostname = /$Hostname$/ GROUP BY time($internal) hostname
    • +
    • +
    +
    +
    +
    +
    +
    +
      +
    • +
    • E
    • +
    • Metric: apps.backend.backend_01.counters.requests.count Alias: Bristow Aggregator: Sum Downsample: 1m Aggregator Sum Tags host = test
    • +
    • +
    +
    +
    +
    +
    +
    +

    Or write a new custom alerting query:

    +
    +
    +
      +
    • +
    • + + + +
    • +
    • + select metric +
    • +
    • + +
    • +
    +
    +
    +
    +
    +
    +
    +
    Define Your States
    +
    +
      +
    • + by +
    • +
    • + +
    • +
    • + the values in the query over the last +
    • +
    • + +
    • +
    +
    +
    +
    +
    +
    +
    +
    +
      +
    • + Warn +
    • +
    • + +
    • +
    • + +
    • +
    • + .notify +
    • +
    • + +
    • +
    • + + + +
    • +
    +
    +
    +
    +
      +
    • + Critical +
    • +
    • + +
    • +
    • + +
    • +
    • + .notify +
    • +
    • + +
    • +
    • + + + +
    • +
    +
    +
    +
    +
    +
    +
    +
    What to Say Variables | Preview
    +
    +
      +
    • + Summary +
    • +
    • + +
    • +
    +
    +
    +
    +
      +
    • + Description +
    • +
    • + +
    • +
    +
    +
    +
    +
    diff --git a/public/app/panels/graph/module.js b/public/app/panels/graph/module.js index 5cdeab799de..a1e56f8c5eb 100644 --- a/public/app/panels/graph/module.js +++ b/public/app/panels/graph/module.js @@ -34,6 +34,7 @@ function (angular, $, _, kbn, moment, TimeSeries, PanelMeta) { $scope.panelMeta.addEditorTab('Axes & Grid', 'app/panels/graph/axisEditor.html'); $scope.panelMeta.addEditorTab('Display Styles', 'app/panels/graph/styleEditor.html'); $scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html'); + $scope.panelMeta.addEditorTab('Alerting', 'app/panels/graph/alerting.html'); $scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()'); $scope.panelMeta.addExtendedMenuItem('Toggle legend', '', 'toggleLegend()'); diff --git a/public/img/CopyQuery.png b/public/img/CopyQuery.png new file mode 100644 index 0000000000000000000000000000000000000000..b9829c23b2ff6146760e6c5f5264a713aceb7e99 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^d_XL~!2%?E=C*AFQsJI1jv*C{$u>XU+bc6mCH%=h z&$5jFPx*lfES-Y-l^f + + + + + + + diff --git a/public/img/envelope.png b/public/img/envelope.png new file mode 100644 index 0000000000000000000000000000000000000000..59ef8a38aba65a69c72ab8a97b1f3421e35d8cac GIT binary patch literal 187 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh0wlLOK8*rWO`a}}Ar*{sFC1NX$Uvsyqkh?Y zwi2b3NhwY2ITLh(Vprr`DCRAH{+*PM@abWfebchTzZ`Iu^9tMWYgE%|E;Ht-+X6|`CL7_(nktyYo+`**QehTO=4 lFAt}D-Cvh6m;2rg2C+2#McIiyOhDH#c)I$ztaD0e0s!QaM^gX* literal 0 HcmV?d00001 diff --git a/public/img/online.svg b/public/img/online.svg new file mode 100644 index 00000000000..279fbec3a90 --- /dev/null +++ b/public/img/online.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/public/img/warn-tiny.svg b/public/img/warn-tiny.svg new file mode 100644 index 00000000000..e8e91f4452c --- /dev/null +++ b/public/img/warn-tiny.svg @@ -0,0 +1,16 @@ + + + + + + + diff --git a/public/img/warn.svg b/public/img/warn.svg new file mode 100644 index 00000000000..8caee82c3f5 --- /dev/null +++ b/public/img/warn.svg @@ -0,0 +1,22 @@ + + + + + + + diff --git a/public/less/alerting.less b/public/less/alerting.less new file mode 100644 index 00000000000..9054584e23e --- /dev/null +++ b/public/less/alerting.less @@ -0,0 +1,42 @@ +.copy-query { + display: block; + width: 30px; + height: 36px; + margin: 0; + padding: 0; + border: 0; + background: transparent url(/img/CopyQuery.png) 50% 50% no-repeat; + cursor: pointer; +} + +.alert-state { + display: inline-block; + padding-left: 30px; + background: 0 50% no-repeat; + background-size: 20px auto; +} + +.alert-state-online { + background-image: url('/img/online.svg'); +} + +.alert-state-warning { + background-image: url('/img/warn-tiny.svg'); +} + +.alert-state-critical { + background-image: url('/img/critical.svg'); +} + +.alert-notify-emails { + width: 400px; + border-right: 1px solid @black; +} + +.alert-notify-emails .bootstrap-tagsinput { + width: 394px; // offset for 8px left padding and border width +} + +.alert-notify-emails .bootstrap-tagsinput input { + border: 0; +} diff --git a/public/less/gfbox.less b/public/less/gfbox.less index eb386f740d7..48d0b6a7ca8 100644 --- a/public/less/gfbox.less +++ b/public/less/gfbox.less @@ -25,6 +25,13 @@ } } +.gf-box-header-save-btn { + padding: 7px 0; + float: right; + color: @grayLight; + font-style: italic; +} + .gf-box-body { padding: 20px; min-height: 150px; diff --git a/public/less/grafana.less b/public/less/grafana.less index 6e23faa214d..0d4e5f44dc0 100644 --- a/public/less/grafana.less +++ b/public/less/grafana.less @@ -18,6 +18,7 @@ @import "fonts.less"; @import "tabs.less"; @import "timepicker.less"; +@import "alerting.less"; .row-control-inner { padding:0px; diff --git a/public/less/overrides.less b/public/less/overrides.less index fb6544cd99c..229627c0896 100644 --- a/public/less/overrides.less +++ b/public/less/overrides.less @@ -560,6 +560,15 @@ div.flot-text { background-color: darken(@purple, 10%); } +.label-tag-email { + padding-left: 25px; + background: @black url(/img/envelope.png) 5px 50% no-repeat !important; + border-color: @black !important; + font-size: 12px; + font-weight: normal; + border-radius: 5px; +} + // inspector .inspector-request-table { diff --git a/public/less/tightform.less b/public/less/tightform.less index 494497653ed..f65a991613a 100644 --- a/public/less/tightform.less +++ b/public/less/tightform.less @@ -156,6 +156,12 @@ input[type=checkbox].tight-form-checkbox { margin: 0; } +.tight-form-textarea { + height: 200px; + margin: 0; + box-sizing: border-box; +} + select.tight-form-input { border: none; border-right: 1px solid @grafanaTargetSegmentBorder; From 23404decead89577dad4b23471aa1bf6645dc263 Mon Sep 17 00:00:00 2001 From: Nick Christus Date: Sun, 11 Oct 2015 16:59:40 -0400 Subject: [PATCH 005/166] added global alerts list stub and styles --- public/app/core/routes/all.js | 3 + .../dashboard/partials/globalAlerts.html | 282 ++++++++++++++++++ public/app/panels/graph/alerting.html | 119 ++++---- public/less/filter-list.less | 167 +++++++++++ public/less/gfbox.less | 4 + public/less/grafana.less | 1 + public/less/variables.dark.less | 6 + public/less/variables.light.less | 6 + 8 files changed, 522 insertions(+), 66 deletions(-) create mode 100644 public/app/features/dashboard/partials/globalAlerts.html create mode 100644 public/less/filter-list.less diff --git a/public/app/core/routes/all.js b/public/app/core/routes/all.js index a7e36a0e228..7a912621ba5 100644 --- a/public/app/core/routes/all.js +++ b/public/app/core/routes/all.js @@ -131,6 +131,9 @@ define([ templateUrl: 'app/partials/reset_password.html', controller : 'ResetPasswordCtrl', }) + .when('/global-alerts', { + templateUrl: 'app/features/dashboard/partials/globalAlerts.html', + }) .otherwise({ templateUrl: 'app/partials/error.html', controller: 'ErrorCtrl' diff --git a/public/app/features/dashboard/partials/globalAlerts.html b/public/app/features/dashboard/partials/globalAlerts.html new file mode 100644 index 00000000000..d66c7e98d8c --- /dev/null +++ b/public/app/features/dashboard/partials/globalAlerts.html @@ -0,0 +1,282 @@ + + + + +
    +
    +

    Global alerts

    + +
    +
    +
      +
    • Filters:
    • +
    • Alert State
    • +
    • +
    • Dashboards
    • +
    • +
    • + + + +
    • +
    +
    +
    +
    +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + 2 selected, showing 6 of 6 total +
    • +
    +
      +
    • + +
      +
      Alert query configure alerting
      +
      +
        +
      • A
      • +
      • apps
      • +
      • +
      • fakesite
      • +
      • counters
      • +
      • requests
      • +
      • count
      • +
      • scaleToSeconds(1)
      • +
      • aliasByNode(2)
      • +
      +
      +
      +
      +
    • +
    • + +
      +
      Alert query configure alerting
      +
      +
        +
      • A
      • +
      • apps
      • +
      • +
      • fakesite
      • +
      • counters
      • +
      • requests
      • +
      • count
      • +
      • scaleToSeconds(1)
      • +
      • aliasByNode(2)
      • +
      +
      +
      +
      +
    • +
    • + +
      +
      Alert query configure alerting
      +
      +
        +
      • A
      • +
      • apps
      • +
      • +
      • fakesite
      • +
      • counters
      • +
      • requests
      • +
      • count
      • +
      • scaleToSeconds(1)
      • +
      • aliasByNode(2)
      • +
      +
      +
      +
      +
    • +
    • + +
      +
      Alert query configure alerting
      +
      +
        +
      • A
      • +
      • apps
      • +
      • +
      • fakesite
      • +
      • counters
      • +
      • requests
      • +
      • count
      • +
      • scaleToSeconds(1)
      • +
      • aliasByNode(2)
      • +
      +
      +
      +
      +
    • +
    • + +
      +
      Alert query configure alerting
      +
      +
        +
      • A
      • +
      • apps
      • +
      • +
      • fakesite
      • +
      • counters
      • +
      • requests
      • +
      • count
      • +
      • scaleToSeconds(1)
      • +
      • aliasByNode(2)
      • +
      +
      +
      +
      +
    • +
    +
    +
    diff --git a/public/app/panels/graph/alerting.html b/public/app/panels/graph/alerting.html index 7230c6ef488..9e08fc4f6cb 100644 --- a/public/app/panels/graph/alerting.html +++ b/public/app/panels/graph/alerting.html @@ -31,77 +31,64 @@
    Choose your query:

    Select an exising query to alert on:

    -
    -
    -
      -
    • -
    • None
    • -
    -
    -
    +
    +
      +
    • +
    • None
    • +
    +
    -
    -
    -
    -
      -
    • -
    • A
    • -
    • apps
    • -
    • -
    • fakesite
    • -
    • counters
    • -
    • requests
    • -
    • count
    • -
    • scaleToSeconds(1)
    • -
    • aliasByNode(2)
    • -
    • -
    -
    -
    +
    +
      +
    • +
    • A
    • +
    • apps
    • +
    • +
    • fakesite
    • +
    • counters
    • +
    • requests
    • +
    • count
    • +
    • scaleToSeconds(1)
    • +
    • aliasByNode(2)
    • +
    • +
    +
    -
    -
    -
      -
    • -
    • B
    • -
    • Metric: us-west-2 AWS/EC2 CPUUtilization Stats: Minimum Maximum Dimensions InstanceIS = i-b0e8a447 Alias {{stat}} Period 60
    • -
    • -
    -
    -
    +
    +
      +
    • +
    • B
    • +
    • Metric: us-west-2 AWS/EC2 CPUUtilization Stats: Minimum Maximum Dimensions InstanceIS = i-b0e8a447 Alias {{stat}} Period 60
    • +
    • +
    +
    -
    -
    -
      -
    • -
    • C
    • -
    • Query: avg(counters_logins) by(server) Legend Format: {{app}} - {{server}} Step: 1s Resolution: 1/2
    • -
    • -
    -
    -
    +
    +
      +
    • +
    • C
    • +
    • Query: avg(counters_logins) by(server) Legend Format: {{app}} - {{server}} Step: 1s Resolution: 1/2
    • +
    • +
    +
    -
    -
    -
      -
    • -
    • D
    • -
    • SELECT mean(value) FROM logins.count WHERE hostname = /$Hostname$/ GROUP BY time($internal) hostname
    • -
    • -
    -
    -
    +
    +
      +
    • +
    • D
    • +
    • SELECT mean(value) FROM logins.count WHERE hostname = /$Hostname$/ GROUP BY time($internal) hostname
    • +
    • +
    +
    -
    -
    -
      -
    • -
    • E
    • -
    • Metric: apps.backend.backend_01.counters.requests.count Alias: Bristow Aggregator: Sum Downsample: 1m Aggregator Sum Tags host = test
    • -
    • -
    -
    -
    +
    +
      +
    • +
    • E
    • +
    • Metric: apps.backend.backend_01.counters.requests.count Alias: Bristow Aggregator: Sum Downsample: 1m Aggregator Sum Tags host = test
    • +
    • +
    +
    diff --git a/public/less/filter-list.less b/public/less/filter-list.less new file mode 100644 index 00000000000..50da5864db0 --- /dev/null +++ b/public/less/filter-list.less @@ -0,0 +1,167 @@ +// ========================================================================== +// FILTER LIST +// ========================================================================== + + + +// List +// -------------------------------------------------------------------------- + +.filter-list { + max-width: 1000px; + margin: 0; + padding: 0; + list-style: none; +} + +.filter-list > li { + padding: 10px; + margin-bottom: 2px; + background: @grafanaPanelBackground; + + &:last-child { + margin: 0; + } +} + + + +// Card +// -------------------------------------------------------------------------- + +.filter-list-card { + display: table; + width: 100%; + margin: 0; + padding: 0; + list-style: none; +} + +.filter-list-card > li { + display: table-cell; + vertical-align: top; +} + +.filter-list-card-select { + width: 23px; + padding-right: 5px; +} + +.filter-list-card-title { + display: block; + font-size: 16px; + font-weight: normal; +} + +.filter-list-card-status { + color: #777; + font-size: 12px; +} + +.filter-list-card-state { + display: inline-block; + padding: 5px 0 0 28px; + background: 0 bottom no-repeat; + background-size: 24px auto; + font-size: 14px; + text-transform: uppercase; + + &.online { + background-image: url('/img/online.svg'); + color: @online; + } + + &.warn { + background-image: url('/img/warn-tiny.svg'); + color: @warn; + } + + &.critical { + background-image: url('/img/critical.svg'); + color: @critical; + } +} + +.filter-list-card-controls { + float: right; +} + +.filter-list-card-links, +.filter-list-card-config, +.filter-list-card-expand { + display: inline-block; + vertical-align: middle; +} + +.filter-list-card-link { + display: block; + color: #777; + text-align: right; + + > a { + color: #777; + } +} + +.filter-list-card-config { + padding: 8px 8px 8px 16px; + color: #777; + font-size: 25px; + + > a { + color: #777; + } +} + +.filter-list-card-expand { + width: 20px; + padding: 8px 0 8px 8px; + color: #aaa; + font-size: 30px; + text-align: center; + cursor: pointer; +} + +.filter-list-card-details { + padding: 20px 0 0 30px; +} + +.filter-list-card-details-heading { + font-weight: normal; + + > a { + float: right; + color: @blue; + font-size: 12px; + } +} + + + +// Filters +// -------------------------------------------------------------------------- + +.filter-list-filters { + display: inline-block; + margin-bottom: 40px; +} + + + +// Actions +// -------------------------------------------------------------------------- + +.filter-list-actions { + margin: 0 0 10px; + padding: 0; + list-style: none; +} + +.filter-list-actions > li { + display: inline-block; + margin-right: 10px; +} + +.filter-list-actions-selected { + text-transform: uppercase; +} diff --git a/public/less/gfbox.less b/public/less/gfbox.less index 48d0b6a7ca8..d173ace1787 100644 --- a/public/less/gfbox.less +++ b/public/less/gfbox.less @@ -80,6 +80,10 @@ max-width: 653px; } +.page-wide { + max-width: none; +} + .admin-page { max-width: 800px; margin-left: 10px; diff --git a/public/less/grafana.less b/public/less/grafana.less index 0d4e5f44dc0..aa58f749b82 100644 --- a/public/less/grafana.less +++ b/public/less/grafana.less @@ -19,6 +19,7 @@ @import "tabs.less"; @import "timepicker.less"; @import "alerting.less"; +@import "filter-list.less"; .row-control-inner { padding:0px; diff --git a/public/less/variables.dark.less b/public/less/variables.dark.less index 3324c3f4b86..af8f3cdec75 100644 --- a/public/less/variables.dark.less +++ b/public/less/variables.dark.less @@ -25,6 +25,12 @@ @purple: #9933CC; @variable: #32D1DF; +// Status colors +// ------------------------- +@online: #10a345; +@warn: #ffc03c; +@critical: #ed2e18; + // grafana Variables // ------------------------- @grafanaPanelBackground: @grayDarker; diff --git a/public/less/variables.light.less b/public/less/variables.light.less index 27dcf8575f4..67a8dfd7257 100644 --- a/public/less/variables.light.less +++ b/public/less/variables.light.less @@ -31,6 +31,12 @@ @purple: #9954BB; @variable: #2AB2E4; +// Status colors +// ------------------------- +@online: #10a345; +@warn: #ffc03c; +@critical: #ed2e18; + // grafana Variables // ------------------------- @grafanaPanelBackground: @grayLighter; From cf89b565a63c6aa869293e6e770c6501f418e780 Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Tue, 6 Oct 2015 17:20:50 +0800 Subject: [PATCH 006/166] initial import of thirdParty route support --- pkg/api/api.go | 2 + pkg/api/index.go | 20 +++++ pkg/api/thirdparty.go | 75 +++++++++++++++++++ pkg/models/third_party.go | 31 ++++++++ pkg/plugins/plugins.go | 47 +++++++++++- public/app/app.js | 4 +- public/app/controllers/sidemenuCtrl.js | 10 +++ public/app/core/routes/all.js | 2 +- .../raintank/plugin.json | 40 ++++++++++ public/views/index.html | 13 +++- 10 files changed, 238 insertions(+), 6 deletions(-) create mode 100644 pkg/api/thirdparty.go create mode 100644 pkg/models/third_party.go create mode 100644 public/app/plugins/thirdPartyIntegration/raintank/plugin.json diff --git a/pkg/api/api.go b/pkg/api/api.go index 27eb3c749db..e9d85dc0c8d 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -185,5 +185,7 @@ func Register(r *macaron.Macaron) { // rendering r.Get("/render/*", reqSignedIn, RenderToPng) + InitThirdPartyRoutes(r) + r.NotFound(NotFoundHandler) } diff --git a/pkg/api/index.go b/pkg/api/index.go index 556db006b2f..072878a2cfd 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -3,6 +3,7 @@ package api import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/middleware" + "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/setting" ) @@ -51,6 +52,25 @@ func setIndexViewData(c *middleware.Context) error { if setting.GoogleTagManagerId != "" { c.Data["GoogleTagManagerId"] = setting.GoogleTagManagerId } + // This can be loaded from the DB/file to allow 3rdParty integration + thirdPartyJs := make([]string, 0) + thirdPartyCss := make([]string, 0) + thirdPartyMenu := make([]*plugins.ThirdPartyMenuItem, 0) + for _, integration := range plugins.Integrations { + for _, js := range integration.Js { + thirdPartyJs = append(thirdPartyJs, js.Src) + } + for _, css := range integration.Css { + thirdPartyCss = append(thirdPartyCss, css.Href) + } + for _, item := range integration.MenuItems { + thirdPartyMenu = append(thirdPartyMenu, item) + } + + } + c.Data["ThirdPartyJs"] = thirdPartyJs + c.Data["ThirdPartyCss"] = thirdPartyCss + c.Data["ThirdPartyMenu"] = thirdPartyMenu return nil } diff --git a/pkg/api/thirdparty.go b/pkg/api/thirdparty.go new file mode 100644 index 00000000000..588c3b40ce7 --- /dev/null +++ b/pkg/api/thirdparty.go @@ -0,0 +1,75 @@ +package api + +import ( + "encoding/json" + "github.com/Unknwon/macaron" + "github.com/grafana/grafana/pkg/middleware" + "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/util" + "log" + "net/http" + "net/http/httputil" + "net/url" +) + +func InitThirdPartyRoutes(r *macaron.Macaron) { + /* + // Handle Auth and role requirements + if route.ReqSignedIn { + c.Invoke(middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true})) + } + if route.ReqGrafanaAdmin { + c.Invoke(middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})) + } + if route.ReqRole != nil { + if *route.ReqRole == m.ROLE_EDITOR { + c.Invoke(middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)) + } + if *route.ReqRole == m.ROLE_ADMIN { + c.Invoke(middleware.RoleAuth(m.ROLE_ADMIN)) + } + } + */ + for _, integration := range plugins.Integrations { + log.Printf("adding routes for integration") + for _, route := range integration.Routes { + log.Printf("adding route %s %s", route.Method, route.Path) + r.Route(util.JoinUrlFragments("/thirdparty/", route.Path), route.Method, ThirdParty(route.Url)) + } + } +} + +func ThirdParty(routeUrl string) macaron.Handler { + return func(c *middleware.Context) { + path := c.Params("*") + + //Create a HTTP header with the context in it. + ctx, err := json.Marshal(c.SignedInUser) + if err != nil { + c.JsonApiErr(500, "Not found", err) + return + } + log.Printf(string(ctx)) + targetUrl, _ := url.Parse(routeUrl) + proxy := NewThirdPartyProxy(string(ctx), path, targetUrl) + proxy.Transport = dataProxyTransport + proxy.ServeHTTP(c.RW(), c.Req.Request) + } +} + +func NewThirdPartyProxy(ctx string, proxyPath string, targetUrl *url.URL) *httputil.ReverseProxy { + director := func(req *http.Request) { + req.URL.Scheme = targetUrl.Scheme + req.URL.Host = targetUrl.Host + req.Host = targetUrl.Host + + req.URL.Path = util.JoinUrlFragments(targetUrl.Path, proxyPath) + + // clear cookie headers + req.Header.Del("Cookie") + req.Header.Del("Set-Cookie") + req.Header.Add("Grafana-Context", ctx) + } + + return &httputil.ReverseProxy{Director: director} +} diff --git a/pkg/models/third_party.go b/pkg/models/third_party.go new file mode 100644 index 00000000000..0bce0d86f8f --- /dev/null +++ b/pkg/models/third_party.go @@ -0,0 +1,31 @@ +package models + +type ThirdPartyRoute struct { + Path string `json:"path"` + Method string `json:"method"` + ReqSignedIn bool `json:"req_signed_in"` + ReqGrafanaAdmin bool `json:"req_grafana_admin"` + ReqRole RoleType `json:"req_role"` + Url string `json:"url"` +} + +type ThirdPartyJs struct { + src string `json:"src"` +} + +type ThirdPartyMenuItem struct { + Text string `json:"text"` + Icon string `json:"icon"` + Href string `json:"href"` +} + +type ThirdPartyCss struct { + Href string `json:"href"` +} + +type ThirdPartyIntegration struct { + Routes []*ThirdPartyRoute `json:"routes"` + Js []*ThirdPartyJs `json:"js"` + Css []*ThirdPartyCss `json:"css"` + MenuItems []*ThirdPartyMenuItem `json:"menu_items"` +} diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 2f7e5264e53..c411a47a977 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -16,8 +16,44 @@ type PluginMeta struct { Name string `json:"name"` } +type ThirdPartyRoute struct { + Path string `json:"path"` + Method string `json:"method"` + ReqSignedIn bool `json:"req_signed_in"` + ReqGrafanaAdmin bool `json:"req_grafana_admin"` + ReqRole string `json:"req_role"` + Url string `json:"url"` +} + +type ThirdPartyJs struct { + Src string `json:"src"` +} + +type ThirdPartyMenuItem struct { + Text string `json:"text"` + Icon string `json:"icon"` + Href string `json:"href"` +} + +type ThirdPartyCss struct { + Href string `json:"href"` +} + +type ThirdPartyIntegration struct { + Routes []*ThirdPartyRoute `json:"routes"` + Js []*ThirdPartyJs `json:"js"` + Css []*ThirdPartyCss `json:"css"` + MenuItems []*ThirdPartyMenuItem `json:"menu_items"` +} + +type ThirdPartyPlugin struct { + PluginType string `json:"pluginType"` + Integration ThirdPartyIntegration `json:"integration"` +} + var ( - DataSources map[string]interface{} + DataSources map[string]interface{} + Integrations []ThirdPartyIntegration ) type PluginScanner struct { @@ -31,6 +67,7 @@ func Init() { func scan(pluginDir string) error { DataSources = make(map[string]interface{}) + Integrations = make([]ThirdPartyIntegration, 0) scanner := &PluginScanner{ pluginPath: pluginDir, @@ -94,6 +131,14 @@ func (scanner *PluginScanner) loadPluginJson(path string) error { DataSources[datasourceType.(string)] = pluginJson } + if pluginType == "thirdPartyIntegration" { + p := ThirdPartyPlugin{} + reader.Seek(0, 0) + if err := jsonParser.Decode(&p); err != nil { + return err + } + Integrations = append(Integrations, p.Integration) + } return nil } diff --git a/public/app/app.js b/public/app/app.js index 4f30df34d89..16ce8a454d8 100644 --- a/public/app/app.js +++ b/public/app/app.js @@ -37,6 +37,8 @@ function (angular, $, _, appLevelRequire) { } else { _.extend(module, register_fns); } + // push it into the apps dependencies + apps_deps.push(module.name); return module; }; @@ -66,8 +68,6 @@ function (angular, $, _, appLevelRequire) { var module_name = 'grafana.'+type; // create the module app.useModule(angular.module(module_name, [])); - // push it into the apps dependencies - apps_deps.push(module_name); }); var preBootRequires = [ diff --git a/public/app/controllers/sidemenuCtrl.js b/public/app/controllers/sidemenuCtrl.js index b7ba32f0d35..bd6538b15cb 100644 --- a/public/app/controllers/sidemenuCtrl.js +++ b/public/app/controllers/sidemenuCtrl.js @@ -29,6 +29,16 @@ function (angular, _, $, config) { href: $scope.getUrl("/datasources"), }); } + + if (_.isArray(window.thirdParty.MainLinks)) { + _.forEach(window.thirdParty.MainLinks, function(item) { + $scope.mainLinks.push({ + text: item.text, + icon: item.icon, + href: $scope.getUrl(item.href) + }); + }); + } }; $scope.loadOrgs = function() { diff --git a/public/app/core/routes/all.js b/public/app/core/routes/all.js index a7e36a0e228..b0e41bc956d 100644 --- a/public/app/core/routes/all.js +++ b/public/app/core/routes/all.js @@ -10,7 +10,7 @@ define([ $locationProvider.html5Mode(true); var loadOrgBundle = new BundleLoader.BundleLoader('app/features/org/all'); - + console.log("adding grafana routes"); $routeProvider .when('/', { templateUrl: 'app/partials/dashboard.html', diff --git a/public/app/plugins/thirdPartyIntegration/raintank/plugin.json b/public/app/plugins/thirdPartyIntegration/raintank/plugin.json new file mode 100644 index 00000000000..7c1dcb0dcee --- /dev/null +++ b/public/app/plugins/thirdPartyIntegration/raintank/plugin.json @@ -0,0 +1,40 @@ +{ + "pluginType": "thirdPartyIntegration", + "integration": { + "routes": [ + { + "path": "/raintank/public/*", + "method": "*", + "req_signed_in": false, + "req_grafana_admin": false, + "req_role": "Admin", + "url": "http://localhost:3001/public" + }, + { + "path": "/raintank/api/*", + "method": "*", + "req_signed_in": true, + "req_grafana_admin": false, + "req_role": "Admin", + "url": "http://localhost:3001/api" + } + ], + "css": [ + { + "href": "/path/to/file.css" + } + ], + "js": [ + { + "src": "/raintank/public/app.js" + } + ], + "menu_items": [ + { + "text": "Menu Text", + "icon": "fa fa-fw fa-database", + "href": "/raintank/test" + } + ] + } +} diff --git a/public/views/index.html b/public/views/index.html index 600e18fb954..9998b0d2b68 100644 --- a/public/views/index.html +++ b/public/views/index.html @@ -13,6 +13,9 @@ [[else]] [[end]] + [[ range $css := .ThirdPartyCss ]] + + [[ end ]] @@ -53,11 +56,17 @@ settings: [[.Settings]], }; + window.thirdParty = { + MainLinks: [[.ThirdPartyMenu]] + }; + require(['app/app'], function (app) { - app.boot(); + app.boot(); }) - + [[ range $js := .ThirdPartyJs]] + + [[ end ]] [[if .GoogleAnalyticsId]] - [[ range $js := .ThirdPartyJs]] - + [[ range $js := .ExternalPluginJs]] + [[ end ]] [[if .GoogleAnalyticsId]] - [[ range $js := .ExternalPluginJs]] - - [[ end ]] + [[if .GoogleAnalyticsId]]