mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-11-16 01:24:24 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 53e1efcdc1 | |||
| 71d5c36ec3 | |||
| be4b1f5f25 | |||
| cd4238274a | |||
| 25dc03c894 | |||
| 4cf9c9174a |
@@ -43,6 +43,14 @@ http {
|
|||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|
||||||
|
# PWA 和 Service Worker 不缓存
|
||||||
|
location ~* (sw\.js|service-worker\.js|workbox-.*\.js|manifest\.webmanifest)$ {
|
||||||
|
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||||
|
add_header Pragma "no-cache";
|
||||||
|
add_header Expires "0";
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
# 静态文件缓存
|
# 静态文件缓存
|
||||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||||
expires 30d;
|
expires 30d;
|
||||||
|
|||||||
@@ -95,11 +95,28 @@ function App() {
|
|||||||
const updateBg = () => {
|
const updateBg = () => {
|
||||||
const customBg = siteConfig.app_bg_image;
|
const customBg = siteConfig.app_bg_image;
|
||||||
if (customBg) {
|
if (customBg) {
|
||||||
document.documentElement.style.setProperty("--custom-bg-image", `url(${customBg})`);
|
if (customBg === "theme") {
|
||||||
document.documentElement.classList.add("has-custom-bg");
|
document.documentElement.style.removeProperty("--custom-bg-image");
|
||||||
|
document.documentElement.style.removeProperty("--custom-bg-color");
|
||||||
|
document.documentElement.classList.add("has-theme-bg");
|
||||||
|
document.documentElement.classList.remove("has-custom-bg");
|
||||||
|
} else if (customBg.startsWith("http") || customBg.startsWith("data:") || customBg.startsWith("/") || customBg.startsWith("blob:")) {
|
||||||
|
document.documentElement.style.setProperty("--custom-bg-image", `url(${customBg})`);
|
||||||
|
document.documentElement.style.setProperty("--custom-bg-color", "transparent");
|
||||||
|
document.documentElement.classList.add("has-custom-bg");
|
||||||
|
document.documentElement.classList.remove("has-theme-bg");
|
||||||
|
} else {
|
||||||
|
// Assume solid color like "#ffffff", "white", etc.
|
||||||
|
document.documentElement.style.setProperty("--custom-bg-image", "none");
|
||||||
|
document.documentElement.style.setProperty("--custom-bg-color", customBg);
|
||||||
|
document.documentElement.classList.add("has-custom-bg");
|
||||||
|
document.documentElement.classList.remove("has-theme-bg");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.style.removeProperty("--custom-bg-image");
|
document.documentElement.style.removeProperty("--custom-bg-image");
|
||||||
|
document.documentElement.style.removeProperty("--custom-bg-color");
|
||||||
document.documentElement.classList.remove("has-custom-bg");
|
document.documentElement.classList.remove("has-custom-bg");
|
||||||
|
document.documentElement.classList.remove("has-theme-bg");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
updateBg();
|
updateBg();
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const ThemeSettings: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="border border-gray-200 dark:border-gray-700">
|
<Card className="shadow-md">
|
||||||
<CardBody className="p-6">
|
<CardBody className="p-6">
|
||||||
<h2 className="text-lg font-medium text-gray-900 dark:text-white mb-5">
|
<h2 className="text-lg font-medium text-gray-900 dark:text-white mb-5">
|
||||||
主题设置
|
主题设置
|
||||||
|
|||||||
@@ -373,16 +373,15 @@ export default function AdminLayout({
|
|||||||
{/* 左侧菜单栏 */}
|
{/* 左侧菜单栏 */}
|
||||||
<aside
|
<aside
|
||||||
className={`
|
className={`
|
||||||
${isMobile ? "fixed h-screen top-0 left-0 rounded-r-3xl" : "relative h-full rounded-3xl"}
|
${isMobile ? "fixed h-screen top-0 left-0 rounded-r-3xl" : "relative h-full rounded-3xl"}
|
||||||
${isMobile && !mobileMenuVisible ? "-translate-x-full" : "translate-x-0"}
|
${isMobile && !mobileMenuVisible ? "-translate-x-full" : "translate-x-0"}
|
||||||
${isMobile ? "w-64" : isCollapsed ? "w-20" : "w-[260px]"}
|
${isMobile ? "w-64" : isCollapsed ? "w-20" : "w-[260px]"}
|
||||||
bg-white/60 dark:bg-zinc-900/60 backdrop-blur-3xl
|
bg-white/20 dark:bg-zinc-900/20 backdrop-blur-3xl
|
||||||
shadow-[0_10px_30px_rgba(0,0,0,0.1)]
|
shadow-[0_10px_30px_rgba(0,0,0,0.1)]
|
||||||
border border-white/80 dark:border-white/10
|
border border-white/80 dark:border-white/10
|
||||||
z-50
|
z-50
|
||||||
transition-all duration-300 ease-in-out
|
transition-all duration-300 ease-in-out
|
||||||
flex flex-col flex-shrink-0
|
flex flex-col flex-shrink-0 `}
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
{/* Logo 区域 */}
|
{/* Logo 区域 */}
|
||||||
<div className="px-6 py-8 flex items-center overflow-hidden whitespace-nowrap box-border">
|
<div className="px-6 py-8 flex items-center overflow-hidden whitespace-nowrap box-border">
|
||||||
@@ -531,7 +530,7 @@ export default function AdminLayout({
|
|||||||
{isMobile && (
|
{isMobile && (
|
||||||
<Button
|
<Button
|
||||||
isIconOnly
|
isIconOnly
|
||||||
className="absolute top-4 left-4 z-40 bg-white/60 dark:bg-zinc-900/60 backdrop-blur-md shadow-sm border border-white/80 dark:border-white/10"
|
className="absolute top-4 left-4 z-40 bg-white/20 dark:bg-zinc-900/20 backdrop-blur-md shadow-sm border border-white/80 dark:border-white/10"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
onPress={toggleMobileMenu}
|
onPress={toggleMobileMenu}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default function H5SimpleLayout({
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col min-h-screen bg-mesh-gradient">
|
<div className="flex flex-col min-h-screen bg-mesh-gradient">
|
||||||
{/* 顶部导航栏 */}
|
{/* 顶部导航栏 */}
|
||||||
<header className="bg-white/60 dark:bg-zinc-900/60 backdrop-blur-xl shadow-sm border-b border-white/80 dark:border-white/10 h-14 safe-top flex-shrink-0 flex items-center justify-between px-4 relative z-10">
|
<header className="bg-white/20 dark:bg-zinc-900/20 backdrop-blur-xl shadow-sm border-b border-white/80 dark:border-white/10 h-14 safe-top flex-shrink-0 flex items-center justify-between px-4 relative z-10">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button isIconOnly size="sm" variant="light" onPress={handleBack}>
|
<Button isIconOnly size="sm" variant="light" onPress={handleBack}>
|
||||||
<BackIcon className="w-5 h-5" />
|
<BackIcon className="w-5 h-5" />
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ export default function H5Layout({ children }: { children: React.ReactNode }) {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col min-h-screen bg-mesh-gradient">
|
<div className="flex flex-col min-h-screen bg-mesh-gradient">
|
||||||
{/* 顶部导航栏 */}
|
{/* 顶部导航栏 */}
|
||||||
<header className="bg-white/60 dark:bg-zinc-900/60 backdrop-blur-xl shadow-sm border-b border-white/80 dark:border-white/10 h-14 safe-top flex-shrink-0 flex items-center justify-between px-4 relative z-10">
|
<header className="bg-white/20 dark:bg-zinc-900/20 backdrop-blur-xl shadow-sm border-b border-white/80 dark:border-white/10 h-14 safe-top flex-shrink-0 flex items-center justify-between px-4 relative z-10">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<BrandLogo size={20} />
|
<BrandLogo size={20} />
|
||||||
<h1 className="text-sm font-bold text-foreground">
|
<h1 className="text-sm font-bold text-foreground">
|
||||||
@@ -186,7 +186,7 @@ export default function H5Layout({ children }: { children: React.ReactNode }) {
|
|||||||
<div aria-hidden className="h-[calc(4rem+var(--safe-area-bottom))]" />
|
<div aria-hidden className="h-[calc(4rem+var(--safe-area-bottom))]" />
|
||||||
|
|
||||||
{/* 底部Tabbar */}
|
{/* 底部Tabbar */}
|
||||||
<nav className="bg-white/70 dark:bg-zinc-900/70 backdrop-blur-2xl border-t border-white/80 dark:border-white/10 h-[calc(4rem+var(--safe-area-bottom))] flex-shrink-0 flex items-center justify-around px-2 fixed bottom-0 left-0 right-0 z-30">
|
<nav className="bg-white/20 dark:bg-zinc-900/20 backdrop-blur-2xl border-t border-white/80 dark:border-white/10 h-[calc(4rem+var(--safe-area-bottom))] flex-shrink-0 flex items-center justify-around px-2 fixed bottom-0 left-0 right-0 z-30">
|
||||||
{filteredTabItems.map((item) => {
|
{filteredTabItems.map((item) => {
|
||||||
const isActive = location.pathname === item.path;
|
const isActive = location.pathname === item.path;
|
||||||
const isMonitor = item.path === "/monitor";
|
const isMonitor = item.path === "/monitor";
|
||||||
|
|||||||
@@ -1,12 +1,42 @@
|
|||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import { registerSW } from "virtual:pwa-register";
|
import { registerSW } from "virtual:pwa-register";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
import App from "./App.tsx";
|
import App from "./App.tsx";
|
||||||
import { Provider } from "./provider.tsx";
|
import { Provider } from "./provider.tsx";
|
||||||
import "@/styles/globals.css";
|
import "@/styles/globals.css";
|
||||||
|
|
||||||
registerSW({ immediate: true });
|
const updateSW = registerSW({
|
||||||
|
immediate: true,
|
||||||
|
onNeedRefresh() {
|
||||||
|
toast(
|
||||||
|
(t) => (
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<span className="text-sm font-medium text-foreground">发现新版本,是否立即刷新以应用更新?</span>
|
||||||
|
<div className="flex gap-2 justify-end">
|
||||||
|
<button
|
||||||
|
className="px-3 py-1.5 text-xs font-medium bg-primary text-primary-foreground hover:bg-primary/90 rounded-md transition-colors"
|
||||||
|
onClick={() => {
|
||||||
|
updateSW(true);
|
||||||
|
toast.dismiss(t.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
刷新
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="px-3 py-1.5 text-xs font-medium bg-default-200 text-default-700 hover:bg-default-300 rounded-md transition-colors"
|
||||||
|
onClick={() => toast.dismiss(t.id)}
|
||||||
|
>
|
||||||
|
稍后
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
{ duration: Infinity, position: "bottom-right" }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
|
|||||||
@@ -621,10 +621,13 @@ export default function ConfigPage() {
|
|||||||
|
|
||||||
const renderBgImageUploader = () => {
|
const renderBgImageUploader = () => {
|
||||||
const bgImage = configs["app_bg_image"] || "";
|
const bgImage = configs["app_bg_image"] || "";
|
||||||
|
const isImage = bgImage.startsWith("http") || bgImage.startsWith("data:") || bgImage.startsWith("/") || bgImage.startsWith("blob:");
|
||||||
|
const isTheme = bgImage === "theme";
|
||||||
|
const isSolidColor = bgImage && !isImage && !isTheme;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 w-full">
|
<div className="flex flex-col gap-4 w-full">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex flex-wrap items-center gap-4">
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
accept="image/*"
|
accept="image/*"
|
||||||
@@ -638,7 +641,23 @@ export default function ConfigPage() {
|
|||||||
onPress={() => bgImageFileInputRef.current?.click()}
|
onPress={() => bgImageFileInputRef.current?.click()}
|
||||||
isLoading={bgImageUploading}
|
isLoading={bgImageUploading}
|
||||||
>
|
>
|
||||||
选择图片
|
上传图片
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
variant="flat"
|
||||||
|
onPress={() => handleConfigChange("app_bg_image", "theme")}
|
||||||
|
isDisabled={bgImageUploading || isTheme}
|
||||||
|
>
|
||||||
|
自适应纯色 (跟随深色模式)
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="default"
|
||||||
|
variant="flat"
|
||||||
|
onPress={() => handleConfigChange("app_bg_image", "#ffffff")}
|
||||||
|
isDisabled={bgImageUploading || bgImage === "#ffffff"}
|
||||||
|
>
|
||||||
|
白色纯色
|
||||||
</Button>
|
</Button>
|
||||||
{bgImage && (
|
{bgImage && (
|
||||||
<Button
|
<Button
|
||||||
@@ -647,11 +666,12 @@ export default function ConfigPage() {
|
|||||||
onPress={() => handleConfigChange("app_bg_image", "")}
|
onPress={() => handleConfigChange("app_bg_image", "")}
|
||||||
isDisabled={bgImageUploading}
|
isDisabled={bgImageUploading}
|
||||||
>
|
>
|
||||||
清除背景
|
恢复默认
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{bgImage && (
|
|
||||||
|
{bgImage && isImage && (
|
||||||
<div className="relative rounded-xl overflow-hidden border border-divider">
|
<div className="relative rounded-xl overflow-hidden border border-divider">
|
||||||
<img
|
<img
|
||||||
src={bgImage}
|
src={bgImage}
|
||||||
@@ -663,6 +683,23 @@ export default function ConfigPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{bgImage && isTheme && (
|
||||||
|
<div className="relative rounded-xl border border-divider bg-background h-32 flex items-center justify-center">
|
||||||
|
<span className="text-foreground text-sm font-medium">当前为自适应纯色背景</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{bgImage && isSolidColor && (
|
||||||
|
<div
|
||||||
|
className="relative rounded-xl border border-divider h-32 flex items-center justify-center"
|
||||||
|
style={{ backgroundColor: bgImage }}
|
||||||
|
>
|
||||||
|
<span className="text-gray-500 bg-white/80 dark:bg-black/80 px-2 py-1 rounded text-sm font-medium border border-gray-200 dark:border-gray-800 shadow-sm">
|
||||||
|
纯色背景 ({bgImage})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4176,7 +4176,7 @@ export default function ForwardPage() {
|
|||||||
{sortedForwards.length} 条规则
|
{sortedForwards.length} 条规则
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Card className="overflow-hidden rounded-2xl border border-white/80 dark:border-white/10 bg-white/40 dark:bg-zinc-900/40 backdrop-blur-2xl shadow-[0_15px_35px_rgba(0,0,0,0.1)]">
|
<Card className="overflow-hidden rounded-2xl border border-white/80 dark:border-white/10 bg-white/20 dark:bg-zinc-900/20 backdrop-blur-3xl shadow-[0_15px_35px_rgba(0,0,0,0.1)]">
|
||||||
<DndContext
|
<DndContext
|
||||||
collisionDetection={pointerWithin}
|
collisionDetection={pointerWithin}
|
||||||
sensors={sensors}
|
sensors={sensors}
|
||||||
@@ -4192,7 +4192,7 @@ export default function ForwardPage() {
|
|||||||
wrapper: "bg-transparent p-0 shadow-none border-none overflow-hidden rounded-[24px]",
|
wrapper: "bg-transparent p-0 shadow-none border-none overflow-hidden rounded-[24px]",
|
||||||
th: "bg-default-100/50 text-default-600 font-semibold text-sm border-b border-divider py-3 uppercase tracking-wider first:rounded-tl-[24px] last:rounded-tr-[24px]",
|
th: "bg-default-100/50 text-default-600 font-semibold text-sm border-b border-divider py-3 uppercase tracking-wider first:rounded-tl-[24px] last:rounded-tr-[24px]",
|
||||||
td: "py-3 border-b border-divider/50 group-data-[last=true]:border-b-0",
|
td: "py-3 border-b border-divider/50 group-data-[last=true]:border-b-0",
|
||||||
tr: "hover:bg-white/40 dark:hover:bg-white/10 transition-colors group",
|
tr: "hover:bg-white/10 dark:hover:bg-white/5 transition-colors group",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
@@ -4327,9 +4327,9 @@ export default function ForwardPage() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`grouped-table-${group.userId}-${group.userName}`}
|
key={`grouped-table-${group.userId}-${group.userName}`}
|
||||||
className="overflow-hidden rounded-2xl border border-white/80 dark:border-white/10 bg-white/60 dark:bg-zinc-900/60 backdrop-blur-3xl shadow-[0_15px_35px_rgba(0,0,0,0.1)]"
|
className="overflow-hidden rounded-2xl border border-white/80 dark:border-white/10 bg-white/20 dark:bg-zinc-900/20 backdrop-blur-3xl shadow-[0_15px_35px_rgba(0,0,0,0.1)]"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between border-b border-white/20 dark:border-white/10 bg-white/40 dark:bg-black/40 backdrop-blur-md px-5 py-4">
|
<div className="flex items-center justify-between border-b border-white/20 dark:border-white/10 bg-white/20 dark:bg-black/20 backdrop-blur-3xl px-5 py-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-sm font-semibold text-foreground">
|
<span className="text-sm font-semibold text-foreground">
|
||||||
{group.userName}
|
{group.userName}
|
||||||
@@ -4415,10 +4415,10 @@ export default function ForwardPage() {
|
|||||||
collapsed={collapsed}
|
collapsed={collapsed}
|
||||||
countClassName="text-xs text-default-600"
|
countClassName="text-xs text-default-600"
|
||||||
groupUserId={group.userId}
|
groupUserId={group.userId}
|
||||||
headerClassName="flex items-center justify-between border-b border-white/20 dark:border-white/10 bg-white/40 dark:bg-black/40 backdrop-blur-md px-4 py-2.5"
|
headerClassName="flex items-center justify-between border-b border-white/20 dark:border-white/10 bg-white/20 dark:bg-black/20 backdrop-blur-3xl px-4 py-2.5"
|
||||||
titleClassName="truncate text-sm font-semibold text-default-700"
|
titleClassName="truncate text-sm font-semibold text-default-700"
|
||||||
tunnel={tunnel}
|
tunnel={tunnel}
|
||||||
wrapperClassName="overflow-hidden rounded-2xl border border-white/80 dark:border-white/10 bg-white/40 dark:bg-zinc-900/40 backdrop-blur-2xl shadow-[0_15px_35px_rgba(0,0,0,0.1)]"
|
wrapperClassName="overflow-hidden rounded-2xl border border-white/80 dark:border-white/10 bg-white/20 dark:bg-zinc-900/20 backdrop-blur-3xl shadow-[0_15px_35px_rgba(0,0,0,0.1)]"
|
||||||
onToggleCollapsed={() =>
|
onToggleCollapsed={() =>
|
||||||
toggleTunnelGroupCollapsed(
|
toggleTunnelGroupCollapsed(
|
||||||
group.userId,
|
group.userId,
|
||||||
@@ -4638,10 +4638,10 @@ export default function ForwardPage() {
|
|||||||
collapsed={collapsed}
|
collapsed={collapsed}
|
||||||
countClassName="text-xs text-default-600"
|
countClassName="text-xs text-default-600"
|
||||||
groupUserId={group.userId}
|
groupUserId={group.userId}
|
||||||
headerClassName="flex items-center justify-between rounded-lg bg-white/40 dark:bg-black/40 backdrop-blur-md border border-white/20 dark:border-white/10 px-3 py-2"
|
headerClassName="flex items-center justify-between rounded-lg bg-white/10 dark:bg-black/10 backdrop-blur-md border border-white/20 dark:border-white/10 px-3 py-2"
|
||||||
titleClassName="truncate text-sm font-semibold text-default-700"
|
titleClassName="truncate text-sm font-semibold text-default-700"
|
||||||
tunnel={tunnel}
|
tunnel={tunnel}
|
||||||
wrapperClassName="rounded-xl border border-divider bg-white/20 dark:bg-black/20 space-y-3"
|
wrapperClassName="rounded-xl border border-divider bg-white/5 dark:bg-black/5 space-y-3"
|
||||||
onToggleCollapsed={() =>
|
onToggleCollapsed={() =>
|
||||||
toggleTunnelGroupCollapsed(
|
toggleTunnelGroupCollapsed(
|
||||||
group.userId,
|
group.userId,
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export default function IndexPage() {
|
|||||||
initial={{ opacity: 0, y: 24 }}
|
initial={{ opacity: 0, y: 24 }}
|
||||||
transition={{ duration: 0.35, ease: [0.25, 0.46, 0.45, 0.94] }}
|
transition={{ duration: 0.35, ease: [0.25, 0.46, 0.45, 0.94] }}
|
||||||
>
|
>
|
||||||
<Card className="w-full bg-white/60 dark:bg-zinc-900/60 backdrop-blur-3xl shadow-[0_20px_40px_rgba(0,0,0,0.15)] border-white/80 dark:border-white/10 rounded-[32px] p-2 sm:p-4">
|
<Card className="w-full bg-white/20 dark:bg-zinc-900/20 backdrop-blur-3xl shadow-[0_20px_40px_rgba(0,0,0,0.15)] border-white/80 dark:border-white/10 rounded-[32px] p-2 sm:p-4">
|
||||||
<CardHeader className="pb-0 pt-6 px-6 flex-col items-center">
|
<CardHeader className="pb-0 pt-6 px-6 flex-col items-center">
|
||||||
<div className="w-14 h-14 bg-primary rounded-2xl flex items-center justify-center mb-4 shadow-sm text-white">
|
<div className="w-14 h-14 bg-primary rounded-2xl flex items-center justify-center mb-4 shadow-sm text-white">
|
||||||
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
|
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
|
||||||
|
|||||||
@@ -327,7 +327,7 @@ export default function LimitPage() {
|
|||||||
<StaggerList className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4">
|
<StaggerList className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4">
|
||||||
{filteredRules.map((rule) => (
|
{filteredRules.map((rule) => (
|
||||||
<StaggerItem key={rule.id}>
|
<StaggerItem key={rule.id}>
|
||||||
<Card className="shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden h-full">
|
<Card className="shadow-sm overflow-hidden h-full">
|
||||||
<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">
|
||||||
<div>
|
<div>
|
||||||
@@ -413,7 +413,7 @@ export default function LimitPage() {
|
|||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
/* 空状态 */
|
/* 空状态 */
|
||||||
<Card className="shadow-sm border border-gray-200 dark:border-gray-700 bg-default-50/50">
|
<Card className="shadow-sm bg-default-50/50">
|
||||||
<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">
|
||||||
暂无限速规则
|
暂无限速规则
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ export default function MonitorPage() {
|
|||||||
<AnimatedPage className="px-3 lg:px-6 py-8">
|
<AnimatedPage className="px-3 lg:px-6 py-8">
|
||||||
{/* 顶部英雄数据指标 (Hero Metrics) */}
|
{/* 顶部英雄数据指标 (Hero Metrics) */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
|
||||||
<div className="rounded-3xl border border-white/80 dark:border-white/10 bg-white/60 dark:bg-zinc-900/60 backdrop-blur-3xl shadow-[0_10px_30px_rgba(0,0,0,0.1)] p-6 relative overflow-hidden flex flex-col justify-between h-40">
|
<div className="rounded-3xl border border-white/80 dark:border-white/10 bg-white/20 dark:bg-zinc-900/20 backdrop-blur-3xl shadow-[0_10px_30px_rgba(0,0,0,0.1)] p-6 relative overflow-hidden flex flex-col justify-between h-40">
|
||||||
<div className="flex justify-between items-center z-10 relative">
|
<div className="flex justify-between items-center z-10 relative">
|
||||||
<span className="text-default-600 font-medium text-sm">System Load</span>
|
<span className="text-default-600 font-medium text-sm">System Load</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -189,7 +189,7 @@ export default function MonitorPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rounded-3xl border border-white/80 dark:border-white/10 bg-white/60 dark:bg-zinc-900/60 backdrop-blur-3xl shadow-[0_10px_30px_rgba(0,0,0,0.1)] p-6 relative overflow-hidden flex flex-col justify-between h-40">
|
<div className="rounded-3xl border border-white/80 dark:border-white/10 bg-white/20 dark:bg-zinc-900/20 backdrop-blur-3xl shadow-[0_10px_30px_rgba(0,0,0,0.1)] p-6 relative overflow-hidden flex flex-col justify-between h-40">
|
||||||
<div className="flex justify-between items-center z-10 relative">
|
<div className="flex justify-between items-center z-10 relative">
|
||||||
<span className="text-default-600 font-medium text-sm">Active Connections</span>
|
<span className="text-default-600 font-medium text-sm">Active Connections</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -206,7 +206,7 @@ export default function MonitorPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rounded-3xl border border-white/80 dark:border-white/10 bg-white/60 dark:bg-zinc-900/60 backdrop-blur-3xl shadow-[0_10px_30px_rgba(0,0,0,0.1)] p-6 relative overflow-hidden flex flex-col justify-between h-40">
|
<div className="rounded-3xl border border-white/80 dark:border-white/10 bg-white/20 dark:bg-zinc-900/20 backdrop-blur-3xl shadow-[0_10px_30px_rgba(0,0,0,0.1)] p-6 relative overflow-hidden flex flex-col justify-between h-40">
|
||||||
<div className="flex justify-between items-center z-10 relative">
|
<div className="flex justify-between items-center z-10 relative">
|
||||||
<span className="text-default-600 font-medium text-sm">Bandwidth</span>
|
<span className="text-default-600 font-medium text-sm">Bandwidth</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -253,7 +253,7 @@ export default function MonitorPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tab Switcher */}
|
{/* Tab Switcher */}
|
||||||
<div className="flex items-center gap-1 p-1 rounded-2xl bg-white/40 dark:bg-black/40 backdrop-blur-lg border border-white/50 dark:border-white/10 w-fit shadow-sm">
|
<div className="flex items-center gap-1 p-1 rounded-2xl bg-white/20 dark:bg-black/20 backdrop-blur-3xl border border-white/50 dark:border-white/10 w-fit shadow-sm">
|
||||||
<button
|
<button
|
||||||
className={`flex items-center gap-1.5 px-5 py-2 rounded-xl text-sm font-semibold transition-all duration-200 ${
|
className={`flex items-center gap-1.5 px-5 py-2 rounded-xl text-sm font-semibold transition-all duration-200 ${
|
||||||
activeTab === "nodes"
|
activeTab === "nodes"
|
||||||
|
|||||||
@@ -1770,7 +1770,7 @@ export default function NodePage() {
|
|||||||
wrapper: "bg-transparent p-0 shadow-none border-none overflow-hidden rounded-2xl",
|
wrapper: "bg-transparent p-0 shadow-none border-none overflow-hidden rounded-2xl",
|
||||||
th: "bg-transparent text-default-600 font-semibold text-sm border-b border-white/20 dark:border-white/10 py-3 uppercase tracking-wider first:rounded-tl-[24px] last:rounded-tr-[24px]",
|
th: "bg-transparent text-default-600 font-semibold text-sm border-b border-white/20 dark:border-white/10 py-3 uppercase tracking-wider first:rounded-tl-[24px] last:rounded-tr-[24px]",
|
||||||
td: "py-3 border-b border-divider/50 group-data-[last=true]:border-b-0",
|
td: "py-3 border-b border-divider/50 group-data-[last=true]:border-b-0",
|
||||||
tr: "hover:bg-white/40 dark:hover:bg-white/10 transition-colors",
|
tr: "hover:bg-white/10 dark:hover:bg-white/5 transition-colors",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
@@ -2249,7 +2249,7 @@ export default function NodePage() {
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-h-20 overflow-y-auto rounded bg-white/70 dark:bg-black/20 p-1.5 [scrollbar-width:thin] [&::-webkit-scrollbar]:w-1">
|
<div className="max-h-20 overflow-y-auto rounded bg-white/10 dark:bg-black/10 p-1.5 [scrollbar-width:thin] [&::-webkit-scrollbar]:w-1">
|
||||||
{remoteUsage.usedPorts.length > 0 ? (
|
{remoteUsage.usedPorts.length > 0 ? (
|
||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
{remoteUsage.usedPorts.map((port) => (
|
{remoteUsage.usedPorts.map((port) => (
|
||||||
@@ -2285,7 +2285,7 @@ export default function NodePage() {
|
|||||||
remoteUsage.bindings.map((binding) => (
|
remoteUsage.bindings.map((binding) => (
|
||||||
<div
|
<div
|
||||||
key={binding.bindingId}
|
key={binding.bindingId}
|
||||||
className="rounded border border-default-200 dark:border-default-100/30 bg-white/70 dark:bg-black/20 p-2"
|
className="rounded border border-default-200 dark:border-default-100/30 bg-white/10 dark:bg-black/10 p-2"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -1446,7 +1446,7 @@ export function MonitorView({ nodeMap, viewMode = "grid" }: MonitorViewProps) {
|
|||||||
|
|
||||||
{/* Active monitor info bar */}
|
{/* Active monitor info bar */}
|
||||||
{resolvedActiveMonitor && (
|
{resolvedActiveMonitor && (
|
||||||
<div className="flex items-center justify-between gap-3 p-3 rounded-2xl bg-white/40 dark:bg-black/40 backdrop-blur-md border border-white/50 dark:border-white/10 shadow-[0_4px_12px_rgba(0,0,0,0.05)]">
|
<div className="flex items-center justify-between gap-3 p-3 rounded-2xl bg-white/20 dark:bg-black/20 backdrop-blur-3xl border border-white/50 dark:border-white/10 shadow-[0_4px_12px_rgba(0,0,0,0.05)]">
|
||||||
<div className="flex items-center gap-3 min-w-0 flex-wrap">
|
<div className="flex items-center gap-3 min-w-0 flex-wrap">
|
||||||
<Chip size="sm" color="primary" variant="flat">{resolvedActiveMonitor.type.toUpperCase()}</Chip>
|
<Chip size="sm" color="primary" variant="flat">{resolvedActiveMonitor.type.toUpperCase()}</Chip>
|
||||||
<span className="font-mono text-xs text-default-500">{resolvedActiveMonitor.target}</span>
|
<span className="font-mono text-xs text-default-500">{resolvedActiveMonitor.target}</span>
|
||||||
|
|||||||
@@ -1586,7 +1586,7 @@ export default function TunnelPage() {
|
|||||||
wrapper: "bg-transparent p-0 shadow-none border-none overflow-hidden rounded-[24px]",
|
wrapper: "bg-transparent p-0 shadow-none border-none overflow-hidden rounded-[24px]",
|
||||||
th: "bg-transparent text-default-600 font-semibold text-sm border-b border-white/20 dark:border-white/10 py-3 uppercase tracking-wider first:rounded-tl-[24px] last:rounded-tr-[24px]",
|
th: "bg-transparent text-default-600 font-semibold text-sm border-b border-white/20 dark:border-white/10 py-3 uppercase tracking-wider first:rounded-tl-[24px] last:rounded-tr-[24px]",
|
||||||
td: "py-3 border-b border-divider/50 group-data-[last=true]:border-b-0",
|
td: "py-3 border-b border-divider/50 group-data-[last=true]:border-b-0",
|
||||||
tr: "hover:bg-white/40 dark:hover:bg-white/10 transition-colors",
|
tr: "hover:bg-white/10 dark:hover:bg-white/5 transition-colors",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
|
|||||||
@@ -227,24 +227,56 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bg-mesh-gradient {
|
.bg-mesh-gradient {
|
||||||
background: radial-gradient(at 0% 0%, #ff9a9e 0%, transparent 50%),
|
|
||||||
radial-gradient(at 100% 0%, #fecfef 0%, transparent 50%),
|
|
||||||
radial-gradient(at 100% 100%, #c2e9fb 0%, transparent 50%),
|
|
||||||
radial-gradient(at 0% 100%, #a1c4fd 0%, transparent 50%);
|
|
||||||
background-color: #f2f2f7;
|
background-color: #f2f2f7;
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .bg-mesh-gradient {
|
.bg-mesh-gradient::before {
|
||||||
background: radial-gradient(at 0% 0%, #4a2b30 0%, transparent 50%),
|
content: "";
|
||||||
radial-gradient(at 100% 0%, #493b45 0%, transparent 50%),
|
position: fixed;
|
||||||
radial-gradient(at 100% 100%, #354756 0%, transparent 50%),
|
top: 0;
|
||||||
radial-gradient(at 0% 100%, #2f394f 0%, transparent 50%);
|
left: 0;
|
||||||
background-color: #1c1c1e;
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: -1;
|
||||||
|
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-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark .bg-mesh-gradient {
|
||||||
|
background-color: #0b1020;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.has-theme-bg .bg-mesh-gradient {
|
||||||
|
background-image: none !important;
|
||||||
|
background-color: #f6f7fb !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark.has-theme-bg .bg-mesh-gradient {
|
||||||
|
background-color: #0b1020 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.has-theme-bg .bg-mesh-gradient::before {
|
||||||
|
background-image: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root.has-custom-bg .bg-mesh-gradient {
|
:root.has-custom-bg .bg-mesh-gradient {
|
||||||
background: var(--custom-bg-image) no-repeat center center fixed !important;
|
background-image: none !important;
|
||||||
|
background-color: var(--custom-bg-color, transparent) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.has-custom-bg .bg-mesh-gradient::before {
|
||||||
|
background-image: var(--custom-bg-image, none) !important;
|
||||||
background-size: cover !important;
|
background-size: cover !important;
|
||||||
|
background-position: center center !important;
|
||||||
|
background-repeat: no-repeat !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default defineConfig({
|
|||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
VitePWA({
|
VitePWA({
|
||||||
registerType: "autoUpdate",
|
registerType: "prompt",
|
||||||
injectRegister: "auto",
|
injectRegister: "auto",
|
||||||
includeAssets: ["favicon.ico", "apple-touch-icon.png"],
|
includeAssets: ["favicon.ico", "apple-touch-icon.png"],
|
||||||
manifest: {
|
manifest: {
|
||||||
|
|||||||
Reference in New Issue
Block a user