From 9729246eaf0aa97993bc7e60060433b9b0a867b9 Mon Sep 17 00:00:00 2001 From: Vladimir Filonov Date: Mon, 18 Nov 2024 16:58:34 +0400 Subject: [PATCH 01/24] feat: provider appdynamics integration using access_token (#2500) --- .../documentation/appdynamics-provider.mdx | 19 ++- .../appdynamics_provider.py | 131 ++++++++++++++---- 2 files changed, 114 insertions(+), 36 deletions(-) diff --git a/docs/providers/documentation/appdynamics-provider.mdx b/docs/providers/documentation/appdynamics-provider.mdx index eef0e7a76..8948964be 100644 --- a/docs/providers/documentation/appdynamics-provider.mdx +++ b/docs/providers/documentation/appdynamics-provider.mdx @@ -7,19 +7,26 @@ description: "AppDynamics provider allows you to get AppDynamics `alerts/actions ## Authentication Parameters The AppDynamics provider requires the following authentication parameter: -- `AppDynamics Username`: Required. This is your AppDynamics account username. -- `AppDynamics Password`: This is the password associated with your AppDynamics Username. +- `AppDynamics Access Token`: Required if username/password is not provided for Bearer token authentication. +- `AppDynamics Username`: Required for Basic Auth authentication. This is your AppDynamics account username. +- `AppDynamics Password`: Required for Basic Auth authentication. This is the password associated with your AppDynamics Username. - `AppDynamics Account Name`: This is your account's name. - `App Id`: The Id of the Application in which you would like to install the webhook. - `Host`: This is the hostname of the AppDynamics instance you wish to connect to. It identifies the AppDynamics server that the API will interact with. ## Connecting with the Provider +1. Ensure you have a AppDynamics account with the necessary [permissions](https://docs.appdynamics.com/accounts/en/cisco-appdynamics-on-premises-user-management/roles-and-permissions). The basic permissions required are `Account Owner` or `Administrator`. Alternatively you can create an account (instructions)[https://docs.appdynamics.com/accounts/en/global-account-administration/access-management/manage-user-accounts] + +### Basic Auth authentication Obtain AppDynamics Username and Password: -1. Ensure you have a AppDynamics account with the necessary [permissions](https://docs.appdynamics.com/accounts/en/cisco-appdynamics-on-premises-user-management/roles-and-permissions). The basic permissions required are `Account Owner` or `Administrator`. Alternatively you can create an account (instructions)[https://docs.appdynamics.com/accounts/en/global-account-administration/access-management/manage-user-accounts] -2. Find your account name [here](https://accounts.appdynamics.com/overview). -3. Determine the Host [here](https://accounts.appdynamics.com/overview). -4. Get the appId of the Appdynamics instance in which you wish to install the webhook into. +1. Find your account name [here](https://accounts.appdynamics.com/overview). + +OR create Access Token: +1. Follow instructions [here](https://docs.appdynamics.com/appd/23.x/latest/en/extend-appdynamics/appdynamics-apis/api-clients) + +1. Determine the Host [here](https://accounts.appdynamics.com/overview). +2. Get the appId of the Appdynamics instance in which you wish to install the webhook into. ## Webhook Integration Modifications diff --git a/keep/providers/appdynamics_provider/appdynamics_provider.py b/keep/providers/appdynamics_provider/appdynamics_provider.py index d0f5e8867..84205eb83 100644 --- a/keep/providers/appdynamics_provider/appdynamics_provider.py +++ b/keep/providers/appdynamics_provider/appdynamics_provider.py @@ -6,11 +6,12 @@ import json import tempfile from pathlib import Path -from typing import List +from typing import List, Optional from urllib.parse import urlencode, urljoin import pydantic import requests +from dateutil import parser from keep.api.models.alert import AlertDto, AlertSeverity from keep.contextmanager.contextmanager import ContextManager @@ -28,14 +29,6 @@ class AppdynamicsProviderAuthConfig: """ AppDynamics authentication configuration. """ - - appDynamicsUsername: str = dataclasses.field( - metadata={ - "required": True, - "description": "AppDynamics Username", - "hint": "Your Username", - }, - ) appDynamicsAccountName: str = dataclasses.field( metadata={ "required": True, @@ -43,14 +36,7 @@ class AppdynamicsProviderAuthConfig: "hint": "AppDynamics Account Name", }, ) - appDynamicsPassword: str = dataclasses.field( - metadata={ - "required": True, - "description": "Password", - "hint": "Password associated with your account", - "sensitive": True, - }, - ) + appId: str = dataclasses.field( metadata={ "required": True, @@ -66,6 +52,47 @@ class AppdynamicsProviderAuthConfig: }, ) + appDynamicsAccessToken: Optional[str] = dataclasses.field( + default=None, + metadata={ + "description": "AppDynamics Access Token", + "hint": "Access Token", + "config_sub_group": "access_token", + "config_main_group": "authentication", + }, + ) + + appDynamicsUsername: Optional[str] = dataclasses.field( + default=None, + metadata={ + "description": "Username", + "hint": "Username associated with your account", + "config_sub_group": "basic_auth", + "config_main_group": "authentication", + }, + ) + appDynamicsPassword: Optional[str] = dataclasses.field( + default=None, + metadata={ + "description": "Password", + "hint": "Password associated with your account", + "sensitive": True, + "config_sub_group": "basic_auth", + "config_main_group": "authentication", + }, + ) + + @pydantic.root_validator + def check_password_or_token(cls, values): + username, password, token = ( + values.get("appDynamicsUsername"), + values.get("appDynamicsPassword"), + values.get("appDynamicsAccessToken") + ) + if not (username and password) and not token: + raise ValueError("Either username/password or access token must be provided") + return values + class AppdynamicsProvider(BaseProvider): """Install Webhooks and receive alerts from AppDynamics.""" @@ -121,7 +148,7 @@ def validate_config(self): f"https://{self.authentication_config.host}" ) - def __get_url(self, paths: List[str] = [], query_params: dict = None, **kwargs): + def __get_url(self, paths: List[str] = None, query_params: dict = None, **kwargs): """ Helper method to build the url for AppDynamics api requests. @@ -133,6 +160,7 @@ def __get_url(self, paths: List[str] = [], query_params: dict = None, **kwargs): # url = https://baseballxyz.saas.appdynamics.com/rest/api/2/issue/createmeta?projectKeys=key1 """ + paths = paths or [] url = urljoin( f"{self.authentication_config.host}/controller", @@ -145,17 +173,41 @@ def __get_url(self, paths: List[str] = [], query_params: dict = None, **kwargs): return url + def get_user_id_by_name(self, name: str) -> Optional[str]: + self.logger.info("Getting user ID by name") + response = requests.get( + url=self.__get_url(paths=["controller/api/rbac/v1/users/"]), + headers=self.__get_headers(), + auth=self.__get_auth(), + ) + if response.ok: + users = response.json() + for user in users["users"]: + if user["name"].lower() == name.lower(): + return user["id"] + return None + else: + self.logger.error( + "Error while validating scopes for AppDynamics", extra=response.json() + ) + def validate_scopes(self) -> dict[str, bool | str]: authenticated = False administrator = "Missing Administrator Privileges" self.logger.info("Validating AppDynamics Scopes") + + user_id = self.get_user_id_by_name(self.authentication_config.appDynamicsAccountName) + + url = self.__get_url( + paths=[ + "controller/api/rbac/v1/users/", + user_id, + ] + ) + response = requests.get( - url=self.__get_url( - paths=[ - "controller/api/rbac/v1/users/name", - self.authentication_config.appDynamicsUsername, - ] - ), + url=url, + headers=self.__get_headers(), auth=self.__get_auth(), ) if response.ok: @@ -178,11 +230,19 @@ def validate_scopes(self) -> dict[str, bool | str]: return {"authenticated": authenticated, "administrator": administrator} + def __get_headers(self): + if self.authentication_config.appDynamicsAccessToken: + return { + "Authorization": f"Bearer {self.authentication_config.appDynamicsAccessToken}", + } + def __get_auth(self) -> tuple[str, str]: - return ( - f"{self.authentication_config.appDynamicsUsername}@{self.authentication_config.appDynamicsAccountName}", - self.authentication_config.appDynamicsPassword, - ) + if self.authentication_config.appDynamicsUsername and self.authentication_config.appDynamicsPassword: + return ( + f"{self.authentication_config.appDynamicsUsername}@{self.authentication_config.appDynamicsAccountName}", + self.authentication_config.appDynamicsPassword, + ) + def __create_http_response_template(self, keep_api_url: str, api_key: str): keep_api_host, keep_api_path = keep_api_url.rsplit("/", 1) @@ -203,11 +263,12 @@ def __create_http_response_template(self, keep_api_url: str, api_key: str): res = requests.post( self.__get_url(paths=["controller/actiontemplate/httprequest"]), files={"template": temp}, + headers=self.__get_headers(), auth=self.__get_auth(), ) res = res.json() temp.close() - if res["success"] == "True": + if res["success"] == "True" or res["success"] is True: self.logger.info("HTTP Response template Successfully Created") else: self.logger.info("HTTP Response template creation failed", extra=res) @@ -228,6 +289,7 @@ def __create_action(self): "actions", ] ), + headers=self.__get_headers(), auth=self.__get_auth(), json={ "actionType": "HTTP_REQUEST", @@ -272,6 +334,7 @@ def setup_webhook( "policies", ] ), + headers=self.__get_headers(), auth=self.__get_auth(), ) @@ -290,6 +353,7 @@ def setup_webhook( policy["id"], ] ), + headers=self.__get_headers(), auth=self.__get_auth(), ).json() if policy_config not in curr_policy["actions"]: @@ -313,6 +377,7 @@ def setup_webhook( policy["id"], ] ), + headers=self.__get_headers(), auth=self.__get_auth(), json=curr_policy, ) @@ -329,10 +394,16 @@ def _format_alert( id=event["id"], name=event["name"], severity=AppdynamicsProvider.SEVERITIES_MAP.get(event["severity"]), - lastReceived=event["lastReceived"], + lastReceived=parser.parse(event["lastReceived"]).isoformat(), message=event["message"], description=event["description"], event_id=event["event_id"], url=event["url"], source=["appdynamics"], ) + + @staticmethod + def parse_event_raw_body(raw_body: bytes | dict) -> dict: + if isinstance(raw_body, dict): + return raw_body + return json.loads(raw_body, strict=False) From 0445e57e4cf21ba61e2de1ac3a15721bd657c61c Mon Sep 17 00:00:00 2001 From: Vladimir Filonov Date: Mon, 18 Nov 2024 17:07:27 +0400 Subject: [PATCH 02/24] feat: new version 0.29.1 (#2526) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6ed27766c..ee75f9569 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "keep" -version = "0.29.0" +version = "0.29.1" description = "Alerting. for developers, by developers." authors = ["Keep Alerting LTD"] readme = "README.md" From ddcf62da8b4d18b1849124a201aaef1cf81869ce Mon Sep 17 00:00:00 2001 From: Tal Date: Mon, 18 Nov 2024 15:08:16 +0200 Subject: [PATCH 03/24] feat(ui): topology logos if possible (#2521) --- keep-ui/app/topology/model/models.ts | 1 + keep-ui/app/topology/ui/map/service-node.tsx | 13 +++++++++++++ keep-ui/public/icons/go-icon.png | Bin 0 -> 15475 bytes keep-ui/public/icons/nextjs-icon.png | Bin 0 -> 10884 bytes keep/api/utils/tenant_utils.py | 6 ++++++ 5 files changed, 20 insertions(+) create mode 100644 keep-ui/public/icons/go-icon.png create mode 100644 keep-ui/public/icons/nextjs-icon.png diff --git a/keep-ui/app/topology/model/models.ts b/keep-ui/app/topology/model/models.ts index 23308640c..2d4c75a32 100644 --- a/keep-ui/app/topology/model/models.ts +++ b/keep-ui/app/topology/model/models.ts @@ -22,6 +22,7 @@ export interface TopologyService { ip_address?: string; mac_address?: string; manufacturer?: string; + category?: string; application_ids: string[]; // Added on client to optimize rendering applications: TopologyApplicationMinimal[]; diff --git a/keep-ui/app/topology/ui/map/service-node.tsx b/keep-ui/app/topology/ui/map/service-node.tsx index a15c7e3c7..b4e43b54a 100644 --- a/keep-ui/app/topology/ui/map/service-node.tsx +++ b/keep-ui/app/topology/ui/map/service-node.tsx @@ -7,6 +7,7 @@ import { ServiceNodeType, TopologyService } from "../../model/models"; import { Badge } from "@tremor/react"; import { getColorForUUID } from "@/app/topology/lib/badge-colors"; import { clsx } from "clsx"; +import Image from "next/image"; const THRESHOLD = 5; @@ -107,6 +108,18 @@ export function ServiceNode({ data, selected }: NodeProps) { onMouseEnter={() => setShowDetails(true)} onMouseLeave={() => setShowDetails(false)} > + {data.category && ( +
+ {data.category} +
+ )} {data.display_name || data.service} {alertCount > 0 && ( SK6{^CpR@OwaIMEmg!t6>5D0`&MOj`40>NU2Krm5wIN*~q zm0c3>fW1~WaDhO$8PUHO9)(iw5C}a)MP3Hsk+D6m>1?F*h1Glxf8@7mAc9oA&@k}S zMuPMsbc~a?89K3YFU2shlstg?aOM}jYE+iRejj;FU7%v`XHt%oBsNIXN!I0oFUhT= zvJ)}p?ZqXfc)a;Y!H&X z&4Nvqyw!ba(WzlBYt-J!;v=GZGvzwB?Zx?_4*sOg1F*9vFZ=GRZ$;iyp4H^&<~J#b zTI0JfkLo-VJ%i^z&cC|LPl{-h2IF1_)M!aPW{{5_VpDU<=3wI%7!2ursV7DIGL#59 z^{R39Hpk(IfGZd?#2nYYy#>aAq*-7eoHfB`8}{W3=zXatO$(#P`J5V7ao8C+kH-vI z;`|cy?s_fSi9^-@-sSzffA43v>8}-g#tlX-Hpo02B^3Z%8%rH2yGO zN&eA}j)>FPxwKXVV)C;IVJ@qDy=1}bjtu#Du!4Z~mPb!%-0CWfl`Ue$deUVsiUr7F zCtGWL^kzG|UNo48_;E}*nS6#w(pbip>*Pnz@J%qF#Bh_`$$@X=b_l7Ji~^R?&KDHx z9Fu$^Ac?SjF5|zNA&9%)ij`48d7u{Dn;Xv+vm5F60P`ZPHX4KFr8b1||@}7)3v-VR-pyynPthI$J1-mjZ8+#t4j% zj~$7gUXVrNOZ7LuSh4C&>5K2n*_EnF0_VKTh1!<)PuNA=B8j^JFprTSeDD>~dMf3l zhWlggT8;(brhN-+s)5?0=(P^KhsO+|q$n0@7I9`>IvtVRJ1nzsFsaZL=fZfFKXko} z1^GwyixQO##?FLOwF^-sE!eU?RR>GY3I^KH@#lFY9eDVz@u&t#FOOgHY#7o)fYm{0 zHvZF7eeS!Q6Z}CsSIb_{oPnn4jm1z-7J-@yu5Fbu^$`(xnqS`nF?dTa&pNaTIgTYm zNs%sOAB-wY3!hn68qpD;@*RR+gN=}N9hh{&XQG|VaThLy744jasJ37r z=HoFZK+JVYV}FODZ(;#sJqJRp^eqf`=Hs%}Kg7Q$HK!M5>ofOnDGiZ)&4)O!k2<&+05a-B24 zCZakj7=3gXjdzrJNqWVx7k}X}D6#?uUT>(ow{D$|uXp6THWTHELEUY#ED<-nO^{oq zMiuAP`}$DWjLMDoJ|DKHdI9((?r;$sUKKwi?q*d-6|4$_;KcUlT!da5G2(kSYAT5e zB+ zRYF8~V0|2w;!hj~9L5C5lKH&z+npJ(=O5+_ZE&GKB3285E90_OXGJ8 zRJD{ouIDvuBNvst(MM($E32z#Ok8$zWmL4NG?NNwD-~AHwo)_W9f@6W?@KpeuL}oa zS*-}SV_l5uLIZeIe=o14b;~^QGvrxayZ1F)peE$QRh8*rXsJ&;nB6+|;P$64APj2I zttq=8q;M+vJw)541*OdXz67RpElZCv`@CVGSrk8F`N{1T6-^jKvU(=${$VpIw_#H( z<9yZMF&^4~}#3lRxiB zrsd~|@l3d_>;a*3v?(sMqSpku_Zxq2cF|(%dHvwh4}wX$_+aM9Kw%~0mdz<?JFnSI;z4%_CEO^XV5+HW!90x7r%F=E-w$-y`N0xp@`!u9!viv)`{^1VZ z%T+YJ!Vu~>C@j*qWTJY!5(b9qW=+nKhRp+Srv6B-+;ljYYgnn)%z~iD(Ae}|MmTLp zb&9rNj6wpj1PXb={j|puy`eC>1s;%yJ{-x|C91*#gpBc+a_%VHU0F{mqeQ z1mOcw0qmQH^_94-U4Ob`z1Z*-VKGOAS|HYFSn0W1jS3rv)U1ebb z5e59;Dbso|j7-bS?jMU`rwB*`#*)=+YXW)j7TwhKjMv60G1+=je~5es zalAxcEdT2J0anYBr`17SdAbn738T?DDxU<+6TR>3=}v_ zZ=H%fOO*ML40fxdFwQOzYmaLrr~*%dQW>I|W0>a*_z8OXM;f}l?Ap)&-SImFXOvFr z5r51P*u{jQJ1@=Y;`P)DXr$(9Fm73lODEa`ec?}X>8s`i%0;!*?ShTp5TXxY5O?v` zYzD@Py_r`sP2{(o1V(BSAlBg8dh+`NyKj-n%2Uer|YpH0Y?rz_g z`9YiNABU;i{^ujVdgY|jL4+DfT`XQq9-=*OIFfN3oh5dOiT8&I z?#nXco9|XlWke3Gv>!|*rRJDzYAd_WxO)yACX()Z|&3Ev?2sg!->FkkTc z$T?eQRhgZ5$A96=^`65g)4@i$g~zqVCirD(PVrNw$;^Kq7q{kLWw_nXDH)eiJG=Ng z4P$0|{5`RLKP)u-5=`i95C`G{dYf~F^vij_LJ?_^?VoK2Jo(x$In&>SE zSQZ#EK^9FiT)yMdWKGBsakowxo%iDNClOo%dmqLi<-Ihyu9i8H?d&e4U+VR;dQ^W> zH^)~Fre~~I#_{en4Y;t%Bq@l3<-6^N_Zt{QHCK)aBjVhxvEb^=4?H!^_Oak@9*b{a zm7Y%7Na4(a&G3oCl>rA)KhdSc%?(6y`rbM07Bg<>0?(a`ZHkA!ivkH&Cdv0NB zKTL#^$-Q~Sa_rcglC4PC@Iwft1z=4|4S{EoyqLE)owm!1d=9#C?N6H=b;pS)|KSi) z1^n_Czc)hQWHMml)Gk$X1P6A_5|8PGMVH*hfsf7gaDRK_GV&$jr=*1c!8gi+{%{u> z%x=%|$(}B_sutGJb;{EjIw6AVJQ!n(<<`?MMisstA${SZEgGWhDAY2?ci;0*zB`v! zwT0<#hxNS4JCAa(NmXN-9qSvkRC_~YZ%PdIC^y7HNxF?=DVakZDJ56(X&!{kBq4uY zKIy`%(x7sp4|H(44jb`E4S`z?LEnXky1#zh_)v|gnd)low}7D9Gop>^3hm|BLcdRg z-xB|z&3_QbYe~PM+#8CT-@d9b9TaSO5Th0JqEHH=Pa&<&w*B6~aL*sXVi@(NRIEex zMjFME_3K)OxvTf%4S^mPMK7%&RNu{Z*QPn8)xnool_`N z_xF5`&p(}#Dha+_scx;_(h%$-W9Sm9H6#BmHl*$KBZ}!t()hc;z?78l^}vD27N+zU z0SZgilMUn+aVbY%@~6vLPg)uOBS!}tAII1mN?{`gc~`vEcRJTQ<4zB2iK0H-_^KzG z>LXpOHUzG;h;y;(ou_$qzgp;jJ?m{MMN%0oCNpA_a?#B;dXG}xkKE%-Ec?OKm)_=T zY5&1YbSk0nS+2Y%?IOJzd*JOK^+mASXM!O=!d8=JFJeIUx`GKzso5Q_CTRYSSl))I z^XIU8CtV93@d;-svSARH7*#m_k+u0cL@mQX#L{stOWVQydTOCqRMk8Uqg~#8u0xwD z7fYJhq^a61-kt8xC#+Jolsc8&l}|PEFNV0P-Lh9%n;9RjOx1m;3(e;Ru^F;+Ig+$O zoyz7K=wIumw`Tn{J0F;Uv&nP!^O&ufA?O)g-Kv|57JFY0jAxhp$oP|&ni&8&q-}afySJf%XF+B-EW9w-`NZ^ zZMnsh2ijwY`1~{`G{lo&h7=Xr%G`4t4_Z9imIbkL;;_(jog-hv?A^eW>^mM2a%C$S zbHniK84U-&Qn81M{yv?%+878`h`M~Dybu-~n`Y_By9?YzVu}waCTB}OloqhLgTaBR zUWT?KFFq-U9TQ6BLaa7dgi2TS63?O&%3+2zrEUQgLM-c($`S6dTI}`GeftWjF~B7o zs>P1Mi=hWMs9LJ*6ZTR^_{;UgAXKc{FA<``&LLy^c@Pf3ajxq9By4|co%i7fHJG#h zr90Vwlo+~XzzPqg1aq>}0brV!C-gph&*;m%U#tT)w?Z*>80>ZGm&D<%B}Kn2Sghr| zVrDt=1F=Wo&@b}v{S+?59ExIE#$#G?`tLyv*2VmerfP*d;lNk-EV@e$3+ zN*6L0cDF4)8UFaNrDJymONUCWsO7*sKzZYR7rj5*I8shzI^gZ8$}_doF9K4cvb^Pw z%*4;q{Qit{=})^KC{0%i>Ty`BvT27SIQ-%$sHwNc3LNXd8)M$N`i)5Js;O}M6qaeg zs=g~vPPsT5_N0}jK>KKjyLzT+d6#yvM7q{Evp9UaJQOF|JL#)q0*>=UJtef=bWn4` zD_M=4?rhx1622(BcQ2ATfeoChkIdTqUii|QtF1eNx<|9~&_cnm05W*K;7kQ`VMF9g zyCiqiREaS`8egt_`}o}zsana~d(y6qe2p;^PA@Qz^HFw&$d^^s05`{?Zm7ak!=qr# z_=)b1m{5EACwE+hr==amTFl%1E{E0Ef}hdQwte>#=U@h!X4Bft@Xhtdy40|vN9);- zhxH)x8BJ9q!o=P$4T%!HoqPUDe6v!EzXY+~W2p5_&sB2#U>Qb`nmlmi#j@wh%lz*_ z;QP5q8!%I-MCQ>9!oy9ZYlSQyX=x zNMa=qd=Jfdi+!VCz?0=3a2j`Gk6>|Hv%7=2DQxH`kQ40U!>B78K3K?!qoKN2x1O(| z)P8ftrN#VLdXwKZPL?mxm3ZJIAVb*T%6nSz9n<&$mYG0x z9lfs5IG?_rX3+=|pNt`!O2P+Rqq_HBvBGsc5Us@G5#kN8?qye5NHqgjU~O02BjwW2 zPtH3%go-(0F*YR$eLt)@_?@p|*PFWM&wOLsWwjE!P7zU8tTCg9RQrqq*0-*FZmMJN zCaln)mw(Zm;(r*5`>8m)t1dy|rZ*H&JCHQ_i5INu%Y19I>XL8>jD+B-+Fr~@@mHu> zkNNvd4f{;dtI6v2xIS~W57dG#z2->mnZ68?5|nt-L2y>@!b!}i8!AT%ye`be^_eln zrN7v~PX&m7{vzSC3%ij2nIjwSR2p|$UH|*XEmQoxH$x-ZQI89|+Q!T6tw1t+9e~ie znVX634_bWu;MZobpRl?#o6LVCoyxz}IU4TTWxKnW+V1HDv^EN`s7voD{P$A(T9})_ zubGM7c9l)B)^zYFAqNH$S!YQCmHq^QpB_9$P)Qhq!;}CKi2@0xzWPytD)eEID%4yW z43pTH6K^Q0#L#2Lm?c%^Eqs)(@drq8ftEre431)=e~xtI6%>B0B>`f^bqIXL!JSek z5zDIbXq3zx)M^5*nt5&V>!16eKm9c{Mi>wXPAtc^tlr{7JF=*_fbFB)QXQ|NReq%- zK-hadiBn0qXVTgJ&>p7}<5WyZ_#J&hju)DcNCRI5OC|JAQrWf35le6~)F(5a2Eqtx zGuujqUGJtMJ>zpjOyOEsiE#PTg%aIIfFnP^QGLZxv;>QuO4HB@IW%n#-q-N#X}P1H zl?EF|72P9>75k&P4;-+*Yz|mSz-vBD^ip+qEyRaHfp|Go?)u@D{2}PNV4p(dqgd8E zO*l(G5URw#3uBAj7d<<8GitUxH!m02&P`C&eV-ZuG>$mmb(Gg-PMmi>YJM)Ady7!` zCpJ~478D0QOj>${lYws)FtFF6VKgy9Lv;Y?e}{eg?LU|@pq z{4B7G2^oBI!F9XG#cYe_0rO_t@u&SDI4@a*sxvE#_Ki7lzqWMKS z5_db9r_nEKYa~D*w0F>V0TKd=x86L;rs-9Y-+f|0%S$#LAS;TsEbnRY3zyk^FhL8N zB@J_TNzg_Xr-sUIN-_3@IG^5^Bg^fo!ioY;(KeC6*`AfaIlDPIE-MoQYvVShDU^XR zKV(h_z;qkXGwZ*ZIu-0*4q#Qu{5CI2gbgqc)QKgzBGvXODhoZT)80{30w!EIp#}{w zS%0R$9|8xJ=O6adK3?!0FTGaMRQKjV8pyUR8y~Np<{4Phf;-B{C_(!nL=TjnHv5#v z@N==YRn{)GViV!Au-i$pToQIU;=);XWDrw?TMb3Rx-?(_g9!#ISw7~-vZ5HPQ0(uo z9OdgB-g;Mcsx*d#fUArKsKB5}{%(3lA@77>4rE(ZD*WdLYb+m_spNcDiH=Cs?ZF_H zInh*lXJh#v!bwoSyx4f}I7d^hazqXXML!n}D0|`vcftTtkYs`Cs2%LVF+jzT3v`{m zdUo487-u`rr~)b26Pj1*Y^k%ci__^+?$ZN6(Y7KxbJ!@5-}mXOTf3&!hfTny%&~xx z^d~w(wp9wd_5hrD3@WC#9mFXLRiPZ99l#^5R20+kLh1VL+G_M5+6ZOzhJK9;=Dgzf zU~ij%F{kv0$KDHNNk$`sHb6jHLZHru1L+vm`S*Xvzl`*2G|?iO4tfZjsiztg#{Pp5 z+lm{Dbtr>Fa9|7%Wkz)WB`nJ6aHO3UY$^Q?K%zS%hz}ZxXcyp?g()VU6N@(R-PZUI z^3db*Jomt_zzF3y%d~mnOaFI}?S02sG!#thlVzUXVQ&q^uct_eJ^R6TFA%T4)LROr788zc7-upcxQ(}%`ivmKt=|3pe-p_0G|8oL--JBNr zdJlnO>RDS?v_LO0(d|jy&3!&G`O&wq*A0gUmnw^qmKGGgA75-)M=z}dBmk;l+mG4! zqVv!zUAi}?ocyl80qIa`$flZzy+BI_!%gIfc2>XE*mt)lTE*uzxt(Xrb6*JLcB&!djtM=~qkx*6-QGikzpvho-#y7zGV__42t9ia6pdd1;P4xYoQW}YWEeNs z`qwUg7qSa<7#iYYWlUg;0fH!%nn5PkU;V6FUnAO}J${}qqe%PHoVB!ntHVwM*nliL zxaae<0jxTx;~#4ljg{)jEn=;Ne5tOKD4z?2z0l_3%bebjd_!#=77Hxe1A*Fie(_99 zHvGT2F3+1-tj89=@K3MB^0l*@C8C<$v{hp%nSgszY?m9jw6SeMOl-BC^+?N)Lt_`7{5`fbGOtzFC-*yP%!BOmMmJ}Tc zex=Wx*l_*w_j1HTHk{zhFq6$l%a@=$T~5T5RV#@{_Q$P3@a@|I{8Ucg*RqF%+@Uh$ z=&Yv6Mxqq%8VdYHmJY_@WPx<(eG-~1egAEAgetJRW2Qu+LivV=rF?Tgrg{S%Qu3)| zq2ZkR{%`Bw{HnYM@>u5_RV{Dj1R)_AH#VHQQ#*YcnKr3G9~*iA>Mo^6G9{q^tMHQO9#SklZjW2z=k0!IuNwWL!&lBwwGB)Qjjr7;-Zz=uBf@yA9?DR*hXeR>8r8RF(Sv=O)zSH>n zRmMDbn1TeM7rQu@*{1)qo&L^?8l3$9mMKl@>m0FfSMI@r3v6pN94A2wmo9`|^o3r? zqwA8dMRMgO{U%Nynw9>{mJUj4CRa}Y7vAEtW=G23D<~pV5US`w(dYZl%qe}ED9bYw zdXnWE!P6|4<0+@=hcEQpn_v1qc(`&>tOKONEF2yY?%|;s!6Nlk2@H8IwQ`yLWYMGN ze8r~X;owuyFKyiCS3@yzfb*LNwUWsG{ggg=4sQzOuKMgDo13yp;p}?4yTAK1#VW>d z71a_=X6yBtwS|HCO^9^F(zVOy)Kuvo(^lR%AGLz>dejDAY;5@owbU0$ic~Q$vevQL zv{n)aq6ttz_BP6@bkRW66qt|!OB}_quhvOZxNt^gBT$s(@f!a|c5nI=F4MDV`>gYC z=hE^jzLEmfgZekMm!XTX4W)oXu}Qe+W^mr338h$cx%VYD{O^~6j3G@=@A6Naic+f} z&1lodHuDF{UjZl7v>nT?T?7rKk*Uv~JAX&y%$Ps=K4`m4Xia8M5pndTFN#AkUYDm@ zAD^EMF7Dz0Ji-f65{bxB_cBL5x7ujEchA5%B%f_}`_}tnknTX~Jyf)e%0GRiBpy_@ zEoPzz&CFPQ(J@zpP7pjgA}5Vt1|1gs4>0vxoq4Ud z(j`gPu094&;jR#at^}8U?XZRI#vvz9|0N>nTDiSdcdwVUV8!}MZEbj`=_@7PxSvvd8+4G1OE=0|2Y#PfH>VK% zrp^PQPQ;yYU|(EM!?+~P!}z;>(@~+2(Fu{`+$sD(H}%U}DP=iP5BGU`fR5mFB{cm0 zJdEjkKA1+oaci$N+BSAa*G1H%rMBR?1BWCk=m|iDUN)JE}qDNd{qcVK(fkqRr0Q+H{P9GH*xS) zIg#Zv$8lq~)aNN_(5n>zv$KoY+r2`-XrkKlS`EPEiFL{=x?=IoSPljuhOSk!N#Y~V z{R+$T4|C@}0$Y+py85~!x->+qW!GzNTSSH0-Y4C;kAeTkvcMAa+W8iTB{F3 z&DVI6?O8w88jQ9oOI7-`zv!WFjHtl<9hfd4?3-2g7LOBs{lnNT;rggH_L~3AuNfSE z;O!m{&I1Dpr*1v|jOAT7i?3r_+OeFgv0lZ%_S?w4WjN&=XhhL+jOdo#p3UnZUHU#v zuy1oq)qjZ?F?f-OzuW0d%<*~|6GXF%t1!(ufJcaf!x&Yvg47;jx5rMAO$3|8J-Na& zv=uFLUml%DUV3cf1i|CL(kSh}3fMq%E7j|`x1FSpEAp1eK;)w~+80C%XmE&$E0$?T z!Xtv+b7)Ejy9fHeDQ^{|s^0sN`15tYD_n9NWV|ok=s3vZ1A@3Yl;W!G9*bF5G33{< z92Cd3@;z1~SW~?#k8Y-&&X*~O;%~z;rraiGxDjK2EyzGc)?-BCHYiEBe|*K=tECMH z=BY(gSyUtV+(`h{m!!0p(Cw8TKE2xj?cv2xT&0;p>)ngr7!d;*xF0!B z;DiANyFR8vLIl6~sBiioRGbxKZp+fQ#{>3)o*v2__?dJG+BbWV=U(2{-G61K!Lu!1ZX~Wc9X|qjL5p|(<9%_ z$e#rGH8S+=053xBj3aJG=)1i+2fJ8`dmEui-_C>{uWDwu;3=te6uNkkG`z6#hE3p zohm#A^h!)z)P8$|^aN6POxF&e2cDXrLnRD_Ip^p=bEHA@Dmmh2bykMTwvx^Q8MOTp z8m@Vdd<+O4`e{9k*thhcRC@Wxpmt38>cvtFB~W4egL4a=Vbk5OwAiy_xMAC1ukmfi zh~n$ghirPG;a92o^aQ>oUZAc!h>=x@oB^kC{4g;499BJ(FGxxi@ z79P9Ua7|le5brcN2_{MhWq`a~@@`(4>KjPoeh$iE@v9|h4Sz^F!MZac6?7jg#=)2p z!0OW3^A4cA$rnP6aP>AjaIq$uQ%)UyE+PkP2a9@3V7QP)t+EVR)V@tY7ydbb<9Rne zj`X}@WC5qd#bVJ->!Vi`ML+9s6wvMV!8|(mB7gY+0i~l}s|A6RRSOa1b1eZN@9zia zKecyuMnFmR$mJWrjTpE07=|o3n%I)StpgJD<&dVEv4giG8O_7*SemOS2|Y`}rJNiF zimScU73w?V1i0?bnJ7jq6hej#V>G2^eOGNV+xu9cZ+2^NBNWqYa>UFQ9pH$yNx^c# z<9V$_ET-Pq0$sCPK_bpL{<&g74j>{@Pk~yU#O|9~p}?mC74&V7`}4z2xrj9Ad?9bgj%Bxw^>ivr z3Zh4wFb#bXB{D%(H^mGW1qH(|`b2=f zhPGJX5Om96^uPd>CPJB`6VUigSeDdO8L+m^WHRvP=iA#s^?JdTsu-K4oXD@EzG9Z{ zt3TV!fb=tJK)T}B9eLU8^TnC3eHlFv_*xe{ey^g*?(n{87X~VxF%s+b-BiXU^-S*1 z;xCy{pvS%)Wh~L=!89Lv@0$coMKe)S?9*v$qu6eB)}pb z4nlRUnm@Rs>91vHnp# zj=UDmdi({8L_j0PFeANORE-$p4mh2}l$WpEg+_Q>4KM2%elvsJUy{;s8N6Oo##Rv8 zp&K;tl9w(5B*=csNIO~CI>V=p2)rIeRs|zuoZ?p5r|C7fY@QD{IBZ1Aq1!5C?0?kn z8JrSriv4+DINAx(A&OwM*tByLa!JDHW=jUDTYkxP`948&_k$GjFCW*c6h4EwPicgu zcLiK$d&lqjFg!z60T4p4-aVRmaBf!Lv-=*h>`p3OK-_WWfR`j2p>O1ph^oA)TNu>0 zA9fD8#Q|(L2WAv`qoLOwiO`OmB1 zI%uO(R{qc5{=Gm4ntxmJ&lo_Q`TyQO{D0hze7mp1x{l03o4ty{WBCeM^Pv9&2e%VS literal 0 HcmV?d00001 diff --git a/keep-ui/public/icons/nextjs-icon.png b/keep-ui/public/icons/nextjs-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..285982a0c706708e22530ad9a2f40f287cc4344b GIT binary patch literal 10884 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 Date: Mon, 18 Nov 2024 18:10:50 +0200 Subject: [PATCH 04/24] fix: Response content for AppD (#2532) --- keep/providers/appdynamics_provider/appdynamics_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keep/providers/appdynamics_provider/appdynamics_provider.py b/keep/providers/appdynamics_provider/appdynamics_provider.py index 84205eb83..2abd4bae2 100644 --- a/keep/providers/appdynamics_provider/appdynamics_provider.py +++ b/keep/providers/appdynamics_provider/appdynamics_provider.py @@ -225,7 +225,7 @@ def validate_scopes(self) -> dict[str, bool | str]: break else: self.logger.error( - "Error while validating scopes for AppDynamics", extra=response.json() + "Error while validating scopes for AppDynamics", extra=response.content ) return {"authenticated": authenticated, "administrator": administrator} From d3e365a51b0c9085967ff4e0bd1841e75af6a163 Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Tue, 19 Nov 2024 12:30:44 +0400 Subject: [PATCH 05/24] feat: skeleton loading state for alerts table (#2511) --- keep-ui/app/alerts/[id]/page.tsx | 2 +- .../app/alerts/alert-table-alert-facets.tsx | 11 +++- .../app/alerts/alert-table-facet-types.tsx | 2 + keep-ui/app/alerts/alert-table-facet.tsx | 18 ++++- keep-ui/app/alerts/alert-table-utils.tsx | 2 +- keep-ui/app/alerts/alert-table.tsx | 19 ++---- keep-ui/app/alerts/alert-tabs.tsx | 12 +--- keep-ui/app/alerts/alerts-table-body.tsx | 30 +++++++-- keep-ui/app/alerts/alerts.client.tsx | 32 --------- keep-ui/app/alerts/alerts.tsx | 20 +++--- keep-ui/components/navbar/AlertsLinks.tsx | 19 ++++-- .../navbar/CustomPresetAlertLinks.tsx | 2 + keep-ui/components/navbar/UserInfo.tsx | 13 ++-- keep-ui/shared/lib/hooks/useMounted.tsx | 11 ++++ keep-ui/utils/hooks/useAlerts.ts | 43 ++++++------ keep-ui/utils/hooks/usePresets.ts | 3 +- tests/e2e_tests/test_end_to_end.py | 66 +++++++++++-------- 17 files changed, 164 insertions(+), 141 deletions(-) delete mode 100644 keep-ui/app/alerts/alerts.client.tsx create mode 100644 keep-ui/shared/lib/hooks/useMounted.tsx diff --git a/keep-ui/app/alerts/[id]/page.tsx b/keep-ui/app/alerts/[id]/page.tsx index 89a007659..2137123d8 100644 --- a/keep-ui/app/alerts/[id]/page.tsx +++ b/keep-ui/app/alerts/[id]/page.tsx @@ -1,4 +1,4 @@ -import AlertsPage from "../alerts.client"; +import AlertsPage from "../alerts"; type PageProps = { params: { id: string }; diff --git a/keep-ui/app/alerts/alert-table-alert-facets.tsx b/keep-ui/app/alerts/alert-table-alert-facets.tsx index 1366558b4..3c57b14ed 100644 --- a/keep-ui/app/alerts/alert-table-alert-facets.tsx +++ b/keep-ui/app/alerts/alert-table-alert-facets.tsx @@ -16,6 +16,7 @@ import { AddFacetModal, } from "./alert-table-facet-dynamic"; import { PlusIcon } from "@heroicons/react/24/outline"; +import { usePathname } from "next/navigation"; export const AlertFacets: React.FC = ({ alerts, @@ -26,7 +27,9 @@ export const AlertFacets: React.FC = ({ onDelete, className, table, + showSkeleton, }) => { + const pathname = usePathname(); const timeRangeFilter = table .getState() .columnFilters.find((filter) => filter.id === "lastReceived"); @@ -35,7 +38,7 @@ export const AlertFacets: React.FC = ({ | { start: Date; end: Date; isFromCalendar: boolean } | undefined; - const presetName = window.location.pathname.split("/").pop() || "default"; + const presetName = pathname?.split("/").pop() || "default"; const [isModalOpen, setIsModalOpen] = useLocalStorage( `addFacetModalOpen-${presetName}`, @@ -207,6 +210,7 @@ export const AlertFacets: React.FC = ({ } facetKey="severity" facetFilters={facetFilters} + showSkeleton={showSkeleton} /> = ({ } facetKey="status" facetFilters={facetFilters} + showSkeleton={showSkeleton} /> = ({ } facetKey="source" facetFilters={facetFilters} + showSkeleton={showSkeleton} /> = ({ } facetKey="assignee" facetFilters={facetFilters} + showSkeleton={showSkeleton} /> = ({ } facetKey="dismissed" facetFilters={facetFilters} + showSkeleton={showSkeleton} /> = ({ handleSelect("incident", value, exclusive, isAllOnly) } facetFilters={facetFilters} + showSkeleton={showSkeleton} /> {/* Dynamic facets */} {dynamicFacets.map((facet) => ( diff --git a/keep-ui/app/alerts/alert-table-facet-types.tsx b/keep-ui/app/alerts/alert-table-facet-types.tsx index d07439d4b..bcee24fd2 100644 --- a/keep-ui/app/alerts/alert-table-facet-types.tsx +++ b/keep-ui/app/alerts/alert-table-facet-types.tsx @@ -29,6 +29,7 @@ export interface FacetProps { facetKey: string; facetFilters: FacetFilters; showIcon?: boolean; + showSkeleton?: boolean; } export interface AlertFacetsProps { @@ -44,4 +45,5 @@ export interface AlertFacetsProps { onDelete: (facetKey: string) => void; className?: string; table: Table; + showSkeleton?: boolean; } diff --git a/keep-ui/app/alerts/alert-table-facet.tsx b/keep-ui/app/alerts/alert-table-facet.tsx index 03fa7556c..83a53a909 100644 --- a/keep-ui/app/alerts/alert-table-facet.tsx +++ b/keep-ui/app/alerts/alert-table-facet.tsx @@ -4,6 +4,8 @@ import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/20/solid"; import { FacetProps } from "./alert-table-facet-types"; import { FacetValue } from "./alert-table-facet-value"; import { useLocalStorage } from "utils/hooks/useLocalStorage"; +import { usePathname } from "next/navigation"; +import Skeleton from "react-loading-skeleton"; export const Facet: React.FC = ({ name, @@ -12,9 +14,11 @@ export const Facet: React.FC = ({ facetKey, facetFilters, showIcon = true, + showSkeleton, }) => { + const pathname = usePathname(); // Get preset name from URL - const presetName = window.location.pathname.split("/").pop() || "default"; + const presetName = pathname?.split("/").pop() || "default"; // Store open/close state in localStorage with a unique key per preset and facet const [isOpen, setIsOpen] = useLocalStorage( @@ -60,7 +64,17 @@ export const Facet: React.FC = ({ )}
- {values.length > 0 ? ( + {showSkeleton ? ( + Array.from({ length: 3 }).map((_, index) => ( +
+ + +
+ )) + ) : values.length > 0 ? ( filteredValues.map((value) => ( ( { // if presetName is alert-history, do not open sidebar @@ -303,21 +303,12 @@ export function AlertTable({ setDynamicFacets={setDynamicFacets} onDelete={handleFacetDelete} table={table} + showSkeleton={showSkeleton} />
- {isAsyncLoading && ( - - Alerts will show up in this table as they are added to Keep... - - )} {/* For dynamic preset, add alert tabs*/} {!presetStatic && ( + {Array(20) + .fill("") + .map((index, rowIndex) => ( + + {table.getAllColumns().map((c, cellIndex) => ( + + + + ))} + + ))} + + ); + } + return ( {table.getRowModel().rows.map((row) => { @@ -100,11 +124,7 @@ export function AlertsTableBody({ "relative z-[1]" // Ensure cell content is above the border )} > - {showSkeleton ? ( - - ) : ( - flexRender(cell.column.columnDef.cell, cell.getContext()) - )} + {flexRender(cell.column.columnDef.cell, cell.getContext())} ); })} diff --git a/keep-ui/app/alerts/alerts.client.tsx b/keep-ui/app/alerts/alerts.client.tsx deleted file mode 100644 index f409867bf..000000000 --- a/keep-ui/app/alerts/alerts.client.tsx +++ /dev/null @@ -1,32 +0,0 @@ -"use client"; - -import { useRouter } from "next/navigation"; -import { useSession } from "next-auth/react"; -import Loading from "../loading"; -import Alerts from "./alerts"; - -type AlertsPageProps = { - presetName: string; -}; - -export default function AlertsPage({ presetName }: AlertsPageProps) { - const { data: session, status } = useSession(); - - const router = useRouter(); - - if (status === "loading") { - return ; - } - - if (status === "unauthenticated") { - console.log("unauthenticated"); - router.push("/signin"); - } - - if (session && !session.tenantId) { - console.log("no tenantId"); - router.push("/signin"); - } - - return ; -} diff --git a/keep-ui/app/alerts/alerts.tsx b/keep-ui/app/alerts/alerts.tsx index d70c6318d..3aa011e64 100644 --- a/keep-ui/app/alerts/alerts.tsx +++ b/keep-ui/app/alerts/alerts.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useEffect, useMemo, useState } from "react"; import { Preset } from "./models"; import { useAlerts } from "utils/hooks/useAlerts"; @@ -16,7 +18,8 @@ import { useRouter, useSearchParams } from "next/navigation"; import AlertChangeStatusModal from "./alert-change-status-modal"; import { useAlertPolling } from "utils/hooks/usePusher"; import NotFound from "@/app/not-found"; -import NotAuthorized from "@/app/not-authorized"; +import { useMounted } from "@/shared/lib/hooks/useMounted"; +import { useSession } from "next-auth/react"; const defaultPresets: Preset[] = [ { @@ -107,6 +110,11 @@ export default function Alerts({ presetName }: AlertsProps) { mutate: mutateAlerts, error: alertsError, } = usePresetAlerts(selectedPreset ? selectedPreset.name : ""); + + // const isMounted = useMounted(); + const { status: sessionStatus } = useSession(); + const isLoading = isAsyncLoading || sessionStatus === "loading"; + useEffect(() => { const fingerprint = searchParams?.get("alertPayloadFingerprint"); if (fingerprint) { @@ -126,14 +134,6 @@ export default function Alerts({ presetName }: AlertsProps) { if (!selectedPreset) { return ; } - if (alertsError) { - if (alertsError.statusCode === 401) { - console.log("unauthenticated 401"); - window.location.href = "/signin"; - return null; - } - return ; - } return ( <> @@ -141,7 +141,7 @@ export default function Alerts({ presetName }: AlertsProps) { key={selectedPreset.name} preset={selectedPreset} alerts={alerts} - isAsyncLoading={isAsyncLoading} + isAsyncLoading={isLoading} setTicketModalAlert={setTicketModalAlert} setNoteModalAlert={setNoteModalAlert} setRunWorkflowModalAlert={setRunWorkflowModalAlert} diff --git a/keep-ui/components/navbar/AlertsLinks.tsx b/keep-ui/components/navbar/AlertsLinks.tsx index 71b998bf6..ce094f415 100644 --- a/keep-ui/components/navbar/AlertsLinks.tsx +++ b/keep-ui/components/navbar/AlertsLinks.tsx @@ -14,6 +14,7 @@ import { useLocalStorage } from "utils/hooks/useLocalStorage"; import { ActionMeta, MultiValue } from "react-select"; import { useTags } from "utils/hooks/useTags"; import { usePresets } from "utils/hooks/usePresets"; +import { useMounted } from "@/shared/lib/hooks/useMounted"; import clsx from "clsx"; type AlertsLinksProps = { @@ -22,6 +23,8 @@ type AlertsLinksProps = { export const AlertsLinks = ({ session }: AlertsLinksProps) => { const [isTagModalOpen, setIsTagModalOpen] = useState(false); + const isMounted = useMounted(); + const [storedTags, setStoredTags] = useLocalStorage( "selectedTags", [] @@ -59,7 +62,7 @@ export const AlertsLinks = ({ session }: AlertsLinksProps) => { // Determine if we should show the feed link const shouldShowFeed = (() => { // If we have server data, check if feed preset exists - if (staticPresets) { + if (staticPresets.length > 0) { return staticPresets.some((preset) => preset.name === "feed"); } @@ -69,13 +72,19 @@ export const AlertsLinks = ({ session }: AlertsLinksProps) => { return staticPresetsOrderFromLS?.some((preset) => preset.name === "feed"); } - // If we're still loading (no data and no error), show based on cache + // For the initial render on the server, always show feed + if (!isMounted) { + return true; + } + return staticPresetsOrderFromLS?.some((preset) => preset.name === "feed"); })(); // Get the current alerts count only if we should show feed const currentAlertsCount = (() => { - if (!shouldShowFeed) return 0; + if (!shouldShowFeed) { + return 0; + } // First try to get from server data const serverPreset = staticPresets?.find( @@ -89,7 +98,7 @@ export const AlertsLinks = ({ session }: AlertsLinksProps) => { const cachedPreset = staticPresetsOrderFromLS?.find( (preset) => preset.name === "feed" ); - return cachedPreset?.alerts_count ?? 0; + return cachedPreset?.alerts_count ?? undefined; })(); return ( @@ -141,7 +150,7 @@ export const AlertsLinks = ({ session }: AlertsLinksProps) => { )} - {session && ( + {session && isMounted && ( {/* React Player for playing alert sound */} ); diff --git a/keep-ui/components/navbar/UserInfo.tsx b/keep-ui/components/navbar/UserInfo.tsx index 6c993d565..40173f5cf 100644 --- a/keep-ui/components/navbar/UserInfo.tsx +++ b/keep-ui/components/navbar/UserInfo.tsx @@ -17,7 +17,8 @@ import * as Frigade from "@frigade/react"; import { useState } from "react"; import Onboarding from "./Onboarding"; import { useSignOut } from "@/shared/lib/useSignOut"; -import { useSetSentryUser } from "@/shared/lib/useSetSentryUser"; + +const ONBOARDING_FLOW_ID = "flow_FHDz1hit"; type UserDropdownProps = { session: Session; @@ -89,7 +90,7 @@ type UserInfoProps = { }; export const UserInfo = ({ session }: UserInfoProps) => { - const [isOnboardingComplete, setIsOnboardingComplete] = useState(false); + const { flow } = Frigade.useFlow(ONBOARDING_FLOW_ID); const [isOnboardingOpen, setIsOnboardingOpen] = useState(false); return ( @@ -113,14 +114,11 @@ export const UserInfo = ({ session }: UserInfoProps) => { Join our Slack - {session && } - {isOnboardingComplete === false && ( + {flow?.isCompleted === false && (
  • setIsOnboardingComplete(true)} + flowId={ONBOARDING_FLOW_ID} onClick={() => setIsOnboardingOpen(true)} - // css={{ backgroundColor: "#F9FAFB" }} /> { />
  • )} + {session && } ); diff --git a/keep-ui/shared/lib/hooks/useMounted.tsx b/keep-ui/shared/lib/hooks/useMounted.tsx new file mode 100644 index 000000000..fe2b29974 --- /dev/null +++ b/keep-ui/shared/lib/hooks/useMounted.tsx @@ -0,0 +1,11 @@ +import { useState, useEffect } from "react"; + +export function useMounted() { + const [isMounted, setIsMounted] = useState(false); + + useEffect(() => { + setIsMounted(true); + }, []); + + return isMounted; +} diff --git a/keep-ui/utils/hooks/useAlerts.ts b/keep-ui/utils/hooks/useAlerts.ts index 28fa523d0..4bbe5fd65 100644 --- a/keep-ui/utils/hooks/useAlerts.ts +++ b/keep-ui/utils/hooks/useAlerts.ts @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useMemo } from "react"; import { AlertDto } from "app/alerts/models"; import { useSession } from "next-auth/react"; import useSWR, { SWRConfiguration } from "swr"; @@ -53,40 +53,37 @@ export const useAlerts = () => { presetName: string, options: SWRConfiguration = { revalidateOnFocus: false } ) => { - const [alertsMap, setAlertsMap] = useState>( - new Map() - ); - const { data: alertsFromEndpoint = [], mutate, isLoading, - error, + error: alertsError, } = useAllAlerts(presetName, options); - useEffect(() => { - if (alertsFromEndpoint.length) { - const newAlertsMap = new Map( - alertsFromEndpoint.map((alertFromEndpoint) => [ - alertFromEndpoint.fingerprint, - { - ...alertFromEndpoint, - lastReceived: toDateObjectWithFallback( - alertFromEndpoint.lastReceived - ), - }, - ]) - ); - - setAlertsMap(newAlertsMap); + const alertsValue = useMemo(() => { + if (!alertsFromEndpoint.length) { + return []; } + + const alertsMap = new Map( + alertsFromEndpoint.map((alertFromEndpoint) => [ + alertFromEndpoint.fingerprint, + { + ...alertFromEndpoint, + lastReceived: toDateObjectWithFallback( + alertFromEndpoint.lastReceived + ), + }, + ]) + ); + return Array.from(alertsMap.values()); }, [alertsFromEndpoint]); return { - data: Array.from(alertsMap.values()), + data: alertsValue, mutate: mutate, isLoading: isLoading, - error: error, + error: alertsError, }; }; diff --git a/keep-ui/utils/hooks/usePresets.ts b/keep-ui/utils/hooks/usePresets.ts index 00e49588f..a0a0d76e2 100644 --- a/keep-ui/utils/hooks/usePresets.ts +++ b/keep-ui/utils/hooks/usePresets.ts @@ -8,8 +8,7 @@ import { useLocalStorage } from "utils/hooks/useLocalStorage"; import { useConfig } from "./useConfig"; import useSWRSubscription from "swr/subscription"; import { useWebsocket } from "./usePusher"; -import { usePathname, useSearchParams } from "next/navigation"; -import moment from "moment"; +import { useSearchParams } from "next/navigation"; export const usePresets = (type?: string, useFilters?: boolean) => { const { data: session } = useSession(); diff --git a/tests/e2e_tests/test_end_to_end.py b/tests/e2e_tests/test_end_to_end.py index 7dcabfa25..80236d1df 100644 --- a/tests/e2e_tests/test_end_to_end.py +++ b/tests/e2e_tests/test_end_to_end.py @@ -33,10 +33,9 @@ # - Spin up the environment using docker-compose. # - Run "playwright codegen localhost:3000" # - Copy the generated code to a new test function. -import re import string import sys - +from datetime import datetime # Running the tests in GitHub Actions: # - Look at the test-pr-e2e.yml file in the .github/workflows directory. @@ -44,31 +43,50 @@ # os.environ["PLAYWRIGHT_HEADLESS"] = "false" -def test_sanity(browser): +def setup_console_listener(page, log_entries): + """Set up console listener to capture logs.""" + page.on("console", lambda msg: (log_entries.append(f"{datetime.now()}: {msg.text}, location: {msg.location}"))) + +def save_failure_artifacts(page, log_entries): + """Save screenshots, HTML content, and console logs on test failure.""" + # Generate unique name for the dump files + current_test_name = ( + "playwright_dump_" + + os.path.basename(__file__)[:-3] + + "_" + + sys._getframe().f_code.co_name + ) + + # Save screenshot + page.screenshot(path=current_test_name + ".png") + + # Save HTML content + with open(current_test_name + ".html", "w", encoding="utf-8") as f: + f.write(page.content()) + + # Save console logs + with open(current_test_name + "_console.log", "w", encoding="utf-8") as f: + f.write("\n".join(log_entries)) + +def test_sanity(browser): # browser is actually a page object + log_entries = [] + setup_console_listener(browser, log_entries) + try: browser.goto("http://localhost:3000/") browser.wait_for_url("http://localhost:3000/incidents") assert "Keep" in browser.title() except Exception: - # Current file + test name for unique html and png dump. - current_test_name = ( - "playwright_dump_" - + os.path.basename(__file__)[:-3] - + "_" - + sys._getframe().f_code.co_name - ) - - browser.screenshot(path=current_test_name + ".png") - with open(current_test_name + ".html", "w") as f: - f.write(browser.content()) + save_failure_artifacts(browser, log_entries) raise - -def test_insert_new_alert(browser): +def test_insert_new_alert(browser): # browser is actually a page object """ Test to insert a new alert - """ + log_entries = [] + setup_console_listener(browser, log_entries) + try: browser.goto( "http://localhost:3000/signin?callbackUrl=http%3A%2F%2Flocalhost%3A3000%2Fproviders" @@ -86,6 +104,8 @@ def test_insert_new_alert(browser): browser.wait_for_timeout(10000) # refresh the page browser.reload() + # wait for badge counter to update + browser.wait_for_timeout(500) feed_badge = browser.get_by_test_id("menu-alerts-feed-badge") feed_count = int(feed_badge.text_content()) assert feed_count > feed_count_before @@ -94,17 +114,7 @@ def test_insert_new_alert(browser): feed_link.click() except Exception: - # Current file + test name for unique html and png dump. - current_test_name = ( - "playwright_dump_" - + os.path.basename(__file__)[:-3] - + "_" - + sys._getframe().f_code.co_name - ) - - browser.screenshot(path=current_test_name + ".png") - with open(current_test_name + ".html", "w") as f: - f.write(browser.content()) + save_failure_artifacts(browser, log_entries) raise From 892a79ee5ad2acc3ef88b6f60bff6ac8a13b81af Mon Sep 17 00:00:00 2001 From: Tal Date: Tue, 19 Nov 2024 11:03:26 +0200 Subject: [PATCH 06/24] fix(ui): styling in alert table when empty (#2531) --- keep-ui/app/alerts/alerts-table-body.tsx | 2 +- .../servicenow_provider/servicenow_provider.py | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/keep-ui/app/alerts/alerts-table-body.tsx b/keep-ui/app/alerts/alerts-table-body.tsx index f3f5e723d..b492379d5 100644 --- a/keep-ui/app/alerts/alerts-table-body.tsx +++ b/keep-ui/app/alerts/alerts-table-body.tsx @@ -35,7 +35,7 @@ export function AlertsTableBody({ if (showEmptyState) { return ( <> -
    +
    Date: Tue, 19 Nov 2024 11:30:31 +0200 Subject: [PATCH 07/24] fix(ui): auth type defaults to NOAUTH (#2529) --- keep-ui/shared/lib/server/getConfig.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/keep-ui/shared/lib/server/getConfig.ts b/keep-ui/shared/lib/server/getConfig.ts index 44b11f30e..cec63a630 100644 --- a/keep-ui/shared/lib/server/getConfig.ts +++ b/keep-ui/shared/lib/server/getConfig.ts @@ -15,6 +15,9 @@ export function getConfig() { authType = AuthenticationType.DB; } else if (authType === NO_AUTH) { authType = AuthenticationType.NOAUTH; + } else { + // Default to NOAUTH + authType = AuthenticationType.NOAUTH; } // we want to support preview branches on vercel From 838d1d7f81d391d843fc13e5d09385bdfb8771fd Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Wed, 20 Nov 2024 12:22:15 +0400 Subject: [PATCH 08/24] refactor: useHydratedSession instead of built-in useSession (#2407) --- keep-ui/app/ai/ai.tsx | 2 +- keep-ui/app/alerts/ViewAlertModal.tsx | 2 +- .../app/alerts/alert-assign-ticket-modal.tsx | 2 +- .../alerts/alert-associate-incident-modal.tsx | 2 +- .../app/alerts/alert-change-status-modal.tsx | 2 +- .../alerts/alert-create-incident-ai-modal.tsx | 2 +- keep-ui/app/alerts/alert-dismiss-modal.tsx | 2 +- keep-ui/app/alerts/alert-menu.tsx | 2 +- keep-ui/app/alerts/alert-note-modal.tsx | 2 +- keep-ui/app/alerts/alert-presets.tsx | 2 +- .../alert-push-alert-to-server-modal.tsx | 2 +- .../app/alerts/alert-run-workflow-modal.tsx | 2 +- keep-ui/app/alerts/alert-tab-modal.tsx | 6 +- keep-ui/app/alerts/alert-tabs.tsx | 2 +- keep-ui/app/alerts/alerts.tsx | 4 +- keep-ui/app/auth-provider.tsx | 17 ++++- keep-ui/app/dashboard/[id]/dashboard.tsx | 65 +++++++++++++------ .../deduplication/DeduplicationSidebar.tsx | 2 +- .../app/deduplication/DeduplicationTable.tsx | 2 +- .../create-or-update-extraction-rule.tsx | 2 +- keep-ui/app/extraction/extractions-table.tsx | 2 +- keep-ui/app/frigade-provider.tsx | 4 +- .../[id]/activity/incident-activity.tsx | 2 +- .../activity/ui/IncidentActivityComment.tsx | 2 +- .../[id]/alerts/incident-alert-menu.tsx | 2 +- keep-ui/app/layout.tsx | 10 ++- .../create-or-update-maintenance-rule.tsx | 2 +- .../maintenance/maintenance-rules-table.tsx | 2 +- .../app/mapping/create-or-edit-mapping.tsx | 2 +- keep-ui/app/mapping/rules-table.tsx | 2 +- keep-ui/app/providers/page.client.tsx | 2 +- keep-ui/app/providers/provider-form.tsx | 2 +- .../CorrelationSidebarBody.tsx | 2 +- .../rules/CorrelationSidebar/DeleteRule.tsx | 2 +- keep-ui/app/settings/auth/permissions-tab.tsx | 2 +- keep-ui/app/settings/auth/users-sidebar.tsx | 12 ++-- keep-ui/app/settings/settings.client.tsx | 30 ++++----- keep-ui/app/topology/model/useTopology.ts | 2 +- .../topology/model/useTopologyApplications.ts | 2 +- .../workflows/[workflow_id]/executions.tsx | 2 +- keep-ui/app/workflows/builder/page.client.tsx | 2 +- .../builder/workflow-execution-results.tsx | 6 +- keep-ui/app/workflows/dragndrop.tsx | 2 +- .../workflows/manual-run-workflow-modal.tsx | 2 +- keep-ui/app/workflows/workflow-tile.tsx | 2 +- keep-ui/app/workflows/workflows.client.tsx | 2 +- .../incidents/model/useIncidentActions.tsx | 2 +- .../shared/lib/hooks/useHydratedSession.tsx | 31 +++++++++ keep-ui/shared/ui/PostHogPageView.tsx | 2 +- keep-ui/utils/hooks/useAI.ts | 2 +- keep-ui/utils/hooks/useAlertQuality.ts | 6 +- keep-ui/utils/hooks/useAlerts.ts | 2 +- .../utils/hooks/useDashboardMetricWidgets.ts | 47 +++++++------- keep-ui/utils/hooks/useDashboardPresets.ts | 45 ++++++++----- keep-ui/utils/hooks/useDashboards.ts | 2 +- keep-ui/utils/hooks/useDeduplicationRules.ts | 2 +- keep-ui/utils/hooks/useExtractionRules.ts | 2 +- keep-ui/utils/hooks/useGroups.ts | 2 +- keep-ui/utils/hooks/useIncidents.ts | 2 +- keep-ui/utils/hooks/useMaintenanceRules.ts | 2 +- keep-ui/utils/hooks/useMappingRules.ts | 2 +- keep-ui/utils/hooks/usePermissions.ts | 2 +- keep-ui/utils/hooks/usePresets.ts | 2 +- keep-ui/utils/hooks/useProviders.ts | 2 +- keep-ui/utils/hooks/usePusher.ts | 2 +- keep-ui/utils/hooks/useRoles.ts | 2 +- keep-ui/utils/hooks/useRules.ts | 2 +- keep-ui/utils/hooks/useScopes.ts | 2 +- keep-ui/utils/hooks/useSearchAlerts.ts | 2 +- keep-ui/utils/hooks/useTags.ts | 2 +- keep-ui/utils/hooks/useUsers.ts | 2 +- keep-ui/utils/hooks/useWorkflowExecutions.ts | 2 +- keep-ui/utils/hooks/useWorkflowRun.ts | 2 +- keep-ui/utils/hooks/useWorkflows.ts | 2 +- 74 files changed, 245 insertions(+), 160 deletions(-) create mode 100644 keep-ui/shared/lib/hooks/useHydratedSession.tsx diff --git a/keep-ui/app/ai/ai.tsx b/keep-ui/app/ai/ai.tsx index 7e9d23aa7..2446337a7 100644 --- a/keep-ui/app/ai/ai.tsx +++ b/keep-ui/app/ai/ai.tsx @@ -1,7 +1,7 @@ "use client"; import { Card, List, ListItem, Title, Subtitle } from "@tremor/react"; import { useAIStats, usePollAILogs } from "utils/hooks/useAI"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useApiUrl } from "utils/hooks/useConfig"; import { toast } from "react-toastify"; import { useEffect, useState, useRef, FormEvent } from "react"; diff --git a/keep-ui/app/alerts/ViewAlertModal.tsx b/keep-ui/app/alerts/ViewAlertModal.tsx index 6bbeca28f..7bc404172 100644 --- a/keep-ui/app/alerts/ViewAlertModal.tsx +++ b/keep-ui/app/alerts/ViewAlertModal.tsx @@ -3,7 +3,7 @@ import Modal from "@/components/ui/Modal"; // Ensure this path matches your proj import { Button, Icon, Switch, Text } from "@tremor/react"; import { toast } from "react-toastify"; import { useApiUrl } from "utils/hooks/useConfig"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { XMarkIcon } from "@heroicons/react/24/outline"; import "./ViewAlertModal.css"; import React, { useState } from "react"; diff --git a/keep-ui/app/alerts/alert-assign-ticket-modal.tsx b/keep-ui/app/alerts/alert-assign-ticket-modal.tsx index f3920c764..0ca7bae0a 100644 --- a/keep-ui/app/alerts/alert-assign-ticket-modal.tsx +++ b/keep-ui/app/alerts/alert-assign-ticket-modal.tsx @@ -4,7 +4,7 @@ import { Button, TextInput, Text } from "@tremor/react"; 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 { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useApiUrl } from "utils/hooks/useConfig"; import { AlertDto } from "./models"; import Modal from "@/components/ui/Modal"; diff --git a/keep-ui/app/alerts/alert-associate-incident-modal.tsx b/keep-ui/app/alerts/alert-associate-incident-modal.tsx index 9d59afdbf..923d30cd8 100644 --- a/keep-ui/app/alerts/alert-associate-incident-modal.tsx +++ b/keep-ui/app/alerts/alert-associate-incident-modal.tsx @@ -2,7 +2,7 @@ import Modal from "@/components/ui/Modal"; import { Button, Divider, Title } from "@tremor/react"; import Select from "@/components/ui/Select"; import { CreateOrUpdateIncidentForm } from "@/features/create-or-update-incident"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { FormEvent, useCallback, useEffect, useState } from "react"; import { toast } from "react-toastify"; import { useApiUrl } from "utils/hooks/useConfig"; diff --git a/keep-ui/app/alerts/alert-change-status-modal.tsx b/keep-ui/app/alerts/alert-change-status-modal.tsx index d090599dc..f68f46e61 100644 --- a/keep-ui/app/alerts/alert-change-status-modal.tsx +++ b/keep-ui/app/alerts/alert-change-status-modal.tsx @@ -9,7 +9,7 @@ import Select, { import { useState } from "react"; import { AlertDto, Status } from "./models"; import { useApiUrl } from "utils/hooks/useConfig"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { toast } from "react-toastify"; import { CheckCircleIcon, diff --git a/keep-ui/app/alerts/alert-create-incident-ai-modal.tsx b/keep-ui/app/alerts/alert-create-incident-ai-modal.tsx index ada499e65..9d077ed66 100644 --- a/keep-ui/app/alerts/alert-create-incident-ai-modal.tsx +++ b/keep-ui/app/alerts/alert-create-incident-ai-modal.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import Modal from "@/components/ui/Modal"; import { Callout, Button, Title, Card } from "@tremor/react"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { toast } from "react-toastify"; import Loading from "../loading"; import { AlertDto } from "./models"; diff --git a/keep-ui/app/alerts/alert-dismiss-modal.tsx b/keep-ui/app/alerts/alert-dismiss-modal.tsx index 11bf3d63f..93976f22d 100644 --- a/keep-ui/app/alerts/alert-dismiss-modal.tsx +++ b/keep-ui/app/alerts/alert-dismiss-modal.tsx @@ -17,7 +17,7 @@ import "react-quill/dist/quill.snow.css"; import { AlertDto } from "./models"; import { format, set, isSameDay, isAfter, addMinutes } from "date-fns"; import { useApiUrl } from "utils/hooks/useConfig"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { usePresets } from "utils/hooks/usePresets"; import { useAlerts } from "utils/hooks/useAlerts"; import { toast } from "react-toastify"; diff --git a/keep-ui/app/alerts/alert-menu.tsx b/keep-ui/app/alerts/alert-menu.tsx index 997be6959..4784090d2 100644 --- a/keep-ui/app/alerts/alert-menu.tsx +++ b/keep-ui/app/alerts/alert-menu.tsx @@ -12,7 +12,7 @@ import { } from "@heroicons/react/24/outline"; import { IoNotificationsOffOutline } from "react-icons/io5"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useApiUrl } from "utils/hooks/useConfig"; import Link from "next/link"; import { ProviderMethod } from "app/providers/providers"; diff --git a/keep-ui/app/alerts/alert-note-modal.tsx b/keep-ui/app/alerts/alert-note-modal.tsx index a737059a9..fb9f6910b 100644 --- a/keep-ui/app/alerts/alert-note-modal.tsx +++ b/keep-ui/app/alerts/alert-note-modal.tsx @@ -7,7 +7,7 @@ const ReactQuill = import "react-quill/dist/quill.snow.css"; import { Button } from "@tremor/react"; import { useApiUrl } from "utils/hooks/useConfig"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { AlertDto } from "./models"; import Modal from "@/components/ui/Modal"; diff --git a/keep-ui/app/alerts/alert-presets.tsx b/keep-ui/app/alerts/alert-presets.tsx index 5263b7b1f..217ff5fc2 100644 --- a/keep-ui/app/alerts/alert-presets.tsx +++ b/keep-ui/app/alerts/alert-presets.tsx @@ -4,7 +4,7 @@ import Modal from "@/components/ui/Modal"; import { Button, Subtitle, TextInput, Switch, Text } from "@tremor/react"; import { useApiUrl } from "utils/hooks/useConfig"; import { toast } from "react-toastify"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { usePresets } from "utils/hooks/usePresets"; import { useTags } from "utils/hooks/useTags"; import { useRouter } from "next/navigation"; diff --git a/keep-ui/app/alerts/alert-push-alert-to-server-modal.tsx b/keep-ui/app/alerts/alert-push-alert-to-server-modal.tsx index 76931c8ed..44576e546 100644 --- a/keep-ui/app/alerts/alert-push-alert-to-server-modal.tsx +++ b/keep-ui/app/alerts/alert-push-alert-to-server-modal.tsx @@ -7,7 +7,7 @@ import { FieldValues, } from "react-hook-form"; import Modal from "@/components/ui/Modal"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useApiUrl } from "utils/hooks/useConfig"; import { useProviders } from "utils/hooks/useProviders"; import ImageWithFallback from "@/components/ImageWithFallback"; diff --git a/keep-ui/app/alerts/alert-run-workflow-modal.tsx b/keep-ui/app/alerts/alert-run-workflow-modal.tsx index 3ade1e15a..514719fb4 100644 --- a/keep-ui/app/alerts/alert-run-workflow-modal.tsx +++ b/keep-ui/app/alerts/alert-run-workflow-modal.tsx @@ -3,7 +3,7 @@ import { AlertDto } from "./models"; import Modal from "@/components/ui/Modal"; import { useWorkflows } from "utils/hooks/useWorkflows"; import { useState } from "react"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useApiUrl } from "utils/hooks/useConfig"; import { toast } from "react-toastify"; import { useRouter } from "next/navigation"; diff --git a/keep-ui/app/alerts/alert-tab-modal.tsx b/keep-ui/app/alerts/alert-tab-modal.tsx index d5a99e7d6..c8a2b232d 100644 --- a/keep-ui/app/alerts/alert-tab-modal.tsx +++ b/keep-ui/app/alerts/alert-tab-modal.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import Modal from "@/components/ui/Modal"; import { Button, TextInput } from "@tremor/react"; import { AlertsRulesBuilder } from "app/alerts/alerts-rules-builder"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useApiUrl } from "utils/hooks/useConfig"; interface AlertTabModalProps { @@ -114,8 +114,8 @@ const AlertTabModal = ({ !newTabName ? "Tab name is required" : !newTabFilter - ? "Tab filter is required (notice you need to click 'enter' to apply the filter)" - : "" + ? "Tab filter is required (notice you need to click 'enter' to apply the filter)" + : "" } > Add Tab diff --git a/keep-ui/app/alerts/alert-tabs.tsx b/keep-ui/app/alerts/alert-tabs.tsx index 42c8ea938..2413c4076 100644 --- a/keep-ui/app/alerts/alert-tabs.tsx +++ b/keep-ui/app/alerts/alert-tabs.tsx @@ -5,7 +5,7 @@ import AlertTabModal from "./alert-tab-modal"; import { evalWithContext } from "./alerts-rules-builder"; import { XMarkIcon } from "@heroicons/react/24/outline"; import { useApiUrl } from "utils/hooks/useConfig"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; interface Tab { id?: string; name: string; diff --git a/keep-ui/app/alerts/alerts.tsx b/keep-ui/app/alerts/alerts.tsx index 3aa011e64..4d0fe7117 100644 --- a/keep-ui/app/alerts/alerts.tsx +++ b/keep-ui/app/alerts/alerts.tsx @@ -18,8 +18,7 @@ import { useRouter, useSearchParams } from "next/navigation"; import AlertChangeStatusModal from "./alert-change-status-modal"; import { useAlertPolling } from "utils/hooks/usePusher"; import NotFound from "@/app/not-found"; -import { useMounted } from "@/shared/lib/hooks/useMounted"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; const defaultPresets: Preset[] = [ { @@ -111,7 +110,6 @@ export default function Alerts({ presetName }: AlertsProps) { error: alertsError, } = usePresetAlerts(selectedPreset ? selectedPreset.name : ""); - // const isMounted = useMounted(); const { status: sessionStatus } = useSession(); const isLoading = isAsyncLoading || sessionStatus === "loading"; diff --git a/keep-ui/app/auth-provider.tsx b/keep-ui/app/auth-provider.tsx index 21e157dfc..8a3107208 100644 --- a/keep-ui/app/auth-provider.tsx +++ b/keep-ui/app/auth-provider.tsx @@ -1,11 +1,24 @@ "use client"; +import { Session } from "next-auth"; import { SessionProvider } from "next-auth/react"; +declare global { + interface Window { + __NEXT_AUTH_SESSION__?: Session | null; + } +} + type Props = { children?: React.ReactNode; + session?: Session | null; }; -export const NextAuthProvider = ({ children }: Props) => { - return {children} +export const NextAuthProvider = ({ children, session }: Props) => { + // Hydrate session on mount + if (typeof window !== "undefined" && !!session) { + window.__NEXT_AUTH_SESSION__ = session; + } + + return {children}; }; diff --git a/keep-ui/app/dashboard/[id]/dashboard.tsx b/keep-ui/app/dashboard/[id]/dashboard.tsx index 77b060624..965f7f5fa 100644 --- a/keep-ui/app/dashboard/[id]/dashboard.tsx +++ b/keep-ui/app/dashboard/[id]/dashboard.tsx @@ -1,20 +1,29 @@ "use client"; -import {useParams} from "next/navigation"; -import {ChangeEvent, useEffect, useState} from "react"; +import { useParams } from "next/navigation"; +import { ChangeEvent, useEffect, useState } from "react"; import GridLayout from "../GridLayout"; import WidgetModal from "../WidgetModal"; -import {Button, Card, Icon, Subtitle, TextInput} from "@tremor/react"; -import {GenericsMetrics, LayoutItem, Threshold, WidgetData, WidgetType} from "../types"; -import {Preset} from "app/alerts/models"; -import {FiEdit2, FiSave} from "react-icons/fi"; -import {useSession} from "next-auth/react"; -import {useDashboards} from "utils/hooks/useDashboards"; -import {useApiUrl} from "utils/hooks/useConfig"; +import { Button, Card, Icon, Subtitle, TextInput } from "@tremor/react"; +import { + GenericsMetrics, + LayoutItem, + Threshold, + WidgetData, + WidgetType, +} from "../types"; +import { Preset } from "app/alerts/models"; +import { FiEdit2, FiSave } from "react-icons/fi"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; +import { useDashboards } from "utils/hooks/useDashboards"; +import { useApiUrl } from "utils/hooks/useConfig"; import "./../styles.css"; -import {toast} from "react-toastify"; -import {GenericFilters} from "@/components/filters/GenericFilters"; -import {useDashboardPreset} from "utils/hooks/useDashboardPresets"; -import {MetricsWidget, useDashboardMetricWidgets} from '@/utils/hooks/useDashboardMetricWidgets'; +import { toast } from "react-toastify"; +import { GenericFilters } from "@/components/filters/GenericFilters"; +import { useDashboardPreset } from "utils/hooks/useDashboardPresets"; +import { + MetricsWidget, + useDashboardMetricWidgets, +} from "@/utils/hooks/useDashboardMetricWidgets"; const DASHBOARD_FILTERS = [ { @@ -33,7 +42,7 @@ const DashboardPage = () => { const [isModalOpen, setIsModalOpen] = useState(false); const [layout, setLayout] = useState([]); const [widgetData, setWidgetData] = useState([]); - const {widgets: allMetricWidgets} = useDashboardMetricWidgets(true); + const { widgets: allMetricWidgets } = useDashboardMetricWidgets(true); const [editingItem, setEditingItem] = useState(null); const [dashboardName, setDashboardName] = useState(decodeURIComponent(id)); const [isEditingName, setIsEditingName] = useState(false); @@ -59,7 +68,9 @@ const DashboardPage = () => { const closeModal = () => setIsModalOpen(false); const handleAddWidget = ( - name: string, widgetType: WidgetType, preset?: Preset , + name: string, + widgetType: WidgetType, + preset?: Preset, thresholds?: Threshold[], metric?: MetricsWidget, genericMetrics?: GenericsMetrics @@ -69,10 +80,25 @@ const DashboardPage = () => { i: uniqueId, x: (layout.length % 12) * 2, y: Math.floor(layout.length / 12) * 2, - w: widgetType === WidgetType.GENERICS_METRICS ? 12 : widgetType === WidgetType.METRIC ? 6 : 3, - h: widgetType === WidgetType.GENERICS_METRICS ? 20 : widgetType === WidgetType.METRIC ? 8 : 3, + w: + widgetType === WidgetType.GENERICS_METRICS + ? 12 + : widgetType === WidgetType.METRIC + ? 6 + : 3, + h: + widgetType === WidgetType.GENERICS_METRICS + ? 20 + : widgetType === WidgetType.METRIC + ? 8 + : 3, minW: widgetType === WidgetType.GENERICS_METRICS ? 10 : 2, - minH: widgetType === WidgetType.GENERICS_METRICS ? 15 : widgetType === WidgetType.METRIC ? 7 : 3, + minH: + widgetType === WidgetType.GENERICS_METRICS + ? 15 + : widgetType === WidgetType.METRIC + ? 7 + : 3, static: false, }; const newWidget: WidgetData = { @@ -82,7 +108,7 @@ const DashboardPage = () => { name, widgetType, genericMetrics, - metric + metric, }; setLayout((prevLayout) => [...prevLayout, newItem]); setWidgetData((prevData) => [...prevData, newWidget]); @@ -90,7 +116,6 @@ const DashboardPage = () => { const handleEditWidget = (id: string, update?: WidgetData) => { let itemToEdit = widgetData.find((d) => d.i === id) || null; - console.log(itemToEdit, update) if (itemToEdit && update) { setEditingItem({ ...itemToEdit, ...update }); } else { diff --git a/keep-ui/app/deduplication/DeduplicationSidebar.tsx b/keep-ui/app/deduplication/DeduplicationSidebar.tsx index 0a3bf8943..48a6d808e 100644 --- a/keep-ui/app/deduplication/DeduplicationSidebar.tsx +++ b/keep-ui/app/deduplication/DeduplicationSidebar.tsx @@ -24,7 +24,7 @@ import { InformationCircleIcon, } from "@heroicons/react/24/outline"; import { useApiUrl } from "utils/hooks/useConfig"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { KeyedMutator } from "swr"; interface ProviderOption { diff --git a/keep-ui/app/deduplication/DeduplicationTable.tsx b/keep-ui/app/deduplication/DeduplicationTable.tsx index 05d634451..3951fbaaa 100644 --- a/keep-ui/app/deduplication/DeduplicationTable.tsx +++ b/keep-ui/app/deduplication/DeduplicationTable.tsx @@ -25,7 +25,7 @@ import DeduplicationSidebar from "app/deduplication/DeduplicationSidebar"; import { TrashIcon, PauseIcon, PlusIcon } from "@heroicons/react/24/outline"; import Image from "next/image"; import { useApiUrl } from "utils/hooks/useConfig"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; const columnHelper = createColumnHelper(); 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 a770f68c9..11d7c032b 100644 --- a/keep-ui/app/extraction/create-or-update-extraction-rule.tsx +++ b/keep-ui/app/extraction/create-or-update-extraction-rule.tsx @@ -13,7 +13,7 @@ import { Switch, Badge, } from "@tremor/react"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { FormEvent, useEffect, useState } from "react"; import { toast } from "react-toastify"; import { useApiUrl } from "utils/hooks/useConfig"; diff --git a/keep-ui/app/extraction/extractions-table.tsx b/keep-ui/app/extraction/extractions-table.tsx index 48fcb7f51..7b621b05b 100644 --- a/keep-ui/app/extraction/extractions-table.tsx +++ b/keep-ui/app/extraction/extractions-table.tsx @@ -18,7 +18,7 @@ import { useReactTable, } from "@tanstack/react-table"; import { MdRemoveCircle, MdModeEdit } from "react-icons/md"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useApiUrl } from "utils/hooks/useConfig"; import { useMappings } from "utils/hooks/useMappingRules"; import { toast } from "react-toastify"; diff --git a/keep-ui/app/frigade-provider.tsx b/keep-ui/app/frigade-provider.tsx index 3d98f4aa3..2ae5b4619 100644 --- a/keep-ui/app/frigade-provider.tsx +++ b/keep-ui/app/frigade-provider.tsx @@ -1,7 +1,7 @@ "use client"; import * as Frigade from "@frigade/react"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; export const FrigadeProvider = ({ children, }: { @@ -14,7 +14,7 @@ export const FrigadeProvider = ({ userId={ session?.user.email === "keep" ? undefined - : session?.user.email ?? session?.user.name + : (session?.user.email ?? session?.user.name) } theme={{ colors: { diff --git a/keep-ui/app/incidents/[id]/activity/incident-activity.tsx b/keep-ui/app/incidents/[id]/activity/incident-activity.tsx index 3b6ea0787..7a62633f6 100644 --- a/keep-ui/app/incidents/[id]/activity/incident-activity.tsx +++ b/keep-ui/app/incidents/[id]/activity/incident-activity.tsx @@ -11,7 +11,7 @@ import { usePollIncidentComments, } from "@/utils/hooks/useIncidents"; import { useAlerts } from "@/utils/hooks/useAlerts"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { IncidentActivityItem } from "./ui/IncidentActivityItem"; import { IncidentActivityComment } from "./ui/IncidentActivityComment"; import { useMemo } from "react"; diff --git a/keep-ui/app/incidents/[id]/activity/ui/IncidentActivityComment.tsx b/keep-ui/app/incidents/[id]/activity/ui/IncidentActivityComment.tsx index 2cd8f0b77..d015b02e9 100644 --- a/keep-ui/app/incidents/[id]/activity/ui/IncidentActivityComment.tsx +++ b/keep-ui/app/incidents/[id]/activity/ui/IncidentActivityComment.tsx @@ -2,7 +2,7 @@ import { IncidentDto } from "@/entities/incidents/model"; import { AuditEvent } from "@/utils/hooks/useAlerts"; import { useApiUrl } from "@/utils/hooks/useConfig"; import { TextInput, Button } from "@tremor/react"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useState, useCallback, useEffect } from "react"; import { toast } from "react-toastify"; import { KeyedMutator } from "swr"; diff --git a/keep-ui/app/incidents/[id]/alerts/incident-alert-menu.tsx b/keep-ui/app/incidents/[id]/alerts/incident-alert-menu.tsx index 03d7c615e..f902df664 100644 --- a/keep-ui/app/incidents/[id]/alerts/incident-alert-menu.tsx +++ b/keep-ui/app/incidents/[id]/alerts/incident-alert-menu.tsx @@ -1,6 +1,6 @@ import { Icon } from "@tremor/react"; import { AlertDto } from "app/alerts/models"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { toast } from "react-toastify"; import { useApiUrl } from "utils/hooks/useConfig"; import { useIncidentAlerts } from "utils/hooks/useIncidents"; diff --git a/keep-ui/app/layout.tsx b/keep-ui/app/layout.tsx index d7b0f90ab..a8ee5503a 100644 --- a/keep-ui/app/layout.tsx +++ b/keep-ui/app/layout.tsx @@ -7,11 +7,13 @@ import { TopologyPollingContextProvider } from "@/app/topology/model/TopologyPol import { FrigadeProvider } from "./frigade-provider"; import { getConfig } from "@/shared/lib/server/getConfig"; import { ConfigProvider } from "./config-provider"; -import "./globals.css"; -import "react-toastify/dist/ReactToastify.css"; import { PHProvider } from "./posthog-provider"; import dynamic from "next/dynamic"; import ReadOnlyBanner from "./read-only-banner"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/pages/api/auth/[...nextauth]"; +import "./globals.css"; +import "react-toastify/dist/ReactToastify.css"; const PostHogPageView = dynamic(() => import("@/shared/ui/PostHogPageView"), { ssr: false, @@ -29,12 +31,14 @@ type RootLayoutProps = { export default async function RootLayout({ children }: RootLayoutProps) { const config = getConfig(); + const session = await getServerSession(authOptions); + return ( - + {/* @ts-ignore-error Server Component */} diff --git a/keep-ui/app/maintenance/create-or-update-maintenance-rule.tsx b/keep-ui/app/maintenance/create-or-update-maintenance-rule.tsx index fc50ca66d..9a71bc59f 100644 --- a/keep-ui/app/maintenance/create-or-update-maintenance-rule.tsx +++ b/keep-ui/app/maintenance/create-or-update-maintenance-rule.tsx @@ -10,7 +10,7 @@ import { Select, SelectItem, } from "@tremor/react"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { FormEvent, useEffect, useState } from "react"; import { toast } from "react-toastify"; import { useApiUrl } from "utils/hooks/useConfig"; diff --git a/keep-ui/app/maintenance/maintenance-rules-table.tsx b/keep-ui/app/maintenance/maintenance-rules-table.tsx index 819caa4c6..790082e83 100644 --- a/keep-ui/app/maintenance/maintenance-rules-table.tsx +++ b/keep-ui/app/maintenance/maintenance-rules-table.tsx @@ -17,7 +17,7 @@ import { useReactTable, } from "@tanstack/react-table"; import { MdRemoveCircle, MdModeEdit } from "react-icons/md"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { toast } from "react-toastify"; import { useApiUrl } from "utils/hooks/useConfig"; import { MaintenanceRule } from "./model"; diff --git a/keep-ui/app/mapping/create-or-edit-mapping.tsx b/keep-ui/app/mapping/create-or-edit-mapping.tsx index 0145999fa..5c0b61a95 100644 --- a/keep-ui/app/mapping/create-or-edit-mapping.tsx +++ b/keep-ui/app/mapping/create-or-edit-mapping.tsx @@ -17,7 +17,7 @@ import { TabPanels, TabPanel, } from "@tremor/react"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { ChangeEvent, FormEvent, diff --git a/keep-ui/app/mapping/rules-table.tsx b/keep-ui/app/mapping/rules-table.tsx index 476ec90f3..dc80a5326 100644 --- a/keep-ui/app/mapping/rules-table.tsx +++ b/keep-ui/app/mapping/rules-table.tsx @@ -18,7 +18,7 @@ import { ExpandedState, } from "@tanstack/react-table"; import { MdRemoveCircle, MdModeEdit } from "react-icons/md"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useApiUrl } from "utils/hooks/useConfig"; import { useMappings } from "utils/hooks/useMappingRules"; import { toast } from "react-toastify"; diff --git a/keep-ui/app/providers/page.client.tsx b/keep-ui/app/providers/page.client.tsx index b44cdf109..aac212780 100644 --- a/keep-ui/app/providers/page.client.tsx +++ b/keep-ui/app/providers/page.client.tsx @@ -1,6 +1,6 @@ "use client"; import { defaultProvider, Provider } from "./providers"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { KeepApiError } from "@/shared/lib/KeepApiError"; import { useApiUrl } from "utils/hooks/useConfig"; import ProvidersTiles from "./providers-tiles"; diff --git a/keep-ui/app/providers/provider-form.tsx b/keep-ui/app/providers/provider-form.tsx index 80e519a7d..5d59cf35a 100644 --- a/keep-ui/app/providers/provider-form.tsx +++ b/keep-ui/app/providers/provider-form.tsx @@ -2,7 +2,7 @@ // There's also a lot of s**t in here, but it works for now 🤷‍♂️ // @ts-nocheck import React, { useEffect, useState, useRef, useCallback } from "react"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { Provider } from "./providers"; import { useApiUrl } from "utils/hooks/useConfig"; import Image from "next/image"; diff --git a/keep-ui/app/rules/CorrelationSidebar/CorrelationSidebarBody.tsx b/keep-ui/app/rules/CorrelationSidebar/CorrelationSidebarBody.tsx index ffb225c04..29d5d8fa2 100644 --- a/keep-ui/app/rules/CorrelationSidebar/CorrelationSidebarBody.tsx +++ b/keep-ui/app/rules/CorrelationSidebar/CorrelationSidebarBody.tsx @@ -7,7 +7,7 @@ import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; import { CorrelationForm } from "./CorrelationForm"; import { CorrelationGroups } from "./CorrelationGroups"; import { CorrelationSubmission } from "./CorrelationSubmission"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useRules } from "utils/hooks/useRules"; import { CorrelationForm as CorrelationFormType } from "."; import { useRouter, useSearchParams } from "next/navigation"; diff --git a/keep-ui/app/rules/CorrelationSidebar/DeleteRule.tsx b/keep-ui/app/rules/CorrelationSidebar/DeleteRule.tsx index feabbeb1d..992a2151c 100644 --- a/keep-ui/app/rules/CorrelationSidebar/DeleteRule.tsx +++ b/keep-ui/app/rules/CorrelationSidebar/DeleteRule.tsx @@ -1,6 +1,6 @@ import { TrashIcon } from "@radix-ui/react-icons"; import { Button } from "@tremor/react"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { MouseEvent } from "react"; import { useApiUrl } from "utils/hooks/useConfig"; import { useRules } from "utils/hooks/useRules"; diff --git a/keep-ui/app/settings/auth/permissions-tab.tsx b/keep-ui/app/settings/auth/permissions-tab.tsx index 6a98e5445..702b00811 100644 --- a/keep-ui/app/settings/auth/permissions-tab.tsx +++ b/keep-ui/app/settings/auth/permissions-tab.tsx @@ -10,7 +10,7 @@ import Loading from "app/loading"; import { PermissionsTable } from "./permissions-table"; import PermissionSidebar from "./permissions-sidebar"; import { useApiUrl } from "utils/hooks/useConfig"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; interface Props { accessToken: string; diff --git a/keep-ui/app/settings/auth/users-sidebar.tsx b/keep-ui/app/settings/auth/users-sidebar.tsx index 7c5752ed5..b485ec6af 100644 --- a/keep-ui/app/settings/auth/users-sidebar.tsx +++ b/keep-ui/app/settings/auth/users-sidebar.tsx @@ -5,8 +5,6 @@ import { Subtitle, Button, TextInput, - SearchSelect, - SearchSelectItem, MultiSelect, MultiSelectItem, Callout, @@ -21,7 +19,7 @@ import { import { useRoles } from "utils/hooks/useRoles"; import { useGroups } from "utils/hooks/useGroups"; import { useApiUrl } from "utils/hooks/useConfig"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { User, Group } from "app/settings/models"; import { AuthenticationType } from "utils/authenticationType"; import { useConfig } from "utils/hooks/useConfig"; @@ -105,7 +103,9 @@ const UsersSidebar = ({ }, [user, setValue, isOpen, reset, clearErrors, identifierType]); const onSubmit: SubmitHandler = async (data) => { - if (!userCreationAllowed) return; + if (!userCreationAllowed) { + return; + } setIsSubmitting(true); clearErrors("root.serverError"); @@ -385,8 +385,8 @@ const UsersSidebar = ({ {isSubmitting ? "Saving..." : isNewUser - ? "Create User" - : "Save"} + ? "Create User" + : "Save"} )}
    diff --git a/keep-ui/app/settings/settings.client.tsx b/keep-ui/app/settings/settings.client.tsx index 0b8c213b2..36d912a69 100644 --- a/keep-ui/app/settings/settings.client.tsx +++ b/keep-ui/app/settings/settings.client.tsx @@ -11,7 +11,7 @@ import { LockClosedIcon, } from "@heroicons/react/24/outline"; import { MdOutlineSecurity } from "react-icons/md"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { useConfig } from "utils/hooks/useConfig"; import { AuthenticationType } from "utils/authenticationType"; @@ -73,24 +73,24 @@ export default function SettingsPage() { newSelectedTab === "users" ? 0 : newSelectedTab === "webhook" - ? 1 - : newSelectedTab === "smtp" - ? 2 - : 0; + ? 1 + : newSelectedTab === "smtp" + ? 2 + : 0; const userSubTabIndex = newUserSubTab === "users" ? 0 : newUserSubTab === "groups" - ? 1 - : newUserSubTab === "roles" - ? 2 - : newUserSubTab === "permissions" - ? 3 - : newUserSubTab === "api-keys" - ? 4 - : newUserSubTab === "sso" - ? 5 - : 0; + ? 1 + : newUserSubTab === "roles" + ? 2 + : newUserSubTab === "permissions" + ? 3 + : newUserSubTab === "api-keys" + ? 4 + : newUserSubTab === "sso" + ? 5 + : 0; setTabIndex(tabIndex); setUserSubTabIndex(userSubTabIndex); setSelectedTab(newSelectedTab); diff --git a/keep-ui/app/topology/model/useTopology.ts b/keep-ui/app/topology/model/useTopology.ts index 41f42354f..b93d42c48 100644 --- a/keep-ui/app/topology/model/useTopology.ts +++ b/keep-ui/app/topology/model/useTopology.ts @@ -1,5 +1,5 @@ import { TopologyService } from "@/app/topology/model/models"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import useSWR, { SWRConfiguration } from "swr"; import { fetcher } from "@/utils/fetcher"; import { useEffect } from "react"; diff --git a/keep-ui/app/topology/model/useTopologyApplications.ts b/keep-ui/app/topology/model/useTopologyApplications.ts index eb6c34b61..eb749d254 100644 --- a/keep-ui/app/topology/model/useTopologyApplications.ts +++ b/keep-ui/app/topology/model/useTopologyApplications.ts @@ -2,7 +2,7 @@ import { TopologyApplication } from "./models"; import { useApiUrl } from "utils/hooks/useConfig"; import useSWR, { SWRConfiguration } from "swr"; import { fetcher } from "@/utils/fetcher"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useCallback, useMemo } from "react"; import { useTopologyBaseKey, useTopology } from "./useTopology"; import { useRevalidateMultiple } from "@/utils/state"; diff --git a/keep-ui/app/workflows/[workflow_id]/executions.tsx b/keep-ui/app/workflows/[workflow_id]/executions.tsx index 86df4cd2b..963588fcd 100644 --- a/keep-ui/app/workflows/[workflow_id]/executions.tsx +++ b/keep-ui/app/workflows/[workflow_id]/executions.tsx @@ -1,7 +1,7 @@ "use client"; import { Callout, Card } from "@tremor/react"; import React, { useEffect, useState } from "react"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { ExclamationCircleIcon } from "@heroicons/react/24/outline"; import Loading from "app/loading"; import { useRouter } from "next/navigation"; diff --git a/keep-ui/app/workflows/builder/page.client.tsx b/keep-ui/app/workflows/builder/page.client.tsx index 73f9223d2..fd418e388 100644 --- a/keep-ui/app/workflows/builder/page.client.tsx +++ b/keep-ui/app/workflows/builder/page.client.tsx @@ -9,7 +9,7 @@ import { ArrowUpOnSquareIcon, PlayIcon, } from "@heroicons/react/20/solid"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { BuilderCard } from "./builder-card"; import Loading from "../../loading"; diff --git a/keep-ui/app/workflows/builder/workflow-execution-results.tsx b/keep-ui/app/workflows/builder/workflow-execution-results.tsx index b468682e2..5983214b7 100644 --- a/keep-ui/app/workflows/builder/workflow-execution-results.tsx +++ b/keep-ui/app/workflows/builder/workflow-execution-results.tsx @@ -7,7 +7,7 @@ import { Card, Title, } from "@tremor/react"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useApiUrl } from "utils/hooks/useConfig"; import Loading from "../../loading"; import { ExclamationCircleIcon } from "@heroicons/react/24/outline"; @@ -211,8 +211,8 @@ export function ExecutionResults({ log.message?.includes("NOT to run") ? "bg-red-100" : log.message?.includes("evaluated to run") - ? "bg-green-100" - : "" + ? "bg-green-100" + : "" }`} key={index} > diff --git a/keep-ui/app/workflows/dragndrop.tsx b/keep-ui/app/workflows/dragndrop.tsx index cc7c1d80f..2cf06f9e3 100644 --- a/keep-ui/app/workflows/dragndrop.tsx +++ b/keep-ui/app/workflows/dragndrop.tsx @@ -1,6 +1,6 @@ import React, { useRef, useState } from "react"; import { useApiUrl } from "utils/hooks/useConfig"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; const FileUpload: React.FC = () => { const apiUrl = useApiUrl(); diff --git a/keep-ui/app/workflows/manual-run-workflow-modal.tsx b/keep-ui/app/workflows/manual-run-workflow-modal.tsx index bd548ae12..825fb9375 100644 --- a/keep-ui/app/workflows/manual-run-workflow-modal.tsx +++ b/keep-ui/app/workflows/manual-run-workflow-modal.tsx @@ -3,7 +3,7 @@ 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 { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useApiUrl } from "utils/hooks/useConfig"; import { toast } from "react-toastify"; import { useRouter } from "next/navigation"; diff --git a/keep-ui/app/workflows/workflow-tile.tsx b/keep-ui/app/workflows/workflow-tile.tsx index ad9a4e636..636ad7e3d 100644 --- a/keep-ui/app/workflows/workflow-tile.tsx +++ b/keep-ui/app/workflows/workflow-tile.tsx @@ -1,6 +1,6 @@ "use client"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { Workflow, Filter } from "./models"; import { useApiUrl } from "utils/hooks/useConfig"; import Image from "next/image"; diff --git a/keep-ui/app/workflows/workflows.client.tsx b/keep-ui/app/workflows/workflows.client.tsx index 8a5d9e34b..58e53fc30 100644 --- a/keep-ui/app/workflows/workflows.client.tsx +++ b/keep-ui/app/workflows/workflows.client.tsx @@ -8,7 +8,7 @@ import { ExclamationCircleIcon, PlusCircleIcon, } from "@heroicons/react/24/outline"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { fetcher } from "../../utils/fetcher"; import { Workflow, MockWorkflow } from "./models"; import { useApiUrl } from "utils/hooks/useConfig"; diff --git a/keep-ui/entities/incidents/model/useIncidentActions.tsx b/keep-ui/entities/incidents/model/useIncidentActions.tsx index 98d676009..76b3074ad 100644 --- a/keep-ui/entities/incidents/model/useIncidentActions.tsx +++ b/keep-ui/entities/incidents/model/useIncidentActions.tsx @@ -1,5 +1,5 @@ import { useApiUrl } from "@/utils/hooks/useConfig"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useCallback } from "react"; import { toast } from "react-toastify"; import { useSWRConfig } from "swr"; diff --git a/keep-ui/shared/lib/hooks/useHydratedSession.tsx b/keep-ui/shared/lib/hooks/useHydratedSession.tsx new file mode 100644 index 000000000..6fddec50f --- /dev/null +++ b/keep-ui/shared/lib/hooks/useHydratedSession.tsx @@ -0,0 +1,31 @@ +"use client"; +import { useState, useEffect } from "react"; +import { useSession as useNextAuthSession } from "next-auth/react"; +import type { Session } from "next-auth"; +import type { UseSessionOptions, SessionContextValue } from "next-auth/react"; + +export function useHydratedSession( + options?: UseSessionOptions +): SessionContextValue { + const [isHydrated, setIsHydrated] = useState(false); + const session = useNextAuthSession(options); + useEffect(() => { + setIsHydrated(true); + }, []); + // Ensure we're in browser environment + const isBrowser = typeof window !== "undefined"; + // On first render, return hydrated session if available + if ( + (!isHydrated || session.status === "loading") && + isBrowser && + window.__NEXT_AUTH_SESSION__ !== null && + window.__NEXT_AUTH_SESSION__ !== undefined + ) { + return { + data: window.__NEXT_AUTH_SESSION__, + status: "authenticated" as const, + update: session.update, + } satisfies SessionContextValue; + } + return session; +} diff --git a/keep-ui/shared/ui/PostHogPageView.tsx b/keep-ui/shared/ui/PostHogPageView.tsx index d267f5968..818737a8d 100644 --- a/keep-ui/shared/ui/PostHogPageView.tsx +++ b/keep-ui/shared/ui/PostHogPageView.tsx @@ -5,7 +5,7 @@ import { usePathname, useSearchParams } from "next/navigation"; import { useEffect } from "react"; import { usePostHog } from "posthog-js/react"; import { useConfig } from "@/utils/hooks/useConfig"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "../lib/hooks/useHydratedSession"; import { NoAuthUserEmail } from "@/utils/authenticationType"; export default function PostHogPageView(): null { diff --git a/keep-ui/utils/hooks/useAI.ts b/keep-ui/utils/hooks/useAI.ts index 329830b32..80f5f2531 100644 --- a/keep-ui/utils/hooks/useAI.ts +++ b/keep-ui/utils/hooks/useAI.ts @@ -1,5 +1,5 @@ import { AILogs, AIStats } from "app/ai/model"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import useSWR, { SWRConfiguration } from "swr"; import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; diff --git a/keep-ui/utils/hooks/useAlertQuality.ts b/keep-ui/utils/hooks/useAlertQuality.ts index 9921da550..3445ae923 100644 --- a/keep-ui/utils/hooks/useAlertQuality.ts +++ b/keep-ui/utils/hooks/useAlertQuality.ts @@ -1,9 +1,9 @@ -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { SWRConfiguration } from "swr"; import { fetcher } from "../fetcher"; import useSWRImmutable from "swr/immutable"; import { useSearchParams } from "next/navigation"; -import { useMemo } from "react"; +import { useMemo } from "react"; import { useApiUrl } from "./useConfig"; export const useAlertQualityMetrics = ( @@ -19,7 +19,7 @@ export const useAlertQualityMetrics = ( const fieldArray = Array.isArray(fields) ? fields : [fields]; fieldArray.forEach((field) => params.append("fields", field)); } - + return params.toString(); }, [fields, searchParams]); // TODO: Proper type needs to be defined. diff --git a/keep-ui/utils/hooks/useAlerts.ts b/keep-ui/utils/hooks/useAlerts.ts index 4bbe5fd65..6b8abf516 100644 --- a/keep-ui/utils/hooks/useAlerts.ts +++ b/keep-ui/utils/hooks/useAlerts.ts @@ -1,6 +1,6 @@ import { useState, useEffect, useMemo } from "react"; import { AlertDto } from "app/alerts/models"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import useSWR, { SWRConfiguration } from "swr"; import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; diff --git a/keep-ui/utils/hooks/useDashboardMetricWidgets.ts b/keep-ui/utils/hooks/useDashboardMetricWidgets.ts index d7d24ecc8..51ab7ba9c 100644 --- a/keep-ui/utils/hooks/useDashboardMetricWidgets.ts +++ b/keep-ui/utils/hooks/useDashboardMetricWidgets.ts @@ -1,7 +1,7 @@ - import {useSession} from "next-auth/react"; - import { useApiUrl } from "./useConfig"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; +import { useApiUrl } from "./useConfig"; import useSWR from "swr"; -import {fetcher} from "@/utils/fetcher"; +import { fetcher } from "@/utils/fetcher"; import { usePathname, useSearchParams } from "next/navigation"; export interface MetricsWidget { @@ -12,7 +12,7 @@ export interface MetricsWidget { interface DistributionData { hour: string; - number: number + number: number; } interface DashboardDistributionData { @@ -20,47 +20,48 @@ interface DashboardDistributionData { ipd: DistributionData[]; apd: DistributionData[]; wpd: DistributionData[]; - } export const useDashboardMetricWidgets = (useFilters?: boolean) => { - const {data: session} = useSession(); + const { data: session } = useSession(); const apiUrl = useApiUrl(); const searchParams = useSearchParams(); const filters = searchParams?.toString(); - const {data, error, mutate} = useSWR( - session ? `${apiUrl}/dashboard/metric-widgets${ - useFilters && filters ? `?${filters}` : "" - }` : null, - (url: string) => fetcher(url, session!.accessToken) - ) - console.log(filters) + const { data, error, mutate } = useSWR( + session + ? `${apiUrl}/dashboard/metric-widgets${ + useFilters && filters ? `?${filters}` : "" + }` + : null, + (url: string) => fetcher(url, session!.accessToken) + ); + console.log(filters); - let widgets: MetricsWidget[] = [] + let widgets: MetricsWidget[] = []; if (data) { - widgets = [ + widgets = [ { id: "mttr", name: "MTTR", - data: data.mttr + data: data.mttr, }, { id: "apd", - "name": "Alerts/Day", - data: data.apd + name: "Alerts/Day", + data: data.apd, }, { id: "ipd", name: "Incidents/Day", - data: data.ipd + data: data.ipd, }, { id: "wpd", name: "Workflows/Day", - data: data.wpd - } + data: data.wpd, + }, ]; } - return {widgets}; -} \ No newline at end of file + return { widgets }; +}; diff --git a/keep-ui/utils/hooks/useDashboardPresets.ts b/keep-ui/utils/hooks/useDashboardPresets.ts index 2f5cd0310..807fd7e1d 100644 --- a/keep-ui/utils/hooks/useDashboardPresets.ts +++ b/keep-ui/utils/hooks/useDashboardPresets.ts @@ -1,16 +1,18 @@ -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { usePresets } from "./usePresets"; import { Preset } from "app/alerts/models"; -import { useMemo } from "react"; +import { useCallback, useMemo } from "react"; import { useSearchParams } from "next/navigation"; export const useDashboardPreset = () => { const { data: session } = useSession(); - const { useAllPresets, useStaticPresets, presetsOrderFromLS, staticPresetsOrderFromLS } = usePresets( - "dashboard", - true - ); + const { + useAllPresets, + useStaticPresets, + presetsOrderFromLS, + staticPresetsOrderFromLS, + } = usePresets("dashboard", true); const { data: presets = [] } = useAllPresets({ revalidateIfStale: false, revalidateOnFocus: false, @@ -20,24 +22,35 @@ export const useDashboardPreset = () => { }); const searchParams = useSearchParams(); - - const checkValidPreset = (preset: Preset) => { - if (!preset.is_private) { - return true; - } - return preset && preset.created_by == session?.user?.email; - }; + const checkValidPreset = useCallback( + (preset: Preset) => { + if (!preset.is_private) { + return true; + } + return preset && preset.created_by == session?.user?.email; + }, + [session] + ); let allPreset = useMemo(() => { /*If any filters are applied on the dashboard, we will fetch live data; otherwise, we will use data from localStorage to sync values between the navbar and the dashboard.*/ - let combinedPresets = searchParams?.toString() ? [...presets, ...fetchedPresets]: [...presetsOrderFromLS, ...staticPresetsOrderFromLS]; + let combinedPresets = searchParams?.toString() + ? [...presets, ...fetchedPresets] + : [...presetsOrderFromLS, ...staticPresetsOrderFromLS]; //private preset checks combinedPresets = combinedPresets.filter((preset) => checkValidPreset(preset) ); return combinedPresets; - }, [presets, fetchedPresets, searchParams, presets, fetchedPresets]); + }, [ + searchParams, + presets, + fetchedPresets, + presetsOrderFromLS, + staticPresetsOrderFromLS, + checkValidPreset, + ]); - return allPreset; + return allPreset; }; diff --git a/keep-ui/utils/hooks/useDashboards.ts b/keep-ui/utils/hooks/useDashboards.ts index 7f3c81245..e3c9e495c 100644 --- a/keep-ui/utils/hooks/useDashboards.ts +++ b/keep-ui/utils/hooks/useDashboards.ts @@ -1,5 +1,5 @@ import useSWR from "swr"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; diff --git a/keep-ui/utils/hooks/useDeduplicationRules.ts b/keep-ui/utils/hooks/useDeduplicationRules.ts index 54b68015b..74b13fd82 100644 --- a/keep-ui/utils/hooks/useDeduplicationRules.ts +++ b/keep-ui/utils/hooks/useDeduplicationRules.ts @@ -1,5 +1,5 @@ import { DeduplicationRule } from "app/deduplication/models"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { SWRConfiguration } from "swr"; import useSWRImmutable from "swr/immutable"; import { useApiUrl } from "./useConfig"; diff --git a/keep-ui/utils/hooks/useExtractionRules.ts b/keep-ui/utils/hooks/useExtractionRules.ts index 0ff32b445..4655326a5 100644 --- a/keep-ui/utils/hooks/useExtractionRules.ts +++ b/keep-ui/utils/hooks/useExtractionRules.ts @@ -1,5 +1,5 @@ import { ExtractionRule } from "app/extraction/model"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import useSWR, { SWRConfiguration } from "swr"; import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; diff --git a/keep-ui/utils/hooks/useGroups.ts b/keep-ui/utils/hooks/useGroups.ts index 3e1dde9a2..fd3c29dae 100644 --- a/keep-ui/utils/hooks/useGroups.ts +++ b/keep-ui/utils/hooks/useGroups.ts @@ -1,5 +1,5 @@ import { Group } from "app/settings/models"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { SWRConfiguration } from "swr"; import useSWRImmutable from "swr/immutable"; import { useApiUrl } from "./useConfig"; diff --git a/keep-ui/utils/hooks/useIncidents.ts b/keep-ui/utils/hooks/useIncidents.ts index 59ae7353f..c24bdc298 100644 --- a/keep-ui/utils/hooks/useIncidents.ts +++ b/keep-ui/utils/hooks/useIncidents.ts @@ -5,7 +5,7 @@ import { PaginatedIncidentsDto, } from "@/entities/incidents/model"; import { PaginatedWorkflowExecutionDto } from "app/workflows/builder/types"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import useSWR, { SWRConfiguration } from "swr"; import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; diff --git a/keep-ui/utils/hooks/useMaintenanceRules.ts b/keep-ui/utils/hooks/useMaintenanceRules.ts index b771615b1..f2d3f618b 100644 --- a/keep-ui/utils/hooks/useMaintenanceRules.ts +++ b/keep-ui/utils/hooks/useMaintenanceRules.ts @@ -1,5 +1,5 @@ import { MaintenanceRule } from "app/maintenance/model"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import useSWR, { SWRConfiguration } from "swr"; import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; diff --git a/keep-ui/utils/hooks/useMappingRules.ts b/keep-ui/utils/hooks/useMappingRules.ts index cf116bc9c..49042ae09 100644 --- a/keep-ui/utils/hooks/useMappingRules.ts +++ b/keep-ui/utils/hooks/useMappingRules.ts @@ -1,5 +1,5 @@ import { MappingRule } from "app/mapping/models"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import useSWR, { SWRConfiguration } from "swr"; import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; diff --git a/keep-ui/utils/hooks/usePermissions.ts b/keep-ui/utils/hooks/usePermissions.ts index b240ec2f6..05307ddc7 100644 --- a/keep-ui/utils/hooks/usePermissions.ts +++ b/keep-ui/utils/hooks/usePermissions.ts @@ -1,5 +1,5 @@ import { Permission } from "app/settings/models"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { SWRConfiguration } from "swr"; import useSWRImmutable from "swr/immutable"; import { useApiUrl } from "./useConfig"; diff --git a/keep-ui/utils/hooks/usePresets.ts b/keep-ui/utils/hooks/usePresets.ts index a0a0d76e2..5065c74b9 100644 --- a/keep-ui/utils/hooks/usePresets.ts +++ b/keep-ui/utils/hooks/usePresets.ts @@ -1,6 +1,6 @@ import { useState, useEffect, useRef } from "react"; import { Preset } from "app/alerts/models"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import useSWR, { SWRConfiguration } from "swr"; import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; diff --git a/keep-ui/utils/hooks/useProviders.ts b/keep-ui/utils/hooks/useProviders.ts index 668af3a1e..06ad74a4d 100644 --- a/keep-ui/utils/hooks/useProviders.ts +++ b/keep-ui/utils/hooks/useProviders.ts @@ -1,4 +1,4 @@ -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useApiUrl } from "./useConfig"; import { SWRConfiguration } from "swr"; import { ProvidersResponse } from "app/providers/providers"; diff --git a/keep-ui/utils/hooks/usePusher.ts b/keep-ui/utils/hooks/usePusher.ts index f1b0c23ed..c430ef054 100644 --- a/keep-ui/utils/hooks/usePusher.ts +++ b/keep-ui/utils/hooks/usePusher.ts @@ -1,6 +1,6 @@ import Pusher, { Options as PusherOptions } from "pusher-js"; import { useConfig } from "./useConfig"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useApiUrl } from "./useConfig"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; diff --git a/keep-ui/utils/hooks/useRoles.ts b/keep-ui/utils/hooks/useRoles.ts index dabfc9b58..cf37634fa 100644 --- a/keep-ui/utils/hooks/useRoles.ts +++ b/keep-ui/utils/hooks/useRoles.ts @@ -1,5 +1,5 @@ import { Role } from "app/settings/models"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { SWRConfiguration } from "swr"; import useSWRImmutable from "swr/immutable"; import { useApiUrl } from "./useConfig"; diff --git a/keep-ui/utils/hooks/useRules.ts b/keep-ui/utils/hooks/useRules.ts index 97976d51e..add981b45 100644 --- a/keep-ui/utils/hooks/useRules.ts +++ b/keep-ui/utils/hooks/useRules.ts @@ -1,4 +1,4 @@ -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import useSWR, { SWRConfiguration } from "swr"; import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; diff --git a/keep-ui/utils/hooks/useScopes.ts b/keep-ui/utils/hooks/useScopes.ts index 775d8717a..122a2a289 100644 --- a/keep-ui/utils/hooks/useScopes.ts +++ b/keep-ui/utils/hooks/useScopes.ts @@ -1,5 +1,5 @@ import { Scope } from "app/settings/models"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { SWRConfiguration } from "swr"; import useSWRImmutable from "swr/immutable"; import { useApiUrl } from "./useConfig"; diff --git a/keep-ui/utils/hooks/useSearchAlerts.ts b/keep-ui/utils/hooks/useSearchAlerts.ts index 01de3fe14..9ef8cb0b0 100644 --- a/keep-ui/utils/hooks/useSearchAlerts.ts +++ b/keep-ui/utils/hooks/useSearchAlerts.ts @@ -1,6 +1,6 @@ import useSWR, { SWRConfiguration } from "swr"; import { AlertDto } from "app/alerts/models"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useApiUrl } from "./useConfig"; import { fetcher } from "utils/fetcher"; import { useDebouncedValue } from "./useDebouncedValue"; diff --git a/keep-ui/utils/hooks/useTags.ts b/keep-ui/utils/hooks/useTags.ts index 124da6649..7587c386f 100644 --- a/keep-ui/utils/hooks/useTags.ts +++ b/keep-ui/utils/hooks/useTags.ts @@ -1,4 +1,4 @@ -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { SWRConfiguration } from "swr"; import useSWRImmutable from "swr/immutable"; import { useApiUrl } from "./useConfig"; diff --git a/keep-ui/utils/hooks/useUsers.ts b/keep-ui/utils/hooks/useUsers.ts index de254e040..723045071 100644 --- a/keep-ui/utils/hooks/useUsers.ts +++ b/keep-ui/utils/hooks/useUsers.ts @@ -1,5 +1,5 @@ import { User } from "app/settings/models"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { SWRConfiguration } from "swr"; import useSWRImmutable from "swr/immutable"; import { useApiUrl } from "./useConfig"; diff --git a/keep-ui/utils/hooks/useWorkflowExecutions.ts b/keep-ui/utils/hooks/useWorkflowExecutions.ts index 8e6fccc27..53fea2d8c 100644 --- a/keep-ui/utils/hooks/useWorkflowExecutions.ts +++ b/keep-ui/utils/hooks/useWorkflowExecutions.ts @@ -3,7 +3,7 @@ import { PaginatedWorkflowExecutionDto, WorkflowExecution, } from "app/workflows/builder/types"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useSearchParams } from "next/navigation"; import useSWR, { SWRConfiguration } from "swr"; import { useApiUrl } from "./useConfig"; diff --git a/keep-ui/utils/hooks/useWorkflowRun.ts b/keep-ui/utils/hooks/useWorkflowRun.ts index dbaa98dc8..a645e9f86 100644 --- a/keep-ui/utils/hooks/useWorkflowRun.ts +++ b/keep-ui/utils/hooks/useWorkflowRun.ts @@ -1,5 +1,5 @@ import { useState } from "react"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useApiUrl } from "./useConfig"; import { useRouter } from "next/navigation"; import { useProviders } from "./useProviders"; diff --git a/keep-ui/utils/hooks/useWorkflows.ts b/keep-ui/utils/hooks/useWorkflows.ts index 26859eaf2..1a8eae55c 100644 --- a/keep-ui/utils/hooks/useWorkflows.ts +++ b/keep-ui/utils/hooks/useWorkflows.ts @@ -1,5 +1,5 @@ import { Workflow } from "app/workflows/models"; -import { useSession } from "next-auth/react"; +import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { SWRConfiguration } from "swr"; import { useApiUrl } from "./useConfig"; import { fetcher } from "../fetcher"; From b96bd67eb87f2bb3198ddd79db8ca5468553c2ea Mon Sep 17 00:00:00 2001 From: Tal Date: Wed, 20 Nov 2024 11:52:51 +0200 Subject: [PATCH 09/24] feat: keep.join function for workflows (#2547) --- keep/api/core/db.py | 4 ++-- keep/functions/__init__.py | 18 +++++++++++++++++- keep/iohandler/iohandler.py | 11 ++--------- .../cloudwatch_provider/cloudwatch_provider.py | 3 +++ poetry.lock | 16 +++++++++++++++- pyproject.toml | 3 ++- 6 files changed, 41 insertions(+), 14 deletions(-) diff --git a/keep/api/core/db.py b/keep/api/core/db.py index e72dce8d2..2032f1cbb 100644 --- a/keep/api/core/db.py +++ b/keep/api/core/db.py @@ -827,7 +827,7 @@ def get_last_workflow_executions(tenant_id: str, limit=20): ) return execution_with_logs - + def get_workflow_executions_count(tenant_id: str): with Session(engine) as session: @@ -1745,7 +1745,7 @@ def get_incident_for_grouping_rule( # Create and add a new incident if it doesn't exist incident = Incident( tenant_id=tenant_id, - user_generated_name=f"Incident generated by rule {rule.name}", + user_generated_name=f"{rule.name}", rule_id=rule.id, rule_fingerprint=rule_fingerprint, is_predicted=False, diff --git a/keep/functions/__init__.py b/keep/functions/__init__.py index 503021a8e..14ab6de64 100644 --- a/keep/functions/__init__.py +++ b/keep/functions/__init__.py @@ -1,11 +1,11 @@ import copy import datetime -import json import re import urllib.parse from datetime import timedelta from itertools import groupby +import json5 as json import pytz from dateutil import parser from dateutil.parser import ParserError @@ -175,6 +175,22 @@ def slice(str_to_slice: str, start: int = 0, end: int = 0) -> str: return str_to_slice[int(start) : int(end)] +def join( + iterable: list | dict | str, delimiter: str = ",", prefix: str | None = None +) -> str: + if isinstance(iterable, str): + iterable = json.loads(iterable) + + if isinstance(iterable, dict): + if prefix: + return delimiter.join([f"{prefix}{k}={v}" for k, v in iterable.items()]) + return delimiter.join([f"{k}={v}" for k, v in iterable.items()]) + + if prefix: + return delimiter.join([f"{prefix}{item}" for item in iterable]) + return delimiter.join([str(item) for item in iterable]) + + def dict_pop(data: str | dict, *args) -> dict: if isinstance(data, str): data = json.loads(data) diff --git a/keep/iohandler/iohandler.py b/keep/iohandler/iohandler.py index 7f0f22fec..4bcbb015c 100644 --- a/keep/iohandler/iohandler.py +++ b/keep/iohandler/iohandler.py @@ -520,17 +520,10 @@ def __get_short_urls(self, urls: list) -> dict: if __name__ == "__main__": # debug & test context_manager = ContextManager("keep") - context_manager.event_context = { - "ticket_id": "1234", - "severity": "high", - "ticket_created_at": "2021-09-01T00:00:00Z", - } + context_manager.event_context = {"tags": {"k1": "v1", "k2": "v2"}} iohandler = IOHandler(context_manager) res = iohandler.render( - iohandler.quote( - "not '{{ alert.ticket_id }}' or (('{{ alert.ticket_status }}' in ['Resolved', 'Closed', 'Canceled']) and ('{{ alert.severity }}' == 'critical' or keep.datetime_compare(keep.utcnow(), keep.to_utc('{{ alert.ticket_created_at }}')) > 168))" - ), - safe=False, + 'https://www.keephq.dev?keep.join("{{alert.tags}}", "&", "prefix_")' ) from asteval import Interpreter diff --git a/keep/providers/cloudwatch_provider/cloudwatch_provider.py b/keep/providers/cloudwatch_provider/cloudwatch_provider.py index 3a04a9521..be200f8ca 100644 --- a/keep/providers/cloudwatch_provider/cloudwatch_provider.py +++ b/keep/providers/cloudwatch_provider/cloudwatch_provider.py @@ -257,6 +257,7 @@ def validate_scopes(self): # 4. validate start query logs_client = self.__generate_client("logs") + query = False try: query = logs_client.start_query( logGroupName="keepTest", @@ -277,6 +278,8 @@ def validate_scopes(self): else: self.logger.info("Error validating AWS logs:StartQuery scope") scopes["logs:StartQuery"] = str(e) + + query_id = False if query: try: query_id = logs_client.describe_queries().get("queries")[0]["queryId"] diff --git a/poetry.lock b/poetry.lock index 5ed29a9fb..65fa8a5cc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2237,6 +2237,20 @@ files = [ {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, ] +[[package]] +name = "json5" +version = "0.9.28" +description = "A Python implementation of the JSON5 data format." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "json5-0.9.28-py3-none-any.whl", hash = "sha256:29c56f1accdd8bc2e037321237662034a7e07921e2b7223281a5ce2c46f0c4df"}, + {file = "json5-0.9.28.tar.gz", hash = "sha256:1f82f36e615bc5b42f1bbd49dbc94b12563c56408c6ffa06414ea310890e9a6e"}, +] + +[package.extras] +dev = ["build (==1.2.2.post1)", "coverage (==7.5.3)", "mypy (==1.13.0)", "pip (==24.3.1)", "pylint (==3.2.3)", "ruff (==0.7.3)", "twine (==5.1.1)", "uv (==0.5.1)"] + [[package]] name = "jwcrypto" version = "1.5.6" @@ -5353,4 +5367,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = ">=3.11,<3.12" -content-hash = "d405584a8c24ff3b5e3c7f4405d24145e5b2bf7b951f5c6279977d7cd0780d5e" +content-hash = "1f7fba5a2c241db5654d7f84018d40650402bdc53e46ef5b09e7fcf3d6f055a8" diff --git a/pyproject.toml b/pyproject.toml index ee75f9569..713a701e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "keep" -version = "0.29.1" +version = "0.29.2" description = "Alerting. for developers, by developers." authors = ["Keep Alerting LTD"] readme = "README.md" @@ -95,6 +95,7 @@ networkx = "^3.3" google-auth = "2.34.0" clickhouse-driver = "^0.2.9" google-cloud-logging = "^3.11.3" +json5 = "^0.9.28" [tool.poetry.group.dev.dependencies] pre-commit = "^3.0.4" pre-commit-hooks = "^4.4.0" From dae291b5ec952ddb02b088d5098ee3bee560ae55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 12:10:02 +0200 Subject: [PATCH 10/24] chore(deps-dev): bump aiohttp from 3.10.2 to 3.10.11 (#2548) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 455 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 285 insertions(+), 170 deletions(-) diff --git a/poetry.lock b/poetry.lock index 65fa8a5cc..cea7ddf06 100644 --- a/poetry.lock +++ b/poetry.lock @@ -24,87 +24,102 @@ files = [ [[package]] name = "aiohttp" -version = "3.10.2" +version = "3.10.11" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.10.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:95213b3d79c7e387144e9cb7b9d2809092d6ff2c044cb59033aedc612f38fb6d"}, - {file = "aiohttp-3.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1aa005f060aff7124cfadaa2493f00a4e28ed41b232add5869e129a2e395935a"}, - {file = "aiohttp-3.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eabe6bf4c199687592f5de4ccd383945f485779c7ffb62a9b9f1f8a3f9756df8"}, - {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e010736fc16d21125c7e2dc5c350cd43c528b85085c04bf73a77be328fe944"}, - {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99f81f9c1529fd8e03be4a7bd7df32d14b4f856e90ef6e9cbad3415dbfa9166c"}, - {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d611d1a01c25277bcdea06879afbc11472e33ce842322496b211319aa95441bb"}, - {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00191d38156e09e8c81ef3d75c0d70d4f209b8381e71622165f22ef7da6f101"}, - {file = "aiohttp-3.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74c091a5ded6cb81785de2d7a8ab703731f26de910dbe0f3934eabef4ae417cc"}, - {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:18186a80ec5a701816adbf1d779926e1069392cf18504528d6e52e14b5920525"}, - {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5a7ceb2a0d2280f23a02c64cd0afdc922079bb950400c3dd13a1ab2988428aac"}, - {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8bd7be6ff6c162a60cb8fce65ee879a684fbb63d5466aba3fa5b9288eb04aefa"}, - {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fae962b62944eaebff4f4fddcf1a69de919e7b967136a318533d82d93c3c6bd1"}, - {file = "aiohttp-3.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a0fde16d284efcacbe15fb0c1013f0967b6c3e379649239d783868230bf1db42"}, - {file = "aiohttp-3.10.2-cp310-cp310-win32.whl", hash = "sha256:f81cd85a0e76ec7b8e2b6636fe02952d35befda4196b8c88f3cec5b4fb512839"}, - {file = "aiohttp-3.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:54ba10eb5a3481c28282eb6afb5f709aedf53cf9c3a31875ffbdc9fc719ffd67"}, - {file = "aiohttp-3.10.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:87fab7f948e407444c2f57088286e00e2ed0003ceaf3d8f8cc0f60544ba61d91"}, - {file = "aiohttp-3.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec6ad66ed660d46503243cbec7b2b3d8ddfa020f984209b3b8ef7d98ce69c3f2"}, - {file = "aiohttp-3.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a4be88807283bd96ae7b8e401abde4ca0bab597ba73b5e9a2d98f36d451e9aac"}, - {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01c98041f90927c2cbd72c22a164bb816fa3010a047d264969cf82e1d4bcf8d1"}, - {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54e36c67e1a9273ecafab18d6693da0fb5ac48fd48417e4548ac24a918c20998"}, - {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7de3ddb6f424af54535424082a1b5d1ae8caf8256ebd445be68c31c662354720"}, - {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dd9c7db94b4692b827ce51dcee597d61a0e4f4661162424faf65106775b40e7"}, - {file = "aiohttp-3.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e57e21e1167705f8482ca29cc5d02702208d8bf4aff58f766d94bcd6ead838cd"}, - {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a1a50e59b720060c29e2951fd9f13c01e1ea9492e5a527b92cfe04dd64453c16"}, - {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:686c87782481fda5ee6ba572d912a5c26d9f98cc5c243ebd03f95222af3f1b0f"}, - {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:dafb4abb257c0ed56dc36f4e928a7341b34b1379bd87e5a15ce5d883c2c90574"}, - {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:494a6f77560e02bd7d1ab579fdf8192390567fc96a603f21370f6e63690b7f3d"}, - {file = "aiohttp-3.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6fe8503b1b917508cc68bf44dae28823ac05e9f091021e0c41f806ebbb23f92f"}, - {file = "aiohttp-3.10.2-cp311-cp311-win32.whl", hash = "sha256:4ddb43d06ce786221c0dfd3c91b4892c318eaa36b903f7c4278e7e2fa0dd5102"}, - {file = "aiohttp-3.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:ca2f5abcb0a9a47e56bac173c01e9f6c6e7f27534d91451c5f22e6a35a5a2093"}, - {file = "aiohttp-3.10.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:14eb6b17f6246959fb0b035d4f4ae52caa870c4edfb6170aad14c0de5bfbf478"}, - {file = "aiohttp-3.10.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:465e445ec348d4e4bd349edd8b22db75f025da9d7b6dc1369c48e7935b85581e"}, - {file = "aiohttp-3.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:341f8ece0276a828d95b70cd265d20e257f5132b46bf77d759d7f4e0443f2906"}, - {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c01fbb87b5426381cd9418b3ddcf4fc107e296fa2d3446c18ce6c76642f340a3"}, - {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c474af073e1a6763e1c5522bbb2d85ff8318197e4c6c919b8d7886e16213345"}, - {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d9076810a5621236e29b2204e67a68e1fe317c8727ee4c9abbfbb1083b442c38"}, - {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8f515d6859e673940e08de3922b9c4a2249653b0ac181169313bd6e4b1978ac"}, - {file = "aiohttp-3.10.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:655e583afc639bef06f3b2446972c1726007a21003cd0ef57116a123e44601bc"}, - {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8da9449a575133828cc99985536552ea2dcd690e848f9d41b48d8853a149a959"}, - {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:19073d57d0feb1865d12361e2a1f5a49cb764bf81a4024a3b608ab521568093a"}, - {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8e98e1845805f184d91fda6f9ab93d7c7b0dddf1c07e0255924bfdb151a8d05"}, - {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:377220a5efde6f9497c5b74649b8c261d3cce8a84cb661be2ed8099a2196400a"}, - {file = "aiohttp-3.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:92f7f4a4dc9cdb5980973a74d43cdbb16286dacf8d1896b6c3023b8ba8436f8e"}, - {file = "aiohttp-3.10.2-cp312-cp312-win32.whl", hash = "sha256:9bb2834a6f11d65374ce97d366d6311a9155ef92c4f0cee543b2155d06dc921f"}, - {file = "aiohttp-3.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:518dc3cb37365255708283d1c1c54485bbacccd84f0a0fb87ed8917ba45eda5b"}, - {file = "aiohttp-3.10.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7f98e70bbbf693086efe4b86d381efad8edac040b8ad02821453083d15ec315f"}, - {file = "aiohttp-3.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9f6f0b252a009e98fe84028a4ec48396a948e7a65b8be06ccfc6ef68cf1f614d"}, - {file = "aiohttp-3.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9360e3ffc7b23565600e729e8c639c3c50d5520e05fdf94aa2bd859eef12c407"}, - {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3988044d1635c7821dd44f0edfbe47e9875427464e59d548aece447f8c22800a"}, - {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a9d59da1543a6f1478c3436fd49ec59be3868bca561a33778b4391005e499d"}, - {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9f49bdb94809ac56e09a310a62f33e5f22973d6fd351aac72a39cd551e98194"}, - {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddfd2dca3f11c365d6857a07e7d12985afc59798458a2fdb2ffa4a0332a3fd43"}, - {file = "aiohttp-3.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c1508ec97b2cd3e120bfe309a4ff8e852e8a7460f1ef1de00c2c0ed01e33c"}, - {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:49904f38667c44c041a0b44c474b3ae36948d16a0398a8f8cd84e2bb3c42a069"}, - {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:352f3a4e5f11f3241a49b6a48bc5b935fabc35d1165fa0d87f3ca99c1fcca98b"}, - {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:fc61f39b534c5d5903490478a0dd349df397d2284a939aa3cbaa2fb7a19b8397"}, - {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:ad2274e707be37420d0b6c3d26a8115295fe9d8e6e530fa6a42487a8ca3ad052"}, - {file = "aiohttp-3.10.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c836bf3c7512100219fe1123743fd8dd9a2b50dd7cfb0c3bb10d041309acab4b"}, - {file = "aiohttp-3.10.2-cp38-cp38-win32.whl", hash = "sha256:53e8898adda402be03ff164b0878abe2d884e3ea03a4701e6ad55399d84b92dc"}, - {file = "aiohttp-3.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:7cc8f65f5b22304693de05a245b6736b14cb5bc9c8a03da6e2ae9ef15f8b458f"}, - {file = "aiohttp-3.10.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9dfc906d656e14004c5bc672399c1cccc10db38df2b62a13fb2b6e165a81c316"}, - {file = "aiohttp-3.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:91b10208b222ddf655c3a3d5b727879d7163db12b634492df41a9182a76edaae"}, - {file = "aiohttp-3.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9fd16b5e1a7bdd14668cd6bde60a2a29b49147a535c74f50d8177d11b38433a7"}, - {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2bfdda4971bd79201f59adbad24ec2728875237e1c83bba5221284dbbf57bda"}, - {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69d73f869cf29e8a373127fc378014e2b17bcfbe8d89134bc6fb06a2f67f3cb3"}, - {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df59f8486507c421c0620a2c3dce81fbf1d54018dc20ff4fecdb2c106d6e6abc"}, - {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df930015db36b460aa9badbf35eccbc383f00d52d4b6f3de2ccb57d064a6ade"}, - {file = "aiohttp-3.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:562b1153ab7f766ee6b8b357ec777a302770ad017cf18505d34f1c088fccc448"}, - {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d984db6d855de58e0fde1ef908d48fe9a634cadb3cf715962722b4da1c40619d"}, - {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:14dc3fcb0d877911d775d511eb617a486a8c48afca0a887276e63db04d3ee920"}, - {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b52a27a5c97275e254704e1049f4b96a81e67d6205f52fa37a4777d55b0e98ef"}, - {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:cd33d9de8cfd006a0d0fe85f49b4183c57e91d18ffb7e9004ce855e81928f704"}, - {file = "aiohttp-3.10.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1238fc979160bc03a92fff9ad021375ff1c8799c6aacb0d8ea1b357ea40932bb"}, - {file = "aiohttp-3.10.2-cp39-cp39-win32.whl", hash = "sha256:e2f43d238eae4f0b04f58d4c0df4615697d4ca3e9f9b1963d49555a94f0f5a04"}, - {file = "aiohttp-3.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:947847f07a8f81d7b39b2d0202fd73e61962ebe17ac2d8566f260679e467da7b"}, - {file = "aiohttp-3.10.2.tar.gz", hash = "sha256:4d1f694b5d6e459352e5e925a42e05bac66655bfde44d81c59992463d2897014"}, + {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5077b1a5f40ffa3ba1f40d537d3bec4383988ee51fbba6b74aa8fb1bc466599e"}, + {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d6a14a4d93b5b3c2891fca94fa9d41b2322a68194422bef0dd5ec1e57d7d298"}, + {file = "aiohttp-3.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffbfde2443696345e23a3c597049b1dd43049bb65337837574205e7368472177"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20b3d9e416774d41813bc02fdc0663379c01817b0874b932b81c7f777f67b217"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b943011b45ee6bf74b22245c6faab736363678e910504dd7531a58c76c9015a"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48bc1d924490f0d0b3658fe5c4b081a4d56ebb58af80a6729d4bd13ea569797a"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e12eb3f4b1f72aaaf6acd27d045753b18101524f72ae071ae1c91c1cd44ef115"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f14ebc419a568c2eff3c1ed35f634435c24ead2fe19c07426af41e7adb68713a"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:72b191cdf35a518bfc7ca87d770d30941decc5aaf897ec8b484eb5cc8c7706f3"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5ab2328a61fdc86424ee540d0aeb8b73bbcad7351fb7cf7a6546fc0bcffa0038"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aa93063d4af05c49276cf14e419550a3f45258b6b9d1f16403e777f1addf4519"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:30283f9d0ce420363c24c5c2421e71a738a2155f10adbb1a11a4d4d6d2715cfc"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e5358addc8044ee49143c546d2182c15b4ac3a60be01c3209374ace05af5733d"}, + {file = "aiohttp-3.10.11-cp310-cp310-win32.whl", hash = "sha256:e1ffa713d3ea7cdcd4aea9cddccab41edf6882fa9552940344c44e59652e1120"}, + {file = "aiohttp-3.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:778cbd01f18ff78b5dd23c77eb82987ee4ba23408cbed233009fd570dda7e674"}, + {file = "aiohttp-3.10.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:80ff08556c7f59a7972b1e8919f62e9c069c33566a6d28586771711e0eea4f07"}, + {file = "aiohttp-3.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c8f96e9ee19f04c4914e4e7a42a60861066d3e1abf05c726f38d9d0a466e695"}, + {file = "aiohttp-3.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fb8601394d537da9221947b5d6e62b064c9a43e88a1ecd7414d21a1a6fba9c24"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ea224cf7bc2d8856d6971cea73b1d50c9c51d36971faf1abc169a0d5f85a382"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db9503f79e12d5d80b3efd4d01312853565c05367493379df76d2674af881caa"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0f449a50cc33f0384f633894d8d3cd020e3ccef81879c6e6245c3c375c448625"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82052be3e6d9e0c123499127782a01a2b224b8af8c62ab46b3f6197035ad94e9"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20063c7acf1eec550c8eb098deb5ed9e1bb0521613b03bb93644b810986027ac"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:489cced07a4c11488f47aab1f00d0c572506883f877af100a38f1fedaa884c3a"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea9b3bab329aeaa603ed3bf605f1e2a6f36496ad7e0e1aa42025f368ee2dc07b"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ca117819d8ad113413016cb29774b3f6d99ad23c220069789fc050267b786c16"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2dfb612dcbe70fb7cdcf3499e8d483079b89749c857a8f6e80263b021745c730"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9b615d3da0d60e7d53c62e22b4fd1c70f4ae5993a44687b011ea3a2e49051b8"}, + {file = "aiohttp-3.10.11-cp311-cp311-win32.whl", hash = "sha256:29103f9099b6068bbdf44d6a3d090e0a0b2be6d3c9f16a070dd9d0d910ec08f9"}, + {file = "aiohttp-3.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:236b28ceb79532da85d59aa9b9bf873b364e27a0acb2ceaba475dc61cffb6f3f"}, + {file = "aiohttp-3.10.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7480519f70e32bfb101d71fb9a1f330fbd291655a4c1c922232a48c458c52710"}, + {file = "aiohttp-3.10.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f65267266c9aeb2287a6622ee2bb39490292552f9fbf851baabc04c9f84e048d"}, + {file = "aiohttp-3.10.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7400a93d629a0608dc1d6c55f1e3d6e07f7375745aaa8bd7f085571e4d1cee97"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f34b97e4b11b8d4eb2c3a4f975be626cc8af99ff479da7de49ac2c6d02d35725"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e7b825da878464a252ccff2958838f9caa82f32a8dbc334eb9b34a026e2c636"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9f92a344c50b9667827da308473005f34767b6a2a60d9acff56ae94f895f385"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f1ab987a27b83c5268a17218463c2ec08dbb754195113867a27b166cd6087"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1dc0f4ca54842173d03322793ebcf2c8cc2d34ae91cc762478e295d8e361e03f"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7ce6a51469bfaacff146e59e7fb61c9c23006495d11cc24c514a455032bcfa03"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:aad3cd91d484d065ede16f3cf15408254e2469e3f613b241a1db552c5eb7ab7d"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f4df4b8ca97f658c880fb4b90b1d1ec528315d4030af1ec763247ebfd33d8b9a"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2e4e18a0a2d03531edbc06c366954e40a3f8d2a88d2b936bbe78a0c75a3aab3e"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ce66780fa1a20e45bc753cda2a149daa6dbf1561fc1289fa0c308391c7bc0a4"}, + {file = "aiohttp-3.10.11-cp312-cp312-win32.whl", hash = "sha256:a919c8957695ea4c0e7a3e8d16494e3477b86f33067478f43106921c2fef15bb"}, + {file = "aiohttp-3.10.11-cp312-cp312-win_amd64.whl", hash = "sha256:b5e29706e6389a2283a91611c91bf24f218962717c8f3b4e528ef529d112ee27"}, + {file = "aiohttp-3.10.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:703938e22434d7d14ec22f9f310559331f455018389222eed132808cd8f44127"}, + {file = "aiohttp-3.10.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9bc50b63648840854e00084c2b43035a62e033cb9b06d8c22b409d56eb098413"}, + {file = "aiohttp-3.10.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f0463bf8b0754bc744e1feb61590706823795041e63edf30118a6f0bf577461"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6c6dec398ac5a87cb3a407b068e1106b20ef001c344e34154616183fe684288"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcaf2d79104d53d4dcf934f7ce76d3d155302d07dae24dff6c9fffd217568067"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25fd5470922091b5a9aeeb7e75be609e16b4fba81cdeaf12981393fb240dd10e"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbde2ca67230923a42161b1f408c3992ae6e0be782dca0c44cb3206bf330dee1"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:249c8ff8d26a8b41a0f12f9df804e7c685ca35a207e2410adbd3e924217b9006"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:878ca6a931ee8c486a8f7b432b65431d095c522cbeb34892bee5be97b3481d0f"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8663f7777ce775f0413324be0d96d9730959b2ca73d9b7e2c2c90539139cbdd6"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6cd3f10b01f0c31481fba8d302b61603a2acb37b9d30e1d14e0f5a58b7b18a31"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e8d8aad9402d3aa02fdc5ca2fe68bcb9fdfe1f77b40b10410a94c7f408b664d"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:38e3c4f80196b4f6c3a85d134a534a56f52da9cb8d8e7af1b79a32eefee73a00"}, + {file = "aiohttp-3.10.11-cp313-cp313-win32.whl", hash = "sha256:fc31820cfc3b2863c6e95e14fcf815dc7afe52480b4dc03393c4873bb5599f71"}, + {file = "aiohttp-3.10.11-cp313-cp313-win_amd64.whl", hash = "sha256:4996ff1345704ffdd6d75fb06ed175938c133425af616142e7187f28dc75f14e"}, + {file = "aiohttp-3.10.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:74baf1a7d948b3d640badeac333af581a367ab916b37e44cf90a0334157cdfd2"}, + {file = "aiohttp-3.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:473aebc3b871646e1940c05268d451f2543a1d209f47035b594b9d4e91ce8339"}, + {file = "aiohttp-3.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c2f746a6968c54ab2186574e15c3f14f3e7f67aef12b761e043b33b89c5b5f95"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d110cabad8360ffa0dec8f6ec60e43286e9d251e77db4763a87dcfe55b4adb92"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0099c7d5d7afff4202a0c670e5b723f7718810000b4abcbc96b064129e64bc7"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0316e624b754dbbf8c872b62fe6dcb395ef20c70e59890dfa0de9eafccd2849d"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a5f7ab8baf13314e6b2485965cbacb94afff1e93466ac4d06a47a81c50f9cca"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c891011e76041e6508cbfc469dd1a8ea09bc24e87e4c204e05f150c4c455a5fa"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9208299251370ee815473270c52cd3f7069ee9ed348d941d574d1457d2c73e8b"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:459f0f32c8356e8125f45eeff0ecf2b1cb6db1551304972702f34cd9e6c44658"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:14cdc8c1810bbd4b4b9f142eeee23cda528ae4e57ea0923551a9af4820980e39"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:971aa438a29701d4b34e4943e91b5e984c3ae6ccbf80dd9efaffb01bd0b243a9"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9a309c5de392dfe0f32ee57fa43ed8fc6ddf9985425e84bd51ed66bb16bce3a7"}, + {file = "aiohttp-3.10.11-cp38-cp38-win32.whl", hash = "sha256:9ec1628180241d906a0840b38f162a3215114b14541f1a8711c368a8739a9be4"}, + {file = "aiohttp-3.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:9c6e0ffd52c929f985c7258f83185d17c76d4275ad22e90aa29f38e211aacbec"}, + {file = "aiohttp-3.10.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cdc493a2e5d8dc79b2df5bec9558425bcd39aff59fc949810cbd0832e294b106"}, + {file = "aiohttp-3.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3e70f24e7d0405be2348da9d5a7836936bf3a9b4fd210f8c37e8d48bc32eca6"}, + {file = "aiohttp-3.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968b8fb2a5eee2770eda9c7b5581587ef9b96fbdf8dcabc6b446d35ccc69df01"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deef4362af9493d1382ef86732ee2e4cbc0d7c005947bd54ad1a9a16dd59298e"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:686b03196976e327412a1b094f4120778c7c4b9cff9bce8d2fdfeca386b89829"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3bf6d027d9d1d34e1c2e1645f18a6498c98d634f8e373395221121f1c258ace8"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:099fd126bf960f96d34a760e747a629c27fb3634da5d05c7ef4d35ef4ea519fc"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c73c4d3dae0b4644bc21e3de546530531d6cdc88659cdeb6579cd627d3c206aa"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0c5580f3c51eea91559db3facd45d72e7ec970b04528b4709b1f9c2555bd6d0b"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fdf6429f0caabfd8a30c4e2eaecb547b3c340e4730ebfe25139779b9815ba138"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d97187de3c276263db3564bb9d9fad9e15b51ea10a371ffa5947a5ba93ad6777"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0acafb350cfb2eba70eb5d271f55e08bd4502ec35e964e18ad3e7d34d71f7261"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c13ed0c779911c7998a58e7848954bd4d63df3e3575f591e321b19a2aec8df9f"}, + {file = "aiohttp-3.10.11-cp39-cp39-win32.whl", hash = "sha256:22b7c540c55909140f63ab4f54ec2c20d2635c0289cdd8006da46f3327f971b9"}, + {file = "aiohttp-3.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:7b26b1551e481012575dab8e3727b16fe7dd27eb2711d2e63ced7368756268fb"}, + {file = "aiohttp-3.10.11.tar.gz", hash = "sha256:9dc2b8f3dcab2e39e0fa309c8da50c3b55e6f34ab25f1a71d3288f24924d33a7"}, ] [package.dependencies] @@ -113,7 +128,7 @@ aiosignal = ">=1.1.2" attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" -yarl = ">=1.0,<2.0" +yarl = ">=1.12.0,<2.0" [package.extras] speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] @@ -3387,6 +3402,113 @@ wcwidth = "*" [package.extras] tests = ["pytest", "pytest-cov", "pytest-lazy-fixtures"] +[[package]] +name = "propcache" +version = "0.2.0" +description = "Accelerated property cache" +optional = false +python-versions = ">=3.8" +files = [ + {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, + {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, + {file = "propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336"}, + {file = "propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad"}, + {file = "propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99"}, + {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354"}, + {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de"}, + {file = "propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b"}, + {file = "propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1"}, + {file = "propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71"}, + {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2"}, + {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7"}, + {file = "propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348"}, + {file = "propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5"}, + {file = "propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3"}, + {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7"}, + {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763"}, + {file = "propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544"}, + {file = "propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032"}, + {file = "propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e"}, + {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861"}, + {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6"}, + {file = "propcache-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed"}, + {file = "propcache-0.2.0-cp38-cp38-win32.whl", hash = "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d"}, + {file = "propcache-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5"}, + {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6"}, + {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638"}, + {file = "propcache-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798"}, + {file = "propcache-0.2.0-cp39-cp39-win32.whl", hash = "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9"}, + {file = "propcache-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df"}, + {file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"}, + {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, +] + [[package]] name = "proto-plus" version = "1.23.0" @@ -5248,106 +5370,99 @@ dev = ["doc8", "flake8", "flake8-import-order", "rstcheck[sphinx]", "sphinx"] [[package]] name = "yarl" -version = "1.9.4" +version = "1.17.2" description = "Yet another URL library" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, - {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, - {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, - {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, - {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, - {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, - {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, - {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, - {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, - {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, - {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, - {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, - {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, - {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, - {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, - {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, + {file = "yarl-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:93771146ef048b34201bfa382c2bf74c524980870bb278e6df515efaf93699ff"}, + {file = "yarl-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8281db240a1616af2f9c5f71d355057e73a1409c4648c8949901396dc0a3c151"}, + {file = "yarl-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:170ed4971bf9058582b01a8338605f4d8c849bd88834061e60e83b52d0c76870"}, + {file = "yarl-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc61b005f6521fcc00ca0d1243559a5850b9dd1e1fe07b891410ee8fe192d0c0"}, + {file = "yarl-1.17.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:871e1b47eec7b6df76b23c642a81db5dd6536cbef26b7e80e7c56c2fd371382e"}, + {file = "yarl-1.17.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a58a2f2ca7aaf22b265388d40232f453f67a6def7355a840b98c2d547bd037f"}, + {file = "yarl-1.17.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:736bb076f7299c5c55dfef3eb9e96071a795cb08052822c2bb349b06f4cb2e0a"}, + {file = "yarl-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8fd51299e21da709eabcd5b2dd60e39090804431292daacbee8d3dabe39a6bc0"}, + {file = "yarl-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:358dc7ddf25e79e1cc8ee16d970c23faee84d532b873519c5036dbb858965795"}, + {file = "yarl-1.17.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:50d866f7b1a3f16f98603e095f24c0eeba25eb508c85a2c5939c8b3870ba2df8"}, + {file = "yarl-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8b9c4643e7d843a0dca9cd9d610a0876e90a1b2cbc4c5ba7930a0d90baf6903f"}, + {file = "yarl-1.17.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d63123bfd0dce5f91101e77c8a5427c3872501acece8c90df457b486bc1acd47"}, + {file = "yarl-1.17.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:4e76381be3d8ff96a4e6c77815653063e87555981329cf8f85e5be5abf449021"}, + {file = "yarl-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:734144cd2bd633a1516948e477ff6c835041c0536cef1d5b9a823ae29899665b"}, + {file = "yarl-1.17.2-cp310-cp310-win32.whl", hash = "sha256:26bfb6226e0c157af5da16d2d62258f1ac578d2899130a50433ffee4a5dfa673"}, + {file = "yarl-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:76499469dcc24759399accd85ec27f237d52dec300daaca46a5352fcbebb1071"}, + {file = "yarl-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:792155279dc093839e43f85ff7b9b6493a8eaa0af1f94f1f9c6e8f4de8c63500"}, + {file = "yarl-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:38bc4ed5cae853409cb193c87c86cd0bc8d3a70fd2268a9807217b9176093ac6"}, + {file = "yarl-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4a8c83f6fcdc327783bdc737e8e45b2e909b7bd108c4da1892d3bc59c04a6d84"}, + {file = "yarl-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c6d5fed96f0646bfdf698b0a1cebf32b8aae6892d1bec0c5d2d6e2df44e1e2d"}, + {file = "yarl-1.17.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:782ca9c58f5c491c7afa55518542b2b005caedaf4685ec814fadfcee51f02493"}, + {file = "yarl-1.17.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ff6af03cac0d1a4c3c19e5dcc4c05252411bf44ccaa2485e20d0a7c77892ab6e"}, + {file = "yarl-1.17.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a3f47930fbbed0f6377639503848134c4aa25426b08778d641491131351c2c8"}, + {file = "yarl-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1fa68a3c921365c5745b4bd3af6221ae1f0ea1bf04b69e94eda60e57958907f"}, + {file = "yarl-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:187df91395c11e9f9dc69b38d12406df85aa5865f1766a47907b1cc9855b6303"}, + {file = "yarl-1.17.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:93d1c8cc5bf5df401015c5e2a3ce75a5254a9839e5039c881365d2a9dcfc6dc2"}, + {file = "yarl-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:11d86c6145ac5c706c53d484784cf504d7d10fa407cb73b9d20f09ff986059ef"}, + {file = "yarl-1.17.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c42774d1d1508ec48c3ed29e7b110e33f5e74a20957ea16197dbcce8be6b52ba"}, + {file = "yarl-1.17.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8e589379ef0407b10bed16cc26e7392ef8f86961a706ade0a22309a45414d7"}, + {file = "yarl-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1056cadd5e850a1c026f28e0704ab0a94daaa8f887ece8dfed30f88befb87bb0"}, + {file = "yarl-1.17.2-cp311-cp311-win32.whl", hash = "sha256:be4c7b1c49d9917c6e95258d3d07f43cfba2c69a6929816e77daf322aaba6628"}, + {file = "yarl-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:ac8eda86cc75859093e9ce390d423aba968f50cf0e481e6c7d7d63f90bae5c9c"}, + {file = "yarl-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dd90238d3a77a0e07d4d6ffdebc0c21a9787c5953a508a2231b5f191455f31e9"}, + {file = "yarl-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c74f0b0472ac40b04e6d28532f55cac8090e34c3e81f118d12843e6df14d0909"}, + {file = "yarl-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4d486ddcaca8c68455aa01cf53d28d413fb41a35afc9f6594a730c9779545876"}, + {file = "yarl-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25b7e93f5414b9a983e1a6c1820142c13e1782cc9ed354c25e933aebe97fcf2"}, + {file = "yarl-1.17.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a0baff7827a632204060f48dca9e63fbd6a5a0b8790c1a2adfb25dc2c9c0d50"}, + {file = "yarl-1.17.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:460024cacfc3246cc4d9f47a7fc860e4fcea7d1dc651e1256510d8c3c9c7cde0"}, + {file = "yarl-1.17.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5870d620b23b956f72bafed6a0ba9a62edb5f2ef78a8849b7615bd9433384171"}, + {file = "yarl-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2941756754a10e799e5b87e2319bbec481ed0957421fba0e7b9fb1c11e40509f"}, + {file = "yarl-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9611b83810a74a46be88847e0ea616794c406dbcb4e25405e52bff8f4bee2d0a"}, + {file = "yarl-1.17.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cd7e35818d2328b679a13268d9ea505c85cd773572ebb7a0da7ccbca77b6a52e"}, + {file = "yarl-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6b981316fcd940f085f646b822c2ff2b8b813cbd61281acad229ea3cbaabeb6b"}, + {file = "yarl-1.17.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:688058e89f512fb7541cb85c2f149c292d3fa22f981d5a5453b40c5da49eb9e8"}, + {file = "yarl-1.17.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56afb44a12b0864d17b597210d63a5b88915d680f6484d8d202ed68ade38673d"}, + {file = "yarl-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:17931dfbb84ae18b287279c1f92b76a3abcd9a49cd69b92e946035cff06bcd20"}, + {file = "yarl-1.17.2-cp312-cp312-win32.whl", hash = "sha256:ff8d95e06546c3a8c188f68040e9d0360feb67ba8498baf018918f669f7bc39b"}, + {file = "yarl-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:4c840cc11163d3c01a9d8aad227683c48cd3e5be5a785921bcc2a8b4b758c4f3"}, + {file = "yarl-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3294f787a437cb5d81846de3a6697f0c35ecff37a932d73b1fe62490bef69211"}, + {file = "yarl-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f1e7fedb09c059efee2533119666ca7e1a2610072076926fa028c2ba5dfeb78c"}, + {file = "yarl-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:da9d3061e61e5ae3f753654813bc1cd1c70e02fb72cf871bd6daf78443e9e2b1"}, + {file = "yarl-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91c012dceadc695ccf69301bfdccd1fc4472ad714fe2dd3c5ab4d2046afddf29"}, + {file = "yarl-1.17.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f11fd61d72d93ac23718d393d2a64469af40be2116b24da0a4ca6922df26807e"}, + {file = "yarl-1.17.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46c465ad06971abcf46dd532f77560181387b4eea59084434bdff97524444032"}, + {file = "yarl-1.17.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef6eee1a61638d29cd7c85f7fd3ac7b22b4c0fabc8fd00a712b727a3e73b0685"}, + {file = "yarl-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4434b739a8a101a837caeaa0137e0e38cb4ea561f39cb8960f3b1e7f4967a3fc"}, + {file = "yarl-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:752485cbbb50c1e20908450ff4f94217acba9358ebdce0d8106510859d6eb19a"}, + {file = "yarl-1.17.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:17791acaa0c0f89323c57da7b9a79f2174e26d5debbc8c02d84ebd80c2b7bff8"}, + {file = "yarl-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5c6ea72fe619fee5e6b5d4040a451d45d8175f560b11b3d3e044cd24b2720526"}, + {file = "yarl-1.17.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db5ac3871ed76340210fe028f535392f097fb31b875354bcb69162bba2632ef4"}, + {file = "yarl-1.17.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7a1606ba68e311576bcb1672b2a1543417e7e0aa4c85e9e718ba6466952476c0"}, + {file = "yarl-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9bc27dd5cfdbe3dc7f381b05e6260ca6da41931a6e582267d5ca540270afeeb2"}, + {file = "yarl-1.17.2-cp313-cp313-win32.whl", hash = "sha256:52492b87d5877ec405542f43cd3da80bdcb2d0c2fbc73236526e5f2c28e6db28"}, + {file = "yarl-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:8e1bf59e035534ba4077f5361d8d5d9194149f9ed4f823d1ee29ef3e8964ace3"}, + {file = "yarl-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c556fbc6820b6e2cda1ca675c5fa5589cf188f8da6b33e9fc05b002e603e44fa"}, + {file = "yarl-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f2f44a4247461965fed18b2573f3a9eb5e2c3cad225201ee858726cde610daca"}, + {file = "yarl-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3a3ede8c248f36b60227eb777eac1dbc2f1022dc4d741b177c4379ca8e75571a"}, + {file = "yarl-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2654caaf5584449d49c94a6b382b3cb4a246c090e72453493ea168b931206a4d"}, + {file = "yarl-1.17.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d41c684f286ce41fa05ab6af70f32d6da1b6f0457459a56cf9e393c1c0b2217"}, + {file = "yarl-1.17.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2270d590997445a0dc29afa92e5534bfea76ba3aea026289e811bf9ed4b65a7f"}, + {file = "yarl-1.17.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18662443c6c3707e2fc7fad184b4dc32dd428710bbe72e1bce7fe1988d4aa654"}, + {file = "yarl-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75ac158560dec3ed72f6d604c81090ec44529cfb8169b05ae6fcb3e986b325d9"}, + {file = "yarl-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1fee66b32e79264f428dc8da18396ad59cc48eef3c9c13844adec890cd339db5"}, + {file = "yarl-1.17.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:585ce7cd97be8f538345de47b279b879e091c8b86d9dbc6d98a96a7ad78876a3"}, + {file = "yarl-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c019abc2eca67dfa4d8fb72ba924871d764ec3c92b86d5b53b405ad3d6aa56b0"}, + {file = "yarl-1.17.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c6e659b9a24d145e271c2faf3fa6dd1fcb3e5d3f4e17273d9e0350b6ab0fe6e2"}, + {file = "yarl-1.17.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:d17832ba39374134c10e82d137e372b5f7478c4cceeb19d02ae3e3d1daed8721"}, + {file = "yarl-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:bc3003710e335e3f842ae3fd78efa55f11a863a89a72e9a07da214db3bf7e1f8"}, + {file = "yarl-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f5ffc6b7ace5b22d9e73b2a4c7305740a339fbd55301d52735f73e21d9eb3130"}, + {file = "yarl-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:48e424347a45568413deec6f6ee2d720de2cc0385019bedf44cd93e8638aa0ed"}, + {file = "yarl-1.17.2-py3-none-any.whl", hash = "sha256:dd7abf4f717e33b7487121faf23560b3a50924f80e4bef62b22dab441ded8f3b"}, + {file = "yarl-1.17.2.tar.gz", hash = "sha256:753eaaa0c7195244c84b5cc159dc8204b7fd99f716f11198f999f2332a86b178"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" +propcache = ">=0.2.0" [[package]] name = "zipp" From 57df5a19fac0a516848cb01c23776af554f8f6c8 Mon Sep 17 00:00:00 2001 From: Tal Date: Wed, 20 Nov 2024 12:49:44 +0200 Subject: [PATCH 11/24] fix: failing tests due to change in incident name (#2550) --- keep/api/routes/preset.py | 12 +++--- keep/functions/__init__.py | 5 ++- .../pagerduty_provider/pagerduty_provider.py | 9 ++++- tests/test_rules_engine.py | 39 ++++++++++++------- 4 files changed, 41 insertions(+), 24 deletions(-) diff --git a/keep/api/routes/preset.py b/keep/api/routes/preset.py index a5051650f..1f3f9b22a 100644 --- a/keep/api/routes/preset.py +++ b/keep/api/routes/preset.py @@ -137,10 +137,10 @@ def pull_data_from_providers( f"Provider {provider.type} ({provider.id}) does not implement pulling incidents", extra=extra, ) - except Exception as e: - logger.error( + except Exception: + logger.exception( f"Unknown error pulling incidents from provider {provider.type} ({provider.id})", - extra={**extra, "error": str(e)}, + extra={**extra, "trace_id": trace_id}, ) else: logger.debug( @@ -165,10 +165,10 @@ def pull_data_from_providers( f"Provider {provider.type} ({provider.id}) does not implement pulling topology data", extra=extra, ) - except Exception as e: - logger.error( + except Exception: + logger.exception( f"Unknown error pulling topology from provider {provider.type} ({provider.id})", - extra={**extra, "error": str(e)}, + extra={**extra}, ) for fingerprint, alert in sorted_provider_alerts_by_fingerprint.items(): diff --git a/keep/functions/__init__.py b/keep/functions/__init__.py index 14ab6de64..b5a8b665d 100644 --- a/keep/functions/__init__.py +++ b/keep/functions/__init__.py @@ -1,11 +1,12 @@ import copy import datetime +import json import re import urllib.parse from datetime import timedelta from itertools import groupby -import json5 as json +import json5 import pytz from dateutil import parser from dateutil.parser import ParserError @@ -179,7 +180,7 @@ def join( iterable: list | dict | str, delimiter: str = ",", prefix: str | None = None ) -> str: if isinstance(iterable, str): - iterable = json.loads(iterable) + iterable = json5.loads(iterable) if isinstance(iterable, dict): if prefix: diff --git a/keep/providers/pagerduty_provider/pagerduty_provider.py b/keep/providers/pagerduty_provider/pagerduty_provider.py index 5e9ea7e2a..2f6ecc8c4 100644 --- a/keep/providers/pagerduty_provider/pagerduty_provider.py +++ b/keep/providers/pagerduty_provider/pagerduty_provider.py @@ -575,6 +575,7 @@ def _query(self, incident_id: str = None): else incidents ) + @staticmethod def _format_alert( event: dict, provider_instance: "BaseProvider" = None ) -> AlertDto: @@ -791,11 +792,15 @@ def _get_incidents(self) -> list[IncidentDto]: raw_incidents = self.__get_all_incidents_or_alerts() incidents = [] for incident in raw_incidents: - incident_dto = self._format_incident({"event": {"data": incident}}) + incident_dto = PagerdutyProvider._format_incident( + {"event": {"data": incident}} + ) incident_alerts = self.__get_all_incidents_or_alerts( incident_id=incident_dto.fingerprint ) - incident_alerts = [self._format_alert(alert) for alert in incident_alerts] + incident_alerts = [ + PagerdutyProvider._format_alert(alert) for alert in incident_alerts + ] incident_dto._alerts = incident_alerts incidents.append(incident_dto) return incidents diff --git a/tests/test_rules_engine.py b/tests/test_rules_engine.py index 5abf1f5a9..40627b372 100644 --- a/tests/test_rules_engine.py +++ b/tests/test_rules_engine.py @@ -2,16 +2,21 @@ import hashlib import json import os -import time import uuid import pytest -from sqlalchemy import desc, asc -from keep.api.core.db import create_rule as create_rule_db, get_last_incidents, get_incident_alerts_by_incident_id +from keep.api.core.db import create_rule as create_rule_db +from keep.api.core.db import get_incident_alerts_by_incident_id, get_last_incidents from keep.api.core.db import get_rules as get_rules_db from keep.api.core.dependencies import SINGLE_TENANT_UUID -from keep.api.models.alert import AlertDto, AlertSeverity, AlertStatus, IncidentSeverity, IncidentStatus +from keep.api.models.alert import ( + AlertDto, + AlertSeverity, + AlertStatus, + IncidentSeverity, + IncidentStatus, +) from keep.api.models.db.alert import Alert from keep.api.models.db.rule import ResolveOn from keep.rulesengine.rulesengine import RulesEngine @@ -264,9 +269,12 @@ def test_incident_attributes(db_session): # check that there are results assert results is not None assert len(results) == 1 - assert results[0].user_generated_name == "Incident generated by rule {}".format(rules[0].name) + assert results[0].user_generated_name == "{}".format(rules[0].name) assert results[0].alerts_count == i + 1 - assert results[0].last_seen_time.isoformat(timespec='milliseconds')+"Z" == alert.lastReceived + assert ( + results[0].last_seen_time.isoformat(timespec="milliseconds") + "Z" + == alert.lastReceived + ) assert results[0].start_time == alerts[0].timestamp @@ -322,7 +330,7 @@ def test_incident_severity(db_session): # check that there are results assert results is not None assert len(results) == 1 - assert results[0].user_generated_name == "Incident generated by rule {}".format(rules[0].name) + assert results[0].user_generated_name == "{}".format(rules[0].name) assert results[0].alerts_count == 3 assert results[0].severity.value == IncidentSeverity.INFO.value @@ -341,7 +349,7 @@ def test_incident_resolution_on_all(db_session, create_alert): definition_cel='(severity == "critical")', created_by="test@keephq.dev", require_approve=False, - resolve_on=ResolveOn.ALL.value + resolve_on=ResolveOn.ALL.value, ) incidents, total_count = get_last_incidents( @@ -353,13 +361,13 @@ def test_incident_resolution_on_all(db_session, create_alert): assert total_count == 0 create_alert( - f"Something went wrong", + "Something went wrong", AlertStatus.FIRING, datetime.datetime.utcnow(), {"severity": AlertSeverity.CRITICAL.value}, ) create_alert( - f"Something went wrong again", + "Something went wrong again", AlertStatus.FIRING, datetime.datetime.utcnow(), {"severity": AlertSeverity.CRITICAL.value}, @@ -387,7 +395,7 @@ def test_incident_resolution_on_all(db_session, create_alert): # Same fingerprint create_alert( - f"Something went wrong", + "Something went wrong", AlertStatus.RESOLVED, datetime.datetime.utcnow(), {"severity": AlertSeverity.CRITICAL.value}, @@ -415,7 +423,7 @@ def test_incident_resolution_on_all(db_session, create_alert): assert incident.status == IncidentStatus.FIRING.value create_alert( - f"Something went wrong again", + "Something went wrong again", AlertStatus.RESOLVED, datetime.datetime.utcnow(), {"severity": AlertSeverity.CRITICAL.value}, @@ -444,9 +452,11 @@ def test_incident_resolution_on_all(db_session, create_alert): @pytest.mark.parametrize( "direction,second_fire_order", - [(ResolveOn.FIRST.value, ('fp2', 'fp1')), (ResolveOn.LAST.value, ('fp2', 'fp1'))] + [(ResolveOn.FIRST.value, ("fp2", "fp1")), (ResolveOn.LAST.value, ("fp2", "fp1"))], ) -def test_incident_resolution_on_edge(db_session, create_alert, direction, second_fire_order): +def test_incident_resolution_on_edge( + db_session, create_alert, direction, second_fire_order +): create_rule_db( tenant_id=SINGLE_TENANT_UUID, @@ -560,6 +570,7 @@ def test_incident_resolution_on_edge(db_session, create_alert, direction, second assert alert_count == 2 assert incident.status == IncidentStatus.RESOLVED.value + # Next steps: # - test that alerts in the same group are being updated correctly # - test group are being updated correctly From 917cbc549c2a25fa13fbac23794c3b9327303232 Mon Sep 17 00:00:00 2001 From: Tal Date: Wed, 20 Nov 2024 13:14:19 +0200 Subject: [PATCH 12/24] fix(pagerduty): enforce new format (#2552) --- keep/providers/pagerduty_provider/pagerduty_provider.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/keep/providers/pagerduty_provider/pagerduty_provider.py b/keep/providers/pagerduty_provider/pagerduty_provider.py index 2f6ecc8c4..598cf091b 100644 --- a/keep/providers/pagerduty_provider/pagerduty_provider.py +++ b/keep/providers/pagerduty_provider/pagerduty_provider.py @@ -577,7 +577,9 @@ def _query(self, incident_id: str = None): @staticmethod def _format_alert( - event: dict, provider_instance: "BaseProvider" = None + event: dict, + provider_instance: "BaseProvider" = None, + force_new_format: bool = False, ) -> AlertDto: # If somebody connected the provider before we refactored it old_format_event = event.get("event", {}) @@ -622,7 +624,7 @@ def _format_alert_old(event: dict) -> AlertDto: last_received = data.pop( "created_at", datetime.datetime.now(tz=datetime.timezone.utc).isoformat() ) - name = data.pop("title") + name = data.pop("title", "unknown title") service = data.pop("service", {}).get("summary", "unknown") environment = next( iter( @@ -799,7 +801,8 @@ def _get_incidents(self) -> list[IncidentDto]: incident_id=incident_dto.fingerprint ) incident_alerts = [ - PagerdutyProvider._format_alert(alert) for alert in incident_alerts + PagerdutyProvider._format_alert(alert, None, force_new_format=True) + for alert in incident_alerts ] incident_dto._alerts = incident_alerts incidents.append(incident_dto) From a5a674ef643f6d4a62e4f027e83d5de37791461f Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Wed, 20 Nov 2024 15:58:13 +0400 Subject: [PATCH 13/24] chore: polish read-only banner (#2554) Co-authored-by: Tal --- keep-ui/app/read-only-banner.tsx | 73 +++++++++++++++++++------------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/keep-ui/app/read-only-banner.tsx b/keep-ui/app/read-only-banner.tsx index f0e49c41e..fd29224e1 100644 --- a/keep-ui/app/read-only-banner.tsx +++ b/keep-ui/app/read-only-banner.tsx @@ -1,41 +1,56 @@ "use client"; import React from "react"; -import { X } from "lucide-react"; -import { useLocalStorage } from "utils/hooks/useLocalStorage"; -import { Card, Text, Button } from "@tremor/react"; +import { Text, Button } from "@tremor/react"; import Image from "next/image"; import KeepPng from "../keep.png"; +import posthog from "posthog-js"; const ReadOnlyBanner = () => { - const [isVisible, setIsVisible] = useLocalStorage( - "read-only-banner-visible", - true - ); - - if (!isVisible) return null; - return ( - -
    -
    - Keep Logo - - Keep is in read-only mode. - +
    +
    + + Keep Logo + Keep is in read-only mode. + +
    + +
    -
    - +
    ); }; From 108dee01ada557650b5d25608611df6c8c51471f Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Wed, 20 Nov 2024 16:36:03 +0400 Subject: [PATCH 14/24] fix: safe event capturing & security options for window opening (#2556) --- keep-ui/app/read-only-banner.tsx | 18 +++++++++++++----- keep-ui/shared/lib/capture.ts | 9 +++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 keep-ui/shared/lib/capture.ts diff --git a/keep-ui/app/read-only-banner.tsx b/keep-ui/app/read-only-banner.tsx index fd29224e1..80af6cd19 100644 --- a/keep-ui/app/read-only-banner.tsx +++ b/keep-ui/app/read-only-banner.tsx @@ -3,7 +3,7 @@ import React from "react"; import { Text, Button } from "@tremor/react"; import Image from "next/image"; import KeepPng from "../keep.png"; -import posthog from "posthog-js"; +import { capture } from "@/shared/lib/capture"; const ReadOnlyBanner = () => { return ( @@ -23,10 +23,14 @@ const ReadOnlyBanner = () => { diff --git a/keep-ui/app/alerts/TitleAndFilters.tsx b/keep-ui/app/(keep)/alerts/TitleAndFilters.tsx similarity index 100% rename from keep-ui/app/alerts/TitleAndFilters.tsx rename to keep-ui/app/(keep)/alerts/TitleAndFilters.tsx diff --git a/keep-ui/app/alerts/ViewAlertModal.css b/keep-ui/app/(keep)/alerts/ViewAlertModal.css similarity index 100% rename from keep-ui/app/alerts/ViewAlertModal.css rename to keep-ui/app/(keep)/alerts/ViewAlertModal.css diff --git a/keep-ui/app/alerts/ViewAlertModal.tsx b/keep-ui/app/(keep)/alerts/ViewAlertModal.tsx similarity index 100% rename from keep-ui/app/alerts/ViewAlertModal.tsx rename to keep-ui/app/(keep)/alerts/ViewAlertModal.tsx diff --git a/keep-ui/app/alerts/[id]/page.tsx b/keep-ui/app/(keep)/alerts/[id]/page.tsx similarity index 100% rename from keep-ui/app/alerts/[id]/page.tsx rename to keep-ui/app/(keep)/alerts/[id]/page.tsx diff --git a/keep-ui/app/alerts/alert-actions.tsx b/keep-ui/app/(keep)/alerts/alert-actions.tsx similarity index 100% rename from keep-ui/app/alerts/alert-actions.tsx rename to keep-ui/app/(keep)/alerts/alert-actions.tsx diff --git a/keep-ui/app/alerts/alert-assign-ticket-modal.tsx b/keep-ui/app/(keep)/alerts/alert-assign-ticket-modal.tsx similarity index 99% rename from keep-ui/app/alerts/alert-assign-ticket-modal.tsx rename to keep-ui/app/(keep)/alerts/alert-assign-ticket-modal.tsx index 0ca7bae0a..5bcd694ae 100644 --- a/keep-ui/app/alerts/alert-assign-ticket-modal.tsx +++ b/keep-ui/app/(keep)/alerts/alert-assign-ticket-modal.tsx @@ -3,7 +3,7 @@ import Select, { components } from "react-select"; import { Button, TextInput, Text } from "@tremor/react"; import { PlusIcon } from "@heroicons/react/20/solid"; import { useForm, Controller, SubmitHandler } from "react-hook-form"; -import { Providers } from "./../providers/providers"; +import { Providers } from "../providers/providers"; import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useApiUrl } from "utils/hooks/useConfig"; import { AlertDto } from "./models"; diff --git a/keep-ui/app/alerts/alert-assignee.tsx b/keep-ui/app/(keep)/alerts/alert-assignee.tsx similarity index 100% rename from keep-ui/app/alerts/alert-assignee.tsx rename to keep-ui/app/(keep)/alerts/alert-assignee.tsx diff --git a/keep-ui/app/alerts/alert-associate-incident-modal.tsx b/keep-ui/app/(keep)/alerts/alert-associate-incident-modal.tsx similarity index 97% rename from keep-ui/app/alerts/alert-associate-incident-modal.tsx rename to keep-ui/app/(keep)/alerts/alert-associate-incident-modal.tsx index 923d30cd8..ff8de5ab5 100644 --- a/keep-ui/app/alerts/alert-associate-incident-modal.tsx +++ b/keep-ui/app/(keep)/alerts/alert-associate-incident-modal.tsx @@ -6,8 +6,11 @@ import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydrated import { FormEvent, useCallback, useEffect, useState } from "react"; import { toast } from "react-toastify"; import { useApiUrl } from "utils/hooks/useConfig"; -import { useIncidents, usePollIncidents } from "../../utils/hooks/useIncidents"; -import Loading from "../loading"; +import { + useIncidents, + usePollIncidents, +} from "../../../utils/hooks/useIncidents"; +import Loading from "@/app/(keep)/loading"; import { AlertDto } from "./models"; import { getIncidentName } from "@/entities/incidents/lib/utils"; diff --git a/keep-ui/app/alerts/alert-change-status-modal.tsx b/keep-ui/app/(keep)/alerts/alert-change-status-modal.tsx similarity index 100% rename from keep-ui/app/alerts/alert-change-status-modal.tsx rename to keep-ui/app/(keep)/alerts/alert-change-status-modal.tsx diff --git a/keep-ui/app/alerts/alert-create-incident-ai-card.tsx b/keep-ui/app/(keep)/alerts/alert-create-incident-ai-card.tsx similarity index 100% rename from keep-ui/app/alerts/alert-create-incident-ai-card.tsx rename to keep-ui/app/(keep)/alerts/alert-create-incident-ai-card.tsx diff --git a/keep-ui/app/alerts/alert-create-incident-ai-modal.tsx b/keep-ui/app/(keep)/alerts/alert-create-incident-ai-modal.tsx similarity index 99% rename from keep-ui/app/alerts/alert-create-incident-ai-modal.tsx rename to keep-ui/app/(keep)/alerts/alert-create-incident-ai-modal.tsx index 9d077ed66..242eead3f 100644 --- a/keep-ui/app/alerts/alert-create-incident-ai-modal.tsx +++ b/keep-ui/app/(keep)/alerts/alert-create-incident-ai-modal.tsx @@ -3,7 +3,7 @@ import Modal from "@/components/ui/Modal"; import { Callout, Button, Title, Card } from "@tremor/react"; import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { toast } from "react-toastify"; -import Loading from "../loading"; +import Loading from "@/app/(keep)/loading"; import { AlertDto } from "./models"; import { IncidentDto, IncidentCandidateDto } from "@/entities/incidents/model"; import { useApiUrl } from "utils/hooks/useConfig"; diff --git a/keep-ui/app/(keep)/alerts/alert-dismiss-modal.css b/keep-ui/app/(keep)/alerts/alert-dismiss-modal.css new file mode 100644 index 000000000..20a0bc089 --- /dev/null +++ b/keep-ui/app/(keep)/alerts/alert-dismiss-modal.css @@ -0,0 +1,37 @@ +.react-datepicker { + font-size: 14px !important; + color: #070707 !important; +} + +.react-datepicker__header { + background-color: white !important; + padding-top: 0px !important; + border: none !important; +} + +.react-datepicker__day-name { + color: #c7c7c7 !important; + font-size: 14px !important; +} + +.react-datepicker__day { + color: black !important; + font-size: 13px !important; +} + +.react-datepicker__day--selected, +.react-datepicker__day--keyboard-selected { + border-radius: 25px !important; + background: orange !important; + color: white !important; +} + +.react-datepicker__time-list-item--selected { + background: orange !important; + color: white !important; +} + +.react-datepicker__day--disabled, +.react-datepicker__time-list-item--disabled { + color: #c7c7c7 !important; +} diff --git a/keep-ui/app/alerts/alert-dismiss-modal.tsx b/keep-ui/app/(keep)/alerts/alert-dismiss-modal.tsx similarity index 100% rename from keep-ui/app/alerts/alert-dismiss-modal.tsx rename to keep-ui/app/(keep)/alerts/alert-dismiss-modal.tsx diff --git a/keep-ui/app/alerts/alert-extra-payload.tsx b/keep-ui/app/(keep)/alerts/alert-extra-payload.tsx similarity index 100% rename from keep-ui/app/alerts/alert-extra-payload.tsx rename to keep-ui/app/(keep)/alerts/alert-extra-payload.tsx diff --git a/keep-ui/app/alerts/alert-fatigue-meter.tsx b/keep-ui/app/(keep)/alerts/alert-fatigue-meter.tsx similarity index 100% rename from keep-ui/app/alerts/alert-fatigue-meter.tsx rename to keep-ui/app/(keep)/alerts/alert-fatigue-meter.tsx diff --git a/keep-ui/app/alerts/alert-history-charts.tsx b/keep-ui/app/(keep)/alerts/alert-history-charts.tsx similarity index 74% rename from keep-ui/app/alerts/alert-history-charts.tsx rename to keep-ui/app/(keep)/alerts/alert-history-charts.tsx index 9abb47d8b..b79e37e65 100644 --- a/keep-ui/app/alerts/alert-history-charts.tsx +++ b/keep-ui/app/(keep)/alerts/alert-history-charts.tsx @@ -1,5 +1,5 @@ import { AreaChart } from "@tremor/react"; -import Loading from "app/loading"; +import Loading from "@/app/(keep)/loading"; import { AlertDto } from "./models"; import { calculateFatigue } from "utils/fatigue"; @@ -36,23 +36,26 @@ export default function AlertHistoryCharts({ timeUnit = "Hours"; } - const rawChartData = [...alerts].reverse().reduce((prev, curr) => { - const date = curr.lastReceived; - const dateKey = getDateKey(date, timeUnit); - if (!prev[dateKey]) { - prev[dateKey] = { - [curr.status]: 1, - }; - } else { - prev[dateKey][curr.status] - ? (prev[dateKey][curr.status] += 1) - : (prev[dateKey][curr.status] = 1); - } - if (categoriesByStatus.includes(curr.status) === false) { - categoriesByStatus.push(curr.status); - } - return prev; - }, {} as { [date: string]: any }); + const rawChartData = [...alerts].reverse().reduce( + (prev, curr) => { + const date = curr.lastReceived; + const dateKey = getDateKey(date, timeUnit); + if (!prev[dateKey]) { + prev[dateKey] = { + [curr.status]: 1, + }; + } else { + prev[dateKey][curr.status] + ? (prev[dateKey][curr.status] += 1) + : (prev[dateKey][curr.status] = 1); + } + if (categoriesByStatus.includes(curr.status) === false) { + categoriesByStatus.push(curr.status); + } + return prev; + }, + {} as { [date: string]: any } + ); if (categoriesByStatus.includes("Fatigueness") === false) { categoriesByStatus.push("Fatigueness"); diff --git a/keep-ui/app/alerts/alert-history.tsx b/keep-ui/app/(keep)/alerts/alert-history.tsx similarity index 100% rename from keep-ui/app/alerts/alert-history.tsx rename to keep-ui/app/(keep)/alerts/alert-history.tsx diff --git a/keep-ui/app/alerts/alert-menu.tsx b/keep-ui/app/(keep)/alerts/alert-menu.tsx similarity index 99% rename from keep-ui/app/alerts/alert-menu.tsx rename to keep-ui/app/(keep)/alerts/alert-menu.tsx index 4784090d2..58846e5ea 100644 --- a/keep-ui/app/alerts/alert-menu.tsx +++ b/keep-ui/app/(keep)/alerts/alert-menu.tsx @@ -15,7 +15,7 @@ import { IoNotificationsOffOutline } from "react-icons/io5"; import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useApiUrl } from "utils/hooks/useConfig"; import Link from "next/link"; -import { ProviderMethod } from "app/providers/providers"; +import { ProviderMethod } from "@/app/(keep)/providers/providers"; import { AlertDto } from "./models"; import { useFloating } from "@floating-ui/react"; import { useProviders } from "utils/hooks/useProviders"; diff --git a/keep-ui/app/alerts/alert-method-modal.tsx b/keep-ui/app/(keep)/alerts/alert-method-modal.tsx similarity index 98% rename from keep-ui/app/alerts/alert-method-modal.tsx rename to keep-ui/app/(keep)/alerts/alert-method-modal.tsx index 0d70ab54e..5bd4bfdb2 100644 --- a/keep-ui/app/alerts/alert-method-modal.tsx +++ b/keep-ui/app/(keep)/alerts/alert-method-modal.tsx @@ -4,11 +4,11 @@ import { Provider, ProviderMethod, ProviderMethodParam, -} from "app/providers/providers"; +} from "@/app/(keep)/providers/providers"; import { getSession } from "next-auth/react"; import { useApiUrl } from "utils/hooks/useConfig"; import { toast } from "react-toastify"; -import Loading from "app/loading"; +import Loading from "@/app/(keep)/loading"; import { Button, TextInput, diff --git a/keep-ui/app/alerts/alert-method-results-table.tsx b/keep-ui/app/(keep)/alerts/alert-method-results-table.tsx similarity index 100% rename from keep-ui/app/alerts/alert-method-results-table.tsx rename to keep-ui/app/(keep)/alerts/alert-method-results-table.tsx diff --git a/keep-ui/app/alerts/alert-name.tsx b/keep-ui/app/(keep)/alerts/alert-name.tsx similarity index 96% rename from keep-ui/app/alerts/alert-name.tsx rename to keep-ui/app/(keep)/alerts/alert-name.tsx index 049338de4..8bcca4638 100644 --- a/keep-ui/app/alerts/alert-name.tsx +++ b/keep-ui/app/(keep)/alerts/alert-name.tsx @@ -139,15 +139,15 @@ export default function AlertName({ relevantWorkflowExecution.workflow_status === "success" ? "green" : relevantWorkflowExecution.workflow_status === "error" - ? "red" - : "gray" + ? "red" + : "gray" }`} tooltip={`${ relevantWorkflowExecution.workflow_status === "success" ? "Last workflow executed successfully" : relevantWorkflowExecution.workflow_status === "error" - ? "Last workflow execution failed" - : undefined + ? "Last workflow execution failed" + : undefined }`} onClick={() => handleWorkflowClick(relevantWorkflowExecution)} className="ml-1 cursor-pointer" diff --git a/keep-ui/app/alerts/alert-note-modal.tsx b/keep-ui/app/(keep)/alerts/alert-note-modal.tsx similarity index 100% rename from keep-ui/app/alerts/alert-note-modal.tsx rename to keep-ui/app/(keep)/alerts/alert-note-modal.tsx diff --git a/keep-ui/app/alerts/alert-pagination.tsx b/keep-ui/app/(keep)/alerts/alert-pagination.tsx similarity index 100% rename from keep-ui/app/alerts/alert-pagination.tsx rename to keep-ui/app/(keep)/alerts/alert-pagination.tsx diff --git a/keep-ui/app/alerts/alert-presets.tsx b/keep-ui/app/(keep)/alerts/alert-presets.tsx similarity index 100% rename from keep-ui/app/alerts/alert-presets.tsx rename to keep-ui/app/(keep)/alerts/alert-presets.tsx diff --git a/keep-ui/app/alerts/alert-push-alert-to-server-modal.tsx b/keep-ui/app/(keep)/alerts/alert-push-alert-to-server-modal.tsx similarity index 100% rename from keep-ui/app/alerts/alert-push-alert-to-server-modal.tsx rename to keep-ui/app/(keep)/alerts/alert-push-alert-to-server-modal.tsx diff --git a/keep-ui/app/alerts/alert-quality-table.tsx b/keep-ui/app/(keep)/alerts/alert-quality-table.tsx similarity index 98% rename from keep-ui/app/alerts/alert-quality-table.tsx rename to keep-ui/app/(keep)/alerts/alert-quality-table.tsx index 88dcf3e2a..29493b5de 100644 --- a/keep-ui/app/alerts/alert-quality-table.tsx +++ b/keep-ui/app/(keep)/alerts/alert-quality-table.tsx @@ -10,7 +10,7 @@ import React, { import { GenericTable } from "@/components/table/GenericTable"; import { useAlertQualityMetrics } from "utils/hooks/useAlertQuality"; import { useProviders } from "utils/hooks/useProviders"; -import { Provider, ProvidersResponse } from "app/providers/providers"; +import { Provider, ProvidersResponse } from "@/app/(keep)/providers/providers"; import { TabGroup, TabList, Tab, Callout } from "@tremor/react"; import { GenericFilters } from "@/components/filters/GenericFilters"; import { useSearchParams } from "next/navigation"; @@ -171,7 +171,9 @@ const QualityTable = ({ id: "alertsCorrelatedToIncidentsPercentage", header: "% of Alerts Correlated to Incidents", cell: ({ row }) => { - return `${row.original.alertsCorrelatedToIncidentsPercentage.toFixed(2)}%`; + return `${row.original.alertsCorrelatedToIncidentsPercentage.toFixed( + 2 + )}%`; }, }), ] as DisplayColumnDef[]; @@ -341,7 +343,6 @@ const AlertQuality = ({ isDashBoard ? (fieldsValue as string | string[]) : "" ); - if (error) { return ( Add Tab diff --git a/keep-ui/app/alerts/alert-table-alert-facets.tsx b/keep-ui/app/(keep)/alerts/alert-table-alert-facets.tsx similarity index 100% rename from keep-ui/app/alerts/alert-table-alert-facets.tsx rename to keep-ui/app/(keep)/alerts/alert-table-alert-facets.tsx diff --git a/keep-ui/app/alerts/alert-table-checkbox.tsx b/keep-ui/app/(keep)/alerts/alert-table-checkbox.tsx similarity index 100% rename from keep-ui/app/alerts/alert-table-checkbox.tsx rename to keep-ui/app/(keep)/alerts/alert-table-checkbox.tsx diff --git a/keep-ui/app/alerts/alert-table-facet-dynamic.tsx b/keep-ui/app/(keep)/alerts/alert-table-facet-dynamic.tsx similarity index 100% rename from keep-ui/app/alerts/alert-table-facet-dynamic.tsx rename to keep-ui/app/(keep)/alerts/alert-table-facet-dynamic.tsx diff --git a/keep-ui/app/alerts/alert-table-facet-types.tsx b/keep-ui/app/(keep)/alerts/alert-table-facet-types.tsx similarity index 100% rename from keep-ui/app/alerts/alert-table-facet-types.tsx rename to keep-ui/app/(keep)/alerts/alert-table-facet-types.tsx diff --git a/keep-ui/app/alerts/alert-table-facet-utils.tsx b/keep-ui/app/(keep)/alerts/alert-table-facet-utils.tsx similarity index 100% rename from keep-ui/app/alerts/alert-table-facet-utils.tsx rename to keep-ui/app/(keep)/alerts/alert-table-facet-utils.tsx diff --git a/keep-ui/app/alerts/alert-table-facet-value.tsx b/keep-ui/app/(keep)/alerts/alert-table-facet-value.tsx similarity index 100% rename from keep-ui/app/alerts/alert-table-facet-value.tsx rename to keep-ui/app/(keep)/alerts/alert-table-facet-value.tsx diff --git a/keep-ui/app/alerts/alert-table-facet.tsx b/keep-ui/app/(keep)/alerts/alert-table-facet.tsx similarity index 100% rename from keep-ui/app/alerts/alert-table-facet.tsx rename to keep-ui/app/(keep)/alerts/alert-table-facet.tsx diff --git a/keep-ui/app/alerts/alert-table-headers.tsx b/keep-ui/app/(keep)/alerts/alert-table-headers.tsx similarity index 98% rename from keep-ui/app/alerts/alert-table-headers.tsx rename to keep-ui/app/(keep)/alerts/alert-table-headers.tsx index 098d74e13..d1606ffc9 100644 --- a/keep-ui/app/alerts/alert-table-headers.tsx +++ b/keep-ui/app/(keep)/alerts/alert-table-headers.tsx @@ -63,8 +63,8 @@ const DraggableHeaderCell = ({ column.getIsPinned() !== false ? "default" : isDragging - ? "grabbing" - : "grab", + ? "grabbing" + : "grab", }; // TODO: fix multiple pinned columns @@ -96,8 +96,8 @@ const DraggableHeaderCell = ({ column.getNextSortingOrder() === "asc" ? "Sort ascending" : column.getNextSortingOrder() === "desc" - ? "Sort descending" - : "Clear sort" + ? "Sort descending" + : "Clear sort" } > {/* Icon logic */} diff --git a/keep-ui/app/alerts/alert-table-tab-panel.tsx b/keep-ui/app/(keep)/alerts/alert-table-tab-panel.tsx similarity index 100% rename from keep-ui/app/alerts/alert-table-tab-panel.tsx rename to keep-ui/app/(keep)/alerts/alert-table-tab-panel.tsx diff --git a/keep-ui/app/alerts/alert-table-utils.tsx b/keep-ui/app/(keep)/alerts/alert-table-utils.tsx similarity index 100% rename from keep-ui/app/alerts/alert-table-utils.tsx rename to keep-ui/app/(keep)/alerts/alert-table-utils.tsx diff --git a/keep-ui/app/alerts/alert-table.tsx b/keep-ui/app/(keep)/alerts/alert-table.tsx similarity index 100% rename from keep-ui/app/alerts/alert-table.tsx rename to keep-ui/app/(keep)/alerts/alert-table.tsx diff --git a/keep-ui/app/alerts/alert-tabs.tsx b/keep-ui/app/(keep)/alerts/alert-tabs.tsx similarity index 100% rename from keep-ui/app/alerts/alert-tabs.tsx rename to keep-ui/app/(keep)/alerts/alert-tabs.tsx diff --git a/keep-ui/app/alerts/alert-timeline.tsx b/keep-ui/app/(keep)/alerts/alert-timeline.tsx similarity index 100% rename from keep-ui/app/alerts/alert-timeline.tsx rename to keep-ui/app/(keep)/alerts/alert-timeline.tsx diff --git a/keep-ui/app/alerts/alerts-rules-builder.tsx b/keep-ui/app/(keep)/alerts/alerts-rules-builder.tsx similarity index 100% rename from keep-ui/app/alerts/alerts-rules-builder.tsx rename to keep-ui/app/(keep)/alerts/alerts-rules-builder.tsx diff --git a/keep-ui/app/alerts/alerts-table-body.css b/keep-ui/app/(keep)/alerts/alerts-table-body.css similarity index 100% rename from keep-ui/app/alerts/alerts-table-body.css rename to keep-ui/app/(keep)/alerts/alerts-table-body.css diff --git a/keep-ui/app/alerts/alerts-table-body.tsx b/keep-ui/app/(keep)/alerts/alerts-table-body.tsx similarity index 99% rename from keep-ui/app/alerts/alerts-table-body.tsx rename to keep-ui/app/(keep)/alerts/alerts-table-body.tsx index b492379d5..0ee55b8a0 100644 --- a/keep-ui/app/alerts/alerts-table-body.tsx +++ b/keep-ui/app/(keep)/alerts/alerts-table-body.tsx @@ -71,7 +71,6 @@ export function AlertsTableBody({ onRowClick(alert); }; - if (showSkeleton) { return ( diff --git a/keep-ui/app/alerts/alerts.tsx b/keep-ui/app/(keep)/alerts/alerts.tsx similarity index 97% rename from keep-ui/app/alerts/alerts.tsx rename to keep-ui/app/(keep)/alerts/alerts.tsx index 4d0fe7117..b5125c4f9 100644 --- a/keep-ui/app/alerts/alerts.tsx +++ b/keep-ui/app/(keep)/alerts/alerts.tsx @@ -11,13 +11,13 @@ import AlertNoteModal from "./alert-note-modal"; import { useProviders } from "utils/hooks/useProviders"; import { AlertDto } from "./models"; import { AlertMethodModal } from "./alert-method-modal"; -import ManualRunWorkflowModal from "@/app/workflows/manual-run-workflow-modal"; +import ManualRunWorkflowModal from "@/app/(keep)/workflows/manual-run-workflow-modal"; import AlertDismissModal from "./alert-dismiss-modal"; import { ViewAlertModal } from "./ViewAlertModal"; import { useRouter, useSearchParams } from "next/navigation"; import AlertChangeStatusModal from "./alert-change-status-modal"; import { useAlertPolling } from "utils/hooks/usePusher"; -import NotFound from "@/app/not-found"; +import NotFound from "@/app/(keep)/not-found"; import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; const defaultPresets: Preset[] = [ diff --git a/keep-ui/app/(keep)/alerts/models.tsx b/keep-ui/app/(keep)/alerts/models.tsx new file mode 100644 index 000000000..0b9561781 --- /dev/null +++ b/keep-ui/app/(keep)/alerts/models.tsx @@ -0,0 +1,135 @@ +export enum Severity { + Critical = "critical", + High = "high", + Warning = "warning", + Low = "low", + Info = "info", + Error = "error", +} + +export const severityMapping: { [id: number]: string } = { + 1: Severity.Low, + 2: Severity.Info, + 3: Severity.Warning, + 4: Severity.High, + 5: Severity.Critical, +}; + +export const reverseSeverityMapping: { [id: string]: number } = { + [Severity.Low]: 1, + [Severity.Info]: 2, + [Severity.Warning]: 3, + [Severity.High]: 4, + [Severity.Critical]: 5, +}; + +export enum Status { + Firing = "firing", + Resolved = "resolved", + Acknowledged = "acknowledged", + Suppressed = "suppressed", + Pending = "pending", +} + +export interface AlertDto { + id: string; + event_id: string; + name: string; + status: Status; + lastReceived: Date; + environment: string; + isDuplicate?: boolean; + duplicateReason?: string; + service?: string; + source: string[]; + message?: string; + description?: string; + severity?: Severity; + url?: string; + pushed: boolean; + generatorURL?: string; + fingerprint: string; + deleted: boolean; + dismissed: boolean; + assignee?: string; + ticket_url: string; + ticket_status?: string; + playbook_url?: string; + providerId?: string; + group?: boolean; + note?: string; + isNoisy?: boolean; + enriched_fields: string[]; + incident?: string; + + // From AlertWithIncidentLinkMetadataDto + is_created_by_ai?: boolean; +} + +interface Option { + readonly label: string; + readonly value: string; +} + +export interface Tag { + id: number; + name: string; +} + +export interface Preset { + id: string; + name: string; + options: Option[]; + is_private: boolean; + is_noisy: boolean; + should_do_noise_now: boolean; + alerts_count: number; + created_by?: string; + tags: Tag[]; +} + +export function getTabsFromPreset(preset: Preset): any[] { + const tabsOption = preset.options.find( + (option) => option.label.toLowerCase() === "tabs" + ); + return tabsOption && Array.isArray(tabsOption.value) ? tabsOption.value : []; +} + +export interface AlertToWorkflowExecution { + workflow_id: string; + workflow_execution_id: string; + alert_fingerprint: string; + workflow_status: + | "timeout" + | "in_progress" + | "success" + | "error" + | "providers_not_configured"; + workflow_started: Date; +} + +export const AlertKnownKeys = [ + "id", + "name", + "status", + "lastReceived", + "isDuplicate", + "duplicateReason", + "source", + "description", + "severity", + "pushed", + "url", + "event_id", + "ticket_url", + "playbook_url", + "ack_status", + "deleted", + "assignee", + "providerId", + "checkbox", + "alertMenu", + "group", + "extraPayload", + "note", +]; diff --git a/keep-ui/app/dashboard/EditGridItemModal.tsx b/keep-ui/app/(keep)/dashboard/EditGridItemModal.tsx similarity index 61% rename from keep-ui/app/dashboard/EditGridItemModal.tsx rename to keep-ui/app/(keep)/dashboard/EditGridItemModal.tsx index 35dfe709e..b131e712c 100644 --- a/keep-ui/app/dashboard/EditGridItemModal.tsx +++ b/keep-ui/app/(keep)/dashboard/EditGridItemModal.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from "react"; import Modal from "@/components/ui/Modal"; -import { Button } from '@tremor/react'; +import { Button } from "@tremor/react"; import { WidgetData, Threshold } from "./types"; interface EditGridItemModalProps { @@ -10,7 +10,12 @@ interface EditGridItemModalProps { onSave: (updatedItem: WidgetData) => void; } -const EditGridItemModal: React.FC = ({ isOpen, onClose, item, onSave }) => { +const EditGridItemModal: React.FC = ({ + isOpen, + onClose, + item, + onSave, +}) => { const [thresholds, setThresholds] = useState([]); useEffect(() => { @@ -29,7 +34,12 @@ const EditGridItemModal: React.FC = ({ isOpen, onClose, return ( {item && ( -
    { e.preventDefault(); handleSave(); }}> + { + e.preventDefault(); + handleSave(); + }} + >
    {thresholds.map((threshold, index) => ( @@ -37,13 +47,27 @@ const EditGridItemModal: React.FC = ({ isOpen, onClose, setThresholds(thresholds.map((t, i) => i === index ? { ...t, value: parseInt(e.target.value, 10) } : t))} + onChange={(e) => + setThresholds( + thresholds.map((t, i) => + i === index + ? { ...t, value: parseInt(e.target.value, 10) } + : t + ) + ) + } className="border p-1" /> setThresholds(thresholds.map((t, i) => i === index ? { ...t, color: e.target.value } : t))} + onChange={(e) => + setThresholds( + thresholds.map((t, i) => + i === index ? { ...t, color: e.target.value } : t + ) + ) + } className="border p-1" />
    diff --git a/keep-ui/app/dashboard/GridItem.tsx b/keep-ui/app/(keep)/dashboard/GridItem.tsx similarity index 63% rename from keep-ui/app/dashboard/GridItem.tsx rename to keep-ui/app/(keep)/dashboard/GridItem.tsx index 6c85e2c43..57077fd1d 100644 --- a/keep-ui/app/dashboard/GridItem.tsx +++ b/keep-ui/app/(keep)/dashboard/GridItem.tsx @@ -1,8 +1,8 @@ import React, { useState } from "react"; import { AreaChart, Card } from "@tremor/react"; import MenuButton from "./MenuButton"; -import {WidgetData, WidgetType} from "./types"; -import AlertQuality from "@/app/alerts/alert-quality-table"; +import { WidgetData, WidgetType } from "./types"; +import AlertQuality from "@/app/(keep)/alerts/alert-quality-table"; import { useSearchParams } from "next/navigation"; interface GridItemProps { @@ -55,14 +55,21 @@ const GridItem: React.FC = ({ } const getColor = () => { let color = "#000000"; - if (item.widgetType === WidgetType.PRESET && item.thresholds && item.preset) { - for (let i = item.thresholds.length - 1; i >= 0; i--) { - if (item.preset && item.preset.alerts_count >= item.thresholds[i].value) { - color = item.thresholds[i].color; - break; + if ( + item.widgetType === WidgetType.PRESET && + item.thresholds && + item.preset + ) { + for (let i = item.thresholds.length - 1; i >= 0; i--) { + if ( + item.preset && + item.preset.alerts_count >= item.thresholds[i].value + ) { + color = item.thresholds[i].color; + break; + } } } - } return color; }; @@ -82,9 +89,9 @@ const GridItem: React.FC = ({ }; return ( - +
    = ({
    )} - { item.metric && ( -
    -
    - - `${Intl.NumberFormat().format(number).toString()}` - } - startEndOnly - connectNulls - showLegend={false} - showTooltip={true} - xAxisLabel="Timestamp" - /> -
    + {item.metric && ( +
    +
    + + `${Intl.NumberFormat().format(number).toString()}` + } + startEndOnly + connectNulls + showLegend={false} + showTooltip={true} + xAxisLabel="Timestamp" + />
    +
    + )} - )} - - -
    - -
    -
    -
    - ); - }; +
    + +
    +
    + + ); +}; - export default GridItem; +export default GridItem; diff --git a/keep-ui/app/dashboard/GridItemContainer.tsx b/keep-ui/app/(keep)/dashboard/GridItemContainer.tsx similarity index 56% rename from keep-ui/app/dashboard/GridItemContainer.tsx rename to keep-ui/app/(keep)/dashboard/GridItemContainer.tsx index 356d8a621..0f0d2d504 100644 --- a/keep-ui/app/dashboard/GridItemContainer.tsx +++ b/keep-ui/app/(keep)/dashboard/GridItemContainer.tsx @@ -9,9 +9,19 @@ interface GridItemContainerProps { onSave: (updateItem: WidgetData) => void; } -const GridItemContainer: React.FC = ({ item, onEdit, onDelete, onSave }) => { +const GridItemContainer: React.FC = ({ + item, + onEdit, + onDelete, + onSave, +}) => { return ( - onEdit(item.i)} onDelete={() => onDelete(item.i)} onSave={onSave}/> + onEdit(item.i)} + onDelete={() => onDelete(item.i)} + onSave={onSave} + /> ); }; diff --git a/keep-ui/app/dashboard/GridLayout.tsx b/keep-ui/app/(keep)/dashboard/GridLayout.tsx similarity index 89% rename from keep-ui/app/dashboard/GridLayout.tsx rename to keep-ui/app/(keep)/dashboard/GridLayout.tsx index d78df13a3..20f4e7d56 100644 --- a/keep-ui/app/dashboard/GridLayout.tsx +++ b/keep-ui/app/(keep)/dashboard/GridLayout.tsx @@ -3,8 +3,8 @@ import { Responsive, WidthProvider, Layout } from "react-grid-layout"; import GridItemContainer from "./GridItemContainer"; import { LayoutItem, WidgetData } from "./types"; import "react-grid-layout/css/styles.css"; -import { Preset } from "app/alerts/models"; -import {MetricsWidget} from "@/utils/hooks/useDashboardMetricWidgets"; +import { Preset } from "@/app/(keep)/alerts/models"; +import { MetricsWidget } from "@/utils/hooks/useDashboardMetricWidgets"; const ResponsiveGridLayout = WidthProvider(Responsive); @@ -27,7 +27,7 @@ const GridLayout: React.FC = ({ onDelete, onSave, presets, - metrics + metrics, }) => { const layouts = { lg: layout }; @@ -63,9 +63,9 @@ const GridLayout: React.FC = ({ alerts_count: preset?.alerts_count ?? 0, }; } else if (item.metric) { - const metric = metrics?.find(m => m?.id === item?.metric?.id); + const metric = metrics?.find((m) => m?.id === item?.metric?.id); if (metric) { - item.metric = {...metric} + item.metric = { ...metric }; } } return ( @@ -78,7 +78,8 @@ const GridLayout: React.FC = ({ onSave={onSave} />
    - ); })} + ); + })} ); diff --git a/keep-ui/app/dashboard/MenuButton.tsx b/keep-ui/app/(keep)/dashboard/MenuButton.tsx similarity index 93% rename from keep-ui/app/dashboard/MenuButton.tsx rename to keep-ui/app/(keep)/dashboard/MenuButton.tsx index b27fb2df3..6ee7302ca 100644 --- a/keep-ui/app/dashboard/MenuButton.tsx +++ b/keep-ui/app/(keep)/dashboard/MenuButton.tsx @@ -11,7 +11,11 @@ interface MenuButtonProps { onSave?: () => void; } -const MenuButton: React.FC = ({ onEdit, onDelete, onSave }) => { +const MenuButton: React.FC = ({ + onEdit, + onDelete, + onSave, +}) => { const stopPropagation = (e: React.MouseEvent) => { e.stopPropagation(); }; @@ -20,7 +24,10 @@ const MenuButton: React.FC = ({ onEdit, onDelete, onSave }) =>
    - + = ({ title, onEdit }) => { active ? "bg-slate-200" : "text-gray-900" } group flex w-full items-center rounded-md px-2 py-2 text-sm`} > - - Edit - + Edit )} diff --git a/keep-ui/app/(keep)/dashboard/WidgetModal.tsx b/keep-ui/app/(keep)/dashboard/WidgetModal.tsx new file mode 100644 index 000000000..81ed554ec --- /dev/null +++ b/keep-ui/app/(keep)/dashboard/WidgetModal.tsx @@ -0,0 +1,430 @@ +import React, { ChangeEvent, useEffect, useState } from "react"; +import Modal from "@/components/ui/Modal"; +import { + Button, + Icon, + Select, + SelectItem, + Subtitle, + TextInput, +} from "@tremor/react"; +import { Trashcan } from "components/icons"; +import { GenericsMetrics, Threshold, WidgetData, WidgetType } from "./types"; +import { Preset } from "@/app/(keep)/alerts/models"; +import { Controller, get, useForm, useWatch } from "react-hook-form"; +import { MetricsWidget } from "@/utils/hooks/useDashboardMetricWidgets"; + +interface WidgetForm { + widgetName: string; + selectedPreset: string; + thresholds: Threshold[]; + widgetType: WidgetType; + selectedMetricWidget: string; + selectedGenericMetrics: string; +} + +interface WidgetModalProps { + isOpen: boolean; + onClose: () => void; + onAddWidget: ( + name: string, + widgetType: WidgetType, + preset?: Preset, + thresholds?: Threshold[], + metric?: MetricsWidget, + genericMetrics?: GenericsMetrics + ) => void; + onEditWidget: (updatedWidget: WidgetData) => void; + presets: Preset[]; + editingItem?: WidgetData | null; + metricWidgets: MetricsWidget[]; +} + +const GENERIC_METRICS = [ + { + key: "alert_quality", + label: "Alert Quality", + widgetType: "table", + meta: { + defaultFilters: { fields: "severity" }, + }, + }, +] as GenericsMetrics[]; + +const WidgetModal: React.FC = ({ + isOpen, + onClose, + onAddWidget, + onEditWidget, + presets, + editingItem, + metricWidgets, +}) => { + const [thresholds, setThresholds] = useState([ + { value: 0, color: "#22c55e" }, // Green + { value: 20, color: "#ef4444" }, // Red + ]); + + const { + control, + handleSubmit, + setValue, + formState: { errors }, + reset, + } = useForm({ + defaultValues: { + widgetName: "", + selectedPreset: "", + thresholds: thresholds, + widgetType: WidgetType.PRESET, + selectedGenericMetrics: "", + }, + }); + + const widgetType = useWatch({ + control, + name: "widgetType", + }); + + useEffect(() => { + if (editingItem) { + setValue("widgetName", editingItem.name); + setValue("widgetType", editingItem.widgetType); + + if (editingItem.thresholds) { + setThresholds(editingItem.thresholds); + } + setValue("selectedPreset", editingItem?.preset?.id ?? ""); + setValue("selectedMetricWidget", editingItem?.metric?.id ?? ""); + setValue( + "selectedGenericMetrics", + editingItem?.genericMetrics?.key ?? "" + ); + } else { + reset({ + widgetName: "", + selectedPreset: "", + selectedMetricWidget: "", + selectedGenericMetrics: "", + thresholds: thresholds, + widgetType: WidgetType.PRESET, + }); + } + }, [editingItem, setValue, reset]); + + const handleThresholdChange = ( + index: number, + field: "value" | "color", + e: ChangeEvent + ) => { + const value = field === "value" ? e.target.value : e.target.value; + const updatedThresholds = thresholds.map((t, i) => + i === index ? { ...t, [field]: value } : t + ); + setThresholds(updatedThresholds); + }; + + const handleThresholdBlur = () => { + setThresholds((prevThresholds) => { + return prevThresholds + .map((t) => ({ + ...t, + value: parseInt(t.value.toString(), 10) || 0, + })) + .sort((a, b) => a.value - b.value); + }); + }; + + const handleAddThreshold = () => { + const maxThreshold = Math.max(...thresholds.map((t) => t.value), 0); + setThresholds([ + ...thresholds, + { value: maxThreshold + 10, color: "#000000" }, + ]); + }; + + const handleRemoveThreshold = (index: number) => { + setThresholds(thresholds.filter((_, i) => i !== index)); + }; + + const deepClone = (obj: GenericsMetrics | undefined) => { + if (!obj) { + return obj; + } + return JSON.parse(JSON.stringify(obj)) as GenericsMetrics; + }; + + const onSubmit = (data: WidgetForm) => { + const preset = presets.find((p) => p.id === data.selectedPreset); + const metric = metricWidgets.find( + (p) => p.id === data.selectedMetricWidget + ); + if (preset || data.selectedGenericMetrics) { + const formattedThresholds = thresholds.map((t) => ({ + ...t, + value: parseInt(t.value.toString(), 10) || 0, + })); + + if (editingItem) { + let updatedWidget: WidgetData = { + ...editingItem, + name: data.widgetName, + widgetType: data.widgetType || WidgetType.PRESET, // backwards compatibility + preset, + thresholds: formattedThresholds, + genericMetrics: editingItem.genericMetrics, + }; + onEditWidget(updatedWidget); + } else { + onAddWidget( + data.widgetName, + data.widgetType, + preset, + formattedThresholds, + undefined, + deepClone( + GENERIC_METRICS.find((g) => g.key === data.selectedGenericMetrics) + ) + ); + // cleanup form + setThresholds([ + { value: 0, color: "#22c55e" }, // Green + { value: 20, color: "#ef4444" }, // Red + ]); + reset({ + widgetName: "", + selectedPreset: "", + thresholds: thresholds, + selectedGenericMetrics: "", + widgetType: WidgetType.PRESET, + }); + } + onClose(); + } + if (metric) { + if (editingItem) { + const updatedWidget: WidgetData = { + ...editingItem, + name: data.widgetName, + widgetType: data.widgetType, + }; + onEditWidget(updatedWidget); + } else { + onAddWidget( + data.widgetName, + data.widgetType, + undefined, + undefined, + metric, + undefined + ); + reset({ + widgetName: "", + selectedPreset: "", + widgetType: WidgetType.PRESET, + thresholds: thresholds, + }); + } + onClose(); + } + }; + + return ( + + +
    + Widget Name + ( + + )} + /> +
    +
    + Widget Type + { + return ( + + ); + }} + /> +
    + {widgetType === WidgetType.PRESET ? ( + <> +
    + Preset + ( + + )} + /> +
    +
    +
    + Thresholds + +
    +
    + {thresholds.map((threshold, index) => ( +
    + handleThresholdChange(index, "value", e)} + onBlur={handleThresholdBlur} + placeholder="Threshold value" + required + /> + handleThresholdChange(index, "color", e)} + className="w-10 h-10 p-1 border" + required + /> + {thresholds.length > 1 && ( + + )} +
    + ))} +
    +
    + + ) : widgetType === WidgetType.GENERICS_METRICS ? ( + <> +
    + Generic Metrics + ( + + )} + /> +
    + + ) : ( +
    + Widget + ( + + )} + /> +
    + )} + + +
    + ); +}; + +export default WidgetModal; diff --git a/keep-ui/app/dashboard/[id]/dashboard.tsx b/keep-ui/app/(keep)/dashboard/[id]/dashboard.tsx similarity index 97% rename from keep-ui/app/dashboard/[id]/dashboard.tsx rename to keep-ui/app/(keep)/dashboard/[id]/dashboard.tsx index 965f7f5fa..6a7302461 100644 --- a/keep-ui/app/dashboard/[id]/dashboard.tsx +++ b/keep-ui/app/(keep)/dashboard/[id]/dashboard.tsx @@ -11,12 +11,12 @@ import { WidgetData, WidgetType, } from "../types"; -import { Preset } from "app/alerts/models"; +import { Preset } from "@/app/(keep)/alerts/models"; import { FiEdit2, FiSave } from "react-icons/fi"; import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { useDashboards } from "utils/hooks/useDashboards"; import { useApiUrl } from "utils/hooks/useConfig"; -import "./../styles.css"; +import "../styles.css"; import { toast } from "react-toastify"; import { GenericFilters } from "@/components/filters/GenericFilters"; import { useDashboardPreset } from "utils/hooks/useDashboardPresets"; @@ -84,21 +84,21 @@ const DashboardPage = () => { widgetType === WidgetType.GENERICS_METRICS ? 12 : widgetType === WidgetType.METRIC - ? 6 - : 3, + ? 6 + : 3, h: widgetType === WidgetType.GENERICS_METRICS ? 20 : widgetType === WidgetType.METRIC - ? 8 - : 3, + ? 8 + : 3, minW: widgetType === WidgetType.GENERICS_METRICS ? 10 : 2, minH: widgetType === WidgetType.GENERICS_METRICS ? 15 : widgetType === WidgetType.METRIC - ? 7 - : 3, + ? 7 + : 3, static: false, }; const newWidget: WidgetData = { diff --git a/keep-ui/app/dashboard/[id]/page.tsx b/keep-ui/app/(keep)/dashboard/[id]/page.tsx similarity index 92% rename from keep-ui/app/dashboard/[id]/page.tsx rename to keep-ui/app/(keep)/dashboard/[id]/page.tsx index 963ca2fad..896c9192c 100644 --- a/keep-ui/app/dashboard/[id]/page.tsx +++ b/keep-ui/app/(keep)/dashboard/[id]/page.tsx @@ -6,7 +6,7 @@ type PageProps = { }; export default function Page({ params }: PageProps) { - return ; + return ; } export const metadata = { diff --git a/keep-ui/app/dashboard/styles.css b/keep-ui/app/(keep)/dashboard/styles.css similarity index 99% rename from keep-ui/app/dashboard/styles.css rename to keep-ui/app/(keep)/dashboard/styles.css index f3e0c0e55..dc1dbc126 100644 --- a/keep-ui/app/dashboard/styles.css +++ b/keep-ui/app/(keep)/dashboard/styles.css @@ -1,4 +1,3 @@ - .grid-item__widget:hover { cursor: move; } diff --git a/keep-ui/app/dashboard/types.tsx b/keep-ui/app/(keep)/dashboard/types.tsx similarity index 73% rename from keep-ui/app/dashboard/types.tsx rename to keep-ui/app/(keep)/dashboard/types.tsx index be80ac842..2de15c968 100644 --- a/keep-ui/app/dashboard/types.tsx +++ b/keep-ui/app/(keep)/dashboard/types.tsx @@ -1,5 +1,5 @@ -import {Preset} from "app/alerts/models"; -import {MetricsWidget} from "@/utils/hooks/useDashboardMetricWidgets"; +import { Preset } from "@/app/(keep)/alerts/models"; +import { MetricsWidget } from "@/utils/hooks/useDashboardMetricWidgets"; export interface LayoutItem { i: string; @@ -19,14 +19,14 @@ export interface GenericsMetrics { meta: { defaultFilters: { [key: string]: string | string[]; - }, - } + }; + }; } export enum WidgetType { - PRESET = 'PRESET', - METRIC = 'METRIC', - GENERICS_METRICS = 'GENERICS_METRICS' + PRESET = "PRESET", + METRIC = "METRIC", + GENERICS_METRICS = "GENERICS_METRICS", } export interface WidgetData extends LayoutItem { diff --git a/keep-ui/app/deduplication/DeduplicationPlaceholder.tsx b/keep-ui/app/(keep)/deduplication/DeduplicationPlaceholder.tsx similarity index 100% rename from keep-ui/app/deduplication/DeduplicationPlaceholder.tsx rename to keep-ui/app/(keep)/deduplication/DeduplicationPlaceholder.tsx diff --git a/keep-ui/app/(keep)/deduplication/DeduplicationSankey.tsx b/keep-ui/app/(keep)/deduplication/DeduplicationSankey.tsx new file mode 100644 index 000000000..e9cb967d2 --- /dev/null +++ b/keep-ui/app/(keep)/deduplication/DeduplicationSankey.tsx @@ -0,0 +1,357 @@ +import { SVGProps } from "react"; + +export const DeduplicationSankey = (props: SVGProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/keep-ui/app/deduplication/DeduplicationSidebar.tsx b/keep-ui/app/(keep)/deduplication/DeduplicationSidebar.tsx similarity index 99% rename from keep-ui/app/deduplication/DeduplicationSidebar.tsx rename to keep-ui/app/(keep)/deduplication/DeduplicationSidebar.tsx index 48a6d808e..99669c327 100644 --- a/keep-ui/app/deduplication/DeduplicationSidebar.tsx +++ b/keep-ui/app/(keep)/deduplication/DeduplicationSidebar.tsx @@ -13,7 +13,7 @@ import { Card, } from "@tremor/react"; import { IoMdClose } from "react-icons/io"; -import { DeduplicationRule } from "app/deduplication/models"; +import { DeduplicationRule } from "@/app/(keep)/deduplication/models"; import { useProviders } from "utils/hooks/useProviders"; import { useDeduplicationFields } from "utils/hooks/useDeduplicationRules"; import { GroupBase } from "react-select"; diff --git a/keep-ui/app/deduplication/DeduplicationTable.tsx b/keep-ui/app/(keep)/deduplication/DeduplicationTable.tsx similarity index 98% rename from keep-ui/app/deduplication/DeduplicationTable.tsx rename to keep-ui/app/(keep)/deduplication/DeduplicationTable.tsx index 3951fbaaa..d122e28cc 100644 --- a/keep-ui/app/deduplication/DeduplicationTable.tsx +++ b/keep-ui/app/(keep)/deduplication/DeduplicationTable.tsx @@ -20,8 +20,8 @@ import { getCoreRowModel, useReactTable, } from "@tanstack/react-table"; -import { DeduplicationRule } from "app/deduplication/models"; -import DeduplicationSidebar from "app/deduplication/DeduplicationSidebar"; +import { DeduplicationRule } from "@/app/(keep)/deduplication/models"; +import DeduplicationSidebar from "@/app/(keep)/deduplication/DeduplicationSidebar"; import { TrashIcon, PauseIcon, PlusIcon } from "@heroicons/react/24/outline"; import Image from "next/image"; import { useApiUrl } from "utils/hooks/useConfig"; diff --git a/keep-ui/app/(keep)/deduplication/client.tsx b/keep-ui/app/(keep)/deduplication/client.tsx new file mode 100644 index 000000000..2e909e405 --- /dev/null +++ b/keep-ui/app/(keep)/deduplication/client.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { useDeduplicationRules } from "utils/hooks/useDeduplicationRules"; +import { DeduplicationPlaceholder } from "./DeduplicationPlaceholder"; +import { DeduplicationTable } from "./DeduplicationTable"; +import Loading from "@/app/(keep)/loading"; + +export const Client = () => { + const { + data: deduplicationRules = [], + isLoading, + mutate: mutateDeduplicationRules, + } = useDeduplicationRules(); + + if (isLoading) { + return ; + } + + if (deduplicationRules.length === 0) { + return ; + } + + return ( + + ); +}; diff --git a/keep-ui/app/deduplication/models.tsx b/keep-ui/app/(keep)/deduplication/models.tsx similarity index 100% rename from keep-ui/app/deduplication/models.tsx rename to keep-ui/app/(keep)/deduplication/models.tsx diff --git a/keep-ui/app/deduplication/page.tsx b/keep-ui/app/(keep)/deduplication/page.tsx similarity index 100% rename from keep-ui/app/deduplication/page.tsx rename to keep-ui/app/(keep)/deduplication/page.tsx diff --git a/keep-ui/app/error.css b/keep-ui/app/(keep)/error.css similarity index 100% rename from keep-ui/app/error.css rename to keep-ui/app/(keep)/error.css diff --git a/keep-ui/app/error.tsx b/keep-ui/app/(keep)/error.tsx similarity index 100% rename from keep-ui/app/error.tsx rename to keep-ui/app/(keep)/error.tsx diff --git a/keep-ui/app/extraction/create-or-update-extraction-rule.tsx b/keep-ui/app/(keep)/extraction/create-or-update-extraction-rule.tsx similarity index 99% rename from keep-ui/app/extraction/create-or-update-extraction-rule.tsx rename to keep-ui/app/(keep)/extraction/create-or-update-extraction-rule.tsx index 11d7c032b..ca50f4b4d 100644 --- a/keep-ui/app/extraction/create-or-update-extraction-rule.tsx +++ b/keep-ui/app/(keep)/extraction/create-or-update-extraction-rule.tsx @@ -20,7 +20,7 @@ import { useApiUrl } from "utils/hooks/useConfig"; import { ExtractionRule } from "./model"; import { extractNamedGroups } from "./extractions-table"; import { useExtractions } from "utils/hooks/useExtractionRules"; -import { AlertsRulesBuilder } from "app/alerts/alerts-rules-builder"; +import { AlertsRulesBuilder } from "@/app/(keep)/alerts/alerts-rules-builder"; interface Props { extractionToEdit: ExtractionRule | null; diff --git a/keep-ui/app/extraction/extraction.tsx b/keep-ui/app/(keep)/extraction/extraction.tsx similarity index 97% rename from keep-ui/app/extraction/extraction.tsx rename to keep-ui/app/(keep)/extraction/extraction.tsx index 1e4544e6d..07ad49115 100644 --- a/keep-ui/app/extraction/extraction.tsx +++ b/keep-ui/app/(keep)/extraction/extraction.tsx @@ -3,7 +3,7 @@ import { Badge, Callout, Card } from "@tremor/react"; import CreateOrUpdateExtractionRule from "./create-or-update-extraction-rule"; import ExtractionsTable from "./extractions-table"; import { useExtractions } from "utils/hooks/useExtractionRules"; -import Loading from "app/loading"; +import Loading from "@/app/(keep)/loading"; import { MdWarning } from "react-icons/md"; import { useState } from "react"; import { ExtractionRule } from "./model"; diff --git a/keep-ui/app/extraction/extractions-table.tsx b/keep-ui/app/(keep)/extraction/extractions-table.tsx similarity index 100% rename from keep-ui/app/extraction/extractions-table.tsx rename to keep-ui/app/(keep)/extraction/extractions-table.tsx diff --git a/keep-ui/app/extraction/layout.tsx b/keep-ui/app/(keep)/extraction/layout.tsx similarity index 70% rename from keep-ui/app/extraction/layout.tsx rename to keep-ui/app/(keep)/extraction/layout.tsx index f063b205f..5e930dc8d 100644 --- a/keep-ui/app/extraction/layout.tsx +++ b/keep-ui/app/(keep)/extraction/layout.tsx @@ -4,7 +4,9 @@ export default function Layout({ children }: { children: any }) { return (
    Extractions - Easily extract more attributes from your alerts using Regex + + Easily extract more attributes from your alerts using Regex + {children}
    diff --git a/keep-ui/app/extraction/model.ts b/keep-ui/app/(keep)/extraction/model.ts similarity index 100% rename from keep-ui/app/extraction/model.ts rename to keep-ui/app/(keep)/extraction/model.ts diff --git a/keep-ui/app/extraction/page.tsx b/keep-ui/app/(keep)/extraction/page.tsx similarity index 100% rename from keep-ui/app/extraction/page.tsx rename to keep-ui/app/(keep)/extraction/page.tsx diff --git a/keep-ui/app/global-error.tsx b/keep-ui/app/(keep)/global-error.tsx similarity index 85% rename from keep-ui/app/global-error.tsx rename to keep-ui/app/(keep)/global-error.tsx index 9bda5feef..9388e06e0 100644 --- a/keep-ui/app/global-error.tsx +++ b/keep-ui/app/(keep)/global-error.tsx @@ -4,7 +4,11 @@ import * as Sentry from "@sentry/nextjs"; import NextError from "next/error"; import { useEffect } from "react"; -export default function GlobalError({ error }: { error: Error & { digest?: string } }) { +export default function GlobalError({ + error, +}: { + error: Error & { digest?: string }; +}) { useEffect(() => { Sentry.captureException(error); }, [error]); @@ -20,4 +24,4 @@ export default function GlobalError({ error }: { error: Error & { digest?: strin ); -} \ No newline at end of file +} diff --git a/keep-ui/app/incidents/[id]/activity/incident-activity.css b/keep-ui/app/(keep)/incidents/[id]/activity/incident-activity.css similarity index 100% rename from keep-ui/app/incidents/[id]/activity/incident-activity.css rename to keep-ui/app/(keep)/incidents/[id]/activity/incident-activity.css diff --git a/keep-ui/app/incidents/[id]/activity/incident-activity.tsx b/keep-ui/app/(keep)/incidents/[id]/activity/incident-activity.tsx similarity index 98% rename from keep-ui/app/incidents/[id]/activity/incident-activity.tsx rename to keep-ui/app/(keep)/incidents/[id]/activity/incident-activity.tsx index 7a62633f6..0c803d477 100644 --- a/keep-ui/app/incidents/[id]/activity/incident-activity.tsx +++ b/keep-ui/app/(keep)/incidents/[id]/activity/incident-activity.tsx @@ -1,6 +1,6 @@ "use client"; -import { AlertDto } from "@/app/alerts/models"; +import { AlertDto } from "@/app/(keep)/alerts/models"; import { IncidentDto } from "@/entities/incidents/model"; import { useUsers } from "@/utils/hooks/useUsers"; import Image from "next/image"; @@ -125,7 +125,7 @@ export function IncidentActivity({ incident }: { incident: IncidentDto }) { id: "newcomment", type: "newcomment", timestamp: new Date().toISOString(), - initiator: session?.user.email, + initiator: session?.user.email ?? "", }; const renderIcon = (activity: IncidentActivity) => { diff --git a/keep-ui/app/incidents/[id]/activity/page.tsx b/keep-ui/app/(keep)/incidents/[id]/activity/page.tsx similarity index 100% rename from keep-ui/app/incidents/[id]/activity/page.tsx rename to keep-ui/app/(keep)/incidents/[id]/activity/page.tsx diff --git a/keep-ui/app/incidents/[id]/activity/ui/IncidentActivityComment.tsx b/keep-ui/app/(keep)/incidents/[id]/activity/ui/IncidentActivityComment.tsx similarity index 100% rename from keep-ui/app/incidents/[id]/activity/ui/IncidentActivityComment.tsx rename to keep-ui/app/(keep)/incidents/[id]/activity/ui/IncidentActivityComment.tsx diff --git a/keep-ui/app/incidents/[id]/activity/ui/IncidentActivityItem.tsx b/keep-ui/app/(keep)/incidents/[id]/activity/ui/IncidentActivityItem.tsx similarity index 90% rename from keep-ui/app/incidents/[id]/activity/ui/IncidentActivityItem.tsx rename to keep-ui/app/(keep)/incidents/[id]/activity/ui/IncidentActivityItem.tsx index e17c570eb..84ccdde64 100644 --- a/keep-ui/app/incidents/[id]/activity/ui/IncidentActivityItem.tsx +++ b/keep-ui/app/(keep)/incidents/[id]/activity/ui/IncidentActivityItem.tsx @@ -1,5 +1,5 @@ -import AlertSeverity from "@/app/alerts/alert-severity"; -import { AlertDto } from "@/app/alerts/models"; +import AlertSeverity from "@/app/(keep)/alerts/alert-severity"; +import { AlertDto } from "@/app/(keep)/alerts/models"; import TimeAgo from "react-timeago"; export function IncidentActivityItem({ activity }: { activity: any }) { diff --git a/keep-ui/app/incidents/[id]/alerts/incident-alert-menu.tsx b/keep-ui/app/(keep)/incidents/[id]/alerts/incident-alert-menu.tsx similarity index 96% rename from keep-ui/app/incidents/[id]/alerts/incident-alert-menu.tsx rename to keep-ui/app/(keep)/incidents/[id]/alerts/incident-alert-menu.tsx index f902df664..799afbc28 100644 --- a/keep-ui/app/incidents/[id]/alerts/incident-alert-menu.tsx +++ b/keep-ui/app/(keep)/incidents/[id]/alerts/incident-alert-menu.tsx @@ -1,5 +1,5 @@ import { Icon } from "@tremor/react"; -import { AlertDto } from "app/alerts/models"; +import { AlertDto } from "@/app/(keep)/alerts/models"; import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession"; import { toast } from "react-toastify"; import { useApiUrl } from "utils/hooks/useConfig"; diff --git a/keep-ui/app/incidents/[id]/alerts/incident-alerts.tsx b/keep-ui/app/(keep)/incidents/[id]/alerts/incident-alerts.tsx similarity index 97% rename from keep-ui/app/incidents/[id]/alerts/incident-alerts.tsx rename to keep-ui/app/(keep)/incidents/[id]/alerts/incident-alerts.tsx index 4d0b45942..7d3d828e8 100644 --- a/keep-ui/app/incidents/[id]/alerts/incident-alerts.tsx +++ b/keep-ui/app/(keep)/incidents/[id]/alerts/incident-alerts.tsx @@ -16,8 +16,8 @@ import { TableRow, } from "@tremor/react"; import Image from "next/image"; -import AlertSeverity from "app/alerts/alert-severity"; -import { AlertDto } from "app/alerts/models"; +import AlertSeverity from "@/app/(keep)/alerts/alert-severity"; +import { AlertDto } from "@/app/(keep)/alerts/models"; import Skeleton from "react-loading-skeleton"; import "react-loading-skeleton/dist/skeleton.css"; import { getAlertLastReceieved } from "utils/helpers"; @@ -25,12 +25,11 @@ import { useIncidentAlerts, usePollIncidentAlerts, } from "utils/hooks/useIncidents"; -import AlertName from "app/alerts/alert-name"; +import AlertName from "@/app/(keep)/alerts/alert-name"; import IncidentAlertMenu from "./incident-alert-menu"; import React, { useEffect, useMemo, useState } from "react"; import type { IncidentDto } from "@/entities/incidents/model"; import { getCommonPinningStylesAndClassNames } from "@/components/ui/table/utils"; -// import AlertTableCheckbox from "@/app/alerts/alert-table-checkbox"; import { EmptyStateCard } from "@/components/ui"; import { useRouter } from "next/navigation"; import { TablePagination } from "@/shared/ui"; diff --git a/keep-ui/app/incidents/[id]/alerts/page.tsx b/keep-ui/app/(keep)/incidents/[id]/alerts/page.tsx similarity index 100% rename from keep-ui/app/incidents/[id]/alerts/page.tsx rename to keep-ui/app/(keep)/incidents/[id]/alerts/page.tsx diff --git a/keep-ui/app/(keep)/incidents/[id]/chat/incident-chat.css b/keep-ui/app/(keep)/incidents/[id]/chat/incident-chat.css new file mode 100644 index 000000000..dcd13cd66 --- /dev/null +++ b/keep-ui/app/(keep)/incidents/[id]/chat/incident-chat.css @@ -0,0 +1,21 @@ +.copilotKitInput { + @apply sticky bottom-0 bg-white !important; + @apply flex items-center outline-none rounded-tremor-default px-3 py-2 mx-2 text-tremor-default focus:ring-2 transition duration-100 border shadow-tremor-input focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted dark:shadow-dark-tremor-input focus:dark:border-dark-tremor-brand-subtle focus:dark:ring-dark-tremor-brand-muted bg-tremor-background dark:bg-dark-tremor-background hover:bg-tremor-background-muted dark:hover:bg-dark-tremor-background-muted text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis border-tremor-border dark:border-dark-tremor-border placeholder:text-tremor-content dark:placeholder:text-dark-tremor-content !important; +} + +.copilotKitInput:hover textarea { + @apply bg-tremor-background-muted dark:bg-dark-tremor-background-muted transition duration-100 !important; +} + +.copilotKitInput textarea { + height: unset !important; + max-height: unset !important; +} + +.copilotKitMessages { + @apply px-4 !important; +} + +.copilotKitMessages .suggestion { + @apply bg-white text-black scale-100 border-tremor-border border-2 hover:border-tremor-brand hover:text-tremor-brand hover:bg-tremor-brand-muted dark:border-dark-tremor-brand dark:hover:bg-dark-tremor-brand-muted transition !important; +} diff --git a/keep-ui/app/incidents/[id]/chat/incident-chat.tsx b/keep-ui/app/(keep)/incidents/[id]/chat/incident-chat.tsx similarity index 98% rename from keep-ui/app/incidents/[id]/chat/incident-chat.tsx rename to keep-ui/app/(keep)/incidents/[id]/chat/incident-chat.tsx index b3909e250..cace4b0c1 100644 --- a/keep-ui/app/incidents/[id]/chat/incident-chat.tsx +++ b/keep-ui/app/(keep)/incidents/[id]/chat/incident-chat.tsx @@ -7,7 +7,7 @@ import type { IncidentDto } from "@/entities/incidents/model"; import { useIncidentAlerts } from "utils/hooks/useIncidents"; import { EmptyStateCard } from "@/components/ui/EmptyStateCard"; import { useRouter } from "next/navigation"; -import Loading from "app/loading"; +import Loading from "@/app/(keep)/loading"; import { useCopilotAction, useCopilotReadable } from "@copilotkit/react-core"; import { Card } from "@tremor/react"; import { useIncidentActions } from "@/entities/incidents/model"; diff --git a/keep-ui/app/incidents/[id]/chat/page.client.tsx b/keep-ui/app/(keep)/incidents/[id]/chat/page.client.tsx similarity index 100% rename from keep-ui/app/incidents/[id]/chat/page.client.tsx rename to keep-ui/app/(keep)/incidents/[id]/chat/page.client.tsx diff --git a/keep-ui/app/incidents/[id]/chat/page.tsx b/keep-ui/app/(keep)/incidents/[id]/chat/page.tsx similarity index 100% rename from keep-ui/app/incidents/[id]/chat/page.tsx rename to keep-ui/app/(keep)/incidents/[id]/chat/page.tsx diff --git a/keep-ui/app/incidents/[id]/getIncidentWithErrorHandling.tsx b/keep-ui/app/(keep)/incidents/[id]/getIncidentWithErrorHandling.tsx similarity index 81% rename from keep-ui/app/incidents/[id]/getIncidentWithErrorHandling.tsx rename to keep-ui/app/(keep)/incidents/[id]/getIncidentWithErrorHandling.tsx index a7a262cca..b67b19590 100644 --- a/keep-ui/app/incidents/[id]/getIncidentWithErrorHandling.tsx +++ b/keep-ui/app/(keep)/incidents/[id]/getIncidentWithErrorHandling.tsx @@ -1,8 +1,6 @@ import { getIncident } from "@/entities/incidents/api/incidents"; -import { getServerSession } from "next-auth"; import { getApiURL } from "@/utils/apiUrl"; -import { authOptions } from "@/pages/api/auth/[...nextauth]"; - +import { auth } from "@/auth"; import { notFound } from "next/navigation"; import { KeepApiError } from "@/shared/lib/KeepApiError"; import { IncidentDto } from "@/entities/incidents/model"; @@ -13,7 +11,7 @@ export async function getIncidentWithErrorHandling( // @ts-ignore ignoring since not found will be handled by nextjs ): Promise { try { - const session = await getServerSession(authOptions); + const session = await auth(); const apiUrl = getApiURL(); const incident = await getIncident(apiUrl, session, id); return incident; diff --git a/keep-ui/app/incidents/[id]/incident-header-skeleton.tsx b/keep-ui/app/(keep)/incidents/[id]/incident-header-skeleton.tsx similarity index 100% rename from keep-ui/app/incidents/[id]/incident-header-skeleton.tsx rename to keep-ui/app/(keep)/incidents/[id]/incident-header-skeleton.tsx diff --git a/keep-ui/app/incidents/[id]/incident-header.tsx b/keep-ui/app/(keep)/incidents/[id]/incident-header.tsx similarity index 98% rename from keep-ui/app/incidents/[id]/incident-header.tsx rename to keep-ui/app/(keep)/incidents/[id]/incident-header.tsx index 1562dc2ac..d6ff1a10b 100644 --- a/keep-ui/app/incidents/[id]/incident-header.tsx +++ b/keep-ui/app/(keep)/incidents/[id]/incident-header.tsx @@ -10,7 +10,7 @@ import { ArrowRightIcon } from "@heroicons/react/16/solid"; import { MdBlock, MdDone, MdModeEdit, MdPlayArrow } from "react-icons/md"; import React, { useState } from "react"; import { useRouter } from "next/navigation"; -import ManualRunWorkflowModal from "@/app/workflows/manual-run-workflow-modal"; +import ManualRunWorkflowModal from "@/app/(keep)/workflows/manual-run-workflow-modal"; import { CreateOrUpdateIncidentForm } from "@/features/create-or-update-incident"; import Modal from "@/components/ui/Modal"; import { IncidentSeverityBadge } from "@/entities/incidents/ui"; diff --git a/keep-ui/app/incidents/[id]/incident-overview.tsx b/keep-ui/app/(keep)/incidents/[id]/incident-overview.tsx similarity index 100% rename from keep-ui/app/incidents/[id]/incident-overview.tsx rename to keep-ui/app/(keep)/incidents/[id]/incident-overview.tsx diff --git a/keep-ui/app/incidents/[id]/incident-tabs-navigation.tsx b/keep-ui/app/(keep)/incidents/[id]/incident-tabs-navigation.tsx similarity index 100% rename from keep-ui/app/incidents/[id]/incident-tabs-navigation.tsx rename to keep-ui/app/(keep)/incidents/[id]/incident-tabs-navigation.tsx diff --git a/keep-ui/app/incidents/[id]/layout.tsx b/keep-ui/app/(keep)/incidents/[id]/layout.tsx similarity index 100% rename from keep-ui/app/incidents/[id]/layout.tsx rename to keep-ui/app/(keep)/incidents/[id]/layout.tsx diff --git a/keep-ui/app/incidents/[id]/not-found.tsx b/keep-ui/app/(keep)/incidents/[id]/not-found.tsx similarity index 100% rename from keep-ui/app/incidents/[id]/not-found.tsx rename to keep-ui/app/(keep)/incidents/[id]/not-found.tsx diff --git a/keep-ui/app/incidents/[id]/route.tsx b/keep-ui/app/(keep)/incidents/[id]/route.tsx similarity index 100% rename from keep-ui/app/incidents/[id]/route.tsx rename to keep-ui/app/(keep)/incidents/[id]/route.tsx diff --git a/keep-ui/app/incidents/[id]/timeline/incident-timeline.tsx b/keep-ui/app/(keep)/incidents/[id]/timeline/incident-timeline.tsx similarity index 98% rename from keep-ui/app/incidents/[id]/timeline/incident-timeline.tsx rename to keep-ui/app/(keep)/incidents/[id]/timeline/incident-timeline.tsx index da442b104..525387b19 100644 --- a/keep-ui/app/incidents/[id]/timeline/incident-timeline.tsx +++ b/keep-ui/app/(keep)/incidents/[id]/timeline/incident-timeline.tsx @@ -1,13 +1,13 @@ "use client"; -import Loading from "@/app/loading"; +import Loading from "@/app/(keep)/loading"; import { EmptyStateCard } from "@/components/ui/EmptyStateCard"; import type { IncidentDto } from "@/entities/incidents/model"; import { AuditEvent, useAlerts } from "@/utils/hooks/useAlerts"; import { useIncidentAlerts } from "@/utils/hooks/useIncidents"; import { Card } from "@tremor/react"; -import AlertSeverity from "app/alerts/alert-severity"; -import { AlertDto } from "app/alerts/models"; +import AlertSeverity from "@/app/(keep)/alerts/alert-severity"; +import { AlertDto } from "@/app/(keep)/alerts/models"; import { format, parseISO } from "date-fns"; import Image from "next/image"; import { useRouter } from "next/navigation"; diff --git a/keep-ui/app/incidents/[id]/timeline/page.tsx b/keep-ui/app/(keep)/incidents/[id]/timeline/page.tsx similarity index 100% rename from keep-ui/app/incidents/[id]/timeline/page.tsx rename to keep-ui/app/(keep)/incidents/[id]/timeline/page.tsx diff --git a/keep-ui/app/incidents/[id]/topology/page.tsx b/keep-ui/app/(keep)/incidents/[id]/topology/page.tsx similarity index 86% rename from keep-ui/app/incidents/[id]/topology/page.tsx rename to keep-ui/app/(keep)/incidents/[id]/topology/page.tsx index 264ffe497..2d617199d 100644 --- a/keep-ui/app/incidents/[id]/topology/page.tsx +++ b/keep-ui/app/(keep)/incidents/[id]/topology/page.tsx @@ -1,5 +1,5 @@ -import { TopologySearchProvider } from "@/app/topology/TopologySearchContext"; -import { TopologyMap } from "@/app/topology/ui/map"; +import { TopologySearchProvider } from "@/app/(keep)/topology/TopologySearchContext"; +import { TopologyMap } from "@/app/(keep)/topology/ui/map"; import { getIncidentWithErrorHandling } from "../getIncidentWithErrorHandling"; import { getIncidentName } from "@/entities/incidents/lib/utils"; diff --git a/keep-ui/app/incidents/[id]/workflows/incident-workflow-empty.tsx b/keep-ui/app/(keep)/incidents/[id]/workflows/incident-workflow-empty.tsx similarity index 91% rename from keep-ui/app/incidents/[id]/workflows/incident-workflow-empty.tsx rename to keep-ui/app/(keep)/incidents/[id]/workflows/incident-workflow-empty.tsx index 7ff9e31a2..8c5b48615 100644 --- a/keep-ui/app/incidents/[id]/workflows/incident-workflow-empty.tsx +++ b/keep-ui/app/(keep)/incidents/[id]/workflows/incident-workflow-empty.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { EmptyStateCard } from "@/components/ui"; -import ManualRunWorkflowModal from "@/app/workflows/manual-run-workflow-modal"; +import ManualRunWorkflowModal from "@/app/(keep)/workflows/manual-run-workflow-modal"; import type { IncidentDto } from "@/entities/incidents/model"; export function IncidentWorkflowsEmptyState({ diff --git a/keep-ui/app/incidents/[id]/workflows/incident-workflow-sidebar.tsx b/keep-ui/app/(keep)/incidents/[id]/workflows/incident-workflow-sidebar.tsx similarity index 96% rename from keep-ui/app/incidents/[id]/workflows/incident-workflow-sidebar.tsx rename to keep-ui/app/(keep)/incidents/[id]/workflows/incident-workflow-sidebar.tsx index 2f028fa8d..de1907f43 100644 --- a/keep-ui/app/incidents/[id]/workflows/incident-workflow-sidebar.tsx +++ b/keep-ui/app/(keep)/incidents/[id]/workflows/incident-workflow-sidebar.tsx @@ -2,12 +2,12 @@ import { Fragment } from "react"; import { Dialog, Transition } from "@headlessui/react"; import { Text, Button, TextInput, Badge, Title, Card } from "@tremor/react"; import { IoMdClose } from "react-icons/io"; -import { WorkflowExecution } from "app/workflows/builder/types"; +import { WorkflowExecution } from "@/app/(keep)/workflows/builder/types"; import { getIcon, getTriggerIcon, extractTriggerValue, -} from "app/workflows/[workflow_id]/workflow-execution-table"; +} from "@/app/(keep)/workflows/[workflow_id]/workflow-execution-table"; import { useWorkflowExecution } from "utils/hooks/useWorkflowExecutions"; interface IncidentWorkflowSidebarProps { @@ -60,8 +60,8 @@ const IncidentWorkflowSidebar: React.FC = ({ selectedExecution.status === "error" ? "red" : selectedExecution.status === "success" - ? "green" - : "orange" + ? "green" + : "orange" } > {selectedExecution.status} diff --git a/keep-ui/app/incidents/[id]/workflows/incident-workflow-table.tsx b/keep-ui/app/(keep)/incidents/[id]/workflows/incident-workflow-table.tsx similarity index 98% rename from keep-ui/app/incidents/[id]/workflows/incident-workflow-table.tsx rename to keep-ui/app/(keep)/incidents/[id]/workflows/incident-workflow-table.tsx index 83d4ccf0b..3143adf65 100644 --- a/keep-ui/app/incidents/[id]/workflows/incident-workflow-table.tsx +++ b/keep-ui/app/(keep)/incidents/[id]/workflows/incident-workflow-table.tsx @@ -25,8 +25,8 @@ import { extractTriggerValue, getIcon, getTriggerIcon, -} from "app/workflows/[workflow_id]/workflow-execution-table"; -import { WorkflowExecution } from "app/workflows/builder/types"; +} from "@/app/(keep)/workflows/[workflow_id]/workflow-execution-table"; +import { WorkflowExecution } from "@/app/(keep)/workflows/builder/types"; import { useEffect, useState } from "react"; import Skeleton from "react-loading-skeleton"; import "react-loading-skeleton/dist/skeleton.css"; diff --git a/keep-ui/app/incidents/[id]/workflows/page.tsx b/keep-ui/app/(keep)/incidents/[id]/workflows/page.tsx similarity index 100% rename from keep-ui/app/incidents/[id]/workflows/page.tsx rename to keep-ui/app/(keep)/incidents/[id]/workflows/page.tsx diff --git a/keep-ui/app/incidents/layout.tsx b/keep-ui/app/(keep)/incidents/layout.tsx similarity index 60% rename from keep-ui/app/incidents/layout.tsx rename to keep-ui/app/(keep)/incidents/layout.tsx index be1b63b46..68c3ab1c5 100644 --- a/keep-ui/app/incidents/layout.tsx +++ b/keep-ui/app/(keep)/incidents/layout.tsx @@ -1,5 +1,5 @@ "use client"; -import { IncidentFilterContextProvider } from "../../features/incident-list/ui/incident-table-filters-context"; +import { IncidentFilterContextProvider } from "../../../features/incident-list/ui/incident-table-filters-context"; export default function Layout({ children }: { children: any }) { return ( diff --git a/keep-ui/app/incidents/page.tsx b/keep-ui/app/(keep)/incidents/page.tsx similarity index 83% rename from keep-ui/app/incidents/page.tsx rename to keep-ui/app/(keep)/incidents/page.tsx index 79420d9c2..d44f334d8 100644 --- a/keep-ui/app/incidents/page.tsx +++ b/keep-ui/app/(keep)/incidents/page.tsx @@ -1,6 +1,5 @@ -import { getServerSession } from "next-auth/next"; import { getApiURL } from "@/utils/apiUrl"; -import { authOptions } from "@/pages/api/auth/[...nextauth]"; +import { auth } from "@/auth"; import { IncidentList } from "@/features/incident-list"; import { getIncidents, @@ -19,7 +18,7 @@ const defaultIncidentsParams: GetIncidentsParams = { export default async function Page() { let incidents: PaginatedIncidentsDto | null = null; try { - const session = await getServerSession(authOptions); + const session = await auth(); const apiUrl = getApiURL(); incidents = await getIncidents(apiUrl, session, defaultIncidentsParams); diff --git a/keep-ui/app/incidents/predicted-incidents-table.tsx b/keep-ui/app/(keep)/incidents/predicted-incidents-table.tsx similarity index 100% rename from keep-ui/app/incidents/predicted-incidents-table.tsx rename to keep-ui/app/(keep)/incidents/predicted-incidents-table.tsx diff --git a/keep-ui/app/layout.tsx b/keep-ui/app/(keep)/layout.tsx similarity index 80% rename from keep-ui/app/layout.tsx rename to keep-ui/app/(keep)/layout.tsx index a8ee5503a..a27212031 100644 --- a/keep-ui/app/layout.tsx +++ b/keep-ui/app/(keep)/layout.tsx @@ -1,18 +1,17 @@ import { ReactNode } from "react"; -import { NextAuthProvider } from "./auth-provider"; +import { NextAuthProvider } from "../auth-provider"; import { Mulish } from "next/font/google"; import { ToastContainer } from "react-toastify"; import Navbar from "components/navbar/Navbar"; -import { TopologyPollingContextProvider } from "@/app/topology/model/TopologyPollingContext"; -import { FrigadeProvider } from "./frigade-provider"; +import { TopologyPollingContextProvider } from "@/app/(keep)/topology/model/TopologyPollingContext"; +import { FrigadeProvider } from "../frigade-provider"; import { getConfig } from "@/shared/lib/server/getConfig"; -import { ConfigProvider } from "./config-provider"; -import { PHProvider } from "./posthog-provider"; +import { ConfigProvider } from "../config-provider"; +import { PHProvider } from "../posthog-provider"; import dynamic from "next/dynamic"; -import ReadOnlyBanner from "./read-only-banner"; -import { getServerSession } from "next-auth"; -import { authOptions } from "@/pages/api/auth/[...nextauth]"; -import "./globals.css"; +import ReadOnlyBanner from "../read-only-banner"; +import { auth } from "@/auth"; +import "@/app/globals.css"; import "react-toastify/dist/ReactToastify.css"; const PostHogPageView = dynamic(() => import("@/shared/ui/PostHogPageView"), { @@ -31,7 +30,7 @@ type RootLayoutProps = { export default async function RootLayout({ children }: RootLayoutProps) { const config = getConfig(); - const session = await getServerSession(authOptions); + const session = await auth(); return ( diff --git a/keep-ui/app/loading.tsx b/keep-ui/app/(keep)/loading.tsx similarity index 100% rename from keep-ui/app/loading.tsx rename to keep-ui/app/(keep)/loading.tsx diff --git a/keep-ui/app/maintenance/create-or-update-maintenance-rule.tsx b/keep-ui/app/(keep)/maintenance/create-or-update-maintenance-rule.tsx similarity index 99% rename from keep-ui/app/maintenance/create-or-update-maintenance-rule.tsx rename to keep-ui/app/(keep)/maintenance/create-or-update-maintenance-rule.tsx index 9a71bc59f..6c46152cf 100644 --- a/keep-ui/app/maintenance/create-or-update-maintenance-rule.tsx +++ b/keep-ui/app/(keep)/maintenance/create-or-update-maintenance-rule.tsx @@ -16,7 +16,7 @@ import { toast } from "react-toastify"; import { useApiUrl } from "utils/hooks/useConfig"; import { MaintenanceRule } from "./model"; import { useMaintenanceRules } from "utils/hooks/useMaintenanceRules"; -import { AlertsRulesBuilder } from "app/alerts/alerts-rules-builder"; +import { AlertsRulesBuilder } from "@/app/(keep)/alerts/alerts-rules-builder"; import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; import { useRouter } from "next/navigation"; diff --git a/keep-ui/app/maintenance/layout.tsx b/keep-ui/app/(keep)/maintenance/layout.tsx similarity index 100% rename from keep-ui/app/maintenance/layout.tsx rename to keep-ui/app/(keep)/maintenance/layout.tsx diff --git a/keep-ui/app/maintenance/maintenance-rules-table.tsx b/keep-ui/app/(keep)/maintenance/maintenance-rules-table.tsx similarity index 100% rename from keep-ui/app/maintenance/maintenance-rules-table.tsx rename to keep-ui/app/(keep)/maintenance/maintenance-rules-table.tsx diff --git a/keep-ui/app/maintenance/maintenance.tsx b/keep-ui/app/(keep)/maintenance/maintenance.tsx similarity index 97% rename from keep-ui/app/maintenance/maintenance.tsx rename to keep-ui/app/(keep)/maintenance/maintenance.tsx index b5c92b542..e012a19d0 100644 --- a/keep-ui/app/maintenance/maintenance.tsx +++ b/keep-ui/app/(keep)/maintenance/maintenance.tsx @@ -1,7 +1,7 @@ "use client"; import { Badge, Callout, Card } from "@tremor/react"; import { useMaintenanceRules } from "utils/hooks/useMaintenanceRules"; -import Loading from "app/loading"; +import Loading from "@/app/(keep)/loading"; import { MdWarning } from "react-icons/md"; import { useState } from "react"; import { MaintenanceRule } from "./model"; diff --git a/keep-ui/app/maintenance/model.ts b/keep-ui/app/(keep)/maintenance/model.ts similarity index 100% rename from keep-ui/app/maintenance/model.ts rename to keep-ui/app/(keep)/maintenance/model.ts diff --git a/keep-ui/app/maintenance/page.tsx b/keep-ui/app/(keep)/maintenance/page.tsx similarity index 100% rename from keep-ui/app/maintenance/page.tsx rename to keep-ui/app/(keep)/maintenance/page.tsx diff --git a/keep-ui/app/mapping/create-or-edit-mapping.tsx b/keep-ui/app/(keep)/mapping/create-or-edit-mapping.tsx similarity index 99% rename from keep-ui/app/mapping/create-or-edit-mapping.tsx rename to keep-ui/app/(keep)/mapping/create-or-edit-mapping.tsx index 5c0b61a95..c526d49bd 100644 --- a/keep-ui/app/mapping/create-or-edit-mapping.tsx +++ b/keep-ui/app/(keep)/mapping/create-or-edit-mapping.tsx @@ -32,7 +32,7 @@ import { useApiUrl } from "utils/hooks/useConfig"; import { useMappings } from "utils/hooks/useMappingRules"; import { MappingRule } from "./models"; import { CreateableSearchSelect } from "@/components/ui/CreateableSearchSelect"; -import { useTopology } from "@/app/topology/model"; +import { useTopology } from "@/app/(keep)/topology/model"; interface Props { editRule: MappingRule | null; diff --git a/keep-ui/app/mapping/layout.tsx b/keep-ui/app/(keep)/mapping/layout.tsx similarity index 100% rename from keep-ui/app/mapping/layout.tsx rename to keep-ui/app/(keep)/mapping/layout.tsx diff --git a/keep-ui/app/mapping/mapping.tsx b/keep-ui/app/(keep)/mapping/mapping.tsx similarity index 97% rename from keep-ui/app/mapping/mapping.tsx rename to keep-ui/app/(keep)/mapping/mapping.tsx index dadb1509b..d38eec395 100644 --- a/keep-ui/app/mapping/mapping.tsx +++ b/keep-ui/app/(keep)/mapping/mapping.tsx @@ -4,7 +4,7 @@ import CreateOrEditMapping from "./create-or-edit-mapping"; import { useMappings } from "utils/hooks/useMappingRules"; import RulesTable from "./rules-table"; import { MdWarning } from "react-icons/md"; -import Loading from "app/loading"; +import Loading from "@/app/(keep)/loading"; import { MappingRule } from "./models"; import { useState } from "react"; diff --git a/keep-ui/app/mapping/models.tsx b/keep-ui/app/(keep)/mapping/models.tsx similarity index 100% rename from keep-ui/app/mapping/models.tsx rename to keep-ui/app/(keep)/mapping/models.tsx diff --git a/keep-ui/app/mapping/page.tsx b/keep-ui/app/(keep)/mapping/page.tsx similarity index 100% rename from keep-ui/app/mapping/page.tsx rename to keep-ui/app/(keep)/mapping/page.tsx diff --git a/keep-ui/app/mapping/rules-table.tsx b/keep-ui/app/(keep)/mapping/rules-table.tsx similarity index 100% rename from keep-ui/app/mapping/rules-table.tsx rename to keep-ui/app/(keep)/mapping/rules-table.tsx diff --git a/keep-ui/app/not-found.tsx b/keep-ui/app/(keep)/not-found.tsx similarity index 100% rename from keep-ui/app/not-found.tsx rename to keep-ui/app/(keep)/not-found.tsx diff --git a/keep-ui/app/notifications-hub/layout.tsx b/keep-ui/app/(keep)/notifications-hub/layout.tsx similarity index 100% rename from keep-ui/app/notifications-hub/layout.tsx rename to keep-ui/app/(keep)/notifications-hub/layout.tsx diff --git a/keep-ui/app/notifications-hub/page.tsx b/keep-ui/app/(keep)/notifications-hub/page.tsx similarity index 100% rename from keep-ui/app/notifications-hub/page.tsx rename to keep-ui/app/(keep)/notifications-hub/page.tsx diff --git a/keep-ui/app/page.tsx b/keep-ui/app/(keep)/page.tsx similarity index 100% rename from keep-ui/app/page.tsx rename to keep-ui/app/(keep)/page.tsx diff --git a/keep-ui/app/(keep)/providers/components/providers-filter-by-label/index.ts b/keep-ui/app/(keep)/providers/components/providers-filter-by-label/index.ts new file mode 100644 index 000000000..03e00556e --- /dev/null +++ b/keep-ui/app/(keep)/providers/components/providers-filter-by-label/index.ts @@ -0,0 +1 @@ +export { ProvidersFilterByLabel } from "./providers-filter-by-label"; diff --git a/keep-ui/app/providers/components/providers-filter-by-label/providers-filter-by-label.tsx b/keep-ui/app/(keep)/providers/components/providers-filter-by-label/providers-filter-by-label.tsx similarity index 73% rename from keep-ui/app/providers/components/providers-filter-by-label/providers-filter-by-label.tsx rename to keep-ui/app/(keep)/providers/components/providers-filter-by-label/providers-filter-by-label.tsx index 4a2d2edd6..09f696c8e 100644 --- a/keep-ui/app/providers/components/providers-filter-by-label/providers-filter-by-label.tsx +++ b/keep-ui/app/(keep)/providers/components/providers-filter-by-label/providers-filter-by-label.tsx @@ -5,11 +5,12 @@ import { useFilterContext, PROVIDER_LABELS } from "../../filter-context"; import type { TProviderLabels } from "../../providers"; export const ProvidersFilterByLabel: FC = (props) => { - const { setProvidersSelectedTags, providersSelectedTags } = useFilterContext(); + const { setProvidersSelectedTags, providersSelectedTags } = + useFilterContext(); const headerSelect = (value: string[]) => { - setProvidersSelectedTags(value as TProviderLabels[]) - } + setProvidersSelectedTags(value as TProviderLabels[]); + }; const options = Object.entries(PROVIDER_LABELS); @@ -22,8 +23,10 @@ export const ProvidersFilterByLabel: FC = (props) => { icon={TagIcon} > {options.map(([value, label]) => ( - {label} + + {label} + ))} - ) -} \ No newline at end of file + ); +}; diff --git a/keep-ui/app/(keep)/providers/components/providers-search/index.ts b/keep-ui/app/(keep)/providers/components/providers-search/index.ts new file mode 100644 index 000000000..8abce6070 --- /dev/null +++ b/keep-ui/app/(keep)/providers/components/providers-search/index.ts @@ -0,0 +1 @@ +export { ProvidersSearch } from "./providers-search"; diff --git a/keep-ui/app/providers/components/providers-search/providers-search.tsx b/keep-ui/app/(keep)/providers/components/providers-search/providers-search.tsx similarity index 76% rename from keep-ui/app/providers/components/providers-search/providers-search.tsx rename to keep-ui/app/(keep)/providers/components/providers-search/providers-search.tsx index 680a65896..5d4965038 100644 --- a/keep-ui/app/providers/components/providers-search/providers-search.tsx +++ b/keep-ui/app/(keep)/providers/components/providers-search/providers-search.tsx @@ -1,14 +1,15 @@ import { FC, ChangeEvent } from "react"; import { TextInput } from "@tremor/react"; import { MagnifyingGlassIcon } from "@heroicons/react/20/solid"; -import {useFilterContext} from "../../filter-context"; +import { useFilterContext } from "../../filter-context"; export const ProvidersSearch: FC = () => { - const { providersSearchString, setProvidersSearchString } = useFilterContext(); + const { providersSearchString, setProvidersSearchString } = + useFilterContext(); const handleChange = (e: ChangeEvent) => { setProvidersSearchString(e.target.value); - } + }; return ( { value={providersSearchString} onChange={handleChange} /> - ) -} \ No newline at end of file + ); +}; diff --git a/keep-ui/app/providers/context.ts b/keep-ui/app/(keep)/providers/context.ts similarity index 100% rename from keep-ui/app/providers/context.ts rename to keep-ui/app/(keep)/providers/context.ts diff --git a/keep-ui/app/providers/datadog-alert.tsx b/keep-ui/app/(keep)/providers/datadog-alert.tsx similarity index 97% rename from keep-ui/app/providers/datadog-alert.tsx rename to keep-ui/app/(keep)/providers/datadog-alert.tsx index 862f6f1ee..925e25842 100644 --- a/keep-ui/app/providers/datadog-alert.tsx +++ b/keep-ui/app/(keep)/providers/datadog-alert.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React from "react"; type DatadogAlertProps = { alert: { diff --git a/keep-ui/app/providers/filter-context/constants.ts b/keep-ui/app/(keep)/providers/filter-context/constants.ts similarity index 53% rename from keep-ui/app/providers/filter-context/constants.ts rename to keep-ui/app/(keep)/providers/filter-context/constants.ts index f6d967eb9..c8f4b5203 100644 --- a/keep-ui/app/providers/filter-context/constants.ts +++ b/keep-ui/app/(keep)/providers/filter-context/constants.ts @@ -1,13 +1,13 @@ import { TProviderLabels } from "../providers"; export const PROVIDER_LABELS: Record = { - alert: 'Alert', - topology: 'Topology', - messaging: 'Messaging', - ticketing: 'Ticketing', - data: 'Data', - queue: 'Queue', - incident: 'Incident' -} + alert: "Alert", + topology: "Topology", + messaging: "Messaging", + ticketing: "Ticketing", + data: "Data", + queue: "Queue", + incident: "Incident", +}; -export const PROVIDER_LABELS_KEYS = Object.keys(PROVIDER_LABELS); \ No newline at end of file +export const PROVIDER_LABELS_KEYS = Object.keys(PROVIDER_LABELS); diff --git a/keep-ui/app/providers/filter-context/filter-context.tsx b/keep-ui/app/(keep)/providers/filter-context/filter-context.tsx similarity index 68% rename from keep-ui/app/providers/filter-context/filter-context.tsx rename to keep-ui/app/(keep)/providers/filter-context/filter-context.tsx index 9886587d9..1e2866721 100644 --- a/keep-ui/app/providers/filter-context/filter-context.tsx +++ b/keep-ui/app/(keep)/providers/filter-context/filter-context.tsx @@ -9,11 +9,16 @@ export const FilterContext = createContext(null); export const FilerContextProvider: FC = ({ children }) => { const searchParams = useSearchParams(); - const [providersSearchString, setProvidersSearchString] = useState(""); + const [providersSearchString, setProvidersSearchString] = + useState(""); - const [providersSelectedTags, setProvidersSelectedTags] = useState(() => { + const [providersSelectedTags, setProvidersSelectedTags] = useState< + TProviderLabels[] + >(() => { const labels = searchParams?.get("labels"); - const labelArray = labels?.split(',').filter(label => PROVIDER_LABELS_KEYS.includes(label)); + const labelArray = labels + ?.split(",") + .filter((label) => PROVIDER_LABELS_KEYS.includes(label)); return (labelArray || []) as TProviderLabels[]; }); @@ -23,7 +28,11 @@ export const FilerContextProvider: FC = ({ children }) => { providersSelectedTags, setProvidersSelectedTags, setProvidersSearchString, - } + }; - return {children} -} \ No newline at end of file + return ( + + {children} + + ); +}; diff --git a/keep-ui/app/(keep)/providers/filter-context/index.ts b/keep-ui/app/(keep)/providers/filter-context/index.ts new file mode 100644 index 000000000..49e19f78e --- /dev/null +++ b/keep-ui/app/(keep)/providers/filter-context/index.ts @@ -0,0 +1,3 @@ +export { FilerContextProvider } from "./filter-context"; +export { useFilterContext } from "./use-filter-context"; +export * from "./constants"; diff --git a/keep-ui/app/providers/filter-context/types.ts b/keep-ui/app/(keep)/providers/filter-context/types.ts similarity index 86% rename from keep-ui/app/providers/filter-context/types.ts rename to keep-ui/app/(keep)/providers/filter-context/types.ts index d77c6dc11..c56f163b6 100644 --- a/keep-ui/app/providers/filter-context/types.ts +++ b/keep-ui/app/(keep)/providers/filter-context/types.ts @@ -3,7 +3,7 @@ import { TProviderLabels } from "../providers"; export interface IFilterContext { providersSearchString: string; - providersSelectedTags: TProviderLabels[], + providersSelectedTags: TProviderLabels[]; setProvidersSearchString: Dispatch>; setProvidersSelectedTags: Dispatch>; -} \ No newline at end of file +} diff --git a/keep-ui/app/(keep)/providers/filter-context/use-filter-context.ts b/keep-ui/app/(keep)/providers/filter-context/use-filter-context.ts new file mode 100644 index 000000000..df2c0925a --- /dev/null +++ b/keep-ui/app/(keep)/providers/filter-context/use-filter-context.ts @@ -0,0 +1,15 @@ +import { useContext } from "react"; +import { FilterContext } from "./filter-context"; +import { IFilterContext } from "./types"; + +export const useFilterContext = (): IFilterContext => { + const filterContext = useContext(FilterContext); + + if (!filterContext) { + throw new ReferenceError( + "Usage of useFilterContext outside of FilterContext provider is forbidden" + ); + } + + return filterContext; +}; diff --git a/keep-ui/app/providers/grafana-alert.tsx b/keep-ui/app/(keep)/providers/grafana-alert.tsx similarity index 97% rename from keep-ui/app/providers/grafana-alert.tsx rename to keep-ui/app/(keep)/providers/grafana-alert.tsx index b4be3f9bb..a8cf16d29 100644 --- a/keep-ui/app/providers/grafana-alert.tsx +++ b/keep-ui/app/(keep)/providers/grafana-alert.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React from "react"; type GrafanaAlertProps = { alert: { diff --git a/keep-ui/app/providers/layout.tsx b/keep-ui/app/(keep)/providers/layout.tsx similarity index 100% rename from keep-ui/app/providers/layout.tsx rename to keep-ui/app/(keep)/providers/layout.tsx diff --git a/keep-ui/app/providers/oauth2/[providerType]/page.tsx b/keep-ui/app/(keep)/providers/oauth2/[providerType]/page.tsx similarity index 89% rename from keep-ui/app/providers/oauth2/[providerType]/page.tsx rename to keep-ui/app/(keep)/providers/oauth2/[providerType]/page.tsx index d980e37d5..52f504f63 100644 --- a/keep-ui/app/providers/oauth2/[providerType]/page.tsx +++ b/keep-ui/app/(keep)/providers/oauth2/[providerType]/page.tsx @@ -1,5 +1,4 @@ -import { getServerSession } from "next-auth/next"; -import { authOptions } from "pages/api/auth/[...nextauth]"; +import { auth } from "@/auth"; import { getApiURL } from "@/utils/apiUrl"; import { redirect } from "next/navigation"; import { cookies } from "next/headers"; @@ -11,7 +10,7 @@ export default async function InstallFromOAuth({ params: { providerType: string }; searchParams: { [key: string]: string }; }) { - const accessToken = await getServerSession(authOptions); + const accessToken = await auth(); // this is server so we can use the old getApiURL const apiUrl = getApiURL(); const cookieStore = cookies(); diff --git a/keep-ui/app/providers/page.client.tsx b/keep-ui/app/(keep)/providers/page.client.tsx similarity index 99% rename from keep-ui/app/providers/page.client.tsx rename to keep-ui/app/(keep)/providers/page.client.tsx index aac212780..fd49f8ff9 100644 --- a/keep-ui/app/providers/page.client.tsx +++ b/keep-ui/app/(keep)/providers/page.client.tsx @@ -5,7 +5,7 @@ import { KeepApiError } from "@/shared/lib/KeepApiError"; import { useApiUrl } from "utils/hooks/useConfig"; import ProvidersTiles from "./providers-tiles"; import React, { useState, useEffect } from "react"; -import Loading from "../loading"; +import Loading from "@/app/(keep)/loading"; import { useFilterContext } from "./filter-context"; import { toast } from "react-toastify"; import { useRouter } from "next/navigation"; diff --git a/keep-ui/app/providers/page.tsx b/keep-ui/app/(keep)/providers/page.tsx similarity index 100% rename from keep-ui/app/providers/page.tsx rename to keep-ui/app/(keep)/providers/page.tsx diff --git a/keep-ui/app/providers/provider-form-scopes.css b/keep-ui/app/(keep)/providers/provider-form-scopes.css similarity index 100% rename from keep-ui/app/providers/provider-form-scopes.css rename to keep-ui/app/(keep)/providers/provider-form-scopes.css diff --git a/keep-ui/app/providers/provider-form-scopes.tsx b/keep-ui/app/(keep)/providers/provider-form-scopes.tsx similarity index 92% rename from keep-ui/app/providers/provider-form-scopes.tsx rename to keep-ui/app/(keep)/providers/provider-form-scopes.tsx index 6820872b4..8457eec0d 100644 --- a/keep-ui/app/providers/provider-form-scopes.tsx +++ b/keep-ui/app/(keep)/providers/provider-form-scopes.tsx @@ -63,11 +63,13 @@ const ProviderFormScopes = ({ { // provider.scopes! is because we validates scopes exists in the parent component provider.scopes!.map((scope) => { - let isScopeString = typeof validatedScopes[scope.name] === 'string'; + let isScopeString = + typeof validatedScopes[scope.name] === "string"; let isScopeLong = false; if (isScopeString) { - isScopeLong = validatedScopes[scope.name].toString().length > 100; + isScopeLong = + validatedScopes[scope.name].toString().length > 100; } return ( @@ -89,7 +91,9 @@ const ProviderFormScopes = ({ ? "gray" : "red" // scope was tested and is a string, meaning it has an error } - className={`truncate ${isScopeLong? 'max-w-lg' : 'max-w-xs' }`} + className={`truncate ${ + isScopeLong ? "max-w-lg" : "max-w-xs" + }`} > {validatedScopes[scope.name] === true ? "Valid" diff --git a/keep-ui/app/providers/provider-form.css b/keep-ui/app/(keep)/providers/provider-form.css similarity index 100% rename from keep-ui/app/providers/provider-form.css rename to keep-ui/app/(keep)/providers/provider-form.css diff --git a/keep-ui/app/providers/provider-form.tsx b/keep-ui/app/(keep)/providers/provider-form.tsx similarity index 99% rename from keep-ui/app/providers/provider-form.tsx rename to keep-ui/app/(keep)/providers/provider-form.tsx index 5d59cf35a..7e58e8a25 100644 --- a/keep-ui/app/providers/provider-form.tsx +++ b/keep-ui/app/(keep)/providers/provider-form.tsx @@ -43,7 +43,7 @@ import { PlusIcon, TrashIcon, } from "@heroicons/react/24/outline"; -import { installWebhook } from "../../utils/helpers"; +import { installWebhook } from "../../../utils/helpers"; import { ProviderSemiAutomated } from "./provider-semi-automated"; import ProviderFormScopes from "./provider-form-scopes"; import Link from "next/link"; diff --git a/keep-ui/app/providers/provider-row.css b/keep-ui/app/(keep)/providers/provider-row.css similarity index 100% rename from keep-ui/app/providers/provider-row.css rename to keep-ui/app/(keep)/providers/provider-row.css diff --git a/keep-ui/app/providers/provider-row.tsx b/keep-ui/app/(keep)/providers/provider-row.tsx similarity index 85% rename from keep-ui/app/providers/provider-row.tsx rename to keep-ui/app/(keep)/providers/provider-row.tsx index 396439730..d5298dcdf 100644 --- a/keep-ui/app/providers/provider-row.tsx +++ b/keep-ui/app/(keep)/providers/provider-row.tsx @@ -1,4 +1,4 @@ -'use client'; +"use client"; import React, { useState, useEffect } from "react"; import { TableRow, TableCell } from "@tremor/react"; import Image from "next/image"; @@ -6,7 +6,6 @@ import "./provider-row.css"; import { Provider } from "./providers"; const ProviderRow = ({ provider }: { provider: Provider }) => { - return ( <> @@ -27,11 +26,13 @@ const ProviderRow = ({ provider }: { provider: Provider }) => {
    {provider.details.name!}
    - {Object.entries(provider.details.authentication).map(([key, value]) => ( -
    - {key}: {value} -
    - ))} + {Object.entries(provider.details.authentication).map( + ([key, value]) => ( +
    + {key}: {value} +
    + ) + )}
    diff --git a/keep-ui/app/providers/provider-semi-automated.tsx b/keep-ui/app/(keep)/providers/provider-semi-automated.tsx similarity index 100% rename from keep-ui/app/providers/provider-semi-automated.tsx rename to keep-ui/app/(keep)/providers/provider-semi-automated.tsx diff --git a/keep-ui/app/providers/provider-tile.css b/keep-ui/app/(keep)/providers/provider-tile.css similarity index 100% rename from keep-ui/app/providers/provider-tile.css rename to keep-ui/app/(keep)/providers/provider-tile.css diff --git a/keep-ui/app/providers/provider-tile.tsx b/keep-ui/app/(keep)/providers/provider-tile.tsx similarity index 100% rename from keep-ui/app/providers/provider-tile.tsx rename to keep-ui/app/(keep)/providers/provider-tile.tsx diff --git a/keep-ui/app/providers/providers-tiles.tsx b/keep-ui/app/(keep)/providers/providers-tiles.tsx similarity index 100% rename from keep-ui/app/providers/providers-tiles.tsx rename to keep-ui/app/(keep)/providers/providers-tiles.tsx diff --git a/keep-ui/app/providers/providers.css b/keep-ui/app/(keep)/providers/providers.css similarity index 100% rename from keep-ui/app/providers/providers.css rename to keep-ui/app/(keep)/providers/providers.css diff --git a/keep-ui/app/providers/providers.tsx b/keep-ui/app/(keep)/providers/providers.tsx similarity index 100% rename from keep-ui/app/providers/providers.tsx rename to keep-ui/app/(keep)/providers/providers.tsx diff --git a/keep-ui/app/providers/table.tsx b/keep-ui/app/(keep)/providers/table.tsx similarity index 58% rename from keep-ui/app/providers/table.tsx rename to keep-ui/app/(keep)/providers/table.tsx index 6f13f50d9..44d137344 100644 --- a/keep-ui/app/providers/table.tsx +++ b/keep-ui/app/(keep)/providers/table.tsx @@ -3,22 +3,19 @@ import { Table } from "@tremor/react"; import ProviderRow from "./provider-row"; import { Providers } from "./providers"; - export default function ProvidersTable({ providers, }: { providers: Providers; }) { - return ( - { - providers.filter((provider) => Object.keys(provider.details).length > 0) - .map((provider) => ( - - )) - } + {providers + .filter((provider) => Object.keys(provider.details).length > 0) + .map((provider) => ( + + ))}
    ); diff --git a/keep-ui/app/rules/CorrelationPlaceholder.tsx b/keep-ui/app/(keep)/rules/CorrelationPlaceholder.tsx similarity index 100% rename from keep-ui/app/rules/CorrelationPlaceholder.tsx rename to keep-ui/app/(keep)/rules/CorrelationPlaceholder.tsx diff --git a/keep-ui/app/rules/CorrelationSidebar/AlertsFoundBadge.tsx b/keep-ui/app/(keep)/rules/CorrelationSidebar/AlertsFoundBadge.tsx similarity index 82% rename from keep-ui/app/rules/CorrelationSidebar/AlertsFoundBadge.tsx rename to keep-ui/app/(keep)/rules/CorrelationSidebar/AlertsFoundBadge.tsx index 407c56c54..49b5ad3a1 100644 --- a/keep-ui/app/rules/CorrelationSidebar/AlertsFoundBadge.tsx +++ b/keep-ui/app/(keep)/rules/CorrelationSidebar/AlertsFoundBadge.tsx @@ -1,6 +1,6 @@ import { Badge } from "@tremor/react"; import Image from "next/image"; -import { AlertDto } from "app/alerts/models"; +import { AlertDto } from "@/app/(keep)/alerts/models"; type AlertsFoundBadgeProps = { alertsFound: AlertDto[]; @@ -30,7 +30,11 @@ export const AlertsFoundBadge = ({ return ( - + {images.map((source, index) => ( } - {alertsFound.length} alert{alertsFound.length > 1 ? "s" : ""} were found{vertical &&
    }matching this condition + {alertsFound.length} alert{alertsFound.length > 1 ? "s" : ""} were + found{vertical &&
    }matching this condition
    diff --git a/keep-ui/app/rules/CorrelationSidebar/CorrelationForm.tsx b/keep-ui/app/(keep)/rules/CorrelationSidebar/CorrelationForm.tsx similarity index 99% rename from keep-ui/app/rules/CorrelationSidebar/CorrelationForm.tsx rename to keep-ui/app/(keep)/rules/CorrelationSidebar/CorrelationForm.tsx index ddb91d15a..9e46cff4d 100644 --- a/keep-ui/app/rules/CorrelationSidebar/CorrelationForm.tsx +++ b/keep-ui/app/(keep)/rules/CorrelationSidebar/CorrelationForm.tsx @@ -11,7 +11,7 @@ import { } from "@tremor/react"; import { Controller, get, useFormContext } from "react-hook-form"; import { CorrelationForm as CorrelationFormType } from "."; -import { AlertDto } from "app/alerts/models"; +import { AlertDto } from "@/app/(keep)/alerts/models"; import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline"; import React from "react"; diff --git a/keep-ui/app/rules/CorrelationSidebar/CorrelationGroups.tsx b/keep-ui/app/(keep)/rules/CorrelationSidebar/CorrelationGroups.tsx similarity index 96% rename from keep-ui/app/rules/CorrelationSidebar/CorrelationGroups.tsx rename to keep-ui/app/(keep)/rules/CorrelationSidebar/CorrelationGroups.tsx index 69c295e5d..cfc82cdd7 100644 --- a/keep-ui/app/rules/CorrelationSidebar/CorrelationGroups.tsx +++ b/keep-ui/app/(keep)/rules/CorrelationSidebar/CorrelationGroups.tsx @@ -19,7 +19,7 @@ export const CorrelationGroups = () => { className="cursor-default" type="button" tooltip="Any Rule consists of one or more Correlations. Each alert group is evaluated separately and the results are combined using AND combinator. - For example, if you want to group alerts that has a severity of 'critical' and another alert with a source of 'Kibana', you would create a rule with two alert groups. + For example, if you want to group alerts that has a severity of 'critical' and another alert with a source of 'Kibana', you would create a rule with two alert groups. The first alert group would have a rule with severity = 'critical' and the second alert group would have a rule with source = 'kibana'." icon={QuestionMarkCircleIcon} size="xs" diff --git a/keep-ui/app/rules/CorrelationSidebar/CorrelationSidebarBody.tsx b/keep-ui/app/(keep)/rules/CorrelationSidebar/CorrelationSidebarBody.tsx similarity index 100% rename from keep-ui/app/rules/CorrelationSidebar/CorrelationSidebarBody.tsx rename to keep-ui/app/(keep)/rules/CorrelationSidebar/CorrelationSidebarBody.tsx diff --git a/keep-ui/app/rules/CorrelationSidebar/CorrelationSidebarHeader.tsx b/keep-ui/app/(keep)/rules/CorrelationSidebar/CorrelationSidebarHeader.tsx similarity index 100% rename from keep-ui/app/rules/CorrelationSidebar/CorrelationSidebarHeader.tsx rename to keep-ui/app/(keep)/rules/CorrelationSidebar/CorrelationSidebarHeader.tsx diff --git a/keep-ui/app/rules/CorrelationSidebar/CorrelationSubmission.tsx b/keep-ui/app/(keep)/rules/CorrelationSidebar/CorrelationSubmission.tsx similarity index 95% rename from keep-ui/app/rules/CorrelationSidebar/CorrelationSubmission.tsx rename to keep-ui/app/(keep)/rules/CorrelationSidebar/CorrelationSubmission.tsx index 1c1605692..a707080a8 100644 --- a/keep-ui/app/rules/CorrelationSidebar/CorrelationSubmission.tsx +++ b/keep-ui/app/(keep)/rules/CorrelationSidebar/CorrelationSubmission.tsx @@ -3,7 +3,7 @@ import { useSearchParams } from "next/navigation"; import { useFormContext } from "react-hook-form"; import { CorrelationForm } from "."; import { AlertsFoundBadge } from "./AlertsFoundBadge"; -import { AlertDto } from "app/alerts/models"; +import { AlertDto } from "@/app/(keep)/alerts/models"; type CorrelationSubmissionProps = { toggle: VoidFunction; diff --git a/keep-ui/app/rules/CorrelationSidebar/DeleteRule.tsx b/keep-ui/app/(keep)/rules/CorrelationSidebar/DeleteRule.tsx similarity index 100% rename from keep-ui/app/rules/CorrelationSidebar/DeleteRule.tsx rename to keep-ui/app/(keep)/rules/CorrelationSidebar/DeleteRule.tsx diff --git a/keep-ui/app/rules/CorrelationSidebar/RuleFields.tsx b/keep-ui/app/(keep)/rules/CorrelationSidebar/RuleFields.tsx similarity index 97% rename from keep-ui/app/rules/CorrelationSidebar/RuleFields.tsx rename to keep-ui/app/(keep)/rules/CorrelationSidebar/RuleFields.tsx index 6c51072aa..af2d8cce3 100644 --- a/keep-ui/app/rules/CorrelationSidebar/RuleFields.tsx +++ b/keep-ui/app/(keep)/rules/CorrelationSidebar/RuleFields.tsx @@ -236,9 +236,9 @@ export const RuleFields = ({ }; const { watch } = useFormContext(); - const timeframeInSeconds = watch("timeUnit") ? TIMEFRAME_UNITS_TO_SECONDS[watch("timeUnit")]( - +watch("timeAmount") - ) : 0; + const timeframeInSeconds = watch("timeUnit") + ? TIMEFRAME_UNITS_TO_SECONDS[watch("timeUnit")](+watch("timeAmount")) + : 0; const { data: alertsFound = [], isLoading } = useSearchAlerts({ query: { combinator: "and", rules: ruleFields }, @@ -260,7 +260,9 @@ export const RuleFields = ({ onRemoveRuleFieldClick(ruleFieldIndex)} + onRemoveFieldClick={() => + onRemoveRuleFieldClick(ruleFieldIndex) + } // add the rule field as an available selection avaliableFields={availableFields.concat({ label: ruleField.field, diff --git a/keep-ui/app/rules/CorrelationSidebar/RuleGroup.tsx b/keep-ui/app/(keep)/rules/CorrelationSidebar/RuleGroup.tsx similarity index 100% rename from keep-ui/app/rules/CorrelationSidebar/RuleGroup.tsx rename to keep-ui/app/(keep)/rules/CorrelationSidebar/RuleGroup.tsx diff --git a/keep-ui/app/rules/CorrelationSidebar/index.tsx b/keep-ui/app/(keep)/rules/CorrelationSidebar/index.tsx similarity index 100% rename from keep-ui/app/rules/CorrelationSidebar/index.tsx rename to keep-ui/app/(keep)/rules/CorrelationSidebar/index.tsx diff --git a/keep-ui/app/rules/CorrelationTable.tsx b/keep-ui/app/(keep)/rules/CorrelationTable.tsx similarity index 90% rename from keep-ui/app/rules/CorrelationTable.tsx rename to keep-ui/app/(keep)/rules/CorrelationTable.tsx index b3dfb81d0..90f33d0e9 100644 --- a/keep-ui/app/rules/CorrelationTable.tsx +++ b/keep-ui/app/(keep)/rules/CorrelationTable.tsx @@ -1,7 +1,8 @@ import { Badge, Button, - Card, Icon, + Card, + Icon, Subtitle, Table, TableBody, @@ -28,14 +29,13 @@ import { DefaultRuleGroupType, parseCEL } from "react-querybuilder"; import { useRouter, useSearchParams } from "next/navigation"; import { FormattedQueryCell } from "./FormattedQueryCell"; import { DeleteRuleCell } from "./CorrelationSidebar/DeleteRule"; -import {PlusIcon} from "@radix-ui/react-icons"; +import { PlusIcon } from "@radix-ui/react-icons"; - -const TIMEFRAME_UNITS_FROM_SECONDS= { +const TIMEFRAME_UNITS_FROM_SECONDS = { seconds: (amount: number) => amount, minutes: (amount: number) => amount / 60, hours: (amount: number) => amount / 3600, - days: (amount: number) => amount / 86400, + days: (amount: number) => amount / 86400, } as const; const columnHelper = createColumnHelper(); @@ -72,7 +72,9 @@ export const CorrelationTable = ({ rules }: CorrelationTableProps) => { return { name: selectedRule.name, description: selectedRule.group_description ?? "", - timeAmount: TIMEFRAME_UNITS_FROM_SECONDS[timeunit](selectedRule.timeframe), + timeAmount: TIMEFRAME_UNITS_FROM_SECONDS[timeunit]( + selectedRule.timeframe + ), timeUnit: timeunit, groupedAttributes: selectedRule.grouping_criteria, requireApprove: selectedRule.require_approve, @@ -117,22 +119,21 @@ export const CorrelationTable = ({ rules }: CorrelationTableProps) => { }), columnHelper.accessor("grouping_criteria", { header: "Grouped by", - cell: (context) => ( - context.getValue().map((group, index) => + cell: (context) => + context.getValue().map((group, index) => ( <> - {group} + + {group} + {context.getValue().length !== index + 1 && ( - - )} + + )} - ) - ), + )), }), columnHelper.accessor("incidents", { header: "Incidents", - cell: (context) => ( - context.getValue() - ), + cell: (context) => context.getValue(), }), columnHelper.display({ id: "menu", diff --git a/keep-ui/app/rules/FormattedQueryCell.tsx b/keep-ui/app/(keep)/rules/FormattedQueryCell.tsx similarity index 100% rename from keep-ui/app/rules/FormattedQueryCell.tsx rename to keep-ui/app/(keep)/rules/FormattedQueryCell.tsx diff --git a/keep-ui/app/rules/client.tsx b/keep-ui/app/(keep)/rules/client.tsx similarity index 90% rename from keep-ui/app/rules/client.tsx rename to keep-ui/app/(keep)/rules/client.tsx index ab52b1cc3..a63fcda66 100644 --- a/keep-ui/app/rules/client.tsx +++ b/keep-ui/app/(keep)/rules/client.tsx @@ -3,7 +3,7 @@ import { useRules } from "utils/hooks/useRules"; import { CorrelationPlaceholder } from "./CorrelationPlaceholder"; import { CorrelationTable } from "./CorrelationTable"; -import Loading from "app/loading"; +import Loading from "@/app/(keep)/loading"; export const Client = () => { const { data: rules = [], isLoading } = useRules(); diff --git a/keep-ui/app/rules/page.tsx b/keep-ui/app/(keep)/rules/page.tsx similarity index 100% rename from keep-ui/app/rules/page.tsx rename to keep-ui/app/(keep)/rules/page.tsx diff --git a/keep-ui/app/rules/ui/PlaceholderSankey.tsx b/keep-ui/app/(keep)/rules/ui/PlaceholderSankey.tsx similarity index 100% rename from keep-ui/app/rules/ui/PlaceholderSankey.tsx rename to keep-ui/app/(keep)/rules/ui/PlaceholderSankey.tsx diff --git a/keep-ui/app/settings/auth/api-key-settings.tsx b/keep-ui/app/(keep)/settings/auth/api-key-settings.tsx similarity index 96% rename from keep-ui/app/settings/auth/api-key-settings.tsx rename to keep-ui/app/(keep)/settings/auth/api-key-settings.tsx index aa8e5621f..51d98fd18 100644 --- a/keep-ui/app/settings/auth/api-key-settings.tsx +++ b/keep-ui/app/(keep)/settings/auth/api-key-settings.tsx @@ -12,14 +12,14 @@ import { Text, Badge, } from "@tremor/react"; -import Loading from "app/loading"; +import Loading from "@/app/(keep)/loading"; import { CopyBlock, a11yLight } from "react-code-blocks"; import useSWR from "swr"; import { useApiUrl } from "utils/hooks/useConfig"; import { KeyIcon, TrashIcon } from "@heroicons/react/24/outline"; import { fetcher } from "utils/fetcher"; import { useState } from "react"; -import { AuthenticationType } from "utils/authenticationType"; +import { AuthType } from "utils/authenticationType"; import CreateApiKeyModal from "../create-api-key-modal"; import { useRoles } from "utils/hooks/useRoles"; import { getSession } from "next-auth/react"; @@ -80,8 +80,8 @@ export default function ApiKeySettings({ accessToken, selectedTab }: Props) { showLineNumbers: false, }); - const authType = configData?.AUTH_TYPE as AuthenticationType; - const createApiKeyEnabled = authType !== AuthenticationType.NOAUTH; + const authType = configData?.AUTH_TYPE as AuthType; + const createApiKeyEnabled = authType !== AuthType.NOAUTH; const handleRegenerate = async ( apiKeyId: string, diff --git a/keep-ui/app/settings/auth/api-key-tab.tsx b/keep-ui/app/(keep)/settings/auth/api-key-tab.tsx similarity index 100% rename from keep-ui/app/settings/auth/api-key-tab.tsx rename to keep-ui/app/(keep)/settings/auth/api-key-tab.tsx diff --git a/keep-ui/app/settings/auth/api-key-table.tsx b/keep-ui/app/(keep)/settings/auth/api-key-table.tsx similarity index 94% rename from keep-ui/app/settings/auth/api-key-table.tsx rename to keep-ui/app/(keep)/settings/auth/api-key-table.tsx index 8fc6f63ad..fe4bd01df 100644 --- a/keep-ui/app/settings/auth/api-key-table.tsx +++ b/keep-ui/app/(keep)/settings/auth/api-key-table.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React from "react"; import { Table, TableBody, @@ -34,7 +34,7 @@ export function APIKeysTable({ apiKeys, onRegenerate, onDelete, - isDisabled = false + isDisabled = false, }: APIKeysTableProps) { const getCopyBlockProps = (secret: string) => ({ theme: { ...a11yLight }, @@ -86,7 +86,9 @@ export function APIKeysTable({ icon={UpdateIcon} variant="light" color="orange" - onClick={(e) => !isDisabled && onRegenerate(key.reference_id, e)} + onClick={(e) => + !isDisabled && onRegenerate(key.reference_id, e) + } disabled={isDisabled} />