diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go
index ea45df6a535..c1e67662f8e 100644
--- a/pkg/services/navtree/navtreeimpl/navtree.go
+++ b/pkg/services/navtree/navtreeimpl/navtree.go
@@ -484,6 +484,15 @@ func (s *ServiceImpl) buildAlertNavLinks(c *models.ReqContext, hasEditPerm bool)
hasAccess := ac.HasAccess(s.accessControl, c)
var alertChildNavs []*navtree.NavLink
+ if !s.features.IsEnabled(featuremgmt.FlagTopnav) {
+ alertChildNavs = append(alertChildNavs, &navtree.NavLink{
+ Text: "Home",
+ Id: "alert-home",
+ Url: s.cfg.AppSubURL + "/alerting/home",
+ Icon: "home",
+ })
+ }
+
if hasAccess(ac.ReqViewer, ac.EvalAny(ac.EvalPermission(ac.ActionAlertingRuleRead), ac.EvalPermission(ac.ActionAlertingRuleExternalRead))) {
alertChildNavs = append(alertChildNavs, &navtree.NavLink{
Text: "Alert rules", SubTitle: "Rules that determine whether an alert will fire", Id: "alert-list", Url: s.cfg.AppSubURL + "/alerting/list", Icon: "list-ul",
@@ -539,7 +548,7 @@ func (s *ServiceImpl) buildAlertNavLinks(c *models.ReqContext, hasEditPerm bool)
if s.features.IsEnabled(featuremgmt.FlagTopnav) {
alertNav.Url = s.cfg.AppSubURL + "/alerting"
} else {
- alertNav.Url = s.cfg.AppSubURL + "/alerting/list"
+ alertNav.Url = s.cfg.AppSubURL + "/alerting/home"
}
return &alertNav
diff --git a/public/app/core/components/NavBar/navBarItem-translations.ts b/public/app/core/components/NavBar/navBarItem-translations.ts
index 4cbebe51972..886393984c9 100644
--- a/public/app/core/components/NavBar/navBarItem-translations.ts
+++ b/public/app/core/components/NavBar/navBarItem-translations.ts
@@ -51,6 +51,8 @@ export function getNavTitle(navId: string | undefined) {
return t('nav.alerting.title', 'Alerting');
case 'alerting-legacy':
return t('nav.alerting-legacy.title', 'Alerting (legacy)');
+ case 'alert-home':
+ return t('nav.alerting-home.title', 'Home');
case 'alert-list':
return t('nav.alerting-list.title', 'Alert rules');
case 'receivers':
diff --git a/public/app/features/alerting/routes.tsx b/public/app/features/alerting/routes.tsx
index 082f631879b..95b2bda7ed5 100644
--- a/public/app/features/alerting/routes.tsx
+++ b/public/app/features/alerting/routes.tsx
@@ -11,16 +11,15 @@ import { AccessControlAction } from 'app/types';
import { evaluateAccess } from './unified/utils/access-control';
-const commonRoutes: RouteDescriptor[] = [
+const commonRoutes: RouteDescriptor[] = [];
+
+const legacyRoutes: RouteDescriptor[] = [
+ ...commonRoutes,
{
path: '/alerting',
component: () =>
config.featureToggles.topnav ? : ,
},
-];
-
-const legacyRoutes: RouteDescriptor[] = [
- ...commonRoutes,
{
path: '/alerting/list',
component: SafeDynamicImport(
@@ -91,6 +90,19 @@ const legacyRoutes: RouteDescriptor[] = [
const unifiedRoutes: RouteDescriptor[] = [
...commonRoutes,
+ config.featureToggles.topnav
+ ? {
+ path: '/alerting',
+ component: SafeDynamicImport(
+ () => import(/* webpackChunkName: "AlertingHome" */ 'app/features/alerting/unified/Home')
+ ),
+ }
+ : {
+ path: '/alerting/home',
+ component: SafeDynamicImport(
+ () => import(/* webpackChunkName: "AlertingHome" */ 'app/features/alerting/unified/Home')
+ ),
+ },
{
path: '/alerting/list',
roles: evaluateAccess(
diff --git a/public/app/features/alerting/unified/Home.tsx b/public/app/features/alerting/unified/Home.tsx
new file mode 100644
index 00000000000..da7684870a4
--- /dev/null
+++ b/public/app/features/alerting/unified/Home.tsx
@@ -0,0 +1,307 @@
+import { css, cx } from '@emotion/css';
+import React from 'react';
+
+import { GrafanaTheme2 } from '@grafana/data';
+import { Stack } from '@grafana/experimental';
+import { config } from '@grafana/runtime';
+import { Icon, LinkButton, useStyles2, useTheme2, Tooltip } from '@grafana/ui';
+
+import { AlertingPageWrapper } from './components/AlertingPageWrapper';
+
+export default function Home() {
+ const theme = useTheme2();
+ const styles = useStyles2(getWelcomePageStyles);
+
+ return (
+
+
+
+
+
+
+
+ Grafana alerting periodically queries your data sources and evaluates the alerting
+ condition you define
+
+
+ If the condition is breached, the alert rule fires and produces alert instances {' '}
+
+
+
+
+
+ Firing instances are sent to the Alertmanager {' '}
+
+
+
+
+
+ Alertmanager routes firing alert instances to notification policies based on whether the
+ labels match
+
+
+ Notifications are sent out to the contact point defined in the matching notification
+ policy
+
+
+
+
+
+
+
+ Create an alert rule by adding queries and expressions from multiple data sources.
+
+
+ Add labels to your alert rules{' '}
+ to connect them to notification policies
+
+
+ Configure contact points to define where to send your notifications to.
+
+
+ Configure notification policies to route your alert instances to contact points.
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+const getWelcomePageStyles = (theme: GrafanaTheme2) => ({
+ grid: css`
+ display: grid;
+ grid-template-rows: min-content auto auto;
+ grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
+ gap: ${theme.spacing(2)};
+ `,
+ ctaContainer: css`
+ grid-column: 1 / span 5;
+ `,
+ flowBlock: css`
+ width: 100%;
+ grid-column: 1 / span 5;
+
+ img {
+ display: block;
+ margin: 0 auto;
+ height: auto;
+ width: 100%;
+ }
+ `,
+ videoBlock: css`
+ grid-column: 3 / span 3;
+ grid-row: 3 / span 1;
+
+ // Video required
+ position: relative;
+ padding: 56.25% 0 0 0; /* 16:9 */
+
+ iframe {
+ position: absolute;
+ top: ${theme.spacing(2)};
+ left: ${theme.spacing(2)};
+ width: calc(100% - ${theme.spacing(4)});
+ height: calc(100% - ${theme.spacing(4)});
+ border: none;
+ }
+ `,
+ gettingStartedBlock: css`
+ grid-column: 1 / span 2;
+ justify-content: space-between;
+
+ ul {
+ margin-left: ${theme.spacing(2)};
+ }
+ `,
+ howItWorks: css`
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: ${theme.spacing(2)};
+ list-style: none inside none;
+ list-style-type: disclosure-closed;
+
+ > li {
+ flex: 1;
+ min-width: 150px;
+ }
+ `,
+});
+
+function WelcomeHeader({ className }: { className?: string }) {
+ const styles = useStyles2(getWelcomeHeaderStyles);
+
+ return (
+
+ );
+}
+
+const getWelcomeHeaderStyles = (theme: GrafanaTheme2) => ({
+ container: css`
+ display: flex;
+ flex-direction: column;
+ padding: ${theme.spacing(4)};
+ background-image: url(public/img/alerting/welcome_cta_bg_${theme.name.toLowerCase()}.svg);
+ background-size: cover;
+ background-clip: padding-box;
+
+ outline: 1px solid hsla(6deg, 60%, 80%, 0.14);
+ outline-offset: -1px;
+ border-radius: 3px;
+ `,
+ ctaContainer: css`
+ padding: ${theme.spacing(4)};
+ display: flex;
+ gap: ${theme.spacing(4)};
+ justify-content: space-between;
+ flex-wrap: wrap;
+ `,
+});
+
+interface WelcomeCTABoxProps {
+ title: string;
+ description: string;
+ icon: React.ComponentProps['name'];
+ href: string;
+ hrefText: string;
+}
+
+function WelcomeCTABox({ title, description, icon, href, hrefText }: WelcomeCTABoxProps) {
+ const styles = useStyles2(getWelcomeCTAButtonStyles);
+
+ return (
+
+
+
+
+
{title}
+
{description}
+
+ {hrefText}
+
+
+ );
+}
+
+const getWelcomeCTAButtonStyles = (theme: GrafanaTheme2) => ({
+ container: css`
+ flex: 1;
+ min-width: 240px;
+ display: grid;
+ gap: ${theme.spacing(1)};
+ grid-template-columns: min-content 1fr 1fr 1fr;
+ grid-template-rows: min-content auto min-content;
+ `,
+
+ title: css`
+ grid-column: 2 / span 3;
+ grid-row: 1;
+ `,
+
+ desc: css`
+ grid-column: 2 / span 3;
+ grid-row: 2;
+ `,
+
+ actionButton: css`
+ grid-column: 2 / span 3;
+ grid-row: 3;
+ max-width: 240px;
+ `,
+
+ icon: css`
+ grid-column: 1;
+ grid-row: 1 / span 2;
+ margin: auto;
+ color: #ff8833;
+ `,
+});
+
+function ContentBox({ children, title, className }: React.PropsWithChildren<{ title?: string; className?: string }>) {
+ const styles = useStyles2(getContentBoxStyles);
+
+ return (
+
+ {title &&
{title} }
+ {children}
+
+ );
+}
+
+const getContentBoxStyles = (theme: GrafanaTheme2) => ({
+ box: css`
+ padding: ${theme.spacing(2)};
+ background-color: ${theme.colors.background.secondary};
+ border-radius: 3px;
+ outline: 1px solid ${theme.colors.border.strong};
+ `,
+});
+
+function ArrowLink({ href, title }: { href: string; title: string }) {
+ const styles = useStyles2(getArrowLinkStyles);
+
+ return (
+
+ {title}
+
+ );
+}
+
+const getArrowLinkStyles = (theme: GrafanaTheme2) => ({
+ link: css`
+ display: block;
+ color: ${theme.colors.text.link};
+ `,
+});
diff --git a/public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx
index fc1ff41babb..d985d37012a 100644
--- a/public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx
+++ b/public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx
@@ -1,9 +1,9 @@
import { css } from '@emotion/css';
-import React, { useState } from 'react';
+import React from 'react';
import { useFormContext } from 'react-hook-form';
import { GrafanaTheme2 } from '@grafana/data';
-import { Card, Link, useStyles2, useTheme2 } from '@grafana/ui';
+import { Card, Link, useStyles2 } from '@grafana/ui';
import { RuleFormType, RuleFormValues } from '../../types/rule-form';
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
@@ -12,14 +12,13 @@ import LabelsField from './LabelsField';
import { RuleEditorSection } from './RuleEditorSection';
export const NotificationsStep = () => {
- const [hideFlowChart, setHideFlowChart] = useState(false);
const styles = useStyles2(getStyles);
- const theme = useTheme2();
- const { watch } = useFormContext();
+ const { watch, getValues } = useFormContext();
const type = watch('type');
const dataSourceName = watch('dataSourceName') ?? GRAFANA_RULES_SOURCE_NAME;
+ const hasLabelsDefined = getNonEmptyLabels(getValues('labels')).length > 0;
return (
{
title="Notifications"
description="Grafana handles the notifications for alerts by assigning labels to alerts. These labels connect alerts to contact points and silence alert instances that have matching labels."
>
-
-
setHideFlowChart(!hideFlowChart)}>
- {`${!hideFlowChart ? 'Hide' : 'Show'} flow chart`}
-
-
- {!hideFlowChart && (
-
- )}
+ {!hasLabelsDefined && (
+
+ Root route – default for all alerts
+
+ Without custom labels, your alert will be routed through the root route. To view and edit the root
+ route, go to notification policies or contact your admin in case
+ you are using non-Grafana alert management.
+
+
+ )}
-
- Root route – default for all alerts
-
- Without custom labels, your alert will be routed through the root route. To view and edit the root route,
- go to notification policies or contact your admin in case you are
- using non-Grafana alert management.
-
-
);
};
+interface Label {
+ key: string;
+ value: string;
+}
+
+function getNonEmptyLabels(labels: Label[]) {
+ return labels.filter((label) => label.key && label.value);
+}
+
const getStyles = (theme: GrafanaTheme2) => ({
contentWrapper: css`
display: flex;
diff --git a/public/img/alerting/at_a_glance_dark.svg b/public/img/alerting/at_a_glance_dark.svg
new file mode 100644
index 00000000000..b36ddedaafe
--- /dev/null
+++ b/public/img/alerting/at_a_glance_dark.svg
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/img/alerting/at_a_glance_light.svg b/public/img/alerting/at_a_glance_light.svg
new file mode 100644
index 00000000000..2053cadb250
--- /dev/null
+++ b/public/img/alerting/at_a_glance_light.svg
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/img/alerting/notification_policy_dark.svg b/public/img/alerting/notification_policy_dark.svg
deleted file mode 100644
index 25d743758dc..00000000000
--- a/public/img/alerting/notification_policy_dark.svg
+++ /dev/null
@@ -1,99 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/public/img/alerting/notification_policy_light.svg b/public/img/alerting/notification_policy_light.svg
deleted file mode 100644
index c48fbe93b00..00000000000
--- a/public/img/alerting/notification_policy_light.svg
+++ /dev/null
@@ -1,97 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/public/img/alerting/welcome_cta_bg_dark.svg b/public/img/alerting/welcome_cta_bg_dark.svg
new file mode 100644
index 00000000000..844f2640066
--- /dev/null
+++ b/public/img/alerting/welcome_cta_bg_dark.svg
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/img/alerting/welcome_cta_bg_light.svg b/public/img/alerting/welcome_cta_bg_light.svg
new file mode 100644
index 00000000000..2b46f1cb732
--- /dev/null
+++ b/public/img/alerting/welcome_cta_bg_light.svg
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+