sysproxy-viewer.tsx 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. import { forwardRef, useImperativeHandle, useState } from "react";
  2. import { useLockFn } from "ahooks";
  3. import { useTranslation } from "react-i18next";
  4. import {
  5. InputAdornment,
  6. List,
  7. ListItem,
  8. ListItemText,
  9. styled,
  10. TextField,
  11. Typography,
  12. Button,
  13. } from "@mui/material";
  14. import { useVerge } from "@/hooks/use-verge";
  15. import { getSystemProxy, getAutotemProxy } from "@/services/cmds";
  16. import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base";
  17. import { Edit } from "@mui/icons-material";
  18. import { EditorViewer } from "@/components/profile/editor-viewer";
  19. import { BaseFieldset } from "@/components/base/base-fieldset";
  20. import getSystem from "@/utils/get-system";
  21. import { TooltipIcon } from "@/components/base/base-tooltip-icon";
  22. const DEFAULT_PAC = `function FindProxyForURL(url, host) {
  23. return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;";
  24. }`;
  25. export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
  26. const { t } = useTranslation();
  27. let validReg;
  28. if (getSystem() === "windows") {
  29. validReg =
  30. /^((\*\.)?([a-zA-Z0-9-]+\.)+[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-]+\.)+[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>))*;?$/;
  31. } else {
  32. validReg =
  33. /^((\*\.)?([a-zA-Z0-9-]+\.)+[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-]+\.)+[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>))*,?$/;
  34. }
  35. const [open, setOpen] = useState(false);
  36. const [editorOpen, setEditorOpen] = useState(false);
  37. const { verge, patchVerge } = useVerge();
  38. type SysProxy = Awaited<ReturnType<typeof getSystemProxy>>;
  39. const [sysproxy, setSysproxy] = useState<SysProxy>();
  40. type AutoProxy = Awaited<ReturnType<typeof getAutotemProxy>>;
  41. const [autoproxy, setAutoproxy] = useState<AutoProxy>();
  42. const {
  43. enable_system_proxy: enabled,
  44. proxy_auto_config,
  45. pac_file_content,
  46. enable_proxy_guard,
  47. system_proxy_bypass,
  48. proxy_guard_duration,
  49. } = verge ?? {};
  50. const [value, setValue] = useState({
  51. guard: enable_proxy_guard,
  52. bypass: system_proxy_bypass,
  53. duration: proxy_guard_duration ?? 10,
  54. pac: proxy_auto_config,
  55. pac_content: pac_file_content ?? DEFAULT_PAC,
  56. });
  57. useImperativeHandle(ref, () => ({
  58. open: () => {
  59. setOpen(true);
  60. setValue({
  61. guard: enable_proxy_guard,
  62. bypass: system_proxy_bypass,
  63. duration: proxy_guard_duration ?? 10,
  64. pac: proxy_auto_config,
  65. pac_content: pac_file_content ?? DEFAULT_PAC,
  66. });
  67. getSystemProxy().then((p) => setSysproxy(p));
  68. getAutotemProxy().then((p) => setAutoproxy(p));
  69. },
  70. close: () => setOpen(false),
  71. }));
  72. const onSave = useLockFn(async () => {
  73. if (value.duration < 1) {
  74. Notice.error(t("Proxy Daemon Duration Cannot be Less than 1 Second"));
  75. return;
  76. }
  77. const patch: Partial<IVergeConfig> = {};
  78. if (value.guard !== enable_proxy_guard) {
  79. patch.enable_proxy_guard = value.guard;
  80. }
  81. if (value.duration !== proxy_guard_duration) {
  82. patch.proxy_guard_duration = value.duration;
  83. }
  84. if (value.bypass !== system_proxy_bypass) {
  85. patch.system_proxy_bypass = value.bypass;
  86. }
  87. if (value.pac !== proxy_auto_config) {
  88. patch.proxy_auto_config = value.pac;
  89. }
  90. if (value.pac_content !== pac_file_content) {
  91. patch.pac_file_content = value.pac_content;
  92. }
  93. if (value.bypass && !validReg.test(value.bypass)) {
  94. Notice.error(t("Invalid Bypass Format"));
  95. return;
  96. }
  97. try {
  98. await patchVerge(patch);
  99. setOpen(false);
  100. } catch (err: any) {
  101. Notice.error(err.message || err.toString());
  102. }
  103. });
  104. return (
  105. <BaseDialog
  106. open={open}
  107. title={t("System Proxy Setting")}
  108. contentSx={{ width: 450, maxHeight: 565 }}
  109. okBtn={t("Save")}
  110. cancelBtn={t("Cancel")}
  111. onClose={() => setOpen(false)}
  112. onCancel={() => setOpen(false)}
  113. onOk={onSave}
  114. >
  115. <List>
  116. <BaseFieldset label={t("Current System Proxy")} padding="15px 10px">
  117. <FlexBox>
  118. <Typography className="label">{t("Enable status")}</Typography>
  119. <Typography className="value">
  120. {value.pac
  121. ? autoproxy?.enable
  122. ? t("Enabled")
  123. : t("Disabled")
  124. : sysproxy?.enable
  125. ? t("Enabled")
  126. : t("Disabled")}
  127. </Typography>
  128. </FlexBox>
  129. {!value.pac && (
  130. <>
  131. <FlexBox>
  132. <Typography className="label">{t("Server Addr")}</Typography>
  133. <Typography className="value">
  134. {sysproxy?.server ? sysproxy.server : t("Not available")}
  135. </Typography>
  136. </FlexBox>
  137. </>
  138. )}
  139. {value.pac && (
  140. <FlexBox>
  141. <Typography className="label">{t("PAC URL")}</Typography>
  142. <Typography className="value">{autoproxy?.url || "-"}</Typography>
  143. </FlexBox>
  144. )}
  145. </BaseFieldset>
  146. <ListItem sx={{ padding: "5px 2px" }}>
  147. <ListItemText primary={t("Use PAC Mode")} />
  148. <Switch
  149. edge="end"
  150. disabled={!enabled}
  151. checked={value.pac}
  152. onChange={(_, e) => setValue((v) => ({ ...v, pac: e }))}
  153. />
  154. </ListItem>
  155. <ListItem sx={{ padding: "5px 2px" }}>
  156. <ListItemText
  157. primary={t("Proxy Guard")}
  158. sx={{ maxWidth: "fit-content" }}
  159. />
  160. <TooltipIcon title={t("Proxy Guard Info")} />
  161. <Switch
  162. edge="end"
  163. disabled={!enabled}
  164. checked={value.guard}
  165. onChange={(_, e) => setValue((v) => ({ ...v, guard: e }))}
  166. sx={{ marginLeft: "auto" }}
  167. />
  168. </ListItem>
  169. <ListItem sx={{ padding: "5px 2px" }}>
  170. <ListItemText primary={t("Guard Duration")} />
  171. <TextField
  172. disabled={!enabled}
  173. size="small"
  174. value={value.duration}
  175. sx={{ width: 100 }}
  176. InputProps={{
  177. endAdornment: <InputAdornment position="end">s</InputAdornment>,
  178. }}
  179. onChange={(e) => {
  180. setValue((v) => ({
  181. ...v,
  182. duration: +e.target.value.replace(/\D/, ""),
  183. }));
  184. }}
  185. />
  186. </ListItem>
  187. {!value.pac && (
  188. <>
  189. <ListItemText primary={t("Proxy Bypass")} />
  190. <TextField
  191. error={value.bypass ? !validReg.test(value.bypass) : false}
  192. disabled={!enabled}
  193. size="small"
  194. autoComplete="off"
  195. multiline
  196. rows={4}
  197. sx={{ width: "100%" }}
  198. value={value.bypass}
  199. onChange={(e) => {
  200. setValue((v) => ({ ...v, bypass: e.target.value }));
  201. }}
  202. />
  203. <ListItemText primary={t("Bypass")} />
  204. <FlexBox>
  205. <TextField
  206. disabled={true}
  207. size="small"
  208. autoComplete="off"
  209. multiline
  210. rows={4}
  211. sx={{ width: "100%" }}
  212. value={sysproxy?.bypass || "-"}
  213. />
  214. </FlexBox>
  215. </>
  216. )}
  217. {value.pac && (
  218. <>
  219. <ListItem sx={{ padding: "5px 2px", alignItems: "start" }}>
  220. <ListItemText
  221. primary={t("PAC Script Content")}
  222. sx={{ padding: "3px 0" }}
  223. />
  224. <Button
  225. startIcon={<Edit />}
  226. variant="outlined"
  227. onClick={() => {
  228. setEditorOpen(true);
  229. }}
  230. >
  231. {t("Edit")} PAC
  232. </Button>
  233. <EditorViewer
  234. title={`${t("Edit")} PAC`}
  235. mode="text"
  236. property={value.pac_content ?? ""}
  237. open={editorOpen}
  238. language="javascript"
  239. onChange={(content) => {
  240. let pac = DEFAULT_PAC;
  241. if (content && content.trim().length > 0) {
  242. pac = content;
  243. }
  244. setValue((v) => ({ ...v, pac_content: pac }));
  245. }}
  246. onClose={() => {
  247. setEditorOpen(false);
  248. }}
  249. />
  250. </ListItem>
  251. </>
  252. )}
  253. </List>
  254. </BaseDialog>
  255. );
  256. });
  257. const FlexBox = styled("div")`
  258. display: flex;
  259. margin-top: 4px;
  260. .label {
  261. flex: none;
  262. //width: 85px;
  263. }
  264. `;