mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Improved RBAC for Alert managers (#48344)
* Initial support for grafana or cloud only alert managers * Handle missing alert manager * Refactor code, fix tests * Fix redirect url * Bring back the test * Improve missing alert manager warning, add useAlertManagerSourceName tests * Fix lint errors * Rename alert manager hook * Refactor alert manager label creation * Improve warnings' messages * Fix linter * Fix warning condition in RuleEditor
This commit is contained in:
parent
696405ba7b
commit
65d7d466d7
@ -132,7 +132,10 @@ const unifiedRoutes: RouteDescriptor[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/alerting/silences',
|
path: '/alerting/silences',
|
||||||
roles: evaluateAccess([AccessControlAction.AlertingInstanceRead], ['Editor', 'Admin']),
|
roles: evaluateAccess(
|
||||||
|
[AccessControlAction.AlertingInstanceRead, AccessControlAction.AlertingInstancesExternalRead],
|
||||||
|
['Editor', 'Admin']
|
||||||
|
),
|
||||||
component: SafeDynamicImport(
|
component: SafeDynamicImport(
|
||||||
() => import(/* webpackChunkName: "AlertSilences" */ 'app/features/alerting/unified/Silences')
|
() => import(/* webpackChunkName: "AlertSilences" */ 'app/features/alerting/unified/Silences')
|
||||||
),
|
),
|
||||||
|
@ -7,9 +7,11 @@ import { Alert, LoadingPlaceholder, useStyles2 } from '@grafana/ui';
|
|||||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||||
|
|
||||||
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||||
|
import { NoAlertManagerWarning } from './components/NoAlertManagerWarning';
|
||||||
import { AlertGroup } from './components/alert-groups/AlertGroup';
|
import { AlertGroup } from './components/alert-groups/AlertGroup';
|
||||||
import { AlertGroupFilter } from './components/alert-groups/AlertGroupFilter';
|
import { AlertGroupFilter } from './components/alert-groups/AlertGroupFilter';
|
||||||
import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName';
|
import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName';
|
||||||
|
import { useAlertManagersByPermission } from './hooks/useAlertManagerSources';
|
||||||
import { useFilteredAmGroups } from './hooks/useFilteredAmGroups';
|
import { useFilteredAmGroups } from './hooks/useFilteredAmGroups';
|
||||||
import { useGroupedAlerts } from './hooks/useGroupedAlerts';
|
import { useGroupedAlerts } from './hooks/useGroupedAlerts';
|
||||||
import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector';
|
import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector';
|
||||||
@ -19,7 +21,8 @@ import { getFiltersFromUrlParams } from './utils/misc';
|
|||||||
import { initialAsyncRequestState } from './utils/redux';
|
import { initialAsyncRequestState } from './utils/redux';
|
||||||
|
|
||||||
const AlertGroups = () => {
|
const AlertGroups = () => {
|
||||||
const [alertManagerSourceName] = useAlertManagerSourceName();
|
const alertManagers = useAlertManagersByPermission('instance');
|
||||||
|
const [alertManagerSourceName] = useAlertManagerSourceName(alertManagers);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [queryParams] = useQueryParams();
|
const [queryParams] = useQueryParams();
|
||||||
const { groupBy = [] } = getFiltersFromUrlParams(queryParams);
|
const { groupBy = [] } = getFiltersFromUrlParams(queryParams);
|
||||||
@ -48,6 +51,14 @@ const AlertGroups = () => {
|
|||||||
};
|
};
|
||||||
}, [dispatch, alertManagerSourceName]);
|
}, [dispatch, alertManagerSourceName]);
|
||||||
|
|
||||||
|
if (!alertManagerSourceName) {
|
||||||
|
return (
|
||||||
|
<AlertingPageWrapper pageId="groups">
|
||||||
|
<NoAlertManagerWarning availableAlertManagers={alertManagers} />
|
||||||
|
</AlertingPageWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertingPageWrapper pageId="groups">
|
<AlertingPageWrapper pageId="groups">
|
||||||
<AlertGroupFilter groups={results} />
|
<AlertGroupFilter groups={results} />
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { Redirect } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Alert, LoadingPlaceholder, useStyles2, withErrorBoundary } from '@grafana/ui';
|
import { Alert, LoadingPlaceholder, useStyles2, withErrorBoundary } from '@grafana/ui';
|
||||||
@ -11,10 +10,12 @@ import { useCleanup } from '../../../core/hooks/useCleanup';
|
|||||||
|
|
||||||
import { AlertManagerPicker } from './components/AlertManagerPicker';
|
import { AlertManagerPicker } from './components/AlertManagerPicker';
|
||||||
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||||
|
import { NoAlertManagerWarning } from './components/NoAlertManagerWarning';
|
||||||
import { AmRootRoute } from './components/amroutes/AmRootRoute';
|
import { AmRootRoute } from './components/amroutes/AmRootRoute';
|
||||||
import { AmSpecificRouting } from './components/amroutes/AmSpecificRouting';
|
import { AmSpecificRouting } from './components/amroutes/AmSpecificRouting';
|
||||||
import { MuteTimingsTable } from './components/amroutes/MuteTimingsTable';
|
import { MuteTimingsTable } from './components/amroutes/MuteTimingsTable';
|
||||||
import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName';
|
import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName';
|
||||||
|
import { useAlertManagersByPermission } from './hooks/useAlertManagerSources';
|
||||||
import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector';
|
import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector';
|
||||||
import { fetchAlertManagerConfigAction, updateAlertManagerConfigAction } from './state/actions';
|
import { fetchAlertManagerConfigAction, updateAlertManagerConfigAction } from './state/actions';
|
||||||
import { AmRouteReceiver, FormAmRoute } from './types/amroutes';
|
import { AmRouteReceiver, FormAmRoute } from './types/amroutes';
|
||||||
@ -26,7 +27,8 @@ const AmRoutes: FC = () => {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const [isRootRouteEditMode, setIsRootRouteEditMode] = useState(false);
|
const [isRootRouteEditMode, setIsRootRouteEditMode] = useState(false);
|
||||||
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName();
|
const alertManagers = useAlertManagersByPermission('notification');
|
||||||
|
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName(alertManagers);
|
||||||
|
|
||||||
const readOnly = alertManagerSourceName ? isVanillaPrometheusAlertManagerDataSource(alertManagerSourceName) : true;
|
const readOnly = alertManagerSourceName ? isVanillaPrometheusAlertManagerDataSource(alertManagerSourceName) : true;
|
||||||
|
|
||||||
@ -100,12 +102,20 @@ const AmRoutes: FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!alertManagerSourceName) {
|
if (!alertManagerSourceName) {
|
||||||
return <Redirect to="/alerting/routes" />;
|
return (
|
||||||
|
<AlertingPageWrapper pageId="am-routes">
|
||||||
|
<NoAlertManagerWarning availableAlertManagers={alertManagers} />
|
||||||
|
</AlertingPageWrapper>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertingPageWrapper pageId="am-routes">
|
<AlertingPageWrapper pageId="am-routes">
|
||||||
<AlertManagerPicker current={alertManagerSourceName} onChange={setAlertManagerSourceName} />
|
<AlertManagerPicker
|
||||||
|
current={alertManagerSourceName}
|
||||||
|
onChange={setAlertManagerSourceName}
|
||||||
|
dataSources={alertManagers}
|
||||||
|
/>
|
||||||
{resultError && !resultLoading && (
|
{resultError && !resultLoading && (
|
||||||
<Alert severity="error" title="Error loading Alertmanager config">
|
<Alert severity="error" title="Error loading Alertmanager config">
|
||||||
{resultError.message || 'Unknown error.'}
|
{resultError.message || 'Unknown error.'}
|
||||||
|
@ -8,6 +8,7 @@ import { MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types';
|
|||||||
|
|
||||||
import MuteTimingForm from './components/amroutes/MuteTimingForm';
|
import MuteTimingForm from './components/amroutes/MuteTimingForm';
|
||||||
import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName';
|
import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName';
|
||||||
|
import { useAlertManagersByPermission } from './hooks/useAlertManagerSources';
|
||||||
import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector';
|
import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector';
|
||||||
import { fetchAlertManagerConfigAction } from './state/actions';
|
import { fetchAlertManagerConfigAction } from './state/actions';
|
||||||
import { initialAsyncRequestState } from './utils/redux';
|
import { initialAsyncRequestState } from './utils/redux';
|
||||||
@ -15,7 +16,8 @@ import { initialAsyncRequestState } from './utils/redux';
|
|||||||
const MuteTimings = () => {
|
const MuteTimings = () => {
|
||||||
const [queryParams] = useQueryParams();
|
const [queryParams] = useQueryParams();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [alertManagerSourceName] = useAlertManagerSourceName();
|
const alertManagers = useAlertManagersByPermission('notification');
|
||||||
|
const [alertManagerSourceName] = useAlertManagerSourceName(alertManagers);
|
||||||
|
|
||||||
const amConfigs = useUnifiedAlertingSelector((state) => state.amConfigs);
|
const amConfigs = useUnifiedAlertingSelector((state) => state.amConfigs);
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import { Alert, LoadingPlaceholder, withErrorBoundary } from '@grafana/ui';
|
|||||||
|
|
||||||
import { AlertManagerPicker } from './components/AlertManagerPicker';
|
import { AlertManagerPicker } from './components/AlertManagerPicker';
|
||||||
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||||
|
import { NoAlertManagerWarning } from './components/NoAlertManagerWarning';
|
||||||
import { EditReceiverView } from './components/receivers/EditReceiverView';
|
import { EditReceiverView } from './components/receivers/EditReceiverView';
|
||||||
import { EditTemplateView } from './components/receivers/EditTemplateView';
|
import { EditTemplateView } from './components/receivers/EditTemplateView';
|
||||||
import { GlobalConfigForm } from './components/receivers/GlobalConfigForm';
|
import { GlobalConfigForm } from './components/receivers/GlobalConfigForm';
|
||||||
@ -13,13 +14,15 @@ import { NewReceiverView } from './components/receivers/NewReceiverView';
|
|||||||
import { NewTemplateView } from './components/receivers/NewTemplateView';
|
import { NewTemplateView } from './components/receivers/NewTemplateView';
|
||||||
import { ReceiversAndTemplatesView } from './components/receivers/ReceiversAndTemplatesView';
|
import { ReceiversAndTemplatesView } from './components/receivers/ReceiversAndTemplatesView';
|
||||||
import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName';
|
import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName';
|
||||||
|
import { useAlertManagersByPermission } from './hooks/useAlertManagerSources';
|
||||||
import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector';
|
import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector';
|
||||||
import { fetchAlertManagerConfigAction, fetchGrafanaNotifiersAction } from './state/actions';
|
import { fetchAlertManagerConfigAction, fetchGrafanaNotifiersAction } from './state/actions';
|
||||||
import { GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
|
import { GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
|
||||||
import { initialAsyncRequestState } from './utils/redux';
|
import { initialAsyncRequestState } from './utils/redux';
|
||||||
|
|
||||||
const Receivers: FC = () => {
|
const Receivers: FC = () => {
|
||||||
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName();
|
const alertManagers = useAlertManagersByPermission('notification');
|
||||||
|
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName(alertManagers);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -54,7 +57,13 @@ const Receivers: FC = () => {
|
|||||||
const disableAmSelect = !isRoot;
|
const disableAmSelect = !isRoot;
|
||||||
|
|
||||||
if (!alertManagerSourceName) {
|
if (!alertManagerSourceName) {
|
||||||
return <Redirect to="/alerting/notifications" />;
|
return isRoot ? (
|
||||||
|
<AlertingPageWrapper pageId="receivers">
|
||||||
|
<NoAlertManagerWarning availableAlertManagers={alertManagers} />
|
||||||
|
</AlertingPageWrapper>
|
||||||
|
) : (
|
||||||
|
<Redirect to="/alerting/notifications" />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -63,6 +72,7 @@ const Receivers: FC = () => {
|
|||||||
current={alertManagerSourceName}
|
current={alertManagerSourceName}
|
||||||
disabled={disableAmSelect}
|
disabled={disableAmSelect}
|
||||||
onChange={setAlertManagerSourceName}
|
onChange={setAlertManagerSourceName}
|
||||||
|
dataSources={alertManagers}
|
||||||
/>
|
/>
|
||||||
{error && !loading && (
|
{error && !loading && (
|
||||||
<Alert severity="error" title="Error loading Alertmanager config">
|
<Alert severity="error" title="Error loading Alertmanager config">
|
||||||
|
@ -75,7 +75,7 @@ const RuleEditor: FC<RuleEditorProps> = ({ match }) => {
|
|||||||
|
|
||||||
const { canCreateGrafanaRules, canCreateCloudRules, canEditRules } = useRulesAccess();
|
const { canCreateGrafanaRules, canCreateCloudRules, canEditRules } = useRulesAccess();
|
||||||
|
|
||||||
if (!canCreateGrafanaRules && !canCreateCloudRules) {
|
if (!identifier && !canCreateGrafanaRules && !canCreateCloudRules) {
|
||||||
return <AlertWarning title="Cannot create rules">Sorry! You are not allowed to create rules.</AlertWarning>;
|
return <AlertWarning title="Cannot create rules">Sorry! You are not allowed to create rules.</AlertWarning>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
import React, { FC, useEffect, useCallback } from 'react';
|
import React, { FC, useCallback, 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';
|
||||||
|
|
||||||
import { Alert, LoadingPlaceholder, withErrorBoundary } from '@grafana/ui';
|
import { Alert, LoadingPlaceholder, withErrorBoundary } from '@grafana/ui';
|
||||||
import { Silence } from 'app/plugins/datasource/alertmanager/types';
|
import { Silence } from 'app/plugins/datasource/alertmanager/types';
|
||||||
import { AccessControlAction } from 'app/types';
|
|
||||||
|
|
||||||
import { AlertManagerPicker } from './components/AlertManagerPicker';
|
import { AlertManagerPicker } from './components/AlertManagerPicker';
|
||||||
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
|
||||||
import { Authorize } from './components/Authorize';
|
import { NoAlertManagerWarning } from './components/NoAlertManagerWarning';
|
||||||
import SilencesEditor from './components/silences/SilencesEditor';
|
import SilencesEditor from './components/silences/SilencesEditor';
|
||||||
import SilencesTable from './components/silences/SilencesTable';
|
import SilencesTable from './components/silences/SilencesTable';
|
||||||
import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName';
|
import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName';
|
||||||
|
import { useAlertManagersByPermission } from './hooks/useAlertManagerSources';
|
||||||
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 { AsyncRequestState, initialAsyncRequestState } from './utils/redux';
|
import { AsyncRequestState, initialAsyncRequestState } from './utils/redux';
|
||||||
|
|
||||||
const Silences: FC = () => {
|
const Silences: FC = () => {
|
||||||
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName();
|
const alertManagers = useAlertManagersByPermission('instance');
|
||||||
|
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName(alertManagers);
|
||||||
|
|
||||||
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 alertsRequests = useUnifiedAlertingSelector((state) => state.amAlerts);
|
||||||
@ -49,14 +51,23 @@ const Silences: FC = () => {
|
|||||||
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) {
|
||||||
return <Redirect to="/alerting/silences" />;
|
return isRoot ? (
|
||||||
|
<AlertingPageWrapper pageId="silences">
|
||||||
|
<NoAlertManagerWarning availableAlertManagers={alertManagers} />
|
||||||
|
</AlertingPageWrapper>
|
||||||
|
) : (
|
||||||
|
<Redirect to="/alerting/silences" />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertingPageWrapper pageId="silences">
|
<AlertingPageWrapper pageId="silences">
|
||||||
<Authorize actions={[AccessControlAction.AlertingInstancesExternalRead]}>
|
<AlertManagerPicker
|
||||||
<AlertManagerPicker disabled={!isRoot} current={alertManagerSourceName} onChange={setAlertManagerSourceName} />
|
disabled={!isRoot}
|
||||||
</Authorize>
|
current={alertManagerSourceName}
|
||||||
|
onChange={setAlertManagerSourceName}
|
||||||
|
dataSources={alertManagers}
|
||||||
|
/>
|
||||||
{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.'}
|
||||||
|
@ -1,39 +1,33 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React, { FC, useMemo } from 'react';
|
import React, { FC, useMemo } from 'react';
|
||||||
|
|
||||||
import { SelectableValue, GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||||
import { Field, Select, useStyles2 } from '@grafana/ui';
|
import { Field, Select, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
import { getAllDataSources } from '../utils/config';
|
import { AlertManagerDataSource, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
|
||||||
import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onChange: (alertManagerSourceName: string) => void;
|
onChange: (alertManagerSourceName: string) => void;
|
||||||
current?: string;
|
current?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
dataSources: AlertManagerDataSource[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AlertManagerPicker: FC<Props> = ({ onChange, current, disabled = false }) => {
|
function getAlertManagerLabel(alertManager: AlertManagerDataSource) {
|
||||||
|
return alertManager.name === GRAFANA_RULES_SOURCE_NAME ? 'Grafana' : alertManager.name.slice(0, 37);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AlertManagerPicker: FC<Props> = ({ onChange, current, dataSources, disabled = false }) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const options: Array<SelectableValue<string>> = useMemo(() => {
|
const options: Array<SelectableValue<string>> = useMemo(() => {
|
||||||
return [
|
return dataSources.map((ds) => ({
|
||||||
{
|
label: getAlertManagerLabel(ds),
|
||||||
label: 'Grafana',
|
value: ds.name,
|
||||||
value: GRAFANA_RULES_SOURCE_NAME,
|
imgUrl: ds.imgUrl,
|
||||||
imgUrl: 'public/img/grafana_icon.svg',
|
meta: ds.meta,
|
||||||
meta: {},
|
}));
|
||||||
},
|
}, [dataSources]);
|
||||||
...getAllDataSources()
|
|
||||||
.filter((ds) => ds.type === DataSourceType.Alertmanager)
|
|
||||||
.map((ds) => ({
|
|
||||||
label: ds.name.slice(0, 37),
|
|
||||||
value: ds.name,
|
|
||||||
imgUrl: ds.meta.info.logos.small,
|
|
||||||
meta: ds.meta,
|
|
||||||
})),
|
|
||||||
];
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Field
|
<Field
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Alert } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { useAlertManagerSourceName } from '../hooks/useAlertManagerSourceName';
|
||||||
|
import { AlertManagerDataSource } from '../utils/datasource';
|
||||||
|
|
||||||
|
import { AlertManagerPicker } from './AlertManagerPicker';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
availableAlertManagers: AlertManagerDataSource[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const NoAlertManagersAvailable = () => (
|
||||||
|
<Alert title="No Alertmanager found" severity="warning">
|
||||||
|
We could not find any external Alertmanagers and you may not have access to the built-in Grafana Alertmanager.
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
|
||||||
|
const OtherAlertManagersAvailable = () => (
|
||||||
|
<Alert title="Selected Alertmanager not found. Select a different Alertmanager." severity="warning">
|
||||||
|
Selected Alertmanager no longer exists or you may not have permission to access it.
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const NoAlertManagerWarning = ({ availableAlertManagers }: Props) => {
|
||||||
|
const [_, setAlertManagerSourceName] = useAlertManagerSourceName(availableAlertManagers);
|
||||||
|
const hasOtherAMs = availableAlertManagers.length > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{hasOtherAMs ? (
|
||||||
|
<>
|
||||||
|
<AlertManagerPicker onChange={setAlertManagerSourceName} dataSources={availableAlertManagers} />
|
||||||
|
<OtherAlertManagersAvailable />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<NoAlertManagersAvailable />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -6,6 +6,7 @@ import { GrafanaTheme2 } from '@grafana/data';
|
|||||||
import { Alert, Button, ConfirmModal, TextArea, HorizontalGroup, Field, Form, useStyles2 } from '@grafana/ui';
|
import { Alert, Button, ConfirmModal, TextArea, HorizontalGroup, Field, Form, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
import { useAlertManagerSourceName } from '../../hooks/useAlertManagerSourceName';
|
import { useAlertManagerSourceName } from '../../hooks/useAlertManagerSourceName';
|
||||||
|
import { useAlertManagersByPermission } from '../../hooks/useAlertManagerSources';
|
||||||
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
|
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
|
||||||
import {
|
import {
|
||||||
deleteAlertManagerConfigAction,
|
deleteAlertManagerConfigAction,
|
||||||
@ -22,7 +23,9 @@ interface FormValues {
|
|||||||
|
|
||||||
export default function AlertmanagerConfig(): JSX.Element {
|
export default function AlertmanagerConfig(): JSX.Element {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName();
|
const alertManagers = useAlertManagersByPermission('notification');
|
||||||
|
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName(alertManagers);
|
||||||
|
|
||||||
const [showConfirmDeleteAMConfig, setShowConfirmDeleteAMConfig] = useState(false);
|
const [showConfirmDeleteAMConfig, setShowConfirmDeleteAMConfig] = useState(false);
|
||||||
const { loading: isDeleting } = useUnifiedAlertingSelector((state) => state.deleteAMConfig);
|
const { loading: isDeleting } = useUnifiedAlertingSelector((state) => state.deleteAMConfig);
|
||||||
const { loading: isSaving } = useUnifiedAlertingSelector((state) => state.saveAMConfig);
|
const { loading: isSaving } = useUnifiedAlertingSelector((state) => state.saveAMConfig);
|
||||||
@ -75,7 +78,11 @@ export default function AlertmanagerConfig(): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<AlertManagerPicker current={alertManagerSourceName} onChange={setAlertManagerSourceName} />
|
<AlertManagerPicker
|
||||||
|
current={alertManagerSourceName}
|
||||||
|
onChange={setAlertManagerSourceName}
|
||||||
|
dataSources={alertManagers}
|
||||||
|
/>
|
||||||
{loadingError && !loading && (
|
{loadingError && !loading && (
|
||||||
<Alert severity="error" title="Error loading Alertmanager configuration">
|
<Alert severity="error" title="Error loading Alertmanager configuration">
|
||||||
{loadingError.message || 'Unknown error.'}
|
{loadingError.message || 'Unknown error.'}
|
||||||
|
@ -7,6 +7,7 @@ import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
|||||||
import { AlertmanagerGroup, AlertState } from 'app/plugins/datasource/alertmanager/types';
|
import { AlertmanagerGroup, AlertState } from 'app/plugins/datasource/alertmanager/types';
|
||||||
|
|
||||||
import { useAlertManagerSourceName } from '../../hooks/useAlertManagerSourceName';
|
import { useAlertManagerSourceName } from '../../hooks/useAlertManagerSourceName';
|
||||||
|
import { useAlertManagersByPermission } from '../../hooks/useAlertManagerSources';
|
||||||
import { getFiltersFromUrlParams } from '../../utils/misc';
|
import { getFiltersFromUrlParams } from '../../utils/misc';
|
||||||
import { AlertManagerPicker } from '../AlertManagerPicker';
|
import { AlertManagerPicker } from '../AlertManagerPicker';
|
||||||
|
|
||||||
@ -24,7 +25,8 @@ export const AlertGroupFilter = ({ groups }: Props) => {
|
|||||||
const { groupBy = [], queryString, alertState } = getFiltersFromUrlParams(queryParams);
|
const { groupBy = [], queryString, alertState } = getFiltersFromUrlParams(queryParams);
|
||||||
const matcherFilterKey = `matcher-${filterKey}`;
|
const matcherFilterKey = `matcher-${filterKey}`;
|
||||||
|
|
||||||
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName();
|
const alertManagers = useAlertManagersByPermission('instance');
|
||||||
|
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName(alertManagers);
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const clearFilters = () => {
|
const clearFilters = () => {
|
||||||
@ -40,7 +42,11 @@ export const AlertGroupFilter = ({ groups }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<AlertManagerPicker current={alertManagerSourceName} onChange={setAlertManagerSourceName} />
|
<AlertManagerPicker
|
||||||
|
current={alertManagerSourceName}
|
||||||
|
onChange={setAlertManagerSourceName}
|
||||||
|
dataSources={alertManagers}
|
||||||
|
/>
|
||||||
<div className={styles.filterSection}>
|
<div className={styles.filterSection}>
|
||||||
<MatcherFilter
|
<MatcherFilter
|
||||||
className={styles.filterInput}
|
className={styles.filterInput}
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
} from 'app/plugins/datasource/alertmanager/types';
|
} from 'app/plugins/datasource/alertmanager/types';
|
||||||
|
|
||||||
import { useAlertManagerSourceName } from '../../hooks/useAlertManagerSourceName';
|
import { useAlertManagerSourceName } from '../../hooks/useAlertManagerSourceName';
|
||||||
|
import { useAlertManagersByPermission } from '../../hooks/useAlertManagerSources';
|
||||||
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
|
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
|
||||||
import { updateAlertManagerConfigAction } from '../../state/actions';
|
import { updateAlertManagerConfigAction } from '../../state/actions';
|
||||||
import { MuteTimingFields } from '../../types/mute-timing-form';
|
import { MuteTimingFields } from '../../types/mute-timing-form';
|
||||||
@ -57,7 +58,8 @@ const useDefaultValues = (muteTiming?: MuteTimeInterval): MuteTimingFields => {
|
|||||||
|
|
||||||
const MuteTimingForm = ({ muteTiming, showError }: Props) => {
|
const MuteTimingForm = ({ muteTiming, showError }: Props) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName();
|
const alertManagers = useAlertManagersByPermission('notification');
|
||||||
|
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName(alertManagers);
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
const defaultAmCortexConfig = { alertmanager_config: {}, template_files: {} };
|
const defaultAmCortexConfig = { alertmanager_config: {}, template_files: {} };
|
||||||
@ -101,7 +103,12 @@ const MuteTimingForm = ({ muteTiming, showError }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertingPageWrapper pageId="am-routes">
|
<AlertingPageWrapper pageId="am-routes">
|
||||||
<AlertManagerPicker current={alertManagerSourceName} onChange={setAlertManagerSourceName} disabled />
|
<AlertManagerPicker
|
||||||
|
current={alertManagerSourceName}
|
||||||
|
onChange={setAlertManagerSourceName}
|
||||||
|
disabled
|
||||||
|
dataSources={alertManagers}
|
||||||
|
/>
|
||||||
{result && !loading && (
|
{result && !loading && (
|
||||||
<FormProvider {...formApi}>
|
<FormProvider {...formApi}>
|
||||||
<form onSubmit={formApi.handleSubmit(onSubmit)} data-testid="mute-timing-form">
|
<form onSubmit={formApi.handleSubmit(onSubmit)} data-testid="mute-timing-form">
|
||||||
|
@ -8,7 +8,6 @@ import { updateAlertManagerConfigAction } from '../../../state/actions';
|
|||||||
import { CloudChannelValues, ReceiverFormValues, CloudChannelMap } from '../../../types/receiver-form';
|
import { CloudChannelValues, ReceiverFormValues, CloudChannelMap } from '../../../types/receiver-form';
|
||||||
import { cloudNotifierTypes } from '../../../utils/cloud-alertmanager-notifier-types';
|
import { cloudNotifierTypes } from '../../../utils/cloud-alertmanager-notifier-types';
|
||||||
import { isVanillaPrometheusAlertManagerDataSource } from '../../../utils/datasource';
|
import { isVanillaPrometheusAlertManagerDataSource } from '../../../utils/datasource';
|
||||||
import { makeAMLink } from '../../../utils/misc';
|
|
||||||
import {
|
import {
|
||||||
cloudReceiverToFormValues,
|
cloudReceiverToFormValues,
|
||||||
formValuesToCloudReceiver,
|
formValuesToCloudReceiver,
|
||||||
@ -53,7 +52,7 @@ export const CloudReceiverForm: FC<Props> = ({ existing, alertManagerSourceName,
|
|||||||
oldConfig: config,
|
oldConfig: config,
|
||||||
alertManagerSourceName,
|
alertManagerSourceName,
|
||||||
successMessage: existing ? 'Contact point updated.' : 'Contact point created.',
|
successMessage: existing ? 'Contact point updated.' : 'Contact point created.',
|
||||||
redirectPath: makeAMLink('/alerting/notifications', alertManagerSourceName),
|
redirectPath: '/alerting/notifications',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,103 @@
|
|||||||
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
|
import { createMemoryHistory } from 'history';
|
||||||
|
import React from 'react';
|
||||||
|
import { MemoryRouter, Router } from 'react-router-dom';
|
||||||
|
|
||||||
|
import store from 'app/core/store';
|
||||||
|
|
||||||
|
import { ALERTMANAGER_NAME_LOCAL_STORAGE_KEY } from '../utils/constants';
|
||||||
|
import { AlertManagerDataSource, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
|
||||||
|
|
||||||
|
import { useAlertManagerSourceName } from './useAlertManagerSourceName';
|
||||||
|
|
||||||
|
const grafanaAm: AlertManagerDataSource = {
|
||||||
|
name: GRAFANA_RULES_SOURCE_NAME,
|
||||||
|
imgUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const externalAmProm: AlertManagerDataSource = {
|
||||||
|
name: 'PrometheusAm',
|
||||||
|
imgUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const externalAmMimir: AlertManagerDataSource = {
|
||||||
|
name: 'MimirAm',
|
||||||
|
imgUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('useAlertManagerSourceName', () => {
|
||||||
|
it('Should return undefined alert manager name when there are no available alert managers', () => {
|
||||||
|
const wrapper: React.FC = ({ children }) => <MemoryRouter>{children}</MemoryRouter>;
|
||||||
|
const { result } = renderHook(() => useAlertManagerSourceName([]), { wrapper });
|
||||||
|
|
||||||
|
const [alertManager] = result.current;
|
||||||
|
|
||||||
|
expect(alertManager).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return Grafana AM when it is available and no alert manager query param exists', () => {
|
||||||
|
const wrapper: React.FC = ({ children }) => <MemoryRouter>{children}</MemoryRouter>;
|
||||||
|
|
||||||
|
const availableAMs = [grafanaAm, externalAmProm, externalAmMimir];
|
||||||
|
const { result } = renderHook(() => useAlertManagerSourceName(availableAMs), { wrapper });
|
||||||
|
|
||||||
|
const [alertManager] = result.current;
|
||||||
|
|
||||||
|
expect(alertManager).toBe(grafanaAm.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return alert manager included in the query param when available', () => {
|
||||||
|
const history = createMemoryHistory();
|
||||||
|
history.push({ search: `alertmanager=${externalAmProm.name}` });
|
||||||
|
const wrapper: React.FC = ({ children }) => <Router history={history}>{children}</Router>;
|
||||||
|
|
||||||
|
const availableAMs = [grafanaAm, externalAmProm, externalAmMimir];
|
||||||
|
const { result } = renderHook(() => useAlertManagerSourceName(availableAMs), { wrapper });
|
||||||
|
|
||||||
|
const [alertManager] = result.current;
|
||||||
|
|
||||||
|
expect(alertManager).toBe(externalAmProm.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return undefined if alert manager included in the query is not available', () => {
|
||||||
|
const history = createMemoryHistory();
|
||||||
|
history.push({ search: `alertmanager=Not available external AM` });
|
||||||
|
const wrapper: React.FC = ({ children }) => <Router history={history}>{children}</Router>;
|
||||||
|
|
||||||
|
const availableAMs = [grafanaAm, externalAmProm, externalAmMimir];
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useAlertManagerSourceName(availableAMs), { wrapper });
|
||||||
|
|
||||||
|
const [alertManager] = result.current;
|
||||||
|
|
||||||
|
expect(alertManager).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return alert manager from store if available and query is empty', () => {
|
||||||
|
const wrapper: React.FC = ({ children }) => <MemoryRouter>{children}</MemoryRouter>;
|
||||||
|
|
||||||
|
const availableAMs = [grafanaAm, externalAmProm, externalAmMimir];
|
||||||
|
store.set(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, externalAmProm.name);
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useAlertManagerSourceName(availableAMs), { wrapper });
|
||||||
|
|
||||||
|
const [alertManager] = result.current;
|
||||||
|
|
||||||
|
expect(alertManager).toBe(externalAmProm.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should prioritize the alert manager from query over store', () => {
|
||||||
|
const history = createMemoryHistory();
|
||||||
|
history.push({ search: `alertmanager=${externalAmProm.name}` });
|
||||||
|
const wrapper: React.FC = ({ children }) => <Router history={history}>{children}</Router>;
|
||||||
|
|
||||||
|
const availableAMs = [grafanaAm, externalAmProm, externalAmMimir];
|
||||||
|
store.set(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, externalAmMimir.name);
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useAlertManagerSourceName(availableAMs), { wrapper });
|
||||||
|
|
||||||
|
const [alertManager] = result.current;
|
||||||
|
|
||||||
|
expect(alertManager).toBe(externalAmProm.name);
|
||||||
|
});
|
||||||
|
});
|
@ -4,25 +4,31 @@ import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
|||||||
import store from 'app/core/store';
|
import store from 'app/core/store';
|
||||||
|
|
||||||
import { ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, ALERTMANAGER_NAME_QUERY_KEY } from '../utils/constants';
|
import { ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, ALERTMANAGER_NAME_QUERY_KEY } from '../utils/constants';
|
||||||
import { getAlertManagerDataSources, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
|
import { AlertManagerDataSource, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
|
||||||
|
|
||||||
function isAlertManagerSource(alertManagerSourceName: string): boolean {
|
function useIsAlertManagerAvailable(availableAlertManagers: AlertManagerDataSource[]) {
|
||||||
return (
|
return useCallback(
|
||||||
alertManagerSourceName === GRAFANA_RULES_SOURCE_NAME ||
|
(alertManagerName: string) => {
|
||||||
!!getAlertManagerDataSources().find((ds) => ds.name === alertManagerSourceName)
|
const availableAlertManagersNames = availableAlertManagers.map((am) => am.name);
|
||||||
|
return availableAlertManagersNames.includes(alertManagerName);
|
||||||
|
},
|
||||||
|
[availableAlertManagers]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* this will return am name either from query params or from local storage or a default (grafana).
|
/* This will return am name either from query params or from local storage or a default (grafana).
|
||||||
*
|
* Due to RBAC permissions Grafana Managed Alert manager or external alert managers may not be available
|
||||||
* fallbackUrl - if provided, will redirect to this url if alertmanager provided in query no longer
|
* In the worst case neihter GMA nor external alert manager is available
|
||||||
*/
|
*/
|
||||||
export function useAlertManagerSourceName(): [string | undefined, (alertManagerSourceName: string) => void] {
|
export function useAlertManagerSourceName(
|
||||||
|
availableAlertManagers: AlertManagerDataSource[]
|
||||||
|
): [string | undefined, (alertManagerSourceName: string) => void] {
|
||||||
const [queryParams, updateQueryParams] = useQueryParams();
|
const [queryParams, updateQueryParams] = useQueryParams();
|
||||||
|
const isAlertManagerAvailable = useIsAlertManagerAvailable(availableAlertManagers);
|
||||||
|
|
||||||
const update = useCallback(
|
const update = useCallback(
|
||||||
(alertManagerSourceName: string) => {
|
(alertManagerSourceName: string) => {
|
||||||
if (!isAlertManagerSource(alertManagerSourceName)) {
|
if (!isAlertManagerAvailable(alertManagerSourceName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (alertManagerSourceName === GRAFANA_RULES_SOURCE_NAME) {
|
if (alertManagerSourceName === GRAFANA_RULES_SOURCE_NAME) {
|
||||||
@ -33,24 +39,29 @@ export function useAlertManagerSourceName(): [string | undefined, (alertManagerS
|
|||||||
updateQueryParams({ [ALERTMANAGER_NAME_QUERY_KEY]: alertManagerSourceName });
|
updateQueryParams({ [ALERTMANAGER_NAME_QUERY_KEY]: alertManagerSourceName });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[updateQueryParams]
|
[updateQueryParams, isAlertManagerAvailable]
|
||||||
);
|
);
|
||||||
|
|
||||||
const querySource = queryParams[ALERTMANAGER_NAME_QUERY_KEY];
|
const querySource = queryParams[ALERTMANAGER_NAME_QUERY_KEY];
|
||||||
|
|
||||||
if (querySource && typeof querySource === 'string') {
|
if (querySource && typeof querySource === 'string') {
|
||||||
if (isAlertManagerSource(querySource)) {
|
if (isAlertManagerAvailable(querySource)) {
|
||||||
return [querySource, update];
|
return [querySource, update];
|
||||||
} else {
|
} else {
|
||||||
// non existing alertmanager
|
// non existing alertmanager
|
||||||
return [undefined, update];
|
return [undefined, update];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const storeSource = store.get(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY);
|
const storeSource = store.get(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY);
|
||||||
if (storeSource && typeof storeSource === 'string' && isAlertManagerSource(storeSource)) {
|
if (storeSource && typeof storeSource === 'string' && isAlertManagerAvailable(storeSource)) {
|
||||||
update(storeSource);
|
update(storeSource);
|
||||||
return [storeSource, update];
|
return [storeSource, update];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [GRAFANA_RULES_SOURCE_NAME, update];
|
if (isAlertManagerAvailable(GRAFANA_RULES_SOURCE_NAME)) {
|
||||||
|
return [GRAFANA_RULES_SOURCE_NAME, update];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [undefined, update];
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { getAlertManagerDataSourcesByPermission } from '../utils/datasource';
|
||||||
|
|
||||||
|
export function useAlertManagersByPermission(accessType: 'instance' | 'notification') {
|
||||||
|
return useMemo(() => getAlertManagerDataSourcesByPermission(accessType), [accessType]);
|
||||||
|
}
|
@ -7,10 +7,12 @@ import { timeIntervalToString } from '../utils/alertmanager';
|
|||||||
import { initialAsyncRequestState } from '../utils/redux';
|
import { initialAsyncRequestState } from '../utils/redux';
|
||||||
|
|
||||||
import { useAlertManagerSourceName } from './useAlertManagerSourceName';
|
import { useAlertManagerSourceName } from './useAlertManagerSourceName';
|
||||||
|
import { useAlertManagersByPermission } from './useAlertManagerSources';
|
||||||
import { useUnifiedAlertingSelector } from './useUnifiedAlertingSelector';
|
import { useUnifiedAlertingSelector } from './useUnifiedAlertingSelector';
|
||||||
|
|
||||||
export function useMuteTimingOptions(): Array<SelectableValue<string>> {
|
export function useMuteTimingOptions(): Array<SelectableValue<string>> {
|
||||||
const [alertManagerSourceName] = useAlertManagerSourceName();
|
const alertManagers = useAlertManagersByPermission('notification');
|
||||||
|
const [alertManagerSourceName] = useAlertManagerSourceName(alertManagers);
|
||||||
const amConfigs = useUnifiedAlertingSelector((state) => state.amConfigs);
|
const amConfigs = useUnifiedAlertingSelector((state) => state.amConfigs);
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
|
@ -9,7 +9,7 @@ function getRulesSourceType(alertManagerSourceName: string): RulesSourceType {
|
|||||||
return isGrafanaRulesSource(alertManagerSourceName) ? 'grafana' : 'external';
|
return isGrafanaRulesSource(alertManagerSourceName) ? 'grafana' : 'external';
|
||||||
}
|
}
|
||||||
|
|
||||||
const instancesPermissions = {
|
export const instancesPermissions = {
|
||||||
read: {
|
read: {
|
||||||
grafana: AccessControlAction.AlertingInstanceRead,
|
grafana: AccessControlAction.AlertingInstanceRead,
|
||||||
external: AccessControlAction.AlertingInstancesExternalRead,
|
external: AccessControlAction.AlertingInstancesExternalRead,
|
||||||
@ -28,7 +28,7 @@ const instancesPermissions = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const notificationsPermissions = {
|
export const notificationsPermissions = {
|
||||||
read: {
|
read: {
|
||||||
grafana: AccessControlAction.AlertingNotificationsRead,
|
grafana: AccessControlAction.AlertingNotificationsRead,
|
||||||
external: AccessControlAction.AlertingNotificationsExternalRead,
|
external: AccessControlAction.AlertingNotificationsExternalRead,
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { DataSourceJsonData, DataSourceInstanceSettings } from '@grafana/data';
|
import { DataSourceInstanceSettings, DataSourceJsonData } from '@grafana/data';
|
||||||
import { contextSrv } from 'app/core/services/context_srv';
|
import { contextSrv } from 'app/core/services/context_srv';
|
||||||
import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types';
|
import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types';
|
||||||
import { AccessControlAction } from 'app/types';
|
import { AccessControlAction } from 'app/types';
|
||||||
import { RulesSource } from 'app/types/unified-alerting';
|
import { RulesSource } from 'app/types/unified-alerting';
|
||||||
|
|
||||||
|
import { instancesPermissions, notificationsPermissions } from './access-control';
|
||||||
import { getAllDataSources } from './config';
|
import { getAllDataSources } from './config';
|
||||||
|
|
||||||
export const GRAFANA_RULES_SOURCE_NAME = 'grafana';
|
export const GRAFANA_RULES_SOURCE_NAME = 'grafana';
|
||||||
@ -15,6 +16,12 @@ export enum DataSourceType {
|
|||||||
Prometheus = 'prometheus',
|
Prometheus = 'prometheus',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AlertManagerDataSource {
|
||||||
|
name: string;
|
||||||
|
imgUrl: string;
|
||||||
|
meta?: DataSourceInstanceSettings['meta'];
|
||||||
|
}
|
||||||
|
|
||||||
export const RulesDataSourceTypes: string[] = [DataSourceType.Loki, DataSourceType.Prometheus];
|
export const RulesDataSourceTypes: string[] = [DataSourceType.Loki, DataSourceType.Prometheus];
|
||||||
|
|
||||||
export function getRulesDataSources() {
|
export function getRulesDataSources() {
|
||||||
@ -37,6 +44,50 @@ export function getAlertManagerDataSources() {
|
|||||||
.sort((a, b) => a.name.localeCompare(b.name));
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const grafanaAlertManagerDataSource: AlertManagerDataSource = {
|
||||||
|
name: GRAFANA_RULES_SOURCE_NAME,
|
||||||
|
imgUrl: 'public/img/grafana_icon.svg',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Used only as a fallback for Alert Group plugin
|
||||||
|
export function getAllAlertManagerDataSources(): AlertManagerDataSource[] {
|
||||||
|
return [
|
||||||
|
grafanaAlertManagerDataSource,
|
||||||
|
...getAlertManagerDataSources().map<AlertManagerDataSource>((ds) => ({
|
||||||
|
name: ds.name,
|
||||||
|
displayName: ds.name,
|
||||||
|
imgUrl: ds.meta.info.logos.small,
|
||||||
|
meta: ds.meta,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAlertManagerDataSourcesByPermission(
|
||||||
|
permission: 'instance' | 'notification'
|
||||||
|
): AlertManagerDataSource[] {
|
||||||
|
const availableDataSources: AlertManagerDataSource[] = [];
|
||||||
|
const permissions = {
|
||||||
|
instance: instancesPermissions.read,
|
||||||
|
notification: notificationsPermissions.read,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (contextSrv.hasPermission(permissions[permission].grafana)) {
|
||||||
|
availableDataSources.push(grafanaAlertManagerDataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contextSrv.hasPermission(permissions[permission].external)) {
|
||||||
|
const cloudSources = getAlertManagerDataSources().map<AlertManagerDataSource>((ds) => ({
|
||||||
|
name: ds.name,
|
||||||
|
displayName: ds.name,
|
||||||
|
imgUrl: ds.meta.info.logos.small,
|
||||||
|
meta: ds.meta,
|
||||||
|
}));
|
||||||
|
availableDataSources.push(...cloudSources);
|
||||||
|
}
|
||||||
|
|
||||||
|
return availableDataSources;
|
||||||
|
}
|
||||||
|
|
||||||
export function getLotexDataSourceByName(dataSourceName: string): DataSourceInstanceSettings {
|
export function getLotexDataSourceByName(dataSourceName: string): DataSourceInstanceSettings {
|
||||||
const dataSource = getDataSourceByName(dataSourceName);
|
const dataSource = getDataSourceByName(dataSourceName);
|
||||||
if (!dataSource) {
|
if (!dataSource) {
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
import { PanelPlugin } from '@grafana/data';
|
import { PanelPlugin } from '@grafana/data';
|
||||||
import { AlertManagerPicker } from 'app/features/alerting/unified/components/AlertManagerPicker';
|
import { AlertManagerPicker } from 'app/features/alerting/unified/components/AlertManagerPicker';
|
||||||
import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
|
import {
|
||||||
|
getAllAlertManagerDataSources,
|
||||||
|
GRAFANA_RULES_SOURCE_NAME,
|
||||||
|
} from 'app/features/alerting/unified/utils/datasource';
|
||||||
|
|
||||||
import { AlertGroupsPanel } from './AlertGroupsPanel';
|
import { AlertGroupsPanel } from './AlertGroupsPanel';
|
||||||
import { AlertGroupPanelOptions } from './types';
|
import { AlertGroupPanelOptions } from './types';
|
||||||
@ -16,12 +19,15 @@ export const plugin = new PanelPlugin<AlertGroupPanelOptions>(AlertGroupsPanel).
|
|||||||
defaultValue: GRAFANA_RULES_SOURCE_NAME,
|
defaultValue: GRAFANA_RULES_SOURCE_NAME,
|
||||||
category: ['Options'],
|
category: ['Options'],
|
||||||
editor: function RenderAlertmanagerPicker(props) {
|
editor: function RenderAlertmanagerPicker(props) {
|
||||||
|
const alertManagers = useMemo(getAllAlertManagerDataSources, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertManagerPicker
|
<AlertManagerPicker
|
||||||
current={props.value}
|
current={props.value}
|
||||||
onChange={(alertManagerSourceName) => {
|
onChange={(alertManagerSourceName) => {
|
||||||
return props.onChange(alertManagerSourceName);
|
return props.onChange(alertManagerSourceName);
|
||||||
}}
|
}}
|
||||||
|
dataSources={alertManagers}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user