client.ts 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. import { z } from 'zod';
  2. const nullableStringArray = z.array(z.string()).nullable().transform((v) => v ?? []);
  3. const nullableNumberArray = z.array(z.number()).nullable().transform((v) => v ?? []);
  4. export const ClientTrafficSchema = z.object({
  5. up: z.number().optional(),
  6. down: z.number().optional(),
  7. total: z.number().optional(),
  8. expiryTime: z.number().optional(),
  9. enable: z.boolean().optional(),
  10. lastOnline: z.number().optional(),
  11. lastSubFetch: z.number().optional(),
  12. });
  13. export const ClientRecordSchema = z.object({
  14. id: z.number().optional(),
  15. email: z.string(),
  16. subId: z.string().optional(),
  17. uuid: z.string().optional(),
  18. password: z.string().optional(),
  19. auth: z.string().optional(),
  20. flow: z.string().optional(),
  21. security: z.string().optional(),
  22. totalGB: z.number().optional(),
  23. expiryTime: z.number().optional(),
  24. limitIp: z.number().optional(),
  25. limitHwid: z.number().optional(),
  26. tgId: z.union([z.number(), z.string()]).optional(),
  27. group: z.string().optional(),
  28. comment: z.string().optional(),
  29. enable: z.boolean().optional(),
  30. reset: z.number().optional(),
  31. inboundIds: nullableNumberArray.optional(),
  32. traffic: ClientTrafficSchema.nullable().optional(),
  33. reverse: z.object({ tag: z.string().optional() }).loose().nullable().optional(),
  34. privateKey: z.string().optional(),
  35. publicKey: z.string().optional(),
  36. allowedIPs: z.string().optional(),
  37. preSharedKey: z.string().optional(),
  38. keepAlive: z.number().optional(),
  39. secret: z.string().optional(),
  40. adTag: z.string().optional(),
  41. createdAt: z.number().optional(),
  42. updatedAt: z.number().optional(),
  43. }).loose();
  44. export const InboundOptionSchema = z.object({
  45. id: z.number(),
  46. remark: z.string().optional(),
  47. tag: z.string().optional(),
  48. protocol: z.string().optional(),
  49. port: z.number().optional(),
  50. tlsFlowCapable: z.boolean().optional(),
  51. ssMethod: z.string().optional(),
  52. wgPublicKey: z.string().optional(),
  53. wgMtu: z.number().optional(),
  54. wgDns: z.string().optional(),
  55. mtprotoDomain: z.string().optional(),
  56. // Hosting node id; absent/null for this panel's own inbounds (#4997).
  57. nodeId: z.number().nullable().optional(),
  58. // Share-host resolution inputs, mirroring the backend resolveInboundAddress so
  59. // the clients page picks the same WireGuard endpoint host as the subscription:
  60. // the hosting node address, the inbound listen, and its share-address strategy.
  61. nodeAddress: z.string().optional(),
  62. listen: z.string().optional(),
  63. shareAddr: z.string().optional(),
  64. shareAddrStrategy: z.string().optional(),
  65. }).loose();
  66. export const InboundOptionsSchema = z.array(InboundOptionSchema);
  67. // The *Count fields are exact; the email arrays stop at the server's cap and
  68. // only feed the hover popovers, so never derive a counter from their length.
  69. export const ClientsSummarySchema = z.object({
  70. total: z.number(),
  71. active: z.number(),
  72. onlineCount: z.number().optional().default(0),
  73. depletedCount: z.number().optional().default(0),
  74. expiringCount: z.number().optional().default(0),
  75. deactiveCount: z.number().optional().default(0),
  76. online: nullableStringArray,
  77. depleted: nullableStringArray,
  78. expiring: nullableStringArray,
  79. deactive: nullableStringArray,
  80. });
  81. const nullableClientArray = z.array(ClientRecordSchema).nullable().transform((v) => v ?? []);
  82. export const ClientPageResponseSchema = z.object({
  83. items: nullableClientArray,
  84. total: z.number(),
  85. filtered: z.number(),
  86. page: z.number(),
  87. pageSize: z.number(),
  88. summary: ClientsSummarySchema.nullable().optional(),
  89. groups: nullableStringArray.optional(),
  90. });
  91. // A per-client external link surfaced in the client's subscription:
  92. // kind=link is a single share link, kind=subscription is a remote sub URL.
  93. export const ExternalLinkSchema = z.object({
  94. kind: z.enum(['link', 'subscription']).default('link'),
  95. value: z.string(),
  96. remark: z.string().optional().default(''),
  97. }).loose();
  98. export const ExternalLinkListSchema = z.array(ExternalLinkSchema).nullable().transform((v) => v ?? []);
  99. export const ClientHydrateSchema = z.object({
  100. client: ClientRecordSchema,
  101. inboundIds: nullableNumberArray,
  102. externalLinks: ExternalLinkListSchema.optional(),
  103. });
  104. export const BulkAdjustResultSchema = z.object({
  105. adjusted: z.number(),
  106. skipped: z
  107. .array(z.object({ email: z.string(), reason: z.string() }))
  108. .optional(),
  109. });
  110. export const BulkDeleteResultSchema = z.object({
  111. deleted: z.number(),
  112. skipped: z
  113. .array(z.object({ email: z.string(), reason: z.string() }))
  114. .optional(),
  115. });
  116. export const BulkSetEnableResultSchema = z.object({
  117. changed: z.number(),
  118. skipped: z
  119. .array(z.object({ email: z.string(), reason: z.string() }))
  120. .optional(),
  121. });
  122. export const BulkCreateResultSchema = z.object({
  123. created: z.number(),
  124. skipped: z
  125. .array(z.object({ email: z.string(), reason: z.string() }))
  126. .optional(),
  127. });
  128. export const DelDepletedResultSchema = z.object({
  129. deleted: z.number().optional(),
  130. });
  131. export const BulkAttachResultSchema = z.object({
  132. attached: z.array(z.string()).nullable().transform((v) => v ?? []),
  133. skipped: z.array(z.string()).nullable().transform((v) => v ?? []),
  134. errors: z.array(z.string()).nullable().transform((v) => v ?? []),
  135. });
  136. export const BulkDetachResultSchema = z.object({
  137. detached: z.array(z.string()).nullable().transform((v) => v ?? []),
  138. skipped: z.array(z.string()).nullable().transform((v) => v ?? []),
  139. errors: z.array(z.string()).nullable().transform((v) => v ?? []),
  140. });
  141. export const OnlinesSchema = nullableStringArray;
  142. export const OnlineByNodeSchema = z
  143. .record(z.string(), nullableStringArray)
  144. .nullable()
  145. .transform((v) => v ?? {});
  146. export const ActiveInboundsByNodeSchema = z
  147. .record(z.string(), nullableStringArray)
  148. .nullable()
  149. .transform((v) => v ?? {});
  150. export const GroupSummarySchema = z.object({
  151. name: z.string(),
  152. clientCount: z.number(),
  153. trafficUsed: z.number().nullable().transform((v) => v ?? 0),
  154. up: z.number().nullable().transform((v) => v ?? 0),
  155. down: z.number().nullable().transform((v) => v ?? 0),
  156. });
  157. export const GroupSummaryListSchema = z.array(GroupSummarySchema).nullable().transform((v) => v ?? []);
  158. export function hasForbiddenClientChars(value: string): boolean {
  159. if (value.includes('/') || value.includes('\\') || value.includes(' ')) return true;
  160. for (let i = 0; i < value.length; i++) {
  161. const code = value.charCodeAt(i);
  162. if (code < 0x20 || code === 0x7f) return true;
  163. }
  164. return false;
  165. }
  166. export const ClientFormSchema = z.object({
  167. email: z
  168. .string()
  169. .trim()
  170. .min(1, 'pages.clients.email')
  171. .refine((v) => !hasForbiddenClientChars(v), 'pages.clients.emailInvalidChars'),
  172. subId: z.string().refine((v) => !hasForbiddenClientChars(v), 'pages.clients.subIdInvalidChars'),
  173. uuid: z.string(),
  174. password: z.string(),
  175. auth: z.string(),
  176. flow: z.string(),
  177. security: z.string(),
  178. reverseTag: z.string(),
  179. totalGB: z.number().min(0),
  180. delayedStart: z.boolean(),
  181. delayedDays: z.number().int().min(0),
  182. reset: z.number().int().min(0),
  183. limitIp: z.number().int().min(0),
  184. limitHwid: z.number().int().min(0),
  185. tgId: z.number().int().min(0),
  186. group: z.string(),
  187. comment: z.string(),
  188. enable: z.boolean(),
  189. inboundIds: z.array(z.number()),
  190. });
  191. export const ClientCreateFormSchema = ClientFormSchema.extend({
  192. inboundIds: z.array(z.number()).min(1, 'pages.clients.selectInbound'),
  193. });
  194. export const ClientBulkAdjustFormSchema = z
  195. .object({
  196. addDays: z.number().int(),
  197. addGB: z.number(),
  198. flow: z.string().optional().default(''),
  199. })
  200. .refine((v) => v.addDays !== 0 || v.addGB !== 0 || v.flow !== '', {
  201. message: 'pages.clients.bulkAdjustNothing',
  202. });
  203. export const ClientBulkAddFormSchema = z.object({
  204. emailMethod: z.number().int().min(0).max(4),
  205. firstNum: z.number().int().min(1),
  206. lastNum: z.number().int().min(1),
  207. emailPrefix: z.string(),
  208. emailPostfix: z.string(),
  209. quantity: z.number().int().min(1).max(1000),
  210. subId: z.string(),
  211. group: z.string(),
  212. comment: z.string(),
  213. flow: z.string(),
  214. limitIp: z.number().int().min(0),
  215. limitHwid: z.number().int().min(0),
  216. totalGB: z.number().min(0),
  217. expiryTime: z.number(),
  218. reset: z.number().int().min(0),
  219. inboundIds: z.array(z.number()).min(1, 'pages.clients.selectInbound'),
  220. });
  221. export type ClientRecord = z.infer<typeof ClientRecordSchema>;
  222. export type ClientTraffic = z.infer<typeof ClientTrafficSchema>;
  223. export type InboundOption = z.infer<typeof InboundOptionSchema>;
  224. export type ExternalLink = z.infer<typeof ExternalLinkSchema>;
  225. export type ClientsSummary = z.infer<typeof ClientsSummarySchema>;
  226. export type ClientPageResponse = z.infer<typeof ClientPageResponseSchema>;
  227. export type ClientHydrate = z.infer<typeof ClientHydrateSchema>;
  228. export type BulkAdjustResult = z.infer<typeof BulkAdjustResultSchema>;
  229. export type BulkDeleteResult = z.infer<typeof BulkDeleteResultSchema>;
  230. export type BulkSetEnableResult = z.infer<typeof BulkSetEnableResultSchema>;
  231. export type BulkCreateResult = z.infer<typeof BulkCreateResultSchema>;
  232. export type BulkAttachResult = z.infer<typeof BulkAttachResultSchema>;
  233. export type BulkDetachResult = z.infer<typeof BulkDetachResultSchema>;
  234. export type ClientBulkAddFormValues = z.infer<typeof ClientBulkAddFormSchema>;
  235. export type ClientBulkAdjustFormValues = z.infer<typeof ClientBulkAdjustFormSchema>;
  236. export type ClientFormValues = z.infer<typeof ClientFormSchema>;
  237. export type GroupSummary = z.infer<typeof GroupSummarySchema>;