Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nexpie-grafana-theme
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kornkitt Poolsup
nexpie-grafana-theme
Commits
5a3c1dc6
Unverified
Commit
5a3c1dc6
authored
Dec 11, 2019
by
Andrej Ocenas
Committed by
GitHub
Dec 11, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Elastic: Add data links in datasource config (#20186)
parent
34299a1b
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
385 additions
and
102 deletions
+385
-102
public/app/core/logs_model.ts
+1
-0
public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.tsx
+14
-0
public/app/plugins/datasource/elasticsearch/configuration/DataLink.tsx
+83
-0
public/app/plugins/datasource/elasticsearch/configuration/DataLinks.test.tsx
+66
-0
public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx
+83
-0
public/app/plugins/datasource/elasticsearch/datasource.test.ts
+89
-66
public/app/plugins/datasource/elasticsearch/datasource.ts
+33
-3
public/app/plugins/datasource/elasticsearch/types.ts
+6
-0
public/app/plugins/datasource/graphite/dashboards/metrictank.json
+8
-31
public/app/plugins/datasource/graphite/plugin.json
+2
-2
No files found.
public/app/core/logs_model.ts
View file @
5a3c1dc6
...
...
@@ -202,6 +202,7 @@ export function dataFrameToLogsModel(dataFrame: DataFrame[], intervalMs: number,
// Create metrics from logs
logsModel
.
series
=
makeSeriesForLogs
(
logsModel
.
rows
,
intervalMs
,
timeZone
);
}
else
{
// We got metrics in the dataFrame so process those
logsModel
.
series
=
getGraphSeriesModel
(
metricSeries
,
timeZone
,
...
...
public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.tsx
View file @
5a3c1dc6
...
...
@@ -4,6 +4,7 @@ import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
import
{
ElasticsearchOptions
}
from
'../types'
;
import
{
defaultMaxConcurrentShardRequests
,
ElasticDetails
}
from
'./ElasticDetails'
;
import
{
LogsConfig
}
from
'./LogsConfig'
;
import
{
DataLinks
}
from
'./DataLinks'
;
export
type
Props
=
DataSourcePluginOptionsEditorProps
<
ElasticsearchOptions
>
;
export
const
ConfigEditor
=
(
props
:
Props
)
=>
{
...
...
@@ -46,6 +47,19 @@ export const ConfigEditor = (props: Props) => {
})
}
/>
<
DataLinks
value=
{
options
.
jsonData
.
dataLinks
}
onChange=
{
newValue
=>
{
onOptionsChange
({
...
options
,
jsonData
:
{
...
options
.
jsonData
,
dataLinks
:
newValue
,
},
});
}
}
/>
</>
);
};
public/app/plugins/datasource/elasticsearch/configuration/DataLink.tsx
0 → 100644
View file @
5a3c1dc6
import
React
from
'react'
;
import
{
css
}
from
'emotion'
;
import
{
Button
,
FormField
,
VariableSuggestion
,
DataLinkInput
,
stylesFactory
}
from
'@grafana/ui'
;
import
{
DataLinkConfig
}
from
'../types'
;
const
getStyles
=
stylesFactory
(()
=>
({
firstRow
:
css
`
display: flex;
`
,
nameField
:
css
`
flex: 2;
`
,
regexField
:
css
`
flex: 3;
`
,
}));
type
Props
=
{
value
:
DataLinkConfig
;
onChange
:
(
value
:
DataLinkConfig
)
=>
void
;
onDelete
:
()
=>
void
;
suggestions
:
VariableSuggestion
[];
className
?:
string
;
};
export
const
DataLink
=
(
props
:
Props
)
=>
{
const
{
value
,
onChange
,
onDelete
,
suggestions
,
className
}
=
props
;
const
styles
=
getStyles
();
const
handleChange
=
(
field
:
keyof
typeof
value
)
=>
(
event
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
onChange
({
...
value
,
[
field
]:
event
.
currentTarget
.
value
,
});
};
return
(
<
div
className=
{
className
}
>
<
div
className=
{
styles
.
firstRow
}
>
<
FormField
className=
{
styles
.
nameField
}
labelWidth=
{
6
}
// A bit of a hack to prevent using default value for the width from FormField
inputWidth=
{
null
}
label=
"Field"
type=
"text"
value=
{
value
.
field
}
tooltip=
{
'Can be exact field name or a regex pattern that will match on the field name.'
}
onChange=
{
handleChange
(
'field'
)
}
/>
<
Button
variant=
{
'inverse'
}
title=
"Remove field"
icon=
{
'fa fa-times'
}
onClick=
{
event
=>
{
event
.
preventDefault
();
onDelete
();
}
}
/>
</
div
>
<
FormField
label=
"URL"
labelWidth=
{
6
}
inputEl=
{
<
DataLinkInput
placeholder=
{
'http://example.com/${__value.raw}'
}
value=
{
value
.
url
||
''
}
onChange=
{
newValue
=>
onChange
({
...
value
,
url
:
newValue
,
})
}
suggestions=
{
suggestions
}
/>
}
className=
{
css
`
width: 100%;
`
}
/>
</
div
>
);
};
public/app/plugins/datasource/elasticsearch/configuration/DataLinks.test.tsx
0 → 100644
View file @
5a3c1dc6
import
React
from
'react'
;
import
{
mount
}
from
'enzyme'
;
import
{
DataLinks
}
from
'./DataLinks'
;
import
{
Button
}
from
'@grafana/ui'
;
import
{
DataLink
}
from
'./DataLink'
;
describe
(
'DataLinks'
,
()
=>
{
let
originalGetSelection
:
typeof
window
.
getSelection
;
beforeAll
(()
=>
{
originalGetSelection
=
window
.
getSelection
;
window
.
getSelection
=
()
=>
null
;
});
afterAll
(()
=>
{
window
.
getSelection
=
originalGetSelection
;
});
it
(
'renders correctly when no fields'
,
()
=>
{
const
wrapper
=
mount
(<
DataLinks
onChange=
{
()
=>
{}
}
/>);
expect
(
wrapper
.
find
(
Button
).
length
).
toBe
(
1
);
expect
(
wrapper
.
find
(
Button
).
contains
(
'Add'
)).
toBeTruthy
();
expect
(
wrapper
.
find
(
DataLink
).
length
).
toBe
(
0
);
});
it
(
'renders correctly when there are fields'
,
()
=>
{
const
wrapper
=
mount
(<
DataLinks
value=
{
testValue
}
onChange=
{
()
=>
{}
}
/>);
expect
(
wrapper
.
find
(
Button
).
filterWhere
(
button
=>
button
.
contains
(
'Add'
)).
length
).
toBe
(
1
);
expect
(
wrapper
.
find
(
DataLink
).
length
).
toBe
(
2
);
});
it
(
'adds new field'
,
()
=>
{
const
onChangeMock
=
jest
.
fn
();
const
wrapper
=
mount
(<
DataLinks
onChange=
{
onChangeMock
}
/>);
const
addButton
=
wrapper
.
find
(
Button
).
filterWhere
(
button
=>
button
.
contains
(
'Add'
));
addButton
.
simulate
(
'click'
);
expect
(
onChangeMock
.
mock
.
calls
[
0
][
0
].
length
).
toBe
(
1
);
});
it
(
'removes field'
,
()
=>
{
const
onChangeMock
=
jest
.
fn
();
const
wrapper
=
mount
(<
DataLinks
value=
{
testValue
}
onChange=
{
onChangeMock
}
/>);
const
removeButton
=
wrapper
.
find
(
DataLink
)
.
at
(
0
)
.
find
(
Button
);
removeButton
.
simulate
(
'click'
);
const
newValue
=
onChangeMock
.
mock
.
calls
[
0
][
0
];
expect
(
newValue
.
length
).
toBe
(
1
);
expect
(
newValue
[
0
]).
toMatchObject
({
field
:
'regex2'
,
url
:
'localhost2'
,
});
});
});
const
testValue
=
[
{
field
:
'regex1'
,
url
:
'localhost1'
,
},
{
field
:
'regex2'
,
url
:
'localhost2'
,
},
];
public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx
0 → 100644
View file @
5a3c1dc6
import
React
from
'react'
;
import
{
css
}
from
'emotion'
;
import
{
Button
,
DataLinkBuiltInVars
,
stylesFactory
,
useTheme
,
VariableOrigin
}
from
'@grafana/ui'
;
import
{
GrafanaTheme
}
from
'@grafana/data'
;
import
{
DataLinkConfig
}
from
'../types'
;
import
{
DataLink
}
from
'./DataLink'
;
const
getStyles
=
stylesFactory
((
theme
:
GrafanaTheme
)
=>
({
infoText
:
css
`
padding-bottom:
${
theme
.
spacing
.
md
}
;
color:
${
theme
.
colors
.
textWeak
}
;
`
,
dataLink
:
css
`
margin-bottom:
${
theme
.
spacing
.
sm
}
;
`
,
}));
type
Props
=
{
value
?:
DataLinkConfig
[];
onChange
:
(
value
:
DataLinkConfig
[])
=>
void
;
};
export
const
DataLinks
=
(
props
:
Props
)
=>
{
const
{
value
,
onChange
}
=
props
;
const
theme
=
useTheme
();
const
styles
=
getStyles
(
theme
);
return
(
<>
<
h3
className=
"page-heading"
>
Data links
</
h3
>
<
div
className=
{
styles
.
infoText
}
>
Add links to existing fields. Links will be shown in log row details next to the field value.
</
div
>
<
div
className=
"gf-form-group"
>
{
value
&&
value
.
map
((
field
,
index
)
=>
{
return
(
<
DataLink
className=
{
styles
.
dataLink
}
key=
{
index
}
value=
{
field
}
onChange=
{
newField
=>
{
const
newDataLinks
=
[...
value
];
newDataLinks
.
splice
(
index
,
1
,
newField
);
onChange
(
newDataLinks
);
}
}
onDelete=
{
()
=>
{
const
newDataLinks
=
[...
value
];
newDataLinks
.
splice
(
index
,
1
);
onChange
(
newDataLinks
);
}
}
suggestions=
{
[
{
value
:
DataLinkBuiltInVars
.
valueRaw
,
label
:
'Raw value'
,
documentation
:
'Raw value of the field'
,
origin
:
VariableOrigin
.
Value
,
},
]
}
/>
);
})
}
<
div
>
<
Button
variant=
{
'inverse'
}
className=
{
css
`
margin-right: 10px;
`
}
icon=
"fa fa-plus"
onClick=
{
event
=>
{
event
.
preventDefault
();
const
newDataLinks
=
[...(
value
||
[]),
{
field
:
''
,
url
:
''
}];
onChange
(
newDataLinks
);
}
}
>
Add
</
Button
>
</
div
>
</
div
>
</>
);
};
public/app/plugins/datasource/elasticsearch/
specs/
datasource.test.ts
→
public/app/plugins/datasource/elasticsearch/datasource.test.ts
View file @
5a3c1dc6
import
angular
from
'angular'
;
import
{
dateMath
}
from
'@grafana/data'
;
import
{
dateMath
,
Field
}
from
'@grafana/data'
;
import
_
from
'lodash'
;
import
{
ElasticDatasource
}
from
'.
.
/datasource'
;
import
{
ElasticDatasource
}
from
'./datasource'
;
import
{
toUtc
,
dateTime
}
from
'@grafana/data'
;
import
{
BackendSrv
}
from
'app/core/services/backend_srv'
;
import
{
TimeSrv
}
from
'app/features/dashboard/services/TimeSrv'
;
import
{
TemplateSrv
}
from
'app/features/templating/template_srv'
;
import
{
DataSourceInstanceSettings
}
from
'@grafana/data'
;
import
{
ElasticsearchOptions
}
from
'.
.
/types'
;
import
{
ElasticsearchOptions
}
from
'./types'
;
describe
(
'ElasticDatasource'
,
function
(
this
:
any
)
{
const
backendSrv
:
any
=
{
...
...
@@ -153,73 +153,23 @@ describe('ElasticDatasource', function(this: any) {
});
describe
(
'When issuing logs query with interval pattern'
,
()
=>
{
let
query
,
queryBuilderSpy
:
any
;
beforeEach
(
async
()
=>
{
async
function
setupDataSource
(
jsonData
?:
Partial
<
ElasticsearchOptions
>
)
{
createDatasource
({
url
:
'http://es.com'
,
database
:
'mock-index'
,
jsonData
:
{
interval
:
'Daily'
,
esVersion
:
2
,
timeField
:
'@timestamp'
}
as
ElasticsearchOptions
,
jsonData
:
{
interval
:
'Daily'
,
esVersion
:
2
,
timeField
:
'@timestamp'
,
...(
jsonData
||
{}),
}
as
ElasticsearchOptions
,
}
as
DataSourceInstanceSettings
<
ElasticsearchOptions
>
);
ctx
.
backendSrv
.
datasourceRequest
=
jest
.
fn
(
options
=>
{
return
Promise
.
resolve
({
data
:
{
responses
:
[
{
aggregations
:
{
'2'
:
{
buckets
:
[
{
doc_count
:
10
,
key
:
1000
,
},
{
doc_count
:
15
,
key
:
2000
,
},
],
},
},
hits
:
{
hits
:
[
{
'@timestamp'
:
[
'2019-06-24T09:51:19.765Z'
],
_id
:
'fdsfs'
,
_type
:
'_doc'
,
_index
:
'mock-index'
,
_source
:
{
'@timestamp'
:
'2019-06-24T09:51:19.765Z'
,
host
:
'djisaodjsoad'
,
message
:
'hello, i am a message'
,
},
fields
:
{
'@timestamp'
:
[
'2019-06-24T09:51:19.765Z'
],
},
},
{
'@timestamp'
:
[
'2019-06-24T09:52:19.765Z'
],
_id
:
'kdospaidopa'
,
_type
:
'_doc'
,
_index
:
'mock-index'
,
_source
:
{
'@timestamp'
:
'2019-06-24T09:52:19.765Z'
,
host
:
'dsalkdakdop'
,
message
:
'hello, i am also message'
,
},
fields
:
{
'@timestamp'
:
[
'2019-06-24T09:52:19.765Z'
],
},
},
],
},
},
],
},
});
return
Promise
.
resolve
(
logsResponse
);
});
query
=
{
const
query
=
{
range
:
{
from
:
toUtc
([
2015
,
4
,
30
,
10
]),
to
:
toUtc
([
2019
,
7
,
1
,
10
]),
...
...
@@ -238,13 +188,31 @@ describe('ElasticDatasource', function(this: any) {
],
};
queryBuilderSpy
=
jest
.
spyOn
(
ctx
.
ds
.
queryBuilder
,
'getLogsQuery'
);
await
ctx
.
ds
.
query
(
query
);
});
const
queryBuilderSpy
=
jest
.
spyOn
(
ctx
.
ds
.
queryBuilder
,
'getLogsQuery'
);
const
response
=
await
ctx
.
ds
.
query
(
query
);
return
{
queryBuilderSpy
,
response
};
}
it
(
'should call getLogsQuery()'
,
()
=>
{
it
(
'should call getLogsQuery()'
,
async
()
=>
{
const
{
queryBuilderSpy
}
=
await
setupDataSource
();
expect
(
queryBuilderSpy
).
toHaveBeenCalled
();
});
it
(
'should enhance fields with links'
,
async
()
=>
{
const
{
response
}
=
await
setupDataSource
({
dataLinks
:
[
{
field
:
'host'
,
url
:
'http://localhost:3000/${__value.raw}'
,
},
],
});
// 1 for logs and 1 for counts.
expect
(
response
.
data
.
length
).
toBe
(
2
);
const
links
=
response
.
data
[
0
].
fields
.
find
((
field
:
Field
)
=>
field
.
name
===
'host'
).
config
.
links
;
expect
(
links
.
length
).
toBe
(
1
);
expect
(
links
[
0
].
url
).
toBe
(
'http://localhost:3000/${__value.raw}'
);
});
});
describe
(
'When issuing document query'
,
()
=>
{
...
...
@@ -645,3 +613,58 @@ describe('ElasticDatasource', function(this: any) {
});
});
});
const
logsResponse
=
{
data
:
{
responses
:
[
{
aggregations
:
{
'2'
:
{
buckets
:
[
{
doc_count
:
10
,
key
:
1000
,
},
{
doc_count
:
15
,
key
:
2000
,
},
],
},
},
hits
:
{
hits
:
[
{
'@timestamp'
:
[
'2019-06-24T09:51:19.765Z'
],
_id
:
'fdsfs'
,
_type
:
'_doc'
,
_index
:
'mock-index'
,
_source
:
{
'@timestamp'
:
'2019-06-24T09:51:19.765Z'
,
host
:
'djisaodjsoad'
,
message
:
'hello, i am a message'
,
},
fields
:
{
'@timestamp'
:
[
'2019-06-24T09:51:19.765Z'
],
},
},
{
'@timestamp'
:
[
'2019-06-24T09:52:19.765Z'
],
_id
:
'kdospaidopa'
,
_type
:
'_doc'
,
_index
:
'mock-index'
,
_source
:
{
'@timestamp'
:
'2019-06-24T09:52:19.765Z'
,
host
:
'dsalkdakdop'
,
message
:
'hello, i am also message'
,
},
fields
:
{
'@timestamp'
:
[
'2019-06-24T09:52:19.765Z'
],
},
},
],
},
},
],
},
};
public/app/plugins/datasource/elasticsearch/datasource.ts
View file @
5a3c1dc6
import
angular
from
'angular'
;
import
_
from
'lodash'
;
import
{
DataSourceApi
,
DataSourceInstanceSettings
,
DataQueryRequest
,
DataQueryResponse
}
from
'@grafana/data'
;
import
{
DataSourceApi
,
DataSourceInstanceSettings
,
DataQueryRequest
,
DataQueryResponse
,
DataFrame
,
}
from
'@grafana/data'
;
import
{
ElasticResponse
}
from
'./elastic_response'
;
import
{
IndexPattern
}
from
'./index_pattern'
;
import
{
ElasticQueryBuilder
}
from
'./query_builder'
;
...
...
@@ -9,7 +15,7 @@ import * as queryDef from './query_def';
import
{
BackendSrv
}
from
'app/core/services/backend_srv'
;
import
{
TemplateSrv
}
from
'app/features/templating/template_srv'
;
import
{
TimeSrv
}
from
'app/features/dashboard/services/TimeSrv'
;
import
{
ElasticsearchOptions
,
ElasticsearchQuery
}
from
'./types'
;
import
{
DataLinkConfig
,
ElasticsearchOptions
,
ElasticsearchQuery
}
from
'./types'
;
export
class
ElasticDatasource
extends
DataSourceApi
<
ElasticsearchQuery
,
ElasticsearchOptions
>
{
basicAuth
:
string
;
...
...
@@ -25,6 +31,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
indexPattern
:
IndexPattern
;
logMessageField
?:
string
;
logLevelField
?:
string
;
dataLinks
:
DataLinkConfig
[];
/** @ngInject */
constructor
(
...
...
@@ -52,6 +59,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
});
this
.
logMessageField
=
settingsData
.
logMessageField
||
''
;
this
.
logLevelField
=
settingsData
.
logLevelField
||
''
;
this
.
dataLinks
=
settingsData
.
dataLinks
||
[];
if
(
this
.
logMessageField
===
''
)
{
this
.
logMessageField
=
null
;
...
...
@@ -369,7 +377,11 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
return
this
.
post
(
url
,
payload
).
then
((
res
:
any
)
=>
{
const
er
=
new
ElasticResponse
(
sentTargets
,
res
);
if
(
sentTargets
.
some
(
target
=>
target
.
isLogsQuery
))
{
return
er
.
getLogs
(
this
.
logMessageField
,
this
.
logLevelField
);
const
response
=
er
.
getLogs
(
this
.
logMessageField
,
this
.
logLevelField
);
for
(
const
dataFrame
of
response
.
data
)
{
this
.
enhanceDataFrame
(
dataFrame
);
}
return
response
;
}
return
er
.
getTimeSeries
();
...
...
@@ -547,6 +559,24 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
return
false
;
}
enhanceDataFrame
(
dataFrame
:
DataFrame
)
{
if
(
this
.
dataLinks
.
length
)
{
for
(
const
field
of
dataFrame
.
fields
)
{
const
dataLink
=
this
.
dataLinks
.
find
(
dataLink
=>
field
.
name
&&
field
.
name
.
match
(
dataLink
.
field
));
if
(
dataLink
)
{
field
.
config
=
field
.
config
||
{};
field
.
config
.
links
=
[
...(
field
.
config
.
links
||
[]),
{
url
:
dataLink
.
url
,
title
:
''
,
},
];
}
}
}
}
private
isPrimitive
(
obj
:
any
)
{
if
(
obj
===
null
||
obj
===
undefined
)
{
return
true
;
...
...
public/app/plugins/datasource/elasticsearch/types.ts
View file @
5a3c1dc6
...
...
@@ -8,6 +8,7 @@ export interface ElasticsearchOptions extends DataSourceJsonData {
maxConcurrentShardRequests
?:
number
;
logMessageField
?:
string
;
logLevelField
?:
string
;
dataLinks
?:
DataLinkConfig
[];
}
export
interface
ElasticsearchAggregation
{
...
...
@@ -24,3 +25,8 @@ export interface ElasticsearchQuery extends DataQuery {
bucketAggs
?:
ElasticsearchAggregation
[];
metrics
?:
ElasticsearchAggregation
[];
}
export
type
DataLinkConfig
=
{
field
:
string
;
url
:
string
;
};
public/app/plugins/datasource/graphite/dashboards/metrictank.json
View file @
5a3c1dc6
...
...
@@ -25,9 +25,7 @@
"limit"
:
100
,
"name"
:
"Annotations & Alerts"
,
"showIn"
:
0
,
"tags"
:
[
"metrictank"
],
"tags"
:
[
"metrictank"
],
"type"
:
"tags"
}
]
...
...
@@ -3258,11 +3256,11 @@
"target"
:
"groupByNodes(perSecond(metrictank.stats.$environment.$instance.idx.*.ops.*.counter32), 'sum', 5, 7)"
,
"textEditor"
:
false
},
{
"refId"
:
"B"
,
"target"
:
"aliasByNode(sumSeriesWithWildcards(metrictank.stats.$environment.$instance.idx.*.add.values.rate32, 2, 3), 3, 4)"
,
"textEditor"
:
false
},
{
"refId"
:
"B"
,
"target"
:
"aliasByNode(sumSeriesWithWildcards(metrictank.stats.$environment.$instance.idx.*.add.values.rate32, 2, 3), 3, 4)"
,
"textEditor"
:
false
},
{
"refId"
:
"C"
,
"target"
:
"aliasByNode(sumSeriesWithWildcards(metrictank.stats.$environment.$instance.idx.*.query-insert.exec.values.rate32, 2, 3), 3, 4)"
,
...
...
@@ -4783,30 +4781,9 @@
"enable"
:
true
,
"notice"
:
false
,
"now"
:
true
,
"refresh_intervals"
:
[
"5s"
,
"10s"
,
"30s"
,
"1m"
,
"5m"
,
"15m"
,
"30m"
,
"1h"
,
"2h"
,
"1d"
],
"refresh_intervals"
:
[
"5s"
,
"10s"
,
"30s"
,
"1m"
,
"5m"
,
"15m"
,
"30m"
,
"1h"
,
"2h"
,
"1d"
],
"status"
:
"Stable"
,
"time_options"
:
[
"5m"
,
"15m"
,
"1h"
,
"6h"
,
"12h"
,
"24h"
,
"2d"
,
"7d"
,
"30d"
],
"time_options"
:
[
"5m"
,
"15m"
,
"1h"
,
"6h"
,
"12h"
,
"24h"
,
"2d"
,
"7d"
,
"30d"
],
"type"
:
"timepicker"
},
"timezone"
:
"utc"
,
...
...
public/app/plugins/datasource/graphite/plugin.json
View file @
5a3c1dc6
...
...
@@ -5,8 +5,8 @@
"category"
:
"tsdb"
,
"includes"
:
[
{
"type"
:
"dashboard"
,
"name"
:
"Graphite Carbon Metrics"
,
"path"
:
"dashboards/carbon_metrics.json"
},
{
"type"
:
"dashboard"
,
"name"
:
"Metrictank (Graphite alternative)"
,
"path"
:
"dashboards/metrictank.json"
}
{
"type"
:
"dashboard"
,
"name"
:
"Graphite Carbon Metrics"
,
"path"
:
"dashboards/carbon_metrics.json"
},
{
"type"
:
"dashboard"
,
"name"
:
"Metrictank (Graphite alternative)"
,
"path"
:
"dashboards/metrictank.json"
}
],
"metrics"
:
true
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment