Browse Source

Merge branch 'develop' into apikey_hashed

Conflicts:
	pkg/api/apikey.go
	pkg/services/sqlstore/migrations.go
Torkel Ödegaard 10 years ago
parent
commit
6a2a6afc1d
100 changed files with 2931 additions and 3974 deletions
  1. 3 0
      .gitignore
  2. 12 0
      CHANGELOG.md
  3. 11 10
      README.md
  4. 4 0
      benchmarks/ab/ab_test.sh
  5. 8 2
      conf/grafana.ini
  6. 0 269
      data/dashboards/annotations.json
  7. 0 409
      data/dashboards/default.json
  8. 0 0
      data/dashboards/grafana-play-home.json
  9. 0 796
      data/dashboards/graph-styles.json
  10. 0 0
      data/dashboards/graph_styles_collapsed.json
  11. 0 0
      data/dashboards/home.json
  12. 0 288
      data/dashboards/templated-graphs-nested.json
  13. 0 272
      data/dashboards/templated-graphs.json
  14. 0 0
      data/dashboards/testing-save.json
  15. 0 519
      data/dashboards/white-theme.json
  16. BIN
      data/png/77dedee4f4d479cea6f9d608353ed098.png
  17. BIN
      data/png/9e956287bee2bc4eabd91f9bb06ab458.png
  18. 16 0
      docker/blocks/influxdb/Dockerfile
  19. 75 0
      docker/blocks/influxdb/config.toml
  20. 6 0
      docker/blocks/influxdb/fig
  21. 0 18
      docker/fig.yml
  22. 2 1
      docker/production/Dockerfile
  23. 31 0
      docker/production/README.md
  24. 15 0
      docker/production/build.sh
  25. 5 0
      docker/production/test_container.sh
  26. 16 2
      main.go
  27. 0 50
      pkg/api/account.go
  28. 29 0
      pkg/api/admin_settings.go
  29. 138 5
      pkg/api/admin_users.go
  30. 29 15
      pkg/api/api.go
  31. 20 3
      pkg/api/apikey.go
  32. 4 4
      pkg/api/dashboard.go
  33. 9 2
      pkg/api/dataproxy.go
  34. 1 1
      pkg/api/dataproxy_test.go
  35. 34 7
      pkg/api/datasources.go
  36. 3 3
      pkg/api/dtos/models.go
  37. 22 0
      pkg/api/dtos/user.go
  38. 25 6
      pkg/api/frontendsettings.go
  39. 3 3
      pkg/api/index.go
  40. 50 0
      pkg/api/org.go
  41. 12 12
      pkg/api/org_users.go
  42. 2 6
      pkg/api/search.go
  43. 51 20
      pkg/api/user.go
  44. 13 0
      pkg/cmd/common.go
  45. 104 0
      pkg/cmd/dashboard.go
  46. 230 0
      pkg/cmd/datasource.go
  47. 0 120
      pkg/cmd/import.go
  48. 99 0
      pkg/cmd/orgs.go
  49. 4 13
      pkg/cmd/web.go
  50. 2 2
      pkg/events/events.go
  51. 92 10
      pkg/log/console.go
  52. 1 1
      pkg/middleware/auth.go
  53. 9 8
      pkg/middleware/middleware.go
  54. 0 62
      pkg/models/account.go
  55. 0 67
      pkg/models/account_user.go
  56. 16 16
      pkg/models/apikey.go
  57. 10 10
      pkg/models/dashboards.go
  58. 20 12
      pkg/models/datasource.go
  59. 65 0
      pkg/models/org.go
  60. 67 0
      pkg/models/org_user.go
  61. 3 4
      pkg/models/search.go
  62. 50 21
      pkg/models/user.go
  63. 0 103
      pkg/services/sqlstore/account.go
  64. 0 67
      pkg/services/sqlstore/account_users.go
  65. 16 16
      pkg/services/sqlstore/apikey.go
  66. 31 0
      pkg/services/sqlstore/apikey_test.go
  67. 8 8
      pkg/services/sqlstore/dashboard.go
  68. 11 11
      pkg/services/sqlstore/dashboard_test.go
  69. 20 9
      pkg/services/sqlstore/datasource.go
  70. 15 15
      pkg/services/sqlstore/datasource_test.go
  71. 0 175
      pkg/services/sqlstore/migrations.go
  72. 75 0
      pkg/services/sqlstore/migrations/apikey_mig.go
  73. 26 0
      pkg/services/sqlstore/migrations/common.go
  74. 89 0
      pkg/services/sqlstore/migrations/dashboard_mig.go
  75. 98 0
      pkg/services/sqlstore/migrations/datasource_mig.go
  76. 51 0
      pkg/services/sqlstore/migrations/migrations.go
  77. 2 2
      pkg/services/sqlstore/migrations/migrations_test.go
  78. 71 0
      pkg/services/sqlstore/migrations/org_mig.go
  79. 91 0
      pkg/services/sqlstore/migrations/user_mig.go
  80. 0 131
      pkg/services/sqlstore/migrator/builder.go
  81. 13 0
      pkg/services/sqlstore/migrator/conditions.go
  82. 40 4
      pkg/services/sqlstore/migrator/dialect.go
  83. 190 0
      pkg/services/sqlstore/migrator/migrations.go
  84. 16 5
      pkg/services/sqlstore/migrator/migrator.go
  85. 10 1
      pkg/services/sqlstore/migrator/postgres_dialect.go
  86. 9 0
      pkg/services/sqlstore/migrator/sqlite_dialect.go
  87. 22 0
      pkg/services/sqlstore/migrator/types.go
  88. 134 0
      pkg/services/sqlstore/org.go
  89. 37 37
      pkg/services/sqlstore/org_test.go
  90. 67 0
      pkg/services/sqlstore/org_users.go
  91. 2 1
      pkg/services/sqlstore/sqlstore.go
  92. 104 47
      pkg/services/sqlstore/user.go
  93. 37 25
      pkg/setting/setting.go
  94. 32 0
      pkg/setting/setting_test.go
  95. 62 13
      src/app/components/kbn.js
  96. 21 46
      src/app/components/settings.js
  97. 0 1
      src/app/controllers/all.js
  98. 0 108
      src/app/controllers/graphiteImport.js
  99. 18 19
      src/app/controllers/search.js
  100. 92 61
      src/app/controllers/sidemenuCtrl.js

+ 3 - 0
.gitignore

@@ -13,9 +13,12 @@ src/css/*.min.css
 *.sublime-workspace
 *.swp
 .idea/
+*.iml
 
 /data/*
 /bin/*
 /grafana-pro
+/grafana
 
 grafana.custom.ini
+fig.yml

+ 12 - 0
CHANGELOG.md

@@ -4,8 +4,11 @@
 - [Issue #1331](https://github.com/grafana/grafana/issues/1331). Graph & Singlestat: New axis/unit format selector and more units (kbytes, Joule, Watt, eV), and new design for graph axis & grid tab and single stat options tab views
 - [Issue #1241](https://github.com/grafana/grafana/issues/1242). Timepicker: New option in timepicker (under dashboard settings), to change ``now`` to be for example ``now-1m``, usefull when you want to ignore last minute because it contains incomplete data
 - [Issue #171](https://github.com/grafana/grafana/issues/171).   Panel: Different time periods, panels can override dashboard relative time and/or add a time shift
+- [Issue #1488](https://github.com/grafana/grafana/issues/1488). Dashboard: Clone dashboard / Save as
 
 **Enhancements**
+- [Issue #1366](https://github.com/grafana/grafana/issues/1366). Graph & Singlestat: Support for additional units, Fahrenheit (°F) and Celsius (°C), Humidity (%H), kW, watt-hour (Wh), kilowatt-hour (kWh), velocities (m/s, km/h, mpg, knot)
+- [Issue #978](https://github.com/grafana/grafana/issues/978).   Graph: Shared tooltip improvement, can now support metrics of different resolution/intervals
 - [Issue #1297](https://github.com/grafana/grafana/issues/1297). Graphite: Added cumulative and minimumBelow graphite functions
 - [Issue #1296](https://github.com/grafana/grafana/issues/1296). InfluxDB: Auto escape column names with special characters. Thanks @steven-aerts
 - [Issue #1321](https://github.com/grafana/grafana/issues/1321). SingleStatPanel: You can now use template variables in pre & postfix
@@ -18,6 +21,15 @@
 - [Issue #1372](https://github.com/grafana/grafana/issues/1372). Graphite: Fix for nested complex queries, where a query references a query that references another query (ie the #[A-Z] syntax)
 - [Issue #1363](https://github.com/grafana/grafana/issues/1363). Templating: Fix to allow custom template variables to contain white space, now only splits on ','
 - [Issue #1359](https://github.com/grafana/grafana/issues/1359). Graph: Fix for all series tooltip showing series with all null values when ``Hide Empty`` option is enabled
+- [Issue #1497](https://github.com/grafana/grafana/issues/1497). Dashboard: Fixed memory leak when switching dashboards
+
+**Changes**
+- Dashboard title change & save will no longer create a new dashboard, it will just change the title.
+
+**OpenTSDB breaking change**
+- [Issue #1438](https://github.com/grafana/grafana/issues/1438). OpenTSDB: Automatic downsample interval passed to OpenTSDB (depends on timespan and graph width)
+- NOTICE, Downsampling is now enabled by default, so if you have not picked a downsample aggregator in your metric query do so or your graphs will be missleading
+- This will make Grafana a lot quicker for OpenTSDB users when viewing large time spans without having to change the downsample interval manually.
 
 **Tech**
 - [Issue #1311](https://github.com/grafana/grafana/issues/1311). Tech: Updated Font-Awesome from 3.2 to 4.2

+ 11 - 10
README.md

@@ -15,6 +15,12 @@ Graphite, InfluxDB & OpenTSDB.
 Grafana 2.0 comes with a backend written in Go. It is not ready for production use yet as there is still a lot of small
 issues to fix and polish missing. But feedback on what is done and bug reports would be greatly appreciated.
 
+## Try it out with docker
+```
+docker run -i -p 3000:3000 grafana/grafana:develop
+```
+The default admin user is admin/admin.
+
 ## building and running
 
 ```
@@ -26,18 +32,13 @@ Building
 ```
 cd $GOPATH/src/github.com/grafana/grafana
 git checkout -t origin/develop
-go run build.go setup (only needed once to install godep)
-go run build.go build
-```
-
-For quicker builds:
-
-```
-godep restore (will pull down all golang lib dependecies in your current GOPATH)
-go build -o ./bin/grafana .
+go run build.go setup            (only needed once to install godep)
+godep restore                    (will pull down all golang lib dependecies in your current GOPATH)
+go build .
 ```
 
-To build less to css for frontend:
+To build less to css for the frontend you will need a recent version of of node (v0.12.0),
+npm (v2.5.0) and grunt (v0.4.5). Run the following:
 
 ```
 npm install

+ 4 - 0
benchmarks/ab/ab_test.sh

@@ -0,0 +1,4 @@
+#!/bin/bash
+
+ab -n 20000 -c 100 -H "Authorization: Bearer vEustw23NSOZ27y3zlj28ZL3B7BpBk5kqR85DOfT5AwiS3nCi33dnsk6nhvXhZdn" \
+  http://localhost:3000/api/dashboards/db/dash1

+ 8 - 2
conf/grafana.ini

@@ -2,12 +2,18 @@ app_name = Grafana
 app_mode = production
 
 [server]
+; protocol (http or https)
 protocol = http
-domain = localhost
-root_url = %(protocol)s://%(domain)s:%(http_port)s/
+; the ip address to bind to, empty will bind to all interfaces
 http_addr =
+; the http port  to use
 http_port = 3000
+; The public facing domain name used to access grafana from a browser
+domain = localhost
+; the full public facing url
+root_url = %(protocol)s://%(domain)s:%(http_port)s/
 router_logging = false
+; the path relative to the binary where the static (html/js/css) files are placed
 static_root_path = public
 enable_gzip = false
 

+ 0 - 269
data/dashboards/annotations.json

@@ -1,269 +0,0 @@
-{
-  "title": "Annotations",
-  "services": {
-    "filter": {
-      "list": [],
-      "time": {
-        "from": "now-1h",
-        "to": "now"
-      }
-    }
-  },
-  "rows": [
-    {
-      "title": "Welcome to Grafana",
-      "height": "350px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 12,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "short",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 0,
-          "linewidth": 1,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": true,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": false,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": false
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(apps.fakesite.web_server_02.counters.requests.count,2)"
-            },
-            {
-              "target": "aliasByNode(apps.fakesite.web_server_01.counters.requests.count,2)"
-            }
-          ],
-          "aliasColors": {},
-          "aliasYAxis": {},
-          "title": "Amnotations example",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        }
-      ],
-      "notice": false
-    },
-    {
-      "title": "test",
-      "height": "350px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 6,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "short",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 1,
-          "linewidth": 1,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": true,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": false,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": false
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(apps.fakesite.web_server_02.counters.request_status.code_304.count,5)"
-            }
-          ],
-          "aliasColors": {
-            "web_server_01": "#1F78C1",
-            "web_server_02": "#6ED0E0"
-          },
-          "aliasYAxis": {},
-          "title": "Annotations example",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        },
-        {
-          "error": false,
-          "span": 6,
-          "editable": true,
-          "type": "text",
-          "loadingEditor": false,
-          "mode": "markdown",
-          "content": "### Annotations\n\n- Annotation is a feature that must be enabled in the dashboards settings / Controls tab / Feature toggles\n- Annotation bar is then visible at the top. \n- Click on the cog to open the Annotations dialog \n- In this dialog you can add or edit annotations \n- Currently only Graphite metrics and Graphite events are supported sources of annotations\n- More datasource options for annotations will be added \n- Click on the annotation name in the bar to toggle the annotation on or off\n\n",
-          "style": {},
-          "title": "Description"
-        }
-      ],
-      "notice": false
-    }
-  ],
-  "editable": true,
-  "failover": false,
-  "panel_hints": true,
-  "style": "dark",
-  "pulldowns": [
-    {
-      "type": "filtering",
-      "collapse": false,
-      "notice": false,
-      "enable": false
-    },
-    {
-      "type": "annotations",
-      "enable": true,
-      "annotations": [
-        {
-          "name": "deploys",
-          "type": "graphite metric",
-          "showLine": true,
-          "iconColor": "#C0C6BE",
-          "lineColor": "rgba(253, 54, 54, 0.77)",
-          "iconSize": 13,
-          "enable": true,
-          "target": "alias(apps.fakesite.web_server_01.counters.request_status.code_500.count, 'deployed v1.3')"
-        },
-        {
-          "name": "puppet apply",
-          "type": "graphite metric",
-          "showLine": true,
-          "iconColor": "#C0C6BE",
-          "lineColor": "rgba(255, 96, 96, 0.592157)",
-          "iconSize": 13,
-          "enable": false,
-          "target": "alias(apps.fakesite.web_server_02.counters.request_status.code_403.count,'puppet apply')"
-        }
-      ]
-    }
-  ],
-  "nav": [
-    {
-      "type": "timepicker",
-      "collapse": false,
-      "notice": false,
-      "enable": true,
-      "status": "Stable",
-      "time_options": [
-        "5m",
-        "15m",
-        "1h",
-        "6h",
-        "12h",
-        "24h",
-        "2d",
-        "7d",
-        "30d"
-      ],
-      "refresh_intervals": [
-        "5s",
-        "10s",
-        "30s",
-        "1m",
-        "5m",
-        "15m",
-        "30m",
-        "1h",
-        "2h",
-        "1d"
-      ],
-      "now": true
-    }
-  ],
-  "loader": {
-    "save_gist": false,
-    "save_elasticsearch": true,
-    "save_local": true,
-    "save_default": true,
-    "save_temp": true,
-    "save_temp_ttl_enable": true,
-    "save_temp_ttl": "30d",
-    "load_gist": false,
-    "load_elasticsearch": true,
-    "load_elasticsearch_size": 20,
-    "load_local": false,
-    "hide": false
-  },
-  "refresh": false,
-  "tags": [
-    "annotations",
-    "graphite",
-    "showcase"
-  ],
-  "timezone": "browser"
-}

+ 0 - 409
data/dashboards/default.json

@@ -1,409 +0,0 @@
-{
-  "title": "Grafana Play Home",
-  "services": {
-    "filter": {
-      "list": [],
-      "time": {
-        "from": "now-15m",
-        "to": "now"
-      }
-    }
-  },
-  "rows": [
-    {
-      "title": "test",
-      "height": "190px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "error": false,
-          "span": 12,
-          "editable": true,
-          "type": "text",
-          "loadingEditor": false,
-          "mode": "html",
-          "content": "<h3 class=\"text-center\">Welcome to grafana demo, playground and interactive tutorial site.</h2>\n\n<div class=\"row-fluid\">\n\t<div class=\"span4\">\n\t\t<h4>Feature showcases</h2>\n\t\t<ul>\n\t\t\t<li>\n\t\t\t\t<a href=\"#/dashboard/file/graph-styles.json\">Graphs styles</a>\n\t\t\t</li>\n\t\t\t<li>\n\t\t\t\t<a href=\"#/dashboard/file/templated-graphs.json\">Templated graphs</a>\n\t\t\t</li>\n\t\t\t<li>\n\t\t\t\t<a href=\"#/dashboard/file/templated-graphs-nested.json\">Templated graphs nested</a>\n\t\t\t</li>\n\t\t\t<li>\n\t\t\t\t<a href=\"#/dashboard/file/annotations.json\">Annotations</a>\n\t\t\t</li>\n\t\t\t<li>\n\t\t\t\t<a href=\"#/dashboard/file/white-theme.json\">White theme</a>\n\t\t\t</li>\n\t\t</ul>\n\t</div>\n\t<div class=\"span4\">\n\t\t<h4>Graphite tutorials</h2>\n\t\t<ul>\n\t\t\t<li>\n\t\t\t\tGraphite introduction (TODO)\n\t\t\t</li>\n\t\t\t<li>\n\t\t\t\tBasic functions (TODO)\n\t\t\t</li>\n\t\t\t<li>\n\t\t\t\tAdvanced functions (TODO)\n\t\t\t</li>\n\t\t\t<li>\n\t\t\t\tTips and tricks (TODO)\n\t\t\t</li>\n\t\t</ul>\n\t</div>\n\t<div class=\"span4\">\n\t\t<h4>InfluxDB examples</h2>\n\t\t<ul>\n\t\t\t<li>\n\t\t\t\tTODO\n\t\t\t</li>\n\t\t</ul>\n\t</div>\n</div>\n\n<script type=\"text/javascript\">(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\nm=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n})(window,document,'script','//www.google-analytics.com/analytics.js','ga');\n\nga('create', 'UA-47280256-1', 'grafana.org');\nga('send', 'pageview');</script>",
-          "style": {},
-          "title": "Grafana demo site"
-        }
-      ],
-      "notice": false
-    },
-    {
-      "title": "test",
-      "height": "250px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 6,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "short",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 3,
-          "linewidth": 2,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": true,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": false,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": false
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(scaleToSeconds(apps.fakesite.*.counters.requests.count,1),2)"
-            }
-          ],
-          "aliasColors": {
-            "web_server_04": "#3F6833",
-            "web_server_03": "#508642",
-            "web_server_02": "#7EB26D",
-            "web_server_01": "#B7DBAB"
-          },
-          "aliasYAxis": {},
-          "title": "server requests",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        },
-        {
-          "span": 6,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "short",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 1,
-          "linewidth": 2,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": false,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": true,
-            "min": true,
-            "max": true,
-            "current": true,
-            "total": false,
-            "avg": false
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "alias(scaleToSeconds(apps.fakesite.web_server_01.counters.requests.count,1),'logins')"
-            },
-            {
-              "target": "alias(timeShift(scaleToSeconds(apps.fakesite.web_server_01.counters.requests.count,1),'1h'),'logins (-1 hour)')"
-            }
-          ],
-          "aliasColors": {
-            "logins": "#7EB26D",
-            "logins (-1 day)": "#447EBC"
-          },
-          "aliasYAxis": {},
-          "title": "logins",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        }
-      ],
-      "notice": false
-    },
-    {
-      "title": "",
-      "height": "300px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 4,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "bytes",
-            "none"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 0,
-          "linewidth": 2,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": false,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": true,
-            "min": true,
-            "max": false,
-            "current": true,
-            "total": false,
-            "avg": false
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "alias(scale(scaleToSeconds(apps.fakesite.web_server_01.counters.requests.count,1),1000000),'memory')"
-            },
-            {
-              "target": "alias(scaleToSeconds(apps.fakesite.web_server_01.counters.request_status.code_302.count,1),'cpu')"
-            }
-          ],
-          "aliasColors": {
-            "cpu": "#E24D42"
-          },
-          "aliasYAxis": {
-            "cpu": 2
-          },
-          "title": "Memory / CPU",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        },
-        {
-          "span": 8,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "ms",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": false,
-          "fill": 1,
-          "linewidth": 2,
-          "points": false,
-          "pointradius": 5,
-          "bars": true,
-          "stack": true,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": true,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": true
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(statsd.fakesite.timers.ads_timer.*,4)"
-            }
-          ],
-          "aliasColors": {
-            "upper_75": "#EAB839",
-            "upper_50": "#7EB26D",
-            "upper_25": "#BA43A9"
-          },
-          "aliasYAxis": {},
-          "title": "client side full page load",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        }
-      ],
-      "notice": false
-    },
-    {
-      "title": "test",
-      "height": "250px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [],
-      "notice": false
-    }
-  ],
-  "editable": true,
-  "failover": false,
-  "panel_hints": true,
-  "style": "dark",
-  "pulldowns": [
-    {
-      "type": "filtering",
-      "collapse": false,
-      "notice": false,
-      "enable": false
-    },
-    {
-      "type": "annotations",
-      "enable": false
-    }
-  ],
-  "nav": [
-    {
-      "type": "timepicker",
-      "collapse": false,
-      "notice": false,
-      "enable": true,
-      "status": "Stable",
-      "time_options": [
-        "5m",
-        "15m",
-        "1h",
-        "6h",
-        "12h",
-        "24h",
-        "2d",
-        "7d",
-        "30d"
-      ],
-      "refresh_intervals": [
-        "5s",
-        "10s",
-        "30s",
-        "1m",
-        "5m",
-        "15m",
-        "30m",
-        "1h",
-        "2h",
-        "1d"
-      ],
-      "now": true
-    }
-  ],
-  "loader": {
-    "save_gist": false,
-    "save_elasticsearch": true,
-    "save_local": true,
-    "save_default": true,
-    "save_temp": true,
-    "save_temp_ttl_enable": true,
-    "save_temp_ttl": "30d",
-    "load_gist": false,
-    "load_elasticsearch": true,
-    "load_elasticsearch_size": 20,
-    "load_local": false,
-    "hide": false
-  },
-  "refresh": false,
-  "tags": [
-    "showcase",
-    "startpage",
-    "home",
-    "default"
-  ],
-  "timezone": "browser"
-}

File diff suppressed because it is too large
+ 0 - 0
data/dashboards/grafana-play-home.json


+ 0 - 796
data/dashboards/graph-styles.json

@@ -1,796 +0,0 @@
-{
-  "title": "Graph styles",
-  "services": {
-    "filter": {
-      "list": [],
-      "time": {
-        "from": "now-15m",
-        "to": "now"
-      }
-    }
-  },
-  "rows": [
-    {
-      "title": "Simple graph",
-      "height": "250px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 6,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "short",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 0,
-          "linewidth": 1,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": false,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": true,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": false
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(scaleToSeconds(apps.backend.*.counters.requests.count,1),2)"
-            }
-          ],
-          "aliasColors": {
-            "web_server_04": "#E24D42",
-            "web_server_03": "#508642",
-            "web_server_02": "#EAB839",
-            "web_server_01": "#EF843C"
-          },
-          "aliasYAxis": {},
-          "title": "Simple graph",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        },
-        {
-          "error": false,
-          "span": 6,
-          "editable": true,
-          "type": "text",
-          "loadingEditor": false,
-          "mode": "markdown",
-          "content": "#### Simple graph\n- Click on the title and select edit to open edit mode\n- The display styles tab allows you change line width, fill, stacking, and more\n- You can change a series color by clicking the colored line in the legend ",
-          "style": {},
-          "title": "Description"
-        }
-      ],
-      "notice": false
-    },
-    {
-      "title": "Stacked Graph",
-      "height": "250px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 6,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "short",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 2,
-          "linewidth": 2,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": true,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": true,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": true
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(scaleToSeconds(apps.fakesite.*.counters.requests.count,1),2)"
-            }
-          ],
-          "aliasColors": {
-            "web_server_04": "#E24D42",
-            "web_server_03": "#EF843C",
-            "web_server_02": "#EAB839",
-            "web_server_01": "#F2C96D"
-          },
-          "aliasYAxis": {},
-          "title": "Stacked lines",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        },
-        {
-          "error": false,
-          "span": 6,
-          "editable": true,
-          "type": "text",
-          "loadingEditor": false,
-          "mode": "markdown",
-          "content": "#### Stacked graph\n- This graph shows stacked series, with area fill and 2px line width\n- We have also added legend values. These can be enabled in the Grid & Axes tab in edit mode. \n- Legend values can be Min, Max, Total, Current and Average",
-          "style": {},
-          "title": "Description"
-        }
-      ],
-      "notice": false
-    },
-    {
-      "title": "Staircase line",
-      "height": "250px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 6,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "ms",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 1,
-          "linewidth": 2,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": true,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": true,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": true
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": true,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(statsd.fakesite.timers.ads_timer.upper_90,4)"
-            },
-            {
-              "target": "aliasByNode(statsd.fakesite.timers.ads_timer.upper_90,4)"
-            }
-          ],
-          "aliasColors": {
-            "upper_75": "#EAB839",
-            "upper_50": "#7EB26D",
-            "upper_25": "#BA43A9"
-          },
-          "aliasYAxis": {},
-          "title": "Staircase line",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        },
-        {
-          "error": false,
-          "span": 6,
-          "editable": true,
-          "type": "text",
-          "loadingEditor": false,
-          "mode": "markdown",
-          "content": "#### Staircase & Y-Axis format\n- In display styles tab you can switch to staircase line \n- In Axes & Grid tab you can change to different Y units & formats.\n",
-          "style": {},
-          "title": "Description"
-        }
-      ],
-      "notice": false
-    },
-    {
-      "title": "Right Y-Axis",
-      "height": "300px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 6,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "bytes",
-            "none"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 0,
-          "linewidth": 2,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": false,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": true,
-            "min": true,
-            "max": false,
-            "current": true,
-            "total": false,
-            "avg": false
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "alias(scale(scaleToSeconds(apps.fakesite.web_server_01.counters.requests.count,1),1000000),'memory')"
-            },
-            {
-              "target": "alias(scaleToSeconds(apps.fakesite.web_server_02.counters.requests.count,1),'cpu')"
-            }
-          ],
-          "aliasColors": {
-            "cpu": "#E24D42"
-          },
-          "aliasYAxis": {
-            "cpu": 2
-          },
-          "title": "Memory / CPU",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        },
-        {
-          "error": false,
-          "span": 6,
-          "editable": true,
-          "type": "text",
-          "loadingEditor": false,
-          "mode": "markdown",
-          "content": "#### Second Y-Axis\n- Click on the series legend color line to open the color selector\n- In the series color selector popup you can also move the series to the Right-Y axis\n- Multiple Y-Axis are great for showing to related series that have different magnitudes ",
-          "style": {},
-          "title": "Description"
-        }
-      ],
-      "notice": false
-    },
-    {
-      "title": "test",
-      "height": "250px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 6,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "none",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 1,
-          "linewidth": 2,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": false,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": true,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": true
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "null",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(apps.fakesite.web_server_01.counters.request_status.code_404.count,4)"
-            }
-          ],
-          "aliasColors": {
-            "upper_75": "#EAB839",
-            "upper_50": "#7EB26D",
-            "upper_25": "#BA43A9"
-          },
-          "aliasYAxis": {},
-          "title": "Null point mode",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        },
-        {
-          "error": false,
-          "span": 6,
-          "editable": true,
-          "type": "text",
-          "loadingEditor": false,
-          "mode": "markdown",
-          "content": "#### Null point mode\n- This option under Display styles tab controls how null values are handled\n- The graph to left shows how the default \"null\" looks. \n- __null__   null values are left null and this leaves empty spaces in the graph\n- __null as zero__   null values are drawn as zero values\n- __connected__   null values are ignored and the line jumps directly to the next value.",
-          "style": {},
-          "title": "Description"
-        }
-      ],
-      "notice": false
-    },
-    {
-      "title": "Thresholds",
-      "height": "250px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 6,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "none",
-            "short"
-          ],
-          "grid": {
-            "max": 700,
-            "min": 0,
-            "threshold1": 400,
-            "threshold2": 600,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 0,
-          "linewidth": 2,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": false,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": true,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": true,
-            "avg": false
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(apps.fakesite.web_server_01.counters.requests.count,4)"
-            }
-          ],
-          "aliasColors": {
-            "upper_75": "#EAB839",
-            "upper_50": "#7EB26D",
-            "upper_25": "#BA43A9",
-            "requests": "#6ED0E0"
-          },
-          "aliasYAxis": {},
-          "title": "Thresholds",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        },
-        {
-          "error": false,
-          "span": 6,
-          "editable": true,
-          "type": "text",
-          "loadingEditor": false,
-          "mode": "markdown",
-          "content": "#### Thresholds\n- You can define thresholds in the Grid & Axes tab in edit mode \n- You can define one or two thresholds, color is also changeable. \n- You can have lower bound thresholds as well. ",
-          "style": {},
-          "title": "Description"
-        }
-      ],
-      "notice": false
-    },
-    {
-      "title": "Bars",
-      "height": "250px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 6,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "ms",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": false,
-          "fill": 1,
-          "linewidth": 2,
-          "points": false,
-          "pointradius": 5,
-          "bars": true,
-          "stack": true,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": true,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": true
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": true,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(statsd.fakesite.timers.ads_timer.*,4)"
-            }
-          ],
-          "aliasColors": {
-            "upper_75": "#EAB839",
-            "upper_50": "#7EB26D",
-            "upper_25": "#BA43A9"
-          },
-          "aliasYAxis": {},
-          "title": "Bars",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        },
-        {
-          "error": false,
-          "span": 6,
-          "editable": true,
-          "type": "text",
-          "loadingEditor": false,
-          "mode": "markdown",
-          "content": "#### Bars\n- In display styles tab you can switch from line to bars\n- The width of the bar is relative to the pixel width between two values\n",
-          "style": {},
-          "title": "Description"
-        }
-      ],
-      "notice": false
-    },
-    {
-      "title": "Graphite PNG",
-      "height": "250px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 6,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "ms",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": false,
-          "fill": 1,
-          "linewidth": 2,
-          "points": false,
-          "pointradius": 2,
-          "bars": false,
-          "stack": true,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": true,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": true
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(apps.backend.*.counters.requests.count,2)"
-            }
-          ],
-          "aliasColors": {
-            "upper_75": "#EAB839",
-            "upper_50": "#7EB26D",
-            "upper_25": "#BA43A9"
-          },
-          "aliasYAxis": {},
-          "title": "Graphite PNG",
-          "datasource": null,
-          "renderer": "png",
-          "annotate": {
-            "enable": false
-          }
-        },
-        {
-          "error": false,
-          "span": 6,
-          "editable": true,
-          "type": "text",
-          "loadingEditor": false,
-          "mode": "markdown",
-          "content": "#### Graphite PNG support\n- You can switch from client side rendering to graphite's server side PNG rendering\n- You cannot click and drag to zoom in in this render mode.\n",
-          "style": {},
-          "title": "Description"
-        }
-      ],
-      "notice": false
-    }
-  ],
-  "editable": true,
-  "failover": false,
-  "panel_hints": true,
-  "style": "dark",
-  "pulldowns": [
-    {
-      "type": "filtering",
-      "collapse": false,
-      "notice": false,
-      "enable": false
-    },
-    {
-      "type": "annotations",
-      "enable": false
-    }
-  ],
-  "nav": [
-    {
-      "type": "timepicker",
-      "collapse": false,
-      "notice": false,
-      "enable": true,
-      "status": "Stable",
-      "time_options": [
-        "5m",
-        "15m",
-        "1h",
-        "6h",
-        "12h",
-        "24h",
-        "2d",
-        "7d",
-        "30d"
-      ],
-      "refresh_intervals": [
-        "5s",
-        "10s",
-        "30s",
-        "1m",
-        "5m",
-        "15m",
-        "30m",
-        "1h",
-        "2h",
-        "1d"
-      ],
-      "now": true
-    }
-  ],
-  "loader": {
-    "save_gist": false,
-    "save_elasticsearch": true,
-    "save_local": true,
-    "save_default": true,
-    "save_temp": true,
-    "save_temp_ttl_enable": true,
-    "save_temp_ttl": "30d",
-    "load_gist": false,
-    "load_elasticsearch": true,
-    "load_elasticsearch_size": 20,
-    "load_local": false,
-    "hide": false
-  },
-  "refresh": false,
-  "tags": [
-    "showcase",
-    "annotations"
-  ],
-  "timezone": "browser"
-}

File diff suppressed because it is too large
+ 0 - 0
data/dashboards/graph_styles_collapsed.json


File diff suppressed because it is too large
+ 0 - 0
data/dashboards/home.json


+ 0 - 288
data/dashboards/templated-graphs-nested.json

@@ -1,288 +0,0 @@
-{
-  "title": "Templated Graphs Nested",
-  "services": {
-    "filter": {
-      "list": [
-        {
-          "type": "filter",
-          "name": "app",
-          "query": "apps.*",
-          "includeAll": true,
-          "options": [
-            {
-              "text": "All",
-              "value": "{backend,fakesite}"
-            },
-            {
-              "text": "backend",
-              "value": "backend"
-            },
-            {
-              "text": "fakesite",
-              "value": "fakesite"
-            }
-          ],
-          "current": {
-            "text": "backend",
-            "value": "backend"
-          }
-        },
-        {
-          "type": "filter",
-          "name": "server",
-          "query": "apps.[[app]].*",
-          "includeAll": true,
-          "options": [
-            {
-              "text": "All",
-              "value": "{backend_01,backend_02,backend_03,backend_04}"
-            },
-            {
-              "text": "backend_01",
-              "value": "backend_01"
-            },
-            {
-              "text": "backend_02",
-              "value": "backend_02"
-            },
-            {
-              "text": "backend_03",
-              "value": "backend_03"
-            },
-            {
-              "text": "backend_04",
-              "value": "backend_04"
-            }
-          ],
-          "current": {
-            "text": "All",
-            "value": "{backend_01,backend_02,backend_03,backend_04}"
-          }
-        }
-      ],
-      "time": {
-        "from": "now-6h",
-        "to": "now"
-      }
-    }
-  },
-  "rows": [
-    {
-      "title": "Row1",
-      "height": "350px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 12,
-          "editable": true,
-          "type": "graphite",
-          "loadingEditor": false,
-          "datasource": null,
-          "renderer": "flot",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "short",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "annotate": {
-            "enable": false
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 1,
-          "linewidth": 1,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": true,
-          "legend": {
-            "show": true,
-            "values": false,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": false
-          },
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "groupByNode(apps.[[app]].[[server]].counters.requests.count,2,'sum')"
-            }
-          ],
-          "aliasColors": {
-            "highres.test": "#1F78C1",
-            "scale(highres.test,3)": "#6ED0E0",
-            "mobile": "#6ED0E0",
-            "tablet": "#EAB839"
-          },
-          "aliasYAxis": {},
-          "title": "Traffic"
-        }
-      ],
-      "notice": false
-    },
-    {
-      "title": "Row1",
-      "height": "350px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 12,
-          "editable": true,
-          "type": "graphite",
-          "loadingEditor": false,
-          "datasource": null,
-          "renderer": "flot",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "short",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "annotate": {
-            "enable": false
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 0,
-          "linewidth": 1,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": false,
-          "legend": {
-            "show": true,
-            "values": false,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": false
-          },
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(movingAverage(scaleToSeconds(apps.[[app]].[[server]].counters.requests.count,60),10),1,2)"
-            }
-          ],
-          "aliasColors": {
-            "highres.test": "#1F78C1",
-            "scale(highres.test,3)": "#6ED0E0",
-            "mobile": "#6ED0E0",
-            "tablet": "#EAB839"
-          },
-          "aliasYAxis": {},
-          "title": "Sessions / min"
-        }
-      ],
-      "notice": false
-    }
-  ],
-  "editable": true,
-  "failover": false,
-  "panel_hints": true,
-  "style": "dark",
-  "pulldowns": [
-    {
-      "type": "filtering",
-      "collapse": false,
-      "notice": false,
-      "enable": true
-    },
-    {
-      "type": "annotations",
-      "enable": false
-    }
-  ],
-  "nav": [
-    {
-      "type": "timepicker",
-      "collapse": false,
-      "notice": false,
-      "enable": true,
-      "status": "Stable",
-      "time_options": [
-        "5m",
-        "15m",
-        "1h",
-        "6h",
-        "12h",
-        "24h",
-        "2d",
-        "7d",
-        "30d"
-      ],
-      "refresh_intervals": [
-        "5s",
-        "10s",
-        "30s",
-        "1m",
-        "5m",
-        "15m",
-        "30m",
-        "1h",
-        "2h",
-        "1d"
-      ],
-      "now": true
-    }
-  ],
-  "loader": {
-    "save_gist": false,
-    "save_elasticsearch": true,
-    "save_local": true,
-    "save_default": true,
-    "save_temp": true,
-    "save_temp_ttl_enable": true,
-    "save_temp_ttl": "30d",
-    "load_gist": false,
-    "load_elasticsearch": true,
-    "load_elasticsearch_size": 20,
-    "load_local": false,
-    "hide": false
-  },
-  "refresh": false,
-  "tags": [
-    "showcase",
-    "templated"
-  ],
-  "timezone": "browser"
-}

+ 0 - 272
data/dashboards/templated-graphs.json

@@ -1,272 +0,0 @@
-{
-  "title": "Templated Graphs",
-  "services": {
-    "filter": {
-      "list": [
-        {
-          "type": "filter",
-          "name": "root",
-          "query": "statsd.fakesite.counters.session_start.*",
-          "includeAll": true,
-          "options": [
-            {
-              "text": "All",
-              "value": "{desktop,mobile,tablet}"
-            },
-            {
-              "text": "desktop",
-              "value": "desktop"
-            },
-            {
-              "text": "mobile",
-              "value": "mobile"
-            },
-            {
-              "text": "tablet",
-              "value": "tablet"
-            }
-          ],
-          "current": {
-            "text": "All",
-            "value": "{desktop,mobile,tablet}"
-          }
-        }
-      ],
-      "time": {
-        "from": "now-6h",
-        "to": "now"
-      }
-    }
-  },
-  "rows": [
-    {
-      "title": "Row1",
-      "height": "350px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 12,
-          "editable": true,
-          "type": "graphite",
-          "loadingEditor": false,
-          "datasource": null,
-          "renderer": "flot",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "short",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "annotate": {
-            "enable": false
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 1,
-          "linewidth": 1,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": false,
-          "legend": {
-            "show": true,
-            "values": false,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": false
-          },
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(summarize(statsd.fakesite.counters.session_start.[[root]].count,'1min','sum'),4)"
-            }
-          ],
-          "aliasColors": {
-            "highres.test": "#1F78C1",
-            "scale(highres.test,3)": "#6ED0E0",
-            "mobile": "#6ED0E0",
-            "tablet": "#EAB839"
-          },
-          "aliasYAxis": {},
-          "title": "Device sessions"
-        }
-      ],
-      "notice": false
-    },
-    {
-      "title": "Row1",
-      "height": "350px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 6,
-          "editable": true,
-          "type": "graphite",
-          "loadingEditor": false,
-          "datasource": null,
-          "renderer": "flot",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "short",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "annotate": {
-            "enable": false
-          },
-          "resolution": 100,
-          "lines": false,
-          "fill": 1,
-          "linewidth": 2,
-          "points": false,
-          "pointradius": 5,
-          "bars": true,
-          "stack": true,
-          "legend": {
-            "show": true,
-            "values": false,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": false
-          },
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(summarize(statsd.fakesite.counters.session_start.[[root]].count,'1h','sum'),4)"
-            }
-          ],
-          "aliasColors": {
-            "highres.test": "#1F78C1",
-            "scale(highres.test,3)": "#6ED0E0",
-            "tablet": "#EAB839",
-            "desktop": "#7EB26D",
-            "mobile": "#6ED0E0"
-          },
-          "aliasYAxis": {},
-          "title": "Device sessions (1h)"
-        },
-        {
-          "error": false,
-          "span": 6,
-          "editable": true,
-          "type": "text",
-          "loadingEditor": false,
-          "mode": "markdown",
-          "content": "#### Templated metric queries / graphs \n- In dashboard settings, in the Controls tab / Feature toggles. You can enable 'Filtering' \n- This feature when enabled will show you a bar bellow the menu.\n- In this bar you can add filters, or what should be named templated metric segments. \n- A filter is a query for a specific metric segment\n- Open any graph in this dashboard and edit mode and you can see that the [[device]] filter is used instead of a wildcard.\n- Try clicking the All link in the filter menu at the top, change device and see that all graphs change to only show values for that device. ",
-          "style": {},
-          "title": "Description"
-        }
-      ],
-      "notice": false
-    }
-  ],
-  "editable": true,
-  "failover": false,
-  "panel_hints": true,
-  "style": "dark",
-  "pulldowns": [
-    {
-      "type": "filtering",
-      "collapse": false,
-      "notice": false,
-      "enable": true
-    },
-    {
-      "type": "annotations",
-      "enable": false
-    }
-  ],
-  "nav": [
-    {
-      "type": "timepicker",
-      "collapse": false,
-      "notice": false,
-      "enable": true,
-      "status": "Stable",
-      "time_options": [
-        "5m",
-        "15m",
-        "1h",
-        "6h",
-        "12h",
-        "24h",
-        "2d",
-        "7d",
-        "30d"
-      ],
-      "refresh_intervals": [
-        "5s",
-        "10s",
-        "30s",
-        "1m",
-        "5m",
-        "15m",
-        "30m",
-        "1h",
-        "2h",
-        "1d"
-      ],
-      "now": true
-    }
-  ],
-  "loader": {
-    "save_gist": false,
-    "save_elasticsearch": true,
-    "save_local": true,
-    "save_default": true,
-    "save_temp": true,
-    "save_temp_ttl_enable": true,
-    "save_temp_ttl": "30d",
-    "load_gist": false,
-    "load_elasticsearch": true,
-    "load_elasticsearch_size": 20,
-    "load_local": false,
-    "hide": false
-  },
-  "refresh": false,
-  "tags": [
-    "showcase",
-    "templated"
-  ],
-  "timezone": "browser"
-}

File diff suppressed because it is too large
+ 0 - 0
data/dashboards/testing-save.json


+ 0 - 519
data/dashboards/white-theme.json

@@ -1,519 +0,0 @@
-{
-  "title": "White theme",
-  "services": {
-    "filter": {
-      "list": [],
-      "time": {
-        "from": "now-1h",
-        "to": "now"
-      }
-    }
-  },
-  "rows": [
-    {
-      "title": "test",
-      "height": "250px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 6,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "short",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 3,
-          "linewidth": 2,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": true,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": false,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": false
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(scaleToSeconds(apps.fakesite.*.counters.requests.count,1),2)"
-            }
-          ],
-          "aliasColors": {
-            "web_server_04": "#3F6833",
-            "web_server_03": "#508642",
-            "web_server_02": "#7EB26D",
-            "web_server_01": "#B7DBAB"
-          },
-          "aliasYAxis": {},
-          "title": "server requests",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        },
-        {
-          "span": 6,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "short",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 1,
-          "linewidth": 2,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": true,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": true,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": true
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(movingAverage(scaleToSeconds(apps.fakesite.*.counters.request_status.code_302.count,1),4),2)"
-            }
-          ],
-          "aliasColors": {
-            "logins": "#7EB26D",
-            "logins (-1 day)": "#447EBC"
-          },
-          "aliasYAxis": {},
-          "title": "logins",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        }
-      ],
-      "notice": false
-    },
-    {
-      "title": "",
-      "height": "300px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 4,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "bytes",
-            "none"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 0,
-          "linewidth": 2,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": false,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": true,
-            "min": true,
-            "max": false,
-            "current": true,
-            "total": false,
-            "avg": false
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "alias(scale(scaleToSeconds(apps.fakesite.web_server_01.counters.requests.count,1),1000000),'memory')"
-            },
-            {
-              "target": "alias(scaleToSeconds(apps.fakesite.web_server_02.counters.requests.count,1),'cpu')"
-            }
-          ],
-          "aliasColors": {
-            "cpu": "#E24D42"
-          },
-          "aliasYAxis": {
-            "cpu": 2
-          },
-          "title": "Memory / CPU",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        },
-        {
-          "span": 8,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "ms",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": false,
-          "fill": 1,
-          "linewidth": 2,
-          "points": false,
-          "pointradius": 5,
-          "bars": true,
-          "stack": true,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": true,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": true
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(statsd.fakesite.timers.ads_timer.*,4)"
-            }
-          ],
-          "aliasColors": {
-            "upper_75": "#EAB839",
-            "upper_50": "#7EB26D",
-            "upper_25": "#BA43A9"
-          },
-          "aliasYAxis": {},
-          "title": "client side full page load",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        }
-      ],
-      "notice": false
-    },
-    {
-      "title": "test",
-      "height": "250px",
-      "editable": true,
-      "collapse": false,
-      "collapsable": true,
-      "panels": [
-        {
-          "span": 6,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "short",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 1,
-          "linewidth": 2,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": true,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": true,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": true
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(movingAverage(scaleToSeconds(apps.fakesite.*.counters.request_status.code_302.count,1),4),2)"
-            }
-          ],
-          "aliasColors": {
-            "logins": "#7EB26D",
-            "logins (-1 day)": "#447EBC",
-            "web_server_03": "#1F78C1",
-            "web_server_02": "#6ED0E0",
-            "web_server_01": "#64B0C8"
-          },
-          "aliasYAxis": {},
-          "title": "logins",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        },
-        {
-          "span": 6,
-          "editable": true,
-          "type": "graphite",
-          "x-axis": true,
-          "y-axis": true,
-          "scale": 1,
-          "y_formats": [
-            "short",
-            "short"
-          ],
-          "grid": {
-            "max": null,
-            "min": 0,
-            "threshold1": null,
-            "threshold2": null,
-            "threshold1Color": "rgba(216, 200, 27, 0.27)",
-            "threshold2Color": "rgba(234, 112, 112, 0.22)"
-          },
-          "resolution": 100,
-          "lines": true,
-          "fill": 1,
-          "linewidth": 2,
-          "points": false,
-          "pointradius": 5,
-          "bars": false,
-          "stack": true,
-          "spyable": true,
-          "options": false,
-          "legend": {
-            "show": true,
-            "values": true,
-            "min": false,
-            "max": false,
-            "current": false,
-            "total": false,
-            "avg": true
-          },
-          "interactive": true,
-          "legend_counts": true,
-          "timezone": "browser",
-          "percentage": false,
-          "zerofill": true,
-          "nullPointMode": "connected",
-          "steppedLine": false,
-          "tooltip": {
-            "value_type": "cumulative",
-            "query_as_alias": true
-          },
-          "targets": [
-            {
-              "target": "aliasByNode(movingAverage(scaleToSeconds(apps.fakesite.*.counters.request_status.code_302.count,1),4),2)"
-            }
-          ],
-          "aliasColors": {
-            "logins": "#7EB26D",
-            "logins (-1 day)": "#447EBC",
-            "web_server_03": "#E24D42",
-            "web_server_02": "#EF843C",
-            "web_server_01": "#EAB839"
-          },
-          "aliasYAxis": {},
-          "title": "logins",
-          "datasource": null,
-          "renderer": "flot",
-          "annotate": {
-            "enable": false
-          }
-        }
-      ],
-      "notice": false
-    }
-  ],
-  "editable": true,
-  "failover": false,
-  "panel_hints": true,
-  "style": "light",
-  "pulldowns": [
-    {
-      "type": "filtering",
-      "collapse": false,
-      "notice": false,
-      "enable": false
-    },
-    {
-      "type": "annotations",
-      "enable": false
-    }
-  ],
-  "nav": [
-    {
-      "type": "timepicker",
-      "collapse": false,
-      "notice": false,
-      "enable": true,
-      "status": "Stable",
-      "time_options": [
-        "5m",
-        "15m",
-        "1h",
-        "6h",
-        "12h",
-        "24h",
-        "2d",
-        "7d",
-        "30d"
-      ],
-      "refresh_intervals": [
-        "5s",
-        "10s",
-        "30s",
-        "1m",
-        "5m",
-        "15m",
-        "30m",
-        "1h",
-        "2h",
-        "1d"
-      ],
-      "now": true
-    }
-  ],
-  "loader": {
-    "save_gist": false,
-    "save_elasticsearch": true,
-    "save_local": true,
-    "save_default": true,
-    "save_temp": true,
-    "save_temp_ttl_enable": true,
-    "save_temp_ttl": "30d",
-    "load_gist": false,
-    "load_elasticsearch": true,
-    "load_elasticsearch_size": 20,
-    "load_local": false,
-    "hide": false
-  },
-  "refresh": false,
-  "tags": [],
-  "timezone": "browser"
-}

BIN
data/png/77dedee4f4d479cea6f9d608353ed098.png


BIN
data/png/9e956287bee2bc4eabd91f9bb06ab458.png


+ 16 - 0
docker/blocks/influxdb/Dockerfile

@@ -0,0 +1,16 @@
+# influxdb
+
+FROM ubuntu
+
+RUN mkdir -p /opt/influxdb/shared/data
+
+ADD http://s3.amazonaws.com/influxdb/influxdb_0.8.8_amd64.deb /influx88.deb
+RUN dpkg -i /influx88.deb
+RUN rm -rf /opt/influxdb/shared/data
+
+ADD config.toml /opt/influxdb/shared/config.toml
+
+EXPOSE 8083 8086 2004
+
+ENTRYPOINT ["/usr/bin/influxdb"]
+CMD ["-config=/opt/influxdb/shared/config.toml"]

+ 75 - 0
docker/blocks/influxdb/config.toml

@@ -0,0 +1,75 @@
+bind-address = "0.0.0.0"
+
+[logging]
+level  = "debug"
+file   = "/opt/influxdb/shared/data/influxdb.log"         # stdout to log to standard out
+
+[admin]
+port   = 8083              # binding is disabled if the port isn't set
+assets = "/opt/influxdb/current/admin"
+
+[api]
+port     = 8086    # binding is disabled if the port isn't set
+
+read-timeout = "5s"
+
+[input_plugins]
+
+  [input_plugins.graphite]
+  enabled = true
+  port = 2004
+  database = "graphite"  # store graphite data in this database
+
+
+[raft]
+port = 8090
+dir  = "/opt/influxdb/shared/data/raft"
+
+[storage]
+dir = "/opt/influxdb/shared/data/db"
+# How many requests to potentially buffer in memory. If the buffer gets filled then writes
+# will still be logged and once the local storage has caught up (or compacted) the writes
+# will be replayed from the WAL
+write-buffer-size = 10000
+default-engine = "rocksdb"
+max-open-shards = 0
+point-batch-size = 100
+write-batch-size = 5000000
+retention-sweep-period = "10m"
+
+[storage.engines.rocksdb]
+max-open-files = 1000
+lru-cache-size = "200m"
+
+[storage.engines.leveldb]
+max-open-files = 1000
+lru-cache-size = "200m"
+
+[cluster]
+protobuf_port = 8099
+protobuf_timeout = "2s" # the write timeout on the protobuf conn any duration parseable by time.ParseDuration
+protobuf_heartbeat = "200ms" # the heartbeat interval between the servers. must be parseable by time.ParseDuration
+protobuf_min_backoff = "1s" # the minimum backoff after a failed heartbeat attempt
+protobuf_max_backoff = "10s" # the maxmimum backoff after a failed heartbeat attempt
+write-buffer-size = 10000
+ax-response-buffer-size = 100000
+oncurrent-shard-query-limit = 10
+
+[sharding]
+  replication-factor = 1
+
+  [sharding.short-term]
+  duration = "7d"
+  split = 1
+
+  [sharding.long-term]
+  duration = "30d"
+  split = 1
+  # split-random = "/^Hf.*/"
+
+[wal]
+dir   = "/opt/influxdb/shared/data/wal"
+flush-after = 1000 # the number of writes after which wal will be flushed, 0 for flushing on every write
+bookmark-after = 1000 # the number of writes after which a bookmark will be created
+index-after = 1000
+requests-per-logfile = 10000

