feat(proxy): add proxy pool and per-connection binding + strictProxy support

- Centralize proxy management with reusable proxy pools
- Per-connection proxy binding with legacy fallback
- Add strictProxy option: fail hard instead of silently falling back to direct
- Resolve alicode-intl conflict: keep alicode-intl support + proxy support

Made-with: Cursor
This commit is contained in:
decolua
2026-03-09 15:46:06 +07:00
parent 4c469291a1
commit 880f4eca91
22 changed files with 1811 additions and 146 deletions
+1
View File
@@ -12,6 +12,7 @@ import { ConfirmModal } from "./Modal";
const navItems = [
{ href: "/dashboard/endpoint", label: "Endpoint", icon: "api" },
{ href: "/dashboard/providers", label: "Providers", icon: "dns" },
{ href: "/dashboard/proxy-pools", label: "Proxy Pools", icon: "lan" },
{ href: "/dashboard/combos", label: "Combos", icon: "layers" },
{ href: "/dashboard/usage", label: "Usage", icon: "bar_chart" },
{ href: "/dashboard/quota", label: "Quota Tracker", icon: "data_usage" },
@@ -2,15 +2,72 @@
import { useState } from "react";
import { usePathname } from "next/navigation";
import { useNotificationStore } from "@/store/notificationStore";
import Sidebar from "../Sidebar";
import Header from "../Header";
function getToastStyle(type) {
if (type === "success") {
return {
wrapper: "border-green-500/30 bg-green-500/10 text-green-600 dark:text-green-400",
icon: "check_circle",
};
}
if (type === "error") {
return {
wrapper: "border-red-500/30 bg-red-500/10 text-red-600 dark:text-red-400",
icon: "error",
};
}
if (type === "warning") {
return {
wrapper: "border-amber-500/30 bg-amber-500/10 text-amber-600 dark:text-amber-400",
icon: "warning",
};
}
return {
wrapper: "border-blue-500/30 bg-blue-500/10 text-blue-600 dark:text-blue-400",
icon: "info",
};
}
export default function DashboardLayout({ children }) {
const [sidebarOpen, setSidebarOpen] = useState(false);
const pathname = usePathname();
const notifications = useNotificationStore((state) => state.notifications);
const removeNotification = useNotificationStore((state) => state.removeNotification);
return (
<div className="flex h-screen w-full overflow-hidden bg-bg">
<div className="fixed top-4 right-4 z-[80] flex w-[min(92vw,380px)] flex-col gap-2">
{notifications.map((n) => {
const style = getToastStyle(n.type);
return (
<div
key={n.id}
className={`rounded-lg border px-3 py-2 shadow-lg backdrop-blur-sm ${style.wrapper}`}
>
<div className="flex items-start gap-2">
<span className="material-symbols-outlined text-[18px] leading-5">{style.icon}</span>
<div className="min-w-0 flex-1">
{n.title ? <p className="text-xs font-semibold mb-0.5">{n.title}</p> : null}
<p className="text-xs whitespace-pre-wrap break-words">{n.message}</p>
</div>
{n.dismissible ? (
<button
type="button"
onClick={() => removeNotification(n.id)}
className="text-current/70 hover:text-current"
aria-label="Dismiss notification"
>
<span className="material-symbols-outlined text-[16px]">close</span>
</button>
) : null}
</div>
</div>
);
})}
</div>
{/* Mobile sidebar overlay */}
{sidebarOpen && (
<div