import { Badge } from "@workspace/ui/components/badge"; import { Button } from "@workspace/ui/components/button"; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger, } from "@workspace/ui/components/sheet"; import { Skeleton } from "@workspace/ui/components/skeleton"; import { Switch } from "@workspace/ui/components/switch"; import { ConfirmButton } from "@workspace/ui/composed/confirm-button"; import { ProTable } from "@workspace/ui/composed/pro-table/pro-table"; import { getUserSubscribeById, getUserSubscribeDevices, kickOfflineByUserDevice, } from "@workspace/ui/services/admin/user"; import { deviceIdToHash } from "@workspace/ui/utils/device"; import { AlertCircle, AlertTriangle, Gauge } from "lucide-react"; import { type ReactNode, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { IpLink } from "@/components/ip-link"; import { formatDate } from "@/utils/common"; function SpeedLimitCard({ subscriptionId }: { subscriptionId: number }) { const { t } = useTranslation("user"); const [detail, setDetail] = useState(null); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); useEffect(() => { let ignore = false; setIsLoading(true); setIsError(false); getUserSubscribeById({ id: subscriptionId }) .then(({ data }) => { if (!ignore) setDetail(data.data || null); }) .catch(() => { if (!ignore) { setDetail(null); setIsError(true); } }) .finally(() => { if (!ignore) setIsLoading(false); }); return () => { ignore = true; }; }, [subscriptionId]); if (isLoading) { return (
); } if (isError) { return (
{t("subscriptionDetailLoadFailed", "Failed to load speed settings.")}
); } if (!detail) { return (
{t("subscriptionDetailEmpty", "No subscription details found.")}
); } if (detail.status !== 1) return null; const baseSpeed = detail.subscribe?.speed_limit ?? 0; const userSpeed = detail.speed_limit ?? 0; const effectiveSpeed = detail.effective_speed ?? 0; const isThrottled = detail.is_throttled; const rules = detail.traffic_limit || []; const hasLimitConfig = baseSpeed > 0 || userSpeed > 0 || effectiveSpeed > 0 || rules.length > 0; if (!(hasLimitConfig || isThrottled)) { return (
{t("speedLimitEmpty", "No user-level speed limit rules configured.")}
); } return (
{isThrottled ? ( ) : ( )}
{isThrottled ? ( <> {t("throttled", "Speed Throttled")} {effectiveSpeed} Mbps {baseSpeed > 0 && ( {t("subscriptionDefault", "Subscription Default")}:{" "} {baseSpeed} Mbps )} ) : ( <> {t("speedLimit", "Speed Limit")} {effectiveSpeed > 0 ? `${effectiveSpeed} Mbps` : t("unlimited", "Unlimited")} )}
{isThrottled && detail.throttle_rule && (

{detail.throttle_rule}

)} {isThrottled && detail.throttle_start && detail.throttle_end && (

{formatDate(detail.throttle_start)} ~{" "} {formatDate(detail.throttle_end)}

)} {rules.length > 0 && (

{t("trafficLimitRules", "Traffic Limit Rules")}

{rules.map((rule, index) => (
{t("trafficLimitRuleSummary", { defaultValue: "{{traffic}} GB in {{value}} {{type}} -> {{speed}} Mbps", traffic: rule.traffic_usage, value: rule.stat_value, type: rule.stat_type === "hour" ? t("hour", "Hour") : t("day", "Day"), speed: rule.speed_limit, })}
))}
)}
); } function SpeedMetric({ label, value }: { label: string; value: number }) { const { t } = useTranslation("user"); return (
{label}
{value > 0 ? `${value} Mbps` : t("unlimited", "Unlimited")}
); } export function SubscriptionDetail({ trigger, userId, subscriptionId, }: { trigger: ReactNode; userId: number; subscriptionId: number; }) { const { t } = useTranslation("user"); const [open, setOpen] = useState(false); return ( {trigger} {t("onlineDevices", "Online Devices")}
{open && } > actions={{ render: (row) => { if (!row.identifier) return []; return [ { await kickOfflineByUserDevice({ id: row.id }); toast.success( t("kickOfflineSuccess", "Device kicked offline") ); }} title={t("confirmOffline", "Confirm Offline")} trigger={ } />, ]; }, }} columns={[ { accessorKey: "enabled", header: t("enable", "Enable"), cell: ({ row }) => ( ), }, { accessorKey: "id", header: "ID" }, { accessorKey: "identifier", header: t("deviceNo", "Device No."), cell: ({ row }) => { const id = row.original.id; return ( {deviceIdToHash(id)} ); }, }, { accessorKey: "user_agent", header: t("userAgent", "User Agent"), }, { accessorKey: "ip", header: "IP", cell: ({ row }) => , }, { accessorKey: "online", header: t("loginStatus", "Login Status"), cell: ({ row }) => ( {row.getValue("online") ? t("online", "Online") : t("offline", "Offline")} ), }, { accessorKey: "updated_at", header: t("lastSeen", "Last Seen"), cell: ({ row }) => formatDate(row.getValue("updated_at")), }, ]} request={async (pagination) => { const { data } = await getUserSubscribeDevices({ user_id: userId, subscribe_id: subscriptionId, ...pagination, }); return { list: data.data?.list || [], total: data.data?.total || 0, }; }} />
); }