Merge pull request #14135 from grafana/panel-edit-in-react

React Panel Editor Mode (For Angular and React Panels)
This commit is contained in:
Torkel Ödegaard
2018-12-03 11:50:06 +01:00
committed by GitHub
102 changed files with 2731 additions and 2460 deletions
+1
View File
@@ -24,6 +24,7 @@ type DataSourcePlugin struct {
Metrics bool `json:"metrics"`
Alerting bool `json:"alerting"`
Explore bool `json:"explore"`
Table bool `json:"tables"`
Logs bool `json:"logs"`
QueryOptions map[string]bool `json:"queryOptions,omitempty"`
BuiltIn bool `json:"builtIn,omitempty"`
@@ -0,0 +1,67 @@
import React, { PureComponent, ReactNode } from 'react';
import ClipboardJS from 'clipboard';
interface Props {
text: () => string;
elType?: string;
onSuccess?: (evt: any) => void;
onError?: (evt: any) => void;
className?: string;
children?: ReactNode;
}
export class CopyToClipboard extends PureComponent<Props> {
clipboardjs: any;
myRef: any;
constructor(props) {
super(props);
this.myRef = React.createRef();
}
componentDidMount() {
const { text, onSuccess, onError } = this.props;
this.clipboardjs = new ClipboardJS(this.myRef.current, {
text: text,
});
if (onSuccess) {
this.clipboardjs.on('success', evt => {
evt.clearSelection();
onSuccess(evt);
});
}
if (onError) {
this.clipboardjs.on('error', evt => {
console.error('Action:', evt.action);
console.error('Trigger:', evt.trigger);
onError(evt);
});
}
}
componentWillUnmount() {
if (this.clipboardjs) {
this.clipboardjs.destroy();
}
}
getElementType = () => {
return this.props.elType || 'button';
};
render() {
const { elType, text, children, onError, onSuccess, ...restProps } = this.props;
return React.createElement(
this.getElementType(),
{
ref: this.myRef,
...restProps,
},
this.props.children
);
}
}
@@ -0,0 +1,43 @@
import React, { PureComponent, ReactNode, ReactElement } from 'react';
import { Label } from './Label';
import { uniqueId } from 'lodash';
interface Props {
label?: ReactNode;
labelClassName?: string;
id?: string;
children: ReactElement<any>;
}
export class Element extends PureComponent<Props> {
elementId: string = this.props.id || uniqueId('form-element-');
get elementLabel() {
const { label, labelClassName } = this.props;
if (label) {
return (
<Label htmlFor={this.elementId} className={labelClassName}>
{label}
</Label>
);
}
return null;
}
get children() {
const { children } = this.props;
return React.cloneElement(children, { id: this.elementId });
}
render() {
return (
<div className="our-custom-wrapper-class">
{this.elementLabel}
{this.children}
</div>
);
}
}
@@ -0,0 +1,53 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { shallow } from 'enzyme';
import { Input, EventsWithValidation } from './Input';
import { ValidationEvents } from 'app/types';
const TEST_ERROR_MESSAGE = 'Value must be empty or less than 3 chars';
const testBlurValidation: ValidationEvents = {
[EventsWithValidation.onBlur]: [
{
rule: (value: string) => {
if (!value || value.length < 3) {
return true;
}
return false;
},
errorMessage: TEST_ERROR_MESSAGE,
},
],
};
describe('Input', () => {
it('renders correctly', () => {
const tree = renderer.create(<Input />).toJSON();
expect(tree).toMatchSnapshot();
});
it('should validate with error onBlur', () => {
const wrapper = shallow(<Input validationEvents={testBlurValidation} />);
const evt = {
persist: jest.fn,
target: {
value: 'I can not be more than 2 chars',
},
};
wrapper.find('input').simulate('blur', evt);
expect(wrapper.state('error')).toBe(TEST_ERROR_MESSAGE);
});
it('should validate without error onBlur', () => {
const wrapper = shallow(<Input validationEvents={testBlurValidation} />);
const evt = {
persist: jest.fn,
target: {
value: 'Hi',
},
};
wrapper.find('input').simulate('blur', evt);
expect(wrapper.state('error')).toBe(null);
});
});
+89
View File
@@ -0,0 +1,89 @@
import React, { PureComponent } from 'react';
import { ValidationEvents, ValidationRule } from 'app/types';
import { validate, hasValidationEvent } from 'app/core/utils/validate';
export enum InputStatus {
Invalid = 'invalid',
Valid = 'valid',
}
export enum InputTypes {
Text = 'text',
Number = 'number',
Password = 'password',
Email = 'email',
}
export enum EventsWithValidation {
onBlur = 'onBlur',
onFocus = 'onFocus',
onChange = 'onChange',
}
interface Props extends React.HTMLProps<HTMLInputElement> {
validationEvents?: ValidationEvents;
hideErrorMessage?: boolean;
// Override event props and append status as argument
onBlur?: (event: React.FocusEvent<HTMLInputElement>, status?: InputStatus) => void;
onFocus?: (event: React.FocusEvent<HTMLInputElement>, status?: InputStatus) => void;
onChange?: (event: React.FormEvent<HTMLInputElement>, status?: InputStatus) => void;
}
export class Input extends PureComponent<Props> {
state = {
error: null,
};
get status() {
return this.state.error ? InputStatus.Invalid : InputStatus.Valid;
}
get isInvalid() {
return this.status === InputStatus.Invalid;
}
validatorAsync = (validationRules: ValidationRule[]) => {
return evt => {
const errors = validate(evt.target.value, validationRules);
this.setState(prevState => {
return {
...prevState,
error: errors ? errors[0] : null,
};
});
};
};
populateEventPropsWithStatus = (restProps, validationEvents: ValidationEvents) => {
const inputElementProps = { ...restProps };
Object.keys(EventsWithValidation).forEach((eventName: EventsWithValidation) => {
if (hasValidationEvent(eventName, validationEvents) || restProps[eventName]) {
inputElementProps[eventName] = async evt => {
evt.persist(); // Needed for async. https://reactjs.org/docs/events.html#event-pooling
if (hasValidationEvent(eventName, validationEvents)) {
await this.validatorAsync(validationEvents[eventName]).apply(this, [evt]);
}
if (restProps[eventName]) {
restProps[eventName].apply(null, [evt, this.status]);
}
};
}
});
return inputElementProps;
};
render() {
const { validationEvents, className, hideErrorMessage, ...restProps } = this.props;
const { error } = this.state;
const inputClassName = 'gf-form-input' + (this.isInvalid ? ' invalid' : '');
const inputElementProps = this.populateEventPropsWithStatus(restProps, validationEvents);
return (
<div className="our-custom-wrapper-class">
<input {...inputElementProps} className={inputClassName} />
{error && !hideErrorMessage && <span>{error}</span>}
</div>
);
}
}
+19
View File
@@ -0,0 +1,19 @@
import React, { PureComponent, ReactNode } from 'react';
interface Props {
children: ReactNode;
htmlFor?: string;
className?: string;
}
export class Label extends PureComponent<Props> {
render() {
const { children, htmlFor, className } = this.props;
return (
<label className={`custom-label-class ${className || ''}`} htmlFor={htmlFor}>
{children}
</label>
);
}
}
@@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Input renders correctly 1`] = `
<div
className="our-custom-wrapper-class"
>
<input
className="gf-form-input"
/>
</div>
`;
+3
View File
@@ -0,0 +1,3 @@
export { Element } from './Element';
export { Input } from './Input';
export { Label } from './Label';
@@ -0,0 +1,51 @@
import React, { PureComponent, createRef } from 'react';
// import JSONFormatterJS, { JSONFormatterConfiguration } from 'json-formatter-js';
import { JsonExplorer } from 'app/core/core'; // We have made some monkey-patching of json-formatter-js so we can't switch right now
interface Props {
className?: string;
json: {};
config?: any;
open?: number;
onDidRender?: (formattedJson: any) => void;
}
export class JSONFormatter extends PureComponent<Props> {
private wrapperRef = createRef<HTMLDivElement>();
static defaultProps = {
open: 3,
config: {
animateOpen: true,
},
};
componentDidMount() {
this.renderJson();
}
componentDidUpdate() {
this.renderJson();
}
renderJson = () => {
const { json, config, open, onDidRender } = this.props;
const wrapperEl = this.wrapperRef.current;
const formatter = new JsonExplorer(json, open, config);
const hasChildren: boolean = wrapperEl.hasChildNodes();
if (hasChildren) {
wrapperEl.replaceChild(formatter.render(), wrapperEl.lastChild);
} else {
wrapperEl.appendChild(formatter.render());
}
if (onDidRender) {
onDidRender(formatter.json);
}
};
render() {
const { className } = this.props;
return <div className={className} ref={this.wrapperRef} />;
}
}
@@ -67,7 +67,7 @@
<gf-form-checkbox
on-change="ctrl.onSelectAllChanged()"
checked="ctrl.selectAllChecked"
switch-class="gf-form-switch--transparent"
switch-class="gf-form-checkbox--transparent"
/>
<div class="search-results-filter-row__filters">
<div class="gf-form-select-wrapper" ng-show="!(ctrl.canMove || ctrl.canDelete)">
@@ -1,11 +1,11 @@
<div ng-repeat="section in ctrl.results" class="search-section">
<div class="search-section__header pointer" ng-hide="section.hideHeader" ng-class="{'selected': section.selected}" ng-click="ctrl.toggleFolderExpand(section)">
<div ng-click="ctrl.toggleSelection(section, $event)">
<div ng-click="ctrl.toggleSelection(section, $event)" class="center-vh">
<gf-form-checkbox
ng-show="ctrl.editable"
on-change="ctrl.selectionChanged($event)"
checked="section.checked"
switch-class="gf-form-switch--transparent gf-form-switch--search-result__section">
switch-class="gf-form-checkbox--transparent">
</gf-form-checkbox>
</div>
<i class="search-section__header__icon" ng-class="section.icon"></i>
@@ -21,12 +21,12 @@
<div ng-if="section.expanded">
<a ng-repeat="item in section.items" class="search-item search-item--indent" ng-class="{'selected': item.selected}" ng-href="{{::item.url}}" >
<div ng-click="ctrl.toggleSelection(item, $event)">
<div ng-click="ctrl.toggleSelection(item, $event)" class="center-vh">
<gf-form-checkbox
ng-show="ctrl.editable"
on-change="ctrl.selectionChanged()"
checked="item.checked"
switch-class="gf-form-switch--transparent gf-form-switch--search-result__item">
switch-class="gf-form-checkbox--transparent">
</gf-form-checkbox>
</div>
<span class="search-item__icon">
+1
View File
@@ -14,3 +14,4 @@ export const DASHBOARD_TOP_PADDING = 20;
export const PANEL_HEADER_HEIGHT = 27;
export const PANEL_BORDER = 2;
export const PANEL_OPTIONS_KEY_PREFIX = 'options-';
@@ -5,6 +5,7 @@ import _ from 'lodash';
export interface AngularComponent {
destroy();
digest();
getScope();
}
export class AngularLoader {
@@ -28,6 +29,9 @@ export class AngularLoader {
digest: () => {
scope.$digest();
},
getScope: () => {
return scope;
},
};
}
}
+2 -3
View File
@@ -86,11 +86,10 @@ export function mergeTablesIntoModel(dst?: TableModel, ...tables: TableModel[]):
if (arguments.length === 1) {
return model;
}
// Single query returns data columns and rows as is
if (arguments.length === 2) {
model.columns = [...tables[0].columns];
model.rows = [...tables[0].rows];
model.columns = tables[0].hasOwnProperty('columns') ? [...tables[0].columns] : [];
model.rows = tables[0].hasOwnProperty('rows') ? [...tables[0].rows] : [];
return model;
}
+9
View File
@@ -159,3 +159,12 @@ export function describeTimeRange(range: RawTimeRange): string {
return range.from.toString() + ' to ' + range.to.toString();
}
export const isValidTimeSpan = (value: string) => {
if (value.indexOf('$') === 0 || value.indexOf('+$') === 0) {
return true;
}
const info = describeTextRange(value);
return info.invalid !== true;
};
+16
View File
@@ -0,0 +1,16 @@
import { ValidationRule, ValidationEvents } from 'app/types';
import { EventsWithValidation } from 'app/core/components/Form/Input';
export const validate = (value: string, validationRules: ValidationRule[]) => {
const errors = validationRules.reduce((acc, currRule) => {
if (!currRule.rule(value)) {
return acc.concat(currRule.errorMessage);
}
return acc;
}, []);
return errors.length > 0 ? errors : null;
};
export const hasValidationEvent = (event: EventsWithValidation, validationEvents: ValidationEvents) => {
return validationEvents && validationEvents[event];
};
@@ -1,4 +1,5 @@
import _ from 'lodash';
import coreModule from 'app/core/core_module';
import { ThresholdMapper } from './state/ThresholdMapper';
import { QueryPart } from 'app/core/components/query_part/query_part';
import alertDef from './state/alertDef';
@@ -430,3 +431,5 @@ export function alertTab() {
controller: AlertTabCtrl,
};
}
coreModule.directive('alertTab', alertTab);
@@ -0,0 +1,72 @@
import React, { PureComponent } from 'react';
import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
import { EditorTabBody } from './EditorTabBody';
import 'app/features/alerting/AlertTabCtrl';
interface Props {
angularPanel?: AngularComponent;
}
export class AlertTab extends PureComponent<Props> {
element: any;
component: AngularComponent;
constructor(props) {
super(props);
}
componentDidMount() {
if (this.shouldLoadAlertTab()) {
this.loadAlertTab();
}
}
componentDidUpdate(prevProps: Props) {
if (this.shouldLoadAlertTab()) {
this.loadAlertTab();
}
}
shouldLoadAlertTab() {
return this.props.angularPanel && this.element;
}
componentWillUnmount() {
if (this.component) {
this.component.destroy();
}
}
loadAlertTab() {
const { angularPanel } = this.props;
const scope = angularPanel.getScope();
// When full page reloading in edit mode the angular panel has on fully compiled & instantiated yet
if (!scope.$$childHead) {
setTimeout(() => {
this.forceUpdate();
});
return;
}
const panelCtrl = scope.$$childHead.ctrl;
const loader = getAngularLoader();
const template = '<alert-tab />';
const scopeProps = {
ctrl: panelCtrl,
};
this.component = loader.load(this.element, scopeProps, template);
}
render() {
return (
<EditorTabBody heading="Alert" toolbarItems={[]}>
<div ref={element => (this.element = element)} />
</EditorTabBody>
);
}
}
@@ -176,10 +176,9 @@ export class DashboardGrid extends React.Component<DashboardGridProps, any> {
renderPanels() {
const panelElements = [];
console.log('render panels');
for (const panel of this.props.dashboard.panels) {
const panelClasses = classNames({ panel: true, 'panel--fullscreen': panel.fullscreen });
const panelClasses = classNames({ 'react-grid-item--fullscreen': panel.fullscreen });
panelElements.push(
<div key={panel.id.toString()} className={panelClasses} id={`panel-${panel.id}`}>
<DashboardPanel
@@ -1,5 +1,6 @@
import React, { PureComponent } from 'react';
import config from 'app/core/config';
import classNames from 'classnames';
import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
import { importPluginModule } from 'app/features/plugins/plugin_loader';
@@ -23,11 +24,11 @@ export interface Props {
export interface State {
plugin: PanelPlugin;
angularPanel: AngularComponent;
}
export class DashboardPanel extends PureComponent<Props, State> {
element: any;
angularPanel: AngularComponent;
element: HTMLElement;
specialPanels = {};
constructor(props) {
@@ -35,6 +36,7 @@ export class DashboardPanel extends PureComponent<Props, State> {
this.state = {
plugin: null,
angularPanel: null,
};
this.specialPanels['row'] = this.renderRow.bind(this);
@@ -54,11 +56,7 @@ export class DashboardPanel extends PureComponent<Props, State> {
}
onPluginTypeChanged = (plugin: PanelPlugin) => {
this.props.panel.changeType(plugin.id);
this.loadPlugin();
};
onAngularPluginTypeChanged = () => {
this.props.panel.changeType(plugin.id, this.state.angularPanel !== null);
this.loadPlugin();
};
@@ -93,28 +91,30 @@ export class DashboardPanel extends PureComponent<Props, State> {
}
componentDidUpdate() {
this.loadPlugin();
// handle angular plugin loading
if (!this.element || this.angularPanel) {
if (!this.element || this.state.angularPanel) {
return;
}
const loader = getAngularLoader();
const template = '<plugin-component type="panel" class="panel-height-helper"></plugin-component>';
const scopeProps = { panel: this.props.panel, dashboard: this.props.dashboard };
this.angularPanel = loader.load(this.element, scopeProps, template);
const angularPanel = loader.load(this.element, scopeProps, template);
this.setState({ angularPanel });
}
cleanUpAngularPanel() {
if (this.angularPanel) {
this.angularPanel.destroy();
this.angularPanel = null;
cleanUpAngularPanel(unmounted?: boolean) {
if (this.state.angularPanel) {
this.state.angularPanel.destroy();
if (!unmounted) {
this.setState({ angularPanel: null });
}
}
}
componentWillUnmount() {
this.cleanUpAngularPanel();
this.cleanUpAngularPanel(true);
}
onMouseEnter = () => {
@@ -129,25 +129,16 @@ export class DashboardPanel extends PureComponent<Props, State> {
const { dashboard, panel } = this.props;
const { plugin } = this.state;
const containerClass = this.props.isEditing ? 'panel-editor-container' : 'panel-height-helper';
const panelWrapperClass = this.props.isEditing ? 'panel-editor-container__panel' : 'panel-height-helper';
// this might look strange with these classes that change when edit, but
// I want to try to keep markup (parents) for panel the same in edit mode to avoide unmount / new mount of panel
return (
<div className={containerClass}>
<div className={panelWrapperClass} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<PanelChrome component={plugin.exports.Panel} panel={panel} dashboard={dashboard} />
</div>
{panel.isEditing && (
<PanelEditor panel={panel} plugin={plugin} dashboard={dashboard} onTypeChanged={this.onPluginTypeChanged} />
)}
</div>
);
return <PanelChrome component={plugin.exports.Panel} panel={panel} dashboard={dashboard} />;
}
renderAngularPanel() {
return <div ref={element => (this.element = element)} className="panel-height-helper" />;
}
render() {
const { panel } = this.props;
const { plugin } = this.state;
const { panel, dashboard, isFullscreen, isEditing } = this.props;
const { plugin, angularPanel } = this.state;
if (this.isSpecial()) {
return this.specialPanels[panel.type]();
@@ -158,19 +149,29 @@ export class DashboardPanel extends PureComponent<Props, State> {
return null;
}
// if exporting PanelComponent it must be a react panel
if (plugin.exports.Panel) {
return this.renderReactPanel();
}
const containerClass = classNames({ 'panel-editor-container': isEditing, 'panel-height-helper': !isEditing });
const panelWrapperClass = classNames({
'panel-wrapper': true,
'panel-wrapper--edit': isEditing,
'panel-wrapper--view': isFullscreen && !isEditing,
});
// legacy angular rendering
return (
<div
ref={element => (this.element = element)}
className="panel-height-helper"
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
/>
<div className={containerClass}>
<div className={panelWrapperClass} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
{plugin.exports.Panel && this.renderReactPanel()}
{plugin.exports.PanelCtrl && this.renderAngularPanel()}
</div>
{panel.isEditing && (
<PanelEditor
panel={panel}
plugin={plugin}
dashboard={dashboard}
angularPanel={angularPanel}
onTypeChanged={this.onPluginTypeChanged}
/>
)}
</div>
);
}
}
@@ -0,0 +1,31 @@
import React, { SFC } from 'react';
import Tooltip from 'app/core/components/Tooltip/Tooltip';
interface Props {
label: string;
placeholder?: string;
name?: string;
value?: string;
onChange?: (evt: any) => void;
tooltipInfo?: any;
}
export const DataSourceOptions: SFC<Props> = ({ label, placeholder, name, value, onChange, tooltipInfo }) => {
const dsOption = (
<div className="gf-form gf-form--flex-end">
<label className="gf-form-label">{label}</label>
<input
type="text"
className="gf-form-input width-6"
placeholder={placeholder}
name={name}
spellCheck={false}
onBlur={evt => onChange(evt.target.value)}
/>
</div>
);
return tooltipInfo ? <Tooltip content={tooltipInfo}>{dsOption}</Tooltip> : dsOption;
};
export default DataSourceOptions;
@@ -2,13 +2,14 @@ import React, { PureComponent } from 'react';
import classNames from 'classnames';
import _ from 'lodash';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { DataSourceSelectItem } from 'app/types';
interface Props {}
interface Props {
onChangeDataSource: (ds: any) => void;
datasources: DataSourceSelectItem[];
}
interface State {
datasources: DataSourceSelectItem[];
searchQuery: string;
}
@@ -17,31 +18,32 @@ export class DataSourcePicker extends PureComponent<Props, State> {
constructor(props) {
super(props);
this.state = {
datasources: getDatasourceSrv().getMetricSources(),
searchQuery: '',
};
}
getDataSources() {
const { datasources, searchQuery } = this.state;
const { searchQuery } = this.state;
const regex = new RegExp(searchQuery, 'i');
const { datasources } = this.props;
const filtered = datasources.filter(item => {
return regex.test(item.name) || regex.test(item.meta.name);
});
return _.sortBy(filtered, 'sort');
return filtered;
}
renderDataSource = (ds: DataSourceSelectItem, index) => {
renderDataSource = (ds: DataSourceSelectItem, index: number) => {
const { onChangeDataSource } = this.props;
const onClick = () => onChangeDataSource(ds);
const cssClass = classNames({
'ds-picker-list__item': true,
});
return (
<div key={index} className={cssClass} title={ds.name}>
<div key={index} className={cssClass} title={ds.name} onClick={onClick}>
<img className="ds-picker-list__img" src={ds.meta.info.logos.small} />
<div className="ds-picker-list__name">{ds.name}</div>
</div>
@@ -54,7 +56,16 @@ export class DataSourcePicker extends PureComponent<Props, State> {
}, 300);
}
onSearchQueryChange = evt => {
const value = evt.target.value;
this.setState(prevState => ({
...prevState,
searchQuery: value,
}));
};
renderFilters() {
const { searchQuery } = this.state;
return (
<>
<label className="gf-form--has-input-icon">
@@ -63,13 +74,11 @@ export class DataSourcePicker extends PureComponent<Props, State> {
className="gf-form-input width-13"
placeholder=""
ref={elem => (this.searchInput = elem)}
onChange={this.onSearchQueryChange}
value={searchQuery}
/>
<i className="gf-form-input-icon fa fa-search" />
</label>
<div className="p-l-1">
<button className="btn toggle-btn gf-form-btn active">All</button>
<button className="btn toggle-btn gf-form-btn">Favorites</button>
</div>
</>
);
}
@@ -4,7 +4,8 @@ import { FadeIn } from 'app/core/components/Animations/FadeIn';
interface Props {
children: JSX.Element;
main: EditorToolBarView;
heading: string;
main?: EditorToolBarView;
toolbarItems: EditorToolBarView[];
}
@@ -12,11 +13,14 @@ export interface EditorToolBarView {
title: string;
imgSrc?: string;
icon?: string;
render: () => JSX.Element;
disabled?: boolean;
onClick?: () => void;
render: (closeFunction: any) => JSX.Element | JSX.Element[];
}
interface State {
openView?: EditorToolBarView;
fadeIn: boolean;
}
export class EditorTabBody extends PureComponent<Props, State> {
@@ -25,9 +29,14 @@ export class EditorTabBody extends PureComponent<Props, State> {
this.state = {
openView: null,
fadeIn: false,
};
}
componentDidMount() {
this.setState({ fadeIn: true });
}
onToggleToolBarView = (item: EditorToolBarView) => {
this.setState({
openView: item === this.state.openView ? null : item,
@@ -38,9 +47,24 @@ export class EditorTabBody extends PureComponent<Props, State> {
this.setState({ openView: null });
};
static getDerivedStateFromProps(props, state) {
if (state.openView) {
const activeToolbarItem = props.toolbarItems.find(
item => item.title === state.openView.title && item.icon === state.openView.icon
);
if (activeToolbarItem) {
return {
...state,
openView: activeToolbarItem,
};
}
}
return state;
}
renderMainSelection(view: EditorToolBarView) {
return (
<div className="toolbar__main" onClick={() => this.onToggleToolBarView(view)} key={view.title}>
<div className="toolbar__main" onClick={() => this.onToggleToolBarView(view)} key={view.title + view.icon}>
<img className="toolbar__main-image" src={view.imgSrc} />
<div className="toolbar__main-name">{view.title}</div>
<i className="fa fa-caret-down" />
@@ -49,9 +73,16 @@ export class EditorTabBody extends PureComponent<Props, State> {
}
renderButton(view: EditorToolBarView) {
const onClick = () => {
if (view.onClick) {
view.onClick();
}
this.onToggleToolBarView(view);
};
return (
<div className="nav-buttons" key={view.title}>
<button className="btn navbar-button" onClick={() => this.onToggleToolBarView(view)}>
<div className="nav-buttons" key={view.title + view.icon}>
<button className="btn navbar-button" onClick={onClick} disabled={view.disabled}>
{view.icon && <i className={view.icon} />} {view.title}
</button>
</div>
@@ -64,29 +95,32 @@ export class EditorTabBody extends PureComponent<Props, State> {
<button className="toolbar-subview__close" onClick={this.onCloseOpenView}>
<i className="fa fa-chevron-up" />
</button>
{view.render()}
{view.render(this.onCloseOpenView)}
</div>
);
}
render() {
const { children, toolbarItems, main } = this.props;
const { openView } = this.state;
const { children, toolbarItems, main, heading } = this.props;
const { openView, fadeIn } = this.state;
return (
<>
<div className="toolbar">
{this.renderMainSelection(main)}
<div className="toolbar__heading">{heading}</div>
{main && this.renderMainSelection(main)}
<div className="gf-form--grow" />
{toolbarItems.map(item => this.renderButton(item))}
</div>
<div className="panel-editor__scroll">
<CustomScrollbar autoHide={false}>
<FadeIn in={openView !== null} duration={200}>
<div className="panel-editor__toolbar-view">{openView && this.renderOpenView(openView)}</div>
</FadeIn>
<div className="panel-editor__content">
<FadeIn in={openView !== null} duration={200}>
{openView && this.renderOpenView(openView)}
<FadeIn in={fadeIn} duration={50}>
{children}
</FadeIn>
{children}
</div>
</CustomScrollbar>
</div>
@@ -0,0 +1,52 @@
import React, { PureComponent } from 'react';
import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
import { EditorTabBody } from './EditorTabBody';
import { PanelModel } from '../panel_model';
import './../../panel/GeneralTabCtrl';
interface Props {
panel: PanelModel;
}
export class GeneralTab extends PureComponent<Props> {
element: any;
component: AngularComponent;
constructor(props) {
super(props);
}
componentDidMount() {
if (!this.element) {
return;
}
const { panel } = this.props;
const loader = getAngularLoader();
const template = '<panel-general-tab />';
const scopeProps = {
ctrl: {
panel: panel,
},
};
this.component = loader.load(this.element, scopeProps, template);
}
componentWillUnmount() {
if (this.component) {
this.component.destroy();
}
}
render() {
return (
<EditorTabBody heading="Basic Panel Options" toolbarItems={[]}>
<div ref={element => (this.element = element)} />
</EditorTabBody>
);
}
}
@@ -3,38 +3,36 @@ import classNames from 'classnames';
import { QueriesTab } from './QueriesTab';
import { VisualizationTab } from './VisualizationTab';
import { GeneralTab } from './GeneralTab';
import { AlertTab } from './AlertTab';
import config from 'app/core/config';
import { store } from 'app/store/store';
import { updateLocation } from 'app/core/actions';
import { AngularComponent } from 'app/core/services/AngularLoader';
import { PanelModel } from '../panel_model';
import { DashboardModel } from '../dashboard_model';
import { PanelPlugin } from 'app/types/plugins';
import Tooltip from 'app/core/components/Tooltip/Tooltip';
interface PanelEditorProps {
panel: PanelModel;
dashboard: DashboardModel;
plugin: PanelPlugin;
angularPanel?: AngularComponent;
onTypeChanged: (newType: PanelPlugin) => void;
}
interface PanelEditorTab {
id: string;
text: string;
icon: string;
}
export class PanelEditor extends PureComponent<PanelEditorProps> {
tabs: PanelEditorTab[];
constructor(props) {
super(props);
this.tabs = [
{ id: 'queries', text: 'Queries', icon: 'fa fa-database' },
{ id: 'visualization', text: 'Visualization', icon: 'fa fa-line-chart' },
{ id: 'alert', text: 'Alert', icon: 'gicon gicon-alert' },
];
}
onChangeTab = (tab: PanelEditorTab) => {
@@ -47,44 +45,64 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
this.forceUpdate();
};
onClose = () => {
store.dispatch(
updateLocation({
query: { tab: null, fullscreen: null, edit: null },
partial: true,
})
);
};
renderCurrentTab(activeTab: string) {
const { panel, dashboard, onTypeChanged, plugin, angularPanel } = this.props;
switch (activeTab) {
case 'advanced':
return <GeneralTab panel={panel} />;
case 'queries':
return <QueriesTab panel={panel} dashboard={dashboard} />;
case 'alerts':
return <AlertTab angularPanel={angularPanel} />;
case 'visualization':
return (
<VisualizationTab
panel={panel}
dashboard={dashboard}
plugin={plugin}
onTypeChanged={onTypeChanged}
angularPanel={angularPanel}
/>
);
default:
return null;
}
}
render() {
const { panel, dashboard, onTypeChanged, plugin } = this.props;
const { location } = store.getState();
const activeTab = location.query.tab || 'queries';
const { plugin } = this.props;
const activeTab = store.getState().location.query.tab || 'queries';
const tabs = [
{ id: 'queries', text: 'Queries' },
{ id: 'visualization', text: 'Visualization' },
{ id: 'advanced', text: 'Advanced' },
];
if (config.alertingEnabled && plugin.id === 'graph') {
tabs.push({
id: 'alerts',
text: 'Alerts',
});
}
return (
<div className="panel-editor-container__editor">
<div className="panel-editor-resizer">
<div className="panel-editor-resizer__handle">
<div className="panel-editor-resizer__handle-dots" />
</div>
</div>
{
// <div className="panel-editor-resizer">
// <div className="panel-editor-resizer__handle">
// <div className="panel-editor-resizer__handle-dots" />
// </div>
// </div>
}
<div className="panel-editor-tabs">
<ul className="gf-tabs">
{this.tabs.map(tab => {
return <TabItem tab={tab} activeTab={activeTab} onClick={this.onChangeTab} key={tab.id} />;
})}
</ul>
<button className="panel-editor-tabs__close" onClick={this.onClose}>
<i className="fa fa-reply" />
</button>
{tabs.map(tab => {
return <TabItem tab={tab} activeTab={activeTab} onClick={this.onChangeTab} key={tab.id} />;
})}
</div>
{activeTab === 'queries' && <QueriesTab panel={panel} dashboard={dashboard} />}
{activeTab === 'visualization' && (
<VisualizationTab panel={panel} dashboard={dashboard} plugin={plugin} onTypeChanged={onTypeChanged} />
)}
<div className="panel-editor__right">{this.renderCurrentTab(activeTab)}</div>
</div>
);
}
@@ -98,15 +116,17 @@ interface TabItemParams {
function TabItem({ tab, activeTab, onClick }: TabItemParams) {
const tabClasses = classNames({
'gf-tabs-link': true,
'panel-editor-tabs__link': true,
active: activeTab === tab.id,
});
return (
<li className="gf-tabs-item" onClick={() => onClick(tab)}>
<div className="panel-editor-tabs__item" onClick={() => onClick(tab)}>
<a className={tabClasses}>
<i className={tab.icon} /> {tab.text}
<Tooltip content={`${tab.text}`} className="popper__manager--block" placement="auto">
<i className={`gicon gicon-${tab.id}${activeTab === tab.id ? '-active' : ''}`} />
</Tooltip>
</a>
</li>
</div>
);
}
@@ -1,23 +1,62 @@
import React, { PureComponent } from 'react';
import React, { SFC, PureComponent } from 'react';
import DataSourceOption from './DataSourceOption';
import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
import { EditorTabBody } from './EditorTabBody';
import { DataSourcePicker } from './DataSourcePicker';
import { PanelModel } from '../panel_model';
import { DashboardModel } from '../dashboard_model';
import './../../panel/metrics_tab';
import config from 'app/core/config';
import { QueryInspector } from './QueryInspector';
import { TimeRangeOptions } from './TimeRangeOptions';
// Services
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv';
import { DataSourceSelectItem } from 'app/types';
import Remarkable from 'remarkable';
interface Props {
panel: PanelModel;
dashboard: DashboardModel;
}
export class QueriesTab extends PureComponent<Props> {
interface Help {
isLoading: boolean;
helpHtml: any;
}
interface State {
currentDatasource: DataSourceSelectItem;
help: Help;
hideTimeOverride: boolean;
}
interface LoadingPlaceholderProps {
text: string;
}
const LoadingPlaceholder: SFC<LoadingPlaceholderProps> = ({ text }) => <h2>{text}</h2>;
export class QueriesTab extends PureComponent<Props, State> {
element: any;
component: AngularComponent;
datasources: DataSourceSelectItem[] = getDatasourceSrv().getMetricSources();
backendSrv: BackendSrv = getBackendSrv();
constructor(props) {
super(props);
const { panel } = props;
this.state = {
currentDatasource: this.datasources.find(datasource => datasource.value === panel.datasource),
help: {
isLoading: false,
helpHtml: null,
},
hideTimeOverride: false,
};
}
componentDidMount() {
@@ -26,7 +65,6 @@ export class QueriesTab extends PureComponent<Props> {
}
const { panel, dashboard } = this.props;
const loader = getAngularLoader();
const template = '<metrics-tab />';
const scopeProps = {
@@ -46,27 +84,191 @@ export class QueriesTab extends PureComponent<Props> {
}
}
onChangeDataSource = datasource => {
const { panel } = this.props;
const { currentDatasource } = this.state;
// switching to mixed
if (datasource.meta.mixed) {
panel.targets.forEach(target => {
target.datasource = panel.datasource;
if (!target.datasource) {
target.datasource = config.defaultDatasource;
}
});
} else if (currentDatasource && currentDatasource.meta.mixed) {
panel.targets.forEach(target => {
delete target.datasource;
});
}
panel.datasource = datasource.value;
panel.refresh();
this.setState(prevState => ({
...prevState,
currentDatasource: datasource,
}));
};
loadHelp = () => {
const { currentDatasource } = this.state;
const hasHelp = currentDatasource.meta.hasQueryHelp;
if (hasHelp) {
this.setState(prevState => ({
...prevState,
help: {
helpHtml: <h2>Loading help...</h2>,
isLoading: true,
},
}));
this.backendSrv
.get(`/api/plugins/${currentDatasource.meta.id}/markdown/query_help`)
.then(res => {
const md = new Remarkable();
const helpHtml = md.render(res); // TODO: Clean out dangerous code? Previous: this.helpHtml = this.$sce.trustAsHtml(md.render(res));
this.setState(prevState => ({
...prevState,
help: {
helpHtml: <div className="markdown-html" dangerouslySetInnerHTML={{ __html: helpHtml }} />,
isLoading: false,
},
}));
})
.catch(() => {
this.setState(prevState => ({
...prevState,
help: {
helpHtml: 'Error occured when loading help',
isLoading: false,
},
}));
});
}
};
renderOptions = close => {
const { currentDatasource } = this.state;
const { queryOptions } = currentDatasource.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} />;
};
renderHelp = () => {
const { helpHtml, isLoading } = this.state.help;
return isLoading ? <LoadingPlaceholder text="Loading help..." /> : helpHtml;
};
render() {
const currentDataSource = {
title: 'ProductionDB',
imgSrc: 'public/app/plugins/datasource/prometheus/img/prometheus_logo.svg',
render: () => <DataSourcePicker />,
const { currentDatasource } = this.state;
const { hasQueryHelp } = currentDatasource.meta;
const dsInformation = {
title: currentDatasource.name,
imgSrc: currentDatasource.meta.info.logos.small,
render: closeOpenView => (
<DataSourcePicker
datasources={this.datasources}
onChangeDataSource={ds => {
closeOpenView();
this.onChangeDataSource(ds);
}}
/>
),
};
const queryInspector = {
title: 'Query Inspector',
render: () => <h2>hello</h2>,
render: this.renderQueryInspector,
};
const dsHelp = {
title: '',
icon: 'fa fa-question',
render: () => <h2>hello</h2>,
disabled: !hasQueryHelp,
onClick: this.loadHelp,
render: this.renderHelp,
};
const options = {
title: '',
icon: 'fa fa-cog',
disabled: false,
render: this.renderOptions,
};
return (
<EditorTabBody main={currentDataSource} toolbarItems={[queryInspector, dsHelp]}>
<div ref={element => (this.element = element)} style={{ width: '100%' }} />
<EditorTabBody heading="Queries" main={dsInformation} toolbarItems={[options, queryInspector, dsHelp]}>
<>
<div ref={element => (this.element = element)} style={{ width: '100%' }} />
</>
</EditorTabBody>
);
}
@@ -0,0 +1,226 @@
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';
interface DsQuery {
isLoading: boolean;
response: {};
}
interface Props {
panel: any;
LoadingPlaceholder: any;
}
interface State {
allNodesExpanded: boolean;
isMocking: boolean;
mockedResponse: string;
dsQuery: DsQuery;
}
export class QueryInspector extends PureComponent<Props, State> {
formattedJson: any;
clipboard: any;
constructor(props) {
super(props);
this.state = {
allNodesExpanded: null,
isMocking: false,
mockedResponse: '',
dsQuery: {
isLoading: false,
response: {},
},
};
}
componentDidMount() {
const { panel } = this.props;
panel.events.on('refresh', this.onPanelRefresh);
appEvents.on('ds-request-response', this.onDataSourceResponse);
panel.refresh();
}
componentWillUnmount() {
const { panel } = this.props;
appEvents.off('ds-request-response', this.onDataSourceResponse);
panel.events.off('refresh', this.onPanelRefresh);
}
handleMocking(response) {
const { mockedResponse } = this.state;
let mockedData;
try {
mockedData = JSON.parse(mockedResponse);
} catch (err) {
appEvents.emit('alert-error', ['R: Failed to parse mocked response']);
return;
}
response.data = mockedData;
}
onPanelRefresh = () => {
this.setState(prevState => ({
...prevState,
dsQuery: {
isLoading: true,
response: {},
},
}));
};
onDataSourceResponse = (response: any = {}) => {
if (this.state.isMocking) {
this.handleMocking(response);
return;
}
response = { ...response }; // clone - dont modify the response
if (response.headers) {
delete response.headers;
}
if (response.config) {
response.request = response.config;
delete response.config;
delete response.request.transformRequest;
delete response.request.transformResponse;
delete response.request.paramSerializer;
delete response.request.jsonpCallbackParam;
delete response.request.headers;
delete response.request.requestId;
delete response.request.inspect;
delete response.request.retry;
delete response.request.timeout;
}
if (response.data) {
response.response = response.data;
delete response.data;
delete response.status;
delete response.statusText;
delete response.$$config;
}
this.setState(prevState => ({
...prevState,
dsQuery: {
isLoading: false,
response: response,
},
}));
};
setFormattedJson = formattedJson => {
this.formattedJson = formattedJson;
};
getTextForClipboard = () => {
return JSON.stringify(this.formattedJson, null, 2);
};
onClipboardSuccess = () => {
appEvents.emit('alert-success', ['Content copied to clipboard']);
};
onToggleExpand = () => {
this.setState(prevState => ({
...prevState,
allNodesExpanded: !this.state.allNodesExpanded,
}));
};
onToggleMocking = () => {
this.setState(prevState => ({
...prevState,
isMocking: !this.state.isMocking,
}));
};
getNrOfOpenNodes = () => {
if (this.state.allNodesExpanded === null) {
return 3; // 3 is default, ie when state is null
} else if (this.state.allNodesExpanded) {
return 20;
}
return 1;
};
setMockedResponse = evt => {
const mockedResponse = evt.target.value;
this.setState(prevState => ({
...prevState,
mockedResponse,
}));
};
renderExpandCollapse = () => {
const { allNodesExpanded } = this.state;
const collapse = (
<>
<i className="fa fa-minus-square-o" /> Collapse All
</>
);
const expand = (
<>
<i className="fa fa-plus-square-o" /> Expand All
</>
);
return allNodesExpanded ? collapse : expand;
};
render() {
const { response, isLoading } = this.state.dsQuery;
const { LoadingPlaceholder } = this.props;
const { isMocking } = this.state;
const openNodes = this.getNrOfOpenNodes();
if (isLoading) {
return <LoadingPlaceholder text="Loading query inspector..." />;
}
return (
<>
<div>
{/*
<button className="btn btn-transparent btn-p-x-0 m-r-1" onClick={this.onToggleMocking}>
Mock response
</button>
*/}
<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}
onSuccess={this.onClipboardSuccess}
>
<i className="fa fa-clipboard" /> Copy to Clipboard
</CopyToClipboard>
</div>
{!isMocking && <JSONFormatter json={response} open={openNodes} onDidRender={this.setFormattedJson} />}
{isMocking && (
<div className="query-troubleshooter__body">
<div className="gf-form p-l-1 gf-form--v-stretch">
<textarea
className="gf-form-input"
style={{ width: '95%' }}
rows={10}
onInput={this.setMockedResponse}
placeholder="JSON"
/>
</div>
</div>
)}
</>
);
}
}
@@ -0,0 +1,111 @@
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">
<i className="fa fa-clock-o" />
</span>
<span className="gf-form-label width-12">Override relative time</span>
<span className="gf-form-label width-6">Last</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">
<i className="fa fa-clock-o" />
</span>
<span className="gf-form-label width-12">Add time shift</span>
<span className="gf-form-label width-6">Amount</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">
<div className="gf-form">
<span className="gf-form-label">
<i className="fa fa-clock-o" />
</span>
</div>
<Switch label="Hide time override info" checked={hideTimeOverride} onChange={this.onToggleTimeOverride} />
</div>
</div>
</>
);
};
}
@@ -1,8 +1,14 @@
// Libraries
import React, { PureComponent } from 'react';
// Utils & Services
import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
// Components
import { EditorTabBody } from './EditorTabBody';
import { VizTypePicker } from './VizTypePicker';
// Types
import { PanelModel } from '../panel_model';
import { DashboardModel } from '../dashboard_model';
import { PanelPlugin } from 'app/types/plugins';
@@ -11,18 +17,26 @@ interface Props {
panel: PanelModel;
dashboard: DashboardModel;
plugin: PanelPlugin;
angularPanel?: AngularComponent;
onTypeChanged: (newType: PanelPlugin) => void;
}
export class VisualizationTab extends PureComponent<Props> {
element: HTMLElement;
angularOptions: AngularComponent;
constructor(props) {
super(props);
}
renderPanelOptions() {
const { plugin, panel } = this.props;
const { plugin, panel, angularPanel } = this.props;
const { PanelOptions } = plugin.exports;
if (angularPanel) {
return <div ref={element => (this.element = element)} />;
}
if (PanelOptions) {
return <PanelOptions options={panel.getOptions()} onChange={this.onPanelOptionsChanged} />;
} else {
@@ -30,6 +44,70 @@ export class VisualizationTab extends PureComponent<Props> {
}
}
componentDidMount() {
if (this.shouldLoadAngularOptions()) {
this.loadAngularOptions();
}
}
componentDidUpdate(prevProps: Props) {
if (this.props.plugin !== prevProps.plugin) {
this.cleanUpAngularOptions();
}
if (this.shouldLoadAngularOptions()) {
this.loadAngularOptions();
}
}
shouldLoadAngularOptions() {
return this.props.angularPanel && this.element && !this.angularOptions;
}
loadAngularOptions() {
const { angularPanel } = this.props;
const scope = angularPanel.getScope();
// When full page reloading in edit mode the angular panel has on fully compiled & instantiated yet
if (!scope.$$childHead) {
setTimeout(() => {
this.forceUpdate();
});
return;
}
const panelCtrl = scope.$$childHead.ctrl;
let template = '';
for (let i = 0; i < panelCtrl.editorTabs.length; i++) {
template += `
<div class="form-section" ng-cloak>
<div class="form-section__header">{{ctrl.editorTabs[${i}].title}}</div>
<div class="form-section__body">
<panel-editor-tab editor-tab="ctrl.editorTabs[${i}]" ctrl="ctrl"></panel-editor-tab>
</div>
</div>
`;
}
const loader = getAngularLoader();
const scopeProps = { ctrl: panelCtrl };
this.angularOptions = loader.load(this.element, scopeProps, template);
}
componentWillUnmount() {
this.cleanUpAngularOptions();
}
cleanUpAngularOptions() {
if (this.angularOptions) {
this.angularOptions.destroy();
this.angularOptions = null;
}
}
onPanelOptionsChanged = (options: any) => {
this.props.panel.updateOptions(options);
this.forceUpdate();
@@ -48,8 +126,14 @@ export class VisualizationTab extends PureComponent<Props> {
},
};
const panelHelp = {
title: '',
icon: 'fa fa-question',
render: () => <h2>Help</h2>,
};
return (
<EditorTabBody main={panelSelection} toolbarItems={[]}>
<EditorTabBody heading="Visualization" main={panelSelection} toolbarItems={[panelHelp]}>
{this.renderPanelOptions()}
</EditorTabBody>
);
@@ -67,10 +67,6 @@ export class VizTypePicker extends PureComponent<Props, State> {
/>
<i className="gf-form-input-icon fa fa-search" />
</label>
<div className="p-l-1">
<button className="btn toggle-btn gf-form-btn active">Basic Types</button>
<button className="btn toggle-btn gf-form-btn">Master Types</button>
</div>
</>
);
}
+54 -3
View File
@@ -1,5 +1,6 @@
import { Emitter } from 'app/core/utils/emitter';
import _ from 'lodash';
import { PANEL_OPTIONS_KEY_PREFIX } from 'app/core/constants';
export interface GridPos {
x: number;
@@ -16,6 +17,42 @@ const notPersistedProperties: { [str: string]: boolean } = {
hasRefreshed: true,
};
// For angular panels we need to clean up properties when changing type
// To make sure the change happens without strange bugs happening when panels use same
// named property with different type / value expectations
// This is not required for react panels
const mustKeepProps: { [str: string]: boolean } = {
id: true,
gridPos: true,
type: true,
title: true,
scopedVars: true,
repeat: true,
repeatIteration: true,
repeatPanelId: true,
repeatDirection: true,
repeatedByRow: true,
minSpan: true,
collapsed: true,
panels: true,
targets: true,
datasource: true,
timeFrom: true,
timeShift: true,
hideTimeOverride: true,
maxDataPoints: true,
interval: true,
description: true,
links: true,
fullscreen: true,
isEditing: true,
hasRefreshed: true,
events: true,
cacheTimeout: true,
nullPointMode: true,
};
const defaults: any = {
gridPos: { x: 0, y: 0, h: 3, w: 6 },
datasource: null,
@@ -57,6 +94,7 @@ export class PanelModel {
isEditing: boolean;
hasRefreshed: boolean;
events: Emitter;
cacheTimeout?: any;
constructor(model) {
this.events = new Emitter();
@@ -82,7 +120,7 @@ export class PanelModel {
}
private getOptionsKey() {
return this.type + 'Options';
return 'options-' + this.type;
}
getSaveModel() {
@@ -146,11 +184,24 @@ export class PanelModel {
this.events.emit('panel-initialized');
}
changeType(pluginId: string) {
changeType(pluginId: string, fromAngularPanel: boolean) {
this.type = pluginId;
delete this.thresholds;
// for now we need to remove alert rules when changing type
delete this.alert;
// for angular panels only we need to remove all events and let angular panels do some cleanup
if (fromAngularPanel) {
this.destroy();
for (const key of _.keys(this)) {
if (mustKeepProps[key] || key.indexOf(PANEL_OPTIONS_KEY_PREFIX) === 0) {
continue;
}
delete this[key];
}
}
}
destroy() {
@@ -12,7 +12,7 @@ export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => {
updateLocation({
query: {
panelId: panel.id,
edit: false,
edit: null,
fullscreen: true,
},
partial: true,
+21
View File
@@ -0,0 +1,21 @@
import React, { SFC } from 'react';
interface Props {
message: any;
}
export const Alert: SFC<Props> = props => {
const { message } = props;
return (
<div className="gf-form-group section">
<div className="alert-error alert">
<div className="alert-icon">
<i className="fa fa-exclamation-triangle" />
</div>
<div className="alert-body">
<div className="alert-title">{message}</div>
</div>
</div>
</div>
);
};
+43 -24
View File
@@ -12,7 +12,7 @@ import {
QueryHintGetter,
QueryHint,
} from 'app/types/explore';
import { RawTimeRange, DataQuery } from 'app/types/series';
import { TimeRange, DataQuery } from 'app/types/series';
import store from 'app/core/store';
import {
DEFAULT_RANGE,
@@ -30,6 +30,8 @@ import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer'
import NoOptionsMessage from 'app/core/components/Picker/NoOptionsMessage';
import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
import { Emitter } from 'app/core/utils/emitter';
import * as dateMath from 'app/core/utils/datemath';
import Panel from './Panel';
import QueryRows from './QueryRows';
@@ -38,6 +40,7 @@ import Logs from './Logs';
import Table from './Table';
import ErrorBoundary from './ErrorBoundary';
import TimePicker from './TimePicker';
import { Alert } from './Error';
interface ExploreProps {
datasourceSrv: DatasourceSrv;
@@ -88,6 +91,7 @@ interface ExploreProps {
*/
export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
el: any;
exploreEvents: Emitter;
/**
* Current query expressions of the rows including their modifications, used for running queries.
* Not kept in component state to prevent edit-render roundtrips.
@@ -132,6 +136,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
};
}
this.modifiedQueries = initialQueries.slice();
this.exploreEvents = new Emitter();
}
async componentDidMount() {
@@ -140,7 +145,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
if (!datasourceSrv) {
throw new Error('No datasource service passed as props.');
}
const datasources = datasourceSrv.getExploreSources();
const datasources = datasourceSrv.getAll();
const exploreDatasources = datasources.map(ds => ({
value: ds.name,
label: ds.name,
@@ -155,19 +160,20 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
} else {
datasource = await datasourceSrv.get();
}
if (!datasource.meta.explore) {
datasource = await datasourceSrv.get(datasources[0].name);
}
await this.setDatasource(datasource);
} else {
this.setState({ datasourceMissing: true });
}
}
componentWillUnmount() {
this.exploreEvents.removeAllListeners();
}
async setDatasource(datasource: any, origin?: DataSource) {
const supportsGraph = datasource.meta.metrics;
const supportsLogs = datasource.meta.logs;
const supportsTable = datasource.meta.metrics;
const supportsTable = datasource.meta.tables;
const datasourceId = datasource.meta.id;
let datasourceError = null;
@@ -317,8 +323,14 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
}
};
onChangeTime = (nextRange: RawTimeRange) => {
const range: RawTimeRange = {
// onChangeTime = (nextRange: RawTimeRange) => {
// const range: RawTimeRange = {
// ...nextRange,
// };
// this.setState({ range }, () => this.onSubmit());
// };
onChangeTime = (nextRange: TimeRange) => {
const range: TimeRange = {
...nextRange,
};
this.setState({ range }, () => this.onSubmit());
@@ -530,16 +542,11 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
const { datasource, range } = this.state;
const { interval, intervalMs } = getIntervals(range, datasource, this.el.offsetWidth);
const configuredQueries = [
{
...queryOptions,
...query,
},
];
const configuredQueries = [Object.assign(query, queryOptions)];
// Clone range for query request
const queryRange: RawTimeRange = { ...range };
// const queryRange: RawTimeRange = { ...range };
// const { from, to, raw } = this.timeSrv.timeRange();
// Datasource is using `panelId + query.refId` for cancellation logic.
// Using `format` here because it relates to the view panel that the request is for.
const panelId = queryOptions.format;
@@ -549,7 +556,12 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
intervalMs,
panelId,
targets: configuredQueries, // Datasources rely on DataQueries being passed under the targets key.
range: queryRange,
range: {
from: dateMath.parse(range.from, false),
to: dateMath.parse(range.to, true),
raw: range,
},
rangeRaw: range,
};
}
@@ -696,17 +708,19 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
}
const { datasource } = this.state;
const datasourceId = datasource.meta.id;
// Run all queries concurrently
// Run all queries concurrentlyso
queries.forEach(async (query, rowIndex) => {
const transaction = this.startQueryTransaction(query, rowIndex, resultType, queryOptions);
try {
const now = Date.now();
const res = await datasource.query(transaction.options);
this.exploreEvents.emit('data-received', res.data || []);
const latency = Date.now() - now;
const results = resultGetter ? resultGetter(res.data) : res.data;
this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId);
this.setState({ graphRange: transaction.options.range });
} catch (response) {
this.exploreEvents.emit('data-error', response);
this.failQueryTransaction(transaction.id, response, datasourceId);
}
});
@@ -759,7 +773,10 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
const graphResult = _.flatten(
queryTransactions.filter(qt => qt.resultType === 'Graph' && qt.done && qt.result).map(qt => qt.result)
);
const tableResult = mergeTablesIntoModel(
//Temp solution... How do detect if ds supports table format?
let tableResult;
tableResult = mergeTablesIntoModel(
new TableModel(),
...queryTransactions.filter(qt => qt.resultType === 'Table' && qt.done && qt.result).map(qt => qt.result)
);
@@ -835,16 +852,16 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
</button>
</div>
</div>
{datasourceLoading ? <div className="explore-container">Loading datasource...</div> : null}
{datasourceMissing ? (
<div className="explore-container">Please add a datasource that supports Explore (e.g., Prometheus).</div>
) : null}
{datasourceError ? (
<div className="explore-container">Error connecting to datasource. [{datasourceError}]</div>
) : null}
{datasourceError && (
<div className="explore-container">
<Alert message={`Error connecting to datasource: ${datasourceError}`} />
</div>
)}
{datasource && !datasourceError ? (
<div className="explore-container">
@@ -858,6 +875,8 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
onExecuteQuery={this.onSubmit}
onRemoveQueryRow={this.onRemoveQueryRow}
transactions={queryTransactions}
exploreEvents={this.exploreEvents}
range={range}
/>
<main className="m-t-2">
<ErrorBoundary>
@@ -0,0 +1,77 @@
import React, { PureComponent } from 'react';
import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
import { Emitter } from 'app/core/utils/emitter';
import { getIntervals } from 'app/core/utils/explore';
import { DataQuery } from 'app/types';
import { RawTimeRange } from 'app/types/series';
import { getTimeSrv } from 'app/features/dashboard/time_srv';
import 'app/features/plugins/plugin_loader';
interface QueryEditorProps {
datasource: any;
error?: string | JSX.Element;
onExecuteQuery?: () => void;
onQueryChange?: (value: DataQuery, override?: boolean) => void;
initialQuery: DataQuery;
exploreEvents: Emitter;
range: RawTimeRange;
}
export default class QueryEditor extends PureComponent<QueryEditorProps, any> {
element: any;
component: AngularComponent;
async componentDidMount() {
if (!this.element) {
return;
}
const { datasource, initialQuery, exploreEvents, range } = this.props;
this.initTimeSrv(range);
const loader = getAngularLoader();
const template = '<plugin-component type="query-ctrl"> </plugin-component>';
const target = { datasource: datasource.name, ...initialQuery };
const scopeProps = {
target,
ctrl: {
refresh: () => {
this.props.onQueryChange(target, false);
this.props.onExecuteQuery();
},
events: exploreEvents,
panel: {
datasource,
targets: [target],
},
dashboard: {
getNextQueryLetter: x => '',
},
hideEditorRowActions: true,
...getIntervals(range, datasource, null), // Possible to get resolution?
},
};
this.component = loader.load(this.element, scopeProps, template);
}
componentWillUnmount() {
if (this.component) {
this.component.destroy();
}
}
initTimeSrv(range) {
const timeSrv = getTimeSrv();
timeSrv.init({
time: range,
refresh: false,
getTimezone: () => 'utc',
timeRangeUpdated: () => console.log('refreshDashboard!'),
});
}
render() {
return <div ref={element => (this.element = element)} style={{ width: '100%' }} />;
}
}
+35 -13
View File
@@ -1,10 +1,13 @@
import React, { PureComponent } from 'react';
import { QueryTransaction, HistoryItem, QueryHint } from 'app/types/explore';
import { Emitter } from 'app/core/utils/emitter';
import DefaultQueryField from './QueryField';
// import DefaultQueryField from './QueryField';
import QueryEditor from './QueryEditor';
import QueryTransactionStatus from './QueryTransactionStatus';
import { DataSource, DataQuery } from 'app/types';
import { RawTimeRange } from 'app/types/series';
function getFirstHintFromTransactions(transactions: QueryTransaction[]): QueryHint {
const transaction = transactions.find(qt => qt.hints && qt.hints.length > 0);
@@ -27,6 +30,8 @@ interface QueryRowCommonProps {
datasource: DataSource;
history: HistoryItem[];
transactions: QueryTransaction[];
exploreEvents: Emitter;
range: RawTimeRange;
}
type QueryRowProps = QueryRowCommonProps &
@@ -36,6 +41,11 @@ type QueryRowProps = QueryRowCommonProps &
};
class QueryRow extends PureComponent<QueryRowProps> {
onExecuteQuery = () => {
const { onExecuteQuery } = this.props;
onExecuteQuery();
};
onChangeQuery = (value: DataQuery, override?: boolean) => {
const { index, onChangeQuery } = this.props;
if (onChangeQuery) {
@@ -76,27 +86,39 @@ class QueryRow extends PureComponent<QueryRowProps> {
};
render() {
const { datasource, history, initialQuery, transactions } = this.props;
const { datasource, history, initialQuery, transactions, exploreEvents, range } = this.props;
const transactionWithError = transactions.find(t => t.error !== undefined);
const hint = getFirstHintFromTransactions(transactions);
const queryError = transactionWithError ? transactionWithError.error : null;
const QueryField = datasource.pluginExports.ExploreQueryField || DefaultQueryField;
const QueryField = datasource.pluginExports.ExploreQueryField;
return (
<div className="query-row">
<div className="query-row-status">
<QueryTransactionStatus transactions={transactions} />
</div>
<div className="query-row-field">
<QueryField
datasource={datasource}
error={queryError}
hint={hint}
initialQuery={initialQuery}
history={history}
onClickHintFix={this.onClickHintFix}
onPressEnter={this.onPressEnter}
onQueryChange={this.onChangeQuery}
/>
{QueryField ? (
<QueryField
datasource={datasource}
error={queryError}
hint={hint}
initialQuery={initialQuery}
history={history}
onClickHintFix={this.onClickHintFix}
onPressEnter={this.onPressEnter}
onQueryChange={this.onChangeQuery}
/>
) : (
<QueryEditor
datasource={datasource}
error={queryError}
onQueryChange={this.onChangeQuery}
onExecuteQuery={this.onExecuteQuery}
initialQuery={initialQuery}
exploreEvents={exploreEvents}
range={range}
/>
)}
</div>
<div className="query-row-tools">
<button className="btn navbar-button navbar-button--tight" onClick={this.onClickClearButton}>
+8 -2
View File
@@ -3,7 +3,7 @@ import moment from 'moment';
import * as dateMath from 'app/core/utils/datemath';
import * as rangeUtil from 'app/core/utils/rangeutil';
import { RawTimeRange } from 'app/types/series';
import { RawTimeRange, TimeRange } from 'app/types/series';
const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
export const DEFAULT_RANGE = {
@@ -120,6 +120,12 @@ export default class TimePicker extends PureComponent<TimePickerProps, TimePicke
to: moment(nextTo),
};
const nextTimeRange: TimeRange = {
raw: nextRange,
from,
to,
};
this.setState(
{
rangeString: rangeUtil.describeTimeRange(nextRange),
@@ -127,7 +133,7 @@ export default class TimePicker extends PureComponent<TimePickerProps, TimePicke
toRaw: nextRange.to.format(DATE_FORMAT),
},
() => {
onChangeTime(nextRange);
onChangeTime(nextTimeRange);
}
);
}
@@ -0,0 +1,22 @@
import coreModule from 'app/core/core_module';
export class GeneralTabCtrl {
panelCtrl: any;
/** @ngInject */
constructor($scope) {
this.panelCtrl = $scope.ctrl;
}
}
/** @ngInject */
export function generalTab() {
'use strict';
return {
restrict: 'E',
templateUrl: 'public/app/features/panel/partials/general_tab.html',
controller: GeneralTabCtrl,
};
}
coreModule.directive('panelGeneralTab', generalTab);
@@ -5,7 +5,6 @@ import config from 'app/core/config';
import { PanelCtrl } from 'app/features/panel/panel_ctrl';
import { getExploreUrl } from 'app/core/utils/explore';
import { metricsTabDirective } from './metrics_tab';
import { applyPanelTimeOverrides, getResolution } from 'app/features/dashboard/utils/panel';
class MetricsPanelCtrl extends PanelCtrl {
@@ -42,7 +41,6 @@ class MetricsPanelCtrl extends PanelCtrl {
this.panel.datasource = this.panel.datasource || null;
this.events.on('refresh', this.onMetricsPanelRefresh.bind(this));
this.events.on('init-edit-mode', this.onInitMetricsPanelEditMode.bind(this));
this.events.on('panel-teardown', this.onPanelTearDown.bind(this));
}
@@ -53,11 +51,6 @@ class MetricsPanelCtrl extends PanelCtrl {
}
}
private onInitMetricsPanelEditMode() {
this.addEditorTab('Metrics', metricsTabDirective, 1, 'fa fa-database');
this.addEditorTab('Time range', 'public/app/features/panel/partials/panelTime.html');
}
private onMetricsPanelRefresh() {
// ignore fetching data if another panel is in fullscreen
if (this.otherPanelInFullscreenMode()) {
+1 -1
View File
@@ -41,7 +41,7 @@ export class MetricsTabCtrl {
this.datasources = datasourceSrv.getMetricSources();
this.panelDsValue = this.panelCtrl.panel.datasource;
// addded here as old query controller expects this on panelCtrl but
// added here as old query controller expects this on panelCtrl but
// they are getting MetricsTabCtrl instead
this.events = this.panel.events;
-1
View File
@@ -91,7 +91,6 @@ export class PanelCtrl {
initEditMode() {
this.editorTabs = [];
this.addEditorTab('General', 'public/app/partials/panelgeneral.html');
this.editModeInitiated = true;
this.events.emit('init-edit-mode', null);
+12 -44
View File
@@ -6,54 +6,22 @@ import baron from 'baron';
const module = angular.module('grafana.directives');
const panelTemplate = `
<div ng-class="{'panel-editor-container': ctrl.panel.isEditing, 'panel-height-helper': !ctrl.panel.isEditing}">
<div ng-class="{'panel-editor-container__panel': ctrl.panel.isEditing, 'panel-height-helper': !ctrl.panel.isEditing}">
<div class="panel-container">
<div class="panel-header" ng-class="{'grid-drag-handle': !ctrl.panel.fullscreen}">
<span class="panel-info-corner">
<i class="fa"></i>
<span class="panel-info-corner-inner"></span>
</span>
<div class="panel-container">
<div class="panel-header" ng-class="{'grid-drag-handle': !ctrl.panel.fullscreen}">
<span class="panel-info-corner">
<i class="fa"></i>
<span class="panel-info-corner-inner"></span>
</span>
<span class="panel-loading" ng-show="ctrl.loading">
<i class="fa fa-spinner fa-spin"></i>
</span>
<span class="panel-loading" ng-show="ctrl.loading">
<i class="fa fa-spinner fa-spin"></i>
</span>
<panel-header class="panel-title-container" panel-ctrl="ctrl"></panel-header>
</div>
<div class="panel-content">
<ng-transclude class="panel-height-helper"></ng-transclude>
</div>
<panel-header class="panel-title-container" panel-ctrl="ctrl"></panel-header>
</div>
</div>
<div ng-if="ctrl.panel.isEditing" ng-class="{'panel-editor-container__editor': ctrl.panel.isEditing,
'panel-height-helper': !ctrl.panel.isEditing}">
<div class="tabbed-view tabbed-view--new">
<div class="tabbed-view-header">
<h3 class="tabbed-view-panel-title">
{{ctrl.pluginName}}
</h3>
<ul class="gf-tabs">
<li class="gf-tabs-item" ng-repeat="tab in ::ctrl.editorTabs">
<a class="gf-tabs-link" ng-click="ctrl.changeTab($index)" ng-class="{active: ctrl.editorTabIndex === $index}">
{{::tab.title}}
</a>
</li>
</ul>
<button class="tabbed-view-close-btn" ng-click="ctrl.exitFullscreen();">
<i class="fa fa-remove"></i>
</button>
</div>
<div class="tabbed-view-body">
<div ng-repeat="tab in ctrl.editorTabs" ng-if="ctrl.editorTabIndex === $index" class="panel-height-helper">
<panel-editor-tab editor-tab="tab" ctrl="ctrl" index="$index"></panel-editor-tab>
</div>
</div>
<div class="panel-content">
<ng-transclude class="panel-height-helper"></ng-transclude>
</div>
</div>
</div>
@@ -9,7 +9,6 @@ function panelEditorTab(dynamicDirectiveSrv) {
scope: {
ctrl: '=',
editorTab: '=',
index: '=',
},
directive: scope => {
const pluginId = scope.ctrl.pluginId;
@@ -1,73 +1,3 @@
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label">Data Source</label>
<gf-form-dropdown model="ctrl.panelDsValue" css-class="gf-size-auto"
lookup-text="true"
get-options="ctrl.getOptions(true)"
on-change="ctrl.datasourceChanged($option)">
</gf-form-dropdown>
</div>
<div class="gf-form gf-form--grow">
<label class="gf-form-label gf-form-label--grow"></label>
</div>
<div class="gf-form" ng-if="ctrl.queryOptions">
<a class="gf-form-label" ng-click="ctrl.toggleOptions()">
<i class="fa fa-fw fa-caret-right" ng-hide="ctrl.optionsOpen"></i><i class="fa fa-fw fa-caret-down" ng-show="ctrl.optionsOpen"></i>Options
</a>
</div>
<div class="gf-form" ng-if="ctrl.hasQueryHelp">
<button class="gf-form-label" ng-click="ctrl.toggleHelp()">
<i class="fa fa-fw fa-caret-right" ng-hide="ctrl.helpOpen"></i><i class="fa fa-fw fa-caret-down" ng-show="ctrl.helpOpen"></i>Help
</button>
</div>
<div class="gf-form">
<button class="gf-form-label" ng-click="ctrl.toggleQueryTroubleshooter()" bs-tooltip="'Display query request & response'">
<i class="fa fa-fw fa-caret-right" ng-hide="ctrl.queryTroubleshooterOpen"></i><i class="fa fa-fw fa-caret-down" ng-show="ctrl.queryTroubleshooterOpen"></i>Query Inspector
</button>
</div>
</div>
<div>
<div ng-if="ctrl.optionsOpen">
<div class="gf-form gf-form--flex-end" ng-if="ctrl.queryOptions.minInterval">
<label class="gf-form-label">Min time interval</label>
<input type="text" class="gf-form-input width-6" placeholder="{{ctrl.panelCtrl.interval}}" ng-model="ctrl.panel.interval" spellcheck="false" ng-model-onblur ng-change="ctrl.panelCtrl.refresh()" />
<info-popover mode="right-absolute">
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.
</info-popover>
</div>
<div class="gf-form gf-form--flex-end" ng-if="ctrl.queryOptions.cacheTimeout">
<label class="gf-form-label width-9">Cache timeout</label>
<input type="text" class="gf-form-input width-6" placeholder="60" ng-model="ctrl.panel.cacheTimeout" ng-model-onblur ng-change="ctrl.panelCtrl.refresh()" spellcheck="false" />
<info-popover mode="right-absolute">
If your time series store has a query cache this option can override the default
cache timeout. Specify a numeric value in seconds.
</info-popover>
</div>
<div class="gf-form gf-form--flex-end" ng-if="ctrl.queryOptions.maxDataPoints">
<label class="gf-form-label width-9">Max data points</label>
<input type="text" class="gf-form-input width-6" placeholder="auto" ng-model-onblur ng-change="ctrl.panelCtrl.refresh()" ng-model="ctrl.panel.maxDataPoints" spellcheck="false" />
<info-popover mode="right-absolute">
The maximum data points the query should return. For graphs this
is automatically set to one data point per pixel.
</info-popover>
</div>
</div>
<div class="grafana-info-box" ng-if="ctrl.helpOpen">
<div class="markdown-html" ng-bind-html="ctrl.helpHtml"></div>
<a class="grafana-info-box__close" ng-click="ctrl.toggleHelp()">
<i class="fa fa-chevron-up"></i>
</a>
</div>
<query-troubleshooter panel-ctrl="ctrl.panelCtrl" is-open="ctrl.queryTroubleshooterOpen"></query-troubleshooter>
</div>
</div>
<div class="query-editor-rows gf-form-group" ng-if="ctrl.datasourceInstance">
<div ng-repeat="target in ctrl.panel.targets" ng-class="{'gf-form-disabled': target.hide}">
@@ -1,59 +1,44 @@
<div class="gf-form-query">
<div class="gf-form gf-form-query-letter-cell">
<div ng-if="!ctrl.hideEditorRowActions" class="gf-form gf-form-query-letter-cell">
<label class="gf-form-label">
<a class="pointer" tabindex="1" ng-click="ctrl.toggleCollapse()">
<span ng-class="{muted: !ctrl.canCollapse}" class="gf-form-query-letter-cell-carret">
<span ng-class="{muted: !ctrl.canCollapse}" class="gf-form-query-letter-cell-carret">
<i class="fa fa-caret-down" ng-hide="ctrl.collapsed"></i>
<i class="fa fa-caret-right" ng-show="ctrl.collapsed"></i>
</span>
<span class="gf-form-query-letter-cell-letter">{{ctrl.target.refId}}</span>
<em class="gf-form-query-letter-cell-ds" ng-show="ctrl.target.datasource">({{ctrl.target.datasource}})</em>
<span class="gf-form-query-letter-cell-letter">{{ ctrl.target.refId }}</span>
<em class="gf-form-query-letter-cell-ds" ng-show="ctrl.target.datasource">({{ ctrl.target.datasource }})</em>
</a>
</label>
</label>
</div>
<div class="gf-form-query-content gf-form-query-content--collapsed" ng-if="ctrl.collapsed">
<div class="gf-form">
<label class="gf-form-label pointer gf-form-label--grow" ng-click="ctrl.toggleCollapse()">
{{ctrl.collapsedText}}
</label>
</div>
</div>
<div class="gf-form-query-content gf-form-query-content--collapsed" ng-if="ctrl.collapsed">
<div class="gf-form">
<label class="gf-form-label pointer gf-form-label--grow" ng-click="ctrl.toggleCollapse()">
{{ ctrl.collapsedText }}
</label>
</div>
</div>
<div ng-transclude class="gf-form-query-content" ng-if="!ctrl.collapsed">
</div>
<div ng-transclude class="gf-form-query-content" ng-if="!ctrl.collapsed"></div>
<div class="gf-form">
<label class="gf-form-label dropdown">
<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
<i class="fa fa-bars"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem" ng-if="ctrl.hasTextEditMode">
<a tabindex="1" ng-click="ctrl.toggleEditorMode()">Toggle Edit Mode</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="ctrl.duplicateQuery()">Duplicate</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="ctrl.moveQuery(-1)">Move up</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="ctrl.moveQuery(1)">Move down</a>
</li>
</ul>
</label>
<div ng-if="!ctrl.hideEditorRowActions" class="gf-form">
<label class="gf-form-label dropdown">
<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1"> <i class="fa fa-bars"></i> </a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem" ng-if="ctrl.hasTextEditMode">
<a tabindex="1" ng-click="ctrl.toggleEditorMode()">Toggle Edit Mode</a>
</li>
<li role="menuitem"><a tabindex="1" ng-click="ctrl.duplicateQuery()">Duplicate</a></li>
<li role="menuitem"><a tabindex="1" ng-click="ctrl.moveQuery(-1)">Move up</a></li>
<li role="menuitem"><a tabindex="1" ng-click="ctrl.moveQuery(1)">Move down</a></li>
</ul>
</label>
<label class="gf-form-label">
<a ng-click="ctrl.toggleHideQuery()" role="menuitem">
<i class="fa fa-eye"></i>
</a>
</label>
<label class="gf-form-label">
<a class="pointer" tabindex="1" ng-click="ctrl.removeQuery(ctrl.target)">
<i class="fa fa-trash"></i>
</a>
</label>
</div>
<a ng-click="ctrl.toggleHideQuery()" role="menuitem"> <i class="fa fa-eye"></i> </a>
</label>
<label class="gf-form-label">
<a class="pointer" tabindex="1" ng-click="ctrl.removeQuery(ctrl.target)"> <i class="fa fa-trash"></i> </a>
</label>
</div>
</div>
@@ -11,11 +11,13 @@ export class QueryRowCtrl {
panelCtrl: any;
panel: any;
collapsed: any;
hideEditorRowActions: boolean;
constructor() {
this.panelCtrl = this.queryCtrl.panelCtrl;
this.target = this.queryCtrl.target;
this.panel = this.panelCtrl.panel;
this.hideEditorRowActions = this.panelCtrl.hideEditorRowActions;
if (!this.target.refId) {
this.target.refId = this.panelCtrl.dashboard.getNextQueryLetter(this.panel);
@@ -74,7 +74,8 @@ export class DatasourceSrv {
}
getAll() {
return config.datasources;
const { datasources } = config;
return Object.keys(datasources).map(name => datasources[name]);
}
getAnnotationSources() {
@@ -91,14 +92,6 @@ export class DatasourceSrv {
return sources;
}
getExploreSources() {
const { datasources } = config;
const es = Object.keys(datasources)
.map(name => datasources[name])
.filter(ds => ds.meta && ds.meta.explore);
return _.sortBy(es, ['name']);
}
getMetricSources(options?) {
const metricSources: DataSourceSelectItem[] = [];
@@ -18,32 +18,6 @@ const templateSrv = {
describe('datasource_srv', () => {
const _datasourceSrv = new DatasourceSrv({}, {}, {}, templateSrv);
describe('when loading explore sources', () => {
beforeEach(() => {
config.datasources = {
explore1: {
name: 'explore1',
meta: { explore: true, metrics: true },
},
explore2: {
name: 'explore2',
meta: { explore: true, metrics: false },
},
nonExplore: {
name: 'nonExplore',
meta: { explore: false, metrics: true },
},
};
});
it('should return list of explore sources', () => {
const exploreSources = _datasourceSrv.getExploreSources();
expect(exploreSources.length).toBe(2);
expect(exploreSources[0].name).toBe('explore1');
expect(exploreSources[1].name).toBe('explore2');
});
});
describe('when loading metric sources', () => {
let metricSources;
const unsortedDatasources = {
@@ -3,13 +3,12 @@
"type": "datasource",
"id": "graphite",
"includes": [
{"type": "dashboard", "name": "Graphite Carbon Metrics", "path": "dashboards/carbon_metrics.json"}
],
"includes": [{ "type": "dashboard", "name": "Graphite Carbon Metrics", "path": "dashboards/carbon_metrics.json" }],
"metrics": true,
"alerting": true,
"annotations": true,
"tables": false,
"queryOptions": {
"maxDataPoints": true,
@@ -27,8 +26,11 @@
"large": "img/graphite_logo.png"
},
"links": [
{"name": "Graphite", "url": "https://graphiteapp.org/"},
{"name": "Graphite 1.1 Release", "url": "https://grafana.com/blog/2018/01/11/graphite-1.1-teaching-an-old-dog-new-tricks/"}
{ "name": "Graphite", "url": "https://graphiteapp.org/" },
{
"name": "Graphite 1.1 Release",
"url": "https://grafana.com/blog/2018/01/11/graphite-1.1-teaching-an-old-dog-new-tricks/"
}
],
"version": "5.0.0"
}
@@ -7,6 +7,7 @@
"annotations": false,
"logs": true,
"explore": true,
"tables": true,
"info": {
"description": "Grafana Logging Data Source for Grafana",
"author": {
@@ -25,4 +26,4 @@
],
"version": "5.3.0"
}
}
}
@@ -18,9 +18,9 @@
"alerting": true,
"annotations": true,
"metrics": true,
"tables": true,
"queryOptions": {
"minInterval": true
}
}
@@ -19,9 +19,9 @@
"alerting": true,
"annotations": true,
"metrics": true,
"tables": true,
"queryOptions": {
"minInterval": true
}
}
@@ -7,6 +7,7 @@
"defaultMatchFormat": "pipe",
"annotations": true,
"alerting": true,
"tables": false,
"info": {
"description": "OpenTSDB Data Source for Grafana",
@@ -19,9 +19,9 @@
"alerting": true,
"annotations": true,
"metrics": true,
"tables": true,
"queryOptions": {
"minInterval": true
}
}
@@ -23,6 +23,7 @@
"alerting": true,
"annotations": true,
"explore": true,
"tables": true,
"queryOptions": {
"minInterval": true
},
@@ -44,4 +45,4 @@
],
"version": "5.0.0"
}
}
}
@@ -5,6 +5,7 @@
"metrics": true,
"alerting": true,
"annotations": true,
"tables": false,
"state": "beta",
"queryOptions": {
"maxDataPoints": true,
+7
View File
@@ -84,6 +84,13 @@ class TestDataDatasource {
}
return this.$q.when(events);
}
testDatasource() {
return Promise.resolve({
status: 'success',
message: 'Data source is working',
});
}
}
export { TestDataDatasource };
+7 -12
View File
@@ -5,8 +5,8 @@ import './time_regions_form';
import template from './template';
import _ from 'lodash';
import config from 'app/core/config';
import { MetricsPanelCtrl, alertTab } from 'app/plugins/sdk';
import { MetricsPanelCtrl } from 'app/plugins/sdk';
import { DataProcessor } from './data_processor';
import { axesEditorComponent } from './axes_editor';
@@ -71,11 +71,11 @@ class GraphCtrl extends MetricsPanelCtrl {
// length of a dash
dashLength: 10,
// length of space between two dashes
spaceLength: 10,
paceLength: 10,
// show hide points
points: false,
// point radius in pixels
pointradius: 5,
pointradius: 2,
// show hide bars
bars: false,
// enable/disable stacking
@@ -135,14 +135,9 @@ class GraphCtrl extends MetricsPanelCtrl {
}
onInitEditMode() {
this.addEditorTab('Axes', axesEditorComponent, 2);
this.addEditorTab('Legend', 'public/app/plugins/panel/graph/tab_legend.html', 3);
this.addEditorTab('Display', 'public/app/plugins/panel/graph/tab_display.html', 4);
if (config.alertingEnabled) {
this.addEditorTab('Alert', alertTab, 5);
}
this.addEditorTab('Display options', 'public/app/plugins/panel/graph/tab_display.html');
this.addEditorTab('Axes', axesEditorComponent);
this.addEditorTab('Legend', 'public/app/plugins/panel/graph/tab_legend.html');
this.subTabIndex = 0;
}
+8
View File
@@ -0,0 +1,8 @@
export interface ValidationRule {
rule: (valueToValidate: string) => boolean;
errorMessage: string;
}
export interface ValidationEvents {
[eventName: string]: ValidationRule[];
}
+3 -1
View File
@@ -30,7 +30,7 @@ import {
AppNotificationTimeout,
} from './appNotifications';
import { DashboardSearchHit } from './search';
import { ValidationEvents, ValidationRule } from './form';
export {
Team,
TeamsState,
@@ -89,6 +89,8 @@ export {
AppNotificationTimeout,
DashboardSearchHit,
UserState,
ValidationEvents,
ValidationRule,
};
export interface StoreState {
+10
View File
@@ -27,6 +27,12 @@ export interface PanelPlugin {
exports?: PluginExports;
}
interface PluginMetaQueryOptions {
cacheTimeout?: boolean;
maxDataPoints?: boolean;
minInterval?: boolean;
}
export interface PluginMeta {
id: string;
name: string;
@@ -35,9 +41,13 @@ export interface PluginMeta {
// Datasource-specific
metrics?: boolean;
tables?: boolean;
logs?: boolean;
explore?: boolean;
annotations?: boolean;
mixed?: boolean;
hasQueryHelp?: boolean;
queryOptions?: PluginMetaQueryOptions;
}
export interface PluginInclude {
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:#0A0A0C;}
.st1{fill:#E3E2E2;}
</style>
<g>
<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z"/>
<path class="st1" d="M50,0C22.4,0,0,22.4,0,50s22.4,50,50,50c27.6,0,50-22.4,50-50S77.6,0,50,0z M5.7,50C5.7,25.6,25.6,5.7,50,5.7
S94.3,25.6,94.3,50S74.4,94.3,50,94.3S5.7,74.4,5.7,50z"/>
<g>
<path class="st1" d="M58.2,51.9c1.2-2.9,1-6.4-1-9.1c-1.5-2.1-3.7-3.3-6.1-3.7c-1.2-0.2-2.5-0.1-3.7,0.2l4,5.5
c1.1,1.5,0.8,3.7-0.8,4.8c-0.8,0.6-1.7,0.8-2.6,0.6c-0.9-0.1-1.7-0.6-2.2-1.4l-4-5.5c-2,3.1-1.9,7.2,0.4,10.4
c1.5,2.1,3.7,3.3,6.1,3.7c0.8,0.1,1.5,0.1,2.3,0.1l12.8,17.6c0.8,1.1,1.9,1.7,3.1,1.9s2.5-0.1,3.5-0.9c2.1-1.5,2.6-4.5,1-6.6
L58.2,51.9z M67.9,71c1,0.2,1.7,1.1,1.5,2.1c-0.2,1-1.1,1.7-2.1,1.5s-1.7-1.1-1.5-2.1C65.9,71.6,66.9,70.9,67.9,71z"/>
<path class="st1" d="M61.5,50.9c0,0.1-0.1,0.3-0.1,0.4C61.4,51.2,61.4,51.1,61.5,50.9z"/>
<path class="st1" d="M59.9,41.6c0.1,0.1,0.1,0.2,0.2,0.3C60,41.9,60,41.7,59.9,41.6z"/>
<path class="st1" d="M61.6,50.1c0,0.2-0.1,0.5-0.1,0.7c0.2-0.8,0.3-1.7,0.3-2.6c0-2.3-0.6-4.4-1.7-6.2
C61.5,44.3,62.1,47.2,61.6,50.1z"/>
<path class="st1" d="M59.7,41.3C59.7,41.4,59.7,41.4,59.7,41.3C59.7,41.4,59.7,41.4,59.7,41.3z"/>
<path class="st1" d="M73.4,59.1l1.9-5.7l-6.4-3c0.1-0.7,0.1-1.5,0.1-2.2c0-0.7,0-1.4-0.1-2.1l6.4-3l-1.9-5.7l-6.9,1.3
c-0.7-1.3-1.6-2.4-2.5-3.5l3.4-6.2l-4.9-3.5l-4.8,5.1c-1.3-0.6-2.7-1.1-4.1-1.4l-0.9-7h-6l-0.9,7c-1.4,0.3-2.8,0.7-4.1,1.3
l-4.8-5.1L32,28.9l3.4,6.2c-1,1.1-1.8,2.2-2.5,3.5L26,37.2L24.1,43l6.4,3c-0.1,0.7-0.1,1.5-0.1,2.2c0,0.7,0,1.4,0.1,2.1l-6.4,3
l1.9,5.7l6.9-1.3c0.7,1.3,1.6,2.4,2.5,3.5L32,67.3l4.9,3.5l4.8-5.1c1.3,0.6,2.7,1.1,4.1,1.4l0.9,7h6l0.9-7c0.2,0,0.4-0.1,0.6-0.1
l-4.8-6.6c-0.5,0-1.1-0.1-1.6-0.1c-6.7-1-11.2-7.3-10.2-14c1-6.7,7.3-11.2,14-10.2c3.5,0.5,6.4,2.5,8.2,5.2c0,0,0,0,0.1,0.1
c0.1,0.1,0.1,0.2,0.1,0.2c0.1,0.1,0.1,0.2,0.2,0.3c0,0,0,0,0,0c1.1,1.8,1.7,3.9,1.7,6.2c0,0.9-0.1,1.8-0.3,2.6c0,0,0,0.1,0,0.1
c0,0.1-0.1,0.3-0.1,0.4c0,0.1,0,0.1-0.1,0.2l4.9,6.7c0.1-0.1,0.2-0.3,0.3-0.4L73.4,59.1z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:#0A0A0C;}
.st2{fill:url(#SVGID_2_);}
.st3{fill:url(#SVGID_3_);}
.st4{fill:url(#SVGID_4_);}
.st5{fill:url(#SVGID_5_);}
.st6{fill:url(#SVGID_6_);}
.st7{fill:url(#SVGID_7_);}
</style>
<g>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="60.5" y1="130.7753" x2="60.5" y2="18.6673">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st0" d="M120.8,50L87.2,16.4C78.1,6.3,64.9,0,50.2,0c-27.6,0-50,22.4-50,50s22.4,50,50,50c14.4,0,27.5-6.1,36.6-15.9
c0.1-0.1,0.1-0.1,0.2-0.2L120.8,50z"/>
</g>
<path class="st1" d="M94.4,49.4C94.4,25,74.6,5.1,50.2,5.1S5.9,25,5.9,49.4s19.9,44.3,44.3,44.3S94.4,73.8,94.4,49.4z"/>
<g>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="56.3802" y1="134.9514" x2="56.3802" y2="30.9934">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st2" d="M58.4,51.3c1.2-2.9,1-6.4-1-9.1c-1.5-2.1-3.7-3.3-6.1-3.7c-1.2-0.2-2.5-0.1-3.7,0.2l4,5.5
c1.1,1.5,0.8,3.7-0.8,4.8c-0.8,0.6-1.7,0.8-2.6,0.6c-0.9-0.1-1.7-0.6-2.2-1.4l-4-5.5c-2,3.1-1.9,7.2,0.4,10.4
c1.5,2.1,3.7,3.3,6.1,3.7c0.8,0.1,1.5,0.1,2.3,0.1l12.8,17.6c0.8,1.1,1.9,1.7,3.1,1.9s2.5-0.1,3.5-0.9c2.1-1.5,2.6-4.5,1-6.6
L58.4,51.3z M68.1,70.4c1,0.2,1.7,1.1,1.5,2.1c-0.2,1-1.1,1.7-2.1,1.5S65.8,73,66,72C66.1,70.9,67.1,70.3,68.1,70.4z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="61.6059" y1="134.9514" x2="61.6059" y2="30.9934">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st3" d="M61.7,50.3c0,0.1-0.1,0.3-0.1,0.4C61.6,50.6,61.6,50.4,61.7,50.3z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="60.1683" y1="134.9514" x2="60.1683" y2="30.9934">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st4" d="M60.1,41c0.1,0.1,0.1,0.2,0.2,0.3C60.2,41.2,60.1,41.1,60.1,41z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="61.1232" y1="134.9514" x2="61.1232" y2="30.9934">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st5" d="M61.8,49.5c0,0.2-0.1,0.5-0.1,0.7c0.2-0.8,0.3-1.7,0.3-2.6c0-2.3-0.6-4.4-1.7-6.2
C61.7,43.7,62.3,46.5,61.8,49.5z"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="59.8929" y1="134.9514" x2="59.8929" y2="30.9934">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st6" d="M59.9,40.7C59.9,40.7,59.9,40.8,59.9,40.7C59.9,40.8,59.9,40.7,59.9,40.7z"/>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="49.8648" y1="134.9514" x2="49.8648" y2="30.9934">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st7" d="M73.6,58.5l1.9-5.7l-6.4-3c0.1-0.7,0.1-1.5,0.1-2.2c0-0.7,0-1.4-0.1-2.1l6.4-3l-1.9-5.7L66.7,38
c-0.7-1.3-1.6-2.4-2.5-3.5l3.4-6.2l-4.9-3.5l-4.8,5.1c-1.3-0.6-2.7-1.1-4.1-1.4l-0.9-7h-6l-0.9,7c-1.4,0.3-2.8,0.7-4.1,1.3
l-4.8-5.1l-4.9,3.5l3.4,6.2c-1,1.1-1.8,2.2-2.5,3.5l-6.9-1.3l-1.9,5.7l6.4,3c-0.1,0.7-0.1,1.5-0.1,2.2c0,0.7,0,1.4,0.1,2.1l-6.4,3
l1.9,5.7l6.9-1.3c0.7,1.3,1.6,2.4,2.5,3.5l-3.4,6.2l4.9,3.5l4.8-5.1c1.3,0.6,2.7,1.1,4.1,1.4l0.9,7h6l0.9-7c0.2,0,0.4-0.1,0.6-0.1
l-4.8-6.6c-0.5,0-1.1-0.1-1.6-0.1c-6.7-1-11.2-7.3-10.2-14c1-6.7,7.3-11.2,14-10.2c3.5,0.5,6.4,2.5,8.2,5.2c0,0,0,0,0.1,0.1
c0.1,0.1,0.1,0.2,0.1,0.2c0.1,0.1,0.1,0.2,0.2,0.3c0,0,0,0,0,0c1.1,1.8,1.7,3.9,1.7,6.2c0,0.9-0.1,1.8-0.3,2.6c0,0,0,0.1,0,0.1
c0,0.1-0.1,0.3-0.1,0.4c0,0.1,0,0.1-0.1,0.2l4.9,6.7c0.1-0.1,0.2-0.3,0.3-0.4L73.6,58.5z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:#0A0A0C;}
.st1{fill:#E3E2E2;}
</style>
<g>
<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z"/>
<path class="st1" d="M50,0C22.4,0,0,22.4,0,50s22.4,50,50,50c27.6,0,50-22.4,50-50S77.6,0,50,0z M5.7,50C5.7,25.6,25.6,5.7,50,5.7
S94.3,25.6,94.3,50S74.4,94.3,50,94.3S5.7,74.4,5.7,50z"/>
<g>
<g>
<g>
<path class="st1" d="M49.3,77.8c4,0,7.3-3.3,7.3-7.3H42C42,74.5,45.3,77.8,49.3,77.8z"/>
</g>
</g>
<g>
<path class="st1" d="M68.2,59v-9.2c0-11.3-5.9-20.8-14-23.5c0-2.7-2.2-4.9-4.9-4.9c-2.7,0-4.9,2.2-4.9,4.9
c-8.1,2.8-14,12.3-14,23.5V59c-2.2,0-4,1.8-4,4s1.8,4,4,4v0h0.1h37.8h0.1v0c2.2,0,4-1.8,4-4S70.4,59.1,68.2,59z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:#0A0A0C;}
.st2{fill:url(#SVGID_2_);}
.st3{fill:url(#SVGID_3_);}
</style>
<g>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="60.5" y1="130.7753" x2="60.5" y2="18.6673">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st0" d="M120.8,50L87.2,16.4C78.1,6.3,64.9,0,50.2,0c-27.6,0-50,22.4-50,50s22.4,50,50,50c14.4,0,27.5-6.1,36.6-15.9
c0.1-0.1,0.1-0.1,0.2-0.2L120.8,50z"/>
</g>
<path class="st1" d="M94.4,49.5c0-24.4-19.9-44.3-44.3-44.3S5.8,25.1,5.8,49.5s19.9,44.3,44.3,44.3S94.4,73.9,94.4,49.5z"/>
<g>
<g>
<g>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="49.3811" y1="132.2195" x2="49.3811" y2="36.6876">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st2" d="M49.4,77.3c4,0,7.3-3.3,7.3-7.3H42.1C42.1,74.1,45.4,77.3,49.4,77.3z"/>
</g>
</g>
<g>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="49.3811" y1="132.2195" x2="49.3811" y2="36.6876">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st3" d="M68.3,58.6v-9.2c0-11.3-5.9-20.8-14-23.5c0-2.7-2.2-4.9-4.9-4.9c-2.7,0-4.9,2.2-4.9,4.9
c-8.1,2.8-14,12.3-14,23.5v9.2c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4v0h0.1h37.8h0.1v0c2.2,0,4-1.8,4-4
C72.3,60.4,70.5,58.6,68.3,58.6z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:#0A0A0C;}
.st1{fill:#E3E2E2;}
</style>
<g>
<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z M49.1,73.7
c-13.9,0-25.7-5.6-25.7-12.3c0-0.7,0.1-1.3,0.4-2c4.4,5,14,8.3,25.3,8.3s20.9-3.4,25.3-8.3c0.2,0.6,0.4,1.3,0.4,2
C74.8,68,63.1,73.7,49.1,73.7z M49.1,62.5c-13.9,0-25.7-5.6-25.7-12.3c0-0.7,0.1-1.3,0.4-2c4.4,5,14,8.3,25.3,8.3
s20.9-3.4,25.3-8.3c0.2,0.6,0.4,1.3,0.4,2C74.8,56.9,63.1,62.5,49.1,62.5z M49.1,51.3c-13.9,0-25.7-5.6-25.7-12.3
c0-6.7,11.8-12.3,25.7-12.3S74.8,32.4,74.8,39C74.8,45.7,63.1,51.3,49.1,51.3z"/>
<path class="st1" d="M49.1,67.7c-11.4,0-20.9-3.4-25.3-8.3c-0.2,0.6-0.4,1.3-0.4,2c0,6.7,11.8,12.3,25.7,12.3S74.8,68,74.8,61.4
c0-0.7-0.1-1.3-0.4-2C70.1,64.4,60.5,67.7,49.1,67.7z"/>
<path class="st1" d="M49.1,56.5c-11.4,0-20.9-3.4-25.3-8.3c-0.2,0.6-0.4,1.3-0.4,2c0,6.7,11.8,12.3,25.7,12.3s25.7-5.6,25.7-12.3
c0-0.7-0.1-1.3-0.4-2C70.1,53.2,60.5,56.5,49.1,56.5z"/>
<path class="st1" d="M49.1,26.7c-13.9,0-25.7,5.6-25.7,12.3c0,6.7,11.8,12.3,25.7,12.3S74.8,45.7,74.8,39
C74.8,32.4,63.1,26.7,49.1,26.7z"/>
<path class="st1" d="M50,0C22.4,0,0,22.4,0,50s22.4,50,50,50c27.6,0,50-22.4,50-50S77.6,0,50,0z M5.7,50C5.7,25.6,25.6,5.7,50,5.7
S94.3,25.6,94.3,50S74.4,94.3,50,94.3S5.7,74.4,5.7,50z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:#0A0A0C;}
.st2{fill:url(#SVGID_2_);}
.st3{fill:url(#SVGID_3_);}
.st4{fill:url(#SVGID_4_);}
</style>
<g>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="60.5" y1="130.7753" x2="60.5" y2="18.6673">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st0" d="M120.8,50L87.2,16.4C78.1,6.3,64.9,0,50.2,0c-27.6,0-50,22.4-50,50s22.4,50,50,50c14.4,0,27.5-6.1,36.6-15.9
c0.1-0.1,0.1-0.1,0.2-0.2L120.8,50z"/>
</g>
<path class="st1" d="M94.4,50c0-24.4-19.9-44.3-44.3-44.3S5.9,25.6,5.9,50s19.9,44.3,44.3,44.3S94.4,74.4,94.4,50z M49.3,73.7
c-13.9,0-25.7-5.6-25.7-12.3c0-0.7,0.1-1.3,0.4-2c4.4,5,14,8.3,25.3,8.3s20.9-3.4,25.3-8.3c0.2,0.6,0.4,1.3,0.4,2
C75,68,63.2,73.7,49.3,73.7z M49.3,62.5c-13.9,0-25.7-5.6-25.7-12.3c0-0.7,0.1-1.3,0.4-2c4.4,5,14,8.3,25.3,8.3s20.9-3.4,25.3-8.3
c0.2,0.6,0.4,1.3,0.4,2C75,56.9,63.2,62.5,49.3,62.5z M49.3,51.3c-13.9,0-25.7-5.6-25.7-12.3c0-6.7,11.8-12.3,25.7-12.3
S75,32.4,75,39C75,45.7,63.2,51.3,49.3,51.3z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="49.2981" y1="130.777" x2="49.2981" y2="18.669">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st2" d="M49.3,67.7c-11.4,0-20.9-3.4-25.3-8.3c-0.2,0.6-0.4,1.3-0.4,2c0,6.7,11.8,12.3,25.7,12.3S75,68,75,61.4
c0-0.7-0.1-1.3-0.4-2C70.2,64.4,60.7,67.7,49.3,67.7z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="49.2981" y1="130.7753" x2="49.2981" y2="18.6673">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st3" d="M49.3,56.5c-11.4,0-20.9-3.4-25.3-8.3c-0.2,0.6-0.4,1.3-0.4,2c0,6.7,11.8,12.3,25.7,12.3S75,56.9,75,50.2
c0-0.7-0.1-1.3-0.4-2C70.2,53.2,60.7,56.5,49.3,56.5z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="49.2981" y1="130.7753" x2="49.2981" y2="18.6673">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st4" d="M49.3,26.7c-13.9,0-25.7,5.6-25.7,12.3c0,6.7,11.8,12.3,25.7,12.3S75,45.7,75,39C75,32.4,63.2,26.7,49.3,26.7z
"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:#0A0A0C;}
.st1{fill:#E3E2E2;}
</style>
<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z"/>
<path class="st1" d="M50,0C22.4,0,0,22.4,0,50s22.4,50,50,50c27.6,0,50-22.4,50-50S77.6,0,50,0z M5.7,50C5.7,25.6,25.6,5.7,50,5.7
S94.3,25.6,94.3,50S74.4,94.3,50,94.3S5.7,74.4,5.7,50z"/>
<g>
<path class="st1" d="M77.6,66.3H25.2V30.9c0-1.5-1.2-2.8-2.8-2.8s-2.8,1.2-2.8,2.8v38.1c0,1.5,1.2,2.8,2.8,2.8h55.2
c1.5,0,2.8-1.2,2.8-2.8S79.1,66.3,77.6,66.3z"/>
<g>
<polygon class="st1" points="77.5,63.6 77.8,49.5 62.7,28.8 46.8,50 35.6,39.2 27.9,43.6 27.9,63.6 "/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:#0A0A0C;}
.st2{fill:url(#SVGID_2_);}
.st3{fill:url(#SVGID_3_);}
</style>
<g>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="60.5" y1="130.7753" x2="60.5" y2="18.6673">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st0" d="M120.8,50L87.2,16.4C78.1,6.3,64.9,0,50.2,0c-27.6,0-50,22.4-50,50s22.4,50,50,50c14.4,0,27.5-6.1,36.6-15.9
c0.1-0.1,0.1-0.1,0.2-0.2L120.8,50z"/>
</g>
<path class="st1" d="M94.1,50c0-24.4-19.9-44.3-44.3-44.3S5.6,25.6,5.6,50s19.9,44.3,44.3,44.3S94.1,74.4,94.1,50z"/>
<g>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="50.6946" y1="113.8319" x2="50.6946" y2="21.7994">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st2" d="M78.3,65.9H25.9V30.5c0-1.5-1.2-2.8-2.8-2.8c-1.5,0-2.8,1.2-2.8,2.8v38.1c0,1.5,1.2,2.8,2.8,2.8h55.2
c1.5,0,2.8-1.2,2.8-2.8S79.8,65.9,78.3,65.9z"/>
<g>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="53.553" y1="113.8319" x2="53.553" y2="21.7994">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<polygon class="st3" points="78.2,63.1 78.5,49 63.4,28.3 47.5,49.5 36.3,38.7 28.6,43.1 28.6,63.1 "/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#52545c;}
</style>
<g>
<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z"/>
<path class="st1" d="M50,0C22.4,0,0,22.4,0,50s22.4,50,50,50c27.6,0,50-22.4,50-50S77.6,0,50,0z M5.7,50C5.7,25.6,25.6,5.7,50,5.7
S94.3,25.6,94.3,50S74.4,94.3,50,94.3S5.7,74.4,5.7,50z"/>
<g>
<path class="st1" d="M58.2,51.9c1.2-2.9,1-6.4-1-9.1c-1.5-2.1-3.7-3.3-6.1-3.7c-1.2-0.2-2.5-0.1-3.7,0.2l4,5.5
c1.1,1.5,0.8,3.7-0.8,4.8c-0.8,0.6-1.7,0.8-2.6,0.6c-0.9-0.1-1.7-0.6-2.2-1.4l-4-5.5c-2,3.1-1.9,7.2,0.4,10.4
c1.5,2.1,3.7,3.3,6.1,3.7c0.8,0.1,1.5,0.1,2.3,0.1l12.8,17.6c0.8,1.1,1.9,1.7,3.1,1.9s2.5-0.1,3.5-0.9c2.1-1.5,2.6-4.5,1-6.6
L58.2,51.9z M67.9,71c1,0.2,1.7,1.1,1.5,2.1c-0.2,1-1.1,1.7-2.1,1.5s-1.7-1.1-1.5-2.1C65.9,71.6,66.9,70.9,67.9,71z"/>
<path class="st1" d="M61.5,50.9c0,0.1-0.1,0.3-0.1,0.4C61.4,51.2,61.4,51.1,61.5,50.9z"/>
<path class="st1" d="M59.9,41.6c0.1,0.1,0.1,0.2,0.2,0.3C60,41.9,60,41.7,59.9,41.6z"/>
<path class="st1" d="M61.6,50.1c0,0.2-0.1,0.5-0.1,0.7c0.2-0.8,0.3-1.7,0.3-2.6c0-2.3-0.6-4.4-1.7-6.2
C61.5,44.3,62.1,47.2,61.6,50.1z"/>
<path class="st1" d="M59.7,41.3C59.7,41.4,59.7,41.4,59.7,41.3C59.7,41.4,59.7,41.4,59.7,41.3z"/>
<path class="st1" d="M73.4,59.1l1.9-5.7l-6.4-3c0.1-0.7,0.1-1.5,0.1-2.2c0-0.7,0-1.4-0.1-2.1l6.4-3l-1.9-5.7l-6.9,1.3
c-0.7-1.3-1.6-2.4-2.5-3.5l3.4-6.2l-4.9-3.5l-4.8,5.1c-1.3-0.6-2.7-1.1-4.1-1.4l-0.9-7h-6l-0.9,7c-1.4,0.3-2.8,0.7-4.1,1.3
l-4.8-5.1L32,28.9l3.4,6.2c-1,1.1-1.8,2.2-2.5,3.5L26,37.2L24.1,43l6.4,3c-0.1,0.7-0.1,1.5-0.1,2.2c0,0.7,0,1.4,0.1,2.1l-6.4,3
l1.9,5.7l6.9-1.3c0.7,1.3,1.6,2.4,2.5,3.5L32,67.3l4.9,3.5l4.8-5.1c1.3,0.6,2.7,1.1,4.1,1.4l0.9,7h6l0.9-7c0.2,0,0.4-0.1,0.6-0.1
l-4.8-6.6c-0.5,0-1.1-0.1-1.6-0.1c-6.7-1-11.2-7.3-10.2-14c1-6.7,7.3-11.2,14-10.2c3.5,0.5,6.4,2.5,8.2,5.2c0,0,0,0,0.1,0.1
c0.1,0.1,0.1,0.2,0.1,0.2c0.1,0.1,0.1,0.2,0.2,0.3c0,0,0,0,0,0c1.1,1.8,1.7,3.9,1.7,6.2c0,0.9-0.1,1.8-0.3,2.6c0,0,0,0.1,0,0.1
c0,0.1-0.1,0.3-0.1,0.4c0,0.1,0,0.1-0.1,0.2l4.9,6.7c0.1-0.1,0.2-0.3,0.3-0.4L73.4,59.1z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:#FFFFFF;}
.st2{fill:url(#SVGID_2_);}
.st3{fill:url(#SVGID_3_);}
.st4{fill:url(#SVGID_4_);}
.st5{fill:url(#SVGID_5_);}
.st6{fill:url(#SVGID_6_);}
.st7{fill:url(#SVGID_7_);}
</style>
<g>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="60.5" y1="130.7753" x2="60.5" y2="18.6673">
<stop offset="0" style="stop-color:#FBB017"/>
<stop offset="1" style="stop-color:#EF4E28"/>
</linearGradient>
<path class="st0" d="M120.8,50L87.2,16.4C78.1,6.3,64.9,0,50.2,0c-27.6,0-50,22.4-50,50s22.4,50,50,50c14.4,0,27.5-6.1,36.6-15.9
c0.1-0.1,0.1-0.1,0.2-0.2L120.8,50z"/>
</g>
<path class="st1" d="M94.3,50.1C94.3,25.7,74.4,5.8,50,5.8S5.8,25.7,5.8,50.1S25.6,94.3,50,94.3S94.3,74.5,94.3,50.1z"/>
<g>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="56.209" y1="135.6298" x2="56.209" y2="31.6718">
<stop offset="0" style="stop-color:#FBB017"/>
<stop offset="1" style="stop-color:#EF4E28"/>
</linearGradient>
<path class="st2" d="M58.2,52c1.2-2.9,1-6.4-1-9.1c-1.5-2.1-3.7-3.3-6.1-3.7c-1.2-0.2-2.5-0.1-3.7,0.2l4,5.5
c1.1,1.5,0.8,3.7-0.8,4.8c-0.8,0.6-1.7,0.8-2.6,0.6c-0.9-0.1-1.7-0.6-2.2-1.4l-4-5.5c-2,3.1-1.9,7.2,0.4,10.4
c1.5,2.1,3.7,3.3,6.1,3.7c0.8,0.1,1.5,0.1,2.3,0.1l12.8,17.6c0.8,1.1,1.9,1.7,3.1,1.9c1.2,0.2,2.5-0.1,3.5-0.9
c2.1-1.5,2.6-4.5,1-6.6L58.2,52z M67.9,71.1c1,0.2,1.7,1.1,1.5,2.1c-0.2,1-1.1,1.7-2.1,1.5c-1-0.2-1.7-1.1-1.5-2.1
C65.9,71.6,66.9,70.9,67.9,71.1z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="61.4347" y1="135.6298" x2="61.4347" y2="31.6718">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st3" d="M61.5,51c0,0.1-0.1,0.3-0.1,0.4C61.4,51.2,61.5,51.1,61.5,51z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="59.9971" y1="135.6298" x2="59.9971" y2="31.6718">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st4" d="M59.9,41.7c0.1,0.1,0.1,0.2,0.2,0.3C60,41.9,60,41.8,59.9,41.7z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="60.952" y1="135.6298" x2="60.952" y2="31.6718">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st5" d="M61.7,50.1c0,0.2-0.1,0.5-0.1,0.7c0.2-0.8,0.3-1.7,0.3-2.6c0-2.3-0.6-4.4-1.7-6.2
C61.5,44.4,62.1,47.2,61.7,50.1z"/>
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="59.7217" y1="135.6298" x2="59.7217" y2="31.6718">
<stop offset="0" style="stop-color:#FFF100"/>
<stop offset="1" style="stop-color:#F05A28"/>
</linearGradient>
<path class="st6" d="M59.7,41.4C59.7,41.4,59.7,41.4,59.7,41.4C59.7,41.4,59.7,41.4,59.7,41.4z"/>
<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="49.6936" y1="135.6298" x2="49.6936" y2="31.6718">
<stop offset="0" style="stop-color:#FBB017"/>
<stop offset="1" style="stop-color:#EF4E28"/>
</linearGradient>
<path class="st7" d="M73.4,59.2l1.9-5.7l-6.4-3c0.1-0.7,0.1-1.5,0.1-2.2c0-0.7,0-1.4-0.1-2.1l6.4-3l-1.9-5.7l-6.9,1.3
c-0.7-1.3-1.6-2.4-2.5-3.5l3.4-6.2l-4.9-3.5l-4.8,5.1c-1.3-0.6-2.7-1.1-4.1-1.4l-0.9-7h-6l-0.9,7c-1.4,0.3-2.8,0.7-4.1,1.3
l-4.8-5.1L32,29l3.4,6.2c-1,1.1-1.8,2.2-2.5,3.5L26,37.3L24.1,43l6.4,3c-0.1,0.7-0.1,1.5-0.1,2.2c0,0.7,0,1.4,0.1,2.1l-6.4,3
l1.9,5.7l6.9-1.3c0.7,1.3,1.6,2.4,2.5,3.5L32,67.4l4.9,3.5l4.8-5.1c1.3,0.6,2.7,1.1,4.1,1.4l0.9,7h6l0.9-7c0.2,0,0.4-0.1,0.6-0.1
l-4.8-6.6c-0.5,0-1.1-0.1-1.6-0.1c-6.7-1-11.2-7.3-10.2-14c1-6.7,7.3-11.2,14-10.2c3.5,0.5,6.4,2.5,8.2,5.2c0,0,0,0,0.1,0.1
c0.1,0.1,0.1,0.2,0.1,0.2c0.1,0.1,0.1,0.2,0.2,0.3c0,0,0,0,0,0c1.1,1.8,1.7,3.9,1.7,6.2c0,0.9-0.1,1.8-0.3,2.6c0,0,0,0.1,0,0.1
c0,0.1-0.1,0.3-0.1,0.4c0,0.1,0,0.1-0.1,0.2l4.9,6.7c0.1-0.1,0.2-0.3,0.3-0.4L73.4,59.2z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#52545c;}
</style>
<g>
<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z"/>
<path class="st1" d="M50,0C22.4,0,0,22.4,0,50s22.4,50,50,50c27.6,0,50-22.4,50-50S77.6,0,50,0z M5.7,50C5.7,25.6,25.6,5.7,50,5.7
S94.3,25.6,94.3,50S74.4,94.3,50,94.3S5.7,74.4,5.7,50z"/>
<g>
<g>
<g>
<path class="st1" d="M49.3,77.8c4,0,7.3-3.3,7.3-7.3H42C42,74.5,45.3,77.8,49.3,77.8z"/>
</g>
</g>
<g>
<path class="st1" d="M68.2,59v-9.2c0-11.3-5.9-20.8-14-23.5c0-2.7-2.2-4.9-4.9-4.9c-2.7,0-4.9,2.2-4.9,4.9
c-8.1,2.8-14,12.3-14,23.5V59c-2.2,0-4,1.8-4,4s1.8,4,4,4v0h0.1h37.8h0.1v0c2.2,0,4-1.8,4-4S70.4,59.1,68.2,59z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:#FFFFFF;}
.st2{fill:url(#SVGID_2_);}
.st3{fill:url(#SVGID_3_);}
</style>
<g>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="60.5" y1="130.7753" x2="60.5" y2="18.6673">
<stop offset="0" style="stop-color:#FBB017"/>
<stop offset="1" style="stop-color:#EF4E28"/>
</linearGradient>
<path class="st0" d="M120.8,50L87.2,16.4C78.1,6.3,64.9,0,50.2,0c-27.6,0-50,22.4-50,50s22.4,50,50,50c14.4,0,27.5-6.1,36.6-15.9
c0.1-0.1,0.1-0.1,0.2-0.2L120.8,50z"/>
</g>
<path class="st1" d="M94.4,49.5c0-24.4-19.9-44.3-44.3-44.3S5.8,25.1,5.8,49.5s19.9,44.3,44.3,44.3S94.4,73.9,94.4,49.5z"/>
<g>
<g>
<g>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="49.3811" y1="132.2195" x2="49.3811" y2="36.6876">
<stop offset="0" style="stop-color:#FBB017"/>
<stop offset="1" style="stop-color:#EF4E28"/>
</linearGradient>
<path class="st2" d="M49.4,77.3c4,0,7.3-3.3,7.3-7.3H42.1C42.1,74.1,45.4,77.3,49.4,77.3z"/>
</g>
</g>
<g>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="49.3811" y1="132.2195" x2="49.3811" y2="36.6876">
<stop offset="0" style="stop-color:#FBB017"/>
<stop offset="1" style="stop-color:#EF4E28"/>
</linearGradient>
<path class="st3" d="M68.3,58.6v-9.2c0-11.3-5.9-20.8-14-23.5c0-2.7-2.2-4.9-4.9-4.9c-2.7,0-4.9,2.2-4.9,4.9
c-8.1,2.8-14,12.3-14,23.5v9.2c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4v0h0.1h37.8h0.1v0c2.2,0,4-1.8,4-4
C72.3,60.4,70.5,58.6,68.3,58.6z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#52545c;}
</style>
<g>
<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z M49.1,73.7
c-13.9,0-25.7-5.6-25.7-12.3c0-0.7,0.1-1.3,0.4-2c4.4,5,14,8.3,25.3,8.3s20.9-3.4,25.3-8.3c0.2,0.6,0.4,1.3,0.4,2
C74.8,68,63.1,73.7,49.1,73.7z M49.1,62.5c-13.9,0-25.7-5.6-25.7-12.3c0-0.7,0.1-1.3,0.4-2c4.4,5,14,8.3,25.3,8.3
s20.9-3.4,25.3-8.3c0.2,0.6,0.4,1.3,0.4,2C74.8,56.9,63.1,62.5,49.1,62.5z M49.1,51.3c-13.9,0-25.7-5.6-25.7-12.3
c0-6.7,11.8-12.3,25.7-12.3S74.8,32.4,74.8,39C74.8,45.7,63.1,51.3,49.1,51.3z"/>
<path class="st1" d="M49.1,67.7c-11.4,0-20.9-3.4-25.3-8.3c-0.2,0.6-0.4,1.3-0.4,2c0,6.7,11.8,12.3,25.7,12.3S74.8,68,74.8,61.4
c0-0.7-0.1-1.3-0.4-2C70.1,64.4,60.5,67.7,49.1,67.7z"/>
<path class="st1" d="M49.1,56.5c-11.4,0-20.9-3.4-25.3-8.3c-0.2,0.6-0.4,1.3-0.4,2c0,6.7,11.8,12.3,25.7,12.3s25.7-5.6,25.7-12.3
c0-0.7-0.1-1.3-0.4-2C70.1,53.2,60.5,56.5,49.1,56.5z"/>
<path class="st1" d="M49.1,26.7c-13.9,0-25.7,5.6-25.7,12.3c0,6.7,11.8,12.3,25.7,12.3S74.8,45.7,74.8,39
C74.8,32.4,63.1,26.7,49.1,26.7z"/>
<path class="st1" d="M50,0C22.4,0,0,22.4,0,50s22.4,50,50,50c27.6,0,50-22.4,50-50S77.6,0,50,0z M5.7,50C5.7,25.6,25.6,5.7,50,5.7
S94.3,25.6,94.3,50S74.4,94.3,50,94.3S5.7,74.4,5.7,50z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:#FFFFFF;}
.st2{fill:url(#SVGID_2_);}
.st3{fill:url(#SVGID_3_);}
.st4{fill:url(#SVGID_4_);}
</style>
<g>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="60.5" y1="130.7753" x2="60.5" y2="18.6673">
<stop offset="0" style="stop-color:#FBB017"/>
<stop offset="1" style="stop-color:#EF4E28"/>
</linearGradient>
<path class="st0" d="M120.8,50L87.2,16.4C78.1,6.3,64.9,0,50.2,0c-27.6,0-50,22.4-50,50s22.4,50,50,50c14.4,0,27.5-6.1,36.6-15.9
c0.1-0.1,0.1-0.1,0.2-0.2L120.8,50z"/>
</g>
<path class="st1" d="M94.4,50c0-24.4-19.9-44.3-44.3-44.3S5.9,25.6,5.9,50s19.9,44.3,44.3,44.3S94.4,74.4,94.4,50z M49.3,73.7
c-13.9,0-25.7-5.6-25.7-12.3c0-0.7,0.1-1.3,0.4-2c4.4,5,14,8.3,25.3,8.3s20.9-3.4,25.3-8.3c0.2,0.6,0.4,1.3,0.4,2
C75,68,63.2,73.7,49.3,73.7z M49.3,62.5c-13.9,0-25.7-5.6-25.7-12.3c0-0.7,0.1-1.3,0.4-2c4.4,5,14,8.3,25.3,8.3s20.9-3.4,25.3-8.3
c0.2,0.6,0.4,1.3,0.4,2C75,56.9,63.2,62.5,49.3,62.5z M49.3,51.3c-13.9,0-25.7-5.6-25.7-12.3c0-6.7,11.8-12.3,25.7-12.3
S75,32.4,75,39C75,45.7,63.2,51.3,49.3,51.3z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="49.2981" y1="130.777" x2="49.2981" y2="18.669">
<stop offset="0" style="stop-color:#FBB017"/>
<stop offset="1" style="stop-color:#EF4E28"/>
</linearGradient>
<path class="st2" d="M49.3,67.7c-11.4,0-20.9-3.4-25.3-8.3c-0.2,0.6-0.4,1.3-0.4,2c0,6.7,11.8,12.3,25.7,12.3S75,68,75,61.4
c0-0.7-0.1-1.3-0.4-2C70.2,64.4,60.7,67.7,49.3,67.7z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="49.2981" y1="130.7753" x2="49.2981" y2="18.6673">
<stop offset="0" style="stop-color:#FBB017"/>
<stop offset="1" style="stop-color:#EF4E28"/>
</linearGradient>
<path class="st3" d="M49.3,56.5c-11.4,0-20.9-3.4-25.3-8.3c-0.2,0.6-0.4,1.3-0.4,2c0,6.7,11.8,12.3,25.7,12.3S75,56.9,75,50.2
c0-0.7-0.1-1.3-0.4-2C70.2,53.2,60.7,56.5,49.3,56.5z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="49.2981" y1="130.7753" x2="49.2981" y2="18.6673">
<stop offset="0" style="stop-color:#FBB017"/>
<stop offset="1" style="stop-color:#EF4E28"/>
</linearGradient>
<path class="st4" d="M49.3,26.7c-13.9,0-25.7,5.6-25.7,12.3c0,6.7,11.8,12.3,25.7,12.3S75,45.7,75,39C75,32.4,63.2,26.7,49.3,26.7z
"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#52545c;}
</style>
<g>
<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z"/>
<path class="st1" d="M50,0C22.4,0,0,22.4,0,50s22.4,50,50,50c27.6,0,50-22.4,50-50S77.6,0,50,0z M5.7,50C5.7,25.6,25.6,5.7,50,5.7
S94.3,25.6,94.3,50S74.4,94.3,50,94.3S5.7,74.4,5.7,50z"/>
<g>
<path class="st1" d="M78,65.8H25.6V30.4c0-1.5-1.2-2.8-2.8-2.8c-1.5,0-2.8,1.2-2.8,2.8v38.1c0,1.5,1.2,2.8,2.8,2.8H78
c1.5,0,2.8-1.2,2.8-2.8C80.7,67.1,79.5,65.8,78,65.8z"/>
<g>
<polygon class="st1" points="77.9,63.1 78.2,49 63.1,28.3 47.2,49.5 36,38.7 28.3,43.1 28.3,63.1 "/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:#FFFFFF;}
.st2{fill:url(#SVGID_2_);}
.st3{fill:url(#SVGID_3_);}
</style>
<g>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="60.5" y1="130.7753" x2="60.5" y2="18.6673">
<stop offset="0" style="stop-color:#FBB017"/>
<stop offset="1" style="stop-color:#EF4E28"/>
</linearGradient>
<path class="st0" d="M120.8,50L87.2,16.4C78.1,6.3,64.9,0,50.2,0c-27.6,0-50,22.4-50,50s22.4,50,50,50c14.4,0,27.5-6.1,36.6-15.9
c0.1-0.1,0.1-0.1,0.2-0.2L120.8,50z"/>
</g>
<path class="st1" d="M94.1,50c0-24.4-19.9-44.3-44.3-44.3S5.6,25.6,5.6,50s19.9,44.3,44.3,44.3S94.1,74.4,94.1,50z"/>
<g>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="50.2576" y1="115.6711" x2="50.2576" y2="19.8747">
<stop offset="0" style="stop-color:#FBB017"/>
<stop offset="1" style="stop-color:#EF4E28"/>
</linearGradient>
<path class="st2" d="M77.8,65.9H25.4V30.5c0-1.5-1.2-2.8-2.8-2.8c-1.5,0-2.8,1.2-2.8,2.8v38.1c0,1.5,1.2,2.8,2.8,2.8h55.2
c1.5,0,2.8-1.2,2.8-2.8S79.4,65.9,77.8,65.9z"/>
<g>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="53.116" y1="115.6711" x2="53.116" y2="19.8747">
<stop offset="0" style="stop-color:#FBB017"/>
<stop offset="1" style="stop-color:#EF4E28"/>
</linearGradient>
<polygon class="st3" points="77.7,63.1 78,49 62.9,28.3 47.1,49.5 35.8,38.7 28.2,43.1 28.2,63.1 "/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

+9
View File
@@ -0,0 +1,9 @@
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="22" cy="22" r="20" fill="#09090B" stroke="#8E8E8E" stroke-width="4"/>
<line x1="12.5925" y1="28.1482" x2="31.4073" y2="28.1482" stroke="#8E8E8E" stroke-width="4" stroke-linecap="round"/>
<ellipse cx="22.0001" cy="20.3704" rx="8.96296" ry="9.77778" fill="#8E8E8E"/>
<circle cx="22.0001" cy="30.1482" r="2.44444" fill="#8E8E8E"/>
<line x1="18.7407" y1="30.463" x2="25.2592" y2="30.463" stroke="#09090B"/>
<rect x="13.0371" y="20.3704" width="17.9259" height="6.51852" fill="#8E8E8E"/>
<ellipse cx="22.0001" cy="11.0001" rx="0.814815" ry="1.22222" fill="#8E8E8E"/>
</svg>

After

Width:  |  Height:  |  Size: 677 B

+9
View File
@@ -0,0 +1,9 @@
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="22" cy="22" r="20" fill="#09090B" stroke="#8E8E8E" stroke-width="4"/>
<line x1="12.5925" y1="28.1482" x2="31.4073" y2="28.1482" stroke="#8E8E8E" stroke-width="4" stroke-linecap="round"/>
<ellipse cx="22.0001" cy="20.3704" rx="8.96296" ry="9.77778" fill="#8E8E8E"/>
<circle cx="22.0001" cy="30.1482" r="2.44444" fill="#8E8E8E"/>
<line x1="18.7407" y1="30.463" x2="25.2592" y2="30.463" stroke="#09090B"/>
<rect x="13.0371" y="20.3704" width="17.9259" height="6.51852" fill="#8E8E8E"/>
<ellipse cx="22.0001" cy="11.0001" rx="0.814815" ry="1.22222" fill="#8E8E8E"/>
</svg>

After

Width:  |  Height:  |  Size: 677 B

@@ -0,0 +1,31 @@
<svg width="65" height="54" viewBox="0 0 65 54" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="27" cy="27" r="25" fill="#09090B" stroke="url(#paint0_linear)" stroke-width="4"/>
<ellipse cx="27" cy="33" rx="14" ry="6" fill="url(#paint1_linear)"/>
<ellipse cx="27" cy="30" rx="14" ry="6" fill="#09090B"/>
<ellipse cx="27" cy="27" rx="14" ry="6" fill="url(#paint2_linear)"/>
<ellipse cx="27" cy="24" rx="14" ry="6" fill="#09090B"/>
<ellipse cx="27" cy="21" rx="14" ry="6" fill="url(#paint3_linear)"/>
<path d="M52 37L65 26.9999L52 17L52 37Z" fill="url(#paint4_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="27" y1="0" x2="27" y2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#D44A3A"/>
<stop offset="1" stop-color="#EB7B18"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="27" y1="2.01165e-07" x2="27" y2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#D44A3A"/>
<stop offset="1" stop-color="#FEBC11"/>
</linearGradient>
<linearGradient id="paint2_linear" x1="27" y1="1.40816e-06" x2="27" y2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#D44A3A"/>
<stop offset="1" stop-color="#FEBC11"/>
</linearGradient>
<linearGradient id="paint3_linear" x1="27" y1="-6.03497e-07" x2="27" y2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#D44A3A"/>
<stop offset="1" stop-color="#FEBC11"/>
</linearGradient>
<linearGradient id="paint4_linear" x1="57" y1="54" x2="57" y2="7.55191e-06" gradientUnits="userSpaceOnUse">
<stop stop-color="#EB7B18"/>
<stop offset="1" stop-color="#D44A3A"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+8
View File
@@ -0,0 +1,8 @@
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="22" cy="22" r="20" fill="#09090B" stroke="#8E8E8E" stroke-width="4"/>
<ellipse cx="21.9999" cy="26.8889" rx="11.4074" ry="4.88889" fill="#8E8E8E"/>
<ellipse cx="21.9999" cy="24.4444" rx="11.4074" ry="4.88889" fill="#09090B"/>
<ellipse cx="21.9999" cy="22" rx="11.4074" ry="4.88889" fill="#8E8E8E"/>
<ellipse cx="21.9999" cy="19.5555" rx="11.4074" ry="4.88889" fill="#09090B"/>
<ellipse cx="21.9999" cy="17.1111" rx="11.4074" ry="4.88889" fill="#8E8E8E"/>
</svg>

After

Width:  |  Height:  |  Size: 570 B

@@ -0,0 +1,31 @@
<svg width="65" height="54" viewBox="0 0 65 54" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="27" cy="27" r="25" fill="#09090B" stroke="url(#paint0_linear)" stroke-width="4"/>
<ellipse cx="27" cy="33" rx="14" ry="6" fill="url(#paint1_linear)"/>
<ellipse cx="27" cy="30" rx="14" ry="6" fill="#09090B"/>
<ellipse cx="27" cy="27" rx="14" ry="6" fill="url(#paint2_linear)"/>
<ellipse cx="27" cy="24" rx="14" ry="6" fill="#09090B"/>
<ellipse cx="27" cy="21" rx="14" ry="6" fill="url(#paint3_linear)"/>
<path d="M52 37L65 26.9999L52 17L52 37Z" fill="url(#paint4_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="27" y1="0" x2="27" y2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#D44A3A"/>
<stop offset="1" stop-color="#EB7B18"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="27" y1="2.01165e-07" x2="27" y2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#D44A3A"/>
<stop offset="1" stop-color="#FEBC11"/>
</linearGradient>
<linearGradient id="paint2_linear" x1="27" y1="1.40816e-06" x2="27" y2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#D44A3A"/>
<stop offset="1" stop-color="#FEBC11"/>
</linearGradient>
<linearGradient id="paint3_linear" x1="27" y1="-6.03497e-07" x2="27" y2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#D44A3A"/>
<stop offset="1" stop-color="#FEBC11"/>
</linearGradient>
<linearGradient id="paint4_linear" x1="57" y1="54" x2="57" y2="7.55191e-06" gradientUnits="userSpaceOnUse">
<stop stop-color="#EB7B18"/>
<stop offset="1" stop-color="#D44A3A"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+8
View File
@@ -0,0 +1,8 @@
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="22" cy="22" r="20" fill="#09090B" stroke="#8E8E8E" stroke-width="4"/>
<ellipse cx="21.9999" cy="26.8889" rx="11.4074" ry="4.88889" fill="#8E8E8E"/>
<ellipse cx="21.9999" cy="24.4444" rx="11.4074" ry="4.88889" fill="#09090B"/>
<ellipse cx="21.9999" cy="22" rx="11.4074" ry="4.88889" fill="#8E8E8E"/>
<ellipse cx="21.9999" cy="19.5555" rx="11.4074" ry="4.88889" fill="#09090B"/>
<ellipse cx="21.9999" cy="17.1111" rx="11.4074" ry="4.88889" fill="#8E8E8E"/>
</svg>

After

Width:  |  Height:  |  Size: 570 B

@@ -0,0 +1,34 @@
<svg width="65" height="54" viewBox="0 0 65 54" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="27" cy="27" r="25" fill="#09090B" stroke="url(#paint0_linear)" stroke-width="4"/>
<path d="M39 21L35 17H39V21Z" fill="url(#paint1_linear)"/>
<line x1="14" y1="15" x2="14" y2="39" stroke="url(#paint2_linear)" stroke-width="2"/>
<line x1="41" y1="38" x2="14" y2="38" stroke="url(#paint3_linear)" stroke-width="2"/>
<path d="M37 19L28 27L25 24L16 33" stroke="url(#paint4_linear)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M52 37L65 26.9999L52 17L52 37Z" fill="url(#paint5_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="27" y1="0" x2="27" y2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#D44A3A"/>
<stop offset="1" stop-color="#EB7B18"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="37" y1="-9.62518e-07" x2="37" y2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#D44A3A"/>
<stop offset="1" stop-color="#EB7B18"/>
</linearGradient>
<linearGradient id="paint2_linear" x1="13" y1="53.5" x2="13" y2="1.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#EB7B18"/>
<stop offset="1" stop-color="#D44A3A"/>
</linearGradient>
<linearGradient id="paint3_linear" x1="27" y1="7.21113e-07" x2="27.5" y2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#D44A3A"/>
<stop offset="1" stop-color="#EB7B18"/>
</linearGradient>
<linearGradient id="paint4_linear" x1="27" y1="-4.4462e-08" x2="27" y2="54" gradientUnits="userSpaceOnUse">
<stop stop-color="#D44A3A"/>
<stop offset="1" stop-color="#EB7B18"/>
</linearGradient>
<linearGradient id="paint5_linear" x1="57" y1="54" x2="57" y2="7.55191e-06" gradientUnits="userSpaceOnUse">
<stop stop-color="#EB7B18"/>
<stop offset="1" stop-color="#D44A3A"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+7
View File
@@ -0,0 +1,7 @@
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="22" cy="22" r="20" fill="#09090B" stroke="#8E8E8E" stroke-width="4"/>
<path d="M31.7778 17.1111L28.5186 13.8518H31.7778V17.1111Z" fill="#8E8E8E"/>
<line x1="11" y1="12" x2="11" y2="32" stroke="#8E8E8E" stroke-width="2"/>
<line x1="33" y1="31" x2="11" y2="31" stroke="#8E8E8E" stroke-width="2"/>
<path d="M30.1482 15.4814L22.8149 22L20.3704 19.5555L13.0371 26.8889" stroke="#8E8E8E" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 563 B

+20 -4
View File
@@ -79,6 +79,7 @@ $brand-gradient: linear-gradient(
);
$page-gradient: linear-gradient(180deg, #222426 10px, rgb(22, 23, 25) 100px);
$edit-gradient: linear-gradient(180deg, rgb(22, 23, 25) 50%, #090909);
// Links
// -------------------------
@@ -268,9 +269,7 @@ $menu-dropdown-shadow: 5px 5px 20px -5px $black;
$tab-border-color: $dark-4;
// Toolbar
$toolbar-bg: $page-header-bg;
$toolbar-shadow: 0 0 20px black;
$toolbar-tab-bg: $gray-blue;
$toolbar-bg: $black;
// Pagination
// -------------------------
@@ -367,6 +366,23 @@ $switch-slider-shadow: 0 0 3px black;
//Checkbox
// -------------------------
$checkbox-bg: $dark-1;
$checkbox-border: 1px solid $gray-2;
$checkbox-border: 1px solid $gray-1;
$checkbox-checked-bg: linear-gradient(0deg, $orange, $red);
$checkbox-color: $dark-1;
//Panel Edit
// -------------------------
$panel-editor-shadow: 0 0 20px black;
$panel-editor-side-menu-shadow: drop-shadow(0 0 10px $black);
$panel-editor-toolbar-view-bg: $black;
$panel-editor-viz-item-shadow: 0 0 8px $dark-5;
$panel-editor-viz-item-border: 1px solid $dark-5;
$panel-editor-viz-item-shadow-hover: 0 0 4px $blue;
$panel-editor-viz-item-border-hover: 1px solid $blue;
$panel-editor-viz-item-bg: $black;
$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-grid-placeholder-bg: darken($blue, 47%);
$panel-grid-placeholder-shadow: 0 0 4px $blue;
+21 -4
View File
@@ -61,7 +61,7 @@ $critical: #ec2128;
// -------------------------
$body-bg: $gray-7;
$page-bg: $gray-7;
$page-bg: $gray-6;
$body-color: $gray-1;
$text-color: $gray-1;
$text-color-strong: $dark-2;
@@ -76,6 +76,8 @@ $textShadow: none;
// gradients
$brand-gradient: linear-gradient(to right, rgba(255, 213, 0, 1) 0%, rgba(255, 68, 0, 1) 99%, rgba(255, 68, 0, 1) 100%);
$page-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%);
//$page-gradient: linear-gradient(180deg, $white 10px, $gray-7 100px);
$edit-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%);
// Links
// -------------------------
@@ -215,9 +217,7 @@ $menu-dropdown-shadow: 5px 5px 10px -5px $gray-1;
$tab-border-color: $gray-5;
// Toolbar
$toolbar-bg: linear-gradient(90deg, #ffffff, #e6eef9);
$toolbar-shadow: 1px 1px 3px #c7d0d8;
$toolbar-tab-bg: $white;
$toolbar-bg: white;
// search
$search-shadow: 0 5px 30px 0 $gray-4;
@@ -379,3 +379,20 @@ $checkbox-bg: $gray-6;
$checkbox-border: 1px solid $gray-3;
$checkbox-checked-bg: linear-gradient(0deg, $yellow, $red);
$checkbox-color: $gray-7;
//Panel Edit
// -------------------------
$panel-editor-shadow: 2px 2px 8px $gray-3;
$panel-editor-side-menu-shadow: drop-shadow(0 0 2px $gray-3);
$panel-editor-toolbar-view-bg: $white;
$panel-editor-viz-item-shadow: 0 0 4px $gray-3;
$panel-editor-viz-item-border: 1px solid $gray-3;
$panel-editor-viz-item-shadow-hover: 0 0 4px $blue-light;
$panel-editor-viz-item-border-hover: 1px solid $blue-light;
$panel-editor-viz-item-bg: $white;
$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-grid-placeholder-bg: lighten($blue, 62%);
$panel-grid-placeholder-shadow: 0 0 4px $blue-light;
+34
View File
@@ -211,3 +211,37 @@
min-width: 20px;
padding-right: 5px;
}
.panel-editor-tabs {
.gicon-advanced-active {
background-image: url('../img/icons_#{$theme-name}_theme/icon_advanced_active.svg');
}
.gicon-advanced {
background-image: url('../img/icons_#{$theme-name}_theme/icon_advanced.svg');
}
.gicon-alerts-active {
background-image: url('../img/icons_#{$theme-name}_theme/icon_alerting_active.svg');
}
.gicon-alerts {
background-image: url('../img/icons_#{$theme-name}_theme/icon_alerting.svg');
}
.gicon-queries-active {
background-image: url('../img/icons_#{$theme-name}_theme/icon_query_active.svg');
}
.gicon-queries {
background-image: url('../img/icons_#{$theme-name}_theme/icon_query.svg');
}
.gicon-visualization-active {
background-image: url('../img/icons_#{$theme-name}_theme/icon_visualize_active.svg');
}
.gicon-visualization {
background-image: url('../img/icons_#{$theme-name}_theme/icon_visualize.svg');
}
}
+6
View File
@@ -172,6 +172,12 @@
padding-right: 20px;
}
// No horizontal padding
.btn-p-x-0 {
padding-left: 0;
padding-right: 0;
}
// External services
// Usage:
// <div class="btn btn-service btn-service--facebook">Button text</div>
+16 -13
View File
@@ -9,18 +9,14 @@
.react-grid-item {
display: none !important;
transition-property: none !important;
}
.panel--fullscreen {
display: block !important;
position: unset !important;
width: 100% !important;
height: 100% !important;
transform: translate(0px, 0px) !important;
}
.panel {
margin: 0 !important;
&--fullscreen {
display: block !important;
position: unset !important;
width: 100% !important;
height: 100% !important;
transform: translate(0px, 0px) !important;
}
}
// Disable grid interaction indicators in fullscreen panels
@@ -61,6 +57,13 @@
}
}
.react-grid-item.react-grid-placeholder {
box-shadow: $panel-grid-placeholder-shadow;
background: $panel-grid-placeholder-bg;
z-index: 0;
opacity: unset;
}
.theme-dark {
.react-grid-item > .react-resizable-handle::after {
border-right: 2px solid $gray-1;
@@ -83,10 +86,10 @@
}
// Disable animation on initial rendering and enable it when component has been mounted.
.react-grid-item.cssTransforms.panel {
.react-grid-item.cssTransforms {
transition-property: none;
}
.animated .react-grid-item.cssTransforms.panel {
.animated .react-grid-item.cssTransforms {
transition-property: transform;
}
@@ -12,6 +12,7 @@
height: 35px;
display: flex;
justify-content: space-between;
align-items: center;
.gf-form-button-row {
padding-top: 0;
+1 -13
View File
@@ -141,19 +141,6 @@ $input-border: 1px solid $input-border-color;
@include border-radius($label-border-radius-sm);
}
.gf-form-checkbox {
flex-shrink: 0;
padding: $input-padding-y $input-padding-x;
line-height: $input-line-height;
.checkbox-label {
display: inline;
cursor: pointer;
padding: $input-padding-y 0.4rem;
line-height: $input-line-height;
}
}
.gf-form-textarea {
max-width: 650px;
}
@@ -180,6 +167,7 @@ $input-border: 1px solid $input-border-color;
@at-root textarea#{&} {
overflow: auto;
white-space: pre-wrap;
height: auto;
}
// Unstyle the caret on `<select>`s in IE10+.
+4
View File
@@ -50,6 +50,10 @@
.navbar-page-btn .fa-caret-down {
display: none;
}
.navbar-buttons--close {
display: flex;
}
}
.navbar-page-btn {
+145 -29
View File
@@ -4,27 +4,73 @@
height: 100%;
}
.panel-editor-container__panel {
flex: 1 1 0;
.panel-wrapper {
height: 100%;
&--edit {
flex: 1 1 0;
height: unset;
margin: 0 $dashboard-padding;
}
&--view {
flex: 1 1 0;
height: 80%;
margin: 0 $dashboard-padding;
padding-top: $dashboard-padding;
}
}
.panel-editor-container__editor {
margin-top: $panel-margin*2;
display: flex;
flex-direction: column;
height: 65%;
flex-direction: row;
height: 60%;
position: relative;
}
.panel-editor__right {
display: flex;
flex-direction: column;
flex-grow: 1;
background: $page-bg;
margin: 0 20px 0 84px;
border-left: 2px solid $orange;
border-radius: 3px;
box-shadow: $panel-editor-shadow;
}
.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;
display: flex;
padding: 0 5px;
}
.panel-editor__content {
padding: 40px 15px;
padding: 40px 20px;
}
.panel-editor__toolbar-view {
background: $panel-editor-toolbar-view-bg;
padding: 20px;
}
.panel-in-fullscreen {
@@ -94,11 +140,10 @@
}
.viz-picker__item {
background: $card-background;
box-shadow: $card-shadow;
background: $panel-editor-viz-item-bg;
border: $panel-editor-viz-item-border;
border-radius: 3px;
height: 90px;
height: 100px;
width: 150px;
flex-shrink: 0;
flex-direction: column;
@@ -107,15 +152,27 @@
display: flex;
margin-right: 10px;
margin-bottom: 10px;
border: 1px solid transparent;
//border: 1px solid transparent;
align-items: center;
justify-content: center;
padding-bottom: 6px;
transition: transform 1 ease;
&:hover {
background: $card-background-hover;
box-shadow: $panel-editor-viz-item-shadow-hover;
background: $panel-editor-viz-item-bg-hover;
border: $panel-editor-viz-item-border-hover;
}
&--selected {
box-shadow: 0 0 12px #ff4d00;
box-shadow: 0 0 6px $orange;
border: 1px solid $orange;
&:hover {
box-shadow: 0 0 6px $orange;
border: 1px solid $orange;
background: $panel-editor-viz-item-bg-hover-active;
}
}
}
@@ -128,6 +185,7 @@
flex-direction: column;
align-self: center;
height: 23px;
font-weight: 500;
}
.viz-picker__item-img {
@@ -137,22 +195,55 @@
.panel-editor-tabs {
position: relative;
z-index: 2;
box-shadow: $page-header-shadow;
border-bottom: 1px solid $page-header-border-color;
padding: 0 $dashboard-padding;
display: flex;
flex-direction: column;
position: absolute;
top: 44px;
left: 20px;
align-items: flex-start;
@include clearfix();
.active.gf-tabs-link {
background: $toolbar-tab-bg;
&::before {
content: '';
display: block;
position: absolute;
top: 10px;
bottom: 10px;
left: 21px;
width: 2px;
background: $panel-editor-tabs-line-color;
}
}
.panel-editor-tabs__close {
padding: 5px 9px;
border-radius: $border-radius;
float: right;
@include buttonBackground($btn-primary-bg, $btn-primary-bg-hl);
.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;
transition: transform 0.1s ease;
margin-right: 5px;
&:hover {
filter: $panel-editor-side-menu-shadow;
transform: translate(-2px, -2px);
transform: scale(1.1);
}
}
}
.ds-picker-list {
@@ -163,21 +254,26 @@
}
.ds-picker-list__item {
background: $card-background;
box-shadow: $card-shadow;
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;
&:hover {
background: $card-background-hover;
background: $panel-editor-viz-item-bg-hover;
border: $panel-editor-viz-item-border-hover;
box-shadow: $panel-editor-viz-item-shadow-hover;
}
&--selected {
box-shadow: 0 0 6px $orange;
border: 1px solid $orange;
.ds-picker-list__name {
color: $text-color;
}
@@ -206,3 +302,23 @@
font-size: $font-size-md;
margin-bottom: 20px;
}
.form-section {
margin-bottom: 10px;
}
.form-section__header {
padding: 5px 10px;
font-size: $font-size-h5;
margin-bottom: 20px;
background: $input-label-bg;
border-radius: 3px;
}
.form-section__body {
padding: 0 10px;
}
.panel-editor-tabs__item-popover {
background: $orange;
}
+7 -18
View File
@@ -78,6 +78,9 @@ input:checked + .gf-form-switch__slider::before {
border: 1px solid $input-border-color;
border-left: none;
border-radius: $input-border-radius;
display: flex;
align-items: center;
justify-content: center;
input {
opacity: 0;
@@ -88,25 +91,14 @@ input:checked + .gf-form-switch__slider::before {
&--transparent {
background: transparent;
border: none;
}
&--search-result__section {
width: 3.05rem;
width: 20px;
height: auto;
position: relative;
top: -5px;
left: 3px;
}
&--search-result__item {
width: 2.7rem;
height: auto;
position: relative;
top: 3px;
padding-left: 7px;
}
&--table-cell {
width: 40px;
width: 20px;
background: transparent;
height: auto;
border: none;
@@ -116,17 +108,14 @@ input:checked + .gf-form-switch__slider::before {
}
.gf-form-switch__checkbox {
position: absolute;
left: 16px;
height: 16px;
width: 16px;
border-radius: 3px;
border: $checkbox-border;
background: $checkbox-bg;
top: 8px;
display: flex;
justify-content: center;
align-items: center;
justify-content: center;
}
input:checked + .gf-form-switch__checkbox::before {
+10 -12
View File
@@ -2,12 +2,18 @@
display: flex;
align-content: center;
align-items: center;
background: $toolbar-bg;
box-shadow: $toolbar-shadow;
padding: 7px 20px 7px 20px;
padding: 3px 20px 3px 20px;
position: relative;
z-index: 1;
flex: 0 0 auto;
background: $toolbar-bg;
border-radius: 3px;
height: 44px;
}
.toolbar__heading {
font-size: $font-size-lg;
padding-right: 20px;
}
.toolbar__main {
@@ -36,20 +42,12 @@
height: 20px;
}
.toolbar-subview {
position: relative;
padding: 20px 20px;
background-color: $empty-list-cta-bg;
top: -45px;
margin: 0 30px 20px 0px;
}
.toolbar-subview__close {
background: transparent;
padding: 4px 8px 4px 9px;
border: none;
position: absolute;
right: 15px;
right: 25px;
top: 20px;
font-size: $font-size-md;
+6
View File
@@ -14,6 +14,12 @@
background: $page-gradient;
}
.panel-in-fullscreen {
.main-view {
background: $edit-gradient;
}
}
.page-container {
margin-left: auto;
margin-right: auto;
+1
View File
@@ -25,6 +25,7 @@ div.flot-text {
bottom: 0;
right: 0;
margin: 0;
.panel-container {
border: none;
z-index: $zindex-sidemenu + 1;
+7
View File
@@ -82,3 +82,10 @@ button.close {
.absolute {
position: absolute;
}
.center-vh {
display: flex;
align-items: center;
justify-content: center;
justify-items: center;
}

Some files were not shown because too many files have changed in this diff Show More