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