From e84664e26d041a85f5ba9830b960c1ea9346f535 Mon Sep 17 00:00:00 2001 From: Shahar Glazner Date: Tue, 22 Oct 2024 16:57:45 +0300 Subject: [PATCH] refactor: use nginx instead of rewrite rules (#2253) --- keep-ui/app/ai/ai.tsx | 4 +- keep-ui/app/alerts/ViewAlertModal.tsx | 96 ++++++---- keep-ui/app/alerts/alert-actions.tsx | 24 +-- .../app/alerts/alert-assign-ticket-modal.tsx | 13 +- .../alerts/alert-associate-incident-modal.tsx | 4 +- .../app/alerts/alert-change-status-modal.tsx | 38 ++-- keep-ui/app/alerts/alert-dismiss-modal.tsx | 38 +++- keep-ui/app/alerts/alert-menu.tsx | 23 +-- keep-ui/app/alerts/alert-method-modal.tsx | 4 +- keep-ui/app/alerts/alert-note-modal.tsx | 5 +- keep-ui/app/alerts/alert-presets.tsx | 12 +- .../alert-push-alert-to-server-modal.tsx | 5 +- .../app/alerts/alert-run-workflow-modal.tsx | 88 +++++++++ keep-ui/app/alerts/alert-tab-modal.tsx | 53 +++--- keep-ui/app/alerts/alert-tabs.tsx | 96 ++++++---- keep-ui/app/dashboard/[id]/dashboard.tsx | 113 ++++++----- .../deduplication/DeduplicationSidebar.tsx | 4 +- .../app/deduplication/DeduplicationTable.tsx | 5 +- .../create-or-update-extraction-rule.tsx | 5 +- keep-ui/app/extraction/extractions-table.tsx | 4 +- .../incidents/[id]/incident-alert-menu.tsx | 4 +- keep-ui/app/incidents/[id]/incident-chat.tsx | 3 + keep-ui/app/incidents/[id]/incident-info.tsx | 4 + .../incidents/create-or-update-incident.tsx | 42 +++-- .../incidents/incident-candidate-actions.tsx | 59 +++--- .../incident-change-same-in-the-past.tsx | 3 + .../incident-change-status-modal.tsx | 27 +-- keep-ui/app/incidents/incidents-table.tsx | 16 +- .../incidents/predicted-incidents-table.tsx | 59 ++++-- .../create-or-update-maintenance-rule.tsx | 6 +- .../maintenance/maintenance-rules-table.tsx | 10 +- .../app/mapping/create-or-edit-mapping.tsx | 5 +- keep-ui/app/mapping/rules-table.tsx | 4 +- .../providers/oauth2/[providerType]/page.tsx | 3 +- keep-ui/app/providers/page.client.tsx | 5 +- keep-ui/app/providers/provider-form.tsx | 16 +- .../app/providers/provider-semi-automated.tsx | 46 ++--- .../CorrelationSidebarBody.tsx | 4 +- .../rules/CorrelationSidebar/DeleteRule.tsx | 4 +- .../app/settings/auth/api-key-settings.tsx | 27 ++- keep-ui/app/settings/auth/groups-sidebar.tsx | 67 +++++-- keep-ui/app/settings/auth/groups-tab.tsx | 53 ++++-- keep-ui/app/settings/auth/permissions-tab.tsx | 176 ++++++++++++------ keep-ui/app/settings/auth/roles-sidebar.tsx | 137 +++++++++++--- keep-ui/app/settings/auth/roles-tab.tsx | 137 ++++++++------ keep-ui/app/settings/auth/sso-settings.tsx | 56 ++++-- keep-ui/app/settings/auth/users-menu.tsx | 5 +- keep-ui/app/settings/auth/users-settings.tsx | 54 +++--- keep-ui/app/settings/auth/users-sidebar.tsx | 154 +++++++++------ keep-ui/app/settings/smtp-settings.tsx | 4 +- keep-ui/app/settings/webhook-settings.tsx | 16 +- keep-ui/app/topology/api/index.ts | 33 ++-- keep-ui/app/topology/model/useTopology.ts | 7 +- .../topology/model/useTopologyApplications.ts | 13 +- keep-ui/app/topology/page.tsx | 6 +- .../workflows/builder/[workflowId]/page.tsx | 1 + .../app/workflows/builder/builder-card.tsx | 10 +- keep-ui/app/workflows/builder/builder.tsx | 6 +- .../builder/workflow-execution-results.tsx | 4 +- keep-ui/app/workflows/dragndrop.tsx | 36 ++-- .../workflows/manual-run-workflow-modal.tsx | 17 +- keep-ui/app/workflows/mockworkflows.tsx | 19 +- keep-ui/app/workflows/workflow-tile.tsx | 6 +- keep-ui/app/workflows/workflows.client.tsx | 4 +- .../navbar/CustomPresetAlertLinks.tsx | 51 ++--- keep-ui/components/navbar/DashboardLinks.tsx | 4 +- keep-ui/middleware.tsx | 2 + keep-ui/pages/api/config.tsx | 14 +- keep-ui/types/internal-config.ts | 6 + keep-ui/utils/apiUrl.ts | 19 +- keep-ui/utils/helpers.ts | 11 +- keep-ui/utils/hooks/useAI.ts | 7 +- keep-ui/utils/hooks/useAlerts.ts | 4 +- keep-ui/utils/hooks/useConfig.ts | 12 ++ keep-ui/utils/hooks/useDashboards.ts | 4 +- keep-ui/utils/hooks/useDeduplicationRules.ts | 6 +- keep-ui/utils/hooks/useExtractionRules.ts | 4 +- keep-ui/utils/hooks/useGroups.ts | 4 +- keep-ui/utils/hooks/useIncidents.ts | 14 +- keep-ui/utils/hooks/useMaintenanceRules.ts | 4 +- keep-ui/utils/hooks/useMappingRules.ts | 4 +- keep-ui/utils/hooks/usePermissions.ts | 4 +- keep-ui/utils/hooks/usePresets.ts | 49 ++++- keep-ui/utils/hooks/useProviders.ts | 4 +- keep-ui/utils/hooks/usePusher.ts | 102 ++++++++-- keep-ui/utils/hooks/useRoles.ts | 4 +- keep-ui/utils/hooks/useRules.ts | 4 +- keep-ui/utils/hooks/useScopes.ts | 4 +- keep-ui/utils/hooks/useSearchAlerts.ts | 6 +- keep-ui/utils/hooks/useTags.ts | 5 +- keep-ui/utils/hooks/useUsers.ts | 4 +- keep-ui/utils/hooks/useWorkflowExecutions.ts | 8 +- keep-ui/utils/hooks/useWorkflowRun.ts | 5 +- keep-ui/utils/hooks/useWorkflows.ts | 4 +- pyproject.toml | 2 +- 95 files changed, 1536 insertions(+), 841 deletions(-) create mode 100644 keep-ui/app/alerts/alert-run-workflow-modal.tsx diff --git a/keep-ui/app/ai/ai.tsx b/keep-ui/app/ai/ai.tsx index 69f331629..7e9d23aa7 100644 --- a/keep-ui/app/ai/ai.tsx +++ b/keep-ui/app/ai/ai.tsx @@ -2,7 +2,7 @@ import { Card, List, ListItem, Title, Subtitle } from "@tremor/react"; import { useAIStats, usePollAILogs } from "utils/hooks/useAI"; import { useSession } from "next-auth/react"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { toast } from "react-toastify"; import { useEffect, useState, useRef, FormEvent } from "react"; import { AILogs } from "./model"; @@ -15,6 +15,7 @@ export default function Ai() { const [newText, setNewText] = useState("Mine incidents"); const [animate, setAnimate] = useState(false); const onlyOnce = useRef(false); + const apiUrl = useApiUrl(); const mutateAILogs = (logs: AILogs) => { setBasicAlgorithmLog(logs.log); @@ -42,7 +43,6 @@ export default function Ai() { e.preventDefault(); setAnimate(true); setNewText("Mining 🚀🚀🚀 ..."); - const apiUrl = getApiURL(); const response = await fetch(`${apiUrl}/incidents/mine`, { method: "POST", headers: { diff --git a/keep-ui/app/alerts/ViewAlertModal.tsx b/keep-ui/app/alerts/ViewAlertModal.tsx index 9284d9474..6bbeca28f 100644 --- a/keep-ui/app/alerts/ViewAlertModal.tsx +++ b/keep-ui/app/alerts/ViewAlertModal.tsx @@ -1,12 +1,12 @@ import { AlertDto } from "./models"; // Adjust the import path as needed import Modal from "@/components/ui/Modal"; // Ensure this path matches your project structure -import {Button, Icon, Switch, Text} from "@tremor/react"; +import { Button, Icon, Switch, Text } from "@tremor/react"; import { toast } from "react-toastify"; -import { getApiURL } from "../../utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { useSession } from "next-auth/react"; import { XMarkIcon } from "@heroicons/react/24/outline"; import "./ViewAlertModal.css"; -import React, {useState} from "react"; +import React, { useState } from "react"; interface ViewAlertModalProps { alert: AlertDto | null | undefined; @@ -14,14 +14,19 @@ interface ViewAlertModalProps { mutate: () => void; } -const objectToJSONLine = (obj: any) => { +const objectToJSONLine = (obj: any) => { return JSON.stringify(obj, null, 2).slice(2, -2); -} +}; -export const ViewAlertModal: React.FC = ({ alert, handleClose, mutate}) => { +export const ViewAlertModal: React.FC = ({ + alert, + handleClose, + mutate, +}) => { const isOpen = !!alert; const [showHighlightedOnly, setShowHighlightedOnly] = useState(false); - const {data: session} = useSession(); + const { data: session } = useSession(); + const apiUrl = useApiUrl(); const unEnrichAlert = async (key: string) => { if (confirm(`Are you sure you want to un-enrich ${key}?`)) { @@ -30,7 +35,7 @@ export const ViewAlertModal: React.FC = ({ alert, handleClo enrichments: [key], fingerprint: alert!.fingerprint, }; - const response = await fetch(`${getApiURL()}/alerts/unenrich`, { + const response = await fetch(`${apiUrl}/alerts/unenrich`, { method: "POST", headers: { "Content-Type": "application/json", @@ -52,35 +57,46 @@ export const ViewAlertModal: React.FC = ({ alert, handleClo toast.error("An unexpected error occurred"); } } - } + }; const highlightKeys = (json: any, keys: string[]) => { - const lines = Object.keys(json).length; - const isLast = (index: number) => index == lines - 1 + const isLast = (index: number) => index == lines - 1; return Object.keys(json).map((key: string, index: number) => { if (keys.includes(key)) { - return

unEnrichAlert(key)}> - - - - {objectToJSONLine({[key]: json[key]})}{isLast(index) ? null : ","} -

+ return ( +

unEnrichAlert(key)} + > + + + + {objectToJSONLine({ [key]: json[key] })} + {isLast(index) ? null : ","} +

