mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'develop' into apikey_hashed
Conflicts: pkg/api/apikey.go pkg/services/sqlstore/migrations.go
This commit is contained in:
commit
6a2a6afc1d
3
.gitignore
vendored
3
.gitignore
vendored
@ -13,9 +13,12 @@ src/css/*.min.css
|
||||
*.sublime-workspace
|
||||
*.swp
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
/data/*
|
||||
/bin/*
|
||||
/grafana-pro
|
||||
/grafana
|
||||
|
||||
grafana.custom.ini
|
||||
fig.yml
|
||||
|
12
CHANGELOG.md
12
CHANGELOG.md
@ -4,8 +4,11 @@
|
||||
- [Issue #1331](https://github.com/grafana/grafana/issues/1331). Graph & Singlestat: New axis/unit format selector and more units (kbytes, Joule, Watt, eV), and new design for graph axis & grid tab and single stat options tab views
|
||||
- [Issue #1241](https://github.com/grafana/grafana/issues/1242). Timepicker: New option in timepicker (under dashboard settings), to change ``now`` to be for example ``now-1m``, usefull when you want to ignore last minute because it contains incomplete data
|
||||
- [Issue #171](https://github.com/grafana/grafana/issues/171). Panel: Different time periods, panels can override dashboard relative time and/or add a time shift
|
||||
- [Issue #1488](https://github.com/grafana/grafana/issues/1488). Dashboard: Clone dashboard / Save as
|
||||
|
||||
**Enhancements**
|
||||
- [Issue #1366](https://github.com/grafana/grafana/issues/1366). Graph & Singlestat: Support for additional units, Fahrenheit (°F) and Celsius (°C), Humidity (%H), kW, watt-hour (Wh), kilowatt-hour (kWh), velocities (m/s, km/h, mpg, knot)
|
||||
- [Issue #978](https://github.com/grafana/grafana/issues/978). Graph: Shared tooltip improvement, can now support metrics of different resolution/intervals
|
||||
- [Issue #1297](https://github.com/grafana/grafana/issues/1297). Graphite: Added cumulative and minimumBelow graphite functions
|
||||
- [Issue #1296](https://github.com/grafana/grafana/issues/1296). InfluxDB: Auto escape column names with special characters. Thanks @steven-aerts
|
||||
- [Issue #1321](https://github.com/grafana/grafana/issues/1321). SingleStatPanel: You can now use template variables in pre & postfix
|
||||
@ -18,6 +21,15 @@
|
||||
- [Issue #1372](https://github.com/grafana/grafana/issues/1372). Graphite: Fix for nested complex queries, where a query references a query that references another query (ie the #[A-Z] syntax)
|
||||
- [Issue #1363](https://github.com/grafana/grafana/issues/1363). Templating: Fix to allow custom template variables to contain white space, now only splits on ','
|
||||
- [Issue #1359](https://github.com/grafana/grafana/issues/1359). Graph: Fix for all series tooltip showing series with all null values when ``Hide Empty`` option is enabled
|
||||
- [Issue #1497](https://github.com/grafana/grafana/issues/1497). Dashboard: Fixed memory leak when switching dashboards
|
||||
|
||||
**Changes**
|
||||
- Dashboard title change & save will no longer create a new dashboard, it will just change the title.
|
||||
|
||||
**OpenTSDB breaking change**
|
||||
- [Issue #1438](https://github.com/grafana/grafana/issues/1438). OpenTSDB: Automatic downsample interval passed to OpenTSDB (depends on timespan and graph width)
|
||||
- NOTICE, Downsampling is now enabled by default, so if you have not picked a downsample aggregator in your metric query do so or your graphs will be missleading
|
||||
- This will make Grafana a lot quicker for OpenTSDB users when viewing large time spans without having to change the downsample interval manually.
|
||||
|
||||
**Tech**
|
||||
- [Issue #1311](https://github.com/grafana/grafana/issues/1311). Tech: Updated Font-Awesome from 3.2 to 4.2
|
||||
|
21
README.md
21
README.md
@ -15,6 +15,12 @@ Graphite, InfluxDB & OpenTSDB.
|
||||
Grafana 2.0 comes with a backend written in Go. It is not ready for production use yet as there is still a lot of small
|
||||
issues to fix and polish missing. But feedback on what is done and bug reports would be greatly appreciated.
|
||||
|
||||
## Try it out with docker
|
||||
```
|
||||
docker run -i -p 3000:3000 grafana/grafana:develop
|
||||
```
|
||||
The default admin user is admin/admin.
|
||||
|
||||
## building and running
|
||||
|
||||
```
|
||||
@ -26,18 +32,13 @@ Building
|
||||
```
|
||||
cd $GOPATH/src/github.com/grafana/grafana
|
||||
git checkout -t origin/develop
|
||||
go run build.go setup (only needed once to install godep)
|
||||
go run build.go build
|
||||
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 .
|
||||
```
|
||||
|
||||
For quicker builds:
|
||||
|
||||
```
|
||||
godep restore (will pull down all golang lib dependecies in your current GOPATH)
|
||||
go build -o ./bin/grafana .
|
||||
```
|
||||
|
||||
To build less to css for frontend:
|
||||
To build less to css for the frontend you will need a recent version of of node (v0.12.0),
|
||||
npm (v2.5.0) and grunt (v0.4.5). Run the following:
|
||||
|
||||
```
|
||||
npm install
|
||||
|
4
benchmarks/ab/ab_test.sh
Executable file
4
benchmarks/ab/ab_test.sh
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
ab -n 20000 -c 100 -H "Authorization: Bearer vEustw23NSOZ27y3zlj28ZL3B7BpBk5kqR85DOfT5AwiS3nCi33dnsk6nhvXhZdn" \
|
||||
http://localhost:3000/api/dashboards/db/dash1
|
@ -2,12 +2,18 @@ app_name = Grafana
|
||||
app_mode = production
|
||||
|
||||
[server]
|
||||
; protocol (http or https)
|
||||
protocol = http
|
||||
domain = localhost
|
||||
root_url = %(protocol)s://%(domain)s:%(http_port)s/
|
||||
; the ip address to bind to, empty will bind to all interfaces
|
||||
http_addr =
|
||||
; the http port to use
|
||||
http_port = 3000
|
||||
; The public facing domain name used to access grafana from a browser
|
||||
domain = localhost
|
||||
; the full public facing url
|
||||
root_url = %(protocol)s://%(domain)s:%(http_port)s/
|
||||
router_logging = false
|
||||
; the path relative to the binary where the static (html/js/css) files are placed
|
||||
static_root_path = public
|
||||
enable_gzip = false
|
||||
|
||||
|
@ -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"
|
||||
}
|
@ -1,409 +0,0 @@
|
||||
{
|
||||
"title": "Grafana Play Home",
|
||||
"services": {
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "now-15m",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"title": "test",
|
||||
"height": "190px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"error": false,
|
||||
"span": 12,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"loadingEditor": false,
|
||||
"mode": "html",
|
||||
"content": "<h3 class=\"text-center\">Welcome to grafana demo, playground and interactive tutorial site.</h2>\n\n<div class=\"row-fluid\">\n\t<div class=\"span4\">\n\t\t<h4>Feature showcases</h2>\n\t\t<ul>\n\t\t\t<li>\n\t\t\t\t<a href=\"#/dashboard/file/graph-styles.json\">Graphs styles</a>\n\t\t\t</li>\n\t\t\t<li>\n\t\t\t\t<a href=\"#/dashboard/file/templated-graphs.json\">Templated graphs</a>\n\t\t\t</li>\n\t\t\t<li>\n\t\t\t\t<a href=\"#/dashboard/file/templated-graphs-nested.json\">Templated graphs nested</a>\n\t\t\t</li>\n\t\t\t<li>\n\t\t\t\t<a href=\"#/dashboard/file/annotations.json\">Annotations</a>\n\t\t\t</li>\n\t\t\t<li>\n\t\t\t\t<a href=\"#/dashboard/file/white-theme.json\">White theme</a>\n\t\t\t</li>\n\t\t</ul>\n\t</div>\n\t<div class=\"span4\">\n\t\t<h4>Graphite tutorials</h2>\n\t\t<ul>\n\t\t\t<li>\n\t\t\t\tGraphite introduction (TODO)\n\t\t\t</li>\n\t\t\t<li>\n\t\t\t\tBasic functions (TODO)\n\t\t\t</li>\n\t\t\t<li>\n\t\t\t\tAdvanced functions (TODO)\n\t\t\t</li>\n\t\t\t<li>\n\t\t\t\tTips and tricks (TODO)\n\t\t\t</li>\n\t\t</ul>\n\t</div>\n\t<div class=\"span4\">\n\t\t<h4>InfluxDB examples</h2>\n\t\t<ul>\n\t\t\t<li>\n\t\t\t\tTODO\n\t\t\t</li>\n\t\t</ul>\n\t</div>\n</div>\n\n<script type=\"text/javascript\">(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\n(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\nm=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\n})(window,document,'script','//www.google-analytics.com/analytics.js','ga');\n\nga('create', 'UA-47280256-1', 'grafana.org');\nga('send', 'pageview');</script>",
|
||||
"style": {},
|
||||
"title": "Grafana demo site"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "test",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "graphite",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"max": null,
|
||||
"min": 0,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 3,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": true,
|
||||
"spyable": true,
|
||||
"options": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"interactive": true,
|
||||
"legend_counts": true,
|
||||
"timezone": "browser",
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "aliasByNode(scaleToSeconds(apps.fakesite.*.counters.requests.count,1),2)"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"web_server_04": "#3F6833",
|
||||
"web_server_03": "#508642",
|
||||
"web_server_02": "#7EB26D",
|
||||
"web_server_01": "#B7DBAB"
|
||||
},
|
||||
"aliasYAxis": {},
|
||||
"title": "server requests",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"annotate": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "graphite",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"max": null,
|
||||
"min": 0,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 1,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"spyable": true,
|
||||
"options": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
"min": true,
|
||||
"max": true,
|
||||
"current": true,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"interactive": true,
|
||||
"legend_counts": true,
|
||||
"timezone": "browser",
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "alias(scaleToSeconds(apps.fakesite.web_server_01.counters.requests.count,1),'logins')"
|
||||
},
|
||||
{
|
||||
"target": "alias(timeShift(scaleToSeconds(apps.fakesite.web_server_01.counters.requests.count,1),'1h'),'logins (-1 hour)')"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"logins": "#7EB26D",
|
||||
"logins (-1 day)": "#447EBC"
|
||||
},
|
||||
"aliasYAxis": {},
|
||||
"title": "logins",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"annotate": {
|
||||
"enable": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "",
|
||||
"height": "300px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 4,
|
||||
"editable": true,
|
||||
"type": "graphite",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"bytes",
|
||||
"none"
|
||||
],
|
||||
"grid": {
|
||||
"max": null,
|
||||
"min": 0,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 0,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"spyable": true,
|
||||
"options": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
"min": true,
|
||||
"max": false,
|
||||
"current": true,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"interactive": true,
|
||||
"legend_counts": true,
|
||||
"timezone": "browser",
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "alias(scale(scaleToSeconds(apps.fakesite.web_server_01.counters.requests.count,1),1000000),'memory')"
|
||||
},
|
||||
{
|
||||
"target": "alias(scaleToSeconds(apps.fakesite.web_server_01.counters.request_status.code_302.count,1),'cpu')"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"cpu": "#E24D42"
|
||||
},
|
||||
"aliasYAxis": {
|
||||
"cpu": 2
|
||||
},
|
||||
"title": "Memory / CPU",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"annotate": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"span": 8,
|
||||
"editable": true,
|
||||
"type": "graphite",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"ms",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"max": null,
|
||||
"min": 0,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": false,
|
||||
"fill": 1,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": true,
|
||||
"stack": true,
|
||||
"spyable": true,
|
||||
"options": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": true
|
||||
},
|
||||
"interactive": true,
|
||||
"legend_counts": true,
|
||||
"timezone": "browser",
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "aliasByNode(statsd.fakesite.timers.ads_timer.*,4)"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"upper_75": "#EAB839",
|
||||
"upper_50": "#7EB26D",
|
||||
"upper_25": "#BA43A9"
|
||||
},
|
||||
"aliasYAxis": {},
|
||||
"title": "client side full page load",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"annotate": {
|
||||
"enable": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "test",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [],
|
||||
"notice": false
|
||||
}
|
||||
],
|
||||
"editable": true,
|
||||
"failover": false,
|
||||
"panel_hints": true,
|
||||
"style": "dark",
|
||||
"pulldowns": [
|
||||
{
|
||||
"type": "filtering",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": false
|
||||
},
|
||||
{
|
||||
"type": "annotations",
|
||||
"enable": false
|
||||
}
|
||||
],
|
||||
"nav": [
|
||||
{
|
||||
"type": "timepicker",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": true,
|
||||
"status": "Stable",
|
||||
"time_options": [
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
"6h",
|
||||
"12h",
|
||||
"24h",
|
||||
"2d",
|
||||
"7d",
|
||||
"30d"
|
||||
],
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"now": true
|
||||
}
|
||||
],
|
||||
"loader": {
|
||||
"save_gist": false,
|
||||
"save_elasticsearch": true,
|
||||
"save_local": true,
|
||||
"save_default": true,
|
||||
"save_temp": true,
|
||||
"save_temp_ttl_enable": true,
|
||||
"save_temp_ttl": "30d",
|
||||
"load_gist": false,
|
||||
"load_elasticsearch": true,
|
||||
"load_elasticsearch_size": 20,
|
||||
"load_local": false,
|
||||
"hide": false
|
||||
},
|
||||
"refresh": false,
|
||||
"tags": [
|
||||
"showcase",
|
||||
"startpage",
|
||||
"home",
|
||||
"default"
|
||||
],
|
||||
"timezone": "browser"
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -1,796 +0,0 @@
|
||||
{
|
||||
"title": "Graph styles",
|
||||
"services": {
|
||||
"filter": {
|
||||
"list": [],
|
||||
"time": {
|
||||
"from": "now-15m",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"title": "Simple graph",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "graphite",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"max": null,
|
||||
"min": 0,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 0,
|
||||
"linewidth": 1,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"spyable": true,
|
||||
"options": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"interactive": true,
|
||||
"legend_counts": true,
|
||||
"timezone": "browser",
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "aliasByNode(scaleToSeconds(apps.backend.*.counters.requests.count,1),2)"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"web_server_04": "#E24D42",
|
||||
"web_server_03": "#508642",
|
||||
"web_server_02": "#EAB839",
|
||||
"web_server_01": "#EF843C"
|
||||
},
|
||||
"aliasYAxis": {},
|
||||
"title": "Simple graph",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"annotate": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"error": false,
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"loadingEditor": false,
|
||||
"mode": "markdown",
|
||||
"content": "#### Simple graph\n- Click on the title and select edit to open edit mode\n- The display styles tab allows you change line width, fill, stacking, and more\n- You can change a series color by clicking the colored line in the legend ",
|
||||
"style": {},
|
||||
"title": "Description"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "Stacked Graph",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "graphite",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"max": null,
|
||||
"min": 0,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 2,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": true,
|
||||
"spyable": true,
|
||||
"options": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": true
|
||||
},
|
||||
"interactive": true,
|
||||
"legend_counts": true,
|
||||
"timezone": "browser",
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "aliasByNode(scaleToSeconds(apps.fakesite.*.counters.requests.count,1),2)"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"web_server_04": "#E24D42",
|
||||
"web_server_03": "#EF843C",
|
||||
"web_server_02": "#EAB839",
|
||||
"web_server_01": "#F2C96D"
|
||||
},
|
||||
"aliasYAxis": {},
|
||||
"title": "Stacked lines",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"annotate": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"error": false,
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"loadingEditor": false,
|
||||
"mode": "markdown",
|
||||
"content": "#### Stacked graph\n- This graph shows stacked series, with area fill and 2px line width\n- We have also added legend values. These can be enabled in the Grid & Axes tab in edit mode. \n- Legend values can be Min, Max, Total, Current and Average",
|
||||
"style": {},
|
||||
"title": "Description"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "Staircase line",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "graphite",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"ms",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"max": null,
|
||||
"min": 0,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 1,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": true,
|
||||
"spyable": true,
|
||||
"options": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": true
|
||||
},
|
||||
"interactive": true,
|
||||
"legend_counts": true,
|
||||
"timezone": "browser",
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": true,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "aliasByNode(statsd.fakesite.timers.ads_timer.upper_90,4)"
|
||||
},
|
||||
{
|
||||
"target": "aliasByNode(statsd.fakesite.timers.ads_timer.upper_90,4)"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"upper_75": "#EAB839",
|
||||
"upper_50": "#7EB26D",
|
||||
"upper_25": "#BA43A9"
|
||||
},
|
||||
"aliasYAxis": {},
|
||||
"title": "Staircase line",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"annotate": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"error": false,
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"loadingEditor": false,
|
||||
"mode": "markdown",
|
||||
"content": "#### Staircase & Y-Axis format\n- In display styles tab you can switch to staircase line \n- In Axes & Grid tab you can change to different Y units & formats.\n",
|
||||
"style": {},
|
||||
"title": "Description"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "Right Y-Axis",
|
||||
"height": "300px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "graphite",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"bytes",
|
||||
"none"
|
||||
],
|
||||
"grid": {
|
||||
"max": null,
|
||||
"min": 0,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 0,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"spyable": true,
|
||||
"options": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
"min": true,
|
||||
"max": false,
|
||||
"current": true,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"interactive": true,
|
||||
"legend_counts": true,
|
||||
"timezone": "browser",
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "alias(scale(scaleToSeconds(apps.fakesite.web_server_01.counters.requests.count,1),1000000),'memory')"
|
||||
},
|
||||
{
|
||||
"target": "alias(scaleToSeconds(apps.fakesite.web_server_02.counters.requests.count,1),'cpu')"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"cpu": "#E24D42"
|
||||
},
|
||||
"aliasYAxis": {
|
||||
"cpu": 2
|
||||
},
|
||||
"title": "Memory / CPU",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"annotate": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"error": false,
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"loadingEditor": false,
|
||||
"mode": "markdown",
|
||||
"content": "#### Second Y-Axis\n- Click on the series legend color line to open the color selector\n- In the series color selector popup you can also move the series to the Right-Y axis\n- Multiple Y-Axis are great for showing to related series that have different magnitudes ",
|
||||
"style": {},
|
||||
"title": "Description"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "test",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "graphite",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"none",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"max": null,
|
||||
"min": 0,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 1,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"spyable": true,
|
||||
"options": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": true
|
||||
},
|
||||
"interactive": true,
|
||||
"legend_counts": true,
|
||||
"timezone": "browser",
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "null",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "aliasByNode(apps.fakesite.web_server_01.counters.request_status.code_404.count,4)"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"upper_75": "#EAB839",
|
||||
"upper_50": "#7EB26D",
|
||||
"upper_25": "#BA43A9"
|
||||
},
|
||||
"aliasYAxis": {},
|
||||
"title": "Null point mode",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"annotate": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"error": false,
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"loadingEditor": false,
|
||||
"mode": "markdown",
|
||||
"content": "#### Null point mode\n- This option under Display styles tab controls how null values are handled\n- The graph to left shows how the default \"null\" looks. \n- __null__ null values are left null and this leaves empty spaces in the graph\n- __null as zero__ null values are drawn as zero values\n- __connected__ null values are ignored and the line jumps directly to the next value.",
|
||||
"style": {},
|
||||
"title": "Description"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "Thresholds",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "graphite",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"none",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"max": 700,
|
||||
"min": 0,
|
||||
"threshold1": 400,
|
||||
"threshold2": 600,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 0,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"spyable": true,
|
||||
"options": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": true,
|
||||
"avg": false
|
||||
},
|
||||
"interactive": true,
|
||||
"legend_counts": true,
|
||||
"timezone": "browser",
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "aliasByNode(apps.fakesite.web_server_01.counters.requests.count,4)"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"upper_75": "#EAB839",
|
||||
"upper_50": "#7EB26D",
|
||||
"upper_25": "#BA43A9",
|
||||
"requests": "#6ED0E0"
|
||||
},
|
||||
"aliasYAxis": {},
|
||||
"title": "Thresholds",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"annotate": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"error": false,
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"loadingEditor": false,
|
||||
"mode": "markdown",
|
||||
"content": "#### Thresholds\n- You can define thresholds in the Grid & Axes tab in edit mode \n- You can define one or two thresholds, color is also changeable. \n- You can have lower bound thresholds as well. ",
|
||||
"style": {},
|
||||
"title": "Description"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "Bars",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "graphite",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"ms",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"max": null,
|
||||
"min": 0,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": false,
|
||||
"fill": 1,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": true,
|
||||
"stack": true,
|
||||
"spyable": true,
|
||||
"options": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": true
|
||||
},
|
||||
"interactive": true,
|
||||
"legend_counts": true,
|
||||
"timezone": "browser",
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": true,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "aliasByNode(statsd.fakesite.timers.ads_timer.*,4)"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"upper_75": "#EAB839",
|
||||
"upper_50": "#7EB26D",
|
||||
"upper_25": "#BA43A9"
|
||||
},
|
||||
"aliasYAxis": {},
|
||||
"title": "Bars",
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"annotate": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"error": false,
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"loadingEditor": false,
|
||||
"mode": "markdown",
|
||||
"content": "#### Bars\n- In display styles tab you can switch from line to bars\n- The width of the bar is relative to the pixel width between two values\n",
|
||||
"style": {},
|
||||
"title": "Description"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "Graphite PNG",
|
||||
"height": "250px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "graphite",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"ms",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"max": null,
|
||||
"min": 0,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": false,
|
||||
"fill": 1,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 2,
|
||||
"bars": false,
|
||||
"stack": true,
|
||||
"spyable": true,
|
||||
"options": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": true,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": true
|
||||
},
|
||||
"interactive": true,
|
||||
"legend_counts": true,
|
||||
"timezone": "browser",
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "aliasByNode(apps.backend.*.counters.requests.count,2)"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"upper_75": "#EAB839",
|
||||
"upper_50": "#7EB26D",
|
||||
"upper_25": "#BA43A9"
|
||||
},
|
||||
"aliasYAxis": {},
|
||||
"title": "Graphite PNG",
|
||||
"datasource": null,
|
||||
"renderer": "png",
|
||||
"annotate": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"error": false,
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"loadingEditor": false,
|
||||
"mode": "markdown",
|
||||
"content": "#### Graphite PNG support\n- You can switch from client side rendering to graphite's server side PNG rendering\n- You cannot click and drag to zoom in in this render mode.\n",
|
||||
"style": {},
|
||||
"title": "Description"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
}
|
||||
],
|
||||
"editable": true,
|
||||
"failover": false,
|
||||
"panel_hints": true,
|
||||
"style": "dark",
|
||||
"pulldowns": [
|
||||
{
|
||||
"type": "filtering",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": false
|
||||
},
|
||||
{
|
||||
"type": "annotations",
|
||||
"enable": false
|
||||
}
|
||||
],
|
||||
"nav": [
|
||||
{
|
||||
"type": "timepicker",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": true,
|
||||
"status": "Stable",
|
||||
"time_options": [
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
"6h",
|
||||
"12h",
|
||||
"24h",
|
||||
"2d",
|
||||
"7d",
|
||||
"30d"
|
||||
],
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"now": true
|
||||
}
|
||||
],
|
||||
"loader": {
|
||||
"save_gist": false,
|
||||
"save_elasticsearch": true,
|
||||
"save_local": true,
|
||||
"save_default": true,
|
||||
"save_temp": true,
|
||||
"save_temp_ttl_enable": true,
|
||||
"save_temp_ttl": "30d",
|
||||
"load_gist": false,
|
||||
"load_elasticsearch": true,
|
||||
"load_elasticsearch_size": 20,
|
||||
"load_local": false,
|
||||
"hide": false
|
||||
},
|
||||
"refresh": false,
|
||||
"tags": [
|
||||
"showcase",
|
||||
"annotations"
|
||||
],
|
||||
"timezone": "browser"
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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"
|
||||
}
|
@ -1,272 +0,0 @@
|
||||
{
|
||||
"title": "Templated Graphs",
|
||||
"services": {
|
||||
"filter": {
|
||||
"list": [
|
||||
{
|
||||
"type": "filter",
|
||||
"name": "root",
|
||||
"query": "statsd.fakesite.counters.session_start.*",
|
||||
"includeAll": true,
|
||||
"options": [
|
||||
{
|
||||
"text": "All",
|
||||
"value": "{desktop,mobile,tablet}"
|
||||
},
|
||||
{
|
||||
"text": "desktop",
|
||||
"value": "desktop"
|
||||
},
|
||||
{
|
||||
"text": "mobile",
|
||||
"value": "mobile"
|
||||
},
|
||||
{
|
||||
"text": "tablet",
|
||||
"value": "tablet"
|
||||
}
|
||||
],
|
||||
"current": {
|
||||
"text": "All",
|
||||
"value": "{desktop,mobile,tablet}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"title": "Row1",
|
||||
"height": "350px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 12,
|
||||
"editable": true,
|
||||
"type": "graphite",
|
||||
"loadingEditor": false,
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"max": null,
|
||||
"min": 0,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": true,
|
||||
"fill": 1,
|
||||
"linewidth": 1,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": false,
|
||||
"stack": false,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "aliasByNode(summarize(statsd.fakesite.counters.session_start.[[root]].count,'1min','sum'),4)"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"highres.test": "#1F78C1",
|
||||
"scale(highres.test,3)": "#6ED0E0",
|
||||
"mobile": "#6ED0E0",
|
||||
"tablet": "#EAB839"
|
||||
},
|
||||
"aliasYAxis": {},
|
||||
"title": "Device sessions"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
},
|
||||
{
|
||||
"title": "Row1",
|
||||
"height": "350px",
|
||||
"editable": true,
|
||||
"collapse": false,
|
||||
"collapsable": true,
|
||||
"panels": [
|
||||
{
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "graphite",
|
||||
"loadingEditor": false,
|
||||
"datasource": null,
|
||||
"renderer": "flot",
|
||||
"x-axis": true,
|
||||
"y-axis": true,
|
||||
"scale": 1,
|
||||
"y_formats": [
|
||||
"short",
|
||||
"short"
|
||||
],
|
||||
"grid": {
|
||||
"max": null,
|
||||
"min": 0,
|
||||
"threshold1": null,
|
||||
"threshold2": null,
|
||||
"threshold1Color": "rgba(216, 200, 27, 0.27)",
|
||||
"threshold2Color": "rgba(234, 112, 112, 0.22)"
|
||||
},
|
||||
"annotate": {
|
||||
"enable": false
|
||||
},
|
||||
"resolution": 100,
|
||||
"lines": false,
|
||||
"fill": 1,
|
||||
"linewidth": 2,
|
||||
"points": false,
|
||||
"pointradius": 5,
|
||||
"bars": true,
|
||||
"stack": true,
|
||||
"legend": {
|
||||
"show": true,
|
||||
"values": false,
|
||||
"min": false,
|
||||
"max": false,
|
||||
"current": false,
|
||||
"total": false,
|
||||
"avg": false
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
"value_type": "cumulative",
|
||||
"query_as_alias": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"target": "aliasByNode(summarize(statsd.fakesite.counters.session_start.[[root]].count,'1h','sum'),4)"
|
||||
}
|
||||
],
|
||||
"aliasColors": {
|
||||
"highres.test": "#1F78C1",
|
||||
"scale(highres.test,3)": "#6ED0E0",
|
||||
"tablet": "#EAB839",
|
||||
"desktop": "#7EB26D",
|
||||
"mobile": "#6ED0E0"
|
||||
},
|
||||
"aliasYAxis": {},
|
||||
"title": "Device sessions (1h)"
|
||||
},
|
||||
{
|
||||
"error": false,
|
||||
"span": 6,
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"loadingEditor": false,
|
||||
"mode": "markdown",
|
||||
"content": "#### Templated metric queries / graphs \n- In dashboard settings, in the Controls tab / Feature toggles. You can enable 'Filtering' \n- This feature when enabled will show you a bar bellow the menu.\n- In this bar you can add filters, or what should be named templated metric segments. \n- A filter is a query for a specific metric segment\n- Open any graph in this dashboard and edit mode and you can see that the [[device]] filter is used instead of a wildcard.\n- Try clicking the All link in the filter menu at the top, change device and see that all graphs change to only show values for that device. ",
|
||||
"style": {},
|
||||
"title": "Description"
|
||||
}
|
||||
],
|
||||
"notice": false
|
||||
}
|
||||
],
|
||||
"editable": true,
|
||||
"failover": false,
|
||||
"panel_hints": true,
|
||||
"style": "dark",
|
||||
"pulldowns": [
|
||||
{
|
||||
"type": "filtering",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": true
|
||||
},
|
||||
{
|
||||
"type": "annotations",
|
||||
"enable": false
|
||||
}
|
||||
],
|
||||
"nav": [
|
||||
{
|
||||
"type": "timepicker",
|
||||
"collapse": false,
|
||||
"notice": false,
|
||||
"enable": true,
|
||||
"status": "Stable",
|
||||
"time_options": [
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
"6h",
|
||||
"12h",
|
||||
"24h",
|
||||
"2d",
|
||||
"7d",
|
||||
"30d"
|
||||
],
|
||||
"refresh_intervals": [
|
||||
"5s",
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"now": true
|
||||
}
|
||||
],
|
||||
"loader": {
|
||||
"save_gist": false,
|
||||
"save_elasticsearch": true,
|
||||
"save_local": true,
|
||||
"save_default": true,
|
||||
"save_temp": true,
|
||||
"save_temp_ttl_enable": true,
|
||||
"save_temp_ttl": "30d",
|
||||
"load_gist": false,
|
||||
"load_elasticsearch": true,
|
||||
"load_elasticsearch_size": 20,
|
||||
"load_local": false,
|
||||
"hide": false
|
||||
},
|
||||
"refresh": false,
|
||||
"tags": [
|
||||
"showcase",
|
||||
"templated"
|
||||
],
|
||||
"timezone": "browser"
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -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"
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 63 KiB |
Binary file not shown.
Before Width: | Height: | Size: 72 KiB |
16
docker/blocks/influxdb/Dockerfile
Normal file
16
docker/blocks/influxdb/Dockerfile
Normal file
@ -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
docker/blocks/influxdb/config.toml
Normal file
75
docker/blocks/influxdb/config.toml
Normal file
@ -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
docker/blocks/influxdb/fig
Normal file
6
docker/blocks/influxdb/fig
Normal file
@ -0,0 +1,6 @@
|
||||
influxdb:
|
||||
build: blocks/influxdb
|
||||
ports:
|
||||
- "2004:2004"
|
||||
- "8083:8083"
|
||||
- "8086:8086"
|
@ -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"
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM phusion/baseimage
|
||||
FROM debian:jessie
|
||||
|
||||
RUN apt-get -y update
|
||||
RUN apt-get -y install libfontconfig
|
||||
@ -10,6 +10,7 @@ ADD tmp/ /opt/grafana/
|
||||
EXPOSE 3000
|
||||
|
||||
VOLUME ["/opt/grafana/data"]
|
||||
VOLUME ["/opt/grafana/conf"]
|
||||
|
||||
WORKDIR /opt/grafana/
|
||||
ENTRYPOINT ["./grafana", "web"]
|
31
docker/production/README.md
Normal file
31
docker/production/README.md
Normal file
@ -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
docker/production/build.sh
Executable file
15
docker/production/build.sh
Executable file
@ -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
docker/production/test_container.sh
Executable file
5
docker/production/test_container.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker run -i -p 3001:3000 \
|
||||
-e "GF_SERVER_ROOT_URL=http://grafana.server.name" \
|
||||
grafana/grafana:develop
|
18
main.go
18
main.go
@ -31,8 +31,22 @@ func main() {
|
||||
app.Name = "Grafana Backend"
|
||||
app.Usage = "grafana web"
|
||||
app.Version = version
|
||||
app.Commands = []cli.Command{cmd.CmdWeb, cmd.CmdImportJson}
|
||||
app.Flags = append(app.Flags, []cli.Flag{}...)
|
||||
app.Commands = []cli.Command{
|
||||
cmd.ListOrgs,
|
||||
cmd.CreateOrg,
|
||||
cmd.DeleteOrg,
|
||||
cmd.ImportDashboard,
|
||||
cmd.ListDataSources,
|
||||
cmd.CreateDataSource,
|
||||
cmd.DescribeDataSource,
|
||||
cmd.DeleteDataSource,
|
||||
cmd.Web}
|
||||
app.Flags = append(app.Flags, []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "config",
|
||||
Usage: "path to grafana.ini config file",
|
||||
},
|
||||
}...)
|
||||
app.Run(os.Args)
|
||||
|
||||
log.Close()
|
||||
|
@ -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
pkg/api/admin_settings.go
Normal file
29
pkg/api/admin_settings.go
Normal file
@ -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)
|
||||
}
|
@ -1,20 +1,153 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func AdminSearchUsers(c *middleware.Context) {
|
||||
// query := c.QueryStrings("q")
|
||||
// page := c.QueryStrings("p")
|
||||
|
||||
query := m.SearchUsersQuery{Query: "", Page: 0, Limit: 20}
|
||||
query := m.SearchUsersQuery{Query: "", Page: 0, Limit: 1000}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
c.JsonApiErr(500, "Failed to fetch collaboratos", err)
|
||||
c.JsonApiErr(500, "Failed to fetch users", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, query.Result)
|
||||
}
|
||||
|
||||
func AdminGetUser(c *middleware.Context) {
|
||||
userId := c.ParamsInt64(":id")
|
||||
|
||||
query := m.GetUserByIdQuery{Id: userId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
c.JsonApiErr(500, "Failed to fetch user", err)
|
||||
return
|
||||
}
|
||||
|
||||
result := m.UserDTO{
|
||||
Name: query.Result.Name,
|
||||
Email: query.Result.Email,
|
||||
Login: query.Result.Login,
|
||||
IsGrafanaAdmin: query.Result.IsAdmin,
|
||||
}
|
||||
|
||||
c.JSON(200, result)
|
||||
}
|
||||
|
||||
func AdminCreateUser(c *middleware.Context, form dtos.AdminCreateUserForm) {
|
||||
cmd := m.CreateUserCommand{
|
||||
Login: form.Login,
|
||||
Email: form.Email,
|
||||
Password: form.Password,
|
||||
Name: form.Name,
|
||||
}
|
||||
|
||||
if len(cmd.Login) == 0 {
|
||||
cmd.Login = cmd.Email
|
||||
if len(cmd.Login) == 0 {
|
||||
c.JsonApiErr(400, "Validation error, need specify either username or email", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(cmd.Password) < 4 {
|
||||
c.JsonApiErr(400, "Password is missing or too short", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "failed to create user", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JsonOK("User created")
|
||||
}
|
||||
|
||||
func AdminUpdateUser(c *middleware.Context, form dtos.AdminUpdateUserForm) {
|
||||
userId := c.ParamsInt64(":id")
|
||||
|
||||
cmd := m.UpdateUserCommand{
|
||||
UserId: userId,
|
||||
Login: form.Login,
|
||||
Email: form.Email,
|
||||
Name: form.Name,
|
||||
}
|
||||
|
||||
if len(cmd.Login) == 0 {
|
||||
cmd.Login = cmd.Email
|
||||
if len(cmd.Login) == 0 {
|
||||
c.JsonApiErr(400, "Validation error, need specify either username or email", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "failed to update user", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JsonOK("User updated")
|
||||
}
|
||||
|
||||
func AdminUpdateUserPassword(c *middleware.Context, form dtos.AdminUpdateUserPasswordForm) {
|
||||
userId := c.ParamsInt64(":id")
|
||||
|
||||
if len(form.Password) < 4 {
|
||||
c.JsonApiErr(400, "New password too short", nil)
|
||||
return
|
||||
}
|
||||
|
||||
userQuery := m.GetUserByIdQuery{Id: userId}
|
||||
|
||||
if err := bus.Dispatch(&userQuery); err != nil {
|
||||
c.JsonApiErr(500, "Could not read user from database", err)
|
||||
return
|
||||
}
|
||||
|
||||
passwordHashed := util.EncodePassword(form.Password, userQuery.Result.Salt)
|
||||
|
||||
cmd := m.ChangeUserPasswordCommand{
|
||||
UserId: userId,
|
||||
NewPassword: passwordHashed,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "Failed to update user password", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JsonOK("User password updated")
|
||||
}
|
||||
|
||||
func AdminUpdateUserPermissions(c *middleware.Context, form dtos.AdminUpdateUserPermissionsForm) {
|
||||
userId := c.ParamsInt64(":id")
|
||||
|
||||
cmd := m.UpdateUserPermissionsCommand{
|
||||
UserId: userId,
|
||||
IsGrafanaAdmin: form.IsGrafanaAdmin,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "Failed to update user permissions", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JsonOK("User permissions updated")
|
||||
}
|
||||
|
||||
func AdminDeleteUser(c *middleware.Context) {
|
||||
userId := c.ParamsInt64(":id")
|
||||
|
||||
cmd := m.DeleteUserCommand{UserId: userId}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "Failed to delete user", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JsonOK("User deleted")
|
||||
}
|
||||
|
@ -25,12 +25,15 @@ func Register(r *macaron.Macaron) {
|
||||
|
||||
// authed views
|
||||
r.Get("/profile/", reqSignedIn, Index)
|
||||
r.Get("/account/", reqSignedIn, Index)
|
||||
r.Get("/account/datasources/", reqSignedIn, Index)
|
||||
r.Get("/account/users/", reqSignedIn, Index)
|
||||
r.Get("/account/apikeys/", reqSignedIn, Index)
|
||||
r.Get("/account/import/", reqSignedIn, Index)
|
||||
r.Get("/org/", reqSignedIn, Index)
|
||||
r.Get("/datasources/", reqSignedIn, Index)
|
||||
r.Get("/org/users/", reqSignedIn, Index)
|
||||
r.Get("/org/apikeys/", reqSignedIn, Index)
|
||||
r.Get("/dashboard/import/", reqSignedIn, Index)
|
||||
r.Get("/admin/settings", reqGrafanaAdmin, Index)
|
||||
r.Get("/admin/users", reqGrafanaAdmin, Index)
|
||||
r.Get("/admin/users/create", reqGrafanaAdmin, Index)
|
||||
r.Get("/admin/users/edit/:id", reqGrafanaAdmin, Index)
|
||||
r.Get("/dashboard/*", reqSignedIn, Index)
|
||||
|
||||
// sign up
|
||||
@ -43,20 +46,21 @@ func Register(r *macaron.Macaron) {
|
||||
r.Group("/user", func() {
|
||||
r.Get("/", GetUser)
|
||||
r.Put("/", bind(m.UpdateUserCommand{}), UpdateUser)
|
||||
r.Post("/using/:id", SetUsingAccount)
|
||||
r.Get("/accounts", GetUserAccounts)
|
||||
r.Post("/using/:id", UserSetUsingOrg)
|
||||
r.Get("/orgs", GetUserOrgList)
|
||||
r.Post("/stars/dashboard/:id", StarDashboard)
|
||||
r.Delete("/stars/dashboard/:id", UnstarDashboard)
|
||||
r.Put("/password", bind(m.ChangeUserPasswordCommand{}), ChangeUserPassword)
|
||||
})
|
||||
|
||||
// account
|
||||
r.Group("/account", func() {
|
||||
r.Get("/", GetAccount)
|
||||
r.Post("/", bind(m.CreateAccountCommand{}), CreateAccount)
|
||||
r.Put("/", bind(m.UpdateAccountCommand{}), UpdateAccount)
|
||||
r.Post("/users", bind(m.AddAccountUserCommand{}), AddAccountUser)
|
||||
r.Get("/users", GetAccountUsers)
|
||||
r.Delete("/users/:id", RemoveAccountUser)
|
||||
r.Group("/org", func() {
|
||||
r.Get("/", GetOrg)
|
||||
r.Post("/", bind(m.CreateOrgCommand{}), CreateOrg)
|
||||
r.Put("/", bind(m.UpdateOrgCommand{}), UpdateOrg)
|
||||
r.Post("/users", bind(m.AddOrgUserCommand{}), AddOrgUser)
|
||||
r.Get("/users", GetOrgUsers)
|
||||
r.Delete("/users/:id", RemoveOrgUser)
|
||||
}, reqAccountAdmin)
|
||||
|
||||
// auth api keys
|
||||
@ -70,9 +74,12 @@ func Register(r *macaron.Macaron) {
|
||||
r.Group("/datasources", func() {
|
||||
r.Combo("/").Get(GetDataSources).Put(AddDataSource).Post(UpdateDataSource)
|
||||
r.Delete("/:id", DeleteDataSource)
|
||||
r.Any("/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
|
||||
r.Get("/:id", GetDataSourceById)
|
||||
}, reqAccountAdmin)
|
||||
|
||||
r.Get("/frontend/settings/", GetFrontendSettings)
|
||||
r.Any("/datasources/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
|
||||
|
||||
// Dashboard
|
||||
r.Group("/dashboards", func() {
|
||||
r.Combo("/db/:slug").Get(GetDashboard).Delete(DeleteDashboard)
|
||||
@ -89,7 +96,14 @@ func Register(r *macaron.Macaron) {
|
||||
|
||||
// admin api
|
||||
r.Group("/api/admin", func() {
|
||||
r.Get("/settings", AdminGetSettings)
|
||||
r.Get("/users", AdminSearchUsers)
|
||||
r.Get("/users/:id", AdminGetUser)
|
||||
r.Post("/users", bind(dtos.AdminCreateUserForm{}), AdminCreateUser)
|
||||
r.Put("/users/:id/details", bind(dtos.AdminUpdateUserForm{}), AdminUpdateUser)
|
||||
r.Put("/users/:id/password", bind(dtos.AdminUpdateUserPasswordForm{}), AdminUpdateUserPassword)
|
||||
r.Put("/users/:id/permissions", bind(dtos.AdminUpdateUserPermissionsForm{}), AdminUpdateUserPermissions)
|
||||
r.Delete("/users/:id", AdminDeleteUser)
|
||||
}, reqGrafanaAdmin)
|
||||
|
||||
// rendering
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func GetApiKeys(c *middleware.Context) {
|
||||
query := m.GetApiKeysQuery{AccountId: c.AccountId}
|
||||
query := m.GetApiKeysQuery{OrgId: c.OrgId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
c.JsonApiErr(500, "Failed to list api keys", err)
|
||||
@ -29,7 +29,7 @@ func GetApiKeys(c *middleware.Context) {
|
||||
func DeleteApiKey(c *middleware.Context) {
|
||||
id := c.ParamsInt64(":id")
|
||||
|
||||
cmd := &m.DeleteApiKeyCommand{Id: id, AccountId: c.AccountId}
|
||||
cmd := &m.DeleteApiKeyCommand{Id: id, OrgId: c.OrgId}
|
||||
|
||||
err := bus.Dispatch(cmd)
|
||||
if err != nil {
|
||||
@ -46,7 +46,7 @@ func AddApiKey(c *middleware.Context, cmd m.AddApiKeyCommand) {
|
||||
return
|
||||
}
|
||||
|
||||
cmd.AccountId = c.AccountId
|
||||
cmd.OrgId = c.OrgId
|
||||
cmd.Key = util.GetRandomString(64)
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
@ -62,3 +62,20 @@ func AddApiKey(c *middleware.Context, cmd m.AddApiKeyCommand) {
|
||||
|
||||
c.JSON(200, result)
|
||||
}
|
||||
|
||||
func UpdateApiKey(c *middleware.Context, cmd m.UpdateApiKeyCommand) {
|
||||
if !cmd.Role.IsValid() {
|
||||
c.JsonApiErr(400, "Invalid role specified", nil)
|
||||
return
|
||||
}
|
||||
|
||||
cmd.OrgId = c.OrgId
|
||||
|
||||
err := bus.Dispatch(&cmd)
|
||||
if err != nil {
|
||||
c.JsonApiErr(500, "Failed to update api key", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JsonOK("API key updated")
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ func isDasboardStarredByUser(c *middleware.Context, dashId int64) (bool, error)
|
||||
func GetDashboard(c *middleware.Context) {
|
||||
slug := c.Params(":slug")
|
||||
|
||||
query := m.GetDashboardQuery{Slug: slug, AccountId: c.AccountId}
|
||||
query := m.GetDashboardQuery{Slug: slug, OrgId: c.OrgId}
|
||||
err := bus.Dispatch(&query)
|
||||
if err != nil {
|
||||
c.JsonApiErr(404, "Dashboard not found", nil)
|
||||
@ -54,13 +54,13 @@ func GetDashboard(c *middleware.Context) {
|
||||
func DeleteDashboard(c *middleware.Context) {
|
||||
slug := c.Params(":slug")
|
||||
|
||||
query := m.GetDashboardQuery{Slug: slug, AccountId: c.AccountId}
|
||||
query := m.GetDashboardQuery{Slug: slug, OrgId: c.OrgId}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
c.JsonApiErr(404, "Dashboard not found", nil)
|
||||
return
|
||||
}
|
||||
|
||||
cmd := m.DeleteDashboardCommand{Slug: slug, AccountId: c.AccountId}
|
||||
cmd := m.DeleteDashboardCommand{Slug: slug, OrgId: c.OrgId}
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "Failed to delete dashboard", err)
|
||||
return
|
||||
@ -72,7 +72,7 @@ func DeleteDashboard(c *middleware.Context) {
|
||||
}
|
||||
|
||||
func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) {
|
||||
cmd.AccountId = c.AccountId
|
||||
cmd.OrgId = c.OrgId
|
||||
|
||||
err := bus.Dispatch(&cmd)
|
||||
if err != nil {
|
||||
|
@ -17,6 +17,7 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string) *httputil.ReverseProxy
|
||||
director := func(req *http.Request) {
|
||||
req.URL.Scheme = target.Scheme
|
||||
req.URL.Host = target.Host
|
||||
req.Host = target.Host
|
||||
|
||||
reqQueryVals := req.URL.Query()
|
||||
|
||||
@ -25,6 +26,12 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string) *httputil.ReverseProxy
|
||||
reqQueryVals.Add("u", ds.User)
|
||||
reqQueryVals.Add("p", ds.Password)
|
||||
req.URL.RawQuery = reqQueryVals.Encode()
|
||||
} else if ds.Type == m.DS_INFLUXDB_08 {
|
||||
req.URL.Path = util.JoinUrlFragments(target.Path, proxyPath)
|
||||
reqQueryVals.Add("db", ds.Database)
|
||||
reqQueryVals.Add("u", ds.User)
|
||||
reqQueryVals.Add("p", ds.Password)
|
||||
req.URL.RawQuery = reqQueryVals.Encode()
|
||||
} else {
|
||||
req.URL.Path = util.JoinUrlFragments(target.Path, proxyPath)
|
||||
}
|
||||
@ -38,8 +45,8 @@ func ProxyDataSourceRequest(c *middleware.Context) {
|
||||
id := c.ParamsInt64(":id")
|
||||
|
||||
query := m.GetDataSourceByIdQuery{
|
||||
Id: id,
|
||||
AccountId: c.AccountId,
|
||||
Id: id,
|
||||
OrgId: c.OrgId,
|
||||
}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func TestAccountDataAccess(t *testing.T) {
|
||||
func TestDataSourceProxy(t *testing.T) {
|
||||
|
||||
Convey("When getting graphite datasource proxy", t, func() {
|
||||
ds := m.DataSource{Url: "htttp://graphite:8080", Type: m.DS_GRAPHITE}
|
||||
|
@ -8,10 +8,9 @@ import (
|
||||
)
|
||||
|
||||
func GetDataSources(c *middleware.Context) {
|
||||
query := m.GetDataSourcesQuery{AccountId: c.AccountId}
|
||||
err := bus.Dispatch(&query)
|
||||
query := m.GetDataSourcesQuery{OrgId: c.OrgId}
|
||||
|
||||
if err != nil {
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
c.JsonApiErr(500, "Failed to query datasources", err)
|
||||
return
|
||||
}
|
||||
@ -20,7 +19,7 @@ func GetDataSources(c *middleware.Context) {
|
||||
for i, ds := range query.Result {
|
||||
result[i] = &dtos.DataSource{
|
||||
Id: ds.Id,
|
||||
AccountId: ds.AccountId,
|
||||
OrgId: ds.OrgId,
|
||||
Name: ds.Name,
|
||||
Url: ds.Url,
|
||||
Type: ds.Type,
|
||||
@ -36,6 +35,34 @@ func GetDataSources(c *middleware.Context) {
|
||||
c.JSON(200, result)
|
||||
}
|
||||
|
||||
func GetDataSourceById(c *middleware.Context) {
|
||||
query := m.GetDataSourceByIdQuery{
|
||||
Id: c.ParamsInt64(":id"),
|
||||
OrgId: c.OrgId,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
c.JsonApiErr(500, "Failed to query datasources", err)
|
||||
return
|
||||
}
|
||||
|
||||
ds := query.Result
|
||||
|
||||
c.JSON(200, &dtos.DataSource{
|
||||
Id: ds.Id,
|
||||
OrgId: ds.OrgId,
|
||||
Name: ds.Name,
|
||||
Url: ds.Url,
|
||||
Type: ds.Type,
|
||||
Access: ds.Access,
|
||||
Password: ds.Password,
|
||||
Database: ds.Database,
|
||||
User: ds.User,
|
||||
BasicAuth: ds.BasicAuth,
|
||||
IsDefault: ds.IsDefault,
|
||||
})
|
||||
}
|
||||
|
||||
func DeleteDataSource(c *middleware.Context) {
|
||||
id := c.ParamsInt64(":id")
|
||||
|
||||
@ -44,7 +71,7 @@ func DeleteDataSource(c *middleware.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
cmd := &m.DeleteDataSourceCommand{Id: id, AccountId: c.AccountId}
|
||||
cmd := &m.DeleteDataSourceCommand{Id: id, OrgId: c.OrgId}
|
||||
|
||||
err := bus.Dispatch(cmd)
|
||||
if err != nil {
|
||||
@ -63,7 +90,7 @@ func AddDataSource(c *middleware.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
cmd.AccountId = c.AccountId
|
||||
cmd.OrgId = c.OrgId
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "Failed to add datasource", err)
|
||||
@ -81,7 +108,7 @@ func UpdateDataSource(c *middleware.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
cmd.AccountId = c.AccountId
|
||||
cmd.OrgId = c.OrgId
|
||||
|
||||
err := bus.Dispatch(&cmd)
|
||||
if err != nil {
|
||||
|
@ -19,8 +19,8 @@ type CurrentUser struct {
|
||||
Login string `json:"login"`
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
AccountRole m.RoleType `json:"accountRole"`
|
||||
AccountName string `json:"acountName"`
|
||||
OrgRole m.RoleType `json:"orgRole"`
|
||||
OrgName string `json:"orgName"`
|
||||
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
|
||||
GravatarUrl string `json:"gravatarUrl"`
|
||||
}
|
||||
@ -38,7 +38,7 @@ type Dashboard struct {
|
||||
|
||||
type DataSource struct {
|
||||
Id int64 `json:"id"`
|
||||
AccountId int64 `json:"accountId"`
|
||||
OrgId int64 `json:"orgId"`
|
||||
Name string `json:"name"`
|
||||
Type m.DsType `json:"type"`
|
||||
Access m.DsAccess `json:"access"`
|
||||
|
22
pkg/api/dtos/user.go
Normal file
22
pkg/api/dtos/user.go
Normal file
@ -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"`
|
||||
}
|
@ -9,23 +9,23 @@ import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func getFrontendSettings(c *middleware.Context) (map[string]interface{}, error) {
|
||||
accountDataSources := make([]*m.DataSource, 0)
|
||||
func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, error) {
|
||||
orgDataSources := make([]*m.DataSource, 0)
|
||||
|
||||
if c.IsSignedIn {
|
||||
query := m.GetDataSourcesQuery{AccountId: c.AccountId}
|
||||
query := m.GetDataSourcesQuery{OrgId: c.OrgId}
|
||||
err := bus.Dispatch(&query)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accountDataSources = query.Result
|
||||
orgDataSources = query.Result
|
||||
}
|
||||
|
||||
datasources := make(map[string]interface{})
|
||||
|
||||
for _, ds := range accountDataSources {
|
||||
for _, ds := range orgDataSources {
|
||||
url := ds.Url
|
||||
|
||||
if ds.Access == m.DS_ACCESS_PROXY {
|
||||
@ -38,7 +38,7 @@ func getFrontendSettings(c *middleware.Context) (map[string]interface{}, error)
|
||||
"default": ds.IsDefault,
|
||||
}
|
||||
|
||||
if ds.Type == m.DS_INFLUXDB {
|
||||
if ds.Type == m.DS_INFLUXDB_08 {
|
||||
if ds.Access == m.DS_ACCESS_DIRECT {
|
||||
dsMap["username"] = ds.User
|
||||
dsMap["password"] = ds.Password
|
||||
@ -46,6 +46,15 @@ func getFrontendSettings(c *middleware.Context) (map[string]interface{}, error)
|
||||
}
|
||||
}
|
||||
|
||||
if ds.Type == m.DS_INFLUXDB {
|
||||
if ds.Access == m.DS_ACCESS_DIRECT {
|
||||
dsMap["username"] = ds.User
|
||||
dsMap["password"] = ds.Password
|
||||
dsMap["database"] = ds.Database
|
||||
dsMap["url"] = url
|
||||
}
|
||||
}
|
||||
|
||||
if ds.Type == m.DS_ES {
|
||||
dsMap["index"] = ds.Database
|
||||
}
|
||||
@ -71,3 +80,13 @@ func getFrontendSettings(c *middleware.Context) (map[string]interface{}, error)
|
||||
|
||||
return jsonObj, nil
|
||||
}
|
||||
|
||||
func GetFrontendSettings(c *middleware.Context) {
|
||||
settings, err := getFrontendSettingsMap(c)
|
||||
if err != nil {
|
||||
c.JsonApiErr(400, "Failed to get frontend settings", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, settings)
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func setIndexViewData(c *middleware.Context) error {
|
||||
settings, err := getFrontendSettings(c)
|
||||
settings, err := getFrontendSettingsMap(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -17,8 +17,8 @@ func setIndexViewData(c *middleware.Context) error {
|
||||
Login: c.Login,
|
||||
Email: c.Email,
|
||||
Name: c.Name,
|
||||
AccountName: c.AccountName,
|
||||
AccountRole: c.AccountRole,
|
||||
OrgName: c.OrgName,
|
||||
OrgRole: c.OrgRole,
|
||||
GravatarUrl: dtos.GetGravatarUrl(c.Email),
|
||||
IsGrafanaAdmin: c.IsGrafanaAdmin,
|
||||
}
|
||||
|
50
pkg/api/org.go
Normal file
50
pkg/api/org.go
Normal file
@ -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")
|
||||
}
|
@ -6,7 +6,7 @@ import (
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func AddAccountUser(c *middleware.Context, cmd m.AddAccountUserCommand) {
|
||||
func AddOrgUser(c *middleware.Context, cmd m.AddOrgUserCommand) {
|
||||
if !cmd.Role.IsValid() {
|
||||
c.JsonApiErr(400, "Invalid role specified", nil)
|
||||
return
|
||||
@ -26,19 +26,19 @@ func AddAccountUser(c *middleware.Context, cmd m.AddAccountUserCommand) {
|
||||
return
|
||||
}
|
||||
|
||||
cmd.AccountId = c.AccountId
|
||||
cmd.OrgId = c.OrgId
|
||||
cmd.UserId = userToAdd.Id
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "Could not add user to account", err)
|
||||
c.JsonApiErr(500, "Could not add user to organization", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JsonOK("User added to account")
|
||||
c.JsonOK("User added to organization")
|
||||
}
|
||||
|
||||
func GetAccountUsers(c *middleware.Context) {
|
||||
query := m.GetAccountUsersQuery{AccountId: c.AccountId}
|
||||
func GetOrgUsers(c *middleware.Context) {
|
||||
query := m.GetOrgUsersQuery{OrgId: c.OrgId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
c.JsonApiErr(500, "Failed to get account user", err)
|
||||
@ -48,18 +48,18 @@ func GetAccountUsers(c *middleware.Context) {
|
||||
c.JSON(200, query.Result)
|
||||
}
|
||||
|
||||
func RemoveAccountUser(c *middleware.Context) {
|
||||
func RemoveOrgUser(c *middleware.Context) {
|
||||
userId := c.ParamsInt64(":id")
|
||||
|
||||
cmd := m.RemoveAccountUserCommand{AccountId: c.AccountId, UserId: userId}
|
||||
cmd := m.RemoveOrgUserCommand{OrgId: c.OrgId, UserId: userId}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
if err == m.ErrLastAccountAdmin {
|
||||
c.JsonApiErr(400, "Cannot remove last account admin", nil)
|
||||
if err == m.ErrLastOrgAdmin {
|
||||
c.JsonApiErr(400, "Cannot remove last organization admin", nil)
|
||||
return
|
||||
}
|
||||
c.JsonApiErr(500, "Failed to remove user from account", err)
|
||||
c.JsonApiErr(500, "Failed to remove user from organization", err)
|
||||
}
|
||||
|
||||
c.JsonOK("User removed from account")
|
||||
c.JsonOK("User removed from organization")
|
||||
}
|
@ -4,7 +4,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
// TODO: this needs to be cached or improved somehow
|
||||
@ -45,7 +44,7 @@ func Search(c *middleware.Context) {
|
||||
|
||||
if tagcloud == "true" {
|
||||
|
||||
query := m.GetDashboardTagsQuery{AccountId: c.AccountId}
|
||||
query := m.GetDashboardTagsQuery{OrgId: c.OrgId}
|
||||
err := bus.Dispatch(&query)
|
||||
if err != nil {
|
||||
c.JsonApiErr(500, "Failed to get tags from database", err)
|
||||
@ -61,7 +60,7 @@ func Search(c *middleware.Context) {
|
||||
UserId: c.UserId,
|
||||
Limit: limit,
|
||||
IsStarred: starred == "true",
|
||||
AccountId: c.AccountId,
|
||||
OrgId: c.OrgId,
|
||||
}
|
||||
|
||||
err := bus.Dispatch(&query)
|
||||
@ -76,9 +75,6 @@ func Search(c *middleware.Context) {
|
||||
}
|
||||
|
||||
result.Dashboards = query.Result
|
||||
for _, dash := range result.Dashboards {
|
||||
dash.Url = setting.ToAbsUrl("dashboard/db/" + dash.Slug)
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(200, result)
|
||||
|
@ -4,13 +4,14 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func GetUser(c *middleware.Context) {
|
||||
query := m.GetUserInfoQuery{UserId: c.UserId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
c.JsonApiErr(500, "Failed to get account", err)
|
||||
c.JsonApiErr(500, "Failed to get user", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -21,23 +22,23 @@ func UpdateUser(c *middleware.Context, cmd m.UpdateUserCommand) {
|
||||
cmd.UserId = c.UserId
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(400, "Failed to update account", err)
|
||||
c.JsonApiErr(400, "Failed to update user", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JsonOK("Account updated")
|
||||
c.JsonOK("User updated")
|
||||
}
|
||||
|
||||
func GetUserAccounts(c *middleware.Context) {
|
||||
query := m.GetUserAccountsQuery{UserId: c.UserId}
|
||||
func GetUserOrgList(c *middleware.Context) {
|
||||
query := m.GetUserOrgListQuery{UserId: c.UserId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
c.JsonApiErr(500, "Failed to get user accounts", err)
|
||||
c.JsonApiErr(500, "Failed to get user organizations", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, ac := range query.Result {
|
||||
if ac.AccountId == c.AccountId {
|
||||
if ac.OrgId == c.OrgId {
|
||||
ac.IsUsing = true
|
||||
break
|
||||
}
|
||||
@ -46,17 +47,17 @@ func GetUserAccounts(c *middleware.Context) {
|
||||
c.JSON(200, query.Result)
|
||||
}
|
||||
|
||||
func validateUsingAccount(userId int64, accountId int64) bool {
|
||||
query := m.GetUserAccountsQuery{UserId: userId}
|
||||
func validateUsingOrg(userId int64, orgId int64) bool {
|
||||
query := m.GetUserOrgListQuery{UserId: userId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// validate that the account id in the list
|
||||
// validate that the org id in the list
|
||||
valid := false
|
||||
for _, other := range query.Result {
|
||||
if other.AccountId == accountId {
|
||||
if other.OrgId == orgId {
|
||||
valid = true
|
||||
}
|
||||
}
|
||||
@ -64,23 +65,53 @@ func validateUsingAccount(userId int64, accountId int64) bool {
|
||||
return valid
|
||||
}
|
||||
|
||||
func SetUsingAccount(c *middleware.Context) {
|
||||
usingAccountId := c.ParamsInt64(":id")
|
||||
func UserSetUsingOrg(c *middleware.Context) {
|
||||
orgId := c.ParamsInt64(":id")
|
||||
|
||||
if !validateUsingAccount(c.UserId, usingAccountId) {
|
||||
c.JsonApiErr(401, "Not a valid account", nil)
|
||||
if !validateUsingOrg(c.UserId, orgId) {
|
||||
c.JsonApiErr(401, "Not a valid organization", nil)
|
||||
return
|
||||
}
|
||||
|
||||
cmd := m.SetUsingAccountCommand{
|
||||
UserId: c.UserId,
|
||||
AccountId: usingAccountId,
|
||||
cmd := m.SetUsingOrgCommand{
|
||||
UserId: c.UserId,
|
||||
OrgId: orgId,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "Failed change active account", err)
|
||||
c.JsonApiErr(500, "Failed change active organization", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JsonOK("Active account changed")
|
||||
c.JsonOK("Active organization changed")
|
||||
}
|
||||
|
||||
func ChangeUserPassword(c *middleware.Context, cmd m.ChangeUserPasswordCommand) {
|
||||
userQuery := m.GetUserByIdQuery{Id: c.UserId}
|
||||
|
||||
if err := bus.Dispatch(&userQuery); err != nil {
|
||||
c.JsonApiErr(500, "Could not read user from database", err)
|
||||
return
|
||||
}
|
||||
|
||||
passwordHashed := util.EncodePassword(cmd.OldPassword, userQuery.Result.Salt)
|
||||
if passwordHashed != userQuery.Result.Password {
|
||||
c.JsonApiErr(401, "Invalid old password", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if len(cmd.NewPassword) < 4 {
|
||||
c.JsonApiErr(400, "New password too short", nil)
|
||||
return
|
||||
}
|
||||
|
||||
cmd.UserId = c.UserId
|
||||
cmd.NewPassword = util.EncodePassword(cmd.NewPassword, userQuery.Result.Salt)
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
c.JsonApiErr(500, "Failed to change user password", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JsonOK("User password changed")
|
||||
}
|
||||
|
13
pkg/cmd/common.go
Normal file
13
pkg/cmd/common.go
Normal file
@ -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
pkg/cmd/dashboard.go
Normal file
104
pkg/cmd/dashboard.go
Normal file
@ -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
pkg/cmd/datasource.go
Normal file
230
pkg/cmd/datasource.go
Normal file
@ -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)
|
||||
}
|
@ -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
pkg/cmd/orgs.go
Normal file
99
pkg/cmd/orgs.go
Normal file
@ -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)
|
||||
}
|
@ -17,23 +17,15 @@ import (
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/services/eventpublisher"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/social"
|
||||
)
|
||||
|
||||
var CmdWeb = cli.Command{
|
||||
var Web = cli.Command{
|
||||
Name: "web",
|
||||
Usage: "grafana web",
|
||||
Usage: "Starts Grafana backend & web server",
|
||||
Description: "Starts Grafana backend & web server",
|
||||
Action: runWeb,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "config",
|
||||
Value: "grafana.ini",
|
||||
Usage: "path to config file",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func newMacaron() *macaron.Macaron {
|
||||
@ -78,10 +70,9 @@ func runWeb(c *cli.Context) {
|
||||
log.Info("Starting Grafana")
|
||||
log.Info("Version: %v, Commit: %v, Build date: %v", setting.BuildVersion, setting.BuildCommit, time.Unix(setting.BuildStamp, 0))
|
||||
|
||||
setting.NewConfigContext()
|
||||
initRuntime(c)
|
||||
|
||||
social.NewOAuthService()
|
||||
sqlstore.NewEngine()
|
||||
sqlstore.EnsureAdminUser()
|
||||
eventpublisher.Init()
|
||||
|
||||
var err error
|
||||
|
@ -50,13 +50,13 @@ func ToOnWriteEvent(event interface{}) (*OnTheWireEvent, error) {
|
||||
return &wireEvent, nil
|
||||
}
|
||||
|
||||
type AccountCreated struct {
|
||||
type OrgCreated struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type AccountUpdated struct {
|
||||
type OrgUpdated struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
@ -6,6 +6,7 @@ package log
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
@ -21,15 +22,26 @@ func NewBrush(color string) Brush {
|
||||
}
|
||||
}
|
||||
|
||||
var colors = []Brush{
|
||||
NewBrush("1;36"), // Trace cyan
|
||||
NewBrush("1;34"), // Debug blue
|
||||
NewBrush("1;32"), // Info green
|
||||
NewBrush("1;33"), // Warn yellow
|
||||
NewBrush("1;31"), // Error red
|
||||
NewBrush("1;35"), // Critical purple
|
||||
NewBrush("1;31"), // Fatal red
|
||||
}
|
||||
var (
|
||||
Red = NewBrush("1;31")
|
||||
Purple = NewBrush("1;35")
|
||||
Yellow = NewBrush("1;33")
|
||||
Green = NewBrush("1;32")
|
||||
Blue = NewBrush("1;34")
|
||||
Cyan = NewBrush("1;36")
|
||||
|
||||
colors = []Brush{
|
||||
Cyan, // Trace cyan
|
||||
Blue, // Debug blue
|
||||
Green, // Info green
|
||||
Yellow, // Warn yellow
|
||||
Red, // Error red
|
||||
Purple, // Critical purple
|
||||
Red, // Fatal red
|
||||
}
|
||||
consoleWriter = &ConsoleWriter{lg: log.New(os.Stdout, "", 0),
|
||||
Level: TRACE}
|
||||
)
|
||||
|
||||
// ConsoleWriter implements LoggerInterface and writes messages to terminal.
|
||||
type ConsoleWriter struct {
|
||||
@ -40,7 +52,7 @@ type ConsoleWriter struct {
|
||||
// create ConsoleWriter returning as LoggerInterface.
|
||||
func NewConsole() LoggerInterface {
|
||||
return &ConsoleWriter{
|
||||
lg: log.New(os.Stdout, "", log.Ldate|log.Ltime),
|
||||
lg: log.New(os.Stderr, "", log.Ldate|log.Ltime),
|
||||
Level: TRACE,
|
||||
}
|
||||
}
|
||||
@ -68,6 +80,76 @@ func (_ *ConsoleWriter) Flush() {
|
||||
func (_ *ConsoleWriter) Destroy() {
|
||||
}
|
||||
|
||||
func printConsole(level int, msg string) {
|
||||
consoleWriter.WriteMsg(msg, 0, level)
|
||||
}
|
||||
|
||||
func printfConsole(level int, format string, v ...interface{}) {
|
||||
consoleWriter.WriteMsg(fmt.Sprintf(format, v...), 0, level)
|
||||
}
|
||||
|
||||
// ConsoleTrace prints to stdout using TRACE colors
|
||||
func ConsoleTrace(s string) {
|
||||
printConsole(TRACE, s)
|
||||
}
|
||||
|
||||
// ConsoleTracef prints a formatted string to stdout using TRACE colors
|
||||
func ConsoleTracef(format string, v ...interface{}) {
|
||||
printfConsole(TRACE, format, v...)
|
||||
}
|
||||
|
||||
// ConsoleDebug prints to stdout using DEBUG colors
|
||||
func ConsoleDebug(s string) {
|
||||
printConsole(DEBUG, s)
|
||||
}
|
||||
|
||||
// ConsoleDebugf prints a formatted string to stdout using DEBUG colors
|
||||
func ConsoleDebugf(format string, v ...interface{}) {
|
||||
printfConsole(DEBUG, format, v...)
|
||||
}
|
||||
|
||||
// ConsoleInfo prints to stdout using INFO colors
|
||||
func ConsoleInfo(s string) {
|
||||
printConsole(INFO, s)
|
||||
}
|
||||
|
||||
// ConsoleInfof prints a formatted string to stdout using INFO colors
|
||||
func ConsoleInfof(format string, v ...interface{}) {
|
||||
printfConsole(INFO, format, v...)
|
||||
}
|
||||
|
||||
// ConsoleWarn prints to stdout using WARN colors
|
||||
func ConsoleWarn(s string) {
|
||||
printConsole(WARN, s)
|
||||
}
|
||||
|
||||
// ConsoleWarnf prints a formatted string to stdout using WARN colors
|
||||
func ConsoleWarnf(format string, v ...interface{}) {
|
||||
printfConsole(WARN, format, v...)
|
||||
}
|
||||
|
||||
// ConsoleError prints to stdout using ERROR colors
|
||||
func ConsoleError(s string) {
|
||||
printConsole(ERROR, s)
|
||||
}
|
||||
|
||||
// ConsoleErrorf prints a formatted string to stdout using ERROR colors
|
||||
func ConsoleErrorf(format string, v ...interface{}) {
|
||||
printfConsole(ERROR, format, v...)
|
||||
}
|
||||
|
||||
// ConsoleFatal prints to stdout using FATAL colors
|
||||
func ConsoleFatal(s string) {
|
||||
printConsole(FATAL, s)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// ConsoleFatalf prints a formatted string to stdout using FATAL colors
|
||||
func ConsoleFatalf(format string, v ...interface{}) {
|
||||
printfConsole(FATAL, format, v...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("console", NewConsole)
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ func RoleAuth(roles ...m.RoleType) macaron.Handler {
|
||||
return func(c *Context) {
|
||||
ok := false
|
||||
for _, role := range roles {
|
||||
if role == c.AccountRole {
|
||||
if role == c.OrgRole {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
|
@ -56,22 +56,23 @@ func GetContextHandler() macaron.Handler {
|
||||
ctx.SignedInUser = &m.SignedInUser{}
|
||||
|
||||
// TODO: fix this
|
||||
ctx.AccountRole = keyInfo.Role
|
||||
ctx.OrgRole = keyInfo.Role
|
||||
ctx.ApiKeyId = keyInfo.Id
|
||||
ctx.AccountId = keyInfo.AccountId
|
||||
ctx.OrgId = keyInfo.OrgId
|
||||
}
|
||||
} else if setting.AnonymousEnabled {
|
||||
accountQuery := m.GetAccountByNameQuery{Name: setting.AnonymousAccountName}
|
||||
if err := bus.Dispatch(&accountQuery); err != nil {
|
||||
if err == m.ErrAccountNotFound {
|
||||
log.Error(3, "Anonymous access account name does not exist", nil)
|
||||
orgQuery := m.GetOrgByNameQuery{Name: setting.AnonymousOrgName}
|
||||
if err := bus.Dispatch(&orgQuery); err != nil {
|
||||
if err == m.ErrOrgNotFound {
|
||||
log.Error(3, "Anonymous access organization name does not exist", nil)
|
||||
}
|
||||
} else {
|
||||
ctx.IsSignedIn = false
|
||||
ctx.HasAnonymousAccess = true
|
||||
ctx.SignedInUser = &m.SignedInUser{}
|
||||
ctx.AccountRole = m.RoleType(setting.AnonymousAccountRole)
|
||||
ctx.AccountId = accountQuery.Result.Id
|
||||
ctx.OrgRole = m.RoleType(setting.AnonymousOrgRole)
|
||||
ctx.OrgId = orgQuery.Result.Id
|
||||
ctx.OrgName = orgQuery.Result.Name
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"`
|
||||
}
|
@ -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"`
|
||||
}
|
@ -8,22 +8,22 @@ import (
|
||||
var ErrInvalidApiKey = errors.New("Invalid API Key")
|
||||
|
||||
type ApiKey struct {
|
||||
Id int64
|
||||
AccountId int64
|
||||
Name string
|
||||
Key string
|
||||
Role RoleType
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
Id int64
|
||||
OrgId int64
|
||||
Name string
|
||||
Key string
|
||||
Role RoleType
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
}
|
||||
|
||||
// ---------------------
|
||||
// COMMANDS
|
||||
type AddApiKeyCommand struct {
|
||||
Name string `json:"name" binding:"Required"`
|
||||
Role RoleType `json:"role" binding:"Required"`
|
||||
AccountId int64 `json:"-"`
|
||||
Key string `json:"-"`
|
||||
Name string `json:"name" binding:"Required"`
|
||||
Role RoleType `json:"role" binding:"Required"`
|
||||
OrgId int64 `json:"-"`
|
||||
Key string `json:"-"`
|
||||
|
||||
Result *ApiKey `json:"-"`
|
||||
}
|
||||
@ -33,20 +33,20 @@ type UpdateApiKeyCommand struct {
|
||||
Name string `json:"name"`
|
||||
Role RoleType `json:"role"`
|
||||
|
||||
AccountId int64 `json:"-"`
|
||||
OrgId int64 `json:"-"`
|
||||
}
|
||||
|
||||
type DeleteApiKeyCommand struct {
|
||||
Id int64 `json:"id"`
|
||||
AccountId int64 `json:"-"`
|
||||
Id int64 `json:"id"`
|
||||
OrgId int64 `json:"-"`
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// QUERIES
|
||||
|
||||
type GetApiKeysQuery struct {
|
||||
AccountId int64
|
||||
Result []*ApiKey
|
||||
OrgId int64
|
||||
Result []*ApiKey
|
||||
}
|
||||
|
||||
type GetApiKeyByKeyQuery struct {
|
||||
|
@ -14,10 +14,10 @@ var (
|
||||
)
|
||||
|
||||
type Dashboard struct {
|
||||
Id int64
|
||||
Slug string
|
||||
AccountId int64
|
||||
Version int
|
||||
Id int64
|
||||
Slug string
|
||||
OrgId int64
|
||||
Version int
|
||||
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
@ -53,7 +53,7 @@ func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
|
||||
dash := &Dashboard{}
|
||||
dash.Data = cmd.Dashboard
|
||||
dash.Title = dash.Data["title"].(string)
|
||||
dash.AccountId = cmd.AccountId
|
||||
dash.OrgId = cmd.OrgId
|
||||
dash.UpdateSlug()
|
||||
|
||||
if dash.Data["id"] != nil {
|
||||
@ -80,14 +80,14 @@ func (dash *Dashboard) UpdateSlug() {
|
||||
|
||||
type SaveDashboardCommand struct {
|
||||
Dashboard map[string]interface{} `json:"dashboard"`
|
||||
AccountId int64 `json:"-"`
|
||||
OrgId int64 `json:"-"`
|
||||
|
||||
Result *Dashboard
|
||||
}
|
||||
|
||||
type DeleteDashboardCommand struct {
|
||||
Slug string
|
||||
AccountId int64
|
||||
Slug string
|
||||
OrgId int64
|
||||
}
|
||||
|
||||
//
|
||||
@ -95,8 +95,8 @@ type DeleteDashboardCommand struct {
|
||||
//
|
||||
|
||||
type GetDashboardQuery struct {
|
||||
Slug string
|
||||
AccountId int64
|
||||
Slug string
|
||||
OrgId int64
|
||||
|
||||
Result *Dashboard
|
||||
}
|
||||
|
@ -8,7 +8,9 @@ import (
|
||||
const (
|
||||
DS_GRAPHITE = "graphite"
|
||||
DS_INFLUXDB = "influxdb"
|
||||
DS_INFLUXDB_08 = "influxdb_08"
|
||||
DS_ES = "elasticsearch"
|
||||
DS_OPENTSDB = "opentsdb"
|
||||
DS_ACCESS_DIRECT = "direct"
|
||||
DS_ACCESS_PROXY = "proxy"
|
||||
)
|
||||
@ -22,9 +24,9 @@ type DsType string
|
||||
type DsAccess string
|
||||
|
||||
type DataSource struct {
|
||||
Id int64
|
||||
AccountId int64
|
||||
Version int
|
||||
Id int64
|
||||
OrgId int64
|
||||
Version int
|
||||
|
||||
Name string
|
||||
Type DsType
|
||||
@ -47,7 +49,7 @@ type DataSource struct {
|
||||
|
||||
// Also acts as api DTO
|
||||
type AddDataSourceCommand struct {
|
||||
AccountId int64 `json:"-"`
|
||||
OrgId int64 `json:"-"`
|
||||
Name string
|
||||
Type DsType
|
||||
Access DsAccess
|
||||
@ -63,7 +65,7 @@ type AddDataSourceCommand struct {
|
||||
// Also acts as api DTO
|
||||
type UpdateDataSourceCommand struct {
|
||||
Id int64
|
||||
AccountId int64
|
||||
OrgId int64
|
||||
Name string
|
||||
Type DsType
|
||||
Access DsAccess
|
||||
@ -75,22 +77,28 @@ type UpdateDataSourceCommand struct {
|
||||
}
|
||||
|
||||
type DeleteDataSourceCommand struct {
|
||||
Id int64
|
||||
AccountId int64
|
||||
Id int64
|
||||
OrgId int64
|
||||
}
|
||||
|
||||
// ---------------------
|
||||
// QUERIES
|
||||
|
||||
type GetDataSourcesQuery struct {
|
||||
AccountId int64
|
||||
Result []*DataSource
|
||||
OrgId int64
|
||||
Result []*DataSource
|
||||
}
|
||||
|
||||
type GetDataSourceByIdQuery struct {
|
||||
Id int64
|
||||
AccountId int64
|
||||
Result DataSource
|
||||
Id int64
|
||||
OrgId int64
|
||||
Result DataSource
|
||||
}
|
||||
|
||||
type GetDataSourceByNameQuery struct {
|
||||
Name string
|
||||
OrgId int64
|
||||
Result DataSource
|
||||
}
|
||||
|
||||
// ---------------------
|
||||
|
65
pkg/models/org.go
Normal file
65
pkg/models/org.go
Normal file
@ -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
pkg/models/org_user.go
Normal file
67
pkg/models/org_user.go
Normal file
@ -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"`
|
||||
}
|
@ -11,7 +11,6 @@ type DashboardSearchHit struct {
|
||||
Title string `json:"title"`
|
||||
Slug string `json:"slug"`
|
||||
Tags []string `json:"tags"`
|
||||
Url string `json:"url"`
|
||||
IsStarred bool `json:"isStarred"`
|
||||
}
|
||||
|
||||
@ -23,7 +22,7 @@ type DashboardTagCloudItem struct {
|
||||
type SearchDashboardsQuery struct {
|
||||
Title string
|
||||
Tag string
|
||||
AccountId int64
|
||||
OrgId int64
|
||||
UserId int64
|
||||
Limit int
|
||||
IsStarred bool
|
||||
@ -32,6 +31,6 @@ type SearchDashboardsQuery struct {
|
||||
}
|
||||
|
||||
type GetDashboardTagsQuery struct {
|
||||
AccountId int64
|
||||
Result []*DashboardTagCloudItem
|
||||
OrgId int64
|
||||
Result []*DashboardTagCloudItem
|
||||
}
|
||||
|
@ -11,18 +11,20 @@ var (
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id int64
|
||||
Version int
|
||||
Email string
|
||||
Name string
|
||||
Login string
|
||||
Password string
|
||||
Salt string
|
||||
Rands string
|
||||
Company string
|
||||
Id int64
|
||||
Version int
|
||||
Email string
|
||||
Name string
|
||||
Login string
|
||||
Password string
|
||||
Salt string
|
||||
Rands string
|
||||
Company string
|
||||
EmailVerified bool
|
||||
Theme string
|
||||
|
||||
IsAdmin bool
|
||||
AccountId int64
|
||||
IsAdmin bool
|
||||
OrgId int64
|
||||
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
@ -50,9 +52,25 @@ type UpdateUserCommand struct {
|
||||
UserId int64 `json:"-"`
|
||||
}
|
||||
|
||||
type SetUsingAccountCommand struct {
|
||||
UserId int64
|
||||
AccountId int64
|
||||
type ChangeUserPasswordCommand struct {
|
||||
OldPassword string `json:"oldPassword"`
|
||||
NewPassword string `json:"newPassword"`
|
||||
|
||||
UserId int64 `json:"-"`
|
||||
}
|
||||
|
||||
type UpdateUserPermissionsCommand struct {
|
||||
IsGrafanaAdmin bool
|
||||
UserId int64 `json:"-"`
|
||||
}
|
||||
|
||||
type DeleteUserCommand struct {
|
||||
UserId int64
|
||||
}
|
||||
|
||||
type SetUsingOrgCommand struct {
|
||||
UserId int64
|
||||
OrgId int64
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
@ -63,6 +81,11 @@ type GetUserByLoginQuery struct {
|
||||
Result *User
|
||||
}
|
||||
|
||||
type GetUserByIdQuery struct {
|
||||
Id int64
|
||||
Result *User
|
||||
}
|
||||
|
||||
type GetSignedInUserQuery struct {
|
||||
UserId int64
|
||||
Result *SignedInUser
|
||||
@ -81,14 +104,19 @@ type SearchUsersQuery struct {
|
||||
Result []*UserSearchHitDTO
|
||||
}
|
||||
|
||||
type GetUserOrgListQuery struct {
|
||||
UserId int64
|
||||
Result []*UserOrgDTO
|
||||
}
|
||||
|
||||
// ------------------------
|
||||
// DTO & Projections
|
||||
|
||||
type SignedInUser struct {
|
||||
UserId int64
|
||||
AccountId int64
|
||||
AccountName string
|
||||
AccountRole RoleType
|
||||
OrgId int64
|
||||
OrgName string
|
||||
OrgRole RoleType
|
||||
Login string
|
||||
Name string
|
||||
Email string
|
||||
@ -97,9 +125,10 @@ type SignedInUser struct {
|
||||
}
|
||||
|
||||
type UserDTO struct {
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
Login string `json:"login"`
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
Login string `json:"login"`
|
||||
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
|
||||
}
|
||||
|
||||
type UserSearchHitDTO struct {
|
||||
|
@ -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
|
||||
})
|
||||
}
|
@ -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
|
||||
})
|
||||
}
|
@ -17,7 +17,7 @@ func init() {
|
||||
}
|
||||
|
||||
func GetApiKeys(query *m.GetApiKeysQuery) error {
|
||||
sess := x.Limit(100, 0).Where("account_id=?", query.AccountId).Asc("name")
|
||||
sess := x.Limit(100, 0).Where("org_id=?", query.OrgId).Asc("name")
|
||||
|
||||
query.Result = make([]*m.ApiKey, 0)
|
||||
return sess.Find(&query.Result)
|
||||
@ -25,8 +25,8 @@ func GetApiKeys(query *m.GetApiKeysQuery) error {
|
||||
|
||||
func DeleteApiKey(cmd *m.DeleteApiKeyCommand) error {
|
||||
return inTransaction(func(sess *xorm.Session) error {
|
||||
var rawSql = "DELETE FROM api_key WHERE id=? and account_id=?"
|
||||
_, err := sess.Exec(rawSql, cmd.Id, cmd.AccountId)
|
||||
var rawSql = "DELETE FROM api_key WHERE id=? and org_id=?"
|
||||
_, err := sess.Exec(rawSql, cmd.Id, cmd.OrgId)
|
||||
return err
|
||||
})
|
||||
}
|
||||
@ -34,12 +34,12 @@ func DeleteApiKey(cmd *m.DeleteApiKeyCommand) error {
|
||||
func AddApiKey(cmd *m.AddApiKeyCommand) error {
|
||||
return inTransaction(func(sess *xorm.Session) error {
|
||||
t := m.ApiKey{
|
||||
AccountId: cmd.AccountId,
|
||||
Name: cmd.Name,
|
||||
Role: cmd.Role,
|
||||
Key: cmd.Key,
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
OrgId: cmd.OrgId,
|
||||
Name: cmd.Name,
|
||||
Role: cmd.Role,
|
||||
Key: cmd.Key,
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
if _, err := sess.Insert(&t); err != nil {
|
||||
@ -53,20 +53,20 @@ func AddApiKey(cmd *m.AddApiKeyCommand) error {
|
||||
func UpdateApiKey(cmd *m.UpdateApiKeyCommand) error {
|
||||
return inTransaction(func(sess *xorm.Session) error {
|
||||
t := m.ApiKey{
|
||||
Id: cmd.Id,
|
||||
AccountId: cmd.AccountId,
|
||||
Name: cmd.Name,
|
||||
Role: cmd.Role,
|
||||
Updated: time.Now(),
|
||||
Id: cmd.Id,
|
||||
OrgId: cmd.OrgId,
|
||||
Name: cmd.Name,
|
||||
Role: cmd.Role,
|
||||
Updated: time.Now(),
|
||||
}
|
||||
_, err := sess.Where("id=? and account_id=?", t.Id, t.AccountId).Update(&t)
|
||||
_, err := sess.Where("id=? and org_id=?", t.Id, t.OrgId).Update(&t)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func GetApiKeyByKey(query *m.GetApiKeyByKeyQuery) error {
|
||||
var apikey m.ApiKey
|
||||
has, err := x.Where("key=?", query.Key).Get(&apikey)
|
||||
has, err := x.Where("`key`=?", query.Key).Get(&apikey)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
31
pkg/services/sqlstore/apikey_test.go
Normal file
31
pkg/services/sqlstore/apikey_test.go
Normal file
@ -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)
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
}
|
@ -22,7 +22,7 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
||||
dash := cmd.GetDashboardModel()
|
||||
|
||||
// try get existing dashboard
|
||||
existing := m.Dashboard{Slug: dash.Slug, AccountId: dash.AccountId}
|
||||
existing := m.Dashboard{Slug: dash.Slug, OrgId: dash.OrgId}
|
||||
hasExisting, err := sess.Get(&existing)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -61,7 +61,7 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
||||
}
|
||||
|
||||
func GetDashboard(query *m.GetDashboardQuery) error {
|
||||
dashboard := m.Dashboard{Slug: query.Slug, AccountId: query.AccountId}
|
||||
dashboard := m.Dashboard{Slug: query.Slug, OrgId: query.OrgId}
|
||||
has, err := x.Get(&dashboard)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -98,9 +98,9 @@ func SearchDashboards(query *m.SearchDashboardsQuery) error {
|
||||
sql.WriteString(" INNER JOIN star on star.dashboard_id = dashboard.id")
|
||||
}
|
||||
|
||||
sql.WriteString(` WHERE dashboard.account_id=?`)
|
||||
sql.WriteString(` WHERE dashboard.org_id=?`)
|
||||
|
||||
params = append(params, query.AccountId)
|
||||
params = append(params, query.OrgId)
|
||||
|
||||
if query.IsStarred {
|
||||
sql.WriteString(` AND star.user_id=?`)
|
||||
@ -158,11 +158,11 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
|
||||
term
|
||||
FROM dashboard
|
||||
INNER JOIN dashboard_tag on dashboard_tag.dashboard_id = dashboard.id
|
||||
WHERE dashboard.account_id=?
|
||||
WHERE dashboard.org_id=?
|
||||
GROUP BY term`
|
||||
|
||||
query.Result = make([]*m.DashboardTagCloudItem, 0)
|
||||
sess := x.Sql(sql, query.AccountId)
|
||||
sess := x.Sql(sql, query.OrgId)
|
||||
err := sess.Find(&query.Result)
|
||||
return err
|
||||
}
|
||||
@ -171,8 +171,8 @@ func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
rawSql := "DELETE FROM Dashboard WHERE account_id=? and slug=?"
|
||||
_, err := sess.Exec(rawSql, cmd.AccountId, cmd.Slug)
|
||||
rawSql := "DELETE FROM dashboard WHERE org_id=? and slug=?"
|
||||
_, err := sess.Exec(rawSql, cmd.OrgId, cmd.Slug)
|
||||
|
||||
return err
|
||||
}
|
||||
|
@ -8,9 +8,9 @@ import (
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func insertTestDashboard(title string, accountId int64, tags ...interface{}) *m.Dashboard {
|
||||
func insertTestDashboard(title string, orgId int64, tags ...interface{}) *m.Dashboard {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
AccountId: accountId,
|
||||
OrgId: orgId,
|
||||
Dashboard: map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": title,
|
||||
@ -40,8 +40,8 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
|
||||
Convey("Should be able to get dashboard", func() {
|
||||
query := m.GetDashboardQuery{
|
||||
Slug: "test-dash-23",
|
||||
AccountId: 1,
|
||||
Slug: "test-dash-23",
|
||||
OrgId: 1,
|
||||
}
|
||||
|
||||
err := GetDashboard(&query)
|
||||
@ -53,8 +53,8 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
|
||||
Convey("Should be able to search for dashboard", func() {
|
||||
query := m.SearchDashboardsQuery{
|
||||
Title: "test",
|
||||
AccountId: 1,
|
||||
Title: "test",
|
||||
OrgId: 1,
|
||||
}
|
||||
|
||||
err := SearchDashboards(&query)
|
||||
@ -66,8 +66,8 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should be able to search for dashboards using tags", func() {
|
||||
query1 := m.SearchDashboardsQuery{Tag: "webapp", AccountId: 1}
|
||||
query2 := m.SearchDashboardsQuery{Tag: "tagdoesnotexist", AccountId: 1}
|
||||
query1 := m.SearchDashboardsQuery{Tag: "webapp", OrgId: 1}
|
||||
query2 := m.SearchDashboardsQuery{Tag: "tagdoesnotexist", OrgId: 1}
|
||||
|
||||
err := SearchDashboards(&query1)
|
||||
err = SearchDashboards(&query2)
|
||||
@ -79,7 +79,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
|
||||
Convey("Should not be able to save dashboard with same name", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
AccountId: 1,
|
||||
OrgId: 1,
|
||||
Dashboard: map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": "test dash 23",
|
||||
@ -92,7 +92,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should be able to get dashboard tags", func() {
|
||||
query := m.GetDashboardTagsQuery{AccountId: 1}
|
||||
query := m.GetDashboardTagsQuery{OrgId: 1}
|
||||
|
||||
err := GetDashboardTags(&query)
|
||||
So(err, ShouldBeNil)
|
||||
@ -113,7 +113,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should be able to search for starred dashboards", func() {
|
||||
query := m.SearchDashboardsQuery{AccountId: 1, UserId: 10, IsStarred: true}
|
||||
query := m.SearchDashboardsQuery{OrgId: 1, UserId: 10, IsStarred: true}
|
||||
err := SearchDashboards(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
@ -15,10 +15,21 @@ func init() {
|
||||
bus.AddHandler("sql", DeleteDataSource)
|
||||
bus.AddHandler("sql", UpdateDataSource)
|
||||
bus.AddHandler("sql", GetDataSourceById)
|
||||
bus.AddHandler("sql", GetDataSourceByName)
|
||||
}
|
||||
|
||||
func GetDataSourceById(query *m.GetDataSourceByIdQuery) error {
|
||||
sess := x.Limit(100, 0).Where("account_id=? AND id=?", query.AccountId, query.Id)
|
||||
sess := x.Limit(100, 0).Where("org_id=? AND id=?", query.OrgId, query.Id)
|
||||
has, err := sess.Get(&query.Result)
|
||||
|
||||
if !has {
|
||||
return m.ErrDataSourceNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func GetDataSourceByName(query *m.GetDataSourceByNameQuery) error {
|
||||
sess := x.Limit(100, 0).Where("org_id=? AND name=?", query.OrgId, query.Name)
|
||||
has, err := sess.Get(&query.Result)
|
||||
|
||||
if !has {
|
||||
@ -28,7 +39,7 @@ func GetDataSourceById(query *m.GetDataSourceByIdQuery) error {
|
||||
}
|
||||
|
||||
func GetDataSources(query *m.GetDataSourcesQuery) error {
|
||||
sess := x.Limit(100, 0).Where("account_id=?", query.AccountId).Asc("name")
|
||||
sess := x.Limit(100, 0).Where("org_id=?", query.OrgId).Asc("name")
|
||||
|
||||
query.Result = make([]*m.DataSource, 0)
|
||||
return sess.Find(&query.Result)
|
||||
@ -36,8 +47,8 @@ func GetDataSources(query *m.GetDataSourcesQuery) error {
|
||||
|
||||
func DeleteDataSource(cmd *m.DeleteDataSourceCommand) error {
|
||||
return inTransaction(func(sess *xorm.Session) error {
|
||||
var rawSql = "DELETE FROM data_source WHERE id=? and account_id=?"
|
||||
_, err := sess.Exec(rawSql, cmd.Id, cmd.AccountId)
|
||||
var rawSql = "DELETE FROM data_source WHERE id=? and org_id=?"
|
||||
_, err := sess.Exec(rawSql, cmd.Id, cmd.OrgId)
|
||||
return err
|
||||
})
|
||||
}
|
||||
@ -46,7 +57,7 @@ func AddDataSource(cmd *m.AddDataSourceCommand) error {
|
||||
|
||||
return inTransaction(func(sess *xorm.Session) error {
|
||||
ds := &m.DataSource{
|
||||
AccountId: cmd.AccountId,
|
||||
OrgId: cmd.OrgId,
|
||||
Name: cmd.Name,
|
||||
Type: cmd.Type,
|
||||
Access: cmd.Access,
|
||||
@ -74,8 +85,8 @@ func AddDataSource(cmd *m.AddDataSourceCommand) error {
|
||||
func updateIsDefaultFlag(ds *m.DataSource, sess *xorm.Session) error {
|
||||
// Handle is default flag
|
||||
if ds.IsDefault {
|
||||
rawSql := "UPDATE data_source SET is_default = 0 WHERE account_id=? AND id <> ?"
|
||||
if _, err := sess.Exec(rawSql, ds.AccountId, ds.Id); err != nil {
|
||||
rawSql := "UPDATE data_source SET is_default = 0 WHERE org_id=? AND id <> ?"
|
||||
if _, err := sess.Exec(rawSql, ds.OrgId, ds.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -87,7 +98,7 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
|
||||
return inTransaction(func(sess *xorm.Session) error {
|
||||
ds := &m.DataSource{
|
||||
Id: cmd.Id,
|
||||
AccountId: cmd.AccountId,
|
||||
OrgId: cmd.OrgId,
|
||||
Name: cmd.Name,
|
||||
Type: cmd.Type,
|
||||
Access: cmd.Access,
|
||||
@ -101,7 +112,7 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
|
||||
|
||||
sess.UseBool("is_default")
|
||||
|
||||
_, err := sess.Where("id=? and account_id=?", ds.Id, ds.AccountId).Update(ds)
|
||||
_, err := sess.Where("id=? and org_id=?", ds.Id, ds.OrgId).Update(ds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -42,16 +42,16 @@ func TestDataAccess(t *testing.T) {
|
||||
Convey("Can add datasource", func() {
|
||||
|
||||
err := AddDataSource(&m.AddDataSourceCommand{
|
||||
AccountId: 10,
|
||||
Type: m.DS_INFLUXDB,
|
||||
Access: m.DS_ACCESS_DIRECT,
|
||||
Url: "http://test",
|
||||
Database: "site",
|
||||
OrgId: 10,
|
||||
Type: m.DS_INFLUXDB,
|
||||
Access: m.DS_ACCESS_DIRECT,
|
||||
Url: "http://test",
|
||||
Database: "site",
|
||||
})
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
query := m.GetDataSourcesQuery{AccountId: 10}
|
||||
query := m.GetDataSourcesQuery{OrgId: 10}
|
||||
err = GetDataSources(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -59,33 +59,33 @@ func TestDataAccess(t *testing.T) {
|
||||
|
||||
ds := query.Result[0]
|
||||
|
||||
So(ds.AccountId, ShouldEqual, 10)
|
||||
So(ds.OrgId, ShouldEqual, 10)
|
||||
So(ds.Database, ShouldEqual, "site")
|
||||
})
|
||||
|
||||
Convey("Given a datasource", func() {
|
||||
|
||||
AddDataSource(&m.AddDataSourceCommand{
|
||||
AccountId: 10,
|
||||
Type: m.DS_GRAPHITE,
|
||||
Access: m.DS_ACCESS_DIRECT,
|
||||
Url: "http://test",
|
||||
OrgId: 10,
|
||||
Type: m.DS_GRAPHITE,
|
||||
Access: m.DS_ACCESS_DIRECT,
|
||||
Url: "http://test",
|
||||
})
|
||||
|
||||
query := m.GetDataSourcesQuery{AccountId: 10}
|
||||
query := m.GetDataSourcesQuery{OrgId: 10}
|
||||
GetDataSources(&query)
|
||||
ds := query.Result[0]
|
||||
|
||||
Convey("Can delete datasource", func() {
|
||||
err := DeleteDataSource(&m.DeleteDataSourceCommand{Id: ds.Id, AccountId: ds.AccountId})
|
||||
err := DeleteDataSource(&m.DeleteDataSourceCommand{Id: ds.Id, OrgId: ds.OrgId})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
GetDataSources(&query)
|
||||
So(len(query.Result), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Can not delete datasource with wrong accountId", func() {
|
||||
err := DeleteDataSource(&m.DeleteDataSourceCommand{Id: ds.Id, AccountId: 123123})
|
||||
Convey("Can not delete datasource with wrong orgId", func() {
|
||||
err := DeleteDataSource(&m.DeleteDataSourceCommand{Id: ds.Id, OrgId: 123123})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
GetDataSources(&query)
|
||||
|
@ -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
pkg/services/sqlstore/migrations/apikey_mig.go
Normal file
75
pkg/services/sqlstore/migrations/apikey_mig.go
Normal file
@ -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
pkg/services/sqlstore/migrations/common.go
Normal file
26
pkg/services/sqlstore/migrations/common.go
Normal file
@ -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
pkg/services/sqlstore/migrations/dashboard_mig.go
Normal file
89
pkg/services/sqlstore/migrations/dashboard_mig.go
Normal file
@ -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
pkg/services/sqlstore/migrations/datasource_mig.go
Normal file
98
pkg/services/sqlstore/migrations/datasource_mig.go
Normal file
@ -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
pkg/services/sqlstore/migrations/migrations.go
Normal file
51
pkg/services/sqlstore/migrations/migrations.go
Normal file
@ -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]))
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package sqlstore
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -32,7 +32,7 @@ func TestMigrations(t *testing.T) {
|
||||
|
||||
mg := NewMigrator(x)
|
||||
mg.LogLevel = log.DEBUG
|
||||
addMigrations(mg)
|
||||
AddMigrations(mg)
|
||||
|
||||
err = mg.Start()
|
||||
So(err, ShouldBeNil)
|
71
pkg/services/sqlstore/migrations/org_mig.go
Normal file
71
pkg/services/sqlstore/migrations/org_mig.go
Normal file
@ -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
pkg/services/sqlstore/migrations/user_mig.go
Normal file
91
pkg/services/sqlstore/migrations/user_mig.go
Normal file
@ -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"))
|
||||
}
|
@ -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
pkg/services/sqlstore/migrator/conditions.go
Normal file
13
pkg/services/sqlstore/migrator/conditions.go
Normal file
@ -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)
|
||||
}
|
@ -20,8 +20,12 @@ type Dialect interface {
|
||||
CreateIndexSql(tableName string, index *Index) string
|
||||
CreateTableSql(table *Table) string
|
||||
AddColumnSql(tableName string, Col *Column) string
|
||||
CopyTableData(sourceTable string, targetTable string, sourceCols []string, targetCols []string) string
|
||||
DropTable(tableName string) string
|
||||
DropIndexSql(tableName string, index *Index) string
|
||||
|
||||
TableCheckSql(tableName string) (string, []interface{})
|
||||
RenameTable(oldName string, newName string) string
|
||||
}
|
||||
|
||||
func NewDialect(name string) Dialect {
|
||||
@ -101,15 +105,47 @@ func (db *BaseDialect) AddColumnSql(tableName string, col *Column) string {
|
||||
func (db *BaseDialect) CreateIndexSql(tableName string, index *Index) string {
|
||||
quote := db.dialect.Quote
|
||||
var unique string
|
||||
var idxName string
|
||||
if index.Type == UniqueIndex {
|
||||
unique = " UNIQUE"
|
||||
idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name)
|
||||
} else {
|
||||
idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name)
|
||||
}
|
||||
|
||||
idxName := index.XName(tableName)
|
||||
|
||||
return fmt.Sprintf("CREATE%s INDEX %v ON %v (%v);", unique,
|
||||
quote(idxName), quote(tableName),
|
||||
quote(strings.Join(index.Cols, quote(","))))
|
||||
}
|
||||
|
||||
func (db *BaseDialect) QuoteColList(cols []string) string {
|
||||
var sourceColsSql = ""
|
||||
for _, col := range cols {
|
||||
sourceColsSql += db.dialect.Quote(col)
|
||||
sourceColsSql += "\n, "
|
||||
}
|
||||
return strings.TrimSuffix(sourceColsSql, "\n, ")
|
||||
}
|
||||
|
||||
func (db *BaseDialect) CopyTableData(sourceTable string, targetTable string, sourceCols []string, targetCols []string) string {
|
||||
sourceColsSql := db.QuoteColList(sourceCols)
|
||||
targetColsSql := db.QuoteColList(targetCols)
|
||||
|
||||
quote := db.dialect.Quote
|
||||
return fmt.Sprintf("INSERT INTO %s (%s) SELECT %s FROM %s", quote(targetTable), targetColsSql, sourceColsSql, quote(sourceTable))
|
||||
}
|
||||
|
||||
func (db *BaseDialect) DropTable(tableName string) string {
|
||||
quote := db.dialect.Quote
|
||||
return fmt.Sprintf("DROP TABLE IF EXISTS %s", quote(tableName))
|
||||
}
|
||||
|
||||
func (db *BaseDialect) RenameTable(oldName string, newName string) string {
|
||||
quote := db.dialect.Quote
|
||||
return fmt.Sprintf("ALTER TABLE %s RENAME TO %s", quote(oldName), quote(newName))
|
||||
}
|
||||
|
||||
func (db *BaseDialect) DropIndexSql(tableName string, index *Index) string {
|
||||
quote := db.dialect.Quote
|
||||
var name string
|
||||
name = index.XName(tableName)
|
||||
return fmt.Sprintf("DROP INDEX %v ON %s", quote(name), quote(tableName))
|
||||
}
|
||||
|
190
pkg/services/sqlstore/migrator/migrations.go
Normal file
190
pkg/services/sqlstore/migrator/migrations.go
Normal file
@ -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)
|
||||
}
|
@ -5,9 +5,9 @@ import (
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/go-xorm/xorm"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
_ "github.com/lib/pq"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
)
|
||||
|
||||
type Migrator struct {
|
||||
@ -70,7 +70,7 @@ func (mg *Migrator) GetMigrationLog() (map[string]MigrationLog, error) {
|
||||
|
||||
func (mg *Migrator) Start() error {
|
||||
if mg.LogLevel <= log.INFO {
|
||||
log.Info("Migrator:: Starting DB migration")
|
||||
log.Info("Migrator: Starting DB migration")
|
||||
}
|
||||
|
||||
logMap, err := mg.GetMigrationLog()
|
||||
@ -82,7 +82,7 @@ func (mg *Migrator) Start() error {
|
||||
_, exists := logMap[m.Id()]
|
||||
if exists {
|
||||
if mg.LogLevel <= log.DEBUG {
|
||||
log.Debug("Migrator:: Skipping migration: %v, Already executed", m.Id())
|
||||
log.Debug("Migrator: Skipping migration: %v, Already executed", m.Id())
|
||||
}
|
||||
continue
|
||||
}
|
||||
@ -114,13 +114,24 @@ func (mg *Migrator) Start() error {
|
||||
|
||||
func (mg *Migrator) exec(m Migration) error {
|
||||
if mg.LogLevel <= log.INFO {
|
||||
log.Info("Migrator::exec migration id: %v", m.Id())
|
||||
log.Info("Migrator: exec migration id: %v", m.Id())
|
||||
}
|
||||
|
||||
err := mg.inTransaction(func(sess *xorm.Session) error {
|
||||
|
||||
condition := m.GetCondition()
|
||||
if condition != nil {
|
||||
sql, args := condition.Sql(mg.dialect)
|
||||
results, err := sess.Query(sql, args...)
|
||||
if err != nil || len(results) == 0 {
|
||||
log.Info("Migrator: skipping migration id: %v, condition not fulfilled", m.Id())
|
||||
return sess.Rollback()
|
||||
}
|
||||
}
|
||||
|
||||
_, err := sess.Exec(m.Sql(mg.dialect))
|
||||
if err != nil {
|
||||
log.Error(3, "Migrator::exec FAILED migration id: %v, err: %v", m.Id(), err)
|
||||
log.Error(3, "Migrator: exec FAILED migration id: %v, err: %v", m.Id(), err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -1,6 +1,9 @@
|
||||
package migrator
|
||||
|
||||
import "strconv"
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Postgres struct {
|
||||
BaseDialect
|
||||
@ -84,3 +87,9 @@ func (db *Postgres) TableCheckSql(tableName string) (string, []interface{}) {
|
||||
sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?"
|
||||
return sql, args
|
||||
}
|
||||
|
||||
func (db *Postgres) DropIndexSql(tableName string, index *Index) string {
|
||||
quote := db.Quote
|
||||
idxName := index.XName(tableName)
|
||||
return fmt.Sprintf("DROP INDEX %v", quote(idxName))
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package migrator
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Sqlite3 struct {
|
||||
BaseDialect
|
||||
}
|
||||
@ -57,3 +59,10 @@ func (db *Sqlite3) TableCheckSql(tableName string) (string, []interface{}) {
|
||||
args := []interface{}{tableName}
|
||||
return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args
|
||||
}
|
||||
|
||||
func (db *Sqlite3) DropIndexSql(tableName string, index *Index) string {
|
||||
quote := db.Quote
|
||||
//var unique string
|
||||
idxName := index.XName(tableName)
|
||||
return fmt.Sprintf("DROP INDEX %v", quote(idxName))
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
package migrator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
POSTGRES = "postgres"
|
||||
SQLITE = "sqlite3"
|
||||
@ -10,6 +15,7 @@ type Migration interface {
|
||||
Sql(dialect Dialect) string
|
||||
Id() string
|
||||
SetId(string)
|
||||
GetCondition() MigrationCondition
|
||||
}
|
||||
|
||||
type SQLType string
|
||||
@ -24,6 +30,7 @@ type Table struct {
|
||||
Name string
|
||||
Columns []*Column
|
||||
PrimaryKeys []string
|
||||
Indices []*Index
|
||||
}
|
||||
|
||||
const (
|
||||
@ -37,6 +44,21 @@ type Index struct {
|
||||
Cols []string
|
||||
}
|
||||
|
||||
func (index *Index) XName(tableName string) string {
|
||||
if index.Name == "" {
|
||||
index.Name = fmt.Sprintf("%s", strings.Join(index.Cols, "_"))
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(index.Name, "UQE_") &&
|
||||
!strings.HasPrefix(index.Name, "IDX_") {
|
||||
if index.Type == UniqueIndex {
|
||||
return fmt.Sprintf("UQE_%v_%v", tableName, index.Name)
|
||||
}
|
||||
return fmt.Sprintf("IDX_%v_%v", tableName, index.Name)
|
||||
}
|
||||
return index.Name
|
||||
}
|
||||
|
||||
var (
|
||||
DB_Bit = "BIT"
|
||||
DB_TinyInt = "TINYINT"
|
||||
|
134
pkg/services/sqlstore/org.go
Normal file
134
pkg/services/sqlstore/org.go
Normal file
@ -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
|
||||
})
|
||||
}
|
@ -14,12 +14,12 @@ func TestAccountDataAccess(t *testing.T) {
|
||||
Convey("Testing Account DB Access", t, func() {
|
||||
InitTestDB(t)
|
||||
|
||||
Convey("Given single account mode", func() {
|
||||
setting.SingleAccountMode = true
|
||||
setting.DefaultAccountName = "test"
|
||||
setting.DefaultAccountRole = "Viewer"
|
||||
Convey("Given single org mode", func() {
|
||||
setting.SingleOrgMode = true
|
||||
setting.DefaultOrgName = "test"
|
||||
setting.DefaultOrgRole = "Viewer"
|
||||
|
||||
Convey("Users should be added to default account", func() {
|
||||
Convey("Users should be added to default organization", func() {
|
||||
ac1cmd := m.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"}
|
||||
ac2cmd := m.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name"}
|
||||
|
||||
@ -28,20 +28,20 @@ func TestAccountDataAccess(t *testing.T) {
|
||||
err = CreateUser(&ac2cmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q1 := m.GetUserAccountsQuery{UserId: ac1cmd.Result.Id}
|
||||
q2 := m.GetUserAccountsQuery{UserId: ac2cmd.Result.Id}
|
||||
GetUserAccounts(&q1)
|
||||
GetUserAccounts(&q2)
|
||||
q1 := m.GetUserOrgListQuery{UserId: ac1cmd.Result.Id}
|
||||
q2 := m.GetUserOrgListQuery{UserId: ac2cmd.Result.Id}
|
||||
GetUserOrgList(&q1)
|
||||
GetUserOrgList(&q2)
|
||||
|
||||
So(q1.Result[0].AccountId, ShouldEqual, q2.Result[0].AccountId)
|
||||
So(q1.Result[0].OrgId, ShouldEqual, q2.Result[0].OrgId)
|
||||
So(q1.Result[0].Role, ShouldEqual, "Viewer")
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given two saved users", func() {
|
||||
setting.SingleAccountMode = false
|
||||
setting.SingleOrgMode = false
|
||||
setting.DefaultOrgName = "test"
|
||||
|
||||
setting.DefaultAccountName = "test"
|
||||
ac1cmd := m.CreateUserCommand{Login: "ac1", Email: "ac1@test.com", Name: "ac1 name"}
|
||||
ac2cmd := m.CreateUserCommand{Login: "ac2", Email: "ac2@test.com", Name: "ac2 name", IsAdmin: true}
|
||||
|
||||
@ -70,14 +70,14 @@ func TestAccountDataAccess(t *testing.T) {
|
||||
So(query.Result[1].Email, ShouldEqual, "ac2@test.com")
|
||||
})
|
||||
|
||||
Convey("Given an added account user", func() {
|
||||
cmd := m.AddAccountUserCommand{
|
||||
AccountId: ac1.AccountId,
|
||||
UserId: ac2.Id,
|
||||
Role: m.ROLE_VIEWER,
|
||||
Convey("Given an added org user", func() {
|
||||
cmd := m.AddOrgUserCommand{
|
||||
OrgId: ac1.OrgId,
|
||||
UserId: ac2.Id,
|
||||
Role: m.ROLE_VIEWER,
|
||||
}
|
||||
|
||||
err := AddAccountUser(&cmd)
|
||||
err := AddOrgUser(&cmd)
|
||||
Convey("Should have been saved without error", func() {
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
@ -88,54 +88,54 @@ func TestAccountDataAccess(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.Email, ShouldEqual, "ac2@test.com")
|
||||
So(query.Result.AccountId, ShouldEqual, ac2.AccountId)
|
||||
So(query.Result.OrgId, ShouldEqual, ac2.OrgId)
|
||||
So(query.Result.Name, ShouldEqual, "ac2 name")
|
||||
So(query.Result.Login, ShouldEqual, "ac2")
|
||||
So(query.Result.AccountRole, ShouldEqual, "Admin")
|
||||
So(query.Result.AccountName, ShouldEqual, "ac2@test.com")
|
||||
So(query.Result.OrgRole, ShouldEqual, "Admin")
|
||||
So(query.Result.OrgName, ShouldEqual, "ac2@test.com")
|
||||
So(query.Result.IsGrafanaAdmin, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Can get user accounts", func() {
|
||||
query := m.GetUserAccountsQuery{UserId: ac2.Id}
|
||||
err := GetUserAccounts(&query)
|
||||
Convey("Can get user organizations", func() {
|
||||
query := m.GetUserOrgListQuery{UserId: ac2.Id}
|
||||
err := GetUserOrgList(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
})
|
||||
|
||||
Convey("Can get account users", func() {
|
||||
query := m.GetAccountUsersQuery{AccountId: ac1.AccountId}
|
||||
err := GetAccountUsers(&query)
|
||||
Convey("Can get organization users", func() {
|
||||
query := m.GetOrgUsersQuery{OrgId: ac1.OrgId}
|
||||
err := GetOrgUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
So(query.Result[0].Role, ShouldEqual, "Admin")
|
||||
})
|
||||
|
||||
Convey("Can set using account", func() {
|
||||
cmd := m.SetUsingAccountCommand{UserId: ac2.Id, AccountId: ac1.Id}
|
||||
err := SetUsingAccount(&cmd)
|
||||
Convey("Can set using org", func() {
|
||||
cmd := m.SetUsingOrgCommand{UserId: ac2.Id, OrgId: ac1.Id}
|
||||
err := SetUsingOrg(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("SignedInUserQuery with a different account", func() {
|
||||
Convey("SignedInUserQuery with a different org", func() {
|
||||
query := m.GetSignedInUserQuery{UserId: ac2.Id}
|
||||
err := GetSignedInUser(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.AccountId, ShouldEqual, ac1.Id)
|
||||
So(query.Result.OrgId, ShouldEqual, ac1.Id)
|
||||
So(query.Result.Email, ShouldEqual, "ac2@test.com")
|
||||
So(query.Result.Name, ShouldEqual, "ac2 name")
|
||||
So(query.Result.Login, ShouldEqual, "ac2")
|
||||
So(query.Result.AccountName, ShouldEqual, "ac1@test.com")
|
||||
So(query.Result.AccountRole, ShouldEqual, "Viewer")
|
||||
So(query.Result.OrgName, ShouldEqual, "ac1@test.com")
|
||||
So(query.Result.OrgRole, ShouldEqual, "Viewer")
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Cannot delete last admin account user", func() {
|
||||
cmd := m.RemoveAccountUserCommand{AccountId: ac1.AccountId, UserId: ac1.Id}
|
||||
err := RemoveAccountUser(&cmd)
|
||||
So(err, ShouldEqual, m.ErrLastAccountAdmin)
|
||||
cmd := m.RemoveOrgUserCommand{OrgId: ac1.OrgId, UserId: ac1.Id}
|
||||
err := RemoveOrgUser(&cmd)
|
||||
So(err, ShouldEqual, m.ErrLastOrgAdmin)
|
||||
})
|
||||
})
|
||||
})
|
67
pkg/services/sqlstore/org_users.go
Normal file
67
pkg/services/sqlstore/org_users.go
Normal file
@ -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
|
||||
})
|
||||
}
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
|
||||
@ -73,7 +74,7 @@ func SetEngine(engine *xorm.Engine, enableLog bool) (err error) {
|
||||
|
||||
migrator := migrator.NewMigrator(x)
|
||||
migrator.LogLevel = log.INFO
|
||||
addMigrations(migrator)
|
||||
migrations.AddMigrations(migrator)
|
||||
|
||||
if err := migrator.Start(); err != nil {
|
||||
return fmt.Errorf("Sqlstore::Migration failed err: %v\n", err)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -15,59 +16,64 @@ import (
|
||||
|
||||
func init() {
|
||||
bus.AddHandler("sql", CreateUser)
|
||||
bus.AddHandler("sql", GetUserById)
|
||||
bus.AddHandler("sql", UpdateUser)
|
||||
bus.AddHandler("sql", ChangeUserPassword)
|
||||
bus.AddHandler("sql", GetUserByLogin)
|
||||
bus.AddHandler("sql", SetUsingAccount)
|
||||
bus.AddHandler("sql", SetUsingOrg)
|
||||
bus.AddHandler("sql", GetUserInfo)
|
||||
bus.AddHandler("sql", GetSignedInUser)
|
||||
bus.AddHandler("sql", SearchUsers)
|
||||
bus.AddHandler("sql", GetUserAccounts)
|
||||
bus.AddHandler("sql", GetUserOrgList)
|
||||
bus.AddHandler("sql", DeleteUser)
|
||||
bus.AddHandler("sql", SetUsingOrg)
|
||||
bus.AddHandler("sql", UpdateUserPermissions)
|
||||
}
|
||||
|
||||
func getAccountIdForNewUser(userEmail string, sess *session) (int64, error) {
|
||||
var account m.Account
|
||||
func getOrgIdForNewUser(userEmail string, sess *session) (int64, error) {
|
||||
var org m.Org
|
||||
|
||||
if setting.SingleAccountMode {
|
||||
has, err := sess.Where("name=?", setting.DefaultAccountName).Get(&account)
|
||||
if setting.SingleOrgMode {
|
||||
has, err := sess.Where("name=?", setting.DefaultOrgName).Get(&org)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if has {
|
||||
return account.Id, nil
|
||||
return org.Id, nil
|
||||
} else {
|
||||
account.Name = setting.DefaultAccountName
|
||||
org.Name = setting.DefaultOrgName
|
||||
}
|
||||
} else {
|
||||
account.Name = userEmail
|
||||
org.Name = userEmail
|
||||
}
|
||||
|
||||
account.Created = time.Now()
|
||||
account.Updated = time.Now()
|
||||
org.Created = time.Now()
|
||||
org.Updated = time.Now()
|
||||
|
||||
if _, err := sess.Insert(&account); err != nil {
|
||||
if _, err := sess.Insert(&org); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return account.Id, nil
|
||||
return org.Id, nil
|
||||
}
|
||||
|
||||
func CreateUser(cmd *m.CreateUserCommand) error {
|
||||
return inTransaction2(func(sess *session) error {
|
||||
accountId, err := getAccountIdForNewUser(cmd.Email, sess)
|
||||
orgId, err := getOrgIdForNewUser(cmd.Email, sess)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create user
|
||||
user := m.User{
|
||||
Email: cmd.Email,
|
||||
Name: cmd.Name,
|
||||
Login: cmd.Login,
|
||||
Company: cmd.Company,
|
||||
IsAdmin: cmd.IsAdmin,
|
||||
AccountId: accountId,
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
Email: cmd.Email,
|
||||
Name: cmd.Name,
|
||||
Login: cmd.Login,
|
||||
Company: cmd.Company,
|
||||
IsAdmin: cmd.IsAdmin,
|
||||
OrgId: orgId,
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
if len(cmd.Password) > 0 {
|
||||
@ -82,20 +88,20 @@ func CreateUser(cmd *m.CreateUserCommand) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// create account user link
|
||||
accountUser := m.AccountUser{
|
||||
AccountId: accountId,
|
||||
UserId: user.Id,
|
||||
Role: m.ROLE_ADMIN,
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
// create org user link
|
||||
orgUser := m.OrgUser{
|
||||
OrgId: orgId,
|
||||
UserId: user.Id,
|
||||
Role: m.ROLE_ADMIN,
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
if setting.SingleAccountMode && !user.IsAdmin {
|
||||
accountUser.Role = m.RoleType(setting.DefaultAccountRole)
|
||||
if setting.SingleOrgMode && !user.IsAdmin {
|
||||
orgUser.Role = m.RoleType(setting.DefaultOrgRole)
|
||||
}
|
||||
|
||||
if _, err = sess.Insert(&accountUser); err != nil {
|
||||
if _, err = sess.Insert(&orgUser); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -112,16 +118,31 @@ func CreateUser(cmd *m.CreateUserCommand) error {
|
||||
})
|
||||
}
|
||||
|
||||
func GetUserById(query *m.GetUserByIdQuery) error {
|
||||
user := new(m.User)
|
||||
has, err := x.Id(query.Id).Get(user)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
} else if has == false {
|
||||
return m.ErrUserNotFound
|
||||
}
|
||||
|
||||
query.Result = user
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetUserByLogin(query *m.GetUserByLoginQuery) error {
|
||||
if query.LoginOrEmail == "" {
|
||||
return m.ErrAccountNotFound
|
||||
return m.ErrUserNotFound
|
||||
}
|
||||
|
||||
user := new(m.User)
|
||||
if strings.Contains(query.LoginOrEmail, "@") {
|
||||
user = &m.User{Email: query.LoginOrEmail}
|
||||
} else {
|
||||
user = &m.User{Login: strings.ToLower(query.LoginOrEmail)}
|
||||
user = &m.User{Login: query.LoginOrEmail}
|
||||
}
|
||||
|
||||
has, err := x.Get(user)
|
||||
@ -163,12 +184,28 @@ func UpdateUser(cmd *m.UpdateUserCommand) error {
|
||||
})
|
||||
}
|
||||
|
||||
func SetUsingAccount(cmd *m.SetUsingAccountCommand) error {
|
||||
func ChangeUserPassword(cmd *m.ChangeUserPasswordCommand) error {
|
||||
return inTransaction2(func(sess *session) error {
|
||||
|
||||
user := m.User{
|
||||
Password: cmd.NewPassword,
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
if _, err := sess.Id(cmd.UserId).Update(&user); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func SetUsingOrg(cmd *m.SetUsingOrgCommand) error {
|
||||
return inTransaction(func(sess *xorm.Session) error {
|
||||
user := m.User{}
|
||||
sess.Id(cmd.UserId).Get(&user)
|
||||
|
||||
user.AccountId = cmd.AccountId
|
||||
user.OrgId = cmd.OrgId
|
||||
_, err := sess.Id(user.Id).Update(&user)
|
||||
return err
|
||||
})
|
||||
@ -193,12 +230,12 @@ func GetUserInfo(query *m.GetUserInfoQuery) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func GetUserAccounts(query *m.GetUserAccountsQuery) error {
|
||||
query.Result = make([]*m.UserAccountDTO, 0)
|
||||
sess := x.Table("account_user")
|
||||
sess.Join("INNER", "account", "account_user.account_id=account.id")
|
||||
sess.Where("account_user.user_id=?", query.UserId)
|
||||
sess.Cols("account.name", "account_user.role", "account_user.account_id")
|
||||
func GetUserOrgList(query *m.GetUserOrgListQuery) error {
|
||||
query.Result = make([]*m.UserOrgDTO, 0)
|
||||
sess := x.Table("org_user")
|
||||
sess.Join("INNER", "org", "org_user.org_id=org.id")
|
||||
sess.Where("org_user.user_id=?", query.UserId)
|
||||
sess.Cols("org.name", "org_user.role", "org_user.org_id")
|
||||
err := sess.Find(&query.Result)
|
||||
return err
|
||||
}
|
||||
@ -210,12 +247,12 @@ func GetSignedInUser(query *m.GetSignedInUserQuery) error {
|
||||
u.email as email,
|
||||
u.login as login,
|
||||
u.name as name,
|
||||
account.name as account_name,
|
||||
account_user.role as account_role,
|
||||
account.id as account_id
|
||||
org.name as org_name,
|
||||
org_user.role as org_role,
|
||||
org.id as org_id
|
||||
FROM ` + dialect.Quote("user") + ` as u
|
||||
LEFT OUTER JOIN account_user on account_user.account_id = u.account_id and account_user.user_id = u.id
|
||||
LEFT OUTER JOIN account on account.id = u.account_id
|
||||
LEFT OUTER JOIN org_user on org_user.org_id = u.org_id and org_user.user_id = u.id
|
||||
LEFT OUTER JOIN org on org.id = u.org_id
|
||||
WHERE u.id=?`
|
||||
|
||||
var user m.SignedInUser
|
||||
@ -240,3 +277,23 @@ func SearchUsers(query *m.SearchUsersQuery) error {
|
||||
err := sess.Find(&query.Result)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteUser(cmd *m.DeleteUserCommand) error {
|
||||
return inTransaction(func(sess *xorm.Session) error {
|
||||
var rawSql = fmt.Sprintf("DELETE FROM %s WHERE id=?", x.Dialect().Quote("user"))
|
||||
_, err := sess.Exec(rawSql, cmd.UserId)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func UpdateUserPermissions(cmd *m.UpdateUserPermissionsCommand) error {
|
||||
return inTransaction(func(sess *xorm.Session) error {
|
||||
user := m.User{}
|
||||
sess.Id(cmd.UserId).Get(&user)
|
||||
|
||||
user.IsAdmin = cmd.IsGrafanaAdmin
|
||||
sess.UseBool("is_admin")
|
||||
_, err := sess.Id(user.Id).Update(&user)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
@ -65,18 +66,18 @@ var (
|
||||
CookieRememberName string
|
||||
DisableUserSignUp bool
|
||||
|
||||
// single account
|
||||
SingleAccountMode bool
|
||||
DefaultAccountName string
|
||||
DefaultAccountRole string
|
||||
// single organization
|
||||
SingleOrgMode bool
|
||||
DefaultOrgName string
|
||||
DefaultOrgRole string
|
||||
|
||||
// Http auth
|
||||
AdminUser string
|
||||
AdminPassword string
|
||||
|
||||
AnonymousEnabled bool
|
||||
AnonymousAccountName string
|
||||
AnonymousAccountRole string
|
||||
AnonymousEnabled bool
|
||||
AnonymousOrgName string
|
||||
AnonymousOrgRole string
|
||||
|
||||
// Session settings.
|
||||
SessionOptions session.Options
|
||||
@ -98,15 +99,10 @@ var (
|
||||
func init() {
|
||||
IsWindows = runtime.GOOS == "windows"
|
||||
log.NewLogger(0, "console", `{"level": 0}`)
|
||||
}
|
||||
|
||||
func getWorkDir() string {
|
||||
p, _ := filepath.Abs(".")
|
||||
return p
|
||||
WorkDir, _ = filepath.Abs(".")
|
||||
}
|
||||
|
||||
func findConfigFiles() []string {
|
||||
WorkDir = getWorkDir()
|
||||
ConfRootPath = path.Join(WorkDir, "conf")
|
||||
filenames := make([]string, 0)
|
||||
|
||||
@ -152,10 +148,29 @@ func ToAbsUrl(relativeUrl string) string {
|
||||
return AppUrl + relativeUrl
|
||||
}
|
||||
|
||||
func NewConfigContext() {
|
||||
func loadEnvVariableOverrides() {
|
||||
for _, section := range Cfg.Sections() {
|
||||
for _, key := range section.Keys() {
|
||||
sectionName := strings.ToUpper(strings.Replace(section.Name(), ".", "_", -1))
|
||||
keyName := strings.ToUpper(strings.Replace(key.Name(), ".", "_", -1))
|
||||
envKey := fmt.Sprintf("GF_%s_%s", sectionName, keyName)
|
||||
envValue := os.Getenv(envKey)
|
||||
|
||||
if len(envValue) > 0 {
|
||||
log.Info("Setting: ENV override found: %s", envKey)
|
||||
key.SetValue(envValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewConfigContext(config string) {
|
||||
configFiles := findConfigFiles()
|
||||
|
||||
//log.Info("Loading config files: %v", configFiles)
|
||||
if config != "" {
|
||||
configFiles = append(configFiles, config)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
for i, file := range configFiles {
|
||||
@ -170,6 +185,8 @@ func NewConfigContext() {
|
||||
}
|
||||
}
|
||||
|
||||
loadEnvVariableOverrides()
|
||||
|
||||
AppName = Cfg.Section("").Key("app_name").MustString("Grafana")
|
||||
Env = Cfg.Section("").Key("app_mode").MustString("development")
|
||||
|
||||
@ -187,11 +204,6 @@ func NewConfigContext() {
|
||||
HttpAddr = server.Key("http_addr").MustString("0.0.0.0")
|
||||
HttpPort = server.Key("http_port").MustString("3000")
|
||||
|
||||
port := os.Getenv("PORT")
|
||||
if port != "" {
|
||||
HttpPort = port
|
||||
}
|
||||
|
||||
StaticRootPath = server.Key("static_root_path").MustString(path.Join(WorkDir, "webapp"))
|
||||
RouterLogging = server.Key("router_logging").MustBool(false)
|
||||
EnableGzip = server.Key("enable_gzip").MustBool(false)
|
||||
@ -208,14 +220,14 @@ func NewConfigContext() {
|
||||
AdminPassword = security.Key("admin_password").String()
|
||||
|
||||
// single account
|
||||
SingleAccountMode = Cfg.Section("account.single").Key("enabled").MustBool(false)
|
||||
DefaultAccountName = Cfg.Section("account.single").Key("account_name").MustString("main")
|
||||
DefaultAccountRole = Cfg.Section("account.single").Key("default_role").In("Editor", []string{"Editor", "Admin", "Viewer"})
|
||||
SingleOrgMode = Cfg.Section("organization.single").Key("enabled").MustBool(false)
|
||||
DefaultOrgName = Cfg.Section("organization.single").Key("org_name").MustString("main")
|
||||
DefaultOrgRole = Cfg.Section("organization.single").Key("default_role").In("Editor", []string{"Editor", "Admin", "Viewer"})
|
||||
|
||||
// anonymous access
|
||||
AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false)
|
||||
AnonymousAccountName = Cfg.Section("auth.anonymous").Key("account_name").String()
|
||||
AnonymousAccountRole = Cfg.Section("auth.anonymous").Key("account_role").String()
|
||||
AnonymousOrgName = Cfg.Section("auth.anonymous").Key("org_name").String()
|
||||
AnonymousOrgRole = Cfg.Section("auth.anonymous").Key("org_role").String()
|
||||
|
||||
// PhantomJS rendering
|
||||
ImagesDir = "data/png"
|
||||
|
32
pkg/setting/setting_test.go
Normal file
32
pkg/setting/setting_test.go
Normal file
@ -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")
|
||||
})
|
||||
|
||||
})
|
||||
}
|
@ -168,14 +168,22 @@ function($, _, moment) {
|
||||
if(_.isDate(text)) {
|
||||
return text;
|
||||
}
|
||||
var time,
|
||||
mathString = "",
|
||||
index,
|
||||
parseString;
|
||||
|
||||
var time;
|
||||
var mathString = "";
|
||||
var index;
|
||||
var parseString;
|
||||
|
||||
if (text.substring(0,3) === "now") {
|
||||
time = new Date();
|
||||
mathString = text.substring("now".length);
|
||||
} else {
|
||||
mathString = text.substring(3);
|
||||
}
|
||||
else if (text.substring(0,5) === 'today') {
|
||||
time = new Date();
|
||||
time.setHours(0,0,0,0);
|
||||
mathString = text.substring(5);
|
||||
}
|
||||
else {
|
||||
index = text.indexOf("||");
|
||||
parseString;
|
||||
if (index === -1) {
|
||||
@ -197,6 +205,11 @@ function($, _, moment) {
|
||||
return kbn.parseDateMath(mathString, time);
|
||||
};
|
||||
|
||||
kbn._timespanRegex = /^\d+[h,m,M,w,s,H,d]$/;
|
||||
kbn.isValidTimeSpan = function(str) {
|
||||
return kbn._timespanRegex.test(str);
|
||||
};
|
||||
|
||||
kbn.parseDateMath = function(mathString, time, roundUp) {
|
||||
var dateTime = moment(time);
|
||||
for (var i = 0; i < mathString.length;) {
|
||||
@ -321,11 +334,15 @@ function($, _, moment) {
|
||||
}
|
||||
|
||||
var steps = 0;
|
||||
var limit = extArray.length;
|
||||
|
||||
while (Math.abs(size) >= factor) {
|
||||
steps++;
|
||||
size /= factor;
|
||||
|
||||
if (steps >= limit) { return "NA"; }
|
||||
}
|
||||
|
||||
if (steps > 0) {
|
||||
decimals = scaledDecimals + (3 * steps);
|
||||
}
|
||||
@ -367,10 +384,21 @@ function($, _, moment) {
|
||||
kbn.valueFormats.bps = kbn.formatFuncCreator(1000, [' bps', ' Kbps', ' Mbps', ' Gbps', ' Tbps', ' Pbps', ' Ebps', ' Zbps', ' Ybps']);
|
||||
kbn.valueFormats.Bps = kbn.formatFuncCreator(1000, [' Bps', ' KBps', ' MBps', ' GBps', ' TBps', ' PBps', ' EBps', ' ZBps', ' YBps']);
|
||||
kbn.valueFormats.short = kbn.formatFuncCreator(1000, ['', ' K', ' Mil', ' Bil', ' Tri', ' Qaudr', ' Quint', ' Sext', ' Sept']);
|
||||
kbn.valueFormats.joule = kbn.formatFuncCreator(1000, [' J', ' kJ', ' MJ', 'GJ', 'TJ', 'PJ', 'EJ', 'ZJ', 'YJ']);
|
||||
kbn.valueFormats.watt = kbn.formatFuncCreator(1000, [' W', ' kW', ' MW', 'GW', 'TW', 'PW', 'EW', 'ZW', 'YW']);
|
||||
kbn.valueFormats.joule = kbn.formatFuncCreator(1000, [' J', ' kJ', ' MJ', ' GJ', ' TJ', ' PJ', ' EJ', ' ZJ', ' YJ']);
|
||||
kbn.valueFormats.watt = kbn.formatFuncCreator(1000, [' W', ' kW', ' MW', ' GW', ' TW', ' PW', ' EW', ' ZW', ' YW']);
|
||||
kbn.valueFormats.kwatt = kbn.formatFuncCreator(1000, [' kW', ' MW', ' GW', ' TW', ' PW', ' EW', ' ZW', ' YW']);
|
||||
kbn.valueFormats.watth = kbn.formatFuncCreator(1000, [' Wh', ' kWh', ' MWh', ' GWh', ' TWh', ' PWh', ' EWh', ' ZWh', ' YWh']);
|
||||
kbn.valueFormats.kwatth = kbn.formatFuncCreator(1000, [' kWh', ' MWh', ' GWh', ' TWh', ' PWh', ' EWh', ' ZWh', ' YWh']);
|
||||
kbn.valueFormats.ev = kbn.formatFuncCreator(1000, [' eV', ' keV', ' MeV', 'GeV', 'TeV', 'PeV', 'EeV', 'ZeV', 'YeV']);
|
||||
kbn.valueFormats.none = kbn.toFixed;
|
||||
kbn.valueFormats.celsius = function(value, decimals) { return kbn.toFixed(value, decimals) + ' °C'; };
|
||||
kbn.valueFormats.farenheit = function(value, decimals) { return kbn.toFixed(value, decimals) + ' °F'; };
|
||||
kbn.valueFormats.humidity = function(value, decimals) { return kbn.toFixed(value, decimals) + ' %H'; };
|
||||
kbn.valueFormats.ppm = function(value, decimals) { return kbn.toFixed(value, decimals) + ' ppm'; };
|
||||
kbn.valueFormats.velocityms = function(value, decimals) { return kbn.toFixed(value, decimals) + ' m/s'; };
|
||||
kbn.valueFormats.velocitykmh = function(value, decimals) { return kbn.toFixed(value, decimals) + ' km/h'; };
|
||||
kbn.valueFormats.velocitymph = function(value, decimals) { return kbn.toFixed(value, decimals) + ' mph'; };
|
||||
kbn.valueFormats.velocityknot = function(value, decimals) { return kbn.toFixed(value, decimals) + ' kn'; };
|
||||
|
||||
kbn.valueFormats.ms = function(size, decimals, scaledDecimals) {
|
||||
if (size === null) { return ""; }
|
||||
@ -493,6 +521,7 @@ function($, _, moment) {
|
||||
{text: 'none' , value: 'none'},
|
||||
{text: 'short', value: 'short'},
|
||||
{text: 'percent', value: 'percent'},
|
||||
{text: 'ppm', value: 'ppm'},
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -509,8 +538,8 @@ function($, _, moment) {
|
||||
submenu: [
|
||||
{text: 'bits', value: 'bits'},
|
||||
{text: 'bytes', value: 'bytes'},
|
||||
{text: 'kilo bytes', value: 'kbytes'},
|
||||
{text: 'mega bytes', value: 'mbytes'},
|
||||
{text: 'kilobytes', value: 'kbytes'},
|
||||
{text: 'megabytes', value: 'mbytes'},
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -523,9 +552,29 @@ function($, _, moment) {
|
||||
{
|
||||
text: 'energy',
|
||||
submenu: [
|
||||
{text: 'watt', value: 'watt'},
|
||||
{text: 'joule', value: 'joule'},
|
||||
{text: 'eV', value: 'ev'},
|
||||
{text: 'watt (W)', value: 'watt'},
|
||||
{text: 'kilowatt (kW)', value: 'kwatt'},
|
||||
{text: 'watt-hour (Wh)', value: 'watth'},
|
||||
{text: 'kilowatt-hour (kWh)', value: 'kwatth'},
|
||||
{text: 'joule (J)', value: 'joule'},
|
||||
{text: 'electron volt (eV)', value: 'ev'},
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'weather',
|
||||
submenu: [
|
||||
{text: 'Celcius (°C)', value: 'celsius' },
|
||||
{text: 'Farenheit (°F)', value: 'farenheit'},
|
||||
{text: 'Humidity (%H)', value: 'humidity' },
|
||||
]
|
||||
},
|
||||
{
|
||||
text: 'velocity',
|
||||
submenu: [
|
||||
{text: 'm/s', value: 'velocityms' },
|
||||
{text: 'km/h', value: 'velocitykmh' },
|
||||
{text: 'mph', value: 'velocitymph' },
|
||||
{text: 'knot (kn)', value: 'velocityknot' },
|
||||
]
|
||||
},
|
||||
];
|
||||
|
@ -1,8 +1,7 @@
|
||||
define([
|
||||
'lodash',
|
||||
'crypto',
|
||||
],
|
||||
function (_, crypto) {
|
||||
function (_) {
|
||||
"use strict";
|
||||
|
||||
return function Settings (options) {
|
||||
@ -27,55 +26,31 @@ function (_, crypto) {
|
||||
playlist_timespan: "1m",
|
||||
unsaved_changes_warning: true,
|
||||
search: { max_results: 100 },
|
||||
admin: {},
|
||||
appSubUrl: ""
|
||||
};
|
||||
|
||||
var settings = _.extend({}, defaults, options);
|
||||
|
||||
var parseBasicAuth = function(datasource) {
|
||||
var passwordEnd = datasource.url.indexOf('@');
|
||||
if (passwordEnd > 0) {
|
||||
var userStart = datasource.url.indexOf('//') + 2;
|
||||
var userAndPassword = datasource.url.substring(userStart, passwordEnd);
|
||||
var bytes = crypto.charenc.Binary.stringToBytes(userAndPassword);
|
||||
datasource.basicAuth = crypto.util.bytesToBase64(bytes);
|
||||
|
||||
var urlHead = datasource.url.substring(0, userStart);
|
||||
datasource.url = urlHead + datasource.url.substring(passwordEnd + 1);
|
||||
}
|
||||
|
||||
return datasource;
|
||||
};
|
||||
|
||||
var parseMultipleHosts = function(datasource) {
|
||||
datasource.urls = _.map(datasource.url.split(","), function (url) { return url.trim(); });
|
||||
return datasource;
|
||||
};
|
||||
|
||||
// backward compatible with old config
|
||||
if (options.graphiteUrl) {
|
||||
settings.datasources.graphite = {
|
||||
type: 'graphite',
|
||||
url: options.graphiteUrl,
|
||||
default: true
|
||||
};
|
||||
}
|
||||
|
||||
if (options.elasticsearch) {
|
||||
settings.datasources.elasticsearch = {
|
||||
type: 'elasticsearch',
|
||||
url: options.elasticsearch,
|
||||
index: options.grafana_index,
|
||||
grafanaDB: true
|
||||
};
|
||||
}
|
||||
|
||||
_.each(settings.datasources, function(datasource, key) {
|
||||
datasource.name = key;
|
||||
if (datasource.url) { parseBasicAuth(datasource); }
|
||||
if (datasource.type === 'influxdb') { parseMultipleHosts(datasource); }
|
||||
});
|
||||
// var parseBasicAuth = function(datasource) {
|
||||
// var passwordEnd = datasource.url.indexOf('@');
|
||||
// if (passwordEnd > 0) {
|
||||
// var userStart = datasource.url.indexOf('//') + 2;
|
||||
// var userAndPassword = datasource.url.substring(userStart, passwordEnd);
|
||||
// var bytes = crypto.charenc.Binary.stringToBytes(userAndPassword);
|
||||
// datasource.basicAuth = crypto.util.bytesToBase64(bytes);
|
||||
//
|
||||
// var urlHead = datasource.url.substring(0, userStart);
|
||||
// datasource.url = urlHead + datasource.url.substring(passwordEnd + 1);
|
||||
// }
|
||||
//
|
||||
// return datasource;
|
||||
// };
|
||||
//
|
||||
// _.each(settings.datasources, function(datasource, key) {
|
||||
// datasource.name = key;
|
||||
// if (datasource.url) { parseBasicAuth(datasource); }
|
||||
// if (datasource.type === 'influxdb') { parseMultipleHosts(datasource); }
|
||||
// });
|
||||
|
||||
if (settings.plugins.panels) {
|
||||
_.extend(settings.panels, settings.plugins.panels);
|
||||
|
@ -3,7 +3,6 @@ define([
|
||||
'./pulldown',
|
||||
'./search',
|
||||
'./metricKeys',
|
||||
'./graphiteImport',
|
||||
'./inspectCtrl',
|
||||
'./jsonEditorCtrl',
|
||||
'./loginCtrl',
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
@ -2,9 +2,8 @@ define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'config',
|
||||
'jquery'
|
||||
],
|
||||
function (angular, _, config, $) {
|
||||
function (angular, _, config) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
@ -15,7 +14,7 @@ function (angular, _, config, $) {
|
||||
$scope.giveSearchFocus = 0;
|
||||
$scope.selectedIndex = -1;
|
||||
$scope.results = {dashboards: [], tags: [], metrics: []};
|
||||
$scope.query = { query: '' };
|
||||
$scope.query = { query: '', tag: '', starred: false };
|
||||
$scope.db = datasourceSrv.getGrafanaDB();
|
||||
$scope.currentSearchId = 0;
|
||||
|
||||
@ -29,7 +28,7 @@ function (angular, _, config, $) {
|
||||
|
||||
$scope.keyDown = function (evt) {
|
||||
if (evt.keyCode === 27) {
|
||||
$scope.appEvent('hide-dash-editor');
|
||||
$scope.dismiss();
|
||||
}
|
||||
if (evt.keyCode === 40) {
|
||||
$scope.moveSelection(1);
|
||||
@ -49,10 +48,7 @@ function (angular, _, config, $) {
|
||||
var selectedDash = $scope.results.dashboards[$scope.selectedIndex];
|
||||
if (selectedDash) {
|
||||
$location.search({});
|
||||
$location.path("/dashboard/db/" + selectedDash.slug);
|
||||
setTimeout(function() {
|
||||
$('body').click(); // hack to force dropdown to close;
|
||||
});
|
||||
$location.path(selectedDash.url);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -61,11 +57,6 @@ function (angular, _, config, $) {
|
||||
$scope.selectedIndex = Math.max(Math.min($scope.selectedIndex + direction, $scope.resultCount - 1), 0);
|
||||
};
|
||||
|
||||
$scope.goToDashboard = function(slug) {
|
||||
$location.search({});
|
||||
$location.path("/dashboard/db/" + slug);
|
||||
};
|
||||
|
||||
$scope.searchDashboards = function() {
|
||||
$scope.currentSearchId = $scope.currentSearchId + 1;
|
||||
var localSearchId = $scope.currentSearchId;
|
||||
@ -74,16 +65,24 @@ function (angular, _, config, $) {
|
||||
.then(function(results) {
|
||||
if (localSearchId < $scope.currentSearchId) { return; }
|
||||
|
||||
if ($scope.query.query === "" && !$scope.query.starred) {
|
||||
results.dashboards.unshift({ title: 'Home', url: config.appSubUrl + '/', isHome: true });
|
||||
}
|
||||
|
||||
$scope.results.dashboards = results.dashboards;
|
||||
$scope.results.tags = results.tags;
|
||||
$scope.resultCount = results.tagsOnly ? results.tags.length : results.dashboards.length;
|
||||
$scope.results.tags = results.tags;
|
||||
$scope.results.dashboards = _.map(results.dashboards, function(dash) {
|
||||
dash.url = 'dashboard/db/' + dash.slug;
|
||||
return dash;
|
||||
});
|
||||
|
||||
if ($scope.queryHasNoFilters()) {
|
||||
$scope.results.dashboards.unshift({ title: 'Home', url: config.appSubUrl + '/', isHome: true });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.queryHasNoFilters = function() {
|
||||
var query = $scope.query;
|
||||
return query.query === '' && query.starred === false && query.tag === '';
|
||||
};
|
||||
|
||||
$scope.filterByTag = function(tag, evt) {
|
||||
$scope.query.tag = tag;
|
||||
$scope.query.tagcloud = false;
|
||||
|
@ -9,87 +9,118 @@ function (angular, _, $, config) {
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('SideMenuCtrl', function($scope, $location, contextSrv) {
|
||||
module.controller('SideMenuCtrl', function($scope, $location, contextSrv, backendSrv) {
|
||||
|
||||
$scope.getUrl = function(url) {
|
||||
return config.appSubUrl + url;
|
||||
};
|
||||
|
||||
$scope.menu = [];
|
||||
$scope.menu.push({
|
||||
text: "Dashboards",
|
||||
icon: "fa fa-th-large",
|
||||
href: $scope.getUrl("/"),
|
||||
});
|
||||
|
||||
if (contextSrv.hasRole('Admin')) {
|
||||
$scope.menu.push({
|
||||
text: "Data Sources",
|
||||
icon: "fa fa-database",
|
||||
href: $scope.getUrl("/account/datasources"),
|
||||
$scope.setupMainNav = function() {
|
||||
$scope.mainLinks.push({
|
||||
text: "Dashboards",
|
||||
icon: "fa fa-fw fa-th-large",
|
||||
href: $scope.getUrl("/"),
|
||||
});
|
||||
$scope.menu.push({
|
||||
text: "Account", href: $scope.getUrl("/account"),
|
||||
requireRole: "Admin",
|
||||
icon: "fa fa-shield",
|
||||
});
|
||||
}
|
||||
|
||||
if (contextSrv.user.isGrafanaAdmin) {
|
||||
$scope.menu.push({
|
||||
text: "Admin", href: $scope.getUrl("/admin/users"),
|
||||
icon: "fa fa-cube",
|
||||
requireSignedIn: true,
|
||||
links: [
|
||||
{ text: 'Settings', href: $scope.getUrl("/admin/settings")},
|
||||
{ text: 'Users', href: $scope.getUrl("/admin/users"), icon: "fa fa-lock" },
|
||||
{ text: 'Log', href: "", icon: "fa fa-lock" },
|
||||
]
|
||||
});
|
||||
}
|
||||
if (contextSrv.hasRole('Admin')) {
|
||||
$scope.mainLinks.push({
|
||||
text: "Data Sources",
|
||||
icon: "fa fa-fw fa-database",
|
||||
href: $scope.getUrl("/datasources"),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.updateState = function() {
|
||||
var currentPath = config.appSubUrl + $location.path();
|
||||
var search = $location.search();
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
_.each(item.links, function(link) {
|
||||
link.active = false;
|
||||
|
||||
if (link.editview) {
|
||||
var params = {};
|
||||
_.each(search, function(value, key) {
|
||||
if (value !== null) { params[key] = value; }
|
||||
});
|
||||
|
||||
params.editview = link.editview;
|
||||
link.href = currentPath + '?' + $.param(params);
|
||||
backendSrv.get('/api/user/orgs').then(function(orgs) {
|
||||
_.each(orgs, function(org) {
|
||||
if (org.isUsing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (link.href === currentPath) {
|
||||
item.active = true;
|
||||
link.active = true;
|
||||
}
|
||||
$scope.orgMenu.push({
|
||||
text: "Switch to " + org.name,
|
||||
icon: "fa fa-fw fa-random",
|
||||
click: function() {
|
||||
$scope.switchOrg(org.orgId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$scope.orgMenu.push({
|
||||
text: "New Organization",
|
||||
icon: "fa fa-fw fa-plus",
|
||||
href: $scope.getUrl('/org/new')
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.switchOrg = function(orgId) {
|
||||
backendSrv.post('/api/user/using/' + orgId).then(function() {
|
||||
window.location.href = $scope.getUrl('/');
|
||||
});
|
||||
};
|
||||
|
||||
$scope.setupAdminNav = function() {
|
||||
$scope.systemSection = true;
|
||||
$scope.grafanaVersion = config.buildInfo.version;
|
||||
|
||||
$scope.mainLinks.push({
|
||||
text: "System info",
|
||||
icon: "fa fa-fw fa-info",
|
||||
href: $scope.getUrl("/admin/settings"),
|
||||
});
|
||||
|
||||
$scope.mainLinks.push({
|
||||
text: "Global Users",
|
||||
icon: "fa fa-fw fa-user",
|
||||
href: $scope.getUrl("/admin/users"),
|
||||
});
|
||||
|
||||
$scope.mainLinks.push({
|
||||
text: "Global Orgs",
|
||||
icon: "fa fa-fw fa-users",
|
||||
href: $scope.getUrl("/admin/orgs"),
|
||||
});
|
||||
};
|
||||
|
||||
$scope.updateMenu = function() {
|
||||
$scope.systemSection = false;
|
||||
$scope.mainLinks = [];
|
||||
$scope.orgMenu = [];
|
||||
|
||||
var currentPath = $location.path();
|
||||
if (currentPath.indexOf('/admin') === 0) {
|
||||
$scope.setupAdminNav();
|
||||
} else {
|
||||
$scope.setupMainNav();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.updateState();
|
||||
$scope.updateMenu();
|
||||
$scope.$on('$routeChangeSuccess', $scope.updateMenu);
|
||||
};
|
||||
});
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user