client.ts 7.7 KB

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