mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-03 10:59:34 +00:00
Adds a per-node TLS verification mode to the Add/Edit Node dialog so the panel can reach nodes that serve HTTPS with a self-signed certificate: - verify (default): normal CA validation. - skip: InsecureSkipVerify, with a clear UI warning that it drops MITM protection. - pin: validates the leaf certificate's SHA-256 (base64 or hex) via VerifyConnection while bypassing the default chain/name check — keeps MITM protection for self-signed certs, the secure alternative to skip. New Node model fields tlsVerifyMode + pinnedCertSha256 (gorm auto-migrated). Probe() selects the HTTP client per node via nodeHTTPClientFor, keeping the SSRF-guarded dialer. A new POST /panel/api/nodes/certFingerprint endpoint (FetchCertFingerprint) lets the UI fetch and pin the node's current certificate in one click. Endpoint documented in api-docs/openapi; i18n added across all locales. Verified end-to-end in Docker (verify rejects, skip bypasses, fetch matches, pin accepts correct / rejects wrong).
77 lines
2.9 KiB
TypeScript
77 lines
2.9 KiB
TypeScript
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
|
|
import { HttpUtil, Msg } from '@/utils';
|
|
import { parseMsg } from '@/utils/zodValidate';
|
|
import { keys } from '@/api/queryKeys';
|
|
import type { NodeRecord } from '@/api/queries/useNodesQuery';
|
|
import { ProbeResultSchema, type ProbeResult } from '@/schemas/node';
|
|
|
|
export type { ProbeResult };
|
|
|
|
export interface NodeUpdateResult {
|
|
id: number;
|
|
name?: string;
|
|
ok: boolean;
|
|
error?: string;
|
|
}
|
|
|
|
export function useNodeMutations() {
|
|
const queryClient = useQueryClient();
|
|
const invalidate = () => queryClient.invalidateQueries({ queryKey: keys.nodes.root() });
|
|
|
|
const createMut = useMutation({
|
|
mutationFn: (payload: Partial<NodeRecord>) =>
|
|
HttpUtil.post('/panel/api/nodes/add', payload),
|
|
onSuccess: (msg) => { if (msg?.success) invalidate(); },
|
|
});
|
|
|
|
const updateMut = useMutation({
|
|
mutationFn: ({ id, payload }: { id: number; payload: Partial<NodeRecord> }) =>
|
|
HttpUtil.post(`/panel/api/nodes/update/${id}`, payload),
|
|
onSuccess: (msg) => { if (msg?.success) invalidate(); },
|
|
});
|
|
|
|
const removeMut = useMutation({
|
|
mutationFn: (id: number) =>
|
|
HttpUtil.post(`/panel/api/nodes/del/${id}`),
|
|
onSuccess: (msg) => { if (msg?.success) invalidate(); },
|
|
});
|
|
|
|
const setEnableMut = useMutation({
|
|
mutationFn: ({ id, enable }: { id: number; enable: boolean }) =>
|
|
HttpUtil.post(`/panel/api/nodes/setEnable/${id}`, { enable }),
|
|
onSuccess: (msg) => { if (msg?.success) invalidate(); },
|
|
});
|
|
|
|
const probeMut = useMutation({
|
|
mutationFn: async (id: number): Promise<Msg<ProbeResult>> => {
|
|
const raw = await HttpUtil.post(`/panel/api/nodes/probe/${id}`);
|
|
return parseMsg(raw, ProbeResultSchema, 'nodes/probe');
|
|
},
|
|
onSuccess: (msg) => { if (msg?.success) invalidate(); },
|
|
});
|
|
|
|
const updatePanelsMut = useMutation({
|
|
mutationFn: (ids: number[]) =>
|
|
HttpUtil.post<NodeUpdateResult[]>('/panel/api/nodes/updatePanel', { ids }, {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
onSuccess: (msg) => { if (msg?.success) invalidate(); },
|
|
});
|
|
|
|
return {
|
|
create: (payload: Partial<NodeRecord>) => createMut.mutateAsync(payload),
|
|
update: (id: number, payload: Partial<NodeRecord>) => updateMut.mutateAsync({ id, payload }),
|
|
remove: (id: number) => removeMut.mutateAsync(id),
|
|
setEnable: (id: number, enable: boolean) => setEnableMut.mutateAsync({ id, enable }),
|
|
probe: (id: number) => probeMut.mutateAsync(id),
|
|
updatePanels: (ids: number[]): Promise<Msg<NodeUpdateResult[]>> => updatePanelsMut.mutateAsync(ids),
|
|
testConnection: async (payload: Partial<NodeRecord>): Promise<Msg<ProbeResult>> => {
|
|
const raw = await HttpUtil.post('/panel/api/nodes/test', payload);
|
|
return parseMsg(raw, ProbeResultSchema, 'nodes/test');
|
|
},
|
|
fetchFingerprint: (payload: Partial<NodeRecord>): Promise<Msg<string>> =>
|
|
HttpUtil.post<string>('/panel/api/nodes/certFingerprint', payload),
|
|
};
|
|
}
|