Commit 34cf305d by Torkel Ödegaard

Merge branch 'master' into dashboard_folders

parents 04da97bf 77136d7a
+++
title = "API Tutorial: How To Create API Tokens And Dashboards For A Specific Organization"
type = "docs"
keywords = ["grafana", "tutorials", "API", "Token", "Org", "Organization"]
[menu.docs]
parent = "tutorials"
weight = 10
+++
# API Tutorial: How To Create API Tokens And Dashboards For A Specific Organization
A common scenario is to want to via the Grafana API setup new Grafana organizations or to add dynamically generated dashboards to an existing organization.
## Authentication
There are two ways to authenticate against the API: basic authentication and API Tokens.
Some parts of the API are only available through basic authentication and these parts of the API usually require that the user is a Grafana Admin. But all organization actions are accessed via an API Token. An API Token is tied to an organization and can be used to create dashboards etc but only for that organization.
## How To Create A New Organization and an API Token
The task is to create a new organization and then add a Token that can be used by other users. In the examples below which use basic auth, the user is `admin` and the password is `admin`.
1. [Create the org](http://docs.grafana.org/http_api/org/#create-organisation). Here is an example using curl:
```
curl -X POST -H "Content-Type: application/json" -d '{"name":"apiorg"}' http://admin:admin@localhost:3000/api/orgs
```
This should return a response: `{"message":"Organization created","orgId":6}`. Use the orgId for the next steps.
2. Optional step. If the org was created previously and/or step 3 fails then first [add your Admin user to the org](http://docs.grafana.org/http_api/org/#add-user-in-organisation):
```
curl -X POST -H "Content-Type: application/json" -d '{"loginOrEmail":"admin", "role": "Admin"}' http://admin:admin@localhost:3000/api/orgs/<org id of new org>/users
```
3. [Switch the org context for the Admin user to the new org](http://docs.grafana.org/http_api/user/#switch-user-context):
```
curl -X POST http://admin:admin@localhost:3000/api/user/using/<id of new org>
```
4. [Create the API token](http://docs.grafana.org/http_api/auth/#create-api-key):
```
curl -X POST -H "Content-Type: application/json" -d '{"name":"apikeycurl", "role": "Admin"}' http://admin:admin@localhost:3000/api/auth/keys
```
This should return a response: `{"name":"apikeycurl","key":"eyJrIjoiR0ZXZmt1UFc0OEpIOGN5RWdUalBJTllUTk83VlhtVGwiLCJuIjoiYXBpa2V5Y3VybCIsImlkIjo2fQ=="}`.
Save the key returned here in your password manager as it is not possible to fetch again it in the future.
## How To Add A Dashboard
Using the Token that was created in the previous step, you can create a dashboard or carry out other actions without having to switch organizations.
1. [Add a dashboard](http://docs.grafana.org/http_api/dashboard/#create-update-dashboard) using the key (or bearer token as it is also called):
```
curl -X POST --insecure -H "Authorization: Bearer eyJrIjoiR0ZXZmt1UFc0OEpIOGN5RWdUalBJTllUTk83VlhtVGwiLCJuIjoiYXBpa2V5Y3VybCIsImlkIjo2fQ==" -H "Content-Type: application/json" -d '{
"dashboard": {
"id": null,
"title": "Production Overview",
"tags": [ "templated" ],
"timezone": "browser",
"rows": [
{
}
],
"schemaVersion": 6,
"version": 0
},
"overwrite": false
}' http://localhost:3000/api/dashboards/db
```
This import will not work if you exported the dashboard via the Share -> Export menu in the Grafana UI (it strips out data source names etc.). View the JSON and save it to a file instead or fetch the dashboard JSON via the API.
...@@ -61,7 +61,7 @@ func (hs *HttpServer) Start(ctx context.Context) error { ...@@ -61,7 +61,7 @@ func (hs *HttpServer) Start(ctx context.Context) error {
return nil return nil
} }
case setting.HTTPS: case setting.HTTPS:
err = hs.httpSrv.ListenAndServeTLS(setting.CertFile, setting.KeyFile) err = hs.listenAndServeTLS(setting.CertFile, setting.KeyFile)
if err == http.ErrServerClosed { if err == http.ErrServerClosed {
hs.log.Debug("server was shutdown gracefully") hs.log.Debug("server was shutdown gracefully")
return nil return nil
...@@ -92,7 +92,7 @@ func (hs *HttpServer) Shutdown(ctx context.Context) error { ...@@ -92,7 +92,7 @@ func (hs *HttpServer) Shutdown(ctx context.Context) error {
return err return err
} }
func (hs *HttpServer) listenAndServeTLS(listenAddr, certfile, keyfile string) error { func (hs *HttpServer) listenAndServeTLS(certfile, keyfile string) error {
if certfile == "" { if certfile == "" {
return fmt.Errorf("cert_file cannot be empty when using HTTPS") return fmt.Errorf("cert_file cannot be empty when using HTTPS")
} }
...@@ -127,14 +127,11 @@ func (hs *HttpServer) listenAndServeTLS(listenAddr, certfile, keyfile string) er ...@@ -127,14 +127,11 @@ func (hs *HttpServer) listenAndServeTLS(listenAddr, certfile, keyfile string) er
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
}, },
} }
srv := &http.Server{
Addr: listenAddr,
Handler: hs.macaron,
TLSConfig: tlsCfg,
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
}
return srv.ListenAndServeTLS(setting.CertFile, setting.KeyFile) hs.httpSrv.TLSConfig = tlsCfg
hs.httpSrv.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0)
return hs.httpSrv.ListenAndServeTLS(setting.CertFile, setting.KeyFile)
} }
func (hs *HttpServer) newMacaron() *macaron.Macaron { func (hs *HttpServer) newMacaron() *macaron.Macaron {
......
...@@ -73,7 +73,7 @@ func (m *MySqlMacroEngine) EvaluateMacro(name string, args []string) (string, er ...@@ -73,7 +73,7 @@ func (m *MySqlMacroEngine) EvaluateMacro(name string, args []string) (string, er
if len(args) == 0 { if len(args) == 0 {
return "", fmt.Errorf("missing time column argument for macro %v", name) return "", fmt.Errorf("missing time column argument for macro %v", name)
} }
return fmt.Sprintf("%s > FROM_UNIXTIME(%d) AND %s < FROM_UNIXTIME(%d)", args[0], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil return fmt.Sprintf("%s >= FROM_UNIXTIME(%d) AND %s <= FROM_UNIXTIME(%d)", args[0], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil
default: default:
return "", fmt.Errorf("Unknown macro %v", name) return "", fmt.Errorf("Unknown macro %v", name)
} }
......
...@@ -36,7 +36,7 @@ func TestMacroEngine(t *testing.T) { ...@@ -36,7 +36,7 @@ func TestMacroEngine(t *testing.T) {
sql, err := engine.Interpolate("WHERE $__timeFilter(time_column)") sql, err := engine.Interpolate("WHERE $__timeFilter(time_column)")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(sql, ShouldEqual, "WHERE time_column > FROM_UNIXTIME(18446744066914186738) AND time_column < FROM_UNIXTIME(18446744066914187038)") So(sql, ShouldEqual, "WHERE time_column >= FROM_UNIXTIME(18446744066914186738) AND time_column <= FROM_UNIXTIME(18446744066914187038)")
}) })
}) })
......
...@@ -77,7 +77,7 @@ export class SearchCtrl { ...@@ -77,7 +77,7 @@ export class SearchCtrl {
this.moveSelection(-1); this.moveSelection(-1);
} }
if (evt.keyCode === 13) { if (evt.keyCode === 13) {
if (this.$scope.tagMode) { if (this.tagsMode) {
var tag = this.results[this.selectedIndex]; var tag = this.results[this.selectedIndex];
if (tag) { if (tag) {
this.filterByTag(tag.term, null); this.filterByTag(tag.term, null);
......
...@@ -841,7 +841,7 @@ function($, _) { ...@@ -841,7 +841,7 @@ function($, _) {
{ {
text: 'temperature', text: 'temperature',
submenu: [ submenu: [
{text: 'Celcius (°C)', value: 'celsius' }, {text: 'Celsius (°C)', value: 'celsius' },
{text: 'Farenheit (°F)', value: 'farenheit' }, {text: 'Farenheit (°F)', value: 'farenheit' },
{text: 'Kelvin (K)', value: 'kelvin' }, {text: 'Kelvin (K)', value: 'kelvin' },
] ]
......
...@@ -46,7 +46,7 @@ Table: ...@@ -46,7 +46,7 @@ Table:
Macros: Macros:
- $__time(column) -&gt; UNIX_TIMESTAMP(column) as time_sec - $__time(column) -&gt; UNIX_TIMESTAMP(column) as time_sec
- $__timeFilter(column) -&gt; UNIX_TIMESTAMP(time_date_time) &gt; from AND UNIX_TIMESTAMP(time_date_time) &lt; 1492750877 - $__timeFilter(column) -&gt; UNIX_TIMESTAMP(time_date_time) &ge; from AND UNIX_TIMESTAMP(time_date_time) &le; 1492750877
</pre> </pre>
</div> </div>
......
...@@ -192,16 +192,16 @@ function pushToXBuckets(buckets, point, bucketNum, seriesName) { ...@@ -192,16 +192,16 @@ function pushToXBuckets(buckets, point, bucketNum, seriesName) {
if (value === null || value === undefined || isNaN(value)) { return; } if (value === null || value === undefined || isNaN(value)) { return; }
// Add series name to point for future identification // Add series name to point for future identification
point.push(seriesName); let point_ext = _.concat(point, seriesName);
if (buckets[bucketNum] && buckets[bucketNum].values) { if (buckets[bucketNum] && buckets[bucketNum].values) {
buckets[bucketNum].values.push(value); buckets[bucketNum].values.push(value);
buckets[bucketNum].points.push(point); buckets[bucketNum].points.push(point_ext);
} else { } else {
buckets[bucketNum] = { buckets[bucketNum] = {
x: bucketNum, x: bucketNum,
values: [value], values: [value],
points: [point] points: [point_ext]
}; };
} }
} }
...@@ -209,7 +209,7 @@ function pushToXBuckets(buckets, point, bucketNum, seriesName) { ...@@ -209,7 +209,7 @@ function pushToXBuckets(buckets, point, bucketNum, seriesName) {
function pushToYBuckets(buckets, bucketNum, value, point, bounds) { function pushToYBuckets(buckets, bucketNum, value, point, bounds) {
var count = 1; var count = 1;
// Use the 3rd argument as scale/count // Use the 3rd argument as scale/count
if (point.length > 2) { if (point.length > 3) {
count = parseInt(point[2]); count = parseInt(point[2]);
} }
if (buckets[bucketNum]) { if (buckets[bucketNum]) {
......
...@@ -172,9 +172,11 @@ export class HeatmapTooltip { ...@@ -172,9 +172,11 @@ export class HeatmapTooltip {
} }
barWidth = Math.max(barWidth, 1); barWidth = Math.max(barWidth, 1);
// Normalize histogram Y axis
let histogramDomain = _.reduce(_.map(histogramData, d => d[1]), (sum, val) => sum + val, 0);
let histYScale = d3.scaleLinear() let histYScale = d3.scaleLinear()
.domain([0, _.max(_.map(histogramData, d => d[1]))]) .domain([0, histogramDomain])
.range([0, HISTOGRAM_HEIGHT]); .range([0, HISTOGRAM_HEIGHT]);
let histogram = this.tooltip.select(".heatmap-histogram") let histogram = this.tooltip.select(".heatmap-histogram")
.append("svg") .append("svg")
......
...@@ -114,18 +114,11 @@ ...@@ -114,18 +114,11 @@
margin-bottom: 4px; margin-bottom: 4px;
@include left-brand-border(); @include left-brand-border();
.search-result-icon::before { &:hover,
content: "\f009"; &.selected {
}
&:hover {
background-color: $tight-form-func-bg; background-color: $tight-form-func-bg;
@include left-brand-border-gradient(); @include left-brand-border-gradient();
} }
&.selected {
background-color: $grafanaListBackground;
}
} }
.search-result-tags { .search-result-tags {
...@@ -138,6 +131,18 @@ ...@@ -138,6 +131,18 @@
} }
} }
.search-result-icon::before {
content: "\f009";
}
.search-item-dash-home .search-result-icon::before {
content: "\f015";
}
.search-item-dash-home .search-result-icon::before {
content: "\f015";
}
.search-item-child { .search-item-child {
margin-left: 20px; margin-left: 20px;
} }
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
.navbar-page-btn { .navbar-page-btn {
border-color: transparent; border-color: transparent;
background: transparent; background: transparent;
transform: translate3d(-95px, 0, 0); transform: translate3d(-50px, 0, 0);
transition: all 1.5s ease-in-out 1s; transition: all 1.5s ease-in-out 1s;
.icon-gf { .icon-gf {
opacity: 0; opacity: 0;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment