From c7374b691027402c68ae14cff30ab46d4f89dc3d Mon Sep 17 00:00:00 2001 From: Hugo Kiyodi Oshiro Date: Fri, 4 Jul 2025 10:52:43 +0200 Subject: [PATCH] Alerting: Add Synthetic and IRM link cards (#107538) --- pkg/services/user/model.go | 2 + .../core/components/Branding/CloudBadge.tsx | 7 + .../Branding/CloudEnterpriseBadge.tsx | 31 +--- .../core/components/Branding/OrangeBadge.tsx | 31 ++++ .../features/alerting/unified/home/AdCard.tsx | 146 ++++++++++++++++++ .../features/alerting/unified/home/Home.tsx | 8 + .../alerting/unified/home/IRMCard.tsx | 28 ++++ .../unified/home/SyntheticMonitoringCard.tsx | 38 +++++ public/img/irm_logo.svg | 45 ++++++ public/img/synthetic_monitoring_logo.svg | 10 ++ public/locales/en-US/grafana.json | 19 ++- 11 files changed, 336 insertions(+), 29 deletions(-) create mode 100644 public/app/core/components/Branding/CloudBadge.tsx create mode 100644 public/app/core/components/Branding/OrangeBadge.tsx create mode 100644 public/app/features/alerting/unified/home/AdCard.tsx create mode 100644 public/app/features/alerting/unified/home/IRMCard.tsx create mode 100644 public/app/features/alerting/unified/home/SyntheticMonitoringCard.tsx create mode 100644 public/img/irm_logo.svg create mode 100644 public/img/synthetic_monitoring_logo.svg diff --git a/pkg/services/user/model.go b/pkg/services/user/model.go index b60bd4ac306..2d88ee79fa8 100644 --- a/pkg/services/user/model.go +++ b/pkg/services/user/model.go @@ -16,6 +16,8 @@ const ( HelpFlagGettingStartedPanelDismissed HelpFlags1 = 1 << iota HelpFlagDashboardHelp1 HelpFlagEnterpriseAuth1 + HelpFlagSyntheticMonitoring1 + HelpFlagIRM1 ) type UpdateEmailActionType string diff --git a/public/app/core/components/Branding/CloudBadge.tsx b/public/app/core/components/Branding/CloudBadge.tsx new file mode 100644 index 00000000000..e98ac79b913 --- /dev/null +++ b/public/app/core/components/Branding/CloudBadge.tsx @@ -0,0 +1,7 @@ +import { t } from '@grafana/i18n'; + +import { OrangeBadge } from './OrangeBadge'; + +export function CloudBadge() { + return ; +} diff --git a/public/app/core/components/Branding/CloudEnterpriseBadge.tsx b/public/app/core/components/Branding/CloudEnterpriseBadge.tsx index a904417053c..ce95d147581 100644 --- a/public/app/core/components/Branding/CloudEnterpriseBadge.tsx +++ b/public/app/core/components/Branding/CloudEnterpriseBadge.tsx @@ -1,32 +1,7 @@ -import { css } from '@emotion/css'; +import { t } from '@grafana/i18n'; -import { GrafanaTheme2 } from '@grafana/data'; -import { Trans } from '@grafana/i18n'; -import { Icon, useStyles2 } from '@grafana/ui'; +import { OrangeBadge } from './OrangeBadge'; export function CloudEnterpriseBadge() { - const styles = useStyles2(getStyles); - return ( -
- - Cloud & Enterprise -
- ); + return ; } - -const getStyles = (theme: GrafanaTheme2) => { - return { - wrapper: css({ - display: 'inline-flex', - padding: theme.spacing(0.5, 1), - borderRadius: theme.shape.radius.pill, - background: theme.colors.gradients.brandHorizontal, - color: theme.colors.primary.contrastText, - fontWeight: theme.typography.fontWeightMedium, - gap: theme.spacing(0.5), - fontSize: theme.typography.bodySmall.fontSize, - lineHeight: theme.typography.bodySmall.lineHeight, - alignItems: 'center', - }), - }; -}; diff --git a/public/app/core/components/Branding/OrangeBadge.tsx b/public/app/core/components/Branding/OrangeBadge.tsx new file mode 100644 index 00000000000..be9d080df79 --- /dev/null +++ b/public/app/core/components/Branding/OrangeBadge.tsx @@ -0,0 +1,31 @@ +import { css } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Icon, useStyles2 } from '@grafana/ui'; + +export function OrangeBadge({ text }: { text: string }) { + const styles = useStyles2(getStyles); + return ( +
+ + {text} +
+ ); +} + +const getStyles = (theme: GrafanaTheme2) => { + return { + wrapper: css({ + display: 'inline-flex', + padding: theme.spacing(0.5, 1), + borderRadius: theme.shape.radius.pill, + background: theme.colors.gradients.brandHorizontal, + color: theme.colors.primary.contrastText, + fontWeight: theme.typography.fontWeightMedium, + gap: theme.spacing(0.5), + fontSize: theme.typography.bodySmall.fontSize, + lineHeight: theme.typography.bodySmall.lineHeight, + alignItems: 'center', + }), + }; +}; diff --git a/public/app/features/alerting/unified/home/AdCard.tsx b/public/app/features/alerting/unified/home/AdCard.tsx new file mode 100644 index 00000000000..01577b83150 --- /dev/null +++ b/public/app/features/alerting/unified/home/AdCard.tsx @@ -0,0 +1,146 @@ +import { css } from '@emotion/css'; +import { useState } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Trans, t } from '@grafana/i18n'; +import { Button, Divider, Icon, IconButton, useStyles2 } from '@grafana/ui'; +import { CloudBadge } from 'app/core/components/Branding/CloudBadge'; +import { backendSrv } from 'app/core/services/backend_srv'; +import { contextSrv } from 'app/core/services/context_srv'; +import { isOpenSourceBuildOrUnlicenced } from 'app/features/admin/EnterpriseAuthFeaturesCard'; + +type AdCardProps = { + title: string; + description: string; + href: string; + logoUrl: string; + items: string[]; + helpFlag: number; +}; + +export default function AdCard({ title, description, href, logoUrl, items, helpFlag }: AdCardProps) { + const styles = useStyles2(getAddCardStyles); + + const helpFlags = contextSrv.user.helpFlags1; + const [isDismissed, setDismissed] = useState(Boolean(helpFlags & helpFlag)); + + const onDismiss = () => { + backendSrv.put(`/api/user/helpflags/${helpFlag}`, undefined, { showSuccessAlert: false }).then((res) => { + contextSrv.user.helpFlags1 = res.helpFlags1; + setDismissed(true); + }); + }; + + if (isDismissed || !isOpenSourceBuildOrUnlicenced()) { + return null; + } + + return ( +
+
+ + +
+
+ {title.concat(' +
+

{title}

+

{description}

+
+
+ +
+ {items.map((item) => ( +
+ + {item} +
+ ))} +
+ + +
+ ); +} + +const getAddCardStyles = (theme: GrafanaTheme2) => ({ + logo: css({ + objectFit: 'contain', + width: '47px', + height: '47px', + }), + + header: css({ + display: 'flex', + alignItems: 'flex-start', + gap: theme.spacing(2), + paddingTop: theme.spacing(2), + height: theme.spacing(8), + }), + + contentColumn: css({ + flex: 1, + }), + + title: css({ + marginBottom: theme.spacing(1), + fontSize: theme.typography.h4.fontSize, + fontWeight: theme.typography.h4.fontWeight, + color: theme.colors.text.primary, + }), + + description: css({ + fontSize: theme.typography.bodySmall.fontSize, + color: theme.colors.text.secondary, + lineHeight: theme.typography.bodySmall.lineHeight, + }), + + itemsList: css({ + display: 'grid', + gridTemplateColumns: '1fr', + gap: theme.spacing(0.5), + [theme.breakpoints.up('xl')]: { + gridTemplateColumns: '1fr 1fr', + gap: theme.spacing(1), + }, + }), + + listItem: css({ + display: 'flex', + alignItems: 'flex-start', + fontSize: theme.typography.bodySmall.fontSize, + color: theme.colors.text.secondary, + lineHeight: theme.typography.bodySmall.lineHeight, + marginBottom: theme.spacing(0.5), + }), + + icon: css({ + marginRight: theme.spacing(1), + color: theme.colors.success.main, + }), + + button: css({ + padding: `0 ${theme.spacing(2)}`, + }), + + buttonIcon: css({ + marginLeft: theme.spacing(1), + }), + + cardBody: css({ + padding: `${theme.spacing(3)} ${theme.spacing(4)} ${theme.spacing(2.25)} ${theme.spacing(4)}`, + backgroundColor: theme.colors.background.secondary, + borderRadius: theme.shape.radius.default, + border: `1px solid ${theme.colors.border.weak}`, + flex: 1, + }), + + preHeader: css({ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + }), +}); diff --git a/public/app/features/alerting/unified/home/Home.tsx b/public/app/features/alerting/unified/home/Home.tsx index d62f6735531..449f956255c 100644 --- a/public/app/features/alerting/unified/home/Home.tsx +++ b/public/app/features/alerting/unified/home/Home.tsx @@ -8,8 +8,10 @@ import { isLocalDevEnv } from '../utils/misc'; import { withPageErrorBoundary } from '../withPageErrorBoundary'; import GettingStarted, { WelcomeHeader } from './GettingStarted'; +import IRMCard from './IRMCard'; import { getInsightsScenes, insightsIsAvailable } from './Insights'; import { PluginIntegrations } from './PluginIntegrations'; +import SyntheticMonitoringCard from './SyntheticMonitoringCard'; function Home() { const insightsEnabled = insightsIsAvailable() || isLocalDevEnv(); @@ -27,6 +29,12 @@ function Home() { + + + + + + {insightsEnabled && ( diff --git a/public/app/features/alerting/unified/home/IRMCard.tsx b/public/app/features/alerting/unified/home/IRMCard.tsx new file mode 100644 index 00000000000..ff22c94943d --- /dev/null +++ b/public/app/features/alerting/unified/home/IRMCard.tsx @@ -0,0 +1,28 @@ +import { t } from '@grafana/i18n'; +import irmSvg from 'img/irm_logo.svg'; + +import AdCard from './AdCard'; + +const LINK = 'https://grafana.com/auth/sign-up/create-user?redirectPath=irm&src=oss-grafana&cnt=alerting-irm'; +const HELP_FLAG_IRM = 0x0010; + +export default function IRMCard() { + return ( + + ); +} diff --git a/public/app/features/alerting/unified/home/SyntheticMonitoringCard.tsx b/public/app/features/alerting/unified/home/SyntheticMonitoringCard.tsx new file mode 100644 index 00000000000..c98b7066c46 --- /dev/null +++ b/public/app/features/alerting/unified/home/SyntheticMonitoringCard.tsx @@ -0,0 +1,38 @@ +import { t } from '@grafana/i18n'; +import syntheticMonitoringSvg from 'img/synthetic_monitoring_logo.svg'; + +import AdCard from './AdCard'; + +const LINK = + 'https://grafana.com/auth/sign-up/create-user?redirectPath=synthetic-monitoring&src=oss-grafana&cnt=alerting-synthetic-monitoring'; +const HELP_FLAG_SYNTHETIC_MONITORING = 0x0008; + +export default function SyntheticMonitoringCard() { + return ( + + ); +} diff --git a/public/img/irm_logo.svg b/public/img/irm_logo.svg new file mode 100644 index 00000000000..7c25966a158 --- /dev/null +++ b/public/img/irm_logo.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/img/synthetic_monitoring_logo.svg b/public/img/synthetic_monitoring_logo.svg new file mode 100644 index 00000000000..3b5b9f49deb --- /dev/null +++ b/public/img/synthetic_monitoring_logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 8c65d4ed09f..629a553e9da 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -352,6 +352,10 @@ "active-timing-fields": { "am-active-timing-select-label-active-timings": "Active timings" }, + "ad": { + "close": "Close", + "learn-more": "Learn more" + }, "add-button": { "add-more": "Add more" }, @@ -1525,8 +1529,20 @@ } }, "home": { + "irm-card-description": "Unify on-call, alerting, and incident response with Grafana Cloud IRM.", + "irm-card-item-1": "Manage on-call schedules with your calendar or Terraform.", + "irm-card-item-2": "Respond to incidents via web, app, Slack, or other channels.", + "irm-card-item-3": "Pinpoint root causes with AI-powered Grafana SIFT.", + "irm-card-item-4": "Analyze past incidents to improve response and resilience.", + "irm-card-title": "Incident response and management", "label-get-started": "Get started", "label-insights": "Insights", + "synthetic-monitoring-card-description": "Monitor critical user flows, websites, and APIs externally, from global locations.", + "synthetic-monitoring-card-item-1": "Simulate end-to-end user journeys with browser checks.", + "synthetic-monitoring-card-item-2": "Run ping, DNS, HTTP/S, and TCP checks at every network layer.", + "synthetic-monitoring-card-item-3": "Use 20+ global probes or private probes behind your firewall.", + "synthetic-monitoring-card-item-4": "Track SLOs with built-in Prometheus-style alerts — right from the UI.", + "synthetic-monitoring-card-title": "Synthetic Monitoring", "title-alerting": "Alerting" }, "import-to-gma": { @@ -3894,7 +3910,8 @@ "close-button": { "tooltip": "Close" }, - "cloud-enterprise-feature-badge": "Cloud & Enterprise ", + "cloud-enterprise-feature-badge": "Cloud & Enterprise", + "cloud-feature-badge": "Cloud", "combobox": { "async": { "error": "An error occurred while loading options."