mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerts/InfoBox: Replaces all uses of InfoBox & FeatureInfoBox with Alert (#33352)
* Alerts/InfoBox: Replaces all uses of InfoBox & FeatureInfoBox with Alert * Fixes some more stuff * fixed border radius
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { FC, HTMLAttributes, ReactNode } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import React, { HTMLAttributes, ReactNode } from 'react';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { GrafanaThemeV2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { useTheme2 } from '../../themes';
|
||||
@@ -18,6 +18,7 @@ export interface Props extends HTMLAttributes<HTMLDivElement> {
|
||||
children?: ReactNode;
|
||||
elevated?: boolean;
|
||||
buttonContent?: React.ReactNode | string;
|
||||
bottomSpacing?: number;
|
||||
}
|
||||
|
||||
function getIconFromSeverity(severity: AlertVariant): string {
|
||||
@@ -34,19 +35,27 @@ function getIconFromSeverity(severity: AlertVariant): string {
|
||||
}
|
||||
}
|
||||
|
||||
export const Alert: FC<Props> = React.forwardRef<HTMLDivElement, Props>(
|
||||
({ title, onRemove, children, buttonContent, elevated, severity = 'error', ...restProps }, ref) => {
|
||||
export const Alert = React.forwardRef<HTMLDivElement, Props>(
|
||||
(
|
||||
{ title, onRemove, children, buttonContent, elevated, bottomSpacing, className, severity = 'error', ...restProps },
|
||||
ref
|
||||
) => {
|
||||
const theme = useTheme2();
|
||||
const styles = getStyles(theme, severity, elevated);
|
||||
const styles = getStyles(theme, severity, elevated, bottomSpacing);
|
||||
|
||||
return (
|
||||
<div ref={ref} className={styles.alert} aria-label={selectors.components.Alert.alert(severity)} {...restProps}>
|
||||
<div
|
||||
ref={ref}
|
||||
className={cx(styles.alert, className)}
|
||||
aria-label={selectors.components.Alert.alert(severity)}
|
||||
{...restProps}
|
||||
>
|
||||
<div className={styles.icon}>
|
||||
<Icon size="xl" name={getIconFromSeverity(severity) as IconName} />
|
||||
</div>
|
||||
<div className={styles.body}>
|
||||
<div className={styles.title}>{title}</div>
|
||||
{children && <div>{children}</div>}
|
||||
{children && <div className={styles.content}>{children}</div>}
|
||||
</div>
|
||||
{/* If onRemove is specified, giving preference to onRemove */}
|
||||
{onRemove && !buttonContent && (
|
||||
@@ -68,19 +77,21 @@ export const Alert: FC<Props> = React.forwardRef<HTMLDivElement, Props>(
|
||||
|
||||
Alert.displayName = 'Alert';
|
||||
|
||||
const getStyles = (theme: GrafanaThemeV2, severity: AlertVariant, elevated?: boolean) => {
|
||||
const getStyles = (theme: GrafanaThemeV2, severity: AlertVariant, elevated?: boolean, bottomSpacing?: number) => {
|
||||
const color = theme.colors[severity];
|
||||
const borderRadius = theme.shape.borderRadius();
|
||||
|
||||
return {
|
||||
alert: css`
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
border-radius: ${theme.shape.borderRadius()};
|
||||
border-radius: ${borderRadius};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
background: ${theme.colors.background.secondary};
|
||||
box-shadow: ${elevated ? theme.shadows.z3 : theme.shadows.z1};
|
||||
margin-bottom: ${theme.spacing(bottomSpacing ?? 2)};
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
@@ -96,6 +107,7 @@ const getStyles = (theme: GrafanaThemeV2, severity: AlertVariant, elevated?: boo
|
||||
icon: css`
|
||||
padding: ${theme.spacing(2, 3)};
|
||||
background: ${color.main};
|
||||
border-radius: ${borderRadius} 0 0 ${borderRadius};
|
||||
color: ${color.contrastText};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -115,6 +127,10 @@ const getStyles = (theme: GrafanaThemeV2, severity: AlertVariant, elevated?: boo
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
`,
|
||||
content: css`
|
||||
color: ${theme.colors.text.secondary};
|
||||
padding-top: ${theme.spacing(1)};
|
||||
`,
|
||||
buttonWrapper: css`
|
||||
padding: ${theme.spacing(1)};
|
||||
background: none;
|
||||
@@ -122,9 +138,8 @@ const getStyles = (theme: GrafanaThemeV2, severity: AlertVariant, elevated?: boo
|
||||
align-items: center;
|
||||
`,
|
||||
close: css`
|
||||
padding: ${theme.spacing(1)};
|
||||
padding: ${theme.spacing(2, 1)};
|
||||
background: none;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
`,
|
||||
};
|
||||
|
@@ -10,6 +10,7 @@ export interface FeatureInfoBoxProps extends Omit<InfoBoxProps, 'title' | 'urlTi
|
||||
featureState?: FeatureState;
|
||||
}
|
||||
|
||||
/** @deprecated use Alert with severity info */
|
||||
export const FeatureInfoBox = React.memo(
|
||||
React.forwardRef<HTMLDivElement, FeatureInfoBoxProps>(({ title, featureState, ...otherProps }, ref) => {
|
||||
const styles = useStyles(getFeatureInfoBoxStyles);
|
||||
@@ -27,6 +28,7 @@ export const FeatureInfoBox = React.memo(
|
||||
return <InfoBox branded title={titleEl} urlTitle="Read documentation" ref={ref} {...otherProps} />;
|
||||
})
|
||||
);
|
||||
|
||||
FeatureInfoBox.displayName = 'FeatureInfoBox';
|
||||
|
||||
const getFeatureInfoBoxStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { FeatureState } from '@grafana/data';
|
||||
import { InfoBox, FeatureInfoBox } from '@grafana/ui';
|
||||
import { InfoBox, FeatureInfoBox, VerticalGroup } from '@grafana/ui';
|
||||
import mdx from './InfoBox.mdx';
|
||||
import { Story } from '@storybook/react';
|
||||
import { FeatureInfoBoxProps } from './FeatureInfoBox';
|
||||
@@ -44,10 +44,25 @@ const defaultProps: FeatureInfoBoxProps = {
|
||||
),
|
||||
};
|
||||
|
||||
const InfoBoxTemplate: Story<InfoBoxProps> = (args) => <InfoBox {...args} />;
|
||||
const InfoBoxTemplate: Story<InfoBoxProps> = (args) => {
|
||||
return (
|
||||
<VerticalGroup>
|
||||
<div>Deprecrated component, use Alert with info severity</div>
|
||||
<InfoBox {...args} />;
|
||||
</VerticalGroup>
|
||||
);
|
||||
};
|
||||
export const infoBox = InfoBoxTemplate.bind({});
|
||||
infoBox.args = defaultProps;
|
||||
|
||||
const FeatureInfoBoxTemplate: Story<FeatureInfoBoxProps> = (args) => <FeatureInfoBox {...args}></FeatureInfoBox>;
|
||||
const FeatureInfoBoxTemplate: Story<FeatureInfoBoxProps> = (args) => {
|
||||
return (
|
||||
<VerticalGroup>
|
||||
<div>Deprecrated component, use Alert with info severity</div>
|
||||
<FeatureInfoBox {...args} />
|
||||
</VerticalGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export const featureInfoBox = FeatureInfoBoxTemplate.bind({});
|
||||
featureInfoBox.args = defaultProps;
|
||||
|
@@ -2,12 +2,8 @@ import React from 'react';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { GrafanaThemeV2 } from '@grafana/data';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { IconButton } from '../IconButton/IconButton';
|
||||
import { HorizontalGroup } from '../Layout/Layout';
|
||||
import { AlertVariant } from '../Alert/Alert';
|
||||
import panelArtDark from './panelArt_dark.svg';
|
||||
import panelArtLight from './panelArt_light.svg';
|
||||
import { stylesFactory, useTheme2 } from '../../themes';
|
||||
import { Alert, AlertVariant } from '../Alert/Alert';
|
||||
import { stylesFactory, useStyles2 } from '../../themes';
|
||||
|
||||
export interface InfoBoxProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
|
||||
children: React.ReactNode;
|
||||
@@ -25,97 +21,33 @@ export interface InfoBoxProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
|
||||
onDismiss?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@public
|
||||
*/
|
||||
/** @deprecated use Alert with severity info */
|
||||
export const InfoBox = React.memo(
|
||||
React.forwardRef<HTMLDivElement, InfoBoxProps>(
|
||||
({ title, className, children, branded, url, urlTitle, onDismiss, severity = 'info', ...otherProps }, ref) => {
|
||||
const theme = useTheme2();
|
||||
const styles = getInfoBoxStyles(theme, severity);
|
||||
const wrapperClassName = cx(branded ? styles.wrapperBranded : styles.wrapper, className);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<div className={wrapperClassName} {...otherProps} ref={ref}>
|
||||
<div>
|
||||
<HorizontalGroup justify={'space-between'} align={'flex-start'}>
|
||||
<div>{typeof title === 'string' ? <h4>{title}</h4> : title}</div>
|
||||
{onDismiss && <IconButton name={'times'} onClick={onDismiss} />}
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
<Alert severity={severity} className={className} {...otherProps} ref={ref} title={title as string}>
|
||||
<div>{children}</div>
|
||||
{url && (
|
||||
<a href={url} className={styles.docsLink} target="_blank" rel="noreferrer">
|
||||
<a href={url} className={cx('external-link', styles.docsLink)} target="_blank" rel="noreferrer">
|
||||
<Icon name="book" /> {urlTitle || 'Read more'}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
InfoBox.displayName = 'InfoBox';
|
||||
|
||||
const getInfoBoxStyles = stylesFactory((theme: GrafanaThemeV2, severity: AlertVariant) => {
|
||||
const color = theme.colors[severity];
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaThemeV2) => {
|
||||
return {
|
||||
wrapper: css`
|
||||
position: relative;
|
||||
padding: ${theme.v1.spacing.md};
|
||||
background-color: ${theme.v1.colors.bg2};
|
||||
border-left: 3px solid ${color.border};
|
||||
margin-bottom: ${theme.v1.spacing.md};
|
||||
flex-grow: 1;
|
||||
color: ${theme.v1.colors.textSemiWeak};
|
||||
box-shadow: ${theme.shadows.z1};
|
||||
|
||||
code {
|
||||
font-size: ${theme.typography.size.sm};
|
||||
background-color: ${theme.v1.colors.bg1};
|
||||
color: ${theme.v1.colors.text};
|
||||
border: 1px solid ${theme.v1.colors.border2};
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&--max-lg {
|
||||
max-width: ${theme.v1.breakpoints.lg};
|
||||
}
|
||||
`,
|
||||
wrapperBranded: css`
|
||||
padding: ${theme.v1.spacing.md};
|
||||
border-radius: ${theme.v1.border.radius.md};
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url(${theme.isLight ? panelArtLight : panelArtDark});
|
||||
border-radius: ${theme.v1.border.radius.md};
|
||||
background-position: 50% 50%;
|
||||
background-size: cover;
|
||||
filter: saturate(80%);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
`,
|
||||
docsLink: css`
|
||||
display: inline-block;
|
||||
margin-top: ${theme.v1.spacing.md};
|
||||
font-size: ${theme.v1.typography.size.sm};
|
||||
color: ${theme.v1.colors.textSemiWeak};
|
||||
margin-top: ${theme.spacing(2)};
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import React, { FC } from 'react';
|
||||
import { CollapsableSection, InfoBox } from '@grafana/ui';
|
||||
import { Alert, CollapsableSection } from '@grafana/ui';
|
||||
import { NotificationChannelOptions } from './NotificationChannelOptions';
|
||||
import { NotificationSettingsProps } from './NotificationChannelForm';
|
||||
import { NotificationChannelSecureFields, NotificationChannelType } from '../../../types';
|
||||
@@ -21,7 +21,7 @@ export const ChannelSettings: FC<Props> = ({
|
||||
}) => {
|
||||
return (
|
||||
<CollapsableSection label={`Optional ${selectedChannel.heading}`} isOpen={false}>
|
||||
{selectedChannel.info !== '' && <InfoBox>{selectedChannel.info}</InfoBox>}
|
||||
{selectedChannel.info !== '' && <Alert severity="info" title={selectedChannel.info ?? ''} />}
|
||||
<NotificationChannelOptions
|
||||
selectedChannelOptions={selectedChannel.options.filter((o) => !o.required)}
|
||||
currentFormValues={currentFormValues}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Field, InfoBox, LoadingPlaceholder } from '@grafana/ui';
|
||||
import { Alert, Field, LoadingPlaceholder } from '@grafana/ui';
|
||||
import React, { FC, useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||
@@ -28,9 +28,9 @@ const AmRoutes: FC = () => {
|
||||
<br />
|
||||
<br />
|
||||
{error && !loading && (
|
||||
<InfoBox severity="error" title={<h4>Error loading alert manager config</h4>}>
|
||||
<Alert severity="error" title="Error loading alert manager config">
|
||||
{error.message || 'Unknown error.'}
|
||||
</InfoBox>
|
||||
</Alert>
|
||||
)}
|
||||
{loading && <LoadingPlaceholder text="loading alert manager config..." />}
|
||||
{result && !loading && !error && <pre>{JSON.stringify(result, null, 2)}</pre>}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Field, InfoBox, LoadingPlaceholder } from '@grafana/ui';
|
||||
import { Field, Alert, LoadingPlaceholder } from '@grafana/ui';
|
||||
import React, { FC, useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||
@@ -36,9 +36,9 @@ const Receivers: FC = () => {
|
||||
<AlertManagerPicker current={alertManagerSourceName} onChange={setAlertManagerSourceName} />
|
||||
</Field>
|
||||
{error && !loading && (
|
||||
<InfoBox severity="error" title={<h4>Error loading alert manager config</h4>}>
|
||||
<Alert severity="error" title="Error loading alert manager config">
|
||||
{error.message || 'Unknown error.'}
|
||||
</InfoBox>
|
||||
</Alert>
|
||||
)}
|
||||
{loading && <LoadingPlaceholder text="loading receivers..." />}
|
||||
{result && !loading && !error && (
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Alert, Button, InfoBox, LoadingPlaceholder } from '@grafana/ui';
|
||||
import { Alert, Button, LoadingPlaceholder } from '@grafana/ui';
|
||||
import Page from 'app/core/components/Page/Page';
|
||||
import { useCleanup } from 'app/core/hooks/useCleanup';
|
||||
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
|
||||
@@ -43,12 +43,12 @@ const ExistingRuleEditor: FC<ExistingRuleEditorProps> = ({ identifier }) => {
|
||||
if (!result) {
|
||||
return (
|
||||
<Page.Contents>
|
||||
<InfoBox severity="warning" title="Rule not found">
|
||||
<Alert severity="warning" title="Rule not found">
|
||||
<p>Sorry! This rule does not exist.</p>
|
||||
<a href="/alerting/list">
|
||||
<Button>To rule list</Button>
|
||||
</a>
|
||||
</InfoBox>
|
||||
</Alert>
|
||||
</Page.Contents>
|
||||
);
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { DataSourceInstanceSettings, GrafanaTheme, urlUtil } from '@grafana/data';
|
||||
import { Icon, InfoBox, useStyles, Button, ButtonGroup, ToolbarButton } from '@grafana/ui';
|
||||
import { useStyles, Button, ButtonGroup, ToolbarButton, Alert } from '@grafana/ui';
|
||||
import { SerializedError } from '@reduxjs/toolkit';
|
||||
import React, { FC, useEffect, useMemo } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
@@ -88,16 +88,7 @@ export const RuleList: FC = () => {
|
||||
return (
|
||||
<AlertingPageWrapper pageId="alert-list" isLoading={loading && !haveResults}>
|
||||
{(promReqeustErrors.length || rulerRequestErrors.length || grafanaPromError) && (
|
||||
<InfoBox
|
||||
data-testid="cloud-rulessource-errors"
|
||||
title={
|
||||
<h4>
|
||||
<Icon className={styles.iconError} name="exclamation-triangle" size="xl" />
|
||||
Errors loading rules
|
||||
</h4>
|
||||
}
|
||||
severity="error"
|
||||
>
|
||||
<Alert data-testid="cloud-rulessource-errors" title="Errors loading rules" severity="error">
|
||||
{grafanaPromError && (
|
||||
<div>Failed to load Grafana threshold rules state: {grafanaPromError.message || 'Unknown error.'}</div>
|
||||
)}
|
||||
@@ -118,7 +109,7 @@ export const RuleList: FC = () => {
|
||||
{error.message || 'Unknown error.'}
|
||||
</div>
|
||||
))}
|
||||
</InfoBox>
|
||||
</Alert>
|
||||
)}
|
||||
{!showNewAlertSplash && (
|
||||
<>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Field, InfoBox, LoadingPlaceholder } from '@grafana/ui';
|
||||
import { Field, Alert, LoadingPlaceholder } from '@grafana/ui';
|
||||
import React, { FC, useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||
@@ -28,9 +28,9 @@ const Silences: FC = () => {
|
||||
<br />
|
||||
<br />
|
||||
{error && !loading && (
|
||||
<InfoBox severity="error" title={<h4>Error loading silences</h4>}>
|
||||
<Alert severity="error" title="Error loading silences">
|
||||
{error.message || 'Unknown error.'}
|
||||
</InfoBox>
|
||||
</Alert>
|
||||
)}
|
||||
{loading && <LoadingPlaceholder text="loading silences..." />}
|
||||
{result && !loading && !error && <pre>{JSON.stringify(result, null, 2)}</pre>}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import React, { FC, useMemo } from 'react';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { PageToolbar, ToolbarButton, useStyles, CustomScrollbar, Spinner, Alert, InfoBox } from '@grafana/ui';
|
||||
import { PageToolbar, ToolbarButton, useStyles, CustomScrollbar, Spinner, Alert } from '@grafana/ui';
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { AlertTypeStep } from './AlertTypeStep';
|
||||
@@ -100,9 +100,10 @@ export const AlertRuleForm: FC<Props> = ({ existing }) => {
|
||||
<CustomScrollbar autoHeightMin="100%" hideHorizontalTrack={true}>
|
||||
<div className={styles.contentInner}>
|
||||
{hasErrors && (
|
||||
<InfoBox severity="error">
|
||||
There are errors in the form below. Please fix them and try saving again.
|
||||
</InfoBox>
|
||||
<Alert
|
||||
severity="error"
|
||||
title="There are errors in the form below. Please fix them and try saving again"
|
||||
/>
|
||||
)}
|
||||
{submitState.error && (
|
||||
<Alert severity="error" title="Error saving rule">
|
||||
|
@@ -74,36 +74,32 @@ export class ShareEmbed extends PureComponent<Props, State> {
|
||||
const isRelativeTime = this.props.dashboard ? this.props.dashboard.time.to === 'now' : false;
|
||||
|
||||
return (
|
||||
<div className="share-modal-body">
|
||||
<div className="share-modal-header">
|
||||
<div className="share-modal-content">
|
||||
<p className="share-modal-info-text">Generate HTML for embedding an iframe with this panel.</p>
|
||||
<Field
|
||||
label="Current time range"
|
||||
description={isRelativeTime ? 'Transforms the current relative time range to an absolute time range' : ''}
|
||||
>
|
||||
<Switch
|
||||
id="share-current-time-range"
|
||||
value={useCurrentTimeRange}
|
||||
onChange={this.onUseCurrentTimeRangeChange}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Theme">
|
||||
<RadioButtonGroup options={themeOptions} value={selectedTheme} onChange={this.onThemeChange} />
|
||||
</Field>
|
||||
<Field
|
||||
label="Embed HTML"
|
||||
description="The HTML code below can be pasted and included in another web page. Unless anonymous access is enabled,
|
||||
<>
|
||||
<p className="share-modal-info-text">Generate HTML for embedding an iframe with this panel.</p>
|
||||
<Field
|
||||
label="Current time range"
|
||||
description={isRelativeTime ? 'Transforms the current relative time range to an absolute time range' : ''}
|
||||
>
|
||||
<Switch
|
||||
id="share-current-time-range"
|
||||
value={useCurrentTimeRange}
|
||||
onChange={this.onUseCurrentTimeRangeChange}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Theme">
|
||||
<RadioButtonGroup options={themeOptions} value={selectedTheme} onChange={this.onThemeChange} />
|
||||
</Field>
|
||||
<Field
|
||||
label="Embed HTML"
|
||||
description="The HTML code below can be pasted and included in another web page. Unless anonymous access is enabled,
|
||||
the user viewing that page need to be signed into Grafana for the graph to load."
|
||||
>
|
||||
<TextArea rows={5} value={iframeHtml} onChange={this.onIframeHtmlChange}></TextArea>
|
||||
</Field>
|
||||
<ClipboardButton variant="primary" getText={this.getIframeHtml} onClipboardCopy={this.onIframeHtmlCopy}>
|
||||
Copy to clipboard
|
||||
</ClipboardButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
>
|
||||
<TextArea rows={5} value={iframeHtml} onChange={this.onIframeHtmlChange}></TextArea>
|
||||
</Field>
|
||||
<ClipboardButton variant="primary" getText={this.getIframeHtml} onClipboardCopy={this.onIframeHtmlCopy}>
|
||||
Copy to clipboard
|
||||
</ClipboardButton>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -90,27 +90,23 @@ export class ShareExport extends PureComponent<Props, State> {
|
||||
const { shareExternally } = this.state;
|
||||
|
||||
return (
|
||||
<div className="share-modal-body">
|
||||
<div className="share-modal-header">
|
||||
<div className="share-modal-content">
|
||||
<p className="share-modal-info-text">Export this dashboard.</p>
|
||||
<Field label="Export for sharing externally">
|
||||
<Switch value={shareExternally} onChange={this.onShareExternallyChange} />
|
||||
</Field>
|
||||
<div className="gf-form-button-row">
|
||||
<Button variant="primary" onClick={this.onSaveAsFile}>
|
||||
Save to file
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={this.onViewJson}>
|
||||
View JSON
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={onDismiss}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<>
|
||||
<p className="share-modal-info-text">Export this dashboard.</p>
|
||||
<Field label="Export for sharing externally">
|
||||
<Switch value={shareExternally} onChange={this.onShareExternallyChange} />
|
||||
</Field>
|
||||
<div className="gf-form-button-row">
|
||||
<Button variant="primary" onClick={this.onSaveAsFile}>
|
||||
Save to file
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={this.onViewJson}>
|
||||
View JSON
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={onDismiss}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -14,13 +14,9 @@ export const ShareGlobalPanel = ({ panel, initialFolderId, onDismiss }: Props) =
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="share-modal-body">
|
||||
<div className="share-modal-header">
|
||||
<div className="share-modal-content">
|
||||
<p className="share-modal-info-text">Add this panel to the panel library.</p>
|
||||
<AddLibraryPanelContents panel={panel} initialFolderId={initialFolderId} onDismiss={onDismiss!} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<>
|
||||
<p className="share-modal-info-text">Add this panel to the panel library.</p>
|
||||
<AddLibraryPanelContents panel={panel} initialFolderId={initialFolderId} onDismiss={onDismiss!} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||
import { Field, RadioButtonGroup, Switch, ClipboardButton, Icon, InfoBox, Input, FieldSet } from '@grafana/ui';
|
||||
import { Field, RadioButtonGroup, Switch, ClipboardButton, Icon, Input, FieldSet, Alert } from '@grafana/ui';
|
||||
import { SelectableValue, PanelModel, AppEvents } from '@grafana/data';
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
import { buildImageUrl, buildShareUrl } from './utils';
|
||||
@@ -90,70 +90,62 @@ export class ShareLink extends PureComponent<Props, State> {
|
||||
const selectors = e2eSelectors.pages.SharePanelModal;
|
||||
|
||||
return (
|
||||
<div className="share-modal-body">
|
||||
<div className="share-modal-header">
|
||||
<div className="share-modal-content">
|
||||
<p className="share-modal-info-text">
|
||||
Create a direct link to this dashboard or panel, customized with the options below.
|
||||
</p>
|
||||
<FieldSet>
|
||||
<Field
|
||||
label="Lock time range"
|
||||
description={
|
||||
isRelativeTime ? 'Transforms the current relative time range to an absolute time range' : ''
|
||||
}
|
||||
>
|
||||
<Switch
|
||||
id="share-current-time-range"
|
||||
value={useCurrentTimeRange}
|
||||
onChange={this.onUseCurrentTimeRangeChange}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Theme">
|
||||
<RadioButtonGroup options={themeOptions} value={selectedTheme} onChange={this.onThemeChange} />
|
||||
</Field>
|
||||
<Field label="Shorten URL">
|
||||
<Switch id="share-shorten-url" value={useShortUrl} onChange={this.onUrlShorten} />
|
||||
</Field>
|
||||
<>
|
||||
<p className="share-modal-info-text">
|
||||
Create a direct link to this dashboard or panel, customized with the options below.
|
||||
</p>
|
||||
<FieldSet>
|
||||
<Field
|
||||
label="Lock time range"
|
||||
description={isRelativeTime ? 'Transforms the current relative time range to an absolute time range' : ''}
|
||||
>
|
||||
<Switch
|
||||
id="share-current-time-range"
|
||||
value={useCurrentTimeRange}
|
||||
onChange={this.onUseCurrentTimeRangeChange}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Theme">
|
||||
<RadioButtonGroup options={themeOptions} value={selectedTheme} onChange={this.onThemeChange} />
|
||||
</Field>
|
||||
<Field label="Shorten URL">
|
||||
<Switch id="share-shorten-url" value={useShortUrl} onChange={this.onUrlShorten} />
|
||||
</Field>
|
||||
|
||||
<Field label="Link URL">
|
||||
<Input
|
||||
value={shareUrl}
|
||||
readOnly
|
||||
addonAfter={
|
||||
<ClipboardButton variant="primary" getText={this.getShareUrl} onClipboardCopy={this.onShareUrlCopy}>
|
||||
<Icon name="copy" /> Copy
|
||||
</ClipboardButton>
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
</FieldSet>
|
||||
{panel && config.rendererAvailable && (
|
||||
<div className="gf-form">
|
||||
<a href={imageUrl} target="_blank" rel="noreferrer" aria-label={selectors.linkToRenderedImage}>
|
||||
<Icon name="camera" /> Direct link rendered image
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
{panel && !config.rendererAvailable && (
|
||||
<InfoBox>
|
||||
<p>
|
||||
<>To render a panel image, you must install the </>
|
||||
<a
|
||||
href="https://grafana.com/grafana/plugins/grafana-image-renderer"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="external-link"
|
||||
>
|
||||
Grafana Image Renderer plugin
|
||||
</a>
|
||||
. Please contact your Grafana administrator to install the plugin.
|
||||
</p>
|
||||
</InfoBox>
|
||||
)}
|
||||
<Field label="Link URL">
|
||||
<Input
|
||||
value={shareUrl}
|
||||
readOnly
|
||||
addonAfter={
|
||||
<ClipboardButton variant="primary" getText={this.getShareUrl} onClipboardCopy={this.onShareUrlCopy}>
|
||||
<Icon name="copy" /> Copy
|
||||
</ClipboardButton>
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
</FieldSet>
|
||||
{panel && config.rendererAvailable && (
|
||||
<div className="gf-form">
|
||||
<a href={imageUrl} target="_blank" rel="noreferrer" aria-label={selectors.linkToRenderedImage}>
|
||||
<Icon name="camera" /> Direct link rendered image
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{panel && !config.rendererAvailable && (
|
||||
<Alert severity="info" title="Image renderer plugin not installed" bottomSpacing={0}>
|
||||
<>To render a panel image, you must install the </>
|
||||
<a
|
||||
href="https://grafana.com/grafana/plugins/grafana-image-renderer"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="external-link"
|
||||
>
|
||||
Grafana image renderer plugin
|
||||
</a>
|
||||
. Please contact your Grafana administrator to install the plugin.
|
||||
</Alert>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -292,16 +292,12 @@ export class ShareSnapshot extends PureComponent<Props, State> {
|
||||
const { isLoading, step } = this.state;
|
||||
|
||||
return (
|
||||
<div className="share-modal-body">
|
||||
<div className="share-modal-header">
|
||||
<div className="share-modal-content">
|
||||
{step === 1 && this.renderStep1()}
|
||||
{step === 2 && this.renderStep2()}
|
||||
{step === 3 && this.renderStep3()}
|
||||
{isLoading && <Spinner inline={true} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<>
|
||||
{step === 1 && this.renderStep1()}
|
||||
{step === 2 && this.renderStep2()}
|
||||
{step === 3 && this.renderStep3()}
|
||||
{isLoading && <Spinner inline={true} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,6 @@ import {
|
||||
Container,
|
||||
CustomScrollbar,
|
||||
Themeable,
|
||||
FeatureInfoBox,
|
||||
VerticalGroup,
|
||||
withTheme,
|
||||
Input,
|
||||
@@ -263,15 +262,12 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
|
||||
}
|
||||
|
||||
return (
|
||||
<FeatureInfoBox
|
||||
<Alert
|
||||
title="Transformations"
|
||||
className={css`
|
||||
margin-bottom: ${this.props.theme.spacing.lg};
|
||||
`}
|
||||
onDismiss={() => {
|
||||
onRemove={() => {
|
||||
onDismiss(true);
|
||||
}}
|
||||
url={getDocsLink(DocsId.Transformations)}
|
||||
severity="info"
|
||||
>
|
||||
<p>
|
||||
Transformations allow you to join, calculate, re-order, hide, and rename your query results before
|
||||
@@ -279,9 +275,16 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
|
||||
Many transforms are not suitable if you're using the Graph visualization, as it currently
|
||||
only only supports time series data. <br />
|
||||
It can help to switch to the Table visualization to understand what a transformation is doing.{' '}
|
||||
<br />
|
||||
</p>
|
||||
</FeatureInfoBox>
|
||||
<a
|
||||
href={getDocsLink(DocsId.Transformations)}
|
||||
className="external-link"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Read more
|
||||
</a>
|
||||
</Alert>
|
||||
);
|
||||
}}
|
||||
</LocalStorageValueProvider>
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { DataSourceSettings, GrafanaTheme } from '@grafana/data';
|
||||
import { FeatureInfoBox, useStyles } from '@grafana/ui';
|
||||
import { css } from '@emotion/css';
|
||||
import { DataSourceSettings } from '@grafana/data';
|
||||
import { Alert } from '@grafana/ui';
|
||||
import React, { FC } from 'react';
|
||||
import { config } from 'app/core/config';
|
||||
import { GrafanaEdition } from '@grafana/data/src/types/config';
|
||||
@@ -13,7 +12,6 @@ export interface Props {
|
||||
}
|
||||
|
||||
export const CloudInfoBox: FC<Props> = ({ dataSource }) => {
|
||||
const styles = useStyles(getStyles);
|
||||
let mainDS = '';
|
||||
let extraDS = '';
|
||||
|
||||
@@ -47,46 +45,29 @@ export const CloudInfoBox: FC<Props> = ({ dataSource }) => {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<FeatureInfoBox
|
||||
<Alert
|
||||
title={`Configure your ${mainDS} data source below`}
|
||||
className={styles.box}
|
||||
branded={false}
|
||||
onDismiss={() => {
|
||||
severity="info"
|
||||
bottomSpacing={4}
|
||||
onRemove={() => {
|
||||
onDismiss(true);
|
||||
}}
|
||||
>
|
||||
<div className={styles.text}>
|
||||
Or skip the effort and get {mainDS} (and {extraDS}) as fully-managed, scalable, and hosted data sources
|
||||
from Grafana Labs with the{' '}
|
||||
<a
|
||||
className="external-link"
|
||||
href={`https://grafana.com/signup/cloud/connect-account?src=grafana-oss&cnt=${dataSource.type}-settings`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
title="The free plan includes 10k active metrics and 50gb storage."
|
||||
>
|
||||
free-forever Grafana Cloud plan
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</FeatureInfoBox>
|
||||
Or skip the effort and get {mainDS} (and {extraDS}) as fully-managed, scalable, and hosted data sources from
|
||||
Grafana Labs with the{' '}
|
||||
<a
|
||||
className="external-link"
|
||||
href={`https://grafana.com/signup/cloud/connect-account?src=grafana-oss&cnt=${dataSource.type}-settings`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
title="The free plan includes 10k active metrics and 50gb storage."
|
||||
>
|
||||
free-forever Grafana Cloud plan
|
||||
</a>
|
||||
.
|
||||
</Alert>
|
||||
);
|
||||
}}
|
||||
</LocalStorageValueProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme) => {
|
||||
return {
|
||||
box: css`
|
||||
margin: 0 0 ${theme.spacing.lg} 0;
|
||||
`,
|
||||
text: css`
|
||||
color: ${theme.colors.textSemiWeak};
|
||||
padding: ${theme.spacing.sm} 0;
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
@@ -21,7 +21,7 @@ import { getNavModel } from 'app/core/selectors/navModel';
|
||||
// Types
|
||||
import { StoreState } from 'app/types/';
|
||||
import { DataSourceSettings } from '@grafana/data';
|
||||
import { Alert, Button, InfoBox, LinkButton } from '@grafana/ui';
|
||||
import { Alert, Button, LinkButton } from '@grafana/ui';
|
||||
import { getDataSourceLoadingNav } from '../state/navModel';
|
||||
import PluginStateinfo from 'app/features/plugins/PluginStateInfo';
|
||||
import { dataSourceLoaded, setDataSourceName, setIsDefault } from '../state/reducers';
|
||||
@@ -127,10 +127,10 @@ export class DataSourceSettingsPage extends PureComponent<Props> {
|
||||
|
||||
renderIsReadOnlyMessage() {
|
||||
return (
|
||||
<InfoBox aria-label={selectors.pages.DataSource.readOnly} severity="info">
|
||||
<Alert aria-label={selectors.pages.DataSource.readOnly} severity="info" title="Provisioned data source">
|
||||
This data source was added by config and cannot be modified using the UI. Please contact your server admin to
|
||||
update this data source.
|
||||
</InfoBox>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { defaults } from 'lodash';
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { InlineField, Select, FeatureInfoBox, Input } from '@grafana/ui';
|
||||
import { QueryEditorProps, SelectableValue, FeatureState, dataFrameFromJSON, rangeUtil } from '@grafana/data';
|
||||
import { InlineField, Select, Alert, Input } from '@grafana/ui';
|
||||
import { QueryEditorProps, SelectableValue, dataFrameFromJSON, rangeUtil } from '@grafana/data';
|
||||
import { GrafanaDatasource } from '../datasource';
|
||||
import { defaultQuery, GrafanaQuery, GrafanaQueryType } from '../types';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
@@ -232,12 +232,10 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FeatureInfoBox title="Grafana Live - Measurements" featureState={FeatureState.alpha}>
|
||||
<p>
|
||||
This supports real-time event streams in Grafana core. This feature is under heavy development. Expect the
|
||||
interfaces and structures to change as this becomes more production ready.
|
||||
</p>
|
||||
</FeatureInfoBox>
|
||||
<Alert title="Grafana Live - Measurements" severity="info">
|
||||
This supports real-time event streams in Grafana core. This feature is under heavy development. Expect the
|
||||
interfaces and structures to change as this becomes more production ready.
|
||||
</Alert>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@@ -1,13 +1,12 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { Select, FeatureInfoBox, Label, stylesFactory } from '@grafana/ui';
|
||||
import { Select, Alert, Label, stylesFactory } from '@grafana/ui';
|
||||
import {
|
||||
LiveChannelScope,
|
||||
LiveChannelAddress,
|
||||
LiveChannelSupport,
|
||||
SelectableValue,
|
||||
StandardEditorProps,
|
||||
FeatureState,
|
||||
GrafanaTheme,
|
||||
} from '@grafana/data';
|
||||
|
||||
@@ -100,12 +99,10 @@ export class LiveChannelEditor extends PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<>
|
||||
<FeatureInfoBox title="Grafana Live" featureState={FeatureState.alpha}>
|
||||
<p>
|
||||
This supports real-time event streams in grafana core. This feature is under heavy development. Expect the
|
||||
intefaces and structures to change as this becomes more production ready.
|
||||
</p>
|
||||
</FeatureInfoBox>
|
||||
<Alert title="Grafana Live" severity="info">
|
||||
This supports real-time event streams in grafana core. This feature is under heavy development. Expect the
|
||||
intefaces and structures to change as this becomes more production ready.
|
||||
</Alert>
|
||||
|
||||
<div>
|
||||
<div className={style.dropWrap}>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Unsubscribable, PartialObserver } from 'rxjs';
|
||||
import { FeatureInfoBox, stylesFactory, Button, JSONFormatter, CustomScrollbar, CodeEditor } from '@grafana/ui';
|
||||
import { Alert, stylesFactory, Button, JSONFormatter, CustomScrollbar, CodeEditor } from '@grafana/ui';
|
||||
import {
|
||||
GrafanaTheme,
|
||||
PanelProps,
|
||||
@@ -126,17 +126,12 @@ export class LivePanel extends PureComponent<Props, State> {
|
||||
const preformatted = `[feature_toggles]
|
||||
enable = live`;
|
||||
return (
|
||||
<FeatureInfoBox
|
||||
title="Grafana Live"
|
||||
style={{
|
||||
height: this.props.height,
|
||||
}}
|
||||
>
|
||||
<Alert title="Grafana Live" severity="info">
|
||||
<p>Grafana live requires a feature flag to run</p>
|
||||
|
||||
<b>custom.ini:</b>
|
||||
<pre>{preformatted}</pre>
|
||||
</FeatureInfoBox>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -298,14 +293,9 @@ export class LivePanel extends PureComponent<Props, State> {
|
||||
const { addr, error } = this.state;
|
||||
if (!addr) {
|
||||
return (
|
||||
<FeatureInfoBox
|
||||
title="Grafana Live"
|
||||
style={{
|
||||
height: this.props.height,
|
||||
}}
|
||||
>
|
||||
<p>Use the panel editor to pick a channel</p>
|
||||
</FeatureInfoBox>
|
||||
<Alert title="Grafana Live" severity="info">
|
||||
Use the panel editor to pick a channel
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
if (error) {
|
||||
|
Reference in New Issue
Block a user