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