diff --git a/App/FeatureSet/Notification/Templates/ProbeOffline.hbs b/App/FeatureSet/Notification/Templates/ProbeOffline.hbs index cbd344172b..f27290493a 100644 --- a/App/FeatureSet/Notification/Templates/ProbeOffline.hbs +++ b/App/FeatureSet/Notification/Templates/ProbeOffline.hbs @@ -2,12 +2,11 @@ {{> Logo this}} -{{> EmailTitle title="Probe cannot monitor your resources" }} +{{> EmailTitle title="ACTION REQUIRED: OneUptime Probe cannot monitor your resources." }} -{{> InfoBlock info="Your password has been updated. You can now log in."}} +{{> InfoBlock info="We have detected that the OneUptime Probe is offline and cannot monitor your resources. This may be due to a network issue, a firewall blocking the probe, or the probe being powered off. Please take the necessary steps to bring the probe back online." }} {{> DetailBoxStart this }} -{{> DetailBoxField title=incidentTitle text="" }} {{> DetailBoxField title="Probe Name: " text=probeName }} {{> DetailBoxField title="Probe Description: " text=probeDescription }} {{#if projectId}} @@ -17,7 +16,7 @@ {{/if}} {{> DetailBoxField title="Probe ID: " text=probeId }} {{#if podId}} -{{> DetailBoxField title="Pod ID: " text=podId }} +{{> DetailBoxField title="Hostname: " text=hostname }} {{/if}} {{> DetailBoxField title="Issue: " text=issue }} {{> DetailBoxEnd this }} diff --git a/Common/Types/Email/EmailTemplateType.ts b/Common/Types/Email/EmailTemplateType.ts index 491265e05d..a904397845 100644 --- a/Common/Types/Email/EmailTemplateType.ts +++ b/Common/Types/Email/EmailTemplateType.ts @@ -1,5 +1,6 @@ enum EmailTemplateType { ForgotPassword = 'ForgotPassword.hbs', + ProbeOffline = 'ProbeOffline.hbs', SignupWelcomeEmail = 'SignupWelcomeEmail.hbs', EmailVerified = 'EmailVerified.hbs', PasswordChanged = 'PasswordChanged.hbs', diff --git a/Ingestor/API/Probe.ts b/Ingestor/API/Probe.ts index 0a5923cd75..e2920a638d 100644 --- a/Ingestor/API/Probe.ts +++ b/Ingestor/API/Probe.ts @@ -19,6 +19,11 @@ import ProbeService from 'CommonServer/Services/ProbeService'; import GlobalConfigService from 'CommonServer/Services/GlobalConfigService'; import Email from 'Common/Types/Email'; import GlobalConfig from 'Model/Models/GlobalConfig'; +import ProjectService from 'CommonServer/Services/ProjectService'; +import User from 'Model/Models/User'; +import MailService from 'CommonServer/Services/MailService'; +import EmailTemplateType from 'Common/Types/Email/EmailTemplateType'; +import logger from 'CommonServer/Utils/Logger'; const router: ExpressRouter = Express.getRouter(); @@ -74,6 +79,8 @@ router.post( select: { _id: true, projectId: true, + name: true, + description: true, }, props: { isRoot: true, @@ -92,6 +99,9 @@ router.post( // If not a global probe then them email project owners. const isGlobalProbe: boolean = !probe.projectId; + const emailsToNotify: Email[] = []; + + let emailReason: string = ''; if (isGlobalProbe) { // email master-admin @@ -121,9 +131,73 @@ router.post( if (adminNotificationEmail) { // email adminNotificationEmail + emailsToNotify.push(adminNotificationEmail); + + emailReason = + 'This email is sent to you becuse you have listed this email as a notification email in the Admin Dashobard. To change this email, please visit the Admin Dashboard > Settings > Email.'; } } else { + if (!probe.projectId) { + return Response.sendErrorResponse( + req, + res, + new BadDataException('Invalid Project ID') + ); + } + // email project owners. + const owners: Array = await ProjectService.getOwners( + probe.projectId! + ); + + for (const owner of owners) { + if (owner.email) { + emailsToNotify.push(owner.email); + } + } + + emailReason = + 'This email is sent to you because you are listed as an owner of the project that this probe is associated with. To change this email, please visit the Project Dashboard > Settings > Teams and Members > Owners.'; + } + + const issue: string = ''; + + if (isWebsiteCheckOffline) { + issue.concat( + 'This probe cannot reach out to monitor websites' + ); + } + + if (isPingCheckOffline) { + issue.concat( + 'This probe cannot reach out to ping other servers / hostnames or IP addresses.' + ); + } + + // now send an email to all the emailsToNotify + for (const email of emailsToNotify) { + MailService.sendMail( + { + toEmail: email, + templateType: EmailTemplateType.ProbeOffline, + subject: 'Probe Offline Notification', + vars: { + probeName: probe.name || '', + description: probe.description || '', + projectId: probe.projectId?.toString() || '', + probeId: probe.id?.toString() || '', + hostname: + statusReport['hostname']?.toString() || '', + emailReason: emailReason, + issue: issue, + }, + }, + { + projectId: probe.projectId, + } + ).catch((err: Error) => { + logger.error(err); + }); } } diff --git a/Probe/Config.ts b/Probe/Config.ts index 8799297cc3..d4690262a6 100644 --- a/Probe/Config.ts +++ b/Probe/Config.ts @@ -56,3 +56,5 @@ if (typeof monitorFetchLimit === 'string') { } export const PROBE_MONITOR_FETCH_LIMIT: number = monitorFetchLimit; + +export const HOSTNAME: string = process.env['HOSTNAME'] || 'localhost'; diff --git a/Probe/Index.ts b/Probe/Index.ts index 908c0ea34a..50d2a42b9c 100644 --- a/Probe/Index.ts +++ b/Probe/Index.ts @@ -18,6 +18,8 @@ const init: PromiseVoidFunction = async (): Promise => { try { // Register this probe. await Register.registerProbe(); + + logger.info('Probe registered'); } catch (err) { logger.error('Register probe failed'); logger.error(err); diff --git a/Probe/Services/Register.ts b/Probe/Services/Register.ts index 237eb54cc1..5673140b99 100644 --- a/Probe/Services/Register.ts +++ b/Probe/Services/Register.ts @@ -1,5 +1,6 @@ import API from 'Common/Utils/API'; import { + HOSTNAME, INGESTOR_URL, PROBE_DESCRIPTION, PROBE_ID, @@ -13,8 +14,49 @@ import HTTPResponse from 'Common/Types/API/HTTPResponse'; import { JSONObject } from 'Common/Types/JSON'; import LocalCache from 'CommonServer/Infrastructure/LocalCache'; import Sleep from 'Common/Types/Sleep'; +import HTTPMethod from 'Common/Types/API/HTTPMethod'; +import ProbeAPIRequest from '../Utils/ProbeAPIRequest'; +import OnlineCheck from '../Utils/OnlineCheck'; export default class Register { + public static async reportOfflineStatus(): Promise { + const pingMonitoringCheck: boolean = + await OnlineCheck.canProbeMonitorPingMonitors(); + const websiteMonitoringCheck: boolean = + await OnlineCheck.canProbeMonitorWebsiteMonitors(); + + if (!pingMonitoringCheck || !websiteMonitoringCheck) { + // Send an email to the admin. + + if (!pingMonitoringCheck) { + logger.error('Ping monitoring is disabled'); + } + + if (!websiteMonitoringCheck) { + logger.error('Website monitoring is disabled'); + } + + // Send an email to the admin. + + await API.fetch( + HTTPMethod.POST, + URL.fromString(INGESTOR_URL.toString()).addRoute( + '/probe/response/ingest' + ), + { + ...ProbeAPIRequest.getDefaultRequestBody(), + statusReport: { + isPingCheckOffline: !pingMonitoringCheck, + isWebsiteCheckOffline: !websiteMonitoringCheck, + hostname: HOSTNAME, + }, + }, + {}, + {} + ); + } + } + public static async registerProbe(): Promise { // register probe with 5 retry and 15 seocnd interval between each retry.