Skip to content

Commit

Permalink
Merge pull request #88 from 4urcloud/dev-esteban-reworkTeamsNotification
Browse files Browse the repository at this point in the history
reworked all teams notifications
  • Loading branch information
estebanmathia authored Dec 7, 2023
2 parents 97486f8 + 533fbb3 commit fd4abd2
Show file tree
Hide file tree
Showing 8 changed files with 445 additions and 26 deletions.
55 changes: 55 additions & 0 deletions Kexa/emails/teams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const levelAlert = ["info", "warning", "error", "fatal"];
export const Teams = {
//https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using?tabs=cURL
OneTeams: (color:string, subject:string, url:string, description:string) => {
return JSON.stringify({
"@type": "MessageCard",
"@context": "http://schema.org/extensions",
"themeColor": color,
"summary": subject,
"sections": [
{
"activityTitle": subject,
"activitySubtitle": description,
"activityImage": "https://kexa.io/kexa-no-background-color.png",
"markdown": true
}
],
"potentialAction": [
{
"@type": "OpenUri",
"name": "Go to ressource",
"targets": [
{
"os": "default",
"uri": url
}
]
}
]
});
},
GlobalTeams: (color:string, subject:string, text:string, errors:{ [x: string]: number; }) => {
return JSON.stringify({
"@type": "MessageCard",
"@context": "http://schema.org/extensions",
"themeColor": color,
"summary": subject,
"sections": [
{
"activityTitle": subject,
"activitySubtitle": "Kexa by 4urcloud",
"activityImage": "https://kexa.io/kexa-no-background-color.png",
"text": text,
"facts": Object.entries(errors).map(([name, value]) => {
return {
"name": name,
"value": value.toString()
};
}),
"markdown": true
}
]
});
},
};
34 changes: 34 additions & 0 deletions Kexa/helpers/extractURL.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const jsdom = require("jsdom")
const { JSDOM } = jsdom
global.DOMParser = new JSDOM().window.DOMParser

export const extractURL = (text: string): string | null => {
return extractFirstURLFromHTML(text)??extractFirstURL(text);
}

function extractFirstURLFromHTML(html: string): string | null {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');

const urlElements = Array.from(doc.querySelectorAll('a[href], img[src]'));

for (const element of urlElements) {
const url = (element as HTMLAnchorElement).href || (element as HTMLImageElement).src;
if (url) {
return url;
}
}

return null;
}

function extractFirstURL(input:string): string | null {
const urlRegex = /(https?:\/\/[^\s]+)/g;
const matches = input.match(urlRegex);

if (matches && matches.length > 0) {
return matches[0];
}

return null;
}
21 changes: 21 additions & 0 deletions Kexa/helpers/spliter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export const splitProperty = (prop: string, delimiter:string, ignore:string="/"):string[] => {
const result = [];
let current = "";
let escape = false;

for (const char of prop) {
if (char === delimiter && !escape) {
result.push(current);
current = "";
} else if (char === ignore && !escape) {
escape = true;
} else {
if(escape && char !== delimiter) current += ignore;
current += char;
escape = false;
}
}

result.push(current);
return result;
}
54 changes: 33 additions & 21 deletions Kexa/services/alerte.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ import { Readable } from "stream";
import { propertyToSend, renderTableAllScan, renderTableAllScanLoud } from "./display.service";
import { groupBy } from "../helpers/groupBy";
import { getConfigOrEnvVar } from "./manageVarEnvironnement.service";
import axios from 'axios';
import { extractURL } from "../helpers/extractURL";
import { Teams } from "../emails/teams";

const jsome = require('jsome');
jsome.level.show = true;
const request = require('request');
const nodemailer = require("nodemailer");
const levelAlert = ["info", "warning", "error", "critical"];
const levelAlert = ["info", "warning", "error", "fatal"];
const colors = ["#4f5660", "#ffcc00", "#cc3300", "#cc3300"];
const config = require('node-config-ts');

