mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 13:38:31 +00:00
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user