mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-30 04:32:53 +00:00
adjust email template for alerts
This commit is contained in:
@@ -8,9 +8,11 @@ import {
|
||||
EmailHeading,
|
||||
EmailInfoSection,
|
||||
EmailLetterHead,
|
||||
EmailSection,
|
||||
EmailSignature,
|
||||
EmailText
|
||||
} from "./components/Email";
|
||||
import ButtonLink from "./components/ButtonLink";
|
||||
|
||||
export type AlertEventType =
|
||||
| "site_online"
|
||||
@@ -23,11 +25,38 @@ export type AlertEventType =
|
||||
| "resource_unhealthy"
|
||||
| "resource_toggle";
|
||||
|
||||
interface Props {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Local preview / layout testing
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// Set to `true` while running `npm run email` or otherwise rendering this
|
||||
// template without real alert context. Uses `alertNotificationFixture` below
|
||||
// and ignores props passed by callers (including real alert sends). Must be
|
||||
// `false` before shipping or triggering real alert emails.
|
||||
|
||||
export const USE_FAKE_ALERT_NOTIFICATION_DATA = true;
|
||||
|
||||
export type AlertNotificationProps = {
|
||||
eventType: AlertEventType;
|
||||
orgId: string;
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
dashboardLink: string;
|
||||
};
|
||||
|
||||
/** Sample props for previews; also used when `USE_FAKE_ALERT_NOTIFICATION_DATA` is true. */
|
||||
export const alertNotificationFixture: AlertNotificationProps = {
|
||||
eventType: "site_online",
|
||||
orgId: "org_preview_7a3c2f91",
|
||||
dashboardLink:
|
||||
"https://app.pangolin.net/org_preview_7a3c2f91/settings/alerting/rules",
|
||||
data: {
|
||||
siteId: 42,
|
||||
healthCheckName: "Edge API – readiness probe",
|
||||
targetUrl: "https://api.example.com/internal/health",
|
||||
lastFailureMessage: "Connection timed out after 5000ms",
|
||||
consecutiveFailures: 3
|
||||
}
|
||||
};
|
||||
|
||||
function getEventMeta(eventType: AlertEventType): {
|
||||
heading: string;
|
||||
@@ -51,7 +80,7 @@ function getEventMeta(eventType: AlertEventType): {
|
||||
heading: "Site Offline",
|
||||
previewText: "A site in your organization has gone offline.",
|
||||
summary:
|
||||
"A site in your organization has gone offline and is no longer reachable. Please investigate as soon as possible.",
|
||||
"A site in your organization has gone offline and is no longer reachable.",
|
||||
statusLabel: "Offline",
|
||||
statusColor: "#dc2626"
|
||||
};
|
||||
@@ -59,8 +88,7 @@ function getEventMeta(eventType: AlertEventType): {
|
||||
return {
|
||||
heading: "Site Status Changed",
|
||||
previewText: "A site in your organization has changed status.",
|
||||
summary:
|
||||
"A site in your organization has changed status. Please review the details below and take action if needed.",
|
||||
summary: "A site in your organization has changed status.",
|
||||
statusLabel: "Status Changed",
|
||||
statusColor: "#f59e0b"
|
||||
};
|
||||
@@ -80,7 +108,7 @@ function getEventMeta(eventType: AlertEventType): {
|
||||
previewText:
|
||||
"A health check in your organization is not healthy.",
|
||||
summary:
|
||||
"A health check in your organization is currently failing. Please review the details below and take action if needed.",
|
||||
"A health check in your organization is currently failing.",
|
||||
statusLabel: "Not Healthy",
|
||||
statusColor: "#dc2626"
|
||||
};
|
||||
@@ -90,7 +118,7 @@ function getEventMeta(eventType: AlertEventType): {
|
||||
previewText:
|
||||
"A health check in your organization has changed status.",
|
||||
summary:
|
||||
"A health check in your organization has changed status. Please review the details below and take action if needed.",
|
||||
"A health check in your organization has changed status.",
|
||||
statusLabel: "Status Changed",
|
||||
statusColor: "#f59e0b"
|
||||
};
|
||||
@@ -108,7 +136,7 @@ function getEventMeta(eventType: AlertEventType): {
|
||||
heading: "Resource Unhealthy",
|
||||
previewText: "A resource in your organization is not healthy.",
|
||||
summary:
|
||||
"A resource in your organization is currently unhealthy. Please review the details below and take action if needed.",
|
||||
"A resource in your organization is currently unhealthy.",
|
||||
statusLabel: "Unhealthy",
|
||||
statusColor: "#dc2626"
|
||||
};
|
||||
@@ -117,17 +145,16 @@ function getEventMeta(eventType: AlertEventType): {
|
||||
heading: "Resource Status Changed",
|
||||
previewText:
|
||||
"A resource in your organization has changed status.",
|
||||
summary:
|
||||
"A resource in your organization has changed status. Please review the details below and take action if needed.",
|
||||
summary: "A resource in your organization has changed status.",
|
||||
statusLabel: "Status Changed",
|
||||
statusColor: "#f59e0b"
|
||||
};
|
||||
default:
|
||||
return {
|
||||
heading: "Alert Notification",
|
||||
previewText: "An alert event has occurred in your organization.",
|
||||
summary:
|
||||
"An alert event has occurred in your organization. Please review the details below and take action if needed.",
|
||||
previewText:
|
||||
"An alert event has occurred in your organization.",
|
||||
summary: "An alert event has occurred in your organization.",
|
||||
statusLabel: "Alert",
|
||||
statusColor: "#f59e0b"
|
||||
};
|
||||
@@ -148,17 +175,22 @@ function formatDataItems(
|
||||
}));
|
||||
}
|
||||
|
||||
export const AlertNotification = ({ eventType, orgId, data }: Props) => {
|
||||
export const AlertNotification = (props: AlertNotificationProps) => {
|
||||
const { eventType, orgId, data, dashboardLink } =
|
||||
USE_FAKE_ALERT_NOTIFICATION_DATA ? alertNotificationFixture : props;
|
||||
const meta = getEventMeta(eventType);
|
||||
const dataItems = formatDataItems(data);
|
||||
|
||||
const allItems: { label: string; value: React.ReactNode }[] = [
|
||||
{ label: "Organization", value: orgId },
|
||||
{ label: "Status", value: (
|
||||
<span style={{ color: meta.statusColor, fontWeight: 600 }}>
|
||||
{meta.statusLabel}
|
||||
</span>
|
||||
)},
|
||||
{
|
||||
label: "Status",
|
||||
value: (
|
||||
<span style={{ color: meta.statusColor, fontWeight: 600 }}>
|
||||
{meta.statusLabel}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{ label: "Time", value: new Date().toUTCString() },
|
||||
...dataItems
|
||||
];
|
||||
@@ -184,10 +216,16 @@ export const AlertNotification = ({ eventType, orgId, data }: Props) => {
|
||||
/>
|
||||
|
||||
<EmailText>
|
||||
Log in to your dashboard to view more details and
|
||||
manage your alert rules.
|
||||
Open your dashboard to view more details and manage
|
||||
your alert rules.
|
||||
</EmailText>
|
||||
|
||||
<EmailSection>
|
||||
<ButtonLink href={dashboardLink}>
|
||||
Open Dashboard
|
||||
</ButtonLink>
|
||||
</EmailSection>
|
||||
|
||||
<EmailFooter>
|
||||
<EmailSignature />
|
||||
</EmailFooter>
|
||||
|
||||
@@ -36,8 +36,8 @@ export const NotifyTrialExpiring = ({
|
||||
: `Your trial for ${orgName} ends in ${daysRemaining} days.`;
|
||||
|
||||
const heading = hasEnded
|
||||
? "Your Trial Has Ended"
|
||||
: "Your Trial Is Ending Soon";
|
||||
? "Your Trial Ended"
|
||||
: "Your Trial is Ending Soon";
|
||||
|
||||
return (
|
||||
<Html>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { build } from "@server/build";
|
||||
// EmailContainer: Wraps the entire email layout
|
||||
export function EmailContainer({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<Container className="bg-white border border-solid border-gray-200 max-w-lg mx-auto my-8 rounded-lg overflow-hidden shadow-sm">
|
||||
<Container className="bg-white border border-solid border-gray-200 max-w-lg mx-auto my-8 rounded-xl overflow-hidden">
|
||||
{children}
|
||||
</Container>
|
||||
);
|
||||
@@ -18,7 +18,7 @@ export function EmailLetterHead() {
|
||||
<Img
|
||||
src="https://fossorial-public-assets.s3.us-east-1.amazonaws.com/word_mark_black.png"
|
||||
alt="Pangolin Logo"
|
||||
width="120"
|
||||
width="180"
|
||||
height="auto"
|
||||
className="mx-auto"
|
||||
/>
|
||||
|
||||
@@ -41,7 +41,6 @@ export async function fireHealthCheckHealthyAlert(
|
||||
orgId,
|
||||
healthCheckId,
|
||||
data: {
|
||||
healthCheckId,
|
||||
...(healthCheckName != null ? { healthCheckName } : {}),
|
||||
...extra
|
||||
}
|
||||
@@ -87,7 +86,6 @@ export async function fireHealthCheckNotHealthyAlert(
|
||||
orgId,
|
||||
healthCheckId,
|
||||
data: {
|
||||
healthCheckId,
|
||||
...(healthCheckName != null ? { healthCheckName } : {}),
|
||||
...extra
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ export async function fireResourceHealthyAlert(
|
||||
orgId,
|
||||
resourceId,
|
||||
data: {
|
||||
resourceId,
|
||||
...(resourceName != null ? { resourceName } : {}),
|
||||
...extra
|
||||
}
|
||||
@@ -87,7 +86,6 @@ export async function fireResourceUnhealthyAlert(
|
||||
orgId,
|
||||
resourceId,
|
||||
data: {
|
||||
resourceId,
|
||||
...(resourceName != null ? { resourceName } : {}),
|
||||
...extra
|
||||
}
|
||||
@@ -133,7 +131,6 @@ export async function fireResourceToggleAlert(
|
||||
orgId,
|
||||
resourceId,
|
||||
data: {
|
||||
resourceId,
|
||||
...(resourceName != null ? { resourceName } : {}),
|
||||
...extra
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ export async function fireSiteOnlineAlert(
|
||||
orgId,
|
||||
siteId,
|
||||
data: {
|
||||
siteId,
|
||||
...(siteName != null ? { siteName } : {}),
|
||||
...extra
|
||||
}
|
||||
@@ -87,7 +86,6 @@ export async function fireSiteOfflineAlert(
|
||||
orgId,
|
||||
siteId,
|
||||
data: {
|
||||
siteId,
|
||||
...(siteName != null ? { siteName } : {}),
|
||||
...extra
|
||||
}
|
||||
|
||||
@@ -36,13 +36,17 @@ export async function sendAlertEmail(
|
||||
const from = config.getNoReplyEmail();
|
||||
const subject = buildSubject(context);
|
||||
|
||||
const baseUrl = config.getRawConfig().app.dashboard_url!.replace(/\/$/, "");
|
||||
const dashboardLink = `${baseUrl}/${context.orgId}/settings`;
|
||||
|
||||
for (const to of recipients) {
|
||||
try {
|
||||
await sendEmail(
|
||||
AlertNotification({
|
||||
eventType: context.eventType,
|
||||
orgId: context.orgId,
|
||||
data: context.data
|
||||
data: context.data,
|
||||
dashboardLink
|
||||
}),
|
||||
{
|
||||
from,
|
||||
|
||||
Reference in New Issue
Block a user