InfluxDB: different config UI for 1x vs 2x (#25723)

This commit is contained in:
Ryan McKinley 2020-06-22 13:03:34 -07:00 committed by GitHub
parent 0797fe88a1
commit 8d1ed33e20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1277 additions and 889 deletions

View File

@ -57,7 +57,7 @@ export const SecretFormField: FunctionComponent<Props> = ({
<>
<input
type="text"
className={cx(`gf-form-input width-${inputWidth! - 2}`, styles.noRadiusInput)}
className={cx(`gf-form-input width-${inputWidth}`, styles.noRadiusInput)}
disabled={true}
value="configured"
{...omit(inputProps, 'value')}

View File

@ -43,36 +43,18 @@ func init() {
tsdb.RegisterTsdbQueryEndpoint("influxdb", NewInfluxDBExecutor)
}
func AllFlux(queries *tsdb.TsdbQuery) (bool, error) {
var hasFlux bool
var allFlux bool
for i, q := range queries.Queries {
qType := q.Model.Get("queryType").MustString("")
if qType == "Flux" {
hasFlux = true
if i == 0 && hasFlux {
allFlux = true
continue
}
}
if allFlux && qType != "Flux" {
return true, fmt.Errorf("when using flux, all queries must be a flux query")
}
}
return allFlux, nil
}
func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{}
allFlux, err := AllFlux(tsdbQuery)
if err != nil {
return nil, err
}
if allFlux {
glog.Info("query", "q", tsdbQuery.Queries)
version := dsInfo.JsonData.Get("version").MustString("")
if version == "Flux" {
return flux.Query(ctx, dsInfo, tsdbQuery)
}
// NOTE: the following path is currently only called from alerting queries
// In dashboards, the request runs through proxy and are managed in the frontend
query, err := e.getQuery(dsInfo, tsdbQuery.Queries, tsdbQuery)
if err != nil {
return nil, err
@ -120,6 +102,7 @@ func (e *InfluxDBExecutor) Query(ctx context.Context, dsInfo *models.DataSource,
return nil, response.Err
}
result := &tsdb.Response{}
result.Results = make(map[string]*tsdb.QueryResult)
result.Results["A"] = e.ResponseParser.Parse(&response, query)

View File

@ -10,41 +10,179 @@ import {
} from '@grafana/data';
import { DataSourceHttpSettings, InlineFormLabel, LegacyForms } from '@grafana/ui';
const { Select, Input, SecretFormField } = LegacyForms;
import { InfluxOptions, InfluxSecureJsonData } from '../types';
import { InfluxOptions, InfluxSecureJsonData, InfluxVersion } from '../types';
const httpModes = [
{ label: 'GET', value: 'GET' },
{ label: 'POST', value: 'POST' },
] as SelectableValue[];
const versions = [
{
label: 'InfluxQL',
value: InfluxVersion.InfluxQL,
description: 'The InfluxDB SQL-like query language. Supported in InfluxDB 1.x',
},
{
label: 'Flux',
value: InfluxVersion.Flux,
description: 'Advanced data scripting and query language. Supported in InfluxDB 2.x and 1.8+ (beta)',
},
] as Array<SelectableValue<InfluxVersion>>;
export type Props = DataSourcePluginOptionsEditorProps<InfluxOptions>;
export class ConfigEditor extends PureComponent<Props> {
// 1x
onResetPassword = () => {
updateDatasourcePluginResetOption(this.props, 'password');
};
// 2x
onResetToken = () => {
updateDatasourcePluginResetOption(this.props, 'token');
};
onToggleFlux = (event: React.SyntheticEvent<HTMLInputElement>) => {
onVersionChanged = (selected: SelectableValue<InfluxVersion>) => {
const { options, onOptionsChange } = this.props;
onOptionsChange({
const copy = {
...options,
jsonData: {
...options.jsonData,
enableFlux: !options.jsonData.enableFlux,
version: selected.value,
},
};
if (selected.value === InfluxVersion.Flux) {
copy.access = 'proxy';
copy.basicAuth = true;
copy.jsonData.httpMode = 'POST';
// Remove old 1x configs
delete copy.user;
delete copy.database;
}
onOptionsChange(copy);
};
onUpdateInflux2xURL = (e: React.SyntheticEvent<HTMLInputElement>) => {
const { options, onOptionsChange } = this.props;
onOptionsChange({
...options,
url: e.currentTarget.value,
access: 'proxy',
basicAuth: true,
});
};
render() {
renderInflux2x() {
const { options } = this.props;
const { secureJsonFields } = options;
const secureJsonData = (options.secureJsonData || {}) as InfluxSecureJsonData;
return (
<div>
<div className="gf-form-group">
<div className="width-30 grafana-info-box">
<h5>Support for flux in Grafana is currently in beta</h5>
<p>
Please report any issues to: <br />
<a href="https://github.com/grafana/grafana/issues/new/choose">
https://github.com/grafana/grafana/issues
</a>
</p>
</div>
</div>
<br />
<h3 className="page-heading">Connection</h3>
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel
className="width-10"
tooltip="This URL needs to be accessible from the grafana backend/server."
>
URL
</InlineFormLabel>
<div className="width-20">
<Input
className="width-20"
value={options.url || ''}
placeholder="http://localhost:9999/api/v2"
onChange={this.onUpdateInflux2xURL}
/>
</div>
</div>
</div>
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-10">Organization</InlineFormLabel>
<div className="width-10">
<Input
className="width-20"
value={options.jsonData.organization || ''}
onChange={onUpdateDatasourceJsonDataOption(this.props, 'organization')}
/>
</div>
</div>
</div>
<div className="gf-form-inline">
<div className="gf-form">
<SecretFormField
isConfigured={(secureJsonFields && secureJsonFields.token) as boolean}
value={secureJsonData.token || ''}
label="Token"
labelWidth={10}
inputWidth={20}
onReset={this.onResetToken}
onChange={onUpdateDatasourceSecureJsonDataOption(this.props, 'token')}
/>
</div>
</div>
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-10">Default Bucket</InlineFormLabel>
<div className="width-10">
<Input
className="width-20"
placeholder="default bucket"
value={options.jsonData.defaultBucket || ''}
onChange={onUpdateDatasourceJsonDataOption(this.props, 'defaultBucket')}
/>
</div>
</div>
</div>
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel
className="width-10"
tooltip="A lower limit for the auto group by time interval. Recommended to be set to write frequency,
for example 1m if your data is written every minute."
>
Min time interval
</InlineFormLabel>
<div className="width-10">
<Input
className="width-10"
placeholder="10s"
value={options.jsonData.timeInterval || ''}
onChange={onUpdateDatasourceJsonDataOption(this.props, 'timeInterval')}
/>
</div>
</div>
</div>
</div>
);
}
renderInflux1x() {
const { options, onOptionsChange } = this.props;
const { secureJsonFields } = options;
const secureJsonData = (options.secureJsonData || {}) as InfluxSecureJsonData;
return (
<>
<div>
<DataSourceHttpSettings
showAccessOptions={true}
dataSourceConfig={options}
@ -54,50 +192,6 @@ export class ConfigEditor extends PureComponent<Props> {
<h3 className="page-heading">InfluxDB Details</h3>
<div className="gf-form-group">
<div className="gf-form-inline">
<LegacyForms.Switch
label="Enable flux"
labelClass="width-10"
checked={options.jsonData.enableFlux || false}
onChange={this.onToggleFlux}
tooltip="Suport flux query endpoint"
/>
</div>
{options.jsonData.enableFlux && (
<>
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-10">Organization</InlineFormLabel>
<div className="width-10">
<Input
className="width-10"
placeholder="enter organization"
value={options.jsonData.organization || ''}
onChange={onUpdateDatasourceJsonDataOption(this.props, 'organization')}
/>
</div>
</div>
</div>
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-10">Default Bucket</InlineFormLabel>
<div className="width-10">
<Input
className="width-10"
placeholder="default bucket"
value={options.jsonData.defaultBucket || ''}
onChange={onUpdateDatasourceJsonDataOption(this.props, 'defaultBucket')}
/>
</div>
</div>
</div>
<br />
<br />
</>
)}
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-10">Database</InlineFormLabel>
@ -135,19 +229,6 @@ export class ConfigEditor extends PureComponent<Props> {
/>
</div>
</div>
<div className="gf-form-inline">
<div className="gf-form">
<SecretFormField
isConfigured={(secureJsonFields && secureJsonFields.token) as boolean}
value={secureJsonData.token || ''}
label="Token"
labelWidth={10}
inputWidth={20}
onReset={this.onResetPassword}
onChange={onUpdateDatasourceSecureJsonDataOption(this.props, 'token')}
/>
</div>
</div>
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel
@ -168,6 +249,7 @@ export class ConfigEditor extends PureComponent<Props> {
</div>
</div>
</div>
<div className="gf-form-group">
<div className="grafana-info-box">
<h5>Database Access</h5>
@ -202,6 +284,31 @@ export class ConfigEditor extends PureComponent<Props> {
</div>
</div>
</div>
</div>
);
}
render() {
const { options } = this.props;
return (
<>
<h3 className="page-heading">Query Language</h3>
<div className="gf-form-group">
<div className="gf-form-inline">
<div className="gf-form">
<Select
className="width-30"
value={options.jsonData.version === InfluxVersion.Flux ? versions[1] : versions[0]}
options={versions}
defaultValue={versions[0]}
onChange={this.onVersionChanged}
/>
</div>
</div>
</div>
{options.jsonData.version === InfluxVersion.Flux ? this.renderInflux2x() : this.renderInflux1x()}
</>
);
}

View File

@ -0,0 +1,142 @@
import React, { Component } from 'react';
import coreModule from 'app/core/core_module';
import { InfluxQuery } from '../types';
import { SelectableValue } from '@grafana/data';
import { InlineFormLabel, LinkButton, Select, TextArea } from '@grafana/ui';
interface Props {
target: InfluxQuery;
change: (target: InfluxQuery) => void;
refresh: () => void;
}
const samples: Array<SelectableValue<string>> = [
{ label: 'Show buckets', description: 'List the avaliable buckets (table)', value: 'buckets()' },
{
label: 'Simple query',
description: 'filter by measurment and field',
value: `from(bucket: "db/rp")
|> range(start: v.timeRangeStart, stop:timeRangeStop)
|> filter(fn: (r) =>
r._measurement == "example-measurement" and
r._field == "example-field"
)`,
},
{
label: 'Grouped Query',
description: 'Group by (min/max/sum/median)',
value: `// v.windowPeriod is a variable referring to the current optimized window period (currently: $interval)
from(bucket: v.bucket)
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_measurement"] == "measurement1" or r["_measurement"] =~ /^.*?regex.*$/)
|> filter(fn: (r) => r["_field"] == "field2" or r["_field"] =~ /^.*?regex.*$/)
|> aggregateWindow(every: v.windowPeriod, fn: mean|median|max|count|derivative|sum)
|> yield(name: "some-name")`,
},
{
label: 'Filter by value',
description: 'Results between a min/max',
value: `// v.bucket, v.timeRangeStart, and v.timeRange stop are all variables supported by the flux plugin and influxdb
from(bucket: v.bucket)
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|> filter(fn: (r) => r["_value"] >= 10 and r["_value"] <= 20)`,
},
{
label: 'Schema Exploration: (measurements)',
description: 'Get a list of measurement using flux',
value: `import "influxdata/influxdb/v1"
v1.measurements(bucket: v.bucket)`,
},
{
label: 'Schema Exploration: (fields)',
description: 'Return every possible key in a single table',
value: `from(bucket: v.bucket)
|> range(start: -30m)
|> keys()
|> keep(columns: ["_value"])
|> group()
|> distinct()`,
},
{
label: 'Schema Exploration: (tag keys)',
description: 'Get a list of tag keys using flux',
value: `import "influxdata/influxdb/v1"
v1.tagKeys(bucket: v.bucket)`,
},
{
label: 'Schema Exploration: (tag values)',
description: 'Get a list of tag values using flux',
value: `import "influxdata/influxdb/v1"
v1.tagValues(
bucket: v.bucket,
tag: "host",
predicate: (r) => true,
start: -1d
)`,
},
];
export class FluxQueryEditor extends Component<Props> {
onFluxQueryChange = (e: any) => {
const { target, change } = this.props;
change({ ...target, query: e.currentTarget.value });
};
onFluxBlur = (e: any) => {
this.props.refresh();
};
onSampleChange = (val: SelectableValue<string>) => {
this.props.change({
...this.props.target,
query: val.value!,
});
// Angular HACK: Since the target does not actually change!
this.forceUpdate();
this.props.refresh();
};
render() {
const { target } = this.props;
return (
<>
<div className="gf-form">
<TextArea
value={target.query || ''}
onChange={this.onFluxQueryChange}
onBlur={this.onFluxBlur}
placeholder="Flux query"
rows={10}
/>
</div>
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel width={6} tooltip="This supports queries copied from chronograph">
Help
</InlineFormLabel>
<Select width={20} options={samples} placeholder="Sample Query" onChange={this.onSampleChange} />
<LinkButton
icon="external-link-alt"
variant="secondary"
target="blank"
href="https://docs.influxdata.com/flux/latest/introduction/getting-started/"
>
Flux docs
</LinkButton>
</div>
<div className="gf-form gf-form--grow">
<div className="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</>
);
}
}
coreModule.directive('fluxQueryEditor', [
'reactDirective',
(reactDirective: any) => {
return reactDirective(FluxQueryEditor, ['target', 'change']);
},
]);

View File

@ -1,11 +1,19 @@
import _ from 'lodash';
import { dateMath, DataSourceInstanceSettings, ScopedVars, DataQueryRequest, DataQueryResponse } from '@grafana/data';
import {
dateMath,
DataSourceInstanceSettings,
ScopedVars,
DataQueryRequest,
DataQueryResponse,
dateTime,
LoadingState,
} from '@grafana/data';
import InfluxSeries from './influx_series';
import InfluxQueryModel from './influx_query_model';
import ResponseParser from './response_parser';
import { InfluxQueryBuilder } from './query_builder';
import { InfluxQuery, InfluxOptions, InfluxQueryType } from './types';
import { InfluxQuery, InfluxOptions, InfluxVersion } from './types';
import { getBackendSrv, getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime';
import { Observable, from } from 'rxjs';
@ -21,7 +29,7 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
interval: any;
responseParser: any;
httpMode: string;
enableFlux: boolean;
is2x: boolean;
constructor(instanceSettings: DataSourceInstanceSettings<InfluxOptions>) {
super(instanceSettings);
@ -40,41 +48,11 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
this.interval = settingsData.timeInterval;
this.httpMode = settingsData.httpMode || 'GET';
this.responseParser = new ResponseParser();
this.enableFlux = !!settingsData.enableFlux;
this.is2x = settingsData.version === InfluxVersion.Flux;
}
query(request: DataQueryRequest<InfluxQuery>): Observable<DataQueryResponse> {
let hasFlux = false;
let allFlux = true;
// Update the queryType fields and manage migrations
for (const target of request.targets) {
if (target.queryType === InfluxQueryType.Flux) {
hasFlux = true;
} else {
allFlux = false;
if (target.queryType === InfluxQueryType.Classic) {
delete target.rawQuery;
} else if (target.rawQuery) {
target.queryType = InfluxQueryType.InfluxQL;
} else if (target.queryType === InfluxQueryType.InfluxQL) {
target.rawQuery = true; // so the old version works
} else {
target.queryType = InfluxQueryType.Classic; // Explicitly set it to classic
delete target.rawQuery;
}
}
}
// Process flux queries (data frame request)
if (hasFlux) {
if (!this.enableFlux) {
throw 'Flux not enabled for this datasource';
}
if (!allFlux) {
throw 'All queries must be flux';
}
// Calls /api/tsdb/query
if (this.is2x) {
return super.query(request);
}
@ -311,8 +289,40 @@ export default class InfluxDatasource extends DataSourceWithBackend<InfluxQuery,
).join('&');
}
// TODO: remove this so that everything gets sent to /healthcheck!
testDatasource() {
if (this.is2x) {
// TODO: eventually use the real /health endpoint
const request: DataQueryRequest<InfluxQuery> = {
targets: [{ refId: 'test', query: 'buckets()' }],
requestId: `${this.id}-health-${Date.now()}`,
dashboardId: 0,
panelId: 0,
interval: '1m',
intervalMs: 60000,
maxDataPoints: 423,
range: {
from: dateTime(1000),
to: dateTime(2000),
},
} as DataQueryRequest<InfluxQuery>;
return super
.query(request)
.toPromise()
.then((res: any) => {
const data: DataQueryResponse = res.data;
if (data && data.state === LoadingState.Done) {
const buckets = data.data[0].length;
return { status: 'success', message: `Data source is working (${buckets} buckets)` };
}
console.log('InfluxDB Error', data);
return { status: 'error', message: 'Error reading buckets' };
})
.catch((err: any) => {
return { status: 'error', message: err.message };
});
}
const queryBuilder = new InfluxQueryBuilder({ measurement: '', tags: [] }, this.database);
const query = queryBuilder.buildExploreQuery('RETENTION POLICIES');

View File

@ -5,6 +5,9 @@ import InfluxStartPage from './components/InfluxStartPage';
import { DataSourcePlugin } from '@grafana/data';
import ConfigEditor from './components/ConfigEditor';
// This adds a directive that is used in the query editor
import './components/FluxQueryEditor';
class InfluxAnnotationsQueryCtrl {
static templateUrl = 'partials/annotations.editor.html';
}

View File

@ -1,37 +1,10 @@
<query-editor-row query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="true">
<div ng-if="ctrl.datasource.enableFlux" class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">QUERY</label>
<div class="gf-form-select-wrapper">
<select
class="gf-form-input gf-size-auto"
ng-model="ctrl.target.queryType"
ng-options="f.value as f.text for f in ctrl.queryTypes"
ng-change="ctrl.refresh()"
></select>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<!-- TODO use monaco flux editor -->
<div ng-if="ctrl.target.queryType === 'Flux'">
<div class="gf-form">
<textarea
rows="3"
class="gf-form-input"
ng-model="ctrl.target.query"
spellcheck="false"
placeholder="Flux Query"
ng-model-onblur
ng-change="ctrl.refresh()"
></textarea>
</div>
</div>
<query-editor-row ng-if="ctrl.datasource.is2x" query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="false">
<flux-query-editor target="ctrl.target" change="ctrl.onChange" refresh="ctrl.refresh"></flux-query-editor>
</query-editor-row>
<div ng-if="ctrl.target.queryType === 'InfluxQL' || !ctrl.target.queryType">
<query-editor-row ng-if="!ctrl.datasource.is2x" query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="true">
<div ng-if="ctrl.target.rawQuery">
<div class="gf-form">
<textarea
rows="3"
@ -72,7 +45,7 @@
</div>
</div>
<div ng-if="ctrl.target.queryType === 'Classic'">
<div ng-if="!ctrl.target.rawQuery">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">FROM</label>

View File

@ -5,16 +5,17 @@ import InfluxQueryModel from './influx_query_model';
import queryPart from './query_part';
import { QueryCtrl } from 'app/plugins/sdk';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { InfluxQueryType } from './types';
import { InfluxQuery } from './types';
import InfluxDatasource from './datasource';
export class InfluxQueryCtrl extends QueryCtrl {
static templateUrl = 'partials/query.editor.html';
datasource: InfluxDatasource;
queryModel: InfluxQueryModel;
queryBuilder: any;
groupBySegment: any;
resultFormats: any[];
queryTypes: any[];
orderByTime: any[];
policySegment: any;
tagSegments: any[];
@ -39,15 +40,6 @@ export class InfluxQueryCtrl extends QueryCtrl {
{ text: 'Table', value: 'table' },
];
// Show a dropdown for flux
if (this.datasource.enableFlux) {
this.queryTypes = [
{ text: 'Classic', value: InfluxQueryType.Classic },
{ text: 'InfluxQL', value: InfluxQueryType.InfluxQL },
{ text: 'Flux', value: InfluxQueryType.Flux },
];
}
this.policySegment = uiSegmentSrv.newSegment(this.target.policy);
if (!this.target.measurement) {
@ -83,6 +75,10 @@ export class InfluxQueryCtrl extends QueryCtrl {
});
}
onChange = (target: InfluxQuery) => {
this.target.query = target.query;
};
removeOrderByTime() {
this.target.orderByTime = 'ASC';
}
@ -194,6 +190,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
return Promise.resolve([{ text: 'Remove', value: 'remove-part' }]);
}
}
return Promise.resolve();
}
handleGroupByPartEvent(part: any, index: any, evt: { name: any }) {
@ -218,6 +215,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
return Promise.resolve([{ text: 'Remove', value: 'remove-part' }]);
}
}
return Promise.resolve();
}
fixTagSegments() {
@ -247,21 +245,14 @@ export class InfluxQueryCtrl extends QueryCtrl {
this.panelCtrl.refresh();
}
// Only valid for InfluxQL queries
toggleEditorMode() {
try {
this.target.query = this.queryModel.render(false);
} catch (err) {
console.log('query render error');
}
const { queryType } = this.target;
if (queryType === InfluxQueryType.Flux || queryType === InfluxQueryType.InfluxQL) {
this.target.queryType = InfluxQueryType.Classic;
this.target.rawQuery = false;
} else if (this.datasource.enableFlux) {
this.target.queryType = InfluxQueryType.Flux;
} else {
this.target.queryType = InfluxQueryType.InfluxQL;
}
this.target.rawQuery = !this.target.rawQuery;
}
getMeasurements(measurementFilter: any) {

View File

@ -1,14 +1,15 @@
import '../query_ctrl';
import { uiSegmentSrv } from 'app/core/services/segment_srv';
import { InfluxQueryCtrl } from '../query_ctrl';
import InfluxDatasource from '../datasource';
describe('InfluxDBQueryCtrl', () => {
const ctx = {} as any;
beforeEach(() => {
InfluxQueryCtrl.prototype.datasource = {
InfluxQueryCtrl.prototype.datasource = ({
metricFindQuery: () => Promise.resolve([]),
};
} as unknown) as InfluxDatasource;
InfluxQueryCtrl.prototype.target = { target: {} };
InfluxQueryCtrl.prototype.panelCtrl = {
panel: {

View File

@ -1,19 +1,28 @@
import { DataQuery, DataSourceJsonData } from '@grafana/data';
export enum InfluxVersion {
InfluxQL = 'InfluxQL',
Flux = 'Flux',
}
export interface InfluxOptions extends DataSourceJsonData {
version?: InfluxVersion;
timeInterval: string;
httpMode: string;
// Influx 2.0
enableFlux?: boolean;
// With Flux
organization?: string;
defaultBucket?: string;
maxSeries?: number;
}
export interface InfluxSecureJsonData {
password?: string;
// For Flux
token?: string;
// In 1x a different password can be sent than then HTTP auth
password?: string;
}
export interface InfluxQueryPart {
@ -29,14 +38,7 @@ export interface InfluxQueryTag {
value: string;
}
export enum InfluxQueryType {
Classic = 'Classic', // IFQL query builder
InfluxQL = 'InfluxQL', // raw ifql
Flux = 'Flux',
}
export interface InfluxQuery extends DataQuery {
queryType?: InfluxQueryType;
policy?: string;
measurement?: string;
resultFormat?: 'time_series' | 'table';
@ -48,6 +50,6 @@ export interface InfluxQuery extends DataQuery {
slimit?: string;
tz?: string;
fill?: string;
rawQuery?: boolean; // deprecated (use raw InfluxQL)
rawQuery?: boolean;
query?: string;
}

View File

@ -386,7 +386,7 @@ $panel-grid-placeholder-bg: darken(#1f60c4, 30%);
$panel-grid-placeholder-shadow: 0 0 4px #3274d9;
// logs
$logs-color-unkown: $gray-2;
$logs-color-unknown: $gray-2;
// toggle-group
$button-toggle-group-btn-active-bg: linear-gradient(90deg, #eb7b18, #d44a3a);

View File

@ -379,7 +379,7 @@ $panel-grid-placeholder-bg: lighten(#5794f2, 30%);
$panel-grid-placeholder-shadow: 0 0 4px #5794f2;
// logs
$logs-color-unkown: $gray-5;
$logs-color-unknown: $gray-5;
// toggle-group
$button-toggle-group-btn-active-bg: $brand-primary;