Part3: Unicons implementation (#23356)

* Create a new Icon component

* Update icons in main sidebar

* Update icons in Useful links and in react components on  main site

* Update icons in Useful links and in main top navigation

* Adjust sizing

* Update panel navigation and timepicker

* Update icons in Panel menu

* Update icons in add panel widget

* Resolve merge conflict

* Fix part of the test errors and type errors

* Fix storybook errors

* Update getAvailableIcons import in storybook knobs

* Fix import path

* Fix SyntaxError: Cannot use import statement outside a module in test environment error

* Remove dynamic imports

* Remove types as using @ts-ignore

* Update snapshot test

* Add @iconscout/react-unicons to the shouldExclude list as it is blundled with es2015 syntax

* Implment icons in Tabs

* Implement icons in search items and  empty  list

* Update buttons

* Update button-related snapshot tests

* Update icons in modals and page headers

* Create anfular wrapper and update all icons on search screen

* Update sizing, remove colors, update snapshot tests

* Remove color prop from icon, remove color implemetation in mono icons

* Remove color props from monochrome icons

* Complete update of icons for search screen

* Update icons for infor tooltips, playlist, permissions

* Support temporarly font awesome icons used in enterprise grafana

* Part1: Unicons implementation (#23197)

* Create a new Icon component

* Update icons in main sidebar

* Update icons in Useful links and in react components on  main site

* Update icons in Useful links and in main top navigation

* Adjust sizing

* Update panel navigation and timepicker

* Update icons in Panel menu

* NewPanelEditor: Fixed so that test alert rule works in new edit mode (#23179)

* Update icons in add panel widget

* Resolve merge conflict

* Fix part of the test errors and type errors

* Fix storybook errors

* Update getAvailableIcons import in storybook knobs

* Fix import path

* Fix SyntaxError: Cannot use import statement outside a module in test environment error

* Remove dynamic imports

* Remove types as using @ts-ignore

* Update snapshot test

* Add @iconscout/react-unicons to the shouldExclude list as it is blundled with es2015 syntax

* Remove color prop from icon, remove color implemetation in mono icons

* Update navbar styling

* Move toPascalCase to utils/string

Co-authored-by: Torkel Ödegaard <torkel@grafana.com>

* Update icons in Explore

* Update icons in alerting

* Update + and x buttons

* Update icons in configurations and settings

* Update close icons

* Update icons in rich history

* Update alert messages

* Add optional chaining to for isFontAwesome variable

* Remove icon mock, set up jest.config

* Fix navbar plus icon

* Fir enable-bacground to enableBackgournd

Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
Ivana Huckova 2020-04-07 16:25:42 +02:00 committed by GitHub
parent d2d61d9bc2
commit bc468e4b92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
95 changed files with 899 additions and 504 deletions

View File

@ -1,8 +1,12 @@
const esModule = '@iconscout/react-unicons';
module.exports = {
verbose: false,
transform: {
'^.+\\.(ts|tsx|js|jsx)$': 'ts-jest',
[`(${esModule}).+\\.js$`]: 'babel-jest',
},
transformIgnorePatterns: [`/node_modules/(?!${esModule})`],
moduleDirectories: ['node_modules', 'public'],
roots: ['<rootDir>/public/app', '<rootDir>/public/test', '<rootDir>/packages', '<rootDir>/scripts'],
testRegex: '(\\.|/)(test)\\.(jsx?|tsx?)$',

View File

@ -1,4 +1,5 @@
import React, { FC, ReactNode } from 'react';
import { Icon, IconName } from '@grafana/ui';
import classNames from 'classnames';
export type AlertVariant = 'success' | 'warning' | 'error' | 'info';
@ -15,16 +16,16 @@ interface AlertProps {
function getIconFromSeverity(severity: AlertVariant): string {
switch (severity) {
case 'error': {
return 'fa fa-exclamation-triangle';
return 'exclamation-triangle';
}
case 'warning': {
return 'fa fa-exclamation-triangle';
return 'exclamation-triangle';
}
case 'info': {
return 'fa fa-info-circle';
return 'info-circle';
}
case 'success': {
return 'fa fa-check';
return 'check';
}
default:
return '';
@ -37,7 +38,7 @@ export const Alert: FC<AlertProps> = ({ title, buttonText, onButtonClick, onRemo
<div className="alert-container">
<div className={alertClass}>
<div className="alert-icon">
<i className={getIconFromSeverity(severity)} />
<Icon size="xl" name={getIconFromSeverity(severity) as IconName} />
</div>
<div className="alert-body">
<div className="alert-title">{title}</div>
@ -46,7 +47,7 @@ export const Alert: FC<AlertProps> = ({ title, buttonText, onButtonClick, onRemo
{/* If onRemove is specified , giving preference to onRemove */}
{onRemove && (
<button type="button" className="alert-close" onClick={onRemove}>
<i className="fa fa fa-remove" />
<Icon name="times" size="lg" />
</button>
)}
{onButtonClick && (

View File

@ -12,7 +12,7 @@ CallToActionCardStories.add('default', () => {
const ctaElements: { [key: string]: JSX.Element } = {
custom: <h1>This is just H1 tag, you can any component as CTA element</h1>,
button: (
<Button size="lg" icon="plus" onClick={action('cta button clicked')}>
<Button size="lg" icon="plus-circle" onClick={action('cta button clicked')}>
Add datasource
</Button>
),

View File

@ -73,7 +73,7 @@ export const DataLinksEditor: FC<DataLinksEditorProps> = React.memo(
)}
{(!value || (value && value.length < (maxLinks || Infinity))) && (
<Button variant="secondary" icon="plus" onClick={() => onAdd()}>
<Button variant="secondary" icon="plus-circle" onClick={() => onAdd()}>
Add link
</Button>
)}

View File

@ -100,7 +100,7 @@ export const DataLinksInlineEditor: React.FC<DataLinksInlineEditorProps> = ({ li
)}
<FullWidthButtonContainer>
<Button size="sm" icon="plus" onClick={onDataLinkAdd} variant="secondary">
<Button size="sm" icon="plus-circle" onClick={onDataLinkAdd} variant="secondary">
Add link
</Button>
</FullWidthButtonContainer>

View File

@ -2,6 +2,7 @@ import React from 'react';
import { KeyValue } from '@grafana/data';
import { css, cx } from 'emotion';
import { Tooltip } from '../Tooltip/Tooltip';
import { Icon } from '../Icon/Icon';
import { CertificationKey } from './CertificationKey';
import { HttpSettingsBaseProps } from './types';
@ -47,7 +48,7 @@ export const TLSAuthSettings: React.FC<HttpSettingsBaseProps> = ({ dataSourceCon
theme="info"
>
<div className="gf-form-help-icon gf-form-help-icon--right-normal">
<i className="fa fa-info-circle" />
<Icon name="info-circle" size="sm" />
</div>
</Tooltip>
</div>

View File

@ -34,7 +34,7 @@ export const FormLabel: FunctionComponent<Props> = ({
{tooltip && (
<Tooltip placement="top" content={tooltip} theme={'info'}>
<div className="gf-form-help-icon gf-form-help-icon--right-normal">
<Icon name="info-circle" />
<Icon name="info-circle" size="sm" style={{ marginBottom: 0 }} />
</div>
</Tooltip>
)}

View File

@ -136,7 +136,6 @@ $select-input-bg-disabled: $input-bg-disabled;
height: 100%;
right: 8px;
top: 1px;
margin-top: -1px;
display: inline-block;
text-align: right;
}

View File

@ -39,7 +39,7 @@ const getIconStyles = stylesFactory((theme: GrafanaTheme, type: IconType) => {
});
export const Icon = React.forwardRef<HTMLDivElement, IconProps>(
({ size = 'md', type = 'default', title, name, className, style, ...divElementProps }, ref) => {
({ size = 'md', type = 'default', name, className, style, ...divElementProps }, ref) => {
const theme = useTheme();
const styles = getIconStyles(theme, type);
const svgSize = getSvgSize(size, theme);

View File

@ -5,7 +5,7 @@ export const Bell: FunctionComponent<SvgProps> = ({ size, ...rest }) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
enable-background="new 0 0 24 24"
enableBackground="new 0 0 24 24"
viewBox="0 0 24 24"
width={size}
height={size}

View File

@ -5,7 +5,7 @@ export const FolderPlus: FunctionComponent<SvgProps> = ({ size, ...rest }) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
enable-background="new 0 0 24 24"
enableBackground="new 0 0 24 24"
viewBox="0 0 24 24"
width={size}
height={size}

View File

@ -5,7 +5,7 @@ export const Import: FunctionComponent<SvgProps> = ({ size, ...rest }) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
enable-background="new 0 0 24 24"
enableBackground="new 0 0 24 24"
viewBox="0 0 24 24"
width={size}
height={size}

View File

@ -5,7 +5,7 @@ export const PlusSquare: FunctionComponent<SvgProps> = ({ size, ...rest }) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
enable-background="new 0 0 24 24"
enableBackground="new 0 0 24 24"
viewBox="0 0 24 24"
width={size}
height={size}

View File

@ -37,7 +37,7 @@ describe('LogDetails', () => {
describe('when labels are present', () => {
it('should render heading', () => {
const wrapper = setup(undefined, { labels: { key1: 'label1', key2: 'label2' } });
expect(wrapper.find({ 'aria-label': 'Log Labels' })).toHaveLength(1);
expect(wrapper.find({ 'aria-label': 'Log Labels' }).hostNodes()).toHaveLength(1);
});
it('should render labels', () => {
const wrapper = setup(undefined, { labels: { key1: 'label1', key2: 'label2' } });
@ -47,7 +47,7 @@ describe('LogDetails', () => {
describe('when row entry has parsable fields', () => {
it('should render heading ', () => {
const wrapper = setup(undefined, { entry: 'test=successful' });
expect(wrapper.find({ title: 'Ad-hoc statistics' })).toHaveLength(1);
expect(wrapper.find({ title: 'Ad-hoc statistics' }).hostNodes()).toHaveLength(1);
});
it('should render parsed fields', () => {
const wrapper = setup(undefined, { entry: 'test=successful' });
@ -116,7 +116,7 @@ describe('LogDetails', () => {
expect(wrapper.find(LogDetailsRow).length).toBe(3);
const traceIdRow = wrapper.find(LogDetailsRow).filter({ parsedKey: 'traceId' });
expect(traceIdRow.length).toBe(1);
expect(traceIdRow.find('a').length).toBe(1);
expect(traceIdRow.find('a').hostNodes().length).toBe(1);
expect((traceIdRow.find('a').getDOMNode() as HTMLAnchorElement).href).toBe('localhost:3210/1234');
});
});

View File

@ -32,16 +32,16 @@ describe('LogDetailsRow', () => {
});
it('should render metrics button', () => {
const wrapper = setup();
expect(wrapper.find('i.fa-signal')).toHaveLength(1);
expect(wrapper.find({ title: 'Ad-hoc statistics' }).hostNodes()).toHaveLength(1);
});
describe('if props is a label', () => {
it('should render filter label button', () => {
const wrapper = setup();
expect(wrapper.find('i.fa-search-plus')).toHaveLength(1);
expect(wrapper.find({ title: 'Filter for value' }).hostNodes()).toHaveLength(1);
});
it('should render filter out label button', () => {
const wrapper = setup();
expect(wrapper.find('i.fa-search-minus')).toHaveLength(1);
expect(wrapper.find({ title: 'Filter out value' }).hostNodes()).toHaveLength(1);
});
});
@ -66,7 +66,10 @@ describe('LogDetailsRow', () => {
});
expect(wrapper.find(LogLabelStats).length).toBe(0);
wrapper.find({ title: 'Ad-hoc statistics' }).simulate('click');
wrapper
.find({ title: 'Ad-hoc statistics' })
.hostNodes()
.simulate('click');
expect(wrapper.find(LogLabelStats).length).toBe(1);
expect(wrapper.find(LogLabelStats).contains('another value')).toBeTruthy();
});

View File

@ -10,6 +10,7 @@ import { stylesFactory } from '../../themes/stylesFactory';
//Components
import { LogLabelStats } from './LogLabelStats';
import { LinkButton } from '../Button/Button';
import { Icon } from '../Icon/Icon';
export interface Props extends Themeable {
parsedValue: string;
@ -94,24 +95,16 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
<tr className={cx(style.logDetailsValue, { [styles.noHoverBackground]: showFieldsStats })}>
{/* Action buttons - show stats/filter results */}
<td className={style.logsDetailsIcon} colSpan={isLabel ? undefined : 3}>
<i title="Ad-hoc statistics" className={`fa fa-signal ${styles.hoverCursor}`} onClick={this.showStats} />
<Icon name="signal" title={'Ad-hoc statistics'} onClick={this.showStats} />
</td>
{isLabel && (
<>
<td className={style.logsDetailsIcon}>
<i
title="Filter for value"
className={`fa fa-search-plus ${styles.hoverCursor}`}
onClick={this.filterLabel}
/>
<Icon name="search-minus" title="Filter for value" onClick={this.filterLabel} />
</td>
<td className={style.logsDetailsIcon}>
<i
title="Filter out value"
className={`fa fa-search-minus ${styles.hoverCursor}`}
onClick={this.filterOutLabel}
/>
<Icon name="search-plus" title="Filter out value" onClick={this.filterOutLabel} />
</td>
</>
)}

View File

@ -1,5 +1,6 @@
import React, { PureComponent } from 'react';
import { Field, LinkModel, LogRowModel, TimeZone, DataQueryResponse, GrafanaTheme } from '@grafana/data';
import { Icon } from '@grafana/ui';
import { cx, css } from 'emotion';
import {
@ -48,6 +49,8 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
topVerticalAlign: css`
label: topVerticalAlign;
vertical-align: top;
margin-top: -${theme.spacing.xs};
margin-left: -${theme.spacing.xxs};
`,
hoverBackground: css`
label: hoverBackground;
@ -129,9 +132,6 @@ class UnThemedLogRow extends PureComponent<Props, State> {
const style = getLogRowStyles(theme, row.logLevel);
const styles = getStyles(theme);
const showUtc = timeZone === 'utc';
const showDetailsClassName = showDetails
? cx(['fa fa-chevron-down', styles.topVerticalAlign])
: cx(['fa fa-chevron-right', styles.topVerticalAlign]);
const hoverBackground = cx(style.logsRow, { [styles.hoverBackground]: hasHoverBackground });
return (
@ -150,7 +150,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
<td className={style.logsRowLevel} />
{!allowDetails && (
<td title={showDetails ? 'Hide log details' : 'See log details'} className={style.logsRowToggleDetails}>
<i className={showDetailsClassName} />
<Icon className={styles.topVerticalAlign} name={showDetails ? 'angle-down' : 'angle-right'} />
</td>
)}
{showTime && showUtc && (

View File

@ -158,8 +158,10 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
logsDetailsIcon: css`
label: logs-row-details__icon;
position: relative;
padding-right: ${theme.spacing.md};
color: ${theme.colors.gray3};
svg {
margin-right: ${theme.spacing.md};
}
`,
logDetailsLabel: css`
label: logs-row-details__label;

View File

@ -6,6 +6,7 @@ import { IconName } from '../../types';
import { Themeable } from '../../types';
import { getModalStyles } from './getModalStyles';
import { ModalHeader } from './ModalHeader';
import { Icon } from '../Icon/Icon';
interface Props extends Themeable {
icon?: IconName;
@ -50,7 +51,7 @@ export class UnthemedModal extends React.PureComponent<Props> {
<div className={styles.modalHeader}>
{typeof title === 'string' ? this.renderDefaultHeader(title) : title}
<a className={styles.modalHeaderClose} onClick={this.onDismiss}>
<i className="fa fa-remove" />
<Icon name="times" />
</a>
</div>
<div className={styles.modalContent}>{this.props.children}</div>

View File

@ -1,5 +1,6 @@
// Libraries
import React, { FunctionComponent } from 'react';
import { Icon } from '@grafana/ui';
interface Props {
title?: string | JSX.Element;
@ -26,7 +27,7 @@ export const PanelOptionsGroup: FunctionComponent<Props> = props => {
<span className="panel-options-group__title">{props.title}</span>
{props.onClose && (
<button className="btn btn-link" onClick={props.onClose}>
<i className="fa fa-remove" />
<Icon name="times" />
</button>
)}
</div>

View File

@ -200,7 +200,7 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
return (
<div className={styles.wrapper}>
<FullWidthButtonContainer className={styles.addButton}>
<Button size="sm" icon="plus" onClick={() => this.onAddThreshold()} variant="secondary">
<Button size="sm" icon="plus-circle" onClick={() => this.onAddThreshold()} variant="secondary">
Add threshold
</Button>
</FullWidthButtonContainer>

View File

@ -118,7 +118,7 @@ export class TransformationsEditor extends React.PureComponent<TransformationsEd
return (
<>
{this.renderTransformationEditors()}
<Button variant="secondary" icon="plus" onClick={this.onTransformationAdd}>
<Button variant="secondary" icon="plus-circle" onClick={this.onTransformationAdd}>
Add transformation
</Button>
</>

View File

@ -98,7 +98,7 @@ export class LegacyValueMappingsEditor extends PureComponent<Props, State> {
removeValueMapping={() => this.onRemoveMapping(valueMapping.id)}
/>
))}
<Button variant="primary" icon="plus" onClick={this.onAddMapping}>
<Button variant="primary" icon="plus-circle" onClick={this.onAddMapping}>
Add mapping
</Button>
</div>

View File

@ -68,7 +68,7 @@ export const ValueMappingsEditor: React.FC<Props> = ({ valueMappings, onChange,
<FullWidthButtonContainer>
<Button
size="sm"
icon="plus"
icon="plus-circle"
onClick={onAdd}
aria-label="ValueMappingsEditor add mapping button"
variant="secondary"

View File

@ -35,7 +35,7 @@ exports[`Render should render component 1`] = `
}
/>
<Button
icon="plus"
icon="plus-circle"
onClick={[Function]}
variant="primary"
>

View File

@ -23,7 +23,7 @@ export function ValuePicker<T>({ label, icon, options, onChange, variant }: Valu
<>
{!isPicking && (
<FullWidthButtonContainer>
<Button size="sm" icon={icon || 'plus'} onClick={() => setIsPicking(true)} variant={variant}>
<Button size="sm" icon={icon || 'plus-circle'} onClick={() => setIsPicking(true)} variant={variant}>
{label}
</Button>
</FullWidthButtonContainer>

View File

@ -3,10 +3,12 @@ export type IconType = 'mono' | 'default';
export type IconName =
| 'fa fa-fw fa-unlock'
| 'fa fa-envelope'
| 'fa fa-spinner'
| 'question-circle'
| 'plus'
| 'angle-up'
| 'history'
| 'angle-down'
| 'filter'
| 'angle-left'
| 'angle-right'
| 'pen'
@ -34,6 +36,8 @@ export type IconName =
| 'panel-add'
| 'arrow-random'
| 'arrow-down'
| 'comment-alt'
| 'arrow-right'
| 'arrow-up'
| 'arrow-from-right'
| 'keyboard'
@ -50,9 +54,12 @@ export type IconName =
| 'folder-plus'
| 'link'
| 'upload'
| 'columns'
| 'home-alt'
| 'channel-add'
| 'calendar-alt'
| 'play'
| 'pause'
| 'calculator-alt'
| 'compass'
| 'sliders-v-alt'
@ -77,12 +84,20 @@ export type IconName =
| 'edit'
| 'shield'
| 'eye'
| 'eye-slash'
| 'filter'
| 'monitor'
| 'plus-circle'
| 'arrow-left'
| 'repeat'
| 'external-link-alt'
| 'minus'
| 'signal'
| 'search-plus'
| 'search-minus'
| 'table'
| 'plus'
| 'heart'
| 'favorite';
export const getAvailableIcons = (): IconName[] => [
@ -102,6 +117,8 @@ export const getAvailableIcons = (): IconName[] => [
'repeat',
'external-link-alt',
'power',
'play',
'pause',
'trash-alt',
'exclamation-triangle',
'times',
@ -152,8 +169,10 @@ export const getAvailableIcons = (): IconName[] => [
'info-circle',
'bug',
'cube',
'history',
'star',
'edit',
'columns',
'eye',
'channel-add',
'monitor',
@ -164,6 +183,7 @@ export const getAvailableIcons = (): IconName[] => [
'folder-open',
'file-copy-alt',
'arrow-down',
'filter',
'arrow-up',
'exchange-alt',
];

View File

@ -1,4 +1,5 @@
import React, { FC } from 'react';
import { Icon } from '@grafana/ui';
export type LayoutMode = LayoutModes.Grid | LayoutModes.List;
@ -22,7 +23,7 @@ const LayoutSelector: FC<Props> = props => {
}}
className={mode === LayoutModes.List ? 'active' : ''}
>
<i className="fa fa-list" />
<Icon name="list-ul" />
</button>
<button
onClick={() => {
@ -30,7 +31,7 @@ const LayoutSelector: FC<Props> = props => {
}}
className={mode === LayoutModes.Grid ? 'active' : ''}
>
<i className="fa fa-th" />
<Icon name="table" />
</button>
</div>
);

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { UserPicker } from 'app/core/components/Select/UserPicker';
import { TeamPicker, Team } from 'app/core/components/Select/TeamPicker';
import { LegacyForms } from '@grafana/ui';
import { LegacyForms, Icon } from '@grafana/ui';
import { SelectableValue } from '@grafana/data';
import { User } from 'app/types';
import {
@ -91,7 +91,7 @@ class AddPermissions extends Component<Props, NewDashboardAclItem> {
return (
<div className="gf-form-inline cta-form">
<button className="cta-form__close btn btn-transparent" onClick={onCancel}>
<i className="fa fa-close" />
<Icon name="times" />
</button>
<form name="addPermission" onSubmit={this.onSubmit}>
<h5>Add Permission For</h5>

View File

@ -1,5 +1,5 @@
import React from 'react';
import { getTagColorsFromName } from '@grafana/ui';
import { getTagColorsFromName, Icon } from '@grafana/ui';
export interface Props {
label: string;
@ -24,7 +24,7 @@ export class TagBadge extends React.Component<Props, any> {
return (
<span className={`label label-tag`} style={tagStyle}>
{removeIcon && <i className="fa fa-remove" />}
{removeIcon && <Icon name="times" />}
{label} {countLabel}
</span>
);

View File

@ -1,5 +1,6 @@
import React from 'react';
import { appEvents } from 'app/core/core';
import { Icon } from '@grafana/ui';
export class HelpModal extends React.PureComponent {
static tabIndex = 0;
@ -52,11 +53,11 @@ export class HelpModal extends React.PureComponent {
<div className="modal-body">
<div className="modal-header">
<h2 className="modal-header-title">
<i className="fa fa-keyboard-o" />
<Icon name="keyboard" size="lg" />
<span className="p-l-1">Shortcuts</span>
</h2>
<a className="modal-header-close" onClick={this.dismiss}>
<i className="fa fa-remove" />
<Icon name="times" style={{ margin: '3px 0 0 0' }} />
</a>
</div>

View File

@ -9,7 +9,7 @@ const template = `
<i class="fa fa-list"></i>
</button>
<button ng-click="ctrl.gridView()" ng-class="{active: ctrl.mode === 'grid'}">
<i class="fa fa-th"></i>
<icon name="table"></i>
</button>
</div>
`;

View File

@ -38,24 +38,25 @@
<div class="search-filter-box" ng-if="ctrl.isEditor || ctrl.hasEditPermissionInFolders">
<a href="dashboard/new" class="search-filter-box-link">
<icon name="'plus-square'" size="'xl'" style="margin-right: 8px;"></icon> New dashboard
<icon name="'plus-square'" type="'mono'" size="'xl'" style="margin-right: 8px;"></icon> New dashboard
</a>
<a href="dashboards/folder/new" class="search-filter-box-link" ng-if="ctrl.isEditor">
<icon name="'folder-plus'" size="'xl'" style="margin-right: 8px;"></icon> New folder
<icon name="'folder-plus'" type="'mono'" size="'xl'" style="margin-right: 8px;"></icon> New folder
</a>
<a
href="dashboard/import"
class="search-filter-box-link"
ng-if="ctrl.isEditor || ctrl.hasEditPermissionInFolders"
>
<icon name="'import'" size="'xl'" style="margin-right: 8px;"></icon> Import dashboard
<icon name="'import'" type="'mono'" size="'xl'" style="margin-right: 8px;"></icon> Import dashboard
</a>
<a
class="search-filter-box-link"
target="_blank"
href="https://grafana.com/dashboards?utm_source=grafana_search"
>
<icon name="'apps'" size="'xl'" style="margin-right: 8px;"></icon> Find dashboards on Grafana.com
<icon name="'apps'" type="'mono'" size="'xl'" style="margin-right: 8px;"></icon> Find dashboards on
Grafana.com
</a>
</div>
</div>

View File

@ -70,7 +70,7 @@ const UserListAdminPageUnConnected: React.FC<Props> = props => {
<th>
Seen&nbsp;
<Tooltip placement="top" content="Time since user was seen using Grafana">
<i className="fa fa-question-circle" />
<Icon name="question-circle" />
</Tooltip>
</th>
<th></th>
@ -113,7 +113,7 @@ const renderUser = (user: UserDTO) => {
{user.isAdmin && (
<a href={editUrl}>
<Tooltip placement="top" content="Grafana Admin">
<i className="fa fa-shield" />
<Icon name="shield" />
</Tooltip>
</a>
)}

View File

@ -52,7 +52,11 @@ exports[`ServerStats Should render table with stats 1`] = `
>
<span
className="page-header__logo"
/>
>
<i
className="fa fa-fw fa-warning css-1lgsh82"
/>
</span>
<div
className="page-header__info-block"
>
@ -99,6 +103,7 @@ exports[`ServerStats Should render table with stats 1`] = `
className="css-vohy86"
onClick={[Function]}
>
<div />
Admin
</li>
</ul>

View File

@ -1,46 +1,52 @@
<page-header model="navModel"></page-header>
<div class="page-container page-body">
<h3 class="page-sub-heading">Edit Organization</h3>
<h3 class="page-sub-heading">Edit Organization</h3>
<form name="orgDetailsForm" class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label width-10">Name</span>
<input type="text" required ng-model="org.name" class="gf-form-input max-width-14" >
</div>
<form name="orgDetailsForm" class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label width-10">Name</span>
<input type="text" required ng-model="org.name" class="gf-form-input max-width-14" />
</div>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-primary" ng-click="update()" ng-show="!createMode">Update</button>
</div>
</form>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-primary" ng-click="update()" ng-show="!createMode">Update</button>
</div>
</form>
<h3 class="page-heading">Organization Users</h3>
<h3 class="page-heading">Organization Users</h3>
<table class="filter-table">
<tr>
<th>Username</th>
<th>Email</th>
<th>Role</th>
<th></th>
</tr>
<tr ng-repeat="orgUser in orgUsers">
<td>{{orgUser.login}}</td>
<td>{{orgUser.email}}</td>
<td>
<table class="filter-table">
<tr>
<th>Username</th>
<th>Email</th>
<th>Role</th>
<th></th>
</tr>
<tr ng-repeat="orgUser in orgUsers">
<td>{{orgUser.login}}</td>
<td>{{orgUser.email}}</td>
<td>
<div class="gf-form">
<span class="gf-form-select-wrapper">
<select type="text" ng-model="orgUser.role" class="gf-form-input max-width-8" ng-options="f for f in ['Viewer', 'Editor', 'Admin']" ng-change="updateOrgUser(orgUser)">
</select>
<select
type="text"
ng-model="orgUser.role"
class="gf-form-input max-width-8"
ng-options="f for f in ['Viewer', 'Editor', 'Admin']"
ng-change="updateOrgUser(orgUser)"
>
</select>
</span>
</div>
</td>
<td style="width: 1%">
<a ng-click="removeOrgUser(orgUser)" class="btn btn-danger btn-small">
<i class="fa fa-remove"></i>
</a>
</td>
</tr>
</table>
</td>
<td style="width: 1%">
<a ng-click="removeOrgUser(orgUser)" class="btn btn-danger btn-small">
<icon name="'times'"></icon>
</a>
</td>
</tr>
</table>
</div>
<footer />

View File

@ -108,7 +108,7 @@
</td>
<td style="width: 1%">
<a ng-click="removeOrgUser(org)" class="btn btn-danger btn-small">
<i class="fa fa-remove"></i>
<icon name="'times'"></icon>
</a>
</td>
</tr>
@ -138,7 +138,7 @@
<td>{{session.browser}} on {{session.os}} {{session.osVersion}}</td>
<td>
<button class="btn btn-danger btn-small" ng-click="revokeUserSession(session.id)">
<i class="fa fa-power-off"></i>
<icon name="'power'" style="margin-top: -2px;"></icon>
</button>
</td>
</tr>
@ -152,7 +152,6 @@
</div>
</div>
<h3 class="page-heading">User status</h3>
<div class="gf-form-group">
@ -177,7 +176,9 @@
>
Enable
</button>
<button type="submit" class="btn btn-danger" ng-click="deleteUser(user)" ng-show="!createMode">Delete User</button>
<button type="submit" class="btn btn-danger" ng-click="deleteUser(user)" ng-show="!createMode">
Delete User
</button>
</div>
</div>
</div>

View File

@ -30,7 +30,7 @@
</td>
<td class="text-right">
<a ng-click="deleteOrg(org)" class="btn btn-danger btn-small">
<i class="fa fa-remove"></i>
<icon name="'times'" size="sm"></icon>
</a>
</td>
</tr>

View File

@ -1,8 +1,8 @@
import React, { PureComponent } from 'react';
// @ts-ignore
import Highlighter from 'react-highlight-words';
import classNames from 'classnames';
import { AlertRule } from '../../types';
import { Icon, IconName } from '@grafana/ui';
export interface Props {
rule: AlertRule;
@ -24,19 +24,11 @@ class AlertRuleItem extends PureComponent<Props> {
render() {
const { rule, onTogglePause } = this.props;
const iconClassName = classNames({
fa: true,
'fa-play': rule.state === 'paused',
'fa-pause': rule.state !== 'paused',
});
const ruleUrl = `${rule.url}?panelId=${rule.panelId}&fullscreen&edit&tab=alert`;
return (
<li className="alert-rule-item">
<span className={`alert-rule-item__icon ${rule.stateClass}`}>
<i className={rule.stateIcon} />
</span>
<Icon size="xl" name={rule.stateIcon as IconName} className={`alert-rule-item__icon ${rule.stateClass}`} />
<div className="alert-rule-item__body">
<div className="alert-rule-item__header">
<div className="alert-rule-item__name">
@ -56,10 +48,10 @@ class AlertRuleItem extends PureComponent<Props> {
title="Pausing an alert rule prevents it from executing"
onClick={onTogglePause}
>
<i className={iconClassName} />
<Icon name={rule.state === 'paused' ? 'play' : 'pause'} />
</button>
<a className="btn btn-small btn-inverse alert-list__btn width-2" href={ruleUrl} title="Edit alert rule">
<i className="gicon gicon-cog" />
<Icon name="cog" />
</a>
</div>
</li>

View File

@ -4,13 +4,11 @@ exports[`Render should render component 1`] = `
<li
className="alert-rule-item"
>
<span
<Icon
className="alert-rule-item__icon state class"
>
<i
className="icon"
/>
</span>
name="icon"
size="xl"
/>
<div
className="alert-rule-item__body"
>
@ -67,8 +65,8 @@ exports[`Render should render component 1`] = `
onClick={[MockFunction]}
title="Pausing an alert rule prevents it from executing"
>
<i
className="fa fa-pause"
<Icon
name="pause"
/>
</button>
<a
@ -76,8 +74,8 @@ exports[`Render should render component 1`] = `
href="https://something.something.darkside?panelId=1&fullscreen&edit&tab=alert"
title="Edit alert rule"
>
<i
className="gicon gicon-cog"
<Icon
name="cog"
/>
</a>
</div>

View File

@ -6,7 +6,7 @@
</h2>
<a class="modal-header-close" ng-click="dismiss();">
<i class="fa fa-remove"></i>
<icon name="'times'"></icon>
</a>
</div>

View File

@ -1,7 +1,5 @@
<div ng-if="ctrl.panel.alert">
<div class="alert alert-error m-b-2" ng-show="ctrl.error">
<i class="fa fa-warning"></i> {{ctrl.error}}
</div>
<div class="alert alert-error m-b-2" ng-show="ctrl.error"><i class="fa fa-warning"></i> {{ctrl.error}}</div>
<div class="panel-options-group">
<div class="panel-options-group__body">
<div class="gf-form-group">
@ -9,33 +7,35 @@
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-6">Name</span>
<input type="text" class="gf-form-input width-20" ng-model="ctrl.alert.name">
<input type="text" class="gf-form-input width-20" ng-model="ctrl.alert.name" />
</div>
<div class="gf-form">
<span class="gf-form-label width-9">Evaluate every</span>
<input class="gf-form-input max-width-6" type="text" ng-model="ctrl.alert.frequency" ng-blur="ctrl.checkFrequency()">
<input
class="gf-form-input max-width-6"
type="text"
ng-model="ctrl.alert.frequency"
ng-blur="ctrl.checkFrequency()"
/>
</div>
<div class="gf-form max-width-11">
<label class="gf-form-label width-5">For</label>
<input type="text" class="gf-form-input max-width-6 gf-form-input--has-help-icon" ng-model="ctrl.alert.for"
spellcheck='false' placeholder="5m">
<input
type="text"
class="gf-form-input max-width-6 gf-form-input--has-help-icon"
ng-model="ctrl.alert.for"
spellcheck="false"
placeholder="5m"
/>
<info-popover mode="right-absolute">
If an alert rule has a configured For and the query violates the configured
threshold it
will first go from OK to Pending.
Going from OK to Pending Grafana will not send any notifications. Once the alert
rule
has
been firing for more than For duration, it will change to Alerting and send alert
notifications.
If an alert rule has a configured For and the query violates the configured threshold it will first go
from OK to Pending. Going from OK to Pending Grafana will not send any notifications. Once the alert rule
has been firing for more than For duration, it will change to Alerting and send alert notifications.
</info-popover>
</div>
</div>
<div class="gf-form" ng-if="ctrl.frequencyWarning">
<label class="gf-form-label text-warning">
<i
class="fa fa-warning"></i> {{ctrl.frequencyWarning}}
</label>
<label class="gf-form-label text-warning"> <i class="fa fa-warning"></i> {{ctrl.frequencyWarning}} </label>
</div>
</div>
@ -43,37 +43,57 @@
<h4 class="section-heading">Conditions</h4>
<div class="gf-form-inline" ng-repeat="conditionModel in ctrl.conditionModels">
<div class="gf-form">
<metric-segment-model css-class="query-keyword width-5" ng-if="$index"
property="conditionModel.operator.type" options="ctrl.evalOperators"
custom="false"></metric-segment-model>
<metric-segment-model
css-class="query-keyword width-5"
ng-if="$index"
property="conditionModel.operator.type"
options="ctrl.evalOperators"
custom="false"
></metric-segment-model>
<span class="gf-form-label query-keyword width-5" ng-if="$index===0">WHEN</span>
</div>
<div class="gf-form">
<query-part-editor class="gf-form-label query-part width-9"
part="conditionModel.reducerPart"
handle-event="ctrl.handleReducerPartEvent(conditionModel, $event)">
<query-part-editor
class="gf-form-label query-part width-9"
part="conditionModel.reducerPart"
handle-event="ctrl.handleReducerPartEvent(conditionModel, $event)"
>
</query-part-editor>
<span class="gf-form-label query-keyword">OF</span>
</div>
<div class="gf-form">
<query-part-editor class="gf-form-label query-part" part="conditionModel.queryPart"
handle-event="ctrl.handleQueryPartEvent(conditionModel, $event)">
<query-part-editor
class="gf-form-label query-part"
part="conditionModel.queryPart"
handle-event="ctrl.handleQueryPartEvent(conditionModel, $event)"
>
</query-part-editor>
</div>
<div class="gf-form">
<metric-segment-model property="conditionModel.evaluator.type" options="ctrl.evalFunctions"
custom="false" css-class="query-keyword"
on-change="ctrl.evaluatorTypeChanged(conditionModel.evaluator)"></metric-segment-model>
<input class="gf-form-input max-width-9" type="number" step="any"
ng-hide="conditionModel.evaluator.params.length === 0"
ng-model="conditionModel.evaluator.params[0]"
ng-change="ctrl.evaluatorParamsChanged()" />
<label class="gf-form-label query-keyword"
ng-show="conditionModel.evaluator.params.length === 2">TO</label>
<input class="gf-form-input max-width-9" type="number" step="any"
ng-if="conditionModel.evaluator.params.length === 2"
ng-model="conditionModel.evaluator.params[1]"
ng-change="ctrl.evaluatorParamsChanged()" />
<metric-segment-model
property="conditionModel.evaluator.type"
options="ctrl.evalFunctions"
custom="false"
css-class="query-keyword"
on-change="ctrl.evaluatorTypeChanged(conditionModel.evaluator)"
></metric-segment-model>
<input
class="gf-form-input max-width-9"
type="number"
step="any"
ng-hide="conditionModel.evaluator.params.length === 0"
ng-model="conditionModel.evaluator.params[0]"
ng-change="ctrl.evaluatorParamsChanged()"
/>
<label class="gf-form-label query-keyword" ng-show="conditionModel.evaluator.params.length === 2">TO</label>
<input
class="gf-form-input max-width-9"
type="number"
step="any"
ng-if="conditionModel.evaluator.params.length === 2"
ng-model="conditionModel.evaluator.params[1]"
ng-change="ctrl.evaluatorParamsChanged()"
/>
</div>
<div class="gf-form">
<label class="gf-form-label">
@ -107,8 +127,11 @@
<div class="gf-form">
<span class="gf-form-label query-keyword">SET STATE TO</span>
<div class="gf-form-select-wrapper">
<select class="gf-form-input" ng-model="ctrl.alert.noDataState"
ng-options="f.value as f.text for f in ctrl.noDataModes">
<select
class="gf-form-input"
ng-model="ctrl.alert.noDataState"
ng-options="f.value as f.text for f in ctrl.noDataModes"
>
</select>
</div>
</div>
@ -121,8 +144,11 @@
<div class="gf-form">
<span class="gf-form-label query-keyword">SET STATE TO</span>
<div class="gf-form-select-wrapper">
<select class="gf-form-input" ng-model="ctrl.alert.executionErrorState"
ng-options="f.value as f.text for f in ctrl.executionErrorModes">
<select
class="gf-form-input"
ng-model="ctrl.alert.executionErrorState"
ng-options="f.value as f.text for f in ctrl.executionErrorModes"
>
</select>
</div>
</div>
@ -142,27 +168,42 @@
<span class="gf-form-label">
<i class="{{nc.iconClass}}"></i>
&nbsp;{{nc.name}}&nbsp;<span ng-if="nc.isDefault">(default)</span>
<i class="fa fa-remove pointer muted" ng-click="ctrl.removeNotification(nc)" ng-if="nc.isDefault === false"></i>
<icon
name="'times'"
class="pointer muted"
ng-click="ctrl.removeNotification(nc)"
ng-if="nc.isDefault === false"
></icon>
</span>
</div>
<div class="gf-form">
<metric-segment segment="ctrl.addNotificationSegment"
get-options="ctrl.getNotifications()"
on-change="ctrl.notificationAdded()"></metric-segment>
<metric-segment
segment="ctrl.addNotificationSegment"
get-options="ctrl.getNotifications()"
on-change="ctrl.notificationAdded()"
></metric-segment>
</div>
</div>
<div class="gf-form gf-form--v-stretch">
<span class="gf-form-label width-8">Message</span>
<textarea class="gf-form-input" rows="10" ng-model="ctrl.alert.message"
placeholder="Notification message details..."></textarea>
<textarea
class="gf-form-input"
rows="10"
ng-model="ctrl.alert.message"
placeholder="Notification message details..."
></textarea>
</div>
<div class="gf-form">
<span class="gf-form-label width-8">Tags</span>
<div class="gf-form-group">
<div class="gf-form-inline" ng-repeat="(name, value) in ctrl.alert.alertRuleTags">
<label class="gf-form-label width-15">{{ name }}</label>
<input class="gf-form-input width-15" placeholder="Tag value..."
ng-model="ctrl.alert.alertRuleTags[name]" type="text"/>
<input
class="gf-form-input width-15"
placeholder="Tag value..."
ng-model="ctrl.alert.alertRuleTags[name]"
type="text"
/>
<label class="gf-form-label">
<a class="pointer" tabindex="1" ng-click="ctrl.removeAlertRuleTag(name)">
<i class="fa fa-trash"></i>
@ -172,10 +213,18 @@
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<input class="gf-form-input width-15" placeholder="New tag name..."
ng-model="ctrl.newAlertRuleTag.name" type="text">
<input class="gf-form-input width-15" placeholder="New tag value..."
ng-model="ctrl.newAlertRuleTag.value" type="text">
<input
class="gf-form-input width-15"
placeholder="New tag name..."
ng-model="ctrl.newAlertRuleTag.name"
type="text"
/>
<input
class="gf-form-input width-15"
placeholder="New tag value..."
ng-model="ctrl.newAlertRuleTag.value"
type="text"
/>
</div>
</div>
<div class="gf-form">

View File

@ -35,7 +35,7 @@
default
</span>
<a ng-click="ctrl.deleteNotification(notification.id)" class="btn btn-danger btn-small">
<i class="fa fa-remove"></i>
<icon name="'times'"></icon>
</a>
</td>
</tr>

View File

@ -75,42 +75,42 @@ function getStateDisplayModel(state: string) {
case 'ok': {
return {
text: 'OK',
iconClass: 'icon-gf icon-gf-online',
iconClass: 'heart',
stateClass: 'alert-state-ok',
};
}
case 'alerting': {
return {
text: 'ALERTING',
iconClass: 'icon-gf icon-gf-critical',
iconClass: 'heartbeat',
stateClass: 'alert-state-critical',
};
}
case 'no_data': {
return {
text: 'NO DATA',
iconClass: 'fa fa-question',
iconClass: 'question-circle',
stateClass: 'alert-state-warning',
};
}
case 'paused': {
return {
text: 'PAUSED',
iconClass: 'fa fa-pause',
iconClass: 'pause',
stateClass: 'alert-state-paused',
};
}
case 'pending': {
return {
text: 'PENDING',
iconClass: 'fa fa-exclamation',
iconClass: 'exclamation-triangle',
stateClass: 'alert-state-warning',
};
}
case 'unknown': {
return {
text: 'UNKNOWN',
iconClass: 'fa fa-question',
iconClass: 'question-circle',
stateClass: 'alert-state-paused',
};
}

View File

@ -131,7 +131,7 @@ describe('Alert rules', () => {
state: 'alerting',
stateAge: newStateDateAge,
stateClass: 'alert-state-critical',
stateIcon: 'icon-gf icon-gf-critical',
stateIcon: 'heartbeat',
stateText: 'ALERTING',
url: '/d/ggHbN42mk/alerting-with-testdata',
},
@ -149,7 +149,7 @@ describe('Alert rules', () => {
state: 'ok',
stateAge: newStateDateAge,
stateClass: 'alert-state-ok',
stateIcon: 'icon-gf icon-gf-online',
stateIcon: 'heart',
stateText: 'OK',
url: '/d/ggHbN42mk/alerting-with-testdata',
},
@ -168,7 +168,7 @@ describe('Alert rules', () => {
state: 'ok',
stateAge: newStateDateAge,
stateClass: 'alert-state-ok',
stateIcon: 'icon-gf icon-gf-online',
stateIcon: 'heart',
stateText: 'OK',
url: '/d/ggHbN42mk/alerting-with-testdata',
},
@ -186,7 +186,7 @@ describe('Alert rules', () => {
state: 'paused',
stateAge: newStateDateAge,
stateClass: 'alert-state-paused',
stateIcon: 'fa fa-pause',
stateIcon: 'pause',
stateText: 'PAUSED',
url: '/d/ggHbN42mk/alerting-with-testdata',
},
@ -207,7 +207,7 @@ describe('Alert rules', () => {
state: 'ok',
stateAge: newStateDateAge,
stateClass: 'alert-state-ok',
stateIcon: 'icon-gf icon-gf-online',
stateIcon: 'heart',
stateText: 'OK',
url: '/d/ggHbN42mk/alerting-with-testdata',
},

View File

@ -31,8 +31,7 @@
<tbody>
<tr ng-repeat="annotation in ctrl.annotations track by annotation.name">
<td style="width:90%" ng-hide="annotation.builtIn" class="pointer" ng-click="ctrl.edit(annotation)">
<i class="fa fa-comment" style="color:{{ annotation.iconColor }}"></i> &nbsp;
{{ annotation.name }}
<i class="fa fa-comment" style="color:{{ annotation.iconColor }}"></i> &nbsp; {{ annotation.name }}
</td>
<td style="width:90%" ng-show="annotation.builtIn" class="pointer" ng-click="ctrl.edit(annotation)">
<i class="gicon gicon-annotation"></i> &nbsp;
@ -53,7 +52,7 @@
class="btn btn-danger btn-small"
ng-hide="annotation.builtIn"
>
<i class="fa fa-remove"></i>
<icon name="'times'"></icon>
</a>
</td>
</tr>
@ -62,7 +61,14 @@
<!-- empty list cta, there is always one built in query -->
<div ng-if="ctrl.annotations.length === 1" class="p-t-2">
<empty-list-cta title="ctrl.emptyListCta.title" buttonIcon="ctrl.emptyListCta.buttonIcon" buttonTitle="ctrl.emptyListCta.buttonTitle" infoBox="ctrl.emptyListCta.infoBox" infoBoxTitle="ctrl.emptyListCta.infoBoxTitle" on-click="ctrl.setupNew"/>
<empty-list-cta
title="ctrl.emptyListCta.title"
buttonIcon="ctrl.emptyListCta.buttonIcon"
buttonTitle="ctrl.emptyListCta.buttonTitle"
infoBox="ctrl.emptyListCta.infoBox"
infoBoxTitle="ctrl.emptyListCta.infoBoxTitle"
on-click="ctrl.setupNew"
/>
</div>
</div>

View File

@ -1,4 +1,5 @@
import React from 'react';
import { Icon } from '@grafana/ui';
export interface Props {
apiKey: string;
@ -10,12 +11,12 @@ export const ApiKeysAddedModal = (props: Props) => {
<div className="modal-body">
<div className="modal-header">
<h2 className="modal-header-title">
<i className="fa fa-key" />
<Icon name="key-skeleton-alt" />
<span className="p-l-1">API Key Created</span>
</h2>
<a className="modal-header-close" ng-click="dismiss();">
<i className="fa fa-remove" />
<Icon name="times" />
</a>
</div>

View File

@ -13,7 +13,15 @@ import ApiKeysAddedModal from './ApiKeysAddedModal';
import config from 'app/core/config';
import appEvents from 'app/core/app_events';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { DeleteButton, EventsWithValidation, FormLabel, LegacyForms, Switch, ValidationEvents } from '@grafana/ui';
import {
DeleteButton,
EventsWithValidation,
FormLabel,
LegacyForms,
Switch,
ValidationEvents,
Icon,
} from '@grafana/ui';
const { Input } = LegacyForms;
import { dateTime, isDateTime, NavModel } from '@grafana/data';
import { FilterInput } from 'app/core/components/FilterInput/FilterInput';
@ -181,7 +189,7 @@ export class ApiKeysPage extends PureComponent<Props, any> {
<SlideDown in={isAdding}>
<div className="cta-form">
<button className="cta-form__close btn btn-transparent" onClick={this.onToggleAdding}>
<i className="fa fa-close" />
<Icon name="times" />
</button>
<h5>Add API Key</h5>
<form className="gf-form-group" onSubmit={this.onAddApiKey}>

View File

@ -10,8 +10,8 @@ exports[`Render should render component 1`] = `
<h2
className="modal-header-title"
>
<i
className="fa fa-key"
<Icon
name="key-skeleton-alt"
/>
<span
className="p-l-1"
@ -23,8 +23,8 @@ exports[`Render should render component 1`] = `
className="modal-header-close"
ng-click="dismiss();"
>
<i
className="fa fa-remove"
<Icon
name="times"
/>
</a>
</div>

View File

@ -53,8 +53,8 @@ exports[`Render should render CTA if there are no API keys 1`] = `
className="cta-form__close btn btn-transparent"
onClick={[Function]}
>
<i
className="fa fa-close"
<Icon
name="times"
/>
</button>
<h5>

View File

@ -11,15 +11,22 @@
class="btn btn-primary"
ng-click="ctrl.setupNew()"
ng-if="ctrl.dashboard.links.length > 0"
ng-hide="ctrl.mode === 'edit' || ctrl.mode === 'new'">
New
</a
ng-hide="ctrl.mode === 'edit' || ctrl.mode === 'new'"
>
New
</a>
</div>
<div ng-if="ctrl.mode == 'list'">
<div ng-if="ctrl.dashboard.links.length === 0">
<empty-list-cta on-click="ctrl.setupNew" title="ctrl.emptyListCta.title" buttonIcon="ctrl.emptyListCta.buttonIcon" buttonTitle="ctrl.emptyListCta.buttonTitle" infoBox="ctrl.emptyListCta.infoBox" infoBoxTitle="ctrl.emptyListCta.infoBoxTitle"/>
<empty-list-cta
on-click="ctrl.setupNew"
title="ctrl.emptyListCta.title"
buttonIcon="ctrl.emptyListCta.buttonIcon"
buttonTitle="ctrl.emptyListCta.buttonTitle"
infoBox="ctrl.emptyListCta.infoBox"
infoBoxTitle="ctrl.emptyListCta.infoBoxTitle"
/>
</div>
<div ng-if="ctrl.dashboard.links.length > 0">
@ -62,7 +69,7 @@
</td>
<td style="width: 1%">
<a ng-click="ctrl.deleteLink($index)" class="btn btn-danger btn-small" ng-hide="annotation.builtIn">
<i class="fa fa-remove"></i>
<icon name="'times'"></icon>
</a>
</td>
</tr>

View File

@ -112,7 +112,7 @@ export const OverrideFieldConfigEditor: React.FC<Props> = props => {
const renderAddOverride = () => {
return (
<ValuePicker
icon="plus"
icon="plus-circle"
label="Add override"
variant="secondary"
options={fieldMatchersUI

View File

@ -115,7 +115,7 @@ export const OverrideEditor: React.FC<OverrideEditorProps> = ({
<div className={styles.propertyPickerWrapper}>
<ValuePicker
label="Set config property"
icon="plus"
icon="plus-circle"
options={configPropertiesOptions}
variant={'link'}
onChange={o => {

View File

@ -6,7 +6,7 @@
</h2>
<a class="modal-header-close" ng-click="ctrl.dismiss();">
<i class="fa fa-remove"></i>
<icon name="'times'"></icon>
</a>
</div>

View File

@ -9,7 +9,7 @@ const template = `
</h2>
<a class="modal-header-close" ng-click="ctrl.dismiss();">
<i class="fa fa-remove"></i>
<icon name="'times'"></icon>
</a>
</div>

View File

@ -7,7 +7,7 @@ import classNames from 'classnames';
import { css } from 'emotion';
import { ExploreId, ExploreItemState } from 'app/types/explore';
import { ToggleButtonGroup, ToggleButton, Tooltip, LegacyForms, SetInterval } from '@grafana/ui';
import { ToggleButtonGroup, ToggleButton, Tooltip, LegacyForms, SetInterval, Icon } from '@grafana/ui';
const { ButtonSelect } = LegacyForms;
import { RawTimeRange, TimeZone, TimeRange, DataQuery, ExploreMode } from '@grafana/data';
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
@ -202,14 +202,21 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
<div className="explore-toolbar-header-title">
{exploreId === 'left' && (
<span className="navbar-page-btn">
<i className="gicon gicon-explore" />
<Icon
name="compass"
size="xl"
className={css`
margin-right: 6px;
margin-bottom: 3px;
`}
/>
Explore
</span>
)}
</div>
{splitted && (
<a className="explore-toolbar-header-close" onClick={() => closeSplit(exploreId)}>
<i className="fa fa-times fa-fw" />
<Icon name="times" />
</a>
)}
</div>
@ -261,7 +268,7 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
<div className="explore-toolbar-content-item">
<Tooltip content={'Return to panel'} placement="bottom">
<button className={panelReturnClasses} onClick={() => this.returnToPanel()}>
<i className="fa fa-arrow-left" />
<Icon name="arrow-left" />
</button>
</Tooltip>
{originDashboardIsEditable && (
@ -281,7 +288,8 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
splitted={splitted}
title="Split"
onClick={split}
iconClassName="fa fa-fw fa-columns icon-margin-right"
icon="columns"
iconClassName="icon-margin-right"
disabled={isLive}
/>
</div>
@ -307,7 +315,8 @@ export class UnConnectedExploreToolbar extends PureComponent<Props> {
splitted={splitted}
title="Clear All"
onClick={this.onClearAll}
iconClassName="fa fa-fw fa-trash icon-margin-right"
icon="trash-alt"
iconClassName="icon-margin-right"
/>
</div>
)}

View File

@ -128,11 +128,8 @@ export function LiveTailButton(props: LiveTailButtonProps) {
[styles.isLive]: isLive && !isPaused,
[styles.isPaused]: isLive && isPaused,
})}
iconClassName={classNames(
'fa',
isPaused || !isLive ? 'fa-play' : 'fa-pause',
isLive && 'icon-brand-gradient'
)}
icon={!isLive ? 'play' : 'pause'}
iconClassName={isLive && 'icon-brand-gradient'}
onClick={onClickMain}
title={'\xa0Live'}
/>

View File

@ -30,10 +30,10 @@ describe('QueryRowActions', () => {
});
it('should change icon to fa-eye-slash when query row result is hidden', () => {
const wrapper = setup({ isDisabled: true });
expect(wrapper.find('i.fa-eye-slash')).toHaveLength(1);
expect(wrapper.find({ title: 'Enable query' })).toHaveLength(1);
});
it('should change icon to fa-eye when query row result is not hidden', () => {
const wrapper = setup({ isDisabled: false });
expect(wrapper.find('i.fa-eye')).toHaveLength(1);
expect(wrapper.find({ title: 'Disable query' })).toHaveLength(1);
});
});

View File

@ -1,4 +1,5 @@
import React from 'react';
import { Icon } from '@grafana/ui';
function formatLatency(value: number) {
return `${(value / 1000).toFixed(1)}s`;
@ -34,7 +35,7 @@ export function QueryRowActions(props: Props) {
className="gf-form-label gf-form-label--btn"
onClick={onClickToggleEditorMode}
>
<i className="fa fa-pencil" />
<Icon name="pen" />
</button>
</div>
)}
@ -48,14 +49,14 @@ export function QueryRowActions(props: Props) {
disabled={isNotStarted}
className="gf-form-label gf-form-label--btn"
onClick={onClickToggleDisabled}
title="Disable/enable query"
title={isDisabled ? 'Enable query' : 'Disable query'}
>
<i className={isDisabled ? 'fa fa-eye-slash' : 'fa fa-eye'} />
<Icon name={isDisabled ? 'eye' : 'eye-slash'} />
</button>
</div>
<div className="gf-form">
<button className="gf-form-label gf-form-label--btn" onClick={onClickRemoveButton} title="Remove query">
<i className="fa fa-minus" />
<Icon name="minus" />
</button>
</div>
</div>

View File

@ -1,4 +1,5 @@
import React, { forwardRef } from 'react';
import { IconName, Icon } from '@grafana/ui';
export enum IconSide {
left = 'left',
@ -10,6 +11,7 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
title: string;
onClick: () => void;
buttonClassName?: string;
icon?: IconName;
iconClassName?: string;
iconSide?: IconSide;
disabled?: boolean;
@ -25,7 +27,17 @@ export const ResponsiveButton = forwardRef<HTMLDivElement, Props>((props, ref) =
};
props = { ...defaultProps, ...props };
const { title, onClick, buttonClassName, iconClassName, splitted, iconSide, disabled, ...divElementProps } = props;
const {
title,
onClick,
buttonClassName,
icon,
iconClassName,
splitted,
iconSide,
disabled,
...divElementProps
} = props;
return (
<div ref={ref} {...divElementProps}>
@ -34,9 +46,9 @@ export const ResponsiveButton = forwardRef<HTMLDivElement, Props>((props, ref) =
onClick={onClick}
disabled={disabled || false}
>
{iconClassName && iconSide === IconSide.left ? <i className={`${iconClassName}`} /> : null}
{icon && iconSide === IconSide.left ? <Icon name={icon} className={iconClassName} /> : null}
<span className="btn-title">{!splitted ? formatBtnTitle(title, iconSide) : ''}</span>
{iconClassName && iconSide === IconSide.right ? <i className={`${iconClassName}`} /> : null}
{icon && iconSide === IconSide.right ? <Icon name={icon} className={iconClassName} /> : null}
</button>
</div>
);

View File

@ -10,7 +10,7 @@ import { stylesFactory, withTheme } from '@grafana/ui';
//Types
import { RichHistoryQuery, ExploreId } from 'app/types/explore';
import { SelectableValue, GrafanaTheme } from '@grafana/data';
import { TabsBar, Tab, TabContent, Themeable, CustomScrollbar, IconName } from '@grafana/ui';
import { TabsBar, Tab, TabContent, Themeable, CustomScrollbar, IconName, Icon } from '@grafana/ui';
//Components
import { RichHistorySettings } from './RichHistorySettings';
@ -169,7 +169,7 @@ class UnThemedRichHistory extends PureComponent<RichHistoryProps, RichHistorySta
height={height}
/>
),
icon: 'fa fa-history',
icon: 'history',
};
const StarredTab = {
@ -186,7 +186,7 @@ class UnThemedRichHistory extends PureComponent<RichHistoryProps, RichHistorySta
exploreId={exploreId}
/>
),
icon: 'fa fa-star',
icon: 'star',
};
const SettingsTab = {
@ -203,7 +203,7 @@ class UnThemedRichHistory extends PureComponent<RichHistoryProps, RichHistorySta
deleteRichHistory={deleteRichHistory}
/>
),
icon: 'gicon gicon-preferences',
icon: 'sliders-v-alt',
};
let tabs = [QueriesTab, StarredTab, SettingsTab];
@ -220,7 +220,7 @@ class UnThemedRichHistory extends PureComponent<RichHistoryProps, RichHistorySta
/>
))}
<div className={styles.close} onClick={onClose}>
<i className="fa fa-times" title="Close query history" />
<Icon name="times" title="Close query history" />
</div>
</TabsBar>
<CustomScrollbar

View File

@ -79,37 +79,29 @@ describe('RichHistoryCard', () => {
describe('commenting', () => {
it('should render comment, if comment present', () => {
const wrapper = setup({ query: starredQueryWithComment });
expect(wrapper.find({ 'aria-label': 'Query comment' })).toHaveLength(1);
expect(wrapper.find({ 'aria-label': 'Query comment' }).hostNodes()).toHaveLength(1);
expect(wrapper.find({ 'aria-label': 'Query comment' }).text()).toEqual('test comment');
});
it('should have title "Edit comment" at comment icon, if comment present', () => {
const wrapper = setup({ query: starredQueryWithComment });
expect(wrapper.find({ title: 'Edit comment' })).toHaveLength(1);
expect(wrapper.find({ title: 'Add comment' })).toHaveLength(0);
expect(wrapper.find({ title: 'Edit comment' }).hostNodes()).toHaveLength(1);
expect(wrapper.find({ title: 'Add comment' }).hostNodes()).toHaveLength(0);
});
it('should have title "Add comment" at comment icon, if no comment present', () => {
const wrapper = setup();
expect(wrapper.find({ title: 'Add comment' })).toHaveLength(1);
expect(wrapper.find({ title: 'Edit comment' })).toHaveLength(0);
expect(wrapper.find({ title: 'Add comment' }).hostNodes()).toHaveLength(1);
expect(wrapper.find({ title: 'Edit comment' }).hostNodes()).toHaveLength(0);
});
});
describe('starring', () => {
it('should have title "Star query", if not starred', () => {
const wrapper = setup();
expect(wrapper.find({ title: 'Star query' })).toHaveLength(1);
});
it('should render fa-star-o icon, if not starred', () => {
const wrapper = setup();
expect(wrapper.find({ title: 'Star query' }).hasClass('fa-star-o')).toBe(true);
expect(wrapper.find({ title: 'Star query' }).hostNodes()).toHaveLength(1);
});
it('should have title "Unstar query", if not starred', () => {
const wrapper = setup({ query: starredQueryWithComment });
expect(wrapper.find({ title: 'Unstar query' })).toHaveLength(1);
});
it('should have fa-star icon, if not starred', () => {
const wrapper = setup({ query: starredQueryWithComment });
expect(wrapper.find({ title: 'Unstar query' }).hasClass('fa-star')).toBe(true);
expect(wrapper.find({ title: 'Unstar query' }).hostNodes()).toHaveLength(1);
});
});
});

View File

@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { connect } from 'react-redux';
import { hot } from 'react-hot-loader';
import { css, cx } from 'emotion';
import { stylesFactory, useTheme, Forms, Button } from '@grafana/ui';
import { stylesFactory, useTheme, Forms, Button, Icon } from '@grafana/ui';
import { GrafanaTheme, AppEvents, DataSourceApi } from '@grafana/data';
import { RichHistoryQuery, ExploreId } from 'app/types/explore';
import { copyStringToClipboard, createUrlFromRichHistory, createDataQuery } from 'app/core/utils/richHistory';
@ -76,9 +76,8 @@ const getStyles = stylesFactory((theme: GrafanaTheme, isRemoved: boolean) => {
display: flex;
justify-content: flex-end;
font-size: ${theme.typography.size.base};
i {
margin: ${theme.spacing.xs};
cursor: pointer;
svg {
margin-left: ${theme.spacing.sm};
}
`,
queryContainer: css`
@ -212,19 +211,20 @@ export function RichHistoryCard(props: Props) {
const queryActionButtons = (
<div className={styles.queryActionButtons}>
<i
className="fa fa-fw fa-comment-o"
<Icon
name="comment-alt"
onClick={toggleActiveUpdateComment}
title={query.comment?.length > 0 ? 'Edit comment' : 'Add comment'}
></i>
<i className="fa fa-fw fa-copy" onClick={onCopyQuery} title="Copy query to clipboard"></i>
{!isRemoved && <i className="fa fa-fw fa-link" onClick={onCreateLink} title="Copy link to clipboard"></i>}
<i className={'fa fa-trash'} title={'Delete query'} onClick={onDeleteQuery}></i>
<i
className={cx('fa fa-fw', query.starred ? 'fa-star starred' : 'fa-star-o')}
/>
<Icon name="copy" onClick={onCopyQuery} title="Copy query to clipboard" />
{!isRemoved && <Icon name="link" onClick={onCreateLink} title="Copy link to clipboard" />}
<Icon name="trash-alt" title={'Delete query'} onClick={onDeleteQuery} />
<Icon
name={query.starred ? 'favorite' : 'star'}
type={query.starred ? 'mono' : 'default'}
onClick={onStarrQuery}
title={query.starred ? 'Unstar query' : 'Star query'}
></i>
/>
</div>
);

View File

@ -40,7 +40,8 @@ export function RunButton(props: Props) {
'navbar-button--danger': loading,
'btn--radius-right-0': showDropdown,
})}
iconClassName={loading ? 'fa fa-spinner fa-fw fa-spin run-icon' : 'fa fa-refresh fa-fw'}
icon={loading ? 'fa fa-spinner' : 'sync'}
iconClassName={loading && ' fa-spin run-icon'}
/>
);

View File

@ -12,8 +12,8 @@ exports[`QueryRowActions should render component 1`] = `
className="gf-form-label gf-form-label--btn"
onClick={[Function]}
>
<i
className="fa fa-pencil"
<Icon
name="pen"
/>
</button>
</div>
@ -35,10 +35,10 @@ exports[`QueryRowActions should render component 1`] = `
className="gf-form-label gf-form-label--btn"
disabled={true}
onClick={[Function]}
title="Disable/enable query"
title="Disable query"
>
<i
className="fa fa-eye"
<Icon
name="eye-slash"
/>
</button>
</div>
@ -50,8 +50,8 @@ exports[`QueryRowActions should render component 1`] = `
onClick={[Function]}
title="Remove query"
>
<i
className="fa fa-minus"
<Icon
name="minus"
/>
</button>
</div>

View File

@ -6,7 +6,7 @@
</h2>
<a class="modal-header-close" ng-click="ctrl.dismiss();">
<i class="fa fa-remove"></i>
<icon name="'times'"></icon>
</a>
</div>

View File

@ -2,7 +2,7 @@
<span style="position: relative;">
<input
type="text"
placeholder="Find dashboards by name"
placeholder="Search dashboards by name"
tabindex="1"
ng-keydown="ctrl.keyDown($event)"
ng-model="ctrl.query.query"

View File

@ -46,7 +46,7 @@
</td>
<td class="text-right">
<a ng-click="ctrl.removePlaylist(playlist)" class="btn btn-danger btn-small">
<i class="fa fa-remove"></i>
<icon name="'times'"></icon>
</a>
</td>
</tr>

View File

@ -6,7 +6,7 @@
</h2>
<a class="modal-header-close" ng-click="dismiss();">
<i class="fa fa-remove"></i>
<icon name="'times'"></icon>
</a>
</div>

View File

@ -24,7 +24,7 @@
<td>{{ session.browser }} on {{ session.os }} {{ session.osVersion }}</td>
<td>
<button class="btn btn-danger btn-small" ng-click="ctrl.revokeUserSession(session.id)">
<i class="fa fa-power-off"></i>
<icon name="'power'" style="margin-top: -2px;"></icon>
</button>
</td>
</tr>

View File

@ -66,7 +66,7 @@ export class TeamGroupSync extends PureComponent<Props, State> {
<td>{group.groupId}</td>
<td style={{ width: '1%' }}>
<a className="btn btn-danger btn-small" onClick={() => this.onRemoveGroup(group)}>
<i className="fa fa-remove" />
<Icon name="times" />
</a>
</td>
</tr>
@ -95,7 +95,7 @@ export class TeamGroupSync extends PureComponent<Props, State> {
<SlideDown in={isAdding}>
<div className="cta-form">
<button className="cta-form__close btn btn-transparent" onClick={this.onToggleAdding}>
<i className="fa fa-close" />
<Icon name="times" />
</button>
<h5>Add External Group</h5>
<form className="gf-form-inline" onSubmit={this.onAddGroup}>

View File

@ -1,5 +1,6 @@
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
import { Icon } from '@grafana/ui';
import { SlideDown } from 'app/core/components/Animations/SlideDown';
import { UserPicker } from 'app/core/components/Select/UserPicker';
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
@ -97,7 +98,7 @@ export class TeamMembers extends PureComponent<Props, State> {
<SlideDown in={isAdding}>
<div className="cta-form">
<button className="cta-form__close btn btn-transparent" onClick={this.onToggleAdding}>
<i className="fa fa-close" />
<Icon name="times" />
</button>
<h5>Add team member</h5>
<div className="gf-form-inline">

View File

@ -33,8 +33,8 @@ exports[`Render should render component 1`] = `
className="cta-form__close btn btn-transparent"
onClick={[Function]}
>
<i
className="fa fa-close"
<Icon
name="times"
/>
</button>
<h5>
@ -124,8 +124,8 @@ exports[`Render should render groups table 1`] = `
className="cta-form__close btn btn-transparent"
onClick={[Function]}
>
<i
className="fa fa-close"
<Icon
name="times"
/>
</button>
<h5>
@ -198,8 +198,8 @@ exports[`Render should render groups table 1`] = `
className="btn btn-danger btn-small"
onClick={[Function]}
>
<i
className="fa fa-remove"
<Icon
name="times"
/>
</a>
</td>
@ -221,8 +221,8 @@ exports[`Render should render groups table 1`] = `
className="btn btn-danger btn-small"
onClick={[Function]}
>
<i
className="fa fa-remove"
<Icon
name="times"
/>
</a>
</td>
@ -244,8 +244,8 @@ exports[`Render should render groups table 1`] = `
className="btn btn-danger btn-small"
onClick={[Function]}
>
<i
className="fa fa-remove"
<Icon
name="times"
/>
</a>
</td>

View File

@ -37,8 +37,8 @@ exports[`Render should render component 1`] = `
className="cta-form__close btn btn-transparent"
onClick={[Function]}
>
<i
className="fa fa-close"
<Icon
name="times"
/>
</button>
<h5>
@ -131,8 +131,8 @@ exports[`Render should render team members 1`] = `
className="cta-form__close btn btn-transparent"
onClick={[Function]}
>
<i
className="fa fa-close"
<Icon
name="times"
/>
</button>
<h5>

View File

@ -94,7 +94,7 @@
class="btn btn-danger btn-small"
aria-label="{{::selectors.tableRowRemoveButtons(variable.name)}}"
>
<i class="fa fa-remove"></i>
<icon name="'times'"></icon>
</a>
</td>
</tr>

View File

@ -2,6 +2,7 @@ import React, { createRef, PureComponent } from 'react';
import { connect } from 'react-redux';
import { Invitee } from 'app/types';
import { revokeInvite } from './state/actions';
import { Icon } from '@grafana/ui';
export interface Props {
invitee: Invitee;
@ -40,7 +41,7 @@ class InviteeRow extends PureComponent<Props> {
</td>
<td>
<button className="btn btn-danger btn-small" onClick={() => revokeInvite(invitee.code)}>
<i className="fa fa-remove" />
<Icon name="times" />
</button>
</td>
</tr>

View File

@ -1,5 +1,6 @@
import React, { FC } from 'react';
import { OrgUser } from 'app/types';
import { Icon } from '@grafana/ui';
export interface Props {
users: OrgUser[];
@ -55,7 +56,7 @@ const UsersTable: FC<Props> = props => {
</td>
<td>
<div onClick={() => onRemoveUser(user)} className="btn btn-danger btn-small">
<i className="fa fa-remove" />
<Icon name="times" />
</div>
</td>
</tr>

View File

@ -127,8 +127,8 @@ exports[`Render should render users table 1`] = `
className="btn btn-danger btn-small"
onClick={[Function]}
>
<i
className="fa fa-remove"
<Icon
name="times"
/>
</div>
</td>
@ -193,8 +193,8 @@ exports[`Render should render users table 1`] = `
className="btn btn-danger btn-small"
onClick={[Function]}
>
<i
className="fa fa-remove"
<Icon
name="times"
/>
</div>
</td>
@ -259,8 +259,8 @@ exports[`Render should render users table 1`] = `
className="btn btn-danger btn-small"
onClick={[Function]}
>
<i
className="fa fa-remove"
<Icon
name="times"
/>
</div>
</td>
@ -325,8 +325,8 @@ exports[`Render should render users table 1`] = `
className="btn btn-danger btn-small"
onClick={[Function]}
>
<i
className="fa fa-remove"
<Icon
name="times"
/>
</div>
</td>
@ -391,8 +391,8 @@ exports[`Render should render users table 1`] = `
className="btn btn-danger btn-small"
onClick={[Function]}
>
<i
className="fa fa-remove"
<Icon
name="times"
/>
</div>
</td>
@ -457,8 +457,8 @@ exports[`Render should render users table 1`] = `
className="btn btn-danger btn-small"
onClick={[Function]}
>
<i
className="fa fa-remove"
<Icon
name="times"
/>
</div>
</td>

View File

@ -3,6 +3,7 @@ import { e2e } from '@grafana/e2e';
import EmptyListCTA from '../../../core/components/EmptyListCTA/EmptyListCTA';
import { QueryVariableModel, VariableModel } from '../../templating/types';
import { toVariableIdentifier, VariableIdentifier } from '../state/types';
import { Icon } from '@grafana/ui';
export interface Props {
variables: VariableModel[];
@ -148,7 +149,7 @@ export class VariableEditorList extends PureComponent<Props> {
variable.name
)}
>
<i className="fa fa-remove" />
<Icon name="times" />
</a>
</td>
</tr>

View File

@ -1,34 +1,51 @@
<div class="modal-body" ng-cloak>
<div class="modal-header">
<h2 class="modal-header-title">
<i class="fa {{icon}}"></i>
<span class="p-l-1">
{{title}}
</span>
</h2>
<div class="modal-header">
<h2 class="modal-header-title">
<i class="fa {{icon}}"></i>
<span class="p-l-1">
{{title}}
</span>
</h2>
<a class="modal-header-close" ng-click="dismiss();">
<i class="fa fa-remove"></i>
</a>
</div>
<a class="modal-header-close" ng-click="dismiss();">
<icon name="'times'"></icon>
</a>
</div>
<div class="modal-content text-center">
<div class="confirm-modal-text">
{{text}}
<div ng-if="text2 && text2htmlBind" class="confirm-modal-text2" ng-bind-html="text2"></div>
<div class="modal-content text-center">
<div class="confirm-modal-text">
{{text}}
<div ng-if="text2 && text2htmlBind" class="confirm-modal-text2" ng-bind-html="text2"></div>
<div ng-if="text2 && !text2htmlBind" class="confirm-modal-text2">{{text2}}</div>
</div>
</div>
<div class="modal-content-confirm-text" ng-if="confirmText">
<input type="text" class="gf-form-input width-16" style="display: inline-block;" placeholder="Type {{confirmText}} to confirm" ng-model="confirmInput" ng-change="updateConfirmText(confirmInput)">
</div>
<div class="confirm-modal-buttons">
<button ng-show="onAltAction" type="button" class="btn btn-primary" ng-click="dismiss();onAltAction();">{{altActionText}}</button>
<button ng-show="onConfirm" type="button" class="btn btn-danger" ng-click="onConfirm();dismiss();" ng-disabled="!confirmTextValid" give-focus="true" aria-label={{selectors.delete}}>{{yesText}}</button>
<button type="button" class="btn btn-inverse" ng-click="dismiss()">{{noText}}</button>
</div>
</div>
<div class="modal-content-confirm-text" ng-if="confirmText">
<input
type="text"
class="gf-form-input width-16"
style="display: inline-block;"
placeholder="Type {{confirmText}} to confirm"
ng-model="confirmInput"
ng-change="updateConfirmText(confirmInput)"
/>
</div>
<div class="confirm-modal-buttons">
<button ng-show="onAltAction" type="button" class="btn btn-primary" ng-click="dismiss();onAltAction();">
{{altActionText}}
</button>
<button
ng-show="onConfirm"
type="button"
class="btn btn-danger"
ng-click="onConfirm();dismiss();"
ng-disabled="!confirmTextValid"
give-focus="true"
aria-label="{{selectors.delete}}"
>
{{yesText}}
</button>
<button type="button" class="btn btn-inverse" ng-click="dismiss()">{{noText}}</button>
</div>
</div>
</div>

View File

@ -5,7 +5,7 @@
</h2>
<button class="tabbed-view-close-btn" ng-click="dismiss()">
<i class="fa fa-remove"></i>
<icon name="'times'"></icon>
</button>
</div>

View File

@ -67,7 +67,7 @@ export const DataLinks = (props: Props) => {
className={css`
margin-right: 10px;
`}
icon="plus"
icon="plus-circle"
onClick={event => {
event.preventDefault();
const newDataLinks = [...(value || []), { field: '', url: '' }];

View File

@ -1,16 +1,33 @@
<query-editor-row query-ctrl="ctrl" can-collapse="false" has-text-edit-mode="ctrl.target.queryType === 'Application Insights'">
<query-editor-row
query-ctrl="ctrl"
can-collapse="false"
has-text-edit-mode="ctrl.target.queryType === 'Application Insights'"
>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Service</label>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
<select class="gf-form-input service-dropdown" ng-model="ctrl.target.queryType" ng-options="f as f for f in ['Application Insights', 'Azure Monitor', 'Azure Log Analytics']"
ng-change="ctrl.onQueryTypeChange()"></select>
<select
class="gf-form-input service-dropdown"
ng-model="ctrl.target.queryType"
ng-options="f as f for f in ['Application Insights', 'Azure Monitor', 'Azure Log Analytics']"
ng-change="ctrl.onQueryTypeChange()"
></select>
</div>
</div>
<div class="gf-form" ng-if="ctrl.target.queryType === 'Azure Monitor' || ctrl.target.queryType === 'Azure Log Analytics'">
<div
class="gf-form"
ng-if="ctrl.target.queryType === 'Azure Monitor' || ctrl.target.queryType === 'Azure Log Analytics'"
>
<label class="gf-form-label query-keyword width-9">Subscription</label>
<gf-form-dropdown model="ctrl.target.subscription" allow-custom="true" lookup-text="true"
get-options="ctrl.getSubscriptions()" on-change="ctrl.onSubscriptionChange()" css-class="min-width-12">
<gf-form-dropdown
model="ctrl.target.subscription"
allow-custom="true"
lookup-text="true"
get-options="ctrl.getSubscriptions()"
on-change="ctrl.onSubscriptionChange()"
css-class="min-width-12"
>
</gf-form-dropdown>
</div>
<div class="gf-form gf-form--grow">
@ -21,20 +38,38 @@
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Resource Group</label>
<gf-form-dropdown model="ctrl.target.azureMonitor.resourceGroup" allow-custom="true" lookup-text="true"
get-options="ctrl.getResourceGroups($query)" on-change="ctrl.onResourceGroupChange()" css-class="min-width-12">
<gf-form-dropdown
model="ctrl.target.azureMonitor.resourceGroup"
allow-custom="true"
lookup-text="true"
get-options="ctrl.getResourceGroups($query)"
on-change="ctrl.onResourceGroupChange()"
css-class="min-width-12"
>
</gf-form-dropdown>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Namespace</label>
<gf-form-dropdown model="ctrl.target.azureMonitor.metricDefinition" allow-custom="true" lookup-text="true"
get-options="ctrl.getMetricDefinitions($query)" on-change="ctrl.onMetricDefinitionChange()" css-class="min-width-20">
<gf-form-dropdown
model="ctrl.target.azureMonitor.metricDefinition"
allow-custom="true"
lookup-text="true"
get-options="ctrl.getMetricDefinitions($query)"
on-change="ctrl.onMetricDefinitionChange()"
css-class="min-width-20"
>
</gf-form-dropdown>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Resource Name</label>
<gf-form-dropdown model="ctrl.target.azureMonitor.resourceName" allow-custom="true" lookup-text="true"
get-options="ctrl.getResourceNames($query)" on-change="ctrl.onResourceNameChange()" css-class="min-width-12">
<gf-form-dropdown
model="ctrl.target.azureMonitor.resourceName"
allow-custom="true"
lookup-text="true"
get-options="ctrl.getResourceNames($query)"
on-change="ctrl.onResourceNameChange()"
css-class="min-width-12"
>
</gf-form-dropdown>
</div>
<div class="gf-form gf-form--grow">
@ -44,21 +79,37 @@
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Metric Namespace</label>
<gf-form-dropdown model="ctrl.target.azureMonitor.metricNamespace" allow-custom="true" lookup-text="true"
get-options="ctrl.getMetricNamespaces($query)" on-change="ctrl.onMetricNamespacesChange()" css-class="min-width-12">
<gf-form-dropdown
model="ctrl.target.azureMonitor.metricNamespace"
allow-custom="true"
lookup-text="true"
get-options="ctrl.getMetricNamespaces($query)"
on-change="ctrl.onMetricNamespacesChange()"
css-class="min-width-12"
>
</gf-form-dropdown>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Metric</label>
<gf-form-dropdown model="ctrl.target.azureMonitor.metricName" allow-custom="true" lookup-text="true"
get-options="ctrl.getMetricNames($query)" on-change="ctrl.onMetricNameChange()" css-class="min-width-12">
<gf-form-dropdown
model="ctrl.target.azureMonitor.metricName"
allow-custom="true"
lookup-text="true"
get-options="ctrl.getMetricNames($query)"
on-change="ctrl.onMetricNameChange()"
css-class="min-width-12"
>
</gf-form-dropdown>
</div>
<div class="gf-form gf-form--grow aggregation-dropdown-wrapper">
<label class="gf-form-label query-keyword width-9">Aggregation</label>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
<select class="gf-form-input width-11" ng-model="ctrl.target.azureMonitor.aggregation" ng-options="f as f for f in ctrl.target.azureMonitor.aggOptions"
ng-change="ctrl.refresh()"></select>
<select
class="gf-form-input width-11"
ng-model="ctrl.target.azureMonitor.aggregation"
ng-options="f as f for f in ctrl.target.azureMonitor.aggOptions"
ng-change="ctrl.refresh()"
></select>
</div>
</div>
</div>
@ -66,8 +117,12 @@
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Time Grain</label>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent timegrainunit-dropdown-wrapper">
<select class="gf-form-input" ng-model="ctrl.target.azureMonitor.timeGrain" ng-options="f.value as f.text for f in ctrl.target.azureMonitor.timeGrains"
ng-change="ctrl.refresh()"></select>
<select
class="gf-form-input"
ng-model="ctrl.target.azureMonitor.timeGrain"
ng-options="f.value as f.text for f in ctrl.target.azureMonitor.timeGrains"
ng-change="ctrl.refresh()"
></select>
</div>
</div>
<div class="gf-form" ng-show="ctrl.target.azureMonitor.timeGrain.trim() === 'auto'">
@ -82,19 +137,35 @@
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Dimension</label>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
<select class="gf-form-input min-width-12" ng-model="ctrl.target.azureMonitor.dimension" ng-options="f.value as f.text for f in ctrl.target.azureMonitor.dimensions"
ng-change="ctrl.refresh()"></select>
<select
class="gf-form-input min-width-12"
ng-model="ctrl.target.azureMonitor.dimension"
ng-options="f.value as f.text for f in ctrl.target.azureMonitor.dimensions"
ng-change="ctrl.refresh()"
></select>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-3">eq</label>
<input type="text" class="gf-form-input width-17" ng-model="ctrl.target.azureMonitor.dimensionFilter"
spellcheck="false" placeholder="auto" ng-blur="ctrl.refresh()">
<input
type="text"
class="gf-form-input width-17"
ng-model="ctrl.target.azureMonitor.dimensionFilter"
spellcheck="false"
placeholder="auto"
ng-blur="ctrl.refresh()"
/>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Top</label>
<input type="text" class="gf-form-input width-3" ng-model="ctrl.target.azureMonitor.top"
spellcheck="false" placeholder="10" ng-blur="ctrl.refresh()">
<input
type="text"
class="gf-form-input width-3"
ng-model="ctrl.target.azureMonitor.top"
spellcheck="false"
placeholder="10"
ng-blur="ctrl.refresh()"
/>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
@ -103,8 +174,14 @@
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Legend Format</label>
<input type="text" class="gf-form-input width-30" ng-model="ctrl.target.azureMonitor.alias" spellcheck="false"
placeholder="alias patterns (see help for more info)" ng-blur="ctrl.refresh()">
<input
type="text"
class="gf-form-input width-30"
ng-model="ctrl.target.azureMonitor.alias"
spellcheck="false"
placeholder="alias patterns (see help for more info)"
ng-blur="ctrl.refresh()"
/>
</div>
<div class="gf-form gf-form--grow">
@ -117,8 +194,14 @@
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Workspace</label>
<gf-form-dropdown model="ctrl.target.azureLogAnalytics.workspace" allow-custom="true" lookup-text="true"
get-options="ctrl.getWorkspaces()" on-change="ctrl.refresh()" css-class="min-width-12">
<gf-form-dropdown
model="ctrl.target.azureLogAnalytics.workspace"
allow-custom="true"
lookup-text="true"
get-options="ctrl.getWorkspaces()"
on-change="ctrl.refresh()"
css-class="min-width-12"
>
</gf-form-dropdown>
<div class="gf-form">
<div class="width-1"></div>
@ -150,8 +233,12 @@
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">Format As</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input gf-size-auto" ng-model="ctrl.target.azureLogAnalytics.resultFormat" ng-options="f.value as f.text for f in ctrl.resultFormats"
ng-change="ctrl.refresh()"></select>
<select
class="gf-form-input gf-size-auto"
ng-model="ctrl.target.azureLogAnalytics.resultFormat"
ng-options="f.value as f.text for f in ctrl.resultFormats"
ng-change="ctrl.refresh()"
></select>
</div>
</div>
<div class="gf-form">
@ -211,7 +298,6 @@
- | summarize count() by Category, bin(TimeGenerated, $__interval)
</pre>
</div>
</div>
<div ng-if="ctrl.target.queryType === 'Application Insights'">
@ -219,16 +305,25 @@
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Metric</label>
<gf-form-dropdown model="ctrl.target.appInsights.metricName" allow-custom="true" lookup-text="true"
get-options="ctrl.getAppInsightsMetricNames($query)" on-change="ctrl.onAppInsightsMetricNameChange()"
css-class="min-width-20">
<gf-form-dropdown
model="ctrl.target.appInsights.metricName"
allow-custom="true"
lookup-text="true"
get-options="ctrl.getAppInsightsMetricNames($query)"
on-change="ctrl.onAppInsightsMetricNameChange()"
css-class="min-width-20"
>
</gf-form-dropdown>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Aggregation</label>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
<select class="gf-form-input" ng-model="ctrl.target.appInsights.aggregation" ng-options="f as f for f in ctrl.target.appInsights.aggOptions"
ng-change="ctrl.refresh()"></select>
<select
class="gf-form-input"
ng-model="ctrl.target.appInsights.aggregation"
ng-options="f as f for f in ctrl.target.appInsights.aggOptions"
ng-change="ctrl.refresh()"
></select>
</div>
</div>
<div class="gf-form gf-form--grow">
@ -238,20 +333,35 @@
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Group By</label>
<gf-form-dropdown allow-custom="true" ng-hide="ctrl.target.appInsights.dimension !== 'none'" model="ctrl.target.appInsights.dimension"
lookup-text="true" get-options="ctrl.getAppInsightsGroupBySegments($query)" on-change="ctrl.refresh()"
css-class="min-width-20">
<gf-form-dropdown
allow-custom="true"
ng-hide="ctrl.target.appInsights.dimension !== 'none'"
model="ctrl.target.appInsights.dimension"
lookup-text="true"
get-options="ctrl.getAppInsightsGroupBySegments($query)"
on-change="ctrl.refresh()"
css-class="min-width-20"
>
</gf-form-dropdown>
<label class="gf-form-label min-width-20 pointer" ng-hide="ctrl.target.appInsights.dimension === 'none'"
ng-click="ctrl.resetAppInsightsGroupBy()">{{ctrl.target.appInsights.dimension}}
<i class="fa fa-remove"></i>
<label
class="gf-form-label min-width-20 pointer"
ng-hide="ctrl.target.appInsights.dimension === 'none'"
ng-click="ctrl.resetAppInsightsGroupBy()"
>{{ctrl.target.appInsights.dimension}}
<icon name="'times'"></icon>
</label>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Filter</label>
<input type="text" class="gf-form-input width-17" ng-model="ctrl.target.appInsights.dimensionFilter" spellcheck="false"
placeholder="your/groupby eq 'a_value'" ng-blur="ctrl.refresh()">
<input
type="text"
class="gf-form-input width-17"
ng-model="ctrl.target.appInsights.dimensionFilter"
spellcheck="false"
placeholder="your/groupby eq 'a_value'"
ng-blur="ctrl.refresh()"
/>
</div>
</div>
<div class="gf-form gf-form--grow">
@ -262,22 +372,42 @@
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Time Grain</label>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent">
<select class="gf-form-input" ng-model="ctrl.target.appInsights.timeGrainType" ng-options="f as f for f in ['auto', 'none', 'specific']"
ng-change="ctrl.updateTimeGrainType()"></select>
<select
class="gf-form-input"
ng-model="ctrl.target.appInsights.timeGrainType"
ng-options="f as f for f in ['auto', 'none', 'specific']"
ng-change="ctrl.updateTimeGrainType()"
></select>
</div>
</div>
<div class="gf-form" ng-hide="ctrl.target.appInsights.timeGrainType === 'auto' || ctrl.target.appInsights.timeGrainType === 'none'">
<input type="text" class="gf-form-input width-3" ng-model="ctrl.target.appInsights.timeGrainCount" spellcheck="false"
placeholder="" ng-blur="ctrl.updateAppInsightsTimeGrain()">
<div
class="gf-form"
ng-hide="ctrl.target.appInsights.timeGrainType === 'auto' || ctrl.target.appInsights.timeGrainType === 'none'"
>
<input
type="text"
class="gf-form-input width-3"
ng-model="ctrl.target.appInsights.timeGrainCount"
spellcheck="false"
placeholder=""
ng-blur="ctrl.updateAppInsightsTimeGrain()"
/>
</div>
<div class="gf-form" ng-hide="ctrl.target.appInsights.timeGrainType === 'auto' || ctrl.target.appInsights.timeGrainType === 'none'">
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent timegrainunit-dropdown-wrapper">
<select class="gf-form-input" ng-model="ctrl.target.appInsights.timeGrainUnit" ng-options="f as f for f in ['minute', 'hour', 'day', 'month', 'year']"
ng-change="ctrl.updateAppInsightsTimeGrain()"></select>
</div>
<div
class="gf-form"
ng-hide="ctrl.target.appInsights.timeGrainType === 'auto' || ctrl.target.appInsights.timeGrainType === 'none'"
>
<div class="gf-form-select-wrapper gf-form-select-wrapper--caret-indent timegrainunit-dropdown-wrapper">
<select
class="gf-form-input"
ng-model="ctrl.target.appInsights.timeGrainUnit"
ng-options="f as f for f in ['minute', 'hour', 'day', 'month', 'year']"
ng-change="ctrl.updateAppInsightsTimeGrain()"
></select>
</div>
</div>
<div class="gf-form" ng-hide="ctrl.target.appInsights.timeGrainType !== 'auto'">
<label class="gf-form-label">Auto Interval</label>
<label class="gf-form-label">Auto Interval</label>
<label class="gf-form-label">{{ctrl.getAppInsightsAutoInterval()}}</label>
</div>
<div class="gf-form gf-form--grow">
@ -287,8 +417,14 @@
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Legend Format</label>
<input type="text" class="gf-form-input width-30" ng-model="ctrl.target.appInsights.alias" spellcheck="false"
placeholder="alias patterns (see help for more info)" ng-blur="ctrl.refresh()">
<input
type="text"
class="gf-form-input width-30"
ng-model="ctrl.target.appInsights.alias"
spellcheck="false"
placeholder="alias patterns (see help for more info)"
ng-blur="ctrl.refresh()"
/>
</div>
</div>
<div class="gf-form gf-form--grow">
@ -314,14 +450,25 @@
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">X-axis</label>
<gf-form-dropdown model="ctrl.target.appInsights.timeColumn" allow-custom="true" placeholder="eg. 'timestamp'"
get-options="ctrl.getAppInsightsColumns($query)" on-change="ctrl.onAppInsightsColumnChange()" css-class="min-width-20">
<gf-form-dropdown
model="ctrl.target.appInsights.timeColumn"
allow-custom="true"
placeholder="eg. 'timestamp'"
get-options="ctrl.getAppInsightsColumns($query)"
on-change="ctrl.onAppInsightsColumnChange()"
css-class="min-width-20"
>
</gf-form-dropdown>
</div>
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Y-axis</label>
<gf-form-dropdown model="ctrl.target.appInsights.valueColumn" allow-custom="true" get-options="ctrl.getAppInsightsColumns($query)"
on-change="ctrl.onAppInsightsColumnChange()" css-class="min-width-20">
<gf-form-dropdown
model="ctrl.target.appInsights.valueColumn"
allow-custom="true"
get-options="ctrl.getAppInsightsColumns($query)"
on-change="ctrl.onAppInsightsColumnChange()"
css-class="min-width-20"
>
</gf-form-dropdown>
</div>
<div class="gf-form gf-form--grow">
@ -331,8 +478,13 @@
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label query-keyword width-9">Split On</label>
<gf-form-dropdown model="ctrl.target.appInsights.segmentColumn" allow-custom="true" get-options="ctrl.getAppInsightsColumns($query)"
on-change="ctrl.onAppInsightsColumnChange()" css-class="min-width-20">
<gf-form-dropdown
model="ctrl.target.appInsights.segmentColumn"
allow-custom="true"
get-options="ctrl.getAppInsightsColumns($query)"
on-change="ctrl.onAppInsightsColumnChange()"
css-class="min-width-20"
>
</gf-form-dropdown>
</div>
<div class="gf-form gf-form--grow">

View File

@ -1,4 +1,5 @@
import React from 'react';
import { Icon } from '@grafana/ui';
export interface FunctionDescriptor {
text: string;
@ -21,12 +22,13 @@ export interface FunctionEditorControlsProps {
const FunctionHelpButton = (props: { description: string; name: string; onDescriptionShow: () => void }) => {
if (props.description) {
return <span className="pointer fa fa-question-circle" onClick={props.onDescriptionShow} />;
return <Icon className="pointer" name="question-circle" onClick={props.onDescriptionShow} />;
}
return (
<span
className="pointer fa fa-question-circle"
<Icon
className="pointer"
name="question-circle"
onClick={() => {
window.open(
'http://graphite.readthedocs.org/en/latest/functions.html#graphite.render.functions.' + props.name,
@ -58,8 +60,8 @@ export const FunctionEditorControls = (
description={func.def.description}
onDescriptionShow={onDescriptionShow}
/>
<span className="pointer fa fa-remove" onClick={() => onRemove(func)} />
<span className="pointer fa fa-arrow-right" onClick={() => onMoveRight(func)} />
<Icon name="times" onClick={() => onRemove(func)} />
<Icon name="arrow-right" onClick={() => onMoveRight(func)} />
</div>
);
};

View File

@ -135,7 +135,7 @@
<div class="gf-form">
<label class="gf-form-label query-keyword width-7">ORDER BY</label>
<label class="gf-form-label pointer" ng-click="ctrl.removeOrderByTime()"
>time <span class="query-keyword">DESC</span> <i class="fa fa-remove"></i
>time <span class="query-keyword">DESC</span> <icon name="'times'"></icon
></label>
</div>
<div class="gf-form gf-form--grow">

View File

@ -70,7 +70,7 @@ export const DerivedFields = (props: Props) => {
className={css`
margin-right: 10px;
`}
icon="plus"
icon="plus-circle"
onClick={event => {
event.preventDefault();
const newDerivedFields = [...(value || []), { name: '', matcherRegex: '' }];

View File

@ -107,7 +107,7 @@
<i class="fa fa-pencil"></i>
</a>
<a ng-click="ctrl.removeFilter($index)">
<i class="fa fa-remove"></i>
<icon name="'times'"></icon>
</a>
</div>
<label class="gf-form-label query-keyword" ng-hide="ctrl.addFilterMode">
@ -154,7 +154,7 @@
<label class="gf-form-label">
<a ng-click="ctrl.addFilter()" ng-hide="ctrl.errors.filters">add filter</a>
<a ng-click="ctrl.closeAddFilterMode()">
<i class="fa fa-remove"></i>
<icon name="'times'"></icon>
</a>
</label>
</div>
@ -183,7 +183,7 @@
<i class="fa fa-pencil"></i>
</a>
<a ng-click="ctrl.removeTag(key)">
<i class="fa fa-remove"></i>
<icon name="'times'"></icon>
</a>
</label>
</div>
@ -213,7 +213,7 @@
</label>
<label class="gf-form-label" >
<a ng-click="ctrl.addTag()" ng-hide="ctrl.errors.tags">add tag</a>
<a ng-click="ctrl.closeAddTagMode()"><i class="fa fa-remove"></i></a>
<a ng-click="ctrl.closeAddTagMode()"><icon name="'times'"></icon></a>
</label>
</div>

View File

@ -13,7 +13,7 @@ export interface Props {
}
const removeText = '-- remove filter --';
const removeOption: SelectableValue<string> = { label: removeText, value: removeText, icon: 'fa fa-remove' };
const removeOption: SelectableValue<string> = { label: removeText, value: removeText, icon: 'times' };
const operators = ['=', '!=', '=~', '!=~'];
export const LabelFilter: FunctionComponent<Props> = ({

View File

@ -2,6 +2,7 @@
import React, { PureComponent } from 'react';
import { PanelProps } from '@grafana/data';
import { Icon } from '@grafana/ui';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { backendSrv } from 'app/core/services/backend_srv';
import { contextSrv } from 'app/core/core';
@ -144,7 +145,7 @@ export class GettingStarted extends PureComponent<PanelProps, State> {
return (
<div className="progress-tracker-container">
<button className="progress-tracker-close-btn" onClick={this.dismiss}>
<i className="fa fa-remove" />
<Icon name="times" />
</button>
<div className="progress-tracker">
{this.steps.map((step, index) => {

View File

@ -1,13 +1,17 @@
<div class="editor-row">
<div class="gf-form-group">
<div class="gf-form">
<span class="gf-form-label">
Type
</span>
<div class="gf-form-select-wrapper">
<select class="gf-form-input" ng-model="ctrl.panel.mappingType"
ng-options="f.value as f.name for f in ctrl.panel.mappingTypes" ng-change="ctrl.refresh()"></select>
</div>
<span class="gf-form-label">
Type
</span>
<div class="gf-form-select-wrapper">
<select
class="gf-form-input"
ng-model="ctrl.panel.mappingType"
ng-options="f.value as f.name for f in ctrl.panel.mappingTypes"
ng-change="ctrl.refresh()"
></select>
</div>
</div>
</div>
</div>
@ -16,13 +20,25 @@
<div class="gf-form-group">
<div class="gf-form" ng-repeat="map in ctrl.panel.valueMaps">
<span class="gf-form-label">
<i class="fa fa-remove pointer" ng-click="ctrl.removeValueMap(map)"></i>
<icon class="'times'" ng-click="ctrl.removeValueMap(map)"></icon>
</span>
<input type="text" ng-model="map.value" placeholder="value" class="gf-form-input max-width-6" ng-blur="ctrl.refresh()">
<input
type="text"
ng-model="map.value"
placeholder="value"
class="gf-form-input max-width-6"
ng-blur="ctrl.refresh()"
/>
<span class="gf-form-label">
<i class="fa fa-arrow-right"></i>
<icon name="'arrow-right'"></icon>
</span>
<input type="text" placeholder="text" ng-model="map.text" class="gf-form-input max-width-8" ng-blur="ctrl.refresh()">
<input
type="text"
placeholder="text"
ng-model="map.text"
class="gf-form-input max-width-8"
ng-blur="ctrl.refresh()"
/>
</div>
<div class="gf-form-button-row">
@ -37,15 +53,15 @@
<h5 class="section-heading">Set range mappings</h5>
<div class="gf-form-group">
<div class="gf-form" ng-repeat="rangeMap in ctrl.panel.rangeMaps">
<span class="gf-form-label">
<i class="fa fa-remove pointer" ng-click="ctrl.removeRangeMap(rangeMap)"></i>
</span>
<span class="gf-form-label">From</span>
<input type="text" ng-model="rangeMap.from" class="gf-form-input max-width-6" ng-blur="ctrl.refresh()">
<span class="gf-form-label">To</span>
<input type="text" ng-model="rangeMap.to" class="gf-form-input max-width-6" ng-blur="ctrl.refresh()">
<span class="gf-form-label">Text</span>
<input type="text" ng-model="rangeMap.text" class="gf-form-input max-width-8" ng-blur="ctrl.refresh()">
<span class="gf-form-label">
<icon name="'times'" ng-click="ctrl.removeRangeMap(rangeMap)"></icon>
</span>
<span class="gf-form-label">From</span>
<input type="text" ng-model="rangeMap.from" class="gf-form-input max-width-6" ng-blur="ctrl.refresh()" />
<span class="gf-form-label">To</span>
<input type="text" ng-model="rangeMap.to" class="gf-form-input max-width-6" ng-blur="ctrl.refresh()" />
<span class="gf-form-label">Text</span>
<input type="text" ng-model="rangeMap.text" class="gf-form-input max-width-8" ng-blur="ctrl.refresh()" />
</div>
<div class="gf-form-button-row">

View File

@ -4,16 +4,39 @@
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label width-12">Apply to columns named</label>
<input type="text" placeholder="Name or regex" class="gf-form-input width-13" ng-model="style.pattern" bs-tooltip="'Specify regex using /my.*regex/ syntax'"
bs-typeahead="editor.getColumnNames" ng-blur="editor.render()" data-min-length=0 data-items=100 ng-model-onblur
data-placement="right">
<input
type="text"
placeholder="Name or regex"
class="gf-form-input width-13"
ng-model="style.pattern"
bs-tooltip="'Specify regex using /my.*regex/ syntax'"
bs-typeahead="editor.getColumnNames"
ng-blur="editor.render()"
data-min-length="0"
data-items="100"
ng-model-onblur
data-placement="right"
/>
</div>
</div>
<div class="gf-form" ng-if="style.type !== 'hidden'">
<label class="gf-form-label width-12">Column Header</label>
<input type="text" class="gf-form-input width-12" ng-model="style.alias" ng-change="editor.render()" ng-model-onblur placeholder="Override header label">
<input
type="text"
class="gf-form-input width-12"
ng-model="style.alias"
ng-change="editor.render()"
ng-model-onblur
placeholder="Override header label"
/>
</div>
<gf-form-switch class="gf-form" label-class="width-12" label="Render value as link" checked="style.link" on-change="editor.render()"></gf-form-switch>
<gf-form-switch
class="gf-form"
label-class="width-12"
label="Render value as link"
checked="style.link"
on-change="editor.render()"
></gf-form-switch>
</div>
<div class="section gf-form-group">
@ -22,37 +45,62 @@
<div class="gf-form">
<label class="gf-form-label width-10">Type</label>
<div class="gf-form-select-wrapper width-16">
<select class="gf-form-input" ng-model="style.type" ng-options="c.value as c.text for c in editor.columnTypes" ng-change="editor.render()"></select>
<select
class="gf-form-input"
ng-model="style.type"
ng-options="c.value as c.text for c in editor.columnTypes"
ng-change="editor.render()"
></select>
</div>
</div>
<div class="gf-form" ng-if="style.type === 'date'">
<label class="gf-form-label width-10">Date Format</label>
<gf-form-dropdown model="style.dateFormat" css-class="gf-form-input width-16" lookup-text="true"
get-options="editor.dateFormats" on-change="editor.render()" allow-custom="true">
<gf-form-dropdown
model="style.dateFormat"
css-class="gf-form-input width-16"
lookup-text="true"
get-options="editor.dateFormats"
on-change="editor.render()"
allow-custom="true"
>
</gf-form-dropdown>
</div>
<div ng-if="style.type === 'string'">
<gf-form-switch class="gf-form" label-class="width-10" ng-if="style.type === 'string'" label="Sanitize HTML" checked="style.sanitize"
on-change="editor.render()"></gf-form-switch>
<gf-form-switch
class="gf-form"
label-class="width-10"
ng-if="style.type === 'string'"
label="Sanitize HTML"
checked="style.sanitize"
on-change="editor.render()"
></gf-form-switch>
</div>
<div ng-if="style.type !== 'hidden'">
<div class="gf-form">
<label class="gf-form-label width-10">Align</label>
<gf-form-dropdown model="style.align"
css-class="gf-form-input width-16"
lookup-text="true"
get-options="editor.alignTypes"
allow-custom="false"
on-change="editor.render()"
allow-custom="false"/>
<gf-form-dropdown
model="style.align"
css-class="gf-form-input width-16"
lookup-text="true"
get-options="editor.alignTypes"
allow-custom="false"
on-change="editor.render()"
allow-custom="false"
/>
</div>
</div>
<div ng-if="style.type === 'string'">
<gf-form-switch class="gf-form" label-class="width-10" ng-if="style.type === 'string'" label="Preserve Formatting" checked="style.preserveFormat"
on-change="editor.render()"></gf-form-switch>
<gf-form-switch
class="gf-form"
label-class="width-10"
ng-if="style.type === 'string'"
label="Preserve Formatting"
checked="style.preserveFormat"
on-change="editor.render()"
></gf-form-switch>
</div>
<div ng-if="style.type === 'number'">
@ -62,8 +110,14 @@
</div>
<div class="gf-form">
<label class="gf-form-label width-10">Decimals</label>
<input type="number" class="gf-form-input width-4" data-placement="right" ng-model="style.decimals" ng-change="editor.render()"
ng-model-onblur>
<input
type="number"
class="gf-form-input width-4"
data-placement="right"
ng-model="style.decimals"
ng-change="editor.render()"
ng-model-onblur
/>
</div>
</div>
</div>
@ -77,20 +131,36 @@
Type
</span>
<div class="gf-form-select-wrapper">
<select class="gf-form-input" ng-model="style.mappingType"
ng-options="c.value as c.text for c in editor.mappingTypes" ng-change="editor.render()"></select>
<select
class="gf-form-input"
ng-model="style.mappingType"
ng-options="c.value as c.text for c in editor.mappingTypes"
ng-change="editor.render()"
></select>
</div>
</div>
<div class="gf-form-group" ng-if="style.mappingType==1">
<div class="gf-form" ng-repeat="map in style.valueMaps">
<span class="gf-form-label">
<i class="fa fa-remove pointer" ng-click="editor.removeValueMap(style, $index)"></i>
<icon name="'times'" ng-click="editor.removeValueMap(style, $index)"></icon>
</span>
<input type="text" class="gf-form-input max-width-6" ng-model="map.value" placeholder="Value" ng-blur="editor.render()">
<input
type="text"
class="gf-form-input max-width-6"
ng-model="map.value"
placeholder="Value"
ng-blur="editor.render()"
/>
<label class="gf-form-label">
<i class="fa fa-arrow-right"></i>
<icon name="'arrow-right'"></icon>
</label>
<input type="text" class="gf-form-input max-width-8" ng-model="map.text" placeholder="Text" ng-blur="editor.render()">
<input
type="text"
class="gf-form-input max-width-8"
ng-model="map.text"
placeholder="Text"
ng-blur="editor.render()"
/>
</div>
<div class="gf-form">
<label class="gf-form-label">
@ -101,18 +171,18 @@
<div class="gf-form-group" ng-if="style.mappingType==2">
<div class="gf-form" ng-repeat="rangeMap in style.rangeMaps">
<span class="gf-form-label">
<i class="fa fa-remove pointer" ng-click="editor.removeRangeMap(style, $index)"></i>
<icon name="'times'" ng-click="editor.removeRangeMap(style, $index)"></icon>
</span>
<span class="gf-form-label">From</span>
<input type="text" ng-model="rangeMap.from" class="gf-form-input max-width-6" ng-blur="editor.render()">
<input type="text" ng-model="rangeMap.from" class="gf-form-input max-width-6" ng-blur="editor.render()" />
<span class="gf-form-label">To</span>
<input type="text" ng-model="rangeMap.to" class="gf-form-input max-width-6" ng-blur="editor.render()">
<input type="text" ng-model="rangeMap.to" class="gf-form-input max-width-6" ng-blur="editor.render()" />
<span class="gf-form-label">Text</span>
<input type="text" ng-model="rangeMap.text" class="gf-form-input max-width-8" ng-blur="editor.render()">
<input type="text" ng-model="rangeMap.text" class="gf-form-input max-width-8" ng-blur="editor.render()" />
</div>
<div class="gf-form">
<label class="gf-form-label">
<a class="pointer" ng-click="editor.addRangeMap(style)"><i class="fa fa-plus"></i></a>
<a class="pointer" ng-click="editor.addRangeMap(style)"><icon name="'plus-circle'"></icon></a>
</label>
</div>
</div>
@ -123,16 +193,28 @@
<div class="section gf-form-group" ng-if="['number', 'string'].indexOf(style.type) !== -1">
<h5 class="section-heading">Thresholds</h5>
<div class="gf-form">
<label class="gf-form-label width-8">Thresholds
<label class="gf-form-label width-8"
>Thresholds
<tip>Comma separated values</tip>
</label>
<input type="text" class="gf-form-input width-10" ng-model="style.thresholds" placeholder="50,80" ng-blur="editor.render()"
array-join>
<input
type="text"
class="gf-form-input width-10"
ng-model="style.thresholds"
placeholder="50,80"
ng-blur="editor.render()"
array-join
/>
</div>
<div class="gf-form">
<label class="gf-form-label width-8">Color Mode</label>
<div class="gf-form-select-wrapper width-10">
<select class="gf-form-input" ng-model="style.colorMode" ng-options="c.value as c.text for c in editor.colorModes" ng-change="editor.render()"></select>
<select
class="gf-form-input"
ng-model="style.colorMode"
ng-options="c.value as c.text for c in editor.colorModes"
ng-change="editor.render()"
></select>
</div>
</div>
<div class="gf-form">
@ -161,17 +243,25 @@
<p>Specify an URL (relative or absolute)</p>
<span>
Use special variables to specify cell values:
<br>
<br />
<em>${__cell}</em> refers to current cell value
<br>
<em>${__cell_n}</em> refers to Nth column value in current row. Column indexes are started from 0. For instance,
<em>${__cell_1}</em> refers to second column's value.
<br>
<em>${__cell:raw}</em> disables all encoding. Useful if the value is a complete URL. By default values are URI encoded.
<br />
<em>${__cell_n}</em> refers to Nth column value in current row. Column indexes are started from 0. For
instance, <em>${__cell_1}</em> refers to second column's value.
<br />
<em>${__cell:raw}</em> disables all encoding. Useful if the value is a complete URL. By default values are
URI encoded.
</span>
</info-popover>
</label>
<input type="text" class="gf-form-input width-29" ng-model="style.linkUrl" ng-blur="editor.render()" ng-model-onblur data-placement="right">
<input
type="text"
class="gf-form-input width-29"
ng-model="style.linkUrl"
ng-blur="editor.render()"
ng-model-onblur
data-placement="right"
/>
</div>
<div class="gf-form">
<label class="gf-form-label width-9">
@ -179,14 +269,25 @@
<info-popover mode="right-normal">
<p>Specify text for link tooltip.</p>
<span>
This title appears when user hovers pointer over the cell with link. Use the same variables as for URL.
This title appears when user hovers pointer over the cell with link. Use the same variables as for URL.
</span>
</info-popover>
</label>
<input type="text" class="gf-form-input width-29" ng-model="style.linkTooltip" ng-blur="editor.render()" ng-model-onblur
data-placement="right">
<input
type="text"
class="gf-form-input width-29"
ng-model="style.linkTooltip"
ng-blur="editor.render()"
ng-model-onblur
data-placement="right"
/>
</div>
<gf-form-switch class="gf-form" label-class="width-9" label="Open in new tab" checked="style.linkTargetBlank"></gf-form-switch>
<gf-form-switch
class="gf-form"
label-class="width-9"
label="Open in new tab"
checked="style.linkTargetBlank"
></gf-form-switch>
</div>
<div class="clearfix"></div>
@ -196,7 +297,7 @@
</button>
</div>
<hr>
<hr />
</div>
<div class="gf-form-button-row">
@ -204,4 +305,3 @@
<i class="fa fa-plus"></i>&nbsp;Add column style
</button>
</div>

View File

@ -18,7 +18,7 @@
</div>
<div class="gf-form" ng-repeat="column in editor.panel.columns">
<label class="gf-form-label">
<i class="pointer fa fa-remove" ng-click="editor.removeColumn(column)"></i>
<icon name="'times'" ng-click="editor.removeColumn(column)"></icon>
<span>{{ column.text }}</span>
</label>
</div>

View File

@ -40,8 +40,7 @@ $input-border: 1px solid $input-border-color;
.gf-form-input-icon {
position: absolute;
top: 50%;
margin-top: -8px;
top: 10px;
font-size: $font-size-lg;
left: 10px;
color: $input-color-placeholder;
@ -370,7 +369,6 @@ $input-border: 1px solid $input-border-color;
.gf-form-help-icon {
flex-grow: 0;
padding-left: $spacer;
color: $text-color-weak;
&--bold {

View File

@ -101,6 +101,7 @@
.alert-rule-item {
display: flex;
align-items: center;
width: 100%;
height: 100%;
background: $card-background;

View File

@ -23,15 +23,6 @@ angular.module('grafana.routes', ['ngRoute']);
jest.mock('app/core/core', () => ({}));
jest.mock('app/features/plugins/plugin_loader', () => ({}));
/* Temporary solution as Jest can't parse Unicons imports.
* Therefore we are mocking in for all tests. Needs to be fixed before merging to master.
*/
jest.mock('@grafana/ui/src/components/Icon/Icon', () => {
return {
Icon: () => null as any,
};
});
configure({ adapter: new Adapter() });
const localStorageMock = (() => {