diff --git a/frontend/src/api/alerts/patch.ts b/frontend/src/api/alerts/patch.ts new file mode 100644 index 0000000000..920b53ae9f --- /dev/null +++ b/frontend/src/api/alerts/patch.ts @@ -0,0 +1,26 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/alerts/patch'; + +const patch = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.patch(`/rules/${props.id}`, { + ...props.data, + }); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default patch; diff --git a/frontend/src/container/ListAlertRules/DeleteAlert.tsx b/frontend/src/container/ListAlertRules/DeleteAlert.tsx index f479de38ab..ac91bfe2f2 100644 --- a/frontend/src/container/ListAlertRules/DeleteAlert.tsx +++ b/frontend/src/container/ListAlertRules/DeleteAlert.tsx @@ -1,10 +1,11 @@ -import { Button } from 'antd'; import { NotificationInstance } from 'antd/lib/notification/index'; import deleteAlerts from 'api/alerts/delete'; import { State } from 'hooks/useFetch'; import React, { useState } from 'react'; import { PayloadProps as DeleteAlertPayloadProps } from 'types/api/alerts/delete'; -import { Alerts } from 'types/api/alerts/getAll'; +import { GettableAlert } from 'types/api/alerts/get'; + +import { ColumnButton } from './styles'; function DeleteAlert({ id, @@ -72,20 +73,20 @@ function DeleteAlert({ }; return ( - + ); } interface DeleteAlertProps { - id: Alerts['id']; - setData: React.Dispatch>; + id: GettableAlert['id']; + setData: React.Dispatch>; notifications: NotificationInstance; } diff --git a/frontend/src/container/ListAlertRules/ListAlert.tsx b/frontend/src/container/ListAlertRules/ListAlert.tsx index 4df6290725..1981e8bfd8 100644 --- a/frontend/src/container/ListAlertRules/ListAlert.tsx +++ b/frontend/src/container/ListAlertRules/ListAlert.tsx @@ -1,6 +1,6 @@ /* eslint-disable react/display-name */ import { PlusOutlined } from '@ant-design/icons'; -import { notification, Tag, Typography } from 'antd'; +import { notification, Typography } from 'antd'; import Table, { ColumnsType } from 'antd/lib/table'; import TextToolTip from 'components/TextToolTip'; import ROUTES from 'constants/routes'; @@ -13,15 +13,16 @@ import { UseQueryResult } from 'react-query'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { ErrorResponse, SuccessResponse } from 'types/api'; -import { Alerts } from 'types/api/alerts/getAll'; +import { GettableAlert } from 'types/api/alerts/get'; import AppReducer from 'types/reducer/app'; import DeleteAlert from './DeleteAlert'; -import { Button, ButtonContainer } from './styles'; +import { Button, ButtonContainer, ColumnButton, StyledTag } from './styles'; import Status from './TableComponents/Status'; +import ToggleAlertState from './ToggleAlertState'; function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { - const [data, setData] = useState(allAlertRules || []); + const [data, setData] = useState(allAlertRules || []); const { t } = useTranslation('common'); const { role } = useSelector((state) => state.app); const [addNewAlert, action] = useComponentPermission( @@ -53,22 +54,27 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { history.push(`${ROUTES.EDIT_ALERTS}?ruleId=${id}`); }; - const columns: ColumnsType = [ + const columns: ColumnsType = [ { title: 'Status', dataIndex: 'state', key: 'state', sorter: (a, b): number => - b.labels.severity.length - a.labels.severity.length, + (b.state ? b.state.charCodeAt(0) : 1000) - + (a.state ? a.state.charCodeAt(0) : 1000), render: (value): JSX.Element => , }, { title: 'Alert Name', dataIndex: 'alert', key: 'name', - sorter: (a, b): number => a.name.charCodeAt(0) - b.name.charCodeAt(0), + sorter: (a, b): number => + (a.alert ? a.alert.charCodeAt(0) : 1000) - + (b.alert ? b.alert.charCodeAt(0) : 1000), render: (value, record): JSX.Element => ( - onEditHandler(record.id.toString())}> + onEditHandler(record.id ? record.id.toString() : '')} + > {value} ), @@ -78,7 +84,8 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { dataIndex: 'labels', key: 'severity', sorter: (a, b): number => - a.labels.severity.length - b.labels.severity.length, + (a.labels ? a.labels.severity.length : 0) - + (b.labels ? b.labels.severity.length : 0), render: (value): JSX.Element => { const objectKeys = Object.keys(value); const withSeverityKey = objectKeys.find((e) => e === 'severity') || ''; @@ -92,6 +99,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { dataIndex: 'labels', key: 'tags', align: 'center', + width: 350, render: (value): JSX.Element => { const objectKeys = Object.keys(value); const withOutSeverityKeys = objectKeys.filter((e) => e !== 'severity'); @@ -104,9 +112,9 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { <> {withOutSeverityKeys.map((e) => { return ( - + {e}: {value[e]} - + ); })} @@ -120,14 +128,19 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { title: 'Action', dataIndex: 'id', key: 'action', - render: (id: Alerts['id']): JSX.Element => { + render: (id: GettableAlert['id'], record): JSX.Element => { return ( <> - + - + + + ); }, @@ -159,8 +172,10 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { } interface ListAlertProps { - allAlertRules: Alerts[]; - refetch: UseQueryResult>['refetch']; + allAlertRules: GettableAlert[]; + refetch: UseQueryResult< + ErrorResponse | SuccessResponse + >['refetch']; } export default ListAlert; diff --git a/frontend/src/container/ListAlertRules/TableComponents/Status.tsx b/frontend/src/container/ListAlertRules/TableComponents/Status.tsx index 33de5fb1db..d935b8d5ba 100644 --- a/frontend/src/container/ListAlertRules/TableComponents/Status.tsx +++ b/frontend/src/container/ListAlertRules/TableComponents/Status.tsx @@ -1,6 +1,6 @@ import { Tag } from 'antd'; import React from 'react'; -import { Alerts } from 'types/api/alerts/getAll'; +import { GettableAlert } from 'types/api/alerts/get'; function Status({ status }: StatusProps): JSX.Element { switch (status) { @@ -16,14 +16,18 @@ function Status({ status }: StatusProps): JSX.Element { return Firing; } + case 'disabled': { + return Disabled; + } + default: { - return Unknown Status; + return Unknown; } } } interface StatusProps { - status: Alerts['state']; + status: GettableAlert['state']; } export default Status; diff --git a/frontend/src/container/ListAlertRules/ToggleAlertState.tsx b/frontend/src/container/ListAlertRules/ToggleAlertState.tsx new file mode 100644 index 0000000000..9b367ea891 --- /dev/null +++ b/frontend/src/container/ListAlertRules/ToggleAlertState.tsx @@ -0,0 +1,108 @@ +import { notification } from 'antd'; +import patchAlert from 'api/alerts/patch'; +import { State } from 'hooks/useFetch'; +import React, { useState } from 'react'; +import { GettableAlert } from 'types/api/alerts/get'; +import { PayloadProps as PatchPayloadProps } from 'types/api/alerts/patch'; + +import { ColumnButton } from './styles'; + +function ToggleAlertState({ + id, + disabled, + setData, +}: ToggleAlertStateProps): JSX.Element { + const [apiStatus, setAPIStatus] = useState>({ + error: false, + errorMessage: '', + loading: false, + success: false, + payload: undefined, + }); + + const defaultErrorMessage = 'Something went wrong'; + + const onToggleHandler = async ( + id: number, + disabled: boolean, + ): Promise => { + try { + setAPIStatus((state) => ({ + ...state, + loading: true, + })); + + const response = await patchAlert({ + id, + data: { + disabled, + }, + }); + + if (response.statusCode === 200) { + setData((state) => { + return state.map((alert) => { + if (alert.id === id) { + return { + ...alert, + disabled: response.payload.disabled, + state: response.payload.state, + }; + } + return alert; + }); + }); + + setAPIStatus((state) => ({ + ...state, + loading: false, + payload: response.payload, + })); + notification.success({ + message: 'Success', + }); + } else { + setAPIStatus((state) => ({ + ...state, + loading: false, + error: true, + errorMessage: response.error || defaultErrorMessage, + })); + + notification.error({ + message: response.error || defaultErrorMessage, + }); + } + } catch (error) { + setAPIStatus((state) => ({ + ...state, + loading: false, + error: true, + errorMessage: defaultErrorMessage, + })); + + notification.error({ + message: defaultErrorMessage, + }); + } + }; + + return ( + => onToggleHandler(id, !disabled)} + type="link" + > + {disabled ? 'Enable' : 'Disable'} + + ); +} + +interface ToggleAlertStateProps { + id: GettableAlert['id']; + disabled: boolean; + setData: React.Dispatch>; +} + +export default ToggleAlertState; diff --git a/frontend/src/container/ListAlertRules/styles.ts b/frontend/src/container/ListAlertRules/styles.ts index fa993568fb..67748b21c0 100644 --- a/frontend/src/container/ListAlertRules/styles.ts +++ b/frontend/src/container/ListAlertRules/styles.ts @@ -1,4 +1,4 @@ -import { Button as ButtonComponent } from 'antd'; +import { Button as ButtonComponent, Tag } from 'antd'; import styled from 'styled-components'; export const ButtonContainer = styled.div` @@ -12,6 +12,20 @@ export const ButtonContainer = styled.div` export const Button = styled(ButtonComponent)` &&& { - margin-left: 1rem; + margin-left: 1em; + } +`; + +export const ColumnButton = styled(ButtonComponent)` + &&& { + padding-left: 0; + padding-right: 0; + margin-right: 1.5em; + } +`; + +export const StyledTag = styled(Tag)` + &&& { + white-space: normal; } `; diff --git a/frontend/src/container/TriggeredAlerts/Filter.tsx b/frontend/src/container/TriggeredAlerts/Filter.tsx index ae61fbc35a..601651cdff 100644 --- a/frontend/src/container/TriggeredAlerts/Filter.tsx +++ b/frontend/src/container/TriggeredAlerts/Filter.tsx @@ -2,7 +2,7 @@ import type { SelectProps } from 'antd'; import { Tag } from 'antd'; import React, { useCallback, useMemo } from 'react'; -import { Alerts } from 'types/api/alerts/getAll'; +import { Alerts } from 'types/api/alerts/getTriggered'; import { Container, Select } from './styles'; diff --git a/frontend/src/container/TriggeredAlerts/FilteredTable/ExapandableRow.tsx b/frontend/src/container/TriggeredAlerts/FilteredTable/ExapandableRow.tsx index fab66e242d..388e2d7499 100644 --- a/frontend/src/container/TriggeredAlerts/FilteredTable/ExapandableRow.tsx +++ b/frontend/src/container/TriggeredAlerts/FilteredTable/ExapandableRow.tsx @@ -2,7 +2,7 @@ import { Tag, Typography } from 'antd'; import convertDateToAmAndPm from 'lib/convertDateToAmAndPm'; import getFormattedDate from 'lib/getFormatedDate'; import React from 'react'; -import { Alerts } from 'types/api/alerts/getAll'; +import { Alerts } from 'types/api/alerts/getTriggered'; import Status from '../TableComponents/AlertStatus'; import { TableCell, TableRow } from './styles'; diff --git a/frontend/src/container/TriggeredAlerts/FilteredTable/TableRow.tsx b/frontend/src/container/TriggeredAlerts/FilteredTable/TableRow.tsx index 2f446adcf5..97619b5f12 100644 --- a/frontend/src/container/TriggeredAlerts/FilteredTable/TableRow.tsx +++ b/frontend/src/container/TriggeredAlerts/FilteredTable/TableRow.tsx @@ -1,7 +1,7 @@ import { MinusSquareOutlined, PlusSquareOutlined } from '@ant-design/icons'; import { Tag } from 'antd'; import React, { useState } from 'react'; -import { Alerts } from 'types/api/alerts/getAll'; +import { Alerts } from 'types/api/alerts/getTriggered'; import ExapandableRow from './ExapandableRow'; import { IconContainer, StatusContainer, TableCell, TableRow } from './styles'; diff --git a/frontend/src/container/TriggeredAlerts/FilteredTable/index.tsx b/frontend/src/container/TriggeredAlerts/FilteredTable/index.tsx index 8c8f47fdfd..a9e56d903d 100644 --- a/frontend/src/container/TriggeredAlerts/FilteredTable/index.tsx +++ b/frontend/src/container/TriggeredAlerts/FilteredTable/index.tsx @@ -1,6 +1,6 @@ import groupBy from 'lodash-es/groupBy'; import React, { useMemo } from 'react'; -import { Alerts } from 'types/api/alerts/getAll'; +import { Alerts } from 'types/api/alerts/getTriggered'; import { Value } from '../Filter'; import { FilterAlerts } from '../utils'; diff --git a/frontend/src/container/TriggeredAlerts/NoFilterTable.tsx b/frontend/src/container/TriggeredAlerts/NoFilterTable.tsx index a9c8064616..ac4e45131a 100644 --- a/frontend/src/container/TriggeredAlerts/NoFilterTable.tsx +++ b/frontend/src/container/TriggeredAlerts/NoFilterTable.tsx @@ -5,7 +5,7 @@ import AlertStatus from 'container/TriggeredAlerts/TableComponents/AlertStatus'; import convertDateToAmAndPm from 'lib/convertDateToAmAndPm'; import getFormattedDate from 'lib/getFormatedDate'; import React from 'react'; -import { Alerts } from 'types/api/alerts/getAll'; +import { Alerts } from 'types/api/alerts/getTriggered'; import { Value } from './Filter'; import { FilterAlerts } from './utils'; diff --git a/frontend/src/container/TriggeredAlerts/TriggeredAlert.tsx b/frontend/src/container/TriggeredAlerts/TriggeredAlert.tsx index 425334e7ba..b12a09d5e4 100644 --- a/frontend/src/container/TriggeredAlerts/TriggeredAlert.tsx +++ b/frontend/src/container/TriggeredAlerts/TriggeredAlert.tsx @@ -1,7 +1,7 @@ import getTriggeredApi from 'api/alerts/getTriggered'; import useInterval from 'hooks/useInterval'; import React, { useState } from 'react'; -import { Alerts } from 'types/api/alerts/getAll'; +import { Alerts } from 'types/api/alerts/getTriggered'; import Filter, { Value } from './Filter'; import FilteredTable from './FilteredTable'; diff --git a/frontend/src/container/TriggeredAlerts/utils.ts b/frontend/src/container/TriggeredAlerts/utils.ts index aab179e1cf..67d3024d6a 100644 --- a/frontend/src/container/TriggeredAlerts/utils.ts +++ b/frontend/src/container/TriggeredAlerts/utils.ts @@ -1,4 +1,4 @@ -import { Alerts } from 'types/api/alerts/getAll'; +import { Alerts } from 'types/api/alerts/getTriggered'; import { Value } from './Filter'; diff --git a/frontend/src/types/api/alerts/def.ts b/frontend/src/types/api/alerts/def.ts index 4154e9c7e3..f417678ee1 100644 --- a/frontend/src/types/api/alerts/def.ts +++ b/frontend/src/types/api/alerts/def.ts @@ -18,6 +18,7 @@ export interface AlertDef { annotations?: Labels; evalWindow?: string; source?: string; + disabled?: boolean; preferredChannels?: string[]; } diff --git a/frontend/src/types/api/alerts/delete.ts b/frontend/src/types/api/alerts/delete.ts index 24dbdc1d8a..5c842ea34c 100644 --- a/frontend/src/types/api/alerts/delete.ts +++ b/frontend/src/types/api/alerts/delete.ts @@ -1,7 +1,7 @@ -import { Alerts } from './getAll'; +import { AlertDef } from './def'; export interface Props { - id: Alerts['id']; + id: AlertDef['id']; } export interface PayloadProps { diff --git a/frontend/src/types/api/alerts/get.ts b/frontend/src/types/api/alerts/get.ts index 69eef474e1..78b637c140 100644 --- a/frontend/src/types/api/alerts/get.ts +++ b/frontend/src/types/api/alerts/get.ts @@ -4,6 +4,13 @@ export interface Props { id: AlertDef['id']; } +export interface GettableAlert extends AlertDef { + id: number; + alert: string; + state: string; + disabled: boolean; +} + export type PayloadProps = { - data: AlertDef; + data: GettableAlert; }; diff --git a/frontend/src/types/api/alerts/getAll.ts b/frontend/src/types/api/alerts/getAll.ts index 501c34a4cb..58351ed703 100644 --- a/frontend/src/types/api/alerts/getAll.ts +++ b/frontend/src/types/api/alerts/getAll.ts @@ -1,32 +1,3 @@ -export interface Alerts { - labels: AlertsLabel; - annotations: { - description: string; - summary: string; - [key: string]: string; - }; - state: string; - name: string; - id: number; - endsAt: string; - fingerprint: string; - generatorURL: string; - receivers: Receivers[]; - startsAt: string; - status: { - inhibitedBy: []; - silencedBy: []; - state: string; - }; - updatedAt: string; -} +import { GettableAlert } from './get'; -interface Receivers { - name: string; -} - -interface AlertsLabel { - [key: string]: string; -} - -export type PayloadProps = Alerts[]; +export type PayloadProps = GettableAlert[]; diff --git a/frontend/src/types/api/alerts/getGroups.ts b/frontend/src/types/api/alerts/getGroups.ts index f7dac48a14..71979d116d 100644 --- a/frontend/src/types/api/alerts/getGroups.ts +++ b/frontend/src/types/api/alerts/getGroups.ts @@ -1,4 +1,4 @@ -import { Alerts } from './getAll'; +import { AlertDef } from './def'; export interface Props { silenced: boolean; @@ -7,8 +7,8 @@ export interface Props { [key: string]: string | boolean; } export interface Group { - alerts: Alerts[]; - label: Alerts['labels']; + alerts: AlertDef[]; + label: AlertDef['labels']; receiver: { [key: string]: string; }; diff --git a/frontend/src/types/api/alerts/getTriggered.ts b/frontend/src/types/api/alerts/getTriggered.ts index 8b0e50a279..97d116b431 100644 --- a/frontend/src/types/api/alerts/getTriggered.ts +++ b/frontend/src/types/api/alerts/getTriggered.ts @@ -1,4 +1,33 @@ -import { Alerts } from './getAll'; +export interface Alerts { + labels: AlertsLabel; + annotations: { + description: string; + summary: string; + [key: string]: string; + }; + state: string; + name: string; + id: number; + endsAt: string; + fingerprint: string; + generatorURL: string; + receivers: Receivers[]; + startsAt: string; + status: { + inhibitedBy: []; + silencedBy: []; + state: string; + }; + updatedAt: string; +} + +interface Receivers { + name: string; +} + +interface AlertsLabel { + [key: string]: string; +} export interface Props { silenced: boolean; diff --git a/frontend/src/types/api/alerts/patch.ts b/frontend/src/types/api/alerts/patch.ts new file mode 100644 index 0000000000..fab1e67cfe --- /dev/null +++ b/frontend/src/types/api/alerts/patch.ts @@ -0,0 +1,12 @@ +import { GettableAlert } from './get'; + +export type PayloadProps = GettableAlert; + +export interface PatchProps { + disabled?: boolean; +} + +export interface Props { + id?: number; + data: PatchProps; +}