diff --git a/examples/workflows/new_github_stars.yml b/examples/workflows/new_github_stars.yml index 84ca9a3e8..4e9484ec6 100644 --- a/examples/workflows/new_github_stars.yml +++ b/examples/workflows/new_github_stars.yml @@ -1,7 +1,7 @@ # Alert when there are new GitHub Stars utilizing keepstate workflow: id: new-github-stars - description: Get new GitHub Stars + description: Notify Slack about new GitHub star for keephq/keep triggers: - type: interval value: 300 @@ -20,7 +20,7 @@ workflow: condition: - name: assert-condition type: assert - assert: "{{ steps.get-github-stars.results.new_stargazers_count }} == 0" # if there are more than 0 new stargazers, trigger the action + assert: "{{ steps.get-github-stars.results.new_stargazers_count }} > 0" # if there are more than 0 new stargazers, trigger the action provider: type: slack config: " {{ providers.slack-demo }} " diff --git a/keep-ui/app/providers/page.client.tsx b/keep-ui/app/providers/page.client.tsx index 208da62b6..5e473736f 100644 --- a/keep-ui/app/providers/page.client.tsx +++ b/keep-ui/app/providers/page.client.tsx @@ -1,5 +1,4 @@ "use client"; -import { FrigadeAnnouncement } from "@frigade/react"; import { Providers, defaultProvider, @@ -200,19 +199,6 @@ export default function ProvidersPage({ return ( <> - { - if (cta === "primary") { - window.open( - "https://calendly.com/d/4p7-8dg-399/keep-onboarding", - "_blank" - ); - } - return true; - }} - /> {installedProviders.length > 0 && ( - - - ); + return ; } export const metadata = { diff --git a/keep-ui/app/settings/api-key-settings.tsx b/keep-ui/app/settings/api-key-settings.tsx index 3c66d6d5f..528c6d955 100644 --- a/keep-ui/app/settings/api-key-settings.tsx +++ b/keep-ui/app/settings/api-key-settings.tsx @@ -13,13 +13,15 @@ interface ApiKeyResponse { interface Props { accessToken: string; + selectedTab: string; } -export default function ApiKeySettings({ accessToken }: Props) { +export default function ApiKeySettings({ accessToken, selectedTab }: Props) { const apiUrl = getApiURL(); const { data, error, isLoading } = useSWR( - `${apiUrl}/settings/apikey`, - (url) => fetcher(url, accessToken) + selectedTab === "api-key" ? `${apiUrl}/settings/apikey` : null, + (url) => fetcher(url, accessToken), + { revalidateOnFocus: false } ); if (isLoading) return ; @@ -28,7 +30,7 @@ export default function ApiKeySettings({ accessToken }: Props) { const copyBlockApiKeyProps = { theme: { ...a11yLight }, language: "text", - text: data?.apiKey || '', + text: data?.apiKey || "", codeBlock: true, showLineNumbers: false, }; diff --git a/keep-ui/app/settings/settings.client.tsx b/keep-ui/app/settings/settings.client.tsx index d5b10ae66..e20dc9735 100644 --- a/keep-ui/app/settings/settings.client.tsx +++ b/keep-ui/app/settings/settings.client.tsx @@ -4,19 +4,40 @@ import { GlobeAltIcon, UserGroupIcon, EnvelopeIcon, - KeyIcon + KeyIcon, } from "@heroicons/react/24/outline"; import UsersSettings from "./users-settings"; import WebhookSettings from "./webhook-settings"; import APIKeySettings from "./api-key-settings"; -import { useSession } from "next-auth/react" +import { useSession } from "next-auth/react"; import Loading from "app/loading"; import SmtpSettings from "./smtp-settings"; -import { useRouter } from "next/navigation"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useState } from "react"; export default function SettingsPage() { const { data: session, status } = useSession(); const router = useRouter(); + const searchParams = useSearchParams()!; + const pathname = usePathname(); + const [selectedTab, setSelectedTab] = useState( + searchParams?.get("selectedTab") || "users" + ); + + const handleTabChange = (tab: string) => { + setSelectedTab(tab); + router.push(`${pathname}?selectedTab=${tab}`); + }; + + // TODO: more robust way to handle this + const tabIndex = + selectedTab === "users" + ? 0 + : selectedTab === "webhook" + ? 1 + : selectedTab === "smtp" + ? 2 + : 3; if (status === "loading") return ; if (status === "unauthenticated") router.push("/signin"); @@ -28,28 +49,46 @@ export default function SettingsPage() { * Think about a proper way to implement it. */ return ( - + - Users - Webhook - SMTP - Api Key + handleTabChange("users")}> + Users + + handleTabChange("webhook")}> + Webhook + + handleTabChange("smtp")}> + SMTP + + handleTabChange("api-key")}> + API Key + - + - + - + diff --git a/keep-ui/app/settings/smtp-settings.tsx b/keep-ui/app/settings/smtp-settings.tsx index 6c5ef90db..17bf25c29 100644 --- a/keep-ui/app/settings/smtp-settings.tsx +++ b/keep-ui/app/settings/smtp-settings.tsx @@ -1,5 +1,5 @@ -import { useState } from 'react'; -import { Card, Button, Title, Subtitle, TextInput } from '@tremor/react'; +import { useState } from "react"; +import { Card, Button, Title, Subtitle, TextInput } from "@tremor/react"; import useSWR from "swr"; import { getApiURL } from "utils/apiUrl"; import { fetcher } from "utils/fetcher"; @@ -32,52 +32,63 @@ interface TestResult { interface Props { accessToken: string; + selectedTab: string; } const isValidPort = (port: number) => { return !isNaN(port) && port > 0 && port <= 65535; -} +}; -export default function SMTPSettingsForm({ accessToken }: Props) { +export default function SMTPSettingsForm({ accessToken, selectedTab }: Props) { const [settings, setSettings] = useState({ - host: '', + host: "", port: 25, - username: '', - password: '', + username: "", + password: "", secure: false, - from_email: '', - to_email: '', + from_email: "", + to_email: "", }); const [testResult, setTestResult] = useState(null); const [errors, setErrors] = useState({}); const [isSaveSuccessful, setSaveSuccessful] = useState(false); - const [errorMessage, setErrorMessage] = useState(''); + const [errorMessage, setErrorMessage] = useState(""); const [shouldFetch, setShouldFetch] = useState(true); const [smtpInstalled, setSmtpInstalled] = useState(false); const [deleteSuccessful, setDeleteSuccessful] = useState(false); const validateSaveFields = () => { const newErrors: SMTPSettingsErrors = {}; - if (!settings.host) newErrors.host = 'Host is required'; - if (!isValidPort(settings.port)) newErrors.port = 'Port is invalid'; - if (!settings.from_email) newErrors.from_email = 'From is required'; + if (!settings.host) newErrors.host = "Host is required"; + if (!isValidPort(settings.port)) newErrors.port = "Port is invalid"; + if (!settings.from_email) newErrors.from_email = "From is required"; setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const validateTestFields = () => { const validSave = validateSaveFields(); - if (!settings.to_email) setErrors(errors => ({ ...errors, to_email: 'To is required for testing' })); + if (!settings.to_email) + setErrors((errors) => ({ + ...errors, + to_email: "To is required for testing", + })); return validSave && settings.to_email; }; const apiUrl = getApiURL(); - const shouldFetchUrl = shouldFetch ? `${apiUrl}/settings/smtp` : null; + const shouldFetchUrl = + shouldFetch && selectedTab === "smtp" ? `${apiUrl}/settings/smtp` : null; - // Use the useSWR hook to fetch the settings - const { data: smtpSettings, error, isValidating: isLoading } = useSWR( + // Use the useSWR hook to fetch the settings + const { + data: smtpSettings, + error, + isValidating: isLoading, + } = useSWR( shouldFetchUrl, // Update with your actual endpoint - url => fetcher(url, accessToken) // Ensure you have an accessToken variable available + (url) => fetcher(url, accessToken), // Ensure you have an accessToken variable available + { revalidateOnFocus: false } ); // Show loading state or error messages if needed @@ -86,13 +97,13 @@ export default function SMTPSettingsForm({ accessToken }: Props) { } // if no errors and we have data, set the settings - if(smtpSettings){ + if (smtpSettings) { // if the SMTP is not installed yet if (Object.keys(smtpSettings).length === 0) { // smtpSettings is an empty object, assign default values - setSettings(previousSettings => ({ + setSettings((previousSettings) => ({ ...previousSettings, // keep other settings if they exist - port: 25 // replace with your actual default port value + port: 25, // replace with your actual default port value })); } else { // smtp is installed @@ -102,48 +113,47 @@ export default function SMTPSettingsForm({ accessToken }: Props) { setShouldFetch(false); } - const onDelete = async () => { const response = await fetch(`${apiUrl}/settings/smtp`, { - method: 'DELETE', + method: "DELETE", headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, }, }); if (response.ok) { // If the delete was successful setDeleteSuccessful(true); setSettings({ - host: '', + host: "", port: 25, - username: '', - password: '', + username: "", + password: "", secure: false, - from_email: '', - to_email: '', + from_email: "", + to_email: "", }); } else { // If the delete failed setDeleteSuccessful(false); const errorData = await response.json(); // assuming the server sends JSON with an error message - setErrorMessage(errorData.message || 'An error occurred while deleting.'); + setErrorMessage(errorData.message || "An error occurred while deleting."); } - } + }; const onSave = async () => { if (!validateSaveFields()) return; - const payload = { ...settings } + const payload = { ...settings }; // Remove 'to_email' if it's empty if (!payload.to_email) { delete payload.to_email; // Remove 'to_email' if it's empty } const response = await fetch(`${apiUrl}/settings/smtp`, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, }, body: JSON.stringify(payload), }); @@ -155,7 +165,7 @@ export default function SMTPSettingsForm({ accessToken }: Props) { // If the save failed setSaveSuccessful(false); const errorData = await response.json(); // assuming the server sends JSON with an error message - setErrorMessage(errorData.message || 'An error occurred while saving.'); + setErrorMessage(errorData.message || "An error occurred while saving."); } }; @@ -163,10 +173,10 @@ export default function SMTPSettingsForm({ accessToken }: Props) { try { if (!validateTestFields()) return; const response = await fetch(`${apiUrl}/settings/smtp/test`, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, }, body: JSON.stringify(settings), }); @@ -176,7 +186,7 @@ export default function SMTPSettingsForm({ accessToken }: Props) { const result = await response.json(); setTestResult({ status: true, - message: 'Success!', + message: "Success!", logs: result.logs || [], }); } else if (response.status === 400) { @@ -184,14 +194,14 @@ export default function SMTPSettingsForm({ accessToken }: Props) { const result = await response.json(); setTestResult({ status: false, - message: result.message || 'Error occurred.', + message: result.message || "Error occurred.", logs: result.logs || [], }); } else { // For any other status, show a static message setTestResult({ status: false, - message: 'Failed to connect to server or process the request.', + message: "Failed to connect to server or process the request.", logs: [], }); } @@ -199,7 +209,7 @@ export default function SMTPSettingsForm({ accessToken }: Props) { // If the request fails to reach the server or there's a network error setTestResult({ status: false, - message: 'Failed to connect to server or process the request.', + message: "Failed to connect to server or process the request.", logs: [], }); } @@ -209,40 +219,46 @@ export default function SMTPSettingsForm({ accessToken }: Props) { const { name, value, type, checked } = e.target; setSettings({ ...settings, - [name]: type === 'checkbox' ? checked : value, + [name]: type === "checkbox" ? checked : value, }); // Also clear errors for that field - setErrors(prevErrors => ({ + setErrors((prevErrors) => ({ ...prevErrors, [name as keyof SMTPSettingsErrors]: undefined, })); }; return ( -
+
SMTP Settings Configure your SMTP server to send emails - +
- + - + {errors.host && (

{errors.host}

)}
- + - + {errors.port && (

{errors.port}

)}
- + - + {errors.from_email && (

{errors.from_email}

)}
- + - +
- + - +
@@ -318,69 +351,88 @@ export default function SMTPSettingsForm({ accessToken }: Props) {
-
-
- - -
- {(isSaveSuccessful === false || deleteSuccessful === false) && ( -
- {errorMessage} -
- )} - {isSaveSuccessful === true && ( -
- SMTP settings saved successfully. -
- )} - {deleteSuccessful === true && ( -
- SMTP settings deleted successfully. -
- )} -
-
+
+
+ + +
+ {(isSaveSuccessful === false || deleteSuccessful === false) && ( +
{errorMessage}
+ )} + {isSaveSuccessful === true && ( +
+ SMTP settings saved successfully. +
+ )} + {deleteSuccessful === true && ( +
+ SMTP settings deleted successfully. +
+ )} +
+
- -
- - - - {errors.to_email && ( -

{errors.to_email}

- )} -
-
- -
-
+ +
+ + + + {errors.to_email && ( +

{errors.to_email}

+ )} +
+
+ +
+
{testResult && ( - - {testResult.status ? 'Success' : 'Failure'} -
- Message:
{testResult.message} -
- Logs: -
{testResult.logs ? testResult.logs.join('\n') : 'No logs available.'}
-
-
-)} - + + {testResult.status ? "Success" : "Failure"} +
+ Message: +
+ {testResult.message} +
+ Logs: +
+              {testResult.logs
+                ? testResult.logs.join("\n")
+                : "No logs available."}
+            
+
+
+ )}
); } diff --git a/keep-ui/app/settings/users-settings.tsx b/keep-ui/app/settings/users-settings.tsx index c0c284690..8f72e24a6 100644 --- a/keep-ui/app/settings/users-settings.tsx +++ b/keep-ui/app/settings/users-settings.tsx @@ -20,24 +20,32 @@ import { User } from "./models"; import UsersMenu from "./users-menu"; import { User as AuthUser } from "next-auth"; import { UserPlusIcon } from "@heroicons/react/24/outline"; -import { AuthenticationType } from 'utils/authenticationType'; +import { AuthenticationType } from "utils/authenticationType"; interface Props { accessToken: string; currentUser?: AuthUser; + selectedTab: string; } interface Config { AUTH_TYPE: string; } -export default function UsersSettings({ accessToken, currentUser }: Props) { +export default function UsersSettings({ + accessToken, + currentUser, + selectedTab, +}: Props) { const apiUrl = getApiURL(); const { data, error, isLoading } = useSWR( - `${apiUrl}/settings/users`, - (url) => fetcher(url, accessToken) + selectedTab === "users" ? `${apiUrl}/settings/users` : null, + (url) => fetcher(url, accessToken), + { revalidateOnFocus: false } ); - const { data: configData } = useSWR('/api/config', fetcher); + const { data: configData } = useSWR("/api/config", fetcher, { + revalidateOnFocus: false, + }); // Determine runtime configuration const authType = configData?.AUTH_TYPE; @@ -47,16 +55,16 @@ export default function UsersSettings({ accessToken, currentUser }: Props) { async function addUser() { let email; let password; - if(authType == AuthenticationType.SINGLE_TENANT ){ + if (authType == AuthenticationType.SINGLE_TENANT) { email = prompt("Enter the user name"); password = prompt("Enter the user password"); - } - else if (authType == AuthenticationType.MULTI_TENANT){ + } else if (authType == AuthenticationType.MULTI_TENANT) { email = prompt("Enter the user email"); password = ""; - } - else{ - alert("Keep cannot add users on NO_AUTH mode. To add users, please set Keep AUTH_TYPE environment variable to either SINGLE_TENANT or MULTI_TENANT"); + } else { + alert( + "Keep cannot add users on NO_AUTH mode. To add users, please set Keep AUTH_TYPE environment variable to either SINGLE_TENANT or MULTI_TENANT" + ); } console.log(email); if (email) { diff --git a/keep-ui/app/settings/webhook-settings.tsx b/keep-ui/app/settings/webhook-settings.tsx index 47405c104..75261fbb8 100644 --- a/keep-ui/app/settings/webhook-settings.tsx +++ b/keep-ui/app/settings/webhook-settings.tsx @@ -17,13 +17,15 @@ interface Webhook { interface Props { accessToken: string; + selectedTab: string; } -export default function WebhookSettings({ accessToken }: Props) { +export default function WebhookSettings({ accessToken, selectedTab }: Props) { const apiUrl = getApiURL(); const { data, error, isLoading } = useSWR( - `${apiUrl}/settings/webhook`, - (url) => fetcher(url, accessToken) + selectedTab === "webhook" ? `${apiUrl}/settings/webhook` : null, + (url) => fetcher(url, accessToken), + { revalidateOnFocus: false } ); const router = useRouter(); @@ -32,6 +34,7 @@ export default function WebhookSettings({ accessToken }: Props) { const example = data.modelSchema.examples[0] as any; example.lastReceived = new Date().toISOString(); + example.fatigueMeter = Math.floor(Math.random() * 100); const code = `curl --location '${data.webhookApi}' \\ --header 'Content-Type: application/json' \\ @@ -42,7 +45,6 @@ export default function WebhookSettings({ accessToken }: Props) { const copyBlockProps = { theme: { ...a11yLight }, customStyle: { - height: "450px", overflowY: "scroll", }, language: "shell", @@ -52,16 +54,16 @@ export default function WebhookSettings({ accessToken }: Props) { }; const tryNow = async () => { - var myHeaders = new Headers(); - myHeaders.append("Content-Type", "application/json"); - myHeaders.append("Accept", "application/json"); - myHeaders.append("X-API-KEY", data.apiKey); + var requestHeaders = new Headers(); + requestHeaders.append("Content-Type", "application/json"); + requestHeaders.append("Accept", "application/json"); + requestHeaders.append("X-API-KEY", data.apiKey); var raw = JSON.stringify(example); const requestOptions = { method: "POST", - headers: myHeaders, + headers: requestHeaders, body: raw, }; diff --git a/keep-ui/app/workflows/workflow-tile.css b/keep-ui/app/workflows/workflow-tile.css index c496c60d3..51cfb088e 100644 --- a/keep-ui/app/workflows/workflow-tile.css +++ b/keep-ui/app/workflows/workflow-tile.css @@ -1,3 +1,3 @@ -.tile-basis { +.workflow-tile-basis { flex-basis: calc(33.333333% - 0.5rem); } diff --git a/keep-ui/app/workflows/workflow-tile.tsx b/keep-ui/app/workflows/workflow-tile.tsx index 491c19158..89aefe29c 100644 --- a/keep-ui/app/workflows/workflow-tile.tsx +++ b/keep-ui/app/workflows/workflow-tile.tsx @@ -386,7 +386,7 @@ function WorkflowTile({ workflow }: { workflow: Workflow }) { .filter(Boolean) as FullProvider[]; const triggerTypes = workflow.triggers.map((trigger) => trigger.type); return ( -
+
{isRunning && (
diff --git a/keep-ui/package-lock.json b/keep-ui/package-lock.json index 9c34c6e13..fc8b82218 100644 --- a/keep-ui/package-lock.json +++ b/keep-ui/package-lock.json @@ -13,7 +13,6 @@ "@fortawesome/free-brands-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/react-fontawesome": "^0.2.0", - "@frigade/react": "^1.35.56", "@headlessui/react": "^1.7.14", "@heroicons/react": "^2.0.18", "@radix-ui/react-icons": "^1.3.0", @@ -290,7 +289,7 @@ "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.12", "postcss-value-parser": "^4.2.0", - "posthog-js": "^1.93.2", + "posthog-js": "^1.93.3", "posthog-node": "^3.1.1", "preact": "^10.13.2", "preact-render-to-string": "^5.2.6", @@ -2519,11 +2518,6 @@ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" }, - "node_modules/@emotion/stylis": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", - "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" - }, "node_modules/@emotion/unitless": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", @@ -2695,115 +2689,6 @@ "react": ">=16.3" } }, - "node_modules/@frigade/react": { - "version": "1.35.56", - "resolved": "https://registry.npmjs.org/@frigade/react/-/react-1.35.56.tgz", - "integrity": "sha512-BbMjhOrMemAfr+Wyq1DzGzAVvc5v+f8rDBSeoClPrtoXFHNbfJJfJzB7iJjidw4KUmw0P723Wu/waAVw5gWNJw==", - "dependencies": { - "core-js-pure": "^3.32.0", - "dompurify": "^3.0.1", - "react-dom": "17.0.2", - "react-error-boundary": "^4.0.4", - "react-portal": "^4.2.2", - "styled-components": "5.3.6", - "styled-system": "^5.1.5", - "swr": "^2.2.0", - "uuid": "^9.0.0", - "zod": "^3.21.4" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - } - }, - "node_modules/@frigade/react/node_modules/@emotion/unitless": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" - }, - "node_modules/@frigade/react/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/@frigade/react/node_modules/react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - }, - "peerDependencies": { - "react": "17.0.2" - } - }, - "node_modules/@frigade/react/node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "node_modules/@frigade/react/node_modules/styled-components": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz", - "integrity": "sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg==", - "hasInstallScript": true, - "dependencies": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/traverse": "^7.4.5", - "@emotion/is-prop-valid": "^1.1.0", - "@emotion/stylis": "^0.8.4", - "@emotion/unitless": "^0.7.4", - "babel-plugin-styled-components": ">= 1.12.0", - "css-to-react-native": "^3.0.0", - "hoist-non-react-statics": "^3.0.0", - "shallowequal": "^1.1.0", - "supports-color": "^5.5.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/styled-components" - }, - "peerDependencies": { - "react": ">= 16.8.0", - "react-dom": ">= 16.8.0", - "react-is": ">= 16.8.0" - } - }, - "node_modules/@frigade/react/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@frigade/react/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@headlessui/react": { "version": "1.7.14", "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.14.tgz", @@ -3387,108 +3272,6 @@ "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.4.0.tgz", "integrity": "sha512-cEjvTPU32OM9lUFegJagO0mRnIn+rbqrG89vV8/xLnLFX0DoR0r1oy5IlTga71Q7uT3Qus7qm7wgeiMT/+Irlg==" }, - "node_modules/@styled-system/background": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/background/-/background-5.1.2.tgz", - "integrity": "sha512-jtwH2C/U6ssuGSvwTN3ri/IyjdHb8W9X/g8Y0JLcrH02G+BW3OS8kZdHphF1/YyRklnrKrBT2ngwGUK6aqqV3A==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/border": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/border/-/border-5.1.5.tgz", - "integrity": "sha512-JvddhNrnhGigtzWRCVuAHepniyVi6hBlimxWDVAdcTuk7aRn9BYJUwfHslURtwYFsF5FoEs8Zmr1oZq2M1AP0A==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/color": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/color/-/color-5.1.2.tgz", - "integrity": "sha512-1kCkeKDZkt4GYkuFNKc7vJQMcOmTl3bJY3YBUs7fCNM6mMYJeT1pViQ2LwBSBJytj3AB0o4IdLBoepgSgGl5MA==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/core": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/core/-/core-5.1.2.tgz", - "integrity": "sha512-XclBDdNIy7OPOsN4HBsawG2eiWfCcuFt6gxKn1x4QfMIgeO6TOlA2pZZ5GWZtIhCUqEPTgIBta6JXsGyCkLBYw==", - "dependencies": { - "object-assign": "^4.1.1" - } - }, - "node_modules/@styled-system/css": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/css/-/css-5.1.5.tgz", - "integrity": "sha512-XkORZdS5kypzcBotAMPBoeckDs9aSZVkvrAlq5K3xP8IMAUek+x2O4NtwoSgkYkWWzVBu6DGdFZLR790QWGG+A==" - }, - "node_modules/@styled-system/flexbox": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/flexbox/-/flexbox-5.1.2.tgz", - "integrity": "sha512-6hHV52+eUk654Y1J2v77B8iLeBNtc+SA3R4necsu2VVinSD7+XY5PCCEzBFaWs42dtOEDIa2lMrgL0YBC01mDQ==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/grid": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/grid/-/grid-5.1.2.tgz", - "integrity": "sha512-K3YiV1KyHHzgdNuNlaw8oW2ktMuGga99o1e/NAfTEi5Zsa7JXxzwEnVSDSBdJC+z6R8WYTCYRQC6bkVFcvdTeg==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/layout": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/layout/-/layout-5.1.2.tgz", - "integrity": "sha512-wUhkMBqSeacPFhoE9S6UF3fsMEKFv91gF4AdDWp0Aym1yeMPpqz9l9qS/6vjSsDPF7zOb5cOKC3tcKKOMuDCPw==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/position": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/position/-/position-5.1.2.tgz", - "integrity": "sha512-60IZfMXEOOZe3l1mCu6sj/2NAyUmES2kR9Kzp7s2D3P4qKsZWxD1Se1+wJvevb+1TP+ZMkGPEYYXRyU8M1aF5A==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/shadow": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/shadow/-/shadow-5.1.2.tgz", - "integrity": "sha512-wqniqYb7XuZM7K7C0d1Euxc4eGtqEe/lvM0WjuAFsQVImiq6KGT7s7is+0bNI8O4Dwg27jyu4Lfqo/oIQXNzAg==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/space": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/space/-/space-5.1.2.tgz", - "integrity": "sha512-+zzYpR8uvfhcAbaPXhH8QgDAV//flxqxSjHiS9cDFQQUSznXMQmxJegbhcdEF7/eNnJgHeIXv1jmny78kipgBA==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/typography": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@styled-system/typography/-/typography-5.1.2.tgz", - "integrity": "sha512-BxbVUnN8N7hJ4aaPOd7wEsudeT7CxarR+2hns8XCX1zp0DFfbWw4xYa/olA0oQaqx7F1hzDg+eRaGzAJbF+jOg==", - "dependencies": { - "@styled-system/core": "^5.1.2" - } - }, - "node_modules/@styled-system/variant": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@styled-system/variant/-/variant-5.1.5.tgz", - "integrity": "sha512-Yn8hXAFoWIro8+Q5J8YJd/mP85Teiut3fsGVR9CAxwgNfIAiqlYxsk5iHU7VHJks/0KjL4ATSjmbtCDC/4l1qw==", - "dependencies": { - "@styled-system/core": "^5.1.2", - "@styled-system/css": "^5.1.5" - } - }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", @@ -4421,6 +4204,8 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz", "integrity": "sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==", + "optional": true, + "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", "@babel/helper-module-imports": "^7.22.5", @@ -4878,16 +4663,6 @@ "url": "https://opencollective.com/core-js" } }, - "node_modules/core-js-pure": { - "version": "3.32.2", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.32.2.tgz", - "integrity": "sha512-Y2rxThOuNywTjnX/PgA5vWM6CZ9QB9sz9oGeCixV8MqXZO70z/5SHzf9EeBrEBK0PN36DnEBBu9O/aGWzKuMZQ==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -5422,11 +5197,6 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, - "node_modules/dompurify": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.5.tgz", - "integrity": "sha512-F9e6wPGtY+8KNMRAVfxeCOHU0/NPWMSENNq4pQctuXRqqdEPW7q3CrLbR5Nse044WwacyjHGOMlvNsBe1y6z9A==" - }, "node_modules/domutils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", @@ -8312,9 +8082,9 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/posthog-js": { - "version": "1.93.2", - "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.93.2.tgz", - "integrity": "sha512-0e2kqlb4kB1/Q9poLFlMF+SUrW+DCzNBHTJuUKl177euE4LChkJipSjy2vpq98qtJ2K3Hxw7ylHf2C+dZCx4RA==", + "version": "1.93.3", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.93.3.tgz", + "integrity": "sha512-jEOWwaQpTRbqLPrDLY6eZr7t95h+LyXqN7Yq1/K6u3V0Y1C9xHtYhpuGzYamirVnCDTbVq22RM++OBUaIpp9Wg==", "dependencies": { "fflate": "^0.4.1" } @@ -8598,17 +8368,6 @@ "react": "^18.2.0" } }, - "node_modules/react-error-boundary": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.11.tgz", - "integrity": "sha512-U13ul67aP5DOSPNSCWQ/eO0AQEYzEFkVljULQIjMV0KlffTAhxuDoBKdO0pb/JZ8mDhMKFZ9NZi0BmLGUiNphw==", - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "peerDependencies": { - "react": ">=16.13.1" - } - }, "node_modules/react-icons": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.9.0.tgz", @@ -8662,18 +8421,6 @@ "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18" } }, - "node_modules/react-portal": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/react-portal/-/react-portal-4.2.2.tgz", - "integrity": "sha512-vS18idTmevQxyQpnde0Td6ZcUlv+pD8GTyR42n3CHUQq9OHi1C4jDE4ZWEbEsrbrLRhSECYiao58cvocwMtP7Q==", - "dependencies": { - "prop-types": "^15.5.8" - }, - "peerDependencies": { - "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0", - "react-dom": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0" - } - }, "node_modules/react-remove-scroll": { "version": "2.5.4", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.4.tgz", @@ -9803,26 +9550,6 @@ } } }, - "node_modules/styled-system": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/styled-system/-/styled-system-5.1.5.tgz", - "integrity": "sha512-7VoD0o2R3RKzOzPK0jYrVnS8iJdfkKsQJNiLRDjikOpQVqQHns/DXWaPZOH4tIKkhAT7I6wIsy9FWTWh2X3q+A==", - "dependencies": { - "@styled-system/background": "^5.1.2", - "@styled-system/border": "^5.1.5", - "@styled-system/color": "^5.1.2", - "@styled-system/core": "^5.1.2", - "@styled-system/flexbox": "^5.1.2", - "@styled-system/grid": "^5.1.2", - "@styled-system/layout": "^5.1.2", - "@styled-system/position": "^5.1.2", - "@styled-system/shadow": "^5.1.2", - "@styled-system/space": "^5.1.2", - "@styled-system/typography": "^5.1.2", - "@styled-system/variant": "^5.1.5", - "object-assign": "^4.1.1" - } - }, "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", diff --git a/keep-ui/package.json b/keep-ui/package.json index c7067b1a0..d19fc9816 100644 --- a/keep-ui/package.json +++ b/keep-ui/package.json @@ -14,7 +14,6 @@ "@fortawesome/free-brands-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/react-fontawesome": "^0.2.0", - "@frigade/react": "^1.35.56", "@headlessui/react": "^1.7.14", "@heroicons/react": "^2.0.18", "@radix-ui/react-icons": "^1.3.0", @@ -291,7 +290,7 @@ "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.12", "postcss-value-parser": "^4.2.0", - "posthog-js": "^1.93.2", + "posthog-js": "^1.93.3", "posthog-node": "^3.1.1", "preact": "^10.13.2", "preact-render-to-string": "^5.2.6", diff --git a/keep/api/models/alert.py b/keep/api/models/alert.py index 3252e8bf8..245e8bf69 100644 --- a/keep/api/models/alert.py +++ b/keep/api/models/alert.py @@ -44,14 +44,16 @@ class Config: "duplicateReason": None, "service": "backend", "source": ["keep"], - "message": "Alert message", - "description": "Alert description", + "message": "Keep: Alert message", + "description": "Keep: Alert description", "severity": "critical", "fatigueMeter": 0, "pushed": True, "event_id": "1234", - "url": "https://www.google.com/search?q=open+source+alert+management", - "fingerprint": "Alert name", + "url": "https://www.keephq.dev?alertId=1234", + "labels": {"key": "value"}, + "ticket_url": "https://www.keephq.dev?enrichedTicketId=456", + "fingerprint": "1234", } ] } diff --git a/keep/workflowmanager/workflowscheduler.py b/keep/workflowmanager/workflowscheduler.py index f6d7bb00f..31966cf9d 100644 --- a/keep/workflowmanager/workflowscheduler.py +++ b/keep/workflowmanager/workflowscheduler.py @@ -343,10 +343,6 @@ def _finish_workflow_execution( status: WorkflowStatus, error=None, ): - # get the previous workflow execution id - previous_execution = get_previous_execution_id( - tenant_id, workflow_id, workflow_execution_id - ) # mark the workflow execution as finished in the db finish_workflow_execution_db( tenant_id=tenant_id, @@ -355,10 +351,15 @@ def _finish_workflow_execution( status=status.value, error=error, ) + # get the previous workflow execution id + previous_execution = get_previous_execution_id( + tenant_id, workflow_id, workflow_execution_id + ) # if error, send an email - if ( - status == WorkflowStatus.ERROR - and previous_execution.status != WorkflowStatus.ERROR.value + if status == WorkflowStatus.ERROR and ( + previous_execution + is None # this means this is the first execution, for example + or previous_execution.status != WorkflowStatus.ERROR.value ): workflow = get_workflow_db(tenant_id=tenant_id, workflow_id=workflow_id) self.logger.info(