diff --git a/docs/overview/servicetopology.mdx b/docs/overview/servicetopology.mdx index 0beafedf6f..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 } > @@ -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. 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 > 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";