MuteTimingsTable.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import { css } from '@emotion/css';
  2. import React, { FC, useMemo, useState } from 'react';
  3. import { useDispatch } from 'react-redux';
  4. import { GrafanaTheme2 } from '@grafana/data';
  5. import { IconButton, LinkButton, Link, useStyles2, ConfirmModal } from '@grafana/ui';
  6. import { contextSrv } from 'app/core/services/context_srv';
  7. import { AlertManagerCortexConfig, MuteTimeInterval, TimeInterval } from 'app/plugins/datasource/alertmanager/types';
  8. import { Authorize } from '../../components/Authorize';
  9. import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
  10. import { deleteMuteTimingAction } from '../../state/actions';
  11. import { getNotificationsPermissions } from '../../utils/access-control';
  12. import {
  13. getTimeString,
  14. getWeekdayString,
  15. getDaysOfMonthString,
  16. getMonthsString,
  17. getYearsString,
  18. } from '../../utils/alertmanager';
  19. import { makeAMLink } from '../../utils/misc';
  20. import { AsyncRequestState, initialAsyncRequestState } from '../../utils/redux';
  21. import { DynamicTable, DynamicTableItemProps, DynamicTableColumnProps } from '../DynamicTable';
  22. import { EmptyAreaWithCTA } from '../EmptyAreaWithCTA';
  23. interface Props {
  24. alertManagerSourceName: string;
  25. muteTimingNames?: string[];
  26. hideActions?: boolean;
  27. }
  28. export const MuteTimingsTable: FC<Props> = ({ alertManagerSourceName, muteTimingNames, hideActions }) => {
  29. const styles = useStyles2(getStyles);
  30. const dispatch = useDispatch();
  31. const permissions = getNotificationsPermissions(alertManagerSourceName);
  32. const amConfigs = useUnifiedAlertingSelector((state) => state.amConfigs);
  33. const [muteTimingName, setMuteTimingName] = useState<string>('');
  34. const { result }: AsyncRequestState<AlertManagerCortexConfig> =
  35. (alertManagerSourceName && amConfigs[alertManagerSourceName]) || initialAsyncRequestState;
  36. const items = useMemo((): Array<DynamicTableItemProps<MuteTimeInterval>> => {
  37. const muteTimings = result?.alertmanager_config?.mute_time_intervals ?? [];
  38. return muteTimings
  39. .filter(({ name }) => (muteTimingNames ? muteTimingNames.includes(name) : true))
  40. .map((mute) => {
  41. return {
  42. id: mute.name,
  43. data: mute,
  44. };
  45. });
  46. }, [result?.alertmanager_config?.mute_time_intervals, muteTimingNames]);
  47. const columns = useColumns(alertManagerSourceName, hideActions, setMuteTimingName);
  48. return (
  49. <div className={styles.container}>
  50. {!hideActions && <h5>Mute timings</h5>}
  51. {!hideActions && (
  52. <p>
  53. Mute timings are a named interval of time that may be referenced in the notification policy tree to mute
  54. particular notification policies for specific times of the day.
  55. </p>
  56. )}
  57. {!hideActions && items.length > 0 && (
  58. <Authorize actions={[permissions.create]}>
  59. <LinkButton
  60. className={styles.addMuteButton}
  61. icon="plus"
  62. variant="primary"
  63. href={makeAMLink('alerting/routes/mute-timing/new', alertManagerSourceName)}
  64. >
  65. New mute timing
  66. </LinkButton>
  67. </Authorize>
  68. )}
  69. {items.length > 0 ? (
  70. <DynamicTable items={items} cols={columns} />
  71. ) : !hideActions ? (
  72. <EmptyAreaWithCTA
  73. text="You haven't created any mute timings yet"
  74. buttonLabel="Add mute timing"
  75. buttonIcon="plus"
  76. buttonSize="lg"
  77. href={makeAMLink('alerting/routes/mute-timing/new', alertManagerSourceName)}
  78. showButton={contextSrv.hasPermission(permissions.create)}
  79. />
  80. ) : (
  81. <p>No mute timings configured</p>
  82. )}
  83. {!hideActions && (
  84. <ConfirmModal
  85. isOpen={!!muteTimingName}
  86. title="Delete mute timing"
  87. body={`Are you sure you would like to delete "${muteTimingName}"`}
  88. confirmText="Delete"
  89. onConfirm={() => dispatch(deleteMuteTimingAction(alertManagerSourceName, muteTimingName))}
  90. onDismiss={() => setMuteTimingName('')}
  91. />
  92. )}
  93. </div>
  94. );
  95. };
  96. function useColumns(alertManagerSourceName: string, hideActions = false, setMuteTimingName: (name: string) => void) {
  97. const permissions = getNotificationsPermissions(alertManagerSourceName);
  98. const userHasEditPermissions = contextSrv.hasPermission(permissions.update);
  99. const userHasDeletePermissions = contextSrv.hasPermission(permissions.delete);
  100. const showActions = !hideActions && (userHasEditPermissions || userHasDeletePermissions);
  101. return useMemo((): Array<DynamicTableColumnProps<MuteTimeInterval>> => {
  102. const columns: Array<DynamicTableColumnProps<MuteTimeInterval>> = [
  103. {
  104. id: 'name',
  105. label: 'Name',
  106. renderCell: function renderName({ data }) {
  107. return data.name;
  108. },
  109. size: '250px',
  110. },
  111. {
  112. id: 'timeRange',
  113. label: 'Time range',
  114. renderCell: ({ data }) => renderTimeIntervals(data.time_intervals),
  115. },
  116. ];
  117. if (showActions) {
  118. columns.push({
  119. id: 'actions',
  120. label: 'Actions',
  121. renderCell: function renderActions({ data }) {
  122. return (
  123. <div>
  124. <Authorize actions={[permissions.update]}>
  125. <Link
  126. href={makeAMLink(`/alerting/routes/mute-timing/edit`, alertManagerSourceName, {
  127. muteName: data.name,
  128. })}
  129. >
  130. <IconButton name="edit" title="Edit mute timing" />
  131. </Link>
  132. </Authorize>
  133. <Authorize actions={[permissions.delete]}>
  134. <IconButton
  135. name={'trash-alt'}
  136. title="Delete mute timing"
  137. onClick={() => setMuteTimingName(data.name)}
  138. />
  139. </Authorize>
  140. </div>
  141. );
  142. },
  143. size: '100px',
  144. });
  145. }
  146. return columns;
  147. }, [alertManagerSourceName, setMuteTimingName, showActions, permissions]);
  148. }
  149. function renderTimeIntervals(timeIntervals: TimeInterval[]) {
  150. return timeIntervals.map((interval, index) => {
  151. const { times, weekdays, days_of_month, months, years } = interval;
  152. const timeString = getTimeString(times);
  153. const weekdayString = getWeekdayString(weekdays);
  154. const daysString = getDaysOfMonthString(days_of_month);
  155. const monthsString = getMonthsString(months);
  156. const yearsString = getYearsString(years);
  157. return (
  158. <React.Fragment key={JSON.stringify(interval) + index}>
  159. {`${timeString} ${weekdayString}`}
  160. <br />
  161. {[daysString, monthsString, yearsString].join(' | ')}
  162. <br />
  163. </React.Fragment>
  164. );
  165. });
  166. }
  167. const getStyles = (theme: GrafanaTheme2) => ({
  168. container: css`
  169. display: flex;
  170. flex-flow: column nowrap;
  171. `,
  172. addMuteButton: css`
  173. margin-bottom: ${theme.spacing(2)};
  174. align-self: flex-end;
  175. `,
  176. });