proxy-groups.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import { useRef } from "react";
  2. import { useLockFn } from "ahooks";
  3. import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
  4. import {
  5. getConnections,
  6. providerHealthCheck,
  7. updateProxy,
  8. deleteConnection,
  9. getGroupProxyDelays,
  10. } from "@/services/api";
  11. import { useProfiles } from "@/hooks/use-profiles";
  12. import { useVerge } from "@/hooks/use-verge";
  13. import { BaseEmpty } from "../base";
  14. import { useRenderList } from "./use-render-list";
  15. import { ProxyRender } from "./proxy-render";
  16. import delayManager from "@/services/delay";
  17. interface Props {
  18. mode: string;
  19. }
  20. export const ProxyGroups = (props: Props) => {
  21. const { mode } = props;
  22. const { renderList, onProxies, onHeadState } = useRenderList(mode);
  23. const { verge } = useVerge();
  24. const { current, patchCurrent } = useProfiles();
  25. const timeout = verge?.default_latency_timeout || 10000;
  26. const virtuosoRef = useRef<VirtuosoHandle>(null);
  27. // 切换分组的节点代理
  28. const handleChangeProxy = useLockFn(
  29. async (group: IProxyGroupItem, proxy: IProxyItem) => {
  30. if (!["Selector", "URLTest", "Fallback"].includes(group.type)) return;
  31. const { name, now } = group;
  32. await updateProxy(name, proxy.name);
  33. onProxies();
  34. // 断开连接
  35. if (verge?.auto_close_connection) {
  36. getConnections().then(({ connections }) => {
  37. connections.forEach((conn) => {
  38. if (conn.chains.includes(now!)) {
  39. deleteConnection(conn.id);
  40. }
  41. });
  42. });
  43. }
  44. // 保存到selected中
  45. if (!current) return;
  46. if (!current.selected) current.selected = [];
  47. const index = current.selected.findIndex(
  48. (item) => item.name === group.name
  49. );
  50. if (index < 0) {
  51. current.selected.push({ name, now: proxy.name });
  52. } else {
  53. current.selected[index] = { name, now: proxy.name };
  54. }
  55. await patchCurrent({ selected: current.selected });
  56. }
  57. );
  58. // 测全部延迟
  59. const handleCheckAll = useLockFn(async (groupName: string) => {
  60. const proxies = renderList
  61. .filter(
  62. (e) => e.group?.name === groupName && (e.type === 2 || e.type === 4)
  63. )
  64. .flatMap((e) => e.proxyCol || e.proxy!)
  65. .filter(Boolean);
  66. const providers = new Set(proxies.map((p) => p!.provider!).filter(Boolean));
  67. if (providers.size) {
  68. Promise.allSettled(
  69. [...providers].map((p) => providerHealthCheck(p))
  70. ).then(() => onProxies());
  71. }
  72. const names = proxies.filter((p) => !p!.provider).map((p) => p!.name);
  73. await Promise.race([
  74. delayManager.checkListDelay(names, groupName, timeout),
  75. getGroupProxyDelays(groupName, delayManager.getUrl(groupName), timeout), // 查询group delays 将清除fixed(不关注调用结果)
  76. ]);
  77. onProxies();
  78. });
  79. // 滚到对应的节点
  80. const handleLocation = (group: IProxyGroupItem) => {
  81. if (!group) return;
  82. const { name, now } = group;
  83. const index = renderList.findIndex(
  84. (e) =>
  85. e.group?.name === name &&
  86. ((e.type === 2 && e.proxy?.name === now) ||
  87. (e.type === 4 && e.proxyCol?.some((p) => p.name === now)))
  88. );
  89. if (index >= 0) {
  90. virtuosoRef.current?.scrollToIndex?.({
  91. index,
  92. align: "center",
  93. behavior: "smooth",
  94. });
  95. }
  96. };
  97. if (mode === "direct") {
  98. return <BaseEmpty text="Direct Mode" />;
  99. }
  100. return (
  101. <Virtuoso
  102. ref={virtuosoRef}
  103. style={{ height: "calc(100% - 16px)" }}
  104. totalCount={renderList.length}
  105. increaseViewportBy={256}
  106. itemContent={(index) => (
  107. <ProxyRender
  108. key={renderList[index].key}
  109. item={renderList[index]}
  110. indent={mode === "rule" || mode === "script"}
  111. onLocation={handleLocation}
  112. onCheckAll={handleCheckAll}
  113. onHeadState={onHeadState}
  114. onChangeProxy={handleChangeProxy}
  115. />
  116. )}
  117. />
  118. );
  119. };