Expand Down Expand Up @@ -164,20 +167,24 @@ export function alertWebhookGlobal(alert: GlobalConfigAlert, compteError: number

export function alertTeamsGlobal(alert: GlobalConfigAlert, compteError: number[], allScan: ResultScan[][]) {
logger.debug("alert Teams Global");
let content = compteRender(allScan);
let nbrError: { [x: string]: number; }[] = [];
let nbrError: { [x: string]: number; } = {};
compteError.forEach((value, index) => {
nbrError.push({
[levelAlert[index]] : value
});
nbrError[levelAlert[index]] = value;
});
content["nbrError"] = nbrError;
content["title"] = "Kexa - Global Alert - "+(alert.name??"Uname");
let content = ""
let render_table = renderTableAllScan(allScan.map(scan => scan.filter(value => value.error.length>0)));
let render_table_loud = renderTableAllScanLoud(allScan.map(scan => scan.filter(value => value.loud)));
content += render_table;
if(render_table_loud.length > 30){
content += "\n\n\n<h3>Loud Section:</h3>\n"
content += render_table_loud;
}
for (const teams_to of alert.to) {
const regex = /^https:\/\/(?:[a-zA-Z0-9_-]+\.)?webhook\.office\.com\/[^\s"]+$/;
if(regex.test(teams_to)) return;
if(!regex.test(teams_to)) return;
logger.debug("send teams to:"+teams_to);
sendCardMessageToTeamsChannel(teams_to, "Kexa - Global Alert - "+ (alert.name??"Uname"), content);
const payload = Teams.GlobalTeams(colors[0], "Global Alert - "+(alert.name??"Uname"), content, nbrError);
sendCardMessageToTeamsChannel(teams_to, payload);
}
}

Expand Down Expand Up @@ -307,11 +314,13 @@ export function alertTeams(detailAlert: ConfigAlert|GlobalConfigAlert ,rule: Rul
logger.debug("alert Teams");
for (const teams_to of detailAlert.to) {
const regex = /^https:\/\/(?:[a-zA-Z0-9_-]+\.)?webhook\.office\.com\/[^\s"]+$/;
if(regex.test(teams_to)) return;
if(!regex.test(teams_to)) return;
let content = propertyToSend(rule, objectResource);
sendCardMessageToTeamsChannel(teams_to, "Kexa - "+levelAlert[rule.level]+" - "+rule.name, content);
const payload = Teams.OneTeams(colors[rule.level], "Kexa - "+levelAlert[rule.level]+" - "+rule.name, extractURL(content)??"", rule.description??"");
sendCardMessageToTeamsChannel(teams_to, payload);
}
}

export function alertEmail(detailAlert: ConfigAlert|GlobalConfigAlert ,rule: Rules, conditions:SubResultScan[], objectResource:any){
logger.debug("alert email");
detailAlert.to.forEach((email_to) => {
Expand Down Expand Up @@ -414,7 +423,7 @@ async function sendWebhook(alert: ConfigAlert, subject: string, content: any) {
for (const webhook_to of alert.to) {
if(!webhook_to.includes("http")) continue;
const payload = {
title: "Kexa scan : ",
title: "Kexa scan : " + subject,
text: content.content,
};
try {
Expand All @@ -426,25 +435,28 @@ async function sendWebhook(alert: ConfigAlert, subject: string, content: any) {
logger.error('Failed to send Webhook.');
}
} catch (error) {
logger.error('Teams webhook : An error occurred:', error);
logger.error('Webhook : An error occurred:', error);
}
}
}

import axios from 'axios';

export async function sendCardMessageToTeamsChannel(channelWebhook: string, subject: string, content: any): Promise<void> {
export async function sendCardMessageToTeamsChannel(channelWebhook: string, payload:string): Promise<void> {
const context = getContext();
if (!channelWebhook) {
logger.error("Cannot retrieve TEAMS_CHANNEL_WEBHOOK_URL from env");
throw("Error on TEAMS_CHANNEL_WEBHOOK_URL retrieve");
}
const payload = {
title: subject,
text: content,
let config = {
method: 'post',
maxBodyLength: Infinity,
url: channelWebhook,
headers: {
'Content-Type': 'application/json'
},
data : payload
};
try {
const response = await axios.post(channelWebhook, payload);
const response = await axios.request(config);
if (response.status === 200) {
context?.log('Card sent successfully!');
logger.info('Card sent successfully!');
Expand Down
5 changes: 3 additions & 2 deletions Kexa/services/analyse.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { extractHeaders } from './addOn.service';
////////////////////////////////////////////////////////////////////////////////////////////////////////

import {getContext, getNewLogger} from "./logger.service";
import { splitProperty } from '../helpers/spliter';
const logger = getNewLogger("AnalyseLogger");

const jsome = require('jsome');
Expand Down Expand Up @@ -430,7 +431,7 @@ export function parentResultScan(subResultScans: SubResultScan[], result: boolea
value: null,
condition: subResultScans.map((value) => value.condition).flat(),
result,
message : subResultScans.map((value) => value.message).join(" || ")
message : subResultScans.map((value) => value.message).filter(item => item != "").join(" || ")
};
}

Expand Down Expand Up @@ -530,7 +531,7 @@ export function resultScan(condition: RulesConditions, value: any, fs: Function[

export function getSubProperty(object:any, property:string): any {
if (property === ".") return object;
let properties = property.split(".");
let properties = splitProperty(property, ".", "/");
let result = object;
properties.forEach(prop => {
result = result[prop];
Expand Down
1 change: 1 addition & 0 deletions documentation/Documentation-Kexa.md
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ Here is the structure and required fields for a new rule :
conditions: # the list of criteria to match
- property : string
# the object field name to check (you can see the objects fields by launching Kexa with npm run start:o option)
# for any property with a dot in his name, add "/" before the dot
condition : enum
# the condition for this comparison (defined in ./enum/condition.enum.ts)
value : string
Expand Down
Loading

0 comments on commit fd4abd2

Please sign in to comment.