mirror of
https://github.com/JetSprow/J-Board-Lite.git
synced 2026-05-01 01:14:10 +05:30
feat: make network insights optional
This commit is contained in:
@@ -786,6 +786,7 @@ model AppConfig {
|
|||||||
reminderDispatchIntervalMinutes Int @default(60)
|
reminderDispatchIntervalMinutes Int @default(60)
|
||||||
trafficSyncEnabled Boolean @default(true)
|
trafficSyncEnabled Boolean @default(true)
|
||||||
trafficSyncIntervalSeconds Int @default(60)
|
trafficSyncIntervalSeconds Int @default(60)
|
||||||
|
networkInsightsEnabled Boolean @default(false)
|
||||||
subscriptionRiskEnabled Boolean @default(true)
|
subscriptionRiskEnabled Boolean @default(true)
|
||||||
subscriptionRiskAutoSuspend Boolean @default(true)
|
subscriptionRiskAutoSuspend Boolean @default(true)
|
||||||
subscriptionRiskWindowHours Int @default(24)
|
subscriptionRiskWindowHours Int @default(24)
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const settingsSchema = z.object({
|
|||||||
reminderDispatchIntervalMinutes: z.coerce.number().int().positive().optional(),
|
reminderDispatchIntervalMinutes: z.coerce.number().int().positive().optional(),
|
||||||
trafficSyncEnabled: z.string().optional(),
|
trafficSyncEnabled: z.string().optional(),
|
||||||
trafficSyncIntervalSeconds: z.coerce.number().int().min(10).optional(),
|
trafficSyncIntervalSeconds: z.coerce.number().int().min(10).optional(),
|
||||||
|
networkInsightsEnabled: z.string().optional(),
|
||||||
subscriptionRiskEnabled: z.string().optional(),
|
subscriptionRiskEnabled: z.string().optional(),
|
||||||
subscriptionRiskAutoSuspend: z.string().optional(),
|
subscriptionRiskAutoSuspend: z.string().optional(),
|
||||||
subscriptionRiskWindowHours: z.coerce.number().int().min(1).max(168).optional(),
|
subscriptionRiskWindowHours: z.coerce.number().int().min(1).max(168).optional(),
|
||||||
@@ -137,6 +138,10 @@ function buildSettingsUpdate(parsed: z.infer<typeof settingsSchema>, current: Aw
|
|||||||
trafficSyncEnabled: optionalBoolean(parsed.trafficSyncEnabled, current.trafficSyncEnabled),
|
trafficSyncEnabled: optionalBoolean(parsed.trafficSyncEnabled, current.trafficSyncEnabled),
|
||||||
trafficSyncIntervalSeconds:
|
trafficSyncIntervalSeconds:
|
||||||
parsed.trafficSyncIntervalSeconds ?? current.trafficSyncIntervalSeconds,
|
parsed.trafficSyncIntervalSeconds ?? current.trafficSyncIntervalSeconds,
|
||||||
|
networkInsightsEnabled: optionalBoolean(
|
||||||
|
parsed.networkInsightsEnabled,
|
||||||
|
current.networkInsightsEnabled,
|
||||||
|
),
|
||||||
subscriptionRiskEnabled: optionalBoolean(
|
subscriptionRiskEnabled: optionalBoolean(
|
||||||
parsed.subscriptionRiskEnabled,
|
parsed.subscriptionRiskEnabled,
|
||||||
current.subscriptionRiskEnabled,
|
current.subscriptionRiskEnabled,
|
||||||
@@ -223,6 +228,7 @@ function revalidateSettingsViews() {
|
|||||||
revalidatePath("/login");
|
revalidatePath("/login");
|
||||||
revalidatePath("/register");
|
revalidatePath("/register");
|
||||||
revalidatePath("/dashboard");
|
revalidatePath("/dashboard");
|
||||||
|
revalidatePath("/store");
|
||||||
revalidatePath("/subscriptions");
|
revalidatePath("/subscriptions");
|
||||||
revalidatePath("/admin/nodes");
|
revalidatePath("/admin/nodes");
|
||||||
revalidatePath("/account");
|
revalidatePath("/account");
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export default async function AdminSettingsPage() {
|
|||||||
reminderDispatchIntervalMinutes: config.reminderDispatchIntervalMinutes,
|
reminderDispatchIntervalMinutes: config.reminderDispatchIntervalMinutes,
|
||||||
trafficSyncEnabled: config.trafficSyncEnabled,
|
trafficSyncEnabled: config.trafficSyncEnabled,
|
||||||
trafficSyncIntervalSeconds: config.trafficSyncIntervalSeconds,
|
trafficSyncIntervalSeconds: config.trafficSyncIntervalSeconds,
|
||||||
|
networkInsightsEnabled: config.networkInsightsEnabled,
|
||||||
subscriptionRiskEnabled: config.subscriptionRiskEnabled,
|
subscriptionRiskEnabled: config.subscriptionRiskEnabled,
|
||||||
subscriptionRiskAutoSuspend: config.subscriptionRiskAutoSuspend,
|
subscriptionRiskAutoSuspend: config.subscriptionRiskAutoSuspend,
|
||||||
subscriptionRiskWindowHours: config.subscriptionRiskWindowHours,
|
subscriptionRiskWindowHours: config.subscriptionRiskWindowHours,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState, type FormEvent } from "react";
|
import { useState, type FormEvent } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { Bell, ChevronDown, Clock3, Gift, LifeBuoy, Mail, Send, Settings2, ShieldAlert, ShieldCheck } from "lucide-react";
|
import { Bell, ChevronDown, Clock3, Gift, LifeBuoy, Mail, RadioTower, Send, Settings2, ShieldAlert, ShieldCheck } from "lucide-react";
|
||||||
import { Button, buttonVariants } from "@/components/ui/button";
|
import { Button, buttonVariants } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
@@ -26,6 +26,7 @@ interface AppConfig {
|
|||||||
reminderDispatchIntervalMinutes: number;
|
reminderDispatchIntervalMinutes: number;
|
||||||
trafficSyncEnabled: boolean;
|
trafficSyncEnabled: boolean;
|
||||||
trafficSyncIntervalSeconds: number;
|
trafficSyncIntervalSeconds: number;
|
||||||
|
networkInsightsEnabled: boolean;
|
||||||
subscriptionRiskEnabled: boolean;
|
subscriptionRiskEnabled: boolean;
|
||||||
subscriptionRiskAutoSuspend: boolean;
|
subscriptionRiskAutoSuspend: boolean;
|
||||||
subscriptionRiskWindowHours: number;
|
subscriptionRiskWindowHours: number;
|
||||||
@@ -245,6 +246,29 @@ export function SettingsForm({ config, coupons }: { config: AppConfig; coupons:
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section className="space-y-4 rounded-lg border border-border bg-muted/25 p-3">
|
||||||
|
<div className="flex items-center gap-2 text-sm font-semibold">
|
||||||
|
<RadioTower className="size-4 text-primary" /> 商城线路展示
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-5 md:grid-cols-2">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="networkInsightsEnabled">三网推荐与线路体验</Label>
|
||||||
|
<select
|
||||||
|
id="networkInsightsEnabled"
|
||||||
|
name="networkInsightsEnabled"
|
||||||
|
defaultValue={String(config.networkInsightsEnabled)}
|
||||||
|
className={selectClassName}
|
||||||
|
>
|
||||||
|
<option value="false">关闭</option>
|
||||||
|
<option value="true">开启</option>
|
||||||
|
</select>
|
||||||
|
<p className="text-xs leading-5 text-muted-foreground">
|
||||||
|
开启后,用户侧商城展示三网推荐、节点延迟、趋势和访问路径;关闭后只保留购买所需的线路入口选择。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section className="space-y-4 rounded-lg border border-border bg-muted/25 p-3">
|
<section className="space-y-4 rounded-lg border border-border bg-muted/25 p-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const metadata: Metadata = {
|
|||||||
|
|
||||||
export default async function StorePage() {
|
export default async function StorePage() {
|
||||||
const session = await getActiveSession();
|
const session = await getActiveSession();
|
||||||
const { plans, availabilityMap, pendingOrder, latencyRecommendations } = await getStorePageData(session?.user.id);
|
const { plans, availabilityMap, pendingOrder, networkInsightsEnabled, latencyRecommendations } = await getStorePageData(session?.user.id);
|
||||||
const proxyPlans = getProxyPlans(plans);
|
const proxyPlans = getProxyPlans(plans);
|
||||||
const streamingPlans = getStreamingPlans(plans);
|
const streamingPlans = getStreamingPlans(plans);
|
||||||
const proxyCards = sortPlansForDisplay(proxyPlans.map((plan) => toProxyPlanCard(plan, availabilityMap.get(plan.id))));
|
const proxyCards = sortPlansForDisplay(proxyPlans.map((plan) => toProxyPlanCard(plan, availabilityMap.get(plan.id))));
|
||||||
@@ -77,7 +77,7 @@ export default async function StorePage() {
|
|||||||
|
|
||||||
<PendingOrderBanner order={pendingOrder} />
|
<PendingOrderBanner order={pendingOrder} />
|
||||||
|
|
||||||
{proxyCards.length > 0 && (
|
{networkInsightsEnabled && proxyCards.length > 0 && (
|
||||||
<StoreLatencyRecommendations initialItems={latencyRecommendations} />
|
<StoreLatencyRecommendations initialItems={latencyRecommendations} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ export default async function StorePage() {
|
|||||||
gridClassName="lg:grid-cols-2 xl:grid-cols-3"
|
gridClassName="lg:grid-cols-2 xl:grid-cols-3"
|
||||||
after={(
|
after={(
|
||||||
<>
|
<>
|
||||||
{proxyNodeIds.length > 0 && (
|
{networkInsightsEnabled && proxyNodeIds.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<LatencyLoader nodeIds={proxyNodeIds} />
|
<LatencyLoader nodeIds={proxyNodeIds} />
|
||||||
<TraceLoader nodeIds={proxyNodeIds} />
|
<TraceLoader nodeIds={proxyNodeIds} />
|
||||||
@@ -99,7 +99,11 @@ export default async function StorePage() {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{proxyCards.map((plan) => (
|
{proxyCards.map((plan) => (
|
||||||
<ProxyPlanCard key={plan.id} plan={plan} />
|
<ProxyPlanCard
|
||||||
|
key={plan.id}
|
||||||
|
plan={plan}
|
||||||
|
networkInsightsEnabled={networkInsightsEnabled}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</StorePlanSection>
|
</StorePlanSection>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -34,9 +34,10 @@ interface Props {
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
plan: ProxyPlan;
|
plan: ProxyPlan;
|
||||||
|
networkInsightsEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProxyDetailDialog({ open, onOpenChange, plan }: Props) {
|
export function ProxyDetailDialog({ open, onOpenChange, plan, networkInsightsEnabled }: Props) {
|
||||||
const fixedTrafficGb = plan.fixedTrafficGb ?? plan.minTrafficGb;
|
const fixedTrafficGb = plan.fixedTrafficGb ?? plan.minTrafficGb;
|
||||||
const [trafficGb, setTrafficGb] = useState(
|
const [trafficGb, setTrafficGb] = useState(
|
||||||
plan.pricingMode === "FIXED_PACKAGE" ? fixedTrafficGb : plan.minTrafficGb,
|
plan.pricingMode === "FIXED_PACKAGE" ? fixedTrafficGb : plan.minTrafficGb,
|
||||||
@@ -121,7 +122,7 @@ export function ProxyDetailDialog({ open, onOpenChange, plan }: Props) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid items-start gap-6 lg:grid-cols-[1fr_20rem]">
|
<div className={`grid items-start gap-6 ${networkInsightsEnabled ? "lg:grid-cols-[1fr_20rem]" : ""}`}>
|
||||||
{/* Left: purchase config — always visible without scrolling */}
|
{/* Left: purchase config — always visible without scrolling */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<ProxyInboundSelect
|
<ProxyInboundSelect
|
||||||
@@ -188,7 +189,7 @@ export function ProxyDetailDialog({ open, onOpenChange, plan }: Props) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right: signal data — supplementary, scrolls independently on desktop */}
|
{networkInsightsEnabled && (
|
||||||
<div className="min-w-0 lg:max-h-[60vh] lg:overflow-y-auto lg:-mr-3 lg:pr-3">
|
<div className="min-w-0 lg:max-h-[60vh] lg:overflow-y-auto lg:-mr-3 lg:pr-3">
|
||||||
<ProxySignalPanel
|
<ProxySignalPanel
|
||||||
latencyItems={latencyItems}
|
latencyItems={latencyItems}
|
||||||
@@ -197,23 +198,28 @@ export function ProxyDetailDialog({ open, onOpenChange, plan }: Props) {
|
|||||||
onLatencyClick={() => setLatencyDialogOpen(true)}
|
onLatencyClick={() => setLatencyDialogOpen(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
{networkInsightsEnabled && (
|
||||||
<ProxyTraceDetailDialog
|
<ProxyTraceDetailDialog
|
||||||
trace={selectedTrace}
|
trace={selectedTrace}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
if (!open) setSelectedTrace(null);
|
if (!open) setSelectedTrace(null);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{networkInsightsEnabled && (
|
||||||
<LatencyDetailDialog
|
<LatencyDetailDialog
|
||||||
nodeId={plan.nodeId}
|
nodeId={plan.nodeId}
|
||||||
open={latencyDialogOpen}
|
open={latencyDialogOpen}
|
||||||
onOpenChange={setLatencyDialogOpen}
|
onOpenChange={setLatencyDialogOpen}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ import type { ProxyPlan } from "./proxy-plan-types";
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
plan: ProxyPlan;
|
plan: ProxyPlan;
|
||||||
|
networkInsightsEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProxyPlanCard({ plan }: Props) {
|
export function ProxyPlanCard({ plan, networkInsightsEnabled }: Props) {
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
const hasInboundOptions = plan.inboundOptions.length > 0;
|
const hasInboundOptions = plan.inboundOptions.length > 0;
|
||||||
const isFixedPackage = plan.pricingMode === "FIXED_PACKAGE";
|
const isFixedPackage = plan.pricingMode === "FIXED_PACKAGE";
|
||||||
@@ -80,7 +81,12 @@ export function ProxyPlanCard({ plan }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<ProxyDetailDialog open={dialogOpen} onOpenChange={setDialogOpen} plan={plan} />
|
<ProxyDetailDialog
|
||||||
|
open={dialogOpen}
|
||||||
|
onOpenChange={setDialogOpen}
|
||||||
|
plan={plan}
|
||||||
|
networkInsightsEnabled={networkInsightsEnabled}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { prisma } from "@/lib/prisma";
|
|||||||
import { normalizeTraceText } from "@/lib/trace-normalize";
|
import { normalizeTraceText } from "@/lib/trace-normalize";
|
||||||
import { getPlanAvailability, type PlanAvailability } from "@/services/plan-availability";
|
import { getPlanAvailability, type PlanAvailability } from "@/services/plan-availability";
|
||||||
import { getLatencyRecommendations } from "@/services/latency-recommendations";
|
import { getLatencyRecommendations } from "@/services/latency-recommendations";
|
||||||
|
import { getAppConfig } from "@/services/app-config";
|
||||||
|
|
||||||
export async function getStorePageData(userId?: string) {
|
export async function getStorePageData(userId?: string) {
|
||||||
const [plans, pendingOrder, latencyRecommendations] = await Promise.all([
|
const [config, plans, pendingOrder] = await Promise.all([
|
||||||
|
getAppConfig(),
|
||||||
prisma.subscriptionPlan.findMany({
|
prisma.subscriptionPlan.findMany({
|
||||||
where: { isActive: true },
|
where: { isActive: true },
|
||||||
include: {
|
include: {
|
||||||
@@ -25,8 +27,10 @@ export async function getStorePageData(userId?: string) {
|
|||||||
orderBy: { createdAt: "desc" },
|
orderBy: { createdAt: "desc" },
|
||||||
})
|
})
|
||||||
: null,
|
: null,
|
||||||
getLatencyRecommendations(),
|
|
||||||
]);
|
]);
|
||||||
|
const latencyRecommendations = config.networkInsightsEnabled
|
||||||
|
? await getLatencyRecommendations()
|
||||||
|
: [];
|
||||||
|
|
||||||
const availabilityMap = new Map<string, PlanAvailability>();
|
const availabilityMap = new Map<string, PlanAvailability>();
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
@@ -39,6 +43,7 @@ export async function getStorePageData(userId?: string) {
|
|||||||
return {
|
return {
|
||||||
plans,
|
plans,
|
||||||
availabilityMap,
|
availabilityMap,
|
||||||
|
networkInsightsEnabled: config.networkInsightsEnabled,
|
||||||
latencyRecommendations,
|
latencyRecommendations,
|
||||||
pendingOrder: pendingOrder
|
pendingOrder: pendingOrder
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { authenticateAgent, isAuthError } from "@/lib/agent-auth";
|
import { authenticateAgent, isAuthError } from "@/lib/agent-auth";
|
||||||
|
import { getAppConfig } from "@/services/app-config";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/agent/latency
|
* POST /api/agent/latency
|
||||||
@@ -11,6 +12,11 @@ export async function POST(req: Request) {
|
|||||||
const auth = await authenticateAgent(req);
|
const auth = await authenticateAgent(req);
|
||||||
if (isAuthError(auth)) return auth;
|
if (isAuthError(auth)) return auth;
|
||||||
const { nodeId } = auth;
|
const { nodeId } = auth;
|
||||||
|
const config = await getAppConfig();
|
||||||
|
|
||||||
|
if (!config.networkInsightsEnabled) {
|
||||||
|
return NextResponse.json({ ok: true, skipped: true });
|
||||||
|
}
|
||||||
|
|
||||||
let body: {
|
let body: {
|
||||||
latencies: Array<{ carrier: string; latencyMs: number }>;
|
latencies: Array<{ carrier: string; latencyMs: number }>;
|
||||||
|
|||||||
@@ -3,11 +3,17 @@ import { prisma } from "@/lib/prisma";
|
|||||||
import { authenticateAgent, isAuthError } from "@/lib/agent-auth";
|
import { authenticateAgent, isAuthError } from "@/lib/agent-auth";
|
||||||
import { normalizeTraceHops, normalizeTraceText } from "@/lib/trace-normalize";
|
import { normalizeTraceHops, normalizeTraceText } from "@/lib/trace-normalize";
|
||||||
import { classifyTraceRoute } from "@/lib/route-classify";
|
import { classifyTraceRoute } from "@/lib/route-classify";
|
||||||
|
import { getAppConfig } from "@/services/app-config";
|
||||||
|
|
||||||
export async function POST(req: Request) {
|
export async function POST(req: Request) {
|
||||||
const auth = await authenticateAgent(req);
|
const auth = await authenticateAgent(req);
|
||||||
if (isAuthError(auth)) return auth;
|
if (isAuthError(auth)) return auth;
|
||||||
const { nodeId } = auth;
|
const { nodeId } = auth;
|
||||||
|
const config = await getAppConfig();
|
||||||
|
|
||||||
|
if (!config.networkInsightsEnabled) {
|
||||||
|
return NextResponse.json({ ok: true, skipped: true });
|
||||||
|
}
|
||||||
|
|
||||||
let body: {
|
let body: {
|
||||||
traces: Array<{
|
traces: Array<{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { getAppConfig } from "@/services/app-config";
|
||||||
|
|
||||||
const RANGES: Record<string, { ms: number; bucketMs: number }> = {
|
const RANGES: Record<string, { ms: number; bucketMs: number }> = {
|
||||||
"1d": { ms: 24 * 60 * 60 * 1000, bucketMs: 5 * 60 * 1000 },
|
"1d": { ms: 24 * 60 * 60 * 1000, bucketMs: 5 * 60 * 1000 },
|
||||||
@@ -8,6 +9,14 @@ const RANGES: Record<string, { ms: number; bucketMs: number }> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function GET(req: Request) {
|
export async function GET(req: Request) {
|
||||||
|
const config = await getAppConfig();
|
||||||
|
if (!config.networkInsightsEnabled) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ carriers: [], points: [], sufficient: false },
|
||||||
|
{ headers: { "Cache-Control": "no-store" } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const { searchParams } = new URL(req.url);
|
const { searchParams } = new URL(req.url);
|
||||||
const nodeId = searchParams.get("nodeId");
|
const nodeId = searchParams.get("nodeId");
|
||||||
const range = searchParams.get("range") ?? "1d";
|
const range = searchParams.get("range") ?? "1d";
|
||||||
|
|||||||
@@ -1,7 +1,21 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { getLatencyRecommendations } from "@/services/latency-recommendations";
|
import { getLatencyRecommendations } from "@/services/latency-recommendations";
|
||||||
|
import { getAppConfig } from "@/services/app-config";
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
|
const config = await getAppConfig();
|
||||||
|
if (!config.networkInsightsEnabled) {
|
||||||
|
return NextResponse.json({
|
||||||
|
items: [],
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
refreshIntervalMs: 5 * 60 * 1000,
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
"Cache-Control": "no-store",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const items = await getLatencyRecommendations();
|
const items = await getLatencyRecommendations();
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
items,
|
items,
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { getAppConfig } from "@/services/app-config";
|
||||||
|
|
||||||
const MAX_NODE_IDS = 100;
|
const MAX_NODE_IDS = 100;
|
||||||
|
|
||||||
export async function GET(req: Request) {
|
export async function GET(req: Request) {
|
||||||
|
const config = await getAppConfig();
|
||||||
|
if (!config.networkInsightsEnabled) {
|
||||||
|
return NextResponse.json({}, { headers: { "Cache-Control": "no-store" } });
|
||||||
|
}
|
||||||
|
|
||||||
const { searchParams } = new URL(req.url);
|
const { searchParams } = new URL(req.url);
|
||||||
const nodeIds = [...new Set(searchParams.get("nodeIds")?.split(",").filter(Boolean) ?? [])]
|
const nodeIds = [...new Set(searchParams.get("nodeIds")?.split(",").filter(Boolean) ?? [])]
|
||||||
.slice(0, MAX_NODE_IDS);
|
.slice(0, MAX_NODE_IDS);
|
||||||
|
|||||||
@@ -2,10 +2,16 @@ import { NextResponse } from "next/server";
|
|||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { classifyTraceRoute } from "@/lib/route-classify";
|
import { classifyTraceRoute } from "@/lib/route-classify";
|
||||||
import { normalizeTraceHops } from "@/lib/trace-normalize";
|
import { normalizeTraceHops } from "@/lib/trace-normalize";
|
||||||
|
import { getAppConfig } from "@/services/app-config";
|
||||||
|
|
||||||
const MAX_NODE_IDS = 100;
|
const MAX_NODE_IDS = 100;
|
||||||
|
|
||||||
export async function GET(req: Request) {
|
export async function GET(req: Request) {
|
||||||
|
const config = await getAppConfig();
|
||||||
|
if (!config.networkInsightsEnabled) {
|
||||||
|
return NextResponse.json({}, { headers: { "Cache-Control": "no-store" } });
|
||||||
|
}
|
||||||
|
|
||||||
const { searchParams } = new URL(req.url);
|
const { searchParams } = new URL(req.url);
|
||||||
const nodeIds = [...new Set(searchParams.get("nodeIds")?.split(",").filter(Boolean) ?? [])]
|
const nodeIds = [...new Set(searchParams.get("nodeIds")?.split(",").filter(Boolean) ?? [])]
|
||||||
.slice(0, MAX_NODE_IDS);
|
.slice(0, MAX_NODE_IDS);
|
||||||
|
|||||||
Reference in New Issue
Block a user