Merge pull request #14135 from grafana/panel-edit-in-react
React Panel Editor Mode (For Angular and React Panels)
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
`;
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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%' }} />;
|
||||
}
|
||||
}
|
||||
@@ -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}>
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -84,6 +84,13 @@ class TestDataDatasource {
|
||||
}
|
||||
return this.$q.when(events);
|
||||
}
|
||||
|
||||
testDatasource() {
|
||||
return Promise.resolve({
|
||||
status: 'success',
|
||||
message: 'Data source is working',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { TestDataDatasource };
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export interface ValidationRule {
|
||||
rule: (valueToValidate: string) => boolean;
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
export interface ValidationEvents {
|
||||
[eventName: string]: ValidationRule[];
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 |
@@ -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,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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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+.
|
||||
|
||||
@@ -50,6 +50,10 @@
|
||||
.navbar-page-btn .fa-caret-down {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-buttons--close {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-page-btn {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -14,6 +14,12 @@
|
||||
background: $page-gradient;
|
||||
}
|
||||
|
||||
.panel-in-fullscreen {
|
||||
.main-view {
|
||||
background: $edit-gradient;
|
||||
}
|
||||
}
|
||||
|
||||
.page-container {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
@@ -25,6 +25,7 @@ div.flot-text {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
|
||||
.panel-container {
|
||||
border: none;
|
||||
z-index: $zindex-sidemenu + 1;
|
||||
|
||||
@@ -82,3 +82,10 @@ button.close {
|
||||
.absolute {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.center-vh {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||