@allbluecn/admin-app 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/package.json +86 -0
  2. package/src/client.tsx +31 -0
  3. package/src/components/admin/admin-user-menu.tsx +90 -0
  4. package/src/components/admin/developer-log.tsx +356 -0
  5. package/src/components/ai-provider/model-list-editor.tsx +147 -0
  6. package/src/components/ai-provider/provider-form.tsx +434 -0
  7. package/src/components/ai-provider/provider-icon.tsx +43 -0
  8. package/src/components/ai-provider/provider-logo.tsx +87 -0
  9. package/src/components/ai-provider/provider-table.tsx +258 -0
  10. package/src/components/auth/admin-login-form.tsx +214 -0
  11. package/src/components/auth/auth-brand-panel.tsx +60 -0
  12. package/src/components/auth/auth-header.tsx +38 -0
  13. package/src/components/auth/auth-layout.tsx +66 -0
  14. package/src/components/auth/auth-providers.tsx +34 -0
  15. package/src/components/auth/icon-input.tsx +46 -0
  16. package/src/components/auth/oauth-section.tsx +51 -0
  17. package/src/components/auth/password-input.tsx +67 -0
  18. package/src/components/common/page-header.tsx +42 -0
  19. package/src/components/common/placeholder-page.tsx +42 -0
  20. package/src/components/component/component-form-dialog.tsx +348 -0
  21. package/src/components/component/component-import-dialog.tsx +235 -0
  22. package/src/components/component/component-table.tsx +153 -0
  23. package/src/components/data-table/data-table-pagination.tsx +114 -0
  24. package/src/components/data-table/data-table.tsx +218 -0
  25. package/src/components/data-table/table-detail-layout.tsx +99 -0
  26. package/src/components/dependency/changelog-dialog.tsx +108 -0
  27. package/src/components/dependency/columns.tsx +232 -0
  28. package/src/components/dependency/dependency-table.tsx +368 -0
  29. package/src/components/dependency/edit-dialog.tsx +200 -0
  30. package/src/components/knowledge-public/bookmark-form-dialog.tsx +215 -0
  31. package/src/components/knowledge-public/compliance-form-dialog.tsx +308 -0
  32. package/src/components/knowledge-public/legal-form-dialog.tsx +315 -0
  33. package/src/components/layout/sidebar-02/app-sidebar.tsx +160 -0
  34. package/src/components/layout/sidebar-02/index.tsx +33 -0
  35. package/src/components/layout/sidebar-02/logo.tsx +40 -0
  36. package/src/components/layout/sidebar-02/nav-main.tsx +153 -0
  37. package/src/components/layout/sidebar-02/nav-notifications.tsx +78 -0
  38. package/src/components/layout/sidebar-02/team-switcher.tsx +106 -0
  39. package/src/components/magicui/animated-theme-toggler.tsx +272 -0
  40. package/src/components/magicui/rainbow-button.tsx +78 -0
  41. package/src/components/market/columns.tsx +151 -0
  42. package/src/components/market/market-app-form.tsx +237 -0
  43. package/src/components/market/market-app-list.tsx +156 -0
  44. package/src/components/monitoring/error-list.tsx +143 -0
  45. package/src/components/monitoring/health-cards.tsx +152 -0
  46. package/src/components/monitoring/metrics-stats.tsx +184 -0
  47. package/src/components/monitoring/request-log-table.tsx +179 -0
  48. package/src/components/providers/theme-toggle.tsx +59 -0
  49. package/src/components/settings/avatar/avatar-config.ts +46 -0
  50. package/src/components/ui/alert-dialog.tsx +216 -0
  51. package/src/components/ui/alert.tsx +76 -0
  52. package/src/components/ui/avatar.tsx +129 -0
  53. package/src/components/ui/badge.tsx +66 -0
  54. package/src/components/ui/button.tsx +84 -0
  55. package/src/components/ui/card.tsx +120 -0
  56. package/src/components/ui/checkbox.tsx +50 -0
  57. package/src/components/ui/collapsible.tsx +50 -0
  58. package/src/components/ui/dialog.tsx +185 -0
  59. package/src/components/ui/dropdown-menu.tsx +286 -0
  60. package/src/components/ui/field.tsx +255 -0
  61. package/src/components/ui/input-group.tsx +171 -0
  62. package/src/components/ui/input.tsx +36 -0
  63. package/src/components/ui/label.tsx +41 -0
  64. package/src/components/ui/pagination.tsx +146 -0
  65. package/src/components/ui/select.tsx +209 -0
  66. package/src/components/ui/separator.tsx +45 -0
  67. package/src/components/ui/sheet.tsx +164 -0
  68. package/src/components/ui/sidebar.tsx +719 -0
  69. package/src/components/ui/skeleton.tsx +30 -0
  70. package/src/components/ui/sonner.tsx +37 -0
  71. package/src/components/ui/switch.tsx +48 -0
  72. package/src/components/ui/table.tsx +133 -0
  73. package/src/components/ui/textarea.tsx +35 -0
  74. package/src/components/ui/tooltip.tsx +74 -0
  75. package/src/components/user/columns.tsx +153 -0
  76. package/src/components/user/settings-activity-log.tsx +358 -0
  77. package/src/components/user/settings-profile/constants.ts +42 -0
  78. package/src/components/user/settings-profile/types.ts +47 -0
  79. package/src/components/user/settings-profile.tsx +664 -0
  80. package/src/components/user/user-create-dialog.tsx +137 -0
  81. package/src/components/user/user-filters.tsx +117 -0
  82. package/src/hooks/use-mobile.ts +36 -0
  83. package/src/instrument.client.ts +34 -0
  84. package/src/lib/anyhub/capabilities.ts +59 -0
  85. package/src/lib/anyhub/types.ts +43 -0
  86. package/src/lib/auth-client.ts +83 -0
  87. package/src/lib/auth-utils.ts +24 -0
  88. package/src/lib/auth.config.ts +264 -0
  89. package/src/lib/avatar.ts +41 -0
  90. package/src/lib/i18n/client.ts +32 -0
  91. package/src/lib/i18n/config.ts +40 -0
  92. package/src/lib/i18n/detect.ts +43 -0
  93. package/src/lib/i18n/locales/en/admin-ai.json +128 -0
  94. package/src/lib/i18n/locales/en/admin-billing.json +56 -0
  95. package/src/lib/i18n/locales/en/admin-users.json +88 -0
  96. package/src/lib/i18n/locales/en/common.json +877 -0
  97. package/src/lib/i18n/locales/en/enterprise.json +208 -0
  98. package/src/lib/i18n/locales/zh/admin-ai.json +128 -0
  99. package/src/lib/i18n/locales/zh/admin-billing.json +56 -0
  100. package/src/lib/i18n/locales/zh/admin-users.json +88 -0
  101. package/src/lib/i18n/locales/zh/common.json +877 -0
  102. package/src/lib/i18n/locales/zh/enterprise.json +208 -0
  103. package/src/lib/i18n/server.ts +18 -0
  104. package/src/lib/i18n/types.ts +5 -0
  105. package/src/lib/monitoring-benchmarks.ts +85 -0
  106. package/src/lib/monitoring-descriptions.ts +78 -0
  107. package/src/lib/prisma.ts +18 -0
  108. package/src/lib/router-context.ts +38 -0
  109. package/src/lib/session.ts +38 -0
  110. package/src/lib/utils.ts +23 -0
  111. package/src/providers/index.tsx +45 -0
  112. package/src/routeTree.gen.ts +1199 -0
  113. package/src/router.tsx +51 -0
  114. package/src/routes/__root.tsx +170 -0
  115. package/src/routes/_nav/ai-providers/$id/edit/route.lazy.tsx +74 -0
  116. package/src/routes/_nav/ai-providers/$id/edit/route.tsx +35 -0
  117. package/src/routes/_nav/ai-providers/index/route.lazy.tsx +33 -0
  118. package/src/routes/_nav/ai-providers/new/route.lazy.tsx +31 -0
  119. package/src/routes/_nav/ai-providers/new/route.tsx +20 -0
  120. package/src/routes/_nav/ai-providers/route.lazy.tsx +26 -0
  121. package/src/routes/_nav/ai-providers/route.tsx +31 -0
  122. package/src/routes/_nav/apps/route.lazy.tsx +30 -0
  123. package/src/routes/_nav/apps/route.tsx +20 -0
  124. package/src/routes/_nav/audit/route.lazy.tsx +100 -0
  125. package/src/routes/_nav/audit/route.tsx +25 -0
  126. package/src/routes/_nav/billing/invoices/route.lazy.tsx +86 -0
  127. package/src/routes/_nav/billing/invoices/route.tsx +25 -0
  128. package/src/routes/_nav/billing/route.lazy.tsx +48 -0
  129. package/src/routes/_nav/billing/route.tsx +27 -0
  130. package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.lazy.tsx +160 -0
  131. package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.tsx +25 -0
  132. package/src/routes/_nav/billing/subscriptions/route.lazy.tsx +113 -0
  133. package/src/routes/_nav/billing/subscriptions/route.tsx +28 -0
  134. package/src/routes/_nav/compliance/route.lazy.tsx +184 -0
  135. package/src/routes/_nav/compliance/route.tsx +25 -0
  136. package/src/routes/_nav/components/route.lazy.tsx +184 -0
  137. package/src/routes/_nav/components/route.tsx +40 -0
  138. package/src/routes/_nav/dashboard/route.lazy.tsx +33 -0
  139. package/src/routes/_nav/dashboard/route.tsx +20 -0
  140. package/src/routes/_nav/dependencies/route.lazy.tsx +66 -0
  141. package/src/routes/_nav/dependencies/route.tsx +32 -0
  142. package/src/routes/_nav/developer-logs/route.lazy.tsx +6 -0
  143. package/src/routes/_nav/developer-logs/route.tsx +14 -0
  144. package/src/routes/_nav/docs/route.lazy.tsx +30 -0
  145. package/src/routes/_nav/docs/route.tsx +20 -0
  146. package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +355 -0
  147. package/src/routes/_nav/knowledge-public/bookmarks/route.tsx +40 -0
  148. package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +245 -0
  149. package/src/routes/_nav/knowledge-public/compliance/route.tsx +40 -0
  150. package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +242 -0
  151. package/src/routes/_nav/knowledge-public/legal/route.tsx +40 -0
  152. package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +235 -0
  153. package/src/routes/_nav/licenses/$licenseId/route.tsx +25 -0
  154. package/src/routes/_nav/licenses/new/route.lazy.tsx +237 -0
  155. package/src/routes/_nav/licenses/route.lazy.tsx +143 -0
  156. package/src/routes/_nav/licenses/route.tsx +25 -0
  157. package/src/routes/_nav/market/route.lazy.tsx +31 -0
  158. package/src/routes/_nav/market/route.tsx +20 -0
  159. package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +305 -0
  160. package/src/routes/_nav/monitoring/ai-usage/route.tsx +3 -0
  161. package/src/routes/_nav/monitoring/errors/route.lazy.tsx +111 -0
  162. package/src/routes/_nav/monitoring/health/route.lazy.tsx +260 -0
  163. package/src/routes/_nav/monitoring/logs/route.lazy.tsx +165 -0
  164. package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +240 -0
  165. package/src/routes/_nav/monitoring/overview/route.lazy.tsx +138 -0
  166. package/src/routes/_nav/monitoring/route.lazy.tsx +68 -0
  167. package/src/routes/_nav/monitoring/route.tsx +20 -0
  168. package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +399 -0
  169. package/src/routes/_nav/monitoring/sentry/route.tsx +20 -0
  170. package/src/routes/_nav/permissions/route.lazy.tsx +504 -0
  171. package/src/routes/_nav/permissions/route.tsx +26 -0
  172. package/src/routes/_nav/route.lazy.tsx +31 -0
  173. package/src/routes/_nav/route.tsx +34 -0
  174. package/src/routes/_nav/settings/route.lazy.tsx +30 -0
  175. package/src/routes/_nav/settings/route.tsx +20 -0
  176. package/src/routes/_nav/sso/route.lazy.tsx +145 -0
  177. package/src/routes/_nav/sso/route.tsx +25 -0
  178. package/src/routes/_nav/users/$userId/route.lazy.tsx +432 -0
  179. package/src/routes/_nav/users/$userId/route.tsx +27 -0
  180. package/src/routes/_nav/users/-components/users-table.tsx +38 -0
  181. package/src/routes/_nav/users/route.lazy.tsx +74 -0
  182. package/src/routes/_nav/users/route.tsx +43 -0
  183. package/src/routes/api/compliance-download/$jobId.ts +35 -0
  184. package/src/routes/api/observability/errors.ts +41 -0
  185. package/src/routes/api/observability/health.ts +30 -0
  186. package/src/routes/api.auth.$.ts +31 -0
  187. package/src/routes/api.sentry-test.ts +33 -0
  188. package/src/routes/index/route.lazy.tsx +22 -0
  189. package/src/routes/index/route.tsx +24 -0
  190. package/src/routes/login/route.lazy.tsx +46 -0
  191. package/src/routes/login/route.tsx +28 -0
  192. package/src/routes/sentry-tunnel.ts +61 -0
  193. package/src/routes/setup/route.lazy.tsx +94 -0
  194. package/src/routes/setup/route.tsx +28 -0
  195. package/src/server/__tests__/admin-ai-alerts.test.ts +113 -0
  196. package/src/server/__tests__/licenses-issue.test.ts +98 -0
  197. package/src/server/admin-ai-alerts.ts +177 -0
  198. package/src/server/admin-ai-usage.ts +134 -0
  199. package/src/server/auth-helpers.ts +51 -0
  200. package/src/server/entitlements.ts +24 -0
  201. package/src/server/knowledge-public/canonical-id.ts +55 -0
  202. package/src/server/knowledge-public/github-sync.ts +86 -0
  203. package/src/server/license-issue.ts +78 -0
  204. package/src/server/middlewares/request.ts +26 -0
  205. package/src/server/observability/debug-middleware.ts +39 -0
  206. package/src/server/observability/error-reporter.ts +99 -0
  207. package/src/server/observability/health-check.ts +140 -0
  208. package/src/server/observability/index.ts +33 -0
  209. package/src/server/observability/logger.ts +49 -0
  210. package/src/server/observability/metrics.ts +105 -0
  211. package/src/server/observability/request-middleware.ts +125 -0
  212. package/src/server/observability/timing.ts +42 -0
  213. package/src/server/pro-services.ts +32 -0
  214. package/src/server/serverFns/admin-billing.ts +94 -0
  215. package/src/server/serverFns/ai-provider/create.ts +92 -0
  216. package/src/server/serverFns/ai-provider/delete.ts +42 -0
  217. package/src/server/serverFns/ai-provider/get.ts +35 -0
  218. package/src/server/serverFns/ai-provider/list.ts +92 -0
  219. package/src/server/serverFns/ai-provider/update.ts +73 -0
  220. package/src/server/serverFns/anyhub/market.ts +64 -0
  221. package/src/server/serverFns/audit.ts +57 -0
  222. package/src/server/serverFns/auth/session.ts +26 -0
  223. package/src/server/serverFns/auth/setup.ts +73 -0
  224. package/src/server/serverFns/compliance.ts +85 -0
  225. package/src/server/serverFns/components/import.ts +197 -0
  226. package/src/server/serverFns/components/list.ts +51 -0
  227. package/src/server/serverFns/components/mutations.ts +84 -0
  228. package/src/server/serverFns/dependency/helpers.ts +245 -0
  229. package/src/server/serverFns/dependency/list.ts +127 -0
  230. package/src/server/serverFns/dependency/sync.ts +123 -0
  231. package/src/server/serverFns/dependency/update.ts +51 -0
  232. package/src/server/serverFns/developer-log-auto-generate.ts +176 -0
  233. package/src/server/serverFns/developer-logs.ts +132 -0
  234. package/src/server/serverFns/knowledge-public/bookmarks/create.ts +53 -0
  235. package/src/server/serverFns/knowledge-public/bookmarks/delete.ts +31 -0
  236. package/src/server/serverFns/knowledge-public/bookmarks/detail.ts +32 -0
  237. package/src/server/serverFns/knowledge-public/bookmarks/list.ts +66 -0
  238. package/src/server/serverFns/knowledge-public/bookmarks/sync-all-github.ts +56 -0
  239. package/src/server/serverFns/knowledge-public/bookmarks/update.ts +50 -0
  240. package/src/server/serverFns/knowledge-public/compliance/create.ts +58 -0
  241. package/src/server/serverFns/knowledge-public/compliance/delete.ts +31 -0
  242. package/src/server/serverFns/knowledge-public/compliance/detail.ts +32 -0
  243. package/src/server/serverFns/knowledge-public/compliance/list.ts +59 -0
  244. package/src/server/serverFns/knowledge-public/compliance/update.ts +53 -0
  245. package/src/server/serverFns/knowledge-public/legal/create.ts +62 -0
  246. package/src/server/serverFns/knowledge-public/legal/delete.ts +31 -0
  247. package/src/server/serverFns/knowledge-public/legal/detail.ts +32 -0
  248. package/src/server/serverFns/knowledge-public/legal/list.ts +60 -0
  249. package/src/server/serverFns/knowledge-public/legal/update.ts +55 -0
  250. package/src/server/serverFns/licenses.ts +92 -0
  251. package/src/server/serverFns/locale.ts +10 -0
  252. package/src/server/serverFns/monitoring/check-ai-alerts.ts +13 -0
  253. package/src/server/serverFns/monitoring/get-ai-usage-overview.ts +25 -0
  254. package/src/server/serverFns/monitoring/get-errors.ts +43 -0
  255. package/src/server/serverFns/monitoring/get-health-settings.ts +35 -0
  256. package/src/server/serverFns/monitoring/get-health.ts +55 -0
  257. package/src/server/serverFns/monitoring/get-metrics.ts +46 -0
  258. package/src/server/serverFns/monitoring/get-request-logs.ts +66 -0
  259. package/src/server/serverFns/monitoring/report-error.ts +55 -0
  260. package/src/server/serverFns/monitoring/reset.ts +70 -0
  261. package/src/server/serverFns/permissions.ts +214 -0
  262. package/src/server/serverFns/sentry/config.ts +88 -0
  263. package/src/server/serverFns/sso-config.ts +83 -0
  264. package/src/server/serverFns/user/create.ts +40 -0
  265. package/src/server/serverFns/user/delete.ts +29 -0
  266. package/src/server/serverFns/user/detail.ts +45 -0
  267. package/src/server/serverFns/user/list.ts +56 -0
  268. package/src/server/serverFns/user/reset-password.ts +28 -0
  269. package/src/server/serverFns/user/update-role.ts +26 -0
  270. package/src/server/serverFns/user/update-status.ts +26 -0
  271. package/src/server.ts +27 -0
  272. package/src/start.ts +85 -0
  273. package/src/styles/_keyframe-animations.scss +91 -0
  274. package/src/styles/_variables.scss +296 -0
  275. package/src/styles/globals.css +200 -0
  276. package/src/types/start-authjs.d.ts +27 -0
