diff --git a/.floo b/.floo
deleted file mode 100644
index 1c2038f98cc..00000000000
--- a/.floo
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "url": "https://floobits.com/raintank/grafana"
-}
diff --git a/.flooignore b/.flooignore
deleted file mode 100644
index 43cddf93bdf..00000000000
--- a/.flooignore
+++ /dev/null
@@ -1,12 +0,0 @@
-#*
-*.o
-*.pyc
-*.pyo
-*~
-extern/
-node_modules/
-tmp/
-data/
-vendor/
-public_gen/
-dist/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 703e91d87f5..b17a86893ad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,17 +2,21 @@
### WIP (in develop branch currently as its unstable or unfinished)
- Dashboard folders
-- User groups
+- User groups
- Dashboard permissions (on folder & dashboard level), permissions can be assigned to groups or individual users
- UX changes to nav & side menu
- New dashboard grid layout system
# 4.5.0 (unreleased)
+# 4.5.0-beta1 (2017-09-05)
+
## New Features
* **Table panel**: Render cell values as links that can have an url template that uses variables from current table row. [#3754](https://github.com/grafana/grafana/issues/3754)
* **Elasticsearch**: Add ad hoc filters directly by clicking values in table panel [#8052](https://github.com/grafana/grafana/issues/8052).
+* **MySQL**: New rich query editor with syntax highlighting
+* **Prometheus**: New rich query editor with syntax highlighting, metric & range auto complete and integrated function docs. [#5117](https://github.com/grafana/grafana/issues/5117)
## Enhancements
@@ -20,20 +24,20 @@
* **Graphite**: Calls to Graphite api /metrics/find now include panel or dashboad time range (from & until) in most cases, [#8055](https://github.com/grafana/grafana/issues/8055)
* **Graphite**: Added new graphite 1.0 functions, available if you set version to 1.0.x in data source settings. New Functions: mapSeries, reduceSeries, isNonNull, groupByNodes, offsetToZero, grep, weightedAverage, removeEmptySeries, aggregateLine, averageOutsidePercentile, delay, exponentialMovingAverage, fallbackSeries, integralByInterval, interpolate, invert, linearRegression, movingMin, movingMax, movingSum, multiplySeriesWithWildcards, pow, powSeries, removeBetweenPercentile, squareRoot, timeSlice, closes [#8261](https://github.com/grafana/grafana/issues/8261)
- **Elasticsearch**: Ad-hoc filters now use query phrase match filters instead of term filters, works on non keyword/raw fields [#9095](https://github.com/grafana/grafana/issues/9095).
-
+
+### Breaking change
+
+* **InfluxDB/Elasticsearch**: The panel & data source option named "Group by time interval" is now named "Min time interval" and does now always define a lower limit for the auto group by time. Without having to use `>` prefix (that prefix still works). This should in theory have close to zero actual impact on existing dashboards. It does mean that if you used this setting to define a hard group by time interval of, say "1d", if you zoomed to a time range wide enough the time range could increase above the "1d" range as the setting is now always considered a lower limit.
+
## Changes
* **InfluxDB**: Change time range filter for absolute time ranges to be inclusive instead of exclusive [#8319](https://github.com/grafana/grafana/issues/8319), thx [@Oxydros](https://github.com/Oxydros)
-
-## Bug Fixes
-
-* **Modals**: Maintain scroll position after opening/leaving modal [#8800](https://github.com/grafana/grafana/issues/8800)
-* **Templating**: You cannot select data source variables as data source for other template variables [#7510](https://github.com/grafana/grafana/issues/7510)
-
-# 4.4.4 (unreleased)
+* **InfluxDB**: Added paranthesis around tag filters in queries [#9131](https://github.com/grafana/grafana/pull/9131)
## Bug Fixes
+* **Modals**: Maintain scroll position after opening/leaving modal [#8800](https://github.com/grafana/grafana/issues/8800)
+* **Templating**: You cannot select data source variables as data source for other template variables [#7510](https://github.com/grafana/grafana/issues/7510)
* **MySQL/Postgres**: Fix for max_idle_conn option default which was wrongly set to zero which does not mean unlimited but means zero, which in practice kind of disables connection pooling, which is not good. Fixes [#8513](https://github.com/grafana/grafana/issues/8513)
# 4.4.3 (2017-08-07)
diff --git a/README.md b/README.md
index 41f777d5dec..5de6a7122ab 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[Grafana](https://grafana.com) [](https://circleci.com/gh/grafana/grafana)
+[Grafana](https://grafana.com) [](https://circleci.com/gh/grafana/grafana) [](https://goreportcard.com/report/github.com/grafana/grafana)
================
[Website](https://grafana.com) |
[Twitter](https://twitter.com/grafana) |
@@ -124,7 +124,7 @@ To build the frontend assets only on changes:
```bash
sudo npm install -g grunt-cli # to do only once to install grunt command line interface
-grunt watch
+grunt && grunt watch
```
### Recompile backend on source change
diff --git a/ROADMAP.md b/ROADMAP.md
index 260c4151442..62e6719c4f0 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,31 +1,29 @@
-# Roadmap (2017-04-23)
+# Roadmap (2017-08-29)
This roadmap is a tentative plan for the core development team. Things change constantly as PRs come in and priorities change.
But it will give you an idea of our current vision and plan.
### Short term (1-4 months)
- - New Heatmap Panel (Implemented and available in master)
- - Support for MySQL & Postgres as data sources (Work started and a alpha version for MySQL is available in master)
- - User Groups & Dashboard folders with ACLs (work started, not yet completed, https://github.com/grafana/grafana/issues/1611#issuecomment-287742633)
- - Improve new user UX
- - Improve docs
- - Support for alerting for Elasticsearch (can be tested in [branch](https://github.com/grafana/grafana/tree/alerting-elasticsearch) but needs more work)
- - Graph annotations (create from grafana, region annotations, better annotation viz)
- - Improve alerting (clustering, silence rules)
+ - Release Grafana v4.5 with fixes and minor enhancements
+ - Release Grafana v5
+ - User groups
+ - Dashboard folders
+ - Dashboard permissions (on folders as well), permissions on groups or users
+ - New Dashboard layout engine
+ - New sidemenu & nav UX
+ - Elasticsearch alerting
### Long term
-- Improved dashboard panel layout engine (to make it easier and enable more flexible layouts)
- Backend plugins to support more Auth options, Alerting data sources & notifications
- Universial time series transformations for any data source (meta queries)
- Reporting
- Web socket & live data streams
-- Migrate to Angular2
+- Migrate to Angular2 or react
### Outside contributions
We know this is being worked on right now by contributors (and we hope to merge it when it's ready).
-- Dashboard revisions (be able to revert dashboard changes)
- Clustering for alert engine (load distribution)
diff --git a/docs/VERSION b/docs/VERSION
index 2fa2c5705ad..b570a0ac21b 100644
--- a/docs/VERSION
+++ b/docs/VERSION
@@ -1 +1 @@
-v4.2
+v4.3
diff --git a/docs/sources/archive.md b/docs/sources/archive.md
index 8ed38b933e6..68cd6f9c321 100644
--- a/docs/sources/archive.md
+++ b/docs/sources/archive.md
@@ -13,6 +13,7 @@ Here you can find links to older versions of the documentation that might be bet
of Grafana.
- [Latest](http://docs.grafana.org)
+- [Version 4.3](http://docs.grafana.org/v4.3)
- [Version 4.2](http://docs.grafana.org/v4.2)
- [Version 4.1](http://docs.grafana.org/v4.1)
- [Version 4.0](http://docs.grafana.org/v4.0)
diff --git a/docs/sources/http_api/admin.md b/docs/sources/http_api/admin.md
index 1efe073e94b..d7e5d3f8b7c 100644
--- a/docs/sources/http_api/admin.md
+++ b/docs/sources/http_api/admin.md
@@ -11,14 +11,16 @@ parent = "http_api"
# Admin API
-The admin http API does not currently work with an api token. Api Token's are currently only linked to an organization and organization role. They cannot given
-the permission of server admin, only user's can be given that permission. So in order to use these API calls you will have to use basic auth and Grafana user
-with Grafana admin permission.
+The Admin HTTP API does not currently work with an API Token. API Tokens are currently only linked to an organization and an organization role. They cannot be given
+the permission of server admin, only users can be given that permission. So in order to use these API calls you will have to use Basic Auth and the Grafana user
+must have the Grafana Admin permission. (The default admin user is called `admin` and has permission to use this API.)
## Settings
`GET /api/admin/settings`
+Only works with Basic Authentication (username and password). See [introduction](http://docs.grafana.org/http_api/admin/#admin-api) for an explanation.
+
**Example Request**:
GET /api/admin/settings
@@ -176,6 +178,8 @@ with Grafana admin permission.
`GET /api/admin/stats`
+Only works with Basic Authentication (username and password). See [introduction](http://docs.grafana.org/http_api/admin/#admin-api) for an explanation.
+
**Example Request**:
GET /api/admin/stats
@@ -203,7 +207,7 @@ with Grafana admin permission.
`POST /api/admin/users`
-Create new user
+Create new user. Only works with Basic Authentication (username and password). See [introduction](http://docs.grafana.org/http_api/admin/#admin-api) for an explanation.
**Example Request**:
@@ -229,7 +233,8 @@ Create new user
`PUT /api/admin/users/:id/password`
-Change password for specific user
+Only works with Basic Authentication (username and password). See [introduction](http://docs.grafana.org/http_api/admin/#admin-api) for an explanation.
+Change password for a specific user.
**Example Request**:
@@ -250,6 +255,8 @@ Change password for specific user
`PUT /api/admin/users/:id/permissions`
+Only works with Basic Authentication (username and password). See [introduction](http://docs.grafana.org/http_api/admin/#admin-api) for an explanation.
+
**Example Request**:
PUT /api/admin/users/2/permissions HTTP/1.1
@@ -269,6 +276,8 @@ Change password for specific user
`DELETE /api/admin/users/:id`
+Only works with Basic Authentication (username and password). See [introduction](http://docs.grafana.org/http_api/admin/#admin-api) for an explanation.
+
**Example Request**:
DELETE /api/admin/users/2 HTTP/1.1
@@ -286,6 +295,8 @@ Change password for specific user
`POST /api/admin/pause-all-alerts`
+Only works with Basic Authentication (username and password). See [introduction](http://docs.grafana.org/http_api/admin/#admin-api) for an explanation.
+
**Example Request**:
POST /api/admin/pause-all-alerts HTTP/1.1
diff --git a/package.json b/package.json
index e70633ac9a8..900a5b0acee 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,10 @@
{
"author": {
"name": "Torkel Ödegaard",
- "company": "Coding Instinct AB"
+ "company": "Grafana Labs"
},
"name": "grafana",
- "version": "5.0.0-pre1",
+ "version": "4.5.0-beta1",
"repository": {
"type": "git",
"url": "http://github.com/grafana/grafana.git"
@@ -63,6 +63,7 @@
},
"license": "Apache-2.0",
"dependencies": {
+ "ace-builds": "^1.2.8",
"eventemitter3": "^2.0.2",
"gaze": "^1.1.2",
"grunt-jscs": "3.0.1",
diff --git a/packaging/deb/control/postinst b/packaging/deb/control/postinst
index 68e1a03f3f0..8e25a0e4124 100755
--- a/packaging/deb/control/postinst
+++ b/packaging/deb/control/postinst
@@ -6,6 +6,7 @@ set -e
IS_UPGRADE=false
+
case "$1" in
configure)
[ -z "$GRAFANA_USER" ] && GRAFANA_USER="grafana"
diff --git a/packaging/deb/default/grafana-server b/packaging/deb/default/grafana-server
index cc5ee866f7d..eaa75830d44 100644
--- a/packaging/deb/default/grafana-server
+++ b/packaging/deb/default/grafana-server
@@ -17,3 +17,6 @@ CONF_FILE=/etc/grafana/grafana.ini
RESTART_ON_UPGRADE=true
PLUGINS_DIR=/var/lib/grafana/plugins
+
+# Only used on systemd systems
+PID_FILE_DIR=/var/run/grafana
diff --git a/packaging/deb/systemd/grafana-server.service b/packaging/deb/systemd/grafana-server.service
index a2eb6f1880a..cb7b87932d1 100644
--- a/packaging/deb/systemd/grafana-server.service
+++ b/packaging/deb/systemd/grafana-server.service
@@ -12,11 +12,13 @@ Group=grafana
Type=simple
Restart=on-failure
WorkingDirectory=/usr/share/grafana
-ExecStart=/usr/sbin/grafana-server \
- --config=${CONF_FILE} \
- --pidfile=${PID_FILE} \
- cfg:default.paths.logs=${LOG_DIR} \
- cfg:default.paths.data=${DATA_DIR} \
+RuntimeDirectory=grafana
+RuntimeDirectoryMode=0750
+ExecStart=/usr/sbin/grafana-server \
+ --config=${CONF_FILE} \
+ --pidfile=${PID_FILE_DIR}/grafana-server.pid \
+ cfg:default.paths.logs=${LOG_DIR} \
+ cfg:default.paths.data=${DATA_DIR} \
cfg:default.paths.plugins=${PLUGINS_DIR}
LimitNOFILE=10000
TimeoutStopSec=20
diff --git a/packaging/rpm/control/postinst b/packaging/rpm/control/postinst
index cf31112679d..0bfca949e7f 100755
--- a/packaging/rpm/control/postinst
+++ b/packaging/rpm/control/postinst
@@ -25,6 +25,7 @@ stopGrafana() {
fi
}
+
# Initial installation: $1 == 1
# Upgrade: $1 == 2, and configured to restart on upgrade
if [ $1 -eq 1 ] ; then
diff --git a/packaging/rpm/sysconfig/grafana-server b/packaging/rpm/sysconfig/grafana-server
index cc5ee866f7d..eaa75830d44 100644
--- a/packaging/rpm/sysconfig/grafana-server
+++ b/packaging/rpm/sysconfig/grafana-server
@@ -17,3 +17,6 @@ CONF_FILE=/etc/grafana/grafana.ini
RESTART_ON_UPGRADE=true
PLUGINS_DIR=/var/lib/grafana/plugins
+
+# Only used on systemd systems
+PID_FILE_DIR=/var/run/grafana
diff --git a/packaging/rpm/systemd/grafana-server.service b/packaging/rpm/systemd/grafana-server.service
index a6e02073d46..3e018e8b176 100644
--- a/packaging/rpm/systemd/grafana-server.service
+++ b/packaging/rpm/systemd/grafana-server.service
@@ -12,11 +12,13 @@ Group=grafana
Type=simple
Restart=on-failure
WorkingDirectory=/usr/share/grafana
-ExecStart=/usr/sbin/grafana-server \
- --config=${CONF_FILE} \
- --pidfile=${PID_FILE} \
- cfg:default.paths.logs=${LOG_DIR} \
- cfg:default.paths.data=${DATA_DIR} \
+RuntimeDirectory=grafana
+RuntimeDirectoryMode=0750
+ExecStart=/usr/sbin/grafana-server \
+ --config=${CONF_FILE} \
+ --pidfile=${PID_FILE_DIR}/grafana-server.pid \
+ cfg:default.paths.logs=${LOG_DIR} \
+ cfg:default.paths.data=${DATA_DIR} \
cfg:default.paths.plugins=${PLUGINS_DIR}
LimitNOFILE=10000
TimeoutStopSec=20
diff --git a/pkg/api/api.go b/pkg/api/api.go
index d4d23b17ef4..4024655775e 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -209,7 +209,7 @@ func (hs *HttpServer) registerRoutes() {
r.Get("/plugins", wrap(GetPluginList))
r.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingById))
- r.Get("/plugins/:pluginId/readme", wrap(GetPluginReadme))
+ r.Get("/plugins/:pluginId/markdown/:name", wrap(GetPluginMarkdown))
r.Group("/plugins", func() {
r.Get("/:pluginId/dashboards/", wrap(GetPluginDashboards))
diff --git a/pkg/api/cloudwatch/metrics.go b/pkg/api/cloudwatch/metrics.go
index 2daa93bfb03..a7d5e528a4a 100644
--- a/pkg/api/cloudwatch/metrics.go
+++ b/pkg/api/cloudwatch/metrics.go
@@ -91,7 +91,7 @@ func init() {
"AWS/SWF": {"DecisionTaskScheduleToStartTime", "DecisionTaskStartToCloseTime", "DecisionTasksCompleted", "StartedDecisionTasksTimedOutOnClose", "WorkflowStartToCloseTime", "WorkflowsCanceled", "WorkflowsCompleted", "WorkflowsContinuedAsNew", "WorkflowsFailed", "WorkflowsTerminated", "WorkflowsTimedOut",
"ActivityTaskScheduleToCloseTime", "ActivityTaskScheduleToStartTime", "ActivityTaskStartToCloseTime", "ActivityTasksCanceled", "ActivityTasksCompleted", "ActivityTasksFailed", "ScheduledActivityTasksTimedOutOnClose", "ScheduledActivityTasksTimedOutOnStart", "StartedActivityTasksTimedOutOnClose", "StartedActivityTasksTimedOutOnHeartbeat"},
"AWS/VPN": {"TunnelState", "TunnelDataIn", "TunnelDataOut"},
- "AWS/WAF": {"AllowedRequests", "BlockedRequests", "CountedRequests"},
+ "WAF": {"AllowedRequests", "BlockedRequests", "CountedRequests"},
"AWS/WorkSpaces": {"Available", "Unhealthy", "ConnectionAttempt", "ConnectionSuccess", "ConnectionFailure", "SessionLaunchTime", "InSessionLatency", "SessionDisconnect"},
"KMS": {"SecondsUntilKeyMaterialExpiration"},
}
@@ -133,7 +133,7 @@ func init() {
"AWS/StorageGateway": {"GatewayId", "GatewayName", "VolumeId"},
"AWS/SWF": {"Domain", "WorkflowTypeName", "WorkflowTypeVersion", "ActivityTypeName", "ActivityTypeVersion"},
"AWS/VPN": {"VpnId", "TunnelIpAddress"},
- "AWS/WAF": {"Rule", "WebACL"},
+ "WAF": {"Rule", "WebACL"},
"AWS/WorkSpaces": {"DirectoryId", "WorkspaceId"},
"KMS": {"KeyId"},
}
@@ -292,11 +292,6 @@ func getAllMetrics(cwData *datasourceInfo) (cloudwatch.ListMetricsOutput, error)
var metricsCacheLock sync.Mutex
func getMetricsForCustomMetrics(dsInfo *datasourceInfo, getAllMetrics func(*datasourceInfo) (cloudwatch.ListMetricsOutput, error)) ([]string, error) {
- result, err := getAllMetrics(dsInfo)
- if err != nil {
- return []string{}, err
- }
-
metricsCacheLock.Lock()
defer metricsCacheLock.Unlock()
@@ -314,6 +309,10 @@ func getMetricsForCustomMetrics(dsInfo *datasourceInfo, getAllMetrics func(*data
if customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Expire.After(time.Now()) {
return customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache, nil
}
+ result, err := getAllMetrics(dsInfo)
+ if err != nil {
+ return []string{}, err
+ }
customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache = make([]string, 0)
customMetricsMetricsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Expire = time.Now().Add(5 * time.Minute)
@@ -330,11 +329,6 @@ func getMetricsForCustomMetrics(dsInfo *datasourceInfo, getAllMetrics func(*data
var dimensionsCacheLock sync.Mutex
func getDimensionsForCustomMetrics(dsInfo *datasourceInfo, getAllMetrics func(*datasourceInfo) (cloudwatch.ListMetricsOutput, error)) ([]string, error) {
- result, err := getAllMetrics(dsInfo)
- if err != nil {
- return []string{}, err
- }
-
dimensionsCacheLock.Lock()
defer dimensionsCacheLock.Unlock()
@@ -352,6 +346,10 @@ func getDimensionsForCustomMetrics(dsInfo *datasourceInfo, getAllMetrics func(*d
if customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Expire.After(time.Now()) {
return customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache, nil
}
+ result, err := getAllMetrics(dsInfo)
+ if err != nil {
+ return []string{}, err
+ }
customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Cache = make([]string, 0)
customMetricsDimensionsMap[dsInfo.Profile][dsInfo.Region][dsInfo.Namespace].Expire = time.Now().Add(5 * time.Minute)
diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go
index fe7122f8557..042c03f9832 100644
--- a/pkg/api/plugins.go
+++ b/pkg/api/plugins.go
@@ -147,15 +147,16 @@ func GetPluginDashboards(c *middleware.Context) Response {
}
}
-func GetPluginReadme(c *middleware.Context) Response {
+func GetPluginMarkdown(c *middleware.Context) Response {
pluginId := c.Params(":pluginId")
+ name := c.Params(":name")
- if content, err := plugins.GetPluginReadme(pluginId); err != nil {
+ if content, err := plugins.GetPluginMarkdown(pluginId, name); err != nil {
if notfound, ok := err.(plugins.PluginNotFoundError); ok {
return ApiError(404, notfound.Error(), nil)
}
- return ApiError(500, "Could not get readme", err)
+ return ApiError(500, "Could not get markdown file", err)
} else {
return Respond(200, content)
}
diff --git a/pkg/cmd/grafana-server/main.go b/pkg/cmd/grafana-server/main.go
index 9245880c30f..4551860a680 100644
--- a/pkg/cmd/grafana-server/main.go
+++ b/pkg/cmd/grafana-server/main.go
@@ -3,10 +3,8 @@ package main
import (
"flag"
"fmt"
- "io/ioutil"
"os"
"os/signal"
- "path/filepath"
"runtime"
"runtime/trace"
"strconv"
@@ -16,7 +14,6 @@ import (
"net/http"
_ "net/http/pprof"
- "github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
@@ -87,46 +84,11 @@ func main() {
server.Start()
}
-func initRuntime() {
- err := setting.NewConfigContext(&setting.CommandLineArgs{
- Config: *configFile,
- HomePath: *homePath,
- Args: flag.Args(),
- })
-
- if err != nil {
- log.Fatal(3, err.Error())
- }
-
- logger := log.New("main")
- logger.Info("Starting Grafana", "version", version, "commit", commit, "compiled", time.Unix(setting.BuildStamp, 0))
-
- setting.LogConfigurationInfo()
-}
-
func initSql() {
sqlstore.NewEngine()
sqlstore.EnsureAdminUser()
}
-func writePIDFile() {
- if *pidFile == "" {
- return
- }
-
- // Ensure the required directory structure exists.
- err := os.MkdirAll(filepath.Dir(*pidFile), 0700)
- if err != nil {
- log.Fatal(3, "Failed to verify pid directory", err)
- }
-
- // Retrieve the PID and write it.
- pid := strconv.Itoa(os.Getpid())
- if err := ioutil.WriteFile(*pidFile, []byte(pid), 0644); err != nil {
- log.Fatal(3, "Failed to write pidfile", err)
- }
-}
-
func listenToSystemSignals(server models.GrafanaServer) {
signalChan := make(chan os.Signal, 1)
ignoreChan := make(chan os.Signal, 1)
diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go
index 30da2342c2b..eab55ec3d9d 100644
--- a/pkg/cmd/grafana-server/server.go
+++ b/pkg/cmd/grafana-server/server.go
@@ -2,7 +2,12 @@ package main
import (
"context"
+ "flag"
+ "io/ioutil"
"os"
+ "path/filepath"
+ "strconv"
+ "time"
"golang.org/x/sync/errgroup"
@@ -45,8 +50,9 @@ type GrafanaServerImpl struct {
func (g *GrafanaServerImpl) Start() {
go listenToSystemSignals(g)
- writePIDFile()
- initRuntime()
+ g.initLogging()
+ g.writePIDFile()
+
initSql()
metrics.Init()
search.Init()
@@ -74,6 +80,22 @@ func (g *GrafanaServerImpl) Start() {
g.startHttpServer()
}
+func (g *GrafanaServerImpl) initLogging() {
+ err := setting.NewConfigContext(&setting.CommandLineArgs{
+ Config: *configFile,
+ HomePath: *homePath,
+ Args: flag.Args(),
+ })
+
+ if err != nil {
+ g.log.Error(err.Error())
+ os.Exit(1)
+ }
+
+ g.log.Info("Starting Grafana", "version", version, "commit", commit, "compiled", time.Unix(setting.BuildStamp, 0))
+ setting.LogConfigurationInfo()
+}
+
func (g *GrafanaServerImpl) startHttpServer() {
g.httpServer = api.NewHttpServer()
@@ -101,3 +123,25 @@ func (g *GrafanaServerImpl) Shutdown(code int, reason string) {
log.Close()
os.Exit(code)
}
+
+func (g *GrafanaServerImpl) writePIDFile() {
+ if *pidFile == "" {
+ return
+ }
+
+ // Ensure the required directory structure exists.
+ err := os.MkdirAll(filepath.Dir(*pidFile), 0700)
+ if err != nil {
+ g.log.Error("Failed to verify pid directory", "error", err)
+ os.Exit(1)
+ }
+
+ // Retrieve the PID and write it.
+ pid := strconv.Itoa(os.Getpid())
+ if err := ioutil.WriteFile(*pidFile, []byte(pid), 0644); err != nil {
+ g.log.Error("Failed to write pidfile", "error", err)
+ os.Exit(1)
+ }
+
+ g.log.Info("Writing PID file", "path", *pidFile, "pid", pid)
+}
diff --git a/pkg/plugins/datasource_plugin.go b/pkg/plugins/datasource_plugin.go
index 8cb0748936b..b36d7081dfe 100644
--- a/pkg/plugins/datasource_plugin.go
+++ b/pkg/plugins/datasource_plugin.go
@@ -1,15 +1,21 @@
package plugins
-import "encoding/json"
+import (
+ "encoding/json"
+ "os"
+ "path/filepath"
+)
type DataSourcePlugin struct {
FrontendPluginBase
- Annotations bool `json:"annotations"`
- Metrics bool `json:"metrics"`
- Alerting bool `json:"alerting"`
- BuiltIn bool `json:"builtIn"`
- Mixed bool `json:"mixed"`
- Routes []*AppPluginRoute `json:"routes"`
+ Annotations bool `json:"annotations"`
+ Metrics bool `json:"metrics"`
+ Alerting bool `json:"alerting"`
+ QueryOptions map[string]bool `json:"queryOptions,omitempty"`
+ BuiltIn bool `json:"builtIn,omitempty"`
+ Mixed bool `json:"mixed,omitempty"`
+ HasQueryHelp bool `json:"hasQueryHelp,omitempty"`
+ Routes []*AppPluginRoute `json:"routes"`
}
func (p *DataSourcePlugin) Load(decoder *json.Decoder, pluginDir string) error {
@@ -21,6 +27,15 @@ func (p *DataSourcePlugin) Load(decoder *json.Decoder, pluginDir string) error {
return err
}
+ // look for help markdown
+ helpPath := filepath.Join(p.PluginDir, "QUERY_HELP.md")
+ if _, err := os.Stat(helpPath); os.IsNotExist(err) {
+ helpPath = filepath.Join(p.PluginDir, "query_help.md")
+ }
+ if _, err := os.Stat(helpPath); err == nil {
+ p.HasQueryHelp = true
+ }
+
DataSources[p.Id] = p
return nil
}
diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go
index fd04852f2f4..541b37c8a8a 100644
--- a/pkg/plugins/models.go
+++ b/pkg/plugins/models.go
@@ -38,8 +38,8 @@ type PluginBase struct {
Includes []*PluginInclude `json:"includes"`
Module string `json:"module"`
BaseUrl string `json:"baseUrl"`
- HideFromList bool `json:"hideFromList"`
- State string `json:"state"`
+ HideFromList bool `json:"hideFromList,omitempty"`
+ State string `json:"state,omitempty"`
IncludedInAppId string `json:"-"`
PluginDir string `json:"-"`
@@ -48,9 +48,6 @@ type PluginBase struct {
GrafanaNetVersion string `json:"-"`
GrafanaNetHasUpdate bool `json:"-"`
-
- // cache for readme file contents
- Readme []byte `json:"-"`
}
func (pb *PluginBase) registerPlugin(pluginDir string) error {
diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go
index b6c3639cbbf..885bd5c9e03 100644
--- a/pkg/plugins/plugins.go
+++ b/pkg/plugins/plugins.go
@@ -3,6 +3,7 @@ package plugins
import (
"encoding/json"
"errors"
+ "fmt"
"io/ioutil"
"os"
"path"
@@ -166,30 +167,24 @@ func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error {
return loader.Load(jsonParser, currentDir)
}
-func GetPluginReadme(pluginId string) ([]byte, error) {
+func GetPluginMarkdown(pluginId string, name string) ([]byte, error) {
plug, exists := Plugins[pluginId]
if !exists {
return nil, PluginNotFoundError{pluginId}
}
- if plug.Readme != nil {
- return plug.Readme, nil
+ path := filepath.Join(plug.PluginDir, fmt.Sprintf("%s.md", strings.ToUpper(name)))
+ if _, err := os.Stat(path); os.IsNotExist(err) {
+ path = filepath.Join(plug.PluginDir, fmt.Sprintf("%s.md", strings.ToLower(name)))
}
- readmePath := filepath.Join(plug.PluginDir, "README.md")
- if _, err := os.Stat(readmePath); os.IsNotExist(err) {
- readmePath = filepath.Join(plug.PluginDir, "readme.md")
+ if _, err := os.Stat(path); os.IsNotExist(err) {
+ return make([]byte, 0), nil
}
- if _, err := os.Stat(readmePath); os.IsNotExist(err) {
- plug.Readme = make([]byte, 0)
- return plug.Readme, nil
- }
-
- if readmeBytes, err := ioutil.ReadFile(readmePath); err != nil {
+ if data, err := ioutil.ReadFile(path); err != nil {
return nil, err
} else {
- plug.Readme = readmeBytes
- return plug.Readme, nil
+ return data, nil
}
}
diff --git a/pkg/services/alerting/notifiers/hipchat.go b/pkg/services/alerting/notifiers/hipchat.go
index 0eb21865b9f..aefa7de6ede 100644
--- a/pkg/services/alerting/notifiers/hipchat.go
+++ b/pkg/services/alerting/notifiers/hipchat.go
@@ -84,7 +84,7 @@ func (this *HipChatNotifier) Notify(evalContext *alerting.EvalContext) error {
return err
}
- message := evalContext.GetNotificationTitle() + " in state " + evalContext.GetStateModel().Text + "
Check Dasboard"
+ message := evalContext.GetNotificationTitle() + " in state " + evalContext.GetStateModel().Text + "
Check Dashboard"
fields := make([]map[string]interface{}, 0)
message += "
"
for index, evt := range evalContext.EvalMatches {
diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go
index 2e38f6983da..e2fc1f29063 100644
--- a/pkg/services/sqlstore/sqlstore.go
+++ b/pkg/services/sqlstore/sqlstore.go
@@ -114,7 +114,7 @@ func getEngine() (*xorm.Engine, error) {
protocol = "unix"
}
- cnnstr = fmt.Sprintf("%s:%s@%s(%s)/%s?charset=utf8mb4&allowNativePasswords=true",
+ cnnstr = fmt.Sprintf("%s:%s@%s(%s)/%s?collation=utf8mb4_unicode_ci&allowNativePasswords=true",
DbCfg.User, DbCfg.Pwd, protocol, DbCfg.Host, DbCfg.Name)
if DbCfg.SslMode == "true" || DbCfg.SslMode == "skip-verify" {
diff --git a/pkg/services/sqlstore/sqlutil/sqlutil.go b/pkg/services/sqlstore/sqlutil/sqlutil.go
index 68ced55722c..4aa2ec27216 100644
--- a/pkg/services/sqlstore/sqlutil/sqlutil.go
+++ b/pkg/services/sqlstore/sqlutil/sqlutil.go
@@ -12,7 +12,7 @@ type TestDB struct {
}
var TestDB_Sqlite3 = TestDB{DriverName: "sqlite3", ConnStr: ":memory:?_loc=Local"}
-var TestDB_Mysql = TestDB{DriverName: "mysql", ConnStr: "grafana:password@tcp(localhost:3306)/grafana_tests?charset=utf8mb4"}
+var TestDB_Mysql = TestDB{DriverName: "mysql", ConnStr: "grafana:password@tcp(localhost:3306)/grafana_tests?collation=utf8mb4_unicode_ci"}
var TestDB_Postgres = TestDB{DriverName: "postgres", ConnStr: "user=grafanatest password=grafanatest host=localhost port=5432 dbname=grafanatest sslmode=disable"}
func CleanDB(x *xorm.Engine) {
diff --git a/pkg/tsdb/influxdb/query.go b/pkg/tsdb/influxdb/query.go
index f271c5d245f..0bcdf1d5291 100644
--- a/pkg/tsdb/influxdb/query.go
+++ b/pkg/tsdb/influxdb/query.go
@@ -151,8 +151,12 @@ func (query *Query) renderMeasurement() string {
func (query *Query) renderWhereClause() string {
res := " WHERE "
conditions := query.renderTags()
- res += strings.Join(conditions, " ")
if len(conditions) > 0 {
+ if len(conditions) > 1 {
+ res += "(" + strings.Join(conditions, " ") + ")"
+ } else {
+ res += conditions[0]
+ }
res += " AND "
}
diff --git a/pkg/tsdb/influxdb/query_test.go b/pkg/tsdb/influxdb/query_test.go
index 533df27f91f..0977ef8c1ce 100644
--- a/pkg/tsdb/influxdb/query_test.go
+++ b/pkg/tsdb/influxdb/query_test.go
@@ -57,7 +57,7 @@ func TestInfluxdbQueryBuilder(t *testing.T) {
rawQuery, err := query.Build(queryContext)
So(err, ShouldBeNil)
- So(rawQuery, ShouldEqual, `SELECT mean("value") FROM "cpu" WHERE "hostname" = 'server1' OR "hostname" = 'server2' AND time > now() - 5m GROUP BY time(5s), "datacenter" fill(null)`)
+ So(rawQuery, ShouldEqual, `SELECT mean("value") FROM "cpu" WHERE ("hostname" = 'server1' OR "hostname" = 'server2') AND time > now() - 5m GROUP BY time(5s), "datacenter" fill(null)`)
})
Convey("can build query with math part", func() {
diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go
index e568ca730c1..e75dab8c04f 100644
--- a/pkg/tsdb/mysql/mysql.go
+++ b/pkg/tsdb/mysql/mysql.go
@@ -66,7 +66,7 @@ func (e *MysqlExecutor) initEngine() error {
}
}
- cnnstr := fmt.Sprintf("%s:%s@%s(%s)/%s?charset=utf8mb4&parseTime=true&loc=UTC", e.datasource.User, e.datasource.Password, "tcp", e.datasource.Url, e.datasource.Database)
+ cnnstr := fmt.Sprintf("%s:%s@%s(%s)/%s?collation=utf8mb4_unicode_ci&parseTime=true&loc=UTC", e.datasource.User, e.datasource.Password, "tcp", e.datasource.Url, e.datasource.Database)
e.log.Debug("getEngine", "connection", cnnstr)
engine, err := xorm.NewEngine("mysql", cnnstr)
diff --git a/public/app/core/components/code_editor/code_editor.ts b/public/app/core/components/code_editor/code_editor.ts
new file mode 100644
index 00000000000..151ebffab5d
--- /dev/null
+++ b/public/app/core/components/code_editor/code_editor.ts
@@ -0,0 +1,209 @@
+/**
+ * codeEditor directive based on Ace code editor
+ * https://github.com/ajaxorg/ace
+ *
+ * Basic usage:
+ *
$1"); + text = text.replace(/`([^`]+)`/, "
$1");
+ return text;
+}
+
+function convertToHTML(item) {
+ var docText = lang.escapeHTML(item.docText);
+ docText = convertMarkDownTags(wrapText(docText, 40));
+ return [
+ "", lang.escapeHTML(item.def), "", "