useNodes.ts 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
  2. import { HttpUtil } from '@/utils';
  3. export interface NodeRecord {
  4. id: number;
  5. name?: string;
  6. remark?: string;
  7. scheme?: string;
  8. address?: string;
  9. port?: number;
  10. basePath?: string;
  11. apiToken?: string;
  12. enable?: boolean;
  13. status?: 'online' | 'offline' | string;
  14. latencyMs?: number;
  15. cpuPct?: number;
  16. memPct?: number;
  17. xrayVersion?: string;
  18. panelVersion?: string;
  19. uptimeSecs?: number;
  20. inboundCount?: number;
  21. clientCount?: number;
  22. onlineCount?: number;
  23. depletedCount?: number;
  24. lastHeartbeat?: number;
  25. lastError?: string;
  26. allowPrivateAddress?: boolean;
  27. [key: string]: unknown;
  28. }
  29. interface ApiMsg<T = unknown> {
  30. success?: boolean;
  31. msg?: string;
  32. obj?: T;
  33. }
  34. interface NodeTotals {
  35. total: number;
  36. online: number;
  37. offline: number;
  38. avgLatency: number;
  39. inbounds: number;
  40. clients: number;
  41. onlineClients: number;
  42. depleted: number;
  43. }
  44. export function useNodes() {
  45. const [nodes, setNodes] = useState<NodeRecord[]>([]);
  46. const [loading, setLoading] = useState(false);
  47. const [fetched, setFetched] = useState(false);
  48. const fetchedRef = useRef(false);
  49. const refresh = useCallback(async () => {
  50. setLoading(true);
  51. try {
  52. const msg = await HttpUtil.get('/panel/api/nodes/list') as ApiMsg<NodeRecord[]>;
  53. if (msg?.success) {
  54. setNodes(Array.isArray(msg.obj) ? msg.obj : []);
  55. }
  56. fetchedRef.current = true;
  57. setFetched(true);
  58. } finally {
  59. setLoading(false);
  60. }
  61. }, []);
  62. const applyNodesEvent = useCallback((payload: unknown) => {
  63. if (Array.isArray(payload)) {
  64. setNodes(payload as NodeRecord[]);
  65. if (!fetchedRef.current) {
  66. fetchedRef.current = true;
  67. setFetched(true);
  68. }
  69. }
  70. }, []);
  71. const create = useCallback(async (payload: Partial<NodeRecord>) => {
  72. const msg = await HttpUtil.post('/panel/api/nodes/add', payload) as ApiMsg;
  73. if (msg?.success) await refresh();
  74. return msg;
  75. }, [refresh]);
  76. const update = useCallback(async (id: number, payload: Partial<NodeRecord>) => {
  77. const msg = await HttpUtil.post(`/panel/api/nodes/update/${id}`, payload) as ApiMsg;
  78. if (msg?.success) await refresh();
  79. return msg;
  80. }, [refresh]);
  81. const remove = useCallback(async (id: number) => {
  82. const msg = await HttpUtil.post(`/panel/api/nodes/del/${id}`) as ApiMsg;
  83. if (msg?.success) await refresh();
  84. return msg;
  85. }, [refresh]);
  86. const setEnable = useCallback(async (id: number, enable: boolean) => {
  87. const msg = await HttpUtil.post(`/panel/api/nodes/setEnable/${id}`, { enable }) as ApiMsg;
  88. if (msg?.success) await refresh();
  89. return msg;
  90. }, [refresh]);
  91. const testConnection = useCallback(async (payload: Partial<NodeRecord>) => {
  92. return await HttpUtil.post('/panel/api/nodes/test', payload) as ApiMsg<{
  93. status: string;
  94. latencyMs?: number;
  95. xrayVersion?: string;
  96. error?: string;
  97. }>;
  98. }, []);
  99. const probe = useCallback(async (id: number) => {
  100. const msg = await HttpUtil.post(`/panel/api/nodes/probe/${id}`) as ApiMsg<{
  101. status: string;
  102. latencyMs?: number;
  103. error?: string;
  104. }>;
  105. if (msg?.success) await refresh();
  106. return msg;
  107. }, [refresh]);
  108. const totals = useMemo<NodeTotals>(() => {
  109. let online = 0;
  110. let offline = 0;
  111. let latencySum = 0;
  112. let latencyCount = 0;
  113. let inbounds = 0;
  114. let clients = 0;
  115. let onlineClients = 0;
  116. let depleted = 0;
  117. for (const n of nodes) {
  118. inbounds += n.inboundCount || 0;
  119. clients += n.clientCount || 0;
  120. onlineClients += n.onlineCount || 0;
  121. depleted += n.depletedCount || 0;
  122. if (!n.enable) continue;
  123. if (n.status === 'online') {
  124. online += 1;
  125. if (n.latencyMs && n.latencyMs > 0) {
  126. latencySum += n.latencyMs;
  127. latencyCount += 1;
  128. }
  129. } else if (n.status === 'offline') {
  130. offline += 1;
  131. }
  132. }
  133. return {
  134. total: nodes.length,
  135. online,
  136. offline,
  137. avgLatency: latencyCount > 0 ? Math.round(latencySum / latencyCount) : 0,
  138. inbounds,
  139. clients,
  140. onlineClients,
  141. depleted,
  142. };
  143. }, [nodes]);
  144. useEffect(() => {
  145. refresh();
  146. }, [refresh]);
  147. return {
  148. nodes,
  149. loading,
  150. fetched,
  151. totals,
  152. refresh,
  153. applyNodesEvent,
  154. create,
  155. update,
  156. remove,
  157. setEnable,
  158. testConnection,
  159. probe,
  160. };
  161. }