hts/apps/migrant/app/[locale]/manage/apis/[apiId]/page.tsx

313 lines
9.5 KiB
TypeScript

import { AreaChart, StackedColumnChart } from "@/components/dashboard/charts";
import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
// import { getTenantId } from "@/lib/auth";
// import { and, db, eq, isNull, schema, sql } from "@/lib/db";
import { formatNumber } from "@/lib/fmt";
// import {
// getActiveKeys,
// getActiveKeysDaily,
// getActiveKeysHourly,
// getVerificationsDaily,
// getVerificationsHourly,
// } from "@/lib/tinybird";
import { BarChart } from "lucide-react";
import { redirect } from "next/navigation";
import { type Interval, IntervalSelect } from "./select";
export const dynamic = "force-dynamic";
export const runtime = "edge";
const getVerificationsHourly = 0
const getActiveKeysHourly = 0
const getVerificationsDaily = 0
const getActiveKeysDaily = 0
export default async function ApiPage(props: {
params: { apiId: string };
searchParams: {
interval?: Interval;
};
}) {
// const tenantId = getTenantId();
// const api = await db.query.apis.findFirst({
// where: (table, { eq, and, isNull }) =>
// and(eq(table.id, props.params.apiId), isNull(table.deletedAt)),
// with: {
// workspace: true,
// },
// });
// if (!api || api.workspace.tenantId !== tenantId) {
// return redirect("/new");
// }
const interval = props.searchParams.interval ?? "7d";
const t = new Date();
t.setUTCDate(1);
t.setUTCHours(0, 0, 0, 0);
const billingCycleStart = t.getTime();
const billingCycleEnd = t.setUTCMonth(t.getUTCMonth() + 1) - 1;
const { getVerificationsPerInterval, getActiveKeysPerInterval, start, end, granularity } =
prepareInterval(interval);
// const query = {
// workspaceId: api.workspaceId,
// apiId: api.id,
// start,
// end,
// };
// const [
// keys,
// verifications,
// activeKeys,
// activeKeysTotal,
// _activeKeysInBillingCycle,
// verificationsInBillingCycle,
// ] = await Promise.all([
// db
// .select({ count: sql<number>`count(*)` })
// .from(schema.keys)
// .where(and(eq(schema.keys.keyAuthId, api.keyAuthId!), isNull(schema.keys.deletedAt)))
// .execute()
// .then((res) => res.at(0)?.count ?? 0),
// getVerificationsPerInterval(query),
// getActiveKeysPerInterval(query),
// getActiveKeys(query),
// getActiveKeys({
// workspaceId: api.workspaceId,
// apiId: api.id,
// start: billingCycleStart,
// end: billingCycleEnd,
// }).then((res) => res.data.at(0)),
// getVerificationsPerInterval({
// workspaceId: api.workspaceId,
// apiId: api.id,
// start: billingCycleStart,
// end: billingCycleEnd,
// }),
// ]);
const successOverTime: { x: string; y: number }[] = [];
const ratelimitedOverTime: { x: string; y: number }[] = [];
const usageExceededOverTime: { x: string; y: number }[] = [];
// for (const d of verifications.data.sort((a, b) => a.time - b.time)) {
// const x = new Date(d.time).toISOString();
// successOverTime.push({ x, y: d.success });
// ratelimitedOverTime.push({ x, y: d.rateLimited });
// usageExceededOverTime.push({ x, y: d.usageExceeded });
// }
const verificationsData = [
...successOverTime.map((d) => ({
...d,
category: "Successful Verifications",
})),
...ratelimitedOverTime.map((d) => ({ ...d, category: "Ratelimited" })),
...usageExceededOverTime.map((d) => ({ ...d, category: "Usage Exceeded" })),
];
// const activeKeysOverTime = activeKeys.data.map(({ time, keys }) => ({
// x: new Date(time).toISOString(),
// y: keys,
// }));
return (
<div className="flex flex-col gap-4">
<Card>
<CardContent className="grid grid-cols-4 divide-x">
<Metric label="Total Keys" value={formatNumber(0)} />
<Metric
label={`Verifications in ${new Date().toLocaleString("en-US", {
month: "long",
})}`}
value={formatNumber(
// verificationsInBillingCycle.data.reduce((sum, day) => sum + day.success, 0),
0
)}
/>
<Metric
label={`Active Keys in ${new Date().toLocaleString("en-US", {
month: "long",
})}`}
value={formatNumber(0)}
/>
</CardContent>
</Card>
<Separator className="my-8" />
<div className="flex items-center justify-between">
<h2 className="text-2xl font-semibold leading-none tracking-tight">Verifications</h2>
<div>
<IntervalSelect defaultSelected={interval} />
</div>
</div>
{verificationsData.some((d) => d.y > 0) ? (
<Card>
<CardHeader>
<div className="grid grid-cols-3 divide-x">
<Metric
label="Successful Verifications"
value={formatNumber(0)}
/>
<Metric
label="Ratelimited"
value={formatNumber(
// verifications.data.reduce((sum, day) => sum + day.rateLimited, 0),
0
)}
/>
<Metric
label="Usage Exceeded"
value={formatNumber(
// verifications.data.reduce((sum, day) => sum + day.usageExceeded, 0),
0
)}
/>
</div>
</CardHeader>
<CardContent>
<StackedColumnChart
data={verificationsData}
timeGranularity={
granularity >= 1000 * 60 * 60 * 24 * 30
? "month"
: granularity >= 1000 * 60 * 60 * 24
? "day"
: "hour"
}
/>
</CardContent>
</Card>
) : (
<EmptyPlaceholder>
<EmptyPlaceholder.Icon>
<BarChart />
</EmptyPlaceholder.Icon>
<EmptyPlaceholder.Title>No usage</EmptyPlaceholder.Title>
<EmptyPlaceholder.Description>
Verify a key or change the range
</EmptyPlaceholder.Description>
</EmptyPlaceholder>
)}
<Separator className="my-8" />
<div className="flex items-center justify-between">
<h2 className="text-2xl font-semibold leading-none tracking-tight">Active Keys</h2>
<div>
<IntervalSelect defaultSelected={interval} />
</div>
</div>
{/* {activeKeysOverTime.some((k) => k.y > 0) ? ( */}
<Card>
<CardHeader>
<div className="grid grid-cols-4 divide-x">
<Metric
label="Total Active Keys"
value={formatNumber(0)}
/>
</div>
</CardHeader>
<CardContent>
<AreaChart
// data={activeKeysOverTime}
tooltipLabel="Active Keys"
timeGranularity={granularity >= 1000 * 60 * 60 * 24 * 30
? "month"
: granularity >= 1000 * 60 * 60 * 24
? "day"
: "hour"} data={[]} />
</CardContent>
</Card>
{/* ) */}
: (
<EmptyPlaceholder>
<EmptyPlaceholder.Icon>
<BarChart />
</EmptyPlaceholder.Icon>
<EmptyPlaceholder.Title>No usage</EmptyPlaceholder.Title>
<EmptyPlaceholder.Description>
Verify a key or change the range
</EmptyPlaceholder.Description>
</EmptyPlaceholder>
)
</div>
);
}
function prepareInterval(interval: Interval) {
const now = new Date();
switch (interval) {
case "24h": {
const end = now.setUTCHours(now.getUTCHours() + 1, 0, 0, 0);
const intervalMs = 1000 * 60 * 60 * 24;
return {
start: end - intervalMs,
end,
intervalMs,
granularity: 1000 * 60 * 60,
getVerificationsPerInterval: getVerificationsHourly,
getActiveKeysPerInterval: getActiveKeysHourly,
};
}
case "7d": {
now.setUTCDate(now.getUTCDate() + 1);
const end = now.setUTCHours(0, 0, 0, 0);
const intervalMs = 1000 * 60 * 60 * 24 * 7;
return {
start: end - intervalMs,
end,
intervalMs,
granularity: 1000 * 60 * 60 * 24,
getVerificationsPerInterval: getVerificationsDaily,
getActiveKeysPerInterval: getActiveKeysDaily,
};
}
case "30d": {
now.setUTCDate(now.getUTCDate() + 1);
const end = now.setUTCHours(0, 0, 0, 0);
const intervalMs = 1000 * 60 * 60 * 24 * 30;
return {
start: end - intervalMs,
end,
intervalMs,
granularity: 1000 * 60 * 60 * 24,
getVerificationsPerInterval: getVerificationsDaily,
getActiveKeysPerInterval: getActiveKeysDaily,
};
}
case "90d": {
now.setUTCDate(now.getUTCDate() + 1);
const end = now.setUTCHours(0, 0, 0, 0);
const intervalMs = 1000 * 60 * 60 * 24 * 90;
return {
start: end - intervalMs,
end,
intervalMs,
granularity: 1000 * 60 * 60 * 24,
getVerificationsPerInterval: getVerificationsDaily,
getActiveKeysPerInterval: getActiveKeysDaily,
};
}
}
}
const Metric: React.FC<{ label: string; value: string }> = ({ label, value }) => {
return (
<div className="flex flex-col items-start justify-center px-4 py-2">
<p className="text-sm text-content-subtle">{label}</p>
<div className="text-2xl font-semibold leading-none tracking-tight">{value}</div>
</div>
);
};