Fix strings and local sites

This commit is contained in:
Owen
2026-04-22 12:23:38 -07:00
parent d0b0d95b9a
commit 3d5260b13e
4 changed files with 66 additions and 36 deletions

View File

@@ -15,6 +15,8 @@ import logger from "@server/logger";
import { AlertContext, WebhookAlertConfig } from "@server/routers/alertRule/types"; import { AlertContext, WebhookAlertConfig } from "@server/routers/alertRule/types";
const REQUEST_TIMEOUT_MS = 15_000; const REQUEST_TIMEOUT_MS = 15_000;
const MAX_RETRIES = 3;
const RETRY_BASE_DELAY_MS = 500;
/** /**
* Sends a single webhook POST for an alert event. * Sends a single webhook POST for an alert event.
@@ -49,6 +51,9 @@ export async function sendAlertWebhook(
const body = JSON.stringify(payload); const body = JSON.stringify(payload);
const headers = buildHeaders(webhookConfig); const headers = buildHeaders(webhookConfig);
let lastError: Error | undefined;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
const controller = new AbortController(); const controller = new AbortController();
const timeoutHandle = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); const timeoutHandle = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
@@ -61,14 +66,24 @@ export async function sendAlertWebhook(
signal: controller.signal signal: controller.signal
}); });
} catch (err: unknown) { } catch (err: unknown) {
clearTimeout(timeoutHandle);
const isAbort = err instanceof Error && err.name === "AbortError"; const isAbort = err instanceof Error && err.name === "AbortError";
if (isAbort) { if (isAbort) {
throw new Error( lastError = new Error(
`Alert webhook: request to "${url}" timed out after ${REQUEST_TIMEOUT_MS} ms` `Alert webhook: request to "${url}" timed out after ${REQUEST_TIMEOUT_MS} ms`
); );
} } else {
const msg = err instanceof Error ? err.message : String(err); const msg = err instanceof Error ? err.message : String(err);
throw new Error(`Alert webhook: request to "${url}" failed ${msg}`); lastError = new Error(`Alert webhook: request to "${url}" failed ${msg}`);
}
if (attempt < MAX_RETRIES) {
const delay = RETRY_BASE_DELAY_MS * 2 ** (attempt - 1);
logger.warn(
`Alert webhook: attempt ${attempt}/${MAX_RETRIES} failed retrying in ${delay} ms. ${lastError.message}`
);
await new Promise((resolve) => setTimeout(resolve, delay));
}
continue;
} finally { } finally {
clearTimeout(timeoutHandle); clearTimeout(timeoutHandle);
} }
@@ -81,13 +96,25 @@ export async function sendAlertWebhook(
} catch { } catch {
// best-effort // best-effort
} }
throw new Error( lastError = new Error(
`Alert webhook: server at "${url}" returned HTTP ${response.status} ${response.statusText}` + `Alert webhook: server at "${url}" returned HTTP ${response.status} ${response.statusText}` +
(snippet ? ` ${snippet}` : "") (snippet ? ` ${snippet}` : "")
); );
if (attempt < MAX_RETRIES) {
const delay = RETRY_BASE_DELAY_MS * 2 ** (attempt - 1);
logger.warn(
`Alert webhook: attempt ${attempt}/${MAX_RETRIES} failed retrying in ${delay} ms. ${lastError.message}`
);
await new Promise((resolve) => setTimeout(resolve, delay));
}
continue;
} }
logger.debug(`Alert webhook sent successfully to "${url}" for event "${context.eventType}"`); logger.debug(`Alert webhook sent successfully to "${url}" for event "${context.eventType}" (attempt ${attempt}/${MAX_RETRIES})`);
return;
}
throw lastError ?? new Error(`Alert webhook: all ${MAX_RETRIES} attempts failed for "${url}"`);
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@@ -113,7 +113,7 @@ export default function GeneralPage() {
return ( return (
<SettingsContainer> <SettingsContainer>
{site?.siteId && site?.orgId && ( {site?.siteId && site?.orgId && site.type != "local" && (
<UptimeAlertSection <UptimeAlertSection
orgId={site.orgId} orgId={site.orgId}
siteId={site.siteId} siteId={site.siteId}

View File

@@ -46,7 +46,7 @@ export type SiteRow = {
mbIn: string; mbIn: string;
mbOut: string; mbOut: string;
orgId: string; orgId: string;
type: "newt" | "wireguard"; type: "newt" | "wireguard" | "local";
newtVersion?: string; newtVersion?: string;
newtUpdateAvailable?: boolean; newtUpdateAvailable?: boolean;
online: boolean; online: boolean;
@@ -236,6 +236,9 @@ export default function SitesTable({
header: () => <span className="p-3">{t("uptime30d")}</span>, header: () => <span className="p-3">{t("uptime30d")}</span>,
cell: ({ row }) => { cell: ({ row }) => {
const originalRow = row.original; const originalRow = row.original;
if (originalRow.type == "local") {
return <span>-</span>;
}
return ( return (
<UptimeMiniBar siteId={originalRow.id} days={30} /> <UptimeMiniBar siteId={originalRow.id} days={30} />
); );

View File

@@ -230,7 +230,7 @@ export default function SupporterStatus({
<p className="text-sm"> <p className="text-sm">
<strong>Business & Enterprise Users:</strong> For larger organizations or teams requiring advanced features, consider our self-serve enterprise license and Enterprise Edition.{" "} <strong>Business & Enterprise Users:</strong> For larger organizations or teams requiring advanced features, consider our self-serve enterprise license and Enterprise Edition.{" "}
<Link <Link
href="https://pangolin.net/pricing?hosting=self-host" href="https://pangolin.net/pricing#Self-Hosted"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="underline inline-flex items-center gap-1" className="underline inline-flex items-center gap-1"