mirror of
https://github.com/fosrl/pangolin.git
synced 2026-04-30 01:31:52 +00:00
make alerts and health checks table server side
This commit is contained in:
@@ -33,7 +33,7 @@ export default function EditAlertRulePage() {
|
||||
|
||||
useEffect(() => {
|
||||
if (isNaN(alertRuleId)) {
|
||||
router.replace(`/${orgId}/settings/alerting`);
|
||||
router.replace(`/${orgId}/settings/alerting/rules`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export default function EditAlertRulePage() {
|
||||
|
||||
useEffect(() => {
|
||||
if (formValues === null) {
|
||||
router.replace(`/${orgId}/settings/alerting`);
|
||||
router.replace(`/${orgId}/settings/alerting/rules`);
|
||||
}
|
||||
}, [formValues, orgId, router]);
|
||||
|
||||
|
||||
86
src/app/[orgId]/settings/alerting/health-checks/page.tsx
Normal file
86
src/app/[orgId]/settings/alerting/health-checks/page.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import HealthChecksTable from "@app/components/HealthChecksTable";
|
||||
import DismissableBanner from "@app/components/DismissableBanner";
|
||||
import { internal } from "@app/lib/api";
|
||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||
import { ListHealthChecksResponse } from "@server/routers/healthChecks/types";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { HeartPulse } from "lucide-react";
|
||||
import type { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Health checks"
|
||||
};
|
||||
|
||||
type AlertingHealthChecksPageProps = {
|
||||
params: Promise<{ orgId: string }>;
|
||||
searchParams: Promise<Record<string, string>>;
|
||||
};
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
function parsePositiveInt(s: string | undefined): number | undefined {
|
||||
if (!s) return undefined;
|
||||
const n = Number(s);
|
||||
if (!Number.isInteger(n) || n <= 0) return undefined;
|
||||
return n;
|
||||
}
|
||||
|
||||
export default async function AlertingHealthChecksPage(
|
||||
props: AlertingHealthChecksPageProps
|
||||
) {
|
||||
const params = await props.params;
|
||||
const searchParams = new URLSearchParams(await props.searchParams);
|
||||
|
||||
const page = Math.max(1, parsePositiveInt(searchParams.get("page") ?? undefined) ?? 1);
|
||||
const pageSize = Math.max(
|
||||
1,
|
||||
parsePositiveInt(searchParams.get("pageSize") ?? undefined) ?? 20
|
||||
);
|
||||
const pageIndex = page - 1;
|
||||
const query = searchParams.get("query") ?? undefined;
|
||||
|
||||
const apiSp = new URLSearchParams();
|
||||
apiSp.set("limit", String(pageSize));
|
||||
apiSp.set("offset", String(pageIndex * pageSize));
|
||||
if (query) apiSp.set("query", query);
|
||||
|
||||
let healthChecks: ListHealthChecksResponse["healthChecks"] = [];
|
||||
let pagination: ListHealthChecksResponse["pagination"] = {
|
||||
total: 0,
|
||||
limit: pageSize,
|
||||
offset: pageIndex * pageSize
|
||||
};
|
||||
try {
|
||||
const res = await internal.get<AxiosResponse<ListHealthChecksResponse>>(
|
||||
`/org/${params.orgId}/health-checks?${apiSp.toString()}`,
|
||||
await authCookieHeader()
|
||||
);
|
||||
const responseData = res.data.data;
|
||||
healthChecks = responseData.healthChecks;
|
||||
pagination = responseData.pagination;
|
||||
} catch {
|
||||
// leave defaults
|
||||
}
|
||||
|
||||
const t = await getTranslations();
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<DismissableBanner
|
||||
storageKey="alerting-health-checks-banner-dismissed"
|
||||
version={1}
|
||||
title={t("alertingHealthChecksBannerTitle")}
|
||||
titleIcon={
|
||||
<HeartPulse className="w-5 h-5 text-primary shrink-0" />
|
||||
}
|
||||
description={t("alertingHealthChecksBannerDescription")}
|
||||
/>
|
||||
<HealthChecksTable
|
||||
orgId={params.orgId}
|
||||
healthChecks={healthChecks}
|
||||
rowCount={pagination.total}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
38
src/app/[orgId]/settings/alerting/layout.tsx
Normal file
38
src/app/[orgId]/settings/alerting/layout.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||
import { HorizontalTabs } from "@app/components/HorizontalTabs";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
type AlertingLayoutProps = {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ orgId: string }>;
|
||||
};
|
||||
|
||||
export default async function AlertingLayout({
|
||||
children,
|
||||
params
|
||||
}: AlertingLayoutProps) {
|
||||
const { orgId } = await params;
|
||||
const t = await getTranslations();
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
title: t("alertingTabRules"),
|
||||
href: `/${orgId}/settings/alerting/rules`,
|
||||
activePrefix: `/${orgId}/settings/alerting`
|
||||
},
|
||||
{
|
||||
title: t("alertingTabHealthChecks"),
|
||||
href: `/${orgId}/settings/alerting/health-checks`
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsSectionTitle
|
||||
title={t("alertingTitle")}
|
||||
description={t("alertingDescription")}
|
||||
/>
|
||||
<HorizontalTabs items={navItems}>{children}</HorizontalTabs>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,58 +1,15 @@
|
||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||
import AlertingRulesTable from "@app/components/AlertingRulesTable";
|
||||
import HealthChecksTable from "@app/components/HealthChecksTable";
|
||||
import DismissableBanner from "@app/components/DismissableBanner";
|
||||
import { HorizontalTabs, TabItem } from "@app/components/HorizontalTabs";
|
||||
import { BellRing, HeartPulse } from "lucide-react";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import type { Metadata } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
type AlertingPageProps = {
|
||||
export const metadata: Metadata = {
|
||||
title: "Alerting"
|
||||
};
|
||||
|
||||
type AlertingIndexPageProps = {
|
||||
params: Promise<{ orgId: string }>;
|
||||
};
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AlertingPage(props: AlertingPageProps) {
|
||||
export default async function AlertingIndexPage(props: AlertingIndexPageProps) {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
|
||||
const tabs: TabItem[] = [
|
||||
{ title: t("alertingTabRules"), href: "" },
|
||||
{ title: t("alertingTabHealthChecks"), href: "" }
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsSectionTitle
|
||||
title={t("alertingTitle")}
|
||||
description={t("alertingDescription")}
|
||||
/>
|
||||
<HorizontalTabs items={tabs} clientSide>
|
||||
<div className="space-y-6">
|
||||
<DismissableBanner
|
||||
storageKey="alerting-rules-banner-dismissed"
|
||||
version={1}
|
||||
title={t("alertingRulesBannerTitle")}
|
||||
titleIcon={
|
||||
<BellRing className="w-5 h-5 text-primary shrink-0" />
|
||||
}
|
||||
description={t("alertingRulesBannerDescription")}
|
||||
/>
|
||||
<AlertingRulesTable orgId={params.orgId} />
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<DismissableBanner
|
||||
storageKey="alerting-health-checks-banner-dismissed"
|
||||
version={1}
|
||||
title={t("alertingHealthChecksBannerTitle")}
|
||||
titleIcon={
|
||||
<HeartPulse className="w-5 h-5 text-primary shrink-0" />
|
||||
}
|
||||
description={t("alertingHealthChecksBannerDescription")}
|
||||
/>
|
||||
<HealthChecksTable orgId={params.orgId} />
|
||||
</div>
|
||||
</HorizontalTabs>
|
||||
</>
|
||||
);
|
||||
redirect(`/${params.orgId}/settings/alerting/rules`);
|
||||
}
|
||||
|
||||
100
src/app/[orgId]/settings/alerting/rules/page.tsx
Normal file
100
src/app/[orgId]/settings/alerting/rules/page.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import AlertingRulesTable from "@app/components/AlertingRulesTable";
|
||||
import DismissableBanner from "@app/components/DismissableBanner";
|
||||
import { internal } from "@app/lib/api";
|
||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||
import type { ListAlertRulesResponse } from "@server/private/routers/alertRule";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { BellRing } from "lucide-react";
|
||||
import type { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Alerting"
|
||||
};
|
||||
|
||||
type AlertingRulesPageProps = {
|
||||
params: Promise<{ orgId: string }>;
|
||||
searchParams: Promise<Record<string, string>>;
|
||||
};
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
function parsePositiveInt(s: string | undefined): number | undefined {
|
||||
if (!s) return undefined;
|
||||
const n = Number(s);
|
||||
if (!Number.isInteger(n) || n <= 0) return undefined;
|
||||
return n;
|
||||
}
|
||||
|
||||
export default async function AlertingRulesPage(props: AlertingRulesPageProps) {
|
||||
const params = await props.params;
|
||||
const searchParams = new URLSearchParams(await props.searchParams);
|
||||
|
||||
const page = Math.max(1, parsePositiveInt(searchParams.get("page") ?? undefined) ?? 1);
|
||||
const pageSize = Math.max(
|
||||
1,
|
||||
parsePositiveInt(searchParams.get("pageSize") ?? undefined) ?? 20
|
||||
);
|
||||
const pageIndex = page - 1;
|
||||
const query = searchParams.get("query") ?? undefined;
|
||||
const sortBy = searchParams.get("sort_by") ?? undefined;
|
||||
const order = searchParams.get("order") ?? undefined;
|
||||
const enabled = searchParams.get("enabled");
|
||||
const enabledParam =
|
||||
enabled === "true" || enabled === "false" ? enabled : undefined;
|
||||
const siteId = parsePositiveInt(searchParams.get("siteId") ?? undefined);
|
||||
const resourceId = parsePositiveInt(
|
||||
searchParams.get("resourceId") ?? undefined
|
||||
);
|
||||
|
||||
const apiSp = new URLSearchParams();
|
||||
apiSp.set("limit", String(pageSize));
|
||||
apiSp.set("offset", String(pageIndex * pageSize));
|
||||
if (query) apiSp.set("query", query);
|
||||
if (siteId != null) apiSp.set("siteId", String(siteId));
|
||||
if (resourceId != null) apiSp.set("resourceId", String(resourceId));
|
||||
if (sortBy) {
|
||||
apiSp.set("sort_by", sortBy);
|
||||
if (order) apiSp.set("order", order);
|
||||
}
|
||||
if (enabledParam) apiSp.set("enabled", enabledParam);
|
||||
|
||||
let alertRules: ListAlertRulesResponse["alertRules"] = [];
|
||||
let pagination: ListAlertRulesResponse["pagination"] = {
|
||||
total: 0,
|
||||
limit: pageSize,
|
||||
offset: pageIndex * pageSize
|
||||
};
|
||||
try {
|
||||
const res = await internal.get<AxiosResponse<ListAlertRulesResponse>>(
|
||||
`/org/${params.orgId}/alert-rules?${apiSp.toString()}`,
|
||||
await authCookieHeader()
|
||||
);
|
||||
const responseData = res.data.data;
|
||||
alertRules = responseData.alertRules;
|
||||
pagination = responseData.pagination;
|
||||
} catch {
|
||||
// leave defaults
|
||||
}
|
||||
|
||||
const t = await getTranslations();
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<DismissableBanner
|
||||
storageKey="alerting-rules-banner-dismissed"
|
||||
version={1}
|
||||
title={t("alertingRulesBannerTitle")}
|
||||
titleIcon={
|
||||
<BellRing className="w-5 h-5 text-primary shrink-0" />
|
||||
}
|
||||
description={t("alertingRulesBannerDescription")}
|
||||
/>
|
||||
<AlertingRulesTable
|
||||
orgId={params.orgId}
|
||||
alertRules={alertRules}
|
||||
rowCount={pagination.total}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
18
src/app/[orgId]/settings/health-checks/page.tsx
Normal file
18
src/app/[orgId]/settings/health-checks/page.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { Metadata } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Health checks"
|
||||
};
|
||||
|
||||
type LegacyHealthChecksPageProps = {
|
||||
params: Promise<{ orgId: string }>;
|
||||
};
|
||||
|
||||
/** @deprecated Use `/settings/alerting/health-checks` */
|
||||
export default async function LegacyHealthChecksRedirect(
|
||||
props: LegacyHealthChecksPageProps
|
||||
) {
|
||||
const params = await props.params;
|
||||
redirect(`/${params.orgId}/settings/alerting/health-checks`);
|
||||
}
|
||||
Reference in New Issue
Block a user