client.ts 8.2 KB

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