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