+ ); } else { if (!showHighlightedOnly || keys.length == 0) { - return

{objectToJSONLine({[key]: json[key]})}{isLast(index) ? null : ","}

+ return ( +

+ {objectToJSONLine({ [key]: json[key] })} + {isLast(index) ? null : ","} +

+ ); } } - }) - } + }); + }; const handleCopy = async () => { if (alert) { @@ -94,23 +110,31 @@ export const ViewAlertModal: React.FC = ({ alert, handleClo }; return ( - +

Alert Details

-
{/* Adjust gap as needed */} +
+ {" "} + {/* Adjust gap as needed */}
setShowHighlightedOnly(!showHighlightedOnly)} - /> -
- diff --git a/keep-ui/app/alerts/alert-actions.tsx b/keep-ui/app/alerts/alert-actions.tsx index 87a7be34c..5a4366944 100644 --- a/keep-ui/app/alerts/alert-actions.tsx +++ b/keep-ui/app/alerts/alert-actions.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { Button } from "@tremor/react"; import { getSession } from "next-auth/react"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { AlertDto } from "./models"; import { PlusIcon } from "@radix-ui/react-icons"; import { toast } from "react-toastify"; @@ -23,14 +23,16 @@ export default function AlertActions({ alerts, clearRowSelection, setDismissModalAlert, - mutateAlerts + mutateAlerts, }: Props) { const router = useRouter(); const { useAllPresets } = usePresets(); + const apiUrl = useApiUrl(); const { mutate: presetsMutator } = useAllPresets({ revalidateOnFocus: false, }); - const [isIncidentSelectorOpen, setIsIncidentSelectorOpen] = useState(false); + const [isIncidentSelectorOpen, setIsIncidentSelectorOpen] = + useState(false); const selectedAlerts = alerts.filter((_alert, index) => selectedRowIds.includes(index.toString()) @@ -54,7 +56,6 @@ export default function AlertActions({ ); const options = [{ value: formattedCel, label: "CEL" }]; const session = await getSession(); - const apiUrl = getApiURL(); const response = await fetch(`${apiUrl}/preset`, { method: "POST", headers: { @@ -82,10 +83,10 @@ export default function AlertActions({ const showIncidentSelector = () => { setIsIncidentSelectorOpen(true); - } + }; const hideIncidentSelector = () => { setIsIncidentSelectorOpen(false); - } + }; const handleSuccessfulAlertsAssociation = () => { hideIncidentSelector(); @@ -93,7 +94,7 @@ export default function AlertActions({ if (mutateAlerts) { mutateAlerts(); } - } + }; return (
@@ -130,10 +131,11 @@ export default function AlertActions({ Associate with incident + isOpen={isIncidentSelectorOpen} + alerts={selectedAlerts} + handleSuccess={handleSuccessfulAlertsAssociation} + handleClose={hideIncidentSelector} + />
); } diff --git a/keep-ui/app/alerts/alert-assign-ticket-modal.tsx b/keep-ui/app/alerts/alert-assign-ticket-modal.tsx index 4b6c99cfc..f3920c764 100644 --- a/keep-ui/app/alerts/alert-assign-ticket-modal.tsx +++ b/keep-ui/app/alerts/alert-assign-ticket-modal.tsx @@ -5,7 +5,7 @@ import { PlusIcon } from "@heroicons/react/20/solid"; import { useForm, Controller, SubmitHandler } from "react-hook-form"; import { Providers } from "./../providers/providers"; import { useSession } from "next-auth/react"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { AlertDto } from "./models"; import Modal from "@/components/ui/Modal"; @@ -45,6 +45,7 @@ const AlertAssignTicketModal = ({ } = useForm(); // get the token const { data: session } = useSession(); + const apiUrl = useApiUrl(); // if this modal should not be open, do nothing if (!alert) return null; @@ -61,7 +62,7 @@ const AlertAssignTicketModal = ({ fingerprint: alert.fingerprint, }; - const response = await fetch(`${getApiURL()}/alerts/enrich`, { + const response = await fetch(`${apiUrl}/alerts/enrich`, { method: "POST", headers: { "Content-Type": "application/json", @@ -225,18 +226,14 @@ const AlertAssignTicketModal = ({
diff --git a/keep-ui/app/alerts/alert-associate-incident-modal.tsx b/keep-ui/app/alerts/alert-associate-incident-modal.tsx index d0124e824..622e8c893 100644 --- a/keep-ui/app/alerts/alert-associate-incident-modal.tsx +++ b/keep-ui/app/alerts/alert-associate-incident-modal.tsx @@ -6,7 +6,7 @@ import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; import { FormEvent, useCallback, useEffect, useState } from "react"; import { toast } from "react-toastify"; -import { getApiURL } from "../../utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { useIncidents, usePollIncidents } from "../../utils/hooks/useIncidents"; import Loading from "../loading"; import { AlertDto } from "./models"; @@ -34,10 +34,10 @@ const AlertAssociateIncidentModal = ({ >(); // get the token const { data: session } = useSession(); + const apiUrl = useApiUrl(); const router = useRouter(); const associateAlertsHandler = async (incidentId: string) => { - const apiUrl = getApiURL(); const response = await fetch(`${apiUrl}/incidents/${incidentId}/alerts`, { method: "POST", headers: { diff --git a/keep-ui/app/alerts/alert-change-status-modal.tsx b/keep-ui/app/alerts/alert-change-status-modal.tsx index 6dcb5bcf6..d090599dc 100644 --- a/keep-ui/app/alerts/alert-change-status-modal.tsx +++ b/keep-ui/app/alerts/alert-change-status-modal.tsx @@ -8,7 +8,7 @@ import Select, { } from "react-select"; import { useState } from "react"; import { AlertDto, Status } from "./models"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { useSession } from "next-auth/react"; import { toast } from "react-toastify"; import { @@ -79,6 +79,7 @@ export default function AlertChangeStatusModal({ const { useAllPresets } = usePresets(); const { mutate: presetsMutator } = useAllPresets(); const { useAllAlerts } = useAlerts(); + const apiUrl = useApiUrl(); const { mutate: alertsMutator } = useAllAlerts(presetName, { revalidateOnMount: false, }); @@ -109,23 +110,26 @@ export default function AlertChangeStatusModal({ } try { - const response = await fetch(`${getApiURL()}/alerts/enrich?dispose_on_new_alert=true`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${session?.accessToken}`, - }, - body: JSON.stringify({ - enrichments: { - status: selectedStatus, - ...(selectedStatus !== Status.Suppressed && { - dismissed: false, - dismissUntil: "", - }), + const response = await fetch( + `${apiUrl}/alerts/enrich?dispose_on_new_alert=true`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${session?.accessToken}`, }, - fingerprint: alert.fingerprint, - }), - }); + body: JSON.stringify({ + enrichments: { + status: selectedStatus, + ...(selectedStatus !== Status.Suppressed && { + dismissed: false, + dismissUntil: "", + }), + }, + fingerprint: alert.fingerprint, + }), + } + ); if (response.ok) { toast.success("Alert status changed successfully!"); diff --git a/keep-ui/app/alerts/alert-dismiss-modal.tsx b/keep-ui/app/alerts/alert-dismiss-modal.tsx index 33b4b9968..11bf3d63f 100644 --- a/keep-ui/app/alerts/alert-dismiss-modal.tsx +++ b/keep-ui/app/alerts/alert-dismiss-modal.tsx @@ -1,12 +1,22 @@ import { useState, useEffect } from "react"; -import { Button, Title, Subtitle, Card, Tab, TabGroup, TabList, TabPanel, TabPanels } from "@tremor/react"; +import { + Button, + Title, + Subtitle, + Card, + Tab, + TabGroup, + TabList, + TabPanel, + TabPanels, +} from "@tremor/react"; import Modal from "@/components/ui/Modal"; import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; import "react-quill/dist/quill.snow.css"; import { AlertDto } from "./models"; import { format, set, isSameDay, isAfter, addMinutes } from "date-fns"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { useSession } from "next-auth/react"; import { usePresets } from "utils/hooks/usePresets"; import { useAlerts } from "utils/hooks/useAlerts"; @@ -34,15 +44,21 @@ export default function AlertDismissModal({ const { useAllPresets } = usePresets(); const { mutate: presetsMutator } = useAllPresets(); const { usePresetAlerts } = useAlerts(); - const { mutate: alertsMutator } = usePresetAlerts(presetName, { revalidateOnMount: false }); + const { mutate: alertsMutator } = usePresetAlerts(presetName, { + revalidateOnMount: false, + }); const { data: session } = useSession(); - + const apiUrl = useApiUrl(); // Ensuring that the useEffect hook is called consistently useEffect(() => { const now = new Date(); const roundedMinutes = Math.ceil(now.getMinutes() / 15) * 15; - const defaultTime = set(now, { minutes: roundedMinutes, seconds: 0, milliseconds: 0 }); + const defaultTime = set(now, { + minutes: roundedMinutes, + seconds: 0, + milliseconds: 0, + }); setSelectedDateTime(defaultTime); }, []); @@ -69,7 +85,8 @@ export default function AlertDismissModal({ return; } - const dismissUntil = selectedTab === 0 ? null : selectedDateTime?.toISOString(); + const dismissUntil = + selectedTab === 0 ? null : selectedDateTime?.toISOString(); const requests = alerts.map((alert: AlertDto) => { const requestData = { enrichments: { @@ -80,7 +97,7 @@ export default function AlertDismissModal({ }, fingerprint: alert.fingerprint, }; - return fetch(`${getApiURL()}/alerts/enrich`, { + return fetch(`${apiUrl}/alerts/enrich`, { method: "POST", headers: { "Content-Type": "application/json", @@ -181,9 +198,12 @@ export default function AlertDismissModal({ filterTime={filterPassedTime} inline calendarClassName="custom-datepicker" - /> - {showError &&
Must choose a date
} + {showError && ( +
+ Must choose a date +
+ )}
Dismiss Comment diff --git a/keep-ui/app/alerts/alert-menu.tsx b/keep-ui/app/alerts/alert-menu.tsx index 0c106ebe6..997be6959 100644 --- a/keep-ui/app/alerts/alert-menu.tsx +++ b/keep-ui/app/alerts/alert-menu.tsx @@ -13,7 +13,7 @@ import { import { IoNotificationsOffOutline } from "react-icons/io5"; import { useSession } from "next-auth/react"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import Link from "next/link"; import { ProviderMethod } from "app/providers/providers"; import { AlertDto } from "./models"; @@ -44,8 +44,7 @@ export default function AlertMenu({ isInSidebar, }: Props) { const router = useRouter(); - - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: { installed_providers: installedProviders } = { installed_providers: [], @@ -256,9 +255,7 @@ export default function AlertMenu({ {({ active }) => ( )} @@ -303,7 +303,10 @@ export default function AlertMenu({ active ? "bg-slate-200" : "text-gray-900" } group flex w-full items-center rounded-md px-2 py-2 text-xs`} > -
{layout.length === 0 ? ( diff --git a/keep-ui/app/deduplication/DeduplicationSidebar.tsx b/keep-ui/app/deduplication/DeduplicationSidebar.tsx index ddc2e6a8f..c09d86e00 100644 --- a/keep-ui/app/deduplication/DeduplicationSidebar.tsx +++ b/keep-ui/app/deduplication/DeduplicationSidebar.tsx @@ -23,7 +23,7 @@ import { ExclamationTriangleIcon, InformationCircleIcon, } from "@heroicons/react/24/outline"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { useSession } from "next-auth/react"; import { KeyedMutator } from "swr"; @@ -75,6 +75,7 @@ const DeduplicationSidebar: React.FC = ({ } = useProviders(); const { data: deduplicationFields = {} } = useDeduplicationFields(); const { data: session } = useSession(); + const apiUrl = useApiUrl(); const alertProviders = useMemo( () => @@ -150,7 +151,6 @@ const DeduplicationSidebar: React.FC = ({ setIsSubmitting(true); clearErrors(); try { - const apiUrl = getApiURL(); let url = `${apiUrl}/deduplications`; if (selectedDeduplicationRule && selectedDeduplicationRule.id) { diff --git a/keep-ui/app/deduplication/DeduplicationTable.tsx b/keep-ui/app/deduplication/DeduplicationTable.tsx index 5bef79f40..05d634451 100644 --- a/keep-ui/app/deduplication/DeduplicationTable.tsx +++ b/keep-ui/app/deduplication/DeduplicationTable.tsx @@ -24,7 +24,7 @@ import { DeduplicationRule } from "app/deduplication/models"; import DeduplicationSidebar from "app/deduplication/DeduplicationSidebar"; import { TrashIcon, PauseIcon, PlusIcon } from "@heroicons/react/24/outline"; import Image from "next/image"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { useSession } from "next-auth/react"; const columnHelper = createColumnHelper(); @@ -43,6 +43,7 @@ export const DeduplicationTable: React.FC = ({ const router = useRouter(); const { data: session } = useSession(); const searchParams = useSearchParams(); + const apiUrl = useApiUrl(); let selectedId = searchParams ? searchParams.get("id") : null; const [isSidebarOpen, setIsSidebarOpen] = useState(false); @@ -72,7 +73,7 @@ export const DeduplicationTable: React.FC = ({ window.confirm("Are you sure you want to delete this deduplication rule?") ) { try { - const url = `${getApiURL()}/deduplications/${rule.id}`; + const url = `${apiUrl}/deduplications/${rule.id}`; const response = await fetch(url, { method: "DELETE", headers: { diff --git a/keep-ui/app/extraction/create-or-update-extraction-rule.tsx b/keep-ui/app/extraction/create-or-update-extraction-rule.tsx index 43b959322..a770f68c9 100644 --- a/keep-ui/app/extraction/create-or-update-extraction-rule.tsx +++ b/keep-ui/app/extraction/create-or-update-extraction-rule.tsx @@ -16,7 +16,7 @@ import { import { useSession } from "next-auth/react"; import { FormEvent, useEffect, useState } from "react"; import { toast } from "react-toastify"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { ExtractionRule } from "./model"; import { extractNamedGroups } from "./extractions-table"; import { useExtractions } from "utils/hooks/useExtractionRules"; @@ -41,6 +41,7 @@ export default function CreateOrUpdateExtractionRule({ const [regex, setRegex] = useState(""); const [extractedAttributes, setExtractedAttributes] = useState([]); const [priority, setPriority] = useState(0); + const apiUrl = useApiUrl(); const editMode = extractionToEdit !== null; useEffect(() => { @@ -75,7 +76,6 @@ export default function CreateOrUpdateExtractionRule({ const addExtraction = async (e: FormEvent) => { e.preventDefault(); - const apiUrl = getApiURL(); const response = await fetch(`${apiUrl}/extraction`, { method: "POST", headers: { @@ -106,7 +106,6 @@ export default function CreateOrUpdateExtractionRule({ // This is the function that will be called on submitting the form in the editMode, it sends a PUT request to the backennd. const updateExtraction = async (e: FormEvent) => { e.preventDefault(); - const apiUrl = getApiURL(); const response = await fetch( `${apiUrl}/extraction/${extractionToEdit?.id}`, { diff --git a/keep-ui/app/extraction/extractions-table.tsx b/keep-ui/app/extraction/extractions-table.tsx index a7183bcde..48fcb7f51 100644 --- a/keep-ui/app/extraction/extractions-table.tsx +++ b/keep-ui/app/extraction/extractions-table.tsx @@ -19,7 +19,7 @@ import { } from "@tanstack/react-table"; import { MdRemoveCircle, MdModeEdit } from "react-icons/md"; import { useSession } from "next-auth/react"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { useMappings } from "utils/hooks/useMappingRules"; import { toast } from "react-toastify"; import { ExtractionRule } from "./model"; @@ -53,6 +53,7 @@ export default function RulesTable({ }: Props) { const { data: session } = useSession(); const { mutate } = useMappings(); + const apiUrl = useApiUrl(); const [expanded, setExpanded] = useState({}); const columns = [ @@ -181,7 +182,6 @@ export default function RulesTable({ }); const deleteExtraction = (extractionId: number) => { - const apiUrl = getApiURL(); if (confirm("Are you sure you want to delete this rule?")) { fetch(`${apiUrl}/extraction/${extractionId}`, { method: "DELETE", diff --git a/keep-ui/app/incidents/[id]/incident-alert-menu.tsx b/keep-ui/app/incidents/[id]/incident-alert-menu.tsx index aaa43ab28..c5e32bed1 100644 --- a/keep-ui/app/incidents/[id]/incident-alert-menu.tsx +++ b/keep-ui/app/incidents/[id]/incident-alert-menu.tsx @@ -3,7 +3,7 @@ import { Icon } from "@tremor/react"; import { AlertDto } from "app/alerts/models"; import { useSession } from "next-auth/react"; import { toast } from "react-toastify"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { useIncidentAlerts } from "utils/hooks/useIncidents"; interface Props { @@ -11,7 +11,7 @@ interface Props { alert: AlertDto; } export default function IncidentAlertMenu({ incidentId, alert }: Props) { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); const { mutate } = useIncidentAlerts(incidentId); diff --git a/keep-ui/app/incidents/[id]/incident-chat.tsx b/keep-ui/app/incidents/[id]/incident-chat.tsx index 338a0a9d6..31634f2f8 100644 --- a/keep-ui/app/incidents/[id]/incident-chat.tsx +++ b/keep-ui/app/incidents/[id]/incident-chat.tsx @@ -14,6 +14,7 @@ import { useRouter } from "next/navigation"; import Loading from "app/loading"; import { useCopilotAction, useCopilotReadable } from "@copilotkit/react-core"; import { updateIncidentRequest } from "../create-or-update-incident"; +import { useApiUrl } from "utils/hooks/useConfig"; import { useSession } from "next-auth/react"; import { toast } from "react-toastify"; import "@copilotkit/react-ui/styles.css"; @@ -21,6 +22,7 @@ import "./incident-chat.css"; export default function IncidentChat({ incident }: { incident: IncidentDto }) { const router = useRouter(); + const apiUrl = useApiUrl(); const { mutate } = useIncidents(true, 20); const { mutate: mutateIncident } = useIncident(incident.id); const { data: alerts, isLoading: alertsLoading } = useIncidentAlerts( @@ -84,6 +86,7 @@ export default function IncidentChat({ incident }: { incident: IncidentDto }) { incidentAssignee: incident.assignee, incidentSameIncidentInThePastId: incident.same_incident_in_the_past_id, generatedByAi: true, + apiUrl: apiUrl!, }); if (response.ok) { diff --git a/keep-ui/app/incidents/[id]/incident-info.tsx b/keep-ui/app/incidents/[id]/incident-info.tsx index 87c9c35fe..cc484e06a 100644 --- a/keep-ui/app/incidents/[id]/incident-info.tsx +++ b/keep-ui/app/incidents/[id]/incident-info.tsx @@ -15,6 +15,7 @@ import { } from "../incident-candidate-actions"; import { useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; +import { useApiUrl } from "utils/hooks/useConfig"; import { format } from "date-fns"; import { ArrowUturnLeftIcon } from "@heroicons/react/24/outline"; import { Disclosure } from "@headlessui/react"; @@ -95,6 +96,7 @@ export default function IncidentInformation({ incident }: Props) { const { data: session } = useSession(); const { mutate } = useIncident(incident.id); const [isFormOpen, setIsFormOpen] = useState(false); + const apiUrl = useApiUrl(); const [runWorkflowModalIncident, setRunWorkflowModalIncident] = useState(); @@ -198,6 +200,7 @@ export default function IncidentInformation({ incident }: Props) { e.preventDefault(); e.stopPropagation(); handleConfirmPredictedIncident({ + apiUrl: apiUrl!, incidentId: incident.id!, mutate, session, @@ -216,6 +219,7 @@ export default function IncidentInformation({ incident }: Props) { e.preventDefault(); e.stopPropagation(); const success = await deleteIncident({ + apiUrl: apiUrl!, incidentId: incident.id!, mutate, session, diff --git a/keep-ui/app/incidents/create-or-update-incident.tsx b/keep-ui/app/incidents/create-or-update-incident.tsx index 2ae75422b..dfaf52940 100644 --- a/keep-ui/app/incidents/create-or-update-incident.tsx +++ b/keep-ui/app/incidents/create-or-update-incident.tsx @@ -13,7 +13,7 @@ import { import { useSession } from "next-auth/react"; import { FormEvent, useEffect, useState } from "react"; import { toast } from "react-toastify"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { IncidentDto } from "./models"; import { useIncidents } from "utils/hooks/useIncidents"; import { Session } from "next-auth"; @@ -36,6 +36,7 @@ export const updateIncidentRequest = async ({ incidentAssignee, incidentSameIncidentInThePastId, generatedByAi, + apiUrl, }: { session: Session | null; incidentId: string; @@ -44,21 +45,24 @@ export const updateIncidentRequest = async ({ incidentAssignee: string; incidentSameIncidentInThePastId: string | null; generatedByAi: boolean; + apiUrl: string; }) => { - const apiUrl = getApiURL(); - const response = await fetch(`${apiUrl}/incidents/${incidentId}?generatedByAi=${generatedByAi}`, { - method: "PUT", - headers: { - Authorization: `Bearer ${session?.accessToken}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - user_generated_name: incidentName, - user_summary: incidentUserSummary, - assignee: incidentAssignee, - same_incident_in_the_past_id: incidentSameIncidentInThePastId, - }), - }); + const response = await fetch( + `${apiUrl}/incidents/${incidentId}?generatedByAi=${generatedByAi}`, + { + method: "PUT", + headers: { + Authorization: `Bearer ${session?.accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + user_generated_name: incidentName, + user_summary: incidentUserSummary, + assignee: incidentAssignee, + same_incident_in_the_past_id: incidentSameIncidentInThePastId, + }), + } + ); return response; }; @@ -73,6 +77,7 @@ export default function CreateOrUpdateIncident({ const [incidentUserSummary, setIncidentUserSummary] = useState(""); const [incidentAssignee, setIncidentAssignee] = useState(""); const { data: users = [] } = useUsers(); + const apiUrl = useApiUrl(); const editMode = incidentToEdit !== null; // Display cancel btn if editing or we need to cancel for another reason (eg. going one step back in the modal etc.) @@ -100,7 +105,6 @@ export default function CreateOrUpdateIncident({ const addIncident = async (e: FormEvent) => { e.preventDefault(); - const apiUrl = getApiURL(); const response = await fetch(`${apiUrl}/incidents`, { method: "POST", headers: { @@ -136,9 +140,11 @@ export default function CreateOrUpdateIncident({ incidentName: incidentName, incidentUserSummary: incidentUserSummary, incidentAssignee: incidentAssignee, - incidentSameIncidentInThePastId: incidentToEdit?.same_incident_in_the_past_id!, + incidentSameIncidentInThePastId: + incidentToEdit?.same_incident_in_the_past_id!, generatedByAi: false, - }) + apiUrl: apiUrl!, + }); if (response.ok) { exitEditMode(); await mutate(); diff --git a/keep-ui/app/incidents/incident-candidate-actions.tsx b/keep-ui/app/incidents/incident-candidate-actions.tsx index 1a8a33e8f..89dbb53a1 100644 --- a/keep-ui/app/incidents/incident-candidate-actions.tsx +++ b/keep-ui/app/incidents/incident-candidate-actions.tsx @@ -1,26 +1,27 @@ -import {getApiURL} from "../../utils/apiUrl"; -import {toast} from "react-toastify"; -import {IncidentDto, PaginatedIncidentsDto} from "./models"; -import {Session} from "next-auth"; +import { toast } from "react-toastify"; +import { IncidentDto, PaginatedIncidentsDto } from "./models"; +import { Session } from "next-auth"; interface Props { incidentId: string; mutate: () => void; session: Session | null; + apiUrl: string; } -export const handleConfirmPredictedIncident = async ({incidentId, mutate, session}: Props) => { - const apiUrl = getApiURL(); - const response = await fetch( - `${apiUrl}/incidents/${incidentId}/confirm`, - { - method: "POST", - headers: { - Authorization: `Bearer ${session?.accessToken}`, - "Content-Type": "application/json", - }, - } - ); +export const handleConfirmPredictedIncident = async ({ + incidentId, + mutate, + session, + apiUrl, +}: Props) => { + const response = await fetch(`${apiUrl}/incidents/${incidentId}/confirm`, { + method: "POST", + headers: { + Authorization: `Bearer ${session?.accessToken}`, + "Content-Type": "application/json", + }, + }); if (response.ok) { await mutate(); toast.success("Predicted incident confirmed successfully"); @@ -29,25 +30,29 @@ export const handleConfirmPredictedIncident = async ({incidentId, mutate, sessio "Failed to confirm predicted incident, please contact us if this issue persists." ); } -} +}; -export const deleteIncident = async ({incidentId, mutate, session}: Props) => { - const apiUrl = getApiURL(); +export const deleteIncident = async ({ + incidentId, + mutate, + session, + apiUrl, +}: Props) => { if (confirm("Are you sure you want to delete this incident?")) { const response = await fetch(`${apiUrl}/incidents/${incidentId}`, { method: "DELETE", headers: { Authorization: `Bearer ${session?.accessToken}`, }, - }) + }); if (response.ok) { - await mutate(); - toast.success("Incident deleted successfully"); - return true - } else { - toast.error("Failed to delete incident, contact us if this persists"); - return false - } + await mutate(); + toast.success("Incident deleted successfully"); + return true; + } else { + toast.error("Failed to delete incident, contact us if this persists"); + return false; + } } }; diff --git a/keep-ui/app/incidents/incident-change-same-in-the-past.tsx b/keep-ui/app/incidents/incident-change-same-in-the-past.tsx index bddd4fee1..eb44026bf 100644 --- a/keep-ui/app/incidents/incident-change-same-in-the-past.tsx +++ b/keep-ui/app/incidents/incident-change-same-in-the-past.tsx @@ -8,6 +8,7 @@ import { useIncidents, usePollIncidents } from "../../utils/hooks/useIncidents"; import Loading from "../loading"; import { updateIncidentRequest } from "./create-or-update-incident"; import { IncidentDto } from "./models"; +import { useApiUrl } from "../../utils/hooks/useConfig"; interface ChangeSameIncidentInThePast { incident: IncidentDto; @@ -28,6 +29,7 @@ const ChangeSameIncidentInThePast = ({ >(); const { data: session } = useSession(); const router = useRouter(); + const apiUrl = useApiUrl(); const associateIncidentHandler = async ( selectedIncidentId: string | null @@ -40,6 +42,7 @@ const ChangeSameIncidentInThePast = ({ incidentUserSummary: incident.user_summary, incidentAssignee: incident.assignee, generatedByAi: false, + apiUrl: apiUrl!, }); if (response.ok) { mutate(); diff --git a/keep-ui/app/incidents/incident-change-status-modal.tsx b/keep-ui/app/incidents/incident-change-status-modal.tsx index 886d205b8..b8c205c05 100644 --- a/keep-ui/app/incidents/incident-change-status-modal.tsx +++ b/keep-ui/app/incidents/incident-change-status-modal.tsx @@ -8,7 +8,7 @@ import Select, { } from "react-select"; import { useState } from "react"; import { IncidentDto, Status } from "./models"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { useSession } from "next-auth/react"; import { toast } from "react-toastify"; import { @@ -71,6 +71,7 @@ export default function IncidentChangeStatusModal({ const { data: session } = useSession(); const [selectedStatus, setSelectedStatus] = useState(null); const [comment, setComment] = useState(""); + const apiUrl = useApiUrl(); if (!incident) return null; @@ -98,17 +99,20 @@ export default function IncidentChangeStatusModal({ } try { - const response = await fetch(`${getApiURL()}/incidents/${incident.id}/status`, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${session?.accessToken}`, - }, - body: JSON.stringify({ + const response = await fetch( + `${apiUrl}/incidents/${incident.id}/status`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${session?.accessToken}`, + }, + body: JSON.stringify({ status: selectedStatus, comment: comment, - }), - }); + }), + } + ); if (response.ok) { toast.success("Incident status changed successfully!"); @@ -126,7 +130,8 @@ export default function IncidentChangeStatusModal({ Change Incident Status - Change status from {incident.status} to: + Change status from {incident.status}{" "} + to:
field.onChange(selectedOption?.name)} // Assuming you want to store the role ID - value={roles.find(role => role.name === field.value)} // Ensure the value is a Role object + onChange={(selectedOption) => + field.onChange(selectedOption?.name) + } // Assuming you want to store the role ID + value={roles.find((role) => role.name === field.value)} // Ensure the value is a Role object options={roles} // Pass the full Role objects getOptionLabel={(role) => role.name} // Use the name for display getOptionValue={(role) => role.name} // Use the name as the value @@ -298,7 +338,11 @@ const UsersSidebar = ({ isOpen, toggle, user, isNewUser, mutateUsers, groupsEnab
{/* Display API Error */} {errors.root?.serverError && ( - + {errors.root.serverError.message} )} @@ -319,7 +363,11 @@ const UsersSidebar = ({ isOpen, toggle, user, isNewUser, mutateUsers, groupsEnab type="submit" disabled={isSubmitting || (isNewUser ? false : !isDirty)} > - {isSubmitting ? "Saving..." : isNewUser ? "Create User" : "Save"} + {isSubmitting + ? "Saving..." + : isNewUser + ? "Create User" + : "Save"} diff --git a/keep-ui/app/settings/smtp-settings.tsx b/keep-ui/app/settings/smtp-settings.tsx index 06f17a4e8..b58f9c0e0 100644 --- a/keep-ui/app/settings/smtp-settings.tsx +++ b/keep-ui/app/settings/smtp-settings.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { Card, Button, Title, Subtitle, TextInput } from "@tremor/react"; import useSWR from "swr"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { fetcher } from "utils/fetcher"; import Loading from "app/loading"; @@ -56,6 +56,7 @@ export default function SMTPSettingsForm({ accessToken, selectedTab }: Props) { const [shouldFetch, setShouldFetch] = useState(true); const [smtpInstalled, setSmtpInstalled] = useState(false); const [deleteSuccessful, setDeleteSuccessful] = useState(false); + const apiUrl = useApiUrl(); const validateSaveFields = () => { const newErrors: SMTPSettingsErrors = {}; @@ -76,7 +77,6 @@ export default function SMTPSettingsForm({ accessToken, selectedTab }: Props) { return validSave && settings.to_email; }; - const apiUrl = getApiURL(); const shouldFetchUrl = shouldFetch && selectedTab === "smtp" ? `${apiUrl}/settings/smtp` : null; diff --git a/keep-ui/app/settings/webhook-settings.tsx b/keep-ui/app/settings/webhook-settings.tsx index a8847fa53..8b3c5708c 100644 --- a/keep-ui/app/settings/webhook-settings.tsx +++ b/keep-ui/app/settings/webhook-settings.tsx @@ -12,14 +12,13 @@ import { Title, TabPanels, TabPanel, - Callout - + Callout, } from "@tremor/react"; import Loading from "app/loading"; import { useRouter } from "next/navigation"; import { CodeBlock, a11yLight } from "react-code-blocks"; import useSWR from "swr"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { fetcher } from "utils/fetcher"; import { toast } from "react-toastify"; import { v4 as uuidv4 } from "uuid"; @@ -39,7 +38,7 @@ interface Props { export default function WebhookSettings({ accessToken, selectedTab }: Props) { const [codeTabIndex, setCodeTabIndex] = useState(0); - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data, error, isLoading } = useSWR( selectedTab === "webhook" ? `${apiUrl}/settings/webhook` : null, @@ -48,20 +47,23 @@ export default function WebhookSettings({ accessToken, selectedTab }: Props) { ); const router = useRouter(); - if (error) return Failed to load webhook settings. -



+

+

{error.message}
+ ); if (!data || isLoading) return ; - const [example] = data.modelSchema.examples; const exampleJson = JSON.stringify( diff --git a/keep-ui/app/topology/api/index.ts b/keep-ui/app/topology/api/index.ts index 18dc9ec19..2d4e3b7f5 100644 --- a/keep-ui/app/topology/api/index.ts +++ b/keep-ui/app/topology/api/index.ts @@ -1,19 +1,19 @@ -import { getApiURL } from "@/utils/apiUrl"; import { fetcher } from "@/utils/fetcher"; import { Session } from "next-auth"; import { TopologyApplication, TopologyService } from "../model/models"; -export function buildTopologyUrl({ - providerIds, - services, - environment, -}: { - providerIds?: string[]; - services?: string[]; - environment?: string; -}) { - const apiUrl = getApiURL(); - +export function buildTopologyUrl( + apiUrl: string, + { + providerIds, + services, + environment, + }: { + providerIds?: string[]; + services?: string[]; + environment?: string; + } +) { const baseUrl = `${apiUrl}/topology`; const params = new URLSearchParams(); @@ -31,17 +31,18 @@ export function buildTopologyUrl({ return `${baseUrl}?${params.toString()}`; } -export async function getApplications(session: Session | null) { +export async function getApplications(apiUrl: string, session: Session | null) { if (!session) { return null; } - const apiUrl = `${getApiURL()}/topology/applications`; - return (await fetcher(apiUrl, session.accessToken)) as Promise< + const url = `${apiUrl}/topology/applications`; + return (await fetcher(url, session.accessToken)) as Promise< TopologyApplication[] >; } export function getTopology( + apiUrl: string, session: Session | null, { providerIds, @@ -56,6 +57,6 @@ export function getTopology( if (!session) { return null; } - const url = buildTopologyUrl({ providerIds, services, environment }); + const url = buildTopologyUrl(apiUrl, { providerIds, services, environment }); return fetcher(url, session.accessToken) as Promise; } diff --git a/keep-ui/app/topology/model/useTopology.ts b/keep-ui/app/topology/model/useTopology.ts index ee57a78ae..41f42354f 100644 --- a/keep-ui/app/topology/model/useTopology.ts +++ b/keep-ui/app/topology/model/useTopology.ts @@ -1,13 +1,13 @@ import { TopologyService } from "@/app/topology/model/models"; import { useSession } from "next-auth/react"; import useSWR, { SWRConfiguration } from "swr"; -import { getApiURL } from "@/utils/apiUrl"; import { fetcher } from "@/utils/fetcher"; import { useEffect } from "react"; import { buildTopologyUrl } from "@/app/topology/api"; import { useTopologyPollingContext } from "@/app/topology/model/TopologyPollingContext"; +import { useApiUrl } from "utils/hooks/useConfig"; -export const topologyBaseKey = `${getApiURL()}/topology`; +export const useTopologyBaseKey = () => `${useApiUrl()}/topology`; type UseTopologyOptions = { providerIds?: string[]; @@ -32,11 +32,12 @@ export const useTopology = ( } ) => { const { data: session } = useSession(); + const apiUrl = useApiUrl(); const pollTopology = useTopologyPollingContext(); const url = !session ? null - : buildTopologyUrl({ providerIds, services, environment }); + : buildTopologyUrl(apiUrl!, { providerIds, services, environment }); const { data, error, mutate } = useSWR( url, diff --git a/keep-ui/app/topology/model/useTopologyApplications.ts b/keep-ui/app/topology/model/useTopologyApplications.ts index 93da4e5f8..eb6c34b61 100644 --- a/keep-ui/app/topology/model/useTopologyApplications.ts +++ b/keep-ui/app/topology/model/useTopologyApplications.ts @@ -1,10 +1,10 @@ import { TopologyApplication } from "./models"; -import { getApiURL } from "@/utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import useSWR, { SWRConfiguration } from "swr"; import { fetcher } from "@/utils/fetcher"; import { useSession } from "next-auth/react"; import { useCallback, useMemo } from "react"; -import { topologyBaseKey, useTopology } from "./useTopology"; +import { useTopologyBaseKey, useTopology } from "./useTopology"; import { useRevalidateMultiple } from "@/utils/state"; type UseTopologyApplicationsOptions = { @@ -19,8 +19,9 @@ export function useTopologyApplications( }, } ) { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); + const topologyBaseKey = useTopologyBaseKey(); const revalidateMultiple = useRevalidateMultiple(); const { topologyData, mutate: mutateTopology } = useTopology(); const topologyApplicationsKey = `${apiUrl}/topology/applications`; @@ -37,7 +38,7 @@ export function useTopologyApplications( const addApplication = useCallback( async (application: Omit) => { - const response = await fetch(`${getApiURL()}/topology/applications`, { + const response = await fetch(`${apiUrl}/topology/applications`, { method: "POST", headers: { "Content-Type": "application/json", @@ -86,7 +87,7 @@ export function useTopologyApplications( ); } const response = await fetch( - `${getApiURL()}/topology/applications/${application.id}`, + `${apiUrl}/topology/applications/${application.id}`, { method: "PUT", headers: { @@ -142,7 +143,7 @@ export function useTopologyApplications( ); } const response = await fetch( - `${getApiURL()}/topology/applications/${applicationId}`, + `${apiUrl}/topology/applications/${applicationId}`, { method: "DELETE", headers: { diff --git a/keep-ui/app/topology/page.tsx b/keep-ui/app/topology/page.tsx index 6b99e854d..c0599aeba 100644 --- a/keep-ui/app/topology/page.tsx +++ b/keep-ui/app/topology/page.tsx @@ -5,6 +5,7 @@ import { authOptions } from "@/pages/api/auth/[...nextauth]"; import { getApplications, getTopology } from "./api"; import { TopologyPageClient } from "./topology-client"; import { Subtitle, Title } from "@tremor/react"; +import { getApiURL } from "@/utils/apiUrl"; export const metadata = { title: "Keep - Service Topology", @@ -21,9 +22,10 @@ type PageProps = { export default async function Page({ searchParams }: PageProps) { const session = await getServerSession(authOptions); + const apiUrl = getApiURL(); - const applications = await getApplications(session); - const topologyServices = await getTopology(session, { + const applications = await getApplications(apiUrl, session); + const topologyServices = await getTopology(apiUrl, session, { providerIds: searchParams.providerIds, services: searchParams.services, environment: searchParams.environment, diff --git a/keep-ui/app/workflows/builder/[workflowId]/page.tsx b/keep-ui/app/workflows/builder/[workflowId]/page.tsx index 8026f07ae..846c3f60a 100644 --- a/keep-ui/app/workflows/builder/[workflowId]/page.tsx +++ b/keep-ui/app/workflows/builder/[workflowId]/page.tsx @@ -9,6 +9,7 @@ export default async function PageWithId({ params: { workflowId: string }; }) { const accessToken = await getServerSession(authOptions); + // server so we can use getApiUrl const apiUrl = getApiURL(); const response = await fetch(`${apiUrl}/workflows/${params.workflowId}/raw`, { headers: { diff --git a/keep-ui/app/workflows/builder/builder-card.tsx b/keep-ui/app/workflows/builder/builder-card.tsx index 8a1b1ca1b..5f534e312 100644 --- a/keep-ui/app/workflows/builder/builder-card.tsx +++ b/keep-ui/app/workflows/builder/builder-card.tsx @@ -2,7 +2,7 @@ import { ExclamationCircleIcon } from "@heroicons/react/24/outline"; import { Card, Callout } from "@tremor/react"; import dynamic from "next/dynamic"; import { useEffect, useState } from "react"; -import { getApiURL } from "../../../utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import Loader from "./loader"; import { Provider } from "../../providers/providers"; import { KeepApiError } from "../../error"; @@ -23,7 +23,7 @@ interface Props { triggerRun: number; workflow?: string; workflowId?: string; - isPreview?:boolean; + isPreview?: boolean; } export function BuilderCard({ @@ -37,13 +37,13 @@ export function BuilderCard({ triggerSave, workflow, workflowId, - isPreview + isPreview, }: Props) { const [providers, setProviders] = useState(null); const [installedProviders, setInstalledProviders] = useState< Provider[] | null >(null); - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data, error, isLoading } = useProviders(); @@ -51,7 +51,7 @@ export function BuilderCard({ throw new KeepApiError( "The builder has failed to load providers", `${apiUrl}/providers`, - `Failed to query ${apiUrl}/providers, is Keep API up?` + `Failed to query ${apiUrl}/providers, is Keep API up?` ); } diff --git a/keep-ui/app/workflows/builder/builder.tsx b/keep-ui/app/workflows/builder/builder.tsx index a929210a1..e2400c65c 100644 --- a/keep-ui/app/workflows/builder/builder.tsx +++ b/keep-ui/app/workflows/builder/builder.tsx @@ -17,7 +17,7 @@ import { globalValidatorV2, stepValidatorV2 } from "./builder-validators"; import Modal from "react-modal"; import { Alert } from "./alert"; import BuilderModalContent from "./builder-modal"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import Loader from "./loader"; import { stringify } from "yaml"; import { useSearchParams } from "next/navigation"; @@ -81,6 +81,7 @@ function Builder({ const searchParams = useSearchParams(); const { errorNode, setErrorNode, canDeploy, synced } = useStore(); + const apiUrl = useApiUrl(); const setStepValidationErrorV2 = (step: V2Step, error: string | null) => { setStepValidationError(error); @@ -102,7 +103,6 @@ function Builder({ }; const updateWorkflow = () => { - const apiUrl = getApiURL(); const url = `${apiUrl}/workflows/${workflowId}`; const method = "PUT"; const headers = { @@ -125,7 +125,6 @@ function Builder({ }; const testRunWorkflow = () => { - const apiUrl = getApiURL(); const url = `${apiUrl}/workflows/test`; const method = "POST"; const headers = { @@ -158,7 +157,6 @@ function Builder({ }; const addWorkflow = () => { - const apiUrl = getApiURL(); const url = `${apiUrl}/workflows/json`; const method = "POST"; const headers = { diff --git a/keep-ui/app/workflows/builder/workflow-execution-results.tsx b/keep-ui/app/workflows/builder/workflow-execution-results.tsx index 8c923cc2b..b468682e2 100644 --- a/keep-ui/app/workflows/builder/workflow-execution-results.tsx +++ b/keep-ui/app/workflows/builder/workflow-execution-results.tsx @@ -8,7 +8,7 @@ import { Title, } from "@tremor/react"; import { useSession } from "next-auth/react"; -import { getApiURL } from "../../../utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import Loading from "../../loading"; import { ExclamationCircleIcon } from "@heroicons/react/24/outline"; import { @@ -36,7 +36,7 @@ export default function WorkflowExecutionResults({ workflow_id, workflow_execution_id, }: WorkflowResultsProps) { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session, status, update } = useSession(); const [refreshInterval, setRefreshInterval] = useState(1000); const [checks, setChecks] = useState(1); diff --git a/keep-ui/app/workflows/dragndrop.tsx b/keep-ui/app/workflows/dragndrop.tsx index 1bd3e8aa3..cc7c1d80f 100644 --- a/keep-ui/app/workflows/dragndrop.tsx +++ b/keep-ui/app/workflows/dragndrop.tsx @@ -1,9 +1,9 @@ import React, { useRef, useState } from "react"; -import { getApiURL } from "../../utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { useSession } from "next-auth/react"; const FileUpload: React.FC = () => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); const [error, setError] = useState(null); const fileInputRef = useRef(null); @@ -23,33 +23,42 @@ const FileUpload: React.FC = () => { if (response.ok) { setError(null); - if(fileInputRef.current) { + if (fileInputRef.current) { fileInputRef.current.value = ""; } window.location.reload(); } else { const errorMessage = await response.text(); setError(errorMessage); - if(fileInputRef.current) { + if (fileInputRef.current) { fileInputRef.current.value = ""; } } } catch (error) { setError("An error occurred during file upload"); - if(fileInputRef.current) { + if (fileInputRef.current) { fileInputRef.current.value = ""; } } }; return ( -
+
- {error &&

Failed to upload the file: {error}

Please try again with another file.

} + {error && ( +

+ Failed to upload the file: {error} +

Please try again with another file. +

+ )}
); }; diff --git a/keep-ui/app/workflows/manual-run-workflow-modal.tsx b/keep-ui/app/workflows/manual-run-workflow-modal.tsx index 753dd0baf..bc1494f33 100644 --- a/keep-ui/app/workflows/manual-run-workflow-modal.tsx +++ b/keep-ui/app/workflows/manual-run-workflow-modal.tsx @@ -1,10 +1,10 @@ -import {Button, Select, SelectItem, Title} from "@tremor/react"; +import { Button, Select, SelectItem, Title } from "@tremor/react"; import Modal from "@/components/ui/Modal"; import { useWorkflows } from "utils/hooks/useWorkflows"; import { useState } from "react"; import { useSession } from "next-auth/react"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { toast } from "react-toastify"; import { useRouter } from "next/navigation"; import { IncidentDto } from "@/app/incidents/models"; @@ -16,7 +16,11 @@ interface Props { handleClose: () => void; } -export default function ManualRunWorkflowModal({ alert, incident, handleClose }: Props) { +export default function ManualRunWorkflowModal({ + alert, + incident, + handleClose, +}: Props) { /** * */ @@ -26,7 +30,7 @@ export default function ManualRunWorkflowModal({ alert, incident, handleClose }: const { data: workflows } = useWorkflows({}); const { data: session } = useSession(); const router = useRouter(); - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const isOpen = !!alert || !!incident; @@ -44,7 +48,10 @@ export default function ManualRunWorkflowModal({ alert, incident, handleClose }: Authorization: `Bearer ${session?.accessToken}`, "Content-Type": "application/json", }, - body: JSON.stringify({"type": alert ? "alert" : "incident", "body": alert ? alert : incident}), + body: JSON.stringify({ + type: alert ? "alert" : "incident", + body: alert ? alert : incident, + }), } ); diff --git a/keep-ui/app/workflows/mockworkflows.tsx b/keep-ui/app/workflows/mockworkflows.tsx index 0ea737931..9e0f0f653 100644 --- a/keep-ui/app/workflows/mockworkflows.tsx +++ b/keep-ui/app/workflows/mockworkflows.tsx @@ -1,6 +1,5 @@ import React, { useState } from "react"; import { MockAction, MockStep, MockWorkflow, Workflow } from "./models"; -import { getApiURL } from "../../utils/apiUrl"; import Loading from "../loading"; import { Button, Card, Tab, TabGroup, TabList } from "@tremor/react"; import Modal from "@/components/ui/Modal"; @@ -18,13 +17,16 @@ export function WorkflowSteps({ workflow }: { workflow: MockWorkflow }) {
{workflow?.steps?.map((step: any, index: number) => { const provider = step?.provider; - if (['threshold', 'assert', 'foreach'].includes(provider?.type)) { + if (["threshold", "assert", "foreach"].includes(provider?.type)) { return null; } return ( <> {provider && ( -
+
{index > 0 && ( )} @@ -42,13 +44,16 @@ export function WorkflowSteps({ workflow }: { workflow: MockWorkflow }) { })} {workflow?.actions?.map((action: any, index: number) => { const provider = action?.provider; - if (['threshold', 'assert', 'foreach'].includes(provider?.type)) { + if (["threshold", "assert", "foreach"].includes(provider?.type)) { return null; } return ( <> {provider && ( -
+
{(index > 0 || isStepPresent) && ( )} @@ -98,7 +103,7 @@ export default function MockWorkflowCardSection({ mockLoading: boolean | null; }) { const router = useRouter(); - const [loadingId, setLoadingId] = useState(null); + const [loadingId, setLoadingId] = useState(null); const getNameFromId = (id: string) => { if (!id) { @@ -147,7 +152,7 @@ export default function MockWorkflowCardSection({ )}
- {mockError && ( + {mockError && (

Error: {mockError.message || "Something went wrong!"}

diff --git a/keep-ui/app/workflows/workflow-tile.tsx b/keep-ui/app/workflows/workflow-tile.tsx index 7e3da37ba..ad9a4e636 100644 --- a/keep-ui/app/workflows/workflow-tile.tsx +++ b/keep-ui/app/workflows/workflow-tile.tsx @@ -2,7 +2,7 @@ import { useSession } from "next-auth/react"; import { Workflow, Filter } from "./models"; -import { getApiURL } from "../../utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import Image from "next/image"; import React, { useState, useMemo } from "react"; import { useRouter } from "next/navigation"; @@ -265,7 +265,7 @@ export const ProvidersCarousel = ({ function WorkflowTile({ workflow }: { workflow: Workflow }) { // Create a set to keep track of unique providers - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); const router = useRouter(); const [openPanel, setOpenPanel] = useState(false); @@ -720,7 +720,7 @@ function WorkflowTile({ workflow }: { workflow: Workflow }) { export function WorkflowTileOld({ workflow }: { workflow: Workflow }) { // Create a set to keep track of unique providers - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); const router = useRouter(); const [openPanel, setOpenPanel] = useState(false); diff --git a/keep-ui/app/workflows/workflows.client.tsx b/keep-ui/app/workflows/workflows.client.tsx index 2d90aa818..8a5d9e34b 100644 --- a/keep-ui/app/workflows/workflows.client.tsx +++ b/keep-ui/app/workflows/workflows.client.tsx @@ -11,7 +11,7 @@ import { import { useSession } from "next-auth/react"; import { fetcher } from "../../utils/fetcher"; import { Workflow, MockWorkflow } from "./models"; -import { getApiURL } from "../../utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import Loading from "../loading"; import React from "react"; import WorkflowsEmptyState from "./noworfklows"; @@ -23,7 +23,7 @@ import Modal from "@/components/ui/Modal"; import MockWorkflowCardSection from "./mockworkflows"; export default function WorkflowsPage() { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const router = useRouter(); const { data: session, status, update } = useSession(); const [fileError, setFileError] = useState(null); diff --git a/keep-ui/components/navbar/CustomPresetAlertLinks.tsx b/keep-ui/components/navbar/CustomPresetAlertLinks.tsx index 460d65fe7..f6465a5ef 100644 --- a/keep-ui/components/navbar/CustomPresetAlertLinks.tsx +++ b/keep-ui/components/navbar/CustomPresetAlertLinks.tsx @@ -1,7 +1,7 @@ import { CSSProperties, useEffect, useState } from "react"; import { Session } from "next-auth"; import { toast } from "react-toastify"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { usePresets } from "utils/hooks/usePresets"; import { AiOutlineSwap } from "react-icons/ai"; import { usePathname, useRouter } from "next/navigation"; @@ -22,7 +22,7 @@ import { CSS } from "@dnd-kit/utilities"; import { Preset } from "app/alerts/models"; import { AiOutlineSound } from "react-icons/ai"; // Using dynamic import to avoid hydration issues with react-player -import dynamic from 'next/dynamic' +import dynamic from "next/dynamic"; const ReactPlayer = dynamic(() => import("react-player"), { ssr: false }); // import css import "./CustomPresetAlertLink.css"; @@ -60,12 +60,7 @@ const PresetAlert = ({ preset, pathname, deletePreset }: PresetAlertProps) => { }; return ( -
  • +
  • { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); - const { useAllPresets, presetsOrderFromLS, setPresetsOrderFromLS } = usePresets(); + const { useAllPresets, presetsOrderFromLS, setPresetsOrderFromLS } = + usePresets(); const { data: presets = [], mutate: presetsMutator } = useAllPresets({ revalidateIfStale: false, revalidateOnFocus: false, @@ -126,16 +122,26 @@ export const CustomPresetAlertLinks = ({ useEffect(() => { const filteredLS = presetsOrderFromLS.filter( - (preset) => !["feed", "deleted", "dismissed", "without-incident", "groups"].includes(preset.name) + (preset) => + ![ + "feed", + "deleted", + "dismissed", + "without-incident", + "groups", + ].includes(preset.name) ); // Combine live presets and local storage order - const combinedOrder = presets.reduce((acc, preset: Preset) => { - if (!acc.find((p) => p.id === preset.id)) { - acc.push(preset); - } - return acc.filter((preset) => checkValidPreset(preset)); - }, [...filteredLS]); + const combinedOrder = presets.reduce( + (acc, preset: Preset) => { + if (!acc.find((p) => p.id === preset.id)) { + acc.push(preset); + } + return acc.filter((preset) => checkValidPreset(preset)); + }, + [...filteredLS] + ); // Only update state if there's an actual change to prevent infinite loops if (JSON.stringify(presetsOrder) !== JSON.stringify(combinedOrder)) { @@ -143,11 +149,12 @@ export const CustomPresetAlertLinks = ({ } }, [presets, presetsOrderFromLS]); // Filter presets based on tags, or return all if no tags are selected - const filteredOrderedPresets = selectedTags.length === 0 - ? presetsOrder - : presetsOrder.filter((preset) => - preset.tags.some((tag) => selectedTags.includes(tag.name)) - ); + const filteredOrderedPresets = + selectedTags.length === 0 + ? presetsOrder + : presetsOrder.filter((preset) => + preset.tags.some((tag) => selectedTags.includes(tag.name)) + ); const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { diff --git a/keep-ui/components/navbar/DashboardLinks.tsx b/keep-ui/components/navbar/DashboardLinks.tsx index 948be30ad..cf0cab3d4 100644 --- a/keep-ui/components/navbar/DashboardLinks.tsx +++ b/keep-ui/components/navbar/DashboardLinks.tsx @@ -15,7 +15,7 @@ import { Disclosure } from "@headlessui/react"; import { IoChevronUp } from "react-icons/io5"; import classNames from "classnames"; import { useDashboards } from "utils/hooks/useDashboards"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "utils/hooks/useConfig"; import { Session } from "next-auth"; import { PlusIcon } from "@radix-ui/react-icons"; @@ -28,6 +28,7 @@ export const DashboardLinks = ({ session }: DashboardProps) => { const { dashboards = [], isLoading, error, mutate } = useDashboards(); const pathname = usePathname(); const router = useRouter(); + const apiUrl = useApiUrl(); const sensors = useSensors(useSensor(PointerSensor), useSensor(TouchSensor)); @@ -51,7 +52,6 @@ export const DashboardLinks = ({ session }: DashboardProps) => { ); if (isDeleteConfirmed) { try { - const apiUrl = getApiURL(); await fetch(`${apiUrl}/dashboard/${id}`, { method: "DELETE", headers: { diff --git a/keep-ui/middleware.tsx b/keep-ui/middleware.tsx index 9c405978d..4f3390cfd 100644 --- a/keep-ui/middleware.tsx +++ b/keep-ui/middleware.tsx @@ -5,6 +5,8 @@ import { getApiURL } from "utils/apiUrl"; export default withAuth( function middleware(req) { const { pathname, searchParams } = new URL(req.url); + // Shahar: This is just for backward compatibility + // **should be removed** // Redirect /backend/ to the API if (pathname.startsWith("/backend/")) { let apiUrl = getApiURL(); diff --git a/keep-ui/pages/api/config.tsx b/keep-ui/pages/api/config.tsx index 07ef9a2a6..e052a7022 100644 --- a/keep-ui/pages/api/config.tsx +++ b/keep-ui/pages/api/config.tsx @@ -1,5 +1,10 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { AuthenticationType, MULTI_TENANT, SINGLE_TENANT, NO_AUTH } from "utils/authenticationType"; +import { + AuthenticationType, + MULTI_TENANT, + SINGLE_TENANT, + NO_AUTH, +} from "utils/authenticationType"; export default async function handler( req: NextApiRequest, @@ -19,13 +24,20 @@ export default async function handler( res.status(200).json({ AUTH_TYPE: authType, PUSHER_DISABLED: process.env.PUSHER_DISABLED === "true", + // could be relative (for ingress) or absolute (e.g. Pusher) PUSHER_HOST: process.env.PUSHER_HOST, PUSHER_PORT: process.env.PUSHER_HOST ? parseInt(process.env.PUSHER_PORT!) : undefined, PUSHER_APP_KEY: process.env.PUSHER_APP_KEY, PUSHER_CLUSTER: process.env.PUSHER_CLUSTER, + // The API URL is used by the server to make requests to the API + // note that we need two different URLs for the client and the server + // because in some environments, e.g. docker-compose, the server can get keep-backend + // whereas the client (browser) can get only localhost API_URL: process.env.API_URL, + // could be relative (e.g. for ingress) or absolute (e.g. for cloud run) + API_URL_CLIENT: process.env.API_URL_CLIENT, POSTHOG_KEY: process.env.POSTHOG_KEY, POSTHOG_DISABLED: process.env.POSTHOG_DISABLED, POSTHOG_HOST: process.env.POSTHOG_HOST, diff --git a/keep-ui/types/internal-config.ts b/keep-ui/types/internal-config.ts index 522e3da87..e8a368cd0 100644 --- a/keep-ui/types/internal-config.ts +++ b/keep-ui/types/internal-config.ts @@ -5,7 +5,13 @@ export interface InternalConfig { PUSHER_PORT?: number; PUSHER_APP_KEY: string; PUSHER_CLUSTER?: string; + PUSHER_INGRESS: boolean; // e.g. for kubernetes/helmchart where the websocket endpoint is through the ingress POSTHOG_KEY: string; POSTHOG_HOST: string; POSTHOG_DISABLED: string; + // the API URL is used by the server to make requests to the API + API_URL: string; + // the API URL for the client (browser) + // optional, defaults to /backend (relative) + API_URL_CLIENT?: string; } diff --git a/keep-ui/utils/apiUrl.ts b/keep-ui/utils/apiUrl.ts index 22478957f..10e8c7878 100644 --- a/keep-ui/utils/apiUrl.ts +++ b/keep-ui/utils/apiUrl.ts @@ -1,21 +1,6 @@ +// server only! export function getApiURL(): string { - // https://github.com/vercel/next.js/issues/5354#issuecomment-520305040 - // https://stackoverflow.com/questions/49411796/how-do-i-detect-whether-i-am-on-server-on-client-in-next-js - - // Some background on this: - // On docker-compose, the browser can't access the "http://keep-backend" url - // since its the name of the container (and not accesible from the host) - // so we need to use the "http://localhost:3000" url instead. - const componentType = typeof window === "undefined" ? "server" : "client"; - - // if its client, use the same url as the browser but with the "/backend" prefix so that middleware.ts can proxy the request to the backend - if (componentType === "client") { - return "/backend"; - } - - // SERVER ONLY FROM HERE ON - - // else, its the server, and we need to check if we are on vercel or not + // we need to check if we are on vercel or not const gitBranchName = process.env.VERCEL_GIT_COMMIT_REF || "notvercel"; // main branch or not vercel - use the normal url if (gitBranchName === "main" || gitBranchName === "notvercel") { diff --git a/keep-ui/utils/helpers.ts b/keep-ui/utils/helpers.ts index a54434487..1fd58bd4d 100644 --- a/keep-ui/utils/helpers.ts +++ b/keep-ui/utils/helpers.ts @@ -1,5 +1,4 @@ import { toast } from "react-toastify"; -import { getApiURL } from "./apiUrl"; import { Provider } from "../app/providers/providers"; import moment from "moment"; import { twMerge } from "tailwind-merge"; @@ -39,12 +38,14 @@ export function toDateObjectWithFallback(date: string | Date) { return new Date(); } -export async function installWebhook(provider: Provider, accessToken: string) { +export async function installWebhook( + provider: Provider, + accessToken: string, + apiUrl: string +) { return toast.promise( fetch( - `${getApiURL()}/providers/install/webhook/${provider.type}/${ - provider.id - }`, + `${apiUrl}/providers/install/webhook/${provider.type}/${provider.id}`, { method: "POST", headers: { diff --git a/keep-ui/utils/hooks/useAI.ts b/keep-ui/utils/hooks/useAI.ts index 57af67d73..329830b32 100644 --- a/keep-ui/utils/hooks/useAI.ts +++ b/keep-ui/utils/hooks/useAI.ts @@ -1,19 +1,18 @@ import { AILogs, AIStats } from "app/ai/model"; import { useSession } from "next-auth/react"; import useSWR, { SWRConfiguration } from "swr"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; import { useWebsocket } from "./usePusher"; import { useCallback, useEffect } from "react"; - export const useAIStats = ( options: SWRConfiguration = { revalidateOnFocus: false, } ) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWR( @@ -38,4 +37,4 @@ export const usePollAILogs = (mutateAILogs: (logs: AILogs) => void) => { unbind("ai-logs-change", handleIncoming); }; }, [bind, unbind, handleIncoming]); -}; \ No newline at end of file +}; diff --git a/keep-ui/utils/hooks/useAlerts.ts b/keep-ui/utils/hooks/useAlerts.ts index 8b0686b62..27ce9555e 100644 --- a/keep-ui/utils/hooks/useAlerts.ts +++ b/keep-ui/utils/hooks/useAlerts.ts @@ -2,7 +2,7 @@ import { useState, useEffect } from "react"; import { AlertDto } from "app/alerts/models"; import { useSession } from "next-auth/react"; import useSWR, { SWRConfiguration } from "swr"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; import { toDateObjectWithFallback } from "utils/helpers"; @@ -16,7 +16,7 @@ export type AuditEvent = { }; export const useAlerts = () => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); const useAlertHistory = ( diff --git a/keep-ui/utils/hooks/useConfig.ts b/keep-ui/utils/hooks/useConfig.ts index 68de69202..7356783e2 100644 --- a/keep-ui/utils/hooks/useConfig.ts +++ b/keep-ui/utils/hooks/useConfig.ts @@ -10,3 +10,15 @@ export const useConfig = () => { fetcher("/api/config", session?.accessToken) ); }; + +export const useApiUrl = () => { + const { data: config } = useConfig(); + + if (config?.API_URL_CLIENT) { + return config.API_URL_CLIENT; + } + + // backward compatibility or for docker-compose or other deployments where the browser + // can't access the API directly + return "/backend"; +}; diff --git a/keep-ui/utils/hooks/useDashboards.ts b/keep-ui/utils/hooks/useDashboards.ts index de61bd967..7f3c81245 100644 --- a/keep-ui/utils/hooks/useDashboards.ts +++ b/keep-ui/utils/hooks/useDashboards.ts @@ -1,6 +1,6 @@ import useSWR from "swr"; import { useSession } from "next-auth/react"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; export interface Dashboard { @@ -11,7 +11,7 @@ export interface Dashboard { export const useDashboards = () => { const { data: session } = useSession(); - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data, error, mutate } = useSWR( session ? `${apiUrl}/dashboard` : null, diff --git a/keep-ui/utils/hooks/useDeduplicationRules.ts b/keep-ui/utils/hooks/useDeduplicationRules.ts index 1d6d43c54..54b68015b 100644 --- a/keep-ui/utils/hooks/useDeduplicationRules.ts +++ b/keep-ui/utils/hooks/useDeduplicationRules.ts @@ -2,11 +2,11 @@ import { DeduplicationRule } from "app/deduplication/models"; import { useSession } from "next-auth/react"; import { SWRConfiguration } from "swr"; import useSWRImmutable from "swr/immutable"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; export const useDeduplicationRules = (options: SWRConfiguration = {}) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWRImmutable( @@ -17,7 +17,7 @@ export const useDeduplicationRules = (options: SWRConfiguration = {}) => { }; export const useDeduplicationFields = (options: SWRConfiguration = {}) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWRImmutable>( diff --git a/keep-ui/utils/hooks/useExtractionRules.ts b/keep-ui/utils/hooks/useExtractionRules.ts index 779509bdd..0ff32b445 100644 --- a/keep-ui/utils/hooks/useExtractionRules.ts +++ b/keep-ui/utils/hooks/useExtractionRules.ts @@ -1,7 +1,7 @@ import { ExtractionRule } from "app/extraction/model"; import { useSession } from "next-auth/react"; import useSWR, { SWRConfiguration } from "swr"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; export const useExtractions = ( @@ -9,7 +9,7 @@ export const useExtractions = ( revalidateOnFocus: false, } ) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWR( diff --git a/keep-ui/utils/hooks/useGroups.ts b/keep-ui/utils/hooks/useGroups.ts index 0c2ed17fe..3e1dde9a2 100644 --- a/keep-ui/utils/hooks/useGroups.ts +++ b/keep-ui/utils/hooks/useGroups.ts @@ -2,11 +2,11 @@ import { Group } from "app/settings/models"; import { useSession } from "next-auth/react"; import { SWRConfiguration } from "swr"; import useSWRImmutable from "swr/immutable"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; export const useGroups = (options: SWRConfiguration = {}) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWRImmutable( diff --git a/keep-ui/utils/hooks/useIncidents.ts b/keep-ui/utils/hooks/useIncidents.ts index 9e94c6386..b3ba58a26 100644 --- a/keep-ui/utils/hooks/useIncidents.ts +++ b/keep-ui/utils/hooks/useIncidents.ts @@ -7,7 +7,7 @@ import { import { PaginatedWorkflowExecutionDto } from "app/workflows/builder/types"; import { useSession } from "next-auth/react"; import useSWR, { SWRConfiguration } from "swr"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; import { useWebsocket } from "./usePusher"; import { useCallback, useEffect } from "react"; @@ -35,7 +35,7 @@ export const useIncidents = ( revalidateOnFocus: false, } ) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); const filtersParams = new URLSearchParams(); @@ -70,7 +70,7 @@ export const useIncidentAlerts = ( revalidateOnFocus: false, } ) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWR( () => @@ -88,7 +88,7 @@ export const useIncidentFutureIncidents = ( revalidateOnFocus: false, } ) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWR( @@ -105,7 +105,7 @@ export const useIncident = ( revalidateOnFocus: false, } ) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWR( @@ -123,7 +123,7 @@ export const useIncidentWorkflowExecutions = ( revalidateOnFocus: false, } ) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWR( () => @@ -192,7 +192,7 @@ export const useIncidentsMeta = ( revalidateOnFocus: false, } ) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWR( diff --git a/keep-ui/utils/hooks/useMaintenanceRules.ts b/keep-ui/utils/hooks/useMaintenanceRules.ts index 0e46f54b9..b771615b1 100644 --- a/keep-ui/utils/hooks/useMaintenanceRules.ts +++ b/keep-ui/utils/hooks/useMaintenanceRules.ts @@ -1,7 +1,7 @@ import { MaintenanceRule } from "app/maintenance/model"; import { useSession } from "next-auth/react"; import useSWR, { SWRConfiguration } from "swr"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; export const useMaintenanceRules = ( @@ -9,7 +9,7 @@ export const useMaintenanceRules = ( revalidateOnFocus: false, } ) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWR( diff --git a/keep-ui/utils/hooks/useMappingRules.ts b/keep-ui/utils/hooks/useMappingRules.ts index 87db305a1..cf116bc9c 100644 --- a/keep-ui/utils/hooks/useMappingRules.ts +++ b/keep-ui/utils/hooks/useMappingRules.ts @@ -1,7 +1,7 @@ import { MappingRule } from "app/mapping/models"; import { useSession } from "next-auth/react"; import useSWR, { SWRConfiguration } from "swr"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; export const useMappings = ( @@ -9,7 +9,7 @@ export const useMappings = ( revalidateOnFocus: false, } ) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWR( diff --git a/keep-ui/utils/hooks/usePermissions.ts b/keep-ui/utils/hooks/usePermissions.ts index 3248cc082..b240ec2f6 100644 --- a/keep-ui/utils/hooks/usePermissions.ts +++ b/keep-ui/utils/hooks/usePermissions.ts @@ -2,11 +2,11 @@ import { Permission } from "app/settings/models"; import { useSession } from "next-auth/react"; import { SWRConfiguration } from "swr"; import useSWRImmutable from "swr/immutable"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; export const usePermissions = (options: SWRConfiguration = {}) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWRImmutable( diff --git a/keep-ui/utils/hooks/usePresets.ts b/keep-ui/utils/hooks/usePresets.ts index b4b9d5509..3559427d9 100644 --- a/keep-ui/utils/hooks/usePresets.ts +++ b/keep-ui/utils/hooks/usePresets.ts @@ -2,7 +2,7 @@ import { useState, useEffect, useRef } from "react"; import { Preset } from "app/alerts/models"; import { useSession } from "next-auth/react"; import useSWR, { SWRConfiguration } from "swr"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; import { useLocalStorage } from "utils/hooks/useLocalStorage"; import { useConfig } from "./useConfig"; @@ -14,7 +14,7 @@ import moment from "moment"; export const usePresets = (type?: string, useFilters?: boolean) => { const { data: session } = useSession(); const { data: configData } = useConfig(); - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); //ideally, we can use pathname. but hardcoding it for now. const isDashBoard = type === "dashboard"; const [presetsOrderFromLS, setPresetsOrderFromLS] = useLocalStorage( @@ -54,8 +54,8 @@ export const usePresets = (type?: string, useFilters?: boolean) => { ...currentPreset, alerts_count: currentPreset.alerts_count + newPreset.alerts_count, created_by: newPreset.created_by, - is_private: newPreset.is_private - }); + is_private: newPreset.is_private, + }); } else { // If the preset is not in the current presets, add it updatedPresets.set(newPresetId, { @@ -71,7 +71,14 @@ export const usePresets = (type?: string, useFilters?: boolean) => { updatePresets( presetsOrderRef.current, newPresets.filter( - (p) => !["feed", "deleted", "dismissed", "without-incident", "groups"].includes(p.name) + (p) => + ![ + "feed", + "deleted", + "dismissed", + "without-incident", + "groups", + ].includes(p.name) ) ) ); @@ -79,7 +86,13 @@ export const usePresets = (type?: string, useFilters?: boolean) => { updatePresets( staticPresetsOrderRef.current, newPresets.filter((p) => - ["feed", "deleted", "dismissed", "without-incident", "groups"].includes(p.name) + [ + "feed", + "deleted", + "dismissed", + "without-incident", + "groups", + ].includes(p.name) ) ) ); @@ -126,10 +139,22 @@ export const usePresets = (type?: string, useFilters?: boolean) => { if (data) { const dynamicPresets = data.filter( (p) => - !["feed", "deleted", "dismissed", "without-incident", "groups"].includes(p.name) + ![ + "feed", + "deleted", + "dismissed", + "without-incident", + "groups", + ].includes(p.name) ); const staticPresets = data.filter((p) => - ["feed", "deleted", "dismissed", "without-incident", "groups"].includes(p.name) + [ + "feed", + "deleted", + "dismissed", + "without-incident", + "groups", + ].includes(p.name) ); //if it is dashboard we don't need to merge with local storage. @@ -193,7 +218,13 @@ export const usePresets = (type?: string, useFilters?: boolean) => { } = useFetchAllPresets(options); const filteredPresets = presets?.filter( (preset) => - !["feed", "deleted", "dismissed", "groups", "without-incident"].includes(preset.name) + ![ + "feed", + "deleted", + "dismissed", + "groups", + "without-incident", + ].includes(preset.name) ); return { data: filteredPresets, diff --git a/keep-ui/utils/hooks/useProviders.ts b/keep-ui/utils/hooks/useProviders.ts index f7d3d854f..668af3a1e 100644 --- a/keep-ui/utils/hooks/useProviders.ts +++ b/keep-ui/utils/hooks/useProviders.ts @@ -1,5 +1,5 @@ import { useSession } from "next-auth/react"; -import { getApiURL } from "../apiUrl"; +import { useApiUrl } from "./useConfig"; import { SWRConfiguration } from "swr"; import { ProvidersResponse } from "app/providers/providers"; import { fetcher } from "../fetcher"; @@ -9,7 +9,7 @@ export const useProviders = ( options: SWRConfiguration = { revalidateOnFocus: false } ) => { const { data: session } = useSession(); - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); return useSWRImmutable( () => (session ? `${apiUrl}/providers` : null), diff --git a/keep-ui/utils/hooks/usePusher.ts b/keep-ui/utils/hooks/usePusher.ts index 106c46384..57828a2a3 100644 --- a/keep-ui/utils/hooks/usePusher.ts +++ b/keep-ui/utils/hooks/usePusher.ts @@ -1,18 +1,21 @@ import Pusher from "pusher-js"; import { useConfig } from "./useConfig"; import { useSession } from "next-auth/react"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; let PUSHER: Pusher | null = null; const POLLING_INTERVAL = 3000; export const useWebsocket = () => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: configData } = useConfig(); const { data: session } = useSession(); let channelName = `private-${session?.tenantId}`; + console.log("useWebsocket: Initializing with config:", configData); + console.log("useWebsocket: Session:", session); + if ( PUSHER === null && configData !== undefined && @@ -20,34 +23,73 @@ export const useWebsocket = () => { configData.PUSHER_DISABLED === false ) { channelName = `private-${session?.tenantId}`; - PUSHER = new Pusher(configData.PUSHER_APP_KEY, { - wsHost: configData.PUSHER_HOST, - wsPort: configData.PUSHER_PORT, - forceTLS: false, - disableStats: true, - enabledTransports: ["ws", "wss"], - cluster: configData.PUSHER_CLUSTER || "local", - channelAuthorization: { - transport: "ajax", - endpoint: `${apiUrl}/pusher/auth`, - headers: { - Authorization: `Bearer ${session?.accessToken!}`, + console.log("useWebsocket: Creating new Pusher instance"); + try { + const isRelativeHost = + configData.PUSHER_HOST && !configData.PUSHER_HOST.includes("://"); + console.log("useWebsocket: isRelativeHost:", isRelativeHost); + PUSHER = new Pusher(configData.PUSHER_APP_KEY, { + wsHost: isRelativeHost + ? window.location.hostname + : configData.PUSHER_HOST, + wsPath: isRelativeHost ? configData.PUSHER_HOST : "", + wsPort: isRelativeHost + ? window.location.protocol === "https:" + ? 443 + : 80 + : configData.PUSHER_PORT, + forceTLS: window.location.protocol === "https:", + disableStats: true, + enabledTransports: ["ws", "wss"], + cluster: configData.PUSHER_CLUSTER || "local", + channelAuthorization: { + transport: "ajax", + endpoint: `${apiUrl}/pusher/auth`, + headers: { + Authorization: `Bearer ${session?.accessToken!}`, + }, }, - }, - }); - PUSHER.subscribe(channelName); + }); + console.log("useWebsocket: Pusher instance created successfully"); + + PUSHER.connection.bind("connected", () => { + console.log("useWebsocket: Pusher connected successfully"); + }); + + PUSHER.connection.bind("error", (err: any) => { + console.error("useWebsocket: Pusher connection error:", err); + }); + + PUSHER.subscribe(channelName) + .bind("pusher:subscription_succeeded", () => { + console.log( + `useWebsocket: Successfully subscribed to ${channelName}` + ); + }) + .bind("pusher:subscription_error", (err: any) => { + console.error( + `useWebsocket: Subscription error for ${channelName}:`, + err + ); + }); + } catch (error) { + console.error("useWebsocket: Error creating Pusher instance:", error); + } } const subscribe = useCallback(() => { + console.log(`useWebsocket: Subscribing to ${channelName}`); return PUSHER?.subscribe(channelName); }, [channelName]); const unsubscribe = useCallback(() => { + console.log(`useWebsocket: Unsubscribing from ${channelName}`); return PUSHER?.unsubscribe(channelName); }, [channelName]); const bind = useCallback( (event: any, callback: any) => { + console.log(`useWebsocket: Binding to event ${event} on ${channelName}`); return PUSHER?.channel(channelName)?.bind(event, callback); }, [channelName] @@ -55,6 +97,9 @@ export const useWebsocket = () => { const unbind = useCallback( (event: any, callback: any) => { + console.log( + `useWebsocket: Unbinding from event ${event} on ${channelName}` + ); return PUSHER?.channel(channelName)?.unbind(event, callback); }, [channelName] @@ -62,12 +107,17 @@ export const useWebsocket = () => { const trigger = useCallback( (event: any, data: any) => { + console.log( + `useWebsocket: Triggering event ${event} on ${channelName} with data:`, + data + ); return PUSHER?.channel(channelName).trigger(event, data); }, [channelName] ); const channel = useCallback(() => { + console.log(`useWebsocket: Getting channel ${channelName}`); return PUSHER?.channel(channelName); }, [channelName]); @@ -86,24 +136,40 @@ export const useAlertPolling = () => { const [pollAlerts, setPollAlerts] = useState(0); const lastPollTimeRef = useRef(0); + console.log("useAlertPolling: Initializing"); + const handleIncoming = useCallback((incoming: any) => { + console.log("useAlertPolling: Received incoming data:", incoming); const currentTime = Date.now(); const timeSinceLastPoll = currentTime - lastPollTimeRef.current; + console.log( + `useAlertPolling: Time since last poll: ${timeSinceLastPoll}ms` + ); + if (timeSinceLastPoll < POLLING_INTERVAL) { + console.log("useAlertPolling: Ignoring poll due to short interval"); setPollAlerts(0); } else { + console.log("useAlertPolling: Updating poll alerts"); lastPollTimeRef.current = currentTime; - setPollAlerts(Math.floor(Math.random() * 10000)); + const newPollValue = Math.floor(Math.random() * 10000); + console.log(`useAlertPolling: New poll value: ${newPollValue}`); + setPollAlerts(newPollValue); } }, []); useEffect(() => { + console.log("useAlertPolling: Setting up event listener for 'poll-alerts'"); bind("poll-alerts", handleIncoming); return () => { + console.log( + "useAlertPolling: Cleaning up event listener for 'poll-alerts'" + ); unbind("poll-alerts", handleIncoming); }; }, [bind, unbind, handleIncoming]); + console.log("useAlertPolling: Current poll alerts value:", pollAlerts); return { data: pollAlerts }; }; diff --git a/keep-ui/utils/hooks/useRoles.ts b/keep-ui/utils/hooks/useRoles.ts index f71a048a6..dabfc9b58 100644 --- a/keep-ui/utils/hooks/useRoles.ts +++ b/keep-ui/utils/hooks/useRoles.ts @@ -2,11 +2,11 @@ import { Role } from "app/settings/models"; import { useSession } from "next-auth/react"; import { SWRConfiguration } from "swr"; import useSWRImmutable from "swr/immutable"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; export const useRoles = (options: SWRConfiguration = {}) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWRImmutable( diff --git a/keep-ui/utils/hooks/useRules.ts b/keep-ui/utils/hooks/useRules.ts index 3a710ed88..97976d51e 100644 --- a/keep-ui/utils/hooks/useRules.ts +++ b/keep-ui/utils/hooks/useRules.ts @@ -1,6 +1,6 @@ import { useSession } from "next-auth/react"; import useSWR, { SWRConfiguration } from "swr"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; export type Rule = { @@ -25,7 +25,7 @@ export type Rule = { }; export const useRules = (options?: SWRConfiguration) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWR( diff --git a/keep-ui/utils/hooks/useScopes.ts b/keep-ui/utils/hooks/useScopes.ts index 8d437e8c1..775d8717a 100644 --- a/keep-ui/utils/hooks/useScopes.ts +++ b/keep-ui/utils/hooks/useScopes.ts @@ -2,11 +2,11 @@ import { Scope } from "app/settings/models"; import { useSession } from "next-auth/react"; import { SWRConfiguration } from "swr"; import useSWRImmutable from "swr/immutable"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; export const useScopes = (options: SWRConfiguration = {}) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWRImmutable( diff --git a/keep-ui/utils/hooks/useSearchAlerts.ts b/keep-ui/utils/hooks/useSearchAlerts.ts index 1eb580ddf..01de3fe14 100644 --- a/keep-ui/utils/hooks/useSearchAlerts.ts +++ b/keep-ui/utils/hooks/useSearchAlerts.ts @@ -1,7 +1,7 @@ import useSWR, { SWRConfiguration } from "swr"; import { AlertDto } from "app/alerts/models"; import { useSession } from "next-auth/react"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; import { useDebouncedValue } from "./useDebouncedValue"; import { RuleGroupType, formatQuery } from "react-querybuilder"; @@ -10,7 +10,7 @@ export const useSearchAlerts = ( args: { query: RuleGroupType; timeframe: number }, options?: SWRConfiguration ) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); const [debouncedArgs] = useDebouncedValue(args, 2000); @@ -30,7 +30,7 @@ export const useSearchAlerts = ( body: JSON.stringify({ query: { cel_query: formatQuery(debouncedRules, "cel"), - sql_query: formatQuery(debouncedRules, "parameterized_named") + sql_query: formatQuery(debouncedRules, "parameterized_named"), }, timeframe: debouncedTimeframe, }), diff --git a/keep-ui/utils/hooks/useTags.ts b/keep-ui/utils/hooks/useTags.ts index 998b7b4fc..124da6649 100644 --- a/keep-ui/utils/hooks/useTags.ts +++ b/keep-ui/utils/hooks/useTags.ts @@ -1,13 +1,12 @@ import { useSession } from "next-auth/react"; import { SWRConfiguration } from "swr"; import useSWRImmutable from "swr/immutable"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; import { Tag } from "app/alerts/models"; - export const useTags = (options: SWRConfiguration = {}) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWRImmutable( diff --git a/keep-ui/utils/hooks/useUsers.ts b/keep-ui/utils/hooks/useUsers.ts index 5a15256c9..de254e040 100644 --- a/keep-ui/utils/hooks/useUsers.ts +++ b/keep-ui/utils/hooks/useUsers.ts @@ -2,11 +2,11 @@ import { User } from "app/settings/models"; import { useSession } from "next-auth/react"; import { SWRConfiguration } from "swr"; import useSWRImmutable from "swr/immutable"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; export const useUsers = (options: SWRConfiguration = {}) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWRImmutable( diff --git a/keep-ui/utils/hooks/useWorkflowExecutions.ts b/keep-ui/utils/hooks/useWorkflowExecutions.ts index 2414ce3c6..2763ff3aa 100644 --- a/keep-ui/utils/hooks/useWorkflowExecutions.ts +++ b/keep-ui/utils/hooks/useWorkflowExecutions.ts @@ -6,7 +6,7 @@ import { import { useSession } from "next-auth/react"; import { useSearchParams } from "next/navigation"; import useSWR, { SWRConfiguration } from "swr"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; export const useWorkflowExecutions = ( @@ -14,7 +14,7 @@ export const useWorkflowExecutions = ( revalidateOnFocus: false, } ) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWR( @@ -30,7 +30,7 @@ export const useWorkflowExecutionsV2 = ( limit: number = 25, offset: number = 0 ) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); const searchParams = useSearchParams(); limit = searchParams?.get("limit") @@ -61,7 +61,7 @@ export const useWorkflowExecution = ( workflowId: string, workflowExecutionId: string ) => { - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); const { data: session } = useSession(); return useSWR( diff --git a/keep-ui/utils/hooks/useWorkflowRun.ts b/keep-ui/utils/hooks/useWorkflowRun.ts index 74a9f9d45..dbaa98dc8 100644 --- a/keep-ui/utils/hooks/useWorkflowRun.ts +++ b/keep-ui/utils/hooks/useWorkflowRun.ts @@ -1,6 +1,6 @@ import { useState } from "react"; import { useSession } from "next-auth/react"; -import { getApiURL } from "utils/apiUrl"; +import { useApiUrl } from "./useConfig"; import { useRouter } from "next/navigation"; import { useProviders } from "./useProviders"; import { Filter, Workflow } from "app/workflows/models"; @@ -19,13 +19,12 @@ export const useWorkflowRun = (workflow: Workflow) => { let message = ""; const [alertFilters, setAlertFilters] = useState([]); const [alertDependencies, setAlertDependencies] = useState([]); + const apiUrl = useApiUrl(); const { data: providersData = { providers: {} } as ProvidersData } = useProviders(); const providers = providersData.providers; - const apiUrl = getApiURL(); - if (!workflow) { return {}; } diff --git a/keep-ui/utils/hooks/useWorkflows.ts b/keep-ui/utils/hooks/useWorkflows.ts index 3739627bd..26859eaf2 100644 --- a/keep-ui/utils/hooks/useWorkflows.ts +++ b/keep-ui/utils/hooks/useWorkflows.ts @@ -1,13 +1,13 @@ import { Workflow } from "app/workflows/models"; import { useSession } from "next-auth/react"; import { SWRConfiguration } from "swr"; -import { getApiURL } from "../apiUrl"; +import { useApiUrl } from "./useConfig"; import { fetcher } from "../fetcher"; import useSWRImmutable from "swr/immutable"; export const useWorkflows = (options: SWRConfiguration = {}) => { const { data: session } = useSession(); - const apiUrl = getApiURL(); + const apiUrl = useApiUrl(); return useSWRImmutable( () => (session ? `${apiUrl}/workflows` : null), diff --git a/pyproject.toml b/pyproject.toml index 1dfcc6d45..1f3adec36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "keep" -version = "0.26.0" +version = "0.27.0" description = "Alerting. for developers, by developers." authors = ["Keep Alerting LTD"] readme = "README.md"