diff --git a/pkg/api/index.go b/pkg/api/index.go index 3d86c694036..d79914672af 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -206,15 +206,23 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto if hs.Cfg.IsNgAlertEnabled() { alertChildNavs = append(alertChildNavs, &dtos.NavLink{Text: "Silences", Id: "silences", Url: hs.Cfg.AppSubURL + "/alerting/silences", Icon: "bell-slash"}) } + if c.OrgRole == models.ROLE_ADMIN || c.OrgRole == models.ROLE_EDITOR { + if hs.Cfg.IsNgAlertEnabled() { + alertChildNavs = append(alertChildNavs, &dtos.NavLink{ + Text: "Contact points", Id: "receivers", Url: hs.Cfg.AppSubURL + "/alerting/notifications", + Icon: "comment-alt-share", + }) + } else { + alertChildNavs = append(alertChildNavs, &dtos.NavLink{ + Text: "Notification channels", Id: "channels", Url: hs.Cfg.AppSubURL + "/alerting/notifications", + Icon: "comment-alt-share", + }) + } + } + if c.OrgRole == models.ROLE_ADMIN && hs.Cfg.IsNgAlertEnabled() { alertChildNavs = append(alertChildNavs, &dtos.NavLink{Text: "Routes", Id: "am-routes", Url: hs.Cfg.AppSubURL + "/alerting/routes", Icon: "sitemap"}) } - if c.OrgRole == models.ROLE_ADMIN || c.OrgRole == models.ROLE_EDITOR { - alertChildNavs = append(alertChildNavs, &dtos.NavLink{ - Text: "Notification channels", Id: "channels", Url: hs.Cfg.AppSubURL + "/alerting/notifications", - Icon: "comment-alt-share", - }) - } navTree = append(navTree, &dtos.NavLink{ Text: "Alerting", diff --git a/public/app/features/alerting/NotificationsIndex.tsx b/public/app/features/alerting/NotificationsIndex.tsx new file mode 100644 index 00000000000..73d33b9c5e2 --- /dev/null +++ b/public/app/features/alerting/NotificationsIndex.tsx @@ -0,0 +1,7 @@ +import { config } from '@grafana/runtime'; +import NotificationsListPage from './NotificationsListPage'; +import Receivers from './unified/Receivers'; + +// route between unified and "old" alerting pages based on feature flag + +export default config.featureToggles.ngalert ? Receivers : NotificationsListPage; diff --git a/public/app/features/alerting/unified/AmRoutes.tsx b/public/app/features/alerting/unified/AmRoutes.tsx index b1bfb6895d3..3a4f0ef279a 100644 --- a/public/app/features/alerting/unified/AmRoutes.tsx +++ b/public/app/features/alerting/unified/AmRoutes.tsx @@ -1,4 +1,4 @@ -import { InfoBox, LoadingPlaceholder } from '@grafana/ui'; +import { Field, InfoBox, LoadingPlaceholder } from '@grafana/ui'; import React, { FC, useEffect } from 'react'; import { useDispatch } from 'react-redux'; import { AlertingPageWrapper } from './components/AlertingPageWrapper'; @@ -22,7 +22,9 @@ const AmRoutes: FC = () => { return ( - + + +

{error && !loading && ( diff --git a/public/app/features/alerting/unified/Receivers.tsx b/public/app/features/alerting/unified/Receivers.tsx new file mode 100644 index 00000000000..f5a0fa9a286 --- /dev/null +++ b/public/app/features/alerting/unified/Receivers.tsx @@ -0,0 +1,40 @@ +import { Field, InfoBox, LoadingPlaceholder } from '@grafana/ui'; +import React, { FC, useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { AlertingPageWrapper } from './components/AlertingPageWrapper'; +import { AlertManagerPicker } from './components/AlertManagerPicker'; +import { TemplatesTable } from './components/receivers/TemplatesTable'; +import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName'; +import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector'; +import { fetchAlertManagerConfigAction } from './state/actions'; +import { initialAsyncRequestState } from './utils/redux'; + +const Receivers: FC = () => { + const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName(); + const dispatch = useDispatch(); + + const config = useUnifiedAlertingSelector((state) => state.amConfigs); + + useEffect(() => { + dispatch(fetchAlertManagerConfigAction(alertManagerSourceName)); + }, [alertManagerSourceName, dispatch]); + + const { result, loading, error } = config[alertManagerSourceName] || initialAsyncRequestState; + + return ( + + + + + {error && !loading && ( + Error loading alert manager config}> + {error.message || 'Unknown error.'} + + )} + {loading && } + {result && !loading && !error && } + + ); +}; + +export default Receivers; diff --git a/public/app/features/alerting/unified/Silences.tsx b/public/app/features/alerting/unified/Silences.tsx index dd1968712b0..eb73b0bd81e 100644 --- a/public/app/features/alerting/unified/Silences.tsx +++ b/public/app/features/alerting/unified/Silences.tsx @@ -1,4 +1,4 @@ -import { InfoBox, LoadingPlaceholder } from '@grafana/ui'; +import { Field, InfoBox, LoadingPlaceholder } from '@grafana/ui'; import React, { FC, useEffect } from 'react'; import { useDispatch } from 'react-redux'; import { AlertingPageWrapper } from './components/AlertingPageWrapper'; @@ -22,7 +22,9 @@ const Silences: FC = () => { return ( - + + +

{error && !loading && ( diff --git a/public/app/features/alerting/unified/api/alertmanager.ts b/public/app/features/alerting/unified/api/alertmanager.ts index eee99169bb6..90b42037b50 100644 --- a/public/app/features/alerting/unified/api/alertmanager.ts +++ b/public/app/features/alerting/unified/api/alertmanager.ts @@ -19,7 +19,10 @@ export async function fetchAlertManagerConfig(alertmanagerSourceName: string): P showSuccessAlert: false, }) .toPromise(); - return result.data; + return { + template_files: result.data.template_files ?? {}, + alertmanager_config: result.data.alertmanager_config ?? {}, + }; } catch (e) { // if no config has been uploaded to grafana, it returns error instead of latest config if ( diff --git a/public/app/features/alerting/unified/components/AlertManagerPicker.tsx b/public/app/features/alerting/unified/components/AlertManagerPicker.tsx index f8e1c6d390b..1b35a1df85d 100644 --- a/public/app/features/alerting/unified/components/AlertManagerPicker.tsx +++ b/public/app/features/alerting/unified/components/AlertManagerPicker.tsx @@ -31,6 +31,7 @@ export const AlertManagerPicker: FC = ({ onChange, current }) => { return (