1import { create } from 'zustand';
2import { subscribeWithSelector } from 'zustand/middleware';
3import { useDashboard } from './dashboard.store';
4import { fetch } from 'service/http';
5import { Inbounds, InboundType } from 'types/inbounds';
6import { z } from 'zod';
7import { HostsFilterType, InboundsFilterType } from 'types';
8import { pageSizeManagers } from 'utils/userPreferenceStorage';
9import { queryClient } from 'service/react-query';
10import { queryIds } from 'constants/query-ids';
11
12export const hostSchema = z.object({
13 remark: z.string().min(1, 'Remark is required'),
14 address: z.string().min(1, 'Address is required'),
15 port: z.coerce.number()
16 .gte(1, 'Port must be more than 1')
17 .lte(65535, 'Port can not be more than 65535')
18 .or(z.string().nullable()),
19 path: z.string(),
20 sni: z.string(),
21 host: z.string(),
22 security: z.enum(['inbound_default', 'none', 'tls']),
23 alpn: z.string().optional(),
24 fingerprint: z.string().optional(),
25});
26
27export type HostSchema = z.infer<typeof hostSchema>;
28export type HostType = HostSchema & { id?: number };
29export type Hosts = HostType[];
30
31export const fetchInbounds = async () => {
32 useDashboard.setState({ loading: true });
33 return fetch('/inbounds')
34 .then((inbounds: Inbounds) => inbounds)
35 .finally(() => {
36 useDashboard.setState({ loading: false });
37 });
38};
39
40export const fetchInboundHosts = async (id: number | undefined): Promise<Hosts> => {
41 if (id !== undefined) {
42 useDashboard.setState({ loading: true });
43 return fetch(`/inbounds/${id}/hosts`)
44 .then((hosts: Hosts) => {
45 return hosts
46 })
47 .finally(() => {
48 useDashboard.setState({ loading: false });
49 });
50 } else { return [] }
51};
52
53type InboundsStateType = {
54 // Loading
55 loading: boolean;
56 setLoading: (value: boolean) => void;
57 // Inbounds
58 selectedInbound: InboundType | undefined;
59 selectInbound: (inbound: InboundType) => void;
60 refetchInbounds: () => void;
61 // Hosts
62 refetchHosts: () => void;
63 hostsFilters: HostsFilterType;
64 inboundsFilters: InboundsFilterType;
65 onHostsFilterChange: (filters: Partial<HostsFilterType>) => void;
66 onInboundsFilterChange: (filters: Partial<InboundsFilterType>) => void;
67 selectedHost: HostType | null;
68 selectHost: (host: HostType) => void;
69 isEditingHost: boolean;
70 onEditingHost: (isEditingHost: boolean, host: HostType | null) => void;
71 editHost: (inboundId: number | undefined, host: HostType) => Promise<boolean | void>;
72 isCreatingHost: boolean;
73 onCreatingHost: (isCreatingHost: boolean) => void;
74 createHost: (inboundId: number | undefined, host: HostSchema) => Promise<boolean | void>;
75 isDeletingHost: boolean;
76 onDeletingHost: (isDeletingHost: boolean, host: HostType) => void;
77 deleteHost: (hostId: number) => void;
78};
79
80export const useInbounds = create(
81 subscribeWithSelector<InboundsStateType>((set, get) => ({
82 selectedHost: null,
83 selectedInbound: undefined,
84 loading: false,
85 isDeletingHost: false,
86 isCreatingHost: false,
87 isEditingHost: false,
88 hostsFilters: {
89 name: '',
90 limit: pageSizeManagers.hosts.getPageSize(),
91 sort: '-created_at',
92 },
93 inboundsFilters: {
94 name: '',
95 limit: pageSizeManagers.inbounds.getPageSize(),
96 sort: '-created_at',
97 },
98 onHostsFilterChange: (filters) => {
99 set({
100 hostsFilters: {
101 ...get().hostsFilters,
102 ...filters,
103 },
104 });
105 },
106 onInboundsFilterChange: (filters) => {
107 set({
108 hostsFilters: {
109 ...get().inboundsFilters,
110 ...filters,
111 },
112 });
113 },
114 setLoading: (value) => {
115 set({ loading: value })
116 },
117 refetchInbounds: () => {
118 queryClient.invalidateQueries({ queryKey: [queryIds.inbounds] });
119 },
120 refetchHosts: async () => {
121 queryClient.invalidateQueries({ queryKey: [queryIds.hosts] });
122 },
123 selectInbound: (host): void => {
124 set({ selectedInbound: host })
125 },
126 selectHost: (host: HostType): void => {
127 set({ selectedHost: host })
128 },
129 onEditingHost: (isEditingHost: boolean, host: HostType | null) => {
130 set({ isEditingHost, selectedHost: host });
131 },
132 editHost: async (hostId: number | undefined, body: HostSchema): Promise<boolean | void> => {
133 if (hostId !== undefined) {
134 useDashboard.setState({ loading: true });
135 return fetch(`/inbounds/hosts/${hostId}`, { method: 'PUT', body })
136 .then(() => true)
137 .catch(() => false)
138 .finally(() => {
139 useDashboard.setState({ loading: false });
140 get().refetchHosts();
141 });
142 }
143 },
144 onCreatingHost: (isCreatingHost: boolean) => {
145 set({ isCreatingHost });
146 },
147 createHost: async (inboundId: number | undefined, body: HostSchema): Promise<boolean | void> => {
148 if (inboundId !== undefined) {
149 useDashboard.setState({ loading: true });
150 return fetch(`/inbounds/${inboundId}/hosts`, { method: 'POST', body })
151 .then(() => true)
152 .catch(() => false)
153 .finally(() => {
154 useDashboard.setState({ loading: false });
155 get().refetchHosts();
156 });
157 }
158 },
159 onDeletingHost: (isDeletingHost: boolean, host: HostType | null) => {
160 set({ isDeletingHost, selectedHost: host });
161 },
162 deleteHost: async (hostId: number): Promise<boolean> => {
163 useDashboard.setState({ loading: true });
164 return fetch(`/inbounds/hosts/${hostId}`, { method: 'DELETE' })
165 .then(() => {
166 get().refetchHosts();
167 return true;
168 })
169 .catch(() => false)
170 .finally(() => {
171 useDashboard.setState({ loading: false });
172 });
173 },
174 }))
175);