Use logsDb for the status history

This commit is contained in:
Owen
2026-04-28 16:38:13 -07:00
parent 85415176ab
commit 2a29062659
6 changed files with 67 additions and 38 deletions

View File

@@ -1,5 +1,5 @@
import { z } from "zod";
import { db, statusHistory } from "@server/db";
import { db, logsDb, statusHistory } from "@server/db";
import { and, eq, gte, asc } from "drizzle-orm";
import cache from "@server/lib/cache";
@@ -27,7 +27,7 @@ export async function getCachedStatusHistory(
const nowSec = Math.floor(Date.now() / 1000);
const startSec = nowSec - days * 86400;
const events = await db
const events = await logsDb
.select()
.from(statusHistory)
.where(
@@ -74,11 +74,11 @@ export const statusHistoryQuerySchema = z
days: z
.string()
.optional()
.transform((v) => (v ? parseInt(v, 10) : 90)),
.transform((v) => (v ? parseInt(v, 10) : 90))
})
.pipe(
z.object({
days: z.number().int().min(1).max(365),
days: z.number().int().min(1).max(365)
})
);
@@ -99,7 +99,14 @@ export interface StatusHistoryResponse {
}
export function computeBuckets(
events: { entityType: string; entityId: number; orgId: string; status: string; timestamp: number; id: number }[],
events: {
entityType: string;
entityId: number;
orgId: string;
status: string;
timestamp: number;
id: number;
}[],
days: number
): { buckets: StatusHistoryDayBucket[]; totalDowntime: number } {
const nowSec = Math.floor(Date.now() / 1000);
@@ -121,7 +128,8 @@ export function computeBuckets(
const currentStatus = lastBeforeDay?.status ?? null;
const windows: { start: number; end: number | null; status: string }[] = [];
const windows: { start: number; end: number | null; status: string }[] =
[];
let dayDowntime = 0;
let dayDegradedTime = 0;
@@ -132,22 +140,21 @@ export function computeBuckets(
if (windowStatus !== null && windowStatus !== evt.status) {
const windowEnd = evt.timestamp;
const isDown =
windowStatus === "offline" ||
windowStatus === "unhealthy";
windowStatus === "offline" || windowStatus === "unhealthy";
const isDegraded = windowStatus === "degraded";
if (isDown) {
dayDowntime += windowEnd - windowStart;
windows.push({
start: windowStart,
end: windowEnd,
status: windowStatus,
status: windowStatus
});
} else if (isDegraded) {
dayDegradedTime += windowEnd - windowStart;
windows.push({
start: windowStart,
end: windowEnd,
status: windowStatus,
status: windowStatus
});
}
}
@@ -159,22 +166,21 @@ export function computeBuckets(
if (windowStatus !== null) {
const finalEnd = Math.min(dayEndSec, nowSec);
const isDown =
windowStatus === "offline" ||
windowStatus === "unhealthy";
windowStatus === "offline" || windowStatus === "unhealthy";
const isDegraded = windowStatus === "degraded";
if (isDown && finalEnd > windowStart) {
dayDowntime += finalEnd - windowStart;
windows.push({
start: windowStart,
end: finalEnd,
status: windowStatus,
status: windowStatus
});
} else if (isDegraded && finalEnd > windowStart) {
dayDegradedTime += finalEnd - windowStart;
windows.push({
start: windowStart,
end: finalEnd,
status: windowStatus,
status: windowStatus
});
}
}
@@ -225,7 +231,7 @@ export function computeBuckets(
uptimePercent: Math.round(uptimePct * 100) / 100,
totalDowntimeSeconds: dayDowntime,
downtimeWindows: windows,
status,
status
});
}

View File

@@ -19,7 +19,8 @@ import {
targetHealthCheck,
targets,
resources,
Transaction
Transaction,
logsDb
} from "@server/db";
import { eq } from "drizzle-orm";
import { invalidateStatusHistoryCache } from "@server/lib/statusHistory";
@@ -52,10 +53,10 @@ export async function fireHealthCheckHealthyAlert(
healthCheckTargetId?: number | null,
extra?: Record<string, unknown>,
send: boolean = true,
trx: Transaction | typeof db = db,
trx: Transaction | typeof db = db
): Promise<void> {
try {
await trx.insert(statusHistory).values({
await logsDb.insert(statusHistory).values({
entityType: "health_check",
entityId: healthCheckId,
orgId: orgId,
@@ -119,7 +120,7 @@ export async function fireHealthCheckUnhealthyAlert(
trx: Transaction | typeof db = db
): Promise<void> {
try {
await trx.insert(statusHistory).values({
await logsDb.insert(statusHistory).values({
entityType: "health_check",
entityId: healthCheckId,
orgId: orgId,
@@ -172,7 +173,7 @@ export async function fireHealthCheckUnknownAlert(
trx: Transaction | typeof db = db
): Promise<void> {
try {
await trx.insert(statusHistory).values({
await logsDb.insert(statusHistory).values({
entityType: "health_check",
entityId: healthCheckId,
orgId: orgId,
@@ -194,7 +195,12 @@ export async function fireHealthCheckUnknownAlert(
}
}
async function handleResource(orgId: string, healthCheckTargetId?: number | null, send: boolean = true, trx: Transaction | typeof db = db) {
async function handleResource(
orgId: string,
healthCheckTargetId?: number | null,
send: boolean = true,
trx: Transaction | typeof db = db
) {
if (!healthCheckTargetId) {
return;
}
@@ -222,7 +228,10 @@ async function handleResource(orgId: string, healthCheckTargetId?: number | null
const otherTargets = await trx
.select({ hcHealth: targetHealthCheck.hcHealth })
.from(targets)
.innerJoin(targetHealthCheck, eq(targetHealthCheck.targetId, targets.targetId))
.innerJoin(
targetHealthCheck,
eq(targetHealthCheck.targetId, targets.targetId)
)
.where(eq(targets.resourceId, resource.resourceId));
let health = "healthy";

View File

@@ -13,7 +13,7 @@
import logger from "@server/logger";
import { processAlerts } from "../processAlerts";
import { db, statusHistory, Transaction } from "@server/db";
import { db, logsDb, statusHistory, Transaction } from "@server/db";
import { invalidateStatusHistoryCache } from "@server/lib/statusHistory";
// ---------------------------------------------------------------------------
@@ -40,7 +40,7 @@ export async function fireResourceHealthyAlert(
trx: Transaction | typeof db = db
): Promise<void> {
try {
await trx.insert(statusHistory).values({
await logsDb.insert(statusHistory).values({
entityType: "resource",
entityId: resourceId,
orgId: orgId,
@@ -101,7 +101,7 @@ export async function fireResourceUnhealthyAlert(
trx: Transaction | typeof db = db
): Promise<void> {
try {
await trx.insert(statusHistory).values({
await logsDb.insert(statusHistory).values({
entityType: "resource",
entityId: resourceId,
orgId: orgId,
@@ -162,7 +162,7 @@ export async function fireResourceDegradedAlert(
trx: Transaction | typeof db = db
): Promise<void> {
try {
await trx.insert(statusHistory).values({
await logsDb.insert(statusHistory).values({
entityType: "resource",
entityId: resourceId,
orgId: orgId,
@@ -223,7 +223,7 @@ export async function fireResourceUnknownAlert(
trx: Transaction | typeof db = db
): Promise<void> {
try {
await trx.insert(statusHistory).values({
await logsDb.insert(statusHistory).values({
entityType: "resource",
entityId: resourceId,
orgId: orgId,

View File

@@ -13,7 +13,13 @@
import logger from "@server/logger";
import { processAlerts } from "../processAlerts";
import { db, sites, statusHistory, targetHealthCheck, Transaction } from "@server/db";
import {
db,
logsDb,
statusHistory,
targetHealthCheck,
Transaction
} from "@server/db";
import { invalidateStatusHistoryCache } from "@server/lib/statusHistory";
import { and, eq, inArray } from "drizzle-orm";
import { fireHealthCheckUnhealthyAlert } from "./healthCheckEvents";
@@ -41,7 +47,7 @@ export async function fireSiteOnlineAlert(
trx: Transaction | typeof db = db
): Promise<void> {
try {
await trx.insert(statusHistory).values({
await logsDb.insert(statusHistory).values({
entityType: "site",
entityId: siteId,
orgId: orgId,
@@ -97,7 +103,7 @@ export async function fireSiteOfflineAlert(
trx: Transaction | typeof db = db
): Promise<void> {
try {
await trx.insert(statusHistory).values({
await logsDb.insert(statusHistory).values({
entityType: "site",
entityId: siteId,
orgId: orgId,

View File

@@ -84,7 +84,7 @@ export async function registerNewt(
maxBatchSize: siteProvisioningKeys.maxBatchSize,
numUsed: siteProvisioningKeys.numUsed,
validUntil: siteProvisioningKeys.validUntil,
approveNewSites: siteProvisioningKeys.approveNewSites,
approveNewSites: siteProvisioningKeys.approveNewSites
})
.from(siteProvisioningKeys)
.innerJoin(
@@ -125,7 +125,10 @@ export async function registerNewt(
);
}
if (keyRecord.maxBatchSize && keyRecord.numUsed >= keyRecord.maxBatchSize) {
if (
keyRecord.maxBatchSize &&
keyRecord.numUsed >= keyRecord.maxBatchSize
) {
return next(
createHttpError(
HttpCode.UNAUTHORIZED,
@@ -134,7 +137,10 @@ export async function registerNewt(
);
}
if (keyRecord.validUntil && new Date(keyRecord.validUntil) < new Date()) {
if (
keyRecord.validUntil &&
new Date(keyRecord.validUntil) < new Date()
) {
return next(
createHttpError(
HttpCode.UNAUTHORIZED,
@@ -154,7 +160,10 @@ export async function registerNewt(
}
if (!org.subnet) {
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "Organization subnet not found")
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Organization subnet not found"
)
);
}
@@ -195,7 +204,6 @@ export async function registerNewt(
let newSiteId: number | undefined;
await db.transaction(async (trx) => {
const newClientAddress = await getNextAvailableClientSubnet(orgId);
if (!newClientAddress) {
return next(
@@ -219,11 +227,11 @@ export async function registerNewt(
address: clientAddress,
type: "newt",
dockerSocketEnabled: true,
status: keyRecord.approveNewSites ? "approved" : "pending",
status: keyRecord.approveNewSites ? "approved" : "pending"
})
.returning();
await trx.insert(statusHistory).values({
await logsDb.insert(statusHistory).values({
entityType: "site",
entityId: newSite.siteId,
orgId: orgId,

View File

@@ -351,7 +351,7 @@ export async function createSite(
})
.returning();
await trx.insert(statusHistory).values({
await logsDb.insert(statusHistory).values({
entityType: "site",
entityId: newSite.siteId,
orgId: orgId,