diff --git a/apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/monitors/[id]/page.tsx b/apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/monitors/[id]/page.tsx
index 86cdc83d..a55be499 100644
--- a/apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/monitors/[id]/page.tsx
+++ b/apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/monitors/[id]/page.tsx
@@ -254,7 +254,9 @@ export default function Page() {
) : (
- {tempMonitor?.regions.length} {t("regions")}{" "}
+ {(tempMonitor?.regions.length ?? 0) +
+ (tempMonitor?.privateLocationCount ?? 0)}{" "}
+ {t("regions")}{" "}
0
- ? Object.keys(data[0]).filter((item) => item !== "timestamp")
+ ? Array.from(
+ new Set(
+ data
+ .flatMap((item) => Object.keys(item))
+ .filter((key) => key !== "timestamp"),
+ ),
+ )
: [];
return regions
diff --git a/packages/api/src/router/statusPage.ts b/packages/api/src/router/statusPage.ts
index 1659d28e..684acc4b 100644
--- a/packages/api/src/router/statusPage.ts
+++ b/packages/api/src/router/statusPage.ts
@@ -1,10 +1,11 @@
import { Events } from "@openstatus/analytics";
-import { and, eq, inArray, sql } from "@openstatus/db";
+import { and, eq, inArray, isNull, sql } from "@openstatus/db";
import {
maintenance,
page,
pageComponent,
pageConfigurationSchema,
+ privateLocationToMonitors,
selectMaintenancePageSchema,
selectPageComponentWithMonitorRelation,
selectPageSchema,
@@ -247,13 +248,46 @@ export const statusPageRouter = createTRPCRouter({
};
});
+ // Add privateLocationCount to each monitor
+ const privateLocationCounts = new Map();
+ if (monitors.length > 0) {
+ const monitorIds = monitors.map((m) => m.id);
+ const privateLocations =
+ await opts.ctx.db.query.privateLocationToMonitors.findMany({
+ where: and(
+ inArray(privateLocationToMonitors.monitorId, monitorIds),
+ isNull(privateLocationToMonitors.deletedAt),
+ ),
+ columns: {
+ monitorId: true,
+ },
+ });
+
+ // Count private locations per monitor
+ for (const pl of privateLocations) {
+ if (pl.monitorId === null) continue;
+ privateLocationCounts.set(
+ pl.monitorId,
+ (privateLocationCounts.get(pl.monitorId) ?? 0) + 1,
+ );
+ }
+ }
+
+ // Create new array with privateLocationCount included (no mutation/cast)
+ const monitorsWithPrivateLocationCount = monitors.map((m) => ({
+ ...m,
+ privateLocationCount: privateLocationCounts.get(m.id) ?? 0,
+ }));
+
// no barType gate: incident-driven error is already suppressed per
// monitor in manual mode; report-driven error (major_outage) must show
- const status = monitors.some((m) => m.status === "error")
+ const status = monitorsWithPrivateLocationCount.some(
+ (m) => m.status === "error",
+ )
? "error"
- : monitors.some((m) => m.status === "degraded")
+ : monitorsWithPrivateLocationCount.some((m) => m.status === "degraded")
? "degraded"
- : monitors.some((m) => m.status === "info")
+ : monitorsWithPrivateLocationCount.some((m) => m.status === "info")
? "info"
: "success";
@@ -430,10 +464,11 @@ export const statusPageRouter = createTRPCRouter({
return selectPublicPageSchemaWithRelation.parse({
..._page,
customTheme,
- monitors,
+ monitors: monitorsWithPrivateLocationCount,
monitorGroups,
trackers,
- incidents: monitors.flatMap((m) => m.incidents) ?? [],
+ incidents:
+ monitorsWithPrivateLocationCount.flatMap((m) => m.incidents) ?? [],
statusReports,
maintenances,
workspacePlan: _page.workspace.plan,
diff --git a/packages/api/src/router/statusPage.utils.test.ts b/packages/api/src/router/statusPage.utils.test.ts
index a9ae1c57..8b582653 100644
--- a/packages/api/src/router/statusPage.utils.test.ts
+++ b/packages/api/src/router/statusPage.utils.test.ts
@@ -59,10 +59,10 @@ function createStatusData(
function createIncident(id: number, daysAgo: number, durationHours = 1): Event {
const from = new Date();
from.setDate(from.getDate() - daysAgo);
- from.setHours(from.getHours() - durationHours);
+ from.setUTCHours(0, 0, 0, 0); // Set to midnight UTC for full-day coverage
const to = new Date(from);
- to.setHours(to.getHours() + durationHours);
+ to.setUTCHours(from.getUTCHours() + durationHours);
return {
id,
@@ -77,10 +77,10 @@ function createIncident(id: number, daysAgo: number, durationHours = 1): Event {
function createReport(id: number, daysAgo: number, durationHours = 2): Event {
const from = new Date();
from.setDate(from.getDate() - daysAgo);
- from.setHours(from.getHours() - durationHours);
+ from.setUTCHours(0, 0, 0, 0); // Set to midnight UTC for full-day coverage
const to = new Date(from);
- to.setHours(to.getHours() + durationHours);
+ to.setUTCHours(from.getUTCHours() + durationHours);
return {
id,
@@ -99,10 +99,10 @@ function createMaintenance(
): Event {
const from = new Date();
from.setDate(from.getDate() - daysAgo);
- from.setHours(from.getHours() - durationHours);
+ from.setUTCHours(0, 0, 0, 0); // Set to midnight UTC for full-day coverage
const to = new Date(from);
- to.setHours(to.getHours() + durationHours);
+ to.setUTCHours(from.getUTCHours() + durationHours);
return {
id,
diff --git a/packages/db/src/schema/shared.ts b/packages/db/src/schema/shared.ts
index 7d7855f9..8237a0cf 100644
--- a/packages/db/src/schema/shared.ts
+++ b/packages/db/src/schema/shared.ts
@@ -95,6 +95,7 @@ const selectPublicMonitorWithStatusSchema = selectPublicMonitorBaseSchema
monitorGroupId: z.number().nullable().optional(),
order: z.number().default(0).optional(),
groupOrder: z.number().default(0).nullish(),
+ privateLocationCount: z.number().optional(),
})
.transform((data) => ({
...data,