node.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import { z } from 'zod';
  2. export const NodeRecordSchema = z.object({
  3. id: z.number(),
  4. name: z.string().optional(),
  5. remark: z.string().optional(),
  6. scheme: z.string().optional(),
  7. address: z.string().optional(),
  8. port: z.number().optional(),
  9. basePath: z.string().optional(),
  10. apiToken: z.string().optional(),
  11. hasApiToken: z.boolean().optional(),
  12. enable: z.boolean().optional(),
  13. status: z.string().optional(),
  14. latencyMs: z.number().optional(),
  15. cpuPct: z.number().optional(),
  16. memPct: z.number().optional(),
  17. xrayVersion: z.string().optional(),
  18. panelVersion: z.string().optional(),
  19. uptimeSecs: z.number().optional(),
  20. inboundCount: z.number().optional(),
  21. clientCount: z.number().optional(),
  22. onlineCount: z.number().optional(),
  23. activeCount: z.number().optional(),
  24. disabledCount: z.number().optional(),
  25. depletedCount: z.number().optional(),
  26. lastHeartbeat: z.number().optional(),
  27. lastError: z.string().optional(),
  28. // Xray state captured from the remote node's own /panel/api/server/status.
  29. // Lets the nodes list show a distinct indicator when the panel API is reachable
  30. // (status=online) but the Xray core on that node has failed.
  31. xrayState: z.string().optional(),
  32. xrayError: z.string().optional(),
  33. allowPrivateAddress: z.boolean().optional(),
  34. tlsVerifyMode: z.enum(['verify', 'skip', 'pin', 'mtls']).optional(),
  35. pinnedCertSha256: z.string().optional(),
  36. inboundSyncMode: z.enum(['all', 'selected']).optional(),
  37. // Backend serializes a nil []string as null for nodes saved before #5178.
  38. inboundTags: z.array(z.string()).nullish(),
  39. outboundTag: z.string().optional(),
  40. // Multi-hop node tree (#4983): a node's stable GUID, its parent's GUID, and
  41. // whether it's a read-only transitive sub-node surfaced from a downstream node.
  42. guid: z.string().optional(),
  43. parentGuid: z.string().optional(),
  44. transitive: z.boolean().optional(),
  45. }).loose();
  46. export const NodeListSchema = z.array(NodeRecordSchema);
  47. export const ProbeResultSchema = z.object({
  48. status: z.string(),
  49. latencyMs: z.number().optional(),
  50. xrayVersion: z.string().optional(),
  51. error: z.string().optional(),
  52. // Present on successful probe; used to surface "connected to panel, but xray failed on node".
  53. xrayState: z.string().optional(),
  54. xrayError: z.string().optional(),
  55. }).loose();
  56. export const NodeFormSchema = z.object({
  57. id: z.number().optional(),
  58. name: z.string().trim().min(1, 'pages.nodes.toasts.fillRequired'),
  59. remark: z.string().optional(),
  60. scheme: z.enum(['http', 'https']),
  61. address: z.string().trim().min(1, 'pages.nodes.toasts.fillRequired'),
  62. port: z.number().int().min(1).max(65535),
  63. basePath: z.string(),
  64. // mTLS nodes authenticate via the client certificate, so the token is optional
  65. // there; every other verify mode still requires one (matches remote.do()).
  66. apiToken: z.string().trim(),
  67. hasStoredToken: z.boolean().optional().default(false),
  68. enable: z.boolean(),
  69. allowPrivateAddress: z.boolean(),
  70. tlsVerifyMode: z.enum(['verify', 'skip', 'pin', 'mtls']),
  71. pinnedCertSha256: z.string().optional().default(''),
  72. inboundSyncMode: z.enum(['all', 'selected']).optional().default('all'),
  73. // Unmounted when sync mode is "all" (absent from antd onFinish values) and
  74. // serialized as null by the backend for a nil slice — tolerate both.
  75. inboundTags: z.array(z.string()).nullish().transform((tags) => tags ?? []),
  76. outboundTag: z.string().optional(),
  77. }).superRefine((val, ctx) => {
  78. if (val.tlsVerifyMode !== 'mtls' && val.apiToken.length === 0 && !val.hasStoredToken) {
  79. ctx.addIssue({
  80. code: 'custom',
  81. path: ['apiToken'],
  82. message: 'pages.nodes.toasts.fillRequired',
  83. });
  84. }
  85. });
  86. export type NodeRecord = z.infer<typeof NodeRecordSchema>;
  87. export type ProbeResult = z.infer<typeof ProbeResultSchema>;
  88. export type NodeFormValues = z.infer<typeof NodeFormSchema>;