clash-verge-rev/src/components/setting/mods/sysproxy-viewer.tsx
2024-07-07 11:16:59 +08:00

295 lines
9.9 KiB
TypeScript

import { forwardRef, useImperativeHandle, useState } from "react";
import { useLockFn } from "ahooks";
import { useTranslation } from "react-i18next";
import {
InputAdornment,
List,
ListItem,
ListItemText,
styled,
TextField,
Typography,
Button,
} from "@mui/material";
import { useVerge } from "@/hooks/use-verge";
import { getSystemProxy, getAutotemProxy } from "@/services/cmds";
import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
import { Edit } from "@mui/icons-material";
import { EditorViewer } from "@/components/profile/editor-viewer";
import { BaseFieldset } from "@/components/base/base-fieldset";
import getSystem from "@/utils/get-system";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
const DEFAULT_PAC = `function FindProxyForURL(url, host) {
return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;";
}`;
export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
const { t } = useTranslation();
let validReg;
if (getSystem() === "windows") {
validReg =
/^((\*\.)?([a-zA-Z0-9-]+\.?)+(local|test|example|invalid|localhost|onion|([a-zA-Z]{2,}))|(\d{1,3}\.){1,3}\d{1,3}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\*|\d{1,3}\.\d{1,3}\.\*|\d{1,3}\.\*|([a-fA-F0-9:]+:+)+[a-fA-F0-9]+|localhost|<local>)(;((\*\.)?([a-zA-Z0-9-]+\.?)+(local|test|example|invalid|localhost|onion|([a-zA-Z]{2,}))|(\d{1,3}\.){1,3}\d{1,3}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\*|\d{1,3}\.\d{1,3}\.\*|\d{1,3}\.\*|([a-fA-F0-9:]+:+)+[a-fA-F0-9]+|localhost|<local>))*;?$/;
} else {
validReg =
/^((\*\.)?([a-zA-Z0-9-]+\.?)+(local|test|example|invalid|localhost|onion|([a-zA-Z]{2,}))|(\d{1,3}\.){1,3}\d{1,3}(\/\d{1,2}|\/3[0-2])?|\d{1,3}\.\d{1,3}\.\d{1,3}\.\*(\/\d{1,2}|\/3[0-2])?|\d{1,3}\.\d{1,3}\.\*(\/\d{1,2}|\/3[0-2])?|\d{1,3}\.\*(\/\d{1,2}|\/3[0-2])?|([a-fA-F0-9:]+:+)+[a-fA-F0-9]+(\/\d{1,3})?|localhost|<local>)(,((\*\.)?([a-zA-Z0-9-]+\.?)+(local|test|example|invalid|localhost|onion|([a-zA-Z]{2,}))|(\d{1,3}\.){1,3}\d{1,3}(\/\d{1,2}|\/3[0-2])?|\d{1,3}\.\d{1,3}\.\d{1,3}\.\*(\/\d{1,2}|\/3[0-2])?|\d{1,3}\.\d{1,3}\.\*(\/\d{1,2}|\/3[0-2])?|\d{1,3}\.\*(\/3[0-2])?|([a-fA-F0-9:]+:+)+[a-fA-F0-9]+(\/\d{1,3})?|localhost|<local>))*,?$/;
}
const [open, setOpen] = useState(false);
const [editorOpen, setEditorOpen] = useState(false);
const { verge, patchVerge } = useVerge();
type SysProxy = Awaited<ReturnType<typeof getSystemProxy>>;
const [sysproxy, setSysproxy] = useState<SysProxy>();
type AutoProxy = Awaited<ReturnType<typeof getAutotemProxy>>;
const [autoproxy, setAutoproxy] = useState<AutoProxy>();
const {
enable_system_proxy: enabled,
proxy_auto_config,
pac_file_content,
enable_proxy_guard,
use_default_bypass,
system_proxy_bypass,
proxy_guard_duration,
} = verge ?? {};
const [value, setValue] = useState({
guard: enable_proxy_guard,
bypass: system_proxy_bypass,
duration: proxy_guard_duration ?? 10,
use_default: use_default_bypass ?? true,
pac: proxy_auto_config,
pac_content: pac_file_content ?? DEFAULT_PAC,
});
useImperativeHandle(ref, () => ({
open: () => {
setOpen(true);
setValue({
guard: enable_proxy_guard,
bypass: system_proxy_bypass,
duration: proxy_guard_duration ?? 10,
use_default: use_default_bypass ?? true,
pac: proxy_auto_config,
pac_content: pac_file_content ?? DEFAULT_PAC,
});
getSystemProxy().then((p) => setSysproxy(p));
getAutotemProxy().then((p) => setAutoproxy(p));
},
close: () => setOpen(false),
}));
const onSave = useLockFn(async () => {
if (value.duration < 1) {
Notice.error(t("Proxy Daemon Duration Cannot be Less than 1 Second"));
return;
}
const patch: Partial<IVergeConfig> = {};
if (value.guard !== enable_proxy_guard) {
patch.enable_proxy_guard = value.guard;
}
if (value.duration !== proxy_guard_duration) {
patch.proxy_guard_duration = value.duration;
}
if (value.bypass !== system_proxy_bypass) {
patch.system_proxy_bypass = value.bypass;
}
if (value.pac !== proxy_auto_config) {
patch.proxy_auto_config = value.pac;
}
if (value.use_default !== use_default_bypass) {
patch.use_default_bypass = value.use_default;
}
if (value.pac_content !== pac_file_content) {
patch.pac_file_content = value.pac_content;
}
if (value.bypass && !validReg.test(value.bypass)) {
Notice.error(t("Invalid Bypass Format"));
return;
}
try {
await patchVerge(patch);
setOpen(false);
} catch (err: any) {
Notice.error(err.message || err.toString());
}
});
return (
<BaseDialog
open={open}
title={t("System Proxy Setting")}
contentSx={{ width: 450, maxHeight: 565 }}
okBtn={t("Save")}
cancelBtn={t("Cancel")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
onOk={onSave}
>
<List>
<BaseFieldset label={t("Current System Proxy")} padding="15px 10px">
<FlexBox>
<Typography className="label">{t("Enable status")}</Typography>
<Typography className="value">
{value.pac
? autoproxy?.enable
? t("Enabled")
: t("Disabled")
: sysproxy?.enable
? t("Enabled")
: t("Disabled")}
</Typography>
</FlexBox>
{!value.pac && (
<>
<FlexBox>
<Typography className="label">{t("Server Addr")}</Typography>
<Typography className="value">
{sysproxy?.server ? sysproxy.server : t("Not available")}
</Typography>
</FlexBox>
</>
)}
{value.pac && (
<FlexBox>
<Typography className="label">{t("PAC URL")}</Typography>
<Typography className="value">{autoproxy?.url || "-"}</Typography>
</FlexBox>
)}
</BaseFieldset>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Use PAC Mode")} />
<Switch
edge="end"
disabled={!enabled}
checked={value.pac}
onChange={(_, e) => setValue((v) => ({ ...v, pac: e }))}
/>
</ListItem>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText
primary={t("Proxy Guard")}
sx={{ maxWidth: "fit-content" }}
/>
<TooltipIcon title={t("Proxy Guard Info")} />
<Switch
edge="end"
disabled={!enabled}
checked={value.guard}
onChange={(_, e) => setValue((v) => ({ ...v, guard: e }))}
sx={{ marginLeft: "auto" }}
/>
</ListItem>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Guard Duration")} />
<TextField
autoComplete="new-password"
disabled={!enabled}
size="small"
value={value.duration}
sx={{ width: 100 }}
InputProps={{
endAdornment: <InputAdornment position="end">s</InputAdornment>,
}}
onChange={(e) => {
setValue((v) => ({
...v,
duration: +e.target.value.replace(/\D/, ""),
}));
}}
/>
</ListItem>
{!value.pac && (
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Always use Default Bypass")} />
<Switch
edge="end"
disabled={!enabled}
checked={value.use_default}
onChange={(_, e) => setValue((v) => ({ ...v, use_default: e }))}
/>
</ListItem>
)}
{!value.pac && (
<>
<ListItemText primary={t("Proxy Bypass")} />
<TextField
autoComplete="new-password"
error={value.bypass ? !validReg.test(value.bypass) : false}
disabled={!enabled}
size="small"
multiline
rows={4}
sx={{ width: "100%" }}
value={value.bypass}
onChange={(e) => {
setValue((v) => ({ ...v, bypass: e.target.value }));
}}
/>
<ListItemText primary={t("Bypass")} />
<FlexBox>
<TextField
autoComplete="new-password"
disabled={true}
size="small"
multiline
rows={4}
sx={{ width: "100%" }}
value={sysproxy?.bypass || "-"}
/>
</FlexBox>
</>
)}
{value.pac && (
<>
<ListItem sx={{ padding: "5px 2px", alignItems: "start" }}>
<ListItemText
primary={t("PAC Script Content")}
sx={{ padding: "3px 0" }}
/>
<Button
startIcon={<Edit />}
variant="outlined"
onClick={() => {
setEditorOpen(true);
}}
>
{t("Edit")} PAC
</Button>
<EditorViewer
open={editorOpen}
title={`${t("Edit")} PAC`}
initialData={Promise.resolve(value.pac_content ?? "")}
language="javascript"
onSave={(_prev, curr) => {
let pac = DEFAULT_PAC;
if (curr && curr.trim().length > 0) {
pac = curr;
}
setValue((v) => ({ ...v, pac_content: pac }));
}}
onClose={() => setEditorOpen(false)}
/>
</ListItem>
</>
)}
</List>
</BaseDialog>
);
});
const FlexBox = styled("div")`
display: flex;
margin-top: 4px;
.label {
flex: none;
//width: 85px;
}
`;