+ 6 - 0
docker/blocks/influxdb/fig

@@ -0,0 +1,6 @@
+influxdb:
+  build: blocks/influxdb
+  ports:
+    - "2004:2004"
+    - "8083:8083"
+    - "8086:8086"

+ 0 - 18
docker/fig.yml

@@ -1,18 +0,0 @@
-mysqltests:
-  image: mysql:latest
-  environment:
-    MYSQL_ROOT_PASSWORD: rootpass
-    MYSQL_DATABASE: grafana_tests
-    MYSQL_USER: grafana
-    MYSQL_PASSWORD: password
-  ports:
-    - "3306:3306"
-
-postgrestest:
-  image: postgres:latest
-  environment:
-    POSTGRES_USER: grafanatest
-    POSTGRES_PASSWORD: grafanatest
-  ports:
-    - "5432:5432"
-

+ 2 - 1
Dockerfile → docker/production/Dockerfile

@@ -1,4 +1,4 @@
-FROM phusion/baseimage
+FROM debian:jessie
 
 RUN apt-get -y update
 RUN apt-get -y install libfontconfig
@@ -10,6 +10,7 @@ ADD tmp/ /opt/grafana/
 EXPOSE 3000
 
 VOLUME ["/opt/grafana/data"]
+VOLUME ["/opt/grafana/conf"]
 
 WORKDIR /opt/grafana/
 ENTRYPOINT ["./grafana", "web"]

+ 31 - 0
docker/production/README.md

@@ -0,0 +1,31 @@
+
+# Grafana docker image
+
+This container currently only contains the in development alpha of Grafana 2.0 (ie non production use). The
+`#develop` tag is constantly updated as we make progress torwards a beta release.
+
+
+## Running your Grafana image
+--------------------------
+
+Start your image binding the external port `3000`.
+
+   docker run -i -p 3000:3000 grafana/grafana
+
+Try it out, default admin user is admin/admin.
+
+
+## Configuring your Grafana container
+
+All options defined in conf/grafana.ini can be overriden using environment variables, for example:
+
+
+```
+docker run -i -p 3000:3000 \
+  -e "GF_SERVER_ROOT_URL=http://grafana.server.name"  \
+  -e "GF_SECURITY_ADMIN_PASSWORD=secret  \
+  grafana/grafana:develop
+```
+
+
+

+ 15 - 0
docker/production/build.sh

@@ -0,0 +1,15 @@
+#!/bin/bash
+
+cp Dockerfile ../../
+cd ../../
+
+go run build.go build
+
+grunt release
+
+docker build --tag "grafana/grafana:develop" .
+
+rm Dockerfile
+cd docker/production
+
+

+ 5 - 0
docker/production/test_container.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+
+docker run -i -p 3001:3000 \
+  -e "GF_SERVER_ROOT_URL=http://grafana.server.name"  \
+  grafana/grafana:develop

+ 16 - 2
main.go

