mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-08 20:16:50 +02:00
Compare commits
10 Commits
1de6e58eef
..
1.18.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 2957d6592d | |||
| de2a22aad8 | |||
| b96db4f133 | |||
| 2a29062659 | |||
| 98406f63af | |||
| 85415176ab | |||
| b81ae3d998 | |||
| 208289f498 | |||
| 8783c47a3c | |||
| 592ca64253 |
@@ -180,36 +180,41 @@ export async function rebuildClientAssociationsFromSiteResource(
|
|||||||
|
|
||||||
/////////// process the client-siteResource associations ///////////
|
/////////// process the client-siteResource associations ///////////
|
||||||
|
|
||||||
// get all of the clients associated with other resources in the same network,
|
// get all of the clients associated with other site resources that share
|
||||||
// joined through siteNetworks so we know which siteId each client belongs to
|
// any of the same sites as this site resource (via siteNetworks). We can't
|
||||||
const allUpdatedClientsFromOtherResourcesOnThisSite = siteResource.networkId
|
// simply filter by networkId since each site resource has its own network;
|
||||||
? await trx
|
// two site resources serving the same site typically belong to different
|
||||||
.select({
|
// networks that both happen to include the site through siteNetworks.
|
||||||
clientId: clientSiteResourcesAssociationsCache.clientId,
|
const sitesListSiteIds = sitesList.map((s) => s.siteId);
|
||||||
siteId: siteNetworks.siteId
|
const allUpdatedClientsFromOtherResourcesOnThisSite =
|
||||||
})
|
sitesListSiteIds.length > 0
|
||||||
.from(clientSiteResourcesAssociationsCache)
|
? await trx
|
||||||
.innerJoin(
|
.select({
|
||||||
siteResources,
|
clientId: clientSiteResourcesAssociationsCache.clientId,
|
||||||
eq(
|
siteId: siteNetworks.siteId
|
||||||
clientSiteResourcesAssociationsCache.siteResourceId,
|
})
|
||||||
siteResources.siteResourceId
|
.from(clientSiteResourcesAssociationsCache)
|
||||||
)
|
.innerJoin(
|
||||||
)
|
siteResources,
|
||||||
.innerJoin(
|
eq(
|
||||||
siteNetworks,
|
clientSiteResourcesAssociationsCache.siteResourceId,
|
||||||
eq(siteNetworks.networkId, siteResources.networkId)
|
siteResources.siteResourceId
|
||||||
)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(siteResources.networkId, siteResource.networkId),
|
|
||||||
ne(
|
|
||||||
siteResources.siteResourceId,
|
|
||||||
siteResource.siteResourceId
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
.innerJoin(
|
||||||
: [];
|
siteNetworks,
|
||||||
|
eq(siteNetworks.networkId, siteResources.networkId)
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
inArray(siteNetworks.siteId, sitesListSiteIds),
|
||||||
|
ne(
|
||||||
|
siteResources.siteResourceId,
|
||||||
|
siteResource.siteResourceId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
// Build a per-site map so the loop below can check by siteId rather than
|
// Build a per-site map so the loop below can check by siteId rather than
|
||||||
// across the entire network.
|
// across the entire network.
|
||||||
|
|||||||
+21
-15
@@ -1,5 +1,5 @@
|
|||||||
import { z } from "zod";
|
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 { and, eq, gte, asc } from "drizzle-orm";
|
||||||
import cache from "@server/lib/cache";
|
import cache from "@server/lib/cache";
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ export async function getCachedStatusHistory(
|
|||||||
const nowSec = Math.floor(Date.now() / 1000);
|
const nowSec = Math.floor(Date.now() / 1000);
|
||||||
const startSec = nowSec - days * 86400;
|
const startSec = nowSec - days * 86400;
|
||||||
|
|
||||||
const events = await db
|
const events = await logsDb
|
||||||
.select()
|
.select()
|
||||||
.from(statusHistory)
|
.from(statusHistory)
|
||||||
.where(
|
.where(
|
||||||
@@ -74,11 +74,11 @@ export const statusHistoryQuerySchema = z
|
|||||||
days: z
|
days: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
.transform((v) => (v ? parseInt(v, 10) : 90)),
|
.transform((v) => (v ? parseInt(v, 10) : 90))
|
||||||
})
|
})
|
||||||
.pipe(
|
.pipe(
|
||||||
z.object({
|
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(
|
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
|
days: number
|
||||||
): { buckets: StatusHistoryDayBucket[]; totalDowntime: number } {
|
): { buckets: StatusHistoryDayBucket[]; totalDowntime: number } {
|
||||||
const nowSec = Math.floor(Date.now() / 1000);
|
const nowSec = Math.floor(Date.now() / 1000);
|
||||||
@@ -121,7 +128,8 @@ export function computeBuckets(
|
|||||||
|
|
||||||
const currentStatus = lastBeforeDay?.status ?? null;
|
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 dayDowntime = 0;
|
||||||
let dayDegradedTime = 0;
|
let dayDegradedTime = 0;
|
||||||
|
|
||||||
@@ -132,22 +140,21 @@ export function computeBuckets(
|
|||||||
if (windowStatus !== null && windowStatus !== evt.status) {
|
if (windowStatus !== null && windowStatus !== evt.status) {
|
||||||
const windowEnd = evt.timestamp;
|
const windowEnd = evt.timestamp;
|
||||||
const isDown =
|
const isDown =
|
||||||
windowStatus === "offline" ||
|
windowStatus === "offline" || windowStatus === "unhealthy";
|
||||||
windowStatus === "unhealthy";
|
|
||||||
const isDegraded = windowStatus === "degraded";
|
const isDegraded = windowStatus === "degraded";
|
||||||
if (isDown) {
|
if (isDown) {
|
||||||
dayDowntime += windowEnd - windowStart;
|
dayDowntime += windowEnd - windowStart;
|
||||||
windows.push({
|
windows.push({
|
||||||
start: windowStart,
|
start: windowStart,
|
||||||
end: windowEnd,
|
end: windowEnd,
|
||||||
status: windowStatus,
|
status: windowStatus
|
||||||
});
|
});
|
||||||
} else if (isDegraded) {
|
} else if (isDegraded) {
|
||||||
dayDegradedTime += windowEnd - windowStart;
|
dayDegradedTime += windowEnd - windowStart;
|
||||||
windows.push({
|
windows.push({
|
||||||
start: windowStart,
|
start: windowStart,
|
||||||
end: windowEnd,
|
end: windowEnd,
|
||||||
status: windowStatus,
|
status: windowStatus
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,22 +166,21 @@ export function computeBuckets(
|
|||||||
if (windowStatus !== null) {
|
if (windowStatus !== null) {
|
||||||
const finalEnd = Math.min(dayEndSec, nowSec);
|
const finalEnd = Math.min(dayEndSec, nowSec);
|
||||||
const isDown =
|
const isDown =
|
||||||
windowStatus === "offline" ||
|
windowStatus === "offline" || windowStatus === "unhealthy";
|
||||||
windowStatus === "unhealthy";
|
|
||||||
const isDegraded = windowStatus === "degraded";
|
const isDegraded = windowStatus === "degraded";
|
||||||
if (isDown && finalEnd > windowStart) {
|
if (isDown && finalEnd > windowStart) {
|
||||||
dayDowntime += finalEnd - windowStart;
|
dayDowntime += finalEnd - windowStart;
|
||||||
windows.push({
|
windows.push({
|
||||||
start: windowStart,
|
start: windowStart,
|
||||||
end: finalEnd,
|
end: finalEnd,
|
||||||
status: windowStatus,
|
status: windowStatus
|
||||||
});
|
});
|
||||||
} else if (isDegraded && finalEnd > windowStart) {
|
} else if (isDegraded && finalEnd > windowStart) {
|
||||||
dayDegradedTime += finalEnd - windowStart;
|
dayDegradedTime += finalEnd - windowStart;
|
||||||
windows.push({
|
windows.push({
|
||||||
start: windowStart,
|
start: windowStart,
|
||||||
end: finalEnd,
|
end: finalEnd,
|
||||||
status: windowStatus,
|
status: windowStatus
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,7 +231,7 @@ export function computeBuckets(
|
|||||||
uptimePercent: Math.round(uptimePct * 100) / 100,
|
uptimePercent: Math.round(uptimePct * 100) / 100,
|
||||||
totalDowntimeSeconds: dayDowntime,
|
totalDowntimeSeconds: dayDowntime,
|
||||||
downtimeWindows: windows,
|
downtimeWindows: windows,
|
||||||
status,
|
status
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ import {
|
|||||||
targetHealthCheck,
|
targetHealthCheck,
|
||||||
targets,
|
targets,
|
||||||
resources,
|
resources,
|
||||||
Transaction
|
Transaction,
|
||||||
|
logsDb
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { invalidateStatusHistoryCache } from "@server/lib/statusHistory";
|
import { invalidateStatusHistoryCache } from "@server/lib/statusHistory";
|
||||||
@@ -52,10 +53,10 @@ export async function fireHealthCheckHealthyAlert(
|
|||||||
healthCheckTargetId?: number | null,
|
healthCheckTargetId?: number | null,
|
||||||
extra?: Record<string, unknown>,
|
extra?: Record<string, unknown>,
|
||||||
send: boolean = true,
|
send: boolean = true,
|
||||||
trx: Transaction | typeof db = db,
|
trx: Transaction | typeof db = db
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await trx.insert(statusHistory).values({
|
await logsDb.insert(statusHistory).values({
|
||||||
entityType: "health_check",
|
entityType: "health_check",
|
||||||
entityId: healthCheckId,
|
entityId: healthCheckId,
|
||||||
orgId: orgId,
|
orgId: orgId,
|
||||||
@@ -119,7 +120,7 @@ export async function fireHealthCheckUnhealthyAlert(
|
|||||||
trx: Transaction | typeof db = db
|
trx: Transaction | typeof db = db
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await trx.insert(statusHistory).values({
|
await logsDb.insert(statusHistory).values({
|
||||||
entityType: "health_check",
|
entityType: "health_check",
|
||||||
entityId: healthCheckId,
|
entityId: healthCheckId,
|
||||||
orgId: orgId,
|
orgId: orgId,
|
||||||
@@ -172,7 +173,7 @@ export async function fireHealthCheckUnknownAlert(
|
|||||||
trx: Transaction | typeof db = db
|
trx: Transaction | typeof db = db
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await trx.insert(statusHistory).values({
|
await logsDb.insert(statusHistory).values({
|
||||||
entityType: "health_check",
|
entityType: "health_check",
|
||||||
entityId: healthCheckId,
|
entityId: healthCheckId,
|
||||||
orgId: orgId,
|
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) {
|
if (!healthCheckTargetId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -222,7 +228,10 @@ async function handleResource(orgId: string, healthCheckTargetId?: number | null
|
|||||||
const otherTargets = await trx
|
const otherTargets = await trx
|
||||||
.select({ hcHealth: targetHealthCheck.hcHealth })
|
.select({ hcHealth: targetHealthCheck.hcHealth })
|
||||||
.from(targets)
|
.from(targets)
|
||||||
.innerJoin(targetHealthCheck, eq(targetHealthCheck.targetId, targets.targetId))
|
.innerJoin(
|
||||||
|
targetHealthCheck,
|
||||||
|
eq(targetHealthCheck.targetId, targets.targetId)
|
||||||
|
)
|
||||||
.where(eq(targets.resourceId, resource.resourceId));
|
.where(eq(targets.resourceId, resource.resourceId));
|
||||||
|
|
||||||
let health = "healthy";
|
let health = "healthy";
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { processAlerts } from "../processAlerts";
|
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";
|
import { invalidateStatusHistoryCache } from "@server/lib/statusHistory";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -40,7 +40,7 @@ export async function fireResourceHealthyAlert(
|
|||||||
trx: Transaction | typeof db = db
|
trx: Transaction | typeof db = db
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await trx.insert(statusHistory).values({
|
await logsDb.insert(statusHistory).values({
|
||||||
entityType: "resource",
|
entityType: "resource",
|
||||||
entityId: resourceId,
|
entityId: resourceId,
|
||||||
orgId: orgId,
|
orgId: orgId,
|
||||||
@@ -101,7 +101,7 @@ export async function fireResourceUnhealthyAlert(
|
|||||||
trx: Transaction | typeof db = db
|
trx: Transaction | typeof db = db
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await trx.insert(statusHistory).values({
|
await logsDb.insert(statusHistory).values({
|
||||||
entityType: "resource",
|
entityType: "resource",
|
||||||
entityId: resourceId,
|
entityId: resourceId,
|
||||||
orgId: orgId,
|
orgId: orgId,
|
||||||
@@ -162,7 +162,7 @@ export async function fireResourceDegradedAlert(
|
|||||||
trx: Transaction | typeof db = db
|
trx: Transaction | typeof db = db
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await trx.insert(statusHistory).values({
|
await logsDb.insert(statusHistory).values({
|
||||||
entityType: "resource",
|
entityType: "resource",
|
||||||
entityId: resourceId,
|
entityId: resourceId,
|
||||||
orgId: orgId,
|
orgId: orgId,
|
||||||
@@ -223,7 +223,7 @@ export async function fireResourceUnknownAlert(
|
|||||||
trx: Transaction | typeof db = db
|
trx: Transaction | typeof db = db
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await trx.insert(statusHistory).values({
|
await logsDb.insert(statusHistory).values({
|
||||||
entityType: "resource",
|
entityType: "resource",
|
||||||
entityId: resourceId,
|
entityId: resourceId,
|
||||||
orgId: orgId,
|
orgId: orgId,
|
||||||
|
|||||||
@@ -13,7 +13,13 @@
|
|||||||
|
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { processAlerts } from "../processAlerts";
|
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 { invalidateStatusHistoryCache } from "@server/lib/statusHistory";
|
||||||
import { and, eq, inArray } from "drizzle-orm";
|
import { and, eq, inArray } from "drizzle-orm";
|
||||||
import { fireHealthCheckUnhealthyAlert } from "./healthCheckEvents";
|
import { fireHealthCheckUnhealthyAlert } from "./healthCheckEvents";
|
||||||
@@ -41,7 +47,7 @@ export async function fireSiteOnlineAlert(
|
|||||||
trx: Transaction | typeof db = db
|
trx: Transaction | typeof db = db
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await trx.insert(statusHistory).values({
|
await logsDb.insert(statusHistory).values({
|
||||||
entityType: "site",
|
entityType: "site",
|
||||||
entityId: siteId,
|
entityId: siteId,
|
||||||
orgId: orgId,
|
orgId: orgId,
|
||||||
@@ -97,7 +103,7 @@ export async function fireSiteOfflineAlert(
|
|||||||
trx: Transaction | typeof db = db
|
trx: Transaction | typeof db = db
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await trx.insert(statusHistory).values({
|
await logsDb.insert(statusHistory).values({
|
||||||
entityType: "site",
|
entityType: "site",
|
||||||
entityId: siteId,
|
entityId: siteId,
|
||||||
orgId: orgId,
|
orgId: orgId,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db, statusHistory } from "@server/db";
|
import { db, logsDb, statusHistory } from "@server/db";
|
||||||
import {
|
import {
|
||||||
siteProvisioningKeys,
|
siteProvisioningKeys,
|
||||||
siteProvisioningKeyOrg,
|
siteProvisioningKeyOrg,
|
||||||
@@ -84,7 +84,7 @@ export async function registerNewt(
|
|||||||
maxBatchSize: siteProvisioningKeys.maxBatchSize,
|
maxBatchSize: siteProvisioningKeys.maxBatchSize,
|
||||||
numUsed: siteProvisioningKeys.numUsed,
|
numUsed: siteProvisioningKeys.numUsed,
|
||||||
validUntil: siteProvisioningKeys.validUntil,
|
validUntil: siteProvisioningKeys.validUntil,
|
||||||
approveNewSites: siteProvisioningKeys.approveNewSites,
|
approveNewSites: siteProvisioningKeys.approveNewSites
|
||||||
})
|
})
|
||||||
.from(siteProvisioningKeys)
|
.from(siteProvisioningKeys)
|
||||||
.innerJoin(
|
.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(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.UNAUTHORIZED,
|
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(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.UNAUTHORIZED,
|
HttpCode.UNAUTHORIZED,
|
||||||
@@ -154,7 +160,10 @@ export async function registerNewt(
|
|||||||
}
|
}
|
||||||
if (!org.subnet) {
|
if (!org.subnet) {
|
||||||
return next(
|
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;
|
let newSiteId: number | undefined;
|
||||||
|
|
||||||
await db.transaction(async (trx) => {
|
await db.transaction(async (trx) => {
|
||||||
|
|
||||||
const newClientAddress = await getNextAvailableClientSubnet(orgId);
|
const newClientAddress = await getNextAvailableClientSubnet(orgId);
|
||||||
if (!newClientAddress) {
|
if (!newClientAddress) {
|
||||||
return next(
|
return next(
|
||||||
@@ -219,11 +227,11 @@ export async function registerNewt(
|
|||||||
address: clientAddress,
|
address: clientAddress,
|
||||||
type: "newt",
|
type: "newt",
|
||||||
dockerSocketEnabled: true,
|
dockerSocketEnabled: true,
|
||||||
status: keyRecord.approveNewSites ? "approved" : "pending",
|
status: keyRecord.approveNewSites ? "approved" : "pending"
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
await trx.insert(statusHistory).values({
|
await logsDb.insert(statusHistory).values({
|
||||||
entityType: "site",
|
entityType: "site",
|
||||||
entityId: newSite.siteId,
|
entityId: newSite.siteId,
|
||||||
orgId: orgId,
|
orgId: orgId,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { clients, db, exitNodes, statusHistory } from "@server/db";
|
import { clients, db, exitNodes, logsDb, statusHistory } from "@server/db";
|
||||||
import { roles, userSites, sites, roleSites, Site, orgs } from "@server/db";
|
import { roles, userSites, sites, roleSites, Site, orgs } from "@server/db";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
@@ -351,7 +351,7 @@ export async function createSite(
|
|||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
await trx.insert(statusHistory).values({
|
await logsDb.insert(statusHistory).values({
|
||||||
entityType: "site",
|
entityType: "site",
|
||||||
entityId: newSite.siteId,
|
entityId: newSite.siteId,
|
||||||
orgId: orgId,
|
orgId: orgId,
|
||||||
|
|||||||
@@ -67,22 +67,9 @@ export async function deleteSiteResource(
|
|||||||
// Delete the site resource
|
// Delete the site resource
|
||||||
const [removedSiteResource] = await trx
|
const [removedSiteResource] = await trx
|
||||||
.delete(siteResources)
|
.delete(siteResources)
|
||||||
.where(and(eq(siteResources.siteResourceId, siteResourceId)))
|
.where(eq(siteResources.siteResourceId, siteResourceId))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
// not sure why this is here...
|
|
||||||
// const [newt] = await trx
|
|
||||||
// .select()
|
|
||||||
// .from(newts)
|
|
||||||
// .where(eq(newts.siteId, removedSiteResource.siteId))
|
|
||||||
// .limit(1);
|
|
||||||
|
|
||||||
// if (!newt) {
|
|
||||||
// return next(
|
|
||||||
// createHttpError(HttpCode.NOT_FOUND, "Newt not found")
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
await rebuildClientAssociationsFromSiteResource(
|
await rebuildClientAssociationsFromSiteResource(
|
||||||
removedSiteResource,
|
removedSiteResource,
|
||||||
trx
|
trx
|
||||||
|
|||||||
@@ -67,11 +67,12 @@ export default async function migration() {
|
|||||||
FROM "siteResources" sr
|
FROM "siteResources" sr
|
||||||
WHERE sr."siteId" IS NOT NULL`
|
WHERE sr."siteId" IS NOT NULL`
|
||||||
);
|
);
|
||||||
const existingSiteResourcesForNetwork = siteResourcesForNetworkQuery.rows as {
|
const existingSiteResourcesForNetwork =
|
||||||
siteResourceId: number;
|
siteResourcesForNetworkQuery.rows as {
|
||||||
orgId: string;
|
siteResourceId: number;
|
||||||
siteId: number;
|
orgId: string;
|
||||||
}[];
|
siteId: number;
|
||||||
|
}[];
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`Found ${existingSiteResourcesForNetwork.length} existing siteResource(s) to migrate to networks`
|
`Found ${existingSiteResourcesForNetwork.length} existing siteResource(s) to migrate to networks`
|
||||||
@@ -446,10 +447,7 @@ export default async function migration() {
|
|||||||
`Migrated ${existingHealthChecks.length} targetHealthCheck row(s) with corrected IDs`
|
`Migrated ${existingHealthChecks.length} targetHealthCheck row(s) with corrected IDs`
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(
|
console.error("Error while migrating targetHealthCheck rows:", e);
|
||||||
"Error while migrating targetHealthCheck rows:",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -493,5 +491,90 @@ export default async function migration() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Seed statusHistory for all existing sites
|
||||||
|
try {
|
||||||
|
const sitesQuery = await db.execute(
|
||||||
|
sql`SELECT "siteId", "orgId", "online" FROM "sites"`
|
||||||
|
);
|
||||||
|
const allSites = sitesQuery.rows as {
|
||||||
|
siteId: number;
|
||||||
|
orgId: string;
|
||||||
|
online: boolean;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
for (const site of allSites) {
|
||||||
|
await db.execute(sql`
|
||||||
|
INSERT INTO "statusHistory" ("entityType", "entityId", "orgId", "status", "timestamp")
|
||||||
|
VALUES ('site', ${site.siteId}, ${site.orgId}, ${site.online ? "online" : "offline"}, ${now})
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Seeded statusHistory for ${allSites.length} site(s)`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error while seeding statusHistory for sites:", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seed statusHistory for all existing resources
|
||||||
|
try {
|
||||||
|
const resourcesQuery = await db.execute(
|
||||||
|
sql`SELECT "resourceId", "orgId", "health" FROM "resources"`
|
||||||
|
);
|
||||||
|
const allResources = resourcesQuery.rows as {
|
||||||
|
resourceId: number;
|
||||||
|
orgId: string;
|
||||||
|
health: string | null;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
for (const resource of allResources) {
|
||||||
|
await db.execute(sql`
|
||||||
|
INSERT INTO "statusHistory" ("entityType", "entityId", "orgId", "status", "timestamp")
|
||||||
|
VALUES ('resource', ${resource.resourceId}, ${resource.orgId}, ${resource.health ?? "unknown"}, ${now})
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Seeded statusHistory for ${allResources.length} resource(s)`
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error while seeding statusHistory for resources:", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seed statusHistory for all existing health checks
|
||||||
|
try {
|
||||||
|
const healthChecksQuery = await db.execute(
|
||||||
|
sql`SELECT "targetHealthCheckId", "orgId", "hcHealth" FROM "targetHealthCheck"`
|
||||||
|
);
|
||||||
|
const allHealthChecks = healthChecksQuery.rows as {
|
||||||
|
targetHealthCheckId: number;
|
||||||
|
orgId: string;
|
||||||
|
hcHealth: string | null;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
for (const hc of allHealthChecks) {
|
||||||
|
await db.execute(sql`
|
||||||
|
INSERT INTO "statusHistory" ("entityType", "entityId", "orgId", "status", "timestamp")
|
||||||
|
VALUES ('health_check', ${hc.targetHealthCheckId}, ${hc.orgId}, ${hc.hcHealth ?? "unknown"}, ${now})
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Seeded statusHistory for ${allHealthChecks.length} health check(s)`
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(
|
||||||
|
"Error while seeding statusHistory for health checks:",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`${version} migration complete`);
|
console.log(`${version} migration complete`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -340,7 +340,6 @@ export default async function migration() {
|
|||||||
ALTER TABLE 'resources' ADD 'wildcard' integer DEFAULT false NOT NULL;
|
ALTER TABLE 'resources' ADD 'wildcard' integer DEFAULT false NOT NULL;
|
||||||
`
|
`
|
||||||
).run();
|
).run();
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
db.pragma("foreign_keys = ON");
|
db.pragma("foreign_keys = ON");
|
||||||
@@ -364,7 +363,11 @@ export default async function migration() {
|
|||||||
const result = insertNetwork.run("resource", sr.orgId);
|
const result = insertNetwork.run("resource", sr.orgId);
|
||||||
const networkId = result.lastInsertRowid as number;
|
const networkId = result.lastInsertRowid as number;
|
||||||
insertSiteNetwork.run(sr.siteId, networkId);
|
insertSiteNetwork.run(sr.siteId, networkId);
|
||||||
updateSiteResource.run(networkId, networkId, sr.siteResourceId);
|
updateSiteResource.run(
|
||||||
|
networkId,
|
||||||
|
networkId,
|
||||||
|
sr.siteResourceId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -454,6 +457,87 @@ export default async function migration() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Migrated database`);
|
console.log(`Migrated database`);
|
||||||
|
|
||||||
|
// Seed statusHistory for all existing sites
|
||||||
|
const allSites = db
|
||||||
|
.prepare(`SELECT "siteId", "orgId", "online" FROM 'sites'`)
|
||||||
|
.all() as { siteId: number; orgId: string; online: number }[];
|
||||||
|
|
||||||
|
const insertSiteHistory = db.prepare(
|
||||||
|
`INSERT INTO 'statusHistory' ("entityType", "entityId", "orgId", "status", "timestamp") VALUES (?, ?, ?, ?, ?)`
|
||||||
|
);
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
const seedSites = db.transaction(() => {
|
||||||
|
for (const site of allSites) {
|
||||||
|
insertSiteHistory.run(
|
||||||
|
"site",
|
||||||
|
site.siteId,
|
||||||
|
site.orgId,
|
||||||
|
site.online ? "online" : "offline",
|
||||||
|
now
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
seedSites();
|
||||||
|
console.log(`Seeded statusHistory for ${allSites.length} site(s)`);
|
||||||
|
|
||||||
|
// Seed statusHistory for all existing resources
|
||||||
|
const allResources = db
|
||||||
|
.prepare(`SELECT "resourceId", "orgId", "health" FROM 'resources'`)
|
||||||
|
.all() as {
|
||||||
|
resourceId: number;
|
||||||
|
orgId: string;
|
||||||
|
health: string | null;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
const insertResourceHistory = db.prepare(
|
||||||
|
`INSERT INTO 'statusHistory' ("entityType", "entityId", "orgId", "status", "timestamp") VALUES (?, ?, ?, ?, ?)`
|
||||||
|
);
|
||||||
|
const seedResources = db.transaction(() => {
|
||||||
|
for (const resource of allResources) {
|
||||||
|
insertResourceHistory.run(
|
||||||
|
"resource",
|
||||||
|
resource.resourceId,
|
||||||
|
resource.orgId,
|
||||||
|
resource.health ?? "unknown",
|
||||||
|
now
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
seedResources();
|
||||||
|
console.log(
|
||||||
|
`Seeded statusHistory for ${allResources.length} resource(s)`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Seed statusHistory for all existing health checks
|
||||||
|
const allHealthChecks = db
|
||||||
|
.prepare(
|
||||||
|
`SELECT "targetHealthCheckId", "orgId", "hcHealth" FROM 'targetHealthCheck'`
|
||||||
|
)
|
||||||
|
.all() as {
|
||||||
|
targetHealthCheckId: number;
|
||||||
|
orgId: string;
|
||||||
|
hcHealth: string | null;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
const insertHealthCheckHistory = db.prepare(
|
||||||
|
`INSERT INTO 'statusHistory' ("entityType", "entityId", "orgId", "status", "timestamp") VALUES (?, ?, ?, ?, ?)`
|
||||||
|
);
|
||||||
|
const seedHealthChecks = db.transaction(() => {
|
||||||
|
for (const hc of allHealthChecks) {
|
||||||
|
insertHealthCheckHistory.run(
|
||||||
|
"health_check",
|
||||||
|
hc.targetHealthCheckId,
|
||||||
|
hc.orgId,
|
||||||
|
hc.hcHealth ?? "unknown",
|
||||||
|
now
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
seedHealthChecks();
|
||||||
|
console.log(
|
||||||
|
`Seeded statusHistory for ${allHealthChecks.length} health check(s)`
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Failed to migrate db:", e);
|
console.log("Failed to migrate db:", e);
|
||||||
throw e;
|
throw e;
|
||||||
|
|||||||
@@ -40,10 +40,14 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
|||||||
<InfoSection>
|
<InfoSection>
|
||||||
<InfoSectionTitle>URL</InfoSectionTitle>
|
<InfoSectionTitle>URL</InfoSectionTitle>
|
||||||
<InfoSectionContent>
|
<InfoSectionContent>
|
||||||
<CopyToClipboard
|
{resource.wildcard ? (
|
||||||
text={fullUrl}
|
<span>{fullUrl}</span>
|
||||||
isLink={true}
|
) : (
|
||||||
/>
|
<CopyToClipboard
|
||||||
|
text={fullUrl}
|
||||||
|
isLink={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</InfoSectionContent>
|
</InfoSectionContent>
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
|
|||||||
Reference in New Issue
Block a user