Compare commits

...

9 Commits

Author SHA1 Message Date
dependabot[bot]
f286d66cbc Bump actions/setup-node from 6.3.0 to 6.4.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.3.0 to 6.4.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](53b83947a5...48b55a011b)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-27 01:36:02 +00:00
Owen Schwartz
81922f54d5 Merge pull request #2889 from fosrl/dev
Fix type imports
2026-04-21 22:18:14 -07:00
Owen
9474792e14 Fix type imports 2026-04-21 22:17:49 -07:00
Owen
0c6acfe282 Fix types 2026-04-21 22:11:06 -07:00
Owen Schwartz
0ae20c0b25 Merge pull request #2888 from fosrl/dev
Fix imports
2026-04-21 22:05:41 -07:00
Owen
bcd3bee148 Properly resolve import issues 2026-04-21 22:05:01 -07:00
Owen
e2814517d6 Fix stub wrong function name 2026-04-21 21:54:46 -07:00
Owen Schwartz
c24db3df0e Merge pull request #2887 from fosrl/dev
Fix cert vars issue in stub
2026-04-21 21:49:31 -07:00
Owen
7ecfc9cbd3 Fix cert vars issue in stub 2026-04-21 21:48:54 -07:00
22 changed files with 129 additions and 115 deletions

View File

@@ -24,7 +24,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '24'

View File

@@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '24'

View File

@@ -9,7 +9,7 @@ export async function fireHealthCheckHealthyAlert(
return;
}
export async function fireHealthCheckNotHealthyAlert(
export async function fireHealthCheckUnhealthyAlert(
orgId: string,
healthCheckId: number,
healthCheckName?: string,

View File

@@ -0,0 +1,20 @@
export async function fireResourceHealthyAlert(
orgId: string,
resourceId: number,
resourceName?: string | null,
extra?: Record<string, unknown>
): Promise<void> {}
export async function fireResourceUnhealthyAlert(
orgId: string,
resourceId: number,
resourceName?: string | null,
extra?: Record<string, unknown>
): Promise<void> {}
export async function fireResourceToggleAlert(
orgId: string,
resourceId: number,
resourceName?: string | null,
extra?: Record<string, unknown>
): Promise<void> {}

View File

@@ -1,2 +1,3 @@
export * from "./events/siteEvents";
export * from "./events/healthCheckEvents";
export * from "./events/resourceEvents";

View File

@@ -1,6 +1,6 @@
export async function getValidCertificatesForDomains(
domains: Set<string>,
useCache: boolean
useCache: boolean = true
): Promise<
Array<{
id: number;

View File

@@ -416,7 +416,8 @@ export class TraefikConfigManager {
// Get valid certificates for domains not covered by wildcards
validCertificates =
await getValidCertificatesForDomains(
domainsToFetch
domainsToFetch,
true
);
this.lastCertificateFetch = new Date();
this.lastKnownDomains = new Set(domains);

View File

@@ -102,7 +102,7 @@ export async function fireHealthCheckUnhealthyAlert(
});
} catch (err) {
logger.error(
`fireHealthCheckNotHealthyAlert: unexpected error for healthCheckId ${healthCheckId}`,
`fireHealthCheckUnhealthyAlert: unexpected error for healthCheckId ${healthCheckId}`,
err
);
}

View File

@@ -11,9 +11,9 @@
* This file is not licensed under the AGPLv3.
*/
export * from "./types";
export * from "./processAlerts";
export * from "./sendAlertWebhook";
export * from "./sendAlertEmail";
export * from "./events/siteEvents";
export * from "./events/healthCheckEvents";
export * from "./events/healthCheckEvents";
export * from "./events/resourceEvents";

View File

@@ -27,9 +27,9 @@ import {
import config from "@server/lib/config";
import { decrypt } from "@server/lib/crypto";
import logger from "@server/logger";
import { AlertContext, WebhookAlertConfig } from "./types";
import { sendAlertWebhook } from "./sendAlertWebhook";
import { sendAlertEmail } from "./sendAlertEmail";
import { AlertContext, WebhookAlertConfig } from "@server/routers/alertRule/types";
/**
* Core alert processing pipeline.

View File

@@ -15,7 +15,7 @@ import { sendEmail } from "@server/emails";
import AlertNotification from "@server/emails/templates/AlertNotification";
import config from "@server/lib/config";
import logger from "@server/logger";
import { AlertContext } from "./types";
import { AlertContext } from "@server/routers/alertRule/types";
/**
* Sends an alert notification email to every address in `recipients`.

View File

@@ -12,7 +12,7 @@
*/
import logger from "@server/logger";
import { AlertContext, WebhookAlertConfig } from "./types";
import { AlertContext, WebhookAlertConfig } from "@server/routers/alertRule/types";
const REQUEST_TIMEOUT_MS = 15_000;
@@ -137,4 +137,4 @@ function buildHeaders(webhookConfig: WebhookAlertConfig): Record<string, string>
}
return headers;
}
}

