import { Delete as DeleteIcon } from "@mui/icons-material"; import { Box, Button, Divider, List, ListItem, TextField } from "@mui/material"; import { useLockFn, useRequest } from "ahooks"; import { forwardRef, useImperativeHandle, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { BaseDialog, Switch } from "@/components/base"; import { useClash } from "@/hooks/use-clash"; import { showNotice } from "@/services/noticeService"; // 定义开发环境的URL列表 // 这些URL在开发模式下会被自动包含在允许的来源中 // 在生产环境中,这些URL会被过滤掉 // 这样可以确保在生产环境中不会意外暴露开发环境的URL const DEV_URLS = [ "tauri://localhost", "http://tauri.localhost", "http://localhost:3000", ]; // 获取完整的源列表,包括开发URL const getFullOrigins = (origins: string[]) => { // 合并现有源和开发URL,并去重 const allOrigins = [...origins, ...DEV_URLS]; const uniqueOrigins = [...new Set(allOrigins)]; return uniqueOrigins; }; // 过滤基础URL(确保后续添加) const filterBaseOriginsForUI = (origins: string[]) => { return origins.filter((origin: string) => !DEV_URLS.includes(origin.trim())); }; // 统一使用的按钮样式 const buttonStyle = { borderRadius: "8px", textTransform: "none", boxShadow: "0 2px 4px rgba(0,0,0,0.1)", transition: "all 0.3s ease", "&:hover": { boxShadow: "0 4px 8px rgba(0,0,0,0.15)", transform: "translateY(-1px)", }, "&:active": { transform: "translateY(0)", }, }; // 添加按钮样式 const addButtonStyle = { ...buttonStyle, backgroundColor: "#4CAF50", color: "white", "&:hover": { backgroundColor: "#388E3C", }, }; // 删除按钮样式 const deleteButtonStyle = { ...buttonStyle, backgroundColor: "#FF5252", color: "white", "&:hover": { backgroundColor: "#D32F2F", }, }; interface ClashHeaderConfigingRef { open: () => void; close: () => void; } export const HeaderConfiguration = forwardRef( (props, ref) => { const { t } = useTranslation(); const { clash, mutateClash, patchClash } = useClash(); const [open, setOpen] = useState(false); // CORS配置状态管理 const [corsConfig, setCorsConfig] = useState<{ allowPrivateNetwork: boolean; allowOrigins: string[]; }>(() => { const cors = clash?.["external-controller-cors"]; const origins = cors?.["allow-origins"] ?? []; return { allowPrivateNetwork: cors?.["allow-private-network"] ?? true, allowOrigins: filterBaseOriginsForUI(origins), }; }); // 处理CORS配置变更 const handleCorsConfigChange = ( key: "allowPrivateNetwork" | "allowOrigins", value: boolean | string[], ) => { setCorsConfig((prev) => ({ ...prev, [key]: value, })); }; // 添加新的允许来源 const handleAddOrigin = () => { handleCorsConfigChange("allowOrigins", [...corsConfig.allowOrigins, ""]); }; // 更新允许来源列表中的某一项 const handleUpdateOrigin = (index: number, value: string) => { const newOrigins = [...corsConfig.allowOrigins]; newOrigins[index] = value; handleCorsConfigChange("allowOrigins", newOrigins); }; // 删除允许来源列表中的某一项 const handleDeleteOrigin = (index: number) => { const newOrigins = [...corsConfig.allowOrigins]; newOrigins.splice(index, 1); handleCorsConfigChange("allowOrigins", newOrigins); }; // 保存配置请求 const { loading, run: saveConfig } = useRequest( async () => { // 保存时使用完整的源列表(包括开发URL) const fullOrigins = getFullOrigins(corsConfig.allowOrigins); await patchClash({ "external-controller-cors": { "allow-private-network": corsConfig.allowPrivateNetwork, "allow-origins": fullOrigins.filter( (origin: string) => origin.trim() !== "", ), }, }); await mutateClash(); }, { manual: true, onSuccess: () => { setOpen(false); showNotice.success( "shared.feedback.notifications.common.saveSuccess", ); }, onError: () => { showNotice.error("shared.feedback.notifications.common.saveFailed"); }, }, ); useImperativeHandle(ref, () => ({ open: () => { const cors = clash?.["external-controller-cors"]; const origins = cors?.["allow-origins"] ?? []; setCorsConfig({ allowPrivateNetwork: cors?.["allow-private-network"] ?? true, allowOrigins: filterBaseOriginsForUI(origins), }); setOpen(true); }, close: () => setOpen(false), })); const handleSave = useLockFn(async () => { await saveConfig(); }); const originEntries = useMemo(() => { const counts: Record = {}; return corsConfig.allowOrigins.map((origin, index) => { const occurrence = (counts[origin] = (counts[origin] ?? 0) + 1); const keyBase = origin || "origin"; return { origin, index, key: `${keyBase}-${occurrence}`, }; }); }, [corsConfig.allowOrigins]); return ( setOpen(false)} onCancel={() => setOpen(false)} onOk={handleSave} > {t("settings.sections.externalCors.fields.allowPrivateNetwork")} handleCorsConfigChange( "allowPrivateNetwork", e.target.checked, ) } />
{t("settings.sections.externalCors.fields.allowedOrigins")}
{originEntries.map(({ origin, index, key }) => (
handleUpdateOrigin(index, e.target.value)} placeholder={t( "settings.sections.externalCors.placeholders.origin", )} inputProps={{ style: { fontSize: 14 } }} />
))}
{t("settings.sections.externalCors.messages.alwaysIncluded", { urls: DEV_URLS.join(", "), })}
); }, );