gitscope
← marzneshin / v0.1.2 / app/dashboard/src/stores/nodes.store.ts
https://dev.kronoz.win/r/marzneshin.git
app/dashboard/src/stores/nodes.store.ts · TypeScript
Blame Raw
  1import { useQuery } from 'react-query';
  2import { fetch } from 'service/http';
  3import { create } from 'zustand';
  4import { z } from 'zod';
  5import { FilterUsageType, NodesFilterType } from 'types/filter';
  6import { pageSizeManagers } from 'utils/userPreferenceStorage';
  7import { useDashboard } from './dashboard.store';
  8import { queryClient } from 'service/react-query';
  9import { queryIds } from 'constants/query-ids';
 10
 11export const NodeSchema = z.object({
 12  name: z.string().min(1),
 13  address: z.string().min(1),
 14  port: z
 15    .number()
 16    .min(1)
 17    .or(z.string().transform((v) => parseFloat(v))),
 18  xray_version: z.string().nullable().optional(),
 19  id: z.number().nullable().optional(),
 20  status: z
 21    .enum(['healthy', 'unhealthy', 'disabled']),
 22  message: z.string().nullable().optional(),
 23  usage_coefficient: z.number().default(1),
 24  add_as_new_host: z.boolean(),
 25});
 26
 27export const NodeCreateSchema = z.object({
 28  name: z.string().min(1),
 29  address: z.string().min(1),
 30  port: z
 31    .number()
 32    .min(1)
 33    .or(z.string().transform((v) => parseFloat(v))),
 34  id: z.number().nullable().optional(),
 35  status: z
 36    .enum(['none', 'disabled']),
 37  usage_coefficient: z.number().default(1),
 38  add_as_new_host: z.boolean().optional(),
 39});
 40
 41export type NodeType = z.infer<typeof NodeSchema>;
 42export type NodeCreate = z.infer<typeof NodeCreateSchema>;
 43
 44export const getNodeDefaultValues = (): NodeType => ({
 45  name: '',
 46  address: '',
 47  status: 'unhealthy',
 48  port: 62050,
 49  xray_version: '',
 50  add_as_new_host: false,
 51  usage_coefficient: 1,
 52});
 53
 54export const getNodeCreateDefaultValues = (): NodeCreate => ({
 55  name: '',
 56  address: '',
 57  status: 'none',
 58  port: 62050,
 59  add_as_new_host: false,
 60  usage_coefficient: 1,
 61  id: 0,
 62});
 63
 64export const FetchNodesQueryKey = 'fetch-nodes-query-key';
 65
 66export function fetchNodes(): Promise<NodeType[]> {
 67  useDashboard.setState({ loading: true })
 68  return fetch('/nodes').then((nodes) => {
 69    return nodes;
 70  }).finally(() => {
 71    useDashboard.setState({ loading: false })
 72  });
 73}
 74
 75export type NodeStore = {
 76  certificate: string,
 77  deletingNode?: NodeCreate | null;
 78  editingNode: NodeCreate | null;
 79  isAddingNode: boolean;
 80  isShowingNodesUsage: boolean;
 81  isEditingNode: boolean;
 82  isEditingCore: boolean;
 83  nodesFilters: NodesFilterType;
 84  refetchNodes: () => void;
 85  refetchCertificate: () => void;
 86  fetchNodesUsage: (query: FilterUsageType) => Promise<void>;
 87  addNode: (node: NodeCreate) => Promise<unknown>;
 88  deleteNode: (node: NodeCreate) => Promise<unknown>;
 89  updateNode: (node: NodeCreate) => Promise<unknown>;
 90  reconnectNode: (node: NodeType) => Promise<unknown>;
 91  onDeletingNode: (node: NodeCreate | undefined) => void;
 92  onEditingNode: (node: NodeType | undefined) => void;
 93  onAddingNode: (isAddingNode: boolean) => void;
 94  onFilterChange: (filters: Partial<NodesFilterType>) => void;
 95  onShowingNodesUsage: (isShowingNodesUsage: boolean) => void;
 96};
 97
 98export const useNodesQuery = () => {
 99  return useQuery({
100    queryKey: FetchNodesQueryKey,
101    queryFn: fetchNodes,
102    refetchInterval: useNodes.getState().editingNode ? 3000 : undefined,
103    refetchOnWindowFocus: false,
104  });
105};
106
107export const useNodes = create<NodeStore>((set, get) => ({
108  certificate: '',
109  editingNode: null,
110  deletingNode: null,
111  isAddingNode: false,
112  isEditingNode: false,
113  isShowingNodesUsage: false,
114  isEditingCore: false,
115  nodesFilters: {
116    name: '',
117    limit: pageSizeManagers.nodes.getPageSize(),
118    sort: '-created_at',
119  },
120  onFilterChange: (filters) => {
121    set({
122      nodesFilters: {
123        ...get().nodesFilters,
124        ...filters,
125      },
126    });
127    get().refetchNodes();
128  },
129  addNode: async (body) => {
130    return fetch('/nodes', { method: 'POST', body }).then(() => { get().refetchCertificate() });
131  },
132  refetchNodes: () => {
133    queryClient.invalidateQueries(queryIds.nodes);
134  },
135  refetchCertificate: () => {
136    fetch('/nodes/settings').then((res) => {
137      set({ certificate: res.certificate });
138    })
139  },
140  fetchNodesUsage: (query: FilterUsageType) => {
141    return fetch('/nodes/usage', { query });
142  },
143  updateNode: (body) => {
144    console.log('update: ' + body.id)
145    return fetch(`/nodes/${body.id}`, {
146      method: 'PUT',
147      body,
148    });
149  },
150  onDeletingNode: (node: NodeCreate | undefined) => {
151    if (node !== undefined) {
152      set({ deletingNode: node });
153    }
154    else {
155      set({ deletingNode: undefined });
156    }
157  },
158  reconnectNode: (body) => {
159    return fetch(`/nodes/${body.id}/reconnect`, {
160      method: 'POST',
161    });
162  },
163  deleteNode: () => {
164    return fetch(`/nodes/${get().deletingNode?.id}`, {
165      method: 'DELETE',
166    });
167  },
168  onEditingNode: (editingNode: NodeType | undefined) => {
169    if (editingNode !== undefined) {
170      const { status, ...rest } = editingNode;
171      set({ editingNode: { ...rest, status: (status === 'healthy' || status === 'unhealthy') ? 'none' : 'disabled' }, isEditingNode: true });
172    }
173    else {
174      set({ editingNode: undefined, isEditingNode: false });
175
176    }
177  },
178  onAddingNode: (isAddingNode: boolean) => {
179    set({ isAddingNode });
180  },
181  onShowingNodesUsage: (isShowingNodesUsage: boolean) => {
182    set({ isShowingNodesUsage });
183  },
184}));