mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-12 16:15:02 +03:00
feat(clients): bind host rules to individual clients in subscriptions (#6620)
Host rules applied per inbound only, so every client of an inbound got the same subscription endpoints (#6618). A client can now be bound to one or more host rules, and its raw, JSON and Clash subscriptions render only those hosts, in either subscription order; an unbound client is unchanged. A binding whose hosts are deleted, disabled or excluded from the format fails closed: the client gets no links there but stays in traffic accounting. Host edits keep row ids, so bindings survive them; nil leaves a stored binding alone and an empty list clears it.
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useMemo } from 'react';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { HttpUtil } from '@/utils';
|
||||
import { parseMsg } from '@/utils/zodValidate';
|
||||
import { keys } from '@/api/queryKeys';
|
||||
|
||||
// One row of the raw hosts table (GET /panel/api/hosts/raw), carrying the
|
||||
// numeric id that per-client host rule bindings reference.
|
||||
export const RawHostSchema = z
|
||||
.object({
|
||||
id: z.number(),
|
||||
inboundId: z.number().optional(),
|
||||
remark: z.string().optional(),
|
||||
address: z.string().optional(),
|
||||
port: z.number().optional(),
|
||||
isDisabled: z.boolean().optional(),
|
||||
})
|
||||
.loose();
|
||||
|
||||
export type RawHost = z.infer<typeof RawHostSchema>;
|
||||
|
||||
const RawHostListSchema = z
|
||||
.array(RawHostSchema)
|
||||
.nullable()
|
||||
.transform((v) => v ?? []);
|
||||
|
||||
async function fetchRawHosts(): Promise<RawHost[]> {
|
||||
const msg = await HttpUtil.get('/panel/api/hosts/raw', undefined, { silent: true });
|
||||
if (!msg?.success) throw new Error(msg?.msg || 'Failed to fetch hosts');
|
||||
const validated = parseMsg(msg, RawHostListSchema, 'hosts/raw');
|
||||
return Array.isArray(validated.obj) ? validated.obj : [];
|
||||
}
|
||||
|
||||
export function useRawHostsQuery() {
|
||||
const query = useQuery({
|
||||
queryKey: keys.hosts.raw(),
|
||||
queryFn: fetchRawHosts,
|
||||
});
|
||||
|
||||
const hosts = useMemo(() => query.data ?? [], [query.data]);
|
||||
|
||||
return {
|
||||
hosts,
|
||||
loading: query.isFetching,
|
||||
fetched: query.data !== undefined || query.isError,
|
||||
fetchError: query.error ? (query.error as Error).message : '',
|
||||
refetch: query.refetch,
|
||||
};
|
||||
}
|
||||
@@ -11,6 +11,7 @@ export const keys = {
|
||||
hosts: {
|
||||
root: () => ['hosts'] as const,
|
||||
list: () => ['hosts', 'list'] as const,
|
||||
raw: () => ['hosts', 'raw'] as const,
|
||||
byInbound: (inboundId: number) => ['hosts', 'byInbound', inboundId] as const,
|
||||
tags: () => ['hosts', 'tags'] as const,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user