mirror of
https://github.com/grafana/grafana.git
synced 2025-02-11 16:15:42 -06:00
274 lines
9.0 KiB
TypeScript
274 lines
9.0 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { sortBy } from 'lodash';
|
|
import { dateMath, dateTime, GrafanaTheme, PanelProps } from '@grafana/data';
|
|
import { Card, CustomScrollbar, Icon, stylesFactory, useStyles } from '@grafana/ui';
|
|
import { css, cx } from '@emotion/css';
|
|
import { getBackendSrv, getTemplateSrv } from '@grafana/runtime';
|
|
import { useAsync } from 'react-use';
|
|
import alertDef from 'app/features/alerting/state/alertDef';
|
|
import { AlertRuleDTO, AnnotationItemDTO } from 'app/types';
|
|
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
|
import { AlertListOptions, ShowOption, SortOrder } from './types';
|
|
|
|
export function AlertList(props: PanelProps<AlertListOptions>) {
|
|
const [noAlertsMessage, setNoAlertsMessage] = useState('');
|
|
|
|
const currentAlertState = useAsync(async () => {
|
|
if (props.options.showOptions !== ShowOption.Current) {
|
|
return;
|
|
}
|
|
|
|
const params: any = {
|
|
state: getStateFilter(props.options.stateFilter),
|
|
};
|
|
const panel = getDashboardSrv().getCurrent()?.getPanelById(props.id)!;
|
|
|
|
if (props.options.alertName) {
|
|
params.query = getTemplateSrv().replace(props.options.alertName, panel.scopedVars);
|
|
}
|
|
|
|
if (props.options.folderId >= 0) {
|
|
params.folderId = props.options.folderId;
|
|
}
|
|
|
|
if (props.options.dashboardTitle) {
|
|
params.dashboardQuery = props.options.dashboardTitle;
|
|
}
|
|
|
|
if (props.options.dashboardAlerts) {
|
|
params.dashboardId = getDashboardSrv().getCurrent()?.id;
|
|
}
|
|
|
|
if (props.options.tags) {
|
|
params.dashboardTag = props.options.tags;
|
|
}
|
|
|
|
const alerts: AlertRuleDTO[] = await getBackendSrv().get(
|
|
'/api/alerts',
|
|
params,
|
|
`alert-list-get-current-alert-state-${props.id}`
|
|
);
|
|
let currentAlerts = sortAlerts(
|
|
props.options.sortOrder,
|
|
alerts.map((al) => ({
|
|
...al,
|
|
stateModel: alertDef.getStateDisplayModel(al.state),
|
|
newStateDateAgo: dateTime(al.newStateDate).locale('en').fromNow(true),
|
|
}))
|
|
);
|
|
|
|
if (currentAlerts.length > props.options.maxItems) {
|
|
currentAlerts = currentAlerts.slice(0, props.options.maxItems);
|
|
}
|
|
setNoAlertsMessage(currentAlerts.length === 0 ? 'No alerts' : '');
|
|
|
|
return currentAlerts;
|
|
}, [
|
|
props.options.showOptions,
|
|
props.options.stateFilter.alerting,
|
|
props.options.stateFilter.execution_error,
|
|
props.options.stateFilter.no_data,
|
|
props.options.stateFilter.ok,
|
|
props.options.stateFilter.paused,
|
|
props.options.stateFilter.pending,
|
|
props.options.maxItems,
|
|
props.options.tags,
|
|
props.options.dashboardAlerts,
|
|
props.options.dashboardTitle,
|
|
props.options.folderId,
|
|
props.options.alertName,
|
|
props.options.sortOrder,
|
|
]);
|
|
|
|
const recentStateChanges = useAsync(async () => {
|
|
if (props.options.showOptions !== ShowOption.RecentChanges) {
|
|
return;
|
|
}
|
|
|
|
const params: any = {
|
|
limit: props.options.maxItems,
|
|
type: 'alert',
|
|
newState: getStateFilter(props.options.stateFilter),
|
|
};
|
|
const currentDashboard = getDashboardSrv().getCurrent();
|
|
|
|
if (props.options.dashboardAlerts) {
|
|
params.dashboardId = currentDashboard?.id;
|
|
}
|
|
|
|
params.from = dateMath.parse(currentDashboard?.time.from)!.unix() * 1000;
|
|
params.to = dateMath.parse(currentDashboard?.time.to)!.unix() * 1000;
|
|
|
|
const data: AnnotationItemDTO[] = await getBackendSrv().get(
|
|
'/api/annotations',
|
|
params,
|
|
`alert-list-get-state-changes-${props.id}`
|
|
);
|
|
const alertHistory = sortAlerts(
|
|
props.options.sortOrder,
|
|
data.map((al) => {
|
|
return {
|
|
...al,
|
|
time: currentDashboard?.formatDate(al.time, 'MMM D, YYYY HH:mm:ss'),
|
|
stateModel: alertDef.getStateDisplayModel(al.newState),
|
|
info: alertDef.getAlertAnnotationInfo(al),
|
|
};
|
|
})
|
|
);
|
|
|
|
setNoAlertsMessage(alertHistory.length === 0 ? 'No alerts in current time range' : '');
|
|
return alertHistory;
|
|
}, [
|
|
props.options.showOptions,
|
|
props.options.maxItems,
|
|
props.options.stateFilter.alerting,
|
|
props.options.stateFilter.execution_error,
|
|
props.options.stateFilter.no_data,
|
|
props.options.stateFilter.ok,
|
|
props.options.stateFilter.paused,
|
|
props.options.stateFilter.pending,
|
|
props.options.dashboardAlerts,
|
|
props.options.sortOrder,
|
|
]);
|
|
|
|
const styles = useStyles(getStyles);
|
|
|
|
return (
|
|
<CustomScrollbar autoHeightMin="100%" autoHeightMax="100%">
|
|
<div className={styles.container}>
|
|
{noAlertsMessage && <div className={styles.noAlertsMessage}>{noAlertsMessage}</div>}
|
|
<section>
|
|
<ol className={styles.alertRuleList}>
|
|
{props.options.showOptions === ShowOption.Current
|
|
? !currentAlertState.loading &&
|
|
currentAlertState.value &&
|
|
currentAlertState.value!.map((alert) => (
|
|
<li className={styles.alertRuleItem} key={`alert-${alert.id}`}>
|
|
<Card
|
|
heading={alert.name}
|
|
href={`${alert.url}?viewPanel=${alert.panelId}`}
|
|
className={styles.cardContainer}
|
|
>
|
|
<Card.Figure className={cx(styles.alertRuleItemIcon, alert.stateModel.stateClass)}>
|
|
<Icon name={alert.stateModel.iconClass} size="xl" className={styles.alertIcon} />
|
|
</Card.Figure>
|
|
<Card.Meta>
|
|
<div className={styles.alertRuleItemText}>
|
|
<span className={alert.stateModel.stateClass}>{alert.stateModel.text}</span>
|
|
<span className={styles.alertRuleItemTime}> for {alert.newStateDateAgo}</span>
|
|
</div>
|
|
</Card.Meta>
|
|
</Card>
|
|
</li>
|
|
))
|
|
: !recentStateChanges.loading &&
|
|
recentStateChanges.value &&
|
|
recentStateChanges.value.map((alert) => (
|
|
<li className={styles.alertRuleItem} key={`alert-${alert.id}`}>
|
|
<Card heading={alert.alertName} className={styles.cardContainer}>
|
|
<Card.Figure className={cx(styles.alertRuleItemIcon, alert.stateModel.stateClass)}>
|
|
<Icon name={alert.stateModel.iconClass} size="xl" />
|
|
</Card.Figure>
|
|
<Card.Meta>
|
|
<span className={cx(styles.alertRuleItemText, alert.stateModel.stateClass)}>
|
|
{alert.stateModel.text}
|
|
</span>
|
|
<span>{alert.time}</span>
|
|
{alert.info && <span className={styles.alertRuleItemInfo}>{alert.info}</span>}
|
|
</Card.Meta>
|
|
</Card>
|
|
</li>
|
|
))}
|
|
</ol>
|
|
</section>
|
|
</div>
|
|
</CustomScrollbar>
|
|
);
|
|
}
|
|
|
|
function sortAlerts(sortOrder: SortOrder, alerts: any[]) {
|
|
if (sortOrder === SortOrder.Importance) {
|
|
// @ts-ignore
|
|
return sortBy(alerts, (a) => alertDef.alertStateSortScore[a.state || a.newState]);
|
|
} else if (sortOrder === SortOrder.TimeAsc) {
|
|
return sortBy(alerts, (a) => new Date(a.newStateDate || a.time));
|
|
} else if (sortOrder === SortOrder.TimeDesc) {
|
|
return sortBy(alerts, (a) => new Date(a.newStateDate || a.time)).reverse();
|
|
}
|
|
|
|
const result = sortBy(alerts, (a) => (a.name || a.alertName).toLowerCase());
|
|
if (sortOrder === SortOrder.AlphaDesc) {
|
|
result.reverse();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function getStateFilter(stateFilter: Record<string, boolean>) {
|
|
return Object.entries(stateFilter)
|
|
.filter(([_, val]) => val)
|
|
.map(([key, _]) => key);
|
|
}
|
|
|
|
const getStyles = stylesFactory((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};
|
|
`,
|
|
alertRuleItemIcon: css`
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
width: ${theme.spacing.xl};
|
|
padding: 0 ${theme.spacing.xs} 0 ${theme.spacing.xxs};
|
|
margin-right: 0px;
|
|
`,
|
|
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};
|
|
`,
|
|
}));
|