mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
QueryTab: Design updates (#23906)
* WIP: first stage * Another take * argghhh * Updated * My brain is mush * Minor progress * Progres * Starting to work * Fixes * fixed e2e
This commit is contained in:
parent
b9a40fc346
commit
49276f2c12
@ -27,7 +27,7 @@ e2e.scenario({
|
||||
|
||||
e2e.flows.openPanelMenuItem(e2e.flows.PanelMenuItems.Edit, PANEL_UNDER_TEST);
|
||||
|
||||
e2e.components.QueryEditorToolbarItem.button('Query inspector')
|
||||
e2e.components.QueryTab.queryInspectorButton()
|
||||
.should('be.visible')
|
||||
.click();
|
||||
|
||||
|
@ -77,6 +77,7 @@ export const Components = {
|
||||
QueryTab: componentFactory({
|
||||
selectors: {
|
||||
content: 'Query editor tab content',
|
||||
queryInspectorButton: 'Query inspector button',
|
||||
},
|
||||
}),
|
||||
AlertTab: componentFactory({
|
||||
|
@ -13,7 +13,7 @@ export interface Props {
|
||||
title?: ReactNode;
|
||||
/** Subtitle shown below the title */
|
||||
subtitle?: ReactNode;
|
||||
/** Should the Drawer be closable by clicking on the mask */
|
||||
/** Should the Drawer be closable by clicking on the mask, defaults to true */
|
||||
closeOnMaskClick?: boolean;
|
||||
/** Render the drawer inside a container on the page */
|
||||
inline?: boolean;
|
||||
@ -70,7 +70,7 @@ export const Drawer: FC<Props> = ({
|
||||
children,
|
||||
inline = false,
|
||||
onClose,
|
||||
closeOnMaskClick = false,
|
||||
closeOnMaskClick = true,
|
||||
scrollableContent = false,
|
||||
title,
|
||||
subtitle,
|
||||
|
@ -3,7 +3,7 @@ import { GrafanaTheme } from '@grafana/data';
|
||||
import { stylesFactory } from '../../themes';
|
||||
|
||||
export const getModalStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
const backdropBackground = theme.colors.bg1;
|
||||
const backdropBackground = theme.colors.bg3;
|
||||
|
||||
return {
|
||||
modal: css`
|
||||
|
@ -329,12 +329,12 @@ export function SelectBase<T>({
|
||||
zIndex: theme.zIndex.dropdown,
|
||||
}),
|
||||
//These are required for the menu positioning to function
|
||||
menu: ({ top, bottom, width, position }: any) => ({
|
||||
menu: ({ top, bottom, position }: any) => ({
|
||||
top,
|
||||
bottom,
|
||||
width,
|
||||
position,
|
||||
marginBottom: !!bottom ? '10px' : '0',
|
||||
'min-width': '100%',
|
||||
zIndex: theme.zIndex.dropdown,
|
||||
}),
|
||||
container: () => ({
|
||||
|
@ -136,7 +136,7 @@ $divider-border-color: $gray-1;
|
||||
$tight-form-func-bg: $dark-9;
|
||||
$tight-form-func-highlight-bg: $dark-10;
|
||||
|
||||
$modal-backdrop-bg: ${theme.colors.bg1};
|
||||
$modal-backdrop-bg: ${theme.colors.bg3};
|
||||
$code-tag-bg: $dark-1;
|
||||
$code-tag-border: $dark-9;
|
||||
|
||||
|
@ -6,6 +6,7 @@ import { useUpdateEffect } from 'react-use';
|
||||
|
||||
interface QueryOperationRowProps {
|
||||
title?: ((props: { isOpen: boolean }) => React.ReactNode) | React.ReactNode;
|
||||
headerElement?: React.ReactNode;
|
||||
actions?:
|
||||
| ((props: { isOpen: boolean; openRow: () => void; closeRow: () => void }) => React.ReactNode)
|
||||
| React.ReactNode;
|
||||
@ -19,6 +20,7 @@ export const QueryOperationRow: React.FC<QueryOperationRowProps> = ({
|
||||
children,
|
||||
actions,
|
||||
title,
|
||||
headerElement,
|
||||
onClose,
|
||||
onOpen,
|
||||
isOpen,
|
||||
@ -64,6 +66,7 @@ export const QueryOperationRow: React.FC<QueryOperationRowProps> = ({
|
||||
>
|
||||
<Icon name={isContentVisible ? 'angle-down' : 'angle-right'} className={styles.collapseIcon} />
|
||||
{title && <span className={styles.title}>{titleElement}</span>}
|
||||
{headerElement}
|
||||
</div>
|
||||
{actions && actionsElement}
|
||||
</HorizontalGroup>
|
||||
@ -76,7 +79,7 @@ export const QueryOperationRow: React.FC<QueryOperationRowProps> = ({
|
||||
const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
return {
|
||||
wrapper: css`
|
||||
margin-bottom: ${theme.spacing.formSpacingBase * 2}px;
|
||||
margin-bottom: ${theme.spacing.md};
|
||||
`,
|
||||
header: css`
|
||||
padding: 0 ${theme.spacing.sm};
|
||||
@ -90,6 +93,9 @@ const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
`,
|
||||
collapseIcon: css`
|
||||
color: ${theme.colors.textWeak};
|
||||
&:hover {
|
||||
color: ${theme.colors.text};
|
||||
}
|
||||
`,
|
||||
titleWrapper: css`
|
||||
display: flex;
|
||||
|
@ -2,9 +2,8 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Components
|
||||
import { LegacyForms } from '@grafana/ui';
|
||||
import { Select } from '@grafana/ui';
|
||||
import { SelectableValue, DataSourceSelectItem } from '@grafana/data';
|
||||
const { Select } = LegacyForms;
|
||||
|
||||
export interface Props {
|
||||
onChange: (ds: DataSourceSelectItem) => void;
|
||||
@ -66,23 +65,22 @@ export class DataSourcePicker extends PureComponent<Props> {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="gf-form-inline">
|
||||
<Select
|
||||
className="ds-picker"
|
||||
isMulti={false}
|
||||
isClearable={false}
|
||||
backspaceRemovesValue={false}
|
||||
onChange={this.onChange}
|
||||
options={options}
|
||||
autoFocus={autoFocus}
|
||||
onBlur={onBlur}
|
||||
openMenuOnFocus={openMenuOnFocus}
|
||||
maxMenuHeight={500}
|
||||
placeholder={placeholder}
|
||||
noOptionsMessage={() => 'No datasources found'}
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
className="ds-picker select-container"
|
||||
isMulti={false}
|
||||
isClearable={false}
|
||||
backspaceRemovesValue={false}
|
||||
onChange={this.onChange}
|
||||
options={options}
|
||||
autoFocus={autoFocus}
|
||||
onBlur={onBlur}
|
||||
openMenuOnFocus={openMenuOnFocus}
|
||||
maxMenuHeight={500}
|
||||
menuPlacement="bottom"
|
||||
placeholder={placeholder}
|
||||
noOptionsMessage="No datasources found"
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -205,7 +205,7 @@ class UnConnectedAlertTab extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
return (
|
||||
<EditorTabBody heading="Alert" toolbarItems={toolbarItems}>
|
||||
<EditorTabBody toolbarItems={toolbarItems}>
|
||||
<div aria-label={e2e.components.AlertTab.selectors.content}>
|
||||
{alert && hasTransformations && (
|
||||
<Alert
|
||||
|
@ -2,244 +2,235 @@
|
||||
<div class="alert alert-error m-b-2" ng-show="ctrl.error">
|
||||
<icon name="'exclamation-triangle'"></icon> {{ctrl.error}}
|
||||
</div>
|
||||
<div class="panel-options-group">
|
||||
<div class="panel-options-group__body">
|
||||
<div class="gf-form-group">
|
||||
<h4 class="section-heading">Rule</h4>
|
||||
<div class="gf-form-inline">
|
||||
<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">
|
||||
<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"
|
||||
ng-blur="ctrl.checkFrequency()"
|
||||
/>
|
||||
</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 gf-form-input--has-help-icon"
|
||||
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 class="gf-form" ng-if="ctrl.frequencyWarning">
|
||||
<label class="gf-form-label text-warning">
|
||||
<icon name="'exclamation-triangle'"></icon> {{ctrl.frequencyWarning}}
|
||||
</label>
|
||||
|
||||
<div class="gf-form-group">
|
||||
<h4 class="section-heading">Rule</h4>
|
||||
<div class="gf-form-inline">
|
||||
<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">
|
||||
<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"
|
||||
ng-blur="ctrl.checkFrequency()"
|
||||
/>
|
||||
</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 gf-form-input--has-help-icon"
|
||||
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 class="gf-form" ng-if="ctrl.frequencyWarning">
|
||||
<label class="gf-form-label text-warning">
|
||||
<icon name="'exclamation-triangle'"></icon> {{ctrl.frequencyWarning}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group">
|
||||
<h4 class="section-heading">Conditions</h4>
|
||||
<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)">
|
||||
<icon name="'trash-alt'"></icon>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label dropdown">
|
||||
<a class="pointer dropdown-toggle" data-toggle="dropdown">
|
||||
<icon name="'plus-circle'"></icon>
|
||||
</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">
|
||||
<h4 class="section-heading">No Data & Error Handling</h4>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-15">If no data or all values are null</span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<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>
|
||||
|
||||
<div class="gf-form-group">
|
||||
<h4 class="section-heading">Conditions</h4>
|
||||
<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)">
|
||||
<icon name="'trash-alt'"></icon>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label dropdown">
|
||||
<a class="pointer dropdown-toggle" data-toggle="dropdown">
|
||||
<icon name="'plus-circle'"></icon>
|
||||
</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 class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-15">If execution error or timeout</span>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group">
|
||||
<h4 class="section-heading">No Data & Error Handling</h4>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-15">If no data or all values are null</span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<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>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-15">If execution error or timeout</span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<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 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>
|
||||
</div>
|
||||
|
||||
<div class="panel-options-group">
|
||||
<div class="panel-options-group__header">Notifications</div>
|
||||
<div class="panel-options-group__body">
|
||||
<h4 class="section-heading">Notifications</h4>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-8">Send to</span>
|
||||
</div>
|
||||
<div class="gf-form" ng-repeat="nc in ctrl.alertNotifications">
|
||||
<span class="gf-form-label">
|
||||
<icon name="'{{nc.iconClass}}'"></icon>
|
||||
{{nc.name}} <span ng-if="nc.isDefault">(default)</span>
|
||||
<icon
|
||||
name="'times'"
|
||||
class="pointer muted"
|
||||
ng-click="ctrl.removeNotification(nc)"
|
||||
ng-if="nc.isDefault === false"
|
||||
></icon>
|
||||
</span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<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 class="gf-form">
|
||||
<span class="gf-form-label width-8">Tags</span>
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form-inline" ng-repeat="(name, value) in ctrl.alert.alertRuleTags">
|
||||
<label class="gf-form-label width-15">{{ name }}</label>
|
||||
<input
|
||||
class="gf-form-input width-15"
|
||||
placeholder="Tag value..."
|
||||
ng-model="ctrl.alert.alertRuleTags[name]"
|
||||
type="text"
|
||||
/>
|
||||
<label class="gf-form-label">
|
||||
<a class="pointer" tabindex="1" ng-click="ctrl.removeAlertRuleTag(name)">
|
||||
<icon name="'trash-alt'"></icon>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-8">Send to</span>
|
||||
<input
|
||||
class="gf-form-input width-15"
|
||||
placeholder="New tag name..."
|
||||
ng-model="ctrl.newAlertRuleTag.name"
|
||||
type="text"
|
||||
/>
|
||||
<input
|
||||
class="gf-form-input width-15"
|
||||
placeholder="New tag value..."
|
||||
ng-model="ctrl.newAlertRuleTag.value"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<div class="gf-form" ng-repeat="nc in ctrl.alertNotifications">
|
||||
<span class="gf-form-label">
|
||||
<icon name="'{{nc.iconClass}}'"></icon>
|
||||
{{nc.name}} <span ng-if="nc.isDefault">(default)</span>
|
||||
<icon
|
||||
name="'times'"
|
||||
class="pointer muted"
|
||||
ng-click="ctrl.removeNotification(nc)"
|
||||
ng-if="nc.isDefault === false"
|
||||
></icon>
|
||||
</span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<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 class="gf-form">
|
||||
<span class="gf-form-label width-8">Tags</span>
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form-inline" ng-repeat="(name, value) in ctrl.alert.alertRuleTags">
|
||||
<label class="gf-form-label width-15">{{ name }}</label>
|
||||
<input
|
||||
class="gf-form-input width-15"
|
||||
placeholder="Tag value..."
|
||||
ng-model="ctrl.alert.alertRuleTags[name]"
|
||||
type="text"
|
||||
/>
|
||||
<label class="gf-form-label">
|
||||
<a class="pointer" tabindex="1" ng-click="ctrl.removeAlertRuleTag(name)">
|
||||
<icon name="'trash-alt'"></icon>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<input
|
||||
class="gf-form-input width-15"
|
||||
placeholder="New tag name..."
|
||||
ng-model="ctrl.newAlertRuleTag.name"
|
||||
type="text"
|
||||
/>
|
||||
<input
|
||||
class="gf-form-input width-15"
|
||||
placeholder="New tag value..."
|
||||
ng-model="ctrl.newAlertRuleTag.value"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">
|
||||
<a class="pointer" tabindex="1" ng-click="ctrl.addAlertRuleTag()">
|
||||
<icon name="'plus-circle'"></icon> Add Tag
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label class="gf-form-label">
|
||||
<a class="pointer" tabindex="1" ng-click="ctrl.addAlertRuleTag()">
|
||||
<icon name="'plus-circle'"></icon> Add Tag
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -15,7 +15,9 @@ interface Props {
|
||||
export const DataSourceOption: FC<Props> = ({ label, placeholder, name, value, onBlur, onChange, tooltipInfo }) => {
|
||||
return (
|
||||
<div className="gf-form gf-form--flex-end">
|
||||
<InlineFormLabel tooltip={tooltipInfo}>{label}</InlineFormLabel>
|
||||
<InlineFormLabel width={9} tooltip={tooltipInfo}>
|
||||
{label}
|
||||
</InlineFormLabel>
|
||||
<Input
|
||||
type="text"
|
||||
className="gf-form-input width-6"
|
||||
|
@ -7,7 +7,6 @@ import { e2e } from '@grafana/e2e';
|
||||
|
||||
interface Props {
|
||||
children: JSX.Element;
|
||||
heading: string;
|
||||
renderToolbar?: () => JSX.Element;
|
||||
toolbarItems?: EditorToolbarView[];
|
||||
scrollTop?: number;
|
||||
@ -110,16 +109,13 @@ export class EditorTabBody extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children, renderToolbar, heading, toolbarItems, scrollTop, setScrollTop } = this.props;
|
||||
const { children, renderToolbar, toolbarItems, scrollTop, setScrollTop } = this.props;
|
||||
const { openView, fadeIn, isOpen } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="toolbar">
|
||||
<div className="toolbar__left">
|
||||
<div className="toolbar__heading">{heading}</div>
|
||||
{renderToolbar && renderToolbar()}
|
||||
</div>
|
||||
{renderToolbar && renderToolbar()}
|
||||
{toolbarItems.map(item => this.renderButton(item))}
|
||||
</div>
|
||||
<div className="panel-editor__scroll">
|
||||
|
@ -1,10 +1,9 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
// Components
|
||||
import { EditorTabBody, EditorToolbarView } from './EditorTabBody';
|
||||
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
||||
import { QueryOptions } from './QueryOptions';
|
||||
import { PanelOptionsGroup } from '@grafana/ui';
|
||||
import { CustomScrollbar, stylesFactory, Button, HorizontalGroup, Modal } from '@grafana/ui';
|
||||
import { getLocationSrv } from '@grafana/runtime';
|
||||
import { QueryEditorRows } from './QueryEditorRows';
|
||||
// Services
|
||||
@ -20,6 +19,7 @@ import { addQuery } from 'app/core/utils/query';
|
||||
import { Unsubscribable } from 'rxjs';
|
||||
import { DashboardQueryEditor, isSharedDashboardQuery } from 'app/plugins/datasource/dashboard';
|
||||
import { expressionDatasource, ExpressionDatasourceID } from 'app/features/expressions/ExpressionDatasource';
|
||||
import { css } from 'emotion';
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
interface Props {
|
||||
@ -35,6 +35,7 @@ interface State {
|
||||
isAddingMixed: boolean;
|
||||
scrollTop: number;
|
||||
data: PanelData;
|
||||
isHelpOpen: boolean;
|
||||
}
|
||||
|
||||
export class QueriesTab extends PureComponent<Props, State> {
|
||||
@ -48,6 +49,7 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
helpContent: null,
|
||||
isPickerOpen: false,
|
||||
isAddingMixed: false,
|
||||
isHelpOpen: false,
|
||||
scrollTop: 0,
|
||||
data: {
|
||||
state: LoadingState.NotStarted,
|
||||
@ -121,6 +123,7 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
|
||||
openQueryInspector = () => {
|
||||
const { panel } = this.props;
|
||||
|
||||
getLocationSrv().update({
|
||||
query: { inspect: panel.id, inspectTab: 'query' },
|
||||
partial: true,
|
||||
@ -128,7 +131,7 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
renderHelp = () => {
|
||||
return <PluginHelp plugin={this.state.currentDS.meta} type="query_help" />;
|
||||
return;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -155,30 +158,45 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
onScrollBottom = () => {
|
||||
this.setState({ scrollTop: this.state.scrollTop + 10000 });
|
||||
this.setState({ scrollTop: 1000 });
|
||||
};
|
||||
|
||||
renderToolbar = () => {
|
||||
const { currentDS, isAddingMixed } = this.state;
|
||||
const showAddButton = !(isAddingMixed || isSharedDashboardQuery(currentDS.name));
|
||||
renderTopSection(styles: QueriesTabStyls) {
|
||||
const { panel } = this.props;
|
||||
const { currentDS, data } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataSourcePicker datasources={this.datasources} onChange={this.onChangeDataSource} current={currentDS} />
|
||||
<div className="flex-grow-1" />
|
||||
{showAddButton && (
|
||||
<button className="btn navbar-button" onClick={this.onAddQueryClick}>
|
||||
Add query
|
||||
</button>
|
||||
)}
|
||||
{isAddingMixed && this.renderMixedPicker()}
|
||||
{config.featureToggles.expressions && (
|
||||
<button className="btn navbar-button" onClick={this.onAddExpressionClick}>
|
||||
Add Expression
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
<div>
|
||||
<div className={styles.dataSourceRow}>
|
||||
<div className={styles.dataSourceRowItem}>
|
||||
<DataSourcePicker datasources={this.datasources} onChange={this.onChangeDataSource} current={currentDS} />
|
||||
</div>
|
||||
<div className={styles.dataSourceRowItem}>
|
||||
<Button variant="secondary" icon="info-circle" title="Open data source help" onClick={this.onOpenHelp} />
|
||||
</div>
|
||||
<div className={styles.dataSourceRowItem}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={this.openQueryInspector}
|
||||
aria-label={e2e.components.QueryTab.selectors.queryInspectorButton}
|
||||
>
|
||||
Query inspector
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.dataSourceRowItemOptions}>
|
||||
<QueryOptions panel={panel} datasource={currentDS} data={data} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onOpenHelp = () => {
|
||||
this.setState({ isHelpOpen: true });
|
||||
};
|
||||
|
||||
onCloseHelp = () => {
|
||||
this.setState({ isHelpOpen: false });
|
||||
};
|
||||
|
||||
renderMixedPicker = () => {
|
||||
@ -218,7 +236,7 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
this.setState({ scrollTop: target.scrollTop });
|
||||
};
|
||||
|
||||
renderQueryBody = () => {
|
||||
renderQueries() {
|
||||
const { panel, dashboard } = this.props;
|
||||
const { currentDS, data } = this.state;
|
||||
|
||||
@ -237,36 +255,83 @@ export class QueriesTab extends PureComponent<Props, State> {
|
||||
dashboard={dashboard}
|
||||
data={data}
|
||||
/>
|
||||
<PanelOptionsGroup>
|
||||
<QueryOptions panel={panel} datasource={currentDS} />
|
||||
</PanelOptionsGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { scrollTop } = this.state;
|
||||
const queryInspector: EditorToolbarView = {
|
||||
title: 'Query inspector',
|
||||
onClick: this.openQueryInspector,
|
||||
};
|
||||
|
||||
const dsHelp: EditorToolbarView = {
|
||||
heading: 'Help',
|
||||
icon: 'question-circle',
|
||||
render: this.renderHelp,
|
||||
};
|
||||
renderAddQueryRow(styles: QueriesTabStyls) {
|
||||
const { currentDS, isAddingMixed } = this.state;
|
||||
const showAddButton = !(isAddingMixed || isSharedDashboardQuery(currentDS.name));
|
||||
|
||||
return (
|
||||
<EditorTabBody
|
||||
heading="Data source"
|
||||
renderToolbar={this.renderToolbar}
|
||||
toolbarItems={[queryInspector, dsHelp]}
|
||||
setScrollTop={this.setScrollTop}
|
||||
<HorizontalGroup spacing="md" align="flex-start">
|
||||
{showAddButton && (
|
||||
<Button icon="plus" onClick={this.onAddQueryClick} variant="secondary">
|
||||
Query
|
||||
</Button>
|
||||
)}
|
||||
{isAddingMixed && this.renderMixedPicker()}
|
||||
{config.featureToggles.expressions && (
|
||||
<Button icon="plus" onClick={this.onAddExpressionClick} variant="secondary">
|
||||
Expression
|
||||
</Button>
|
||||
)}
|
||||
</HorizontalGroup>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { scrollTop, isHelpOpen } = this.state;
|
||||
const styles = getStyles();
|
||||
|
||||
return (
|
||||
<CustomScrollbar
|
||||
autoHeightMin="100%"
|
||||
autoHide={true}
|
||||
updateAfterMountMs={300}
|
||||
scrollTop={scrollTop}
|
||||
setScrollTop={this.setScrollTop}
|
||||
>
|
||||
<>{this.renderQueryBody()}</>
|
||||
</EditorTabBody>
|
||||
<div className={styles.innerWrapper}>
|
||||
{this.renderTopSection(styles)}
|
||||
<div className={styles.queriesWrapper}>{this.renderQueries()}</div>
|
||||
{this.renderAddQueryRow(styles)}
|
||||
|
||||
{isHelpOpen && (
|
||||
<Modal title="Data source help" isOpen={true} onDismiss={this.onCloseHelp}>
|
||||
<PluginHelp plugin={this.state.currentDS.meta} type="query_help" />
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
</CustomScrollbar>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyles = stylesFactory(() => {
|
||||
const { theme } = config;
|
||||
|
||||
return {
|
||||
innerWrapper: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: ${theme.spacing.md};
|
||||
`,
|
||||
dataSourceRow: css`
|
||||
display: flex;
|
||||
margin-bottom: ${theme.spacing.md};
|
||||
`,
|
||||
dataSourceRowItem: css`
|
||||
margin-right: ${theme.spacing.inlineFormMargin};
|
||||
`,
|
||||
dataSourceRowItemOptions: css`
|
||||
flex-grow: 1;
|
||||
`,
|
||||
queriesWrapper: css`
|
||||
padding-bottom: 16px;
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
type QueriesTabStyls = ReturnType<typeof getStyles>;
|
||||
|
@ -73,24 +73,20 @@ export class QueryEditorRows extends PureComponent<Props> {
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
return (
|
||||
<div className="query-editor-rows">
|
||||
{props.queries.map((query, index) => (
|
||||
<QueryEditorRow
|
||||
dataSourceValue={query.datasource || props.datasource.value}
|
||||
key={query.refId}
|
||||
panel={props.panel}
|
||||
dashboard={props.dashboard}
|
||||
data={props.data}
|
||||
query={query}
|
||||
onChange={query => this.onChangeQuery(query, index)}
|
||||
onRemoveQuery={this.onRemoveQuery}
|
||||
onAddQuery={this.onAddQuery}
|
||||
onMoveQuery={this.onMoveQuery}
|
||||
inMixedMode={props.datasource.meta.mixed}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
return props.queries.map((query, index) => (
|
||||
<QueryEditorRow
|
||||
dataSourceValue={query.datasource || props.datasource.value}
|
||||
key={query.refId}
|
||||
panel={props.panel}
|
||||
dashboard={props.dashboard}
|
||||
data={props.data}
|
||||
query={query}
|
||||
onChange={query => this.onChangeQuery(query, index)}
|
||||
onRemoveQuery={this.onRemoveQuery}
|
||||
onAddQuery={this.onAddQuery}
|
||||
onMoveQuery={this.onMoveQuery}
|
||||
inMixedMode={props.datasource.meta.mixed}
|
||||
/>
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,25 @@
|
||||
import React, { PureComponent, ChangeEvent, FocusEvent, ReactText } from 'react';
|
||||
|
||||
// Utils
|
||||
import { rangeUtil, DataSourceSelectItem } from '@grafana/data';
|
||||
import { rangeUtil, DataSourceSelectItem, PanelData } from '@grafana/data';
|
||||
|
||||
// Components
|
||||
import { EventsWithValidation, LegacyInputStatus, LegacyForms, ValidationEvents, InlineFormLabel } from '@grafana/ui';
|
||||
import {
|
||||
EventsWithValidation,
|
||||
LegacyInputStatus,
|
||||
LegacyForms,
|
||||
ValidationEvents,
|
||||
InlineFormLabel,
|
||||
stylesFactory,
|
||||
} from '@grafana/ui';
|
||||
import { DataSourceOption } from './DataSourceOption';
|
||||
const { Input, Switch } = LegacyForms;
|
||||
|
||||
// Types
|
||||
import { PanelModel } from '../state';
|
||||
import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow';
|
||||
import { config } from 'app/core/config';
|
||||
import { css } from 'emotion';
|
||||
|
||||
const timeRangeValidationEvents: ValidationEvents = {
|
||||
[EventsWithValidation.onBlur]: [
|
||||
@ -33,6 +43,7 @@ const emptyToNull = (value: string) => {
|
||||
interface Props {
|
||||
panel: PanelModel;
|
||||
datasource: DataSourceSelectItem;
|
||||
data: PanelData;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@ -42,6 +53,7 @@ interface State {
|
||||
maxDataPoints: string | ReactText;
|
||||
interval: string;
|
||||
hideTimeOverride: boolean;
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export class QueryOptions extends PureComponent<Props, State> {
|
||||
@ -95,6 +107,7 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
maxDataPoints: props.panel.maxDataPoints || '',
|
||||
interval: props.panel.interval || '',
|
||||
hideTimeOverride: props.panel.hideTimeOverride || false,
|
||||
isOpen: false,
|
||||
};
|
||||
}
|
||||
|
||||
@ -180,15 +193,57 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
});
|
||||
};
|
||||
|
||||
onOpenOptions = () => {
|
||||
this.setState({ isOpen: true });
|
||||
};
|
||||
|
||||
onCloseOptions = () => {
|
||||
this.setState({ isOpen: false });
|
||||
};
|
||||
|
||||
renderCollapsedText(styles: StylesType): React.ReactNode | undefined {
|
||||
const { data } = this.props;
|
||||
const { isOpen, maxDataPoints, interval } = this.state;
|
||||
|
||||
if (isOpen) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let mdDesc = maxDataPoints;
|
||||
if (maxDataPoints === '' && data.request) {
|
||||
mdDesc = `auto = ${data.request.maxDataPoints}`;
|
||||
}
|
||||
|
||||
let intervalDesc = interval;
|
||||
if (intervalDesc === '' && data.request) {
|
||||
intervalDesc = `auto = ${data.request.interval}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{<div className={styles.collapsedText}>MD = {mdDesc}</div>}
|
||||
{<div className={styles.collapsedText}>Interval = {intervalDesc}</div>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { hideTimeOverride } = this.state;
|
||||
const { relativeTime, timeShift } = this.state;
|
||||
const { relativeTime, timeShift, isOpen } = this.state;
|
||||
const styles = getStyles();
|
||||
|
||||
return (
|
||||
<div className="gf-form-inline">
|
||||
<QueryOperationRow
|
||||
title="Options"
|
||||
headerElement={this.renderCollapsedText(styles)}
|
||||
isOpen={isOpen}
|
||||
onOpen={this.onOpenOptions}
|
||||
onClose={this.onCloseOptions}
|
||||
>
|
||||
{this.renderOptions()}
|
||||
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel>Relative time</InlineFormLabel>
|
||||
<InlineFormLabel width={9}>Relative time</InlineFormLabel>
|
||||
<Input
|
||||
type="text"
|
||||
className="width-6"
|
||||
@ -202,7 +257,7 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
</div>
|
||||
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label">Time shift</span>
|
||||
<span className="gf-form-label width-9">Time shift</span>
|
||||
<Input
|
||||
type="text"
|
||||
className="width-6"
|
||||
@ -216,10 +271,29 @@ export class QueryOptions extends PureComponent<Props, State> {
|
||||
</div>
|
||||
{(timeShift || relativeTime) && (
|
||||
<div className="gf-form-inline">
|
||||
<Switch label="Hide time info" checked={hideTimeOverride} onChange={this.onToggleTimeOverride} />
|
||||
<Switch
|
||||
label="Hide time info"
|
||||
labelClass="width-9"
|
||||
checked={hideTimeOverride}
|
||||
onChange={this.onToggleTimeOverride}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</QueryOperationRow>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyles = stylesFactory(() => {
|
||||
const { theme } = config;
|
||||
|
||||
return {
|
||||
collapsedText: css`
|
||||
margin-left: ${theme.spacing.md};
|
||||
font-size: ${theme.typography.size.sm};
|
||||
color: ${theme.colors.textWeak};
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
type StylesType = ReturnType<typeof getStyles>;
|
||||
|
@ -138,7 +138,7 @@ $divider-border-color: $gray-1;
|
||||
$tight-form-func-bg: $dark-9;
|
||||
$tight-form-func-highlight-bg: $dark-10;
|
||||
|
||||
$modal-backdrop-bg: #141619;
|
||||
$modal-backdrop-bg: #2c3235;
|
||||
$code-tag-bg: $dark-1;
|
||||
$code-tag-border: $dark-9;
|
||||
|
||||
|
@ -109,6 +109,7 @@ $input-border: 1px solid $input-border-color;
|
||||
font-size: $font-size-sm;
|
||||
background-color: $input-label-bg;
|
||||
height: $input-height;
|
||||
line-height: $input-height;
|
||||
margin-right: $space-xs;
|
||||
border-radius: $input-border-radius;
|
||||
justify-content: space-between;
|
||||
|
@ -1,9 +1,3 @@
|
||||
.panel-editor-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.panel-wrapper {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
@ -16,45 +10,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.panel-editor-container__editor {
|
||||
margin-top: $space-lg;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1 1 0;
|
||||
position: relative;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.panel-editor__right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
background: $input-bg;
|
||||
margin: 0 20px 0 84px;
|
||||
width: calc(100% - 84px);
|
||||
border-radius: 3px;
|
||||
box-shadow: $panel-editor-shadow;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.panel-editor__close {
|
||||
@include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl);
|
||||
position: absolute;
|
||||
left: 11px;
|
||||
top: 5px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
i {
|
||||
flex-grow: 1;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-editor__scroll {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
@ -65,7 +20,7 @@
|
||||
}
|
||||
|
||||
.panel-editor__content {
|
||||
padding: 16px;
|
||||
padding: 0 16px 16px 16px;
|
||||
}
|
||||
|
||||
.panel-in-fullscreen {
|
||||
@ -86,88 +41,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.panel-editor-container__resizer {
|
||||
position: relative;
|
||||
margin-top: -3px;
|
||||
}
|
||||
|
||||
.panel-editor-resizer__handle {
|
||||
position: relative;
|
||||
display: block;
|
||||
background: $vertical-resize-handle-bg;
|
||||
width: 150px;
|
||||
margin-left: -75px;
|
||||
height: 6px;
|
||||
cursor: ns-resize;
|
||||
border-radius: 3px;
|
||||
margin: 0 auto;
|
||||
|
||||
&::before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
top: 2px;
|
||||
border-top: 2px dotted $vertical-resize-handle-dots;
|
||||
}
|
||||
|
||||
&:hover::before {
|
||||
border-color: $vertical-resize-handle-dots-hover;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-editor-tabs {
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 44px;
|
||||
left: 20px;
|
||||
align-items: flex-start;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
bottom: 10px;
|
||||
left: 21px;
|
||||
width: 2px;
|
||||
background: $panel-editor-tabs-line-color;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-editor-tabs__item {
|
||||
margin-bottom: 25px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
text-align: center;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-editor-tabs__link {
|
||||
display: inline-block;
|
||||
|
||||
&.active {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gicon {
|
||||
height: 44px;
|
||||
width: 53px;
|
||||
margin-right: 5px;
|
||||
transition: transform 0.1s ease 0.1s;
|
||||
|
||||
&:hover {
|
||||
filter: $panel-editor-side-menu-shadow;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ds-picker {
|
||||
position: relative;
|
||||
min-width: 200px;
|
||||
|
@ -11,10 +11,6 @@
|
||||
color: $gray-2;
|
||||
}
|
||||
|
||||
.query-editor-rows {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.tight-form-func {
|
||||
background: $tight-form-func-bg;
|
||||
|
||||
|
@ -2,12 +2,9 @@
|
||||
display: flex;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
padding: 3px 20px 3px 20px;
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
background: $toolbar-bg;
|
||||
border-radius: 3px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.toolbar__heading {
|
||||
|
Loading…
Reference in New Issue
Block a user