View File

@@ -31,6 +31,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { encrypt } from "@server/lib/crypto";
import config from "@server/lib/config";
import { CreateAlertRuleResponse } from "@server/routers/alertRule/types";
export const SITE_EVENT_TYPES = ["site_online", "site_offline", "site_toggle"] as const;
export const HC_EVENT_TYPES = [
@@ -169,10 +170,6 @@ const bodySchema = z
}
});
export type CreateAlertRuleResponse = {
alertRuleId: number;
};
registry.registerPath({
method: "put",
path: "/org/{orgId}/alert-rule",

View File

@@ -32,7 +32,7 @@ import { OpenAPITags, registry } from "@server/openApi";
import { and, eq } from "drizzle-orm";
import { decrypt } from "@server/lib/crypto";
import config from "@server/lib/config";
import { WebhookAlertConfig } from "#private/lib/alerts/types";
import { GetAlertRuleResponse, WebhookAlertConfig } from "@server/routers/alertRule/types";
const paramsSchema = z
.object({
@@ -41,43 +41,6 @@ const paramsSchema = z
})
.strict();
export type GetAlertRuleResponse = {
alertRuleId: number;
orgId: string;
name: string;
eventType:
| "site_online"
| "site_offline"
| "site_toggle"
| "health_check_healthy"
| "health_check_unhealthy"
| "health_check_toggle"
| "resource_healthy"
| "resource_unhealthy"
| "resource_toggle";
enabled: boolean;
cooldownSeconds: number;
lastTriggeredAt: number | null;
createdAt: number;
updatedAt: number;
siteIds: number[];
healthCheckIds: number[];
resourceIds: number[];
recipients: {
recipientId: number;
userId: string | null;
roleId: number | null;
email: string | null;
}[];
webhookActions: {
webhookActionId: number;
webhookUrl: string;
enabled: boolean;
lastSentAt: number | null;
config: WebhookAlertConfig | null;
}[];
};
registry.registerPath({
method: "get",
path: "/org/{orgId}/alert-rule/{alertRuleId}",

View File

@@ -27,6 +27,7 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { and, asc, desc, eq, inArray, like, or, sql } from "drizzle-orm";
import { ListAlertRulesResponse } from "@server/routers/alertRule/types";
const paramsSchema = z.strictObject({
orgId: z.string().nonempty()
@@ -84,28 +85,6 @@ const HEALTH_CHECK_ALERT_EVENT_TYPES = [
"health_check_toggle"
] as const;
export type ListAlertRulesResponse = {
alertRules: {
alertRuleId: number;
orgId: string;
name: string;
eventType: string;
enabled: boolean;
cooldownSeconds: number;
lastTriggeredAt: number | null;
createdAt: number;
updatedAt: number;
siteIds: number[];
healthCheckIds: number[];
resourceIds: number[];
}[];
pagination: {
total: number;
limit: number;
offset: number;
};
};
registry.registerPath({
method: "get",
path: "/org/{orgId}/alert-rules",

View File

@@ -1,36 +1,65 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
export type ListAlertRulesResponse = {
alertRules: {
alertRuleId: number;
orgId: string;
name: string;
eventType: string;
enabled: boolean;
cooldownSeconds: number;
lastTriggeredAt: number | null;
createdAt: number;
updatedAt: number;
siteIds: number[];
healthCheckIds: number[];
resourceIds: number[];
}[];
pagination: {
total: number;
limit: number;
offset: number;
};
};
// ---------------------------------------------------------------------------
// Alert event types
// ---------------------------------------------------------------------------
export type CreateAlertRuleResponse = {
alertRuleId: number;
};
export type AlertEventType =
| "site_online"
| "site_offline"
| "site_toggle"
| "health_check_healthy"
| "health_check_unhealthy"
| "health_check_toggle"
| "resource_healthy"
| "resource_unhealthy"
| "resource_toggle";
// ---------------------------------------------------------------------------
// Webhook authentication config (stored as encrypted JSON in the DB)
// ---------------------------------------------------------------------------
export type WebhookAuthType = "none" | "bearer" | "basic" | "custom";
export type GetAlertRuleResponse = {
alertRuleId: number;
orgId: string;
name: string;
eventType:
| "site_online"
| "site_offline"
| "site_toggle"
| "health_check_healthy"
| "health_check_unhealthy"
| "health_check_toggle"
| "resource_healthy"
| "resource_unhealthy"
| "resource_toggle";
enabled: boolean;
cooldownSeconds: number;
lastTriggeredAt: number | null;
createdAt: number;
updatedAt: number;
siteIds: number[];
healthCheckIds: number[];
resourceIds: number[];
recipients: {
recipientId: number;
userId: string | null;
roleId: number | null;
email: string | null;
}[];
webhookActions: {
webhookActionId: number;
webhookUrl: string;
enabled: boolean;
lastSentAt: number | null;
config: WebhookAlertConfig | null;
}[];
};
/**
* Stored as an encrypted JSON blob in `alertWebhookActions.config`.
@@ -52,6 +81,27 @@ export interface WebhookAlertConfig {
method?: string;
}
// ---------------------------------------------------------------------------
// Alert event types
// ---------------------------------------------------------------------------
export type AlertEventType =
| "site_online"
| "site_offline"
| "site_toggle"
| "health_check_healthy"
| "health_check_unhealthy"
| "health_check_toggle"
| "resource_healthy"
| "resource_unhealthy"
| "resource_toggle";
// ---------------------------------------------------------------------------
// Webhook authentication config (stored as encrypted JSON in the DB)
// ---------------------------------------------------------------------------
export type WebhookAuthType = "none" | "bearer" | "basic" | "custom";
// ---------------------------------------------------------------------------
// Internal alert event passed through the processing pipeline
// ---------------------------------------------------------------------------

View File

@@ -14,7 +14,10 @@ import {
fireHealthCheckHealthyAlert,
fireHealthCheckUnhealthyAlert
} from "#dynamic/lib/alerts";
import { fireResourceHealthyAlert, fireResourceUnhealthyAlert } from "@server/private/lib/alerts/events/resourceEvents";
import {
fireResourceHealthyAlert,
fireResourceUnhealthyAlert
} from "#dynamic/lib/alerts";
interface TargetHealthStatus {
status: string;
@@ -223,13 +226,13 @@ export const handleHealthcheckStatusMessage: MessageHandler = async (
await fireHealthCheckUnhealthyAlert(
orgId,
targetCheck.targetHealthCheckId,
targetCheck.name
targetCheck.name ?? undefined
);
} else if (healthStatus.status === "healthy") {
await fireHealthCheckHealthyAlert(
orgId,
targetCheck.targetHealthCheckId,
targetCheck.name
targetCheck.name ?? undefined
);
}

View File

@@ -2,7 +2,7 @@ import AlertingRulesTable from "@app/components/AlertingRulesTable";
import DismissableBanner from "@app/components/DismissableBanner";
import { internal } from "@app/lib/api";
import { authCookieHeader } from "@app/lib/api/cookies";
import type { ListAlertRulesResponse } from "@server/private/routers/alertRule";
import type { ListAlertRulesResponse } from "@server/routers/alertRule/types";
import { AxiosResponse } from "axios";
import { BellRing } from "lucide-react";
import type { Metadata } from "next";

View File

@@ -12,7 +12,7 @@ import { useParams, useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
import type { AxiosResponse } from "axios";
import type { GetAlertRuleResponse } from "@server/private/routers/alertRule";
import type { GetAlertRuleResponse } from "@server/routers/alertRule/types";
import type { AlertRuleFormValues } from "@app/lib/alertRuleForm";
export default function EditAlertRulePage() {

View File

@@ -131,7 +131,7 @@ export function LayoutSidebar({
const showTrial =
build === "saas" &&
Boolean(orgId) &&
subscriptionContext?.isTrial
subscriptionContext?.isTrial;
return (
<div

View File

@@ -28,7 +28,7 @@ import {
} from "@app/lib/alertRuleForm";
import { createApiClient, formatAxiosError } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
import type { CreateAlertRuleResponse } from "@server/private/routers/alertRule";
import type { CreateAlertRuleResponse } from "@server/routers/alertRule/types";
import type { AxiosResponse } from "axios";
import { zodResolver } from "@hookform/resolvers/zod";
import { ChevronLeft, Cog, Flag, Zap } from "lucide-react";

View File

@@ -11,7 +11,7 @@ import type {
ListResourceNamesResponse,
ListResourcesResponse
} from "@server/routers/resource";
import type { ListAlertRulesResponse } from "@server/private/routers/alertRule";
import type { ListAlertRulesResponse } from "@server/routers/alertRule/types";
import type { ListRolesResponse } from "@server/routers/role";
import type { ListSitesResponse } from "@server/routers/site";
import type {