Add resource column to hc and remove —

This commit is contained in:
Owen
2026-04-16 17:42:30 -07:00
parent b958537f3e
commit d6c15c8b81
60 changed files with 257 additions and 211 deletions

View File

@@ -189,9 +189,11 @@ export const targetHealthCheck = pgTable("targetHealthCheck", {
targetId: integer("targetId").references(() => targets.targetId, {
onDelete: "cascade"
}),
orgId: varchar("orgId").references(() => orgs.orgId, {
onDelete: "cascade"
}),
orgId: varchar("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
name: varchar("name"),
hcEnabled: boolean("hcEnabled").notNull().default(false),
hcPath: varchar("hcPath"),

View File

@@ -209,11 +209,14 @@ export const targetHealthCheck = sqliteTable("targetHealthCheck", {
targetHealthCheckId: integer("targetHealthCheckId").primaryKey({
autoIncrement: true
}),
targetId: integer("targetId")
.references(() => targets.targetId, { onDelete: "cascade" }),
orgId: text("orgId").references(() => orgs.orgId, {
targetId: integer("targetId").references(() => targets.targetId, {
onDelete: "cascade"
}),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
name: text("name"),
hcEnabled: integer("hcEnabled", { mode: "boolean" })
.notNull()
@@ -294,7 +297,7 @@ export const siteResources = sqliteTable("siteResources", {
onDelete: "set null"
}),
subdomain: text("subdomain"),
fullDomain: text("fullDomain"),
fullDomain: text("fullDomain")
});
export const networks = sqliteTable("networks", {

View File

@@ -83,7 +83,7 @@ function formatDataItems(
.replace(/([A-Z])/g, " $1")
.replace(/^./, (s) => s.toUpperCase())
.trim(),
value: String(value ?? "")
value: String(value ?? "-")
}));
}
@@ -137,4 +137,4 @@ export const AlertNotification = ({ eventType, orgId, data }: Props) => {
);
};
export default AlertNotification;
export default AlertNotification;

View File

@@ -32,7 +32,7 @@ export const EnterpriseEditionKeyGenerated = ({
}: EnterpriseEditionKeyGeneratedProps) => {
const previewText = personalUseOnly
? "Your Enterprise Edition key for personal use is ready"
: "Thank you for your purchase your Enterprise Edition key is ready";
: "Thank you for your purchase - your Enterprise Edition key is ready";
return (
<Html>

View File

@@ -825,7 +825,7 @@ async function handleSubnetProxyTargetUpdates(
// Check if this client still has access to another resource
// on this specific site with the same destination. We scope
// by siteId (via siteNetworks) rather than networkId because
// removePeerData operates per-site a resource on a different
// removePeerData operates per-site - a resource on a different
// site sharing the same network should not block removal here.
const destinationStillInUse = await trx
.select()
@@ -980,7 +980,7 @@ export async function rebuildClientAssociationsFromClient(
)
: [];
// Group by siteId for site-level associations look up via siteNetworks since
// Group by siteId for site-level associations - look up via siteNetworks since
// siteResources no longer carries a direct siteId column.
const networkIds = Array.from(
new Set(
@@ -1459,7 +1459,7 @@ async function handleMessagesForClientResources(
// Check if this client still has access to another resource
// on this specific site with the same destination. We scope
// by siteId (via siteNetworks) rather than networkId because
// removePeerData operates per-site a resource on a different
// removePeerData operates per-site - a resource on a different
// site sharing the same network should not block removal here.
const destinationStillInUse = await trx
.select()

View File

@@ -1011,7 +1011,7 @@ export class TraefikConfigManager {
);
if (!isUnused) {
// Domain is still active remove from pending deletion if it was queued
// Domain is still active - remove from pending deletion if it was queued
if (this.pendingDeletion.has(dirName)) {
logger.info(
`Certificate ${dirName} is active again, cancelling pending deletion`
@@ -1021,7 +1021,7 @@ export class TraefikConfigManager {
continue;
}
// Domain is unused add to pending deletion or decrement its counter
// Domain is unused - add to pending deletion or decrement its counter
if (!this.pendingDeletion.has(dirName)) {
const graceCycles = 3;
logger.info(
@@ -1036,7 +1036,7 @@ export class TraefikConfigManager {
);
this.pendingDeletion.set(dirName, remaining);
} else {
// Grace period expired actually delete now
// Grace period expired - actually delete now
this.pendingDeletion.delete(dirName);
const domainDir = path.join(certsPath, dirName);

View File

@@ -24,7 +24,7 @@ function encodePath(path: string | null | undefined): string {
/**
* Exact replica of the OLD key computation from upstream main.
* Uses sanitize() for paths this is what had the collision bug.
* Uses sanitize() for paths - this is what had the collision bug.
*/
function oldKeyComputation(
resourceId: number,
@@ -44,7 +44,7 @@ function oldKeyComputation(
/**
* Replica of the NEW key computation from our fix.
* Uses encodePath() for paths collision-free.
* Uses encodePath() for paths - collision-free.
*/
function newKeyComputation(
resourceId: number,
@@ -195,11 +195,11 @@ function runTests() {
true,
"/a/b and /a-b MUST have different keys"
);
console.log(" PASS: collision fix /a/b vs /a-b have different keys");
console.log(" PASS: collision fix - /a/b vs /a-b have different keys");
passed++;
}
// Test 9: demonstrate the old bug old code maps /a/b and /a-b to same key
// Test 9: demonstrate the old bug - old code maps /a/b and /a-b to same key
{
const oldKeyAB = oldKeyComputation(1, "/a/b", "prefix", null, null);
const oldKeyDash = oldKeyComputation(1, "/a-b", "prefix", null, null);
@@ -208,11 +208,11 @@ function runTests() {
oldKeyDash,
"old code MUST have this collision (confirms the bug exists)"
);
console.log(" PASS: confirmed old code bug /a/b and /a-b collided");
console.log(" PASS: confirmed old code bug - /a/b and /a-b collided");
passed++;
}
// Test 10: /api/v1 and /api-v1 old code collision, new code fixes it
// Test 10: /api/v1 and /api-v1 - old code collision, new code fixes it
{
const oldKey1 = oldKeyComputation(1, "/api/v1", "prefix", null, null);
const oldKey2 = oldKeyComputation(1, "/api-v1", "prefix", null, null);
@@ -229,11 +229,11 @@ function runTests() {
true,
"new code must separate /api/v1 and /api-v1"
);
console.log(" PASS: collision fix /api/v1 vs /api-v1");
console.log(" PASS: collision fix - /api/v1 vs /api-v1");
passed++;
}
// Test 11: /app.v2 and /app/v2 and /app-v2 three-way collision fixed
// Test 11: /app.v2 and /app/v2 and /app-v2 - three-way collision fixed
{
const a = newKeyComputation(1, "/app.v2", "prefix", null, null);
const b = newKeyComputation(1, "/app/v2", "prefix", null, null);
@@ -245,14 +245,14 @@ function runTests() {
"three paths must produce three unique keys"
);
console.log(
" PASS: collision fix three-way /app.v2, /app/v2, /app-v2"
" PASS: collision fix - three-way /app.v2, /app/v2, /app-v2"
);
passed++;
}
// ── Edge cases ───────────────────────────────────────────────────
// Test 12: same path in different resources always separate
// Test 12: same path in different resources - always separate
{
const key1 = newKeyComputation(1, "/api", "prefix", null, null);
const key2 = newKeyComputation(2, "/api", "prefix", null, null);
@@ -261,11 +261,11 @@ function runTests() {
true,
"different resources with same path must have different keys"
);
console.log(" PASS: edge case same path, different resources");
console.log(" PASS: edge case - same path, different resources");
passed++;
}
// Test 13: same resource, different pathMatchType separate keys
// Test 13: same resource, different pathMatchType - separate keys
{
const exact = newKeyComputation(1, "/api", "exact", null, null);
const prefix = newKeyComputation(1, "/api", "prefix", null, null);
@@ -274,11 +274,11 @@ function runTests() {
true,
"exact vs prefix must have different keys"
);
console.log(" PASS: edge case same path, different match types");
console.log(" PASS: edge case - same path, different match types");
passed++;
}
// Test 14: same resource and path, different rewrite config separate keys
// Test 14: same resource and path, different rewrite config - separate keys
{
const noRewrite = newKeyComputation(1, "/api", "prefix", null, null);
const withRewrite = newKeyComputation(
@@ -293,7 +293,7 @@ function runTests() {
true,
"with vs without rewrite must have different keys"
);
console.log(" PASS: edge case same path, different rewrite config");
console.log(" PASS: edge case - same path, different rewrite config");
passed++;
}
@@ -308,7 +308,7 @@ function runTests() {
paths.length,
"special URL chars must produce unique keys"
);
console.log(" PASS: edge case special URL characters in paths");
console.log(" PASS: edge case - special URL characters in paths");
passed++;
}

View File

@@ -144,7 +144,7 @@ async function pushCertUpdateToAffectedNewts(
await cache.del(`cert:${resource.fullDomain}`);
}
// Generate target once same cert applies to all sites for this resource
// Generate target once - same cert applies to all sites for this resource
const newTargets = await generateSubnetProxyTargetV2(
resource,
resourceClients
@@ -157,7 +157,7 @@ async function pushCertUpdateToAffectedNewts(
continue;
}
// Construct the old targets same routing shape but with the previous cert/key.
// Construct the old targets - same routing shape but with the previous cert/key.
// The newt only uses destPrefix/sourcePrefixes for removal, but we keep the
// semantics correct so the update message accurately reflects what changed.
const oldTargets: SubnetProxyTargetV2[] = newTargets.map((t) => ({

View File

@@ -153,7 +153,7 @@ export async function flushConnectionLogToDb(): Promise<void> {
);
}
// Stop processing further batches from this snapshot they will
// Stop processing further batches from this snapshot - they will
// be picked up via the re-queued records on the next flush.
const remaining = snapshot.slice(i + INSERT_BATCH_SIZE);
if (remaining.length > 0) {
@@ -180,7 +180,7 @@ const flushTimer = setInterval(async () => {
}, FLUSH_INTERVAL_MS);
// Calling unref() means this timer will not keep the Node.js event loop alive
// on its own the process can still exit normally when there is no other work
// on its own - the process can still exit normally when there is no other work
// left. The graceful-shutdown path will call flushConnectionLogToDb() explicitly
// before process.exit(), so no data is lost.
flushTimer.unref();
@@ -223,7 +223,7 @@ export function logConnectionAudit(record: ConnectionLogRecord): void {
buffer.push(record);
if (buffer.length >= MAX_BUFFERED_RECORDS) {
// Fire and forget errors are handled inside flushConnectionLogToDb
// Fire and forget - errors are handled inside flushConnectionLogToDb
flushConnectionLogToDb().catch((error) => {
logger.error(
"Unexpected error during size-triggered connection log flush:",
@@ -231,4 +231,4 @@ export function logConnectionAudit(record: ConnectionLogRecord): void {
);
});
}
}
}

View File

@@ -37,7 +37,7 @@ const DEFAULT_FORMAT: PayloadFormat = "json_array";
*
* **Payload formats** (controlled by `config.format`):
*
* - `json_array` (default) one POST per batch, body is a JSON array:
* - `json_array` (default) - one POST per batch, body is a JSON array:
* ```json
* [
* { "event": "request", "timestamp": "2024-01-01T00:00:00.000Z", "data": { … } },
@@ -46,7 +46,7 @@ const DEFAULT_FORMAT: PayloadFormat = "json_array";
* ```
* `Content-Type: application/json`
*
* - `ndjson` one POST per batch, body is newline-delimited JSON (one object
* - `ndjson` - one POST per batch, body is newline-delimited JSON (one object
* per line, no outer array). Required by Splunk HEC, Elastic/OpenSearch,
* and Grafana Loki:
* ```
@@ -55,7 +55,7 @@ const DEFAULT_FORMAT: PayloadFormat = "json_array";
* ```
* `Content-Type: application/x-ndjson`
*
* - `json_single` one POST **per event**, body is a plain JSON object.
* - `json_single` - one POST **per event**, body is a plain JSON object.
* Use only for endpoints that cannot handle batches at all.
*
* With a body template each event is rendered through the template before
@@ -319,4 +319,4 @@ function epochSecondsToIso(epochSeconds: number): string {
function escapeJsonString(value: string): string {
// JSON.stringify produces `"<escaped>"` strip the outer quotes.
return JSON.stringify(value).slice(1, -1);
}
}

View File

@@ -60,9 +60,9 @@ export type AuthType = "none" | "bearer" | "basic" | "custom";
/**
* Controls how the batch of events is serialised into the HTTP request body.
*
* - `json_array` `[{…}, {…}]` default; one POST per batch wrapped in a
* - `json_array` `[{…}, {…}]` - default; one POST per batch wrapped in a
* JSON array. Works with most generic webhooks and Datadog.
* - `ndjson` `{…}\n{…}` newline-delimited JSON, one object per
* - `ndjson` `{…}\n{…}` - newline-delimited JSON, one object per
* line. Required by Splunk HEC, Elastic/OpenSearch, Loki.
* - `json_single` one HTTP POST per event, body is a plain JSON object.
* Use only for endpoints that cannot handle batches at all.
@@ -131,4 +131,4 @@ export interface DestinationFailureState {
nextRetryAt: number;
/** Date.now() value of the very first failure in the current streak */
firstFailedAt: number;
}
}

View File

@@ -267,7 +267,7 @@ export async function getTraefikConfig(
});
});
// Query siteResources in HTTP mode with SSL enabled and aliases cert generation / HTTPS edge
// Query siteResources in HTTP mode with SSL enabled and aliases - cert generation / HTTPS edge
const siteResourcesWithFullDomain = await db
.select({
siteResourceId: siteResources.siteResourceId,
@@ -1010,7 +1010,7 @@ export async function getTraefikConfig(
}
}
// HTTPS router presence of this entry triggers cert generation
// HTTPS router - presence of this entry triggers cert generation
config_output.http.routers[siteResourceRouterName] = {
entryPoints: [
config.getRawConfig().traefik.https_entrypoint
@@ -1022,7 +1022,7 @@ export async function getTraefikConfig(
tls
};
// Assets bypass router lets Next.js static files load without rewrite
// Assets bypass router - lets Next.js static files load without rewrite
config_output.http.routers[`${siteResourceRouterName}-assets`] = {
entryPoints: [
config.getRawConfig().traefik.https_entrypoint

View File

@@ -11,7 +11,7 @@
* This file is not licensed under the AGPLv3.
*/
import { db, targetHealthCheck } from "@server/db";
import { db, targetHealthCheck, targets, resources } from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
@@ -84,12 +84,36 @@ export async function listHealthChecks(
const whereClause = and(
eq(targetHealthCheck.orgId, orgId),
isNull(targetHealthCheck.targetId)
);
const list = await db
.select()
.select({
targetHealthCheckId: targetHealthCheck.targetHealthCheckId,
name: targetHealthCheck.name,
hcEnabled: targetHealthCheck.hcEnabled,
hcHealth: targetHealthCheck.hcHealth,
hcMode: targetHealthCheck.hcMode,
hcHostname: targetHealthCheck.hcHostname,
hcPort: targetHealthCheck.hcPort,
hcPath: targetHealthCheck.hcPath,
hcScheme: targetHealthCheck.hcScheme,
hcMethod: targetHealthCheck.hcMethod,
hcInterval: targetHealthCheck.hcInterval,
hcUnhealthyInterval: targetHealthCheck.hcUnhealthyInterval,
hcTimeout: targetHealthCheck.hcTimeout,
hcHeaders: targetHealthCheck.hcHeaders,
hcFollowRedirects: targetHealthCheck.hcFollowRedirects,
hcStatus: targetHealthCheck.hcStatus,
hcTlsServerName: targetHealthCheck.hcTlsServerName,
hcHealthyThreshold: targetHealthCheck.hcHealthyThreshold,
hcUnhealthyThreshold: targetHealthCheck.hcUnhealthyThreshold,
resourceId: resources.resourceId,
resourceName: resources.name,
resourceNiceId: resources.niceId
})
.from(targetHealthCheck)
.leftJoin(targets, eq(targetHealthCheck.targetId, targets.targetId))
.leftJoin(resources, eq(targets.resourceId, resources.resourceId))
.where(whereClause)
.orderBy(sql`${targetHealthCheck.targetHealthCheckId} DESC`)
.limit(limit)
@@ -124,7 +148,10 @@ export async function listHealthChecks(
hcStatus: row.hcStatus ?? null,
hcTlsServerName: row.hcTlsServerName ?? null,
hcHealthyThreshold: row.hcHealthyThreshold ?? null,
hcUnhealthyThreshold: row.hcUnhealthyThreshold ?? null
hcUnhealthyThreshold: row.hcUnhealthyThreshold ?? null,
resourceId: row.resourceId ?? null,
resourceName: row.resourceName ?? null,
resourceNiceId: row.resourceNiceId ?? null
})),
pagination: {
total: count,

View File

@@ -88,11 +88,11 @@ async function dbQueryRows<T extends Record<string, unknown>>(
): Promise<T[]> {
const anyDb = db as any;
if (typeof anyDb.execute === "function") {
// PostgreSQL (node-postgres via Drizzle) returns { rows: [...] } or an array
// PostgreSQL (node-postgres via Drizzle) - returns { rows: [...] } or an array
const result = await anyDb.execute(query);
return (Array.isArray(result) ? result : (result.rows ?? [])) as T[];
}
// SQLite (better-sqlite3 via Drizzle) returns an array directly
// SQLite (better-sqlite3 via Drizzle) - returns an array directly
return (await anyDb.all(query)) as T[];
}
@@ -106,7 +106,7 @@ function isSQLite(): boolean {
* Swaps out the accumulator before writing so that any bandwidth messages
* received during the flush are captured in the new accumulator rather than
* being lost or causing contention. Sites are updated in chunks via a single
* batch UPDATE per chunk. Failed chunks are discarded exact per-flush
* batch UPDATE per chunk. Failed chunks are discarded - exact per-flush
* accuracy is not critical and re-queuing is not worth the added complexity.
*
* This function is exported so that the application's graceful-shutdown
@@ -125,7 +125,7 @@ export async function flushSiteBandwidthToDb(): Promise<void> {
const currentTime = new Date().toISOString();
// Sort by publicKey for consistent lock ordering across concurrent
// writers deadlock-prevention strategy.
// writers - deadlock-prevention strategy.
const sortedEntries = [...snapshot.entries()].sort(([a], [b]) =>
a.localeCompare(b)
);
@@ -150,7 +150,7 @@ export async function flushSiteBandwidthToDb(): Promise<void> {
try {
rows = await withDeadlockRetry(async () => {
if (isSQLite()) {
// SQLite: one UPDATE per row no need for batch efficiency here.
// SQLite: one UPDATE per row - no need for batch efficiency here.
const results: { orgId: string; pubKey: string }[] = [];
for (const [publicKey, { bytesIn, bytesOut }] of chunk) {
const result = await dbQueryRows<{
@@ -170,7 +170,7 @@ export async function flushSiteBandwidthToDb(): Promise<void> {
return results;
}
// PostgreSQL: batch UPDATE … FROM (VALUES …) single round-trip per chunk.
// PostgreSQL: batch UPDATE … FROM (VALUES …) - single round-trip per chunk.
const valuesList = chunk.map(([publicKey, { bytesIn, bytesOut }]) =>
sql`(${publicKey}::text, ${bytesIn}::real, ${bytesOut}::real)`
);
@@ -191,7 +191,7 @@ export async function flushSiteBandwidthToDb(): Promise<void> {
`Failed to flush bandwidth chunk [${i}${chunkEnd}], discarding ${chunk.length} site(s):`,
error
);
// Discard the chunk exact per-flush accuracy is not critical.
// Discard the chunk - exact per-flush accuracy is not critical.
continue;
}
@@ -232,7 +232,7 @@ export async function flushSiteBandwidthToDb(): Promise<void> {
totalBandwidth
);
if (bandwidthUsage) {
// Fire-and-forget don't block the flush on limit checking.
// Fire-and-forget - don't block the flush on limit checking.
usageService
.checkLimitSet(
orgId,
@@ -298,7 +298,7 @@ export async function updateSiteBandwidth(
exitNodeId?: number
): Promise<void> {
for (const { publicKey, bytesIn, bytesOut } of bandwidthData) {
// Skip peers that haven't transferred any data writing zeros to the
// Skip peers that haven't transferred any data - writing zeros to the
// database would be a no-op anyway.
if (bytesIn <= 0 && bytesOut <= 0) {
continue;

View File

@@ -19,6 +19,9 @@ export type ListHealthChecksResponse = {
hcTlsServerName: string | null;
hcHealthyThreshold: number | null;
hcUnhealthyThreshold: number | null;
resourceId: number | null;
resourceName: string | null;
resourceNiceId: string | null;
}[];
pagination: {
total: number;

View File

@@ -88,7 +88,7 @@ export async function flushBandwidthToDb(): Promise<void> {
const currentTime = new Date().toISOString();
// Sort by publicKey for consistent lock ordering across concurrent
// writers this is the same deadlock-prevention strategy used in the
// writers - this is the same deadlock-prevention strategy used in the
// original per-message implementation.
const sortedEntries = [...snapshot.entries()].sort(([a], [b]) =>
a.localeCompare(b)
@@ -143,7 +143,7 @@ const flushTimer = setInterval(async () => {
}, FLUSH_INTERVAL_MS);
// Calling unref() means this timer will not keep the Node.js event loop alive
// on its own the process can still exit normally when there is no other work
// on its own - the process can still exit normally when there is no other work
// left. The graceful-shutdown path (see server/cleanup.ts) will call
// flushBandwidthToDb() explicitly before process.exit(), so no data is lost.
flushTimer.unref();
@@ -167,7 +167,7 @@ export const handleReceiveBandwidthMessage: MessageHandler = async (
// Accumulate the incoming data in memory; the periodic timer (and the
// shutdown hook) will take care of writing it to the database.
for (const { publicKey, bytesIn, bytesOut } of bandwidthData) {
// Skip peers that haven't transferred any data writing zeros to the
// Skip peers that haven't transferred any data - writing zeros to the
// database would be a no-op anyway.
if (bytesIn <= 0 && bytesOut <= 0) {
continue;

View File

@@ -16,7 +16,7 @@ const OFFLINE_THRESHOLD_BANDWIDTH_MS = 8 * 60 * 1000; // 8 minutes
* Starts the background interval that checks for newt sites that haven't
* pinged recently and marks them as offline. For backward compatibility,
* a site is only marked offline when there is no active WebSocket connection
* either so older newt versions that don't send pings but remain connected
* either - so older newt versions that don't send pings but remain connected
* continue to be treated as online.
*/
export const startNewtOfflineChecker = (): void => {
@@ -63,7 +63,7 @@ export const startNewtOfflineChecker = (): void => {
);
if (isConnected) {
logger.debug(
`Newt ${staleSite.newtId} has not pinged recently but is still connected via WebSocket keeping site ${staleSite.siteId} online`
`Newt ${staleSite.newtId} has not pinged recently but is still connected via WebSocket - keeping site ${staleSite.siteId} online`
);
continue;
}

View File

@@ -112,7 +112,7 @@ async function flushSitePingsToDb(): Promise<void> {
try {
const newlyOnlineSites = await withRetry(async () => {
// Only update sites that were offline these are the
// Only update sites that were offline - these are the
// offline→online transitions. .returning() gives us exactly
// the site IDs that changed state.
const transitioned = await db
@@ -249,7 +249,7 @@ async function flushClientPingsToDb(): Promise<void> {
}
/**
* Flush everything called by the interval timer and during shutdown.
* Flush everything - called by the interval timer and during shutdown.
*/
export async function flushPingsToDb(): Promise<void> {
await flushSitePingsToDb();
@@ -314,7 +314,7 @@ function isTransientError(error: any): boolean {
return true;
}
// PostgreSQL deadlock detected always safe to retry (one winner guaranteed)
// PostgreSQL deadlock detected - always safe to retry (one winner guaranteed)
if (code === "40P01" || message.includes("deadlock")) {
return true;
}

View File

@@ -249,7 +249,7 @@ export async function registerNewt(
dateCreated: moment().toISOString()
});
// Consume the provisioning key cascade removes siteProvisioningKeyOrg
// Consume the provisioning key - cascade removes siteProvisioningKeyOrg
await trx
.update(siteProvisioningKeys)
.set({

View File

@@ -211,7 +211,7 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
continue;
}
// Trigger the peer add handshake if the peer was already added this will be a no-op
// Trigger the peer add handshake - if the peer was already added this will be a no-op
await initPeerAddHandshake(
client.clientId,
{
@@ -236,4 +236,4 @@ export const handleOlmServerInitAddPeerHandshake: MessageHandler = async (
}
return;
};
};

View File

@@ -228,7 +228,7 @@ export async function createTarget(
healthCheck = await db
.insert(targetHealthCheck)
.values({
name: `${targetData.ip}:${targetData.port}`,
orgId: resource.orgId,
targetId: newTarget[0].targetId,
hcEnabled: targetData.hcEnabled ?? false,
hcPath: targetData.hcPath ?? null,

View File

@@ -47,7 +47,7 @@ export const messageHandlers: Record<string, MessageHandler> = {
"ws/round-trip/complete": handleRoundTripMessage
};
// Start the ping accumulator for all builds it batches per-site online/lastPing
// Start the ping accumulator for all builds - it batches per-site online/lastPing
// updates into periodic bulk writes, preventing connection pool exhaustion.
startPingAccumulator();