mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge pull request #14494 from grafana/panel-edit-style-changes
Panel edit style changes
This commit is contained in:
commit
b9a05a8543
@ -1,17 +1,17 @@
|
||||
#! /usr/bin/env bash
|
||||
version=5.4.1
|
||||
version=5.4.2
|
||||
|
||||
wget https://dl.grafana.com/oss/release/grafana_${version}_amd64.deb
|
||||
# wget https://dl.grafana.com/oss/release/grafana_${version}_amd64.deb
|
||||
#
|
||||
# package_cloud push grafana/stable/debian/jessie grafana_${version}_amd64.deb
|
||||
# package_cloud push grafana/stable/debian/wheezy grafana_${version}_amd64.deb
|
||||
# package_cloud push grafana/stable/debian/stretch grafana_${version}_amd64.deb
|
||||
#
|
||||
# package_cloud push grafana/testing/debian/jessie grafana_${version}_amd64.deb
|
||||
# package_cloud push grafana/testing/debian/wheezy grafana_${version}_amd64.deb --verbose
|
||||
# package_cloud push grafana/testing/debian/stretch grafana_${version}_amd64.deb --verbose
|
||||
|
||||
package_cloud push grafana/stable/debian/jessie grafana_${version}_amd64.deb
|
||||
package_cloud push grafana/stable/debian/wheezy grafana_${version}_amd64.deb
|
||||
package_cloud push grafana/stable/debian/stretch grafana_${version}_amd64.deb
|
||||
|
||||
package_cloud push grafana/testing/debian/jessie grafana_${version}_amd64.deb
|
||||
package_cloud push grafana/testing/debian/wheezy grafana_${version}_amd64.deb --verbose
|
||||
package_cloud push grafana/testing/debian/stretch grafana_${version}_amd64.deb --verbose
|
||||
|
||||
wget https://dl.grafana.com/release/grafana-${version}-1.x86_64.rpm
|
||||
wget https://dl.grafana.com/oss/release/grafana-${version}-1.x86_64.rpm
|
||||
|
||||
package_cloud push grafana/testing/el/6 grafana-${version}-1.x86_64.rpm --verbose
|
||||
package_cloud push grafana/testing/el/7 grafana-${version}-1.x86_64.rpm --verbose
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { ValidationEvents, ValidationRule } from 'app/types';
|
||||
import { validate, hasValidationEvent } from 'app/core/utils/validate';
|
||||
|
||||
@ -31,6 +32,10 @@ interface Props extends React.HTMLProps<HTMLInputElement> {
|
||||
}
|
||||
|
||||
export class Input extends PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
className: '',
|
||||
};
|
||||
|
||||
state = {
|
||||
error: null,
|
||||
};
|
||||
@ -76,7 +81,7 @@ export class Input extends PureComponent<Props> {
|
||||
render() {
|
||||
const { validationEvents, className, hideErrorMessage, ...restProps } = this.props;
|
||||
const { error } = this.state;
|
||||
const inputClassName = 'gf-form-input' + (this.isInvalid ? ' invalid' : '');
|
||||
const inputClassName = classNames('gf-form-input', { invalid: this.isInvalid }, className);
|
||||
const inputElementProps = this.populateEventPropsWithStatus(restProps, validationEvents);
|
||||
|
||||
return (
|
||||
|
@ -36,25 +36,25 @@ export default class UnitPicker extends PureComponent<Props> {
|
||||
};
|
||||
});
|
||||
|
||||
const styles = {
|
||||
...ResetStyles,
|
||||
menu: () => ({
|
||||
maxHeight: '75%',
|
||||
overflow: 'scroll',
|
||||
}),
|
||||
menuList: () =>
|
||||
({
|
||||
overflowY: 'auto',
|
||||
position: 'relative',
|
||||
} as React.CSSProperties),
|
||||
valueContainer: () =>
|
||||
({
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
maxWidth: '90px',
|
||||
whiteSpace: 'nowrap',
|
||||
} as React.CSSProperties),
|
||||
};
|
||||
// const styles = {
|
||||
// ...ResetStyles,
|
||||
// menu: () => ({
|
||||
// maxHeight: '75%',
|
||||
// overflow: 'scroll',
|
||||
// }),
|
||||
// menuList: () =>
|
||||
// ({
|
||||
// overflowY: 'auto',
|
||||
// position: 'relative',
|
||||
// } as React.CSSProperties),
|
||||
// valueContainer: () =>
|
||||
// ({
|
||||
// overflow: 'hidden',
|
||||
// textOverflow: 'ellipsis',
|
||||
// maxWidth: '90px',
|
||||
// whiteSpace: 'nowrap',
|
||||
// } as React.CSSProperties),
|
||||
// };
|
||||
|
||||
const value = groupOptions.map(group => {
|
||||
return group.options.find(option => option.value === defaultValue);
|
||||
@ -74,7 +74,7 @@ export default class UnitPicker extends PureComponent<Props> {
|
||||
Group: UnitGroup,
|
||||
Option: UnitOption,
|
||||
}}
|
||||
styles={styles}
|
||||
styles={ResetStyles}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1,187 +1,189 @@
|
||||
<div class="edit-tab-with-sidemenu" ng-if="ctrl.alert">
|
||||
<aside class="edit-sidemenu-aside">
|
||||
<ul class="edit-sidemenu">
|
||||
<li ng-class="{active: ctrl.subTabIndex === 0}">
|
||||
<a ng-click="ctrl.changeTabIndex(0)">Alert Config</a>
|
||||
</li>
|
||||
<li ng-class="{active: ctrl.subTabIndex === 1}">
|
||||
<a ng-click="ctrl.changeTabIndex(1)">
|
||||
Notifications <span class="muted">({{ctrl.alertNotifications.length}})</span>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-class="{active: ctrl.subTabIndex === 2}">
|
||||
<a ng-click="ctrl.changeTabIndex(2)">State history</a>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-click="ctrl.delete()">Delete</a>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
<div class="panel-option-section__body">
|
||||
<div class="edit-tab-with-sidemenu" ng-if="ctrl.alert">
|
||||
<aside class="edit-sidemenu-aside">
|
||||
<ul class="edit-sidemenu">
|
||||
<li ng-class="{active: ctrl.subTabIndex === 0}">
|
||||
<a ng-click="ctrl.changeTabIndex(0)">Alert Config</a>
|
||||
</li>
|
||||
<li ng-class="{active: ctrl.subTabIndex === 1}">
|
||||
<a ng-click="ctrl.changeTabIndex(1)">
|
||||
Notifications <span class="muted">({{ctrl.alertNotifications.length}})</span>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-class="{active: ctrl.subTabIndex === 2}">
|
||||
<a ng-click="ctrl.changeTabIndex(2)">State history</a>
|
||||
</li>
|
||||
<li>
|
||||
<a ng-click="ctrl.delete()">Delete</a>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<div class="edit-tab-content">
|
||||
<div ng-if="ctrl.subTabIndex === 0">
|
||||
<div class="alert alert-error m-b-2" ng-show="ctrl.error">
|
||||
<i class="fa fa-warning"></i> {{ctrl.error}}
|
||||
</div>
|
||||
<div class="edit-tab-content">
|
||||
<div ng-if="ctrl.subTabIndex === 0">
|
||||
<div class="alert alert-error m-b-2" ng-show="ctrl.error">
|
||||
<i class="fa fa-warning"></i> {{ctrl.error}}
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group">
|
||||
<h5 class="section-heading">Alert Config</h5>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-6">Name</span>
|
||||
<input type="text" class="gf-form-input width-20" ng-model="ctrl.alert.name">
|
||||
</div>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-9">Evaluate every</span>
|
||||
<input class="gf-form-input max-width-6" type="text" ng-model="ctrl.alert.frequency">
|
||||
</div>
|
||||
<div class="gf-form max-width-11">
|
||||
<label class="gf-form-label width-5">For</label>
|
||||
<input type="text" class="gf-form-input max-width-6" ng-model="ctrl.alert.for" spellcheck='false' placeholder="5m">
|
||||
<info-popover mode="right-absolute">
|
||||
If an alert rule has a configured For and the query violates the configured threshold it will first go from OK to Pending.
|
||||
Going from OK to Pending Grafana will not send any notifications. Once the alert rule has been firing for more than For duration, it will change to Alerting and send alert notifications.
|
||||
</info-popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form-group">
|
||||
<h5 class="section-heading">Alert Config</h5>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-6">Name</span>
|
||||
<input type="text" class="gf-form-input width-20" ng-model="ctrl.alert.name">
|
||||
</div>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-9">Evaluate every</span>
|
||||
<input class="gf-form-input max-width-6" type="text" ng-model="ctrl.alert.frequency">
|
||||
</div>
|
||||
<div class="gf-form max-width-11">
|
||||
<label class="gf-form-label width-5">For</label>
|
||||
<input type="text" class="gf-form-input max-width-6" ng-model="ctrl.alert.for" spellcheck='false' placeholder="5m">
|
||||
<info-popover mode="right-absolute">
|
||||
If an alert rule has a configured For and the query violates the configured threshold it will first go from OK to Pending.
|
||||
Going from OK to Pending Grafana will not send any notifications. Once the alert rule has been firing for more than For duration, it will change to Alerting and send alert notifications.
|
||||
</info-popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group">
|
||||
<h5 class="section-heading">Conditions</h5>
|
||||
<div class="gf-form-inline" ng-repeat="conditionModel in ctrl.conditionModels">
|
||||
<div class="gf-form">
|
||||
<metric-segment-model css-class="query-keyword width-5" ng-if="$index" property="conditionModel.operator.type" options="ctrl.evalOperators" custom="false"></metric-segment-model>
|
||||
<span class="gf-form-label query-keyword width-5" ng-if="$index===0">WHEN</span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<query-part-editor class="gf-form-label query-part width-9" part="conditionModel.reducerPart" handle-event="ctrl.handleReducerPartEvent(conditionModel, $event)">
|
||||
</query-part-editor>
|
||||
<span class="gf-form-label query-keyword">OF</span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<query-part-editor class="gf-form-label query-part" part="conditionModel.queryPart" handle-event="ctrl.handleQueryPartEvent(conditionModel, $event)">
|
||||
</query-part-editor>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<metric-segment-model property="conditionModel.evaluator.type" options="ctrl.evalFunctions" custom="false" css-class="query-keyword" on-change="ctrl.evaluatorTypeChanged(conditionModel.evaluator)"></metric-segment-model>
|
||||
<input class="gf-form-input max-width-9" type="number" step="any" ng-hide="conditionModel.evaluator.params.length === 0" ng-model="conditionModel.evaluator.params[0]" ng-change="ctrl.evaluatorParamsChanged()">
|
||||
<label class="gf-form-label query-keyword" ng-show="conditionModel.evaluator.params.length === 2">TO</label>
|
||||
<input class="gf-form-input max-width-9" type="number" step="any" ng-if="conditionModel.evaluator.params.length === 2" ng-model="conditionModel.evaluator.params[1]" ng-change="ctrl.evaluatorParamsChanged()">
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">
|
||||
<a class="pointer" tabindex="1" ng-click="ctrl.removeCondition($index)">
|
||||
<i class="fa fa-trash"></i>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form-group">
|
||||
<h5 class="section-heading">Conditions</h5>
|
||||
<div class="gf-form-inline" ng-repeat="conditionModel in ctrl.conditionModels">
|
||||
<div class="gf-form">
|
||||
<metric-segment-model css-class="query-keyword width-5" ng-if="$index" property="conditionModel.operator.type" options="ctrl.evalOperators" custom="false"></metric-segment-model>
|
||||
<span class="gf-form-label query-keyword width-5" ng-if="$index===0">WHEN</span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<query-part-editor class="gf-form-label query-part width-9" part="conditionModel.reducerPart" handle-event="ctrl.handleReducerPartEvent(conditionModel, $event)">
|
||||
</query-part-editor>
|
||||
<span class="gf-form-label query-keyword">OF</span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<query-part-editor class="gf-form-label query-part" part="conditionModel.queryPart" handle-event="ctrl.handleQueryPartEvent(conditionModel, $event)">
|
||||
</query-part-editor>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<metric-segment-model property="conditionModel.evaluator.type" options="ctrl.evalFunctions" custom="false" css-class="query-keyword" on-change="ctrl.evaluatorTypeChanged(conditionModel.evaluator)"></metric-segment-model>
|
||||
<input class="gf-form-input max-width-9" type="number" step="any" ng-hide="conditionModel.evaluator.params.length === 0" ng-model="conditionModel.evaluator.params[0]" ng-change="ctrl.evaluatorParamsChanged()">
|
||||
<label class="gf-form-label query-keyword" ng-show="conditionModel.evaluator.params.length === 2">TO</label>
|
||||
<input class="gf-form-input max-width-9" type="number" step="any" ng-if="conditionModel.evaluator.params.length === 2" ng-model="conditionModel.evaluator.params[1]" ng-change="ctrl.evaluatorParamsChanged()">
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">
|
||||
<a class="pointer" tabindex="1" ng-click="ctrl.removeCondition($index)">
|
||||
<i class="fa fa-trash"></i>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label dropdown">
|
||||
<a class="pointer dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa fa-plus"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li ng-repeat="ct in ctrl.conditionTypes" role="menuitem">
|
||||
<a ng-click="ctrl.addCondition(ct.value);">{{ct.text}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label dropdown">
|
||||
<a class="pointer dropdown-toggle" data-toggle="dropdown">
|
||||
<i class="fa fa-plus"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li ng-repeat="ct in ctrl.conditionTypes" role="menuitem">
|
||||
<a ng-click="ctrl.addCondition(ct.value);">{{ct.text}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-18">If no data or all values are null</span>
|
||||
<span class="gf-form-label query-keyword">SET STATE TO</span>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input" ng-model="ctrl.alert.noDataState" ng-options="f.value as f.text for f in ctrl.noDataModes">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-18">If no data or all values are null</span>
|
||||
<span class="gf-form-label query-keyword">SET STATE TO</span>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input" ng-model="ctrl.alert.noDataState" ng-options="f.value as f.text for f in ctrl.noDataModes">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-18">If execution error or timeout</span>
|
||||
<span class="gf-form-label query-keyword">SET STATE TO</span>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input" ng-model="ctrl.alert.executionErrorState" ng-options="f.value as f.text for f in ctrl.executionErrorModes">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-18">If execution error or timeout</span>
|
||||
<span class="gf-form-label query-keyword">SET STATE TO</span>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input" ng-model="ctrl.alert.executionErrorState" ng-options="f.value as f.text for f in ctrl.executionErrorModes">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button class="btn btn-inverse" ng-click="ctrl.test()">
|
||||
Test Rule
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form-button-row">
|
||||
<button class="btn btn-inverse" ng-click="ctrl.test()">
|
||||
Test Rule
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group" ng-if="ctrl.testing">
|
||||
Evaluating rule <i class="fa fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
<div class="gf-form-group" ng-if="ctrl.testing">
|
||||
Evaluating rule <i class="fa fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group" ng-if="ctrl.testResult">
|
||||
<json-tree root-name="result" object="ctrl.testResult" start-expanded="true"></json-tree>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form-group" ng-if="ctrl.testResult">
|
||||
<json-tree root-name="result" object="ctrl.testResult" start-expanded="true"></json-tree>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group" ng-if="ctrl.subTabIndex === 1">
|
||||
<h5 class="section-heading">Notifications</h5>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form max-width-30">
|
||||
<span class="gf-form-label width-8">Send to</span>
|
||||
<span class="gf-form-label" ng-repeat="nc in ctrl.alertNotifications" ng-style="{'background-color': nc.bgColor }">
|
||||
<i class="{{nc.iconClass}}"></i> {{nc.name}}
|
||||
<i class="fa fa-remove pointer muted" ng-click="ctrl.removeNotification($index)" ng-if="nc.isDefault === false"></i>
|
||||
</span>
|
||||
<metric-segment segment="ctrl.addNotificationSegment" get-options="ctrl.getNotifications()" on-change="ctrl.notificationAdded()"></metric-segment>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form gf-form--v-stretch">
|
||||
<span class="gf-form-label width-8">Message</span>
|
||||
<textarea class="gf-form-input" rows="10" ng-model="ctrl.alert.message" placeholder="Notification message details..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form-group" ng-if="ctrl.subTabIndex === 1">
|
||||
<h5 class="section-heading">Notifications</h5>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form max-width-30">
|
||||
<span class="gf-form-label width-8">Send to</span>
|
||||
<span class="gf-form-label" ng-repeat="nc in ctrl.alertNotifications" ng-style="{'background-color': nc.bgColor }">
|
||||
<i class="{{nc.iconClass}}"></i> {{nc.name}}
|
||||
<i class="fa fa-remove pointer muted" ng-click="ctrl.removeNotification($index)" ng-if="nc.isDefault === false"></i>
|
||||
</span>
|
||||
<metric-segment segment="ctrl.addNotificationSegment" get-options="ctrl.getNotifications()" on-change="ctrl.notificationAdded()"></metric-segment>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form gf-form--v-stretch">
|
||||
<span class="gf-form-label width-8">Message</span>
|
||||
<textarea class="gf-form-input" rows="10" ng-model="ctrl.alert.message" placeholder="Notification message details..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group" style="max-width: 720px;" ng-if="ctrl.subTabIndex === 2">
|
||||
<button class="btn btn-mini btn-danger pull-right" ng-click="ctrl.clearHistory()"><i class="fa fa-trash"></i> Clear history</button>
|
||||
<h5 class="section-heading" style="whitespace: nowrap">
|
||||
State history <span class="muted small">(last 50 state changes)</span>
|
||||
</h5>
|
||||
<div class="gf-form-group" style="max-width: 720px;" ng-if="ctrl.subTabIndex === 2">
|
||||
<button class="btn btn-mini btn-danger pull-right" ng-click="ctrl.clearHistory()"><i class="fa fa-trash"></i> Clear history</button>
|
||||
<h5 class="section-heading" style="whitespace: nowrap">
|
||||
State history <span class="muted small">(last 50 state changes)</span>
|
||||
</h5>
|
||||
|
||||
<div ng-show="ctrl.alertHistory.length === 0">
|
||||
<br>
|
||||
<i>No state changes recorded</i>
|
||||
</div>
|
||||
<div ng-show="ctrl.alertHistory.length === 0">
|
||||
<br>
|
||||
<i>No state changes recorded</i>
|
||||
</div>
|
||||
|
||||
<ol class="alert-rule-list" >
|
||||
<li class="alert-rule-item" ng-repeat="al in ctrl.alertHistory">
|
||||
<div class="alert-rule-item__icon {{al.stateModel.stateClass}}">
|
||||
<i class="{{al.stateModel.iconClass}}"></i>
|
||||
</div>
|
||||
<div class="alert-rule-item__body">
|
||||
<div class="alert-rule-item__header">
|
||||
<div class="alert-rule-item__text">
|
||||
<span class="{{al.stateModel.stateClass}}">{{al.stateModel.text}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="alert-list-info">{{al.info}}</span>
|
||||
</div>
|
||||
<div class="alert-rule-item__time">
|
||||
<span>{{al.time}}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group" ng-if="!ctrl.alert">
|
||||
<div class="gf-form-button-row">
|
||||
<button class="btn btn-inverse" ng-click="ctrl.enable()">
|
||||
<i class="icon-gf icon-gf-alert"></i>
|
||||
Create Alert
|
||||
</button>
|
||||
</div>
|
||||
<ol class="alert-rule-list" >
|
||||
<li class="alert-rule-item" ng-repeat="al in ctrl.alertHistory">
|
||||
<div class="alert-rule-item__icon {{al.stateModel.stateClass}}">
|
||||
<i class="{{al.stateModel.iconClass}}"></i>
|
||||
</div>
|
||||
<div class="alert-rule-item__body">
|
||||
<div class="alert-rule-item__header">
|
||||
<div class="alert-rule-item__text">
|
||||
<span class="{{al.stateModel.stateClass}}">{{al.stateModel.text}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="alert-list-info">{{al.info}}</span>
|
||||
</div>
|
||||
<div class="alert-rule-item__time">
|
||||
<span>{{al.time}}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group" ng-if="!ctrl.alert">
|
||||
<div class="gf-form-button-row">
|
||||
<button class="btn btn-inverse" ng-click="ctrl.enable()">
|
||||
<i class="icon-gf icon-gf-alert"></i>
|
||||
Create Alert
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -141,7 +141,6 @@ export class DashboardMigrator {
|
||||
|
||||
// ensure query refIds
|
||||
panelUpgrades.push(panel => {
|
||||
console.log('asdasd', panel);
|
||||
_.each(panel.targets, target => {
|
||||
if (!target.refId) {
|
||||
target.refId = panel.getNextQueryLetter && panel.getNextQueryLetter();
|
||||
|
@ -76,16 +76,16 @@ export class DashboardPanel extends PureComponent<Props, State> {
|
||||
// unmount angular panel
|
||||
this.cleanUpAngularPanel();
|
||||
|
||||
if (plugin.exports) {
|
||||
this.setState({ plugin: plugin });
|
||||
} else {
|
||||
plugin.exports = await importPluginModule(plugin.module);
|
||||
this.setState({ plugin: plugin });
|
||||
}
|
||||
|
||||
if (panel.type !== pluginId) {
|
||||
this.props.panel.changeType(pluginId, fromAngularPanel);
|
||||
}
|
||||
|
||||
if (plugin.exports) {
|
||||
this.setState({ plugin: plugin, angularPanel: null });
|
||||
} else {
|
||||
plugin.exports = await importPluginModule(plugin.module);
|
||||
this.setState({ plugin: plugin, angularPanel: null });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,18 +106,15 @@ export class DashboardPanel extends PureComponent<Props, State> {
|
||||
this.setState({ angularPanel });
|
||||
}
|
||||
|
||||
cleanUpAngularPanel(unmounted?: boolean) {
|
||||
cleanUpAngularPanel() {
|
||||
if (this.state.angularPanel) {
|
||||
this.state.angularPanel.destroy();
|
||||
|
||||
if (!unmounted) {
|
||||
this.setState({ angularPanel: null });
|
||||
}
|
||||
this.element = null;
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.cleanUpAngularPanel(true);
|
||||
this.cleanUpAngularPanel();
|
||||
}
|
||||
|
||||
onMouseEnter = () => {
|
||||
|
@ -1,6 +1,10 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Components
|
||||
import CustomScrollbar from 'app/core/components/CustomScrollbar/CustomScrollbar';
|
||||
import { FadeIn } from 'app/core/components/Animations/FadeIn';
|
||||
import { PanelOptionSection } from './PanelOptionSection';
|
||||
|
||||
interface Props {
|
||||
children: JSX.Element;
|
||||
@ -10,7 +14,8 @@ interface Props {
|
||||
}
|
||||
|
||||
export interface EditorToolBarView {
|
||||
title: string;
|
||||
title?: string;
|
||||
heading?: string;
|
||||
imgSrc?: string;
|
||||
icon?: string;
|
||||
disabled?: boolean;
|
||||
@ -88,12 +93,9 @@ export class EditorTabBody extends PureComponent<Props, State> {
|
||||
|
||||
renderOpenView(view: EditorToolBarView) {
|
||||
return (
|
||||
<div className="toolbar-subview">
|
||||
<button className="toolbar-subview__close" onClick={this.onCloseOpenView}>
|
||||
<i className="fa fa-chevron-up" />
|
||||
</button>
|
||||
{view.render(this.onCloseOpenView)}
|
||||
</div>
|
||||
<PanelOptionSection title={view.title || view.heading} onClose={this.onCloseOpenView}>
|
||||
{view.render()}
|
||||
</PanelOptionSection>
|
||||
);
|
||||
}
|
||||
|
||||
@ -115,10 +117,10 @@ export class EditorTabBody extends PureComponent<Props, State> {
|
||||
</div>
|
||||
<div className="panel-editor__scroll">
|
||||
<CustomScrollbar autoHide={false}>
|
||||
<FadeIn in={isOpen} duration={200} unmountOnExit={true}>
|
||||
<div className="panel-editor__toolbar-view">{openView && this.renderOpenView(openView)}</div>
|
||||
</FadeIn>
|
||||
<div className="panel-editor__content">
|
||||
<FadeIn in={isOpen} duration={200} unmountOnExit={true}>
|
||||
{openView && this.renderOpenView(openView)}
|
||||
</FadeIn>
|
||||
<FadeIn in={fadeIn} duration={50}>
|
||||
{children}
|
||||
</FadeIn>
|
||||
|
@ -0,0 +1,26 @@
|
||||
// Libraries
|
||||
import React, { SFC } from 'react';
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
onClose?: () => void;
|
||||
children: JSX.Element | JSX.Element[];
|
||||
}
|
||||
|
||||
export const PanelOptionSection: SFC<Props> = props => {
|
||||
return (
|
||||
<div className="panel-option-section">
|
||||
{props.title && (
|
||||
<div className="panel-option-section__header">
|
||||
{props.title}
|
||||
{props.onClose && (
|
||||
<button className="btn btn-link" onClick={props.onClose}>
|
||||
<i className="fa fa-remove" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="panel-option-section__body">{props.children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -4,13 +4,13 @@ import Remarkable from 'remarkable';
|
||||
import _ from 'lodash';
|
||||
|
||||
// Components
|
||||
import DataSourceOption from './DataSourceOption';
|
||||
import './../../panel/metrics_tab';
|
||||
import { EditorTabBody } from './EditorTabBody';
|
||||
import { DataSourcePicker } from './DataSourcePicker';
|
||||
import { QueryInspector } from './QueryInspector';
|
||||
import { TimeRangeOptions } from './TimeRangeOptions';
|
||||
import './../../panel/metrics_tab';
|
||||
import { QueryOptions } from './QueryOptions';
|
||||
import { AngularQueryComponentScope } from 'app/features/panel/metrics_tab';
|
||||
import { PanelOptionSection } from './PanelOptionSection';
|
||||
|
||||
// Services
|
||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
@ -157,75 +157,6 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
}
|
||||
};
|
||||
|
||||
renderOptions = close => {
|
||||
const { currentDS } = this.state;
|
||||
const { queryOptions } = currentDS.meta;
|
||||
const { panel } = this.props;
|
||||
|
||||
const onChangeFn = (panelKey: string) => {
|
||||
return (value: string | number) => {
|
||||
panel[panelKey] = value;
|
||||
panel.refresh();
|
||||
};
|
||||
};
|
||||
|
||||
const allOptions = {
|
||||
cacheTimeout: {
|
||||
label: 'Cache timeout',
|
||||
placeholder: '60',
|
||||
name: 'cacheTimeout',
|
||||
value: panel.cacheTimeout,
|
||||
tooltipInfo: (
|
||||
<>
|
||||
If your time series store has a query cache this option can override the default cache timeout. Specify a
|
||||
numeric value in seconds.
|
||||
</>
|
||||
),
|
||||
},
|
||||
maxDataPoints: {
|
||||
label: 'Max data points',
|
||||
placeholder: 'auto',
|
||||
name: 'maxDataPoints',
|
||||
value: panel.maxDataPoints,
|
||||
tooltipInfo: (
|
||||
<>
|
||||
The maximum data points the query should return. For graphs this is automatically set to one data point per
|
||||
pixel.
|
||||
</>
|
||||
),
|
||||
},
|
||||
minInterval: {
|
||||
label: 'Min time interval',
|
||||
placeholder: '0',
|
||||
name: 'minInterval',
|
||||
value: panel.interval,
|
||||
panelKey: 'interval',
|
||||
tooltipInfo: (
|
||||
<>
|
||||
A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example{' '}
|
||||
<code>1m</code> if your data is written every minute. Access auto interval via variable{' '}
|
||||
<code>$__interval</code> for time range string and <code>$__interval_ms</code> for numeric variable that can
|
||||
be used in math expressions.
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
const dsOptions = queryOptions
|
||||
? Object.keys(queryOptions).map(key => {
|
||||
const options = allOptions[key];
|
||||
return <DataSourceOption key={key} {...options} onChange={onChangeFn(allOptions[key].panelKey || key)} />;
|
||||
})
|
||||
: null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TimeRangeOptions panel={this.props.panel} />
|
||||
{dsOptions}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
renderQueryInspector = () => {
|
||||
const { panel } = this.props;
|
||||
return <QueryInspector panel={panel} LoadingPlaceholder={LoadingPlaceholder} />;
|
||||
@ -316,46 +247,42 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
const dsHelp = {
|
||||
title: '',
|
||||
heading: 'Help',
|
||||
icon: 'fa fa-question',
|
||||
disabled: !hasQueryHelp,
|
||||
onClick: this.loadHelp,
|
||||
render: this.renderHelp,
|
||||
};
|
||||
|
||||
const options = {
|
||||
title: 'Time Range',
|
||||
icon: '',
|
||||
disabled: false,
|
||||
render: this.renderOptions,
|
||||
};
|
||||
|
||||
return (
|
||||
<EditorTabBody
|
||||
heading="Queries"
|
||||
renderToolbar={this.renderToolbar}
|
||||
toolbarItems={[options, queryInspector, dsHelp]}
|
||||
>
|
||||
<div className="query-editor-rows gf-form-group">
|
||||
<div ref={element => (this.element = element)} />
|
||||
<EditorTabBody heading="Queries" renderToolbar={this.renderToolbar} toolbarItems={[queryInspector, dsHelp]}>
|
||||
<>
|
||||
<PanelOptionSection>
|
||||
<div className="query-editor-rows gf-form-group">
|
||||
<div ref={element => (this.element = element)} />
|
||||
|
||||
<div className="gf-form-query">
|
||||
<div className="gf-form gf-form-query-letter-cell">
|
||||
<label className="gf-form-label">
|
||||
<span className="gf-form-query-letter-cell-carret muted">
|
||||
<i className="fa fa-caret-down" />
|
||||
</span>
|
||||
<span className="gf-form-query-letter-cell-letter">{panel.getNextQueryLetter()}</span>
|
||||
</label>
|
||||
{!isAddingMixed && (
|
||||
<button className="btn btn-secondary gf-form-btn" onClick={this.onAddQueryClick}>
|
||||
Add Query
|
||||
</button>
|
||||
)}
|
||||
{isAddingMixed && this.renderMixedPicker()}
|
||||
<div className="gf-form-query">
|
||||
<div className="gf-form gf-form-query-letter-cell">
|
||||
<label className="gf-form-label">
|
||||
<span className="gf-form-query-letter-cell-carret muted">
|
||||
<i className="fa fa-caret-down" />
|
||||
</span>
|
||||
<span className="gf-form-query-letter-cell-letter">{panel.getNextQueryLetter()}</span>
|
||||
</label>
|
||||
{!isAddingMixed && (
|
||||
<button className="btn btn-secondary gf-form-btn" onClick={this.onAddQueryClick}>
|
||||
Add Query
|
||||
</button>
|
||||
)}
|
||||
{isAddingMixed && this.renderMixedPicker()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PanelOptionSection>
|
||||
<PanelOptionSection>
|
||||
<QueryOptions panel={panel} datasource={currentDS} />
|
||||
</PanelOptionSection>
|
||||
</>
|
||||
</EditorTabBody>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { JSONFormatter } from 'app/core/components/JSONFormatter/JSONFormatter';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
|
||||
@ -187,16 +187,10 @@ export class QueryInspector extends PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
{/*
|
||||
<button className="btn btn-transparent btn-p-x-0 m-r-1" onClick={this.onToggleMocking}>
|
||||
Mock response
|
||||
</button>
|
||||
*/}
|
||||
<div className="pull-right">
|
||||
<button className="btn btn-transparent btn-p-x-0 m-r-1" onClick={this.onToggleExpand}>
|
||||
{this.renderExpandCollapse()}
|
||||
</button>
|
||||
|
||||
<CopyToClipboard
|
||||
className="btn btn-transparent btn-p-x-0"
|
||||
text={this.getTextForClipboard}
|
||||
|
167
public/app/features/dashboard/dashgrid/QueryOptions.tsx
Normal file
167
public/app/features/dashboard/dashgrid/QueryOptions.tsx
Normal file
@ -0,0 +1,167 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Utils
|
||||
import { isValidTimeSpan } from 'app/core/utils/rangeutil';
|
||||
|
||||
// Components
|
||||
import { Switch } from 'app/core/components/Switch/Switch';
|
||||
import { Input } from 'app/core/components/Form';
|
||||
import { EventsWithValidation } from 'app/core/components/Form/Input';
|
||||
import { InputStatus } from 'app/core/components/Form/Input';
|
||||
import DataSourceOption from './DataSourceOption';
|
||||
|
||||
// Types
|
||||
import { PanelModel } from '../panel_model';
|
||||
import { ValidationEvents, DataSourceSelectItem } from 'app/types';
|
||||
|
||||
const timeRangeValidationEvents: ValidationEvents = {
|
||||
[EventsWithValidation.onBlur]: [
|
||||
{
|
||||
rule: value => {
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
return isValidTimeSpan(value);
|
||||
},
|
||||
errorMessage: 'Not a valid timespan',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const emptyToNull = (value: string) => {
|
||||
return value === '' ? null : value;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
panel: PanelModel;
|
||||
datasource: DataSourceSelectItem;
|
||||
}
|
||||
|
||||
export class QueryOptions extends PureComponent<Props> {
|
||||
onOverrideTime = (evt, status: InputStatus) => {
|
||||
const { value } = evt.target;
|
||||
const { panel } = this.props;
|
||||
const emptyToNullValue = emptyToNull(value);
|
||||
if (status === InputStatus.Valid && panel.timeFrom !== emptyToNullValue) {
|
||||
panel.timeFrom = emptyToNullValue;
|
||||
panel.refresh();
|
||||
}
|
||||
};
|
||||
|
||||
onTimeShift = (evt, status: InputStatus) => {
|
||||
const { value } = evt.target;
|
||||
const { panel } = this.props;
|
||||
const emptyToNullValue = emptyToNull(value);
|
||||
if (status === InputStatus.Valid && panel.timeShift !== emptyToNullValue) {
|
||||
panel.timeShift = emptyToNullValue;
|
||||
panel.refresh();
|
||||
}
|
||||
};
|
||||
|
||||
onToggleTimeOverride = () => {
|
||||
const { panel } = this.props;
|
||||
panel.hideTimeOverride = !panel.hideTimeOverride;
|
||||
panel.refresh();
|
||||
};
|
||||
|
||||
renderOptions() {
|
||||
const { datasource, panel } = this.props;
|
||||
const { queryOptions } = datasource.meta;
|
||||
|
||||
if (!queryOptions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onChangeFn = (panelKey: string) => {
|
||||
return (value: string | number) => {
|
||||
panel[panelKey] = value;
|
||||
panel.refresh();
|
||||
};
|
||||
};
|
||||
|
||||
const allOptions = {
|
||||
cacheTimeout: {
|
||||
label: 'Cache timeout',
|
||||
placeholder: '60',
|
||||
name: 'cacheTimeout',
|
||||
value: panel.cacheTimeout,
|
||||
tooltipInfo: (
|
||||
<>
|
||||
If your time series store has a query cache this option can override the default cache timeout. Specify a
|
||||
numeric value in seconds.
|
||||
</>
|
||||
),
|
||||
},
|
||||
maxDataPoints: {
|
||||
label: 'Max data points',
|
||||
placeholder: 'auto',
|
||||
name: 'maxDataPoints',
|
||||
value: panel.maxDataPoints,
|
||||
tooltipInfo: (
|
||||
<>
|
||||
The maximum data points the query should return. For graphs this is automatically set to one data point per
|
||||
pixel.
|
||||
</>
|
||||
),
|
||||
},
|
||||
minInterval: {
|
||||
label: 'Min time interval',
|
||||
placeholder: '0',
|
||||
name: 'minInterval',
|
||||
value: panel.interval,
|
||||
panelKey: 'interval',
|
||||
tooltipInfo: (
|
||||
<>
|
||||
A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example{' '}
|
||||
<code>1m</code> if your data is written every minute. Access auto interval via variable{' '}
|
||||
<code>$__interval</code> for time range string and <code>$__interval_ms</code> for numeric variable that can
|
||||
be used in math expressions.
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
return Object.keys(queryOptions).map(key => {
|
||||
const options = allOptions[key];
|
||||
return <DataSourceOption key={key} {...options} onChange={onChangeFn(allOptions[key].panelKey || key)} />;
|
||||
});
|
||||
}
|
||||
|
||||
render = () => {
|
||||
const hideTimeOverride = this.props.panel.hideTimeOverride;
|
||||
return (
|
||||
<div className="gf-form-inline">
|
||||
{this.renderOptions()}
|
||||
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label">Relative time</span>
|
||||
<Input
|
||||
type="text"
|
||||
className="width-6"
|
||||
placeholder="1h"
|
||||
onBlur={this.onOverrideTime}
|
||||
validationEvents={timeRangeValidationEvents}
|
||||
hideErrorMessage={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label">Time shift</span>
|
||||
<Input
|
||||
type="text"
|
||||
className="width-6"
|
||||
placeholder="1h"
|
||||
onBlur={this.onTimeShift}
|
||||
validationEvents={timeRangeValidationEvents}
|
||||
hideErrorMessage={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="gf-form-inline">
|
||||
<Switch label="Hide time info" checked={hideTimeOverride} onChange={this.onToggleTimeOverride} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Switch } from 'app/core/components/Switch/Switch';
|
||||
import { Input } from 'app/core/components/Form';
|
||||
import { isValidTimeSpan } from 'app/core/utils/rangeutil';
|
||||
import { ValidationEvents } from 'app/types';
|
||||
import { EventsWithValidation } from 'app/core/components/Form/Input';
|
||||
import { PanelModel } from '../panel_model';
|
||||
import { InputStatus } from 'app/core/components/Form/Input';
|
||||
|
||||
const timeRangeValidationEvents: ValidationEvents = {
|
||||
[EventsWithValidation.onBlur]: [
|
||||
{
|
||||
rule: value => {
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
return isValidTimeSpan(value);
|
||||
},
|
||||
errorMessage: 'Not a valid timespan',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const emptyToNull = (value: string) => {
|
||||
return value === '' ? null : value;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
panel: PanelModel;
|
||||
}
|
||||
|
||||
export class TimeRangeOptions extends PureComponent<Props> {
|
||||
onOverrideTime = (evt, status: InputStatus) => {
|
||||
const { value } = evt.target;
|
||||
const { panel } = this.props;
|
||||
const emptyToNullValue = emptyToNull(value);
|
||||
if (status === InputStatus.Valid && panel.timeFrom !== emptyToNullValue) {
|
||||
panel.timeFrom = emptyToNullValue;
|
||||
panel.refresh();
|
||||
}
|
||||
};
|
||||
|
||||
onTimeShift = (evt, status: InputStatus) => {
|
||||
const { value } = evt.target;
|
||||
const { panel } = this.props;
|
||||
const emptyToNullValue = emptyToNull(value);
|
||||
if (status === InputStatus.Valid && panel.timeShift !== emptyToNullValue) {
|
||||
panel.timeShift = emptyToNullValue;
|
||||
panel.refresh();
|
||||
}
|
||||
};
|
||||
|
||||
onToggleTimeOverride = () => {
|
||||
const { panel } = this.props;
|
||||
panel.hideTimeOverride = !panel.hideTimeOverride;
|
||||
panel.refresh();
|
||||
};
|
||||
|
||||
render = () => {
|
||||
const hideTimeOverride = this.props.panel.hideTimeOverride;
|
||||
return (
|
||||
<>
|
||||
<h5 className="section-heading">Time Range</h5>
|
||||
|
||||
<div className="gf-form-group">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-12">Override relative time</span>
|
||||
<Input
|
||||
type="text"
|
||||
className="gf-form-input max-width-8"
|
||||
placeholder="1h"
|
||||
onBlur={this.onOverrideTime}
|
||||
validationEvents={timeRangeValidationEvents}
|
||||
hideErrorMessage={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label width-12">Add time shift</span>
|
||||
<Input
|
||||
type="text"
|
||||
className="gf-form-input max-width-8"
|
||||
placeholder="1h"
|
||||
onBlur={this.onTimeShift}
|
||||
validationEvents={timeRangeValidationEvents}
|
||||
hideErrorMessage={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="gf-form-inline">
|
||||
<Switch label="Hide time override info" checked={hideTimeOverride} onChange={this.onToggleTimeOverride} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
@ -8,6 +8,7 @@ import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoa
|
||||
import { EditorTabBody } from './EditorTabBody';
|
||||
import { VizTypePicker } from './VizTypePicker';
|
||||
import { FadeIn } from 'app/core/components/Animations/FadeIn';
|
||||
import { PanelOptionSection } from './PanelOptionSection';
|
||||
|
||||
// Types
|
||||
import { PanelModel } from '../panel_model';
|
||||
@ -59,11 +60,15 @@ export class VisualizationTab extends PureComponent<Props, State> {
|
||||
return <div ref={element => (this.element = element)} />;
|
||||
}
|
||||
|
||||
if (PanelOptions) {
|
||||
return <PanelOptions options={this.getPanelDefaultOptions()} onChange={this.onPanelOptionsChanged} />;
|
||||
} else {
|
||||
return <p>Visualization has no options</p>;
|
||||
}
|
||||
return (
|
||||
<PanelOptionSection>
|
||||
{PanelOptions ? (
|
||||
<PanelOptions options={this.getPanelDefaultOptions()} onChange={this.onPanelOptionsChanged} />
|
||||
) : (
|
||||
<p>Visualization has no options</p>
|
||||
)}
|
||||
</PanelOptionSection>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -105,9 +110,9 @@ export class VisualizationTab extends PureComponent<Props, State> {
|
||||
for (let i = 0; i < panelCtrl.editorTabs.length; i++) {
|
||||
template +=
|
||||
`
|
||||
<div class="form-section" ng-cloak>` +
|
||||
(i > -1 ? `<div class="form-section__header">{{ctrl.editorTabs[${i}].title}}</div>` : '') +
|
||||
`<div class="form-section__body">
|
||||
<div class="panel-option-section" ng-cloak>` +
|
||||
(i > 0 ? `<div class="panel-option-section__header">{{ctrl.editorTabs[${i}].title}}</div>` : '') +
|
||||
`<div class="panel-option-section__body">
|
||||
<panel-editor-tab editor-tab="ctrl.editorTabs[${i}]" ctrl="ctrl"></panel-editor-tab>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,8 +1,4 @@
|
||||
<div class="editor-row">
|
||||
<h5 class="section-heading">
|
||||
Drilldown / detail link<tip>These links appear in the dropdown menu in the panel menu. </tip></h5>
|
||||
</h5>
|
||||
|
||||
<div class="gf-form-group" ng-repeat="link in panel.links">
|
||||
<div class="section">
|
||||
<div class="gf-form max-width-25">
|
||||
|
@ -177,6 +177,9 @@ export class DataSourceSettings extends PureComponent<Props, State> {
|
||||
<div className="page-container page-body">
|
||||
<div>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
{this.isReadOnly() && this.renderIsReadOnlyMessage()}
|
||||
{this.shouldRenderInfoBox() && <div className="grafana-info-box">{this.getInfoText()}</div>}
|
||||
|
||||
<BasicSettings
|
||||
dataSourceName={dataSource.name}
|
||||
isDefault={dataSource.isDefault}
|
||||
@ -184,9 +187,6 @@ export class DataSourceSettings extends PureComponent<Props, State> {
|
||||
onNameChange={name => setDataSourceName(name)}
|
||||
/>
|
||||
|
||||
{this.shouldRenderInfoBox() && <div className="grafana-info-box">{this.getInfoText()}</div>}
|
||||
|
||||
{this.isReadOnly() && this.renderIsReadOnlyMessage()}
|
||||
{dataSourceMeta.module && (
|
||||
<PluginSettings
|
||||
dataSource={dataSource}
|
||||
|
@ -12,17 +12,17 @@ exports[`Render should render alpha info text 1`] = `
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="grafana-info-box"
|
||||
>
|
||||
This plugin is marked as being in alpha state, which means it is in early development phase and updates will include breaking changes.
|
||||
</div>
|
||||
<BasicSettings
|
||||
dataSourceName="gdev-cloudwatch"
|
||||
isDefault={false}
|
||||
onDefaultChange={[Function]}
|
||||
onNameChange={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="grafana-info-box"
|
||||
>
|
||||
This plugin is marked as being in alpha state, which means it is in early development phase and updates will include breaking changes.
|
||||
</div>
|
||||
<PluginSettings
|
||||
dataSource={
|
||||
Object {
|
||||
@ -111,17 +111,17 @@ exports[`Render should render beta info text 1`] = `
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="grafana-info-box"
|
||||
>
|
||||
This plugin is marked as being in a beta development state. This means it is in currently in active development and could be missing important features.
|
||||
</div>
|
||||
<BasicSettings
|
||||
dataSourceName="gdev-cloudwatch"
|
||||
isDefault={false}
|
||||
onDefaultChange={[Function]}
|
||||
onNameChange={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="grafana-info-box"
|
||||
>
|
||||
This plugin is marked as being in a beta development state. This means it is in currently in active development and could be missing important features.
|
||||
</div>
|
||||
<PluginSettings
|
||||
dataSource={
|
||||
Object {
|
||||
@ -304,17 +304,17 @@ exports[`Render should render is ready only message 1`] = `
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="grafana-info-box span8"
|
||||
>
|
||||
This datasource was added by config and cannot be modified using the UI. Please contact your server admin to update this datasource.
|
||||
</div>
|
||||
<BasicSettings
|
||||
dataSourceName="gdev-cloudwatch"
|
||||
isDefault={false}
|
||||
onDefaultChange={[Function]}
|
||||
onNameChange={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="grafana-info-box span8"
|
||||
>
|
||||
This datasource was added by config and cannot be modified using the UI. Please contact your server admin to update this datasource.
|
||||
</div>
|
||||
<PluginSettings
|
||||
dataSource={
|
||||
Object {
|
||||
|
@ -1,37 +1,49 @@
|
||||
<div class="editor-row">
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">Info</h5>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-7">Title</span>
|
||||
<input type="text" class="gf-form-input width-25" ng-model='ctrl.panel.title' ng-model-onblur></input>
|
||||
</div>
|
||||
<div class="gf-form gf-form--v-stretch">
|
||||
<span class="gf-form-label width-7">Description</span>
|
||||
<textarea class="gf-form-input width-25" rows="3" ng-model="ctrl.panel.description" ng-model-onblur placeholder="Panel description, supports markdown & links"></textarea>
|
||||
</div>
|
||||
<gf-form-switch class="gf-form" label-class="width-7" switch-class="max-width-6" label="Transparent" checked="ctrl.panel.transparent" on-change="ctrl.render()"></gf-form-switch>
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">Repeat</h5>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-9">For each value of</span>
|
||||
<dash-repeat-option panel="ctrl.panel"></dash-repeat-option>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.panel.repeat">
|
||||
<span class="gf-form-label width-9">Direction</span>
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.repeatDirection" ng-options="f.value as f.text for f in [{value: 'v', text: 'Vertical'}, {value: 'h', text: 'Horizontal'}]">
|
||||
<option value=""></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.panel.repeat && ctrl.panel.repeatDirection == 'h'">
|
||||
<span class="gf-form-label width-9">Min width</span>
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.minSpan" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]">
|
||||
<option value=""></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<panel-links-editor panel="ctrl.panel"></panel-links-editor>
|
||||
<div class="panel-option-section">
|
||||
<!-- <div class="panel-option-section__header">Information</div> -->
|
||||
<div class="panel-option-section__body">
|
||||
<div class="section">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-7">Title</span>
|
||||
<input type="text" class="gf-form-input width-25" ng-model='ctrl.panel.title' ng-model-onblur></input>
|
||||
</div>
|
||||
<gf-form-switch class="gf-form" label-class="width-7" switch-class="max-width-6" label="Transparent" checked="ctrl.panel.transparent" on-change="ctrl.render()"></gf-form-switch>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="gf-form gf-form--v-stretch">
|
||||
<span class="gf-form-label width-7">Description</span>
|
||||
<textarea class="gf-form-input width-25" rows="5" ng-model="ctrl.panel.description" ng-model-onblur placeholder="Panel description, supports markdown & links"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-option-section">
|
||||
<div class="panel-option-section__header">Repeating</div>
|
||||
<div class="panel-option-section__body">
|
||||
<div class="section">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-9">Repat</span>
|
||||
<dash-repeat-option panel="ctrl.panel"></dash-repeat-option>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.panel.repeat">
|
||||
<span class="gf-form-label width-9">Direction</span>
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.repeatDirection" ng-options="f.value as f.text for f in [{value: 'v', text: 'Vertical'}, {value: 'h', text: 'Horizontal'}]">
|
||||
<option value=""></option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.panel.repeat && ctrl.panel.repeatDirection == 'h'">
|
||||
<span class="gf-form-label width-9">Min width</span>
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.minSpan" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]">
|
||||
<option value=""></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-option-section">
|
||||
<div class="panel-option-section__header">Drildown Links</div>
|
||||
<div class="panel-option-section__body">
|
||||
<panel-links-editor panel="ctrl.panel"></panel-links-editor>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -22,10 +22,10 @@
|
||||
</div>
|
||||
<gf-form-switch ng-disabled="!ctrl.panel.lines" class="gf-form" label="Staircase" label-class="width-8" checked="ctrl.panel.steppedLine" on-change="ctrl.render()">
|
||||
</gf-form-switch>
|
||||
<div class="gf-form">
|
||||
<div class="gf-form" ng-if="ctrl.panel.points">
|
||||
<label class="gf-form-label width-8">Point Radius</label>
|
||||
<div class="gf-form-select-wrapper max-width-5">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.pointradius" ng-options="f for f in [0.5,1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()" ng-disabled="!ctrl.panel.points"></select>
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.pointradius" ng-options="f for f in [0.5,1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -66,7 +66,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group">
|
||||
<div>
|
||||
<div class="gf-form-inline" ng-repeat="override in ctrl.panel.seriesOverrides" ng-controller="SeriesOverridesCtrl">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">alias or regex</label>
|
||||
@ -85,16 +85,16 @@
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="gf-form">
|
||||
<span class="dropdown" dropdown-typeahead="overrideMenu" dropdown-typeahead-on-select="setOverride($item, $subItem)">
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">
|
||||
<i class="fa fa-trash pointer" ng-click="ctrl.removeSeriesOverride(override)"></i>
|
||||
|
@ -387,6 +387,9 @@ $panel-editor-tabs-line-color: #e3e3e3;
|
||||
$panel-editor-viz-item-bg-hover: darken($blue, 47%);
|
||||
$panel-editor-viz-item-bg-hover-active: darken($orange, 45%);
|
||||
|
||||
$panel-option-section-border: 1px solid $dark-3;
|
||||
$panel-option-section-header-bg: linear-gradient(0deg, $gray-blue, $dark-1);
|
||||
|
||||
$panel-grid-placeholder-bg: darken($blue, 47%);
|
||||
$panel-grid-placeholder-shadow: 0 0 4px $blue;
|
||||
|
||||
|
@ -396,6 +396,9 @@ $panel-editor-tabs-line-color: $dark-5;
|
||||
$panel-editor-viz-item-bg-hover: lighten($blue, 62%);
|
||||
$panel-editor-viz-item-bg-hover-active: lighten($orange, 34%);
|
||||
|
||||
$panel-option-section-border: 1px solid $gray-6;
|
||||
$panel-option-section-header-bg: linear-gradient(0deg, $gray-6, $gray-7);
|
||||
|
||||
$panel-grid-placeholder-bg: lighten($blue, 62%);
|
||||
$panel-grid-placeholder-shadow: 0 0 4px $blue-light;
|
||||
|
||||
|
@ -32,7 +32,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
background: $page-bg;
|
||||
background: $input-bg;
|
||||
margin: 0 20px 0 84px;
|
||||
border-radius: 3px;
|
||||
box-shadow: $panel-editor-shadow;
|
||||
@ -63,12 +63,7 @@
|
||||
}
|
||||
|
||||
.panel-editor__content {
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.panel-editor__toolbar-view {
|
||||
background: $panel-editor-toolbar-view-bg;
|
||||
padding: 20px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.panel-in-fullscreen {
|
||||
@ -132,8 +127,7 @@
|
||||
}
|
||||
|
||||
.viz-picker {
|
||||
margin-top: -40px;
|
||||
padding: 20px;
|
||||
padding: 0px 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@ -143,11 +137,11 @@
|
||||
}
|
||||
|
||||
.viz-picker__item {
|
||||
background: $panel-bg;
|
||||
border: $panel-border;
|
||||
background: $panel-editor-viz-item-bg;
|
||||
border: $panel-editor-viz-item-border;
|
||||
border-radius: 3px;
|
||||
height: 100px;
|
||||
width: 150px;
|
||||
width: 145px;
|
||||
flex-shrink: 0;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
@ -243,87 +237,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
.ds-picker-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 13px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ds-picker-list__item {
|
||||
background: $panel-editor-viz-item-bg;
|
||||
border: $panel-editor-viz-item-border;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
margin-bottom: 3px;
|
||||
padding: 5px 15px;
|
||||
align-items: center;
|
||||
height: 44px;
|
||||
|
||||
&--selected {
|
||||
background: $panel-editor-viz-item-bg-hover;
|
||||
border: $panel-editor-viz-item-border-hover;
|
||||
box-shadow: $panel-editor-viz-item-shadow-hover;
|
||||
}
|
||||
|
||||
&--active {
|
||||
box-shadow: 0 0 6px $orange;
|
||||
border: 1px solid $orange;
|
||||
|
||||
.ds-picker-list__name {
|
||||
color: $text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ds-picker {
|
||||
position: relative;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.ds-picker-menu {
|
||||
min-width: 400px;
|
||||
max-width: 500px;
|
||||
position: absolute;
|
||||
background: $panel-editor-toolbar-view-bg;
|
||||
padding: 5px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ds-picker-list__name {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
font-size: $font-size-md;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.ds-picker-list__img {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.form-option-box {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-option-box__header {
|
||||
border-bottom: 2px solid $dark-4;
|
||||
padding: 5px 0px;
|
||||
font-size: $font-size-md;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
.panel-option-section {
|
||||
margin-bottom: 10px;
|
||||
border: $panel-option-section-border;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
.form-section__header {
|
||||
padding: 5px 10px;
|
||||
font-size: $font-size-h5;
|
||||
margin-bottom: 20px;
|
||||
background: $input-label-bg;
|
||||
border-radius: 3px;
|
||||
.panel-option-section__header {
|
||||
padding: 4px 20px;
|
||||
font-size: 1.1rem;
|
||||
background: $panel-option-section-header-bg;
|
||||
position: relative;
|
||||
|
||||
.btn {
|
||||
@ -333,10 +261,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.form-section__body {
|
||||
padding: 0 10px;
|
||||
}
|
||||
.panel-option-section__body {
|
||||
padding: 20px;
|
||||
background: $page-bg;
|
||||
|
||||
.panel-editor-tabs__item-popover {
|
||||
background: $orange;
|
||||
&--queries {
|
||||
min-height: 200px;
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,10 @@
|
||||
color: $orange;
|
||||
}
|
||||
|
||||
.query-editor-rows {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.gf-form-query {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -58,6 +58,6 @@
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
font-size: 1.1rem;
|
||||
font-size: $font-size-md;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user