gitscope
← marzneshin / v0.1.2 / 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): Promise<Hosts> => {
 48  useDashboard.setState({ loading: true });
 49  return fetch(`/inbounds/${id}/hosts`)
 50    .then((hosts: Hosts) => hosts)
 51    .finally(() => {
 52      useDashboard.setState({ loading: false });
 53    });
 54};
 55
 56type InboundsStateType = {
 57  // Loading
 58  loading: boolean;
 59  setLoading: (value: boolean) => void;
 60  // Inbounds
 61  selectedInbound: InboundType | null;
 62  selectInbound: (inbound: InboundType) => void;
 63  refetchInbounds: () => void;
 64  // Hosts
 65  refetchHosts: () => void;
 66  hostsFilters: HostsFilterType;
 67  inboundsFilters: InboundsFilterType;
 68  onHostsFilterChange: (filters: Partial<HostsFilterType>) => void;
 69  onInboundsFilterChange: (filters: Partial<InboundsFilterType>) => void;
 70  selectedHost: HostType | null;
 71  selectHost: (host: HostType) => void;
 72  isEditingHost: boolean;
 73  onEditingHost: (isEditingHost: boolean, host: HostType | null) => void;
 74  editHost: (inboundId: number | undefined, host: HostType) => Promise<boolean | void>;
 75  isCreatingHost: boolean;
 76  onCreatingHost: (isCreatingHost: boolean) => void;
 77  createHost: (inboundId: number | undefined, host: HostSchema) => Promise<boolean | void>;
 78  isDeletingHost: boolean;
 79  onDeletingHost: (isDeletingHost: boolean, host: HostType) => void;
 80  deleteHost: (hostId: number) => void;
 81};
 82
 83export const useInbounds = create(
 84  subscribeWithSelector<InboundsStateType>((set, get) => ({
 85    selectedHost: null,
 86    selectedInbound: null,
 87    loading: false,
 88    isDeletingHost: false,
 89    isCreatingHost: false,
 90    isEditingHost: false,
 91    hostsFilters: {
 92      name: '',
 93      limit: pageSizeManagers.hosts.getPageSize(),
 94      sort: '-created_at',
 95    },
 96    inboundsFilters: {
 97      name: '',
 98      limit: pageSizeManagers.inbounds.getPageSize(),
 99      sort: '-created_at',
100    },
101    onHostsFilterChange: (filters) => {
102      set({
103        hostsFilters: {
104          ...get().hostsFilters,
105          ...filters,
106        },
107      });
108    },
109    onInboundsFilterChange: (filters) => {
110      set({
111        hostsFilters: {
112          ...get().inboundsFilters,
113          ...filters,
114        },
115      });
116    },
117    setLoading: (value) => {
118      set({ loading: value })
119    },
120    refetchInbounds: () => {
121      queryClient.invalidateQueries(queryIds.inbounds);
122    },
123    refetchHosts: () => {
124      queryClient.invalidateQueries(queryIds.hosts);
125    },
126    selectInbound: (host): void => {
127      set({ selectedInbound: host })
128    },
129    selectHost: (host: HostType): void => {
130      set({ selectedHost: host })
131    },
132    onEditingHost: (isEditingHost: boolean, host: HostType | null) => {
133      set({ isEditingHost, selectedHost: host });
134    },
135    editHost: async (hostId: number | undefined, body: HostSchema): Promise<boolean | void> => {
136      if (hostId !== undefined) {
137        useDashboard.setState({ loading: true });
138        return fetch(`/inbounds/hosts/${hostId}`, { method: 'PUT', body })
139          .then(() => true)
140          .catch(() => false)
141          .finally(() => {
142            useDashboard.setState({ loading: false });
143          });
144      }
145    },
146    onCreatingHost: (isCreatingHost: boolean) => {
147      set({ isCreatingHost });
148    },
149    createHost: async (inboundId: number | undefined, body: HostSchema): Promise<boolean | void> => {
150      if (inboundId !== undefined) {
151        useDashboard.setState({ loading: true });
152        return fetch(`/inbounds/${inboundId}/hosts/${body}`, { method: 'POST', body })
153          .then(() => true)
154          .catch(() => false)
155          .finally(() => {
156            useDashboard.setState({ loading: false });
157          });
158      }
159    },
160    onDeletingHost: (isDeletingHost: boolean, host: HostType | null) => {
161      set({ isDeletingHost, selectedHost: host });
162    },
163    deleteHost: async (hostId: number): Promise<boolean> => {
164      useDashboard.setState({ loading: true });
165      return fetch(`/inbounds/hosts/${hostId}`, { method: 'DELETE' })
166        .then(() => true)
167        .catch(() => false)
168        .finally(() => {
169          useDashboard.setState({ loading: false });
170        });
171    },
172  }))
173);