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