From 52136efb0b605579fdc3744e12b58940d54e6dc2 Mon Sep 17 00:00:00 2001 From: ausias-armesto Date: Thu, 28 May 2026 14:11:13 +0200 Subject: [PATCH 1/3] fix: resolve "window is not defined" SSR crash on direct URL access to alerts page (#6540) --- .../ui/__tests__/alerts-fingerprint.test.tsx | 1 + keep-ui/app/(keep)/alerts/[id]/ui/alerts.tsx | 15 ++++----- .../(keep)/alerts/fingerprint/[fp]/page.tsx | 18 ++++++++++ .../[fp]/ui/alert-fingerprint-page.tsx | 33 +++++++++++++++++++ keep-ui/entities/alerts/model/useAlerts.ts | 12 +++++++ .../shared/ui/MonacoCELEditor/MonacoCel.tsx | 15 +++++---- .../ui/MonacoCELEditor/monaco-cel-editor.tsx | 2 +- 7 files changed, 80 insertions(+), 16 deletions(-) create mode 100644 keep-ui/app/(keep)/alerts/fingerprint/[fp]/page.tsx create mode 100644 keep-ui/app/(keep)/alerts/fingerprint/[fp]/ui/alert-fingerprint-page.tsx diff --git a/keep-ui/app/(keep)/alerts/[id]/ui/__tests__/alerts-fingerprint.test.tsx b/keep-ui/app/(keep)/alerts/[id]/ui/__tests__/alerts-fingerprint.test.tsx index be6b579423..5798b2fcf3 100644 --- a/keep-ui/app/(keep)/alerts/[id]/ui/__tests__/alerts-fingerprint.test.tsx +++ b/keep-ui/app/(keep)/alerts/[id]/ui/__tests__/alerts-fingerprint.test.tsx @@ -24,6 +24,7 @@ import Alerts from "../alerts"; jest.mock("next/navigation", () => ({ useRouter: jest.fn(), useSearchParams: jest.fn(), + usePathname: jest.fn().mockReturnValue("/alerts/feed"), })); // ─── Mock data hooks ───────────────────────────────────────────────────────── diff --git a/keep-ui/app/(keep)/alerts/[id]/ui/alerts.tsx b/keep-ui/app/(keep)/alerts/[id]/ui/alerts.tsx index ed70b60635..5f35949f2f 100644 --- a/keep-ui/app/(keep)/alerts/[id]/ui/alerts.tsx +++ b/keep-ui/app/(keep)/alerts/[id]/ui/alerts.tsx @@ -1,7 +1,7 @@ "use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { useRouter, useSearchParams } from "next/navigation"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { type AlertDto, type AlertsQuery } from "@/entities/alerts/model"; import { usePresets, type Preset } from "@/entities/presets/model"; import { AlertHistoryModal } from "@/features/alerts/alert-history"; @@ -61,6 +61,7 @@ export default function Alerts({ presetName, initialFacets }: AlertsProps) { [providersData.installed_providers] ); + const pathname = usePathname(); const searchParams = useSearchParams(); // hooks for the note and ticket modals const [noteModalAlert, setNoteModalAlert] = useState(); @@ -165,18 +166,16 @@ export default function Alerts({ presetName, initialFacets }: AlertsProps) { ); const resetUrlAfterModal = useCallback(() => { - const currentParams = new URLSearchParams(window.location.search); + const currentParams = new URLSearchParams(searchParams?.toString() ?? ""); Array.from(currentParams.keys()) .filter((paramKey) => paramKey !== "cel") .forEach((paramKey) => currentParams.delete(paramKey)); - let url = `${window.location.pathname}`; - - if (currentParams.toString()) { - url += `?${currentParams.toString()}`; - } + const url = currentParams.toString() + ? `${pathname}?${currentParams.toString()}` + : pathname; router.replace(url); - }, [router]); + }, [router, pathname, searchParams]); // if we don't have presets data yet, just show loading if (!selectedPreset && isPresetsLoading) { diff --git a/keep-ui/app/(keep)/alerts/fingerprint/[fp]/page.tsx b/keep-ui/app/(keep)/alerts/fingerprint/[fp]/page.tsx new file mode 100644 index 0000000000..9b3c69f4f5 --- /dev/null +++ b/keep-ui/app/(keep)/alerts/fingerprint/[fp]/page.tsx @@ -0,0 +1,18 @@ +import { AlertFingerprintPage } from "./ui/alert-fingerprint-page"; + +type PageProps = { + params: Promise<{ fp: string }>; +}; + +export default async function Page({ params }: PageProps) { + const { fp } = await params; + return ; +} + +export async function generateMetadata({ params }: PageProps) { + const { fp } = await params; + return { + title: `Keep - Alert ${fp}`, + description: "View alert details", + }; +} diff --git a/keep-ui/app/(keep)/alerts/fingerprint/[fp]/ui/alert-fingerprint-page.tsx b/keep-ui/app/(keep)/alerts/fingerprint/[fp]/ui/alert-fingerprint-page.tsx new file mode 100644 index 0000000000..77d783210a --- /dev/null +++ b/keep-ui/app/(keep)/alerts/fingerprint/[fp]/ui/alert-fingerprint-page.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useAlerts } from "@/entities/alerts/model"; +import { ViewAlertModal } from "@/features/alerts/view-raw-alert"; +import { KeepLoader } from "@/shared/ui"; +import NotFound from "@/app/(keep)/not-found"; + +interface AlertFingerprintPageProps { + fingerprint: string; +} + +export function AlertFingerprintPage({ fingerprint }: AlertFingerprintPageProps) { + const router = useRouter(); + const { useAlertByFingerprint } = useAlerts(); + const { data: alert, error, isLoading, mutate } = useAlertByFingerprint(fingerprint); + + if (isLoading) { + return ; + } + + if (error || !alert) { + return ; + } + + return ( + router.replace("/alerts/feed")} + mutate={mutate} + /> + ); +} diff --git a/keep-ui/entities/alerts/model/useAlerts.ts b/keep-ui/entities/alerts/model/useAlerts.ts index 9fa0a2dd96..e5288a0c6e 100644 --- a/keep-ui/entities/alerts/model/useAlerts.ts +++ b/keep-ui/entities/alerts/model/useAlerts.ts @@ -108,6 +108,17 @@ export const useAlerts = () => { ); }; + const useAlertByFingerprint = ( + fingerprint: string | null | undefined, + options: SWRConfiguration = { revalidateOnFocus: false } + ) => { + return useSWR( + () => (api.isReady() && fingerprint ? `/alerts/${fingerprint}` : null), + (url) => api.get(url), + options + ); + }; + const useErrorAlerts = ( options: SWRConfiguration = { revalidateOnFocus: false } ) => { @@ -220,6 +231,7 @@ export const useAlerts = () => { useAllAlerts, usePresetAlerts, useAlertAudit, + useAlertByFingerprint, useMultipleFingerprintsAlertAudit, useErrorAlerts, useLastAlerts, diff --git a/keep-ui/shared/ui/MonacoCELEditor/MonacoCel.tsx b/keep-ui/shared/ui/MonacoCELEditor/MonacoCel.tsx index e3251ce753..1f0b942e90 100644 --- a/keep-ui/shared/ui/MonacoCELEditor/MonacoCel.tsx +++ b/keep-ui/shared/ui/MonacoCELEditor/MonacoCel.tsx @@ -2,17 +2,12 @@ import { Editor, EditorProps, loader } from "@monaco-editor/react"; import { useEffect, useRef, useState } from "react"; -import * as monaco from "monaco-editor"; interface MonacoCelProps extends EditorProps { onMonacoLoaded?: (monacoInstance: typeof import("monaco-editor")) => void; onMonacoLoadFailure?: (error: Error) => void; } -// Monaco Editor - imported as an npm package instead of loading from the CDN to support air-gapped environments -// https://github.com/suren-atoyan/monaco-react?tab=readme-ov-file#use-monaco-editor-as-an-npm-package -loader.config({ monaco }); - export function MonacoCelBase(props: MonacoCelProps) { const [isLoaded, setIsLoaded] = useState(false); const onMonacoLoadedRef = useRef( @@ -25,8 +20,14 @@ export function MonacoCelBase(props: MonacoCelProps) { onMonacoLoadFailureRef.current = props.onMonacoLoadFailure; useEffect(() => { - loader - .init() + // Monaco Editor - imported as an npm package instead of loading from the + // CDN to support air-gapped environments. + // https://github.com/suren-atoyan/monaco-react?tab=readme-ov-file#use-monaco-editor-as-an-npm-package + import("monaco-editor") + .then((monaco) => { + loader.config({ monaco }); + return loader.init(); + }) .then((monacoInstance) => { onMonacoLoadedRef.current?.(monacoInstance); setIsLoaded(true); diff --git a/keep-ui/shared/ui/MonacoCELEditor/monaco-cel-editor.tsx b/keep-ui/shared/ui/MonacoCELEditor/monaco-cel-editor.tsx index fd49556139..ca67dfdc6a 100644 --- a/keep-ui/shared/ui/MonacoCELEditor/monaco-cel-editor.tsx +++ b/keep-ui/shared/ui/MonacoCELEditor/monaco-cel-editor.tsx @@ -5,7 +5,7 @@ import { useEffect, useRef, useState } from "react"; import { ErrorComponent } from "../ErrorComponent/ErrorComponent"; import { setupCustomCellanguage } from "./cel-support"; import { MonacoCelBase } from "./MonacoCel"; -import { editor, Token } from "monaco-editor"; +import type { editor, Token } from "monaco-editor"; import "./editor.scss"; import { useCelValidation } from "./validation-hook"; From b1d190655d66e2eab6b049c0b04e45388bb230e8 Mon Sep 17 00:00:00 2001 From: copperwisp Date: Thu, 28 May 2026 14:16:21 +0200 Subject: [PATCH 2/3] docs: clarify Grafana service topology support (#6499) --- docs/overview/servicetopology.mdx | 9 ++++++++- docs/providers/documentation/grafana-provider.mdx | 13 +++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/docs/overview/servicetopology.mdx b/docs/overview/servicetopology.mdx index 0beafedf6f..97fde8b6e6 100644 --- a/docs/overview/servicetopology.mdx +++ b/docs/overview/servicetopology.mdx @@ -44,6 +44,13 @@ The Service Topology feature in Keep provides a visual representation of your se } > + + } + > -5. Go to Keep – you should see an alert from Grafana! +5. Go to Keep - you should see an alert from Grafana! **Alternative Validation Methods (When Keep is Not Accessible Externally):** @@ -92,7 +92,7 @@ If Keep is not accessible externally and the webhook cannot be created, you can - Trigger the alert and check Grafana's logs for errors or confirmation that the alert was sent. 2. **Check Logs in Grafana:** - - Access Grafana’s log files or use the **Explore** feature to query logs related to the alerting mechanism. + - Access Grafana's log files or use the **Explore** feature to query logs related to the alerting mechanism. - Ensure there are no errors related to the webhook integration and that alerts are processed correctly. 3. **Verify Integration Status:** @@ -103,11 +103,16 @@ If Keep is not accessible externally and the webhook cannot be created, you can 4. **Network and Connectivity Check:** - Use network monitoring tools to ensure Grafana can reach Keep or any alternative endpoint configured for alerts. +## Service Topology + +Grafana can contribute data to Keep's Service Topology map when the provider is configured with a topology datasource UID. Keep queries Grafana service graph metrics from that datasource and uses the `client` and `server` labels to build services and dependencies. + -**Topology Map** is generated from the traces collect by Tempo. +The Topology Map is generated from traces collected by Tempo and exposed through a Prometheus-compatible datasource. + To get the Datasource UID, go to: 1. Connections > Data Sources. -2. Click the Prometheus instance which is scraping data from Tempo > Your URL is in the format `https://host/connections/datasources/edit/` +2. Click the Prometheus instance that is scraping data from Tempo. The URL is in the format `https://host/connections/datasources/edit/`. 3. Copy that DATASOURCE_UID and use it while installing the provider. From 9bdd4dcfa1ffec484377244322f16fe549aefd3f Mon Sep 17 00:00:00 2001 From: ayoubil <32102142+ayoubil@users.noreply.github.com> Date: Thu, 28 May 2026 14:22:29 +0200 Subject: [PATCH 3/3] docs: fix PagerDuty capitalization across provider documentation (#6405) Co-authored-by: ayb --- docs/overview/servicetopology.mdx | 2 +- .../providers/documentation/pagerduty-provider.mdx | 14 +++++++------- docs/providers/overview.md | 2 +- docs/providers/overview.mdx | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/overview/servicetopology.mdx b/docs/overview/servicetopology.mdx index 97fde8b6e6..666701b7b2 100644 --- a/docs/overview/servicetopology.mdx +++ b/docs/overview/servicetopology.mdx @@ -24,7 +24,7 @@ The Service Topology feature in Keep provides a visual representation of your se } > diff --git a/docs/providers/documentation/pagerduty-provider.mdx b/docs/providers/documentation/pagerduty-provider.mdx index 2296cf5191..33ea4593bf 100644 --- a/docs/providers/documentation/pagerduty-provider.mdx +++ b/docs/providers/documentation/pagerduty-provider.mdx @@ -1,12 +1,12 @@ --- -title: "Pagerduty Provider" -description: "Pagerduty Provider allows integration with PagerDuty to create, manage, and synchronize incidents and alerts within Keep." +title: "PagerDuty Provider" +description: "PagerDuty Provider allows integration with PagerDuty to create, manage, and synchronize incidents and alerts within Keep." --- import AutoGeneratedSnippet from '/snippets/providers/pagerduty-snippet-autogenerated.mdx'; ## Description -The Pagerduty Provider enables integration with PagerDuty to create, manage, and synchronize incidents and alerts within Keep. It supports both direct API key authentication and OAuth2, allowing greater flexibility for secure integration. +The PagerDuty Provider enables integration with PagerDuty to create, manage, and synchronize incidents and alerts within Keep. It supports both direct API key authentication and OAuth2, allowing greater flexibility for secure integration. @@ -85,13 +85,13 @@ An expired trial while using the free version of PagerDuty may result in the "pa ## Webhook Integration Modifications -The webhook integration adds Keep as a destination within the "Integrations" API within Pagerduty. -This grants Keep access to the following scopes within Pagerduty: +The webhook integration adds Keep as a destination within the "Integrations" API within PagerDuty. +This grants Keep access to the following scopes within PagerDuty: - `webhook_subscriptions_read` - `webhook_subscriptions_write` ## Useful Links -- Pagerduty Events API documentation: https://v2.developer.pagerduty.com/docs/send-an-event-events-api-v2 -- Pagerduty Incidents API documentation: https://v2.developer.pagerduty.com/docs/create-an-incident-incidents-api-v2 +- PagerDuty Events API documentation: https://v2.developer.pagerduty.com/docs/send-an-event-events-api-v2 +- PagerDuty Incidents API documentation: https://v2.developer.pagerduty.com/docs/create-an-incident-incidents-api-v2 diff --git a/docs/providers/overview.md b/docs/providers/overview.md index 3beb65763a..67395259ab 100644 --- a/docs/providers/overview.md +++ b/docs/providers/overview.md @@ -84,7 +84,7 @@ By leveraging Keep Providers, users are able to deeply integrate Keep with the t - [OpenSearch Serverless](/providers/documentation/opensearchserverless-provider) - [Openshift](/providers/documentation/openshift-provider) - [Opsgenie](/providers/documentation/opsgenie-provider) -- [Pagerduty](/providers/documentation/pagerduty-provider) +- [PagerDuty](/providers/documentation/pagerduty-provider) - [Pagertree](/providers/documentation/pagertree-provider) - [Parseable](/providers/documentation/parseable-provider) - [Pingdom](/providers/documentation/pingdom-provider) diff --git a/docs/providers/overview.mdx b/docs/providers/overview.mdx index f9aae0cb3e..332c53fe4f 100644 --- a/docs/providers/overview.mdx +++ b/docs/providers/overview.mdx @@ -621,7 +621,7 @@ By leveraging Keep Providers, users are able to deeply integrate Keep with the t >