diff --git a/keep-ui/app/(keep)/alerts/alert-actions.tsx b/keep-ui/app/(keep)/alerts/alert-actions.tsx index 50d07ced1..7adca461e 100644 --- a/keep-ui/app/(keep)/alerts/alert-actions.tsx +++ b/keep-ui/app/(keep)/alerts/alert-actions.tsx @@ -17,6 +17,8 @@ interface Props { clearRowSelection: () => void; setDismissModalAlert?: (alert: AlertDto[] | null) => void; mutateAlerts?: () => void; + setIsIncidentSelectorOpen: (open: boolean) => void; + isIncidentSelectorOpen: boolean; } export default function AlertActions({ @@ -25,6 +27,8 @@ export default function AlertActions({ clearRowSelection, setDismissModalAlert, mutateAlerts, + setIsIncidentSelectorOpen, + isIncidentSelectorOpen, }: Props) { const router = useRouter(); const { useAllPresets } = usePresets(); @@ -32,8 +36,7 @@ export default function AlertActions({ const { mutate: presetsMutator } = useAllPresets({ revalidateOnFocus: false, }); - const [isIncidentSelectorOpen, setIsIncidentSelectorOpen] = - useState(false); + const [isCreateIncidentWithAIOpen, setIsCreateIncidentWithAIOpen] = useState(false); diff --git a/keep-ui/app/(keep)/alerts/alert-menu.tsx b/keep-ui/app/(keep)/alerts/alert-menu.tsx index 60cea9491..02f954fef 100644 --- a/keep-ui/app/(keep)/alerts/alert-menu.tsx +++ b/keep-ui/app/(keep)/alerts/alert-menu.tsx @@ -25,13 +25,14 @@ import { useRouter } from "next/navigation"; interface Props { alert: AlertDto; - isMenuOpen: boolean; - setIsMenuOpen: (key: string) => void; + isMenuOpen?: boolean; + setIsMenuOpen?: (key: string) => void; setRunWorkflowModalAlert?: (alert: AlertDto) => void; setDismissModalAlert?: (alert: AlertDto[]) => void; setChangeStatusAlert?: (alert: AlertDto) => void; presetName: string; isInSidebar?: boolean; + setIsIncidentSelectorOpen?: (open: boolean) => void; } export default function AlertMenu({ @@ -43,6 +44,7 @@ export default function AlertMenu({ setChangeStatusAlert, presetName, isInSidebar, + setIsIncidentSelectorOpen, }: Props) { const router = useRouter(); const apiUrl = useApiUrl(); @@ -145,11 +147,11 @@ export default function AlertMenu({ const canAssign = true; // TODO: keep track of assignments for auditing const handleMenuToggle = () => { - setIsMenuOpen(alert.fingerprint); + setIsMenuOpen!(alert.fingerprint); }; const handleCloseMenu = () => { - setIsMenuOpen(""); + setIsMenuOpen!(""); }; useEffect(() => { @@ -168,8 +170,8 @@ export default function AlertMenu({ {({ active }) => ( )} - - {({ active }) => ( - - + {!isInSidebar && ( + + {({ active }) => ( + + + )} + {({ active }) => ( )} @@ -243,8 +251,8 @@ export default function AlertMenu({ callAssignEndpoint(); handleCloseMenu(); }} - className={`${ - active ? "bg-slate-200" : "text-gray-900" + className={`${active ? "bg-slate-200" : "text-gray-900"} ${ + isInSidebar ? "text-nowrap" : "" } group flex w-full items-center rounded-md px-2 py-2 text-xs`} > + )} ); diff --git a/keep-ui/app/(keep)/alerts/alert-name.tsx b/keep-ui/app/(keep)/alerts/alert-name.tsx index 8bcca4638..d65780d22 100644 --- a/keep-ui/app/(keep)/alerts/alert-name.tsx +++ b/keep-ui/app/(keep)/alerts/alert-name.tsx @@ -62,7 +62,7 @@ export default function AlertName({ } return ( -
+
{name}
diff --git a/keep-ui/app/(keep)/alerts/alert-sidebar.tsx b/keep-ui/app/(keep)/alerts/alert-sidebar.tsx index 41ae7b46c..0050ec5ab 100644 --- a/keep-ui/app/(keep)/alerts/alert-sidebar.tsx +++ b/keep-ui/app/(keep)/alerts/alert-sidebar.tsx @@ -2,28 +2,39 @@ import { Fragment } from "react"; import Image from "next/image"; import { Dialog, Transition } from "@headlessui/react"; import { AlertDto } from "./models"; -import { Button, Title, Badge } from "@tremor/react"; +import { Button, Title, Badge, Divider } from "@tremor/react"; import { IoMdClose } from "react-icons/io"; import AlertTimeline from "./alert-timeline"; import { useAlerts } from "utils/hooks/useAlerts"; import { TopologyMap } from "../topology/ui/map"; import { TopologySearchProvider } from "@/app/(keep)/topology/TopologySearchContext"; -import { - AlertSeverityBorderIcon, - AlertSeverityLabel, -} from "./alert-severity-border"; +import { AlertSeverityLabel } from "./alert-severity-border"; import { FieldHeader } from "@/shared/ui/FieldHeader"; import { QuestionMarkCircleIcon } from "@heroicons/react/20/solid"; import { Tooltip } from "@/shared/ui/Tooltip"; import { Link } from "@/components/ui"; +import { useProviders } from "@/utils/hooks/useProviders"; +import AlertMenu from "./alert-menu"; type AlertSidebarProps = { isOpen: boolean; toggle: VoidFunction; alert: AlertDto | null; + setRunWorkflowModalAlert?: (alert: AlertDto) => void; + setDismissModalAlert?: (alert: AlertDto[] | null) => void; + setChangeStatusAlert?: (alert: AlertDto) => void; + setIsIncidentSelectorOpen: (open: boolean) => void; }; -const AlertSidebar = ({ isOpen, toggle, alert }: AlertSidebarProps) => { +const AlertSidebar = ({ + isOpen, + toggle, + alert, + setRunWorkflowModalAlert, + setDismissModalAlert, + setChangeStatusAlert, + setIsIncidentSelectorOpen, +}: AlertSidebarProps) => { const { useAlertAudit } = useAlerts(); const { data: auditData, @@ -31,6 +42,11 @@ const AlertSidebar = ({ isOpen, toggle, alert }: AlertSidebarProps) => { mutate, } = useAlertAudit(alert?.fingerprint || ""); + const { data: providers } = useProviders(); + const providerName = + providers?.installed_providers.find((p) => p.id === alert?.providerId) + ?.display_name || alert?.providerId; + const handleRefresh = async () => { console.log("Refresh button clicked"); await mutate(); @@ -62,9 +78,18 @@ const AlertSidebar = ({ isOpen, toggle, alert }: AlertSidebarProps) => {
- {/*Will add soon*/} - {/**/} - {/**/} + + { {alert && (
-

- Name - {alert.name} -

-

- Service - - {alert.service} - -

+ {alert.service && ( +

+ Service + + {alert.service} + +

+ )}

Source { height={24} className="inline-block w-6 h-6" /> + {providerName}

Description diff --git a/keep-ui/app/(keep)/alerts/alert-table-tab-panel.tsx b/keep-ui/app/(keep)/alerts/alert-table-tab-panel.tsx index a6d906466..e4f5310b5 100644 --- a/keep-ui/app/(keep)/alerts/alert-table-tab-panel.tsx +++ b/keep-ui/app/(keep)/alerts/alert-table-tab-panel.tsx @@ -89,6 +89,9 @@ export default function AlertTableTabPanel({ presetId={preset.id} presetTabs={presetTabs} mutateAlerts={mutateAlerts} + setRunWorkflowModalAlert={setRunWorkflowModalAlert} + setDismissModalAlert={setDismissModalAlert} + setChangeStatusAlert={setChangeStatusAlert} /> ); } diff --git a/keep-ui/app/(keep)/alerts/alert-table.tsx b/keep-ui/app/(keep)/alerts/alert-table.tsx index cab5055af..123e617b0 100644 --- a/keep-ui/app/(keep)/alerts/alert-table.tsx +++ b/keep-ui/app/(keep)/alerts/alert-table.tsx @@ -54,6 +54,9 @@ interface Props { isMenuColDisplayed?: boolean; setDismissedModalAlert?: (alert: AlertDto[] | null) => void; mutateAlerts?: () => void; + setRunWorkflowModalAlert?: (alert: AlertDto) => void; + setDismissModalAlert?: (alert: AlertDto[] | null) => void; + setChangeStatusAlert?: (alert: AlertDto) => void; } export function AlertTable({ @@ -69,6 +72,9 @@ export function AlertTable({ isRefreshAllowed = true, setDismissedModalAlert, mutateAlerts, + setRunWorkflowModalAlert, + setDismissModalAlert, + setChangeStatusAlert, }: Props) { const a11yContainerRef = useRef(null); @@ -149,6 +155,8 @@ export function AlertTable({ const [selectedTab, setSelectedTab] = useState(0); const [selectedAlert, setSelectedAlert] = useState(null); const [isSidebarOpen, setIsSidebarOpen] = useState(false); + const [isIncidentSelectorOpen, setIsIncidentSelectorOpen] = + useState(false); const filteredAlerts = alerts.filter((alert) => { // First apply tab filter @@ -285,6 +293,8 @@ export function AlertTable({ clearRowSelection={table.resetRowSelection} setDismissModalAlert={setDismissedModalAlert} mutateAlerts={mutateAlerts} + setIsIncidentSelectorOpen={setIsIncidentSelectorOpen} + isIncidentSelectorOpen={isIncidentSelectorOpen} /> ) : ( @@ -373,6 +383,20 @@ export function AlertTable({ isOpen={isSidebarOpen} toggle={() => setIsSidebarOpen(false)} alert={selectedAlert} + setRunWorkflowModalAlert={setRunWorkflowModalAlert} + setDismissModalAlert={setDismissModalAlert} + setChangeStatusAlert={setChangeStatusAlert} + setIsIncidentSelectorOpen={() => { + if (selectedAlert) { + table + .getRowModel() + .rows.find( + (row) => row.original.fingerprint === selectedAlert.fingerprint + ) + ?.toggleSelected(); + setIsIncidentSelectorOpen(true); + } + }} />

); diff --git a/keep/api/core/demo_mode.py b/keep/api/core/demo_mode.py index c81fcdc3b..dffce22a5 100644 --- a/keep/api/core/demo_mode.py +++ b/keep/api/core/demo_mode.py @@ -304,7 +304,7 @@ def get_installed_providers(keep_api_key, keep_api_url): headers={"x-api-key": keep_api_key}, ) response.raise_for_status() - return response.json()['installed_providers'] + return response.json()["installed_providers"] def simulate_alerts( @@ -316,7 +316,7 @@ def simulate_alerts( clean_old_incidents=False, ): logger.info("Simulating alerts...") - + GENERATE_DEDUPLICATIONS = False providers = [ @@ -341,9 +341,13 @@ def simulate_alerts( existing_providers_to_their_ids = {} for existing_provider in existing_installed_providers: - if existing_provider['type'] in providers: - existing_providers_to_their_ids[existing_provider['type']] = existing_provider['id'] - logger.info(f"Existing installed existing_providers_to_their_ids: {existing_providers_to_their_ids}") + if existing_provider["type"] in providers: + existing_providers_to_their_ids[existing_provider["type"]] = ( + existing_provider["id"] + ) + logger.info( + f"Existing installed existing_providers_to_their_ids: {existing_providers_to_their_ids}" + ) if demo_correlation_rules: logger.info("Creating correlation rules...") @@ -371,8 +375,12 @@ def simulate_alerts( send_alert_url = "{}/alerts/event/{}".format(keep_api_url, provider_type) if provider_type in existing_providers_to_their_ids: - send_alert_url_params["provider_id"] = existing_providers_to_their_ids[provider_type] - logger.info(f"Provider type: {provider_type}, send_alert_url_params now are: {send_alert_url_params}") + send_alert_url_params["provider_id"] = existing_providers_to_their_ids[ + provider_type + ] + logger.info( + f"Provider type: {provider_type}, send_alert_url_params now are: {send_alert_url_params}" + ) provider = provider_classes[provider_type] alert = provider.simulate_alert() @@ -392,10 +400,16 @@ def simulate_alerts( if "provider_id" not in send_alert_url_params: send_alert_url_params["provider_id"] = f"{provider_type}-{env}" + else: + alert["environment"] = random.choice( + ["prod-01", "prod-02", "prod-03"] + ) prepared_request = PreparedRequest() prepared_request.prepare_url(send_alert_url, send_alert_url_params) - logger.info(f"Sending alert to {prepared_request.url} with url params {send_alert_url_params}") + logger.info( + f"Sending alert to {prepared_request.url} with url params {send_alert_url_params}" + ) response = requests.post( prepared_request.url, @@ -423,11 +437,13 @@ def simulate_alerts( time.sleep(sleep_interval) -def launch_demo_mode_thread(keep_api_url=None, keep_api_key=None) -> threading.Thread | None: +def launch_demo_mode_thread( + keep_api_url=None, keep_api_key=None +) -> threading.Thread | None: if not KEEP_LIVE_DEMO_MODE: logger.info("Not launching the demo mode.") return - + logger.info("Launching demo mode.") if keep_api_key is None: diff --git a/keep/api/routes/providers.py b/keep/api/routes/providers.py index 61eb1789b..c5432e06d 100644 --- a/keep/api/routes/providers.py +++ b/keep/api/routes/providers.py @@ -6,6 +6,7 @@ from typing import Callable, Optional from fastapi import APIRouter, Body, Depends, HTTPException, Request +from fastapi.encoders import jsonable_encoder from fastapi.responses import JSONResponse from sqlmodel import Session, select from starlette.datastructures import UploadFile @@ -13,6 +14,7 @@ from keep.api.core.config import config from keep.api.core.db import count_alerts, get_provider_distribution, get_session from keep.api.models.db.provider import Provider +from keep.api.models.provider import Provider as ProviderDTO from keep.api.models.provider import ProviderAlertsCountResponseDTO from keep.api.models.webhook import ProviderWebhookSettings from keep.api.utils.tenant_utils import get_or_create_api_key @@ -91,7 +93,11 @@ def get_providers( } -@router.get("/export", description="export all installed providers") +@router.get( + "/export", + description="export all installed providers", + response_model=list[ProviderDTO], +) def get_installed_providers( authenticated_entity: AuthenticatedEntity = Depends( IdentityManagerFactory.get_auth_verifier(["read:providers"]) @@ -103,18 +109,7 @@ def get_installed_providers( installed_providers = ProvidersFactory.get_installed_providers( tenant_id, providers, include_details=True ) - - is_localhost = _is_localhost() - - try: - return { - "installed_providers": installed_providers, - "is_localhost": is_localhost, - } - except Exception as e: - logger.info(f"execption in {e}") - logger.exception("Failed to get providers") - return {"installed_providers": [], "is_localhost": is_localhost} + return JSONResponse(content=jsonable_encoder(installed_providers), status_code=200) @router.get( diff --git a/keep/providers/providers_factory.py b/keep/providers/providers_factory.py index 16bffd92c..5d5ac0afb 100644 --- a/keep/providers/providers_factory.py +++ b/keep/providers/providers_factory.py @@ -434,14 +434,14 @@ def get_installed_providers( context_manager = ContextManager(tenant_id=tenant_id) secret_manager = SecretManagerFactory.get_secret_manager(context_manager) for p in installed_providers: - provider: Provider = next( + provider: Provider | None = next( filter( lambda provider: provider.type == p.type, all_providers, ), None, ) - if not provider: + if provider is None: logger.warning(f"Installed provider {p.type} does not exist anymore?") continue provider_copy = provider.copy() @@ -451,6 +451,7 @@ def get_installed_providers( provider_copy.last_pull_time = p.last_pull_time provider_copy.provisioned = p.provisioned provider_copy.pulling_enabled = p.pulling_enabled + provider_copy.installed = True try: provider_auth = {"name": p.name} if include_details: diff --git a/pyproject.toml b/pyproject.toml index 6916093f5..93e71975e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "keep" -version = "0.30.1" +version = "0.30.2" description = "Alerting. for developers, by developers." authors = ["Keep Alerting LTD"] packages = [{include = "keep"}]