feat: release v3.0.0 risk telemetry

This commit is contained in:
JetSprow
2026-04-29 18:30:49 +10:00
parent e109f6b246
commit a5f962db84
31 changed files with 1367 additions and 80 deletions

View File

@@ -91,7 +91,7 @@ export function NodeActions({ node, siteUrl }: { node: NodeActionValue; siteUrl:
size="sm"
variant="outline"
title="撤销这个探测 Token"
description="撤销后,延迟线路探测程序将无法继续上报数据。"
description="撤销后,延迟线路探测和节点日志风控程序将无法继续上报数据。"
confirmLabel="撤销 Token"
successMessage="探测 Token 已撤销"
errorMessage="撤销失败"
@@ -170,7 +170,7 @@ export function NodeActions({ node, siteUrl }: { node: NodeActionValue; siteUrl:
</p>
)}
<p className="text-xs leading-5 text-muted-foreground">
Agent `/api/agent/latency` `/api/agent/trace` 3x-ui API
Agent `/api/agent/latency``/api/agent/trace` 3x-ui/Xray access logAgent 3x-ui
</p>
</div>
</DialogContent>

View File

@@ -48,6 +48,11 @@ export default async function AdminSettingsPage() {
subscriptionRiskCountrySuspend: config.subscriptionRiskCountrySuspend,
subscriptionRiskIpLimitPerHour: config.subscriptionRiskIpLimitPerHour,
subscriptionRiskTokenLimitPerHour: config.subscriptionRiskTokenLimitPerHour,
nodeAccessRiskEnabled: config.nodeAccessRiskEnabled,
nodeAccessConnectionWarning: config.nodeAccessConnectionWarning,
nodeAccessConnectionSuspend: config.nodeAccessConnectionSuspend,
nodeAccessUniqueTargetWarning: config.nodeAccessUniqueTargetWarning,
nodeAccessUniqueTargetSuspend: config.nodeAccessUniqueTargetSuspend,
inviteRewardEnabled: config.inviteRewardEnabled,
inviteRewardRate: Number(config.inviteRewardRate),
inviteRewardCouponId: config.inviteRewardCouponId,

View File

@@ -37,6 +37,11 @@ interface AppConfig {
subscriptionRiskCountrySuspend: number;
subscriptionRiskIpLimitPerHour: number;
subscriptionRiskTokenLimitPerHour: number;
nodeAccessRiskEnabled: boolean;
nodeAccessConnectionWarning: number;
nodeAccessConnectionSuspend: number;
nodeAccessUniqueTargetWarning: number;
nodeAccessUniqueTargetSuspend: number;
inviteRewardEnabled: boolean;
inviteRewardRate: number;
inviteRewardCouponId: string | null;
@@ -389,9 +394,37 @@ export function SettingsForm({ config, coupons }: { config: AppConfig; coupons:
defaultValue={config.subscriptionRiskTokenLimitPerHour}
/>
</div>
<div className="space-y-2">
<Label htmlFor="nodeAccessRiskEnabled"></Label>
<select
id="nodeAccessRiskEnabled"
name="nodeAccessRiskEnabled"
defaultValue={String(config.nodeAccessRiskEnabled)}
className={selectClassName}
>
<option value="true"> Agent Xray </option>
<option value="false"></option>
</select>
</div>
<div className="space-y-2">
<Label htmlFor="nodeAccessConnectionWarning"></Label>
<Input id="nodeAccessConnectionWarning" name="nodeAccessConnectionWarning" type="number" min={1} max={100000} defaultValue={config.nodeAccessConnectionWarning} />
</div>
<div className="space-y-2">
<Label htmlFor="nodeAccessConnectionSuspend"></Label>
<Input id="nodeAccessConnectionSuspend" name="nodeAccessConnectionSuspend" type="number" min={1} max={100000} defaultValue={config.nodeAccessConnectionSuspend} />
</div>
<div className="space-y-2">
<Label htmlFor="nodeAccessUniqueTargetWarning"></Label>
<Input id="nodeAccessUniqueTargetWarning" name="nodeAccessUniqueTargetWarning" type="number" min={1} max={100000} defaultValue={config.nodeAccessUniqueTargetWarning} />
</div>
<div className="space-y-2">
<Label htmlFor="nodeAccessUniqueTargetSuspend"></Label>
<Input id="nodeAccessUniqueTargetSuspend" name="nodeAccessUniqueTargetSuspend" type="number" min={1} max={100000} defaultValue={config.nodeAccessUniqueTargetSuspend} />
</div>
</div>
<p className="text-xs leading-5 text-muted-foreground">
24 4 5 2 /3 /2 3 IP 180 / 60 /
24 4 5 2 /3 /2 3 IP 180 / 60 / Agent XRAY_ACCESS_LOG_PATH Agent
</p>
</div>
)}

View File

@@ -44,7 +44,7 @@ function WorldRiskMap({ summary }: { summary: SubscriptionRiskGeoSummary }) {
viewBox="0 0 360 180"
className="h-[15rem] w-full bg-[radial-gradient(circle_at_30%_20%,color-mix(in_oklch,var(--primary)_10%,transparent),transparent_30%),linear-gradient(135deg,var(--muted),var(--card))]"
role="img"
aria-label="订阅访问 IP 世界地图分布"
aria-label="订阅访问与节点连接 IP 世界地图分布"
>
<rect width="360" height="180" rx="12" fill="transparent" />
{[-120, -60, 0, 60, 120].map((longitude) => {
@@ -164,7 +164,7 @@ export function SubscriptionRiskGeoDetails({ summary }: { summary: SubscriptionR
<details className="group rounded-xl border border-border/70 bg-muted/20">
<summary className="flex cursor-pointer list-none items-center justify-between gap-3 px-3 py-2 text-sm font-medium [&::-webkit-details-marker]:hidden">
<span className="flex items-center gap-2">
<MapPin className="size-4 text-primary" /> IP 访
<MapPin className="size-4 text-primary" /> IP 访/
</span>
<ChevronDown className="size-4 text-muted-foreground transition-transform group-open:rotate-180" />
</summary>

View File

@@ -31,6 +31,14 @@ function reasonLabel(reason: SubscriptionRiskEvent["reason"]) {
return "国家异常警告";
case "COUNTRY_VARIANCE_SUSPEND":
return "国家异常暂停";
case "NODE_ACCESS_VOLUME_WARNING":
return "节点高频警告";
case "NODE_ACCESS_VOLUME_SUSPEND":
return "节点高频暂停";
case "NODE_ACCESS_TARGET_WARNING":
return "目标分散警告";
case "NODE_ACCESS_TARGET_SUSPEND":
return "目标分散暂停";
}
}
@@ -255,7 +263,7 @@ export function SubscriptionRiskTable({ events }: { events: SubscriptionRiskEven
return (
<EmptyState
title="暂无订阅风控事件"
description="订阅链接出现跨城市、跨省份或跨国家访问异常后,会在这里进入人工跟进队列。"
description="订阅链接或节点真实连接出现跨城市、跨省份或跨国家异常后,会在这里进入人工跟进队列。"
/>
);
}

View File

@@ -7,7 +7,7 @@ import { getSubscriptionRiskEvents } from "./risk-data";
export const metadata: Metadata = {
title: "订阅风控",
description: "查看订阅访问异常、关联用户和人工处理状态。",
description: "查看订阅访问与节点连接异常、关联用户和人工处理状态。",
};
export default async function AdminSubscriptionRiskPage({
@@ -22,7 +22,7 @@ export default async function AdminSubscriptionRiskPage({
<PageHeader
eyebrow="商品与订单"
title="订阅风控"
description="订阅链接跨城市跨省份访问异常后,会进入这里供管理员确认、备注、恢复或继续处置。"
description="订阅链接或节点真实连接出现跨城市跨省份访问异常后,会进入这里供管理员确认、备注、恢复或继续处置。"
/>
<AdminFilterBar

View File

@@ -67,6 +67,14 @@ function reasonLabel(reason: SubscriptionRiskEvent["reason"]) {
return "国家异常警告";
case "COUNTRY_VARIANCE_SUSPEND":
return "国家异常暂停";
case "NODE_ACCESS_VOLUME_WARNING":
return "节点高频警告";
case "NODE_ACCESS_VOLUME_SUSPEND":
return "节点高频暂停";
case "NODE_ACCESS_TARGET_WARNING":
return "目标分散警告";
case "NODE_ACCESS_TARGET_SUSPEND":
return "目标分散暂停";
}
}
@@ -113,7 +121,7 @@ export function SubscriptionAccessRiskSection({
</span>
<div>
<h3 className="text-lg font-semibold tracking-[-0.02em]">访</h3>
<p className="mt-0.5 text-sm text-muted-foreground"> IP</p>
<p className="mt-0.5 text-sm text-muted-foreground"> IP</p>
</div>
</div>
<Link href="/admin/subscription-risk" className={buttonVariants({ variant: "outline", size: "sm" })}>