@@ -0,0 +1,137 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+
19
+ import { useState, useTransition } from "react";
20
+ import {
21
+ Dialog,
22
+ DialogContent,
23
+ DialogHeader,
24
+ DialogTitle,
25
+ DialogFooter,
26
+ } from "@/components/ui/dialog";
27
+ import { Button } from "@/components/ui/button";
28
+ import { Input } from "@/components/ui/input";
29
+ import { Label } from "@/components/ui/label";
30
+ import {
31
+ Select,
32
+ SelectContent,
33
+ SelectItem,
34
+ SelectTrigger,
35
+ SelectValue,
36
+ } from "@/components/ui/select";
37
+ import { createUserFn } from "@/server/serverFns/user/create";
38
+ import { toast } from '@/components/ui/sonner';
39
+
40
+ interface UserCreateDialogProps {
41
+ open: boolean;
42
+ onOpenChange: (open: boolean) => void;
43
+ }
44
+
45
+ export function UserCreateDialog({ open, onOpenChange }: UserCreateDialogProps) {
46
+ const [isPending, startTransition] = useTransition();
47
+ const [form, setForm] = useState({
48
+ email: "",
49
+ username: "",
50
+ password: "",
51
+ role: "USER" as const,
52
+ });
53
+
54
+ function handleSubmit(e: React.FormEvent) {
55
+ e.preventDefault();
56
+ startTransition(async () => {
57
+ try {
58
+ await createUserFn({
59
+ data: {
60
+ email: form.email,
61
+ password: form.password,
62
+ name: form.username,
63
+ role: form.role,
64
+ },
65
+ });
66
+ toast.success("用户创建成功");
67
+ setForm({ email: "", username: "", password: "", role: "USER" });
68
+ onOpenChange(false);
69
+ } catch (err) {
70
+ toast.error((err as Error).message || "创建失败");
71
+ }
72
+ });
73
+ }
74
+
75
+ return (
76
+ <Dialog open={open} onOpenChange={onOpenChange}>
77
+ <DialogContent className="sm:max-w-106.25">
78
+ <DialogHeader>
79
+ <DialogTitle>创建用户</DialogTitle>
80
+ </DialogHeader>
81
+ <form onSubmit={handleSubmit}>
82
+ <div className="grid gap-4 py-4">
83
+ <div className="grid gap-2">
84
+ <Label htmlFor="email">邮箱</Label>
85
+ <Input
86
+ id="email"
87
+ type="email"
88
+ required
89
+ value={form.email}
90
+ onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))}
91
+ />
92
+ </div>
93
+ <div className="grid gap-2">
94
+ <Label htmlFor="username">用户名</Label>
95
+ <Input
96
+ id="username"
97
+ required
98
+ value={form.username}
99
+ onChange={(e) => setForm((f) => ({ ...f, username: e.target.value }))}
100
+ />
101
+ </div>
102
+ <div className="grid gap-2">
103
+ <Label htmlFor="password">密码</Label>
104
+ <Input
105
+ id="password"
106
+ type="password"
107
+ required
108
+ value={form.password}
109
+ onChange={(e) => setForm((f) => ({ ...f, password: e.target.value }))}
110
+ />
111
+ </div>
112
+ <div className="grid gap-2">
113
+ <Label>角色</Label>
114
+ <Select value={form.role} onValueChange={(v) => setForm((f) => ({ ...f, role: v as typeof form.role }))}>
115
+ <SelectTrigger>
116
+ <SelectValue />
117
+ </SelectTrigger>
118
+ <SelectContent>
119
+ <SelectItem value="USER">用户</SelectItem>
120
+ <SelectItem value="ADMIN">管理员</SelectItem>
121
+ </SelectContent>
122
+ </Select>
123
+ </div>
124
+ </div>
125
+ <DialogFooter>
126
+ <Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
127
+ 取消
128
+ </Button>
129
+ <Button type="submit" disabled={isPending}>
130
+ {isPending ? "创建中..." : "创建"}
131
+ </Button>
132
+ </DialogFooter>
133
+ </form>
134
+ </DialogContent>
135
+ </Dialog>
136
+ );
137
+ }
@@ -0,0 +1,117 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+
19
+ import { useNavigate, useSearch } from "@tanstack/react-router";
20
+ import { useCallback } from "react";
21
+ import { useTranslation } from 'react-i18next'
22
+ import { Input } from "@/components/ui/input";
23
+ import { Button } from "@/components/ui/button";
24
+ import {
25
+ Select,
26
+ SelectContent,
27
+ SelectItem,
28
+ SelectTrigger,
29
+ SelectValue,
30
+ } from "@/components/ui/select";
31
+ import { SearchIcon, XIcon } from "lucide-react";
32
+
33
+ interface UserFiltersProps {
34
+ search?: string
35
+ role?: string
36
+ status?: string
37
+ }
38
+
39
+ export function UserFilters({ search, role, status }: UserFiltersProps) {
40
+ const { t } = useTranslation('common')
41
+ const navigate = useNavigate()
42
+
43
+ const updateParam = useCallback(
44
+ (key: string, value: string | undefined) => {
45
+ const next = { search, role, status } as Record<string, string | undefined>
46
+ if (value && value !== "all") {
47
+ next[key] = value
48
+ } else {
49
+ delete next[key]
50
+ }
51
+ navigate({ to: "/users", search: next })
52
+ },
53
+ [navigate, search, role, status]
54
+ )
55
+
56
+ const clearFilters = useCallback(() => {
57
+ navigate({ to: "/users", search: {} })
58
+ }, [navigate])
59
+
60
+ const hasFilters = !!(search || role || status)
61
+
62
+ return (
63
+ <div className="flex flex-col gap-3 sm:flex-row sm:items-center">
64
+ <div className="relative flex-1 max-w-sm">
65
+ <SearchIcon className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
66
+ <Input
67
+ placeholder={t('userFilters.searchPlaceholder')}
68
+ className="pl-8"
69
+ defaultValue={search || ""}
70
+ onChange={(e) => {
71
+ const value = e.target.value || undefined
72
+ const next: Record<string, string | undefined> = { search: value, role, status }
73
+ if (!value) delete next.search
74
+ navigate({ to: "/users", search: next })
75
+ }}
76
+ />
77
+ </div>
78
+
79
+ <Select
80
+ value={role || "all"}
81
+ onValueChange={(v) => updateParam("role", v)}
82
+ >
83
+ <SelectTrigger className="w-32.5">
84
+ <SelectValue placeholder={t('userFilters.rolePlaceholder')} />
85
+ </SelectTrigger>
86
+ <SelectContent>
87
+ <SelectItem value="all">{t('userFilters.role.all')}</SelectItem>
88
+ <SelectItem value="SUPER_ADMIN">{t('userFilters.role.SUPER_ADMIN')}</SelectItem>
89
+ <SelectItem value="ADMIN">{t('userFilters.role.ADMIN')}</SelectItem>
90
+ <SelectItem value="USER">{t('userFilters.role.USER')}</SelectItem>
91
+ </SelectContent>
92
+ </Select>
93
+
94
+ <Select
95
+ value={status || "all"}
96
+ onValueChange={(v) => updateParam("status", v)}
97
+ >
98
+ <SelectTrigger className="w-32.5">
99
+ <SelectValue placeholder={t('userFilters.statusPlaceholder')} />
100
+ </SelectTrigger>
101
+ <SelectContent>
102
+ <SelectItem value="all">{t('userFilters.status.all')}</SelectItem>
103
+ <SelectItem value="ACTIVE">{t('userFilters.status.ACTIVE')}</SelectItem>
104
+ <SelectItem value="INACTIVE">{t('userFilters.status.INACTIVE')}</SelectItem>
105
+ <SelectItem value="BANNED">{t('userFilters.status.BANNED')}</SelectItem>
106
+ </SelectContent>
107
+ </Select>
108
+
109
+ {hasFilters && (
110
+ <Button variant="ghost" size="sm" onClick={clearFilters}>
111
+ <XIcon className="mr-1 h-4 w-4" />
112
+ {t('userFilters.clearFilters')}
113
+ </Button>
114
+ )}
115
+ </div>
116
+ );
117
+ }
@@ -0,0 +1,36 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ import * as React from "react"
19
+
20
+ const MOBILE_BREAKPOINT = 768
21
+
22
+ export function useIsMobile() {
23
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
24
+
25
+ React.useEffect(() => {
26
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
27
+ const onChange = () => {
28
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
29
+ }
30
+ mql.addEventListener("change", onChange)
31
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
32
+ return () => mql.removeEventListener("change", onChange)
33
+ }, [])
34
+
35
+ return !!isMobile
36
+ }
@@ -0,0 +1,34 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ import * as Sentry from '@sentry/tanstackstart-react'
19
+
20
+ const dsn = import.meta.env.VITE_SENTRY_DSN
21
+
22
+ if (dsn) {
23
+ Sentry.init({
24
+ dsn,
25
+ environment: import.meta.env.VITE_SENTRY_ENVIRONMENT || import.meta.env.MODE,
26
+ integrations: [
27
+ Sentry.replayIntegration(),
28
+ ],
29
+ tracesSampleRate: 1.0,
30
+ replaysSessionSampleRate: 0.1,
31
+ replaysOnErrorSampleRate: 1.0,
32
+ enableLogs: true,
33
+ })
34
+ }
@@ -0,0 +1,59 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ export const CAPABILITY_GROUPS = [
19
+ {
20
+ id: "prd",
21
+ label: "PRD",
22
+ capabilities: [
23
+ { id: "prd:read", label: "读取 PRD" },
24
+ { id: "prd:write", label: "写入 PRD" },
25
+ { id: "prd:delete", label: "删除 PRD" },
26
+ ],
27
+ },
28
+ {
29
+ id: "doc",
30
+ label: "文档",
31
+ capabilities: [
32
+ { id: "doc:read", label: "读取文档" },
33
+ { id: "doc:write", label: "写入文档" },
34
+ ],
35
+ },
36
+ {
37
+ id: "ai",
38
+ label: "AI 配置",
39
+ capabilities: [
40
+ { id: "ai:read", label: "读取配置" },
41
+ { id: "ai:write", label: "修改配置" },
42
+ ],
43
+ },
44
+ {
45
+ id: "user",
46
+ label: "用户信息",
47
+ capabilities: [
48
+ { id: "user:read", label: "读取信息" },
49
+ ],
50
+ },
51
+ {
52
+ id: "team",
53
+ label: "团队",
54
+ capabilities: [
55
+ { id: "team:read", label: "读取团队" },
56
+ { id: "team:write", label: "写入团队" },
57
+ ],
58
+ },
59
+ ] as const
@@ -0,0 +1,43 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ export interface MarketAppFormData {
19
+ name: string
20
+ displayName: string
21
+ description?: string
22
+ entryUrl: string
23
+ icon?: string
24
+ category?: string
25
+ tags?: string
26
+ capabilities: Record<string, boolean>
27
+ }
28
+
29
+ export interface MarketAppItem {
30
+ id: string
31
+ name: string
32
+ displayName: string
33
+ description: string | null
34
+ entryUrl: string
35
+ icon: string | null
36
+ category: string | null
37
+ tags: string | null
38
+ downloads: number
39
+ capabilities: Record<string, boolean>
40
+ status: string
41
+ publishedAt: string | null
42
+ createdAt: string
43
+ }
@@ -0,0 +1,83 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ import { useQuery } from '@tanstack/react-query'
19
+
20
+ export interface Session {
21
+ user: {
22
+ id: string
23
+ email: string
24
+ name: string
25
+ role: 'USER' | 'ADMIN' | 'SUPER_ADMIN'
26
+ image?: string | null
27
+ }
28
+ expires: string
29
+ }
30
+
31
+ export function useSession() {
32
+ return useQuery({
33
+ queryKey: ['session'],
34
+ queryFn: async () => {
35
+ const res = await fetch('/api/auth/session', { credentials: 'include' })
36
+ if (!res.ok) return null
37
+ const data = await res.json()
38
+ return data?.user ? (data as Session) : null
39
+ },
40
+ staleTime: 5 * 60 * 1000,
41
+ refetchOnWindowFocus: false,
42
+ })
43
+ }
44
+
45
+ export async function signIn(provider: string, options?: { redirectTo?: string; email?: string; password?: string; remember?: boolean }) {
46
+ const csrfRes = await fetch('/api/auth/csrf', { credentials: 'include' })
47
+ const { csrfToken } = await csrfRes.json()
48
+ const callbackUrl = options?.redirectTo ?? '/'
49
+ const form = document.createElement('form')
50
+ form.method = 'POST'
51
+ form.action = '/api/auth/callback/credentials'
52
+ form.style.display = 'none'
53
+ const fields = [
54
+ { name: 'email', value: options?.email ?? '' },
55
+ { name: 'password', value: options?.password ?? '' },
56
+ { name: 'remember', value: String(options?.remember ?? false) },
57
+ { name: 'stayLoggedIn', value: String(options?.remember ?? false) },
58
+ { name: 'callbackUrl', value: callbackUrl },
59
+ { name: 'csrfToken', value: csrfToken },
60
+ ]
61
+ for (const field of fields) {
62
+ const input = document.createElement('input')
63
+ input.type = 'hidden'
64
+ input.name = field.name
65
+ input.value = field.value
66
+ form.appendChild(input)
67
+ }
68
+ document.body.appendChild(form)
69
+ form.submit()
70
+ return { success: true }
71
+ }
72
+
73
+ export async function signOut() {
74
+ const csrfRes = await fetch('/api/auth/csrf', { credentials: 'include' })
75
+ const { csrfToken } = await csrfRes.json()
76
+ await fetch('/api/auth/signout', {
77
+ method: 'POST',
78
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
79
+ body: new URLSearchParams({ csrfToken }).toString(),
80
+ credentials: 'include',
81
+ })
82
+ return { success: true }
83
+ }
@@ -0,0 +1,24 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ const EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
19
+
20
+ export function validateEmail(email: string): string | null {
21
+ if (!email) return "邮箱不能为空";
22
+ if (!EMAIL_REGEX.test(email)) return "请输入有效的邮箱地址";
23
+ return null;
24
+ }