Merge remote-tracking branch 'upstream/master' into postgres-query-builder

This commit is contained in:
Sven Klemm
2018-03-28 20:43:15 +02:00
281 changed files with 89684 additions and 4088 deletions
View File
+20 -1
View File
@@ -1,20 +1,39 @@
# 5.1.0 (unreleased)
* **MSSQL**: New Microsoft SQL Server data source [#10093](https://github.com/grafana/grafana/pull/10093), [#11298](https://github.com/grafana/grafana/pull/11298), thx [@linuxchips](https://github.com/linuxchips)
* **Prometheus**: The heatmap panel now support Prometheus histograms [#10009](https://github.com/grafana/grafana/issues/10009)
* **Postgres/MySQL**: Ability to insert 0s or nulls for missing intervals [#9487](https://github.com/grafana/grafana/issues/9487), thanks [@svenklemm](https://github.com/svenklemm)
* **Postgres/MySQL/MSSQL**: Fix precision for the time column in table mode [#11306](https://github.com/grafana/grafana/issues/11306)
* **Graph**: Align left and right Y-axes to one level [#1271](https://github.com/grafana/grafana/issues/1271) & [#2740](https://github.com/grafana/grafana/issues/2740) thx [@ilgizar](https://github.com/ilgizar)
* **Graph**: Thresholds for Right Y axis [#7107](https://github.com/grafana/grafana/issues/7107), thx [@ilgizar](https://github.com/ilgizar)
* **Graph**: Support multiple series stacking in histogram mode [#8151](https://github.com/grafana/grafana/issues/8151), thx [@mtanda](https://github.com/mtanda)
* **Alerting**: Pausing/un alerts now updates new_state_date [#10942](https://github.com/grafana/grafana/pull/10942)
* **Alerting**: Support Pagerduty notification channel using Pagerduty V2 API [#10531](https://github.com/grafana/grafana/issues/10531), thx [@jbaublitz](https://github.com/jbaublitz)
* **Templating**: Add comma templating format [#10632](https://github.com/grafana/grafana/issues/10632), thx [@mtanda](https://github.com/mtanda)
* **Prometheus**: Show template variable candidate in query editor [#9210](https://github.com/grafana/grafana/issues/9210), thx [@mtanda](https://github.com/mtanda)
* **Prometheus**: Support POST for query and query_range [#9859](https://github.com/grafana/grafana/pull/9859), thx [@mtanda](https://github.com/mtanda)
* **Alerting**: Add support for retries on alert queries [#5855](https://github.com/grafana/grafana/issues/5855), thx [@Thib17](https://github.com/Thib17)
### Minor
* **OpsGenie**: Add triggered alerts as description [#11046](https://github.com/grafana/grafana/pull/11046), thx [@llamashoes](https://github.com/llamashoes)
* **Cloudwatch**: Support high resolution metrics [#10925](https://github.com/grafana/grafana/pull/10925), thx [@mtanda](https://github.com/mtanda)
* **Cloudwatch**: Add dimension filtering to CloudWatch `dimension_values()` [#10029](https://github.com/grafana/grafana/issues/10029), thx [@willyhutw](https://github.com/willyhutw)
* **Units**: Second to HH:mm:ss formatter [#11107](https://github.com/grafana/grafana/issues/11107), thx [@gladdiologist](https://github.com/gladdiologist)
* **Units**: Second to HH:mm:ss formatter [#11107](https://github.com/grafana/grafana/issues/11107), thx [@gladdiologist](https://github.com/gladdiologist)
* **Singlestat**: Add color to prefix and postfix in singlestat panel [#11143](https://github.com/grafana/grafana/pull/11143), thx [@ApsOps](https://github.com/ApsOps)
* **Dashboards**: Version cleanup fails on old databases with many entries [#11278](https://github.com/grafana/grafana/issues/11278)
* **Server**: Adjust permissions of unix socket [#11343](https://github.com/grafana/grafana/pull/11343), thx [@corny](https://github.com/corny)
* **Shortcuts**: Add shortcut for duplicate panel [#11102](https://github.com/grafana/grafana/issues/11102)
* **AuthProxy**: Support IPv6 in Auth proxy white list [#11330](https://github.com/grafana/grafana/pull/11330), thx [@corny](https://github.com/corny)
* **SMTP**: Don't connect to STMP server using TLS unless configured. [#7189](https://github.com/grafana/grafana/issues/7189)
# 5.0.4 (2018-03-28)
* **Docker** Can't start Grafana on Kubernetes 1.7.14, 1.8.9, or 1.9.4 [#140 in grafana-docker repo](https://github.com/grafana/grafana-docker/issues/140) thx [@suquant](https://github.com/suquant)
* **Dashboard** Fixed bug where collapsed panels could not be directly linked to/renderer [#11114](https://github.com/grafana/grafana/issues/11114) & [#11086](https://github.com/grafana/grafana/issues/11086) & [#11296](https://github.com/grafana/grafana/issues/11296)
* **Dashboard** Provisioning dashboard with alert rules should create alerts [#11247](https://github.com/grafana/grafana/issues/11247)
* **Snapshots** For snapshots, the Graph panel renders the legend incorrectly on right hand side [#11318](https://github.com/grafana/grafana/issues/11318)
* **Alerting** Link back to Grafana returns wrong URL if root_path contains sub-path components [#11403](https://github.com/grafana/grafana/issues/11403)
* **Alerting** Incorrect default value for upload images setting for alert notifiers [#11413](https://github.com/grafana/grafana/pull/11413)
# 5.0.3 (2018-03-16)
* **Mysql**: Mysql panic occurring occasionally upon Grafana dashboard access (a bigger patch than the one in 5.0.2) [#11155](https://github.com/grafana/grafana/issues/11155)
Generated
+35 -186
View File
@@ -27,37 +27,7 @@
[[projects]]
name = "github.com/aws/aws-sdk-go"
packages = [
"aws",
"aws/awserr",
"aws/awsutil",
"aws/client",
"aws/client/metadata",
"aws/corehandlers",
"aws/credentials",
"aws/credentials/ec2rolecreds",
"aws/credentials/endpointcreds",
"aws/credentials/stscreds",
"aws/defaults",
"aws/ec2metadata",
"aws/endpoints",
"aws/request",
"aws/session",
"aws/signer/v4",
"internal/shareddefaults",
"private/protocol",
"private/protocol/ec2query",
"private/protocol/query",
"private/protocol/query/queryutil",
"private/protocol/rest",
"private/protocol/restxml",
"private/protocol/xml/xmlutil",
"service/cloudwatch",
"service/ec2",
"service/ec2/ec2iface",
"service/s3",
"service/sts"
]
packages = ["aws","aws/awserr","aws/awsutil","aws/client","aws/client/metadata","aws/corehandlers","aws/credentials","aws/credentials/ec2rolecreds","aws/credentials/endpointcreds","aws/credentials/stscreds","aws/defaults","aws/ec2metadata","aws/endpoints","aws/request","aws/session","aws/signer/v4","internal/shareddefaults","private/protocol","private/protocol/ec2query","private/protocol/query","private/protocol/query/queryutil","private/protocol/rest","private/protocol/restxml","private/protocol/xml/xmlutil","service/cloudwatch","service/ec2","service/ec2/ec2iface","service/s3","service/sts"]
revision = "decd990ddc5dcdf2f73309cbcab90d06b996ca28"
version = "v1.12.67"
@@ -103,6 +73,11 @@
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
name = "github.com/denisenkom/go-mssqldb"
packages = [".","internal/cp"]
revision = "270bc3860bb94dd3a3ffd047377d746c5e276726"
[[projects]]
name = "github.com/fatih/color"
packages = ["."]
@@ -142,13 +117,7 @@
[[projects]]
branch = "master"
name = "github.com/go-macaron/session"
packages = [
".",
"memcache",
"mysql",
"postgres",
"redis"
]
packages = [".","memcache","postgres","redis"]
revision = "b8e286a0dba8f4999042d6b258daf51b31d08938"
[[projects]]
@@ -171,23 +140,19 @@
[[projects]]
name = "github.com/go-xorm/core"
packages = ["."]
revision = "e8409d73255791843585964791443dbad877058c"
revision = "da1adaf7a28ca792961721a34e6e04945200c890"
version = "v0.5.7"
[[projects]]
name = "github.com/go-xorm/xorm"
packages = ["."]
revision = "6687a2b4e824f4d87f2d65060ec5cb0d896dff1e"
revision = "1933dd69e294c0a26c0266637067f24dbb25770c"
version = "v0.6.4"
[[projects]]
branch = "master"
name = "github.com/golang/protobuf"
packages = [
"proto",
"ptypes",
"ptypes/any",
"ptypes/duration",
"ptypes/timestamp"
]
packages = ["proto","ptypes","ptypes/any","ptypes/duration","ptypes/timestamp"]
revision = "c65a0412e71e8b9b3bfd22925720d23c0f054237"
[[projects]]
@@ -256,10 +221,7 @@
[[projects]]
name = "github.com/klauspost/compress"
packages = [
"flate",
"gzip"
]
packages = ["flate","gzip"]
revision = "6c8db69c4b49dd4df1fff66996cf556176d0b9bf"
version = "v1.2.1"
@@ -290,10 +252,7 @@
[[projects]]
branch = "master"
name = "github.com/lib/pq"
packages = [
".",
"oid"
]
packages = [".","oid"]
revision = "61fe37aa2ee24fabcdbe5c4ac1d4ac566f88f345"
[[projects]]
@@ -328,11 +287,7 @@
[[projects]]
name = "github.com/opentracing/opentracing-go"
packages = [
".",
"ext",
"log"
]
packages = [".","ext","log"]
revision = "1949ddbfd147afd4d964a9f00b24eb291e0e7c38"
version = "v1.0.2"
@@ -344,12 +299,7 @@
[[projects]]
name = "github.com/prometheus/client_golang"
packages = [
"api",
"api/prometheus/v1",
"prometheus",
"prometheus/promhttp"
]
packages = ["api","api/prometheus/v1","prometheus","prometheus/promhttp"]
revision = "967789050ba94deca04a5e84cce8ad472ce313c1"
version = "v0.9.0-pre1"
@@ -362,22 +312,13 @@
[[projects]]
branch = "master"
name = "github.com/prometheus/common"
packages = [
"expfmt",
"internal/bitbucket.org/ww/goautoneg",
"model"
]
packages = ["expfmt","internal/bitbucket.org/ww/goautoneg","model"]
revision = "89604d197083d4781071d3c65855d24ecfb0a563"
[[projects]]
branch = "master"
name = "github.com/prometheus/procfs"
packages = [
".",
"internal/util",
"nfsd",
"xfs"
]
packages = [".","internal/util","nfsd","xfs"]
revision = "85fadb6e89903ef7cca6f6a804474cd5ea85b6e1"
[[projects]]
@@ -394,21 +335,13 @@
[[projects]]
name = "github.com/smartystreets/assertions"
packages = [
".",
"internal/go-render/render",
"internal/oglematchers"
]
packages = [".","internal/go-render/render","internal/oglematchers"]
revision = "0b37b35ec7434b77e77a4bb29b79677cced992ea"
version = "1.8.1"
[[projects]]
name = "github.com/smartystreets/goconvey"
packages = [
"convey",
"convey/gotest",
"convey/reporting"
]
packages = ["convey","convey/gotest","convey/reporting"]
revision = "9e8dc3f972df6c8fcc0375ef492c24d0bb204857"
version = "1.6.3"
@@ -420,21 +353,7 @@
[[projects]]
name = "github.com/uber/jaeger-client-go"
packages = [
".",
"config",
"internal/baggage",
"internal/baggage/remote",
"internal/spanlog",
"log",
"rpcmetrics",
"thrift-gen/agent",
"thrift-gen/baggage",
"thrift-gen/jaeger",
"thrift-gen/sampling",
"thrift-gen/zipkincore",
"utils"
]
packages = [".","config","internal/baggage","internal/baggage/remote","internal/spanlog","log","rpcmetrics","thrift-gen/agent","thrift-gen/baggage","thrift-gen/jaeger","thrift-gen/sampling","thrift-gen/zipkincore","utils"]
revision = "3ac96c6e679cb60a74589b0d0aa7c70a906183f7"
version = "v2.11.2"
@@ -446,10 +365,7 @@
[[projects]]
name = "github.com/yudai/gojsondiff"
packages = [
".",
"formatter"
]
packages = [".","formatter"]
revision = "7b1b7adf999dab73a6eb02669c3d82dbb27a3dd6"
version = "1.0.0"
@@ -462,34 +378,19 @@
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["pbkdf2"]
packages = ["md4","pbkdf2"]
revision = "3d37316aaa6bd9929127ac9a527abf408178ea7b"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = [
"context",
"context/ctxhttp",
"http2",
"http2/hpack",
"idna",
"internal/timeseries",
"lex/httplex",
"trace"
]
packages = ["context","context/ctxhttp","http2","http2/hpack","idna","internal/timeseries","lex/httplex","trace"]
revision = "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec"
[[projects]]
branch = "master"
name = "golang.org/x/oauth2"
packages = [
".",
"google",
"internal",
"jws",
"jwt"
]
packages = [".","google","internal","jws","jwt"]
revision = "b28fcf2b08a19742b43084fb40ab78ac6c3d8067"
[[projects]]
@@ -507,39 +408,12 @@
[[projects]]
branch = "master"
name = "golang.org/x/text"
packages = [
"collate",
"collate/build",
"internal/colltab",
"internal/gen",
"internal/tag",
"internal/triegen",
"internal/ucd",
"language",
"secure/bidirule",
"transform",
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable"
]
packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"]
revision = "e19ae1496984b1c655b8044a65c0300a3c878dd3"
[[projects]]
name = "google.golang.org/appengine"
packages = [
".",
"cloudsql",
"internal",
"internal/app_identity",
"internal/base",
"internal/datastore",
"internal/log",
"internal/modules",
"internal/remote_api",
"internal/urlfetch",
"urlfetch"
]
packages = [".","cloudsql","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/urlfetch","urlfetch"]
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
version = "v1.0.0"
@@ -551,32 +425,7 @@
[[projects]]
name = "google.golang.org/grpc"
packages = [
".",
"balancer",
"balancer/base",
"balancer/roundrobin",
"codes",
"connectivity",
"credentials",
"encoding",
"grpclb/grpc_lb_v1/messages",
"grpclog",
"health",
"health/grpc_health_v1",
"internal",
"keepalive",
"metadata",
"naming",
"peer",
"resolver",
"resolver/dns",
"resolver/passthrough",
"stats",
"status",
"tap",
"transport"
]
packages = [".","balancer","balancer/base","balancer/roundrobin","codes","connectivity","credentials","encoding","grpclb/grpc_lb_v1/messages","grpclog","health","health/grpc_health_v1","internal","keepalive","metadata","naming","peer","resolver","resolver/dns","resolver/passthrough","stats","status","tap","transport"]
revision = "6b51017f791ae1cfbec89c52efdf444b13b550ef"
version = "v1.9.2"
@@ -598,12 +447,6 @@
revision = "567b2bfa514e796916c4747494d6ff5132a1dfce"
version = "v1"
[[projects]]
branch = "v2"
name = "gopkg.in/gomail.v2"
packages = ["."]
revision = "81ebce5c23dfd25c6c67194b37d3dd3f338c98b1"
[[projects]]
name = "gopkg.in/ini.v1"
packages = ["."]
@@ -616,6 +459,12 @@
revision = "75f2e9b42e99652f0d82b28ccb73648f44615faa"
version = "v1.2.4"
[[projects]]
branch = "v2"
name = "gopkg.in/mail.v2"
packages = ["."]
revision = "5bc5c8bb07bd8d2803831fbaf8cbd630fcde2c68"
[[projects]]
name = "gopkg.in/redis.v2"
packages = ["."]
@@ -631,6 +480,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "4de68f1342ba98a637ec8ca7496aeeae2021bf9e4c7c80db7924e14709151a62"
inputs-digest = "ad3c71fd3244369c313978e9e7464c7116faee764386439a17de0707a08103aa"
solver-name = "gps-cdcl"
solver-version = 1
+7 -5
View File
@@ -85,13 +85,11 @@ ignored = [
[[constraint]]
name = "github.com/go-xorm/core"
revision = "e8409d73255791843585964791443dbad877058c"
#version = "0.5.7" //keeping this since we would rather depend on version then commit
version = "0.5.7"
[[constraint]]
name = "github.com/go-xorm/xorm"
revision = "6687a2b4e824f4d87f2d65060ec5cb0d896dff1e"
#version = "0.6.4" //keeping this since we would rather depend on version then commit
version = "0.6.4"
[[constraint]]
name = "github.com/gorilla/websocket"
@@ -174,7 +172,7 @@ ignored = [
name = "golang.org/x/sync"
[[constraint]]
name = "gopkg.in/gomail.v2"
name = "gopkg.in/mail.v2"
branch = "v2"
[[constraint]]
@@ -197,3 +195,7 @@ ignored = [
[[constraint]]
branch = "master"
name = "github.com/teris-io/shortid"
[[constraint]]
name = "github.com/denisenkom/go-mssqldb"
revision = "270bc3860bb94dd3a3ffd047377d746c5e276726"
+4
View File
@@ -79,6 +79,10 @@ func main() {
case "setup":
setup()
case "build-srv":
clean()
build("grafana-server", "./pkg/cmd/grafana-server", []string{})
case "build-cli":
clean()
build("grafana-cli", "./pkg/cmd/grafana-cli", []string{})
+6
View File
@@ -82,6 +82,9 @@ max_idle_conn = 2
# Max conn setting default is 0 (mean not set)
max_open_conn =
# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours)
conn_max_lifetime = 14400
# Set to true to log the sql calls and execution times.
log_queries =
@@ -125,6 +128,9 @@ cookie_secure = false
session_life_time = 86400
gc_interval_time = 86400
# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours)
conn_max_lifetime = 14400
#################################### Data proxy ###########################
[dataproxy]
+3
View File
@@ -90,6 +90,9 @@
# Max conn setting default is 0 (mean not set)
;max_open_conn =
# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours)
;conn_max_lifetime = 14400
# Set to true to log the sql calls and execution times.
log_queries =
+5
View File
@@ -0,0 +1,5 @@
FROM microsoft/mssql-server-linux:2017-CU4
WORKDIR /usr/setup
COPY . /usr/setup
RUN chmod +x /usr/setup/setup.sh
CMD /bin/bash ./entrypoint.sh
+2
View File
@@ -0,0 +1,2 @@
#start SQL Server and run setup script
/usr/setup/setup.sh & /opt/mssql/bin/sqlservr
+12
View File
@@ -0,0 +1,12 @@
#/bin/bash
#wait for the SQL Server to come up
sleep 15s
cat /usr/setup/setup.sql.template | awk '{
gsub(/%%DB%%/,"'$MSSQL_DATABASE'");
gsub(/%%USER%%/,"'$MSSQL_USER'");
gsub(/%%PWD%%/,"'$MSSQL_PASSWORD'")
}1' > /usr/setup/setup.sql
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $MSSQL_SA_PASSWORD -d master -i /usr/setup/setup.sql
@@ -0,0 +1,14 @@
CREATE LOGIN %%USER%% WITH PASSWORD = '%%PWD%%'
GO
CREATE DATABASE %%DB%%;
GO
USE %%DB%%;
GO
CREATE USER %%USER%% FOR LOGIN %%USER%%;
GO
EXEC sp_addrolemember 'db_owner', '%%USER%%';
GO
+539
View File
@@ -0,0 +1,539 @@
{
"__inputs": [
{
"name": "DS_MSSQL",
"label": "MSSQL",
"description": "",
"type": "datasource",
"pluginId": "mssql",
"pluginName": "MSSQL"
}
],
"__requires": [
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "5.0.0"
},
{
"type": "panel",
"id": "graph",
"name": "Graph",
"version": "5.0.0"
},
{
"type": "datasource",
"id": "mssql",
"name": "MSSQL",
"version": "1.0.0"
},
{
"type": "panel",
"id": "table",
"name": "Table",
"version": "5.0.0"
}
],
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"description": "A dashboard visualizing data generated from grafana/fake-data-gen",
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"id": null,
"iteration": 1520976748896,
"links": [],
"panels": [
{
"aliasColors": {
"total avg": "#6ed0e0"
},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "${DS_MSSQL}",
"fill": 2,
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 0
},
"id": 2,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "null",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [
{
"alias": "total avg",
"fill": 0,
"pointradius": 3,
"points": true
}
],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"alias": "",
"format": "time_series",
"rawSql": "SELECT\n $__timeGroup(createdAt,'$summarize') as time,\n avg(value) as value,\n hostname as metric\nFROM \n grafana_metric\nWHERE\n $__timeFilter(createdAt) AND\n measurement = 'logins.count' AND\n hostname IN($host)\nGROUP BY $__timeGroup(createdAt,'$summarize'), hostname\nORDER BY 1",
"refId": "A"
},
{
"alias": "",
"format": "time_series",
"rawSql": "SELECT\n $__timeGroup(createdAt,'$summarize') as time,\n min(value) as value,\n 'total avg' as metric\nFROM \n grafana_metric\nWHERE\n $__timeFilter(createdAt) AND\n measurement = 'logins.count'\nGROUP BY $__timeGroup(createdAt,'$summarize')\nORDER BY 1",
"refId": "B"
}
],
"thresholds": [],
"timeFrom": null,
"timeShift": null,
"title": "Average logins / $summarize",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"decimals": null,
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "${DS_MSSQL}",
"fill": 2,
"gridPos": {
"h": 18,
"w": 12,
"x": 12,
"y": 0
},
"id": 8,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "null",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"alias": "",
"format": "time_series",
"rawSql": "SELECT\n $__timeGroup(createdAt,'$summarize') as time,\n avg(value) as value,\n 'started' as metric\nFROM \n grafana_metric\nWHERE\n $__timeFilter(createdAt) AND\n measurement = 'payment.started'\nGROUP BY $__timeGroup(createdAt,'$summarize')\nORDER BY 1",
"refId": "A"
},
{
"alias": "",
"format": "time_series",
"rawSql": "SELECT\n $__timeGroup(createdAt,'$summarize') as time,\n avg(value) as value,\n 'ended' as \"metric\"\nFROM \n grafana_metric\nWHERE\n $__timeFilter(createdAt) AND\n measurement = 'payment.ended'\nGROUP BY $__timeGroup(createdAt,'$summarize')\nORDER BY 1",
"refId": "B"
}
],
"thresholds": [],
"timeFrom": null,
"timeShift": null,
"title": "Average payments started/ended / $summarize",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "${DS_MSSQL}",
"fill": 2,
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 9
},
"id": 6,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "null",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"alias": "",
"format": "time_series",
"rawSql": "SELECT\n $__timeGroup(createdAt,'$summarize') as time,\n max(value) as value,\n hostname as metric\nFROM \n grafana_metric\nWHERE\n $__timeFilter(createdAt) AND\n measurement = 'cpu' AND\n hostname IN($host)\nGROUP BY $__timeGroup(createdAt,'$summarize'), hostname\nORDER BY 1",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeShift": null,
"title": "Max CPU / $summarize",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
},
{
"columns": [],
"datasource": "${DS_MSSQL}",
"fontSize": "100%",
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 18
},
"id": 4,
"links": [],
"pageSize": null,
"scroll": true,
"showHeader": true,
"sort": {
"col": 0,
"desc": true
},
"styles": [
{
"alias": "Time",
"dateFormat": "YYYY-MM-DD HH:mm:ss",
"pattern": "Time",
"type": "date"
},
{
"alias": "",
"colorMode": null,
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"decimals": 2,
"pattern": "/.*/",
"thresholds": [],
"type": "number",
"unit": "short"
}
],
"targets": [
{
"alias": "",
"format": "table",
"rawSql": "SELECT createdAt as Time, source, datacenter, hostname, value FROM grafana_metric WHERE hostname in($host)",
"refId": "A"
}
],
"title": "Values",
"transform": "table",
"type": "table"
}
],
"schemaVersion": 16,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"allValue": null,
"current": {},
"datasource": "${DS_MSSQL}",
"hide": 0,
"includeAll": false,
"label": "Datacenter",
"multi": false,
"name": "datacenter",
"options": [],
"query": "SELECT DISTINCT datacenter FROM grafana_metric",
"refresh": 1,
"regex": "",
"sort": 1,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"allValue": null,
"current": {},
"datasource": "${DS_MSSQL}",
"hide": 0,
"includeAll": true,
"label": "Hostname",
"multi": true,
"name": "host",
"options": [],
"query": "SELECT DISTINCT hostname FROM grafana_metric WHERE datacenter='$datacenter'",
"refresh": 1,
"regex": "",
"sort": 1,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"auto": false,
"auto_count": 30,
"auto_min": "10s",
"current": {
"text": "1m",
"value": "1m"
},
"hide": 0,
"label": "Summarize",
"name": "summarize",
"options": [
{
"selected": false,
"text": "1s",
"value": "1s"
},
{
"selected": false,
"text": "10s",
"value": "10s"
},
{
"selected": false,
"text": "30s",
"value": "30s"
},
{
"selected": true,
"text": "1m",
"value": "1m"
},
{
"selected": false,
"text": "5m",
"value": "5m"
},
{
"selected": false,
"text": "10m",
"value": "10m"
},
{
"selected": false,
"text": "30m",
"value": "30m"
},
{
"selected": false,
"text": "1h",
"value": "1h"
},
{
"selected": false,
"text": "6h",
"value": "6h"
},
{
"selected": false,
"text": "12h",
"value": "12h"
},
{
"selected": false,
"text": "1d",
"value": "1d"
},
{
"selected": false,
"text": "7d",
"value": "7d"
},
{
"selected": false,
"text": "14d",
"value": "14d"
},
{
"selected": false,
"text": "30d",
"value": "30d"
}
],
"query": "1s,10s,30s,1m,5m,10m,30m,1h,6h,12h,1d,7d,14d,30d",
"refresh": 2,
"type": "interval"
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"timezone": "",
"title": "Grafana Fake Data Gen - MSSQL",
"uid": "86Js1xRmk",
"version": 11
}
+19
View File
@@ -0,0 +1,19 @@
mssql:
build:
context: blocks/mssql/build
environment:
ACCEPT_EULA: Y
MSSQL_SA_PASSWORD: Password!
MSSQL_PID: Express
MSSQL_DATABASE: grafana
MSSQL_USER: grafana
MSSQL_PASSWORD: Password!
ports:
- "1433:1433"
fake-mssql-data:
image: grafana/fake-data-gen
network_mode: bridge
environment:
FD_DATASOURCE: mssql
FD_PORT: 1433
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
mssqltests:
build:
context: blocks/mssql/build
environment:
ACCEPT_EULA: Y
MSSQL_SA_PASSWORD: Password!
MSSQL_PID: Express
MSSQL_DATABASE: grafanatest
MSSQL_USER: grafana
MSSQL_PASSWORD: Password!
ports:
- "1433:1433"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+22 -13
View File
@@ -11,11 +11,13 @@ weight = 8
# Provisioning Grafana
## Config file
In previous versions of Grafana, you could only use the API for provisioning data sources and dashboards. But that required the service to be running before you started creating dashboards and you also needed to set up credentials for the HTTP API. In v5.0 we decided to improve this experience by adding a new active provisioning system that uses config files. This will make GitOps more natural as data sources and dashboards can be defined via files that can be version controlled. We hope to extend this system to later add support for users, orgs and alerts as well.
## Config File
Checkout the [configuration](/installation/configuration) page for more information on what you can configure in `grafana.ini`
### Config file locations
### Config File Locations
- Default configuration from `$WORKING_DIR/conf/defaults.ini`
- Custom configuration from `$WORKING_DIR/conf/custom.ini`
@@ -26,7 +28,7 @@ Checkout the [configuration](/installation/configuration) page for more informat
> `/etc/grafana/grafana.ini`. This path is specified in the Grafana
> init.d script using `--config` file parameter.
### Using environment variables
### Using Environment Variables
All options in the configuration file (listed below) can be overridden
using environment variables using the syntax:
@@ -59,7 +61,7 @@ export GF_AUTH_GOOGLE_CLIENT_SECRET=newS3cretKey
<hr />
## Configuration management tools
## Configuration Management Tools
Currently we do not provide any scripts/manifests for configuring Grafana. Rather than spending time learning and creating scripts/manifests for each tool, we think our time is better spent making Grafana easier to provision. Therefore, we heavily relay on the expertise of the community.
@@ -76,10 +78,12 @@ Saltstack | [https://github.com/salt-formulas/salt-formula-grafana](https://gith
It's possible to manage datasources in Grafana by adding one or more yaml config files in the [`provisioning/datasources`](/installation/configuration/#provisioning) directory. Each config file can contain a list of `datasources` that will be added or updated during start up. If the datasource already exists, Grafana will update it to match the configuration file. The config file can also contain a list of datasources that should be deleted. That list is called `delete_datasources`. Grafana will delete datasources listed in `delete_datasources` before inserting/updating those in the `datasource` list.
### Running multiple Grafana instances.
### Running Multiple Grafana Instances
If you are running multiple instances of Grafana you might run into problems if they have different versions of the `datasource.yaml` configuration file. The best way to solve this problem is to add a version number to each datasource in the configuration and increase it when you update the config. Grafana will only update datasources with the same or lower version number than specified in the config. That way, old configs cannot overwrite newer configs if they restart at the same time.
### Example datasource config file
### Example Datasource Config File
```yaml
# config file version
apiVersion: 1
@@ -133,14 +137,20 @@ datasources:
editable: false
```
#### Json data
#### Custom Settings per Datasource
| Datasource | Misc |
| ---- | ---- |
| Elasticsearch | Elasticsearch uses the `database` property to configure the index for a datasource |
#### Json Data
Since not all datasources have the same configuration settings we only have the most common ones as fields. The rest should be stored as a json blob in the `json_data` field. Here are the most common settings that the core datasources use.
| Name | Type | Datasource |Description |
| ----| ---- | ---- | --- |
| Name | Type | Datasource | Description |
| ---- | ---- | ---- | ---- |
| tlsAuth | boolean | *All* | Enable TLS authentication using client cert configured in secure json data |
| tlsAuthWithCACert | boolean | *All* | Enable TLS authtication using CA cert |
| tlsAuthWithCACert | boolean | *All* | Enable TLS authentication using CA cert |
| tlsSkipVerify | boolean | *All* | Controls whether a client verifies the server's certificate chain and host name. |
| graphiteVersion | string | Graphite | Graphite version |
| timeInterval | string | Elastic, Influxdb & Prometheus | Lowest interval/step value that should be used for this data source |
@@ -155,8 +165,7 @@ Since not all datasources have the same configuration settings we only have the
| tsdbResolution | string | OpenTsdb | Resolution |
| sslmode | string | Postgre | SSLmode. 'disable', 'require', 'verify-ca' or 'verify-full' |
#### Secure Json data
#### Secure Json Data
`{"authType":"keys","defaultRegion":"us-west-2","timeField":"@timestamp"}`
@@ -194,7 +203,7 @@ providers:
When Grafana starts, it will update/insert all dashboards available in the configured path. Then later on poll that path and look for updated json files and insert those update/insert those into the database.
### Reuseable dashboard urls
### Reuseable Dashboard Urls
If the dashboard in the json file contains an [uid](/reference/dashboard/#json-fields), Grafana will force insert/update on that uid. This allows you to migrate dashboards betweens Grafana instances and provisioning Grafana from configuration without breaking the urls given since the new dashboard url uses the uid as identifer.
When Grafana starts, it will update/insert all dashboards available in the configured folders. If you modify the file, the dashboard will also be updated.
+2
View File
@@ -41,6 +41,8 @@ Grafana ships with the following set of notification types:
To enable email notifications you have to setup [SMTP settings](/installation/configuration/#smtp)
in the Grafana config. Email notifications will upload an image of the alert graph to an
external image destination if available or fallback to attaching the image to the email.
Be aware that if you use the `local` image storage email servers and clients might not be
able to access the image.
### Slack
@@ -75,7 +75,7 @@ You can reference queries by the row “letter” that they’re on (similar to
## Point consolidation
All Graphite metrics are consolidated so that Graphite doesn't return more data points than there are pixels in the graph. By default,
this consolidation is done using `avg` function. You can how Graphite consolidates metrics by adding the Graphite consolidateBy function.
this consolidation is done using `avg` function. You can control how Graphite consolidates metrics by adding the Graphite consolidateBy function.
> *Notice* This means that legend summary values (max, min, total) cannot be all correct at the same time. They are calculated
> client side by Grafana. And depending on your consolidation function only one or two can be correct at the same time.
+1
View File
@@ -42,6 +42,7 @@ Hit `?` on your keyboard to open the shortcuts help modal.
- `e` Toggle panel edit view
- `v` Toggle panel fullscreen view
- `p` `s` Open Panel Share Modal
- `p` `d` Duplicate Panel
- `p` `r` Remove Panel
### Time Range
@@ -234,7 +234,12 @@ The maximum number of connections in the idle connection pool.
### max_open_conn
The maximum number of open connections to the database.
### conn_max_lifetime
Sets the maximum amount of time a connection may be reused. The default is 14400 (which means 14400 seconds or 4 hours). For MySQL, this setting should be shorter than the [`wait_timeout`](https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_wait_timeout) variable.
### log_queries
Set to `true` to log the sql calls and execution times.
<hr />
+3 -3
View File
@@ -15,7 +15,7 @@ weight = 1
Description | Download
------------ | -------------
Stable for Debian-based Linux | [grafana_5.0.3_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.0.3_amd64.deb)
Stable for Debian-based Linux | [grafana_5.0.4_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.0.4_amd64.deb)
Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing
installation.
@@ -24,9 +24,9 @@ installation.
```bash
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.0.3_amd64.deb
wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.0.4_amd64.deb
sudo apt-get install -y adduser libfontconfig
sudo dpkg -i grafana_5.0.3_amd64.deb
sudo dpkg -i grafana_5.0.4_amd64.deb
```
## APT Repository
+7 -7
View File
@@ -15,7 +15,7 @@ weight = 2
Description | Download
------------ | -------------
Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [5.0.3 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.3-1.x86_64.rpm)
Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [5.0.4 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.4-1.x86_64.rpm)
Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing
@@ -26,7 +26,7 @@ installation.
You can install Grafana using Yum directly.
```bash
$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.3-1.x86_64.rpm
$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.4-1.x86_64.rpm
```
Or install manually using `rpm`.
@@ -34,15 +34,15 @@ Or install manually using `rpm`.
#### On CentOS / Fedora / Redhat:
```bash
$ wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.3-1.x86_64.rpm
$ wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.4-1.x86_64.rpm
$ sudo yum install initscripts fontconfig
$ sudo rpm -Uvh grafana-5.0.3-1.x86_64.rpm
$ sudo rpm -Uvh grafana-5.0.4-1.x86_64.rpm
```
#### On OpenSuse:
```bash
$ sudo rpm -i --nodeps grafana-5.0.3-1.x86_64.rpm
$ sudo rpm -i --nodeps grafana-5.0.4-1.x86_64.rpm
```
## Install via YUM Repository
@@ -52,7 +52,7 @@ Add the following to a new file at `/etc/yum.repos.d/grafana.repo`
```bash
[grafana]
name=grafana
baseurl=https://packagecloud.io/grafana/stable/el/6/$basearch
baseurl=https://packagecloud.io/grafana/stable/el/7/$basearch
repo_gpgcheck=1
enabled=1
gpgcheck=1
@@ -64,7 +64,7 @@ sslcacert=/etc/pki/tls/certs/ca-bundle.crt
There is also a testing repository if you want beta or release candidates.
```bash
baseurl=https://packagecloud.io/grafana/testing/el/6/$basearch
baseurl=https://packagecloud.io/grafana/testing/el/7/$basearch
```
Then install Grafana via the `yum` command.
+1 -1
View File
@@ -23,7 +23,7 @@ Before upgrading it can be a good idea to backup your Grafana database. This wil
#### sqlite
If you use sqlite you only need to make a backup of you `grafana.db` file. This is usually located at `/var/lib/grafana/grafana.db` on unix system.
If you use sqlite you only need to make a backup of your `grafana.db` file. This is usually located at `/var/lib/grafana/grafana.db` on unix system.
If you are unsure what database you use and where it is stored check you grafana configuration file. If you
installed grafana to custom location using a binary tar/zip it is usally in `<grafana_install_dir>/data`.
+1 -2
View File
@@ -8,12 +8,11 @@ parent = "installation"
weight = 3
+++
# Installing on Windows
Description | Download
------------ | -------------
Latest stable package for Windows | [grafana-5.0.3.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.3.windows-x64.zip)
Latest stable package for Windows | [grafana-5.0.4.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.4.windows-x64.zip)
Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing
installation.
+70 -1
View File
@@ -1,6 +1,6 @@
+++
title = "Variables"
keywords = ["grafana", "templating", "documentation", "guide"]
keywords = ["grafana", "templating", "documentation", "guide", "template", "variable"]
type = "docs"
[menu.docs]
name = "Variables"
@@ -80,6 +80,73 @@ Option | Description
*Regex* | Regex to filter or capture specific parts of the names return by your data source query. Optional.
*Sort* | Define sort order for options in dropdown. **Disabled** means that the order of options returned by your data source query will be used.
#### Using regex to filter/modify values in the Variable dropdown
Using the Regex Query Option, you filter the list of options returned by the Variable query or modify the options returned.
Examples of filtering on the following list of options:
```text
backend_01
backend_02
backend_03
backend_04
```
##### Filter so that only the options that end with `01` or `02` are returned:
Regex:
```regex
/.*[01|02]/
```
Result:
```text
backend_01
backend_02
```
##### Filter and modify the options using a regex capture group to return part of the text:
Regex:
```regex
/.*(01|02)/
```
Result:
```text
01
02
```
#### Filter and modify - Prometheus Example
List of options:
```text
up{instance="demo.robustperception.io:9090",job="prometheus"} 1 1521630638000
up{instance="demo.robustperception.io:9093",job="alertmanager"} 1 1521630638000
up{instance="demo.robustperception.io:9100",job="node"} 1 1521630638000
```
Regex:
```regex
/.*instance="([^"]*).*/
```
Result:
```text
demo.robustperception.io:9090
demo.robustperception.io:9093
demo.robustperception.io:9100
```
### Query expressions
The query expressions are different for each data source.
@@ -107,6 +174,8 @@ Interpolating a variable with multiple values selected is tricky as it is not st
is valid in the given context where the variable is used. Grafana tries to solve this by allowing each data source plugin to
inform the templating interpolation engine what format to use for multiple values.
Note that the *Custom all value* option on the variable will have to be left blank for Grafana to format all values into a single string.
**Graphite**, for example, uses glob expressions. A variable with multiple values would, in this case, be interpolated as `{host1,host2,host3}` if
the current variable value was *host1*, *host2* and *host3*.
+1 -1
View File
@@ -143,7 +143,7 @@ td[class="stack-column-center"] {
<center>
<p style="text-align: center; font-size: 12px; color: #999999;">
Sent by <a href="[[.AppUrl]]">Grafana v[[.BuildVersion]]</a>
<br />&copy; 2016 Grafana and raintank
<br />&copy; 2018 Grafana Labs
</p>
</center>
</td>
+1 -1
View File
@@ -118,7 +118,7 @@
"prettier --write",
"git add"
],
"*.go": [
"*pkg/**/*.go": [
"gofmt -w -s",
"git add"
]
+9
View File
@@ -1,6 +1,7 @@
package api
import (
"regexp"
"strings"
"github.com/grafana/grafana/pkg/bus"
@@ -21,6 +22,14 @@ func AdminGetSettings(c *m.ReqContext) {
if strings.Contains(keyName, "secret") || strings.Contains(keyName, "password") || (strings.Contains(keyName, "provider_config")) {
value = "************"
}
if strings.Contains(keyName, "url") {
var rgx = regexp.MustCompile(`.*:\/\/([^:]*):([^@]*)@.*?$`)
var subs = rgx.FindAllSubmatch([]byte(value), -1)
if subs != nil && len(subs[0]) == 3 {
value = strings.Replace(value, string(subs[0][1]), "******", 1)
value = strings.Replace(value, string(subs[0][2]), "******", 1)
}
}
jsonSec[keyName] = value
}
+7 -7
View File
@@ -47,14 +47,14 @@ func AdminCreateUser(c *m.ReqContext, form dtos.AdminCreateUserForm) {
}
func AdminUpdateUserPassword(c *m.ReqContext, form dtos.AdminUpdateUserPasswordForm) {
userId := c.ParamsInt64(":id")
userID := c.ParamsInt64(":id")
if len(form.Password) < 4 {
c.JsonApiErr(400, "New password too short", nil)
return
}
userQuery := m.GetUserByIdQuery{Id: userId}
userQuery := m.GetUserByIdQuery{Id: userID}
if err := bus.Dispatch(&userQuery); err != nil {
c.JsonApiErr(500, "Could not read user from database", err)
@@ -64,7 +64,7 @@ func AdminUpdateUserPassword(c *m.ReqContext, form dtos.AdminUpdateUserPasswordF
passwordHashed := util.EncodePassword(form.Password, userQuery.Result.Salt)
cmd := m.ChangeUserPasswordCommand{
UserId: userId,
UserId: userID,
NewPassword: passwordHashed,
}
@@ -77,10 +77,10 @@ func AdminUpdateUserPassword(c *m.ReqContext, form dtos.AdminUpdateUserPasswordF
}
func AdminUpdateUserPermissions(c *m.ReqContext, form dtos.AdminUpdateUserPermissionsForm) {
userId := c.ParamsInt64(":id")
userID := c.ParamsInt64(":id")
cmd := m.UpdateUserPermissionsCommand{
UserId: userId,
UserId: userID,
IsGrafanaAdmin: form.IsGrafanaAdmin,
}
@@ -93,9 +93,9 @@ func AdminUpdateUserPermissions(c *m.ReqContext, form dtos.AdminUpdateUserPermis
}
func AdminDeleteUser(c *m.ReqContext) {
userId := c.ParamsInt64(":id")
userID := c.ParamsInt64(":id")
cmd := m.DeleteUserCommand{UserId: userId}
cmd := m.DeleteUserCommand{UserId: userID}
if err := bus.Dispatch(&cmd); err != nil {
c.JsonApiErr(500, "Failed to delete user", err)
+39 -39
View File
@@ -26,10 +26,10 @@ func ValidateOrgAlert(c *m.ReqContext) {
}
func GetAlertStatesForDashboard(c *m.ReqContext) Response {
dashboardId := c.QueryInt64("dashboardId")
dashboardID := c.QueryInt64("dashboardId")
if dashboardId == 0 {
return ApiError(400, "Missing query parameter dashboardId", nil)
if dashboardID == 0 {
return Error(400, "Missing query parameter dashboardId", nil)
}
query := m.GetAlertStatesForDashboardQuery{
@@ -38,10 +38,10 @@ func GetAlertStatesForDashboard(c *m.ReqContext) Response {
}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Failed to fetch alert states", err)
return Error(500, "Failed to fetch alert states", err)
}
return Json(200, query.Result)
return JSON(200, query.Result)
}
// GET /api/alerts
@@ -60,20 +60,20 @@ func GetAlerts(c *m.ReqContext) Response {
}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "List alerts failed", err)
return Error(500, "List alerts failed", err)
}
for _, alert := range query.Result {
alert.Url = m.GetDashboardUrl(alert.DashboardUid, alert.DashboardSlug)
}
return Json(200, query.Result)
return JSON(200, query.Result)
}
// POST /api/alerts/test
func AlertTest(c *m.ReqContext, dto dtos.AlertTestCommand) Response {
if _, idErr := dto.Dashboard.Get("id").Int64(); idErr != nil {
return ApiError(400, "The dashboard needs to be saved at least once before you can test an alert rule", nil)
return Error(400, "The dashboard needs to be saved at least once before you can test an alert rule", nil)
}
backendCmd := alerting.AlertTestCommand{
@@ -84,9 +84,9 @@ func AlertTest(c *m.ReqContext, dto dtos.AlertTestCommand) Response {
if err := bus.Dispatch(&backendCmd); err != nil {
if validationErr, ok := err.(alerting.ValidationError); ok {
return ApiError(422, validationErr.Error(), nil)
return Error(422, validationErr.Error(), nil)
}
return ApiError(500, "Failed to test rule", err)
return Error(500, "Failed to test rule", err)
}
res := backendCmd.Result
@@ -109,7 +109,7 @@ func AlertTest(c *m.ReqContext, dto dtos.AlertTestCommand) Response {
dtoRes.TimeMs = fmt.Sprintf("%1.3fms", res.GetDurationMs())
return Json(200, dtoRes)
return JSON(200, dtoRes)
}
// GET /api/alerts/:id
@@ -118,21 +118,21 @@ func GetAlert(c *m.ReqContext) Response {
query := m.GetAlertByIdQuery{Id: id}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "List alerts failed", err)
return Error(500, "List alerts failed", err)
}
return Json(200, &query.Result)
return JSON(200, &query.Result)
}
func GetAlertNotifiers(c *m.ReqContext) Response {
return Json(200, alerting.GetNotifiers())
return JSON(200, alerting.GetNotifiers())
}
func GetAlertNotifications(c *m.ReqContext) Response {
query := &m.GetAllAlertNotificationsQuery{OrgId: c.OrgId}
if err := bus.Dispatch(query); err != nil {
return ApiError(500, "Failed to get alert notifications", err)
return Error(500, "Failed to get alert notifications", err)
}
result := make([]*dtos.AlertNotification, 0)
@@ -148,40 +148,40 @@ func GetAlertNotifications(c *m.ReqContext) Response {
})
}
return Json(200, result)
return JSON(200, result)
}
func GetAlertNotificationById(c *m.ReqContext) Response {
func GetAlertNotificationByID(c *m.ReqContext) Response {
query := &m.GetAlertNotificationsQuery{
OrgId: c.OrgId,
Id: c.ParamsInt64("notificationId"),
}
if err := bus.Dispatch(query); err != nil {
return ApiError(500, "Failed to get alert notifications", err)
return Error(500, "Failed to get alert notifications", err)
}
return Json(200, query.Result)
return JSON(200, query.Result)
}
func CreateAlertNotification(c *m.ReqContext, cmd m.CreateAlertNotificationCommand) Response {
cmd.OrgId = c.OrgId
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to create alert notification", err)
return Error(500, "Failed to create alert notification", err)
}
return Json(200, cmd.Result)
return JSON(200, cmd.Result)
}
func UpdateAlertNotification(c *m.ReqContext, cmd m.UpdateAlertNotificationCommand) Response {
cmd.OrgId = c.OrgId
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to update alert notification", err)
return Error(500, "Failed to update alert notification", err)
}
return Json(200, cmd.Result)
return JSON(200, cmd.Result)
}
func DeleteAlertNotification(c *m.ReqContext) Response {
@@ -191,10 +191,10 @@ func DeleteAlertNotification(c *m.ReqContext) Response {
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to delete alert notification", err)
return Error(500, "Failed to delete alert notification", err)
}
return ApiSuccess("Notification deleted")
return Success("Notification deleted")
}
//POST /api/alert-notifications/test
@@ -207,41 +207,41 @@ func NotificationTest(c *m.ReqContext, dto dtos.NotificationTestCommand) Respons
if err := bus.Dispatch(cmd); err != nil {
if err == m.ErrSmtpNotEnabled {
return ApiError(412, err.Error(), err)
return Error(412, err.Error(), err)
}
return ApiError(500, "Failed to send alert notifications", err)
return Error(500, "Failed to send alert notifications", err)
}
return ApiSuccess("Test notification sent")
return Success("Test notification sent")
}
//POST /api/alerts/:alertId/pause
func PauseAlert(c *m.ReqContext, dto dtos.PauseAlertCommand) Response {
alertId := c.ParamsInt64("alertId")
alertID := c.ParamsInt64("alertId")
query := m.GetAlertByIdQuery{Id: alertId}
query := m.GetAlertByIdQuery{Id: alertID}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Get Alert failed", err)
return Error(500, "Get Alert failed", err)
}
guardian := guardian.New(query.Result.DashboardId, c.OrgId, c.SignedInUser)
if canEdit, err := guardian.CanEdit(); err != nil || !canEdit {
if err != nil {
return ApiError(500, "Error while checking permissions for Alert", err)
return Error(500, "Error while checking permissions for Alert", err)
}
return ApiError(403, "Access denied to this dashboard and alert", nil)
return Error(403, "Access denied to this dashboard and alert", nil)
}
cmd := m.PauseAlertCommand{
OrgId: c.OrgId,
AlertIds: []int64{alertId},
AlertIds: []int64{alertID},
Paused: dto.Paused,
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "", err)
return Error(500, "", err)
}
var response m.AlertStateType = m.AlertStatePending
@@ -252,12 +252,12 @@ func PauseAlert(c *m.ReqContext, dto dtos.PauseAlertCommand) Response {
}
result := map[string]interface{}{
"alertId": alertId,
"alertId": alertID,
"state": response,
"message": "Alert " + pausedState,
}
return Json(200, result)
return JSON(200, result)
}
//POST /api/admin/pause-all-alerts
@@ -267,7 +267,7 @@ func PauseAllAlerts(c *m.ReqContext, dto dtos.PauseAllAlertsCommand) Response {
}
if err := bus.Dispatch(&updateCmd); err != nil {
return ApiError(500, "Failed to pause alerts", err)
return Error(500, "Failed to pause alerts", err)
}
var response m.AlertStateType = m.AlertStatePending
@@ -283,5 +283,5 @@ func PauseAllAlerts(c *m.ReqContext, dto dtos.PauseAllAlertsCommand) Response {
"alertsAffected": updateCmd.ResultCount,
}
return Json(200, result)
return JSON(200, result)
}
+47 -47
View File
@@ -30,7 +30,7 @@ func GetAnnotations(c *m.ReqContext) Response {
items, err := repo.Find(query)
if err != nil {
return ApiError(500, "Failed to get annotations", err)
return Error(500, "Failed to get annotations", err)
}
for _, item := range items {
@@ -40,7 +40,7 @@ func GetAnnotations(c *m.ReqContext) Response {
item.Time = item.Time * 1000
}
return Json(200, items)
return JSON(200, items)
}
type CreateAnnotationError struct {
@@ -52,7 +52,7 @@ func (e *CreateAnnotationError) Error() string {
}
func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response {
if canSave, err := canSaveByDashboardId(c, cmd.DashboardId); err != nil || !canSave {
if canSave, err := canSaveByDashboardID(c, cmd.DashboardId); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
@@ -60,7 +60,7 @@ func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response {
if cmd.Text == "" {
err := &CreateAnnotationError{"text field should not be empty"}
return ApiError(500, "Failed to save annotation", err)
return Error(500, "Failed to save annotation", err)
}
item := annotations.Item{
@@ -79,7 +79,7 @@ func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response {
}
if err := repo.Save(&item); err != nil {
return ApiError(500, "Failed to save annotation", err)
return Error(500, "Failed to save annotation", err)
}
startID := item.Id
@@ -93,24 +93,24 @@ func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response {
}
if err := repo.Update(&item); err != nil {
return ApiError(500, "Failed set regionId on annotation", err)
return Error(500, "Failed set regionId on annotation", err)
}
item.Id = 0
item.Epoch = cmd.TimeEnd / 1000
if err := repo.Save(&item); err != nil {
return ApiError(500, "Failed save annotation for region end time", err)
return Error(500, "Failed save annotation for region end time", err)
}
return Json(200, util.DynMap{
return JSON(200, util.DynMap{
"message": "Annotation added",
"id": startID,
"endId": item.Id,
})
}
return Json(200, util.DynMap{
return JSON(200, util.DynMap{
"message": "Annotation added",
"id": startID,
})
@@ -129,7 +129,7 @@ func PostGraphiteAnnotation(c *m.ReqContext, cmd dtos.PostGraphiteAnnotationsCmd
if cmd.What == "" {
err := &CreateAnnotationError{"what field should not be empty"}
return ApiError(500, "Failed to save Graphite annotation", err)
return Error(500, "Failed to save Graphite annotation", err)
}
if cmd.When == 0 {
@@ -152,12 +152,12 @@ func PostGraphiteAnnotation(c *m.ReqContext, cmd dtos.PostGraphiteAnnotationsCmd
tagsArray = append(tagsArray, tagStr)
} else {
err := &CreateAnnotationError{"tag should be a string"}
return ApiError(500, "Failed to save Graphite annotation", err)
return Error(500, "Failed to save Graphite annotation", err)
}
}
default:
err := &CreateAnnotationError{"unsupported tags format"}
return ApiError(500, "Failed to save Graphite annotation", err)
return Error(500, "Failed to save Graphite annotation", err)
}
item := annotations.Item{
@@ -169,35 +169,35 @@ func PostGraphiteAnnotation(c *m.ReqContext, cmd dtos.PostGraphiteAnnotationsCmd
}
if err := repo.Save(&item); err != nil {
return ApiError(500, "Failed to save Graphite annotation", err)
return Error(500, "Failed to save Graphite annotation", err)
}
return Json(200, util.DynMap{
return JSON(200, util.DynMap{
"message": "Graphite annotation added",
"id": item.Id,
})
}
func UpdateAnnotation(c *m.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response {
annotationId := c.ParamsInt64(":annotationId")
annotationID := c.ParamsInt64(":annotationId")
repo := annotations.GetRepository()
if resp := canSave(c, repo, annotationId); resp != nil {
if resp := canSave(c, repo, annotationID); resp != nil {
return resp
}
item := annotations.Item{
OrgId: c.OrgId,
UserId: c.UserId,
Id: annotationId,
Id: annotationID,
Epoch: cmd.Time / 1000,
Text: cmd.Text,
Tags: cmd.Tags,
}
if err := repo.Update(&item); err != nil {
return ApiError(500, "Failed to update annotation", err)
return Error(500, "Failed to update annotation", err)
}
if cmd.IsRegion {
@@ -210,11 +210,11 @@ func UpdateAnnotation(c *m.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response {
itemRight.Id = 0
if err := repo.Update(&itemRight); err != nil {
return ApiError(500, "Failed to update annotation for region end time", err)
return Error(500, "Failed to update annotation for region end time", err)
}
}
return ApiSuccess("Annotation updated")
return Success("Annotation updated")
}
func DeleteAnnotations(c *m.ReqContext, cmd dtos.DeleteAnnotationsCmd) Response {
@@ -227,57 +227,57 @@ func DeleteAnnotations(c *m.ReqContext, cmd dtos.DeleteAnnotationsCmd) Response
})
if err != nil {
return ApiError(500, "Failed to delete annotations", err)
return Error(500, "Failed to delete annotations", err)
}
return ApiSuccess("Annotations deleted")
return Success("Annotations deleted")
}
func DeleteAnnotationById(c *m.ReqContext) Response {
func DeleteAnnotationByID(c *m.ReqContext) Response {
repo := annotations.GetRepository()
annotationId := c.ParamsInt64(":annotationId")
annotationID := c.ParamsInt64(":annotationId")
if resp := canSave(c, repo, annotationId); resp != nil {
if resp := canSave(c, repo, annotationID); resp != nil {
return resp
}
err := repo.Delete(&annotations.DeleteParams{
Id: annotationId,
Id: annotationID,
})
if err != nil {
return ApiError(500, "Failed to delete annotation", err)
return Error(500, "Failed to delete annotation", err)
}
return ApiSuccess("Annotation deleted")
return Success("Annotation deleted")
}
func DeleteAnnotationRegion(c *m.ReqContext) Response {
repo := annotations.GetRepository()
regionId := c.ParamsInt64(":regionId")
regionID := c.ParamsInt64(":regionId")
if resp := canSave(c, repo, regionId); resp != nil {
if resp := canSave(c, repo, regionID); resp != nil {
return resp
}
err := repo.Delete(&annotations.DeleteParams{
RegionId: regionId,
RegionId: regionID,
})
if err != nil {
return ApiError(500, "Failed to delete annotation region", err)
return Error(500, "Failed to delete annotation region", err)
}
return ApiSuccess("Annotation region deleted")
return Success("Annotation region deleted")
}
func canSaveByDashboardId(c *m.ReqContext, dashboardId int64) (bool, error) {
if dashboardId == 0 && !c.SignedInUser.HasRole(m.ROLE_EDITOR) {
func canSaveByDashboardID(c *m.ReqContext, dashboardID int64) (bool, error) {
if dashboardID == 0 && !c.SignedInUser.HasRole(m.ROLE_EDITOR) {
return false, nil
}
if dashboardId > 0 {
guardian := guardian.New(dashboardId, c.OrgId, c.SignedInUser)
if dashboardID > 0 {
guardian := guardian.New(dashboardID, c.OrgId, c.SignedInUser)
if canEdit, err := guardian.CanEdit(); err != nil || !canEdit {
return false, err
}
@@ -286,32 +286,32 @@ func canSaveByDashboardId(c *m.ReqContext, dashboardId int64) (bool, error) {
return true, nil
}
func canSave(c *m.ReqContext, repo annotations.Repository, annotationId int64) Response {
items, err := repo.Find(&annotations.ItemQuery{AnnotationId: annotationId, OrgId: c.OrgId})
func canSave(c *m.ReqContext, repo annotations.Repository, annotationID int64) Response {
items, err := repo.Find(&annotations.ItemQuery{AnnotationId: annotationID, OrgId: c.OrgId})
if err != nil || len(items) == 0 {
return ApiError(500, "Could not find annotation to update", err)
return Error(500, "Could not find annotation to update", err)
}
dashboardId := items[0].DashboardId
dashboardID := items[0].DashboardId
if canSave, err := canSaveByDashboardId(c, dashboardId); err != nil || !canSave {
if canSave, err := canSaveByDashboardID(c, dashboardID); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
return nil
}
func canSaveByRegionId(c *m.ReqContext, repo annotations.Repository, regionId int64) Response {
items, err := repo.Find(&annotations.ItemQuery{RegionId: regionId, OrgId: c.OrgId})
func canSaveByRegionID(c *m.ReqContext, repo annotations.Repository, regionID int64) Response {
items, err := repo.Find(&annotations.ItemQuery{RegionId: regionID, OrgId: c.OrgId})
if err != nil || len(items) == 0 {
return ApiError(500, "Could not find annotation to update", err)
return Error(500, "Could not find annotation to update", err)
}
dashboardId := items[0].DashboardId
dashboardID := items[0].DashboardId
if canSave, err := canSaveByDashboardId(c, dashboardId); err != nil || !canSave {
if canSave, err := canSaveByDashboardID(c, dashboardID); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
+4 -4
View File
@@ -41,7 +41,7 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
sc.handlerFunc = DeleteAnnotationById
sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 403)
})
@@ -68,7 +68,7 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
sc.handlerFunc = DeleteAnnotationById
sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200)
})
@@ -132,7 +132,7 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
sc.handlerFunc = DeleteAnnotationById
sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 403)
})
@@ -159,7 +159,7 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
sc.handlerFunc = DeleteAnnotationById
sc.handlerFunc = DeleteAnnotationByID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
So(sc.resp.Code, ShouldEqual, 200)
})
+24 -24
View File
@@ -9,14 +9,14 @@ import (
)
// Register adds http routes
func (hs *HttpServer) registerRoutes() {
func (hs *HTTPServer) registerRoutes() {
macaronR := hs.macaron
reqSignedIn := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true})
reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})
reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)
reqOrgAdmin := middleware.RoleAuth(m.ROLE_ADMIN)
redirectFromLegacyDashboardUrl := middleware.RedirectFromLegacyDashboardUrl()
redirectFromLegacyDashboardSoloUrl := middleware.RedirectFromLegacyDashboardSoloUrl()
redirectFromLegacyDashboardURL := middleware.RedirectFromLegacyDashboardURL()
redirectFromLegacyDashboardSoloURL := middleware.RedirectFromLegacyDashboardSoloURL()
quota := middleware.Quota
bind := binding.Bind
@@ -67,11 +67,11 @@ func (hs *HttpServer) registerRoutes() {
r.Get("/d/:uid/:slug", reqSignedIn, Index)
r.Get("/d/:uid", reqSignedIn, Index)
r.Get("/dashboard/db/:slug", reqSignedIn, redirectFromLegacyDashboardUrl, Index)
r.Get("/dashboard/db/:slug", reqSignedIn, redirectFromLegacyDashboardURL, Index)
r.Get("/dashboard/script/*", reqSignedIn, Index)
r.Get("/dashboard-solo/snapshot/*", Index)
r.Get("/d-solo/:uid/:slug", reqSignedIn, Index)
r.Get("/dashboard-solo/db/:slug", reqSignedIn, redirectFromLegacyDashboardSoloUrl, Index)
r.Get("/dashboard-solo/db/:slug", reqSignedIn, redirectFromLegacyDashboardSoloURL, Index)
r.Get("/dashboard-solo/script/*", reqSignedIn, Index)
r.Get("/import/dashboard", reqSignedIn, Index)
r.Get("/dashboards/", reqSignedIn, Index)
@@ -110,7 +110,7 @@ func (hs *HttpServer) registerRoutes() {
r.Get("/api/snapshots-delete/:key", reqEditorRole, wrap(DeleteDashboardSnapshot))
// api renew session based on remember cookie
r.Get("/api/login/ping", quota("session"), LoginApiPing)
r.Get("/api/login/ping", quota("session"), LoginAPIPing)
// authed api
r.Group("/api", func(apiRoute RouteRegister) {
@@ -139,7 +139,7 @@ func (hs *HttpServer) registerRoutes() {
apiRoute.Group("/users", func(usersRoute RouteRegister) {
usersRoute.Get("/", wrap(SearchUsers))
usersRoute.Get("/search", wrap(SearchUsersWithPaging))
usersRoute.Get("/:id", wrap(GetUserById))
usersRoute.Get("/:id", wrap(GetUserByID))
usersRoute.Get("/:id/orgs", wrap(GetUserOrgList))
// query parameters /users/lookup?loginOrEmail=admin@example.com
usersRoute.Get("/lookup", wrap(GetUserByLoginOrEmail))
@@ -149,11 +149,11 @@ func (hs *HttpServer) registerRoutes() {
// team (admin permission required)
apiRoute.Group("/teams", func(teamsRoute RouteRegister) {
teamsRoute.Get("/:teamId", wrap(GetTeamById))
teamsRoute.Get("/:teamId", wrap(GetTeamByID))
teamsRoute.Get("/search", wrap(SearchTeams))
teamsRoute.Post("/", bind(m.CreateTeamCommand{}), wrap(CreateTeam))
teamsRoute.Put("/:teamId", bind(m.UpdateTeamCommand{}), wrap(UpdateTeam))
teamsRoute.Delete("/:teamId", wrap(DeleteTeamById))
teamsRoute.Delete("/:teamId", wrap(DeleteTeamByID))
teamsRoute.Get("/:teamId/members", wrap(GetTeamMembers))
teamsRoute.Post("/:teamId/members", bind(m.AddTeamMemberCommand{}), wrap(AddTeamMember))
teamsRoute.Delete("/:teamId/members/:userId", wrap(RemoveTeamMember))
@@ -192,10 +192,10 @@ func (hs *HttpServer) registerRoutes() {
// orgs (admin routes)
apiRoute.Group("/orgs/:orgId", func(orgsRoute RouteRegister) {
orgsRoute.Get("/", wrap(GetOrgById))
orgsRoute.Get("/", wrap(GetOrgByID))
orgsRoute.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrg))
orgsRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddress))
orgsRoute.Delete("/", wrap(DeleteOrgById))
orgsRoute.Delete("/", wrap(DeleteOrgByID))
orgsRoute.Get("/users", wrap(GetOrgUsers))
orgsRoute.Post("/users", bind(m.AddOrgUserCommand{}), wrap(AddOrgUser))
orgsRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUser))
@@ -211,9 +211,9 @@ func (hs *HttpServer) registerRoutes() {
// auth api keys
apiRoute.Group("/auth/keys", func(keysRoute RouteRegister) {
keysRoute.Get("/", wrap(GetApiKeys))
keysRoute.Post("/", quota("api_key"), bind(m.AddApiKeyCommand{}), wrap(AddApiKey))
keysRoute.Delete("/:id", wrap(DeleteApiKey))
keysRoute.Get("/", wrap(GetAPIKeys))
keysRoute.Post("/", quota("api_key"), bind(m.AddApiKeyCommand{}), wrap(AddAPIKey))
keysRoute.Delete("/:id", wrap(DeleteAPIKey))
}, reqOrgAdmin)
// Preferences
@@ -226,16 +226,16 @@ func (hs *HttpServer) registerRoutes() {
datasourceRoute.Get("/", wrap(GetDataSources))
datasourceRoute.Post("/", quota("data_source"), bind(m.AddDataSourceCommand{}), wrap(AddDataSource))
datasourceRoute.Put("/:id", bind(m.UpdateDataSourceCommand{}), wrap(UpdateDataSource))
datasourceRoute.Delete("/:id", wrap(DeleteDataSourceById))
datasourceRoute.Delete("/:id", wrap(DeleteDataSourceByID))
datasourceRoute.Delete("/name/:name", wrap(DeleteDataSourceByName))
datasourceRoute.Get("/:id", wrap(GetDataSourceById))
datasourceRoute.Get("/:id", wrap(GetDataSourceByID))
datasourceRoute.Get("/name/:name", wrap(GetDataSourceByName))
}, reqOrgAdmin)
apiRoute.Get("/datasources/id/:name", wrap(GetDataSourceIdByName), reqSignedIn)
apiRoute.Get("/datasources/id/:name", wrap(GetDataSourceIDByName), reqSignedIn)
apiRoute.Get("/plugins", wrap(GetPluginList))
apiRoute.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingById))
apiRoute.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingByID))
apiRoute.Get("/plugins/:pluginId/markdown/:name", wrap(GetPluginMarkdown))
apiRoute.Group("/plugins", func(pluginRoute RouteRegister) {
@@ -250,11 +250,11 @@ func (hs *HttpServer) registerRoutes() {
// Folders
apiRoute.Group("/folders", func(folderRoute RouteRegister) {
folderRoute.Get("/", wrap(GetFolders))
folderRoute.Get("/id/:id", wrap(GetFolderById))
folderRoute.Get("/id/:id", wrap(GetFolderByID))
folderRoute.Post("/", bind(m.CreateFolderCommand{}), wrap(CreateFolder))
folderRoute.Group("/:uid", func(folderUidRoute RouteRegister) {
folderUidRoute.Get("/", wrap(GetFolderByUid))
folderUidRoute.Get("/", wrap(GetFolderByUID))
folderUidRoute.Put("/", bind(m.UpdateFolderCommand{}), wrap(UpdateFolder))
folderUidRoute.Delete("/", wrap(DeleteFolder))
@@ -268,7 +268,7 @@ func (hs *HttpServer) registerRoutes() {
// Dashboard
apiRoute.Group("/dashboards", func(dashboardRoute RouteRegister) {
dashboardRoute.Get("/uid/:uid", wrap(GetDashboard))
dashboardRoute.Delete("/uid/:uid", wrap(DeleteDashboardByUid))
dashboardRoute.Delete("/uid/:uid", wrap(DeleteDashboardByUID))
dashboardRoute.Get("/db/:slug", wrap(GetDashboard))
dashboardRoute.Delete("/db/:slug", wrap(DeleteDashboard))
@@ -314,7 +314,7 @@ func (hs *HttpServer) registerRoutes() {
// metrics
apiRoute.Post("/tsdb/query", bind(dtos.MetricRequest{}), wrap(QueryMetrics))
apiRoute.Get("/tsdb/testdata/scenarios", wrap(GetTestDataScenarios))
apiRoute.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, wrap(GenerateSqlTestData))
apiRoute.Get("/tsdb/testdata/gensql", reqGrafanaAdmin, wrap(GenerateSQLTestData))
apiRoute.Get("/tsdb/testdata/random-walk", wrap(GetTestDataRandomWalk))
apiRoute.Group("/alerts", func(alertsRoute RouteRegister) {
@@ -332,7 +332,7 @@ func (hs *HttpServer) registerRoutes() {
alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), wrap(NotificationTest))
alertNotifications.Post("/", bind(m.CreateAlertNotificationCommand{}), wrap(CreateAlertNotification))
alertNotifications.Put("/:notificationId", bind(m.UpdateAlertNotificationCommand{}), wrap(UpdateAlertNotification))
alertNotifications.Get("/:notificationId", wrap(GetAlertNotificationById))
alertNotifications.Get("/:notificationId", wrap(GetAlertNotificationByID))
alertNotifications.Delete("/:notificationId", wrap(DeleteAlertNotification))
}, reqEditorRole)
@@ -341,7 +341,7 @@ func (hs *HttpServer) registerRoutes() {
apiRoute.Group("/annotations", func(annotationsRoute RouteRegister) {
annotationsRoute.Post("/", bind(dtos.PostAnnotationsCmd{}), wrap(PostAnnotation))
annotationsRoute.Delete("/:annotationId", wrap(DeleteAnnotationById))
annotationsRoute.Delete("/:annotationId", wrap(DeleteAnnotationByID))
annotationsRoute.Put("/:annotationId", bind(dtos.UpdateAnnotationsCmd{}), wrap(UpdateAnnotation))
annotationsRoute.Delete("/region/:regionId", wrap(DeleteAnnotationRegion))
annotationsRoute.Post("/graphite", reqEditorRole, bind(dtos.PostGraphiteAnnotationsCmd{}), wrap(PostGraphiteAnnotation))
+10 -10
View File
@@ -7,11 +7,11 @@ import (
m "github.com/grafana/grafana/pkg/models"
)
func GetApiKeys(c *m.ReqContext) Response {
func GetAPIKeys(c *m.ReqContext) Response {
query := m.GetApiKeysQuery{OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Failed to list api keys", err)
return Error(500, "Failed to list api keys", err)
}
result := make([]*m.ApiKeyDTO, len(query.Result))
@@ -23,25 +23,25 @@ func GetApiKeys(c *m.ReqContext) Response {
}
}
return Json(200, result)
return JSON(200, result)
}
func DeleteApiKey(c *m.ReqContext) Response {
func DeleteAPIKey(c *m.ReqContext) Response {
id := c.ParamsInt64(":id")
cmd := &m.DeleteApiKeyCommand{Id: id, OrgId: c.OrgId}
err := bus.Dispatch(cmd)
if err != nil {
return ApiError(500, "Failed to delete API key", err)
return Error(500, "Failed to delete API key", err)
}
return ApiSuccess("API key deleted")
return Success("API key deleted")
}
func AddApiKey(c *m.ReqContext, cmd m.AddApiKeyCommand) Response {
func AddAPIKey(c *m.ReqContext, cmd m.AddApiKeyCommand) Response {
if !cmd.Role.IsValid() {
return ApiError(400, "Invalid role specified", nil)
return Error(400, "Invalid role specified", nil)
}
cmd.OrgId = c.OrgId
@@ -50,12 +50,12 @@ func AddApiKey(c *m.ReqContext, cmd m.AddApiKeyCommand) Response {
cmd.Key = newKeyInfo.HashedKey
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to add API key", err)
return Error(500, "Failed to add API key", err)
}
result := &dtos.NewApiKeyResult{
Name: cmd.Result.Name,
Key: newKeyInfo.ClientSecret}
return Json(200, result)
return JSON(200, result)
}
+2 -2
View File
@@ -55,11 +55,11 @@ func InitAppPluginRoutes(r *macaron.Macaron) {
}
}
func AppPluginRoute(route *plugins.AppPluginRoute, appId string) macaron.Handler {
func AppPluginRoute(route *plugins.AppPluginRoute, appID string) macaron.Handler {
return func(c *m.ReqContext) {
path := c.Params("*")
proxy := pluginproxy.NewApiPluginProxy(c, path, route, appId)
proxy := pluginproxy.NewApiPluginProxy(c, path, route, appID)
proxy.Transport = pluginProxyTransport
proxy.ServeHTTP(c.Resp, c.Req.Request)
}
+13 -9
View File
@@ -11,10 +11,10 @@ import (
var (
NotFound = func() Response {
return ApiError(404, "Not found", nil)
return Error(404, "Not found", nil)
}
ServerError = func(err error) Response {
return ApiError(500, "Server error", err)
return Error(500, "Server error", err)
}
)
@@ -67,22 +67,25 @@ func (r *NormalResponse) Header(key, value string) *NormalResponse {
return r
}
// functions to create responses
// Empty create an empty response
func Empty(status int) *NormalResponse {
return Respond(status, nil)
}
func Json(status int, body interface{}) *NormalResponse {
// JSON create a JSON response
func JSON(status int, body interface{}) *NormalResponse {
return Respond(status, body).Header("Content-Type", "application/json")
}
func ApiSuccess(message string) *NormalResponse {
// Success create a successful response
func Success(message string) *NormalResponse {
resp := make(map[string]interface{})
resp["message"] = message
return Json(200, resp)
return JSON(200, resp)
}
func ApiError(status int, message string, err error) *NormalResponse {
// Error create a erroneous response
func Error(status int, message string, err error) *NormalResponse {
data := make(map[string]interface{})
switch status {
@@ -102,7 +105,7 @@ func ApiError(status int, message string, err error) *NormalResponse {
}
}
resp := Json(status, data)
resp := JSON(status, data)
if err != nil {
resp.errMessage = message
@@ -112,6 +115,7 @@ func ApiError(status int, message string, err error) *NormalResponse {
return resp
}
// Respond create a response
func Respond(status int, body interface{}) *NormalResponse {
var b []byte
var err error
@@ -122,7 +126,7 @@ func Respond(status int, body interface{}) *NormalResponse {
b = []byte(t)
default:
if b, err = json.Marshal(body); err != nil {
return ApiError(500, "body json marshal", err)
return Error(500, "body json marshal", err)
}
}
return &NormalResponse{
+1 -1
View File
@@ -99,7 +99,7 @@ func setupScenarioContext(url string) *scenarioContext {
}))
sc.m.Use(middleware.GetContextHandler())
sc.m.Use(middleware.Sessioner(&session.Options{}))
sc.m.Use(middleware.Sessioner(&session.Options{}, 0))
return sc
}
+52 -55
View File
@@ -22,12 +22,12 @@ import (
"github.com/grafana/grafana/pkg/util"
)
func isDashboardStarredByUser(c *m.ReqContext, dashId int64) (bool, error) {
func isDashboardStarredByUser(c *m.ReqContext, dashID int64) (bool, error) {
if !c.IsSignedIn {
return false, nil
}
query := m.IsStarredByUserQuery{UserId: c.UserId, DashboardId: dashId}
query := m.IsStarredByUserQuery{UserId: c.UserId, DashboardId: dashID}
if err := bus.Dispatch(&query); err != nil {
return false, err
}
@@ -37,10 +37,10 @@ func isDashboardStarredByUser(c *m.ReqContext, dashId int64) (bool, error) {
func dashboardGuardianResponse(err error) Response {
if err != nil {
return ApiError(500, "Error while checking dashboard permissions", err)
return Error(500, "Error while checking dashboard permissions", err)
}
return ApiError(403, "Access denied to this dashboard", nil)
return Error(403, "Access denied to this dashboard", nil)
}
func GetDashboard(c *m.ReqContext) Response {
@@ -60,7 +60,7 @@ func GetDashboard(c *m.ReqContext) Response {
isStarred, err := isDashboardStarredByUser(c, dash.Id)
if err != nil {
return ApiError(500, "Error while checking if dashboard was starred by user", err)
return Error(500, "Error while checking if dashboard was starred by user", err)
}
// Finding creator and last updater of the dashboard
@@ -96,7 +96,7 @@ func GetDashboard(c *m.ReqContext) Response {
if dash.FolderId > 0 {
query := m.GetDashboardQuery{Id: dash.FolderId, OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Dashboard folder could not be read", err)
return Error(500, "Dashboard folder could not be read", err)
}
meta.FolderTitle = query.Result.Title
meta.FolderUrl = query.Result.GetUrl()
@@ -111,31 +111,29 @@ func GetDashboard(c *m.ReqContext) Response {
}
c.TimeRequest(metrics.M_Api_Dashboard_Get)
return Json(200, dto)
return JSON(200, dto)
}
func getUserLogin(userId int64) string {
query := m.GetUserByIdQuery{Id: userId}
func getUserLogin(userID int64) string {
query := m.GetUserByIdQuery{Id: userID}
err := bus.Dispatch(&query)
if err != nil {
return "Anonymous"
} else {
user := query.Result
return user.Login
}
return query.Result.Login
}
func getDashboardHelper(orgId int64, slug string, id int64, uid string) (*m.Dashboard, Response) {
func getDashboardHelper(orgID int64, slug string, id int64, uid string) (*m.Dashboard, Response) {
var query m.GetDashboardQuery
if len(uid) > 0 {
query = m.GetDashboardQuery{Uid: uid, Id: id, OrgId: orgId}
query = m.GetDashboardQuery{Uid: uid, Id: id, OrgId: orgID}
} else {
query = m.GetDashboardQuery{Slug: slug, Id: id, OrgId: orgId}
query = m.GetDashboardQuery{Slug: slug, Id: id, OrgId: orgID}
}
if err := bus.Dispatch(&query); err != nil {
return nil, ApiError(404, "Dashboard not found", err)
return nil, Error(404, "Dashboard not found", err)
}
return query.Result, nil
@@ -145,11 +143,11 @@ func DeleteDashboard(c *m.ReqContext) Response {
query := m.GetDashboardsBySlugQuery{OrgId: c.OrgId, Slug: c.Params(":slug")}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Failed to retrieve dashboards by slug", err)
return Error(500, "Failed to retrieve dashboards by slug", err)
}
if len(query.Result) > 1 {
return Json(412, util.DynMap{"status": "multiple-slugs-exists", "message": m.ErrDashboardsWithSameSlugExists.Error()})
return JSON(412, util.DynMap{"status": "multiple-slugs-exists", "message": m.ErrDashboardsWithSameSlugExists.Error()})
}
dash, rsp := getDashboardHelper(c.OrgId, c.Params(":slug"), 0, "")
@@ -164,16 +162,16 @@ func DeleteDashboard(c *m.ReqContext) Response {
cmd := m.DeleteDashboardCommand{OrgId: c.OrgId, Id: dash.Id}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to delete dashboard", err)
return Error(500, "Failed to delete dashboard", err)
}
return Json(200, util.DynMap{
return JSON(200, util.DynMap{
"title": dash.Title,
"message": fmt.Sprintf("Dashboard %s deleted", dash.Title),
})
}
func DeleteDashboardByUid(c *m.ReqContext) Response {
func DeleteDashboardByUID(c *m.ReqContext) Response {
dash, rsp := getDashboardHelper(c.OrgId, "", 0, c.Params(":uid"))
if rsp != nil {
return rsp
@@ -186,10 +184,10 @@ func DeleteDashboardByUid(c *m.ReqContext) Response {
cmd := m.DeleteDashboardCommand{OrgId: c.OrgId, Id: dash.Id}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to delete dashboard", err)
return Error(500, "Failed to delete dashboard", err)
}
return Json(200, util.DynMap{
return JSON(200, util.DynMap{
"title": dash.Title,
"message": fmt.Sprintf("Dashboard %s deleted", dash.Title),
})
@@ -204,10 +202,10 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
if dash.Id == 0 && dash.Uid == "" {
limitReached, err := quota.QuotaReached(c, "dashboard")
if err != nil {
return ApiError(500, "failed to get quota", err)
return Error(500, "failed to get quota", err)
}
if limitReached {
return ApiError(403, "Quota reached", nil)
return Error(403, "Quota reached", nil)
}
}
@@ -231,23 +229,23 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
err == m.ErrFolderNotFound ||
err == m.ErrDashboardFolderCannotHaveParent ||
err == m.ErrDashboardFolderNameExists {
return ApiError(400, err.Error(), nil)
return Error(400, err.Error(), nil)
}
if err == m.ErrDashboardUpdateAccessDenied {
return ApiError(403, err.Error(), err)
return Error(403, err.Error(), err)
}
if err == m.ErrDashboardContainsInvalidAlertData {
return ApiError(500, "Invalid alert data. Cannot save dashboard", err)
return Error(500, "Invalid alert data. Cannot save dashboard", err)
}
if err != nil {
if err == m.ErrDashboardWithSameNameInFolderExists {
return Json(412, util.DynMap{"status": "name-exists", "message": err.Error()})
return JSON(412, util.DynMap{"status": "name-exists", "message": err.Error()})
}
if err == m.ErrDashboardVersionMismatch {
return Json(412, util.DynMap{"status": "version-mismatch", "message": err.Error()})
return JSON(412, util.DynMap{"status": "version-mismatch", "message": err.Error()})
}
if pluginErr, ok := err.(m.UpdatePluginDashboardError); ok {
message := "The dashboard belongs to plugin " + pluginErr.PluginId + "."
@@ -255,20 +253,20 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
if pluginDef, exist := plugins.Plugins[pluginErr.PluginId]; exist {
message = "The dashboard belongs to plugin " + pluginDef.Name + "."
}
return Json(412, util.DynMap{"status": "plugin-dashboard", "message": message})
return JSON(412, util.DynMap{"status": "plugin-dashboard", "message": message})
}
if err == m.ErrDashboardNotFound {
return Json(404, util.DynMap{"status": "not-found", "message": err.Error()})
return JSON(404, util.DynMap{"status": "not-found", "message": err.Error()})
}
return ApiError(500, "Failed to save dashboard", err)
return Error(500, "Failed to save dashboard", err)
}
if err == m.ErrDashboardFailedToUpdateAlertData {
return ApiError(500, "Invalid alert data. Cannot save dashboard", err)
return Error(500, "Invalid alert data. Cannot save dashboard", err)
}
c.TimeRequest(metrics.M_Api_Dashboard_Save)
return Json(200, util.DynMap{
return JSON(200, util.DynMap{
"status": "success",
"slug": dashboard.Slug,
"version": dashboard.Version,
@@ -281,7 +279,7 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
func GetHomeDashboard(c *m.ReqContext) Response {
prefsQuery := m.GetPreferencesWithDefaultsQuery{OrgId: c.OrgId, UserId: c.UserId}
if err := bus.Dispatch(&prefsQuery); err != nil {
return ApiError(500, "Failed to get preferences", err)
return Error(500, "Failed to get preferences", err)
}
if prefsQuery.Result.HomeDashboardId != 0 {
@@ -290,16 +288,15 @@ func GetHomeDashboard(c *m.ReqContext) Response {
if err == nil {
url := m.GetDashboardUrl(slugQuery.Result.Uid, slugQuery.Result.Slug)
dashRedirect := dtos.DashboardRedirect{RedirectUri: url}
return Json(200, &dashRedirect)
} else {
log.Warn("Failed to get slug from database, %s", err.Error())
return JSON(200, &dashRedirect)
}
log.Warn("Failed to get slug from database, %s", err.Error())
}
filePath := path.Join(setting.StaticRootPath, "dashboards/home.json")
file, err := os.Open(filePath)
if err != nil {
return ApiError(500, "Failed to load home dashboard", err)
return Error(500, "Failed to load home dashboard", err)
}
dash := dtos.DashboardFullWithMeta{}
@@ -309,14 +306,14 @@ func GetHomeDashboard(c *m.ReqContext) Response {
jsonParser := json.NewDecoder(file)
if err := jsonParser.Decode(&dash.Dashboard); err != nil {
return ApiError(500, "Failed to load home dashboard", err)
return Error(500, "Failed to load home dashboard", err)
}
if c.HasUserRole(m.ROLE_ADMIN) && !c.HasHelpFlag(m.HelpFlagGettingStartedPanelDismissed) {
addGettingStartedPanelToHomeDashboard(dash.Dashboard)
}
return Json(200, &dash)
return JSON(200, &dash)
}
func addGettingStartedPanelToHomeDashboard(dash *simplejson.Json) {
@@ -339,22 +336,22 @@ func addGettingStartedPanelToHomeDashboard(dash *simplejson.Json) {
// GetDashboardVersions returns all dashboard versions as JSON
func GetDashboardVersions(c *m.ReqContext) Response {
dashId := c.ParamsInt64(":dashboardId")
dashID := c.ParamsInt64(":dashboardId")
guardian := guardian.New(dashId, c.OrgId, c.SignedInUser)
guardian := guardian.New(dashID, c.OrgId, c.SignedInUser)
if canSave, err := guardian.CanSave(); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
query := m.GetDashboardVersionsQuery{
OrgId: c.OrgId,
DashboardId: dashId,
DashboardId: dashID,
Limit: c.QueryInt("limit"),
Start: c.QueryInt("start"),
}
if err := bus.Dispatch(&query); err != nil {
return ApiError(404, fmt.Sprintf("No versions found for dashboardId %d", dashId), err)
return Error(404, fmt.Sprintf("No versions found for dashboardId %d", dashID), err)
}
for _, version := range query.Result {
@@ -373,26 +370,26 @@ func GetDashboardVersions(c *m.ReqContext) Response {
}
}
return Json(200, query.Result)
return JSON(200, query.Result)
}
// GetDashboardVersion returns the dashboard version with the given ID.
func GetDashboardVersion(c *m.ReqContext) Response {
dashId := c.ParamsInt64(":dashboardId")
dashID := c.ParamsInt64(":dashboardId")
guardian := guardian.New(dashId, c.OrgId, c.SignedInUser)
guardian := guardian.New(dashID, c.OrgId, c.SignedInUser)
if canSave, err := guardian.CanSave(); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
query := m.GetDashboardVersionQuery{
OrgId: c.OrgId,
DashboardId: dashId,
DashboardId: dashID,
Version: c.ParamsInt(":id"),
}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, fmt.Sprintf("Dashboard version %d not found for dashboardId %d", query.Version, dashId), err)
return Error(500, fmt.Sprintf("Dashboard version %d not found for dashboardId %d", query.Version, dashID), err)
}
creator := "Anonymous"
@@ -405,7 +402,7 @@ func GetDashboardVersion(c *m.ReqContext) Response {
CreatedBy: creator,
}
return Json(200, dashVersionMeta)
return JSON(200, dashVersionMeta)
}
// POST /api/dashboards/calculate-diff performs diffs on two dashboards
@@ -441,9 +438,9 @@ func CalculateDashboardDiff(c *m.ReqContext, apiOptions dtos.CalculateDiffOption
result, err := dashdiffs.CalculateDiff(&options)
if err != nil {
if err == m.ErrDashboardVersionNotFound {
return ApiError(404, "Dashboard version not found", err)
return Error(404, "Dashboard version not found", err)
}
return ApiError(500, "Unable to compute diff", err)
return Error(500, "Unable to compute diff", err)
}
if options.DiffType == dashdiffs.DiffDelta {
@@ -467,7 +464,7 @@ func RestoreDashboardVersion(c *m.ReqContext, apiCmd dtos.RestoreDashboardVersio
versionQuery := m.GetDashboardVersionQuery{DashboardId: dash.Id, Version: apiCmd.Version, OrgId: c.OrgId}
if err := bus.Dispatch(&versionQuery); err != nil {
return ApiError(404, "Dashboard version not found", nil)
return Error(404, "Dashboard version not found", nil)
}
version := versionQuery.Result
+16 -16
View File
@@ -10,14 +10,14 @@ import (
)
func GetDashboardPermissionList(c *m.ReqContext) Response {
dashId := c.ParamsInt64(":dashboardId")
dashID := c.ParamsInt64(":dashboardId")
_, rsp := getDashboardHelper(c.OrgId, "", dashId, "")
_, rsp := getDashboardHelper(c.OrgId, "", dashID, "")
if rsp != nil {
return rsp
}
g := guardian.New(dashId, c.OrgId, c.SignedInUser)
g := guardian.New(dashID, c.OrgId, c.SignedInUser)
if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin {
return dashboardGuardianResponse(err)
@@ -25,7 +25,7 @@ func GetDashboardPermissionList(c *m.ReqContext) Response {
acl, err := g.GetAcl()
if err != nil {
return ApiError(500, "Failed to get dashboard permissions", err)
return Error(500, "Failed to get dashboard permissions", err)
}
for _, perm := range acl {
@@ -34,29 +34,29 @@ func GetDashboardPermissionList(c *m.ReqContext) Response {
}
}
return Json(200, acl)
return JSON(200, acl)
}
func UpdateDashboardPermissions(c *m.ReqContext, apiCmd dtos.UpdateDashboardAclCommand) Response {
dashId := c.ParamsInt64(":dashboardId")
dashID := c.ParamsInt64(":dashboardId")
_, rsp := getDashboardHelper(c.OrgId, "", dashId, "")
_, rsp := getDashboardHelper(c.OrgId, "", dashID, "")
if rsp != nil {
return rsp
}
g := guardian.New(dashId, c.OrgId, c.SignedInUser)
g := guardian.New(dashID, c.OrgId, c.SignedInUser)
if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin {
return dashboardGuardianResponse(err)
}
cmd := m.UpdateDashboardAclCommand{}
cmd.DashboardId = dashId
cmd.DashboardId = dashID
for _, item := range apiCmd.Items {
cmd.Items = append(cmd.Items, &m.DashboardAcl{
OrgId: c.OrgId,
DashboardId: dashId,
DashboardId: dashID,
UserId: item.UserId,
TeamId: item.TeamId,
Role: item.Role,
@@ -70,21 +70,21 @@ func UpdateDashboardPermissions(c *m.ReqContext, apiCmd dtos.UpdateDashboardAclC
if err != nil {
if err == guardian.ErrGuardianPermissionExists ||
err == guardian.ErrGuardianOverride {
return ApiError(400, err.Error(), err)
return Error(400, err.Error(), err)
}
return ApiError(500, "Error while checking dashboard permissions", err)
return Error(500, "Error while checking dashboard permissions", err)
}
return ApiError(403, "Cannot remove own admin permission for a folder", nil)
return Error(403, "Cannot remove own admin permission for a folder", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrDashboardAclInfoMissing || err == m.ErrDashboardPermissionDashboardEmpty {
return ApiError(409, err.Error(), err)
return Error(409, err.Error(), err)
}
return ApiError(500, "Failed to create permission", err)
return Error(500, "Failed to create permission", err)
}
return ApiSuccess("Dashboard permissions updated")
return Success("Dashboard permissions updated")
}
+10 -10
View File
@@ -99,32 +99,32 @@ func DeleteDashboardSnapshot(c *m.ReqContext) Response {
err := bus.Dispatch(query)
if err != nil {
return ApiError(500, "Failed to get dashboard snapshot", err)
return Error(500, "Failed to get dashboard snapshot", err)
}
if query.Result == nil {
return ApiError(404, "Failed to get dashboard snapshot", nil)
return Error(404, "Failed to get dashboard snapshot", nil)
}
dashboard := query.Result.Dashboard
dashboardId := dashboard.Get("id").MustInt64()
dashboardID := dashboard.Get("id").MustInt64()
guardian := guardian.New(dashboardId, c.OrgId, c.SignedInUser)
guardian := guardian.New(dashboardID, c.OrgId, c.SignedInUser)
canEdit, err := guardian.CanEdit()
if err != nil {
return ApiError(500, "Error while checking permissions for snapshot", err)
return Error(500, "Error while checking permissions for snapshot", err)
}
if !canEdit && query.Result.UserId != c.SignedInUser.UserId {
return ApiError(403, "Access denied to this snapshot", nil)
return Error(403, "Access denied to this snapshot", nil)
}
cmd := &m.DeleteDashboardSnapshotCommand{DeleteKey: key}
if err := bus.Dispatch(cmd); err != nil {
return ApiError(500, "Failed to delete dashboard snapshot", err)
return Error(500, "Failed to delete dashboard snapshot", err)
}
return Json(200, util.DynMap{"message": "Snapshot deleted. It might take an hour before it's cleared from a CDN cache."})
return JSON(200, util.DynMap{"message": "Snapshot deleted. It might take an hour before it's cleared from a CDN cache."})
}
// GET /api/dashboard/snapshots
@@ -145,7 +145,7 @@ func SearchDashboardSnapshots(c *m.ReqContext) Response {
err := bus.Dispatch(&searchQuery)
if err != nil {
return ApiError(500, "Search failed", err)
return Error(500, "Search failed", err)
}
dtos := make([]*m.DashboardSnapshotDTO, len(searchQuery.Result))
@@ -165,5 +165,5 @@ func SearchDashboardSnapshots(c *m.ReqContext) Response {
}
}
return Json(200, dtos)
return JSON(200, dtos)
}
+13 -13
View File
@@ -105,7 +105,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc)
CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() {
@@ -165,7 +165,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc)
CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 200)
Convey("Should lookup dashboard by uid", func() {
@@ -271,7 +271,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc)
CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() {
@@ -329,7 +329,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc)
CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() {
@@ -398,7 +398,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc)
CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 200)
Convey("Should lookup dashboard by uid", func() {
@@ -468,7 +468,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc)
CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() {
@@ -527,7 +527,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc)
CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 200)
Convey("Should lookup dashboard by uid", func() {
@@ -594,7 +594,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc)
CallDeleteDashboardByUID(sc)
So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() {
@@ -638,7 +638,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
Convey("Should result in 412 Precondition failed", func() {
So(sc.resp.Code, ShouldEqual, 412)
result := sc.ToJson()
result := sc.ToJSON()
So(result.Get("status").MustString(), ShouldEqual, "multiple-slugs-exists")
So(result.Get("message").MustString(), ShouldEqual, m.ErrDashboardsWithSameSlugExists.Error())
})
@@ -686,7 +686,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
Convey("It should return correct response data", func() {
result := sc.ToJson()
result := sc.ToJSON()
So(result.Get("status").MustString(), ShouldEqual, "success")
So(result.Get("id").MustInt64(), ShouldEqual, 2)
So(result.Get("uid").MustString(), ShouldEqual, "uid")
@@ -837,12 +837,12 @@ func CallDeleteDashboard(sc *scenarioContext) {
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
}
func CallDeleteDashboardByUid(sc *scenarioContext) {
func CallDeleteDashboardByUID(sc *scenarioContext) {
bus.AddHandler("test", func(cmd *m.DeleteDashboardCommand) error {
return nil
})
sc.handlerFunc = DeleteDashboardByUid
sc.handlerFunc = DeleteDashboardByUID
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
}
@@ -903,7 +903,7 @@ func postDiffScenario(desc string, url string, routePattern string, cmd dtos.Cal
})
}
func (sc *scenarioContext) ToJson() *simplejson.Json {
func (sc *scenarioContext) ToJSON() *simplejson.Json {
var result *simplejson.Json
err := json.NewDecoder(sc.resp.Body).Decode(&result)
So(err, ShouldBeNil)
+5 -5
View File
@@ -13,19 +13,19 @@ import (
const HeaderNameNoBackendCache = "X-Grafana-NoCache"
func (hs *HttpServer) getDatasourceById(id int64, orgId int64, nocache bool) (*m.DataSource, error) {
func (hs *HTTPServer) getDatasourceByID(id int64, orgID int64, nocache bool) (*m.DataSource, error) {
cacheKey := fmt.Sprintf("ds-%d", id)
if !nocache {
if cached, found := hs.cache.Get(cacheKey); found {
ds := cached.(*m.DataSource)
if ds.OrgId == orgId {
if ds.OrgId == orgID {
return ds, nil
}
}
}
query := m.GetDataSourceByIdQuery{Id: id, OrgId: orgId}
query := m.GetDataSourceByIdQuery{Id: id, OrgId: orgID}
if err := bus.Dispatch(&query); err != nil {
return nil, err
}
@@ -34,12 +34,12 @@ func (hs *HttpServer) getDatasourceById(id int64, orgId int64, nocache bool) (*m
return query.Result, nil
}
func (hs *HttpServer) ProxyDataSourceRequest(c *m.ReqContext) {
func (hs *HTTPServer) ProxyDataSourceRequest(c *m.ReqContext) {
c.TimeRequest(metrics.M_DataSource_ProxyReq_Timer)
nocache := c.Req.Header.Get(HeaderNameNoBackendCache) == "true"
ds, err := hs.getDatasourceById(c.ParamsInt64(":id"), c.OrgId, nocache)
ds, err := hs.getDatasourceByID(c.ParamsInt64(":id"), c.OrgId, nocache)
if err != nil {
c.JsonApiErr(500, "Unable to load datasource meta data", err)
+39 -40
View File
@@ -14,7 +14,7 @@ func GetDataSources(c *m.ReqContext) Response {
query := m.GetDataSourcesQuery{OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Failed to query datasources", err)
return Error(500, "Failed to query datasources", err)
}
result := make(dtos.DataSourceList, 0)
@@ -46,10 +46,10 @@ func GetDataSources(c *m.ReqContext) Response {
sort.Sort(result)
return Json(200, &result)
return JSON(200, &result)
}
func GetDataSourceById(c *m.ReqContext) Response {
func GetDataSourceByID(c *m.ReqContext) Response {
query := m.GetDataSourceByIdQuery{
Id: c.ParamsInt64(":id"),
OrgId: c.OrgId,
@@ -57,66 +57,66 @@ func GetDataSourceById(c *m.ReqContext) Response {
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrDataSourceNotFound {
return ApiError(404, "Data source not found", nil)
return Error(404, "Data source not found", nil)
}
return ApiError(500, "Failed to query datasources", err)
return Error(500, "Failed to query datasources", err)
}
ds := query.Result
dtos := convertModelToDtos(ds)
return Json(200, &dtos)
return JSON(200, &dtos)
}
func DeleteDataSourceById(c *m.ReqContext) Response {
func DeleteDataSourceByID(c *m.ReqContext) Response {
id := c.ParamsInt64(":id")
if id <= 0 {
return ApiError(400, "Missing valid datasource id", nil)
return Error(400, "Missing valid datasource id", nil)
}
ds, err := getRawDataSourceById(id, c.OrgId)
ds, err := getRawDataSourceByID(id, c.OrgId)
if err != nil {
return ApiError(400, "Failed to delete datasource", nil)
return Error(400, "Failed to delete datasource", nil)
}
if ds.ReadOnly {
return ApiError(403, "Cannot delete read-only data source", nil)
return Error(403, "Cannot delete read-only data source", nil)
}
cmd := &m.DeleteDataSourceByIdCommand{Id: id, OrgId: c.OrgId}
err = bus.Dispatch(cmd)
if err != nil {
return ApiError(500, "Failed to delete datasource", err)
return Error(500, "Failed to delete datasource", err)
}
return ApiSuccess("Data source deleted")
return Success("Data source deleted")
}
func DeleteDataSourceByName(c *m.ReqContext) Response {
name := c.Params(":name")
if name == "" {
return ApiError(400, "Missing valid datasource name", nil)
return Error(400, "Missing valid datasource name", nil)
}
getCmd := &m.GetDataSourceByNameQuery{Name: name, OrgId: c.OrgId}
if err := bus.Dispatch(getCmd); err != nil {
return ApiError(500, "Failed to delete datasource", err)
return Error(500, "Failed to delete datasource", err)
}
if getCmd.Result.ReadOnly {
return ApiError(403, "Cannot delete read-only data source", nil)
return Error(403, "Cannot delete read-only data source", nil)
}
cmd := &m.DeleteDataSourceByNameCommand{Name: name, OrgId: c.OrgId}
err := bus.Dispatch(cmd)
if err != nil {
return ApiError(500, "Failed to delete datasource", err)
return Error(500, "Failed to delete datasource", err)
}
return ApiSuccess("Data source deleted")
return Success("Data source deleted")
}
func AddDataSource(c *m.ReqContext, cmd m.AddDataSourceCommand) Response {
@@ -124,14 +124,14 @@ func AddDataSource(c *m.ReqContext, cmd m.AddDataSourceCommand) Response {
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrDataSourceNameExists {
return ApiError(409, err.Error(), err)
return Error(409, err.Error(), err)
}
return ApiError(500, "Failed to add datasource", err)
return Error(500, "Failed to add datasource", err)
}
ds := convertModelToDtos(cmd.Result)
return Json(200, util.DynMap{
return JSON(200, util.DynMap{
"message": "Datasource added",
"id": cmd.Result.Id,
"name": cmd.Result.Name,
@@ -143,21 +143,20 @@ func UpdateDataSource(c *m.ReqContext, cmd m.UpdateDataSourceCommand) Response {
cmd.OrgId = c.OrgId
cmd.Id = c.ParamsInt64(":id")
err := fillWithSecureJsonData(&cmd)
err := fillWithSecureJSONData(&cmd)
if err != nil {
return ApiError(500, "Failed to update datasource", err)
return Error(500, "Failed to update datasource", err)
}
err = bus.Dispatch(&cmd)
if err != nil {
if err == m.ErrDataSourceUpdatingOldVersion {
return ApiError(500, "Failed to update datasource. Reload new version and try again", err)
} else {
return ApiError(500, "Failed to update datasource", err)
return Error(500, "Failed to update datasource. Reload new version and try again", err)
}
return Error(500, "Failed to update datasource", err)
}
ds := convertModelToDtos(cmd.Result)
return Json(200, util.DynMap{
return JSON(200, util.DynMap{
"message": "Datasource updated",
"id": cmd.Id,
"name": cmd.Name,
@@ -165,12 +164,12 @@ func UpdateDataSource(c *m.ReqContext, cmd m.UpdateDataSourceCommand) Response {
})
}
func fillWithSecureJsonData(cmd *m.UpdateDataSourceCommand) error {
func fillWithSecureJSONData(cmd *m.UpdateDataSourceCommand) error {
if len(cmd.SecureJsonData) == 0 {
return nil
}
ds, err := getRawDataSourceById(cmd.Id, cmd.OrgId)
ds, err := getRawDataSourceByID(cmd.Id, cmd.OrgId)
if err != nil {
return err
}
@@ -179,8 +178,8 @@ func fillWithSecureJsonData(cmd *m.UpdateDataSourceCommand) error {
return m.ErrDatasourceIsReadOnly
}
secureJsonData := ds.SecureJsonData.Decrypt()
for k, v := range secureJsonData {
secureJSONData := ds.SecureJsonData.Decrypt()
for k, v := range secureJSONData {
if _, ok := cmd.SecureJsonData[k]; !ok {
cmd.SecureJsonData[k] = v
@@ -190,10 +189,10 @@ func fillWithSecureJsonData(cmd *m.UpdateDataSourceCommand) error {
return nil
}
func getRawDataSourceById(id int64, orgId int64) (*m.DataSource, error) {
func getRawDataSourceByID(id int64, orgID int64) (*m.DataSource, error) {
query := m.GetDataSourceByIdQuery{
Id: id,
OrgId: orgId,
OrgId: orgID,
}
if err := bus.Dispatch(&query); err != nil {
@@ -209,25 +208,25 @@ func GetDataSourceByName(c *m.ReqContext) Response {
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrDataSourceNotFound {
return ApiError(404, "Data source not found", nil)
return Error(404, "Data source not found", nil)
}
return ApiError(500, "Failed to query datasources", err)
return Error(500, "Failed to query datasources", err)
}
dtos := convertModelToDtos(query.Result)
dtos.ReadOnly = true
return Json(200, &dtos)
return JSON(200, &dtos)
}
// Get /api/datasources/id/:name
func GetDataSourceIdByName(c *m.ReqContext) Response {
func GetDataSourceIDByName(c *m.ReqContext) Response {
query := m.GetDataSourceByNameQuery{Name: c.Params(":name"), OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrDataSourceNotFound {
return ApiError(404, "Data source not found", nil)
return Error(404, "Data source not found", nil)
}
return ApiError(500, "Failed to query datasources", err)
return Error(500, "Failed to query datasources", err)
}
ds := query.Result
@@ -235,7 +234,7 @@ func GetDataSourceIdByName(c *m.ReqContext) Response {
Id: ds.Id,
}
return Json(200, &dtos)
return JSON(200, &dtos)
}
func convertModelToDtos(ds *m.DataSource) dtos.DataSource {
+4
View File
@@ -50,6 +50,10 @@ type UserStars struct {
}
func GetGravatarUrl(text string) string {
if setting.DisableGravatar {
return "/public/img/user_profile.png"
}
if text == "" {
return ""
}
+2 -2
View File
@@ -2,12 +2,12 @@ package dtos
type Prefs struct {
Theme string `json:"theme"`
HomeDashboardId int64 `json:"homeDashboardId"`
HomeDashboardID int64 `json:"homeDashboardId"`
Timezone string `json:"timezone"`
}
type UpdatePrefsCmd struct {
Theme string `json:"theme"`
HomeDashboardId int64 `json:"homeDashboardId"`
HomeDashboardID int64 `json:"homeDashboardId"`
Timezone string `json:"timezone"`
}
+15 -15
View File
@@ -28,30 +28,30 @@ func GetFolders(c *m.ReqContext) Response {
})
}
return Json(200, result)
return JSON(200, result)
}
func GetFolderByUid(c *m.ReqContext) Response {
func GetFolderByUID(c *m.ReqContext) Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
folder, err := s.GetFolderByUid(c.Params(":uid"))
folder, err := s.GetFolderByUID(c.Params(":uid"))
if err != nil {
return toFolderError(err)
}
g := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
return Json(200, toFolderDto(g, folder))
return JSON(200, toFolderDto(g, folder))
}
func GetFolderById(c *m.ReqContext) Response {
func GetFolderByID(c *m.ReqContext) Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
folder, err := s.GetFolderById(c.ParamsInt64(":id"))
folder, err := s.GetFolderByID(c.ParamsInt64(":id"))
if err != nil {
return toFolderError(err)
}
g := guardian.New(folder.Id, c.OrgId, c.SignedInUser)
return Json(200, toFolderDto(g, folder))
return JSON(200, toFolderDto(g, folder))
}
func CreateFolder(c *m.ReqContext, cmd m.CreateFolderCommand) Response {
@@ -62,7 +62,7 @@ func CreateFolder(c *m.ReqContext, cmd m.CreateFolderCommand) Response {
}
g := guardian.New(cmd.Result.Id, c.OrgId, c.SignedInUser)
return Json(200, toFolderDto(g, cmd.Result))
return JSON(200, toFolderDto(g, cmd.Result))
}
func UpdateFolder(c *m.ReqContext, cmd m.UpdateFolderCommand) Response {
@@ -73,7 +73,7 @@ func UpdateFolder(c *m.ReqContext, cmd m.UpdateFolderCommand) Response {
}
g := guardian.New(cmd.Result.Id, c.OrgId, c.SignedInUser)
return Json(200, toFolderDto(g, cmd.Result))
return JSON(200, toFolderDto(g, cmd.Result))
}
func DeleteFolder(c *m.ReqContext) Response {
@@ -83,7 +83,7 @@ func DeleteFolder(c *m.ReqContext) Response {
return toFolderError(err)
}
return Json(200, util.DynMap{
return JSON(200, util.DynMap{
"title": f.Title,
"message": fmt.Sprintf("Folder %s deleted", f.Title),
})
@@ -127,20 +127,20 @@ func toFolderError(err error) Response {
err == m.ErrDashboardTypeMismatch ||
err == m.ErrDashboardInvalidUid ||
err == m.ErrDashboardUidToLong {
return ApiError(400, err.Error(), nil)
return Error(400, err.Error(), nil)
}
if err == m.ErrFolderAccessDenied {
return ApiError(403, "Access denied", err)
return Error(403, "Access denied", err)
}
if err == m.ErrFolderNotFound {
return Json(404, util.DynMap{"status": "not-found", "message": m.ErrFolderNotFound.Error()})
return JSON(404, util.DynMap{"status": "not-found", "message": m.ErrFolderNotFound.Error()})
}
if err == m.ErrFolderVersionMismatch {
return Json(412, util.DynMap{"status": "version-mismatch", "message": m.ErrFolderVersionMismatch.Error()})
return JSON(412, util.DynMap{"status": "version-mismatch", "message": m.ErrFolderVersionMismatch.Error()})
}
return ApiError(500, "Folder API error", err)
return Error(500, "Folder API error", err)
}
+10 -10
View File
@@ -12,7 +12,7 @@ import (
func GetFolderPermissionList(c *m.ReqContext) Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
folder, err := s.GetFolderByUid(c.Params(":uid"))
folder, err := s.GetFolderByUID(c.Params(":uid"))
if err != nil {
return toFolderError(err)
@@ -26,7 +26,7 @@ func GetFolderPermissionList(c *m.ReqContext) Response {
acl, err := g.GetAcl()
if err != nil {
return ApiError(500, "Failed to get folder permissions", err)
return Error(500, "Failed to get folder permissions", err)
}
for _, perm := range acl {
@@ -38,12 +38,12 @@ func GetFolderPermissionList(c *m.ReqContext) Response {
}
}
return Json(200, acl)
return JSON(200, acl)
}
func UpdateFolderPermissions(c *m.ReqContext, apiCmd dtos.UpdateDashboardAclCommand) Response {
s := dashboards.NewFolderService(c.OrgId, c.SignedInUser)
folder, err := s.GetFolderByUid(c.Params(":uid"))
folder, err := s.GetFolderByUID(c.Params(":uid"))
if err != nil {
return toFolderError(err)
@@ -79,13 +79,13 @@ func UpdateFolderPermissions(c *m.ReqContext, apiCmd dtos.UpdateDashboardAclComm
if err != nil {
if err == guardian.ErrGuardianPermissionExists ||
err == guardian.ErrGuardianOverride {
return ApiError(400, err.Error(), err)
return Error(400, err.Error(), err)
}
return ApiError(500, "Error while checking folder permissions", err)
return Error(500, "Error while checking folder permissions", err)
}
return ApiError(403, "Cannot remove own admin permission for a folder", nil)
return Error(403, "Cannot remove own admin permission for a folder", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
@@ -97,11 +97,11 @@ func UpdateFolderPermissions(c *m.ReqContext, apiCmd dtos.UpdateDashboardAclComm
}
if err == m.ErrFolderAclInfoMissing || err == m.ErrFolderPermissionFolderEmpty {
return ApiError(409, err.Error(), err)
return Error(409, err.Error(), err)
}
return ApiError(500, "Failed to create permission", err)
return Error(500, "Failed to create permission", err)
}
return ApiSuccess("Folder permissions updated")
return Success("Folder permissions updated")
}
+5 -5
View File
@@ -17,7 +17,7 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
Convey("Folder permissions test", t, func() {
Convey("Given folder not exists", func() {
mock := &fakeFolderService{
GetFolderByUidError: m.ErrFolderNotFound,
GetFolderByUIDError: m.ErrFolderNotFound,
}
origNewFolderService := dashboards.NewFolderService
@@ -49,7 +49,7 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false})
mock := &fakeFolderService{
GetFolderByUidResult: &m.Folder{
GetFolderByUIDResult: &m.Folder{
Id: 1,
Uid: "uid",
Title: "Folder",
@@ -96,7 +96,7 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
})
mock := &fakeFolderService{
GetFolderByUidResult: &m.Folder{
GetFolderByUIDResult: &m.Folder{
Id: 1,
Uid: "uid",
Title: "Folder",
@@ -142,7 +142,7 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
})
mock := &fakeFolderService{
GetFolderByUidResult: &m.Folder{
GetFolderByUIDResult: &m.Folder{
Id: 1,
Uid: "uid",
Title: "Folder",
@@ -178,7 +178,7 @@ func TestFolderPermissionApiEndpoint(t *testing.T) {
)
mock := &fakeFolderService{
GetFolderByUidResult: &m.Folder{
GetFolderByUIDResult: &m.Folder{
Id: 1,
Uid: "uid",
Title: "Folder",
+11 -11
View File
@@ -133,8 +133,8 @@ func TestFoldersApiEndpoint(t *testing.T) {
})
}
func callGetFolderByUid(sc *scenarioContext) {
sc.handlerFunc = GetFolderByUid
func callGetFolderByUID(sc *scenarioContext) {
sc.handlerFunc = GetFolderByUID
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
}
@@ -204,10 +204,10 @@ func updateFolderScenario(desc string, url string, routePattern string, mock *fa
type fakeFolderService struct {
GetFoldersResult []*m.Folder
GetFoldersError error
GetFolderByUidResult *m.Folder
GetFolderByUidError error
GetFolderByIdResult *m.Folder
GetFolderByIdError error
GetFolderByUIDResult *m.Folder
GetFolderByUIDError error
GetFolderByIDResult *m.Folder
GetFolderByIDError error
CreateFolderResult *m.Folder
CreateFolderError error
UpdateFolderResult *m.Folder
@@ -221,12 +221,12 @@ func (s *fakeFolderService) GetFolders(limit int) ([]*m.Folder, error) {
return s.GetFoldersResult, s.GetFoldersError
}
func (s *fakeFolderService) GetFolderById(id int64) (*m.Folder, error) {
return s.GetFolderByIdResult, s.GetFolderByIdError
func (s *fakeFolderService) GetFolderByID(id int64) (*m.Folder, error) {
return s.GetFolderByIDResult, s.GetFolderByIDError
}
func (s *fakeFolderService) GetFolderByUid(uid string) (*m.Folder, error) {
return s.GetFolderByUidResult, s.GetFolderByUidError
func (s *fakeFolderService) GetFolderByUID(uid string) (*m.Folder, error) {
return s.GetFolderByUIDResult, s.GetFolderByUIDError
}
func (s *fakeFolderService) CreateFolder(cmd *m.CreateFolderCommand) error {
@@ -234,7 +234,7 @@ func (s *fakeFolderService) CreateFolder(cmd *m.CreateFolderCommand) error {
return s.CreateFolderError
}
func (s *fakeFolderService) UpdateFolder(existingUid string, cmd *m.UpdateFolderCommand) error {
func (s *fakeFolderService) UpdateFolder(existingUID string, cmd *m.UpdateFolderCommand) error {
cmd.Result = s.UpdateFolderResult
return s.UpdateFolderError
}
+15 -12
View File
@@ -29,7 +29,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
type HttpServer struct {
type HTTPServer struct {
log log.Logger
macaron *macaron.Macaron
context context.Context
@@ -39,14 +39,14 @@ type HttpServer struct {
httpSrv *http.Server
}
func NewHttpServer() *HttpServer {
return &HttpServer{
func NewHTTPServer() *HTTPServer {
return &HTTPServer{
log: log.New("http.server"),
cache: gocache.New(5*time.Minute, 10*time.Minute),
}
}
func (hs *HttpServer) Start(ctx context.Context) error {
func (hs *HTTPServer) Start(ctx context.Context) error {
var err error
hs.context = ctx
@@ -74,12 +74,15 @@ func (hs *HttpServer) Start(ctx context.Context) error {
return nil
}
case setting.SOCKET:
ln, err := net.Listen("unix", setting.SocketPath)
ln, err := net.ListenUnix("unix", &net.UnixAddr{Name: setting.SocketPath, Net: "unix"})
if err != nil {
hs.log.Debug("server was shutdown gracefully")
return nil
}
// Make socket writable by group
os.Chmod(setting.SocketPath, 0660)
err = hs.httpSrv.Serve(ln)
if err != nil {
hs.log.Debug("server was shutdown gracefully")
@@ -93,13 +96,13 @@ func (hs *HttpServer) Start(ctx context.Context) error {
return err
}
func (hs *HttpServer) Shutdown(ctx context.Context) error {
func (hs *HTTPServer) Shutdown(ctx context.Context) error {
err := hs.httpSrv.Shutdown(ctx)
hs.log.Info("Stopped HTTP server")
return err
}
func (hs *HttpServer) listenAndServeTLS(certfile, keyfile string) error {
func (hs *HTTPServer) listenAndServeTLS(certfile, keyfile string) error {
if certfile == "" {
return fmt.Errorf("cert_file cannot be empty when using HTTPS")
}
@@ -141,7 +144,7 @@ func (hs *HttpServer) listenAndServeTLS(certfile, keyfile string) error {
return hs.httpSrv.ListenAndServeTLS(setting.CertFile, setting.KeyFile)
}
func (hs *HttpServer) newMacaron() *macaron.Macaron {
func (hs *HTTPServer) newMacaron() *macaron.Macaron {
macaron.Env = setting.Env
m := macaron.New()
@@ -175,7 +178,7 @@ func (hs *HttpServer) newMacaron() *macaron.Macaron {
m.Use(hs.healthHandler)
m.Use(hs.metricsEndpoint)
m.Use(middleware.GetContextHandler())
m.Use(middleware.Sessioner(&setting.SessionOptions))
m.Use(middleware.Sessioner(&setting.SessionOptions, setting.SessionConnMaxLifetime))
m.Use(middleware.OrgRedirect())
// needs to be after context handler
@@ -188,7 +191,7 @@ func (hs *HttpServer) newMacaron() *macaron.Macaron {
return m
}
func (hs *HttpServer) metricsEndpoint(ctx *macaron.Context) {
func (hs *HTTPServer) metricsEndpoint(ctx *macaron.Context) {
if ctx.Req.Method != "GET" || ctx.Req.URL.Path != "/metrics" {
return
}
@@ -197,7 +200,7 @@ func (hs *HttpServer) metricsEndpoint(ctx *macaron.Context) {
ServeHTTP(ctx.Resp, ctx.Req.Request)
}
func (hs *HttpServer) healthHandler(ctx *macaron.Context) {
func (hs *HTTPServer) healthHandler(ctx *macaron.Context) {
notHeadOrGet := ctx.Req.Method != http.MethodGet && ctx.Req.Method != http.MethodHead
if notHeadOrGet || ctx.Req.URL.Path != "/api/health" {
return
@@ -221,7 +224,7 @@ func (hs *HttpServer) healthHandler(ctx *macaron.Context) {
ctx.Resp.Write(dataBytes)
}
func (hs *HttpServer) mapStatic(m *macaron.Macaron, rootDir string, dir string, prefix string) {
func (hs *HTTPServer) mapStatic(m *macaron.Macaron, rootDir string, dir string, prefix string) {
headers := func(c *macaron.Context) {
c.Resp.Header().Set("Cache-Control", "public, max-age=3600")
}
+15 -14
View File
@@ -32,13 +32,13 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
locale = parts[0]
}
appUrl := setting.AppUrl
appSubUrl := setting.AppSubUrl
appURL := setting.AppUrl
appSubURL := setting.AppSubUrl
// special case when doing localhost call from phantomjs
if c.IsRenderCall {
appUrl = fmt.Sprintf("%s://localhost:%s", setting.Protocol, setting.HttpPort)
appSubUrl = ""
appURL = fmt.Sprintf("%s://localhost:%s", setting.Protocol, setting.HttpPort)
appSubURL = ""
settings["appSubUrl"] = ""
}
@@ -62,8 +62,8 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
},
Settings: settings,
Theme: prefs.Theme,
AppUrl: appUrl,
AppSubUrl: appSubUrl,
AppUrl: appURL,
AppSubUrl: appSubURL,
GoogleAnalyticsId: setting.GoogleAnalyticsId,
GoogleTagManagerId: setting.GoogleTagManagerId,
BuildVersion: setting.BuildVersion,
@@ -80,8 +80,8 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
data.User.Name = data.User.Login
}
themeUrlParam := c.Query("theme")
if themeUrlParam == "light" {
themeURLParam := c.Query("theme")
if themeURLParam == "light" {
data.User.LightTheme = true
data.Theme = "light"
}
@@ -299,12 +299,12 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
}
func Index(c *m.ReqContext) {
if data, err := setIndexViewData(c); err != nil {
data, err := setIndexViewData(c)
if err != nil {
c.Handle(500, "Failed to get settings", err)
return
} else {
c.HTML(200, "index", data)
}
c.HTML(200, "index", data)
}
func NotFoundHandler(c *m.ReqContext) {
@@ -313,10 +313,11 @@ func NotFoundHandler(c *m.ReqContext) {
return
}
if data, err := setIndexViewData(c); err != nil {
data, err := setIndexViewData(c)
if err != nil {
c.Handle(500, "Failed to get settings", err)
return
} else {
c.HTML(404, "index", data)
}
c.HTML(404, "index", data)
}
+7 -7
View File
@@ -14,7 +14,7 @@ import (
)
const (
VIEW_INDEX = "index"
ViewIndex = "index"
)
func LoginView(c *m.ReqContext) {
@@ -40,7 +40,7 @@ func LoginView(c *m.ReqContext) {
}
if !tryLoginUsingRememberCookie(c) {
c.HTML(200, VIEW_INDEX, viewData)
c.HTML(200, ViewIndex, viewData)
return
}
@@ -87,7 +87,7 @@ func tryLoginUsingRememberCookie(c *m.ReqContext) bool {
return true
}
func LoginApiPing(c *m.ReqContext) {
func LoginAPIPing(c *m.ReqContext) {
if !tryLoginUsingRememberCookie(c) {
c.JsonApiErr(401, "Unauthorized", nil)
return
@@ -98,7 +98,7 @@ func LoginApiPing(c *m.ReqContext) {
func LoginPost(c *m.ReqContext, cmd dtos.LoginCommand) Response {
if setting.DisableLoginForm {
return ApiError(401, "Login is disabled", nil)
return Error(401, "Login is disabled", nil)
}
authQuery := login.LoginUserQuery{
@@ -109,10 +109,10 @@ func LoginPost(c *m.ReqContext, cmd dtos.LoginCommand) Response {
if err := bus.Dispatch(&authQuery); err != nil {
if err == login.ErrInvalidCredentials || err == login.ErrTooManyLoginAttempts {
return ApiError(401, "Invalid username or password", err)
return Error(401, "Invalid username or password", err)
}
return ApiError(500, "Error while trying to authenticate user", err)
return Error(500, "Error while trying to authenticate user", err)
}
user := authQuery.User
@@ -130,7 +130,7 @@ func LoginPost(c *m.ReqContext, cmd dtos.LoginCommand) Response {
metrics.M_Api_Login_Post.Inc()
return Json(200, result)
return JSON(200, result)
}
func loginUserWithUser(user *m.User, c *m.ReqContext) {
+14 -14
View File
@@ -17,17 +17,17 @@ func QueryMetrics(c *m.ReqContext, reqDto dtos.MetricRequest) Response {
timeRange := tsdb.NewTimeRange(reqDto.From, reqDto.To)
if len(reqDto.Queries) == 0 {
return ApiError(400, "No queries found in query", nil)
return Error(400, "No queries found in query", nil)
}
dsId, err := reqDto.Queries[0].Get("datasourceId").Int64()
dsID, err := reqDto.Queries[0].Get("datasourceId").Int64()
if err != nil {
return ApiError(400, "Query missing datasourceId", nil)
return Error(400, "Query missing datasourceId", nil)
}
dsQuery := m.GetDataSourceByIdQuery{Id: dsId, OrgId: c.OrgId}
dsQuery := m.GetDataSourceByIdQuery{Id: dsID, OrgId: c.OrgId}
if err := bus.Dispatch(&dsQuery); err != nil {
return ApiError(500, "failed to fetch data source", err)
return Error(500, "failed to fetch data source", err)
}
request := &tsdb.TsdbQuery{TimeRange: timeRange}
@@ -44,7 +44,7 @@ func QueryMetrics(c *m.ReqContext, reqDto dtos.MetricRequest) Response {
resp, err := tsdb.HandleRequest(context.Background(), dsQuery.Result, request)
if err != nil {
return ApiError(500, "Metric request error", err)
return Error(500, "Metric request error", err)
}
statusCode := 200
@@ -56,7 +56,7 @@ func QueryMetrics(c *m.ReqContext, reqDto dtos.MetricRequest) Response {
}
}
return Json(statusCode, &resp)
return JSON(statusCode, &resp)
}
// GET /api/tsdb/testdata/scenarios
@@ -72,22 +72,22 @@ func GetTestDataScenarios(c *m.ReqContext) Response {
})
}
return Json(200, &result)
return JSON(200, &result)
}
// Genereates a index out of range error
func GenerateError(c *m.ReqContext) Response {
var array []string
return Json(200, array[20])
return JSON(200, array[20])
}
// GET /api/tsdb/testdata/gensql
func GenerateSqlTestData(c *m.ReqContext) Response {
func GenerateSQLTestData(c *m.ReqContext) Response {
if err := bus.Dispatch(&m.InsertSqlTestDataCommand{}); err != nil {
return ApiError(500, "Failed to insert test data", err)
return Error(500, "Failed to insert test data", err)
}
return Json(200, &util.DynMap{"message": "OK"})
return JSON(200, &util.DynMap{"message": "OK"})
}
// GET /api/tsdb/testdata/random-walk
@@ -111,8 +111,8 @@ func GetTestDataRandomWalk(c *m.ReqContext) Response {
resp, err := tsdb.HandleRequest(context.Background(), dsInfo, request)
if err != nil {
return ApiError(500, "Metric request error", err)
return Error(500, "Metric request error", err)
}
return Json(200, &resp)
return JSON(200, &resp)
}
+28 -28
View File
@@ -15,7 +15,7 @@ func GetOrgCurrent(c *m.ReqContext) Response {
}
// GET /api/orgs/:orgId
func GetOrgById(c *m.ReqContext) Response {
func GetOrgByID(c *m.ReqContext) Response {
return getOrgHelper(c.ParamsInt64(":orgId"))
}
@@ -24,10 +24,10 @@ func GetOrgByName(c *m.ReqContext) Response {
query := m.GetOrgByNameQuery{Name: c.Params(":name")}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrOrgNotFound {
return ApiError(404, "Organization not found", err)
return Error(404, "Organization not found", err)
}
return ApiError(500, "Failed to get organization", err)
return Error(500, "Failed to get organization", err)
}
org := query.Result
result := m.OrgDetailsDTO{
@@ -43,18 +43,18 @@ func GetOrgByName(c *m.ReqContext) Response {
},
}
return Json(200, &result)
return JSON(200, &result)
}
func getOrgHelper(orgId int64) Response {
query := m.GetOrgByIdQuery{Id: orgId}
func getOrgHelper(orgID int64) Response {
query := m.GetOrgByIdQuery{Id: orgID}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrOrgNotFound {
return ApiError(404, "Organization not found", err)
return Error(404, "Organization not found", err)
}
return ApiError(500, "Failed to get organization", err)
return Error(500, "Failed to get organization", err)
}
org := query.Result
@@ -71,26 +71,26 @@ func getOrgHelper(orgId int64) Response {
},
}
return Json(200, &result)
return JSON(200, &result)
}
// POST /api/orgs
func CreateOrg(c *m.ReqContext, cmd m.CreateOrgCommand) Response {
if !c.IsSignedIn || (!setting.AllowUserOrgCreate && !c.IsGrafanaAdmin) {
return ApiError(403, "Access denied", nil)
return Error(403, "Access denied", nil)
}
cmd.UserId = c.UserId
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrOrgNameTaken {
return ApiError(409, "Organization name taken", err)
return Error(409, "Organization name taken", err)
}
return ApiError(500, "Failed to create organization", err)
return Error(500, "Failed to create organization", err)
}
metrics.M_Api_Org_Create.Inc()
return Json(200, &util.DynMap{
return JSON(200, &util.DynMap{
"orgId": cmd.Result.Id,
"message": "Organization created",
})
@@ -106,16 +106,16 @@ func UpdateOrg(c *m.ReqContext, form dtos.UpdateOrgForm) Response {
return updateOrgHelper(form, c.ParamsInt64(":orgId"))
}
func updateOrgHelper(form dtos.UpdateOrgForm, orgId int64) Response {
cmd := m.UpdateOrgCommand{Name: form.Name, OrgId: orgId}
func updateOrgHelper(form dtos.UpdateOrgForm, orgID int64) Response {
cmd := m.UpdateOrgCommand{Name: form.Name, OrgId: orgID}
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrOrgNameTaken {
return ApiError(400, "Organization name taken", err)
return Error(400, "Organization name taken", err)
}
return ApiError(500, "Failed to update organization", err)
return Error(500, "Failed to update organization", err)
}
return ApiSuccess("Organization updated")
return Success("Organization updated")
}
// PUT /api/org/address
@@ -128,9 +128,9 @@ func UpdateOrgAddress(c *m.ReqContext, form dtos.UpdateOrgAddressForm) Response
return updateOrgAddressHelper(form, c.ParamsInt64(":orgId"))
}
func updateOrgAddressHelper(form dtos.UpdateOrgAddressForm, orgId int64) Response {
func updateOrgAddressHelper(form dtos.UpdateOrgAddressForm, orgID int64) Response {
cmd := m.UpdateOrgAddressCommand{
OrgId: orgId,
OrgId: orgID,
Address: m.Address{
Address1: form.Address1,
Address2: form.Address2,
@@ -142,21 +142,21 @@ func updateOrgAddressHelper(form dtos.UpdateOrgAddressForm, orgId int64) Respons
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to update org address", err)
return Error(500, "Failed to update org address", err)
}
return ApiSuccess("Address updated")
return Success("Address updated")
}
// GET /api/orgs/:orgId
func DeleteOrgById(c *m.ReqContext) Response {
func DeleteOrgByID(c *m.ReqContext) Response {
if err := bus.Dispatch(&m.DeleteOrgCommand{Id: c.ParamsInt64(":orgId")}); err != nil {
if err == m.ErrOrgNotFound {
return ApiError(404, "Failed to delete organization. ID not found", nil)
return Error(404, "Failed to delete organization. ID not found", nil)
}
return ApiError(500, "Failed to update organization", err)
return Error(500, "Failed to update organization", err)
}
return ApiSuccess("Organization deleted")
return Success("Organization deleted")
}
func SearchOrgs(c *m.ReqContext) Response {
@@ -168,8 +168,8 @@ func SearchOrgs(c *m.ReqContext) Response {
}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Failed to search orgs", err)
return Error(500, "Failed to search orgs", err)
}
return Json(200, query.Result)
return JSON(200, query.Result)
}
+42 -43
View File
@@ -16,30 +16,30 @@ func GetPendingOrgInvites(c *m.ReqContext) Response {
query := m.GetTempUsersQuery{OrgId: c.OrgId, Status: m.TmpUserInvitePending}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Failed to get invites from db", err)
return Error(500, "Failed to get invites from db", err)
}
for _, invite := range query.Result {
invite.Url = setting.ToAbsUrl("invite/" + invite.Code)
}
return Json(200, query.Result)
return JSON(200, query.Result)
}
func AddOrgInvite(c *m.ReqContext, inviteDto dtos.AddInviteForm) Response {
if !inviteDto.Role.IsValid() {
return ApiError(400, "Invalid role specified", nil)
return Error(400, "Invalid role specified", nil)
}
// first try get existing user
userQuery := m.GetUserByLoginQuery{LoginOrEmail: inviteDto.LoginOrEmail}
if err := bus.Dispatch(&userQuery); err != nil {
if err != m.ErrUserNotFound {
return ApiError(500, "Failed to query db for existing user check", err)
return Error(500, "Failed to query db for existing user check", err)
}
if setting.DisableLoginForm {
return ApiError(401, "User could not be found", nil)
return Error(401, "User could not be found", nil)
}
} else {
return inviteExistingUserToOrg(c, userQuery.Result, &inviteDto)
@@ -56,7 +56,7 @@ func AddOrgInvite(c *m.ReqContext, inviteDto dtos.AddInviteForm) Response {
cmd.RemoteAddr = c.Req.RemoteAddr
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to save invite to database", err)
return Error(500, "Failed to save invite to database", err)
}
// send invite email
@@ -74,18 +74,18 @@ func AddOrgInvite(c *m.ReqContext, inviteDto dtos.AddInviteForm) Response {
}
if err := bus.Dispatch(&emailCmd); err != nil {
return ApiError(500, "Failed to send email invite", err)
return Error(500, "Failed to send email invite", err)
}
emailSentCmd := m.UpdateTempUserWithEmailSentCommand{Code: cmd.Result.Code}
if err := bus.Dispatch(&emailSentCmd); err != nil {
return ApiError(500, "Failed to update invite with email sent info", err)
return Error(500, "Failed to update invite with email sent info", err)
}
return ApiSuccess(fmt.Sprintf("Sent invite to %s", inviteDto.LoginOrEmail))
return Success(fmt.Sprintf("Sent invite to %s", inviteDto.LoginOrEmail))
}
return ApiSuccess(fmt.Sprintf("Created invite for %s", inviteDto.LoginOrEmail))
return Success(fmt.Sprintf("Created invite for %s", inviteDto.LoginOrEmail))
}
func inviteExistingUserToOrg(c *m.ReqContext, user *m.User, inviteDto *dtos.AddInviteForm) Response {
@@ -93,29 +93,28 @@ func inviteExistingUserToOrg(c *m.ReqContext, user *m.User, inviteDto *dtos.AddI
createOrgUserCmd := m.AddOrgUserCommand{OrgId: c.OrgId, UserId: user.Id, Role: inviteDto.Role}
if err := bus.Dispatch(&createOrgUserCmd); err != nil {
if err == m.ErrOrgUserAlreadyAdded {
return ApiError(412, fmt.Sprintf("User %s is already added to organization", inviteDto.LoginOrEmail), err)
return Error(412, fmt.Sprintf("User %s is already added to organization", inviteDto.LoginOrEmail), err)
}
return ApiError(500, "Error while trying to create org user", err)
} else {
if inviteDto.SendEmail && util.IsEmail(user.Email) {
emailCmd := m.SendEmailCommand{
To: []string{user.Email},
Template: "invited_to_org.html",
Data: map[string]interface{}{
"Name": user.NameOrFallback(),
"OrgName": c.OrgName,
"InvitedBy": util.StringsFallback3(c.Name, c.Email, c.Login),
},
}
if err := bus.Dispatch(&emailCmd); err != nil {
return ApiError(500, "Failed to send email invited_to_org", err)
}
}
return ApiSuccess(fmt.Sprintf("Existing Grafana user %s added to org %s", user.NameOrFallback(), c.OrgName))
return Error(500, "Error while trying to create org user", err)
}
if inviteDto.SendEmail && util.IsEmail(user.Email) {
emailCmd := m.SendEmailCommand{
To: []string{user.Email},
Template: "invited_to_org.html",
Data: map[string]interface{}{
"Name": user.NameOrFallback(),
"OrgName": c.OrgName,
"InvitedBy": util.StringsFallback3(c.Name, c.Email, c.Login),
},
}
if err := bus.Dispatch(&emailCmd); err != nil {
return Error(500, "Failed to send email invited_to_org", err)
}
}
return Success(fmt.Sprintf("Existing Grafana user %s added to org %s", user.NameOrFallback(), c.OrgName))
}
func RevokeInvite(c *m.ReqContext) Response {
@@ -123,7 +122,7 @@ func RevokeInvite(c *m.ReqContext) Response {
return rsp
}
return ApiSuccess("Invite revoked")
return Success("Invite revoked")
}
func GetInviteInfoByCode(c *m.ReqContext) Response {
@@ -131,14 +130,14 @@ func GetInviteInfoByCode(c *m.ReqContext) Response {
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrTempUserNotFound {
return ApiError(404, "Invite not found", nil)
return Error(404, "Invite not found", nil)
}
return ApiError(500, "Failed to get invite", err)
return Error(500, "Failed to get invite", err)
}
invite := query.Result
return Json(200, dtos.InviteInfo{
return JSON(200, dtos.InviteInfo{
Email: invite.Email,
Name: invite.Name,
Username: invite.Email,
@@ -151,14 +150,14 @@ func CompleteInvite(c *m.ReqContext, completeInvite dtos.CompleteInviteForm) Res
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrTempUserNotFound {
return ApiError(404, "Invite not found", nil)
return Error(404, "Invite not found", nil)
}
return ApiError(500, "Failed to get invite", err)
return Error(500, "Failed to get invite", err)
}
invite := query.Result
if invite.Status != m.TmpUserInvitePending {
return ApiError(412, fmt.Sprintf("Invite cannot be used in status %s", invite.Status), nil)
return Error(412, fmt.Sprintf("Invite cannot be used in status %s", invite.Status), nil)
}
cmd := m.CreateUserCommand{
@@ -170,7 +169,7 @@ func CompleteInvite(c *m.ReqContext, completeInvite dtos.CompleteInviteForm) Res
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "failed to create user", err)
return Error(500, "failed to create user", err)
}
user := &cmd.Result
@@ -189,14 +188,14 @@ func CompleteInvite(c *m.ReqContext, completeInvite dtos.CompleteInviteForm) Res
metrics.M_Api_User_SignUpCompleted.Inc()
metrics.M_Api_User_SignUpInvite.Inc()
return ApiSuccess("User created and logged in")
return Success("User created and logged in")
}
func updateTempUserStatus(code string, status m.TempUserStatus) (bool, Response) {
// update temp user status
updateTmpUserCmd := m.UpdateTempUserStatusCommand{Code: code, Status: status}
if err := bus.Dispatch(&updateTmpUserCmd); err != nil {
return false, ApiError(500, "Failed to update invite status", err)
return false, Error(500, "Failed to update invite status", err)
}
return true, nil
@@ -207,7 +206,7 @@ func applyUserInvite(user *m.User, invite *m.TempUserDTO, setActive bool) (bool,
addOrgUserCmd := m.AddOrgUserCommand{OrgId: invite.OrgId, UserId: user.Id, Role: invite.Role}
if err := bus.Dispatch(&addOrgUserCmd); err != nil {
if err != m.ErrOrgUserAlreadyAdded {
return false, ApiError(500, "Error while trying to create org user", err)
return false, Error(500, "Error while trying to create org user", err)
}
}
@@ -219,7 +218,7 @@ func applyUserInvite(user *m.User, invite *m.TempUserDTO, setActive bool) (bool,
if setActive {
// set org to active
if err := bus.Dispatch(&m.SetUsingOrgCommand{OrgId: invite.OrgId, UserId: user.Id}); err != nil {
return false, ApiError(500, "Failed to set org as active", err)
return false, Error(500, "Failed to set org as active", err)
}
}
+23 -23
View File
@@ -20,13 +20,13 @@ func AddOrgUser(c *m.ReqContext, cmd m.AddOrgUserCommand) Response {
func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
if !cmd.Role.IsValid() {
return ApiError(400, "Invalid role specified", nil)
return Error(400, "Invalid role specified", nil)
}
userQuery := m.GetUserByLoginQuery{LoginOrEmail: cmd.LoginOrEmail}
err := bus.Dispatch(&userQuery)
if err != nil {
return ApiError(404, "User not found", nil)
return Error(404, "User not found", nil)
}
userToAdd := userQuery.Result
@@ -35,12 +35,12 @@ func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrOrgUserAlreadyAdded {
return ApiError(409, "User is already member of this organization", nil)
return Error(409, "User is already member of this organization", nil)
}
return ApiError(500, "Could not add user to organization", err)
return Error(500, "Could not add user to organization", err)
}
return ApiSuccess("User added to organization")
return Success("User added to organization")
}
// GET /api/org/users
@@ -53,22 +53,22 @@ func GetOrgUsers(c *m.ReqContext) Response {
return getOrgUsersHelper(c.ParamsInt64(":orgId"), "", 0)
}
func getOrgUsersHelper(orgId int64, query string, limit int) Response {
func getOrgUsersHelper(orgID int64, query string, limit int) Response {
q := m.GetOrgUsersQuery{
OrgId: orgId,
OrgId: orgID,
Query: query,
Limit: limit,
}
if err := bus.Dispatch(&q); err != nil {
return ApiError(500, "Failed to get account user", err)
return Error(500, "Failed to get account user", err)
}
for _, user := range q.Result {
user.AvatarUrl = dtos.GetGravatarUrl(user.Email)
}
return Json(200, q.Result)
return JSON(200, q.Result)
}
// PATCH /api/org/users/:userId
@@ -87,41 +87,41 @@ func UpdateOrgUser(c *m.ReqContext, cmd m.UpdateOrgUserCommand) Response {
func updateOrgUserHelper(cmd m.UpdateOrgUserCommand) Response {
if !cmd.Role.IsValid() {
return ApiError(400, "Invalid role specified", nil)
return Error(400, "Invalid role specified", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrLastOrgAdmin {
return ApiError(400, "Cannot change role so that there is no organization admin left", nil)
return Error(400, "Cannot change role so that there is no organization admin left", nil)
}
return ApiError(500, "Failed update org user", err)
return Error(500, "Failed update org user", err)
}
return ApiSuccess("Organization user updated")
return Success("Organization user updated")
}
// DELETE /api/org/users/:userId
func RemoveOrgUserForCurrentOrg(c *m.ReqContext) Response {
userId := c.ParamsInt64(":userId")
return removeOrgUserHelper(c.OrgId, userId)
userID := c.ParamsInt64(":userId")
return removeOrgUserHelper(c.OrgId, userID)
}
// DELETE /api/orgs/:orgId/users/:userId
func RemoveOrgUser(c *m.ReqContext) Response {
userId := c.ParamsInt64(":userId")
orgId := c.ParamsInt64(":orgId")
return removeOrgUserHelper(orgId, userId)
userID := c.ParamsInt64(":userId")
orgID := c.ParamsInt64(":orgId")
return removeOrgUserHelper(orgID, userID)
}
func removeOrgUserHelper(orgId int64, userId int64) Response {
cmd := m.RemoveOrgUserCommand{OrgId: orgId, UserId: userId}
func removeOrgUserHelper(orgID int64, userID int64) Response {
cmd := m.RemoveOrgUserCommand{OrgId: orgID, UserId: userID}
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrLastOrgAdmin {
return ApiError(400, "Cannot remove last organization admin", nil)
return Error(400, "Cannot remove last organization admin", nil)
}
return ApiError(500, "Failed to remove user from organization", err)
return Error(500, "Failed to remove user from organization", err)
}
return ApiSuccess("User removed from organization")
return Success("User removed from organization")
}
+8 -8
View File
@@ -12,15 +12,15 @@ func SendResetPasswordEmail(c *m.ReqContext, form dtos.SendResetPasswordEmailFor
if err := bus.Dispatch(&userQuery); err != nil {
c.Logger.Info("Requested password reset for user that was not found", "user", userQuery.LoginOrEmail)
return ApiError(200, "Email sent", err)
return Error(200, "Email sent", err)
}
emailCmd := m.SendResetPasswordEmailCommand{User: userQuery.Result}
if err := bus.Dispatch(&emailCmd); err != nil {
return ApiError(500, "Failed to send email", err)
return Error(500, "Failed to send email", err)
}
return ApiSuccess("Email sent")
return Success("Email sent")
}
func ResetPassword(c *m.ReqContext, form dtos.ResetUserPasswordForm) Response {
@@ -28,13 +28,13 @@ func ResetPassword(c *m.ReqContext, form dtos.ResetUserPasswordForm) Response {
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrInvalidEmailCode {
return ApiError(400, "Invalid or expired reset password code", nil)
return Error(400, "Invalid or expired reset password code", nil)
}
return ApiError(500, "Unknown error validating email code", err)
return Error(500, "Unknown error validating email code", err)
}
if form.NewPassword != form.ConfirmPassword {
return ApiError(400, "Passwords do not match", nil)
return Error(400, "Passwords do not match", nil)
}
cmd := m.ChangeUserPasswordCommand{}
@@ -42,8 +42,8 @@ func ResetPassword(c *m.ReqContext, form dtos.ResetUserPasswordForm) Response {
cmd.NewPassword = util.EncodePassword(form.NewPassword, query.Result.Salt)
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to change user password", err)
return Error(500, "Failed to change user password", err)
}
return ApiSuccess("User password changed")
return Success("User password changed")
}
+17 -17
View File
@@ -55,10 +55,10 @@ func SearchPlaylists(c *m.ReqContext) Response {
err := bus.Dispatch(&searchQuery)
if err != nil {
return ApiError(500, "Search failed", err)
return Error(500, "Search failed", err)
}
return Json(200, searchQuery.Result)
return JSON(200, searchQuery.Result)
}
func GetPlaylist(c *m.ReqContext) Response {
@@ -66,7 +66,7 @@ func GetPlaylist(c *m.ReqContext) Response {
cmd := m.GetPlaylistByIdQuery{Id: id}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Playlist not found", err)
return Error(500, "Playlist not found", err)
}
playlistDTOs, _ := LoadPlaylistItemDTOs(id)
@@ -79,7 +79,7 @@ func GetPlaylist(c *m.ReqContext) Response {
Items: playlistDTOs,
}
return Json(200, dto)
return JSON(200, dto)
}
func LoadPlaylistItemDTOs(id int64) ([]m.PlaylistItemDTO, error) {
@@ -120,21 +120,21 @@ func GetPlaylistItems(c *m.ReqContext) Response {
playlistDTOs, err := LoadPlaylistItemDTOs(id)
if err != nil {
return ApiError(500, "Could not load playlist items", err)
return Error(500, "Could not load playlist items", err)
}
return Json(200, playlistDTOs)
return JSON(200, playlistDTOs)
}
func GetPlaylistDashboards(c *m.ReqContext) Response {
playlistId := c.ParamsInt64(":id")
playlistID := c.ParamsInt64(":id")
playlists, err := LoadPlaylistDashboards(c.OrgId, c.SignedInUser, playlistId)
playlists, err := LoadPlaylistDashboards(c.OrgId, c.SignedInUser, playlistID)
if err != nil {
return ApiError(500, "Could not load dashboards", err)
return Error(500, "Could not load dashboards", err)
}
return Json(200, playlists)
return JSON(200, playlists)
}
func DeletePlaylist(c *m.ReqContext) Response {
@@ -142,34 +142,34 @@ func DeletePlaylist(c *m.ReqContext) Response {
cmd := m.DeletePlaylistCommand{Id: id, OrgId: c.OrgId}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to delete playlist", err)
return Error(500, "Failed to delete playlist", err)
}
return Json(200, "")
return JSON(200, "")
}
func CreatePlaylist(c *m.ReqContext, cmd m.CreatePlaylistCommand) Response {
cmd.OrgId = c.OrgId
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to create playlist", err)
return Error(500, "Failed to create playlist", err)
}
return Json(200, cmd.Result)
return JSON(200, cmd.Result)
}
func UpdatePlaylist(c *m.ReqContext, cmd m.UpdatePlaylistCommand) Response {
cmd.OrgId = c.OrgId
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to save playlist", err)
return Error(500, "Failed to save playlist", err)
}
playlistDTOs, err := LoadPlaylistItemDTOs(cmd.Id)
if err != nil {
return ApiError(500, "Failed to save playlist", err)
return Error(500, "Failed to save playlist", err)
}
cmd.Result.Items = playlistDTOs
return Json(200, cmd.Result)
return JSON(200, cmd.Result)
}
+31 -33
View File
@@ -11,11 +11,11 @@ import (
"github.com/grafana/grafana/pkg/services/search"
)
func populateDashboardsById(dashboardByIds []int64, dashboardIdOrder map[int64]int) (dtos.PlaylistDashboardsSlice, error) {
func populateDashboardsByID(dashboardByIDs []int64, dashboardIDOrder map[int64]int) (dtos.PlaylistDashboardsSlice, error) {
result := make(dtos.PlaylistDashboardsSlice, 0)
if len(dashboardByIds) > 0 {
dashboardQuery := m.GetDashboardsQuery{DashboardIds: dashboardByIds}
if len(dashboardByIDs) > 0 {
dashboardQuery := m.GetDashboardsQuery{DashboardIds: dashboardByIDs}
if err := bus.Dispatch(&dashboardQuery); err != nil {
return result, err
}
@@ -26,7 +26,7 @@ func populateDashboardsById(dashboardByIds []int64, dashboardIdOrder map[int64]i
Slug: item.Slug,
Title: item.Title,
Uri: "db/" + item.Slug,
Order: dashboardIdOrder[item.Id],
Order: dashboardIDOrder[item.Id],
})
}
}
@@ -34,29 +34,27 @@ func populateDashboardsById(dashboardByIds []int64, dashboardIdOrder map[int64]i
return result, nil
}
func populateDashboardsByTag(orgId int64, signedInUser *m.SignedInUser, dashboardByTag []string, dashboardTagOrder map[string]int) dtos.PlaylistDashboardsSlice {
func populateDashboardsByTag(orgID int64, signedInUser *m.SignedInUser, dashboardByTag []string, dashboardTagOrder map[string]int) dtos.PlaylistDashboardsSlice {
result := make(dtos.PlaylistDashboardsSlice, 0)
if len(dashboardByTag) > 0 {
for _, tag := range dashboardByTag {
searchQuery := search.Query{
Title: "",
Tags: []string{tag},
SignedInUser: signedInUser,
Limit: 100,
IsStarred: false,
OrgId: orgId,
}
for _, tag := range dashboardByTag {
searchQuery := search.Query{
Title: "",
Tags: []string{tag},
SignedInUser: signedInUser,
Limit: 100,
IsStarred: false,
OrgId: orgID,
}
if err := bus.Dispatch(&searchQuery); err == nil {
for _, item := range searchQuery.Result {
result = append(result, dtos.PlaylistDashboard{
Id: item.Id,
Title: item.Title,
Uri: item.Uri,
Order: dashboardTagOrder[tag],
})
}
if err := bus.Dispatch(&searchQuery); err == nil {
for _, item := range searchQuery.Result {
result = append(result, dtos.PlaylistDashboard{
Id: item.Id,
Title: item.Title,
Uri: item.Uri,
Order: dashboardTagOrder[tag],
})
}
}
}
@@ -64,19 +62,19 @@ func populateDashboardsByTag(orgId int64, signedInUser *m.SignedInUser, dashboar
return result
}
func LoadPlaylistDashboards(orgId int64, signedInUser *m.SignedInUser, playlistId int64) (dtos.PlaylistDashboardsSlice, error) {
playlistItems, _ := LoadPlaylistItems(playlistId)
func LoadPlaylistDashboards(orgID int64, signedInUser *m.SignedInUser, playlistID int64) (dtos.PlaylistDashboardsSlice, error) {
playlistItems, _ := LoadPlaylistItems(playlistID)
dashboardByIds := make([]int64, 0)
dashboardByIDs := make([]int64, 0)
dashboardByTag := make([]string, 0)
dashboardIdOrder := make(map[int64]int)
dashboardIDOrder := make(map[int64]int)
dashboardTagOrder := make(map[string]int)
for _, i := range playlistItems {
if i.Type == "dashboard_by_id" {
dashboardId, _ := strconv.ParseInt(i.Value, 10, 64)
dashboardByIds = append(dashboardByIds, dashboardId)
dashboardIdOrder[dashboardId] = i.Order
dashboardID, _ := strconv.ParseInt(i.Value, 10, 64)
dashboardByIDs = append(dashboardByIDs, dashboardID)
dashboardIDOrder[dashboardID] = i.Order
}
if i.Type == "dashboard_by_tag" {
@@ -87,9 +85,9 @@ func LoadPlaylistDashboards(orgId int64, signedInUser *m.SignedInUser, playlistI
result := make(dtos.PlaylistDashboardsSlice, 0)
var k, _ = populateDashboardsById(dashboardByIds, dashboardIdOrder)
var k, _ = populateDashboardsByID(dashboardByIDs, dashboardIDOrder)
result = append(result, k...)
result = append(result, populateDashboardsByTag(orgId, signedInUser, dashboardByTag, dashboardTagOrder)...)
result = append(result, populateDashboardsByTag(orgID, signedInUser, dashboardByTag, dashboardTagOrder)...)
sort.Sort(result)
return result, nil
+25 -14
View File
@@ -25,8 +25,8 @@ import (
)
var (
logger log.Logger = log.New("data-proxy-log")
client *http.Client = &http.Client{
logger = log.New("data-proxy-log")
client = &http.Client{
Timeout: time.Second * 30,
Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
}
@@ -49,14 +49,14 @@ type DataSourceProxy struct {
}
func NewDataSourceProxy(ds *m.DataSource, plugin *plugins.DataSourcePlugin, ctx *m.ReqContext, proxyPath string) *DataSourceProxy {
targetUrl, _ := url.Parse(ds.Url)
targetURL, _ := url.Parse(ds.Url)
return &DataSourceProxy{
ds: ds,
plugin: plugin,
ctx: ctx,
proxyPath: proxyPath,
targetUrl: targetUrl,
targetUrl: targetURL,
}
}
@@ -89,6 +89,9 @@ func (proxy *DataSourceProxy) HandleRequest() {
span.SetTag("user_id", proxy.ctx.SignedInUser.UserId)
span.SetTag("org_id", proxy.ctx.SignedInUser.OrgId)
proxy.addTraceFromHeaderValue(span, "X-Panel-Id", "panel_id")
proxy.addTraceFromHeaderValue(span, "X-Dashboard-Id", "dashboard_id")
opentracing.GlobalTracer().Inject(
span.Context(),
opentracing.HTTPHeaders,
@@ -98,6 +101,14 @@ func (proxy *DataSourceProxy) HandleRequest() {
proxy.ctx.Resp.Header().Del("Set-Cookie")
}
func (proxy *DataSourceProxy) addTraceFromHeaderValue(span opentracing.Span, headerName string, tagName string) {
panelId := proxy.ctx.Req.Header.Get(headerName)
dashId, err := strconv.Atoi(panelId)
if err == nil {
span.SetTag(tagName, dashId)
}
}
func (proxy *DataSourceProxy) getDirector() func(req *http.Request) {
return func(req *http.Request) {
req.URL.Scheme = proxy.targetUrl.Scheme
@@ -279,16 +290,16 @@ func (proxy *DataSourceProxy) applyRoute(req *http.Request) {
SecureJsonData: proxy.ds.SecureJsonData.Decrypt(),
}
routeUrl, err := url.Parse(proxy.route.Url)
routeURL, err := url.Parse(proxy.route.Url)
if err != nil {
logger.Error("Error parsing plugin route url")
return
}
req.URL.Scheme = routeUrl.Scheme
req.URL.Host = routeUrl.Host
req.Host = routeUrl.Host
req.URL.Path = util.JoinUrlFragments(routeUrl.Path, proxy.proxyPath)
req.URL.Scheme = routeURL.Scheme
req.URL.Host = routeURL.Host
req.Host = routeURL.Host
req.URL.Path = util.JoinUrlFragments(routeURL.Path, proxy.proxyPath)
if err := addHeaders(&req.Header, proxy.route, data); err != nil {
logger.Error("Failed to render plugin headers", "error", err)
@@ -320,11 +331,11 @@ func (proxy *DataSourceProxy) getAccessToken(data templateData) (string, error)
params := make(url.Values)
for key, value := range proxy.route.TokenAuth.Params {
if interpolatedParam, err := interpolateString(value, data); err != nil {
interpolatedParam, err := interpolateString(value, data)
if err != nil {
return "", err
} else {
params.Add(key, interpolatedParam)
}
params.Add(key, interpolatedParam)
}
getTokenReq, _ := http.NewRequest("POST", urlInterpolated, bytes.NewBufferString(params.Encode()))
@@ -354,13 +365,13 @@ func (proxy *DataSourceProxy) getAccessToken(data templateData) (string, error)
func interpolateString(text string, data templateData) (string, error) {
t, err := template.New("content").Parse(text)
if err != nil {
return "", errors.New(fmt.Sprintf("Could not parse template %s.", text))
return "", fmt.Errorf("could not parse template %s", text)
}
var contentBuf bytes.Buffer
err = t.Execute(&contentBuf, data)
if err != nil {
return "", errors.New(fmt.Sprintf("Failed to execute template %s.", text))
return "", fmt.Errorf("failed to execute template %s", text)
}
return contentBuf.String(), nil
+8 -8
View File
@@ -107,8 +107,8 @@ func TestDSRouteRule(t *testing.T) {
proxy := NewDataSourceProxy(ds, plugin, ctx, "/render")
requestUrl, _ := url.Parse("http://grafana.com/sub")
req := http.Request{URL: requestUrl}
requestURL, _ := url.Parse("http://grafana.com/sub")
req := http.Request{URL: requestURL}
proxy.getDirector()(&req)
@@ -132,8 +132,8 @@ func TestDSRouteRule(t *testing.T) {
ctx := &m.ReqContext{}
proxy := NewDataSourceProxy(ds, plugin, ctx, "")
requestUrl, _ := url.Parse("http://grafana.com/sub")
req := http.Request{URL: requestUrl}
requestURL, _ := url.Parse("http://grafana.com/sub")
req := http.Request{URL: requestURL}
proxy.getDirector()(&req)
@@ -162,8 +162,8 @@ func TestDSRouteRule(t *testing.T) {
ctx := &m.ReqContext{}
proxy := NewDataSourceProxy(ds, plugin, ctx, "")
requestUrl, _ := url.Parse("http://grafana.com/sub")
req := http.Request{URL: requestUrl, Header: make(http.Header)}
requestURL, _ := url.Parse("http://grafana.com/sub")
req := http.Request{URL: requestURL, Header: make(http.Header)}
cookies := "grafana_user=admin; grafana_remember=99; grafana_sess=11; JSESSION_ID=test"
req.Header.Set("Cookie", cookies)
@@ -188,8 +188,8 @@ func TestDSRouteRule(t *testing.T) {
ctx := &m.ReqContext{}
proxy := NewDataSourceProxy(ds, plugin, ctx, "")
requestUrl, _ := url.Parse("http://grafana.com/sub")
req := http.Request{URL: requestUrl, Header: make(http.Header)}
requestURL, _ := url.Parse("http://grafana.com/sub")
req := http.Request{URL: requestURL, Header: make(http.Header)}
cookies := "grafana_user=admin; grafana_remember=99; grafana_sess=11; JSESSION_ID=test"
req.Header.Set("Cookie", cookies)
+9 -9
View File
@@ -19,10 +19,10 @@ type templateData struct {
SecureJsonData map[string]string
}
func getHeaders(route *plugins.AppPluginRoute, orgId int64, appId string) (http.Header, error) {
func getHeaders(route *plugins.AppPluginRoute, orgId int64, appID string) (http.Header, error) {
result := http.Header{}
query := m.GetPluginSettingByIdQuery{OrgId: orgId, PluginId: appId}
query := m.GetPluginSettingByIdQuery{OrgId: orgId, PluginId: appID}
if err := bus.Dispatch(&query); err != nil {
return nil, err
@@ -37,16 +37,16 @@ func getHeaders(route *plugins.AppPluginRoute, orgId int64, appId string) (http.
return result, err
}
func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPluginRoute, appId string) *httputil.ReverseProxy {
targetUrl, _ := url.Parse(route.Url)
func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPluginRoute, appID string) *httputil.ReverseProxy {
targetURL, _ := url.Parse(route.Url)
director := func(req *http.Request) {
req.URL.Scheme = targetUrl.Scheme
req.URL.Host = targetUrl.Host
req.Host = targetUrl.Host
req.URL.Scheme = targetURL.Scheme
req.URL.Host = targetURL.Host
req.Host = targetURL.Host
req.URL.Path = util.JoinUrlFragments(targetUrl.Path, proxyPath)
req.URL.Path = util.JoinUrlFragments(targetURL.Path, proxyPath)
// clear cookie headers
req.Header.Del("Cookie")
@@ -80,7 +80,7 @@ func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPl
req.Header.Add("X-Grafana-Context", string(ctxJson))
if len(route.Headers) > 0 {
headers, err := getHeaders(route, ctx.OrgId, appId)
headers, err := getHeaders(route, ctx.OrgId, appID)
if err != nil {
ctx.JsonApiErr(500, "Could not generate plugin route header", err)
return
+58 -56
View File
@@ -19,7 +19,7 @@ func GetPluginList(c *m.ReqContext) Response {
pluginSettingsMap, err := plugins.GetPluginSettings(c.OrgId)
if err != nil {
return ApiError(500, "Failed to get list of plugins", err)
return Error(500, "Failed to get list of plugins", err)
}
result := make(dtos.PluginList, 0)
@@ -75,92 +75,94 @@ func GetPluginList(c *m.ReqContext) Response {
}
sort.Sort(result)
return Json(200, result)
return JSON(200, result)
}
func GetPluginSettingById(c *m.ReqContext) Response {
pluginId := c.Params(":pluginId")
func GetPluginSettingByID(c *m.ReqContext) Response {
pluginID := c.Params(":pluginId")
if def, exists := plugins.Plugins[pluginId]; !exists {
return ApiError(404, "Plugin not found, no installed plugin with that id", nil)
} else {
dto := &dtos.PluginSetting{
Type: def.Type,
Id: def.Id,
Name: def.Name,
Info: &def.Info,
Dependencies: &def.Dependencies,
Includes: def.Includes,
BaseUrl: def.BaseUrl,
Module: def.Module,
DefaultNavUrl: def.DefaultNavUrl,
LatestVersion: def.GrafanaNetVersion,
HasUpdate: def.GrafanaNetHasUpdate,
State: def.State,
}
query := m.GetPluginSettingByIdQuery{PluginId: pluginId, OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
if err != m.ErrPluginSettingNotFound {
return ApiError(500, "Failed to get login settings", nil)
}
} else {
dto.Enabled = query.Result.Enabled
dto.Pinned = query.Result.Pinned
dto.JsonData = query.Result.JsonData
}
return Json(200, dto)
def, exists := plugins.Plugins[pluginID]
if !exists {
return Error(404, "Plugin not found, no installed plugin with that id", nil)
}
dto := &dtos.PluginSetting{
Type: def.Type,
Id: def.Id,
Name: def.Name,
Info: &def.Info,
Dependencies: &def.Dependencies,
Includes: def.Includes,
BaseUrl: def.BaseUrl,
Module: def.Module,
DefaultNavUrl: def.DefaultNavUrl,
LatestVersion: def.GrafanaNetVersion,
HasUpdate: def.GrafanaNetHasUpdate,
State: def.State,
}
query := m.GetPluginSettingByIdQuery{PluginId: pluginID, OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
if err != m.ErrPluginSettingNotFound {
return Error(500, "Failed to get login settings", nil)
}
} else {
dto.Enabled = query.Result.Enabled
dto.Pinned = query.Result.Pinned
dto.JsonData = query.Result.JsonData
}
return JSON(200, dto)
}
func UpdatePluginSetting(c *m.ReqContext, cmd m.UpdatePluginSettingCmd) Response {
pluginId := c.Params(":pluginId")
pluginID := c.Params(":pluginId")
cmd.OrgId = c.OrgId
cmd.PluginId = pluginId
cmd.PluginId = pluginID
if _, ok := plugins.Apps[cmd.PluginId]; !ok {
return ApiError(404, "Plugin not installed.", nil)
return Error(404, "Plugin not installed.", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to update plugin setting", err)
return Error(500, "Failed to update plugin setting", err)
}
return ApiSuccess("Plugin settings updated")
return Success("Plugin settings updated")
}
func GetPluginDashboards(c *m.ReqContext) Response {
pluginId := c.Params(":pluginId")
pluginID := c.Params(":pluginId")
if list, err := plugins.GetPluginDashboards(c.OrgId, pluginId); err != nil {
list, err := plugins.GetPluginDashboards(c.OrgId, pluginID)
if err != nil {
if notfound, ok := err.(plugins.PluginNotFoundError); ok {
return ApiError(404, notfound.Error(), nil)
return Error(404, notfound.Error(), nil)
}
return ApiError(500, "Failed to get plugin dashboards", err)
} else {
return Json(200, list)
return Error(500, "Failed to get plugin dashboards", err)
}
return JSON(200, list)
}
func GetPluginMarkdown(c *m.ReqContext) Response {
pluginId := c.Params(":pluginId")
pluginID := c.Params(":pluginId")
name := c.Params(":name")
if content, err := plugins.GetPluginMarkdown(pluginId, name); err != nil {
content, err := plugins.GetPluginMarkdown(pluginID, name)
if err != nil {
if notfound, ok := err.(plugins.PluginNotFoundError); ok {
return ApiError(404, notfound.Error(), nil)
return Error(404, notfound.Error(), nil)
}
return ApiError(500, "Could not get markdown file", err)
} else {
resp := Respond(200, content)
resp.Header("Content-Type", "text/plain; charset=utf-8")
return resp
return Error(500, "Could not get markdown file", err)
}
resp := Respond(200, content)
resp.Header("Content-Type", "text/plain; charset=utf-8")
return resp
}
func ImportDashboard(c *m.ReqContext, apiCmd dtos.ImportDashboardCommand) Response {
@@ -176,8 +178,8 @@ func ImportDashboard(c *m.ReqContext, apiCmd dtos.ImportDashboardCommand) Respon
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to import dashboard", err)
return Error(500, "Failed to import dashboard", err)
}
return Json(200, cmd.Result)
return JSON(200, cmd.Result)
}
+13 -13
View File
@@ -13,10 +13,10 @@ func SetHomeDashboard(c *m.ReqContext, cmd m.SavePreferencesCommand) Response {
cmd.OrgId = c.OrgId
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to set home dashboard", err)
return Error(500, "Failed to set home dashboard", err)
}
return ApiSuccess("Home dashboard set")
return Success("Home dashboard set")
}
// GET /api/user/preferences
@@ -24,20 +24,20 @@ func GetUserPreferences(c *m.ReqContext) Response {
return getPreferencesFor(c.OrgId, c.UserId)
}
func getPreferencesFor(orgId int64, userId int64) Response {
prefsQuery := m.GetPreferencesQuery{UserId: userId, OrgId: orgId}
func getPreferencesFor(orgID int64, userID int64) Response {
prefsQuery := m.GetPreferencesQuery{UserId: userID, OrgId: orgID}
if err := bus.Dispatch(&prefsQuery); err != nil {
return ApiError(500, "Failed to get preferences", err)
return Error(500, "Failed to get preferences", err)
}
dto := dtos.Prefs{
Theme: prefsQuery.Result.Theme,
HomeDashboardId: prefsQuery.Result.HomeDashboardId,
HomeDashboardID: prefsQuery.Result.HomeDashboardId,
Timezone: prefsQuery.Result.Timezone,
}
return Json(200, &dto)
return JSON(200, &dto)
}
// PUT /api/user/preferences
@@ -45,20 +45,20 @@ func UpdateUserPreferences(c *m.ReqContext, dtoCmd dtos.UpdatePrefsCmd) Response
return updatePreferencesFor(c.OrgId, c.UserId, &dtoCmd)
}
func updatePreferencesFor(orgId int64, userId int64, dtoCmd *dtos.UpdatePrefsCmd) Response {
func updatePreferencesFor(orgID int64, userID int64, dtoCmd *dtos.UpdatePrefsCmd) Response {
saveCmd := m.SavePreferencesCommand{
UserId: userId,
OrgId: orgId,
UserId: userID,
OrgId: orgID,
Theme: dtoCmd.Theme,
Timezone: dtoCmd.Timezone,
HomeDashboardId: dtoCmd.HomeDashboardId,
HomeDashboardId: dtoCmd.HomeDashboardID,
}
if err := bus.Dispatch(&saveCmd); err != nil {
return ApiError(500, "Failed to save preferences", err)
return Error(500, "Failed to save preferences", err)
}
return ApiSuccess("Preferences updated")
return Success("Preferences updated")
}
// GET /api/org/preferences
+14 -14
View File
@@ -8,60 +8,60 @@ import (
func GetOrgQuotas(c *m.ReqContext) Response {
if !setting.Quota.Enabled {
return ApiError(404, "Quotas not enabled", nil)
return Error(404, "Quotas not enabled", nil)
}
query := m.GetOrgQuotasQuery{OrgId: c.ParamsInt64(":orgId")}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Failed to get org quotas", err)
return Error(500, "Failed to get org quotas", err)
}
return Json(200, query.Result)
return JSON(200, query.Result)
}
func UpdateOrgQuota(c *m.ReqContext, cmd m.UpdateOrgQuotaCmd) Response {
if !setting.Quota.Enabled {
return ApiError(404, "Quotas not enabled", nil)
return Error(404, "Quotas not enabled", nil)
}
cmd.OrgId = c.ParamsInt64(":orgId")
cmd.Target = c.Params(":target")
if _, ok := setting.Quota.Org.ToMap()[cmd.Target]; !ok {
return ApiError(404, "Invalid quota target", nil)
return Error(404, "Invalid quota target", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to update org quotas", err)
return Error(500, "Failed to update org quotas", err)
}
return ApiSuccess("Organization quota updated")
return Success("Organization quota updated")
}
func GetUserQuotas(c *m.ReqContext) Response {
if !setting.Quota.Enabled {
return ApiError(404, "Quotas not enabled", nil)
return Error(404, "Quotas not enabled", nil)
}
query := m.GetUserQuotasQuery{UserId: c.ParamsInt64(":id")}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Failed to get org quotas", err)
return Error(500, "Failed to get org quotas", err)
}
return Json(200, query.Result)
return JSON(200, query.Result)
}
func UpdateUserQuota(c *m.ReqContext, cmd m.UpdateUserQuotaCmd) Response {
if !setting.Quota.Enabled {
return ApiError(404, "Quotas not enabled", nil)
return Error(404, "Quotas not enabled", nil)
}
cmd.UserId = c.ParamsInt64(":id")
cmd.Target = c.Params(":target")
if _, ok := setting.Quota.User.ToMap()[cmd.Target]; !ok {
return ApiError(404, "Invalid quota target", nil)
return Error(404, "Invalid quota target", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to update org quotas", err)
return Error(500, "Failed to update org quotas", err)
}
return ApiSuccess("Organization quota updated")
return Success("Organization quota updated")
}
+8 -8
View File
@@ -25,19 +25,19 @@ func Search(c *m.ReqContext) {
permission = m.PERMISSION_EDIT
}
dbids := make([]int64, 0)
dbIDs := make([]int64, 0)
for _, id := range c.QueryStrings("dashboardIds") {
dashboardId, err := strconv.ParseInt(id, 10, 64)
dashboardID, err := strconv.ParseInt(id, 10, 64)
if err == nil {
dbids = append(dbids, dashboardId)
dbIDs = append(dbIDs, dashboardID)
}
}
folderIds := make([]int64, 0)
folderIDs := make([]int64, 0)
for _, id := range c.QueryStrings("folderIds") {
folderId, err := strconv.ParseInt(id, 10, 64)
folderID, err := strconv.ParseInt(id, 10, 64)
if err == nil {
folderIds = append(folderIds, folderId)
folderIDs = append(folderIDs, folderID)
}
}
@@ -48,9 +48,9 @@ func Search(c *m.ReqContext) {
Limit: limit,
IsStarred: starred == "true",
OrgId: c.OrgId,
DashboardIds: dbids,
DashboardIds: dbIDs,
Type: dashboardType,
FolderIds: folderIds,
FolderIds: folderIDs,
Permission: permission,
}
+13 -13
View File
@@ -12,7 +12,7 @@ import (
// GET /api/user/signup/options
func GetSignUpOptions(c *m.ReqContext) Response {
return Json(200, util.DynMap{
return JSON(200, util.DynMap{
"verifyEmailEnabled": setting.VerifyEmailEnabled,
"autoAssignOrg": setting.AutoAssignOrg,
})
@@ -21,12 +21,12 @@ func GetSignUpOptions(c *m.ReqContext) Response {
// POST /api/user/signup
func SignUp(c *m.ReqContext, form dtos.SignUpForm) Response {
if !setting.AllowUserSignUp {
return ApiError(401, "User signup is disabled", nil)
return Error(401, "User signup is disabled", nil)
}
existing := m.GetUserByLoginQuery{LoginOrEmail: form.Email}
if err := bus.Dispatch(&existing); err == nil {
return ApiError(422, "User with same email address already exists", nil)
return Error(422, "User with same email address already exists", nil)
}
cmd := m.CreateTempUserCommand{}
@@ -38,7 +38,7 @@ func SignUp(c *m.ReqContext, form dtos.SignUpForm) Response {
cmd.RemoteAddr = c.Req.RemoteAddr
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to create signup", err)
return Error(500, "Failed to create signup", err)
}
bus.Publish(&events.SignUpStarted{
@@ -48,12 +48,12 @@ func SignUp(c *m.ReqContext, form dtos.SignUpForm) Response {
metrics.M_Api_User_SignUpStarted.Inc()
return Json(200, util.DynMap{"status": "SignUpCreated"})
return JSON(200, util.DynMap{"status": "SignUpCreated"})
}
func SignUpStep2(c *m.ReqContext, form dtos.SignUpStep2Form) Response {
if !setting.AllowUserSignUp {
return ApiError(401, "User signup is disabled", nil)
return Error(401, "User signup is disabled", nil)
}
createUserCmd := m.CreateUserCommand{
@@ -75,12 +75,12 @@ func SignUpStep2(c *m.ReqContext, form dtos.SignUpStep2Form) Response {
// check if user exists
existing := m.GetUserByLoginQuery{LoginOrEmail: form.Email}
if err := bus.Dispatch(&existing); err == nil {
return ApiError(401, "User with same email address already exists", nil)
return Error(401, "User with same email address already exists", nil)
}
// dispatch create command
if err := bus.Dispatch(&createUserCmd); err != nil {
return ApiError(500, "Failed to create user", err)
return Error(500, "Failed to create user", err)
}
// publish signup event
@@ -98,7 +98,7 @@ func SignUpStep2(c *m.ReqContext, form dtos.SignUpStep2Form) Response {
// check for pending invites
invitesQuery := m.GetTempUsersQuery{Email: form.Email, Status: m.TmpUserInvitePending}
if err := bus.Dispatch(&invitesQuery); err != nil {
return ApiError(500, "Failed to query database for invites", err)
return Error(500, "Failed to query database for invites", err)
}
apiResponse := util.DynMap{"message": "User sign up completed successfully", "code": "redirect-to-landing-page"}
@@ -112,7 +112,7 @@ func SignUpStep2(c *m.ReqContext, form dtos.SignUpStep2Form) Response {
loginUserWithUser(user, c)
metrics.M_Api_User_SignUpCompleted.Inc()
return Json(200, apiResponse)
return JSON(200, apiResponse)
}
func verifyUserSignUpEmail(email string, code string) (bool, Response) {
@@ -120,14 +120,14 @@ func verifyUserSignUpEmail(email string, code string) (bool, Response) {
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrTempUserNotFound {
return false, ApiError(404, "Invalid email verification code", nil)
return false, Error(404, "Invalid email verification code", nil)
}
return false, ApiError(500, "Failed to read temp user", err)
return false, Error(500, "Failed to read temp user", err)
}
tempUser := query.Result
if tempUser.Email != email {
return false, ApiError(404, "Email verification code does not match email", nil)
return false, Error(404, "Email verification code does not match email", nil)
}
return true, nil
+7 -7
View File
@@ -7,20 +7,20 @@ import (
func StarDashboard(c *m.ReqContext) Response {
if !c.IsSignedIn {
return ApiError(412, "You need to sign in to star dashboards", nil)
return Error(412, "You need to sign in to star dashboards", nil)
}
cmd := m.StarDashboardCommand{UserId: c.UserId, DashboardId: c.ParamsInt64(":id")}
if cmd.DashboardId <= 0 {
return ApiError(400, "Missing dashboard id", nil)
return Error(400, "Missing dashboard id", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to star dashboard", err)
return Error(500, "Failed to star dashboard", err)
}
return ApiSuccess("Dashboard starred!")
return Success("Dashboard starred!")
}
func UnstarDashboard(c *m.ReqContext) Response {
@@ -28,12 +28,12 @@ func UnstarDashboard(c *m.ReqContext) Response {
cmd := m.UnstarDashboardCommand{UserId: c.UserId, DashboardId: c.ParamsInt64(":id")}
if cmd.DashboardId <= 0 {
return ApiError(400, "Missing dashboard id", nil)
return Error(400, "Missing dashboard id", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to unstar dashboard", err)
return Error(500, "Failed to unstar dashboard", err)
}
return ApiSuccess("Dashboard unstarred")
return Success("Dashboard unstarred")
}
+16 -16
View File
@@ -12,12 +12,12 @@ func CreateTeam(c *m.ReqContext, cmd m.CreateTeamCommand) Response {
cmd.OrgId = c.OrgId
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrTeamNameTaken {
return ApiError(409, "Team name taken", err)
return Error(409, "Team name taken", err)
}
return ApiError(500, "Failed to create Team", err)
return Error(500, "Failed to create Team", err)
}
return Json(200, &util.DynMap{
return JSON(200, &util.DynMap{
"teamId": cmd.Result.Id,
"message": "Team created",
})
@@ -29,23 +29,23 @@ func UpdateTeam(c *m.ReqContext, cmd m.UpdateTeamCommand) Response {
cmd.Id = c.ParamsInt64(":teamId")
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrTeamNameTaken {
return ApiError(400, "Team name taken", err)
return Error(400, "Team name taken", err)
}
return ApiError(500, "Failed to update Team", err)
return Error(500, "Failed to update Team", err)
}
return ApiSuccess("Team updated")
return Success("Team updated")
}
// DELETE /api/teams/:teamId
func DeleteTeamById(c *m.ReqContext) Response {
func DeleteTeamByID(c *m.ReqContext) Response {
if err := bus.Dispatch(&m.DeleteTeamCommand{OrgId: c.OrgId, Id: c.ParamsInt64(":teamId")}); err != nil {
if err == m.ErrTeamNotFound {
return ApiError(404, "Failed to delete Team. ID not found", nil)
return Error(404, "Failed to delete Team. ID not found", nil)
}
return ApiError(500, "Failed to update Team", err)
return Error(500, "Failed to update Team", err)
}
return ApiSuccess("Team deleted")
return Success("Team deleted")
}
// GET /api/teams/search
@@ -68,7 +68,7 @@ func SearchTeams(c *m.ReqContext) Response {
}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Failed to search Teams", err)
return Error(500, "Failed to search Teams", err)
}
for _, team := range query.Result.Teams {
@@ -78,20 +78,20 @@ func SearchTeams(c *m.ReqContext) Response {
query.Result.Page = page
query.Result.PerPage = perPage
return Json(200, query.Result)
return JSON(200, query.Result)
}
// GET /api/teams/:teamId
func GetTeamById(c *m.ReqContext) Response {
func GetTeamByID(c *m.ReqContext) Response {
query := m.GetTeamByIdQuery{OrgId: c.OrgId, Id: c.ParamsInt64(":teamId")}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrTeamNotFound {
return ApiError(404, "Team not found", err)
return Error(404, "Team not found", err)
}
return ApiError(500, "Failed to get Team", err)
return Error(500, "Failed to get Team", err)
}
return Json(200, &query.Result)
return JSON(200, &query.Result)
}
+10 -10
View File
@@ -12,14 +12,14 @@ func GetTeamMembers(c *m.ReqContext) Response {
query := m.GetTeamMembersQuery{OrgId: c.OrgId, TeamId: c.ParamsInt64(":teamId")}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Failed to get Team Members", err)
return Error(500, "Failed to get Team Members", err)
}
for _, member := range query.Result {
member.AvatarUrl = dtos.GetGravatarUrl(member.Email)
}
return Json(200, query.Result)
return JSON(200, query.Result)
}
// POST /api/teams/:teamId/members
@@ -29,17 +29,17 @@ func AddTeamMember(c *m.ReqContext, cmd m.AddTeamMemberCommand) Response {
if err := bus.Dispatch(&cmd); err != nil {
if err == m.ErrTeamNotFound {
return ApiError(404, "Team not found", nil)
return Error(404, "Team not found", nil)
}
if err == m.ErrTeamMemberAlreadyAdded {
return ApiError(400, "User is already added to this team", nil)
return Error(400, "User is already added to this team", nil)
}
return ApiError(500, "Failed to add Member to Team", err)
return Error(500, "Failed to add Member to Team", err)
}
return Json(200, &util.DynMap{
return JSON(200, &util.DynMap{
"message": "Member added to Team",
})
}
@@ -48,14 +48,14 @@ func AddTeamMember(c *m.ReqContext, cmd m.AddTeamMemberCommand) Response {
func RemoveTeamMember(c *m.ReqContext) Response {
if err := bus.Dispatch(&m.RemoveTeamMemberCommand{OrgId: c.OrgId, TeamId: c.ParamsInt64(":teamId"), UserId: c.ParamsInt64(":userId")}); err != nil {
if err == m.ErrTeamNotFound {
return ApiError(404, "Team not found", nil)
return Error(404, "Team not found", nil)
}
if err == m.ErrTeamMemberNotFound {
return ApiError(404, "Team member not found", nil)
return Error(404, "Team member not found", nil)
}
return ApiError(500, "Failed to remove Member from Team", err)
return Error(500, "Failed to remove Member from Team", err)
}
return ApiSuccess("Team Member removed")
return Success("Team Member removed")
}
+51 -51
View File
@@ -14,21 +14,21 @@ func GetSignedInUser(c *m.ReqContext) Response {
}
// GET /api/users/:id
func GetUserById(c *m.ReqContext) Response {
func GetUserByID(c *m.ReqContext) Response {
return getUserUserProfile(c.ParamsInt64(":id"))
}
func getUserUserProfile(userId int64) Response {
query := m.GetUserProfileQuery{UserId: userId}
func getUserUserProfile(userID int64) Response {
query := m.GetUserProfileQuery{UserId: userID}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrUserNotFound {
return ApiError(404, m.ErrUserNotFound.Error(), nil)
return Error(404, m.ErrUserNotFound.Error(), nil)
}
return ApiError(500, "Failed to get user", err)
return Error(500, "Failed to get user", err)
}
return Json(200, query.Result)
return JSON(200, query.Result)
}
// GET /api/users/lookup
@@ -36,9 +36,9 @@ func GetUserByLoginOrEmail(c *m.ReqContext) Response {
query := m.GetUserByLoginQuery{LoginOrEmail: c.Query("loginOrEmail")}
if err := bus.Dispatch(&query); err != nil {
if err == m.ErrUserNotFound {
return ApiError(404, m.ErrUserNotFound.Error(), nil)
return Error(404, m.ErrUserNotFound.Error(), nil)
}
return ApiError(500, "Failed to get user", err)
return Error(500, "Failed to get user", err)
}
user := query.Result
result := m.UserProfileDTO{
@@ -50,17 +50,17 @@ func GetUserByLoginOrEmail(c *m.ReqContext) Response {
IsGrafanaAdmin: user.IsAdmin,
OrgId: user.OrgId,
}
return Json(200, &result)
return JSON(200, &result)
}
// POST /api/user
func UpdateSignedInUser(c *m.ReqContext, cmd m.UpdateUserCommand) Response {
if setting.AuthProxyEnabled {
if setting.AuthProxyHeaderProperty == "email" && cmd.Email != c.Email {
return ApiError(400, "Not allowed to change email when auth proxy is using email property", nil)
return Error(400, "Not allowed to change email when auth proxy is using email property", nil)
}
if setting.AuthProxyHeaderProperty == "username" && cmd.Login != c.Login {
return ApiError(400, "Not allowed to change username when auth proxy is using username property", nil)
return Error(400, "Not allowed to change username when auth proxy is using username property", nil)
}
}
cmd.UserId = c.UserId
@@ -75,35 +75,35 @@ func UpdateUser(c *m.ReqContext, cmd m.UpdateUserCommand) Response {
//POST /api/users/:id/using/:orgId
func UpdateUserActiveOrg(c *m.ReqContext) Response {
userId := c.ParamsInt64(":id")
orgId := c.ParamsInt64(":orgId")
userID := c.ParamsInt64(":id")
orgID := c.ParamsInt64(":orgId")
if !validateUsingOrg(userId, orgId) {
return ApiError(401, "Not a valid organization", nil)
if !validateUsingOrg(userID, orgID) {
return Error(401, "Not a valid organization", nil)
}
cmd := m.SetUsingOrgCommand{UserId: userId, OrgId: orgId}
cmd := m.SetUsingOrgCommand{UserId: userID, OrgId: orgID}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to change active organization", err)
return Error(500, "Failed to change active organization", err)
}
return ApiSuccess("Active organization changed")
return Success("Active organization changed")
}
func handleUpdateUser(cmd m.UpdateUserCommand) Response {
if len(cmd.Login) == 0 {
cmd.Login = cmd.Email
if len(cmd.Login) == 0 {
return ApiError(400, "Validation error, need to specify either username or email", nil)
return Error(400, "Validation error, need to specify either username or email", nil)
}
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to update user", err)
return Error(500, "Failed to update user", err)
}
return ApiSuccess("User updated")
return Success("User updated")
}
// GET /api/user/orgs
@@ -116,18 +116,18 @@ func GetUserOrgList(c *m.ReqContext) Response {
return getUserOrgList(c.ParamsInt64(":id"))
}
func getUserOrgList(userId int64) Response {
query := m.GetUserOrgListQuery{UserId: userId}
func getUserOrgList(userID int64) Response {
query := m.GetUserOrgListQuery{UserId: userID}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Failed to get user organizations", err)
return Error(500, "Failed to get user organizations", err)
}
return Json(200, query.Result)
return JSON(200, query.Result)
}
func validateUsingOrg(userId int64, orgId int64) bool {
query := m.GetUserOrgListQuery{UserId: userId}
func validateUsingOrg(userID int64, orgID int64) bool {
query := m.GetUserOrgListQuery{UserId: userID}
if err := bus.Dispatch(&query); err != nil {
return false
@@ -136,7 +136,7 @@ func validateUsingOrg(userId int64, orgId int64) bool {
// validate that the org id in the list
valid := false
for _, other := range query.Result {
if other.OrgId == orgId {
if other.OrgId == orgID {
valid = true
}
}
@@ -146,30 +146,30 @@ func validateUsingOrg(userId int64, orgId int64) bool {
// POST /api/user/using/:id
func UserSetUsingOrg(c *m.ReqContext) Response {
orgId := c.ParamsInt64(":id")
orgID := c.ParamsInt64(":id")
if !validateUsingOrg(c.UserId, orgId) {
return ApiError(401, "Not a valid organization", nil)
if !validateUsingOrg(c.UserId, orgID) {
return Error(401, "Not a valid organization", nil)
}
cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgId}
cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgID}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to change active organization", err)
return Error(500, "Failed to change active organization", err)
}
return ApiSuccess("Active organization changed")
return Success("Active organization changed")
}
// GET /profile/switch-org/:id
func ChangeActiveOrgAndRedirectToHome(c *m.ReqContext) {
orgId := c.ParamsInt64(":id")
orgID := c.ParamsInt64(":id")
if !validateUsingOrg(c.UserId, orgId) {
if !validateUsingOrg(c.UserId, orgID) {
NotFoundHandler(c)
}
cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgId}
cmd := m.SetUsingOrgCommand{UserId: c.UserId, OrgId: orgID}
if err := bus.Dispatch(&cmd); err != nil {
NotFoundHandler(c)
@@ -180,53 +180,53 @@ func ChangeActiveOrgAndRedirectToHome(c *m.ReqContext) {
func ChangeUserPassword(c *m.ReqContext, cmd m.ChangeUserPasswordCommand) Response {
if setting.LdapEnabled || setting.AuthProxyEnabled {
return ApiError(400, "Not allowed to change password when LDAP or Auth Proxy is enabled", nil)
return Error(400, "Not allowed to change password when LDAP or Auth Proxy is enabled", nil)
}
userQuery := m.GetUserByIdQuery{Id: c.UserId}
if err := bus.Dispatch(&userQuery); err != nil {
return ApiError(500, "Could not read user from database", err)
return Error(500, "Could not read user from database", err)
}
passwordHashed := util.EncodePassword(cmd.OldPassword, userQuery.Result.Salt)
if passwordHashed != userQuery.Result.Password {
return ApiError(401, "Invalid old password", nil)
return Error(401, "Invalid old password", nil)
}
password := m.Password(cmd.NewPassword)
if password.IsWeak() {
return ApiError(400, "New password is too short", nil)
return Error(400, "New password is too short", nil)
}
cmd.UserId = c.UserId
cmd.NewPassword = util.EncodePassword(cmd.NewPassword, userQuery.Result.Salt)
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to change user password", err)
return Error(500, "Failed to change user password", err)
}
return ApiSuccess("User password changed")
return Success("User password changed")
}
// GET /api/users
func SearchUsers(c *m.ReqContext) Response {
query, err := searchUser(c)
if err != nil {
return ApiError(500, "Failed to fetch users", err)
return Error(500, "Failed to fetch users", err)
}
return Json(200, query.Result.Users)
return JSON(200, query.Result.Users)
}
// GET /api/users/search
func SearchUsersWithPaging(c *m.ReqContext) Response {
query, err := searchUser(c)
if err != nil {
return ApiError(500, "Failed to fetch users", err)
return Error(500, "Failed to fetch users", err)
}
return Json(200, query.Result)
return JSON(200, query.Result)
}
func searchUser(c *m.ReqContext) (*m.SearchUsersQuery, error) {
@@ -269,10 +269,10 @@ func SetHelpFlag(c *m.ReqContext) Response {
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to update help flag", err)
return Error(500, "Failed to update help flag", err)
}
return Json(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1})
return JSON(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1})
}
func ClearHelpFlags(c *m.ReqContext) Response {
@@ -282,8 +282,8 @@ func ClearHelpFlags(c *m.ReqContext) Response {
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to update help flag", err)
return Error(500, "Failed to update help flag", err)
}
return Json(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1})
return JSON(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1})
}
+2 -2
View File
@@ -49,7 +49,7 @@ type GrafanaServerImpl struct {
childRoutines *errgroup.Group
log log.Logger
httpServer *api.HttpServer
httpServer *api.HTTPServer
}
func (g *GrafanaServerImpl) Start() error {
@@ -120,7 +120,7 @@ func (g *GrafanaServerImpl) initLogging() {
}
func (g *GrafanaServerImpl) startHttpServer() error {
g.httpServer = api.NewHttpServer()
g.httpServer = api.NewHTTPServer()
err := g.httpServer.Start(g.context)
+3 -3
View File
@@ -17,10 +17,10 @@ type AuthOptions struct {
}
func getRequestUserId(c *m.ReqContext) int64 {
userId := c.Session.Get(session.SESS_KEY_USERID)
userID := c.Session.Get(session.SESS_KEY_USERID)
if userId != nil {
return userId.(int64)
if userID != nil {
return userID.(int64)
}
return 0
+51 -52
View File
@@ -1,8 +1,8 @@
package middleware
import (
"errors"
"fmt"
"net"
"strings"
"time"
@@ -14,7 +14,7 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
func initContextWithAuthProxy(ctx *m.ReqContext, orgId int64) bool {
func initContextWithAuthProxy(ctx *m.ReqContext, orgID int64) bool {
if !setting.AuthProxyEnabled {
return false
}
@@ -25,37 +25,37 @@ func initContextWithAuthProxy(ctx *m.ReqContext, orgId int64) bool {
}
// if auth proxy ip(s) defined, check if request comes from one of those
if err := checkAuthenticationProxy(ctx, proxyHeaderValue); err != nil {
if err := checkAuthenticationProxy(ctx.Req.RemoteAddr, proxyHeaderValue); err != nil {
ctx.Handle(407, "Proxy authentication required", err)
return true
}
query := getSignedInUserQueryForProxyAuth(proxyHeaderValue)
query.OrgId = orgId
query.OrgId = orgID
if err := bus.Dispatch(query); err != nil {
if err != m.ErrUserNotFound {
ctx.Handle(500, "Failed to find user specified in auth proxy header", err)
return true
}
if setting.AuthProxyAutoSignUp {
cmd := getCreateUserCommandForProxyAuth(proxyHeaderValue)
if setting.LdapEnabled {
cmd.SkipOrgSetup = true
}
if err := bus.Dispatch(cmd); err != nil {
ctx.Handle(500, "Failed to create user specified in auth proxy header", err)
return true
}
query = &m.GetSignedInUserQuery{UserId: cmd.Result.Id, OrgId: orgId}
if err := bus.Dispatch(query); err != nil {
ctx.Handle(500, "Failed find user after creation", err)
return true
}
} else {
if !setting.AuthProxyAutoSignUp {
return false
}
cmd := getCreateUserCommandForProxyAuth(proxyHeaderValue)
if setting.LdapEnabled {
cmd.SkipOrgSetup = true
}
if err := bus.Dispatch(cmd); err != nil {
ctx.Handle(500, "Failed to create user specified in auth proxy header", err)
return true
}
query = &m.GetSignedInUserQuery{UserId: cmd.Result.Id, OrgId: orgID}
if err := bus.Dispatch(query); err != nil {
ctx.Handle(500, "Failed find user after creation", err)
return true
}
}
// initialize session
@@ -96,53 +96,52 @@ func initContextWithAuthProxy(ctx *m.ReqContext, orgId int64) bool {
}
var syncGrafanaUserWithLdapUser = func(ctx *m.ReqContext, query *m.GetSignedInUserQuery) error {
if setting.LdapEnabled {
expireEpoch := time.Now().Add(time.Duration(-setting.AuthProxyLdapSyncTtl) * time.Minute).Unix()
if !setting.LdapEnabled {
return nil
}
var lastLdapSync int64
if lastLdapSyncInSession := ctx.Session.Get(session.SESS_KEY_LASTLDAPSYNC); lastLdapSyncInSession != nil {
lastLdapSync = lastLdapSyncInSession.(int64)
}
expireEpoch := time.Now().Add(time.Duration(-setting.AuthProxyLdapSyncTtl) * time.Minute).Unix()
if lastLdapSync < expireEpoch {
ldapCfg := login.LdapCfg
var lastLdapSync int64
if lastLdapSyncInSession := ctx.Session.Get(session.SESS_KEY_LASTLDAPSYNC); lastLdapSyncInSession != nil {
lastLdapSync = lastLdapSyncInSession.(int64)
}
for _, server := range ldapCfg.Servers {
author := login.NewLdapAuthenticator(server)
if err := author.SyncSignedInUser(query.Result); err != nil {
return err
}
if lastLdapSync < expireEpoch {
ldapCfg := login.LdapCfg
for _, server := range ldapCfg.Servers {
author := login.NewLdapAuthenticator(server)
if err := author.SyncSignedInUser(query.Result); err != nil {
return err
}
ctx.Session.Set(session.SESS_KEY_LASTLDAPSYNC, time.Now().Unix())
}
ctx.Session.Set(session.SESS_KEY_LASTLDAPSYNC, time.Now().Unix())
}
return nil
}
func checkAuthenticationProxy(ctx *m.ReqContext, proxyHeaderValue string) error {
if len(strings.TrimSpace(setting.AuthProxyWhitelist)) > 0 {
proxies := strings.Split(setting.AuthProxyWhitelist, ",")
remoteAddrSplit := strings.Split(ctx.Req.RemoteAddr, ":")
sourceIP := remoteAddrSplit[0]
func checkAuthenticationProxy(remoteAddr string, proxyHeaderValue string) error {
if len(strings.TrimSpace(setting.AuthProxyWhitelist)) == 0 {
return nil
}
found := false
for _, proxyIP := range proxies {
if sourceIP == strings.TrimSpace(proxyIP) {
found = true
break
}
}
proxies := strings.Split(setting.AuthProxyWhitelist, ",")
sourceIP, _, err := net.SplitHostPort(remoteAddr)
if err != nil {
return err
}
if !found {
msg := fmt.Sprintf("Request for user (%s) is not from the authentication proxy", proxyHeaderValue)
err := errors.New(msg)
return err
// Compare allowed IP addresses to actual address
for _, proxyIP := range proxies {
if sourceIP == strings.TrimSpace(proxyIP) {
return nil
}
}
return nil
return fmt.Errorf("Request for user (%s) from %s is not from the authentication proxy", proxyHeaderValue, sourceIP)
}
func getSignedInUserQueryForProxyAuth(headerVal string) *m.GetSignedInUserQuery {
+6 -6
View File
@@ -10,8 +10,8 @@ import (
"gopkg.in/macaron.v1"
)
func getDashboardUrlBySlug(orgId int64, slug string) (string, error) {
query := m.GetDashboardQuery{Slug: slug, OrgId: orgId}
func getDashboardURLBySlug(orgID int64, slug string) (string, error) {
query := m.GetDashboardQuery{Slug: slug, OrgId: orgID}
if err := bus.Dispatch(&query); err != nil {
return "", m.ErrDashboardNotFound
@@ -20,12 +20,12 @@ func getDashboardUrlBySlug(orgId int64, slug string) (string, error) {
return m.GetDashboardUrl(query.Result.Uid, query.Result.Slug), nil
}
func RedirectFromLegacyDashboardUrl() macaron.Handler {
func RedirectFromLegacyDashboardURL() macaron.Handler {
return func(c *m.ReqContext) {
slug := c.Params("slug")
if slug != "" {
if url, err := getDashboardUrlBySlug(c.OrgId, slug); err == nil {
if url, err := getDashboardURLBySlug(c.OrgId, slug); err == nil {
url = fmt.Sprintf("%s?%s", url, c.Req.URL.RawQuery)
c.Redirect(url, 301)
return
@@ -34,13 +34,13 @@ func RedirectFromLegacyDashboardUrl() macaron.Handler {
}
}
func RedirectFromLegacyDashboardSoloUrl() macaron.Handler {
func RedirectFromLegacyDashboardSoloURL() macaron.Handler {
return func(c *m.ReqContext) {
slug := c.Params("slug")
renderRequest := c.QueryBool("render")
if slug != "" {
if url, err := getDashboardUrlBySlug(c.OrgId, slug); err == nil {
if url, err := getDashboardURLBySlug(c.OrgId, slug); err == nil {
if renderRequest && strings.Contains(url, setting.AppSubUrl) {
url = strings.Replace(url, setting.AppSubUrl, "", 1)
}
+10 -10
View File
@@ -13,8 +13,8 @@ import (
func TestMiddlewareDashboardRedirect(t *testing.T) {
Convey("Given the dashboard redirect middleware", t, func() {
bus.ClearBusHandlers()
redirectFromLegacyDashboardUrl := RedirectFromLegacyDashboardUrl()
redirectFromLegacyDashboardSoloUrl := RedirectFromLegacyDashboardSoloUrl()
redirectFromLegacyDashboardUrl := RedirectFromLegacyDashboardURL()
redirectFromLegacyDashboardSoloUrl := RedirectFromLegacyDashboardSoloURL()
fakeDash := m.NewDashboard("Child dash")
fakeDash.Id = 1
@@ -34,9 +34,9 @@ func TestMiddlewareDashboardRedirect(t *testing.T) {
Convey("Should redirect to new dashboard url with a 301 Moved Permanently", func() {
So(sc.resp.Code, ShouldEqual, 301)
redirectUrl, _ := sc.resp.Result().Location()
So(redirectUrl.Path, ShouldEqual, m.GetDashboardUrl(fakeDash.Uid, fakeDash.Slug))
So(len(redirectUrl.Query()), ShouldEqual, 2)
redirectURL, _ := sc.resp.Result().Location()
So(redirectURL.Path, ShouldEqual, m.GetDashboardUrl(fakeDash.Uid, fakeDash.Slug))
So(len(redirectURL.Query()), ShouldEqual, 2)
})
})
@@ -47,11 +47,11 @@ func TestMiddlewareDashboardRedirect(t *testing.T) {
Convey("Should redirect to new dashboard url with a 301 Moved Permanently", func() {
So(sc.resp.Code, ShouldEqual, 301)
redirectUrl, _ := sc.resp.Result().Location()
expectedUrl := m.GetDashboardUrl(fakeDash.Uid, fakeDash.Slug)
expectedUrl = strings.Replace(expectedUrl, "/d/", "/d-solo/", 1)
So(redirectUrl.Path, ShouldEqual, expectedUrl)
So(len(redirectUrl.Query()), ShouldEqual, 2)
redirectURL, _ := sc.resp.Result().Location()
expectedURL := m.GetDashboardUrl(fakeDash.Uid, fakeDash.Slug)
expectedURL = strings.Replace(expectedURL, "/d/", "/d-solo/", 1)
So(redirectURL.Path, ShouldEqual, expectedURL)
So(len(redirectURL.Query()), ShouldEqual, 2)
})
})
})
+23 -5
View File
@@ -226,11 +226,11 @@ func TestMiddlewareContext(t *testing.T) {
})
})
middlewareScenario("When auth_proxy is enabled and request RemoteAddr is not trusted", func(sc *scenarioContext) {
middlewareScenario("When auth_proxy is enabled and IPv4 request RemoteAddr is not trusted", func(sc *scenarioContext) {
setting.AuthProxyEnabled = true
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
setting.AuthProxyHeaderProperty = "username"
setting.AuthProxyWhitelist = "192.168.1.1, 192.168.2.1"
setting.AuthProxyWhitelist = "192.168.1.1, 2001::23"
sc.fakeReq("GET", "/")
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
@@ -239,6 +239,24 @@ func TestMiddlewareContext(t *testing.T) {
Convey("should return 407 status code", func() {
So(sc.resp.Code, ShouldEqual, 407)
So(sc.resp.Body.String(), ShouldContainSubstring, "Request for user (torkelo) from 192.168.3.1 is not from the authentication proxy")
})
})
middlewareScenario("When auth_proxy is enabled and IPv6 request RemoteAddr is not trusted", func(sc *scenarioContext) {
setting.AuthProxyEnabled = true
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
setting.AuthProxyHeaderProperty = "username"
setting.AuthProxyWhitelist = "192.168.1.1, 2001::23"
sc.fakeReq("GET", "/")
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
sc.req.RemoteAddr = "[2001:23]:12345"
sc.exec()
Convey("should return 407 status code", func() {
So(sc.resp.Code, ShouldEqual, 407)
So(sc.resp.Body.String(), ShouldContainSubstring, "Request for user (torkelo) from 2001:23 is not from the authentication proxy")
})
})
@@ -246,7 +264,7 @@ func TestMiddlewareContext(t *testing.T) {
setting.AuthProxyEnabled = true
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
setting.AuthProxyHeaderProperty = "username"
setting.AuthProxyWhitelist = "192.168.1.1, 192.168.2.1"
setting.AuthProxyWhitelist = "192.168.1.1, 2001::23"
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
query.Result = &m.SignedInUser{OrgId: 4, UserId: 33}
@@ -255,7 +273,7 @@ func TestMiddlewareContext(t *testing.T) {
sc.fakeReq("GET", "/")
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
sc.req.RemoteAddr = "192.168.2.1:12345"
sc.req.RemoteAddr = "[2001::23]:12345"
sc.exec()
Convey("Should init context with user info", func() {
@@ -338,7 +356,7 @@ func middlewareScenario(desc string, fn scenarioFunc) {
sc.m.Use(GetContextHandler())
// mock out gc goroutine
session.StartSessionGC = func() {}
sc.m.Use(Sessioner(&ms.Options{}))
sc.m.Use(Sessioner(&ms.Options{}, 0))
sc.m.Use(OrgRedirect())
sc.m.Use(AddDefaultResponseHeaders())
+7 -7
View File
@@ -14,10 +14,10 @@ import (
func TestRecoveryMiddleware(t *testing.T) {
Convey("Given an api route that panics", t, func() {
apiUrl := "/api/whatever"
recoveryScenario("recovery middleware should return json", apiUrl, func(sc *scenarioContext) {
apiURL := "/api/whatever"
recoveryScenario("recovery middleware should return json", apiURL, func(sc *scenarioContext) {
sc.handlerFunc = PanicHandler
sc.fakeReq("GET", apiUrl).exec()
sc.fakeReq("GET", apiURL).exec()
sc.req.Header.Add("content-type", "application/json")
So(sc.resp.Code, ShouldEqual, 500)
@@ -27,10 +27,10 @@ func TestRecoveryMiddleware(t *testing.T) {
})
Convey("Given a non-api route that panics", t, func() {
apiUrl := "/whatever"
recoveryScenario("recovery middleware should return html", apiUrl, func(sc *scenarioContext) {
apiURL := "/whatever"
recoveryScenario("recovery middleware should return html", apiURL, func(sc *scenarioContext) {
sc.handlerFunc = PanicHandler
sc.fakeReq("GET", apiUrl).exec()
sc.fakeReq("GET", apiURL).exec()
So(sc.resp.Code, ShouldEqual, 500)
So(sc.resp.Header().Get("content-type"), ShouldEqual, "text/html; charset=UTF-8")
@@ -63,7 +63,7 @@ func recoveryScenario(desc string, url string, fn scenarioFunc) {
sc.m.Use(GetContextHandler())
// mock out gc goroutine
session.StartSessionGC = func() {}
sc.m.Use(Sessioner(&ms.Options{}))
sc.m.Use(Sessioner(&ms.Options{}, 0))
sc.m.Use(OrgRedirect())
sc.m.Use(AddDefaultResponseHeaders())
+7 -7
View File
@@ -19,16 +19,16 @@ func initContextWithRenderAuth(ctx *m.ReqContext) bool {
renderKeysLock.Lock()
defer renderKeysLock.Unlock()
if renderUser, exists := renderKeys[key]; !exists {
renderUser, exists := renderKeys[key]
if !exists {
ctx.JsonApiErr(401, "Invalid Render Key", nil)
return true
} else {
ctx.IsSignedIn = true
ctx.SignedInUser = renderUser
ctx.IsRenderCall = true
return true
}
ctx.IsSignedIn = true
ctx.SignedInUser = renderUser
ctx.IsRenderCall = true
return true
}
type renderContextFunc func(key string) (string, error)
+2 -2
View File
@@ -8,8 +8,8 @@ import (
"github.com/grafana/grafana/pkg/services/session"
)
func Sessioner(options *ms.Options) macaron.Handler {
session.Init(options)
func Sessioner(options *ms.Options, sessionConnMaxLifetime int64) macaron.Handler {
session.Init(options, sessionConnMaxLifetime)
return func(ctx *m.ReqContext) {
ctx.Next()
+3 -3
View File
@@ -209,14 +209,14 @@ func GetDashboardFolderUrl(isFolder bool, uid string, slug string) string {
return GetDashboardUrl(uid, slug)
}
// Return the html url for a dashboard
// GetDashboardUrl return the html url for a dashboard
func GetDashboardUrl(uid string, slug string) string {
return fmt.Sprintf("%s/d/%s/%s", setting.AppSubUrl, uid, slug)
}
// Return the full url for a dashboard
// GetFullDashboardUrl return the full url for a dashboard
func GetFullDashboardUrl(uid string, slug string) string {
return fmt.Sprintf("%s%s", setting.AppUrl, GetDashboardUrl(uid, slug))
return fmt.Sprintf("%sd/%s/%s", setting.AppUrl, uid, slug)
}
// GetFolderUrl return the html url for a folder
+13
View File
@@ -4,11 +4,24 @@ import (
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
)
func TestDashboardModel(t *testing.T) {
Convey("Generate full dashboard url", t, func() {
setting.AppUrl = "http://grafana.local/"
fullUrl := GetFullDashboardUrl("uid", "my-dashboard")
So(fullUrl, ShouldEqual, "http://grafana.local/d/uid/my-dashboard")
})
Convey("Generate relative dashboard url", t, func() {
setting.AppUrl = ""
fullUrl := GetDashboardUrl("uid", "my-dashboard")
So(fullUrl, ShouldEqual, "/d/uid/my-dashboard")
})
Convey("When generating slug", t, func() {
dashboard := NewDashboard("Grafana Play Home")
dashboard.UpdateSlug()
+2
View File
@@ -19,6 +19,7 @@ const (
DS_PROMETHEUS = "prometheus"
DS_POSTGRES = "postgres"
DS_MYSQL = "mysql"
DS_MSSQL = "mssql"
DS_ACCESS_DIRECT = "direct"
DS_ACCESS_PROXY = "proxy"
)
@@ -68,6 +69,7 @@ var knownDatasourcePlugins map[string]bool = map[string]bool{
DS_OPENTSDB: true,
DS_POSTGRES: true,
DS_MYSQL: true,
DS_MSSQL: true,
"opennms": true,
"abhisant-druid-datasource": true,
"dalmatinerdb-datasource": true,
+6 -13
View File
@@ -13,11 +13,7 @@ func init() {
func validateDashboardAlerts(cmd *m.ValidateDashboardAlertsCommand) error {
extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId)
if _, err := extractor.GetAlerts(); err != nil {
return err
}
return nil
return extractor.ValidateAlerts()
}
func updateDashboardAlerts(cmd *m.UpdateDashboardAlertsCommand) error {
@@ -29,15 +25,12 @@ func updateDashboardAlerts(cmd *m.UpdateDashboardAlertsCommand) error {
extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId)
if alerts, err := extractor.GetAlerts(); err != nil {
return err
} else {
saveAlerts.Alerts = alerts
}
if err := bus.Dispatch(&saveAlerts); err != nil {
alerts, err := extractor.GetAlerts()
if err != nil {
return err
}
return nil
saveAlerts.Alerts = alerts
return bus.Dispatch(&saveAlerts)
}
+64 -26
View File
@@ -86,17 +86,63 @@ func (e *Engine) runJobDispatcher(grafanaCtx context.Context) error {
case <-grafanaCtx.Done():
return dispatcherGroup.Wait()
case job := <-e.execQueue:
dispatcherGroup.Go(func() error { return e.processJob(alertCtx, job) })
dispatcherGroup.Go(func() error { return e.processJobWithRetry(alertCtx, job) })
}
}
}
var (
unfinishedWorkTimeout time.Duration = time.Second * 5
alertTimeout time.Duration = time.Second * 30
// TODO: Make alertTimeout and alertMaxAttempts configurable in the config file.
alertTimeout time.Duration = time.Second * 30
alertMaxAttempts int = 3
)
func (e *Engine) processJob(grafanaCtx context.Context, job *Job) error {
func (e *Engine) processJobWithRetry(grafanaCtx context.Context, job *Job) error {
defer func() {
if err := recover(); err != nil {
e.log.Error("Alert Panic", "error", err, "stack", log.Stack(1))
}
}()
cancelChan := make(chan context.CancelFunc, alertMaxAttempts)
attemptChan := make(chan int, 1)
// Initialize with first attemptID=1
attemptChan <- 1
job.Running = true
for {
select {
case <-grafanaCtx.Done():
// In case grafana server context is cancel, let a chance to job processing
// to finish gracefully - by waiting a timeout duration - before forcing its end.
unfinishedWorkTimer := time.NewTimer(unfinishedWorkTimeout)
select {
case <-unfinishedWorkTimer.C:
return e.endJob(grafanaCtx.Err(), cancelChan, job)
case <-attemptChan:
return e.endJob(nil, cancelChan, job)
}
case attemptID, more := <-attemptChan:
if !more {
return e.endJob(nil, cancelChan, job)
}
go e.processJob(attemptID, attemptChan, cancelChan, job)
}
}
}
func (e *Engine) endJob(err error, cancelChan chan context.CancelFunc, job *Job) error {
job.Running = false
close(cancelChan)
for cancelFn := range cancelChan {
cancelFn()
}
return err
}
func (e *Engine) processJob(attemptID int, attemptChan chan int, cancelChan chan context.CancelFunc, job *Job) {
defer func() {
if err := recover(); err != nil {
e.log.Error("Alert Panic", "error", err, "stack", log.Stack(1))
@@ -104,14 +150,13 @@ func (e *Engine) processJob(grafanaCtx context.Context, job *Job) error {
}()
alertCtx, cancelFn := context.WithTimeout(context.Background(), alertTimeout)
cancelChan <- cancelFn
span := opentracing.StartSpan("alert execution")
alertCtx = opentracing.ContextWithSpan(alertCtx, span)
job.Running = true
evalContext := NewEvalContext(alertCtx, job.Rule)
evalContext.Ctx = alertCtx
done := make(chan struct{})
go func() {
defer func() {
if err := recover(); err != nil {
@@ -122,43 +167,36 @@ func (e *Engine) processJob(grafanaCtx context.Context, job *Job) error {
tlog.String("message", "failed to execute alert rule. panic was recovered."),
)
span.Finish()
close(done)
close(attemptChan)
}
}()
e.evalHandler.Eval(evalContext)
e.resultHandler.Handle(evalContext)
span.SetTag("alertId", evalContext.Rule.Id)
span.SetTag("dashboardId", evalContext.Rule.DashboardId)
span.SetTag("firing", evalContext.Firing)
span.SetTag("nodatapoints", evalContext.NoDataFound)
span.SetTag("attemptID", attemptID)
if evalContext.Error != nil {
ext.Error.Set(span, true)
span.LogFields(
tlog.Error(evalContext.Error),
tlog.String("message", "alerting execution failed"),
tlog.String("message", "alerting execution attempt failed"),
)
if attemptID < alertMaxAttempts {
span.Finish()
e.log.Debug("Job Execution attempt triggered retry", "timeMs", evalContext.GetDurationMs(), "alertId", evalContext.Rule.Id, "name", evalContext.Rule.Name, "firing", evalContext.Firing, "attemptID", attemptID)
attemptChan <- (attemptID + 1)
return
}
}
evalContext.Rule.State = evalContext.GetNewState()
e.resultHandler.Handle(evalContext)
span.Finish()
close(done)
e.log.Debug("Job Execution completed", "timeMs", evalContext.GetDurationMs(), "alertId", evalContext.Rule.Id, "name", evalContext.Rule.Name, "firing", evalContext.Firing, "attemptID", attemptID)
close(attemptChan)
}()
var err error = nil
select {
case <-grafanaCtx.Done():
select {
case <-time.After(unfinishedWorkTimeout):
cancelFn()
err = grafanaCtx.Err()
case <-done:
}
case <-done:
}
e.log.Debug("Job Execution completed", "timeMs", evalContext.GetDurationMs(), "alertId", evalContext.Rule.Id, "name", evalContext.Rule.Name, "firing", evalContext.Firing)
job.Running = false
cancelFn()
return err
}
+118
View File
@@ -0,0 +1,118 @@
package alerting
import (
"context"
"errors"
"math"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
type FakeEvalHandler struct {
SuccessCallID int // 0 means never sucess
CallNb int
}
func NewFakeEvalHandler(successCallID int) *FakeEvalHandler {
return &FakeEvalHandler{
SuccessCallID: successCallID,
CallNb: 0,
}
}
func (handler *FakeEvalHandler) Eval(evalContext *EvalContext) {
handler.CallNb++
if handler.CallNb != handler.SuccessCallID {
evalContext.Error = errors.New("Fake evaluation failure")
}
}
type FakeResultHandler struct{}
func (handler *FakeResultHandler) Handle(evalContext *EvalContext) error {
return nil
}
func TestEngineProcessJob(t *testing.T) {
Convey("Alerting engine job processing", t, func() {
engine := NewEngine()
engine.resultHandler = &FakeResultHandler{}
job := &Job{Running: true, Rule: &Rule{}}
Convey("Should trigger retry if needed", func() {
Convey("error + not last attempt -> retry", func() {
engine.evalHandler = NewFakeEvalHandler(0)
for i := 1; i < alertMaxAttempts; i++ {
attemptChan := make(chan int, 1)
cancelChan := make(chan context.CancelFunc, alertMaxAttempts)
engine.processJob(i, attemptChan, cancelChan, job)
nextAttemptID, more := <-attemptChan
So(nextAttemptID, ShouldEqual, i+1)
So(more, ShouldEqual, true)
So(<-cancelChan, ShouldNotBeNil)
}
})
Convey("error + last attempt -> no retry", func() {
engine.evalHandler = NewFakeEvalHandler(0)
attemptChan := make(chan int, 1)
cancelChan := make(chan context.CancelFunc, alertMaxAttempts)
engine.processJob(alertMaxAttempts, attemptChan, cancelChan, job)
nextAttemptID, more := <-attemptChan
So(nextAttemptID, ShouldEqual, 0)
So(more, ShouldEqual, false)
So(<-cancelChan, ShouldNotBeNil)
})
Convey("no error -> no retry", func() {
engine.evalHandler = NewFakeEvalHandler(1)
attemptChan := make(chan int, 1)
cancelChan := make(chan context.CancelFunc, alertMaxAttempts)
engine.processJob(1, attemptChan, cancelChan, job)
nextAttemptID, more := <-attemptChan
So(nextAttemptID, ShouldEqual, 0)
So(more, ShouldEqual, false)
So(<-cancelChan, ShouldNotBeNil)
})
})
Convey("Should trigger as many retries as needed", func() {
Convey("never sucess -> max retries number", func() {
expectedAttempts := alertMaxAttempts
evalHandler := NewFakeEvalHandler(0)
engine.evalHandler = evalHandler
engine.processJobWithRetry(context.TODO(), job)
So(evalHandler.CallNb, ShouldEqual, expectedAttempts)
})
Convey("always sucess -> never retry", func() {
expectedAttempts := 1
evalHandler := NewFakeEvalHandler(1)
engine.evalHandler = evalHandler
engine.processJobWithRetry(context.TODO(), job)
So(evalHandler.CallNb, ShouldEqual, expectedAttempts)
})
Convey("some errors before sucess -> some retries", func() {
expectedAttempts := int(math.Ceil(float64(alertMaxAttempts) / 2))
evalHandler := NewFakeEvalHandler(expectedAttempts)
engine.evalHandler = evalHandler
engine.processJobWithRetry(context.TODO(), job)
So(evalHandler.CallNb, ShouldEqual, expectedAttempts)
})
})
})
}
+31
View File
@@ -112,3 +112,34 @@ func (c *EvalContext) GetRuleUrl() (string, error) {
return fmt.Sprintf(urlFormat, m.GetFullDashboardUrl(ref.Uid, ref.Slug), c.Rule.PanelId, c.Rule.OrgId), nil
}
}
func (c *EvalContext) GetNewState() m.AlertStateType {
if c.Error != nil {
c.log.Error("Alert Rule Result Error",
"ruleId", c.Rule.Id,
"name", c.Rule.Name,
"error", c.Error,
"changing state to", c.Rule.ExecutionErrorState.ToAlertState())
if c.Rule.ExecutionErrorState == m.ExecutionErrorKeepState {
return c.PrevAlertState
}
return c.Rule.ExecutionErrorState.ToAlertState()
} else if c.Firing {
return m.AlertStateAlerting
} else if c.NoDataFound {
c.log.Info("Alert Rule returned no data",
"ruleId", c.Rule.Id,
"name", c.Rule.Name,
"changing state to", c.Rule.NoDataState.ToAlertState())
if c.Rule.NoDataState == m.NoDataKeepState {
return c.PrevAlertState
}
return c.Rule.NoDataState.ToAlertState()
}
return m.AlertStateOK
}
+68 -1
View File
@@ -2,6 +2,7 @@ package alerting
import (
"context"
"fmt"
"testing"
"github.com/grafana/grafana/pkg/models"
@@ -12,7 +13,7 @@ func TestAlertingEvalContext(t *testing.T) {
Convey("Eval context", t, func() {
ctx := NewEvalContext(context.TODO(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}})
Convey("Should update alert state", func() {
Convey("Should update alert state when needed", func() {
Convey("ok -> alerting", func() {
ctx.PrevAlertState = models.AlertStateOK
@@ -28,5 +29,71 @@ func TestAlertingEvalContext(t *testing.T) {
So(ctx.ShouldUpdateAlertState(), ShouldBeFalse)
})
})
Convey("Should compute and replace properly new rule state", func() {
dummieError := fmt.Errorf("dummie error")
Convey("ok -> alerting", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Firing = true
ctx.Rule.State = ctx.GetNewState()
So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
})
Convey("ok -> error(alerting)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Error = dummieError
ctx.Rule.ExecutionErrorState = models.ExecutionErrorSetAlerting
ctx.Rule.State = ctx.GetNewState()
So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
})
Convey("ok -> error(keep_last)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Error = dummieError
ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
ctx.Rule.State = ctx.GetNewState()
So(ctx.Rule.State, ShouldEqual, models.AlertStateOK)
})
Convey("pending -> error(keep_last)", func() {
ctx.PrevAlertState = models.AlertStatePending
ctx.Error = dummieError
ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
ctx.Rule.State = ctx.GetNewState()
So(ctx.Rule.State, ShouldEqual, models.AlertStatePending)
})
Convey("ok -> no_data(alerting)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.NoDataState = models.NoDataSetAlerting
ctx.NoDataFound = true
ctx.Rule.State = ctx.GetNewState()
So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
})
Convey("ok -> no_data(keep_last)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.NoDataState = models.NoDataKeepState
ctx.NoDataFound = true
ctx.Rule.State = ctx.GetNewState()
So(ctx.Rule.State, ShouldEqual, models.AlertStateOK)
})
Convey("pending -> no_data(keep_last)", func() {
ctx.PrevAlertState = models.AlertStatePending
ctx.Rule.NoDataState = models.NoDataKeepState
ctx.NoDataFound = true
ctx.Rule.State = ctx.GetNewState()
So(ctx.Rule.State, ShouldEqual, models.AlertStatePending)
})
})
})
}
-34
View File
@@ -7,7 +7,6 @@ import (
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/models"
)
type DefaultEvalHandler struct {
@@ -66,40 +65,7 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) {
context.Firing = firing
context.NoDataFound = noDataFound
context.EndTime = time.Now()
context.Rule.State = e.getNewState(context)
elapsedTime := context.EndTime.Sub(context.StartTime).Nanoseconds() / int64(time.Millisecond)
metrics.M_Alerting_Execution_Time.Observe(float64(elapsedTime))
}
// This should be move into evalContext once its been refactored. (Carl Bergquist)
func (handler *DefaultEvalHandler) getNewState(evalContext *EvalContext) models.AlertStateType {
if evalContext.Error != nil {
handler.log.Error("Alert Rule Result Error",
"ruleId", evalContext.Rule.Id,
"name", evalContext.Rule.Name,
"error", evalContext.Error,
"changing state to", evalContext.Rule.ExecutionErrorState.ToAlertState())
if evalContext.Rule.ExecutionErrorState == models.ExecutionErrorKeepState {
return evalContext.PrevAlertState
} else {
return evalContext.Rule.ExecutionErrorState.ToAlertState()
}
} else if evalContext.Firing {
return models.AlertStateAlerting
} else if evalContext.NoDataFound {
handler.log.Info("Alert Rule returned no data",
"ruleId", evalContext.Rule.Id,
"name", evalContext.Rule.Name,
"changing state to", evalContext.Rule.NoDataState.ToAlertState())
if evalContext.Rule.NoDataState == models.NoDataKeepState {
return evalContext.PrevAlertState
} else {
return evalContext.Rule.NoDataState.ToAlertState()
}
}
return models.AlertStateOK
}
@@ -2,10 +2,8 @@ package alerting
import (
"context"
"fmt"
"testing"
"github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey"
)
@@ -203,73 +201,5 @@ func TestAlertingEvaluationHandler(t *testing.T) {
handler.Eval(context)
So(context.NoDataFound, ShouldBeTrue)
})
Convey("EvalHandler can replace alert state based for errors and no_data", func() {
ctx := NewEvalContext(context.TODO(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}})
dummieError := fmt.Errorf("dummie error")
Convey("Should update alert state", func() {
Convey("ok -> alerting", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Firing = true
So(handler.getNewState(ctx), ShouldEqual, models.AlertStateAlerting)
})
Convey("ok -> error(alerting)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Error = dummieError
ctx.Rule.ExecutionErrorState = models.ExecutionErrorSetAlerting
ctx.Rule.State = handler.getNewState(ctx)
So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
})
Convey("ok -> error(keep_last)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Error = dummieError
ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
ctx.Rule.State = handler.getNewState(ctx)
So(ctx.Rule.State, ShouldEqual, models.AlertStateOK)
})
Convey("pending -> error(keep_last)", func() {
ctx.PrevAlertState = models.AlertStatePending
ctx.Error = dummieError
ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState
ctx.Rule.State = handler.getNewState(ctx)
So(ctx.Rule.State, ShouldEqual, models.AlertStatePending)
})
Convey("ok -> no_data(alerting)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.NoDataState = models.NoDataSetAlerting
ctx.NoDataFound = true
ctx.Rule.State = handler.getNewState(ctx)
So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting)
})
Convey("ok -> no_data(keep_last)", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.NoDataState = models.NoDataKeepState
ctx.NoDataFound = true
ctx.Rule.State = handler.getNewState(ctx)
So(ctx.Rule.State, ShouldEqual, models.AlertStateOK)
})
Convey("pending -> no_data(keep_last)", func() {
ctx.PrevAlertState = models.AlertStatePending
ctx.Rule.NoDataState = models.NoDataKeepState
ctx.NoDataFound = true
ctx.Rule.State = handler.getNewState(ctx)
So(ctx.Rule.State, ShouldEqual, models.AlertStatePending)
})
})
})
})
}
+57 -40
View File
@@ -11,76 +11,78 @@ import (
m "github.com/grafana/grafana/pkg/models"
)
// DashAlertExtractor extracts alerts from the dashboard json
type DashAlertExtractor struct {
Dash *m.Dashboard
OrgId int64
OrgID int64
log log.Logger
}
func NewDashAlertExtractor(dash *m.Dashboard, orgId int64) *DashAlertExtractor {
// NewDashAlertExtractor returns a new DashAlertExtractor
func NewDashAlertExtractor(dash *m.Dashboard, orgID int64) *DashAlertExtractor {
return &DashAlertExtractor{
Dash: dash,
OrgId: orgId,
OrgID: orgID,
log: log.New("alerting.extractor"),
}
}
func (e *DashAlertExtractor) lookupDatasourceId(dsName string) (*m.DataSource, error) {
func (e *DashAlertExtractor) lookupDatasourceID(dsName string) (*m.DataSource, error) {
if dsName == "" {
query := &m.GetDataSourcesQuery{OrgId: e.OrgId}
query := &m.GetDataSourcesQuery{OrgId: e.OrgID}
if err := bus.Dispatch(query); err != nil {
return nil, err
} else {
for _, ds := range query.Result {
if ds.IsDefault {
return ds, nil
}
}
for _, ds := range query.Result {
if ds.IsDefault {
return ds, nil
}
}
} else {
query := &m.GetDataSourceByNameQuery{Name: dsName, OrgId: e.OrgId}
query := &m.GetDataSourceByNameQuery{Name: dsName, OrgId: e.OrgID}
if err := bus.Dispatch(query); err != nil {
return nil, err
} else {
return query.Result, nil
}
return query.Result, nil
}
return nil, errors.New("Could not find datasource id for " + dsName)
}
func findPanelQueryByRefId(panel *simplejson.Json, refId string) *simplejson.Json {
func findPanelQueryByRefID(panel *simplejson.Json, refID string) *simplejson.Json {
for _, targetsObj := range panel.Get("targets").MustArray() {
target := simplejson.NewFromAny(targetsObj)
if target.Get("refId").MustString() == refId {
if target.Get("refId").MustString() == refID {
return target
}
}
return nil
}
func copyJson(in *simplejson.Json) (*simplejson.Json, error) {
rawJson, err := in.MarshalJSON()
func copyJSON(in *simplejson.Json) (*simplejson.Json, error) {
rawJSON, err := in.MarshalJSON()
if err != nil {
return nil, err
}
return simplejson.NewJson(rawJson)
return simplejson.NewJson(rawJSON)
}
func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json) ([]*m.Alert, error) {
func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json, validateAlertFunc func(*m.Alert) bool) ([]*m.Alert, error) {
alerts := make([]*m.Alert, 0)
for _, panelObj := range jsonWithPanels.Get("panels").MustArray() {
panel := simplejson.NewFromAny(panelObj)
collapsedJson, collapsed := panel.CheckGet("collapsed")
collapsedJSON, collapsed := panel.CheckGet("collapsed")
// check if the panel is collapsed
if collapsed && collapsedJson.MustBool() {
if collapsed && collapsedJSON.MustBool() {
// extract alerts from sub panels for collapsed panels
als, err := e.GetAlertFromPanels(panel)
als, err := e.getAlertFromPanels(panel, validateAlertFunc)
if err != nil {
return nil, err
}
@@ -95,7 +97,7 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json)
continue
}
panelId, err := panel.Get("id").Int64()
panelID, err := panel.Get("id").Int64()
if err != nil {
return nil, fmt.Errorf("panel id is required. err %v", err)
}
@@ -113,8 +115,8 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json)
alert := &m.Alert{
DashboardId: e.Dash.Id,
OrgId: e.OrgId,
PanelId: panelId,
OrgId: e.OrgID,
PanelId: panelID,
Id: jsonAlert.Get("id").MustInt64(),
Name: jsonAlert.Get("name").MustString(),
Handler: jsonAlert.Get("handler").MustInt64(),
@@ -126,11 +128,11 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json)
jsonCondition := simplejson.NewFromAny(condition)
jsonQuery := jsonCondition.Get("query")
queryRefId := jsonQuery.Get("params").MustArray()[0].(string)
panelQuery := findPanelQueryByRefId(panel, queryRefId)
queryRefID := jsonQuery.Get("params").MustArray()[0].(string)
panelQuery := findPanelQueryByRefID(panel, queryRefID)
if panelQuery == nil {
reason := fmt.Sprintf("Alert on PanelId: %v refers to query(%s) that cannot be found", alert.PanelId, queryRefId)
reason := fmt.Sprintf("Alert on PanelId: %v refers to query(%s) that cannot be found", alert.PanelId, queryRefID)
return nil, ValidationError{Reason: reason}
}
@@ -141,12 +143,13 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json)
dsName = panel.Get("datasource").MustString()
}
if datasource, err := e.lookupDatasourceId(dsName); err != nil {
datasource, err := e.lookupDatasourceID(dsName)
if err != nil {
return nil, err
} else {
jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id)
}
jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id)
if interval, err := panel.Get("interval").String(); err == nil {
panelQuery.Set("interval", interval)
}
@@ -162,21 +165,28 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json)
return nil, err
}
if alert.ValidToSave() {
alerts = append(alerts, alert)
} else {
if !validateAlertFunc(alert) {
e.log.Debug("Invalid Alert Data. Dashboard, Org or Panel ID is not correct", "alertName", alert.Name, "panelId", alert.PanelId)
return nil, m.ErrDashboardContainsInvalidAlertData
}
alerts = append(alerts, alert)
}
return alerts, nil
}
func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
e.log.Debug("GetAlerts")
func validateAlertRule(alert *m.Alert) bool {
return alert.ValidToSave()
}
dashboardJson, err := copyJson(e.Dash.Data)
// GetAlerts extracts alerts from the dashboard json and does full validation on the alert json data
func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
return e.extractAlerts(validateAlertRule)
}
func (e *DashAlertExtractor) extractAlerts(validateFunc func(alert *m.Alert) bool) ([]*m.Alert, error) {
dashboardJSON, err := copyJSON(e.Dash.Data)
if err != nil {
return nil, err
}
@@ -185,11 +195,11 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
// We extract alerts from rows to be backwards compatible
// with the old dashboard json model.
rows := dashboardJson.Get("rows").MustArray()
rows := dashboardJSON.Get("rows").MustArray()
if len(rows) > 0 {
for _, rowObj := range rows {
row := simplejson.NewFromAny(rowObj)
a, err := e.GetAlertFromPanels(row)
a, err := e.getAlertFromPanels(row, validateFunc)
if err != nil {
return nil, err
}
@@ -197,7 +207,7 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
alerts = append(alerts, a...)
}
} else {
a, err := e.GetAlertFromPanels(dashboardJson)
a, err := e.getAlertFromPanels(dashboardJSON, validateFunc)
if err != nil {
return nil, err
}
@@ -208,3 +218,10 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
e.log.Debug("Extracted alerts from dashboard", "alertCount", len(alerts))
return alerts, nil
}
// ValidateAlerts validates alerts in the dashboard json but does not require a valid dashboard id
// in the first validation pass
func (e *DashAlertExtractor) ValidateAlerts() error {
_, err := e.extractAlerts(func(alert *m.Alert) bool { return alert.OrgId != 0 && alert.PanelId != 0 })
return err
}
+21
View File
@@ -240,5 +240,26 @@ func TestAlertRuleExtraction(t *testing.T) {
So(len(alerts), ShouldEqual, 4)
})
})
Convey("Parse and validate dashboard without id and containing an alert", func() {
json, err := ioutil.ReadFile("./test-data/dash-without-id.json")
So(err, ShouldBeNil)
dashJSON, err := simplejson.NewJson(json)
So(err, ShouldBeNil)
dash := m.NewDashboardFromJson(dashJSON)
extractor := NewDashAlertExtractor(dash, 1)
err = extractor.ValidateAlerts()
Convey("Should validate without error", func() {
So(err, ShouldBeNil)
})
Convey("Should fail on save", func() {
_, err := extractor.GetAlerts()
So(err, ShouldEqual, m.ErrDashboardContainsInvalidAlertData)
})
})
})
}
+5 -1
View File
@@ -15,7 +15,11 @@ type NotifierBase struct {
}
func NewNotifierBase(id int64, isDefault bool, name, notifierType string, model *simplejson.Json) NotifierBase {
uploadImage := model.Get("uploadImage").MustBool(false)
uploadImage := true
value, exist := model.CheckGet("uploadImage")
if exist {
uploadImage = value.MustBool()
}
return NotifierBase{
Id: id,
@@ -4,6 +4,7 @@ import (
"context"
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
. "github.com/smartystreets/goconvey/convey"
@@ -11,6 +12,29 @@ import (
func TestBaseNotifier(t *testing.T) {
Convey("Base notifier tests", t, func() {
Convey("default constructor for notifiers", func() {
bJson := simplejson.New()
Convey("can parse false value", func() {
bJson.Set("uploadImage", false)
base := NewNotifierBase(1, false, "name", "email", bJson)
So(base.UploadImage, ShouldBeFalse)
})
Convey("can parse true value", func() {
bJson.Set("uploadImage", true)
base := NewNotifierBase(1, false, "name", "email", bJson)
So(base.UploadImage, ShouldBeTrue)
})
Convey("default value should be true for backwards compatibility", func() {
base := NewNotifierBase(1, false, "name", "email", bJson)
So(base.UploadImage, ShouldBeTrue)
})
})
Convey("should notify", func() {
Convey("pending -> ok", func() {
context := alerting.NewEvalContext(context.TODO(), &alerting.Rule{
@@ -0,0 +1,281 @@
{
"title": "Influxdb",
"tags": [
"apa"
],
"style": "dark",
"timezone": "browser",
"editable": true,
"hideControls": false,
"sharedCrosshair": false,
"rows": [
{
"collapse": false,
"editable": true,
"height": "450px",
"panels": [
{
"alert": {
"conditions": [
{
"evaluator": {
"params": [
10
],
"type": "gt"
},
"query": {
"params": [
"B",
"5m",
"now"
]
},
"reducer": {
"params": [],
"type": "avg"
},
"type": "query"
}
],
"frequency": "3s",
"handler": 1,
"name": "Influxdb",
"noDataState": "no_data",
"notifications": [
{
"id": 6
}
]
},
"alerting": {},
"aliasColors": {
"logins.count.count": "#890F02"
},
"bars": false,
"datasource": "InfluxDB",
"editable": true,
"error": false,
"fill": 1,
"grid": {},
"id": 1,
"interval": ">10s",
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"datacenter"
],
"type": "tag"
},
{
"params": [
"none"
],
"type": "fill"
}
],
"hide": false,
"measurement": "logins.count",
"policy": "default",
"query": "SELECT 8 * count(\"value\") FROM \"logins.count\" WHERE $timeFilter GROUP BY time($interval), \"datacenter\" fill(none)",
"rawQuery": true,
"refId": "B",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "count"
}
]
],
"tags": []
},
{
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"hide": true,
"measurement": "cpu",
"policy": "default",
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "mean"
}
],
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "sum"
}
]
],
"tags": []
}
],
"thresholds": [
{
"colorMode": "critical",
"fill": true,
"line": true,
"op": "gt",
"value": 10
}
],
"timeFrom": null,
"timeShift": null,
"title": "Panel Title",
"tooltip": {
"msResolution": false,
"ordering": "alphabetical",
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
},
{
"editable": true,
"error": false,
"id": 2,
"isNew": true,
"limit": 10,
"links": [],
"show": "current",
"span": 2,
"stateFilter": [
"alerting"
],
"title": "Alert status",
"type": "alertlist"
}
],
"title": "Row"
}
],
"time": {
"from": "now-5m",
"to": "now"
},
"timepicker": {
"now": true,
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"templating": {
"list": []
},
"annotations": {
"list": []
},
"schemaVersion": 13,
"version": 120,
"links": [],
"gnetId": null
}
@@ -279,4 +279,4 @@
"version": 120,
"links": [],
"gnetId": null
}
}
+1
View File
@@ -53,6 +53,7 @@ func testAlertRule(rule *Rule) *EvalContext {
context.IsTestRun = true
handler.Eval(context)
context.Rule.State = context.GetNewState()
return context
}
+4 -4
View File
@@ -10,8 +10,8 @@ import (
// FolderService service for operating on folders
type FolderService interface {
GetFolders(limit int) ([]*models.Folder, error)
GetFolderById(id int64) (*models.Folder, error)
GetFolderByUid(uid string) (*models.Folder, error)
GetFolderByID(id int64) (*models.Folder, error)
GetFolderByUID(uid string) (*models.Folder, error)
CreateFolder(cmd *models.CreateFolderCommand) error
UpdateFolder(uid string, cmd *models.UpdateFolderCommand) error
DeleteFolder(uid string) (*models.Folder, error)
@@ -57,7 +57,7 @@ func (dr *dashboardServiceImpl) GetFolders(limit int) ([]*models.Folder, error)
return folders, nil
}
func (dr *dashboardServiceImpl) GetFolderById(id int64) (*models.Folder, error) {
func (dr *dashboardServiceImpl) GetFolderByID(id int64) (*models.Folder, error) {
query := models.GetDashboardQuery{OrgId: dr.orgId, Id: id}
dashFolder, err := getFolder(query)
@@ -76,7 +76,7 @@ func (dr *dashboardServiceImpl) GetFolderById(id int64) (*models.Folder, error)
return dashToFolder(dashFolder), nil
}
func (dr *dashboardServiceImpl) GetFolderByUid(uid string) (*models.Folder, error) {
func (dr *dashboardServiceImpl) GetFolderByUID(uid string) (*models.Folder, error) {
query := models.GetDashboardQuery{OrgId: dr.orgId, Uid: uid}
dashFolder, err := getFolder(query)

Some files were not shown because too many files have changed in this diff Show More