mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: New alert list panel component (#34614)
This commit is contained in:
parent
7c25465b3a
commit
7dd5a065ba
@ -67,6 +67,7 @@ export const getAvailableIcons = () =>
|
||||
'file-copy-alt',
|
||||
'filter',
|
||||
'folder',
|
||||
'fire',
|
||||
'folder-open',
|
||||
'folder-plus',
|
||||
'folder-upload',
|
||||
@ -86,6 +87,7 @@ export const getAvailableIcons = () =>
|
||||
'heart-break',
|
||||
'history',
|
||||
'home-alt',
|
||||
'hourglass',
|
||||
'import',
|
||||
'info-circle',
|
||||
'key-skeleton-alt',
|
||||
|
@ -19,10 +19,12 @@ const conditionTypes = [{ text: 'Query', value: 'query' }];
|
||||
|
||||
const alertStateSortScore = {
|
||||
alerting: 1,
|
||||
firing: 1,
|
||||
no_data: 2,
|
||||
pending: 3,
|
||||
ok: 4,
|
||||
paused: 5,
|
||||
inactive: 5,
|
||||
};
|
||||
|
||||
export enum EvalFunction {
|
||||
@ -111,7 +113,7 @@ function getStateDisplayModel(state: string) {
|
||||
case 'pending': {
|
||||
return {
|
||||
text: 'PENDING',
|
||||
iconClass: 'exclamation-triangle',
|
||||
iconClass: 'hourglass',
|
||||
stateClass: 'alert-state-warning',
|
||||
};
|
||||
}
|
||||
@ -122,6 +124,22 @@ function getStateDisplayModel(state: string) {
|
||||
stateClass: 'alert-state-paused',
|
||||
};
|
||||
}
|
||||
|
||||
case 'firing': {
|
||||
return {
|
||||
text: 'FIRING',
|
||||
iconClass: 'fire',
|
||||
stateClass: '',
|
||||
};
|
||||
}
|
||||
|
||||
case 'inactive': {
|
||||
return {
|
||||
text: 'INACTIVE',
|
||||
iconClass: 'check',
|
||||
stateClass: '',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
throw { message: 'Unknown alert state' };
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { RuleNamespace } from 'app/types/unified-alerting';
|
||||
import { PromRulesResponse } from 'app/types/unified-alerting-dto';
|
||||
import { getDatasourceAPIId } from '../utils/datasource';
|
||||
import { getAllRulesSourceNames, getDatasourceAPIId } from '../utils/datasource';
|
||||
|
||||
export async function fetchRules(dataSourceName: string): Promise<RuleNamespace[]> {
|
||||
const response = await getBackendSrv()
|
||||
@ -30,3 +30,17 @@ export async function fetchRules(dataSourceName: string): Promise<RuleNamespace[
|
||||
|
||||
return Object.values(nsMap);
|
||||
}
|
||||
|
||||
export async function fetchAllRules(): Promise<RuleNamespace[]> {
|
||||
const namespaces = [] as Array<Promise<RuleNamespace[]>>;
|
||||
getAllRulesSourceNames().forEach(async (name) => {
|
||||
namespaces.push(
|
||||
fetchRules(name).catch((e) => {
|
||||
return [];
|
||||
// TODO add error comms
|
||||
})
|
||||
);
|
||||
});
|
||||
const promises = await Promise.all(namespaces);
|
||||
return promises.flat();
|
||||
}
|
||||
|
@ -1,19 +1,7 @@
|
||||
import { GrafanaAlertState, PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||
import React, { FC } from 'react';
|
||||
import { alertStateToReadable } from '../../utils/rules';
|
||||
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',
|
||||
};
|
||||
|
||||
import { alertStateToReadable, alertStateToState } from '../../utils/rules';
|
||||
import { StateTag } from '../StateTag';
|
||||
interface Props {
|
||||
state: PromAlertingRuleState | GrafanaAlertState;
|
||||
}
|
||||
|
@ -2,9 +2,9 @@ import { css } from '@emotion/css';
|
||||
import { GrafanaTheme2, intervalToAbbreviatedDurationString } from '@grafana/data';
|
||||
import { HorizontalGroup, Spinner, useStyles2 } from '@grafana/ui';
|
||||
import { CombinedRule } from 'app/types/unified-alerting';
|
||||
import { GrafanaAlertState, PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||
import React, { FC, useMemo } from 'react';
|
||||
import { isAlertingRule, isRecordingRule } from '../../utils/rules';
|
||||
import { isAlertingRule, isRecordingRule, getFirstActiveAt } from '../../utils/rules';
|
||||
import { AlertStateTag } from './AlertStateTag';
|
||||
|
||||
interface Props {
|
||||
@ -26,15 +26,7 @@ export const RuleState: FC<Props> = ({ rule, isDeleting, isCreating }) => {
|
||||
promRule.state !== PromAlertingRuleState.Inactive
|
||||
) {
|
||||
// find earliest alert
|
||||
const firstActiveAt = promRule.alerts.reduce((prev, alert) => {
|
||||
if (alert.activeAt && alert.state !== GrafanaAlertState.Normal) {
|
||||
const activeAt = new Date(alert.activeAt);
|
||||
if (prev === null || prev.getTime() > activeAt.getTime()) {
|
||||
return activeAt;
|
||||
}
|
||||
}
|
||||
return prev;
|
||||
}, null as Date | null);
|
||||
const firstActiveAt = getFirstActiveAt(promRule);
|
||||
|
||||
// calculate time elapsed from earliest alert
|
||||
if (firstActiveAt) {
|
||||
|
@ -98,6 +98,17 @@ export function fetchAllPromAndRulerRulesAction(force = false): ThunkResult<void
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchAllPromRulesAction(force = false): ThunkResult<void> {
|
||||
return (dispatch, getStore) => {
|
||||
const { promRules } = getStore().unifiedAlerting;
|
||||
getAllRulesSourceNames().map((name) => {
|
||||
if (force || !promRules[name]?.loading) {
|
||||
dispatch(fetchPromRulesAction(name));
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
async function findExistingRule(ruleIdentifier: RuleIdentifier): Promise<RuleWithLocation | null> {
|
||||
if (isGrafanaRuleIdentifier(ruleIdentifier)) {
|
||||
const namespaces = await fetchRulerRules(GRAFANA_RULES_SOURCE_NAME);
|
||||
|
@ -14,15 +14,18 @@ import {
|
||||
AlertingRule,
|
||||
CloudRuleIdentifier,
|
||||
GrafanaRuleIdentifier,
|
||||
PromRuleWithLocation,
|
||||
RecordingRule,
|
||||
Rule,
|
||||
RuleIdentifier,
|
||||
RuleNamespace,
|
||||
RuleWithLocation,
|
||||
} from 'app/types/unified-alerting';
|
||||
import { AsyncRequestState } from './redux';
|
||||
import { RULER_NOT_SUPPORTED_MSG } from './constants';
|
||||
import { hash } from './misc';
|
||||
import { capitalize } from 'lodash';
|
||||
import { State } from '../components/StateTag';
|
||||
|
||||
export function isAlertingRule(rule: Rule): rule is AlertingRule {
|
||||
return rule.type === PromRuleType.Alerting;
|
||||
@ -142,3 +145,42 @@ export function alertStateToReadable(state: PromAlertingRuleState | GrafanaAlert
|
||||
}
|
||||
return capitalize(state);
|
||||
}
|
||||
|
||||
export const flattenRules = (rules: RuleNamespace[]) => {
|
||||
return rules.reduce<PromRuleWithLocation[]>((acc, { dataSourceName, name: namespaceName, groups }) => {
|
||||
groups.forEach(({ name: groupName, rules }) => {
|
||||
rules.forEach((rule) => {
|
||||
if (isAlertingRule(rule)) {
|
||||
acc.push({ dataSourceName, namespaceName, groupName, rule });
|
||||
}
|
||||
});
|
||||
});
|
||||
return acc;
|
||||
}, []);
|
||||
};
|
||||
|
||||
export 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',
|
||||
};
|
||||
|
||||
export function getFirstActiveAt(promRule: AlertingRule) {
|
||||
if (!promRule.alerts) {
|
||||
return null;
|
||||
}
|
||||
return promRule.alerts.reduce((prev, alert) => {
|
||||
if (alert.activeAt && alert.state !== GrafanaAlertState.Normal) {
|
||||
const activeAt = new Date(alert.activeAt);
|
||||
if (prev === null || prev.getTime() > activeAt.getTime()) {
|
||||
return activeAt;
|
||||
}
|
||||
}
|
||||
return prev;
|
||||
}, null as Date | null);
|
||||
}
|
||||
|
78
public/app/plugins/panel/alertlist/AlertInstances.tsx
Normal file
78
public/app/plugins/panel/alertlist/AlertInstances.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import pluralize from 'pluralize';
|
||||
import { Icon, useStyles2 } from '@grafana/ui';
|
||||
import { Alert, PromRuleWithLocation } from 'app/types/unified-alerting';
|
||||
import { AlertLabels } from 'app/features/alerting/unified/components/AlertLabels';
|
||||
import { AlertStateTag } from 'app/features/alerting/unified/components/rules/AlertStateTag';
|
||||
import { dateTime, GrafanaTheme2 } from '@grafana/data';
|
||||
import { css } from '@emotion/css';
|
||||
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||
import { omit } from 'lodash';
|
||||
import { alertInstanceKey } from 'app/features/alerting/unified/utils/rules';
|
||||
|
||||
interface Props {
|
||||
ruleWithLocation: PromRuleWithLocation;
|
||||
showInstances: boolean;
|
||||
}
|
||||
|
||||
export const AlertInstances = ({ ruleWithLocation, showInstances }: Props) => {
|
||||
const { rule } = ruleWithLocation;
|
||||
const [displayInstances, setDisplayInstances] = useState<boolean>(showInstances);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
useEffect(() => {
|
||||
setDisplayInstances(showInstances);
|
||||
}, [showInstances]);
|
||||
|
||||
// sort instances, because API returns them in random order every time
|
||||
const sortedAlerts = useMemo(
|
||||
(): Alert[] =>
|
||||
displayInstances
|
||||
? rule.alerts.slice().sort((a, b) => alertInstanceKey(a).localeCompare(alertInstanceKey(b)))
|
||||
: [],
|
||||
[rule, displayInstances]
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{rule.state !== PromAlertingRuleState.Inactive && (
|
||||
<div className={styles.instance} onClick={() => setDisplayInstances(!displayInstances)}>
|
||||
<Icon name={displayInstances ? 'angle-down' : 'angle-right'} size={'md'} />
|
||||
<span>{`${rule.alerts.length} ${pluralize('instance', rule.alerts.length)}`}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!!sortedAlerts.length && (
|
||||
<ol className={styles.list}>
|
||||
{sortedAlerts.map((alert, index) => {
|
||||
return (
|
||||
<li className={styles.listItem} key={`${alert.activeAt}-${index}`}>
|
||||
<div>
|
||||
<AlertStateTag state={alert.state} />
|
||||
<span className={styles.date}>{dateTime(alert.activeAt).format('YYYY-MM-DD HH:mm:ss')}</span>
|
||||
</div>
|
||||
<AlertLabels labels={omit(alert.labels, 'alertname')} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
instance: css`
|
||||
cursor: pointer;
|
||||
`,
|
||||
list: css`
|
||||
list-style-type: none;
|
||||
`,
|
||||
listItem: css`
|
||||
margin-top: ${theme.spacing(1)};
|
||||
`,
|
||||
date: css`
|
||||
font-size: ${theme.typography.bodySmall.fontSize};
|
||||
padding-left: ${theme.spacing(0.5)};
|
||||
`,
|
||||
});
|
296
public/app/plugins/panel/alertlist/UnifiedAlertList.tsx
Normal file
296
public/app/plugins/panel/alertlist/UnifiedAlertList.tsx
Normal file
@ -0,0 +1,296 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { sortBy } from 'lodash';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { GrafanaTheme, GrafanaTheme2, intervalToAbbreviatedDurationString, PanelProps } from '@grafana/data';
|
||||
import { CustomScrollbar, Icon, IconName, LoadingPlaceholder, useStyles, useStyles2 } from '@grafana/ui';
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { AlertInstances } from './AlertInstances';
|
||||
import alertDef from 'app/features/alerting/state/alertDef';
|
||||
import { SortOrder, UnifiedAlertListOptions } from './types';
|
||||
|
||||
import { flattenRules, alertStateToState, getFirstActiveAt } from 'app/features/alerting/unified/utils/rules';
|
||||
import { PromRuleWithLocation } from 'app/types/unified-alerting';
|
||||
import { fetchAllPromRulesAction } from 'app/features/alerting/unified/state/actions';
|
||||
import { useUnifiedAlertingSelector } from 'app/features/alerting/unified/hooks/useUnifiedAlertingSelector';
|
||||
import { getAllRulesSourceNames } from 'app/features/alerting/unified/utils/datasource';
|
||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { Annotation, RULE_LIST_POLL_INTERVAL_MS } from 'app/features/alerting/unified/utils/constants';
|
||||
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||
|
||||
export function UnifiedAlertList(props: PanelProps<UnifiedAlertListOptions>) {
|
||||
const dispatch = useDispatch();
|
||||
const rulesDataSourceNames = useMemo(getAllRulesSourceNames, []);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchAllPromRulesAction());
|
||||
const interval = setInterval(() => dispatch(fetchAllPromRulesAction()), RULE_LIST_POLL_INTERVAL_MS);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
const promRulesRequests = useUnifiedAlertingSelector((state) => state.promRules);
|
||||
|
||||
const dispatched = rulesDataSourceNames.some((name) => promRulesRequests[name]?.dispatched);
|
||||
const loading = rulesDataSourceNames.some((name) => promRulesRequests[name]?.loading);
|
||||
const haveResults = rulesDataSourceNames.some(
|
||||
(name) => promRulesRequests[name]?.result?.length && !promRulesRequests[name]?.error
|
||||
);
|
||||
|
||||
const styles = useStyles(getStyles);
|
||||
const stateStyle = useStyles2(getStateTagStyles);
|
||||
|
||||
const rules = useMemo(
|
||||
() =>
|
||||
filterRules(
|
||||
props.options,
|
||||
sortRules(
|
||||
props.options.sortOrder,
|
||||
Object.values(promRulesRequests).flatMap(({ result = [] }) => flattenRules(result))
|
||||
)
|
||||
),
|
||||
[props.options, promRulesRequests]
|
||||
);
|
||||
|
||||
const rulesToDisplay = rules.length <= props.options.maxItems ? rules : rules.slice(0, props.options.maxItems);
|
||||
|
||||
const noAlertsMessage = rules.length ? '' : 'No alerts';
|
||||
|
||||
return (
|
||||
<CustomScrollbar autoHeightMin="100%" autoHeightMax="100%">
|
||||
<div className={styles.container}>
|
||||
{dispatched && loading && !haveResults && <LoadingPlaceholder text="Loading..." />}
|
||||
{noAlertsMessage && <div className={styles.noAlertsMessage}>{noAlertsMessage}</div>}
|
||||
<section>
|
||||
<ol className={styles.alertRuleList}>
|
||||
{haveResults &&
|
||||
rulesToDisplay.map((ruleWithLocation, index) => {
|
||||
const { rule, namespaceName, groupName } = ruleWithLocation;
|
||||
const firstActiveAt = getFirstActiveAt(rule);
|
||||
return (
|
||||
<li
|
||||
className={styles.alertRuleItem}
|
||||
key={`alert-${namespaceName}-${groupName}-${rule.name}-${index}`}
|
||||
>
|
||||
<div className={stateStyle.icon}>
|
||||
<Icon
|
||||
name={alertDef.getStateDisplayModel(rule.state).iconClass as IconName}
|
||||
className={stateStyle[alertStateToState[rule.state]]}
|
||||
size={'lg'}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className={styles.instanceDetails}>
|
||||
<div className={styles.alertName} title={rule.name}>
|
||||
{rule.name}
|
||||
</div>
|
||||
<div className={styles.alertDuration}>
|
||||
<span className={stateStyle[alertStateToState[rule.state]]}>{rule.state.toUpperCase()}</span>{' '}
|
||||
{firstActiveAt && rule.state !== PromAlertingRuleState.Inactive && (
|
||||
<>
|
||||
for{' '}
|
||||
<span>
|
||||
{intervalToAbbreviatedDurationString({
|
||||
start: firstActiveAt,
|
||||
end: Date.now(),
|
||||
})}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<AlertInstances ruleWithLocation={ruleWithLocation} showInstances={props.options.showInstances} />
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
</section>
|
||||
</div>
|
||||
</CustomScrollbar>
|
||||
);
|
||||
}
|
||||
|
||||
function sortRules(sortOrder: SortOrder, rules: PromRuleWithLocation[]) {
|
||||
if (sortOrder === SortOrder.Importance) {
|
||||
// @ts-ignore
|
||||
return sortBy(rules, (rule) => alertDef.alertStateSortScore[rule.state]);
|
||||
} else if (sortOrder === SortOrder.TimeAsc) {
|
||||
return sortBy(rules, (rule) => getFirstActiveAt(rule.rule) || new Date());
|
||||
} else if (sortOrder === SortOrder.TimeDesc) {
|
||||
return sortBy(rules, (rule) => getFirstActiveAt(rule.rule) || new Date()).reverse();
|
||||
}
|
||||
const result = sortBy(rules, (rule) => rule.rule.name.toLowerCase());
|
||||
if (sortOrder === SortOrder.AlphaDesc) {
|
||||
result.reverse();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function filterRules(options: PanelProps<UnifiedAlertListOptions>['options'], rules: PromRuleWithLocation[]) {
|
||||
let filteredRules = [...rules];
|
||||
if (options.dashboardAlerts) {
|
||||
const dashboardUid = getDashboardSrv().getCurrent()?.uid;
|
||||
filteredRules = filteredRules.filter(({ rule: { annotations = {} } }) =>
|
||||
Object.entries(annotations).some(([key, value]) => key === Annotation.dashboardUID && value === dashboardUid)
|
||||
);
|
||||
}
|
||||
if (options.alertName) {
|
||||
filteredRules = filteredRules.filter(({ rule: { name } }) =>
|
||||
name.toLocaleLowerCase().includes(options.alertName.toLocaleLowerCase())
|
||||
);
|
||||
}
|
||||
if (Object.values(options.stateFilter).some((value) => value)) {
|
||||
filteredRules = filteredRules.filter((rule) => {
|
||||
return (
|
||||
(options.stateFilter.firing && rule.rule.state === PromAlertingRuleState.Firing) ||
|
||||
(options.stateFilter.pending && rule.rule.state === PromAlertingRuleState.Pending) ||
|
||||
(options.stateFilter.inactive && rule.rule.state === PromAlertingRuleState.Inactive)
|
||||
);
|
||||
});
|
||||
}
|
||||
if (options.folder) {
|
||||
filteredRules = filteredRules.filter((rule) => {
|
||||
return rule.namespaceName === options.folder.title;
|
||||
});
|
||||
}
|
||||
|
||||
return filteredRules;
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme) => ({
|
||||
cardContainer: css`
|
||||
padding: ${theme.spacing.xs} 0 ${theme.spacing.xxs} 0;
|
||||
line-height: ${theme.typography.lineHeight.md};
|
||||
margin-bottom: 0px;
|
||||
`,
|
||||
container: css`
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
`,
|
||||
alertRuleList: css`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
list-style-type: none;
|
||||
`,
|
||||
alertRuleItem: css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: ${theme.colors.bg2};
|
||||
padding: ${theme.spacing.xs} ${theme.spacing.sm};
|
||||
border-radius: ${theme.border.radius.md};
|
||||
margin-bottom: ${theme.spacing.xs};
|
||||
|
||||
& > * {
|
||||
margin-right: ${theme.spacing.sm};
|
||||
}
|
||||
`,
|
||||
alertName: css`
|
||||
font-size: ${theme.typography.size.md};
|
||||
font-weight: ${theme.typography.weight.bold};
|
||||
`,
|
||||
alertDuration: css`
|
||||
font-size: ${theme.typography.size.sm};
|
||||
`,
|
||||
alertRuleItemText: css`
|
||||
font-weight: ${theme.typography.weight.bold};
|
||||
font-size: ${theme.typography.size.sm};
|
||||
margin: 0;
|
||||
`,
|
||||
alertRuleItemTime: css`
|
||||
color: ${theme.colors.textWeak};
|
||||
font-weight: normal;
|
||||
white-space: nowrap;
|
||||
`,
|
||||
alertRuleItemInfo: css`
|
||||
font-weight: normal;
|
||||
flex-grow: 2;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
`,
|
||||
noAlertsMessage: css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`,
|
||||
alertIcon: css`
|
||||
margin-right: ${theme.spacing.xs};
|
||||
`,
|
||||
instanceDetails: css`
|
||||
min-width: 1px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`,
|
||||
});
|
||||
|
||||
const getStateTagStyles = (theme: GrafanaTheme2) => ({
|
||||
common: css`
|
||||
width: 70px;
|
||||
text-align: center;
|
||||
align-self: stretch;
|
||||
|
||||
display: inline-block;
|
||||
color: white;
|
||||
border-radius: ${theme.shape.borderRadius()};
|
||||
font-size: ${theme.typography.size.sm};
|
||||
/* padding: ${theme.spacing(2, 0)}; */
|
||||
text-transform: capitalize;
|
||||
line-height: 1.2;
|
||||
flex-shrink: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
`,
|
||||
icon: css`
|
||||
margin-top: ${theme.spacing(2.5)};
|
||||
align-self: flex-start;
|
||||
`,
|
||||
// good: css`
|
||||
// background-color: ${theme.colors.success.main};
|
||||
// border: solid 1px ${theme.colors.success.main};
|
||||
// color: ${theme.colors.success.contrastText};
|
||||
// `,
|
||||
// warning: css`
|
||||
// background-color: ${theme.colors.warning.main};
|
||||
// border: solid 1px ${theme.colors.warning.main};
|
||||
// color: ${theme.colors.warning.contrastText};
|
||||
// `,
|
||||
// bad: css`
|
||||
// background-color: ${theme.colors.error.main};
|
||||
// border: solid 1px ${theme.colors.error.main};
|
||||
// color: ${theme.colors.error.contrastText};
|
||||
// `,
|
||||
// neutral: css`
|
||||
// background-color: ${theme.colors.secondary.main};
|
||||
// border: solid 1px ${theme.colors.secondary.main};
|
||||
// `,
|
||||
// info: css`
|
||||
// background-color: ${theme.colors.primary.main};
|
||||
// border: solid 1px ${theme.colors.primary.main};
|
||||
// color: ${theme.colors.primary.contrastText};
|
||||
// `,
|
||||
good: css`
|
||||
color: ${theme.colors.success.main};
|
||||
`,
|
||||
bad: css`
|
||||
color: ${theme.colors.error.main};
|
||||
`,
|
||||
warning: css`
|
||||
color: ${theme.colors.warning.main};
|
||||
`,
|
||||
neutral: css`
|
||||
color: ${theme.colors.secondary.main};
|
||||
`,
|
||||
info: css`
|
||||
color: ${theme.colors.primary.main};
|
||||
`,
|
||||
});
|
@ -2,15 +2,18 @@ import React from 'react';
|
||||
import { PanelPlugin } from '@grafana/data';
|
||||
import { TagsInput } from '@grafana/ui';
|
||||
import { AlertList } from './AlertList';
|
||||
import { UnifiedAlertList } from './UnifiedAlertList';
|
||||
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
|
||||
import { AlertListOptions, ShowOption, SortOrder } from './types';
|
||||
import { AlertListOptions, UnifiedAlertListOptions, ShowOption, SortOrder } from './types';
|
||||
import { alertListPanelMigrationHandler } from './AlertListMigrationHandler';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { RuleFolderPicker } from 'app/features/alerting/unified/components/rule-editor/RuleFolderPicker';
|
||||
|
||||
function showIfCurrentState(options: AlertListOptions) {
|
||||
return options.showOptions === ShowOption.Current;
|
||||
}
|
||||
|
||||
export const plugin = new PanelPlugin<AlertListOptions>(AlertList)
|
||||
const alertList = new PanelPlugin<AlertListOptions>(AlertList)
|
||||
.setPanelOptions((builder) => {
|
||||
builder
|
||||
.addSelect({
|
||||
@ -140,3 +143,84 @@ export const plugin = new PanelPlugin<AlertListOptions>(AlertList)
|
||||
});
|
||||
})
|
||||
.setMigrationHandler(alertListPanelMigrationHandler);
|
||||
|
||||
const unifiedAlertList = new PanelPlugin<UnifiedAlertListOptions>(UnifiedAlertList).setPanelOptions((builder) => {
|
||||
builder
|
||||
.addNumberInput({
|
||||
name: 'Max items',
|
||||
path: 'maxItems',
|
||||
defaultValue: 20,
|
||||
category: ['Options'],
|
||||
})
|
||||
.addSelect({
|
||||
name: 'Sort order',
|
||||
path: 'sortOrder',
|
||||
settings: {
|
||||
options: [
|
||||
{ label: 'Alphabetical (asc)', value: SortOrder.AlphaAsc },
|
||||
{ label: 'Alphabetical (desc)', value: SortOrder.AlphaDesc },
|
||||
{ label: 'Importance', value: SortOrder.Importance },
|
||||
{ label: 'Time (asc)', value: SortOrder.TimeAsc },
|
||||
{ label: 'Time (desc)', value: SortOrder.TimeDesc },
|
||||
],
|
||||
},
|
||||
defaultValue: SortOrder.AlphaAsc,
|
||||
category: ['Options'],
|
||||
})
|
||||
.addBooleanSwitch({
|
||||
path: 'dashboardAlerts',
|
||||
name: 'Alerts from this dashboard',
|
||||
defaultValue: false,
|
||||
category: ['Options'],
|
||||
})
|
||||
.addBooleanSwitch({
|
||||
path: 'showInstances',
|
||||
name: 'Show alert instances',
|
||||
defaultValue: false,
|
||||
category: ['Options'],
|
||||
})
|
||||
.addTextInput({
|
||||
path: 'alertName',
|
||||
name: 'Alert name',
|
||||
defaultValue: '',
|
||||
category: ['Filter'],
|
||||
})
|
||||
.addCustomEditor({
|
||||
path: 'folder',
|
||||
name: 'Folder',
|
||||
id: 'folder',
|
||||
defaultValue: null,
|
||||
editor: function RenderFolderPicker(props) {
|
||||
return (
|
||||
<RuleFolderPicker
|
||||
{...props}
|
||||
enableReset={true}
|
||||
onChange={({ title, id }) => {
|
||||
return props.onChange({ title, id });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
category: ['Filter'],
|
||||
})
|
||||
.addBooleanSwitch({
|
||||
path: 'stateFilter.firing',
|
||||
name: 'Alerting',
|
||||
defaultValue: true,
|
||||
category: ['State filter'],
|
||||
})
|
||||
.addBooleanSwitch({
|
||||
path: 'stateFilter.pending',
|
||||
name: 'Pending',
|
||||
defaultValue: true,
|
||||
category: ['State filter'],
|
||||
})
|
||||
.addBooleanSwitch({
|
||||
path: 'stateFilter.inactive',
|
||||
name: 'Inactive',
|
||||
defaultValue: false,
|
||||
category: ['State filter'],
|
||||
});
|
||||
});
|
||||
|
||||
export const plugin = config.featureToggles.ngalert ? unifiedAlertList : alertList;
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||
|
||||
export enum SortOrder {
|
||||
AlphaAsc = 1,
|
||||
AlphaDesc,
|
||||
@ -29,3 +31,15 @@ export interface AlertListOptions {
|
||||
};
|
||||
folderId: number;
|
||||
}
|
||||
|
||||
export interface UnifiedAlertListOptions {
|
||||
maxItems: number;
|
||||
sortOrder: SortOrder;
|
||||
dashboardAlerts: boolean;
|
||||
alertName: string;
|
||||
showInstances: boolean;
|
||||
folder: { id: number; title: string };
|
||||
stateFilter: {
|
||||
[K in PromAlertingRuleState]: boolean;
|
||||
};
|
||||
}
|
||||
|
@ -101,6 +101,13 @@ export interface RuleWithLocation {
|
||||
rule: RulerRuleDTO;
|
||||
}
|
||||
|
||||
export interface PromRuleWithLocation {
|
||||
rule: AlertingRule;
|
||||
dataSourceName: string;
|
||||
namespaceName: string;
|
||||
groupName: string;
|
||||
}
|
||||
|
||||
export interface CloudRuleIdentifier {
|
||||
ruleSourceName: string;
|
||||
namespace: string;
|
||||
|
Loading…
Reference in New Issue
Block a user