tun-viewer.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import { forwardRef, useImperativeHandle, useState } from "react";
  2. import { useLockFn } from "ahooks";
  3. import { useTranslation } from "react-i18next";
  4. import {
  5. List,
  6. ListItem,
  7. ListItemText,
  8. MenuItem,
  9. Select,
  10. Switch,
  11. TextField,
  12. } from "@mui/material";
  13. import { useClash } from "@/hooks/use-clash";
  14. import { BaseDialog, DialogRef, Notice } from "@/components/base";
  15. export const TunViewer = forwardRef<DialogRef>((props, ref) => {
  16. const { t } = useTranslation();
  17. const { clash, mutateClash, patchClash } = useClash();
  18. const [open, setOpen] = useState(false);
  19. const [values, setValues] = useState({
  20. stack: "gVisor",
  21. device: "Mihomo",
  22. autoRoute: true,
  23. autoDetectInterface: true,
  24. dnsHijack: ["any:53", "tcp://any:53"],
  25. strictRoute: true,
  26. mtu: 9000,
  27. });
  28. useImperativeHandle(ref, () => ({
  29. open: () => {
  30. setOpen(true);
  31. setValues({
  32. stack: clash?.tun.stack ?? "gVisor",
  33. device: clash?.tun.device ?? "Mihomo",
  34. autoRoute: clash?.tun["auto-route"] ?? true,
  35. autoDetectInterface: clash?.tun["auto-detect-interface"] ?? true,
  36. dnsHijack: clash?.tun["dns-hijack"] ?? ["any:53", "tcp://any:53"],
  37. strictRoute: clash?.tun["strict-route"] ?? true,
  38. mtu: clash?.tun.mtu ?? 9000,
  39. });
  40. },
  41. close: () => setOpen(false),
  42. }));
  43. const onSave = useLockFn(async () => {
  44. try {
  45. let tun = {
  46. stack: values.stack,
  47. device: values.device,
  48. "auto-route": values.autoRoute,
  49. "auto-detect-interface": values.autoDetectInterface,
  50. "dns-hijack": values.dnsHijack,
  51. "strict-route": values.strictRoute,
  52. mtu: values.mtu,
  53. };
  54. await patchClash({ tun });
  55. await mutateClash(
  56. (old) => ({
  57. ...(old! || {}),
  58. tun,
  59. }),
  60. false
  61. );
  62. setOpen(false);
  63. } catch (err: any) {
  64. Notice.error(err.message || err.toString());
  65. }
  66. });
  67. return (
  68. <BaseDialog
  69. open={open}
  70. title={t("Tun Mode")}
  71. contentSx={{ width: 450 }}
  72. okBtn={t("Save")}
  73. cancelBtn={t("Cancel")}
  74. onClose={() => setOpen(false)}
  75. onCancel={() => setOpen(false)}
  76. onOk={onSave}
  77. >
  78. <List>
  79. <ListItem sx={{ padding: "5px 2px" }}>
  80. <ListItemText primary={t("Stack")} />
  81. <Select
  82. size="small"
  83. sx={{ width: 100, "> div": { py: "7.5px" } }}
  84. value={values.stack}
  85. onChange={(e) => {
  86. setValues((v) => ({
  87. ...v,
  88. stack: e.target.value as string,
  89. }));
  90. }}
  91. >
  92. {["System", "gVisor", "Mixed"].map((i) => (
  93. <MenuItem value={i} key={i}>
  94. {i}
  95. </MenuItem>
  96. ))}
  97. </Select>
  98. </ListItem>
  99. <ListItem sx={{ padding: "5px 2px" }}>
  100. <ListItemText primary={t("Device")} />
  101. <TextField
  102. size="small"
  103. autoComplete="off"
  104. autoCorrect="off"
  105. autoCapitalize="off"
  106. spellCheck="false"
  107. sx={{ width: 250 }}
  108. value={values.device}
  109. placeholder="Mihomo"
  110. onChange={(e) =>
  111. setValues((v) => ({ ...v, device: e.target.value }))
  112. }
  113. />
  114. </ListItem>
  115. <ListItem sx={{ padding: "5px 2px" }}>
  116. <ListItemText primary={t("Auto Route")} />
  117. <Switch
  118. edge="end"
  119. checked={values.autoRoute}
  120. onChange={(_, c) => setValues((v) => ({ ...v, autoRoute: c }))}
  121. />
  122. </ListItem>
  123. <ListItem sx={{ padding: "5px 2px" }}>
  124. <ListItemText primary={t("Strict Route")} />
  125. <Switch
  126. edge="end"
  127. checked={values.strictRoute}
  128. onChange={(_, c) => setValues((v) => ({ ...v, strictRoute: c }))}
  129. />
  130. </ListItem>
  131. <ListItem sx={{ padding: "5px 2px" }}>
  132. <ListItemText primary={t("Auto Detect Interface")} />
  133. <Switch
  134. edge="end"
  135. checked={values.autoDetectInterface}
  136. onChange={(_, c) =>
  137. setValues((v) => ({ ...v, autoDetectInterface: c }))
  138. }
  139. />
  140. </ListItem>
  141. <ListItem sx={{ padding: "5px 2px" }}>
  142. <ListItemText primary={t("DNS Hijack")} />
  143. <TextField
  144. size="small"
  145. autoComplete="off"
  146. autoCorrect="off"
  147. autoCapitalize="off"
  148. spellCheck="false"
  149. sx={{ width: 250 }}
  150. value={values.dnsHijack.join(",")}
  151. placeholder="Please use , to separate multiple DNS servers"
  152. onChange={(e) =>
  153. setValues((v) => ({ ...v, dnsHijack: e.target.value.split(",") }))
  154. }
  155. />
  156. </ListItem>
  157. <ListItem sx={{ padding: "5px 2px" }}>
  158. <ListItemText primary={t("MTU")} />
  159. <TextField
  160. size="small"
  161. type="number"
  162. autoComplete="off"
  163. autoCorrect="off"
  164. autoCapitalize="off"
  165. spellCheck="false"
  166. sx={{ width: 250 }}
  167. value={values.mtu}
  168. placeholder="9000"
  169. onChange={(e) =>
  170. setValues((v) => ({
  171. ...v,
  172. mtu: parseInt(e.target.value),
  173. }))
  174. }
  175. />
  176. </ListItem>
  177. </List>
  178. </BaseDialog>
  179. );
  180. });