Pārlūkot izejas kodu

Merge branch 'develop' into apikey_hashed

Conflicts:
	pkg/api/apikey.go
	pkg/services/sqlstore/migrations.go
Torkel Ödegaard 10 gadi atpakaļ
vecāks
revīzija
6a2a6afc1d
100 mainītis faili ar 2931 papildinājumiem un 3974 dzēšanām
  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
 *.sublime-workspace
 *.swp
 *.swp
 .idea/
 .idea/
+*.iml
 
 
 /data/*
 /data/*
 /bin/*
 /bin/*
 /grafana-pro
 /grafana-pro
+/grafana
 
 
 grafana.custom.ini
 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 #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 #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 #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**
 **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 #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 #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
 - [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 #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 #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 #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**
 **Tech**
 - [Issue #1311](https://github.com/grafana/grafana/issues/1311). Tech: Updated Font-Awesome from 3.2 to 4.2
 - [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
 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.
 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
 ## building and running
 
 
 ```
 ```
@@ -26,18 +32,13 @@ Building
 ```
 ```
 cd $GOPATH/src/github.com/grafana/grafana
 cd $GOPATH/src/github.com/grafana/grafana
 git checkout -t origin/develop
 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
 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
 app_mode = production
 
 
 [server]
 [server]
+; protocol (http or https)
 protocol = http
 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 =
 http_addr =
+; the http port  to use
 http_port = 3000
 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
 router_logging = false
+; the path relative to the binary where the static (html/js/css) files are placed
 static_root_path = public
 static_root_path = public
 enable_gzip = false
 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"
-}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 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"
-}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
data/dashboards/graph_styles_collapsed.json


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 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"
-}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 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 update
 RUN apt-get -y install libfontconfig
 RUN apt-get -y install libfontconfig
@@ -10,6 +10,7 @@ ADD tmp/ /opt/grafana/
 EXPOSE 3000
 EXPOSE 3000
 
 
 VOLUME ["/opt/grafana/data"]
 VOLUME ["/opt/grafana/data"]
+VOLUME ["/opt/grafana/conf"]
 
 
 WORKDIR /opt/grafana/
 WORKDIR /opt/grafana/
 ENTRYPOINT ["./grafana", "web"]
 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.Name = "Grafana Backend"
 	app.Usage = "grafana web"
 	app.Usage = "grafana web"
 	app.Version = version
 	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)
 	app.Run(os.Args)
 
 
 	log.Close()
 	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
 package api
 
 
 import (
 import (
+	"github.com/grafana/grafana/pkg/api/dtos"
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/middleware"
 	"github.com/grafana/grafana/pkg/middleware"
 	m "github.com/grafana/grafana/pkg/models"
 	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/util"
 )
 )
 
 
 func AdminSearchUsers(c *middleware.Context) {
 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 {
 	if err := bus.Dispatch(&query); err != nil {
-		c.JsonApiErr(500, "Failed to fetch collaboratos", err)
+		c.JsonApiErr(500, "Failed to fetch users", err)
 		return
 		return
 	}
 	}
 
 
 	c.JSON(200, query.Result)
 	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
 	// authed views
 	r.Get("/profile/", reqSignedIn, Index)
 	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", reqGrafanaAdmin, Index)
+	r.Get("/admin/users/create", reqGrafanaAdmin, Index)
+	r.Get("/admin/users/edit/:id", reqGrafanaAdmin, Index)
 	r.Get("/dashboard/*", reqSignedIn, Index)
 	r.Get("/dashboard/*", reqSignedIn, Index)
 
 
 	// sign up
 	// sign up
@@ -43,20 +46,21 @@ func Register(r *macaron.Macaron) {
 		r.Group("/user", func() {
 		r.Group("/user", func() {
 			r.Get("/", GetUser)
 			r.Get("/", GetUser)
 			r.Put("/", bind(m.UpdateUserCommand{}), UpdateUser)
 			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.Post("/stars/dashboard/:id", StarDashboard)
 			r.Delete("/stars/dashboard/:id", UnstarDashboard)
 			r.Delete("/stars/dashboard/:id", UnstarDashboard)
+			r.Put("/password", bind(m.ChangeUserPasswordCommand{}), ChangeUserPassword)
 		})
 		})
 
 
 		// account
 		// 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)
 		}, reqAccountAdmin)
 
 
 		// auth api keys
 		// auth api keys
@@ -70,9 +74,12 @@ func Register(r *macaron.Macaron) {
 		r.Group("/datasources", func() {
 		r.Group("/datasources", func() {
 			r.Combo("/").Get(GetDataSources).Put(AddDataSource).Post(UpdateDataSource)
 			r.Combo("/").Get(GetDataSources).Put(AddDataSource).Post(UpdateDataSource)
 			r.Delete("/:id", DeleteDataSource)
 			r.Delete("/:id", DeleteDataSource)
-			r.Any("/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
+			r.Get("/:id", GetDataSourceById)
 		}, reqAccountAdmin)
 		}, reqAccountAdmin)
 
 
+		r.Get("/frontend/settings/", GetFrontendSettings)
+		r.Any("/datasources/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
+
 		// Dashboard
 		// Dashboard
 		r.Group("/dashboards", func() {
 		r.Group("/dashboards", func() {
 			r.Combo("/db/:slug").Get(GetDashboard).Delete(DeleteDashboard)
 			r.Combo("/db/:slug").Get(GetDashboard).Delete(DeleteDashboard)
@@ -89,7 +96,14 @@ func Register(r *macaron.Macaron) {
 
 
 	// admin api
 	// admin api
 	r.Group("/api/admin", func() {
 	r.Group("/api/admin", func() {
+		r.Get("/settings", AdminGetSettings)
 		r.Get("/users", AdminSearchUsers)
 		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)
 	}, reqGrafanaAdmin)
 
 
 	// rendering
 	// rendering

+ 20 - 3
pkg/api/apikey.go

@@ -8,7 +8,7 @@ import (
 )
 )
 
 
 func GetApiKeys(c *middleware.Context) {
 func GetApiKeys(c *middleware.Context) {
-	query := m.GetApiKeysQuery{AccountId: c.AccountId}
+	query := m.GetApiKeysQuery{OrgId: c.OrgId}
 
 
 	if err := bus.Dispatch(&query); err != nil {
 	if err := bus.Dispatch(&query); err != nil {
 		c.JsonApiErr(500, "Failed to list api keys", err)
 		c.JsonApiErr(500, "Failed to list api keys", err)
@@ -29,7 +29,7 @@ func GetApiKeys(c *middleware.Context) {
 func DeleteApiKey(c *middleware.Context) {
 func DeleteApiKey(c *middleware.Context) {
 	id := c.ParamsInt64(":id")
 	id := c.ParamsInt64(":id")
 
 
-	cmd := &m.DeleteApiKeyCommand{Id: id, AccountId: c.AccountId}
+	cmd := &m.DeleteApiKeyCommand{Id: id, OrgId: c.OrgId}
 
 
 	err := bus.Dispatch(cmd)
 	err := bus.Dispatch(cmd)
 	if err != nil {
 	if err != nil {
@@ -46,7 +46,7 @@ func AddApiKey(c *middleware.Context, cmd m.AddApiKeyCommand) {
 		return
 		return
 	}
 	}
 
 
-	cmd.AccountId = c.AccountId
+	cmd.OrgId = c.OrgId
 	cmd.Key = util.GetRandomString(64)
 	cmd.Key = util.GetRandomString(64)
 
 
 	if err := bus.Dispatch(&cmd); err != nil {
 	if err := bus.Dispatch(&cmd); err != nil {
@@ -62,3 +62,20 @@ func AddApiKey(c *middleware.Context, cmd m.AddApiKeyCommand) {
 
 
 	c.JSON(200, result)
 	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) {
 func GetDashboard(c *middleware.Context) {
 	slug := c.Params(":slug")
 	slug := c.Params(":slug")
 
 
-	query := m.GetDashboardQuery{Slug: slug, AccountId: c.AccountId}
+	query := m.GetDashboardQuery{Slug: slug, OrgId: c.OrgId}
 	err := bus.Dispatch(&query)
 	err := bus.Dispatch(&query)
 	if err != nil {
 	if err != nil {
 		c.JsonApiErr(404, "Dashboard not found", nil)
 		c.JsonApiErr(404, "Dashboard not found", nil)
@@ -54,13 +54,13 @@ func GetDashboard(c *middleware.Context) {
 func DeleteDashboard(c *middleware.Context) {
 func DeleteDashboard(c *middleware.Context) {
 	slug := c.Params(":slug")
 	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 {
 	if err := bus.Dispatch(&query); err != nil {
 		c.JsonApiErr(404, "Dashboard not found", nil)
 		c.JsonApiErr(404, "Dashboard not found", nil)
 		return
 		return
 	}
 	}
 
 
-	cmd := m.DeleteDashboardCommand{Slug: slug, AccountId: c.AccountId}
+	cmd := m.DeleteDashboardCommand{Slug: slug, OrgId: c.OrgId}
 	if err := bus.Dispatch(&cmd); err != nil {
 	if err := bus.Dispatch(&cmd); err != nil {
 		c.JsonApiErr(500, "Failed to delete dashboard", err)
 		c.JsonApiErr(500, "Failed to delete dashboard", err)
 		return
 		return
@@ -72,7 +72,7 @@ func DeleteDashboard(c *middleware.Context) {
 }
 }
 
 
 func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) {
 func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) {
-	cmd.AccountId = c.AccountId
+	cmd.OrgId = c.OrgId
 
 
 	err := bus.Dispatch(&cmd)
 	err := bus.Dispatch(&cmd)
 	if err != nil {
 	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) {
 	director := func(req *http.Request) {
 		req.URL.Scheme = target.Scheme
 		req.URL.Scheme = target.Scheme
 		req.URL.Host = target.Host
 		req.URL.Host = target.Host
+		req.Host = target.Host
 
 
 		reqQueryVals := req.URL.Query()
 		reqQueryVals := req.URL.Query()
 
 
@@ -25,6 +26,12 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string) *httputil.ReverseProxy
 			reqQueryVals.Add("u", ds.User)
 			reqQueryVals.Add("u", ds.User)
 			reqQueryVals.Add("p", ds.Password)
 			reqQueryVals.Add("p", ds.Password)
 			req.URL.RawQuery = reqQueryVals.Encode()
 			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 {
 		} else {
 			req.URL.Path = util.JoinUrlFragments(target.Path, proxyPath)
 			req.URL.Path = util.JoinUrlFragments(target.Path, proxyPath)
 		}
 		}
@@ -38,8 +45,8 @@ func ProxyDataSourceRequest(c *middleware.Context) {
 	id := c.ParamsInt64(":id")
 	id := c.ParamsInt64(":id")
 
 
 	query := m.GetDataSourceByIdQuery{
 	query := m.GetDataSourceByIdQuery{
-		Id:        id,
-		AccountId: c.AccountId,
+		Id:    id,
+		OrgId: c.OrgId,
 	}
 	}
 
 
 	err := bus.Dispatch(&query)
 	err := bus.Dispatch(&query)

+ 1 - 1
pkg/api/dataproxy_test.go

@@ -10,7 +10,7 @@ import (
 	m "github.com/grafana/grafana/pkg/models"
 	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() {
 	Convey("When getting graphite datasource proxy", t, func() {
 		ds := m.DataSource{Url: "htttp://graphite:8080", Type: m.DS_GRAPHITE}
 		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) {
 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)
 		c.JsonApiErr(500, "Failed to query datasources", err)
 		return
 		return
 	}
 	}
@@ -20,7 +19,7 @@ func GetDataSources(c *middleware.Context) {
 	for i, ds := range query.Result {
 	for i, ds := range query.Result {
 		result[i] = &dtos.DataSource{
 		result[i] = &dtos.DataSource{
 			Id:        ds.Id,
 			Id:        ds.Id,
-			AccountId: ds.AccountId,
+			OrgId:     ds.OrgId,
 			Name:      ds.Name,
 			Name:      ds.Name,
 			Url:       ds.Url,
 			Url:       ds.Url,
 			Type:      ds.Type,
 			Type:      ds.Type,
@@ -36,6 +35,34 @@ func GetDataSources(c *middleware.Context) {
 	c.JSON(200, result)
 	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) {
 func DeleteDataSource(c *middleware.Context) {
 	id := c.ParamsInt64(":id")
 	id := c.ParamsInt64(":id")
 
 
@@ -44,7 +71,7 @@ func DeleteDataSource(c *middleware.Context) {
 		return
 		return
 	}
 	}
 
 
-	cmd := &m.DeleteDataSourceCommand{Id: id, AccountId: c.AccountId}
+	cmd := &m.DeleteDataSourceCommand{Id: id, OrgId: c.OrgId}
 
 
 	err := bus.Dispatch(cmd)
 	err := bus.Dispatch(cmd)
 	if err != nil {
 	if err != nil {
@@ -63,7 +90,7 @@ func AddDataSource(c *middleware.Context) {
 		return
 		return
 	}
 	}
 
 
-	cmd.AccountId = c.AccountId
+	cmd.OrgId = c.OrgId
 
 
 	if err := bus.Dispatch(&cmd); err != nil {
 	if err := bus.Dispatch(&cmd); err != nil {
 		c.JsonApiErr(500, "Failed to add datasource", err)
 		c.JsonApiErr(500, "Failed to add datasource", err)
@@ -81,7 +108,7 @@ func UpdateDataSource(c *middleware.Context) {
 		return
 		return
 	}
 	}
 
 
-	cmd.AccountId = c.AccountId
+	cmd.OrgId = c.OrgId
 
 
 	err := bus.Dispatch(&cmd)
 	err := bus.Dispatch(&cmd)
 	if err != nil {
 	if err != nil {

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

@@ -19,8 +19,8 @@ type CurrentUser struct {
 	Login          string     `json:"login"`
 	Login          string     `json:"login"`
 	Email          string     `json:"email"`
 	Email          string     `json:"email"`
 	Name           string     `json:"name"`
 	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"`
 	IsGrafanaAdmin bool       `json:"isGrafanaAdmin"`
 	GravatarUrl    string     `json:"gravatarUrl"`
 	GravatarUrl    string     `json:"gravatarUrl"`
 }
 }
@@ -38,7 +38,7 @@ type Dashboard struct {
 
 
 type DataSource struct {
 type DataSource struct {
 	Id        int64      `json:"id"`
 	Id        int64      `json:"id"`
-	AccountId int64      `json:"accountId"`
+	OrgId     int64      `json:"orgId"`
 	Name      string     `json:"name"`
 	Name      string     `json:"name"`
 	Type      m.DsType   `json:"type"`
 	Type      m.DsType   `json:"type"`
 	Access    m.DsAccess `json:"access"`
 	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"
 	"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 {
 	if c.IsSignedIn {
-		query := m.GetDataSourcesQuery{AccountId: c.AccountId}
+		query := m.GetDataSourcesQuery{OrgId: c.OrgId}
 		err := bus.Dispatch(&query)
 		err := bus.Dispatch(&query)
 
 
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
 
 
-		accountDataSources = query.Result
+		orgDataSources = query.Result
 	}
 	}
 
 
 	datasources := make(map[string]interface{})
 	datasources := make(map[string]interface{})
 
 
-	for _, ds := range accountDataSources {
+	for _, ds := range orgDataSources {
 		url := ds.Url
 		url := ds.Url
 
 
 		if ds.Access == m.DS_ACCESS_PROXY {
 		if ds.Access == m.DS_ACCESS_PROXY {
@@ -38,7 +38,7 @@ func getFrontendSettings(c *middleware.Context) (map[string]interface{}, error)
 			"default": ds.IsDefault,
 			"default": ds.IsDefault,
 		}
 		}
 
 
-		if ds.Type == m.DS_INFLUXDB {
+		if ds.Type == m.DS_INFLUXDB_08 {
 			if ds.Access == m.DS_ACCESS_DIRECT {
 			if ds.Access == m.DS_ACCESS_DIRECT {
 				dsMap["username"] = ds.User
 				dsMap["username"] = ds.User
 				dsMap["password"] = ds.Password
 				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 {
 		if ds.Type == m.DS_ES {
 			dsMap["index"] = ds.Database
 			dsMap["index"] = ds.Database
 		}
 		}
@@ -71,3 +80,13 @@ func getFrontendSettings(c *middleware.Context) (map[string]interface{}, error)
 
 
 	return jsonObj, nil
 	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 {
 func setIndexViewData(c *middleware.Context) error {
-	settings, err := getFrontendSettings(c)
+	settings, err := getFrontendSettingsMap(c)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -17,8 +17,8 @@ func setIndexViewData(c *middleware.Context) error {
 		Login:          c.Login,
 		Login:          c.Login,
 		Email:          c.Email,
 		Email:          c.Email,
 		Name:           c.Name,
 		Name:           c.Name,
-		AccountName:    c.AccountName,
-		AccountRole:    c.AccountRole,
+		OrgName:        c.OrgName,
+		OrgRole:        c.OrgRole,
 		GravatarUrl:    dtos.GetGravatarUrl(c.Email),
 		GravatarUrl:    dtos.GetGravatarUrl(c.Email),
 		IsGrafanaAdmin: c.IsGrafanaAdmin,
 		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"
 	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() {
 	if !cmd.Role.IsValid() {
 		c.JsonApiErr(400, "Invalid role specified", nil)
 		c.JsonApiErr(400, "Invalid role specified", nil)
 		return
 		return
@@ -26,19 +26,19 @@ func AddAccountUser(c *middleware.Context, cmd m.AddAccountUserCommand) {
 		return
 		return
 	}
 	}
 
 
-	cmd.AccountId = c.AccountId
+	cmd.OrgId = c.OrgId
 	cmd.UserId = userToAdd.Id
 	cmd.UserId = userToAdd.Id
 
 
 	if err := bus.Dispatch(&cmd); err != nil {
 	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
 		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 {
 	if err := bus.Dispatch(&query); err != nil {
 		c.JsonApiErr(500, "Failed to get account user", err)
 		c.JsonApiErr(500, "Failed to get account user", err)
@@ -48,18 +48,18 @@ func GetAccountUsers(c *middleware.Context) {
 	c.JSON(200, query.Result)
 	c.JSON(200, query.Result)
 }
 }
 
 
-func RemoveAccountUser(c *middleware.Context) {
+func RemoveOrgUser(c *middleware.Context) {
 	userId := c.ParamsInt64(":id")
 	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 := 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
 			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/bus"
 	"github.com/grafana/grafana/pkg/middleware"
 	"github.com/grafana/grafana/pkg/middleware"
 	m "github.com/grafana/grafana/pkg/models"
 	m "github.com/grafana/grafana/pkg/models"
-	"github.com/grafana/grafana/pkg/setting"
 )
 )
 
 
 // TODO: this needs to be cached or improved somehow
 // TODO: this needs to be cached or improved somehow
@@ -45,7 +44,7 @@ func Search(c *middleware.Context) {
 
 
 	if tagcloud == "true" {
 	if tagcloud == "true" {
 
 
-		query := m.GetDashboardTagsQuery{AccountId: c.AccountId}
+		query := m.GetDashboardTagsQuery{OrgId: c.OrgId}
 		err := bus.Dispatch(&query)
 		err := bus.Dispatch(&query)
 		if err != nil {
 		if err != nil {
 			c.JsonApiErr(500, "Failed to get tags from database", err)
 			c.JsonApiErr(500, "Failed to get tags from database", err)
@@ -61,7 +60,7 @@ func Search(c *middleware.Context) {
 			UserId:    c.UserId,
 			UserId:    c.UserId,
 			Limit:     limit,
 			Limit:     limit,
 			IsStarred: starred == "true",
 			IsStarred: starred == "true",
-			AccountId: c.AccountId,
+			OrgId:     c.OrgId,
 		}
 		}
 
 
 		err := bus.Dispatch(&query)
 		err := bus.Dispatch(&query)
@@ -76,9 +75,6 @@ func Search(c *middleware.Context) {
 		}
 		}
 
 
 		result.Dashboards = query.Result
 		result.Dashboards = query.Result
-		for _, dash := range result.Dashboards {
-			dash.Url = setting.ToAbsUrl("dashboard/db/" + dash.Slug)
-		}
 	}
 	}
 
 
 	c.JSON(200, result)
 	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/bus"
 	"github.com/grafana/grafana/pkg/middleware"
 	"github.com/grafana/grafana/pkg/middleware"
 	m "github.com/grafana/grafana/pkg/models"
 	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/util"
 )
 )
 
 
 func GetUser(c *middleware.Context) {
 func GetUser(c *middleware.Context) {
 	query := m.GetUserInfoQuery{UserId: c.UserId}
 	query := m.GetUserInfoQuery{UserId: c.UserId}
 
 
 	if err := bus.Dispatch(&query); err != nil {
 	if err := bus.Dispatch(&query); err != nil {
-		c.JsonApiErr(500, "Failed to get account", err)
+		c.JsonApiErr(500, "Failed to get user", err)
 		return
 		return
 	}
 	}
 
 
@@ -21,23 +22,23 @@ func UpdateUser(c *middleware.Context, cmd m.UpdateUserCommand) {
 	cmd.UserId = c.UserId
 	cmd.UserId = c.UserId
 
 
 	if err := bus.Dispatch(&cmd); err != nil {
 	if err := bus.Dispatch(&cmd); err != nil {
-		c.JsonApiErr(400, "Failed to update account", err)
+		c.JsonApiErr(400, "Failed to update user", err)
 		return
 		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 {
 	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
 		return
 	}
 	}
 
 
 	for _, ac := range query.Result {
 	for _, ac := range query.Result {
-		if ac.AccountId == c.AccountId {
+		if ac.OrgId == c.OrgId {
 			ac.IsUsing = true
 			ac.IsUsing = true
 			break
 			break
 		}
 		}
@@ -46,17 +47,17 @@ func GetUserAccounts(c *middleware.Context) {
 	c.JSON(200, query.Result)
 	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 {
 	if err := bus.Dispatch(&query); err != nil {
 		return false
 		return false
 	}
 	}
 
 
-	// validate that the account id in the list
+	// validate that the org id in the list
 	valid := false
 	valid := false
 	for _, other := range query.Result {
 	for _, other := range query.Result {
-		if other.AccountId == accountId {
+		if other.OrgId == orgId {
 			valid = true
 			valid = true
 		}
 		}
 	}
 	}
@@ -64,23 +65,53 @@ func validateUsingAccount(userId int64, accountId int64) bool {
 	return valid
 	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
 		return
 	}
 	}
 
 
-	cmd := m.SetUsingAccountCommand{
-		UserId:    c.UserId,
-		AccountId: usingAccountId,
+	cmd := m.SetUsingOrgCommand{
+		UserId: c.UserId,
+		OrgId:  orgId,
 	}
 	}
 
 
 	if err := bus.Dispatch(&cmd); err != nil {
 	if err := bus.Dispatch(&cmd); err != nil {
-		c.JsonApiErr(500, "Failed change active account", err)
+		c.JsonApiErr(500, "Failed change active organization", err)
 		return
 		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/log"
 	"github.com/grafana/grafana/pkg/middleware"
 	"github.com/grafana/grafana/pkg/middleware"
 	"github.com/grafana/grafana/pkg/services/eventpublisher"
 	"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/setting"
 	"github.com/grafana/grafana/pkg/social"
 	"github.com/grafana/grafana/pkg/social"
 )
 )
 
 
-var CmdWeb = cli.Command{
+var Web = cli.Command{
 	Name:        "web",
 	Name:        "web",
-	Usage:       "grafana web",
+	Usage:       "Starts Grafana backend & web server",
 	Description: "Starts Grafana backend & web server",
 	Description: "Starts Grafana backend & web server",
 	Action:      runWeb,
 	Action:      runWeb,
-	Flags: []cli.Flag{
-		cli.StringFlag{
-			Name:  "config",
-			Value: "grafana.ini",
-			Usage: "path to config file",
-		},
-	},
 }
 }
 
 
 func newMacaron() *macaron.Macaron {
 func newMacaron() *macaron.Macaron {
@@ -78,10 +70,9 @@ func runWeb(c *cli.Context) {
 	log.Info("Starting Grafana")
 	log.Info("Starting Grafana")
 	log.Info("Version: %v, Commit: %v, Build date: %v", setting.BuildVersion, setting.BuildCommit, time.Unix(setting.BuildStamp, 0))
 	log.Info("Version: %v, Commit: %v, Build date: %v", setting.BuildVersion, setting.BuildCommit, time.Unix(setting.BuildStamp, 0))
 
 
-	setting.NewConfigContext()
+	initRuntime(c)
+
 	social.NewOAuthService()
 	social.NewOAuthService()
-	sqlstore.NewEngine()
-	sqlstore.EnsureAdminUser()
 	eventpublisher.Init()
 	eventpublisher.Init()
 
 
 	var err error
 	var err error

+ 2 - 2
pkg/events/events.go

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

+ 92 - 10
pkg/log/console.go

@@ -6,6 +6,7 @@ package log
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
+	"fmt"
 	"log"
 	"log"
 	"os"
 	"os"
 	"runtime"
 	"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.
 // ConsoleWriter implements LoggerInterface and writes messages to terminal.
 type ConsoleWriter struct {
 type ConsoleWriter struct {
@@ -40,7 +52,7 @@ type ConsoleWriter struct {
 // create ConsoleWriter returning as LoggerInterface.
 // create ConsoleWriter returning as LoggerInterface.
 func NewConsole() LoggerInterface {
 func NewConsole() LoggerInterface {
 	return &ConsoleWriter{
 	return &ConsoleWriter{
-		lg:    log.New(os.Stdout, "", log.Ldate|log.Ltime),
+		lg:    log.New(os.Stderr, "", log.Ldate|log.Ltime),
 		Level: TRACE,
 		Level: TRACE,
 	}
 	}
 }
 }
@@ -68,6 +80,76 @@ func (_ *ConsoleWriter) Flush() {
 func (_ *ConsoleWriter) Destroy() {
 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() {
 func init() {
 	Register("console", NewConsole)
 	Register("console", NewConsole)
 }
 }

+ 1 - 1
pkg/middleware/auth.go

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

+ 9 - 8
pkg/middleware/middleware.go

@@ -56,22 +56,23 @@ func GetContextHandler() macaron.Handler {
 				ctx.SignedInUser = &m.SignedInUser{}
 				ctx.SignedInUser = &m.SignedInUser{}
 
 
 				// TODO: fix this
 				// TODO: fix this
-				ctx.AccountRole = keyInfo.Role
+				ctx.OrgRole = keyInfo.Role
 				ctx.ApiKeyId = keyInfo.Id
 				ctx.ApiKeyId = keyInfo.Id
-				ctx.AccountId = keyInfo.AccountId
+				ctx.OrgId = keyInfo.OrgId
 			}
 			}
 		} else if setting.AnonymousEnabled {
 		} 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 {
 			} else {
 				ctx.IsSignedIn = false
 				ctx.IsSignedIn = false
 				ctx.HasAnonymousAccess = true
 				ctx.HasAnonymousAccess = true
 				ctx.SignedInUser = &m.SignedInUser{}
 				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")
 var ErrInvalidApiKey = errors.New("Invalid API Key")
 
 
 type ApiKey struct {
 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
 // COMMANDS
 type AddApiKeyCommand struct {
 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:"-"`
 	Result *ApiKey `json:"-"`
 }
 }
@@ -33,20 +33,20 @@ type UpdateApiKeyCommand struct {
 	Name string   `json:"name"`
 	Name string   `json:"name"`
 	Role RoleType `json:"role"`
 	Role RoleType `json:"role"`
 
 
-	AccountId int64 `json:"-"`
+	OrgId int64 `json:"-"`
 }
 }
 
 
 type DeleteApiKeyCommand struct {
 type DeleteApiKeyCommand struct {
-	Id        int64 `json:"id"`
-	AccountId int64 `json:"-"`
+	Id    int64 `json:"id"`
+	OrgId int64 `json:"-"`
 }
 }
 
 
 // ----------------------
 // ----------------------
 // QUERIES
 // QUERIES
 
 
 type GetApiKeysQuery struct {
 type GetApiKeysQuery struct {
-	AccountId int64
-	Result    []*ApiKey
+	OrgId  int64
+	Result []*ApiKey
 }
 }
 
 
 type GetApiKeyByKeyQuery struct {
 type GetApiKeyByKeyQuery struct {

+ 10 - 10
pkg/models/dashboards.go

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

+ 20 - 12
pkg/models/datasource.go

@@ -8,7 +8,9 @@ import (
 const (
 const (
 	DS_GRAPHITE      = "graphite"
 	DS_GRAPHITE      = "graphite"
 	DS_INFLUXDB      = "influxdb"
 	DS_INFLUXDB      = "influxdb"
+	DS_INFLUXDB_08   = "influxdb_08"
 	DS_ES            = "elasticsearch"
 	DS_ES            = "elasticsearch"
+	DS_OPENTSDB      = "opentsdb"
 	DS_ACCESS_DIRECT = "direct"
 	DS_ACCESS_DIRECT = "direct"
 	DS_ACCESS_PROXY  = "proxy"
 	DS_ACCESS_PROXY  = "proxy"
 )
 )
@@ -22,9 +24,9 @@ type DsType string
 type DsAccess string
 type DsAccess string
 
 
 type DataSource struct {
 type DataSource struct {
-	Id        int64
-	AccountId int64
-	Version   int
+	Id      int64
+	OrgId   int64
+	Version int
 
 
 	Name              string
 	Name              string
 	Type              DsType
 	Type              DsType
@@ -47,7 +49,7 @@ type DataSource struct {
 
 
 // Also acts as api DTO
 // Also acts as api DTO
 type AddDataSourceCommand struct {
 type AddDataSourceCommand struct {
-	AccountId int64 `json:"-"`
+	OrgId     int64 `json:"-"`
 	Name      string
 	Name      string
 	Type      DsType
 	Type      DsType
 	Access    DsAccess
 	Access    DsAccess
@@ -63,7 +65,7 @@ type AddDataSourceCommand struct {
 // Also acts as api DTO
 // Also acts as api DTO
 type UpdateDataSourceCommand struct {
 type UpdateDataSourceCommand struct {
 	Id        int64
 	Id        int64
-	AccountId int64
+	OrgId     int64
 	Name      string
 	Name      string
 	Type      DsType
 	Type      DsType
 	Access    DsAccess
 	Access    DsAccess
@@ -75,22 +77,28 @@ type UpdateDataSourceCommand struct {
 }
 }
 
 
 type DeleteDataSourceCommand struct {
 type DeleteDataSourceCommand struct {
-	Id        int64
-	AccountId int64
+	Id    int64
+	OrgId int64
 }
 }
 
 
 // ---------------------
 // ---------------------
 // QUERIES
 // QUERIES
 
 
 type GetDataSourcesQuery struct {
 type GetDataSourcesQuery struct {
-	AccountId int64
-	Result    []*DataSource
+	OrgId  int64
+	Result []*DataSource
 }
 }
 
 
 type GetDataSourceByIdQuery struct {
 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"`
 	Title     string   `json:"title"`
 	Slug      string   `json:"slug"`
 	Slug      string   `json:"slug"`
 	Tags      []string `json:"tags"`
 	Tags      []string `json:"tags"`
-	Url       string   `json:"url"`
 	IsStarred bool     `json:"isStarred"`
 	IsStarred bool     `json:"isStarred"`
 }
 }
 
 
@@ -23,7 +22,7 @@ type DashboardTagCloudItem struct {
 type SearchDashboardsQuery struct {
 type SearchDashboardsQuery struct {
 	Title     string
 	Title     string
 	Tag       string
 	Tag       string
-	AccountId int64
+	OrgId     int64
 	UserId    int64
 	UserId    int64
 	Limit     int
 	Limit     int
 	IsStarred bool
 	IsStarred bool
@@ -32,6 +31,6 @@ type SearchDashboardsQuery struct {
 }
 }
 
 
 type GetDashboardTagsQuery 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 {
 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
 	Created time.Time
 	Updated time.Time
 	Updated time.Time
@@ -50,9 +52,25 @@ type UpdateUserCommand struct {
 	UserId int64 `json:"-"`
 	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
 	Result       *User
 }
 }
 
 
+type GetUserByIdQuery struct {
+	Id     int64
+	Result *User
+}
+
 type GetSignedInUserQuery struct {
 type GetSignedInUserQuery struct {
 	UserId int64
 	UserId int64
 	Result *SignedInUser
 	Result *SignedInUser
@@ -81,14 +104,19 @@ type SearchUsersQuery struct {
 	Result []*UserSearchHitDTO
 	Result []*UserSearchHitDTO
 }
 }
 
 
+type GetUserOrgListQuery struct {
+	UserId int64
+	Result []*UserOrgDTO
+}
+
 // ------------------------
 // ------------------------
 // DTO & Projections
 // DTO & Projections
 
 
 type SignedInUser struct {
 type SignedInUser struct {
 	UserId         int64
 	UserId         int64
-	AccountId      int64
-	AccountName    string
-	AccountRole    RoleType
+	OrgId          int64
+	OrgName        string
+	OrgRole        RoleType
 	Login          string
 	Login          string
 	Name           string
 	Name           string
 	Email          string
 	Email          string
@@ -97,9 +125,10 @@ type SignedInUser struct {
 }
 }
 
 
 type UserDTO 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 {
 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 {
 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)
 	query.Result = make([]*m.ApiKey, 0)
 	return sess.Find(&query.Result)
 	return sess.Find(&query.Result)
@@ -25,8 +25,8 @@ func GetApiKeys(query *m.GetApiKeysQuery) error {
 
 
 func DeleteApiKey(cmd *m.DeleteApiKeyCommand) error {
 func DeleteApiKey(cmd *m.DeleteApiKeyCommand) error {
 	return inTransaction(func(sess *xorm.Session) 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
 		return err
 	})
 	})
 }
 }
@@ -34,12 +34,12 @@ func DeleteApiKey(cmd *m.DeleteApiKeyCommand) error {
 func AddApiKey(cmd *m.AddApiKeyCommand) error {
 func AddApiKey(cmd *m.AddApiKeyCommand) error {
 	return inTransaction(func(sess *xorm.Session) error {
 	return inTransaction(func(sess *xorm.Session) error {
 		t := m.ApiKey{
 		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 {
 		if _, err := sess.Insert(&t); err != nil {
@@ -53,20 +53,20 @@ func AddApiKey(cmd *m.AddApiKeyCommand) error {
 func UpdateApiKey(cmd *m.UpdateApiKeyCommand) error {
 func UpdateApiKey(cmd *m.UpdateApiKeyCommand) error {
 	return inTransaction(func(sess *xorm.Session) error {
 	return inTransaction(func(sess *xorm.Session) error {
 		t := m.ApiKey{
 		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
 		return err
 	})
 	})
 }
 }
 
 
 func GetApiKeyByKey(query *m.GetApiKeyByKeyQuery) error {
 func GetApiKeyByKey(query *m.GetApiKeyByKeyQuery) error {
 	var apikey m.ApiKey
 	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 {
 	if err != nil {
 		return err
 		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()
 		dash := cmd.GetDashboardModel()
 
 
 		// try get existing dashboard
 		// 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)
 		hasExisting, err := sess.Get(&existing)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
@@ -61,7 +61,7 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
 }
 }
 
 
 func GetDashboard(query *m.GetDashboardQuery) 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)
 	has, err := x.Get(&dashboard)
 	if err != nil {
 	if err != nil {
 		return err
 		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(" 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 {
 	if query.IsStarred {
 		sql.WriteString(` AND star.user_id=?`)
 		sql.WriteString(` AND star.user_id=?`)
@@ -158,11 +158,11 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
 						term
 						term
 					FROM dashboard
 					FROM dashboard
 					INNER JOIN dashboard_tag on dashboard_tag.dashboard_id = dashboard.id
 					INNER JOIN dashboard_tag on dashboard_tag.dashboard_id = dashboard.id
-					WHERE dashboard.account_id=?
+					WHERE dashboard.org_id=?
 					GROUP BY term`
 					GROUP BY term`
 
 
 	query.Result = make([]*m.DashboardTagCloudItem, 0)
 	query.Result = make([]*m.DashboardTagCloudItem, 0)
-	sess := x.Sql(sql, query.AccountId)
+	sess := x.Sql(sql, query.OrgId)
 	err := sess.Find(&query.Result)
 	err := sess.Find(&query.Result)
 	return err
 	return err
 }
 }
@@ -171,8 +171,8 @@ func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
 	sess := x.NewSession()
 	sess := x.NewSession()
 	defer sess.Close()
 	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
 	return err
 }
 }

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

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

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

@@ -15,10 +15,21 @@ func init() {
 	bus.AddHandler("sql", DeleteDataSource)
 	bus.AddHandler("sql", DeleteDataSource)
 	bus.AddHandler("sql", UpdateDataSource)
 	bus.AddHandler("sql", UpdateDataSource)
 	bus.AddHandler("sql", GetDataSourceById)
 	bus.AddHandler("sql", GetDataSourceById)
+	bus.AddHandler("sql", GetDataSourceByName)
 }
 }
 
 
 func GetDataSourceById(query *m.GetDataSourceByIdQuery) error {
 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)
 	has, err := sess.Get(&query.Result)
 
 
 	if !has {
 	if !has {
@@ -28,7 +39,7 @@ func GetDataSourceById(query *m.GetDataSourceByIdQuery) error {
 }
 }
 
 
 func GetDataSources(query *m.GetDataSourcesQuery) 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)
 	query.Result = make([]*m.DataSource, 0)
 	return sess.Find(&query.Result)
 	return sess.Find(&query.Result)
@@ -36,8 +47,8 @@ func GetDataSources(query *m.GetDataSourcesQuery) error {
 
 
 func DeleteDataSource(cmd *m.DeleteDataSourceCommand) error {
 func DeleteDataSource(cmd *m.DeleteDataSourceCommand) error {
 	return inTransaction(func(sess *xorm.Session) 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
 		return err
 	})
 	})
 }
 }
@@ -46,7 +57,7 @@ func AddDataSource(cmd *m.AddDataSourceCommand) error {
 
 
 	return inTransaction(func(sess *xorm.Session) error {
 	return inTransaction(func(sess *xorm.Session) error {
 		ds := &m.DataSource{
 		ds := &m.DataSource{
-			AccountId: cmd.AccountId,
+			OrgId:     cmd.OrgId,
 			Name:      cmd.Name,
 			Name:      cmd.Name,
 			Type:      cmd.Type,
 			Type:      cmd.Type,
 			Access:    cmd.Access,
 			Access:    cmd.Access,
@@ -74,8 +85,8 @@ func AddDataSource(cmd *m.AddDataSourceCommand) error {
 func updateIsDefaultFlag(ds *m.DataSource, sess *xorm.Session) error {
 func updateIsDefaultFlag(ds *m.DataSource, sess *xorm.Session) error {
 	// Handle is default flag
 	// Handle is default flag
 	if ds.IsDefault {
 	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
 			return err
 		}
 		}
 	}
 	}
@@ -87,7 +98,7 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
 	return inTransaction(func(sess *xorm.Session) error {
 	return inTransaction(func(sess *xorm.Session) error {
 		ds := &m.DataSource{
 		ds := &m.DataSource{
 			Id:        cmd.Id,
 			Id:        cmd.Id,
-			AccountId: cmd.AccountId,
+			OrgId:     cmd.OrgId,
 			Name:      cmd.Name,
 			Name:      cmd.Name,
 			Type:      cmd.Type,
 			Type:      cmd.Type,
 			Access:    cmd.Access,
 			Access:    cmd.Access,
@@ -101,7 +112,7 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
 
 
 		sess.UseBool("is_default")
 		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 {
 		if err != nil {
 			return err
 			return err
 		}
 		}

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

@@ -42,16 +42,16 @@ func TestDataAccess(t *testing.T) {
 		Convey("Can add datasource", func() {
 		Convey("Can add datasource", func() {
 
 
 			err := AddDataSource(&m.AddDataSourceCommand{
 			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)
 			So(err, ShouldBeNil)
 
 
-			query := m.GetDataSourcesQuery{AccountId: 10}
+			query := m.GetDataSourcesQuery{OrgId: 10}
 			err = GetDataSources(&query)
 			err = GetDataSources(&query)
 			So(err, ShouldBeNil)
 			So(err, ShouldBeNil)
 
 
@@ -59,33 +59,33 @@ func TestDataAccess(t *testing.T) {
 
 
 			ds := query.Result[0]
 			ds := query.Result[0]
 
 
-			So(ds.AccountId, ShouldEqual, 10)
+			So(ds.OrgId, ShouldEqual, 10)
 			So(ds.Database, ShouldEqual, "site")
 			So(ds.Database, ShouldEqual, "site")
 		})
 		})
 
 
 		Convey("Given a datasource", func() {
 		Convey("Given a datasource", func() {
 
 
 			AddDataSource(&m.AddDataSourceCommand{
 			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)
 			GetDataSources(&query)
 			ds := query.Result[0]
 			ds := query.Result[0]
 
 
 			Convey("Can delete datasource", func() {
 			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)
 				So(err, ShouldBeNil)
 
 
 				GetDataSources(&query)
 				GetDataSources(&query)
 				So(len(query.Result), ShouldEqual, 0)
 				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)
 				So(err, ShouldBeNil)
 
 
 				GetDataSources(&query)
 				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 (
 import (
 	"fmt"
 	"fmt"
@@ -32,7 +32,7 @@ func TestMigrations(t *testing.T) {
 
 
 			mg := NewMigrator(x)
 			mg := NewMigrator(x)
 			mg.LogLevel = log.DEBUG
 			mg.LogLevel = log.DEBUG
-			addMigrations(mg)
+			AddMigrations(mg)
 
 
 			err = mg.Start()
 			err = mg.Start()
 			So(err, ShouldBeNil)
 			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
 	CreateIndexSql(tableName string, index *Index) string
 	CreateTableSql(table *Table) string
 	CreateTableSql(table *Table) string
 	AddColumnSql(tableName string, Col *Column) 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{})
 	TableCheckSql(tableName string) (string, []interface{})
+	RenameTable(oldName string, newName string) string
 }
 }
 
 
 func NewDialect(name string) Dialect {
 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 {
 func (db *BaseDialect) CreateIndexSql(tableName string, index *Index) string {
 	quote := db.dialect.Quote
 	quote := db.dialect.Quote
 	var unique string
 	var unique string
-	var idxName string
 	if index.Type == UniqueIndex {
 	if index.Type == UniqueIndex {
 		unique = " UNIQUE"
 		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,
 	return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v);", unique,
 		quote(idxName), quote(tableName),
 		quote(idxName), quote(tableName),
 		quote(strings.Join(index.Cols, quote(","))))
 		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-sql-driver/mysql"
 	"github.com/go-xorm/xorm"
 	"github.com/go-xorm/xorm"
+	"github.com/grafana/grafana/pkg/log"
 	_ "github.com/lib/pq"
 	_ "github.com/lib/pq"
 	_ "github.com/mattn/go-sqlite3"
 	_ "github.com/mattn/go-sqlite3"
-	"github.com/grafana/grafana/pkg/log"
 )
 )
 
 
 type Migrator struct {
 type Migrator struct {
@@ -70,7 +70,7 @@ func (mg *Migrator) GetMigrationLog() (map[string]MigrationLog, error) {
 
 
 func (mg *Migrator) Start() error {
 func (mg *Migrator) Start() error {
 	if mg.LogLevel <= log.INFO {
 	if mg.LogLevel <= log.INFO {
-		log.Info("Migrator:: Starting DB migration")
+		log.Info("Migrator: Starting DB migration")
 	}
 	}
 
 
 	logMap, err := mg.GetMigrationLog()
 	logMap, err := mg.GetMigrationLog()
@@ -82,7 +82,7 @@ func (mg *Migrator) Start() error {
 		_, exists := logMap[m.Id()]
 		_, exists := logMap[m.Id()]
 		if exists {
 		if exists {
 			if mg.LogLevel <= log.DEBUG {
 			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
 			continue
 		}
 		}
@@ -114,13 +114,24 @@ func (mg *Migrator) Start() error {
 
 
 func (mg *Migrator) exec(m Migration) error {
 func (mg *Migrator) exec(m Migration) error {
 	if mg.LogLevel <= log.INFO {
 	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 {
 	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))
 		_, err := sess.Exec(m.Sql(mg.dialect))
 		if err != nil {
 		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 err
 		}
 		}
 		return nil
 		return nil

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

@@ -1,6 +1,9 @@
 package migrator
 package migrator
 
 
-import "strconv"
+import (
+	"fmt"
+	"strconv"
+)
 
 
 type Postgres struct {
 type Postgres struct {
 	BaseDialect
 	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`=?"
 	sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?"
 	return sql, args
 	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
 package migrator
 
 
+import "fmt"
+
 type Sqlite3 struct {
 type Sqlite3 struct {
 	BaseDialect
 	BaseDialect
 }
 }
@@ -57,3 +59,10 @@ func (db *Sqlite3) TableCheckSql(tableName string) (string, []interface{}) {
 	args := []interface{}{tableName}
 	args := []interface{}{tableName}
 	return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args
 	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
 package migrator
 
 
+import (
+	"fmt"
+	"strings"
+)
+
 const (
 const (
 	POSTGRES = "postgres"
 	POSTGRES = "postgres"
 	SQLITE   = "sqlite3"
 	SQLITE   = "sqlite3"
@@ -10,6 +15,7 @@ type Migration interface {
 	Sql(dialect Dialect) string
 	Sql(dialect Dialect) string
 	Id() string
 	Id() string
 	SetId(string)
 	SetId(string)
+	GetCondition() MigrationCondition
 }
 }
 
 
 type SQLType string
 type SQLType string
@@ -24,6 +30,7 @@ type Table struct {
 	Name        string
 	Name        string
 	Columns     []*Column
 	Columns     []*Column
 	PrimaryKeys []string
 	PrimaryKeys []string
+	Indices     []*Index
 }
 }
 
 
 const (
 const (
@@ -37,6 +44,21 @@ type Index struct {
 	Cols []string
 	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 (
 var (
 	DB_Bit       = "BIT"
 	DB_Bit       = "BIT"
 	DB_TinyInt   = "TINYINT"
 	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() {
 	Convey("Testing Account DB Access", t, func() {
 		InitTestDB(t)
 		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"}
 				ac1cmd := m.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"}
 				ac2cmd := m.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 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)
 				err = CreateUser(&ac2cmd)
 				So(err, ShouldBeNil)
 				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")
 				So(q1.Result[0].Role, ShouldEqual, "Viewer")
 			})
 			})
 		})
 		})
 
 
 		Convey("Given two saved users", func() {
 		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"}
 			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}
 			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")
 				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() {
 				Convey("Should have been saved without error", func() {
 					So(err, ShouldBeNil)
 					So(err, ShouldBeNil)
 				})
 				})
@@ -88,54 +88,54 @@ func TestAccountDataAccess(t *testing.T) {
 
 
 					So(err, ShouldBeNil)
 					So(err, ShouldBeNil)
 					So(query.Result.Email, ShouldEqual, "ac2@test.com")
 					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.Name, ShouldEqual, "ac2 name")
 					So(query.Result.Login, ShouldEqual, "ac2")
 					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)
 					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(err, ShouldBeNil)
 					So(len(query.Result), ShouldEqual, 2)
 					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(err, ShouldBeNil)
 					So(len(query.Result), ShouldEqual, 2)
 					So(len(query.Result), ShouldEqual, 2)
 					So(query.Result[0].Role, ShouldEqual, "Admin")
 					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)
 					So(err, ShouldBeNil)
 
 
-					Convey("SignedInUserQuery with a different account", func() {
+					Convey("SignedInUserQuery with a different org", func() {
 						query := m.GetSignedInUserQuery{UserId: ac2.Id}
 						query := m.GetSignedInUserQuery{UserId: ac2.Id}
 						err := GetSignedInUser(&query)
 						err := GetSignedInUser(&query)
 
 
 						So(err, ShouldBeNil)
 						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.Email, ShouldEqual, "ac2@test.com")
 						So(query.Result.Name, ShouldEqual, "ac2 name")
 						So(query.Result.Name, ShouldEqual, "ac2 name")
 						So(query.Result.Login, ShouldEqual, "ac2")
 						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() {
 				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/bus"
 	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/log"
 	m "github.com/grafana/grafana/pkg/models"
 	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/services/sqlstore/migrator"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/setting"
 
 
@@ -73,7 +74,7 @@ func SetEngine(engine *xorm.Engine, enableLog bool) (err error) {
 
 
 	migrator := migrator.NewMigrator(x)
 	migrator := migrator.NewMigrator(x)
 	migrator.LogLevel = log.INFO
 	migrator.LogLevel = log.INFO
-	addMigrations(migrator)
+	migrations.AddMigrations(migrator)
 
 
 	if err := migrator.Start(); err != nil {
 	if err := migrator.Start(); err != nil {
 		return fmt.Errorf("Sqlstore::Migration failed err: %v\n", err)
 		return fmt.Errorf("Sqlstore::Migration failed err: %v\n", err)

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

@@ -1,6 +1,7 @@
 package sqlstore
 package sqlstore
 
 
 import (
 import (
+	"fmt"
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
@@ -15,59 +16,64 @@ import (
 
 
 func init() {
 func init() {
 	bus.AddHandler("sql", CreateUser)
 	bus.AddHandler("sql", CreateUser)
+	bus.AddHandler("sql", GetUserById)
 	bus.AddHandler("sql", UpdateUser)
 	bus.AddHandler("sql", UpdateUser)
+	bus.AddHandler("sql", ChangeUserPassword)
 	bus.AddHandler("sql", GetUserByLogin)
 	bus.AddHandler("sql", GetUserByLogin)
-	bus.AddHandler("sql", SetUsingAccount)
+	bus.AddHandler("sql", SetUsingOrg)
 	bus.AddHandler("sql", GetUserInfo)
 	bus.AddHandler("sql", GetUserInfo)
 	bus.AddHandler("sql", GetSignedInUser)
 	bus.AddHandler("sql", GetSignedInUser)
 	bus.AddHandler("sql", SearchUsers)
 	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 {
 		if err != nil {
 			return 0, err
 			return 0, err
 		}
 		}
 		if has {
 		if has {
-			return account.Id, nil
+			return org.Id, nil
 		} else {
 		} else {
-			account.Name = setting.DefaultAccountName
+			org.Name = setting.DefaultOrgName
 		}
 		}
 	} else {
 	} 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 0, err
 	}
 	}
 
 
-	return account.Id, nil
+	return org.Id, nil
 }
 }
 
 
 func CreateUser(cmd *m.CreateUserCommand) error {
 func CreateUser(cmd *m.CreateUserCommand) error {
 	return inTransaction2(func(sess *session) error {
 	return inTransaction2(func(sess *session) error {
-		accountId, err := getAccountIdForNewUser(cmd.Email, sess)
+		orgId, err := getOrgIdForNewUser(cmd.Email, sess)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
 
 
 		// create user
 		// create user
 		user := m.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 {
 		if len(cmd.Password) > 0 {
@@ -82,20 +88,20 @@ func CreateUser(cmd *m.CreateUserCommand) error {
 			return err
 			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
 			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 {
 func GetUserByLogin(query *m.GetUserByLoginQuery) error {
 	if query.LoginOrEmail == "" {
 	if query.LoginOrEmail == "" {
-		return m.ErrAccountNotFound
+		return m.ErrUserNotFound
 	}
 	}
 
 
 	user := new(m.User)
 	user := new(m.User)
 	if strings.Contains(query.LoginOrEmail, "@") {
 	if strings.Contains(query.LoginOrEmail, "@") {
 		user = &m.User{Email: query.LoginOrEmail}
 		user = &m.User{Email: query.LoginOrEmail}
 	} else {
 	} else {
-		user = &m.User{Login: strings.ToLower(query.LoginOrEmail)}
+		user = &m.User{Login: query.LoginOrEmail}
 	}
 	}
 
 
 	has, err := x.Get(user)
 	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 {
 	return inTransaction(func(sess *xorm.Session) error {
 		user := m.User{}
 		user := m.User{}
 		sess.Id(cmd.UserId).Get(&user)
 		sess.Id(cmd.UserId).Get(&user)
 
 
-		user.AccountId = cmd.AccountId
+		user.OrgId = cmd.OrgId
 		_, err := sess.Id(user.Id).Update(&user)
 		_, err := sess.Id(user.Id).Update(&user)
 		return err
 		return err
 	})
 	})
@@ -193,12 +230,12 @@ func GetUserInfo(query *m.GetUserInfoQuery) error {
 	return err
 	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)
 	err := sess.Find(&query.Result)
 	return err
 	return err
 }
 }
@@ -210,12 +247,12 @@ func GetSignedInUser(query *m.GetSignedInUserQuery) error {
 	                u.email        as email,
 	                u.email        as email,
 	                u.login        as login,
 	                u.login        as login,
 									u.name         as name,
 									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
 	                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=?`
 	                WHERE u.id=?`
 
 
 	var user m.SignedInUser
 	var user m.SignedInUser
@@ -240,3 +277,23 @@ func SearchUsers(query *m.SearchUsersQuery) error {
 	err := sess.Find(&query.Result)
 	err := sess.Find(&query.Result)
 	return err
 	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
 package setting
 
 
 import (
 import (
+	"fmt"
 	"net/url"
 	"net/url"
 	"os"
 	"os"
 	"path"
 	"path"
@@ -65,18 +66,18 @@ var (
 	CookieRememberName string
 	CookieRememberName string
 	DisableUserSignUp  bool
 	DisableUserSignUp  bool
 
 
-	// single account
-	SingleAccountMode  bool
-	DefaultAccountName string
-	DefaultAccountRole string
+	// single organization
+	SingleOrgMode  bool
+	DefaultOrgName string
+	DefaultOrgRole string
 
 
 	// Http auth
 	// Http auth
 	AdminUser     string
 	AdminUser     string
 	AdminPassword string
 	AdminPassword string
 
 
-	AnonymousEnabled     bool
-	AnonymousAccountName string
-	AnonymousAccountRole string
+	AnonymousEnabled bool
+	AnonymousOrgName string
+	AnonymousOrgRole string
 
 
 	// Session settings.
 	// Session settings.
 	SessionOptions session.Options
 	SessionOptions session.Options
@@ -98,15 +99,10 @@ var (
 func init() {
 func init() {
 	IsWindows = runtime.GOOS == "windows"
 	IsWindows = runtime.GOOS == "windows"
 	log.NewLogger(0, "console", `{"level": 0}`)
 	log.NewLogger(0, "console", `{"level": 0}`)
-}
-
-func getWorkDir() string {
-	p, _ := filepath.Abs(".")
-	return p
+	WorkDir, _ = filepath.Abs(".")
 }
 }
 
 
 func findConfigFiles() []string {
 func findConfigFiles() []string {
-	WorkDir = getWorkDir()
 	ConfRootPath = path.Join(WorkDir, "conf")
 	ConfRootPath = path.Join(WorkDir, "conf")
 	filenames := make([]string, 0)
 	filenames := make([]string, 0)
 
 
@@ -152,10 +148,29 @@ func ToAbsUrl(relativeUrl string) string {
 	return AppUrl + relativeUrl
 	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()
 	configFiles := findConfigFiles()
 
 
-	//log.Info("Loading config files: %v", configFiles)
+	if config != "" {
+		configFiles = append(configFiles, config)
+	}
+
 	var err error
 	var err error
 
 
 	for i, file := range configFiles {
 	for i, file := range configFiles {
@@ -170,6 +185,8 @@ func NewConfigContext() {
 		}
 		}
 	}
 	}
 
 
+	loadEnvVariableOverrides()
+
 	AppName = Cfg.Section("").Key("app_name").MustString("Grafana")
 	AppName = Cfg.Section("").Key("app_name").MustString("Grafana")
 	Env = Cfg.Section("").Key("app_mode").MustString("development")
 	Env = Cfg.Section("").Key("app_mode").MustString("development")
 
 
@@ -187,11 +204,6 @@ func NewConfigContext() {
 	HttpAddr = server.Key("http_addr").MustString("0.0.0.0")
 	HttpAddr = server.Key("http_addr").MustString("0.0.0.0")
 	HttpPort = server.Key("http_port").MustString("3000")
 	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"))
 	StaticRootPath = server.Key("static_root_path").MustString(path.Join(WorkDir, "webapp"))
 	RouterLogging = server.Key("router_logging").MustBool(false)
 	RouterLogging = server.Key("router_logging").MustBool(false)
 	EnableGzip = server.Key("enable_gzip").MustBool(false)
 	EnableGzip = server.Key("enable_gzip").MustBool(false)
@@ -208,14 +220,14 @@ func NewConfigContext() {
 	AdminPassword = security.Key("admin_password").String()
 	AdminPassword = security.Key("admin_password").String()
 
 
 	// single account
 	// 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
 	// anonymous access
 	AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false)
 	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
 	// PhantomJS rendering
 	ImagesDir = "data/png"
 	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)) {
     if(_.isDate(text)) {
       return text;
       return text;
     }
     }
-    var time,
-      mathString = "",
-      index,
-      parseString;
+
+    var time;
+    var mathString = "";
+    var index;
+    var parseString;
+
     if (text.substring(0,3) === "now") {
     if (text.substring(0,3) === "now") {
       time = new Date();
       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("||");
       index = text.indexOf("||");
       parseString;
       parseString;
       if (index === -1) {
       if (index === -1) {
@@ -197,6 +205,11 @@ function($, _, moment) {
     return kbn.parseDateMath(mathString, time);
     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) {
   kbn.parseDateMath = function(mathString, time, roundUp) {
     var dateTime = moment(time);
     var dateTime = moment(time);
     for (var i = 0; i < mathString.length;) {
     for (var i = 0; i < mathString.length;) {
@@ -321,11 +334,15 @@ function($, _, moment) {
       }
       }
 
 
       var steps = 0;
       var steps = 0;
+      var limit = extArray.length;
 
 
       while (Math.abs(size) >= factor) {
       while (Math.abs(size) >= factor) {
         steps++;
         steps++;
         size /= factor;
         size /= factor;
+
+        if (steps >= limit) { return "NA"; }
       }
       }
+
       if (steps > 0) {
       if (steps > 0) {
         decimals = scaledDecimals + (3 * steps);
         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.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.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.ev = kbn.formatFuncCreator(1000, [' eV', ' keV', ' MeV', 'GeV', 'TeV', 'PeV', 'EeV', 'ZeV', 'YeV']);
   kbn.valueFormats.none = kbn.toFixed;
   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) {
   kbn.valueFormats.ms = function(size, decimals, scaledDecimals) {
     if (size === null) { return ""; }
     if (size === null) { return ""; }
@@ -493,6 +521,7 @@ function($, _, moment) {
           {text: 'none' , value: 'none'},
           {text: 'none' , value: 'none'},
           {text: 'short', value: 'short'},
           {text: 'short', value: 'short'},
           {text: 'percent', value: 'percent'},
           {text: 'percent', value: 'percent'},
+          {text: 'ppm', value: 'ppm'},
         ]
         ]
       },
       },
       {
       {
@@ -509,8 +538,8 @@ function($, _, moment) {
         submenu: [
         submenu: [
           {text: 'bits', value: 'bits'},
           {text: 'bits', value: 'bits'},
           {text: 'bytes', value: 'bytes'},
           {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',
         text: 'energy',
         submenu: [
         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([
 define([
   'lodash',
   'lodash',
-  'crypto',
 ],
 ],
-function (_, crypto) {
+function (_) {
   "use strict";
   "use strict";
 
 
   return function Settings (options) {
   return function Settings (options) {
@@ -27,55 +26,31 @@ function (_, crypto) {
       playlist_timespan: "1m",
       playlist_timespan: "1m",
       unsaved_changes_warning: true,
       unsaved_changes_warning: true,
       search: { max_results: 100 },
       search: { max_results: 100 },
-      admin: {},
       appSubUrl: ""
       appSubUrl: ""
     };
     };
 
 
     var settings = _.extend({}, defaults, options);
     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) {
     if (settings.plugins.panels) {
       _.extend(settings.panels, settings.plugins.panels);
       _.extend(settings.panels, settings.plugins.panels);

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

@@ -3,7 +3,6 @@ define([
   './pulldown',
   './pulldown',
   './search',
   './search',
   './metricKeys',
   './metricKeys',
-  './graphiteImport',
   './inspectCtrl',
   './inspectCtrl',
   './jsonEditorCtrl',
   './jsonEditorCtrl',
   './loginCtrl',
   './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',
   'angular',
   'lodash',
   'lodash',
   'config',
   'config',
-  'jquery'
 ],
 ],
-function (angular, _, config, $) {
+function (angular, _, config) {
   'use strict';
   'use strict';
 
 
   var module = angular.module('grafana.controllers');
   var module = angular.module('grafana.controllers');
@@ -15,7 +14,7 @@ function (angular, _, config, $) {
       $scope.giveSearchFocus = 0;
       $scope.giveSearchFocus = 0;
       $scope.selectedIndex = -1;
       $scope.selectedIndex = -1;
       $scope.results = {dashboards: [], tags: [], metrics: []};
       $scope.results = {dashboards: [], tags: [], metrics: []};
-      $scope.query = { query: '' };
+      $scope.query = { query: '', tag: '', starred: false };
       $scope.db = datasourceSrv.getGrafanaDB();
       $scope.db = datasourceSrv.getGrafanaDB();
       $scope.currentSearchId = 0;
       $scope.currentSearchId = 0;
 
 
@@ -29,7 +28,7 @@ function (angular, _, config, $) {
 
 
     $scope.keyDown = function (evt) {
     $scope.keyDown = function (evt) {
       if (evt.keyCode === 27) {
       if (evt.keyCode === 27) {
-        $scope.appEvent('hide-dash-editor');
+        $scope.dismiss();
       }
       }
       if (evt.keyCode === 40) {
       if (evt.keyCode === 40) {
         $scope.moveSelection(1);
         $scope.moveSelection(1);
@@ -49,10 +48,7 @@ function (angular, _, config, $) {
         var selectedDash = $scope.results.dashboards[$scope.selectedIndex];
         var selectedDash = $scope.results.dashboards[$scope.selectedIndex];
         if (selectedDash) {
         if (selectedDash) {
           $location.search({});
           $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.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.searchDashboards = function() {
       $scope.currentSearchId = $scope.currentSearchId + 1;
       $scope.currentSearchId = $scope.currentSearchId + 1;
       var localSearchId = $scope.currentSearchId;
       var localSearchId = $scope.currentSearchId;
@@ -74,16 +65,24 @@ function (angular, _, config, $) {
         .then(function(results) {
         .then(function(results) {
           if (localSearchId < $scope.currentSearchId) { return; }
           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.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.filterByTag = function(tag, evt) {
       $scope.query.tag = tag;
       $scope.query.tag = tag;
       $scope.query.tagcloud = false;
       $scope.query.tagcloud = false;

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

@@ -9,87 +9,118 @@ function (angular, _, $, config) {
 
 
   var module = angular.module('grafana.controllers');
   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) {
     $scope.getUrl = function(url) {
       return config.appSubUrl + 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.init = function() {
-      $scope.updateState();
+      $scope.updateMenu();
+      $scope.$on('$routeChangeSuccess', $scope.updateMenu);
     };
     };
   });
   });
 
 

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels