mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-11-16 01:24:24 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1987960fce | |||
| 4c782eb815 | |||
| 1e3b38f427 | |||
| 2166936f57 |
Binary file not shown.
|
After Width: | Height: | Size: 666 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 929 KiB |
@@ -13,10 +13,10 @@ export const AnimatedPage = ({
|
|||||||
className?: string;
|
className?: string;
|
||||||
}) => (
|
}) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ y: 0 }}
|
||||||
className={className}
|
className={className}
|
||||||
exit={{ opacity: 0, y: -8 }}
|
exit={{ y: -8 }}
|
||||||
initial={{ opacity: 0, y: 16 }}
|
initial={{ y: 16 }}
|
||||||
transition={{ duration: 0.28, ease: [0.25, 0.46, 0.45, 0.94] }}
|
transition={{ duration: 0.28, ease: [0.25, 0.46, 0.45, 0.94] }}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -28,9 +28,8 @@ export const AnimatedPage = ({
|
|||||||
* Children should use `staggerItem` as their `variants` prop.
|
* Children should use `staggerItem` as their `variants` prop.
|
||||||
*/
|
*/
|
||||||
export const staggerContainer = {
|
export const staggerContainer = {
|
||||||
hidden: { opacity: 0 },
|
hidden: {},
|
||||||
show: {
|
show: {
|
||||||
opacity: 1,
|
|
||||||
transition: {
|
transition: {
|
||||||
staggerChildren: 0.05,
|
staggerChildren: 0.05,
|
||||||
delayChildren: 0.05,
|
delayChildren: 0.05,
|
||||||
@@ -42,9 +41,8 @@ export const staggerContainer = {
|
|||||||
* Individual stagger item variant — fade-in + slide-up.
|
* Individual stagger item variant — fade-in + slide-up.
|
||||||
*/
|
*/
|
||||||
export const staggerItem = {
|
export const staggerItem = {
|
||||||
hidden: { opacity: 0, y: 12 },
|
hidden: { y: 12 },
|
||||||
show: {
|
show: {
|
||||||
opacity: 1,
|
|
||||||
y: 0,
|
y: 0,
|
||||||
transition: { duration: 0.25, ease: [0.25, 0.46, 0.45, 0.94] },
|
transition: { duration: 0.25, ease: [0.25, 0.46, 0.45, 0.94] },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export default function AdminLayout({
|
|||||||
const [isCollapsed, setIsCollapsed] = useState(
|
const [isCollapsed, setIsCollapsed] = useState(
|
||||||
() => localStorage.getItem("sidebar_collapsed") === "true",
|
() => localStorage.getItem("sidebar_collapsed") === "true",
|
||||||
);
|
);
|
||||||
const [isAdmin, setIsAdmin] = useState(false);
|
const [isAdmin, setIsAdmin] = useState(() => getAdminFlag());
|
||||||
const [monitorAllowed, setMonitorAllowed] = useState<boolean | null>(null);
|
const [monitorAllowed, setMonitorAllowed] = useState<boolean | null>(null);
|
||||||
const [monitorAccessReason, setMonitorAccessReason] = useState<string | null>(
|
const [monitorAccessReason, setMonitorAccessReason] = useState<string | null>(
|
||||||
null,
|
null,
|
||||||
@@ -433,7 +433,7 @@ export default function AdminLayout({
|
|||||||
>
|
>
|
||||||
{isActive && (
|
{isActive && (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute inset-0 rounded-2xl bg-white/80 dark:bg-white/10 shadow-[0_2px_8px_rgba(0,0,0,0.04)] border border-white/50 dark:border-white/5"
|
className="absolute inset-0 rounded-2xl bg-white/60 dark:bg-white/10 backdrop-blur-xl shadow-[0_12px_36px_rgba(0,0,0,0.18)] border border-white dark:border-white/20"
|
||||||
layoutId="sidebar-active"
|
layoutId="sidebar-active"
|
||||||
transition={{
|
transition={{
|
||||||
type: "spring",
|
type: "spring",
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ interface TabItem {
|
|||||||
export default function H5Layout({ children }: { children: React.ReactNode }) {
|
export default function H5Layout({ children }: { children: React.ReactNode }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [isAdmin, setIsAdmin] = useState(false);
|
const [isAdmin, setIsAdmin] = useState(() => getAdminFlag());
|
||||||
const [monitorAllowed, setMonitorAllowed] = useState<boolean | null>(null);
|
const [monitorAllowed, setMonitorAllowed] = useState<boolean | null>(null);
|
||||||
const [monitorAccessReason, setMonitorAccessReason] = useState<string | null>(
|
const [monitorAccessReason, setMonitorAccessReason] = useState<string | null>(
|
||||||
null,
|
null,
|
||||||
|
|||||||
@@ -1067,7 +1067,7 @@ export default function DashboardPage() {
|
|||||||
{group.forwards.map((forward) => (
|
{group.forwards.map((forward) => (
|
||||||
<div
|
<div
|
||||||
key={forward.id}
|
key={forward.id}
|
||||||
className="bg-white dark:bg-default-100/50 border border-gray-200 dark:border-default-200 rounded-lg p-3 hover:shadow-md transition-shadow"
|
className="bg-white/10 dark:bg-black/10 backdrop-blur-md border border-white/20 dark:border-white/10 rounded-lg p-3 shadow-sm hover:shadow-md transition-all duration-300"
|
||||||
>
|
>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
@@ -1076,7 +1076,7 @@ export default function DashboardPage() {
|
|||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<button
|
<button
|
||||||
className={`block px-2 py-1 bg-default-50/50 dark:bg-default-100/20 backdrop-blur-md rounded-lg border border-default-200/50 dark:border-default-700/50 font-mono text-xs truncate text-foreground transition-colors duration-200 ${hasMultipleIps(forward.inIp) ? "cursor-pointer hover:bg-default-100 dark:hover:bg-default-200/50" : ""}`}
|
className={`block px-2 py-1 bg-white/20 dark:bg-black/20 backdrop-blur-md rounded-lg border border-white/20 dark:border-white/10 font-mono text-xs truncate text-foreground transition-all duration-300 ${hasMultipleIps(forward.inIp) ? "cursor-pointer hover:bg-white/30 dark:hover:bg-white/10" : ""}`}
|
||||||
disabled={!hasMultipleIps(forward.inIp)}
|
disabled={!hasMultipleIps(forward.inIp)}
|
||||||
title={formatInAddress(
|
title={formatInAddress(
|
||||||
forward.inIp,
|
forward.inIp,
|
||||||
@@ -1097,7 +1097,7 @@ export default function DashboardPage() {
|
|||||||
↓
|
↓
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
className={`block px-2 py-1 bg-default-50/50 dark:bg-default-100/20 backdrop-blur-md rounded-lg border border-default-200/50 dark:border-default-700/50 font-mono text-xs truncate text-foreground transition-colors duration-200 ${hasMultipleRemoteAddresses(forward.remoteAddr) ? "cursor-pointer hover:bg-default-100 dark:hover:bg-default-200/50" : ""}`}
|
className={`block px-2 py-1 bg-white/20 dark:bg-black/20 backdrop-blur-md rounded-lg border border-white/20 dark:border-white/10 font-mono text-xs truncate text-foreground transition-all duration-300 ${hasMultipleRemoteAddresses(forward.remoteAddr) ? "cursor-pointer hover:bg-white/30 dark:hover:bg-white/10" : ""}`}
|
||||||
disabled={
|
disabled={
|
||||||
!hasMultipleRemoteAddresses(
|
!hasMultipleRemoteAddresses(
|
||||||
forward.remoteAddr,
|
forward.remoteAddr,
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export const FlowChartCard = ({
|
|||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white/70 dark:bg-zinc-900/70 backdrop-blur-xl border border-white/50 rounded-2xl shadow-[0_10px_30px_rgba(0,0,0,0.1)] p-4">
|
<div className="bg-white/20 dark:bg-black/20 backdrop-blur-3xl border border-white/50 rounded-2xl shadow-[0_10px_30px_rgba(0,0,0,0.1)] p-4">
|
||||||
<p className="font-medium text-foreground">{`时间: ${label ?? ""}`}</p>
|
<p className="font-medium text-foreground">{`时间: ${label ?? ""}`}</p>
|
||||||
<p className="text-primary font-semibold mt-1">{`流量: ${formatFlow(flowValue)}`}</p>
|
<p className="text-primary font-semibold mt-1">{`流量: ${formatFlow(flowValue)}`}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3689,7 +3689,7 @@ export default function ForwardPage() {
|
|||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
key={forward.id}
|
key={forward.id}
|
||||||
className="group h-full flex flex-col overflow-hidden"
|
className="group h-full flex flex-col overflow-hidden bg-white/20 dark:bg-zinc-900/20 backdrop-blur-3xl border border-white/80 dark:border-white/10 shadow-[0_15px_35px_rgba(0,0,0,0.1)]"
|
||||||
>
|
>
|
||||||
<CardHeader className="pb-2 md:pb-2">
|
<CardHeader className="pb-2 md:pb-2">
|
||||||
<div className="flex justify-between items-start w-full">
|
<div className="flex justify-between items-start w-full">
|
||||||
@@ -3746,7 +3746,7 @@ export default function ForwardPage() {
|
|||||||
{/* 地址信息 */}
|
{/* 地址信息 */}
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<button
|
<button
|
||||||
className={`cursor-pointer px-2 py-1 bg-default-50 dark:bg-default-100/50 rounded border border-default-200 dark:border-default-300 transition-colors duration-200 ${
|
className={`cursor-pointer px-2 py-1 bg-white/20 dark:bg-black/20 backdrop-blur-md rounded border border-default-200 dark:border-default-300 transition-colors duration-200 ${
|
||||||
hasMultipleAddresses(forward.inIp)
|
hasMultipleAddresses(forward.inIp)
|
||||||
? "hover:bg-default-100 dark:hover:bg-default-200/50"
|
? "hover:bg-default-100 dark:hover:bg-default-200/50"
|
||||||
: ""
|
: ""
|
||||||
@@ -3786,7 +3786,7 @@ export default function ForwardPage() {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className={`cursor-pointer px-2 py-1 bg-default-50 dark:bg-default-100/50 rounded border border-default-200 dark:border-default-300 transition-colors duration-200 ${
|
className={`cursor-pointer px-2 py-1 bg-white/20 dark:bg-black/20 backdrop-blur-md rounded border border-default-200 dark:border-default-300 transition-colors duration-200 ${
|
||||||
hasMultipleAddresses(forward.remoteAddr)
|
hasMultipleAddresses(forward.remoteAddr)
|
||||||
? "hover:bg-default-100 dark:hover:bg-default-200/50"
|
? "hover:bg-default-100 dark:hover:bg-default-200/50"
|
||||||
: ""
|
: ""
|
||||||
@@ -4256,7 +4256,7 @@ export default function ForwardPage() {
|
|||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Card>
|
<Card className="bg-white/20 dark:bg-zinc-900/20 backdrop-blur-3xl border border-white/80 dark:border-white/10 shadow-[0_15px_35px_rgba(0,0,0,0.1)]">
|
||||||
<CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]">
|
<CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]">
|
||||||
<h3 className="text-xl font-medium text-foreground tracking-tight mb-2">
|
<h3 className="text-xl font-medium text-foreground tracking-tight mb-2">
|
||||||
暂无规则配置
|
暂无规则配置
|
||||||
@@ -4302,7 +4302,7 @@ export default function ForwardPage() {
|
|||||||
</DndContext>
|
</DndContext>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Card>
|
<Card className="bg-white/20 dark:bg-zinc-900/20 backdrop-blur-3xl border border-white/80 dark:border-white/10 shadow-[0_15px_35px_rgba(0,0,0,0.1)]">
|
||||||
<CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]">
|
<CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]">
|
||||||
<h3 className="text-xl font-medium text-foreground tracking-tight mb-2">
|
<h3 className="text-xl font-medium text-foreground tracking-tight mb-2">
|
||||||
暂无规则配置
|
暂无规则配置
|
||||||
@@ -4562,7 +4562,7 @@ export default function ForwardPage() {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Card>
|
<Card className="bg-white/20 dark:bg-zinc-900/20 backdrop-blur-3xl border border-white/80 dark:border-white/10 shadow-[0_15px_35px_rgba(0,0,0,0.1)]">
|
||||||
<CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]">
|
<CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]">
|
||||||
<h3 className="text-xl font-medium text-foreground tracking-tight mb-2">
|
<h3 className="text-xl font-medium text-foreground tracking-tight mb-2">
|
||||||
暂无规则配置
|
暂无规则配置
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
|
import { AnimatedPage } from "@/components/animated-page";
|
||||||
|
import { PageLoadingState } from "@/components/page-state";
|
||||||
import { Card, CardBody, CardHeader } from "@/shadcn-bridge/heroui/card";
|
import { Card, CardBody, CardHeader } from "@/shadcn-bridge/heroui/card";
|
||||||
import { Button } from "@/shadcn-bridge/heroui/button";
|
import { Button } from "@/shadcn-bridge/heroui/button";
|
||||||
import { Input } from "@/shadcn-bridge/heroui/input";
|
import { Input } from "@/shadcn-bridge/heroui/input";
|
||||||
@@ -22,7 +24,7 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/shadcn-bridge/heroui/table";
|
} from "@/shadcn-bridge/heroui/table";
|
||||||
import { Chip } from "@/shadcn-bridge/heroui/chip";
|
import { Chip } from "@/shadcn-bridge/heroui/chip";
|
||||||
import { Spinner } from "@/shadcn-bridge/heroui/spinner";
|
|
||||||
import {
|
import {
|
||||||
assignGroupPermission,
|
assignGroupPermission,
|
||||||
assignTunnelsToGroup,
|
assignTunnelsToGroup,
|
||||||
@@ -469,14 +471,12 @@ export default function GroupPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-3 lg:px-6 py-8 space-y-6">
|
<AnimatedPage className="px-3 lg:px-6 py-8 space-y-6">
|
||||||
{loading && (
|
{loading ? (
|
||||||
<div className="flex justify-center py-10">
|
<PageLoadingState message="正在加载..." />
|
||||||
<Spinner size="lg" />
|
) : (
|
||||||
</div>
|
<>
|
||||||
)}
|
<Card>
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardHeader className="flex flex-row items-center gap-3 pb-2">
|
<CardHeader className="flex flex-row items-center gap-3 pb-2">
|
||||||
<h3 className="text-lg font-semibold">隧道分组</h3>
|
<h3 className="text-lg font-semibold">隧道分组</h3>
|
||||||
<Button
|
<Button
|
||||||
@@ -922,6 +922,8 @@ export default function GroupPage() {
|
|||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</>
|
||||||
|
)}
|
||||||
|
</AnimatedPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ const SortableItem = ({
|
|||||||
export default function NodePage() {
|
export default function NodePage() {
|
||||||
const [nodeList, setNodeList] = useState<Node[]>([]);
|
const [nodeList, setNodeList] = useState<Node[]>([]);
|
||||||
const [nodeOrder, setNodeOrder] = useState<number[]>([]);
|
const [nodeOrder, setNodeOrder] = useState<number[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(true);
|
||||||
const [localSearchKeyword, setLocalSearchKeyword] = useLocalStorageState(
|
const [localSearchKeyword, setLocalSearchKeyword] = useLocalStorageState(
|
||||||
"node-search-keyword-local",
|
"node-search-keyword-local",
|
||||||
"",
|
"",
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export default function PanelSharingPage() {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
const [nodes, setNodes] = useState<Node[]>([]);
|
const [nodes, setNodes] = useState<Node[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(true);
|
||||||
const [remoteUsageLoading, setRemoteUsageLoading] = useState(false);
|
const [remoteUsageLoading, setRemoteUsageLoading] = useState(false);
|
||||||
|
|
||||||
// Modals
|
// Modals
|
||||||
|
|||||||
@@ -1578,7 +1578,7 @@ export default function TunnelPage() {
|
|||||||
{/* 隧道卡片网格 */}
|
{/* 隧道卡片网格 */}
|
||||||
{tunnels.length > 0 ? (
|
{tunnels.length > 0 ? (
|
||||||
viewMode === "list" ? (
|
viewMode === "list" ? (
|
||||||
<Card>
|
<Card className="bg-white/20 dark:bg-zinc-900/20 backdrop-blur-3xl border border-white/80 dark:border-white/10 shadow-[0_15px_35px_rgba(0,0,0,0.1)]">
|
||||||
<Table
|
<Table
|
||||||
aria-label="隧道列表"
|
aria-label="隧道列表"
|
||||||
className="overflow-x-auto min-w-full"
|
className="overflow-x-auto min-w-full"
|
||||||
@@ -1721,7 +1721,7 @@ export default function TunnelPage() {
|
|||||||
{(listeners) => (
|
{(listeners) => (
|
||||||
<Card
|
<Card
|
||||||
key={tunnel.id}
|
key={tunnel.id}
|
||||||
className="group overflow-hidden"
|
className="group overflow-hidden bg-white/20 dark:bg-zinc-900/20 backdrop-blur-3xl border border-white/80 dark:border-white/10 shadow-[0_15px_35px_rgba(0,0,0,0.1)]"
|
||||||
>
|
>
|
||||||
<CardHeader className="pb-2 md:pb-2">
|
<CardHeader className="pb-2 md:pb-2">
|
||||||
<div className="flex justify-between items-start w-full">
|
<div className="flex justify-between items-start w-full">
|
||||||
@@ -1771,7 +1771,7 @@ export default function TunnelPage() {
|
|||||||
<div className="pt-2 border-t border-divider">
|
<div className="pt-2 border-t border-divider">
|
||||||
<div className="flex items-center justify-center gap-2 text-xs">
|
<div className="flex items-center justify-center gap-2 text-xs">
|
||||||
{/* 入口节点 */}
|
{/* 入口节点 */}
|
||||||
<div className="flex items-center gap-1 px-2 py-1 bg-primary-50 dark:bg-primary-100/20 rounded border border-primary-200 dark:border-primary-300/20">
|
<div className="flex items-center gap-1 px-2 py-1 bg-primary-50/30 dark:bg-primary-100/20 backdrop-blur-md rounded border border-primary-200/50 dark:border-primary-300/20">
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="w-3 h-3 text-primary-600"
|
className="w-3 h-3 text-primary-600"
|
||||||
@@ -1806,7 +1806,7 @@ export default function TunnelPage() {
|
|||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
{/* 转发链 */}
|
{/* 转发链 */}
|
||||||
<div className="flex items-center gap-1 px-2 py-1 bg-secondary-50 dark:bg-secondary-100/20 rounded border border-secondary-200 dark:border-secondary-300/20">
|
<div className="flex items-center gap-1 px-2 py-1 bg-secondary-50/30 dark:bg-secondary-100/20 backdrop-blur-md rounded border border-secondary-200/50 dark:border-secondary-300/20">
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="w-3 h-3 text-secondary-600"
|
className="w-3 h-3 text-secondary-600"
|
||||||
@@ -1844,7 +1844,7 @@ export default function TunnelPage() {
|
|||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
{/* 出口节点 */}
|
{/* 出口节点 */}
|
||||||
<div className="flex items-center gap-1 px-2 py-1 bg-success-50 dark:bg-success-100/20 rounded border border-success-200 dark:border-success-300/20">
|
<div className="flex items-center gap-1 px-2 py-1 bg-success-50/30 dark:bg-success-100/20 backdrop-blur-md rounded border border-success-200/50 dark:border-success-300/20">
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="w-3 h-3 text-success-600"
|
className="w-3 h-3 text-success-600"
|
||||||
@@ -1871,7 +1871,7 @@ export default function TunnelPage() {
|
|||||||
<div
|
<div
|
||||||
className={`grid gap-2 ${tunnel.type === 2 && tunnel.ipPreference ? "grid-cols-3" : "grid-cols-2"}`}
|
className={`grid gap-2 ${tunnel.type === 2 && tunnel.ipPreference ? "grid-cols-3" : "grid-cols-2"}`}
|
||||||
>
|
>
|
||||||
<div className="text-center p-1.5 bg-default-50 dark:bg-default-100/30 rounded">
|
<div className="text-center p-1.5 bg-white/5 dark:bg-black/5 backdrop-blur-3xl rounded-lg border border-divider">
|
||||||
<div className="text-xs text-default-500">
|
<div className="text-xs text-default-500">
|
||||||
流量计算
|
流量计算
|
||||||
</div>
|
</div>
|
||||||
@@ -1879,7 +1879,7 @@ export default function TunnelPage() {
|
|||||||
{getTunnelFlowDisplay(tunnel.flow)}
|
{getTunnelFlowDisplay(tunnel.flow)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center p-1.5 bg-default-50 dark:bg-default-100/30 rounded">
|
<div className="text-center p-1.5 bg-white/5 dark:bg-black/5 backdrop-blur-3xl rounded-lg border border-divider">
|
||||||
<div className="text-xs text-default-500">
|
<div className="text-xs text-default-500">
|
||||||
流量倍率
|
流量倍率
|
||||||
</div>
|
</div>
|
||||||
@@ -1888,7 +1888,7 @@ export default function TunnelPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{tunnel.type === 2 && tunnel.ipPreference && (
|
{tunnel.type === 2 && tunnel.ipPreference && (
|
||||||
<div className="text-center p-1.5 bg-default-50 dark:bg-default-100/30 rounded">
|
<div className="text-center p-1.5 bg-white/5 dark:bg-black/5 backdrop-blur-3xl rounded-lg border border-divider">
|
||||||
<div className="text-xs text-default-500">
|
<div className="text-xs text-default-500">
|
||||||
连接偏好
|
连接偏好
|
||||||
</div>
|
</div>
|
||||||
@@ -1986,7 +1986,7 @@ export default function TunnelPage() {
|
|||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
/* 空状态 */
|
/* 空状态 */
|
||||||
<Card>
|
<Card className="bg-white/20 dark:bg-zinc-900/20 backdrop-blur-3xl border border-white/80 dark:border-white/10 shadow-[0_15px_35px_rgba(0,0,0,0.1)]">
|
||||||
<CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]">
|
<CardBody className="text-center py-20 flex flex-col items-center justify-center min-h-[240px]">
|
||||||
<h3 className="text-xl font-medium text-foreground tracking-tight mb-2">
|
<h3 className="text-xl font-medium text-foreground tracking-tight mb-2">
|
||||||
暂无隧道配置
|
暂无隧道配置
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ const normalizeUserTunnelItem = (item: Partial<UserTunnel>): UserTunnel => {
|
|||||||
export default function UserPage() {
|
export default function UserPage() {
|
||||||
// 状态管理
|
// 状态管理
|
||||||
const [users, setUsers] = useState<User[]>([]);
|
const [users, setUsers] = useState<User[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(true);
|
||||||
const [searchKeyword, setSearchKeyword] = useLocalStorageState(
|
const [searchKeyword, setSearchKeyword] = useLocalStorageState(
|
||||||
"user-search-keyword",
|
"user-search-keyword",
|
||||||
"",
|
"",
|
||||||
@@ -466,10 +466,6 @@ export default function UserPage() {
|
|||||||
void loadMonitorPermissions();
|
void loadMonitorPermissions();
|
||||||
}, [loadMonitorPermissions, loadSpeedLimits, loadTunnels, loadUserGroups]);
|
}, [loadMonitorPermissions, loadSpeedLimits, loadTunnels, loadUserGroups]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
void loadUsers();
|
|
||||||
}, [loadUsers]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchDebounceRef.current) {
|
if (searchDebounceRef.current) {
|
||||||
clearTimeout(searchDebounceRef.current);
|
clearTimeout(searchDebounceRef.current);
|
||||||
|
|||||||
@@ -241,16 +241,16 @@ body {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background: url('https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?q=80&w=2564&auto=format&fit=crop') no-repeat center center;
|
background: url('/images/bg-light.jpg') no-repeat center center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root.dark .bg-mesh-gradient {
|
.dark .bg-mesh-gradient {
|
||||||
background-color: #0b1020;
|
background-color: #0b1020;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .bg-mesh-gradient::before {
|
.dark .bg-mesh-gradient::before {
|
||||||
background: url('https://images.unsplash.com/photo-1557682250-33bd709cbe85?q=80&w=2564&auto=format&fit=crop') no-repeat center center;
|
background: url('/images/bg-dark.jpg') no-repeat center center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user