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-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 && ( 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) 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"