mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: miscllaneous UI fixes & improvements (#33734)
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Alert, Field, LoadingPlaceholder, useStyles2 } from '@grafana/ui';
|
import { Alert, LoadingPlaceholder, useStyles2 } from '@grafana/ui';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { Redirect } from 'react-router-dom';
|
import { Redirect } from 'react-router-dom';
|
||||||
import { Receiver } from 'app/plugins/datasource/alertmanager/types';
|
import { Receiver } from 'app/plugins/datasource/alertmanager/types';
|
||||||
@@ -96,9 +96,7 @@ const AmRoutes: FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertingPageWrapper pageId="am-routes">
|
<AlertingPageWrapper pageId="am-routes">
|
||||||
<Field label="Choose alert manager">
|
|
||||||
<AlertManagerPicker current={alertManagerSourceName} onChange={setAlertManagerSourceName} />
|
<AlertManagerPicker current={alertManagerSourceName} onChange={setAlertManagerSourceName} />
|
||||||
</Field>
|
|
||||||
{savingError && !saving && (
|
{savingError && !saving && (
|
||||||
<Alert severity="error" title="Error saving alert manager config">
|
<Alert severity="error" title="Error saving alert manager config">
|
||||||
{savingError.message || 'Unknown error.'}
|
{savingError.message || 'Unknown error.'}
|
||||||
@@ -112,7 +110,6 @@ const AmRoutes: FC = () => {
|
|||||||
{resultLoading && <LoadingPlaceholder text="Loading alert manager config..." />}
|
{resultLoading && <LoadingPlaceholder text="Loading alert manager config..." />}
|
||||||
{result && !resultLoading && !resultError && (
|
{result && !resultLoading && !resultError && (
|
||||||
<>
|
<>
|
||||||
<div className={styles.break} />
|
|
||||||
<AmRootRoute
|
<AmRootRoute
|
||||||
alertManagerSourceName={alertManagerSourceName}
|
alertManagerSourceName={alertManagerSourceName}
|
||||||
isEditMode={isRootRouteEditMode}
|
isEditMode={isRootRouteEditMode}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Field, Alert, LoadingPlaceholder } from '@grafana/ui';
|
import { Alert, LoadingPlaceholder } from '@grafana/ui';
|
||||||
import React, { FC, useEffect } from 'react';
|
import React, { FC, useEffect } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { Redirect, Route, RouteChildrenProps, Switch, useLocation } from 'react-router-dom';
|
import { Redirect, Route, RouteChildrenProps, Switch, useLocation } from 'react-router-dom';
|
||||||
@@ -50,9 +50,11 @@ const Receivers: FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertingPageWrapper pageId="receivers">
|
<AlertingPageWrapper pageId="receivers">
|
||||||
<Field label={disableAmSelect ? 'Alert manager' : 'Choose alert manager'} disabled={disableAmSelect}>
|
<AlertManagerPicker
|
||||||
<AlertManagerPicker current={alertManagerSourceName} onChange={setAlertManagerSourceName} />
|
current={alertManagerSourceName}
|
||||||
</Field>
|
disabled={disableAmSelect}
|
||||||
|
onChange={setAlertManagerSourceName}
|
||||||
|
/>
|
||||||
{error && !loading && (
|
{error && !loading && (
|
||||||
<Alert severity="error" title="Error loading alert manager config">
|
<Alert severity="error" title="Error loading alert manager config">
|
||||||
{error.message || 'Unknown error.'}
|
{error.message || 'Unknown error.'}
|
||||||
|
|||||||
@@ -2,23 +2,29 @@ import React, { FC, useEffect, useCallback } from 'react';
|
|||||||
import { Alert, LoadingPlaceholder } from '@grafana/ui';
|
import { Alert, LoadingPlaceholder } from '@grafana/ui';
|
||||||
|
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { Redirect, Route, RouteChildrenProps, Switch } from 'react-router-dom';
|
import { Redirect, Route, RouteChildrenProps, Switch, useLocation } from 'react-router-dom';
|
||||||
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||||
import SilencesTable from './components/silences/SilencesTable';
|
import SilencesTable from './components/silences/SilencesTable';
|
||||||
import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName';
|
import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName';
|
||||||
import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector';
|
import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector';
|
||||||
import { fetchAmAlertsAction, fetchSilencesAction } from './state/actions';
|
import { fetchAmAlertsAction, fetchSilencesAction } from './state/actions';
|
||||||
import { SILENCES_POLL_INTERVAL_MS } from './utils/constants';
|
import { SILENCES_POLL_INTERVAL_MS } from './utils/constants';
|
||||||
import { initialAsyncRequestState } from './utils/redux';
|
import { AsyncRequestState, initialAsyncRequestState } from './utils/redux';
|
||||||
import SilencesEditor from './components/silences/SilencesEditor';
|
import SilencesEditor from './components/silences/SilencesEditor';
|
||||||
|
import { AlertManagerPicker } from './components/AlertManagerPicker';
|
||||||
|
import { Silence } from 'app/plugins/datasource/alertmanager/types';
|
||||||
|
|
||||||
const Silences: FC = () => {
|
const Silences: FC = () => {
|
||||||
const [alertManagerSourceName = '', setAlertManagerSourceName] = useAlertManagerSourceName();
|
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const silences = useUnifiedAlertingSelector((state) => state.silences);
|
const silences = useUnifiedAlertingSelector((state) => state.silences);
|
||||||
|
const alertsRequests = useUnifiedAlertingSelector((state) => state.amAlerts);
|
||||||
|
const alertsRequest = alertManagerSourceName
|
||||||
|
? alertsRequests[alertManagerSourceName] || initialAsyncRequestState
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const alerts =
|
const location = useLocation();
|
||||||
useUnifiedAlertingSelector((state) => state.amAlerts)[alertManagerSourceName] || initialAsyncRequestState;
|
const isRoot = location.pathname.endsWith('/alerting/silences');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function fetchAll() {
|
function fetchAll() {
|
||||||
@@ -34,7 +40,9 @@ const Silences: FC = () => {
|
|||||||
};
|
};
|
||||||
}, [alertManagerSourceName, dispatch]);
|
}, [alertManagerSourceName, dispatch]);
|
||||||
|
|
||||||
const { result, loading, error } = silences[alertManagerSourceName] || initialAsyncRequestState;
|
const { result, loading, error }: AsyncRequestState<Silence[]> =
|
||||||
|
(alertManagerSourceName && silences[alertManagerSourceName]) || initialAsyncRequestState;
|
||||||
|
|
||||||
const getSilenceById = useCallback((id: string) => result && result.find((silence) => silence.id === id), [result]);
|
const getSilenceById = useCallback((id: string) => result && result.find((silence) => silence.id === id), [result]);
|
||||||
|
|
||||||
if (!alertManagerSourceName) {
|
if (!alertManagerSourceName) {
|
||||||
@@ -43,20 +51,20 @@ const Silences: FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertingPageWrapper pageId="silences">
|
<AlertingPageWrapper pageId="silences">
|
||||||
|
<AlertManagerPicker disabled={!isRoot} current={alertManagerSourceName} onChange={setAlertManagerSourceName} />
|
||||||
{error && !loading && (
|
{error && !loading && (
|
||||||
<Alert severity="error" title="Error loading silences">
|
<Alert severity="error" title="Error loading silences">
|
||||||
{error.message || 'Unknown error.'}
|
{error.message || 'Unknown error.'}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
{loading && <LoadingPlaceholder text="loading silences..." />}
|
{loading && <LoadingPlaceholder text="loading silences..." />}
|
||||||
{result && !error && alerts.result && (
|
{result && !error && alertsRequest?.result && (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/alerting/silences">
|
<Route exact path="/alerting/silences">
|
||||||
<SilencesTable
|
<SilencesTable
|
||||||
silences={result}
|
silences={result}
|
||||||
alertManagerAlerts={alerts.result}
|
alertManagerAlerts={alertsRequest.result}
|
||||||
alertManagerSourceName={alertManagerSourceName}
|
alertManagerSourceName={alertManagerSourceName}
|
||||||
setAlertManagerSourceName={setAlertManagerSourceName}
|
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route exact path="/alerting/silence/new">
|
<Route exact path="/alerting/silence/new">
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue, GrafanaTheme2 } from '@grafana/data';
|
||||||
import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
|
import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
|
||||||
import React, { FC, useMemo } from 'react';
|
import React, { FC, useMemo } from 'react';
|
||||||
import { Select } from '@grafana/ui';
|
import { Field, Select, useStyles2 } from '@grafana/ui';
|
||||||
import { getAllDataSources } from '../utils/config';
|
import { getAllDataSources } from '../utils/config';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onChange: (alertManagerSourceName: string) => void;
|
onChange: (alertManagerSourceName: string) => void;
|
||||||
@@ -11,6 +12,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const AlertManagerPicker: FC<Props> = ({ onChange, current, disabled = false }) => {
|
export const AlertManagerPicker: FC<Props> = ({ onChange, current, disabled = false }) => {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const options: Array<SelectableValue<string>> = useMemo(() => {
|
const options: Array<SelectableValue<string>> = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -30,13 +33,16 @@ export const AlertManagerPicker: FC<Props> = ({ onChange, current, disabled = fa
|
|||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// no need to show the picker if there's only one option
|
||||||
|
if (options.length === 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Field className={styles.field} label={disabled ? 'Alert manager' : 'Choose alert manager'} disabled={disabled}>
|
||||||
<Select
|
<Select
|
||||||
disabled={disabled}
|
|
||||||
width={29}
|
width={29}
|
||||||
className="ds-picker select-container"
|
className="ds-picker select-container"
|
||||||
isMulti={false}
|
|
||||||
isClearable={false}
|
|
||||||
backspaceRemovesValue={false}
|
backspaceRemovesValue={false}
|
||||||
onChange={(value) => value.value && onChange(value.value)}
|
onChange={(value) => value.value && onChange(value.value)}
|
||||||
options={options}
|
options={options}
|
||||||
@@ -45,5 +51,12 @@ export const AlertManagerPicker: FC<Props> = ({ onChange, current, disabled = fa
|
|||||||
value={current}
|
value={current}
|
||||||
getOptionLabel={(o) => o.label}
|
getOptionLabel={(o) => o.label}
|
||||||
/>
|
/>
|
||||||
|
</Field>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
|
field: css`
|
||||||
|
margin-bottom: ${theme.spacing(4)};
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { useStyles2 } from '@grafana/ui';
|
||||||
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
|
||||||
import { SilenceState, AlertState } from 'app/plugins/datasource/alertmanager/types';
|
|
||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
|
export type State = 'good' | 'bad' | 'warning' | 'neutral' | 'info';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
status: PromAlertingRuleState | SilenceState | AlertState;
|
state: State;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StateTag: FC<Props> = ({ children, status }) => {
|
export const StateTag: FC<Props> = ({ children, state }) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
return <span className={cx(styles.common, styles[status])}>{children || status}</span>;
|
return <span className={cx(styles.common, styles[state])}>{children || state}</span>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => ({
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
@@ -25,37 +25,27 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
`,
|
`,
|
||||||
[PromAlertingRuleState.Inactive]: css`
|
good: css`
|
||||||
background-color: ${theme.colors.success.main};
|
background-color: ${theme.colors.success.main};
|
||||||
border: solid 1px ${theme.colors.success.main};
|
border: solid 1px ${theme.colors.success.main};
|
||||||
color: ${theme.colors.success.contrastText};
|
color: ${theme.colors.success.contrastText};
|
||||||
`,
|
`,
|
||||||
[PromAlertingRuleState.Pending]: css`
|
warning: css`
|
||||||
background-color: ${theme.colors.warning.main};
|
background-color: ${theme.colors.warning.main};
|
||||||
border: solid 1px ${theme.colors.warning.main};
|
border: solid 1px ${theme.colors.warning.main};
|
||||||
color: ${theme.colors.warning.contrastText};
|
color: ${theme.colors.warning.contrastText};
|
||||||
`,
|
`,
|
||||||
[PromAlertingRuleState.Firing]: css`
|
bad: css`
|
||||||
background-color: ${theme.colors.error.main};
|
background-color: ${theme.colors.error.main};
|
||||||
border: solid 1px ${theme.colors.error.main};
|
border: solid 1px ${theme.colors.error.main};
|
||||||
color: ${theme.colors.error.contrastText};
|
color: ${theme.colors.error.contrastText};
|
||||||
`,
|
`,
|
||||||
[SilenceState.Expired]: css`
|
neutral: css`
|
||||||
background-color: ${theme.colors.secondary.main};
|
background-color: ${theme.colors.secondary.main};
|
||||||
border: solid 1px ${theme.colors.secondary.main};
|
border: solid 1px ${theme.colors.secondary.main};
|
||||||
color: ${theme.colors.secondary.contrastText};
|
color: ${theme.colors.secondary.contrastText};
|
||||||
`,
|
`,
|
||||||
[SilenceState.Active]: css`
|
info: css`
|
||||||
background-color: ${theme.colors.success.main};
|
|
||||||
border: solid 1px ${theme.colors.success.main};
|
|
||||||
color: ${theme.colors.success.contrastText};
|
|
||||||
`,
|
|
||||||
[AlertState.Unprocessed]: css`
|
|
||||||
background-color: ${theme.colors.secondary.main};
|
|
||||||
border: solid 1px ${theme.colors.secondary.main};
|
|
||||||
color: ${theme.colors.secondary.contrastText};
|
|
||||||
`,
|
|
||||||
[AlertState.Suppressed]: css`
|
|
||||||
background-color: ${theme.colors.primary.main};
|
background-color: ${theme.colors.primary.main};
|
||||||
border: solid 1px ${theme.colors.primary.main};
|
border: solid 1px ${theme.colors.primary.main};
|
||||||
color: ${theme.colors.primary.contrastText};
|
color: ${theme.colors.primary.contrastText};
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ export const AmRootRouteForm: FC<AmRootRouteFormProps> = ({
|
|||||||
</Collapse>
|
</Collapse>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<Button type="submit">Save</Button>
|
<Button type="submit">Save</Button>
|
||||||
<Button onClick={onCancel} type="reset" variant="secondary">
|
<Button onClick={onCancel} type="reset" variant="secondary" fill="outline">
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
|
|||||||
)}
|
)}
|
||||||
<div className={styles.buttonGroup}>
|
<div className={styles.buttonGroup}>
|
||||||
<Button type="submit">Save policy</Button>
|
<Button type="submit">Save policy</Button>
|
||||||
<Button onClick={onCancel} type="button" variant="secondary">
|
<Button onClick={onCancel} fill="outline" type="button" variant="secondary">
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { GrafanaTheme } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { LinkButton, useStyles } from '@grafana/ui';
|
import { LinkButton, useStyles2 } from '@grafana/ui';
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -8,13 +8,21 @@ interface Props {
|
|||||||
description: string;
|
description: string;
|
||||||
addButtonLabel: string;
|
addButtonLabel: string;
|
||||||
addButtonTo: string;
|
addButtonTo: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ReceiversSection: FC<Props> = ({ title, description, addButtonLabel, addButtonTo, children }) => {
|
export const ReceiversSection: FC<Props> = ({
|
||||||
const styles = useStyles(getStyles);
|
className,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
addButtonLabel,
|
||||||
|
addButtonTo,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.heading}>
|
<div className={cx(styles.heading, className)}>
|
||||||
<div>
|
<div>
|
||||||
<h4>{title}</h4>
|
<h4>{title}</h4>
|
||||||
<p className={styles.description}>{description}</p>
|
<p className={styles.description}>{description}</p>
|
||||||
@@ -28,13 +36,12 @@ export const ReceiversSection: FC<Props> = ({ title, description, addButtonLabel
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme) => ({
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
heading: css`
|
heading: css`
|
||||||
margin-top: ${theme.spacing.xl};
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
`,
|
`,
|
||||||
description: css`
|
description: css`
|
||||||
color: ${theme.colors.textSemiWeak};
|
color: ${theme.colors.text.secondary};
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { extractReadableNotifierTypes } from '../../utils/receivers';
|
|||||||
import { ActionIcon } from '../rules/ActionIcon';
|
import { ActionIcon } from '../rules/ActionIcon';
|
||||||
import { ReceiversSection } from './ReceiversSection';
|
import { ReceiversSection } from './ReceiversSection';
|
||||||
import { makeAMLink } from '../../utils/misc';
|
import { makeAMLink } from '../../utils/misc';
|
||||||
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
config: AlertManagerCortexConfig;
|
config: AlertManagerCortexConfig;
|
||||||
@@ -15,6 +17,7 @@ interface Props {
|
|||||||
|
|
||||||
export const ReceiversTable: FC<Props> = ({ config, alertManagerName }) => {
|
export const ReceiversTable: FC<Props> = ({ config, alertManagerName }) => {
|
||||||
const tableStyles = useStyles2(getAlertTableStyles);
|
const tableStyles = useStyles2(getAlertTableStyles);
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const grafanaNotifiers = useUnifiedAlertingSelector((state) => state.grafanaNotifiers);
|
const grafanaNotifiers = useUnifiedAlertingSelector((state) => state.grafanaNotifiers);
|
||||||
|
|
||||||
@@ -29,6 +32,7 @@ export const ReceiversTable: FC<Props> = ({ config, alertManagerName }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ReceiversSection
|
<ReceiversSection
|
||||||
|
className={styles.section}
|
||||||
title="Contact points"
|
title="Contact points"
|
||||||
description="Define where the notifications will be sent to, for example email or Slack."
|
description="Define where the notifications will be sent to, for example email or Slack."
|
||||||
addButtonLabel="New contact point"
|
addButtonLabel="New contact point"
|
||||||
@@ -75,3 +79,9 @@ export const ReceiversTable: FC<Props> = ({ config, alertManagerName }) => {
|
|||||||
</ReceiversSection>
|
</ReceiversSection>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
|
section: css`
|
||||||
|
margin-top: ${theme.spacing(4)};
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ export const TemplateForm: FC<Props> = ({ existing, alertManagerSourceName, conf
|
|||||||
href={makeAMLink('alerting/notifications', alertManagerSourceName)}
|
href={makeAMLink('alerting/notifications', alertManagerSourceName)}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
type="button"
|
type="button"
|
||||||
|
fill="outline"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
|
|||||||
@@ -120,7 +120,8 @@ export function ReceiverForm<R extends ChannelValues>({
|
|||||||
<LinkButton
|
<LinkButton
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
href={makeAMLink('/alerting/notifications', alertManagerSourceName)}
|
fill="outline"
|
||||||
|
href={makeAMLink('alerting/notifications', alertManagerSourceName)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { Select } from '@grafana/ui';
|
import { Select } from '@grafana/ui';
|
||||||
import { SelectBaseProps } from '@grafana/ui/src/components/Select/types';
|
import { SelectBaseProps } from '@grafana/ui/src/components/Select/types';
|
||||||
import { GrafanaAlertState } from 'app/types/unified-alerting-dto';
|
import { GrafanaAlertStateDecision } from 'app/types/unified-alerting-dto';
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
type Props = Omit<SelectBaseProps<GrafanaAlertState>, 'options'>;
|
type Props = Omit<SelectBaseProps<GrafanaAlertStateDecision>, 'options'>;
|
||||||
|
|
||||||
const options: SelectableValue[] = [
|
const options: SelectableValue[] = [
|
||||||
{ value: GrafanaAlertState.Alerting, label: 'Alerting' },
|
{ value: GrafanaAlertStateDecision.Alerting, label: 'Alerting' },
|
||||||
{ value: GrafanaAlertState.NoData, label: 'No Data' },
|
{ value: GrafanaAlertStateDecision.NoData, label: 'No Data' },
|
||||||
{ value: GrafanaAlertState.KeepLastState, label: 'Keep Last State' },
|
{ value: GrafanaAlertStateDecision.KeepLastState, label: 'Keep Last State' },
|
||||||
{ value: GrafanaAlertState.OK, label: 'OK' },
|
{ value: GrafanaAlertStateDecision.OK, label: 'OK' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const GrafanaAlertStatePicker: FC<Props> = (props) => <Select options={options} {...props} />;
|
export const GrafanaAlertStatePicker: FC<Props> = (props) => <Select options={options} {...props} />;
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ export const AlertInstanceDetails: FC<Props> = ({ instance }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
{instance.value && (
|
||||||
<DetailsField label="Value" horizontal={true}>
|
<DetailsField label="Value" horizontal={true}>
|
||||||
{instance.value}
|
{instance.value}
|
||||||
</DetailsField>
|
</DetailsField>
|
||||||
|
)}
|
||||||
{annotations.map(([key, value]) => (
|
{annotations.map(([key, value]) => (
|
||||||
<DetailsField key={key} label={key} horizontal={true}>
|
<DetailsField key={key} label={key} horizontal={true}>
|
||||||
<Annotation annotationKey={key} value={value} />
|
<Annotation annotationKey={key} value={value} />
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { getAlertTableStyles } from '../../styles/table';
|
|||||||
import { alertInstanceKey } from '../../utils/rules';
|
import { alertInstanceKey } from '../../utils/rules';
|
||||||
import { AlertLabels } from '../AlertLabels';
|
import { AlertLabels } from '../AlertLabels';
|
||||||
import { CollapseToggle } from '../CollapseToggle';
|
import { CollapseToggle } from '../CollapseToggle';
|
||||||
import { StateTag } from '../StateTag';
|
|
||||||
import { AlertInstanceDetails } from './AlertInstanceDetails';
|
import { AlertInstanceDetails } from './AlertInstanceDetails';
|
||||||
|
import { AlertStateTag } from './AlertStateTag';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
instances: AlertingRule['alerts'];
|
instances: AlertingRule['alerts'];
|
||||||
@@ -45,25 +45,32 @@ export const AlertInstancesTable: FC<Props> = ({ instances }) => {
|
|||||||
{instances.map((instance, idx) => {
|
{instances.map((instance, idx) => {
|
||||||
const key = alertInstanceKey(instance);
|
const key = alertInstanceKey(instance);
|
||||||
const isExpanded = expandedKeys.includes(key);
|
const isExpanded = expandedKeys.includes(key);
|
||||||
|
|
||||||
|
// don't allow expanding if there's nothing to show
|
||||||
|
const isExpandable = instance.value || !!Object.keys(instance.annotations ?? {}).length;
|
||||||
return (
|
return (
|
||||||
<Fragment key={key}>
|
<Fragment key={key}>
|
||||||
<tr className={idx % 2 === 0 ? tableStyles.evenRow : undefined}>
|
<tr className={idx % 2 === 0 ? tableStyles.evenRow : undefined}>
|
||||||
<td>
|
<td>
|
||||||
|
{isExpandable && (
|
||||||
<CollapseToggle
|
<CollapseToggle
|
||||||
isCollapsed={!isExpanded}
|
isCollapsed={!isExpanded}
|
||||||
onToggle={() => toggleExpandedState(key)}
|
onToggle={() => toggleExpandedState(key)}
|
||||||
data-testid="alert-collapse-toggle"
|
data-testid="alert-collapse-toggle"
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<StateTag status={instance.state} />
|
<AlertStateTag state={instance.state} />
|
||||||
</td>
|
</td>
|
||||||
<td className={styles.labelsCell}>
|
<td className={styles.labelsCell}>
|
||||||
<AlertLabels labels={instance.labels} />
|
<AlertLabels labels={instance.labels} />
|
||||||
</td>
|
</td>
|
||||||
<td className={styles.createdCell}>{instance.activeAt.substr(0, 19).replace('T', ' ')}</td>
|
<td className={styles.createdCell}>
|
||||||
|
{instance.activeAt.startsWith('0001') ? '-' : instance.activeAt.substr(0, 19).replace('T', ' ')}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{isExpanded && (
|
{isExpanded && isExpandable && (
|
||||||
<tr className={idx % 2 === 0 ? tableStyles.evenRow : undefined}>
|
<tr className={idx % 2 === 0 ? tableStyles.evenRow : undefined}>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td colSpan={3}>
|
<td colSpan={3}>
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { GrafanaAlertState, PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import { State, StateTag } from '../StateTag';
|
||||||
|
|
||||||
|
const alertStateToState: Record<PromAlertingRuleState | GrafanaAlertState, State> = {
|
||||||
|
[PromAlertingRuleState.Inactive]: 'good',
|
||||||
|
[PromAlertingRuleState.Firing]: 'bad',
|
||||||
|
[PromAlertingRuleState.Pending]: 'warning',
|
||||||
|
[GrafanaAlertState.Alerting]: 'bad',
|
||||||
|
[GrafanaAlertState.Error]: 'bad',
|
||||||
|
[GrafanaAlertState.NoData]: 'info',
|
||||||
|
[GrafanaAlertState.Normal]: 'good',
|
||||||
|
[GrafanaAlertState.Pending]: 'warning',
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
state: PromAlertingRuleState | GrafanaAlertState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AlertStateTag: FC<Props> = ({ state }) => <StateTag state={alertStateToState[state]}>{state}</StateTag>;
|
||||||
@@ -4,14 +4,14 @@ import { useStyles } from '@grafana/ui';
|
|||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { GrafanaTheme } from '@grafana/data';
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
import { isAlertingRule, isGrafanaRulerRule } from '../../utils/rules';
|
import { isAlertingRule, isGrafanaRulerRule } from '../../utils/rules';
|
||||||
import { isCloudRulesSource, isGrafanaRulesSource } from '../../utils/datasource';
|
import { isCloudRulesSource } from '../../utils/datasource';
|
||||||
import { Annotation } from '../Annotation';
|
import { Annotation } from '../Annotation';
|
||||||
import { AlertLabels } from '../AlertLabels';
|
import { AlertLabels } from '../AlertLabels';
|
||||||
import { AlertInstancesTable } from './AlertInstancesTable';
|
import { AlertInstancesTable } from './AlertInstancesTable';
|
||||||
import { DetailsField } from '../DetailsField';
|
import { DetailsField } from '../DetailsField';
|
||||||
import { RuleQuery } from './RuleQuery';
|
|
||||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||||
import { ExpressionDatasourceUID } from 'app/features/expressions/ExpressionDatasource';
|
import { ExpressionDatasourceUID } from 'app/features/expressions/ExpressionDatasource';
|
||||||
|
import { Expression } from '../Expression';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
rule: CombinedRule;
|
rule: CombinedRule;
|
||||||
@@ -57,13 +57,15 @@ export const RuleDetails: FC<Props> = ({ rule, rulesSource }) => {
|
|||||||
<AlertLabels labels={rule.labels} />
|
<AlertLabels labels={rule.labels} />
|
||||||
</DetailsField>
|
</DetailsField>
|
||||||
)}
|
)}
|
||||||
|
{isCloudRulesSource(rulesSource) && (
|
||||||
<DetailsField
|
<DetailsField
|
||||||
label={isGrafanaRulesSource(rulesSource) ? 'Query' : 'Expression'}
|
label="Expression"
|
||||||
className={cx({ [styles.exprRow]: !!annotations.length })}
|
className={cx({ [styles.exprRow]: !!annotations.length })}
|
||||||
horizontal={true}
|
horizontal={true}
|
||||||
>
|
>
|
||||||
<RuleQuery rule={rule} rulesSource={rulesSource} />
|
<Expression expression={rule.query} rulesSource={rulesSource} />
|
||||||
</DetailsField>
|
</DetailsField>
|
||||||
|
)}
|
||||||
{annotations.map(([key, value]) => (
|
{annotations.map(([key, value]) => (
|
||||||
<DetailsField key={key} label={key} horizontal={true}>
|
<DetailsField key={key} label={key} horizontal={true}>
|
||||||
<Annotation annotationKey={key} value={value} />
|
<Annotation annotationKey={key} value={value} />
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
import { useTheme2 } from '@grafana/ui';
|
|
||||||
import { CombinedRule, RulesSource } from 'app/types/unified-alerting';
|
|
||||||
import React, { FC, useState } from 'react';
|
|
||||||
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
|
|
||||||
import { isGrafanaRulerRule } from '../../utils/rules';
|
|
||||||
import { Expression } from '../Expression';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
rule: CombinedRule;
|
|
||||||
rulesSource: RulesSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RuleQuery: FC<Props> = ({ rule, rulesSource }) => {
|
|
||||||
const { rulerRule } = rule;
|
|
||||||
const theme = useTheme2();
|
|
||||||
|
|
||||||
const [isHidden, setIsHidden] = useState(true);
|
|
||||||
|
|
||||||
if (rulesSource !== GRAFANA_RULES_SOURCE_NAME) {
|
|
||||||
return <Expression expression={rule.query} rulesSource={rulesSource} />;
|
|
||||||
}
|
|
||||||
if (rulerRule && isGrafanaRulerRule(rulerRule)) {
|
|
||||||
// @TODO: better grafana queries vizualization read-only
|
|
||||||
if (isHidden) {
|
|
||||||
return (
|
|
||||||
<a style={{ color: theme.colors.text.link }} onClick={() => setIsHidden(false)}>
|
|
||||||
Show raw query JSON
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <pre>{JSON.stringify(rulerRule.grafana_alert.data, null, 2)}</pre>;
|
|
||||||
}
|
|
||||||
return <pre>@TODO: handle grafana prom rule case</pre>;
|
|
||||||
};
|
|
||||||
@@ -4,7 +4,6 @@ import React, { FC, Fragment, useState } from 'react';
|
|||||||
import { getRuleIdentifier, isAlertingRule, stringifyRuleIdentifier } from '../../utils/rules';
|
import { getRuleIdentifier, isAlertingRule, stringifyRuleIdentifier } from '../../utils/rules';
|
||||||
import { CollapseToggle } from '../CollapseToggle';
|
import { CollapseToggle } from '../CollapseToggle';
|
||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { StateTag } from '../StateTag';
|
|
||||||
import { RuleDetails } from './RuleDetails';
|
import { RuleDetails } from './RuleDetails';
|
||||||
import { getAlertTableStyles } from '../../styles/table';
|
import { getAlertTableStyles } from '../../styles/table';
|
||||||
import { ActionIcon } from './ActionIcon';
|
import { ActionIcon } from './ActionIcon';
|
||||||
@@ -14,6 +13,7 @@ import { useDispatch } from 'react-redux';
|
|||||||
import { deleteRuleAction } from '../../state/actions';
|
import { deleteRuleAction } from '../../state/actions';
|
||||||
import { useHasRuler } from '../../hooks/useHasRuler';
|
import { useHasRuler } from '../../hooks/useHasRuler';
|
||||||
import { CombinedRule } from 'app/types/unified-alerting';
|
import { CombinedRule } from 'app/types/unified-alerting';
|
||||||
|
import { AlertStateTag } from './AlertStateTag';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
rules: CombinedRule[];
|
rules: CombinedRule[];
|
||||||
@@ -63,7 +63,7 @@ export const RulesTable: FC<Props> = ({
|
|||||||
const wrapperClass = cx(styles.wrapper, { [styles.wrapperMargin]: showGuidelines });
|
const wrapperClass = cx(styles.wrapper, { [styles.wrapperMargin]: showGuidelines });
|
||||||
|
|
||||||
if (!rules.length) {
|
if (!rules.length) {
|
||||||
return <div className={wrapperClass}>{emptyMessage}</div>;
|
return <div className={cx(wrapperClass, styles.emptyMessage)}>{emptyMessage}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -126,7 +126,7 @@ export const RulesTable: FC<Props> = ({
|
|||||||
data-testid="rule-collapse-toggle"
|
data-testid="rule-collapse-toggle"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>{promRule && isAlertingRule(promRule) ? <StateTag status={promRule.state} /> : 'n/a'}</td>
|
<td>{promRule && isAlertingRule(promRule) ? <AlertStateTag state={promRule.state} /> : 'n/a'}</td>
|
||||||
<td>{rule.name}</td>
|
<td>{rule.name}</td>
|
||||||
{showGroupColumn && (
|
{showGroupColumn && (
|
||||||
<td>{isCloudRulesSource(rulesSource) ? `${namespace.name} > ${group.name}` : namespace.name}</td>
|
<td>{isCloudRulesSource(rulesSource) ? `${namespace.name} > ${group.name}` : namespace.name}</td>
|
||||||
@@ -194,6 +194,9 @@ export const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
wrapperMargin: css`
|
wrapperMargin: css`
|
||||||
margin-left: 36px;
|
margin-left: 36px;
|
||||||
`,
|
`,
|
||||||
|
emptyMessage: css`
|
||||||
|
padding: ${theme.spacing(1)};
|
||||||
|
`,
|
||||||
wrapper: css`
|
wrapper: css`
|
||||||
margin-top: ${theme.spacing(3)};
|
margin-top: ${theme.spacing(3)};
|
||||||
width: auto;
|
width: auto;
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { AlertState } from 'app/plugins/datasource/alertmanager/types';
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import { State, StateTag } from '../StateTag';
|
||||||
|
|
||||||
|
const alertStateToState: Record<AlertState, State> = {
|
||||||
|
[AlertState.Active]: 'bad',
|
||||||
|
[AlertState.Unprocessed]: 'neutral',
|
||||||
|
[AlertState.Suppressed]: 'info',
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
state: AlertState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AmAlertStateTag: FC<Props> = ({ state }) => <StateTag state={alertStateToState[state]}>{state}</StateTag>;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { SilenceState } from 'app/plugins/datasource/alertmanager/types';
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import { State, StateTag } from '../StateTag';
|
||||||
|
|
||||||
|
const silenceStateToState: Record<SilenceState, State> = {
|
||||||
|
[SilenceState.Active]: 'good',
|
||||||
|
[SilenceState.Expired]: 'neutral',
|
||||||
|
[SilenceState.Pending]: 'neutral',
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
state: SilenceState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SilenceStateTag: FC<Props> = ({ state }) => (
|
||||||
|
<StateTag state={silenceStateToState[state]}>{state}</StateTag>
|
||||||
|
);
|
||||||
@@ -2,7 +2,6 @@ import React, { FC, Fragment, useState } from 'react';
|
|||||||
import { dateMath, GrafanaTheme, toDuration } from '@grafana/data';
|
import { dateMath, GrafanaTheme, toDuration } from '@grafana/data';
|
||||||
import { css, cx } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { Silence, AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types';
|
import { Silence, AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types';
|
||||||
import { StateTag } from '../StateTag';
|
|
||||||
import { CollapseToggle } from '../CollapseToggle';
|
import { CollapseToggle } from '../CollapseToggle';
|
||||||
import { ActionButton } from '../rules/ActionButton';
|
import { ActionButton } from '../rules/ActionButton';
|
||||||
import { ActionIcon } from '../rules/ActionIcon';
|
import { ActionIcon } from '../rules/ActionIcon';
|
||||||
@@ -11,6 +10,7 @@ import SilencedAlertsTable from './SilencedAlertsTable';
|
|||||||
import { expireSilenceAction } from '../../state/actions';
|
import { expireSilenceAction } from '../../state/actions';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { Matchers } from './Matchers';
|
import { Matchers } from './Matchers';
|
||||||
|
import { SilenceStateTag } from './SilenceStateTag';
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
silence: Silence;
|
silence: Silence;
|
||||||
@@ -41,7 +41,7 @@ const SilenceTableRow: FC<Props> = ({ silence, className, silencedAlerts, alertM
|
|||||||
<CollapseToggle isCollapsed={isCollapsed} onToggle={(value) => setIsCollapsed(value)} />
|
<CollapseToggle isCollapsed={isCollapsed} onToggle={(value) => setIsCollapsed(value)} />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<StateTag status={status.state}>{status.state}</StateTag>
|
<SilenceStateTag state={status.state} />
|
||||||
</td>
|
</td>
|
||||||
<td className={styles.matchersCell}>
|
<td className={styles.matchersCell}>
|
||||||
<Matchers matchers={matchers} />
|
<Matchers matchers={matchers} />
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types';
|
import { AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types';
|
||||||
import React, { FC, useState } from 'react';
|
import React, { FC, useState } from 'react';
|
||||||
import { CollapseToggle } from '../CollapseToggle';
|
import { CollapseToggle } from '../CollapseToggle';
|
||||||
import { StateTag } from '../StateTag';
|
|
||||||
import { ActionIcon } from '../rules/ActionIcon';
|
import { ActionIcon } from '../rules/ActionIcon';
|
||||||
import { getAlertTableStyles } from '../../styles/table';
|
import { getAlertTableStyles } from '../../styles/table';
|
||||||
import { useStyles2 } from '@grafana/ui';
|
import { useStyles2 } from '@grafana/ui';
|
||||||
import { dateTimeAsMoment, toDuration } from '@grafana/data';
|
import { dateTimeAsMoment, toDuration } from '@grafana/data';
|
||||||
import { AlertLabels } from '../AlertLabels';
|
import { AlertLabels } from '../AlertLabels';
|
||||||
|
import { AmAlertStateTag } from './AmAlertStateTag';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
alert: AlertmanagerAlert;
|
alert: AlertmanagerAlert;
|
||||||
@@ -30,7 +30,7 @@ export const SilencedAlertsTableRow: FC<Props> = ({ alert, className }) => {
|
|||||||
<CollapseToggle isCollapsed={isCollapsed} onToggle={(collapsed) => setIsCollapsed(collapsed)} />
|
<CollapseToggle isCollapsed={isCollapsed} onToggle={(collapsed) => setIsCollapsed(collapsed)} />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<StateTag status={alert.status.state}>{alert.status.state}</StateTag>
|
<AmAlertStateTag state={alert.status.state} />
|
||||||
</td>
|
</td>
|
||||||
<td>for {alertDuration} seconds</td>
|
<td>for {alertDuration} seconds</td>
|
||||||
<td>{alertName}</td>
|
<td>{alertName}</td>
|
||||||
|
|||||||
@@ -1,26 +1,19 @@
|
|||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Icon, useStyles2, Link, Button, Field } from '@grafana/ui';
|
import { Icon, useStyles2, Link, Button } from '@grafana/ui';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { AlertmanagerAlert, Silence } from 'app/plugins/datasource/alertmanager/types';
|
import { AlertmanagerAlert, Silence } from 'app/plugins/datasource/alertmanager/types';
|
||||||
import SilenceTableRow from './SilenceTableRow';
|
import SilenceTableRow from './SilenceTableRow';
|
||||||
import { getAlertTableStyles } from '../../styles/table';
|
import { getAlertTableStyles } from '../../styles/table';
|
||||||
import { NoSilencesSplash } from './NoSilencesCTA';
|
import { NoSilencesSplash } from './NoSilencesCTA';
|
||||||
import { AlertManagerPicker } from '../AlertManagerPicker';
|
|
||||||
import { makeAMLink } from '../../utils/misc';
|
import { makeAMLink } from '../../utils/misc';
|
||||||
interface Props {
|
interface Props {
|
||||||
silences: Silence[];
|
silences: Silence[];
|
||||||
alertManagerAlerts: AlertmanagerAlert[];
|
alertManagerAlerts: AlertmanagerAlert[];
|
||||||
alertManagerSourceName: string;
|
alertManagerSourceName: string;
|
||||||
setAlertManagerSourceName(name: string): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SilencesTable: FC<Props> = ({
|
const SilencesTable: FC<Props> = ({ silences, alertManagerAlerts, alertManagerSourceName }) => {
|
||||||
silences,
|
|
||||||
alertManagerAlerts,
|
|
||||||
alertManagerSourceName,
|
|
||||||
setAlertManagerSourceName,
|
|
||||||
}) => {
|
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const tableStyles = useStyles2(getAlertTableStyles);
|
const tableStyles = useStyles2(getAlertTableStyles);
|
||||||
|
|
||||||
@@ -30,9 +23,6 @@ const SilencesTable: FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Field label="Choose alert manager">
|
|
||||||
<AlertManagerPicker current={alertManagerSourceName} onChange={setAlertManagerSourceName} />
|
|
||||||
</Field>
|
|
||||||
{!!silences.length && (
|
{!!silences.length && (
|
||||||
<>
|
<>
|
||||||
<Link href={makeAMLink('/alerting/silence/new', alertManagerSourceName)}>
|
<Link href={makeAMLink('/alerting/silence/new', alertManagerSourceName)}>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { GrafanaQuery, GrafanaAlertState } from 'app/types/unified-alerting-dto';
|
import { GrafanaQuery, GrafanaAlertStateDecision } from 'app/types/unified-alerting-dto';
|
||||||
|
|
||||||
export enum RuleFormType {
|
export enum RuleFormType {
|
||||||
threshold = 'threshold',
|
threshold = 'threshold',
|
||||||
@@ -17,8 +17,8 @@ export interface RuleFormValues {
|
|||||||
// threshold alerts
|
// threshold alerts
|
||||||
queries: GrafanaQuery[];
|
queries: GrafanaQuery[];
|
||||||
condition: string | null; // refId of the query that gets alerted on
|
condition: string | null; // refId of the query that gets alerted on
|
||||||
noDataState: GrafanaAlertState;
|
noDataState: GrafanaAlertStateDecision;
|
||||||
execErrState: GrafanaAlertState;
|
execErrState: GrafanaAlertStateDecision;
|
||||||
folder: { title: string; id: number } | null;
|
folder: { title: string; id: number } | null;
|
||||||
evaluateEvery: string;
|
evaluateEvery: string;
|
||||||
evaluateFor: string;
|
evaluateFor: string;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/t
|
|||||||
import { RuleWithLocation } from 'app/types/unified-alerting';
|
import { RuleWithLocation } from 'app/types/unified-alerting';
|
||||||
import {
|
import {
|
||||||
Annotations,
|
Annotations,
|
||||||
GrafanaAlertState,
|
GrafanaAlertStateDecision,
|
||||||
GrafanaQuery,
|
GrafanaQuery,
|
||||||
Labels,
|
Labels,
|
||||||
PostableRuleGrafanaRuleDTO,
|
PostableRuleGrafanaRuleDTO,
|
||||||
@@ -28,8 +28,8 @@ export const defaultFormValues: RuleFormValues = Object.freeze({
|
|||||||
folder: null,
|
folder: null,
|
||||||
queries: [],
|
queries: [],
|
||||||
condition: '',
|
condition: '',
|
||||||
noDataState: GrafanaAlertState.NoData,
|
noDataState: GrafanaAlertStateDecision.NoData,
|
||||||
execErrState: GrafanaAlertState.Alerting,
|
execErrState: GrafanaAlertStateDecision.Alerting,
|
||||||
evaluateEvery: '1m',
|
evaluateEvery: '1m',
|
||||||
evaluateFor: '5m',
|
evaluateFor: '5m',
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,14 @@ export enum PromAlertingRuleState {
|
|||||||
Pending = 'pending',
|
Pending = 'pending',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum GrafanaAlertState {
|
||||||
|
Normal = 'Normal',
|
||||||
|
Alerting = 'Alerting',
|
||||||
|
Pending = 'Pending',
|
||||||
|
NoData = 'NoData',
|
||||||
|
Error = 'Error',
|
||||||
|
}
|
||||||
|
|
||||||
export enum PromRuleType {
|
export enum PromRuleType {
|
||||||
Alerting = 'alerting',
|
Alerting = 'alerting',
|
||||||
Recording = 'recording',
|
Recording = 'recording',
|
||||||
@@ -29,7 +37,7 @@ export interface PromAlertingRuleDTO extends PromRuleDTOBase {
|
|||||||
alerts: Array<{
|
alerts: Array<{
|
||||||
labels: Labels;
|
labels: Labels;
|
||||||
annotations: Annotations;
|
annotations: Annotations;
|
||||||
state: Exclude<PromAlertingRuleState, PromAlertingRuleState.Inactive>;
|
state: Exclude<PromAlertingRuleState | GrafanaAlertState, PromAlertingRuleState.Inactive>;
|
||||||
activeAt: string;
|
activeAt: string;
|
||||||
value: string;
|
value: string;
|
||||||
}>;
|
}>;
|
||||||
@@ -86,7 +94,7 @@ export interface RulerAlertingRuleDTO extends RulerRuleBaseDTO {
|
|||||||
annotations?: Annotations;
|
annotations?: Annotations;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum GrafanaAlertState {
|
export enum GrafanaAlertStateDecision {
|
||||||
Alerting = 'Alerting',
|
Alerting = 'Alerting',
|
||||||
NoData = 'NoData',
|
NoData = 'NoData',
|
||||||
KeepLastState = 'KeepLastState',
|
KeepLastState = 'KeepLastState',
|
||||||
@@ -104,8 +112,8 @@ export interface PostableGrafanaRuleDefinition {
|
|||||||
uid?: string;
|
uid?: string;
|
||||||
title: string;
|
title: string;
|
||||||
condition: string;
|
condition: string;
|
||||||
no_data_state: GrafanaAlertState;
|
no_data_state: GrafanaAlertStateDecision;
|
||||||
exec_err_state: GrafanaAlertState;
|
exec_err_state: GrafanaAlertStateDecision;
|
||||||
data: GrafanaQuery[];
|
data: GrafanaQuery[];
|
||||||
}
|
}
|
||||||
export interface GrafanaRuleDefinition extends PostableGrafanaRuleDefinition {
|
export interface GrafanaRuleDefinition extends PostableGrafanaRuleDefinition {
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ import {
|
|||||||
Labels,
|
Labels,
|
||||||
Annotations,
|
Annotations,
|
||||||
RulerRuleGroupDTO,
|
RulerRuleGroupDTO,
|
||||||
|
GrafanaAlertState,
|
||||||
} from './unified-alerting-dto';
|
} from './unified-alerting-dto';
|
||||||
|
|
||||||
export type Alert = {
|
export type Alert = {
|
||||||
activeAt: string;
|
activeAt: string;
|
||||||
annotations: { [key: string]: string };
|
annotations: { [key: string]: string };
|
||||||
labels: { [key: string]: string };
|
labels: { [key: string]: string };
|
||||||
state: PromAlertingRuleState;
|
state: PromAlertingRuleState | GrafanaAlertState;
|
||||||
value: string;
|
value: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user