From 8972cc4d8690d872ffe9ef613ea5f438de09e75f Mon Sep 17 00:00:00 2001 From: Simon Larsen Date: Mon, 17 Mar 2025 19:59:35 +0000 Subject: [PATCH] feat: integrate CaptureSpan decorator into various classes for enhanced telemetry tracking --- .../Types/AnalyticsDatabase/QueryHelper.ts | 1 + .../MeteredPlan/TelemetryMeteredPlan.ts | 1 + .../Permissions/AccessControlPermission.ts | 1 - Common/Server/Types/Database/QueryUtil.ts | 1 + Common/Server/Types/Database/SelectUtil.ts | 1 + .../BaseModel/DeleteManyBaseModel.ts | 1 - .../Types/Workflow/Components/Schedule.ts | 1 + .../AnalyticsDatabase/StatementGenerator.ts | 1 + Common/Server/Utils/Browser.ts | 1 + Common/Server/Utils/CronTab.ts | 1 + Common/Server/Utils/Encryption.ts | 1 + Common/Server/Utils/Express.ts | 1 + Common/Server/Utils/Greenlock/Greenlock.ts | 572 ++++++++---------- .../Criteria/CustomCodeMonitorCriteria.ts | 1 + .../Monitor/Criteria/TraceMonitorCriteria.ts | 2 +- Dashboard/src/Routes/MonitorsRoutes.tsx | 37 ++ .../Utils/Breadcrumbs/MonitorBreadcrumbs.ts | 12 + Dashboard/src/Utils/RouteMap.ts | 15 + 18 files changed, 338 insertions(+), 313 deletions(-) diff --git a/Common/Server/Types/AnalyticsDatabase/QueryHelper.ts b/Common/Server/Types/AnalyticsDatabase/QueryHelper.ts index 4937def087..cd883f045e 100644 --- a/Common/Server/Types/AnalyticsDatabase/QueryHelper.ts +++ b/Common/Server/Types/AnalyticsDatabase/QueryHelper.ts @@ -4,6 +4,7 @@ import Includes from "Common/Types/BaseDatabase/Includes"; import LessThan from "Common/Types/BaseDatabase/LessThan"; import ObjectID from "Common/Types/ObjectID"; import { CompareType } from "../../../Types/Database/CompareBase"; +import CaptureSpan from "../../Utils/Telemetry/CaptureSpan"; export default class QueryHelper { @CaptureSpan() diff --git a/Common/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts b/Common/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts index 3966dad8e4..ee89c176b3 100644 --- a/Common/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts +++ b/Common/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts @@ -7,6 +7,7 @@ import ProductType from "Common/Types/MeteredPlan/ProductType"; import ObjectID from "Common/Types/ObjectID"; import Project from "Common/Models/DatabaseModels/Project"; import TelemetryUsageBilling from "Common/Models/DatabaseModels/TelemetryUsageBilling"; +import CaptureSpan from "../../../Utils/Telemetry/CaptureSpan"; export default class TelemetryMeteredPlan extends ServerMeteredPlan { private _productType!: ProductType; diff --git a/Common/Server/Types/Database/Permissions/AccessControlPermission.ts b/Common/Server/Types/Database/Permissions/AccessControlPermission.ts index 046fcc465e..a971736e4e 100644 --- a/Common/Server/Types/Database/Permissions/AccessControlPermission.ts +++ b/Common/Server/Types/Database/Permissions/AccessControlPermission.ts @@ -21,7 +21,6 @@ import Permission, { } from "Common/Types/Permission"; import CaptureSpan from "../../../Utils/Telemetry/CaptureSpan"; - export default class AccessControlPermission { @CaptureSpan() public static async checkAccessControlBlockPermissionByModel< diff --git a/Common/Server/Types/Database/QueryUtil.ts b/Common/Server/Types/Database/QueryUtil.ts index 36617f5355..3b1dff45ec 100644 --- a/Common/Server/Types/Database/QueryUtil.ts +++ b/Common/Server/Types/Database/QueryUtil.ts @@ -19,6 +19,7 @@ import ObjectID from "Common/Types/ObjectID"; import Typeof from "Common/Types/Typeof"; import { FindOperator } from "typeorm/find-options/FindOperator"; import { CompareType } from "../../../Types/Database/CompareBase"; +import CaptureSpan from "../../Utils/Telemetry/CaptureSpan"; export default class QueryUtil { @CaptureSpan() diff --git a/Common/Server/Types/Database/SelectUtil.ts b/Common/Server/Types/Database/SelectUtil.ts index 46264dc2db..f5a92d6cda 100644 --- a/Common/Server/Types/Database/SelectUtil.ts +++ b/Common/Server/Types/Database/SelectUtil.ts @@ -5,6 +5,7 @@ import BaseModel, { } from "Common/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel"; import { JSONObject } from "Common/Types/JSON"; import Typeof from "Common/Types/Typeof"; +import CaptureSpan from "../../Utils/Telemetry/CaptureSpan"; export default class SelectUtil { @CaptureSpan() diff --git a/Common/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.ts b/Common/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.ts index 28e3e0c9ba..2634024902 100644 --- a/Common/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.ts +++ b/Common/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.ts @@ -10,7 +10,6 @@ import PositiveNumber from "Common/Types/PositiveNumber"; import Text from "Common/Types/Text"; import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; import BaseModelComponents from "Common/Types/Workflow/Components/BaseModel"; -import CaptureSpan from "../../../../Utils/Telemetry/CaptureSpan"; export default class DeleteManyBaseModel< TBaseModel extends BaseModel, diff --git a/Common/Server/Types/Workflow/Components/Schedule.ts b/Common/Server/Types/Workflow/Components/Schedule.ts index 5533ae78dc..72c1618df7 100644 --- a/Common/Server/Types/Workflow/Components/Schedule.ts +++ b/Common/Server/Types/Workflow/Components/Schedule.ts @@ -14,6 +14,7 @@ import ComponentMetadata, { Port } from "Common/Types/Workflow/Component"; import ComponentID from "Common/Types/Workflow/ComponentID"; import ScheduleComponents from "Common/Types/Workflow/Components/Schedule"; import Workflow from "Common/Models/DatabaseModels/Workflow"; +import CaptureSpan from "../../../Utils/Telemetry/CaptureSpan"; export default class WebhookTrigger extends TriggerCode { public constructor() { diff --git a/Common/Server/Utils/AnalyticsDatabase/StatementGenerator.ts b/Common/Server/Utils/AnalyticsDatabase/StatementGenerator.ts index 7f6181c53a..3208c83400 100644 --- a/Common/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +++ b/Common/Server/Utils/AnalyticsDatabase/StatementGenerator.ts @@ -30,6 +30,7 @@ import JSONFunctions from "Common/Types/JSONFunctions"; import AggregateBy, { AggregateUtil, } from "../../Types/AnalyticsDatabase/AggregateBy"; +import CaptureSpan from "../Telemetry/CaptureSpan"; export default class StatementGenerator { public model!: TBaseModel; diff --git a/Common/Server/Utils/Browser.ts b/Common/Server/Utils/Browser.ts index 30fec11366..743164d863 100644 --- a/Common/Server/Utils/Browser.ts +++ b/Common/Server/Utils/Browser.ts @@ -9,6 +9,7 @@ import BadDataException from "../../Types/Exception/BadDataException"; import ScreenSizeType from "../../Types/ScreenSizeType"; import BrowserType from "../../Types/BrowserType"; import logger from "./Logger"; +import CaptureSpan from "./Telemetry/CaptureSpan"; export type Page = PlaywrightPage; export type Browser = PlaywrightBrowser; diff --git a/Common/Server/Utils/CronTab.ts b/Common/Server/Utils/CronTab.ts index f953ebcd3f..ad24e3661c 100644 --- a/Common/Server/Utils/CronTab.ts +++ b/Common/Server/Utils/CronTab.ts @@ -1,5 +1,6 @@ import BadDataException from "Common/Types/Exception/BadDataException"; import CronParser, { CronExpression } from "cron-parser"; +import CaptureSpan from "./Telemetry/CaptureSpan"; export default class CronTab { @CaptureSpan() diff --git a/Common/Server/Utils/Encryption.ts b/Common/Server/Utils/Encryption.ts index 1a31005a4d..bbdaaa93a5 100644 --- a/Common/Server/Utils/Encryption.ts +++ b/Common/Server/Utils/Encryption.ts @@ -1,5 +1,6 @@ import { EncryptionSecret } from "../EnvironmentConfig"; import CryptoJS from "crypto-js"; +import CaptureSpan from "./Telemetry/CaptureSpan"; export default class Encryption { @CaptureSpan() diff --git a/Common/Server/Utils/Express.ts b/Common/Server/Utils/Express.ts index e9c6eab1b2..6a7e828cb9 100644 --- a/Common/Server/Utils/Express.ts +++ b/Common/Server/Utils/Express.ts @@ -13,6 +13,7 @@ import UserType from "Common/Types/UserType"; import "ejs"; import express from "express"; import { Server, createServer } from "http"; +import CaptureSpan from "./Telemetry/CaptureSpan"; export type RequestHandler = express.RequestHandler; export type NextFunction = express.NextFunction; diff --git a/Common/Server/Utils/Greenlock/Greenlock.ts b/Common/Server/Utils/Greenlock/Greenlock.ts index 67d4350eb0..e4e4cbc321 100644 --- a/Common/Server/Utils/Greenlock/Greenlock.ts +++ b/Common/Server/Utils/Greenlock/Greenlock.ts @@ -17,7 +17,7 @@ import AcmeCertificate from "Common/Models/DatabaseModels/AcmeCertificate"; import AcmeChallenge from "Common/Models/DatabaseModels/AcmeChallenge"; import acme from "acme-client"; import { Challenge } from "acme-client/types/rfc8555"; -import Telemetry, { Span } from "../Telemetry"; +import CaptureSpan from "../Telemetry/CaptureSpan"; export default class GreenlockUtil { @CaptureSpan() @@ -25,145 +25,114 @@ export default class GreenlockUtil { validateCname: (domain: string) => Promise; notifyDomainRemoved: (domain: string) => Promise; }): Promise { - return await Telemetry.startActiveSpan>({ - name: "GreenlockUtil.renewAllCertsWhichAreExpiringSoon", - fn: async (span: Span): Promise => { + try { + logger.debug("Renewing all certificates"); + + // get all certificates which are expiring soon + + const certificates: AcmeCertificate[] = + await AcmeCertificateService.findBy({ + query: { + expiresAt: QueryHelper.lessThanEqualTo( + OneUptimeDate.addRemoveDays( + OneUptimeDate.getCurrentDate(), + 40, // 40 days before expiry + ), + ), + }, + limit: LIMIT_MAX, + skip: 0, + select: { + domain: true, + }, + sort: { + expiresAt: SortOrder.Ascending, + }, + props: { + isRoot: true, + }, + }); + + logger.debug( + `Found ${certificates.length} certificates which are expiring soon`, + ); + + // order certificate for each domain + + for (const certificate of certificates) { + if (!certificate.domain) { + continue; + } + + logger.debug( + `Renewing certificate for domain: ${certificate.domain}`, + ); + try { - logger.debug("Renewing all certificates"); - - // get all certificates which are expiring soon - - const certificates: AcmeCertificate[] = - await AcmeCertificateService.findBy({ - query: { - expiresAt: QueryHelper.lessThanEqualTo( - OneUptimeDate.addRemoveDays( - OneUptimeDate.getCurrentDate(), - 40, // 40 days before expiry - ), - ), - }, - limit: LIMIT_MAX, - skip: 0, - select: { - domain: true, - }, - sort: { - expiresAt: SortOrder.Ascending, - }, - props: { - isRoot: true, - }, - }); - - logger.debug( - `Found ${certificates.length} certificates which are expiring soon`, + //validate cname + const isValidCname: boolean = await data.validateCname( + certificate.domain, ); - // order certificate for each domain - - for (const certificate of certificates) { - if (!certificate.domain) { - continue; - } - + if (!isValidCname) { logger.debug( - `Renewing certificate for domain: ${certificate.domain}`, + `CNAME is not valid for domain: ${certificate.domain}`, ); - try { - //validate cname - const isValidCname: boolean = await data.validateCname( - certificate.domain, - ); + // if cname is not valid then remove the domain + await GreenlockUtil.removeDomain(certificate.domain); + await data.notifyDomainRemoved(certificate.domain); - if (!isValidCname) { - logger.debug( - `CNAME is not valid for domain: ${certificate.domain}`, - ); + logger.error( + `Cname is not valid for domain: ${certificate.domain}`, + ); + } else { + logger.debug( + `CNAME is valid for domain: ${certificate.domain}`, + ); - // if cname is not valid then remove the domain - await GreenlockUtil.removeDomain(certificate.domain); - await data.notifyDomainRemoved(certificate.domain); + await GreenlockUtil.orderCert({ + domain: certificate.domain, + validateCname: data.validateCname, + }); - logger.error( - `Cname is not valid for domain: ${certificate.domain}`, - ); - } else { - logger.debug( - `CNAME is valid for domain: ${certificate.domain}`, - ); - - await GreenlockUtil.orderCert({ - domain: certificate.domain, - validateCname: data.validateCname, - }); - - logger.debug( - `Certificate renewed for domain: ${certificate.domain}`, - ); - } - } catch (e) { - logger.error( - `Error renewing certificate for domain: ${certificate.domain}`, - ); - logger.error(e); - } + logger.debug( + `Certificate renewed for domain: ${certificate.domain}`, + ); } - - Telemetry.endSpan(span); } catch (e) { - logger.error("Error renewing all certificates"); + logger.error( + `Error renewing certificate for domain: ${certificate.domain}`, + ); logger.error(e); - - // record exception - Telemetry.recordExceptionMarkSpanAsErrorAndEndSpan({ - span, - exception: e, - }); - - throw e; } - }, - }); + } + } catch (e) { + logger.error("Error renewing all certificates"); + logger.error(e); + + throw e; + } } @CaptureSpan() public static async removeDomain(domain: string): Promise { - return await Telemetry.startActiveSpan>({ - name: "GreenlockUtil.orderCert", - options: { - attributes: { + try { + // remove certificate for this domain. + await AcmeCertificateService.deleteBy({ + query: { domain: domain, }, - }, - fn: async (span: Span): Promise => { - try { - // remove certificate for this domain. - await AcmeCertificateService.deleteBy({ - query: { - domain: domain, - }, - limit: 1, - skip: 0, - props: { - isRoot: true, - }, - }); - - Telemetry.endSpan(span); - } catch (err) { - logger.error(`Error removing domain: ${domain}`); - - Telemetry.recordExceptionMarkSpanAsErrorAndEndSpan({ - span, - exception: err, - }); - - throw err; - } - }, - }); + limit: 1, + skip: 0, + props: { + isRoot: true, + }, + }); + } catch (err) { + logger.error(`Error removing domain: ${domain}`); + throw err; + } } @CaptureSpan() @@ -171,218 +140,201 @@ export default class GreenlockUtil { domain: string; validateCname: (domain: string) => Promise; }): Promise { - return await Telemetry.startActiveSpan>({ - name: "GreenlockUtil.orderCert", - options: { - attributes: { - domain: data.domain, - }, - }, - fn: async (span: Span): Promise => { - try { - logger.debug( - `GreenlockUtil - Ordering certificate for domain: ${data.domain}`, - ); + try { + logger.debug( + `GreenlockUtil - Ordering certificate for domain: ${data.domain}`, + ); - let { domain } = data; + let { domain } = data; - domain = domain.trim().toLowerCase(); + domain = domain.trim().toLowerCase(); - const acmeAccountKeyInBase64: string = LetsEncryptAccountKey; + const acmeAccountKeyInBase64: string = LetsEncryptAccountKey; - if (!acmeAccountKeyInBase64) { - throw new ServerException( - "No lets encrypt account key found in environment variables. Please add one.", + if (!acmeAccountKeyInBase64) { + throw new ServerException( + "No lets encrypt account key found in environment variables. Please add one.", + ); + } + + let acmeAccountKey: string = Buffer.from( + acmeAccountKeyInBase64, + "base64", + ).toString(); + + acmeAccountKey = Text.replaceAll(acmeAccountKey, "\\n", "\n"); + + //validate cname + + logger.debug(`Validating cname for domain: ${domain}`); + + const isValidCname: boolean = await data.validateCname(domain); + + if (!isValidCname) { + logger.debug(`CNAME is not valid for domain: ${domain}`); + logger.debug(`Removing domain: ${domain}`); + + await GreenlockUtil.removeDomain(domain); + logger.error(`Cname is not valid for domain: ${domain}`); + throw new BadDataException( + "Cname is not valid for domain " + domain, + ); + } + + logger.debug(`Cname is valid for domain: ${domain}`); + + const client: acme.Client = new acme.Client({ + directoryUrl: acme.directory.letsencrypt.production, + accountKey: acmeAccountKey, + }); + + const [certificateKey, certificateRequest] = + await acme.crypto.createCsr({ + commonName: domain, + }); + + logger.debug(`Ordering certificate for domain: ${domain}`); + + const certificate: string = await client.auto({ + csr: certificateRequest, + email: LetsEncryptNotificationEmail.toString(), + termsOfServiceAgreed: true, + challengePriority: ["http-01"], // only http-01 challenge is supported by oneuptime + challengeCreateFn: async ( + authz: acme.Authorization, + challenge: Challenge, + keyAuthorization: string, + ) => { + // Satisfy challenge here + /* http-01 */ + if (challenge.type === "http-01") { + logger.debug( + `Creating challenge for domain: ${authz.identifier.value}`, ); - } - let acmeAccountKey: string = Buffer.from( - acmeAccountKeyInBase64, - "base64", - ).toString(); + const acmeChallenge: AcmeChallenge = new AcmeChallenge(); + acmeChallenge.challenge = keyAuthorization; + acmeChallenge.token = challenge.token; + acmeChallenge.domain = authz.identifier.value; - acmeAccountKey = Text.replaceAll(acmeAccountKey, "\\n", "\n"); - - //validate cname - - logger.debug(`Validating cname for domain: ${domain}`); - - const isValidCname: boolean = await data.validateCname(domain); - - if (!isValidCname) { - logger.debug(`CNAME is not valid for domain: ${domain}`); - logger.debug(`Removing domain: ${domain}`); - - await GreenlockUtil.removeDomain(domain); - logger.error(`Cname is not valid for domain: ${domain}`); - throw new BadDataException( - "Cname is not valid for domain " + domain, - ); - } - - logger.debug(`Cname is valid for domain: ${domain}`); - - const client: acme.Client = new acme.Client({ - directoryUrl: acme.directory.letsencrypt.production, - accountKey: acmeAccountKey, - }); - - const [certificateKey, certificateRequest] = - await acme.crypto.createCsr({ - commonName: domain, - }); - - logger.debug(`Ordering certificate for domain: ${domain}`); - - const certificate: string = await client.auto({ - csr: certificateRequest, - email: LetsEncryptNotificationEmail.toString(), - termsOfServiceAgreed: true, - challengePriority: ["http-01"], // only http-01 challenge is supported by oneuptime - challengeCreateFn: async ( - authz: acme.Authorization, - challenge: Challenge, - keyAuthorization: string, - ) => { - // Satisfy challenge here - /* http-01 */ - if (challenge.type === "http-01") { - logger.debug( - `Creating challenge for domain: ${authz.identifier.value}`, - ); - - const acmeChallenge: AcmeChallenge = new AcmeChallenge(); - acmeChallenge.challenge = keyAuthorization; - acmeChallenge.token = challenge.token; - acmeChallenge.domain = authz.identifier.value; - - await AcmeChallengeService.create({ - data: acmeChallenge, - props: { - isRoot: true, - }, - }); - - logger.debug( - `Challenge created for domain: ${authz.identifier.value}`, - ); - } - }, - challengeRemoveFn: async ( - authz: acme.Authorization, - challenge: Challenge, - ) => { - // Clean up challenge here - - logger.debug( - `Removing challenge for domain: ${authz.identifier.value}`, - ); - - if (challenge.type === "http-01") { - await AcmeChallengeService.deleteBy({ - query: { - domain: authz.identifier.value, - }, - limit: 1, - skip: 0, - props: { - isRoot: true, - }, - }); - } - - logger.debug( - `Challenge removed for domain: ${authz.identifier.value}`, - ); - }, - }); - - logger.debug(`Certificate ordered for domain: ${domain}`); - - // get expires at date from certificate - const cert: acme.CertificateInfo = - acme.crypto.readCertificateInfo(certificate); - const issuedAt: Date = cert.notBefore; - const expiresAt: Date = cert.notAfter; - - logger.debug(`Certificate expires at: ${expiresAt}`); - logger.debug(`Certificate issued at: ${issuedAt}`); - - // check if the certificate is already in the database. - const existingCertificate: AcmeCertificate | null = - await AcmeCertificateService.findOneBy({ - query: { - domain: domain, - }, - select: { - _id: true, - }, + await AcmeChallengeService.create({ + data: acmeChallenge, props: { isRoot: true, }, }); - if (existingCertificate) { - logger.debug(`Updating certificate for domain: ${domain}`); + logger.debug( + `Challenge created for domain: ${authz.identifier.value}`, + ); + } + }, + challengeRemoveFn: async ( + authz: acme.Authorization, + challenge: Challenge, + ) => { + // Clean up challenge here - // update the certificate - await AcmeCertificateService.updateBy({ + logger.debug( + `Removing challenge for domain: ${authz.identifier.value}`, + ); + + if (challenge.type === "http-01") { + await AcmeChallengeService.deleteBy({ query: { - domain: domain, + domain: authz.identifier.value, }, limit: 1, skip: 0, - data: { - certificate: certificate.toString(), - certificateKey: certificateKey.toString(), - issuedAt: issuedAt, - expiresAt: expiresAt, - }, props: { isRoot: true, }, }); - - logger.debug(`Certificate updated for domain: ${domain}`); - } else { - logger.debug(`Creating certificate for domain: ${domain}`); - // create the certificate - const acmeCertificate: AcmeCertificate = new AcmeCertificate(); - - acmeCertificate.domain = domain; - acmeCertificate.certificate = certificate.toString(); - acmeCertificate.certificateKey = certificateKey.toString(); - acmeCertificate.issuedAt = issuedAt; - acmeCertificate.expiresAt = expiresAt; - - await AcmeCertificateService.create({ - data: acmeCertificate, - props: { - isRoot: true, - }, - }); - - logger.debug(`Certificate created for domain: ${domain}`); } - Telemetry.endSpan(span); - } catch (e) { - logger.error(`Error ordering certificate for domain: ${data.domain}`); - - Telemetry.recordExceptionMarkSpanAsErrorAndEndSpan({ - span, - exception: e, - }); - - if (e instanceof Exception) { - throw e; - } - - throw new ServerException( - `Unable to order certificate for ${data.domain}. Please contact support at support@oneuptime.com for more information.`, + logger.debug( + `Challenge removed for domain: ${authz.identifier.value}`, ); - } - }, - }); + }, + }); + + logger.debug(`Certificate ordered for domain: ${domain}`); + + // get expires at date from certificate + const cert: acme.CertificateInfo = + acme.crypto.readCertificateInfo(certificate); + const issuedAt: Date = cert.notBefore; + const expiresAt: Date = cert.notAfter; + + logger.debug(`Certificate expires at: ${expiresAt}`); + logger.debug(`Certificate issued at: ${issuedAt}`); + + // check if the certificate is already in the database. + const existingCertificate: AcmeCertificate | null = + await AcmeCertificateService.findOneBy({ + query: { + domain: domain, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + }, + }); + + if (existingCertificate) { + logger.debug(`Updating certificate for domain: ${domain}`); + + // update the certificate + await AcmeCertificateService.updateBy({ + query: { + domain: domain, + }, + limit: 1, + skip: 0, + data: { + certificate: certificate.toString(), + certificateKey: certificateKey.toString(), + issuedAt: issuedAt, + expiresAt: expiresAt, + }, + props: { + isRoot: true, + }, + }); + + logger.debug(`Certificate updated for domain: ${domain}`); + } else { + logger.debug(`Creating certificate for domain: ${domain}`); + // create the certificate + const acmeCertificate: AcmeCertificate = new AcmeCertificate(); + + acmeCertificate.domain = domain; + acmeCertificate.certificate = certificate.toString(); + acmeCertificate.certificateKey = certificateKey.toString(); + acmeCertificate.issuedAt = issuedAt; + acmeCertificate.expiresAt = expiresAt; + + await AcmeCertificateService.create({ + data: acmeCertificate, + props: { + isRoot: true, + }, + }); + + logger.debug(`Certificate created for domain: ${domain}`); + } + } catch (e) { + logger.error(`Error ordering certificate for domain: ${data.domain}`); + + if (e instanceof Exception) { + throw e; + } + + throw new ServerException( + `Unable to order certificate for ${data.domain}. Please contact support at support@oneuptime.com for more information.`, + ); + } } } diff --git a/Common/Server/Utils/Monitor/Criteria/CustomCodeMonitorCriteria.ts b/Common/Server/Utils/Monitor/Criteria/CustomCodeMonitorCriteria.ts index 1839a0dcbc..c6c8361595 100644 --- a/Common/Server/Utils/Monitor/Criteria/CustomCodeMonitorCriteria.ts +++ b/Common/Server/Utils/Monitor/Criteria/CustomCodeMonitorCriteria.ts @@ -1,6 +1,7 @@ import CompareCriteria from "./CompareCriteria"; import { CheckOn, CriteriaFilter } from "Common/Types/Monitor/CriteriaFilter"; import CustomCodeMonitorResponse from "Common/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse"; +import CaptureSpan from "../../Telemetry/CaptureSpan"; export default class CustomCodeMonitoringCriteria { @CaptureSpan() diff --git a/Common/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.ts b/Common/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.ts index 2c5bf274c6..fdb6c5b7f3 100644 --- a/Common/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.ts +++ b/Common/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.ts @@ -5,7 +5,7 @@ import CompareCriteria from "./CompareCriteria"; import { CheckOn, CriteriaFilter } from "Common/Types/Monitor/CriteriaFilter"; export default class TraceMonitorCriteria { -@CaptureSpan() + @CaptureSpan() public static async isMonitorInstanceCriteriaFilterMet(input: { dataToProcess: DataToProcess; criteriaFilter: CriteriaFilter; diff --git a/Dashboard/src/Routes/MonitorsRoutes.tsx b/Dashboard/src/Routes/MonitorsRoutes.tsx index 41d8ab3182..7a9ca02c18 100644 --- a/Dashboard/src/Routes/MonitorsRoutes.tsx +++ b/Dashboard/src/Routes/MonitorsRoutes.tsx @@ -21,6 +21,17 @@ const MonitorPage: LazyExoticComponent> = return import("../Pages/Monitor/Monitors"); }); + const WorkspaceConnectionSlack: LazyExoticComponent> = + lazy(() => { + return import("../Pages/Monitor/WorkspaceConnectionSlack"); + }); + + const WorkspaceConnectionTeams: LazyExoticComponent> = + lazy(() => { + return import("../Pages/Monitor/WorkspaceConnectionMicrosoftTeams"); + }); + + const MonitorViewMetrics: LazyExoticComponent< FunctionComponent > = lazy(() => { @@ -176,6 +187,7 @@ const MonitorRoutes: FunctionComponent = ( } /> + = ( } /> + + + + } + /> + + + + + } + /> + + | undefined { "Monitors", "Inoperational", ]), + //slack connection + ...BuildBreadcrumbLinksByTitles(PageMap.MONITORS_WORKSPACE_CONNECTION_SLACK, [ + "Project", + "Monitors", + "Slack", + ]), + // ms teams connection + ...BuildBreadcrumbLinksByTitles(PageMap.MONITORS_WORKSPACE_CONNECTION_MICROSOFT_TEAMS, [ + "Project", + "Monitors", + "Microsoft Teams", + ]), ...BuildBreadcrumbLinksByTitles(PageMap.MONITORS_DISABLED, [ "Project", "Monitors", diff --git a/Dashboard/src/Utils/RouteMap.ts b/Dashboard/src/Utils/RouteMap.ts index 019700f4fe..e8899ad2e5 100644 --- a/Dashboard/src/Utils/RouteMap.ts +++ b/Dashboard/src/Utils/RouteMap.ts @@ -12,6 +12,9 @@ export const MonitorsRoutePath: Dictionary = { [PageMap.MONITORS_DISABLED]: "disabled", [PageMap.MONITORS_PROBE_DISCONNECTED]: "probe-disconnected", [PageMap.MONITORS_PROBE_DISABLED]: "probe-disabled", + [PageMap.MONITORS_WORKSPACE_CONNECTION_SLACK]: "workspace-connection-slack", + [PageMap.MONITORS_WORKSPACE_CONNECTION_MICROSOFT_TEAMS]: + "workspace-connection-microsoft-teams", [PageMap.MONITOR_VIEW]: `${RouteParams.ModelID}`, [PageMap.MONITOR_VIEW_INTERVAL]: `${RouteParams.ModelID}/interval`, @@ -327,6 +330,18 @@ const RouteMap: Dictionary = { }`, ), + [PageMap.MONITORS_WORKSPACE_CONNECTION_SLACK]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitors/${ + MonitorsRoutePath[PageMap.MONITORS_WORKSPACE_CONNECTION_SLACK] + }`, + ), + + [PageMap.MONITORS_WORKSPACE_CONNECTION_MICROSOFT_TEAMS]: new Route( + `/dashboard/${RouteParams.ProjectID}/monitors/${ + MonitorsRoutePath[PageMap.MONITORS_WORKSPACE_CONNECTION_MICROSOFT_TEAMS] + }`, + ), + [PageMap.MONITOR_CREATE]: new Route( `/dashboard/${RouteParams.ProjectID}/monitors/${ MonitorsRoutePath[PageMap.MONITOR_CREATE]