@@ -31,8 +31,22 @@ func main() {
 	app.Name = "Grafana Backend"
 	app.Usage = "grafana web"
 	app.Version = version
-	app.Commands = []cli.Command{cmd.CmdWeb, cmd.CmdImportJson}
-	app.Flags = append(app.Flags, []cli.Flag{}...)
+	app.Commands = []cli.Command{
+		cmd.ListOrgs,
+		cmd.CreateOrg,
+		cmd.DeleteOrg,
+		cmd.ImportDashboard,
+		cmd.ListDataSources,
+		cmd.CreateDataSource,
+		cmd.DescribeDataSource,
+		cmd.DeleteDataSource,
+		cmd.Web}
+	app.Flags = append(app.Flags, []cli.Flag{
+		cli.StringFlag{
+			Name:  "config",
+			Usage: "path to grafana.ini config file",
+		},
+	}...)
 	app.Run(os.Args)
 
 	log.Close()

+ 0 - 50
pkg/api/account.go

@@ -1,50 +0,0 @@
-package api
-
-import (
-	"github.com/grafana/grafana/pkg/bus"
-	"github.com/grafana/grafana/pkg/middleware"
-	m "github.com/grafana/grafana/pkg/models"
-)
-
-func GetAccount(c *middleware.Context) {
-	query := m.GetAccountByIdQuery{Id: c.AccountId}
-
-	if err := bus.Dispatch(&query); err != nil {
-		if err == m.ErrAccountNotFound {
-			c.JsonApiErr(404, "Account not found", err)
-			return
-		}
-
-		c.JsonApiErr(500, "Failed to get account", err)
-		return
-	}
-
-	account := m.AccountDTO{
-		Id:   query.Result.Id,
-		Name: query.Result.Name,
-	}
-
-	c.JSON(200, &account)
-}
-
-func CreateAccount(c *middleware.Context, cmd m.CreateAccountCommand) {
-	cmd.UserId = c.UserId
-
-	if err := bus.Dispatch(&cmd); err != nil {
-		c.JsonApiErr(500, "Failed to create account", err)
-		return
-	}
-
-	c.JsonOK("Account created")
-}
-
-func UpdateAccount(c *middleware.Context, cmd m.UpdateAccountCommand) {
-	cmd.AccountId = c.AccountId
-
-	if err := bus.Dispatch(&cmd); err != nil {
-		c.JsonApiErr(500, "Failed to update account", err)
-		return
-	}
-
-	c.JsonOK("Account updated")
-}

+ 29 - 0
pkg/api/admin_settings.go

@@ -0,0 +1,29 @@
+package api
+
+import (
+	"strings"
+
+	"github.com/grafana/grafana/pkg/middleware"
+	"github.com/grafana/grafana/pkg/setting"
+)
+
+func AdminGetSettings(c *middleware.Context) {
+	settings := make(map[string]interface{})
+
+	for _, section := range setting.Cfg.Sections() {
+		jsonSec := make(map[string]interface{})
+		settings[section.Name()] = jsonSec
+
+		for _, key := range section.Keys() {
+			keyName := key.Name()
+			value := key.Value()
+			if strings.Contains(keyName, "secret") || strings.Contains(keyName, "password") {
+				value = "************"
+			}
+
+			jsonSec[keyName] = value
+		}
+	}
+
+	c.JSON(200, settings)
+}

+ 138 - 5
pkg/api/admin_users.go

@@ -1,20 +1,153 @@
 package api
 
 import (
+	"github.com/grafana/grafana/pkg/api/dtos"
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/middleware"
 	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/util"
 )
 
 func AdminSearchUsers(c *middleware.Context) {
-	// query := c.QueryStrings("q")
-	// page := c.QueryStrings("p")
-
-	query := m.SearchUsersQuery{Query: "", Page: 0, Limit: 20}
+	query := m.SearchUsersQuery{Query: "", Page: 0, Limit: 1000}
 	if err := bus.Dispatch(&query); err != nil {
-		c.JsonApiErr(500, "Failed to fetch collaboratos", err)
+		c.JsonApiErr(500, "Failed to fetch users", err)
 		return
 	}
 
 	c.JSON(200, query.Result)
 }
+
+func AdminGetUser(c *middleware.Context) {
+	userId := c.ParamsInt64(":id")
+
+	query := m.GetUserByIdQuery{Id: userId}
+
+	if err := bus.Dispatch(&query); err != nil {
+		c.JsonApiErr(500, "Failed to fetch user", err)
+		return
+	}
+
+	result := m.UserDTO{
+		Name:           query.Result.Name,
+		Email:          query.Result.Email,
+		Login:          query.Result.Login,
+		IsGrafanaAdmin: query.Result.IsAdmin,
+	}
+
+	c.JSON(200, result)
+}
+
+func AdminCreateUser(c *middleware.Context, form dtos.AdminCreateUserForm) {
+	cmd := m.CreateUserCommand{
+		Login:    form.Login,
+		Email:    form.Email,
+		Password: form.Password,
+		Name:     form.Name,
+	}
+
+	if len(cmd.Login) == 0 {
+		cmd.Login = cmd.Email
+		if len(cmd.Login) == 0 {
+			c.JsonApiErr(400, "Validation error, need specify either username or email", nil)
+			return
+		}
+	}
+
+	if len(cmd.Password) < 4 {
+		c.JsonApiErr(400, "Password is missing or too short", nil)
+		return
+	}
+
+	if err := bus.Dispatch(&cmd); err != nil {
+		c.JsonApiErr(500, "failed to create user", err)
+		return
+	}
+
+	c.JsonOK("User created")
+}
+
+func AdminUpdateUser(c *middleware.Context, form dtos.AdminUpdateUserForm) {
+	userId := c.ParamsInt64(":id")
+
+	cmd := m.UpdateUserCommand{
+		UserId: userId,
+		Login:  form.Login,
+		Email:  form.Email,
+		Name:   form.Name,
+	}
+
+	if len(cmd.Login) == 0 {
+		cmd.Login = cmd.Email
+		if len(cmd.Login) == 0 {
+			c.JsonApiErr(400, "Validation error, need specify either username or email", nil)
+			return
+		}
+	}
+
+	if err := bus.Dispatch(&cmd); err != nil {
+		c.JsonApiErr(500, "failed to update user", err)
+		return
+	}
+
+	c.JsonOK("User updated")
+}
+
+func AdminUpdateUserPassword(c *middleware.Context, form dtos.AdminUpdateUserPasswordForm) {
+	userId := c.ParamsInt64(":id")
+
+	if len(form.Password) < 4 {
+		c.JsonApiErr(400, "New password too short", nil)
+		return
+	}
+
+	userQuery := m.GetUserByIdQuery{Id: userId}
+
+	if err := bus.Dispatch(&userQuery); err != nil {
+		c.JsonApiErr(500, "Could not read user from database", err)
+		return
+	}
+
+	passwordHashed := util.EncodePassword(form.Password, userQuery.Result.Salt)
+
+	cmd := m.ChangeUserPasswordCommand{
+		UserId:      userId,
+		NewPassword: passwordHashed,
+	}
+
+	if err := bus.Dispatch(&cmd); err != nil {
+		c.JsonApiErr(500, "Failed to update user password", err)
+		return
+	}
+
+	c.JsonOK("User password updated")
+}
+
+func AdminUpdateUserPermissions(c *middleware.Context, form dtos.AdminUpdateUserPermissionsForm) {
+	userId := c.ParamsInt64(":id")
+
+	cmd := m.UpdateUserPermissionsCommand{
+		UserId:         userId,
+		IsGrafanaAdmin: form.IsGrafanaAdmin,
+	}
+
+	if err := bus.Dispatch(&cmd); err != nil {
+		c.JsonApiErr(500, "Failed to update user permissions", err)
+		return
+	}
+
+	c.JsonOK("User permissions updated")
+}
+
+func AdminDeleteUser(c *middleware.Context) {
+	userId := c.ParamsInt64(":id")
+
+	cmd := m.DeleteUserCommand{UserId: userId}
+
+	if err := bus.Dispatch(&cmd); err != nil {
+		c.JsonApiErr(500, "Failed to delete user", err)
+		return
+	}
+
+	c.JsonOK("User deleted")
+}

+ 29 - 15
pkg/api/api.go

@@ -25,12 +25,15 @@ func Register(r *macaron.Macaron) {
 
 	// authed views
 	r.Get("/profile/", reqSignedIn, Index)
-	r.Get("/account/", reqSignedIn, Index)
-	r.Get("/account/datasources/", reqSignedIn, Index)
-	r.Get("/account/users/", reqSignedIn, Index)
-	r.Get("/account/apikeys/", reqSignedIn, Index)
-	r.Get("/account/import/", reqSignedIn, Index)
+	r.Get("/org/", reqSignedIn, Index)
+	r.Get("/datasources/", reqSignedIn, Index)
+	r.Get("/org/users/", reqSignedIn, Index)
+	r.Get("/org/apikeys/", reqSignedIn, Index)
+	r.Get("/dashboard/import/", reqSignedIn, Index)
+	r.Get("/admin/settings", reqGrafanaAdmin, Index)
 	r.Get("/admin/users", reqGrafanaAdmin, Index)
+	r.Get("/admin/users/create", reqGrafanaAdmin, Index)
+	r.Get("/admin/users/edit/:id", reqGrafanaAdmin, Index)
 	r.Get("/dashboard/*", reqSignedIn, Index)
 
 	// sign up
@@ -43,20 +46,21 @@ func Register(r *macaron.Macaron) {
 		r.Group("/user", func() {
 			r.Get("/", GetUser)
 			r.Put("/", bind(m.UpdateUserCommand{}), UpdateUser)
-			r.Post("/using/:id", SetUsingAccount)
-			r.Get("/accounts", GetUserAccounts)
+			r.Post("/using/:id", UserSetUsingOrg)
+			r.Get("/orgs", GetUserOrgList)
 			r.Post("/stars/dashboard/:id", StarDashboard)
 			r.Delete("/stars/dashboard/:id", UnstarDashboard)
+			r.Put("/password", bind(m.ChangeUserPasswordCommand{}), ChangeUserPassword)
 		})
 
 		// account
-		r.Group("/account", func() {
-			r.Get("/", GetAccount)
-			r.Post("/", bind(m.CreateAccountCommand{}), CreateAccount)
-			r.Put("/", bind(m.UpdateAccountCommand{}), UpdateAccount)
-			r.Post("/users", bind(m.AddAccountUserCommand{}), AddAccountUser)
-			r.Get("/users", GetAccountUsers)
-			r.Delete("/users/:id", RemoveAccountUser)
+		r.Group("/org", func() {
+			r.Get("/", GetOrg)
+			r.Post("/", bind(m.CreateOrgCommand{}), CreateOrg)
+			r.Put("/", bind(m.UpdateOrgCommand{}), UpdateOrg)
+			r.Post("/users", bind(m.AddOrgUserCommand{}), AddOrgUser)
+			r.Get("/users", GetOrgUsers)
+			r.Delete("/users/:id", RemoveOrgUser)
 		}, reqAccountAdmin)
 
 		// auth api keys
@@ -70,9 +74,12 @@ func Register(r *macaron.Macaron) {
 		r.Group("/datasources", func() {
 			r.Combo("/").Get(GetDataSources).Put(AddDataSource).Post(UpdateDataSource)
 			r.Delete("/:id", DeleteDataSource)
-			r.Any("/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
+			r.Get("/:id", GetDataSourceById)
 		}, reqAccountAdmin)
 
+		r.Get("/frontend/settings/", GetFrontendSettings)
+		r.Any("/datasources/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
+
 		// Dashboard
 		r.Group("/dashboards", func() {
 			r.Combo("/db/:slug").Get(GetDashboard).Delete(DeleteDashboard)
@@ -89,7 +96,14 @@ func Register(r *macaron.Macaron) {
 
 	// admin api
 	r.Group("/api/admin", func() {
+		r.Get("/settings", AdminGetSettings)
 		r.Get("/users", AdminSearchUsers)
+		r.Get("/users/:id", AdminGetUser)
+		r.Post("/users", bind(dtos.AdminCreateUserForm{}), AdminCreateUser)
+		r.Put("/users/:id/details", bind(dtos.AdminUpdateUserForm{}), AdminUpdateUser)
+		r.Put("/users/:id/password", bind(dtos.AdminUpdateUserPasswordForm{}), AdminUpdateUserPassword)
+		r.Put("/users/:id/permissions", bind(dtos.AdminUpdateUserPermissionsForm{}), AdminUpdateUserPermissions)
+		r.Delete("/users/:id", AdminDeleteUser)
 	}, reqGrafanaAdmin)
 
 	// rendering

+ 20 - 3
pkg/api/apikey.go

@@ -8,7 +8,7 @@ import (
 )
 
 func GetApiKeys(c *middleware.Context) {
-	query := m.GetApiKeysQuery{AccountId: c.AccountId}
+	query := m.GetApiKeysQuery{OrgId: c.OrgId}
 
 	if err := bus.Dispatch(&query); err != nil {
 		c.JsonApiErr(500, "Failed to list api keys", err)
@@ -29,7 +29,7 @@ func GetApiKeys(c *middleware.Context) {
 func DeleteApiKey(c *middleware.Context) {
 	id := c.ParamsInt64(":id")
 
-	cmd := &m.DeleteApiKeyCommand{Id: id, AccountId: c.AccountId}
+	cmd := &m.DeleteApiKeyCommand{Id: id, OrgId: c.OrgId}
 
 	err := bus.Dispatch(cmd)
 	if err != nil {
@@ -46,7 +46,7 @@ func AddApiKey(c *middleware.Context, cmd m.AddApiKeyCommand) {
 		return
 	}
 
-	cmd.AccountId = c.AccountId
+	cmd.OrgId = c.OrgId
 	cmd.Key = util.GetRandomString(64)
 
 	if err := bus.Dispatch(&cmd); err != nil {
@@ -62,3 +62,20 @@ func AddApiKey(c *middleware.Context, cmd m.AddApiKeyCommand) {
 
 	c.JSON(200, result)
 }
+
+func UpdateApiKey(c *middleware.Context, cmd m.UpdateApiKeyCommand) {
+	if !cmd.Role.IsValid() {
+		c.JsonApiErr(400, "Invalid role specified", nil)
+		return
+	}
+
+	cmd.OrgId = c.OrgId
+
+	err := bus.Dispatch(&cmd)
+	if err != nil {
+		c.JsonApiErr(500, "Failed to update api key", err)
+		return
+	}
+
+	c.JsonOK("API key updated")
+}

+ 4 - 4
pkg/api/dashboard.go

@@ -29,7 +29,7 @@ func isDasboardStarredByUser(c *middleware.Context, dashId int64) (bool, error)
 func GetDashboard(c *middleware.Context) {
 	slug := c.Params(":slug")
 
-	query := m.GetDashboardQuery{Slug: slug, AccountId: c.AccountId}
+	query := m.GetDashboardQuery{Slug: slug, OrgId: c.OrgId}
 	err := bus.Dispatch(&query)
 	if err != nil {
 		c.JsonApiErr(404, "Dashboard not found", nil)
@@ -54,13 +54,13 @@ func GetDashboard(c *middleware.Context) {
 func DeleteDashboard(c *middleware.Context) {
 	slug := c.Params(":slug")
 
-	query := m.GetDashboardQuery{Slug: slug, AccountId: c.AccountId}
+	query := m.GetDashboardQuery{Slug: slug, OrgId: c.OrgId}
 	if err := bus.Dispatch(&query); err != nil {
 		c.JsonApiErr(404, "Dashboard not found", nil)
 		return
 	}
 
-	cmd := m.DeleteDashboardCommand{Slug: slug, AccountId: c.AccountId}
+	cmd := m.DeleteDashboardCommand{Slug: slug, OrgId: c.OrgId}
 	if err := bus.Dispatch(&cmd); err != nil {
 		c.JsonApiErr(500, "Failed to delete dashboard", err)
 		return
@@ -72,7 +72,7 @@ func DeleteDashboard(c *middleware.Context) {
 }
 
 func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) {
-	cmd.AccountId = c.AccountId
+	cmd.OrgId = c.OrgId
 
 	err := bus.Dispatch(&cmd)
 	if err != nil {

+ 9 - 2
pkg/api/dataproxy.go

@@ -17,6 +17,7 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string) *httputil.ReverseProxy
 	director := func(req *http.Request) {
 		req.URL.Scheme = target.Scheme
 		req.URL.Host = target.Host
+		req.Host = target.Host
 
 		reqQueryVals := req.URL.Query()
 
@@ -25,6 +26,12 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string) *httputil.ReverseProxy
 			reqQueryVals.Add("u", ds.User)
 			reqQueryVals.Add("p", ds.Password)
 			req.URL.RawQuery = reqQueryVals.Encode()
+		} else if ds.Type == m.DS_INFLUXDB_08 {
+			req.URL.Path = util.JoinUrlFragments(target.Path, proxyPath)
+			reqQueryVals.Add("db", ds.Database)
+			reqQueryVals.Add("u", ds.User)
+			reqQueryVals.Add("p", ds.Password)
+			req.URL.RawQuery = reqQueryVals.Encode()
 		} else {
 			req.URL.Path = util.JoinUrlFragments(target.Path, proxyPath)
 		}
@@ -38,8 +45,8 @@ func ProxyDataSourceRequest(c *middleware.Context) {
 	id := c.ParamsInt64(":id")
 
 	query := m.GetDataSourceByIdQuery{
-		Id:        id,
-		AccountId: c.AccountId,
+		Id:    id,
+		OrgId: c.OrgId,
 	}
 
 	err := bus.Dispatch(&query)

+ 1 - 1
pkg/api/dataproxy_test.go

@@ -10,7 +10,7 @@ import (
 	m "github.com/grafana/grafana/pkg/models"
 )
 
-func TestAccountDataAccess(t *testing.T) {
+func TestDataSourceProxy(t *testing.T) {
 
 	Convey("When getting graphite datasource proxy", t, func() {
 		ds := m.DataSource{Url: "htttp://graphite:8080", Type: m.DS_GRAPHITE}

+ 34 - 7
pkg/api/datasources.go

@@ -8,10 +8,9 @@ import (
 )
 
 func GetDataSources(c *middleware.Context) {
-	query := m.GetDataSourcesQuery{AccountId: c.AccountId}
-	err := bus.Dispatch(&query)
+	query := m.GetDataSourcesQuery{OrgId: c.OrgId}
 
-	if err != nil {
+	if err := bus.Dispatch(&query); err != nil {
 		c.JsonApiErr(500, "Failed to query datasources", err)
 		return
 	}
@@ -20,7 +19,7 @@ func GetDataSources(c *middleware.Context) {
 	for i, ds := range query.Result {
 		result[i] = &dtos.DataSource{
 			Id:        ds.Id,
-			AccountId: ds.AccountId,
+			OrgId:     ds.OrgId,
 			Name:      ds.Name,
 			Url:       ds.Url,
 			Type:      ds.Type,
@@ -36,6 +35,34 @@ func GetDataSources(c *middleware.Context) {
 	c.JSON(200, result)
 }
 
+func GetDataSourceById(c *middleware.Context) {
+	query := m.GetDataSourceByIdQuery{
+		Id:    c.ParamsInt64(":id"),
+		OrgId: c.OrgId,
+	}
+
+	if err := bus.Dispatch(&query); err != nil {
+		c.JsonApiErr(500, "Failed to query datasources", err)
+		return
+	}
+
+	ds := query.Result
+
+	c.JSON(200, &dtos.DataSource{
+		Id:        ds.Id,
+		OrgId:     ds.OrgId,
+		Name:      ds.Name,
+		Url:       ds.Url,
+		Type:      ds.Type,
+		Access:    ds.Access,
+		Password:  ds.Password,
+		Database:  ds.Database,
+		User:      ds.User,
+		BasicAuth: ds.BasicAuth,
+		IsDefault: ds.IsDefault,
+	})
+}
+
 func DeleteDataSource(c *middleware.Context) {
 	id := c.ParamsInt64(":id")
 
@@ -44,7 +71,7 @@ func DeleteDataSource(c *middleware.Context) {
 		return
 	}
 
-	cmd := &m.DeleteDataSourceCommand{Id: id, AccountId: c.AccountId}
+	cmd := &m.DeleteDataSourceCommand{Id: id, OrgId: c.OrgId}
 
 	err := bus.Dispatch(cmd)
 	if err != nil {
@@ -63,7 +90,7 @@ func AddDataSource(c *middleware.Context) {
 		return
 	}
 
-	cmd.AccountId = c.AccountId
+	cmd.OrgId = c.OrgId
 
 	if err := bus.Dispatch(&cmd); err != nil {
 		c.JsonApiErr(500, "Failed to add datasource", err)
@@ -81,7 +108,7 @@ func UpdateDataSource(c *middleware.Context) {
 		return
 	}
 
-	cmd.AccountId = c.AccountId
+	cmd.OrgId = c.OrgId
 
 	err := bus.Dispatch(&cmd)
 	if err != nil {

+ 3 - 3
pkg/api/dtos/models.go

@@ -19,8 +19,8 @@ type CurrentUser struct {
 	Login          string     `json:"login"`
 	Email          string     `json:"email"`
 	Name           string     `json:"name"`
-	AccountRole    m.RoleType `json:"accountRole"`
-	AccountName    string     `json:"acountName"`
+	OrgRole        m.RoleType `json:"orgRole"`
+	OrgName        string     `json:"orgName"`
 	IsGrafanaAdmin bool       `json:"isGrafanaAdmin"`
 	GravatarUrl    string     `json:"gravatarUrl"`
 }
@@ -38,7 +38,7 @@ type Dashboard struct {
 
 type DataSource struct {
 	Id        int64      `json:"id"`
-	AccountId int64      `json:"accountId"`
+	OrgId     int64      `json:"orgId"`
 	Name      string     `json:"name"`
 	Type      m.DsType   `json:"type"`
 	Access    m.DsAccess `json:"access"`

+ 22 - 0
pkg/api/dtos/user.go

@@ -0,0 +1,22 @@
+package dtos
+
+type AdminCreateUserForm struct {
+	Email    string `json:"email"`
+	Login    string `json:"login"`
+	Name     string `json:"name"`
+	Password string `json:"password" binding:"Required"`
+}
+
+type AdminUpdateUserForm struct {
+	Email string `json:"email"`
+	Login string `json:"login"`
+	Name  string `json:"name"`
+}
+
+type AdminUpdateUserPasswordForm struct {
+	Password string `json:"password" binding:"Required"`
+}
+
+type AdminUpdateUserPermissionsForm struct {
+	IsGrafanaAdmin bool `json:"IsGrafanaAdmin" binding:"Required"`
+}

+ 25 - 6
pkg/api/frontendsettings.go

@@ -9,23 +9,23 @@ import (
 	"github.com/grafana/grafana/pkg/setting"
 )
 
-func getFrontendSettings(c *middleware.Context) (map[string]interface{}, error) {
-	accountDataSources := make([]*m.DataSource, 0)
+func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, error) {
+	orgDataSources := make([]*m.DataSource, 0)
 
 	if c.IsSignedIn {
-		query := m.GetDataSourcesQuery{AccountId: c.AccountId}
+		query := m.GetDataSourcesQuery{OrgId: c.OrgId}
 		err := bus.Dispatch(&query)
 
 		if err != nil {
 			return nil, err
 		}
 
-		accountDataSources = query.Result
+		orgDataSources = query.Result
 	}
 
 	datasources := make(map[string]interface{})
 
-	for _, ds := range accountDataSources {
+	for _, ds := range orgDataSources {
 		url := ds.Url
 
 		if ds.Access == m.DS_ACCESS_PROXY {
@@ -38,7 +38,7 @@ func getFrontendSettings(c *middleware.Context) (map[string]interface{}, error)
 			"default": ds.IsDefault,
 		}
 
-		if ds.Type == m.DS_INFLUXDB {
+		if ds.Type == m.DS_INFLUXDB_08 {
 			if ds.Access == m.DS_ACCESS_DIRECT {
 				dsMap["username"] = ds.User
 				dsMap["password"] = ds.Password
@@ -46,6 +46,15 @@ func getFrontendSettings(c *middleware.Context) (map[string]interface{}, error)
 			}
 		}
 
+		if ds.Type == m.DS_INFLUXDB {
+			if ds.Access == m.DS_ACCESS_DIRECT {
+				dsMap["username"] = ds.User
+				dsMap["password"] = ds.Password
+				dsMap["database"] = ds.Database
+				dsMap["url"] = url
+			}
+		}
+
 		if ds.Type == m.DS_ES {
 			dsMap["index"] = ds.Database
 		}
@@ -71,3 +80,13 @@ func getFrontendSettings(c *middleware.Context) (map[string]interface{}, error)
 
 	return jsonObj, nil
 }
+
+func GetFrontendSettings(c *middleware.Context) {
+	settings, err := getFrontendSettingsMap(c)
+	if err != nil {
+		c.JsonApiErr(400, "Failed to get frontend settings", err)
+		return
+	}
+
+	c.JSON(200, settings)
+}

+ 3 - 3
pkg/api/index.go

@@ -7,7 +7,7 @@ import (
 )
 
 func setIndexViewData(c *middleware.Context) error {
-	settings, err := getFrontendSettings(c)
+	settings, err := getFrontendSettingsMap(c)
 	if err != nil {
 		return err
 	}
@@ -17,8 +17,8 @@ func setIndexViewData(c *middleware.Context) error {
 		Login:          c.Login,
 		Email:          c.Email,
 		Name:           c.Name,
-		AccountName:    c.AccountName,
-		AccountRole:    c.AccountRole,
+		OrgName:        c.OrgName,
+		OrgRole:        c.OrgRole,
 		GravatarUrl:    dtos.GetGravatarUrl(c.Email),
 		IsGrafanaAdmin: c.IsGrafanaAdmin,
 	}

+ 50 - 0
pkg/api/org.go

@@ -0,0 +1,50 @@
+package api
+
+import (
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/middleware"
+	m "github.com/grafana/grafana/pkg/models"
+)
+
+func GetOrg(c *middleware.Context) {
+	query := m.GetOrgByIdQuery{Id: c.OrgId}
+
+	if err := bus.Dispatch(&query); err != nil {
+		if err == m.ErrOrgNotFound {
+			c.JsonApiErr(404, "Organization not found", err)
+			return
+		}
+
+		c.JsonApiErr(500, "Failed to get organization", err)
+		return
+	}
+
+	org := m.OrgDTO{
+		Id:   query.Result.Id,
+		Name: query.Result.Name,
+	}
+
+	c.JSON(200, &org)
+}
+
+func CreateOrg(c *middleware.Context, cmd m.CreateOrgCommand) {
+	cmd.UserId = c.UserId
+
+	if err := bus.Dispatch(&cmd); err != nil {
+		c.JsonApiErr(500, "Failed to create organization", err)
+		return
+	}
+
+	c.JsonOK("Organization created")
+}
+
+func UpdateOrg(c *middleware.Context, cmd m.UpdateOrgCommand) {
+	cmd.OrgId = c.OrgId
+
+	if err := bus.Dispatch(&cmd); err != nil {
+		c.JsonApiErr(500, "Failed to update organization", err)
+		return
+	}
+
+	c.JsonOK("Organization updated")
+}

+ 12 - 12
pkg/api/account_users.go → pkg/api/org_users.go

@@ -6,7 +6,7 @@ import (
 	m "github.com/grafana/grafana/pkg/models"
 )
 
-func AddAccountUser(c *middleware.Context, cmd m.AddAccountUserCommand) {
+func AddOrgUser(c *middleware.Context, cmd m.AddOrgUserCommand) {
 	if !cmd.Role.IsValid() {
 		c.JsonApiErr(400, "Invalid role specified", nil)
 		return
@@ -26,19 +26,19 @@ func AddAccountUser(c *middleware.Context, cmd m.AddAccountUserCommand) {
 		return
 	}
 
-	cmd.AccountId = c.AccountId
+	cmd.OrgId = c.OrgId
 	cmd.UserId = userToAdd.Id
 
 	if err := bus.Dispatch(&cmd); err != nil {
-		c.JsonApiErr(500, "Could not add user to account", err)
+		c.JsonApiErr(500, "Could not add user to organization", err)
 		return
 	}
 
-	c.JsonOK("User added to account")
+	c.JsonOK("User added to organization")
 }
 
-func GetAccountUsers(c *middleware.Context) {
-	query := m.GetAccountUsersQuery{AccountId: c.AccountId}
+func GetOrgUsers(c *middleware.Context) {
+	query := m.GetOrgUsersQuery{OrgId: c.OrgId}
 
 	if err := bus.Dispatch(&query); err != nil {
 		c.JsonApiErr(500, "Failed to get account user", err)
@@ -48,18 +48,18 @@ func GetAccountUsers(c *middleware.Context) {
 	c.JSON(200, query.Result)
 }
 
-func RemoveAccountUser(c *middleware.Context) {
+func RemoveOrgUser(c *middleware.Context) {
 	userId := c.ParamsInt64(":id")
 
-	cmd := m.RemoveAccountUserCommand{AccountId: c.AccountId, UserId: userId}
+	cmd := m.RemoveOrgUserCommand{OrgId: c.OrgId, UserId: userId}
 
 	if err := bus.Dispatch(&cmd); err != nil {
-		if err == m.ErrLastAccountAdmin {
-			c.JsonApiErr(400, "Cannot remove last account admin", nil)
+		if err == m.ErrLastOrgAdmin {
+			c.JsonApiErr(400, "Cannot remove last organization admin", nil)
 			return
 		}
-		c.JsonApiErr(500, "Failed to remove user from account", err)
+		c.JsonApiErr(500, "Failed to remove user from organization", err)
 	}
 
-	c.JsonOK("User removed from account")
+	c.JsonOK("User removed from organization")
 }

+ 2 - 6
pkg/api/search.go

@@ -4,7 +4,6 @@ import (
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/middleware"
 	m "github.com/grafana/grafana/pkg/models"
-	"github.com/grafana/grafana/pkg/setting"
 )
 
 // TODO: this needs to be cached or improved somehow
@@ -45,7 +44,7 @@ func Search(c *middleware.Context) {
 
 	if tagcloud == "true" {
 
-		query := m.GetDashboardTagsQuery{AccountId: c.AccountId}
+		query := m.GetDashboardTagsQuery{OrgId: c.OrgId}
 		err := bus.Dispatch(&query)
 		if err != nil {
 			c.JsonApiErr(500, "Failed to get tags from database", err)
@@ -61,7 +60,7 @@ func Search(c *middleware.Context) {
 			UserId:    c.UserId,
 			Limit:     limit,
 			IsStarred: starred == "true",
-			AccountId: c.AccountId,
+			OrgId:     c.OrgId,
 		}
 
 		err := bus.Dispatch(&query)
@@ -76,9 +75,6 @@ func Search(c *middleware.Context) {
 		}
 
 		result.Dashboards = query.Result
-		for _, dash := range result.Dashboards {
-			dash.Url = setting.ToAbsUrl("dashboard/db/" + dash.Slug)
-		}
 	}
 
 	c.JSON(200, result)

+ 51 - 20
pkg/api/user.go

@@ -4,13 +4,14 @@ import (
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/middleware"
 	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/util"
 )
 
 func GetUser(c *middleware.Context) {
 	query := m.GetUserInfoQuery{UserId: c.UserId}
 
 	if err := bus.Dispatch(&query); err != nil {
-		c.JsonApiErr(500, "Failed to get account", err)
+		c.JsonApiErr(500, "Failed to get user", err)
 		return
 	}
 
@@ -21,23 +22,23 @@ func UpdateUser(c *middleware.Context, cmd m.UpdateUserCommand) {
 	cmd.UserId = c.UserId
 
 	if err := bus.Dispatch(&cmd); err != nil {
-		c.JsonApiErr(400, "Failed to update account", err)
+		c.JsonApiErr(400, "Failed to update user", err)
 		return
 	}
 
-	c.JsonOK("Account updated")
+	c.JsonOK("User updated")
 }
 
-func GetUserAccounts(c *middleware.Context) {
-	query := m.GetUserAccountsQuery{UserId: c.UserId}
+func GetUserOrgList(c *middleware.Context) {
+	query := m.GetUserOrgListQuery{UserId: c.UserId}
 
 	if err := bus.Dispatch(&query); err != nil {
-		c.JsonApiErr(500, "Failed to get user accounts", err)
+		c.JsonApiErr(500, "Failed to get user organizations", err)
 		return
 	}
 
 	for _, ac := range query.Result {
-		if ac.AccountId == c.AccountId {
+		if ac.OrgId == c.OrgId {
 			ac.IsUsing = true
 			break
 		}
@@ -46,17 +47,17 @@ func GetUserAccounts(c *middleware.Context) {
 	c.JSON(200, query.Result)
 }
 
-func validateUsingAccount(userId int64, accountId int64) bool {
-	query := m.GetUserAccountsQuery{UserId: userId}
+func validateUsingOrg(userId int64, orgId int64) bool {
+	query := m.GetUserOrgListQuery{UserId: userId}
 
 	if err := bus.Dispatch(&query); err != nil {
 		return false
 	}
 
-	// validate that the account id in the list
+	// validate that the org id in the list
 	valid := false
 	for _, other := range query.Result {
-		if other.AccountId == accountId {
+		if other.OrgId == orgId {
 			valid = true
 		}
 	}
@@ -64,23 +65,53 @@ func validateUsingAccount(userId int64, accountId int64) bool {
 	return valid
 }
 
-func SetUsingAccount(c *middleware.Context) {
-	usingAccountId := c.ParamsInt64(":id")
+func UserSetUsingOrg(c *middleware.Context) {
+	orgId := c.ParamsInt64(":id")
 
-	if !validateUsingAccount(c.UserId, usingAccountId) {
-		c.JsonApiErr(401, "Not a valid account", nil)
+	if !validateUsingOrg(c.UserId, orgId) {
+		c.JsonApiErr(401, "Not a valid organization", nil)
 		return
 	}
 
-	cmd := m.SetUsingAccountCommand{
-		UserId:    c.UserId,
-		AccountId: usingAccountId,
+	cmd := m.SetUsingOrgCommand{
+		UserId: c.UserId,
+		OrgId:  orgId,
 	}
 
 	if err := bus.Dispatch(&cmd); err != nil {
-		c.JsonApiErr(500, "Failed change active account", err)
+		c.JsonApiErr(500, "Failed change active organization", err)
 		return
 	}
 
-	c.JsonOK("Active account changed")
+	c.JsonOK("Active organization changed")
+}
+
+func ChangeUserPassword(c *middleware.Context, cmd m.ChangeUserPasswordCommand) {
+	userQuery := m.GetUserByIdQuery{Id: c.UserId}
+
+	if err := bus.Dispatch(&userQuery); err != nil {
+		c.JsonApiErr(500, "Could not read user from database", err)
+		return
+	}
+
+	passwordHashed := util.EncodePassword(cmd.OldPassword, userQuery.Result.Salt)
+	if passwordHashed != userQuery.Result.Password {
+		c.JsonApiErr(401, "Invalid old password", nil)
+		return
+	}
+
+	if len(cmd.NewPassword) < 4 {
+		c.JsonApiErr(400, "New password too short", nil)
+		return
+	}
+
+	cmd.UserId = c.UserId
+	cmd.NewPassword = util.EncodePassword(cmd.NewPassword, userQuery.Result.Salt)
+
+	if err := bus.Dispatch(&cmd); err != nil {
+		c.JsonApiErr(500, "Failed to change user password", err)
+		return
+	}
+
+	c.JsonOK("User password changed")
 }

+ 13 - 0
pkg/cmd/common.go

@@ -0,0 +1,13 @@
+package cmd
+
+import (
+	"github.com/codegangsta/cli"
+	"github.com/grafana/grafana/pkg/services/sqlstore"
+	"github.com/grafana/grafana/pkg/setting"
+)
+
+func initRuntime(c *cli.Context) {
+	setting.NewConfigContext(c.GlobalString("config"))
+	sqlstore.NewEngine()
+	sqlstore.EnsureAdminUser()
+}

+ 104 - 0
pkg/cmd/dashboard.go

@@ -0,0 +1,104 @@
+package cmd
+
+import (
+	"encoding/json"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/codegangsta/cli"
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/log"
+	m "github.com/grafana/grafana/pkg/models"
+)
+
+var ImportDashboard = cli.Command{
+	Name:        "dashboards:import",
+	Usage:       "imports dashboards in JSON from a directory",
+	Description: "Starts Grafana import process",
+	Action:      runImport,
+	Flags: []cli.Flag{
+		cli.StringFlag{
+			Name:  "dir",
+			Usage: "path to folder containing json dashboards",
+		},
+	},
+}
+
+func runImport(c *cli.Context) {
+	dir := c.String("dir")
+	if len(dir) == 0 {
+		log.ConsoleFatalf("Missing command flag --dir")
+	}
+
+	file, err := os.Stat(dir)
+	if os.IsNotExist(err) {
+		log.ConsoleFatalf("Directory does not exist: %v", dir)
+	}
+
+	if !file.IsDir() {
+		log.ConsoleFatalf("%v is not a directory", dir)
+	}
+
+	if !c.Args().Present() {
+		log.ConsoleFatal("Organization name arg is required")
+	}
+
+	orgName := c.Args().First()
+
+	initRuntime(c)
+
+	orgQuery := m.GetOrgByNameQuery{Name: orgName}
+	if err := bus.Dispatch(&orgQuery); err != nil {
+		log.ConsoleFatalf("Failed to find account", err)
+	}
+
+	orgId := orgQuery.Result.Id
+
+	visitor := func(path string, f os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+		if f.IsDir() {
+			return nil
+		}
+		if strings.HasSuffix(f.Name(), ".json") {
+			if err := importDashboard(path, orgId); err != nil {
+				log.ConsoleFatalf("Failed to import dashboard file: %v,  err: %v", path, err)
+			}
+		}
+		return nil
+	}
+
+	if err := filepath.Walk(dir, visitor); err != nil {
+		log.ConsoleFatalf("Failed to scan dir for json files: %v", err)
+	}
+}
+
+func importDashboard(path string, orgId int64) error {
+	log.ConsoleInfof("Importing %v", path)
+
+	reader, err := os.Open(path)
+	if err != nil {
+		return err
+	}
+
+	dash := m.NewDashboard("temp")
+	jsonParser := json.NewDecoder(reader)
+
+	if err := jsonParser.Decode(&dash.Data); err != nil {
+		return err
+	}
+	dash.Data["id"] = nil
+
+	cmd := m.SaveDashboardCommand{
+		OrgId:     orgId,
+		Dashboard: dash.Data,
+	}
+
+	if err := bus.Dispatch(&cmd); err != nil {
+		return err
+	}
+
+	return nil
+}

+ 230 - 0
pkg/cmd/datasource.go

@@ -0,0 +1,230 @@
+package cmd
+
+import (
+	"fmt"
+	"os"
+	"text/tabwriter"
+
+	"github.com/codegangsta/cli"
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/log"
+	m "github.com/grafana/grafana/pkg/models"
+)
+
+var (
+	ListDataSources = cli.Command{
+		Name:        "datasources",
+		Usage:       "list datasources",
+		Description: "Lists the datasources in the system",
+		Action:      listDatasources,
+	}
+	CreateDataSource = cli.Command{
+		Name:        "datasources:create",
+		Usage:       "creates a new datasource",
+		Description: "Creates a new datasource",
+		Action:      createDataSource,
+		Flags: []cli.Flag{
+			cli.StringFlag{
+				Name:  "type",
+				Value: "graphite",
+				Usage: fmt.Sprintf("Datasource type [%s,%s,%s,%s]",
+					m.DS_GRAPHITE, m.DS_INFLUXDB, m.DS_ES, m.DS_OPENTSDB),
+			},
+			cli.StringFlag{
+				Name:  "access",
+				Value: "proxy",
+				Usage: "Datasource access [proxy,direct]",
+			},
+			cli.BoolFlag{
+				Name:  "default",
+				Usage: "Make this the default datasource",
+			},
+			cli.StringFlag{
+				Name:  "db",
+				Usage: "InfluxDB DB",
+			},
+			cli.StringFlag{
+				Name:  "user",
+				Usage: "InfluxDB username",
+			},
+			cli.StringFlag{
+				Name:  "password",
+				Usage: "InfluxDB password",
+			},
+		},
+	}
+	DescribeDataSource = cli.Command{
+		Name:        "datasources:info",
+		Usage:       "describe the details of a datasource",
+		Description: "Describes the details of a datasource",
+		Action:      describeDataSource,
+	}
+	DeleteDataSource = cli.Command{
+		Name:        "datasources:delete",
+		Usage:       "Deletes a datasource",
+		Description: "Deletes a datasource",
+		Action:      deleteDataSource,
+	}
+)
+
+func createDataSource(c *cli.Context) {
+	initRuntime(c)
+
+	if len(c.Args()) != 3 {
+		log.ConsoleFatal("Missing required arguments")
+	}
+
+	name := c.Args().First()
+	ds := c.Args()[1]
+	url := c.Args()[2]
+	dsType := c.String("type")
+	dsAccess := c.String("access")
+	dsDefault := c.Bool("default")
+
+	orgQuery := m.GetOrgByNameQuery{Name: name}
+	if err := bus.Dispatch(&orgQuery); err != nil {
+		log.ConsoleFatalf("Failed to find organization: %s", err)
+	}
+
+	orgId := orgQuery.Result.Id
+
+	query := m.GetDataSourceByNameQuery{OrgId: orgId, Name: ds}
+	if err := bus.Dispatch(&query); err != nil {
+		if err != m.ErrDataSourceNotFound {
+			log.ConsoleFatalf("Failed to query for existing datasource: %s", err)
+		}
+	}
+
+	if query.Result.Id > 0 {
+		log.ConsoleFatalf("DataSource %s already exists", ds)
+	}
+
+	cmd := m.AddDataSourceCommand{
+		OrgId:     orgId,
+		Name:      ds,
+		Url:       url,
+		Type:      m.DsType(dsType),
+		Access:    m.DsAccess(dsAccess),
+		IsDefault: dsDefault,
+	}
+
+	switch dsType {
+	case m.DS_INFLUXDB:
+		db := c.String("db")
+		if db == "" {
+			log.ConsoleFatal("db name is required for influxdb datasources")
+		}
+		cmd.Database = db
+		cmd.User = c.String("user")
+		cmd.Password = c.String("password")
+	}
+
+	if err := bus.Dispatch(&cmd); err != nil {
+		log.ConsoleFatalf("Failed to create datasource: %s", err)
+	}
+	datasource := cmd.Result
+
+	log.ConsoleInfof("Datasource %s created", datasource.Name)
+}
+
+func listDatasources(c *cli.Context) {
+	initRuntime(c)
+
+	if !c.Args().Present() {
+		log.ConsoleFatal("Account name arg is required")
+	}
+
+	name := c.Args().First()
+	orgQuery := m.GetOrgByNameQuery{Name: name}
+	if err := bus.Dispatch(&orgQuery); err != nil {
+		log.ConsoleFatalf("Failed to find organization: %s", err)
+	}
+
+	orgId := orgQuery.Result.Id
+
+	query := m.GetDataSourcesQuery{OrgId: orgId}
+	if err := bus.Dispatch(&query); err != nil {
+		log.ConsoleFatalf("Failed to find datasources: %s", err)
+	}
+
+	w := tabwriter.NewWriter(os.Stdout, 8, 1, 4, ' ', 0)
+
+	fmt.Fprintf(w, "ID\tNAME\tURL\tTYPE\tACCESS\tDEFAULT\n")
+	for _, ds := range query.Result {
+		fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%t\n", ds.Id, ds.Name, ds.Url, ds.Type,
+			ds.Access, ds.IsDefault)
+	}
+	w.Flush()
+}
+
+func describeDataSource(c *cli.Context) {
+	initRuntime(c)
+
+	if len(c.Args()) != 2 {
+		log.ConsoleFatal("Organization and datasource name args are required")
+	}
+
+	name := c.Args().First()
+	ds := c.Args()[1]
+
+	orgQuery := m.GetOrgByNameQuery{Name: name}
+	if err := bus.Dispatch(&orgQuery); err != nil {
+		log.ConsoleFatalf("Failed to find organization: %s", err)
+	}
+
+	orgId := orgQuery.Result.Id
+
+	query := m.GetDataSourceByNameQuery{OrgId: orgId, Name: ds}
+	if err := bus.Dispatch(&query); err != nil {
+		log.ConsoleFatalf("Failed to find datasource: %s", err)
+	}
+	datasource := query.Result
+
+	w := tabwriter.NewWriter(os.Stdout, 8, 1, 4, ' ', 0)
+	fmt.Fprintf(w, "NAME\t%s\n", datasource.Name)
+	fmt.Fprintf(w, "URL\t%s\n", datasource.Url)
+	fmt.Fprintf(w, "DEFAULT\t%t\n", datasource.IsDefault)
+	fmt.Fprintf(w, "ACCESS\t%s\n", datasource.Access)
+	fmt.Fprintf(w, "TYPE\t%s\n", datasource.Type)
+
+	switch datasource.Type {
+	case m.DS_INFLUXDB:
+		fmt.Fprintf(w, "DATABASE\t%s\n", datasource.Database)
+		fmt.Fprintf(w, "DB USER\t%s\n", datasource.User)
+		fmt.Fprintf(w, "DB PASSWORD\t%s\n", datasource.Password)
+	case m.DS_ES:
+		fmt.Fprintf(w, "INDEX\t%s\n", datasource.Database)
+	}
+	w.Flush()
+}
+
+func deleteDataSource(c *cli.Context) {
+	initRuntime(c)
+
+	if len(c.Args()) != 2 {
+		log.ConsoleFatal("Account and datasource name args are required")
+	}
+
+	name := c.Args().First()
+	ds := c.Args()[1]
+
+	orgQuery := m.GetOrgByNameQuery{Name: name}
+	if err := bus.Dispatch(&orgQuery); err != nil {
+		log.ConsoleFatalf("Failed to find organization: %s", err)
+	}
+
+	orgId := orgQuery.Result.Id
+
+	query := m.GetDataSourceByNameQuery{OrgId: orgId, Name: ds}
+	if err := bus.Dispatch(&query); err != nil {
+		log.ConsoleFatalf("Failed to find datasource: %s", err)
+	}
+	datasource := query.Result
+
+	cmd := m.DeleteDataSourceCommand{OrgId: orgId, Id: datasource.Id}
+	if err := bus.Dispatch(&cmd); err != nil {
+		log.ConsoleFatalf("Failed to delete datasource: %s", err)
+	}
+
+	log.ConsoleInfof("DataSource %s deleted", ds)
+}

+ 0 - 120
pkg/cmd/import.go

@@ -1,120 +0,0 @@
-package cmd
-
-import (
-	"encoding/json"
-	"os"
-	"path/filepath"
-	"strings"
-
-	"github.com/codegangsta/cli"
-	"github.com/grafana/grafana/pkg/bus"
-	"github.com/grafana/grafana/pkg/log"
-	m "github.com/grafana/grafana/pkg/models"
-	"github.com/grafana/grafana/pkg/services/sqlstore"
-	"github.com/grafana/grafana/pkg/setting"
-)
-
-var CmdImportJson = cli.Command{
-	Name:        "import-json",
-	Usage:       "grafana import",
-	Description: "Starts Grafana import process",
-	Action:      runImport,
-	Flags: []cli.Flag{
-		cli.StringFlag{
-			Name:  "dir",
-			Usage: "path to folder containing json dashboards",
-		},
-		cli.StringFlag{
-			Name:  "account",
-			Usage: "Account name to save dashboards under",
-		},
-		cli.StringFlag{
-			Name:  "config",
-			Value: "grafana.ini",
-			Usage: "path to config file",
-		},
-	},
-}
-
-func runImport(c *cli.Context) {
-	dir := c.String("dir")
-	if len(dir) == 0 {
-		log.Error(3, "Missing command flag --dir")
-		return
-	}
-
-	file, err := os.Stat(dir)
-	if os.IsNotExist(err) {
-		log.Error(3, "Directory does not exist: %v", dir)
-		return
-	}
-
-	if !file.IsDir() {
-		log.Error(3, "%v is not a directory", dir)
-		return
-	}
-
-	accountName := c.String("account")
-	if len(accountName) == 0 {
-		log.Error(3, "Missing command flag --account")
-		return
-	}
-
-	setting.NewConfigContext()
-	sqlstore.NewEngine()
-	sqlstore.EnsureAdminUser()
-
-	accountQuery := m.GetAccountByNameQuery{Name: accountName}
-	if err := bus.Dispatch(&accountQuery); err != nil {
-		log.Error(3, "Failed to find account", err)
-		return
-	}
-
-	accountId := accountQuery.Result.Id
-
-	visitor := func(path string, f os.FileInfo, err error) error {
-		if err != nil {
-			return err
-		}
-		if f.IsDir() {
-			return nil
-		}
-		if strings.HasSuffix(f.Name(), ".json") {
-			if err := importDashboard(path, accountId); err != nil {
-				log.Error(3, "Failed to import dashboard file: %v,  err: %v", path, err)
-			}
-		}
-		return nil
-	}
-
-	if err := filepath.Walk(dir, visitor); err != nil {
-		log.Error(3, "failed to scan dir for json files: %v", err)
-	}
-}
-
-func importDashboard(path string, accountId int64) error {
-	log.Info("Importing %v", path)
-
-	reader, err := os.Open(path)
-	if err != nil {
-		return err
-	}
-
-	dash := m.NewDashboard("temp")
-	jsonParser := json.NewDecoder(reader)
-
-	if err := jsonParser.Decode(&dash.Data); err != nil {
-		return err
-	}
-
-	cmd := m.SaveDashboardCommand{
-		AccountId: accountId,
-		Dashboard: dash.Data,
-	}
-
-	if err := bus.Dispatch(&cmd); err != nil {
-		return err
-	}
-
-	return nil
-}

+ 99 - 0
pkg/cmd/orgs.go

@@ -0,0 +1,99 @@
+package cmd
+
+import (
+	"fmt"
+	"os"
+	"text/tabwriter"
+
+	"github.com/codegangsta/cli"
+
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/log"
+	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/setting"
+)
+
+var ListOrgs = cli.Command{
+	Name:        "orgs",
+	Usage:       "list organizations",
+	Description: "Lists the organizations in the system",
+	Action:      listOrgs,
+}
+
+var CreateOrg = cli.Command{
+	Name:        "orgs:create",
+	Usage:       "Creates a new organization",
+	Description: "Creates a new organization",
+	Action:      createOrg,
+}
+
+var DeleteOrg = cli.Command{
+	Name:        "orgs:delete",
+	Usage:       "Delete an existing organization",
+	Description: "Deletes an existing organization",
+	Action:      deleteOrg,
+}
+
+func listOrgs(c *cli.Context) {
+	initRuntime(c)
+
+	orgsQuery := m.GetOrgListQuery{}
+	if err := bus.Dispatch(&orgsQuery); err != nil {
+		log.ConsoleFatalf("Failed to find organizations: %s", err)
+	}
+
+	w := tabwriter.NewWriter(os.Stdout, 8, 1, 4, ' ', 0)
+
+	fmt.Fprintf(w, "ID\tNAME\n")
+	for _, org := range orgsQuery.Result {
+		fmt.Fprintf(w, "%d\t%s\n", org.Id, org.Name)
+	}
+	w.Flush()
+}
+
+func createOrg(c *cli.Context) {
+	initRuntime(c)
+
+	if !c.Args().Present() {
+		log.ConsoleFatal("Organization name arg is required")
+	}
+
+	name := c.Args().First()
+
+	adminQuery := m.GetUserByLoginQuery{LoginOrEmail: setting.AdminUser}
+
+	if err := bus.Dispatch(&adminQuery); err == m.ErrUserNotFound {
+		log.ConsoleFatalf("Failed to find default admin user: %s", err)
+	}
+
+	adminUser := adminQuery.Result
+
+	cmd := m.CreateOrgCommand{Name: name, UserId: adminUser.Id}
+	if err := bus.Dispatch(&cmd); err != nil {
+		log.ConsoleFatalf("Failed to create organization: %s", err)
+	}
+
+	log.ConsoleInfof("Organization %s created for admin user %s\n", name, adminUser.Email)
+}
+
+func deleteOrg(c *cli.Context) {
+	initRuntime(c)
+
+	if !c.Args().Present() {
+		log.ConsoleFatal("Organization name arg is required")
+	}
+
+	name := c.Args().First()
+	orgQuery := m.GetOrgByNameQuery{Name: name}
+	if err := bus.Dispatch(&orgQuery); err != nil {
+		log.ConsoleFatalf("Failed to find organization: %s", err)
+	}
+
+	orgId := orgQuery.Result.Id
+	cmd := m.DeleteOrgCommand{Id: orgId}
+	if err := bus.Dispatch(&cmd); err != nil {
+		log.ConsoleFatalf("Failed to delete organization: %s", err)
+	}
+
+	log.ConsoleInfof("Organization %s deleted", name)
+}

+ 4 - 13
pkg/cmd/web.go

@@ -17,23 +17,15 @@ import (
 	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/middleware"
 	"github.com/grafana/grafana/pkg/services/eventpublisher"
-	"github.com/grafana/grafana/pkg/services/sqlstore"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/social"
 )
 
-var CmdWeb = cli.Command{
+var Web = cli.Command{
 	Name:        "web",
-	Usage:       "grafana web",
+	Usage:       "Starts Grafana backend & web server",
 	Description: "Starts Grafana backend & web server",
 	Action:      runWeb,
-	Flags: []cli.Flag{
-		cli.StringFlag{
-			Name:  "config",
-			Value: "grafana.ini",
-			Usage: "path to config file",
-		},
-	},
 }
 
 func newMacaron() *macaron.Macaron {
@@ -78,10 +70,9 @@ func runWeb(c *cli.Context) {
 	log.Info("Starting Grafana")
 	log.Info("Version: %v, Commit: %v, Build date: %v", setting.BuildVersion, setting.BuildCommit, time.Unix(setting.BuildStamp, 0))
 
-	setting.NewConfigContext()
+	initRuntime(c)
+
 	social.NewOAuthService()
-	sqlstore.NewEngine()
-	sqlstore.EnsureAdminUser()
 	eventpublisher.Init()
 
 	var err error

+ 2 - 2
pkg/events/events.go

@@ -50,13 +50,13 @@ func ToOnWriteEvent(event interface{}) (*OnTheWireEvent, error) {
 	return &wireEvent, nil
 }
 
-type AccountCreated struct {
+type OrgCreated struct {
 	Timestamp time.Time `json:"timestamp"`
 	Id        int64     `json:"id"`
 	Name      string    `json:"name"`
 }
 
-type AccountUpdated struct {
+type OrgUpdated struct {
 	Timestamp time.Time `json:"timestamp"`
 	Id        int64     `json:"id"`
 	Name      string    `json:"name"`

+ 92 - 10
pkg/log/console.go

@@ -6,6 +6,7 @@ package log
 
 import (
 	"encoding/json"
+	"fmt"
 	"log"
 	"os"
 	"runtime"
@@ -21,15 +22,26 @@ func NewBrush(color string) Brush {
 	}
 }
 
-var colors = []Brush{
-	NewBrush("1;36"), // Trace      cyan
-	NewBrush("1;34"), // Debug      blue
-	NewBrush("1;32"), // Info       green
-	NewBrush("1;33"), // Warn       yellow
-	NewBrush("1;31"), // Error      red
-	NewBrush("1;35"), // Critical   purple
-	NewBrush("1;31"), // Fatal      red
-}
+var (
+	Red    = NewBrush("1;31")
+	Purple = NewBrush("1;35")
+	Yellow = NewBrush("1;33")
+	Green  = NewBrush("1;32")
+	Blue   = NewBrush("1;34")
+	Cyan   = NewBrush("1;36")
+
+	colors = []Brush{
+		Cyan,   // Trace      cyan
+		Blue,   // Debug      blue
+		Green,  // Info       green
+		Yellow, // Warn       yellow
+		Red,    // Error      red
+		Purple, // Critical   purple
+		Red,    // Fatal      red
+	}
+	consoleWriter = &ConsoleWriter{lg: log.New(os.Stdout, "", 0),
+		Level: TRACE}
+)
 
 // ConsoleWriter implements LoggerInterface and writes messages to terminal.
 type ConsoleWriter struct {
@@ -40,7 +52,7 @@ type ConsoleWriter struct {
 // create ConsoleWriter returning as LoggerInterface.
 func NewConsole() LoggerInterface {
 	return &ConsoleWriter{
-		lg:    log.New(os.Stdout, "", log.Ldate|log.Ltime),
+		lg:    log.New(os.Stderr, "", log.Ldate|log.Ltime),
 		Level: TRACE,
 	}
 }
@@ -68,6 +80,76 @@ func (_ *ConsoleWriter) Flush() {
 func (_ *ConsoleWriter) Destroy() {
 }
 
+func printConsole(level int, msg string) {
+	consoleWriter.WriteMsg(msg, 0, level)
+}
+
+func printfConsole(level int, format string, v ...interface{}) {
+	consoleWriter.WriteMsg(fmt.Sprintf(format, v...), 0, level)
+}
+
+// ConsoleTrace prints to stdout using TRACE colors
+func ConsoleTrace(s string) {
+	printConsole(TRACE, s)
+}
+
+// ConsoleTracef prints a formatted string to stdout using TRACE colors
+func ConsoleTracef(format string, v ...interface{}) {
+	printfConsole(TRACE, format, v...)
+}
+
+// ConsoleDebug prints to stdout using DEBUG colors
+func ConsoleDebug(s string) {
+	printConsole(DEBUG, s)
+}
+
+// ConsoleDebugf prints a formatted string to stdout using DEBUG colors
+func ConsoleDebugf(format string, v ...interface{}) {
+	printfConsole(DEBUG, format, v...)
+}
+
+// ConsoleInfo prints to stdout using INFO colors
+func ConsoleInfo(s string) {
+	printConsole(INFO, s)
+}
+
+// ConsoleInfof prints a formatted string to stdout using INFO colors
+func ConsoleInfof(format string, v ...interface{}) {
+	printfConsole(INFO, format, v...)
+}
+
+// ConsoleWarn prints to stdout using WARN colors
+func ConsoleWarn(s string) {
+	printConsole(WARN, s)
+}
+
+// ConsoleWarnf prints a formatted string to stdout using WARN colors
+func ConsoleWarnf(format string, v ...interface{}) {
+	printfConsole(WARN, format, v...)
+}
+
+// ConsoleError prints to stdout using ERROR colors
+func ConsoleError(s string) {
+	printConsole(ERROR, s)
+}
+
+// ConsoleErrorf prints a formatted string to stdout using ERROR colors
+func ConsoleErrorf(format string, v ...interface{}) {
+	printfConsole(ERROR, format, v...)
+}
+
+// ConsoleFatal prints to stdout using FATAL colors
+func ConsoleFatal(s string) {
+	printConsole(FATAL, s)
+	os.Exit(1)
+}
+
+// ConsoleFatalf prints a formatted string to stdout using FATAL colors
+func ConsoleFatalf(format string, v ...interface{}) {
+	printfConsole(FATAL, format, v...)
+	os.Exit(1)
+}
+
 func init() {
 	Register("console", NewConsole)
 }

+ 1 - 1
pkg/middleware/auth.go

@@ -56,7 +56,7 @@ func RoleAuth(roles ...m.RoleType) macaron.Handler {
 	return func(c *Context) {
 		ok := false
 		for _, role := range roles {
-			if role == c.AccountRole {
+			if role == c.OrgRole {
 				ok = true
 				break
 			}

+ 9 - 8
pkg/middleware/middleware.go

@@ -56,22 +56,23 @@ func GetContextHandler() macaron.Handler {
 				ctx.SignedInUser = &m.SignedInUser{}
 
 				// TODO: fix this
-				ctx.AccountRole = keyInfo.Role
+				ctx.OrgRole = keyInfo.Role
 				ctx.ApiKeyId = keyInfo.Id
-				ctx.AccountId = keyInfo.AccountId
+				ctx.OrgId = keyInfo.OrgId
 			}
 		} else if setting.AnonymousEnabled {
-			accountQuery := m.GetAccountByNameQuery{Name: setting.AnonymousAccountName}
-			if err := bus.Dispatch(&accountQuery); err != nil {
-				if err == m.ErrAccountNotFound {
-					log.Error(3, "Anonymous access account name does not exist", nil)
+			orgQuery := m.GetOrgByNameQuery{Name: setting.AnonymousOrgName}
+			if err := bus.Dispatch(&orgQuery); err != nil {
+				if err == m.ErrOrgNotFound {
+					log.Error(3, "Anonymous access organization name does not exist", nil)
 				}
 			} else {
 				ctx.IsSignedIn = false
 				ctx.HasAnonymousAccess = true
 				ctx.SignedInUser = &m.SignedInUser{}
-				ctx.AccountRole = m.RoleType(setting.AnonymousAccountRole)
-				ctx.AccountId = accountQuery.Result.Id
+				ctx.OrgRole = m.RoleType(setting.AnonymousOrgRole)
+				ctx.OrgId = orgQuery.Result.Id
+				ctx.OrgName = orgQuery.Result.Name
 			}
 		}
 

+ 0 - 62
pkg/models/account.go

@@ -1,62 +0,0 @@
-package models
-
-import (
-	"errors"
-	"time"
-)
-
-// Typed errors
-var (
-	ErrAccountNotFound = errors.New("Account not found")
-)
-
-type Account struct {
-	Id      int64
-	Version int
-	Name    string
-	Created time.Time
-	Updated time.Time
-}
-
-// ---------------------
-// COMMANDS
-
-type CreateAccountCommand struct {
-	Name string `json:"name" binding:"Required"`
-
-	// initial admin user for account
-	UserId int64   `json:"-"`
-	Result Account `json:"-"`
-}
-
-type UpdateAccountCommand struct {
-	Name      string `json:"name" binding:"Required"`
-	AccountId int64  `json:"-"`
-}
-
-type GetUserAccountsQuery struct {
-	UserId int64
-	Result []*UserAccountDTO
-}
-
-type GetAccountByIdQuery struct {
-	Id     int64
-	Result *Account
-}
-
-type GetAccountByNameQuery struct {
-	Name   string
-	Result *Account
-}
-
-type AccountDTO struct {
-	Id   int64  `json:"id"`
-	Name string `json:"name"`
-}
-
-type UserAccountDTO struct {
-	AccountId int64    `json:"accountId"`
-	Name      string   `json:"name"`
-	Role      RoleType `json:"role"`
-	IsUsing   bool     `json:"isUsing"`
-}

+ 0 - 67
pkg/models/account_user.go

@@ -1,67 +0,0 @@
-package models
-
-import (
-	"errors"
-	"time"
-)
-
-// Typed errors
-var (
-	ErrInvalidRoleType  = errors.New("Invalid role type")
-	ErrLastAccountAdmin = errors.New("Cannot remove last account admin")
-)
-
-type RoleType string
-
-const (
-	ROLE_VIEWER RoleType = "Viewer"
-	ROLE_EDITOR RoleType = "Editor"
-	ROLE_ADMIN  RoleType = "Admin"
-)
-
-func (r RoleType) IsValid() bool {
-	return r == ROLE_VIEWER || r == ROLE_ADMIN || r == ROLE_EDITOR
-}
-
-type AccountUser struct {
-	AccountId int64
-	UserId    int64
-	Role      RoleType
-	Created   time.Time
-	Updated   time.Time
-}
-
-// ---------------------
-// COMMANDS
-
-type RemoveAccountUserCommand struct {
-	UserId    int64
-	AccountId int64
-}
-
-type AddAccountUserCommand struct {
-	LoginOrEmail string   `json:"loginOrEmail" binding:"Required"`
-	Role         RoleType `json:"role" binding:"Required"`
-
-	AccountId int64 `json:"-"`
-	UserId    int64 `json:"-"`
-}
-
-// ----------------------
-// QUERIES
-
-type GetAccountUsersQuery struct {
-	AccountId int64
-	Result    []*AccountUserDTO
-}
-
-// ----------------------
-// Projections and DTOs
-
-type AccountUserDTO struct {
-	AccountId int64  `json:"accountId"`
-	UserId    int64  `json:"userId"`
-	Email     string `json:"email"`
-	Login     string `json:"login"`
-	Role      string `json:"role"`
-}

+ 16 - 16
pkg/models/apikey.go

@@ -8,22 +8,22 @@ import (
 var ErrInvalidApiKey = errors.New("Invalid API Key")
 
 type ApiKey struct {
-	Id        int64
-	AccountId int64
-	Name      string
-	Key       string
-	Role      RoleType
-	Created   time.Time
-	Updated   time.Time
+	Id      int64
+	OrgId   int64
+	Name    string
+	Key     string
+	Role    RoleType
+	Created time.Time
+	Updated time.Time
 }
 
 // ---------------------
 // COMMANDS
 type AddApiKeyCommand struct {
-	Name      string   `json:"name" binding:"Required"`
-	Role      RoleType `json:"role" binding:"Required"`
-	AccountId int64    `json:"-"`
-	Key       string   `json:"-"`
+	Name  string   `json:"name" binding:"Required"`
+	Role  RoleType `json:"role" binding:"Required"`
+	OrgId int64    `json:"-"`
+	Key   string   `json:"-"`
 
 	Result *ApiKey `json:"-"`
 }
@@ -33,20 +33,20 @@ type UpdateApiKeyCommand struct {
 	Name string   `json:"name"`
 	Role RoleType `json:"role"`
 
-	AccountId int64 `json:"-"`
+	OrgId int64 `json:"-"`
 }
 
 type DeleteApiKeyCommand struct {
-	Id        int64 `json:"id"`
-	AccountId int64 `json:"-"`
+	Id    int64 `json:"id"`
+	OrgId int64 `json:"-"`
 }
 
 // ----------------------
 // QUERIES
 
 type GetApiKeysQuery struct {
-	AccountId int64
-	Result    []*ApiKey
+	OrgId  int64
+	Result []*ApiKey
 }
 
 type GetApiKeyByKeyQuery struct {

+ 10 - 10
pkg/models/dashboards.go

@@ -14,10 +14,10 @@ var (
 )
 
 type Dashboard struct {
-	Id        int64
-	Slug      string
-	AccountId int64
-	Version   int
+	Id      int64
+	Slug    string
+	OrgId   int64
+	Version int
 
 	Created time.Time
 	Updated time.Time
@@ -53,7 +53,7 @@ func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
 	dash := &Dashboard{}
 	dash.Data = cmd.Dashboard
 	dash.Title = dash.Data["title"].(string)
-	dash.AccountId = cmd.AccountId
+	dash.OrgId = cmd.OrgId
 	dash.UpdateSlug()
 
 	if dash.Data["id"] != nil {
@@ -80,14 +80,14 @@ func (dash *Dashboard) UpdateSlug() {
 
 type SaveDashboardCommand struct {
 	Dashboard map[string]interface{} `json:"dashboard"`
-	AccountId int64                  `json:"-"`
+	OrgId     int64                  `json:"-"`
 
 	Result *Dashboard
 }
 
 type DeleteDashboardCommand struct {
-	Slug      string
-	AccountId int64
+	Slug  string
+	OrgId int64
 }
 
 //
@@ -95,8 +95,8 @@ type DeleteDashboardCommand struct {
 //
 
 type GetDashboardQuery struct {
-	Slug      string
-	AccountId int64
+	Slug  string
+	OrgId int64
 
 	Result *Dashboard
 }

+ 20 - 12
pkg/models/datasource.go

@@ -8,7 +8,9 @@ import (
 const (
 	DS_GRAPHITE      = "graphite"
 	DS_INFLUXDB      = "influxdb"
+	DS_INFLUXDB_08   = "influxdb_08"
 	DS_ES            = "elasticsearch"
+	DS_OPENTSDB      = "opentsdb"
 	DS_ACCESS_DIRECT = "direct"
 	DS_ACCESS_PROXY  = "proxy"
 )
@@ -22,9 +24,9 @@ type DsType string
 type DsAccess string
 
 type DataSource struct {
-	Id        int64
-	AccountId int64
-	Version   int
+	Id      int64
+	OrgId   int64
+	Version int
 
 	Name              string
 	Type              DsType
@@ -47,7 +49,7 @@ type DataSource struct {
 
 // Also acts as api DTO
 type AddDataSourceCommand struct {
-	AccountId int64 `json:"-"`
+	OrgId     int64 `json:"-"`
 	Name      string
 	Type      DsType
 	Access    DsAccess
@@ -63,7 +65,7 @@ type AddDataSourceCommand struct {
 // Also acts as api DTO
 type UpdateDataSourceCommand struct {
 	Id        int64
-	AccountId int64
+	OrgId     int64
 	Name      string
 	Type      DsType
 	Access    DsAccess
@@ -75,22 +77,28 @@ type UpdateDataSourceCommand struct {
 }
 
 type DeleteDataSourceCommand struct {
-	Id        int64
-	AccountId int64
+	Id    int64
+	OrgId int64
 }
 
 // ---------------------
 // QUERIES
 
 type GetDataSourcesQuery struct {
-	AccountId int64
-	Result    []*DataSource
+	OrgId  int64
+	Result []*DataSource
 }
 
 type GetDataSourceByIdQuery struct {
-	Id        int64
-	AccountId int64
-	Result    DataSource
+	Id     int64
+	OrgId  int64
+	Result DataSource
+}
+
+type GetDataSourceByNameQuery struct {
+	Name   string
+	OrgId  int64
+	Result DataSource
 }
 
 // ---------------------

+ 65 - 0
pkg/models/org.go

@@ -0,0 +1,65 @@
+package models
+
+import (
+	"errors"
+	"time"
+)
+
+// Typed errors
+var (
+	ErrOrgNotFound = errors.New("Organization not found")
+)
+
+type Org struct {
+	Id      int64
+	Version int
+	Name    string
+	Created time.Time
+	Updated time.Time
+}
+
+// ---------------------
+// COMMANDS
+
+type CreateOrgCommand struct {
+	Name string `json:"name" binding:"Required"`
+
+	// initial admin user for account
+	UserId int64 `json:"-"`
+	Result Org   `json:"-"`
+}
+
+type DeleteOrgCommand struct {
+	Id int64
+}
+
+type UpdateOrgCommand struct {
+	Name  string `json:"name" binding:"Required"`
+	OrgId int64  `json:"-"`
+}
+
+type GetOrgByIdQuery struct {
+	Id     int64
+	Result *Org
+}
+
+type GetOrgByNameQuery struct {
+	Name   string
+	Result *Org
+}
+
+type GetOrgListQuery struct {
+	Result []*Org
+}
+
+type OrgDTO struct {
+	Id   int64  `json:"id"`
+	Name string `json:"name"`
+}
+
+type UserOrgDTO struct {
+	OrgId   int64    `json:"orgId"`
+	Name    string   `json:"name"`
+	Role    RoleType `json:"role"`
+	IsUsing bool     `json:"isUsing"`
+}

+ 67 - 0
pkg/models/org_user.go

@@ -0,0 +1,67 @@
+package models
+
+import (
+	"errors"
+	"time"
+)
+
+// Typed errors
+var (
+	ErrInvalidRoleType = errors.New("Invalid role type")
+	ErrLastOrgAdmin    = errors.New("Cannot remove last organization admin")
+)
+
+type RoleType string
+
+const (
+	ROLE_VIEWER RoleType = "Viewer"
+	ROLE_EDITOR RoleType = "Editor"
+	ROLE_ADMIN  RoleType = "Admin"
+)
+
+func (r RoleType) IsValid() bool {
+	return r == ROLE_VIEWER || r == ROLE_ADMIN || r == ROLE_EDITOR
+}
+
+type OrgUser struct {
+	OrgId   int64
+	UserId  int64
+	Role    RoleType
+	Created time.Time
+	Updated time.Time
+}
+
+// ---------------------
+// COMMANDS
+
+type RemoveOrgUserCommand struct {
+	UserId int64
+	OrgId  int64
+}
+
+type AddOrgUserCommand struct {
+	LoginOrEmail string   `json:"loginOrEmail" binding:"Required"`
+	Role         RoleType `json:"role" binding:"Required"`
+
+	OrgId  int64 `json:"-"`
+	UserId int64 `json:"-"`
+}
+
+// ----------------------
+// QUERIES
+
+type GetOrgUsersQuery struct {
+	OrgId  int64
+	Result []*OrgUserDTO
+}
+
+// ----------------------
+// Projections and DTOs
+
+type OrgUserDTO struct {
+	OrgId  int64  `json:"orgId"`
+	UserId int64  `json:"userId"`
+	Email  string `json:"email"`
+	Login  string `json:"login"`
+	Role   string `json:"role"`
+}

+ 3 - 4
pkg/models/search.go

@@ -11,7 +11,6 @@ type DashboardSearchHit struct {
 	Title     string   `json:"title"`
 	Slug      string   `json:"slug"`
 	Tags      []string `json:"tags"`
-	Url       string   `json:"url"`
 	IsStarred bool     `json:"isStarred"`
 }
 
@@ -23,7 +22,7 @@ type DashboardTagCloudItem struct {
 type SearchDashboardsQuery struct {
 	Title     string
 	Tag       string
-	AccountId int64
+	OrgId     int64
 	UserId    int64
 	Limit     int
 	IsStarred bool
@@ -32,6 +31,6 @@ type SearchDashboardsQuery struct {
 }
 
 type GetDashboardTagsQuery struct {
-	AccountId int64
-	Result    []*DashboardTagCloudItem
+	OrgId  int64
+	Result []*DashboardTagCloudItem
 }

+ 50 - 21
pkg/models/user.go

@@ -11,18 +11,20 @@ var (
 )
 
 type User struct {
-	Id       int64
-	Version  int
-	Email    string
-	Name     string
-	Login    string
-	Password string
-	Salt     string
-	Rands    string
-	Company  string
-
-	IsAdmin   bool
-	AccountId int64
+	Id            int64
+	Version       int
+	Email         string
+	Name          string
+	Login         string
+	Password      string
+	Salt          string
+	Rands         string
+	Company       string
+	EmailVerified bool
+	Theme         string
+
+	IsAdmin bool
+	OrgId   int64
 
 	Created time.Time
 	Updated time.Time
@@ -50,9 +52,25 @@ type UpdateUserCommand struct {
 	UserId int64 `json:"-"`
 }
 
-type SetUsingAccountCommand struct {
-	UserId    int64
-	AccountId int64
+type ChangeUserPasswordCommand struct {
+	OldPassword string `json:"oldPassword"`
+	NewPassword string `json:"newPassword"`
+
+	UserId int64 `json:"-"`
+}
+
+type UpdateUserPermissionsCommand struct {
+	IsGrafanaAdmin bool
+	UserId         int64 `json:"-"`
+}
+
+type DeleteUserCommand struct {
+	UserId int64
+}
+
+type SetUsingOrgCommand struct {
+	UserId int64
+	OrgId  int64
 }
 
 // ----------------------
@@ -63,6 +81,11 @@ type GetUserByLoginQuery struct {
 	Result       *User
 }
 
+type GetUserByIdQuery struct {
+	Id     int64
+	Result *User
+}
+
 type GetSignedInUserQuery struct {
 	UserId int64
 	Result *SignedInUser
@@ -81,14 +104,19 @@ type SearchUsersQuery struct {
 	Result []*UserSearchHitDTO
 }
 
+type GetUserOrgListQuery struct {
+	UserId int64
+	Result []*UserOrgDTO
+}
+
 // ------------------------
 // DTO & Projections
 
 type SignedInUser struct {
 	UserId         int64
-	AccountId      int64
-	AccountName    string
-	AccountRole    RoleType
+	OrgId          int64
+	OrgName        string
+	OrgRole        RoleType
 	Login          string
 	Name           string
 	Email          string
@@ -97,9 +125,10 @@ type SignedInUser struct {
 }
 
 type UserDTO struct {
-	Email string `json:"email"`
-	Name  string `json:"name"`
-	Login string `json:"login"`
+	Email          string `json:"email"`
+	Name           string `json:"name"`
+	Login          string `json:"login"`
+	IsGrafanaAdmin bool   `json:"isGrafanaAdmin"`
 }
 
 type UserSearchHitDTO struct {

+ 0 - 103
pkg/services/sqlstore/account.go

@@ -1,103 +0,0 @@
-package sqlstore
-
-import (
-	"time"
-
-	"github.com/grafana/grafana/pkg/bus"
-	"github.com/grafana/grafana/pkg/events"
-	m "github.com/grafana/grafana/pkg/models"
-)
-
-func init() {
-	bus.AddHandler("sql", GetAccountById)
-	bus.AddHandler("sql", CreateAccount)
-	bus.AddHandler("sql", SetUsingAccount)
-	bus.AddHandler("sql", UpdateAccount)
-	bus.AddHandler("sql", GetAccountByName)
-}
-
-func GetAccountById(query *m.GetAccountByIdQuery) error {
-	var account m.Account
-	exists, err := x.Id(query.Id).Get(&account)
-	if err != nil {
-		return err
-	}
-
-	if !exists {
-		return m.ErrAccountNotFound
-	}
-
-	query.Result = &account
-	return nil
-}
-
-func GetAccountByName(query *m.GetAccountByNameQuery) error {
-	var account m.Account
-	exists, err := x.Where("name=?", query.Name).Get(&account)
-	if err != nil {
-		return err
-	}
-
-	if !exists {
-		return m.ErrAccountNotFound
-	}
-
-	query.Result = &account
-	return nil
-}
-
-func CreateAccount(cmd *m.CreateAccountCommand) error {
-	return inTransaction2(func(sess *session) error {
-
-		account := m.Account{
-			Name:    cmd.Name,
-			Created: time.Now(),
-			Updated: time.Now(),
-		}
-
-		if _, err := sess.Insert(&account); err != nil {
-			return err
-		}
-
-		user := m.AccountUser{
-			AccountId: account.Id,
-			UserId:    cmd.UserId,
-			Role:      m.ROLE_ADMIN,
-			Created:   time.Now(),
-			Updated:   time.Now(),
-		}
-
-		_, err := sess.Insert(&user)
-		cmd.Result = account
-
-		sess.publishAfterCommit(&events.AccountCreated{
-			Timestamp: account.Created,
-			Id:        account.Id,
-			Name:      account.Name,
-		})
-
-		return err
-	})
-}
-
-func UpdateAccount(cmd *m.UpdateAccountCommand) error {
-	return inTransaction2(func(sess *session) error {
-
-		account := m.Account{
-			Name:    cmd.Name,
-			Updated: time.Now(),
-		}
-
-		if _, err := sess.Id(cmd.AccountId).Update(&account); err != nil {
-			return err
-		}
-
-		sess.publishAfterCommit(&events.AccountUpdated{
-			Timestamp: account.Updated,
-			Id:        account.Id,
-			Name:      account.Name,
-		})
-
-		return nil
-	})
-}

+ 0 - 67
pkg/services/sqlstore/account_users.go

@@ -1,67 +0,0 @@
-package sqlstore
-
-import (
-	"fmt"
-	"time"
-
-	"github.com/go-xorm/xorm"
-
-	"github.com/grafana/grafana/pkg/bus"
-	m "github.com/grafana/grafana/pkg/models"
-)
-
-func init() {
-	bus.AddHandler("sql", AddAccountUser)
-	bus.AddHandler("sql", RemoveAccountUser)
-	bus.AddHandler("sql", GetAccountUsers)
-}
-
-func AddAccountUser(cmd *m.AddAccountUserCommand) error {
-	return inTransaction(func(sess *xorm.Session) error {
-
-		entity := m.AccountUser{
-			AccountId: cmd.AccountId,
-			UserId:    cmd.UserId,
-			Role:      cmd.Role,
-			Created:   time.Now(),
-			Updated:   time.Now(),
-		}
-
-		_, err := sess.Insert(&entity)
-		return err
-	})
-}
-
-func GetAccountUsers(query *m.GetAccountUsersQuery) error {
-	query.Result = make([]*m.AccountUserDTO, 0)
-	sess := x.Table("account_user")
-	sess.Join("INNER", "user", fmt.Sprintf("account_user.user_id=%s.id", x.Dialect().Quote("user")))
-	sess.Where("account_user.account_id=?", query.AccountId)
-	sess.Cols("account_user.account_id", "account_user.user_id", "user.email", "user.login", "account_user.role")
-	sess.Asc("user.email", "user.login")
-
-	err := sess.Find(&query.Result)
-	return err
-}
-
-func RemoveAccountUser(cmd *m.RemoveAccountUserCommand) error {
-	return inTransaction(func(sess *xorm.Session) error {
-		var rawSql = "DELETE FROM account_user WHERE account_id=? and user_id=?"
-		_, err := sess.Exec(rawSql, cmd.AccountId, cmd.UserId)
-		if err != nil {
-			return err
-		}
-
-		// validate that there is an admin user left
-		res, err := sess.Query("SELECT 1 from account_user WHERE account_id=? and role='Admin'", cmd.AccountId)
-		if err != nil {
-			return err
-		}
-
-		if len(res) == 0 {
-			return m.ErrLastAccountAdmin
-		}
-
-		return err
-	})
-}

+ 16 - 16
pkg/services/sqlstore/apikey.go

@@ -17,7 +17,7 @@ func init() {
 }
 
 func GetApiKeys(query *m.GetApiKeysQuery) error {
-	sess := x.Limit(100, 0).Where("account_id=?", query.AccountId).Asc("name")
+	sess := x.Limit(100, 0).Where("org_id=?", query.OrgId).Asc("name")
 
 	query.Result = make([]*m.ApiKey, 0)
 	return sess.Find(&query.Result)
@@ -25,8 +25,8 @@ func GetApiKeys(query *m.GetApiKeysQuery) error {
 
 func DeleteApiKey(cmd *m.DeleteApiKeyCommand) error {
 	return inTransaction(func(sess *xorm.Session) error {
-		var rawSql = "DELETE FROM api_key WHERE id=? and account_id=?"
-		_, err := sess.Exec(rawSql, cmd.Id, cmd.AccountId)
+		var rawSql = "DELETE FROM api_key WHERE id=? and org_id=?"
+		_, err := sess.Exec(rawSql, cmd.Id, cmd.OrgId)
 		return err
 	})
 }
@@ -34,12 +34,12 @@ func DeleteApiKey(cmd *m.DeleteApiKeyCommand) error {
 func AddApiKey(cmd *m.AddApiKeyCommand) error {
 	return inTransaction(func(sess *xorm.Session) error {
 		t := m.ApiKey{
-			AccountId: cmd.AccountId,
-			Name:      cmd.Name,
-			Role:      cmd.Role,
-			Key:       cmd.Key,
-			Created:   time.Now(),
-			Updated:   time.Now(),
+			OrgId:   cmd.OrgId,
+			Name:    cmd.Name,
+			Role:    cmd.Role,
+			Key:     cmd.Key,
+			Created: time.Now(),
+			Updated: time.Now(),
 		}
 
 		if _, err := sess.Insert(&t); err != nil {
@@ -53,20 +53,20 @@ func AddApiKey(cmd *m.AddApiKeyCommand) error {
 func UpdateApiKey(cmd *m.UpdateApiKeyCommand) error {
 	return inTransaction(func(sess *xorm.Session) error {
 		t := m.ApiKey{
-			Id:        cmd.Id,
-			AccountId: cmd.AccountId,
-			Name:      cmd.Name,
-			Role:      cmd.Role,
-			Updated:   time.Now(),
+			Id:      cmd.Id,
+			OrgId:   cmd.OrgId,
+			Name:    cmd.Name,
+			Role:    cmd.Role,
+			Updated: time.Now(),
 		}
-		_, err := sess.Where("id=? and account_id=?", t.Id, t.AccountId).Update(&t)
+		_, err := sess.Where("id=? and org_id=?", t.Id, t.OrgId).Update(&t)
 		return err
 	})
 }
 
 func GetApiKeyByKey(query *m.GetApiKeyByKeyQuery) error {
 	var apikey m.ApiKey
-	has, err := x.Where("key=?", query.Key).Get(&apikey)
+	has, err := x.Where("`key`=?", query.Key).Get(&apikey)
 
 	if err != nil {
 		return err

+ 31 - 0
pkg/services/sqlstore/apikey_test.go

@@ -0,0 +1,31 @@
+package sqlstore
+
+import (
+	"testing"
+
+	. "github.com/smartystreets/goconvey/convey"
+
+	m "github.com/grafana/grafana/pkg/models"
+)
+
+func TestApiKeyDataAccess(t *testing.T) {
+
+	Convey("Testing API Key data access", t, func() {
+		InitTestDB(t)
+
+		Convey("Given saved api key", func() {
+			cmd := m.AddApiKeyCommand{OrgId: 1, Key: "hello"}
+			err := AddApiKey(&cmd)
+			So(err, ShouldBeNil)
+
+			Convey("Should be able to get key by key", func() {
+				query := m.GetApiKeyByKeyQuery{Key: "hello"}
+				err = GetApiKeyByKey(&query)
+				So(err, ShouldBeNil)
+
+				So(query.Result, ShouldNotBeNil)
+			})
+
+		})
+	})
+}

+ 8 - 8
pkg/services/sqlstore/dashboard.go

@@ -22,7 +22,7 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
 		dash := cmd.GetDashboardModel()
 
 		// try get existing dashboard
-		existing := m.Dashboard{Slug: dash.Slug, AccountId: dash.AccountId}
+		existing := m.Dashboard{Slug: dash.Slug, OrgId: dash.OrgId}
 		hasExisting, err := sess.Get(&existing)
 		if err != nil {
 			return err
@@ -61,7 +61,7 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
 }
 
 func GetDashboard(query *m.GetDashboardQuery) error {
-	dashboard := m.Dashboard{Slug: query.Slug, AccountId: query.AccountId}
+	dashboard := m.Dashboard{Slug: query.Slug, OrgId: query.OrgId}
 	has, err := x.Get(&dashboard)
 	if err != nil {
 		return err
@@ -98,9 +98,9 @@ func SearchDashboards(query *m.SearchDashboardsQuery) error {
 		sql.WriteString(" INNER JOIN star on star.dashboard_id = dashboard.id")
 	}
 
-	sql.WriteString(` WHERE dashboard.account_id=?`)
+	sql.WriteString(` WHERE dashboard.org_id=?`)
 
-	params = append(params, query.AccountId)
+	params = append(params, query.OrgId)
 
 	if query.IsStarred {
 		sql.WriteString(` AND star.user_id=?`)
@@ -158,11 +158,11 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
 						term
 					FROM dashboard
 					INNER JOIN dashboard_tag on dashboard_tag.dashboard_id = dashboard.id
-					WHERE dashboard.account_id=?
+					WHERE dashboard.org_id=?
 					GROUP BY term`
 
 	query.Result = make([]*m.DashboardTagCloudItem, 0)
-	sess := x.Sql(sql, query.AccountId)
+	sess := x.Sql(sql, query.OrgId)
 	err := sess.Find(&query.Result)
 	return err
 }
@@ -171,8 +171,8 @@ func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
 	sess := x.NewSession()
 	defer sess.Close()
 
-	rawSql := "DELETE FROM Dashboard WHERE account_id=? and slug=?"
-	_, err := sess.Exec(rawSql, cmd.AccountId, cmd.Slug)
+	rawSql := "DELETE FROM dashboard WHERE org_id=? and slug=?"
+	_, err := sess.Exec(rawSql, cmd.OrgId, cmd.Slug)
 
 	return err
 }

+ 11 - 11
pkg/services/sqlstore/dashboard_test.go

@@ -8,9 +8,9 @@ import (
 	m "github.com/grafana/grafana/pkg/models"
 )
 
-func insertTestDashboard(title string, accountId int64, tags ...interface{}) *m.Dashboard {
+func insertTestDashboard(title string, orgId int64, tags ...interface{}) *m.Dashboard {
 	cmd := m.SaveDashboardCommand{
-		AccountId: accountId,
+		OrgId: orgId,
 		Dashboard: map[string]interface{}{
 			"id":    nil,
 			"title": title,
@@ -40,8 +40,8 @@ func TestDashboardDataAccess(t *testing.T) {
 
 			Convey("Should be able to get dashboard", func() {
 				query := m.GetDashboardQuery{
-					Slug:      "test-dash-23",
-					AccountId: 1,
+					Slug:  "test-dash-23",
+					OrgId: 1,
 				}
 
 				err := GetDashboard(&query)
@@ -53,8 +53,8 @@ func TestDashboardDataAccess(t *testing.T) {
 
 			Convey("Should be able to search for dashboard", func() {
 				query := m.SearchDashboardsQuery{
-					Title:     "test",
-					AccountId: 1,
+					Title: "test",
+					OrgId: 1,
 				}
 
 				err := SearchDashboards(&query)
@@ -66,8 +66,8 @@ func TestDashboardDataAccess(t *testing.T) {
 			})
 
 			Convey("Should be able to search for dashboards using tags", func() {
-				query1 := m.SearchDashboardsQuery{Tag: "webapp", AccountId: 1}
-				query2 := m.SearchDashboardsQuery{Tag: "tagdoesnotexist", AccountId: 1}
+				query1 := m.SearchDashboardsQuery{Tag: "webapp", OrgId: 1}
+				query2 := m.SearchDashboardsQuery{Tag: "tagdoesnotexist", OrgId: 1}
 
 				err := SearchDashboards(&query1)
 				err = SearchDashboards(&query2)
@@ -79,7 +79,7 @@ func TestDashboardDataAccess(t *testing.T) {
 
 			Convey("Should not be able to save dashboard with same name", func() {
 				cmd := m.SaveDashboardCommand{
-					AccountId: 1,
+					OrgId: 1,
 					Dashboard: map[string]interface{}{
 						"id":    nil,
 						"title": "test dash 23",
@@ -92,7 +92,7 @@ func TestDashboardDataAccess(t *testing.T) {
 			})
 
 			Convey("Should be able to get dashboard tags", func() {
-				query := m.GetDashboardTagsQuery{AccountId: 1}
+				query := m.GetDashboardTagsQuery{OrgId: 1}
 
 				err := GetDashboardTags(&query)
 				So(err, ShouldBeNil)
@@ -113,7 +113,7 @@ func TestDashboardDataAccess(t *testing.T) {
 				})
 
 				Convey("Should be able to search for starred dashboards", func() {
-					query := m.SearchDashboardsQuery{AccountId: 1, UserId: 10, IsStarred: true}
+					query := m.SearchDashboardsQuery{OrgId: 1, UserId: 10, IsStarred: true}
 					err := SearchDashboards(&query)
 
 					So(err, ShouldBeNil)

+ 20 - 9
pkg/services/sqlstore/datasource.go

@@ -15,10 +15,21 @@ func init() {
 	bus.AddHandler("sql", DeleteDataSource)
 	bus.AddHandler("sql", UpdateDataSource)
 	bus.AddHandler("sql", GetDataSourceById)
+	bus.AddHandler("sql", GetDataSourceByName)
 }
 
 func GetDataSourceById(query *m.GetDataSourceByIdQuery) error {
-	sess := x.Limit(100, 0).Where("account_id=? AND id=?", query.AccountId, query.Id)
+	sess := x.Limit(100, 0).Where("org_id=? AND id=?", query.OrgId, query.Id)
+	has, err := sess.Get(&query.Result)
+
+	if !has {
+		return m.ErrDataSourceNotFound
+	}
+	return err
+}
+
+func GetDataSourceByName(query *m.GetDataSourceByNameQuery) error {
+	sess := x.Limit(100, 0).Where("org_id=? AND name=?", query.OrgId, query.Name)
 	has, err := sess.Get(&query.Result)
 
 	if !has {
@@ -28,7 +39,7 @@ func GetDataSourceById(query *m.GetDataSourceByIdQuery) error {
 }
 
 func GetDataSources(query *m.GetDataSourcesQuery) error {
-	sess := x.Limit(100, 0).Where("account_id=?", query.AccountId).Asc("name")
+	sess := x.Limit(100, 0).Where("org_id=?", query.OrgId).Asc("name")
 
 	query.Result = make([]*m.DataSource, 0)
 	return sess.Find(&query.Result)
@@ -36,8 +47,8 @@ func GetDataSources(query *m.GetDataSourcesQuery) error {
 
 func DeleteDataSource(cmd *m.DeleteDataSourceCommand) error {
 	return inTransaction(func(sess *xorm.Session) error {
-		var rawSql = "DELETE FROM data_source WHERE id=? and account_id=?"
-		_, err := sess.Exec(rawSql, cmd.Id, cmd.AccountId)
+		var rawSql = "DELETE FROM data_source WHERE id=? and org_id=?"
+		_, err := sess.Exec(rawSql, cmd.Id, cmd.OrgId)
 		return err
 	})
 }
@@ -46,7 +57,7 @@ func AddDataSource(cmd *m.AddDataSourceCommand) error {
 
 	return inTransaction(func(sess *xorm.Session) error {
 		ds := &m.DataSource{
-			AccountId: cmd.AccountId,
+			OrgId:     cmd.OrgId,
 			Name:      cmd.Name,
 			Type:      cmd.Type,
 			Access:    cmd.Access,
@@ -74,8 +85,8 @@ func AddDataSource(cmd *m.AddDataSourceCommand) error {
 func updateIsDefaultFlag(ds *m.DataSource, sess *xorm.Session) error {
 	// Handle is default flag
 	if ds.IsDefault {
-		rawSql := "UPDATE data_source SET is_default = 0 WHERE account_id=? AND id <> ?"
-		if _, err := sess.Exec(rawSql, ds.AccountId, ds.Id); err != nil {
+		rawSql := "UPDATE data_source SET is_default = 0 WHERE org_id=? AND id <> ?"
+		if _, err := sess.Exec(rawSql, ds.OrgId, ds.Id); err != nil {
 			return err
 		}
 	}
@@ -87,7 +98,7 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
 	return inTransaction(func(sess *xorm.Session) error {
 		ds := &m.DataSource{
 			Id:        cmd.Id,
-			AccountId: cmd.AccountId,
+			OrgId:     cmd.OrgId,
 			Name:      cmd.Name,
 			Type:      cmd.Type,
 			Access:    cmd.Access,
@@ -101,7 +112,7 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
 
 		sess.UseBool("is_default")
 
-		_, err := sess.Where("id=? and account_id=?", ds.Id, ds.AccountId).Update(ds)
+		_, err := sess.Where("id=? and org_id=?", ds.Id, ds.OrgId).Update(ds)
 		if err != nil {
 			return err
 		}

+ 15 - 15
pkg/services/sqlstore/datasource_test.go

@@ -42,16 +42,16 @@ func TestDataAccess(t *testing.T) {
 		Convey("Can add datasource", func() {
 
 			err := AddDataSource(&m.AddDataSourceCommand{
-				AccountId: 10,
-				Type:      m.DS_INFLUXDB,
-				Access:    m.DS_ACCESS_DIRECT,
-				Url:       "http://test",
-				Database:  "site",
+				OrgId:    10,
+				Type:     m.DS_INFLUXDB,
+				Access:   m.DS_ACCESS_DIRECT,
+				Url:      "http://test",
+				Database: "site",
 			})
 
 			So(err, ShouldBeNil)
 
-			query := m.GetDataSourcesQuery{AccountId: 10}
+			query := m.GetDataSourcesQuery{OrgId: 10}
 			err = GetDataSources(&query)
 			So(err, ShouldBeNil)
 
@@ -59,33 +59,33 @@ func TestDataAccess(t *testing.T) {
 
 			ds := query.Result[0]
 
-			So(ds.AccountId, ShouldEqual, 10)
+			So(ds.OrgId, ShouldEqual, 10)
 			So(ds.Database, ShouldEqual, "site")
 		})
 
 		Convey("Given a datasource", func() {
 
 			AddDataSource(&m.AddDataSourceCommand{
-				AccountId: 10,
-				Type:      m.DS_GRAPHITE,
-				Access:    m.DS_ACCESS_DIRECT,
-				Url:       "http://test",
+				OrgId:  10,
+				Type:   m.DS_GRAPHITE,
+				Access: m.DS_ACCESS_DIRECT,
+				Url:    "http://test",
 			})
 
-			query := m.GetDataSourcesQuery{AccountId: 10}
+			query := m.GetDataSourcesQuery{OrgId: 10}
 			GetDataSources(&query)
 			ds := query.Result[0]
 
 			Convey("Can delete datasource", func() {
-				err := DeleteDataSource(&m.DeleteDataSourceCommand{Id: ds.Id, AccountId: ds.AccountId})
+				err := DeleteDataSource(&m.DeleteDataSourceCommand{Id: ds.Id, OrgId: ds.OrgId})
 				So(err, ShouldBeNil)
 
 				GetDataSources(&query)
 				So(len(query.Result), ShouldEqual, 0)
 			})
 
-			Convey("Can not delete datasource with wrong accountId", func() {
-				err := DeleteDataSource(&m.DeleteDataSourceCommand{Id: ds.Id, AccountId: 123123})
+			Convey("Can not delete datasource with wrong orgId", func() {
+				err := DeleteDataSource(&m.DeleteDataSourceCommand{Id: ds.Id, OrgId: 123123})
 				So(err, ShouldBeNil)
 
 				GetDataSources(&query)

+ 0 - 175
pkg/services/sqlstore/migrations.go

@@ -1,175 +0,0 @@
-package sqlstore
-
-import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
-
-// --- Migration Guide line ---
-// 1. Never change a migration that is committed and pushed to master
-// 2. Always add new migrations (to change or undo previous migrations)
-// 3. Some migraitons are not yet written (rename column, table, drop table, index etc)
-
-func addMigrations(mg *Migrator) {
-	addMigrationLogMigrations(mg)
-	addUserMigrations(mg)
-	addStarMigrations(mg)
-	addAccountMigrations(mg)
-	addDashboardMigration(mg)
-	addDataSourceMigration(mg)
-	addApiKeyMigrations(mg)
-}
-
-func addMigrationLogMigrations(mg *Migrator) {
-	mg.AddMigration("create migration_log table", new(AddTableMigration).
-		Name("migration_log").WithColumns(
-		&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
-		&Column{Name: "migration_id", Type: DB_NVarchar, Length: 255},
-		&Column{Name: "sql", Type: DB_Text},
-		&Column{Name: "success", Type: DB_Bool},
-		&Column{Name: "error", Type: DB_Text},
-		&Column{Name: "timestamp", Type: DB_DateTime},
-	))
-}
-
-func addUserMigrations(mg *Migrator) {
-	mg.AddMigration("create user table", new(AddTableMigration).
-		Name("user").WithColumns(
-		&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
-		&Column{Name: "version", Type: DB_Int, Nullable: false},
-		&Column{Name: "login", Type: DB_NVarchar, Length: 255, Nullable: false},
-		&Column{Name: "email", Type: DB_NVarchar, Length: 255, Nullable: false},
-		&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true},
-		&Column{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
-		&Column{Name: "salt", Type: DB_NVarchar, Length: 50, Nullable: true},
-		&Column{Name: "rands", Type: DB_NVarchar, Length: 50, Nullable: true},
-		&Column{Name: "company", Type: DB_NVarchar, Length: 255, Nullable: true},
-		&Column{Name: "account_id", Type: DB_BigInt, Nullable: false},
-		&Column{Name: "is_admin", Type: DB_Bool, Nullable: false},
-		&Column{Name: "created", Type: DB_DateTime, Nullable: false},
-		&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
-	))
-
-	//-------  user table indexes ------------------
-	mg.AddMigration("add unique index user.login", new(AddIndexMigration).
-		Table("user").Columns("login").Unique())
-	mg.AddMigration("add unique index user.email", new(AddIndexMigration).
-		Table("user").Columns("email").Unique())
-}
-
-func addStarMigrations(mg *Migrator) {
-	mg.AddMigration("create star table", new(AddTableMigration).
-		Name("star").WithColumns(
-		&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
-		&Column{Name: "user_id", Type: DB_BigInt, Nullable: false},
-		&Column{Name: "dashboard_id", Type: DB_BigInt, Nullable: false},
-	))
-
-	mg.AddMigration("add unique index star.user_id_dashboard_id", new(AddIndexMigration).
-		Table("star").Columns("user_id", "dashboard_id").Unique())
-}
-
-func addAccountMigrations(mg *Migrator) {
-	mg.AddMigration("create account table", new(AddTableMigration).
-		Name("account").WithColumns(
-		&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
-		&Column{Name: "version", Type: DB_Int, Nullable: false},
-		&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
-		&Column{Name: "created", Type: DB_DateTime, Nullable: false},
-		&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
-	))
-
-	mg.AddMigration("add unique index account.name", new(AddIndexMigration).
-		Table("account").Columns("name").Unique())
-
-	//-------  account_user table -------------------
-	mg.AddMigration("create account_user table", new(AddTableMigration).
-		Name("account_user").WithColumns(
-		&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
-		&Column{Name: "account_id", Type: DB_BigInt},
-		&Column{Name: "user_id", Type: DB_BigInt},
-		&Column{Name: "role", Type: DB_NVarchar, Length: 20},
-		&Column{Name: "created", Type: DB_DateTime},
-		&Column{Name: "updated", Type: DB_DateTime},
-	))
-
-	mg.AddMigration("add unique index account_user_aid_uid", new(AddIndexMigration).
-		Name("aid_uid").Table("account_user").Columns("account_id", "user_id").Unique())
-}
-
-func addDashboardMigration(mg *Migrator) {
-	mg.AddMigration("create dashboard table", new(AddTableMigration).
-		Name("dashboard").WithColumns(
-		&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
-		&Column{Name: "version", Type: DB_Int, Nullable: false},
-		&Column{Name: "slug", Type: DB_NVarchar, Length: 255, Nullable: false},
-		&Column{Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false},
-		&Column{Name: "data", Type: DB_Text, Nullable: false},
-		&Column{Name: "account_id", Type: DB_BigInt, Nullable: false},
-		&Column{Name: "created", Type: DB_DateTime, Nullable: false},
-		&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
-	))
-
-	mg.AddMigration("create dashboard_tag table", new(AddTableMigration).
-		Name("dashboard_tag").WithColumns(
-		&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
-		&Column{Name: "dashboard_id", Type: DB_BigInt, Nullable: false},
-		&Column{Name: "term", Type: DB_NVarchar, Length: 50, Nullable: false},
-	))
-
-	//-------  indexes ------------------
-	mg.AddMigration("add index dashboard.account_id", new(AddIndexMigration).
-		Table("dashboard").Columns("account_id"))
-
-	mg.AddMigration("add unique index dashboard_account_id_slug", new(AddIndexMigration).
-		Table("dashboard").Columns("account_id", "slug").Unique())
-
-	mg.AddMigration("add unique index dashboard_tag.dasboard_id_term", new(AddIndexMigration).
-		Table("dashboard_tag").Columns("dashboard_id", "term").Unique())
-}
-
-func addDataSourceMigration(mg *Migrator) {
-	mg.AddMigration("create data_source table", new(AddTableMigration).
-		Name("data_source").WithColumns(
-		&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
-		&Column{Name: "account_id", Type: DB_BigInt, Nullable: false},
-		&Column{Name: "version", Type: DB_Int, Nullable: false},
-		&Column{Name: "type", Type: DB_NVarchar, Length: 255, Nullable: false},
-		&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
-		&Column{Name: "access", Type: DB_NVarchar, Length: 255, Nullable: false},
-		&Column{Name: "url", Type: DB_NVarchar, Length: 255, Nullable: false},
-		&Column{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
-		&Column{Name: "user", Type: DB_NVarchar, Length: 255, Nullable: true},
-		&Column{Name: "database", Type: DB_NVarchar, Length: 255, Nullable: true},
-		&Column{Name: "basic_auth", Type: DB_Bool, Nullable: false},
-		&Column{Name: "basic_auth_user", Type: DB_NVarchar, Length: 255, Nullable: true},
-		&Column{Name: "basic_auth_password", Type: DB_NVarchar, Length: 255, Nullable: true},
-		&Column{Name: "is_default", Type: DB_Bool, Nullable: false},
-		&Column{Name: "created", Type: DB_DateTime, Nullable: false},
-		&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
-	))
-
-	//-------  indexes ------------------
-	mg.AddMigration("add index data_source.account_id", new(AddIndexMigration).
-		Table("data_source").Columns("account_id"))
-
-	mg.AddMigration("add unique index data_source.account_id_name", new(AddIndexMigration).
-		Table("data_source").Columns("account_id", "name").Unique())
-}
-
-func addApiKeyMigrations(mg *Migrator) {
-	mg.AddMigration("create api_key table", new(AddTableMigration).
-		Name("api_key").WithColumns(
-		&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
-		&Column{Name: "account_id", Type: DB_BigInt, Nullable: false},
-		&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
-		&Column{Name: "key", Type: DB_Varchar, Length: 64, Nullable: false},
-		&Column{Name: "role", Type: DB_NVarchar, Length: 255, Nullable: false},
-		&Column{Name: "created", Type: DB_DateTime, Nullable: false},
-		&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
-	))
-
-	//-------  indexes ------------------
-	mg.AddMigration("add index api_key.account_id", new(AddIndexMigration).
-		Table("api_key").Columns("account_id"))
-
-	mg.AddMigration("add index api_key.account_id_name", new(AddIndexMigration).
-		Table("api_key").Columns("account_id", "name").Unique())
-}

+ 75 - 0
pkg/services/sqlstore/migrations/apikey_mig.go

@@ -0,0 +1,75 @@
+package migrations
+
+import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
+
+func addApiKeyMigrations(mg *Migrator) {
+	apiKeyV1 := Table{
+		Name: "api_key",
+		Columns: []*Column{
+			&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
+			&Column{Name: "account_id", Type: DB_BigInt, Nullable: false},
+			&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "key", Type: DB_Varchar, Length: 64, Nullable: false},
+			&Column{Name: "role", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "created", Type: DB_DateTime, Nullable: false},
+			&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
+		},
+		Indices: []*Index{
+			&Index{Cols: []string{"account_id"}},
+			&Index{Cols: []string{"key"}, Type: UniqueIndex},
+			&Index{Cols: []string{"account_id", "name"}, Type: UniqueIndex},
+		},
+	}
+
+	// create table
+	mg.AddMigration("create api_key table", NewAddTableMigration(apiKeyV1))
+	// create indices
+	mg.AddMigration("add index api_key.account_id", NewAddIndexMigration(apiKeyV1, apiKeyV1.Indices[0]))
+	mg.AddMigration("add index api_key.key", NewAddIndexMigration(apiKeyV1, apiKeyV1.Indices[1]))
+	mg.AddMigration("add index api_key.account_id_name", NewAddIndexMigration(apiKeyV1, apiKeyV1.Indices[2]))
+
+	// ---------------------
+	// account -> org changes
+
+	// drop indexes
+	addDropAllIndicesMigrations(mg, "v1", apiKeyV1)
+	// rename table
+	addTableRenameMigration(mg, "api_key", "api_key_v1", "v1")
+
+	apiKeyV2 := Table{
+		Name: "api_key",
+		Columns: []*Column{
+			&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
+			&Column{Name: "org_id", Type: DB_BigInt, Nullable: false},
+			&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "key", Type: DB_Varchar, Length: 64, Nullable: false},
+			&Column{Name: "role", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "created", Type: DB_DateTime, Nullable: false},
+			&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
+		},
+		Indices: []*Index{
+			&Index{Cols: []string{"org_id"}},
+			&Index{Cols: []string{"key"}, Type: UniqueIndex},
+			&Index{Cols: []string{"org_id", "name"}, Type: UniqueIndex},
+		},
+	}
+
+	// create v2 table
+	mg.AddMigration("create api_key table v2", NewAddTableMigration(apiKeyV2))
+
+	// add v2 indíces
+	addTableIndicesMigrations(mg, "v2", apiKeyV2)
+
+	//------- copy data from v1 to v2 -------------------
+	mg.AddMigration("copy api_key v1 to v2", NewCopyTableDataMigration("api_key", "api_key_v1", map[string]string{
+		"id":      "id",
+		"org_id":  "account_id",
+		"name":    "name",
+		"key":     "key",
+		"role":    "role",
+		"created": "created",
+		"updated": "updated",
+	}))
+
+	mg.AddMigration("Drop old table api_key_v1", NewDropTableMigration("api_key_v1"))
+}

+ 26 - 0
pkg/services/sqlstore/migrations/common.go

@@ -0,0 +1,26 @@
+package migrations
+
+import (
+	"fmt"
+
+	. "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
+)
+
+func addDropAllIndicesMigrations(mg *Migrator, versionSuffix string, table Table) {
+	for _, index := range table.Indices {
+		migrationId := fmt.Sprintf("drop index %s - %s", index.XName(table.Name), versionSuffix)
+		mg.AddMigration(migrationId, NewDropIndexMigration(table, index))
+	}
+}
+
+func addTableIndicesMigrations(mg *Migrator, versionSuffix string, table Table) {
+	for _, index := range table.Indices {
+		migrationId := fmt.Sprintf("create index %s - %s", index.XName(table.Name), versionSuffix)
+		mg.AddMigration(migrationId, NewAddIndexMigration(table, index))
+	}
+}
+
+func addTableRenameMigration(mg *Migrator, oldName string, newName string, versionSuffix string) {
+	migrationId := fmt.Sprintf("Rename table %s to %s - %s", oldName, newName, versionSuffix)
+	mg.AddMigration(migrationId, NewRenameTableMigration(oldName, newName))
+}

+ 89 - 0
pkg/services/sqlstore/migrations/dashboard_mig.go

@@ -0,0 +1,89 @@
+package migrations
+
+import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
+
+func addDashboardMigration(mg *Migrator) {
+	var dashboardV1 = Table{
+		Name: "dashboard",
+		Columns: []*Column{
+			&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
+			&Column{Name: "version", Type: DB_Int, Nullable: false},
+			&Column{Name: "slug", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "data", Type: DB_Text, Nullable: false},
+			&Column{Name: "account_id", Type: DB_BigInt, Nullable: false},
+			&Column{Name: "created", Type: DB_DateTime, Nullable: false},
+			&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
+		},
+		Indices: []*Index{
+			&Index{Cols: []string{"account_id"}},
+			&Index{Cols: []string{"account_id", "slug"}, Type: UniqueIndex},
+		},
+	}
+
+	mg.AddMigration("create dashboard table", NewAddTableMigration(dashboardV1))
+
+	//-------  indexes ------------------
+	mg.AddMigration("add index dashboard.account_id", NewAddIndexMigration(dashboardV1, dashboardV1.Indices[0]))
+	mg.AddMigration("add unique index dashboard_account_id_slug", NewAddIndexMigration(dashboardV1, dashboardV1.Indices[1]))
+
+	dashboardTagV1 := Table{
+		Name: "dashboard_tag",
+		Columns: []*Column{
+			&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
+			&Column{Name: "dashboard_id", Type: DB_BigInt, Nullable: false},
+			&Column{Name: "term", Type: DB_NVarchar, Length: 50, Nullable: false},
+		},
+		Indices: []*Index{
+			&Index{Cols: []string{"dashboard_id", "term"}, Type: UniqueIndex},
+		},
+	}
+
+	mg.AddMigration("create dashboard_tag table", NewAddTableMigration(dashboardTagV1))
+	mg.AddMigration("add unique index dashboard_tag.dasboard_id_term", NewAddIndexMigration(dashboardTagV1, dashboardTagV1.Indices[0]))
+
+	// ---------------------
+	// account -> org changes
+
+	//-------  drop dashboard indexes ------------------
+	addDropAllIndicesMigrations(mg, "v1", dashboardTagV1)
+	//------- rename table ------------------
+	addTableRenameMigration(mg, "dashboard", "dashboard_v1", "v1")
+
+	// dashboard v2
+	var dashboardV2 = Table{
+		Name: "dashboard",
+		Columns: []*Column{
+			&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
+			&Column{Name: "version", Type: DB_Int, Nullable: false},
+			&Column{Name: "slug", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "data", Type: DB_Text, Nullable: false},
+			&Column{Name: "org_id", Type: DB_BigInt, Nullable: false},
+			&Column{Name: "created", Type: DB_DateTime, Nullable: false},
+			&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
+		},
+		Indices: []*Index{
+			&Index{Cols: []string{"org_id"}},
+			&Index{Cols: []string{"org_id", "slug"}, Type: UniqueIndex},
+		},
+	}
+
+	// recreate table
+	mg.AddMigration("create dashboard v2", NewAddTableMigration(dashboardV2))
+	// recreate indices
+	addTableIndicesMigrations(mg, "v2", dashboardV2)
+	// copy data
+	mg.AddMigration("copy dashboard v1 to v2", NewCopyTableDataMigration("dashboard", "dashboard_v1", map[string]string{
+		"id":      "id",
+		"version": "version",
+		"slug":    "slug",
+		"title":   "title",
+		"data":    "data",
+		"org_id":  "account_id",
+		"created": "created",
+		"updated": "updated",
+	}))
+
+	mg.AddMigration("drop table dashboard_v1", NewDropTableMigration("dashboard_v1"))
+}

+ 98 - 0
pkg/services/sqlstore/migrations/datasource_mig.go

@@ -0,0 +1,98 @@
+package migrations
+
+import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
+
+func addDataSourceMigration(mg *Migrator) {
+	var tableV1 = Table{
+		Name: "data_source",
+		Columns: []*Column{
+			&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
+			&Column{Name: "account_id", Type: DB_BigInt, Nullable: false},
+			&Column{Name: "version", Type: DB_Int, Nullable: false},
+			&Column{Name: "type", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "access", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "url", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "user", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "database", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "basic_auth", Type: DB_Bool, Nullable: false},
+			&Column{Name: "basic_auth_user", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "basic_auth_password", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "is_default", Type: DB_Bool, Nullable: false},
+			&Column{Name: "created", Type: DB_DateTime, Nullable: false},
+			&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
+		},
+		Indices: []*Index{
+			&Index{Cols: []string{"account_id"}},
+			&Index{Cols: []string{"account_id", "name"}, Type: UniqueIndex},
+		},
+	}
+
+	mg.AddMigration("create data_source table", NewAddTableMigration(tableV1))
+	mg.AddMigration("add index data_source.account_id", NewAddIndexMigration(tableV1, tableV1.Indices[0]))
+	mg.AddMigration("add unique index data_source.account_id_name", NewAddIndexMigration(tableV1, tableV1.Indices[1]))
+
+	// ---------------------
+	// account -> org changes
+
+	// drop v1 indices
+	addDropAllIndicesMigrations(mg, "v1", tableV1)
+	// rename table
+	addTableRenameMigration(mg, "data_source", "data_source_v1", "v1")
+
+	// new table
+	var tableV2 = Table{
+		Name: "data_source",
+		Columns: []*Column{
+			&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
+			&Column{Name: "org_id", Type: DB_BigInt, Nullable: false},
+			&Column{Name: "version", Type: DB_Int, Nullable: false},
+			&Column{Name: "type", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "access", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "url", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "user", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "database", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "basic_auth", Type: DB_Bool, Nullable: false},
+			&Column{Name: "basic_auth_user", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "basic_auth_password", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "is_default", Type: DB_Bool, Nullable: false},
+			&Column{Name: "json_data", Type: DB_Text, Nullable: true},
+			&Column{Name: "created", Type: DB_DateTime, Nullable: false},
+			&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
+		},
+		Indices: []*Index{
+			&Index{Cols: []string{"org_id"}},
+			&Index{Cols: []string{"org_id", "name"}, Type: UniqueIndex},
+		},
+	}
+
+	// create v2 table
+	mg.AddMigration("create data_source table v2", NewAddTableMigration(tableV2))
+
+	// add v2 indíces
+	addTableIndicesMigrations(mg, "v2", tableV2)
+
+	//------- copy data from v1 to v2 -------------------
+	mg.AddMigration("copy data_source v1 to v2", NewCopyTableDataMigration("data_source", "data_source_v1", map[string]string{
+		"id":                  "id",
+		"org_id":              "account_id",
+		"version":             "version",
+		"type":                "type",
+		"name":                "name",
+		"access":              "access",
+		"url":                 "password",
+		"user":                "user",
+		"database":            "database",
+		"basic_auth":          "basic_auth",
+		"basic_auth_user":     "basic_auth_user",
+		"basic_auth_password": "basic_auth_password",
+		"is_default":          "is_default",
+		"created":             "created",
+		"updated":             "updated",
+	}))
+
+	mg.AddMigration("Drop old table data_source_v1", NewDropTableMigration("data_source_old"))
+}

+ 51 - 0
pkg/services/sqlstore/migrations/migrations.go

@@ -0,0 +1,51 @@
+package migrations
+
+import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
+
+// --- Migration Guide line ---
+// 1. Never change a migration that is committed and pushed to master
+// 2. Always add new migrations (to change or undo previous migrations)
+// 3. Some migraitons are not yet written (rename column, table, drop table, index etc)
+
+func AddMigrations(mg *Migrator) {
+	addMigrationLogMigrations(mg)
+	addUserMigrations(mg)
+	addStarMigrations(mg)
+	addOrgMigrations(mg)
+	addDashboardMigration(mg)
+	addDataSourceMigration(mg)
+	addApiKeyMigrations(mg)
+}
+
+func addMigrationLogMigrations(mg *Migrator) {
+	migrationLogV1 := Table{
+		Name: "migration_log",
+		Columns: []*Column{
+			&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
+			&Column{Name: "migration_id", Type: DB_NVarchar, Length: 255},
+			&Column{Name: "sql", Type: DB_Text},
+			&Column{Name: "success", Type: DB_Bool},
+			&Column{Name: "error", Type: DB_Text},
+			&Column{Name: "timestamp", Type: DB_DateTime},
+		},
+	}
+
+	mg.AddMigration("create migration_log table", NewAddTableMigration(migrationLogV1))
+}
+
+func addStarMigrations(mg *Migrator) {
+	starV1 := Table{
+		Name: "star",
+		Columns: []*Column{
+			&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
+			&Column{Name: "user_id", Type: DB_BigInt, Nullable: false},
+			&Column{Name: "dashboard_id", Type: DB_BigInt, Nullable: false},
+		},
+		Indices: []*Index{
+			&Index{Cols: []string{"user_id", "dashboard_id"}, Type: UniqueIndex},
+		},
+	}
+
+	mg.AddMigration("create star table", NewAddTableMigration(starV1))
+	mg.AddMigration("add unique index star.user_id_dashboard_id", NewAddIndexMigration(starV1, starV1.Indices[0]))
+}

+ 2 - 2
pkg/services/sqlstore/migrations_test.go → pkg/services/sqlstore/migrations/migrations_test.go

@@ -1,4 +1,4 @@
-package sqlstore
+package migrations
 
 import (
 	"fmt"
@@ -32,7 +32,7 @@ func TestMigrations(t *testing.T) {
 
 			mg := NewMigrator(x)
 			mg.LogLevel = log.DEBUG
-			addMigrations(mg)
+			AddMigrations(mg)
 
 			err = mg.Start()
 			So(err, ShouldBeNil)

+ 71 - 0
pkg/services/sqlstore/migrations/org_mig.go

@@ -0,0 +1,71 @@
+package migrations
+
+import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
+
+func addOrgMigrations(mg *Migrator) {
+	orgV1 := Table{
+		Name: "org",
+		Columns: []*Column{
+			&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
+			&Column{Name: "version", Type: DB_Int, Nullable: false},
+			&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "address1", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "address2", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "city", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "state", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "zip_code", Type: DB_NVarchar, Length: 50, Nullable: true},
+			&Column{Name: "country", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "billing_email", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "created", Type: DB_DateTime, Nullable: false},
+			&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
+		},
+		Indices: []*Index{
+			&Index{Cols: []string{"name"}, Type: UniqueIndex},
+		},
+	}
+
+	// add org v1
+	mg.AddMigration("create org table v1", NewAddTableMigration(orgV1))
+	addTableIndicesMigrations(mg, "v1", orgV1)
+
+	orgUserV1 := Table{
+		Name: "org_user",
+		Columns: []*Column{
+			&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
+			&Column{Name: "org_id", Type: DB_BigInt},
+			&Column{Name: "user_id", Type: DB_BigInt},
+			&Column{Name: "role", Type: DB_NVarchar, Length: 20},
+			&Column{Name: "created", Type: DB_DateTime},
+			&Column{Name: "updated", Type: DB_DateTime},
+		},
+		Indices: []*Index{
+			&Index{Cols: []string{"org_id"}},
+			&Index{Cols: []string{"org_id", "user_id"}, Type: UniqueIndex},
+		},
+	}
+
+	//-------  org_user table -------------------
+	mg.AddMigration("create org_user table v1", NewAddTableMigration(orgUserV1))
+	addTableIndicesMigrations(mg, "v1", orgUserV1)
+
+	//-------  copy data from old table-------------------
+	mg.AddMigration("copy data account to org", NewCopyTableDataMigration("org", "account", map[string]string{
+		"id":      "id",
+		"version": "version",
+		"name":    "name",
+		"created": "created",
+		"updated": "updated",
+	}).IfTableExists("account"))
+
+	mg.AddMigration("copy data account_user to org_user", NewCopyTableDataMigration("org_user", "account_user", map[string]string{
+		"id":      "id",
+		"org_id":  "account_id",
+		"user_id": "user_id",
+		"role":    "role",
+		"created": "created",
+		"updated": "updated",
+	}).IfTableExists("account_user"))
+
+	mg.AddMigration("Drop old table account", NewDropTableMigration("account"))
+	mg.AddMigration("Drop old table account_user", NewDropTableMigration("account_user"))
+}

+ 91 - 0
pkg/services/sqlstore/migrations/user_mig.go

@@ -0,0 +1,91 @@
+package migrations
+
+import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
+
+func addUserMigrations(mg *Migrator) {
+	userV1 := Table{
+		Name: "user",
+		Columns: []*Column{
+			&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
+			&Column{Name: "version", Type: DB_Int, Nullable: false},
+			&Column{Name: "login", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "email", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "salt", Type: DB_NVarchar, Length: 50, Nullable: true},
+			&Column{Name: "rands", Type: DB_NVarchar, Length: 50, Nullable: true},
+			&Column{Name: "company", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "account_id", Type: DB_BigInt, Nullable: false},
+			&Column{Name: "is_admin", Type: DB_Bool, Nullable: false},
+			&Column{Name: "created", Type: DB_DateTime, Nullable: false},
+			&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
+		},
+		Indices: []*Index{
+			&Index{Cols: []string{"login"}, Type: UniqueIndex},
+			&Index{Cols: []string{"email"}, Type: UniqueIndex},
+		},
+	}
+
+	// create table
+	mg.AddMigration("create user table", NewAddTableMigration(userV1))
+	// add indices
+	mg.AddMigration("add unique index user.login", NewAddIndexMigration(userV1, userV1.Indices[0]))
+	mg.AddMigration("add unique index user.email", NewAddIndexMigration(userV1, userV1.Indices[1]))
+
+	// ---------------------
+	// account -> org changes
+
+	//-------  drop indexes ------------------
+	addDropAllIndicesMigrations(mg, "v1", userV1)
+
+	//------- rename table ------------------
+	addTableRenameMigration(mg, "user", "user_v1", "v1")
+
+	//------- recreate table with new column names ------------------
+	userV2 := Table{
+		Name: "user",
+		Columns: []*Column{
+			&Column{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
+			&Column{Name: "version", Type: DB_Int, Nullable: false},
+			&Column{Name: "login", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "email", Type: DB_NVarchar, Length: 255, Nullable: false},
+			&Column{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "password", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "salt", Type: DB_NVarchar, Length: 50, Nullable: true},
+			&Column{Name: "rands", Type: DB_NVarchar, Length: 50, Nullable: true},
+			&Column{Name: "company", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "org_id", Type: DB_BigInt, Nullable: false},
+			&Column{Name: "is_admin", Type: DB_Bool, Nullable: false},
+			&Column{Name: "email_verified", Type: DB_Bool, Nullable: true},
+			&Column{Name: "theme", Type: DB_NVarchar, Length: 255, Nullable: true},
+			&Column{Name: "created", Type: DB_DateTime, Nullable: false},
+			&Column{Name: "updated", Type: DB_DateTime, Nullable: false},
+		},
+		Indices: []*Index{
+			&Index{Cols: []string{"login"}, Type: UniqueIndex},
+			&Index{Cols: []string{"email"}, Type: UniqueIndex},
+		},
+	}
+
+	mg.AddMigration("create user table v2", NewAddTableMigration(userV2))
+	addTableIndicesMigrations(mg, "v2", userV2)
+
+	//------- copy data from v1 to v2 -------------------
+	mg.AddMigration("copy data_source v1 to v2", NewCopyTableDataMigration("user", "user_v1", map[string]string{
+		"id":       "id",
+		"version":  "version",
+		"login":    "login",
+		"email":    "email",
+		"name":     "name",
+		"password": "password",
+		"salt":     "salt",
+		"rands":    "rands",
+		"company":  "company",
+		"org_id":   "account_id",
+		"is_admin": "is_admin",
+		"created":  "created",
+		"updated":  "updated",
+	}))
+
+	mg.AddMigration("Drop old table user_v1", NewDropTableMigration("user_v1"))
+}

+ 0 - 131
pkg/services/sqlstore/migrator/builder.go

@@ -1,131 +0,0 @@
-package migrator
-
-import (
-	"fmt"
-	"strings"
-)
-
-type MigrationBase struct {
-	id string
-}
-
-func (m *MigrationBase) Id() string {
-	return m.id
-}
-
-func (m *MigrationBase) SetId(id string) {
-	m.id = id
-}
-
-type RawSqlMigration struct {
-	MigrationBase
-
-	sqlite string
-	mysql  string
-}
-
-func (m *RawSqlMigration) Sql(dialect Dialect) string {
-	switch dialect.DriverName() {
-	case MYSQL:
-		return m.mysql
-	case SQLITE:
-		return m.sqlite
-	}
-
-	panic("db type not supported")
-}
-
-func (m *RawSqlMigration) Sqlite(sql string) *RawSqlMigration {
-	m.sqlite = sql
-	return m
-}
-
-func (m *RawSqlMigration) Mysql(sql string) *RawSqlMigration {
-	m.mysql = sql
-	return m
-}
-
-type AddColumnMigration struct {
-	MigrationBase
-	tableName string
-	column    *Column
-}
-
-func (m *AddColumnMigration) Table(tableName string) *AddColumnMigration {
-	m.tableName = tableName
-	return m
-}
-
-func (m *AddColumnMigration) Column(col *Column) *AddColumnMigration {
-	m.column = col
-	return m
-}
-
-func (m *AddColumnMigration) Sql(dialect Dialect) string {
-	return dialect.AddColumnSql(m.tableName, m.column)
-}
-
-type AddIndexMigration struct {
-	MigrationBase
-	tableName string
-	index     Index
-}
-
-func (m *AddIndexMigration) Name(name string) *AddIndexMigration {
-	m.index.Name = name
-	return m
-}
-
-func (m *AddIndexMigration) Table(tableName string) *AddIndexMigration {
-	m.tableName = tableName
-	return m
-}
-
-func (m *AddIndexMigration) Unique() *AddIndexMigration {
-	m.index.Type = UniqueIndex
-	return m
-}
-
-func (m *AddIndexMigration) Columns(columns ...string) *AddIndexMigration {
-	m.index.Cols = columns
-	return m
-}
-
-func (m *AddIndexMigration) Sql(dialect Dialect) string {
-	if m.index.Name == "" {
-		m.index.Name = fmt.Sprintf("%s", strings.Join(m.index.Cols, "_"))
-	}
-	return dialect.CreateIndexSql(m.tableName, &m.index)
-}
-
-type AddTableMigration struct {
-	MigrationBase
-	table Table
-}
-
-func (m *AddTableMigration) Sql(d Dialect) string {
-	return d.CreateTableSql(&m.table)
-}
-
-func (m *AddTableMigration) Name(name string) *AddTableMigration {
-	m.table.Name = name
-	return m
-}
-
-func (m *AddTableMigration) WithColumns(columns ...*Column) *AddTableMigration {
-	for _, col := range columns {
-		m.table.Columns = append(m.table.Columns, col)
-		if col.IsPrimaryKey {
-			m.table.PrimaryKeys = append(m.table.PrimaryKeys, col.Name)
-		}
-	}
-	return m
-}
-
-func (m *AddTableMigration) WithColumn(col *Column) *AddTableMigration {
-	m.table.Columns = append(m.table.Columns, col)
-	if col.IsPrimaryKey {
-		m.table.PrimaryKeys = append(m.table.PrimaryKeys, col.Name)
-	}
-	return m
-}

+ 13 - 0
pkg/services/sqlstore/migrator/conditions.go

@@ -0,0 +1,13 @@
+package migrator
+
+type MigrationCondition interface {
+	Sql(dialect Dialect) (string, []interface{})
+}
+
+type IfTableExistsCondition struct {
+	TableName string
+}
+
+func (c *IfTableExistsCondition) Sql(dialect Dialect) (string, []interface{}) {
+	return dialect.TableCheckSql(c.TableName)
+}

+ 40 - 4
pkg/services/sqlstore/migrator/dialect.go

@@ -20,8 +20,12 @@ type Dialect interface {
 	CreateIndexSql(tableName string, index *Index) string
 	CreateTableSql(table *Table) string
 	AddColumnSql(tableName string, Col *Column) string
+	CopyTableData(sourceTable string, targetTable string, sourceCols []string, targetCols []string) string
+	DropTable(tableName string) string
+	DropIndexSql(tableName string, index *Index) string
 
 	TableCheckSql(tableName string) (string, []interface{})
+	RenameTable(oldName string, newName string) string
 }
 
 func NewDialect(name string) Dialect {
@@ -101,15 +105,47 @@ func (db *BaseDialect) AddColumnSql(tableName string, col *Column) string {
 func (db *BaseDialect) CreateIndexSql(tableName string, index *Index) string {
 	quote := db.dialect.Quote
 	var unique string
-	var idxName string
 	if index.Type == UniqueIndex {
 		unique = " UNIQUE"
-		idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name)
-	} else {
-		idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name)
 	}
 
+	idxName := index.XName(tableName)
+
 	return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v);", unique,
 		quote(idxName), quote(tableName),
 		quote(strings.Join(index.Cols, quote(","))))
 }
+
+func (db *BaseDialect) QuoteColList(cols []string) string {
+	var sourceColsSql = ""
+	for _, col := range cols {
+		sourceColsSql += db.dialect.Quote(col)
+		sourceColsSql += "\n, "
+	}
+	return strings.TrimSuffix(sourceColsSql, "\n, ")
+}
+
+func (db *BaseDialect) CopyTableData(sourceTable string, targetTable string, sourceCols []string, targetCols []string) string {
+	sourceColsSql := db.QuoteColList(sourceCols)
+	targetColsSql := db.QuoteColList(targetCols)
+
+	quote := db.dialect.Quote
+	return fmt.Sprintf("INSERT INTO %s (%s) SELECT %s FROM %s", quote(targetTable), targetColsSql, sourceColsSql, quote(sourceTable))
+}
+
+func (db *BaseDialect) DropTable(tableName string) string {
+	quote := db.dialect.Quote
+	return fmt.Sprintf("DROP TABLE IF EXISTS %s", quote(tableName))
+}
+
+func (db *BaseDialect) RenameTable(oldName string, newName string) string {
+	quote := db.dialect.Quote
+	return fmt.Sprintf("ALTER TABLE %s RENAME TO %s", quote(oldName), quote(newName))
+}
+
+func (db *BaseDialect) DropIndexSql(tableName string, index *Index) string {
+	quote := db.dialect.Quote
+	var name string
+	name = index.XName(tableName)
+	return fmt.Sprintf("DROP INDEX %v ON %s", quote(name), quote(tableName))
+}

+ 190 - 0
pkg/services/sqlstore/migrator/migrations.go

@@ -0,0 +1,190 @@
+package migrator
+
+import (
+	"fmt"
+	"strings"
+)
+
+type MigrationBase struct {
+	id        string
+	Condition MigrationCondition
+}
+
+func (m *MigrationBase) Id() string {
+	return m.id
+}
+
+func (m *MigrationBase) SetId(id string) {
+	m.id = id
+}
+
+func (m *MigrationBase) GetCondition() MigrationCondition {
+	return m.Condition
+}
+
+type RawSqlMigration struct {
+	MigrationBase
+
+	sqlite string
+	mysql  string
+}
+
+func (m *RawSqlMigration) Sql(dialect Dialect) string {
+	switch dialect.DriverName() {
+	case MYSQL:
+		return m.mysql
+	case SQLITE:
+		return m.sqlite
+	}
+
+	panic("db type not supported")
+}
+
+func (m *RawSqlMigration) Sqlite(sql string) *RawSqlMigration {
+	m.sqlite = sql
+	return m
+}
+
+func (m *RawSqlMigration) Mysql(sql string) *RawSqlMigration {
+	m.mysql = sql
+	return m
+}
+
+type AddColumnMigration struct {
+	MigrationBase
+	tableName string
+	column    *Column
+}
+
+func (m *AddColumnMigration) Table(tableName string) *AddColumnMigration {
+	m.tableName = tableName
+	return m
+}
+
+func (m *AddColumnMigration) Column(col *Column) *AddColumnMigration {
+	m.column = col
+	return m
+}
+
+func (m *AddColumnMigration) Sql(dialect Dialect) string {
+	return dialect.AddColumnSql(m.tableName, m.column)
+}
+
+type AddIndexMigration struct {
+	MigrationBase
+	tableName string
+	index     *Index
+}
+
+func NewAddIndexMigration(table Table, index *Index) *AddIndexMigration {
+	return &AddIndexMigration{tableName: table.Name, index: index}
+}
+
+func (m *AddIndexMigration) Table(tableName string) *AddIndexMigration {
+	m.tableName = tableName
+	return m
+}
+
+func (m *AddIndexMigration) Sql(dialect Dialect) string {
+	return dialect.CreateIndexSql(m.tableName, m.index)
+}
+
+type DropIndexMigration struct {
+	MigrationBase
+	tableName string
+	index     *Index
+}
+
+func NewDropIndexMigration(table Table, index *Index) *DropIndexMigration {
+	return &DropIndexMigration{tableName: table.Name, index: index}
+}
+
+func (m *DropIndexMigration) Sql(dialect Dialect) string {
+	if m.index.Name == "" {
+		m.index.Name = fmt.Sprintf("%s", strings.Join(m.index.Cols, "_"))
+	}
+	return dialect.DropIndexSql(m.tableName, m.index)
+}
+
+type AddTableMigration struct {
+	MigrationBase
+	table Table
+}
+
+func NewAddTableMigration(table Table) *AddTableMigration {
+	for _, col := range table.Columns {
+		if col.IsPrimaryKey {
+			table.PrimaryKeys = append(table.PrimaryKeys, col.Name)
+		}
+	}
+	return &AddTableMigration{table: table}
+}
+
+func (m *AddTableMigration) Sql(d Dialect) string {
+	return d.CreateTableSql(&m.table)
+}
+
+type DropTableMigration struct {
+	MigrationBase
+	tableName string
+}
+
+func NewDropTableMigration(tableName string) *DropTableMigration {
+	return &DropTableMigration{tableName: tableName}
+}
+
+func (m *DropTableMigration) Sql(d Dialect) string {
+	return d.DropTable(m.tableName)
+}
+
+type RenameTableMigration struct {
+	MigrationBase
+	oldName string
+	newName string
+}
+
+func NewRenameTableMigration(oldName string, newName string) *RenameTableMigration {
+	return &RenameTableMigration{oldName: oldName, newName: newName}
+}
+
+func (m *RenameTableMigration) IfTableExists(tableName string) *RenameTableMigration {
+	m.Condition = &IfTableExistsCondition{TableName: tableName}
+	return m
+}
+
+func (m *RenameTableMigration) Rename(oldName string, newName string) *RenameTableMigration {
+	m.oldName = oldName
+	m.newName = newName
+	return m
+}
+
+func (m *RenameTableMigration) Sql(d Dialect) string {
+	return d.RenameTable(m.oldName, m.newName)
+}
+
+type CopyTableDataMigration struct {
+	MigrationBase
+	sourceTable string
+	targetTable string
+	sourceCols  []string
+	targetCols  []string
+	colMap      map[string]string
+}
+
+func NewCopyTableDataMigration(targetTable string, sourceTable string, colMap map[string]string) *CopyTableDataMigration {
+	m := &CopyTableDataMigration{sourceTable: sourceTable, targetTable: targetTable}
+	for key, value := range colMap {
+		m.targetCols = append(m.targetCols, key)
+		m.sourceCols = append(m.sourceCols, value)
+	}
+	return m
+}
+
+func (m *CopyTableDataMigration) IfTableExists(tableName string) *CopyTableDataMigration {
+	m.Condition = &IfTableExistsCondition{TableName: tableName}
+	return m
+}
+
+func (m *CopyTableDataMigration) Sql(d Dialect) string {
+	return d.CopyTableData(m.sourceTable, m.targetTable, m.sourceCols, m.targetCols)
+}

+ 16 - 5
pkg/services/sqlstore/migrator/migrator.go

@@ -5,9 +5,9 @@ import (
 
 	_ "github.com/go-sql-driver/mysql"
 	"github.com/go-xorm/xorm"
+	"github.com/grafana/grafana/pkg/log"
 	_ "github.com/lib/pq"
 	_ "github.com/mattn/go-sqlite3"
-	"github.com/grafana/grafana/pkg/log"
 )
 
 type Migrator struct {
@@ -70,7 +70,7 @@ func (mg *Migrator) GetMigrationLog() (map[string]MigrationLog, error) {
 
 func (mg *Migrator) Start() error {
 	if mg.LogLevel <= log.INFO {
-		log.Info("Migrator:: Starting DB migration")
+		log.Info("Migrator: Starting DB migration")
 	}
 
 	logMap, err := mg.GetMigrationLog()
@@ -82,7 +82,7 @@ func (mg *Migrator) Start() error {
 		_, exists := logMap[m.Id()]
 		if exists {
 			if mg.LogLevel <= log.DEBUG {
-				log.Debug("Migrator:: Skipping migration: %v, Already executed", m.Id())
+				log.Debug("Migrator: Skipping migration: %v, Already executed", m.Id())
 			}
 			continue
 		}
@@ -114,13 +114,24 @@ func (mg *Migrator) Start() error {
 
 func (mg *Migrator) exec(m Migration) error {
 	if mg.LogLevel <= log.INFO {
-		log.Info("Migrator::exec migration id: %v", m.Id())
+		log.Info("Migrator: exec migration id: %v", m.Id())
 	}
 
 	err := mg.inTransaction(func(sess *xorm.Session) error {
+
+		condition := m.GetCondition()
+		if condition != nil {
+			sql, args := condition.Sql(mg.dialect)
+			results, err := sess.Query(sql, args...)
+			if err != nil || len(results) == 0 {
+				log.Info("Migrator: skipping migration id: %v, condition not fulfilled", m.Id())
+				return sess.Rollback()
+			}
+		}
+
 		_, err := sess.Exec(m.Sql(mg.dialect))
 		if err != nil {
-			log.Error(3, "Migrator::exec FAILED migration id: %v, err: %v", m.Id(), err)
+			log.Error(3, "Migrator: exec FAILED migration id: %v, err: %v", m.Id(), err)
 			return err
 		}
 		return nil

+ 10 - 1
pkg/services/sqlstore/migrator/postgres_dialect.go

@@ -1,6 +1,9 @@
 package migrator
 
-import "strconv"
+import (
+	"fmt"
+	"strconv"
+)
 
 type Postgres struct {
 	BaseDialect
@@ -84,3 +87,9 @@ func (db *Postgres) TableCheckSql(tableName string) (string, []interface{}) {
 	sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?"
 	return sql, args
 }
+
+func (db *Postgres) DropIndexSql(tableName string, index *Index) string {
+	quote := db.Quote
+	idxName := index.XName(tableName)
+	return fmt.Sprintf("DROP INDEX %v", quote(idxName))
+}

+ 9 - 0
pkg/services/sqlstore/migrator/sqlite_dialect.go

@@ -1,5 +1,7 @@
 package migrator
 
+import "fmt"
+
 type Sqlite3 struct {
 	BaseDialect
 }
@@ -57,3 +59,10 @@ func (db *Sqlite3) TableCheckSql(tableName string) (string, []interface{}) {
 	args := []interface{}{tableName}
 	return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args
 }
+
+func (db *Sqlite3) DropIndexSql(tableName string, index *Index) string {
+	quote := db.Quote
+	//var unique string
+	idxName := index.XName(tableName)
+	return fmt.Sprintf("DROP INDEX %v", quote(idxName))
+}

+ 22 - 0
pkg/services/sqlstore/migrator/types.go

@@ -1,5 +1,10 @@
 package migrator
 
+import (
+	"fmt"
+	"strings"
+)
+
 const (
 	POSTGRES = "postgres"
 	SQLITE   = "sqlite3"
@@ -10,6 +15,7 @@ type Migration interface {
 	Sql(dialect Dialect) string
 	Id() string
 	SetId(string)
+	GetCondition() MigrationCondition
 }
 
 type SQLType string
@@ -24,6 +30,7 @@ type Table struct {
 	Name        string
 	Columns     []*Column
 	PrimaryKeys []string
+	Indices     []*Index
 }
 
 const (
@@ -37,6 +44,21 @@ type Index struct {
 	Cols []string
 }
 
+func (index *Index) XName(tableName string) string {
+	if index.Name == "" {
+		index.Name = fmt.Sprintf("%s", strings.Join(index.Cols, "_"))
+	}
+
+	if !strings.HasPrefix(index.Name, "UQE_") &&
+		!strings.HasPrefix(index.Name, "IDX_") {
+		if index.Type == UniqueIndex {
+			return fmt.Sprintf("UQE_%v_%v", tableName, index.Name)
+		}
+		return fmt.Sprintf("IDX_%v_%v", tableName, index.Name)
+	}
+	return index.Name
+}
+
 var (
 	DB_Bit       = "BIT"
 	DB_TinyInt   = "TINYINT"

+ 134 - 0
pkg/services/sqlstore/org.go

@@ -0,0 +1,134 @@
+package sqlstore
+
+import (
+	"time"
+
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/events"
+	"github.com/grafana/grafana/pkg/log"
+	m "github.com/grafana/grafana/pkg/models"
+)
+
+func init() {
+	bus.AddHandler("sql", GetOrgById)
+	bus.AddHandler("sql", CreateOrg)
+	bus.AddHandler("sql", UpdateOrg)
+	bus.AddHandler("sql", GetOrgByName)
+	bus.AddHandler("sql", GetOrgList)
+	bus.AddHandler("sql", DeleteOrg)
+}
+
+func GetOrgList(query *m.GetOrgListQuery) error {
+	return x.Find(&query.Result)
+}
+
+func GetOrgById(query *m.GetOrgByIdQuery) error {
+	var org m.Org
+	exists, err := x.Id(query.Id).Get(&org)
+	if err != nil {
+		return err
+	}
+
+	if !exists {
+		return m.ErrOrgNotFound
+	}
+
+	query.Result = &org
+	return nil
+}
+
+func GetOrgByName(query *m.GetOrgByNameQuery) error {
+	var org m.Org
+	exists, err := x.Where("name=?", query.Name).Get(&org)
+	if err != nil {
+		return err
+	}
+
+	if !exists {
+		return m.ErrOrgNotFound
+	}
+
+	query.Result = &org
+	return nil
+}
+
+func CreateOrg(cmd *m.CreateOrgCommand) error {
+	return inTransaction2(func(sess *session) error {
+
+		org := m.Org{
+			Name:    cmd.Name,
+			Created: time.Now(),
+			Updated: time.Now(),
+		}
+
+		if _, err := sess.Insert(&org); err != nil {
+			return err
+		}
+
+		user := m.OrgUser{
+			OrgId:   org.Id,
+			UserId:  cmd.UserId,
+			Role:    m.ROLE_ADMIN,
+			Created: time.Now(),
+			Updated: time.Now(),
+		}
+
+		_, err := sess.Insert(&user)
+		cmd.Result = org
+
+		sess.publishAfterCommit(&events.OrgCreated{
+			Timestamp: org.Created,
+			Id:        org.Id,
+			Name:      org.Name,
+		})
+
+		return err
+	})
+}
+
+func UpdateOrg(cmd *m.UpdateOrgCommand) error {
+	return inTransaction2(func(sess *session) error {
+
+		org := m.Org{
+			Name:    cmd.Name,
+			Updated: time.Now(),
+		}
+
+		if _, err := sess.Id(cmd.OrgId).Update(&org); err != nil {
+			return err
+		}
+
+		sess.publishAfterCommit(&events.OrgUpdated{
+			Timestamp: org.Updated,
+			Id:        org.Id,
+			Name:      org.Name,
+		})
+
+		return nil
+	})
+}
+
+func DeleteOrg(cmd *m.DeleteOrgCommand) error {
+	return inTransaction2(func(sess *session) error {
+
+		deletes := []string{
+			"DELETE FROM star WHERE EXISTS (SELECT 1 FROM dashboard WHERE org_id = ?)",
+			"DELETE FROM dashboard_tag WHERE EXISTS (SELECT 1 FROM dashboard WHERE org_id = ?)",
+			"DELETE FROM dashboard WHERE org_id = ?",
+			"DELETE FROM api_key WHERE org_id = ?",
+			"DELETE FROM data_source WHERE org_id = ?",
+			"DELETE FROM org_user WHERE org_id = ?",
+			"DELETE FROM org WHERE id = ?",
+		}
+
+		for _, sql := range deletes {
+			log.Trace(sql)
+			_, err := sess.Exec(sql, cmd.Id)
+			if err != nil {
+				return err
+			}
+		}
+
+		return nil
+	})
+}

+ 37 - 37
pkg/services/sqlstore/account_test.go → pkg/services/sqlstore/org_test.go

@@ -14,12 +14,12 @@ func TestAccountDataAccess(t *testing.T) {
 	Convey("Testing Account DB Access", t, func() {
 		InitTestDB(t)
 
-		Convey("Given single account mode", func() {
-			setting.SingleAccountMode = true
-			setting.DefaultAccountName = "test"
-			setting.DefaultAccountRole = "Viewer"
+		Convey("Given single org mode", func() {
+			setting.SingleOrgMode = true
+			setting.DefaultOrgName = "test"
+			setting.DefaultOrgRole = "Viewer"
 
-			Convey("Users should be added to default account", func() {
+			Convey("Users should be added to default organization", func() {
 				ac1cmd := m.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"}
 				ac2cmd := m.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name"}
 
@@ -28,20 +28,20 @@ func TestAccountDataAccess(t *testing.T) {
 				err = CreateUser(&ac2cmd)
 				So(err, ShouldBeNil)
 
-				q1 := m.GetUserAccountsQuery{UserId: ac1cmd.Result.Id}
-				q2 := m.GetUserAccountsQuery{UserId: ac2cmd.Result.Id}
-				GetUserAccounts(&q1)
-				GetUserAccounts(&q2)
+				q1 := m.GetUserOrgListQuery{UserId: ac1cmd.Result.Id}
+				q2 := m.GetUserOrgListQuery{UserId: ac2cmd.Result.Id}
+				GetUserOrgList(&q1)
+				GetUserOrgList(&q2)
 
-				So(q1.Result[0].AccountId, ShouldEqual, q2.Result[0].AccountId)
+				So(q1.Result[0].OrgId, ShouldEqual, q2.Result[0].OrgId)
 				So(q1.Result[0].Role, ShouldEqual, "Viewer")
 			})
 		})
 
 		Convey("Given two saved users", func() {
-			setting.SingleAccountMode = false
+			setting.SingleOrgMode = false
+			setting.DefaultOrgName = "test"
 
-			setting.DefaultAccountName = "test"
 			ac1cmd := m.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"}
 			ac2cmd := m.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name", IsAdmin: true}
 
@@ -70,14 +70,14 @@ func TestAccountDataAccess(t *testing.T) {
 				So(query.Result[1].Email, ShouldEqual, "ac2@test.com")
 			})
 
-			Convey("Given an added account user", func() {
-				cmd := m.AddAccountUserCommand{
-					AccountId: ac1.AccountId,
-					UserId:    ac2.Id,
-					Role:      m.ROLE_VIEWER,
+			Convey("Given an added org user", func() {
+				cmd := m.AddOrgUserCommand{
+					OrgId:  ac1.OrgId,
+					UserId: ac2.Id,
+					Role:   m.ROLE_VIEWER,
 				}
 
-				err := AddAccountUser(&cmd)
+				err := AddOrgUser(&cmd)
 				Convey("Should have been saved without error", func() {
 					So(err, ShouldBeNil)
 				})
@@ -88,54 +88,54 @@ func TestAccountDataAccess(t *testing.T) {
 
 					So(err, ShouldBeNil)
 					So(query.Result.Email, ShouldEqual, "ac2@test.com")
-					So(query.Result.AccountId, ShouldEqual, ac2.AccountId)
+					So(query.Result.OrgId, ShouldEqual, ac2.OrgId)
 					So(query.Result.Name, ShouldEqual, "ac2 name")
 					So(query.Result.Login, ShouldEqual, "ac2")
-					So(query.Result.AccountRole, ShouldEqual, "Admin")
-					So(query.Result.AccountName, ShouldEqual, "ac2@test.com")
+					So(query.Result.OrgRole, ShouldEqual, "Admin")
+					So(query.Result.OrgName, ShouldEqual, "ac2@test.com")
 					So(query.Result.IsGrafanaAdmin, ShouldBeTrue)
 				})
 
-				Convey("Can get user accounts", func() {
-					query := m.GetUserAccountsQuery{UserId: ac2.Id}
-					err := GetUserAccounts(&query)
+				Convey("Can get user organizations", func() {
+					query := m.GetUserOrgListQuery{UserId: ac2.Id}
+					err := GetUserOrgList(&query)
 
 					So(err, ShouldBeNil)
 					So(len(query.Result), ShouldEqual, 2)
 				})
 
-				Convey("Can get account users", func() {
-					query := m.GetAccountUsersQuery{AccountId: ac1.AccountId}
-					err := GetAccountUsers(&query)
+				Convey("Can get organization users", func() {
+					query := m.GetOrgUsersQuery{OrgId: ac1.OrgId}
+					err := GetOrgUsers(&query)
 
 					So(err, ShouldBeNil)
 					So(len(query.Result), ShouldEqual, 2)
 					So(query.Result[0].Role, ShouldEqual, "Admin")
 				})
 
-				Convey("Can set using account", func() {
-					cmd := m.SetUsingAccountCommand{UserId: ac2.Id, AccountId: ac1.Id}
-					err := SetUsingAccount(&cmd)
+				Convey("Can set using org", func() {
+					cmd := m.SetUsingOrgCommand{UserId: ac2.Id, OrgId: ac1.Id}
+					err := SetUsingOrg(&cmd)
 					So(err, ShouldBeNil)
 
-					Convey("SignedInUserQuery with a different account", func() {
+					Convey("SignedInUserQuery with a different org", func() {
 						query := m.GetSignedInUserQuery{UserId: ac2.Id}
 						err := GetSignedInUser(&query)
 
 						So(err, ShouldBeNil)
-						So(query.Result.AccountId, ShouldEqual, ac1.Id)
+						So(query.Result.OrgId, ShouldEqual, ac1.Id)
 						So(query.Result.Email, ShouldEqual, "ac2@test.com")
 						So(query.Result.Name, ShouldEqual, "ac2 name")
 						So(query.Result.Login, ShouldEqual, "ac2")
-						So(query.Result.AccountName, ShouldEqual, "ac1@test.com")
-						So(query.Result.AccountRole, ShouldEqual, "Viewer")
+						So(query.Result.OrgName, ShouldEqual, "ac1@test.com")
+						So(query.Result.OrgRole, ShouldEqual, "Viewer")
 					})
 				})
 
 				Convey("Cannot delete last admin account user", func() {
-					cmd := m.RemoveAccountUserCommand{AccountId: ac1.AccountId, UserId: ac1.Id}
-					err := RemoveAccountUser(&cmd)
-					So(err, ShouldEqual, m.ErrLastAccountAdmin)
+					cmd := m.RemoveOrgUserCommand{OrgId: ac1.OrgId, UserId: ac1.Id}
+					err := RemoveOrgUser(&cmd)
+					So(err, ShouldEqual, m.ErrLastOrgAdmin)
 				})
 			})
 		})

+ 67 - 0
pkg/services/sqlstore/org_users.go

@@ -0,0 +1,67 @@
+package sqlstore
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/go-xorm/xorm"
+
+	"github.com/grafana/grafana/pkg/bus"
+	m "github.com/grafana/grafana/pkg/models"
+)
+
+func init() {
+	bus.AddHandler("sql", AddOrgUser)
+	bus.AddHandler("sql", RemoveOrgUser)
+	bus.AddHandler("sql", GetOrgUsers)
+}
+
+func AddOrgUser(cmd *m.AddOrgUserCommand) error {
+	return inTransaction(func(sess *xorm.Session) error {
+
+		entity := m.OrgUser{
+			OrgId:   cmd.OrgId,
+			UserId:  cmd.UserId,
+			Role:    cmd.Role,
+			Created: time.Now(),
+			Updated: time.Now(),
+		}
+
+		_, err := sess.Insert(&entity)
+		return err
+	})
+}
+
+func GetOrgUsers(query *m.GetOrgUsersQuery) error {
+	query.Result = make([]*m.OrgUserDTO, 0)
+	sess := x.Table("org_user")
+	sess.Join("INNER", "user", fmt.Sprintf("org_user.user_id=%s.id", x.Dialect().Quote("user")))
+	sess.Where("org_user.org_id=?", query.OrgId)
+	sess.Cols("org_user.org_id", "org_user.user_id", "user.email", "user.login", "org_user.role")
+	sess.Asc("user.email", "user.login")
+
+	err := sess.Find(&query.Result)
+	return err
+}
+
+func RemoveOrgUser(cmd *m.RemoveOrgUserCommand) error {
+	return inTransaction(func(sess *xorm.Session) error {
+		var rawSql = "DELETE FROM org_user WHERE org_id=? and user_id=?"
+		_, err := sess.Exec(rawSql, cmd.OrgId, cmd.UserId)
+		if err != nil {
+			return err
+		}
+
+		// validate that there is an admin user left
+		res, err := sess.Query("SELECT 1 from org_user WHERE org_id=? and role='Admin'", cmd.OrgId)
+		if err != nil {
+			return err
+		}
+
+		if len(res) == 0 {
+			return m.ErrLastOrgAdmin
+		}
+
+		return err
+	})
+}

+ 2 - 1
pkg/services/sqlstore/sqlstore.go

@@ -9,6 +9,7 @@ import (
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/log"
 	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
 	"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
 	"github.com/grafana/grafana/pkg/setting"
 
@@ -73,7 +74,7 @@ func SetEngine(engine *xorm.Engine, enableLog bool) (err error) {
 
 	migrator := migrator.NewMigrator(x)
 	migrator.LogLevel = log.INFO
-	addMigrations(migrator)
+	migrations.AddMigrations(migrator)
 
 	if err := migrator.Start(); err != nil {
 		return fmt.Errorf("Sqlstore::Migration failed err: %v\n", err)

+ 104 - 47
pkg/services/sqlstore/user.go

@@ -1,6 +1,7 @@
 package sqlstore
 
 import (
+	"fmt"
 	"strings"
 	"time"
 
@@ -15,59 +16,64 @@ import (
 
 func init() {
 	bus.AddHandler("sql", CreateUser)
+	bus.AddHandler("sql", GetUserById)
 	bus.AddHandler("sql", UpdateUser)
+	bus.AddHandler("sql", ChangeUserPassword)
 	bus.AddHandler("sql", GetUserByLogin)
-	bus.AddHandler("sql", SetUsingAccount)
+	bus.AddHandler("sql", SetUsingOrg)
 	bus.AddHandler("sql", GetUserInfo)
 	bus.AddHandler("sql", GetSignedInUser)
 	bus.AddHandler("sql", SearchUsers)
-	bus.AddHandler("sql", GetUserAccounts)
+	bus.AddHandler("sql", GetUserOrgList)
+	bus.AddHandler("sql", DeleteUser)
+	bus.AddHandler("sql", SetUsingOrg)
+	bus.AddHandler("sql", UpdateUserPermissions)
 }
 
-func getAccountIdForNewUser(userEmail string, sess *session) (int64, error) {
-	var account m.Account
+func getOrgIdForNewUser(userEmail string, sess *session) (int64, error) {
+	var org m.Org
 
-	if setting.SingleAccountMode {
-		has, err := sess.Where("name=?", setting.DefaultAccountName).Get(&account)
+	if setting.SingleOrgMode {
+		has, err := sess.Where("name=?", setting.DefaultOrgName).Get(&org)
 		if err != nil {
 			return 0, err
 		}
 		if has {
-			return account.Id, nil
+			return org.Id, nil
 		} else {
-			account.Name = setting.DefaultAccountName
+			org.Name = setting.DefaultOrgName
 		}
 	} else {
-		account.Name = userEmail
+		org.Name = userEmail
 	}
 
-	account.Created = time.Now()
-	account.Updated = time.Now()
+	org.Created = time.Now()
+	org.Updated = time.Now()
 
-	if _, err := sess.Insert(&account); err != nil {
+	if _, err := sess.Insert(&org); err != nil {
 		return 0, err
 	}
 
-	return account.Id, nil
+	return org.Id, nil
 }
 
 func CreateUser(cmd *m.CreateUserCommand) error {
 	return inTransaction2(func(sess *session) error {
-		accountId, err := getAccountIdForNewUser(cmd.Email, sess)
+		orgId, err := getOrgIdForNewUser(cmd.Email, sess)
 		if err != nil {
 			return err
 		}
 
 		// create user
 		user := m.User{
-			Email:     cmd.Email,
-			Name:      cmd.Name,
-			Login:     cmd.Login,
-			Company:   cmd.Company,
-			IsAdmin:   cmd.IsAdmin,
-			AccountId: accountId,
-			Created:   time.Now(),
-			Updated:   time.Now(),
+			Email:   cmd.Email,
+			Name:    cmd.Name,
+			Login:   cmd.Login,
+			Company: cmd.Company,
+			IsAdmin: cmd.IsAdmin,
+			OrgId:   orgId,
+			Created: time.Now(),
+			Updated: time.Now(),
 		}
 
 		if len(cmd.Password) > 0 {
@@ -82,20 +88,20 @@ func CreateUser(cmd *m.CreateUserCommand) error {
 			return err
 		}
 
-		// create account user link
-		accountUser := m.AccountUser{
-			AccountId: accountId,
-			UserId:    user.Id,
-			Role:      m.ROLE_ADMIN,
-			Created:   time.Now(),
-			Updated:   time.Now(),
+		// create org user link
+		orgUser := m.OrgUser{
+			OrgId:   orgId,
+			UserId:  user.Id,
+			Role:    m.ROLE_ADMIN,
+			Created: time.Now(),
+			Updated: time.Now(),
 		}
 
-		if setting.SingleAccountMode && !user.IsAdmin {
-			accountUser.Role = m.RoleType(setting.DefaultAccountRole)
+		if setting.SingleOrgMode && !user.IsAdmin {
+			orgUser.Role = m.RoleType(setting.DefaultOrgRole)
 		}
 
-		if _, err = sess.Insert(&accountUser); err != nil {
+		if _, err = sess.Insert(&orgUser); err != nil {
 			return err
 		}
 
@@ -112,16 +118,31 @@ func CreateUser(cmd *m.CreateUserCommand) error {
 	})
 }
 
+func GetUserById(query *m.GetUserByIdQuery) error {
+	user := new(m.User)
+	has, err := x.Id(query.Id).Get(user)
+
+	if err != nil {
+		return err
+	} else if has == false {
+		return m.ErrUserNotFound
+	}
+
+	query.Result = user
+
+	return nil
+}
+
 func GetUserByLogin(query *m.GetUserByLoginQuery) error {
 	if query.LoginOrEmail == "" {
-		return m.ErrAccountNotFound
+		return m.ErrUserNotFound
 	}
 
 	user := new(m.User)
 	if strings.Contains(query.LoginOrEmail, "@") {
 		user = &m.User{Email: query.LoginOrEmail}
 	} else {
-		user = &m.User{Login: strings.ToLower(query.LoginOrEmail)}
+		user = &m.User{Login: query.LoginOrEmail}
 	}
 
 	has, err := x.Get(user)
@@ -163,12 +184,28 @@ func UpdateUser(cmd *m.UpdateUserCommand) error {
 	})
 }
 
-func SetUsingAccount(cmd *m.SetUsingAccountCommand) error {
+func ChangeUserPassword(cmd *m.ChangeUserPasswordCommand) error {
+	return inTransaction2(func(sess *session) error {
+
+		user := m.User{
+			Password: cmd.NewPassword,
+			Updated:  time.Now(),
+		}
+
+		if _, err := sess.Id(cmd.UserId).Update(&user); err != nil {
+			return err
+		}
+
+		return nil
+	})
+}
+
+func SetUsingOrg(cmd *m.SetUsingOrgCommand) error {
 	return inTransaction(func(sess *xorm.Session) error {
 		user := m.User{}
 		sess.Id(cmd.UserId).Get(&user)
 
-		user.AccountId = cmd.AccountId
+		user.OrgId = cmd.OrgId
 		_, err := sess.Id(user.Id).Update(&user)
 		return err
 	})
@@ -193,12 +230,12 @@ func GetUserInfo(query *m.GetUserInfoQuery) error {
 	return err
 }
 
-func GetUserAccounts(query *m.GetUserAccountsQuery) error {
-	query.Result = make([]*m.UserAccountDTO, 0)
-	sess := x.Table("account_user")
-	sess.Join("INNER", "account", "account_user.account_id=account.id")
-	sess.Where("account_user.user_id=?", query.UserId)
-	sess.Cols("account.name", "account_user.role", "account_user.account_id")
+func GetUserOrgList(query *m.GetUserOrgListQuery) error {
+	query.Result = make([]*m.UserOrgDTO, 0)
+	sess := x.Table("org_user")
+	sess.Join("INNER", "org", "org_user.org_id=org.id")
+	sess.Where("org_user.user_id=?", query.UserId)
+	sess.Cols("org.name", "org_user.role", "org_user.org_id")
 	err := sess.Find(&query.Result)
 	return err
 }
@@ -210,12 +247,12 @@ func GetSignedInUser(query *m.GetSignedInUserQuery) error {
 	                u.email        as email,
 	                u.login        as login,
 									u.name         as name,
-	                account.name      as account_name,
-	                account_user.role as account_role,
-	                account.id        as account_id
+	                org.name       as org_name,
+	                org_user.role  as org_role,
+	                org.id         as org_id
 	                FROM ` + dialect.Quote("user") + ` as u
-									LEFT OUTER JOIN account_user on account_user.account_id = u.account_id and account_user.user_id = u.id
-	                LEFT OUTER JOIN account on account.id = u.account_id
+									LEFT OUTER JOIN org_user on org_user.org_id = u.org_id and org_user.user_id = u.id
+	                LEFT OUTER JOIN org on org.id = u.org_id
 	                WHERE u.id=?`
 
 	var user m.SignedInUser
@@ -240,3 +277,23 @@ func SearchUsers(query *m.SearchUsersQuery) error {
 	err := sess.Find(&query.Result)
 	return err
 }
+
+func DeleteUser(cmd *m.DeleteUserCommand) error {
+	return inTransaction(func(sess *xorm.Session) error {
+		var rawSql = fmt.Sprintf("DELETE FROM %s WHERE id=?", x.Dialect().Quote("user"))
+		_, err := sess.Exec(rawSql, cmd.UserId)
+		return err
+	})
+}
+
+func UpdateUserPermissions(cmd *m.UpdateUserPermissionsCommand) error {
+	return inTransaction(func(sess *xorm.Session) error {
+		user := m.User{}
+		sess.Id(cmd.UserId).Get(&user)
+
+		user.IsAdmin = cmd.IsGrafanaAdmin
+		sess.UseBool("is_admin")
+		_, err := sess.Id(user.Id).Update(&user)
+		return err
+	})
+}

+ 37 - 25
pkg/setting/setting.go

@@ -4,6 +4,7 @@
 package setting
 
 import (
+	"fmt"
 	"net/url"
 	"os"
 	"path"
@@ -65,18 +66,18 @@ var (
 	CookieRememberName string
 	DisableUserSignUp  bool
 
-	// single account
-	SingleAccountMode  bool
-	DefaultAccountName string
-	DefaultAccountRole string
+	// single organization
+	SingleOrgMode  bool
+	DefaultOrgName string
+	DefaultOrgRole string
 
 	// Http auth
 	AdminUser     string
 	AdminPassword string
 
-	AnonymousEnabled     bool
-	AnonymousAccountName string
-	AnonymousAccountRole string
+	AnonymousEnabled bool
+	AnonymousOrgName string
+	AnonymousOrgRole string
 
 	// Session settings.
 	SessionOptions session.Options
@@ -98,15 +99,10 @@ var (
 func init() {
 	IsWindows = runtime.GOOS == "windows"
 	log.NewLogger(0, "console", `{"level": 0}`)
-}
-
-func getWorkDir() string {
-	p, _ := filepath.Abs(".")
-	return p
+	WorkDir, _ = filepath.Abs(".")
 }
 
 func findConfigFiles() []string {
-	WorkDir = getWorkDir()
 	ConfRootPath = path.Join(WorkDir, "conf")
 	filenames := make([]string, 0)
 
@@ -152,10 +148,29 @@ func ToAbsUrl(relativeUrl string) string {
 	return AppUrl + relativeUrl
 }
 
-func NewConfigContext() {
+func loadEnvVariableOverrides() {
+	for _, section := range Cfg.Sections() {
+		for _, key := range section.Keys() {
+			sectionName := strings.ToUpper(strings.Replace(section.Name(), ".", "_", -1))
+			keyName := strings.ToUpper(strings.Replace(key.Name(), ".", "_", -1))
+			envKey := fmt.Sprintf("GF_%s_%s", sectionName, keyName)
+			envValue := os.Getenv(envKey)
+
+			if len(envValue) > 0 {
+				log.Info("Setting: ENV override found: %s", envKey)
+				key.SetValue(envValue)
+			}
+		}
+	}
+}
+
+func NewConfigContext(config string) {
 	configFiles := findConfigFiles()
 
-	//log.Info("Loading config files: %v", configFiles)
+	if config != "" {
+		configFiles = append(configFiles, config)
+	}
+
 	var err error
 
 	for i, file := range configFiles {
@@ -170,6 +185,8 @@ func NewConfigContext() {
 		}
 	}
 
+	loadEnvVariableOverrides()
+
 	AppName = Cfg.Section("").Key("app_name").MustString("Grafana")
 	Env = Cfg.Section("").Key("app_mode").MustString("development")
 
@@ -187,11 +204,6 @@ func NewConfigContext() {
 	HttpAddr = server.Key("http_addr").MustString("0.0.0.0")
 	HttpPort = server.Key("http_port").MustString("3000")
 
-	port := os.Getenv("PORT")
-	if port != "" {
-		HttpPort = port
-	}
-
 	StaticRootPath = server.Key("static_root_path").MustString(path.Join(WorkDir, "webapp"))
 	RouterLogging = server.Key("router_logging").MustBool(false)
 	EnableGzip = server.Key("enable_gzip").MustBool(false)
@@ -208,14 +220,14 @@ func NewConfigContext() {
 	AdminPassword = security.Key("admin_password").String()
 
 	// single account
-	SingleAccountMode = Cfg.Section("account.single").Key("enabled").MustBool(false)
-	DefaultAccountName = Cfg.Section("account.single").Key("account_name").MustString("main")
-	DefaultAccountRole = Cfg.Section("account.single").Key("default_role").In("Editor", []string{"Editor", "Admin", "Viewer"})
+	SingleOrgMode = Cfg.Section("organization.single").Key("enabled").MustBool(false)
+	DefaultOrgName = Cfg.Section("organization.single").Key("org_name").MustString("main")
+	DefaultOrgRole = Cfg.Section("organization.single").Key("default_role").In("Editor", []string{"Editor", "Admin", "Viewer"})
 
 	// anonymous access
 	AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false)
-	AnonymousAccountName = Cfg.Section("auth.anonymous").Key("account_name").String()
-	AnonymousAccountRole = Cfg.Section("auth.anonymous").Key("account_role").String()
+	AnonymousOrgName = Cfg.Section("auth.anonymous").Key("org_name").String()
+	AnonymousOrgRole = Cfg.Section("auth.anonymous").Key("org_role").String()
 
 	// PhantomJS rendering
 	ImagesDir = "data/png"

+ 32 - 0
pkg/setting/setting_test.go

@@ -0,0 +1,32 @@
+package setting
+
+import (
+	"os"
+	"path/filepath"
+	"testing"
+
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+func TestLoadingSettings(t *testing.T) {
+
+	WorkDir, _ = filepath.Abs("../../")
+
+	Convey("Testing loading settings from ini file", t, func() {
+
+		Convey("Given the default ini files", func() {
+			NewConfigContext("")
+
+			So(AppName, ShouldEqual, "Grafana")
+			So(AdminUser, ShouldEqual, "admin")
+		})
+
+		Convey("Should be able to override via environment variables", func() {
+			os.Setenv("GF_SECURITY_ADMIN_USER", "superduper")
+			NewConfigContext("")
+
+			So(AdminUser, ShouldEqual, "superduper")
+		})
+
+	})
+}

+ 62 - 13
src/app/components/kbn.js

@@ -168,14 +168,22 @@ function($, _, moment) {
     if(_.isDate(text)) {
       return text;
     }
-    var time,
-      mathString = "",
-      index,
-      parseString;
+
+    var time;
+    var mathString = "";
+    var index;
+    var parseString;
+
     if (text.substring(0,3) === "now") {
       time = new Date();
-      mathString = text.substring("now".length);
-    } else {
+      mathString = text.substring(3);
+    }
+    else if (text.substring(0,5) === 'today') {
+      time = new Date();
+      time.setHours(0,0,0,0);
+      mathString = text.substring(5);
+    }
+    else {
       index = text.indexOf("||");
       parseString;
       if (index === -1) {
@@ -197,6 +205,11 @@ function($, _, moment) {
     return kbn.parseDateMath(mathString, time);
   };
 
+  kbn._timespanRegex = /^\d+[h,m,M,w,s,H,d]$/;
+  kbn.isValidTimeSpan = function(str) {
+    return kbn._timespanRegex.test(str);
+  };
+
   kbn.parseDateMath = function(mathString, time, roundUp) {
     var dateTime = moment(time);
     for (var i = 0; i < mathString.length;) {
@@ -321,11 +334,15 @@ function($, _, moment) {
       }
 
       var steps = 0;
+      var limit = extArray.length;
 
       while (Math.abs(size) >= factor) {
         steps++;
         size /= factor;
+
+        if (steps >= limit) { return "NA"; }
       }
+
       if (steps > 0) {
         decimals = scaledDecimals + (3 * steps);
       }
@@ -367,10 +384,21 @@ function($, _, moment) {
   kbn.valueFormats.bps = kbn.formatFuncCreator(1000, [' bps', ' Kbps', ' Mbps', ' Gbps', ' Tbps', ' Pbps', ' Ebps', ' Zbps', ' Ybps']);
   kbn.valueFormats.Bps = kbn.formatFuncCreator(1000, [' Bps', ' KBps', ' MBps', ' GBps', ' TBps', ' PBps', ' EBps', ' ZBps', ' YBps']);
   kbn.valueFormats.short = kbn.formatFuncCreator(1000, ['', ' K', ' Mil', ' Bil', ' Tri', ' Qaudr', ' Quint', ' Sext', ' Sept']);
-  kbn.valueFormats.joule = kbn.formatFuncCreator(1000, [' J', ' kJ', ' MJ', 'GJ', 'TJ', 'PJ', 'EJ', 'ZJ', 'YJ']);
-  kbn.valueFormats.watt = kbn.formatFuncCreator(1000, [' W', ' kW', ' MW', 'GW', 'TW', 'PW', 'EW', 'ZW', 'YW']);
+  kbn.valueFormats.joule = kbn.formatFuncCreator(1000, [' J', ' kJ', ' MJ', ' GJ', ' TJ', ' PJ', ' EJ', ' ZJ', ' YJ']);
+  kbn.valueFormats.watt = kbn.formatFuncCreator(1000, [' W', ' kW', ' MW', ' GW', ' TW', ' PW', ' EW', ' ZW', ' YW']);
+  kbn.valueFormats.kwatt = kbn.formatFuncCreator(1000, [' kW', ' MW', ' GW', ' TW', ' PW', ' EW', ' ZW', ' YW']);
+  kbn.valueFormats.watth = kbn.formatFuncCreator(1000, [' Wh', ' kWh', ' MWh', ' GWh', ' TWh', ' PWh', ' EWh', ' ZWh', ' YWh']);
+  kbn.valueFormats.kwatth = kbn.formatFuncCreator(1000, [' kWh', ' MWh', ' GWh', ' TWh', ' PWh', ' EWh', ' ZWh', ' YWh']);
   kbn.valueFormats.ev = kbn.formatFuncCreator(1000, [' eV', ' keV', ' MeV', 'GeV', 'TeV', 'PeV', 'EeV', 'ZeV', 'YeV']);
   kbn.valueFormats.none = kbn.toFixed;
+  kbn.valueFormats.celsius = function(value, decimals) { return kbn.toFixed(value, decimals) + ' °C'; };
+  kbn.valueFormats.farenheit = function(value, decimals) { return kbn.toFixed(value, decimals) + ' °F'; };
+  kbn.valueFormats.humidity = function(value, decimals) { return kbn.toFixed(value, decimals) + ' %H'; };
+  kbn.valueFormats.ppm = function(value, decimals) { return kbn.toFixed(value, decimals) + ' ppm'; };
+  kbn.valueFormats.velocityms = function(value, decimals) { return kbn.toFixed(value, decimals) + ' m/s'; };
+  kbn.valueFormats.velocitykmh = function(value, decimals) { return kbn.toFixed(value, decimals) + ' km/h'; };
+  kbn.valueFormats.velocitymph = function(value, decimals) { return kbn.toFixed(value, decimals) + ' mph'; };
+  kbn.valueFormats.velocityknot = function(value, decimals) { return kbn.toFixed(value, decimals) + ' kn'; };
 
   kbn.valueFormats.ms = function(size, decimals, scaledDecimals) {
     if (size === null) { return ""; }
@@ -493,6 +521,7 @@ function($, _, moment) {
           {text: 'none' , value: 'none'},
           {text: 'short', value: 'short'},
           {text: 'percent', value: 'percent'},
+          {text: 'ppm', value: 'ppm'},
         ]
       },
       {
@@ -509,8 +538,8 @@ function($, _, moment) {
         submenu: [
           {text: 'bits', value: 'bits'},
           {text: 'bytes', value: 'bytes'},
-          {text: 'kilo bytes', value: 'kbytes'},
-          {text: 'mega bytes', value: 'mbytes'},
+          {text: 'kilobytes', value: 'kbytes'},
+          {text: 'megabytes', value: 'mbytes'},
         ]
       },
       {
@@ -523,9 +552,29 @@ function($, _, moment) {
       {
         text: 'energy',
         submenu: [
-          {text: 'watt', value: 'watt'},
-          {text: 'joule', value: 'joule'},
-          {text: 'eV', value: 'ev'},
+          {text: 'watt (W)',              value: 'watt'},
+          {text: 'kilowatt (kW)',         value: 'kwatt'},
+          {text: 'watt-hour (Wh)',        value: 'watth'},
+          {text: 'kilowatt-hour (kWh)',   value: 'kwatth'},
+          {text: 'joule (J)',             value: 'joule'},
+          {text: 'electron volt (eV)',    value: 'ev'},
+        ]
+      },
+      {
+        text: 'weather',
+        submenu: [
+          {text: 'Celcius (°C)',         value: 'celsius'  },
+          {text: 'Farenheit (°F)',       value: 'farenheit'},
+          {text: 'Humidity (%H)',        value: 'humidity' },
+        ]
+      },
+      {
+        text: 'velocity',
+        submenu: [
+          {text: 'm/s',  value: 'velocityms'  },
+          {text: 'km/h', value: 'velocitykmh'  },
+          {text: 'mph',  value: 'velocitymph'  },
+          {text: 'knot (kn)', value: 'velocityknot'  },
         ]
       },
     ];

+ 21 - 46
src/app/components/settings.js

@@ -1,8 +1,7 @@
 define([
   'lodash',
-  'crypto',
 ],
-function (_, crypto) {
+function (_) {
   "use strict";
 
   return function Settings (options) {
@@ -27,55 +26,31 @@ function (_, crypto) {
       playlist_timespan: "1m",
       unsaved_changes_warning: true,
       search: { max_results: 100 },
-      admin: {},
       appSubUrl: ""
     };
 
     var settings = _.extend({}, defaults, options);
 
-    var parseBasicAuth = function(datasource) {
-      var passwordEnd = datasource.url.indexOf('@');
-      if (passwordEnd > 0) {
-        var userStart = datasource.url.indexOf('//') + 2;
-        var userAndPassword = datasource.url.substring(userStart, passwordEnd);
-        var bytes = crypto.charenc.Binary.stringToBytes(userAndPassword);
-        datasource.basicAuth = crypto.util.bytesToBase64(bytes);
-
-        var urlHead = datasource.url.substring(0, userStart);
-        datasource.url = urlHead + datasource.url.substring(passwordEnd + 1);
-      }
-
-      return datasource;
-    };
-
-    var parseMultipleHosts = function(datasource) {
-      datasource.urls = _.map(datasource.url.split(","), function (url) { return url.trim(); });
-      return datasource;
-    };
-
-    // backward compatible with old config
-    if (options.graphiteUrl) {
-      settings.datasources.graphite = {
-        type: 'graphite',
-        url: options.graphiteUrl,
-        default: true
-      };
-    }
-
-    if (options.elasticsearch) {
-      settings.datasources.elasticsearch = {
-        type: 'elasticsearch',
-        url: options.elasticsearch,
-        index: options.grafana_index,
-        grafanaDB: true
-      };
-    }
-
-    _.each(settings.datasources, function(datasource, key) {
-      datasource.name = key;
-      if (datasource.url) { parseBasicAuth(datasource); }
-      if (datasource.type === 'influxdb') { parseMultipleHosts(datasource); }
-    });
+    // var parseBasicAuth = function(datasource) {
+    //   var passwordEnd = datasource.url.indexOf('@');
+    //   if (passwordEnd > 0) {
+    //     var userStart = datasource.url.indexOf('//') + 2;
+    //     var userAndPassword = datasource.url.substring(userStart, passwordEnd);
+    //     var bytes = crypto.charenc.Binary.stringToBytes(userAndPassword);
+    //     datasource.basicAuth = crypto.util.bytesToBase64(bytes);
+    //
+    //     var urlHead = datasource.url.substring(0, userStart);
+    //     datasource.url = urlHead + datasource.url.substring(passwordEnd + 1);
+    //   }
+    //
+    //   return datasource;
+    // };
+    //
+    // _.each(settings.datasources, function(datasource, key) {
+    //   datasource.name = key;
+    //   if (datasource.url) { parseBasicAuth(datasource); }
+    //   if (datasource.type === 'influxdb') { parseMultipleHosts(datasource); }
+    // });
 
     if (settings.plugins.panels) {
       _.extend(settings.panels, settings.plugins.panels);

+ 0 - 1
src/app/controllers/all.js

@@ -3,7 +3,6 @@ define([
   './pulldown',
   './search',
   './metricKeys',
-  './graphiteImport',
   './inspectCtrl',
   './jsonEditorCtrl',
   './loginCtrl',

+ 0 - 108
src/app/controllers/graphiteImport.js

@@ -1,108 +0,0 @@
-define([
-  'angular',
-  'app',
-  'lodash',
-  'kbn'
-],
-function (angular, app, _, kbn) {
-  'use strict';
-
-  var module = angular.module('grafana.controllers');
-
-  module.controller('GraphiteImportCtrl', function($scope, $rootScope, $timeout, datasourceSrv, $location) {
-
-    $scope.init = function() {
-      $scope.datasources = datasourceSrv.getMetricSources();
-      $scope.setDatasource(null);
-    };
-
-    $scope.setDatasource = function(datasource) {
-      $scope.datasource = datasourceSrv.get(datasource);
-
-      if (!$scope.datasource) {
-        $scope.error = "Cannot find datasource " + datasource;
-        return;
-      }
-    };
-
-    $scope.listAll = function(query) {
-      delete $scope.error;
-
-      $scope.datasource.listDashboards(query)
-        .then(function(results) {
-          $scope.dashboards = results;
-        })
-        .then(null, function(err) {
-          $scope.error = err.message || 'Error while fetching list of dashboards';
-        });
-    };
-
-    $scope.import = function(dashName) {
-      delete $scope.error;
-
-      $scope.datasource.loadDashboard(dashName)
-        .then(function(results) {
-          if (!results.data || !results.data.state) {
-            throw { message: 'no dashboard state received from graphite' };
-          }
-
-          graphiteToGrafanaTranslator(results.data.state, $scope.datasource.name);
-        })
-        .then(null, function(err) {
-          $scope.error = err.message || 'Failed to import dashboard';
-        });
-    };
-
-    function graphiteToGrafanaTranslator(state, datasource) {
-      var graphsPerRow = 2;
-      var rowHeight = 300;
-      var rowTemplate;
-      var currentRow;
-      var panel;
-
-      rowTemplate = {
-        title: '',
-        panels: [],
-        height: rowHeight
-      };
-
-      currentRow = angular.copy(rowTemplate);
-
-      var newDashboard = angular.copy($scope.dashboard);
-      newDashboard.rows = [];
-      newDashboard.title = state.name;
-      newDashboard.rows.push(currentRow);
-
-      _.each(state.graphs, function(graph, index) {
-        if (currentRow.panels.length === graphsPerRow) {
-          currentRow = angular.copy(rowTemplate);
-          newDashboard.rows.push(currentRow);
-        }
-
-        panel = {
-          type: 'graph',
-          span: 12 / graphsPerRow,
-          title: graph[1].title,
-          targets: [],
-          datasource: datasource,
-          id: index + 1
-        };
-
-        _.each(graph[1].target, function(target) {
-          panel.targets.push({
-            target: target
-          });
-        });
-
-        currentRow.panels.push(panel);
-      });
-
-      window.grafanaImportDashboard = newDashboard;
-      $location.path('/dashboard/import/' + kbn.slugifyForUrl(newDashboard.title));
-
-      $scope.dismiss();
-    }
-
-  });
-
-});

+ 18 - 19
src/app/controllers/search.js

@@ -2,9 +2,8 @@ define([
   'angular',
   'lodash',
   'config',
-  'jquery'
 ],
-function (angular, _, config, $) {
+function (angular, _, config) {
   'use strict';
 
   var module = angular.module('grafana.controllers');
@@ -15,7 +14,7 @@ function (angular, _, config, $) {
       $scope.giveSearchFocus = 0;
       $scope.selectedIndex = -1;
       $scope.results = {dashboards: [], tags: [], metrics: []};
-      $scope.query = { query: '' };
+      $scope.query = { query: '', tag: '', starred: false };
       $scope.db = datasourceSrv.getGrafanaDB();
       $scope.currentSearchId = 0;
 
@@ -29,7 +28,7 @@ function (angular, _, config, $) {
 
     $scope.keyDown = function (evt) {
       if (evt.keyCode === 27) {
-        $scope.appEvent('hide-dash-editor');
+        $scope.dismiss();
       }
       if (evt.keyCode === 40) {
         $scope.moveSelection(1);
@@ -49,10 +48,7 @@ function (angular, _, config, $) {
         var selectedDash = $scope.results.dashboards[$scope.selectedIndex];
         if (selectedDash) {
           $location.search({});
-          $location.path("/dashboard/db/" + selectedDash.slug);
-          setTimeout(function() {
-            $('body').click(); // hack to force dropdown to close;
-          });
+          $location.path(selectedDash.url);
         }
       }
     };
@@ -61,11 +57,6 @@ function (angular, _, config, $) {
       $scope.selectedIndex = Math.max(Math.min($scope.selectedIndex + direction, $scope.resultCount - 1), 0);
     };
 
-    $scope.goToDashboard = function(slug) {
-      $location.search({});
-      $location.path("/dashboard/db/" + slug);
-    };
-
     $scope.searchDashboards = function() {
       $scope.currentSearchId = $scope.currentSearchId + 1;
       var localSearchId = $scope.currentSearchId;
@@ -74,16 +65,24 @@ function (angular, _, config, $) {
         .then(function(results) {
           if (localSearchId < $scope.currentSearchId) { return; }
 
-          if ($scope.query.query === "" && !$scope.query.starred) {
-            results.dashboards.unshift({ title: 'Home', url: config.appSubUrl + '/', isHome: true });
-          }
-
-          $scope.results.dashboards = results.dashboards;
-          $scope.results.tags = results.tags;
           $scope.resultCount = results.tagsOnly ? results.tags.length : results.dashboards.length;
+          $scope.results.tags = results.tags;
+          $scope.results.dashboards = _.map(results.dashboards, function(dash) {
+            dash.url = 'dashboard/db/' + dash.slug;
+            return dash;
+          });
+
+          if ($scope.queryHasNoFilters()) {
+            $scope.results.dashboards.unshift({ title: 'Home', url: config.appSubUrl + '/', isHome: true });
+          }
         });
     };
 
+    $scope.queryHasNoFilters = function() {
+      var query = $scope.query;
+      return query.query === '' && query.starred === false && query.tag === '';
+    };
+
     $scope.filterByTag = function(tag, evt) {
       $scope.query.tag = tag;
       $scope.query.tagcloud = false;

+ 92 - 61
src/app/controllers/sidemenuCtrl.js

@@ -9,87 +9,118 @@ function (angular, _, $, config) {
 
   var module = angular.module('grafana.controllers');
 
-  module.controller('SideMenuCtrl', function($scope, $location, contextSrv) {
+  module.controller('SideMenuCtrl', function($scope, $location, contextSrv, backendSrv) {
 
     $scope.getUrl = function(url) {
       return config.appSubUrl + url;
     };
 
-    $scope.menu = [];
-    $scope.menu.push({
-      text: "Dashboards",
-      icon: "fa fa-th-large",
-      href: $scope.getUrl("/"),
-    });
-
-    if (contextSrv.hasRole('Admin')) {
-      $scope.menu.push({
-        text: "Data Sources",
-        icon: "fa fa-database",
-        href: $scope.getUrl("/account/datasources"),
+    $scope.setupMainNav = function() {
+      $scope.mainLinks.push({
+        text: "Dashboards",
+        icon: "fa fa-fw fa-th-large",
+        href: $scope.getUrl("/"),
       });
-      $scope.menu.push({
-        text: "Account", href: $scope.getUrl("/account"),
-        requireRole: "Admin",
-        icon: "fa fa-shield",
-      });
-    }
-
-    if (contextSrv.user.isGrafanaAdmin) {
-      $scope.menu.push({
-        text: "Admin", href: $scope.getUrl("/admin/users"),
-        icon: "fa fa-cube",
-        requireSignedIn: true,
-        links: [
-          { text: 'Settings', href: $scope.getUrl("/admin/settings")},
-          { text: 'Users',    href: $scope.getUrl("/admin/users"), icon: "fa fa-lock" },
-          { text: 'Log',      href: "", icon: "fa fa-lock" },
-        ]
-      });
-    }
 
-    $scope.updateState = function() {
-      var currentPath = config.appSubUrl + $location.path();
-      var search = $location.search();
+      if (contextSrv.hasRole('Admin')) {
+        $scope.mainLinks.push({
+          text: "Data Sources",
+          icon: "fa fa-fw fa-database",
+          href: $scope.getUrl("/datasources"),
+        });
+      }
+    };
+
+    $scope.loadOrgs = function() {
+      $scope.orgMenu = [];
 
-      _.each($scope.menu, function(item) {
-        item.active = false;
+      if (contextSrv.hasRole('Admin')) {
+        $scope.orgMenu.push({
+          text: "Organization settings",
+          href: $scope.getUrl("/org"),
+        });
+        $scope.orgMenu.push({
+          text: "Users",
+          href: $scope.getUrl("/org/users"),
+        });
+        $scope.orgMenu.push({
+          text: "API Keys",
+          href: $scope.getUrl("/org/apikeys"),
+        });
+      }
 
-        if (item.href === currentPath) {
-          item.active = true;
-        }
+      if ($scope.orgMenu.length > 0) {
+        $scope.orgMenu.push({ cssClass: 'divider' });
+      }
 
-        if (item.startsWith) {
-          if (currentPath.indexOf(item.startsWith) === 0) {
-            item.active = true;
-            item.href = currentPath;
+      backendSrv.get('/api/user/orgs').then(function(orgs) {
+        _.each(orgs, function(org) {
+          if (org.isUsing) {
+            return;
           }
-        }
 
-        _.each(item.links, function(link) {
-          link.active = false;
+          $scope.orgMenu.push({
+            text: "Switch to " + org.name,
+            icon: "fa fa-fw fa-random",
+            click: function() {
+              $scope.switchOrg(org.orgId);
+            }
+          });
+        });
 
-          if (link.editview) {
-            var params = {};
-            _.each(search, function(value, key) {
-              if (value !== null) { params[key] = value; }
-            });
+        $scope.orgMenu.push({
+          text: "New Organization",
+          icon: "fa fa-fw fa-plus",
+          href: $scope.getUrl('/org/new')
+        });
+      });
+    };
 
-            params.editview = link.editview;
-            link.href = currentPath + '?' + $.param(params);
-          }
+    $scope.switchOrg = function(orgId) {
+      backendSrv.post('/api/user/using/' + orgId).then(function() {
+        window.location.href = $scope.getUrl('/');
+      });
+    };
 
-          if (link.href === currentPath) {
-            item.active = true;
-            link.active = true;
-          }
-        });
+    $scope.setupAdminNav = function() {
+      $scope.systemSection = true;
+      $scope.grafanaVersion = config.buildInfo.version;
+
+      $scope.mainLinks.push({
+        text: "System info",
+        icon: "fa fa-fw fa-info",
+        href: $scope.getUrl("/admin/settings"),
       });
 
+      $scope.mainLinks.push({
+        text: "Global Users",
+        icon: "fa fa-fw fa-user",
+        href: $scope.getUrl("/admin/users"),
+      });
+
+      $scope.mainLinks.push({
+        text: "Global Orgs",
+        icon: "fa fa-fw fa-users",
+        href: $scope.getUrl("/admin/orgs"),
+      });
+    };
+
+    $scope.updateMenu = function() {
+      $scope.systemSection = false;
+      $scope.mainLinks = [];
+      $scope.orgMenu = [];
+
+      var currentPath = $location.path();
+      if (currentPath.indexOf('/admin') === 0) {
+        $scope.setupAdminNav();
+      } else {
+        $scope.setupMainNav();
+      }
     };
 
     $scope.init = function() {
-      $scope.updateState();
+      $scope.updateMenu();
+      $scope.$on('$routeChangeSuccess', $scope.updateMenu);
     };
   });
 

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