@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
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@allbluecn/admin-app",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "license": "AGPL-3.0-or-later",
6
+ "type": "module",
7
+ "dependencies": {
8
+ "@auth/core": "^0.41.2",
9
+ "@lobehub/icons": "^5.16.0",
10
+ "@prisma/client": "^7.8.0",
11
+ "@radix-ui/react-avatar": "^1.1.11",
12
+ "@radix-ui/react-collapsible": "^1.1.12",
13
+ "@radix-ui/react-dialog": "^1.1.15",
14
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
15
+ "@radix-ui/react-separator": "^1.1.8",
16
+ "@radix-ui/react-slot": "^1.2.4",
17
+ "@radix-ui/react-tooltip": "^1.2.8",
18
+ "@sentry/tanstackstart-react": "^10.67.0",
19
+ "@tabler/icons-react": "^3.44.0",
20
+ "@tanstack/charts": "^0.16.0",
21
+ "@tanstack/react-form": "2.0.0-alpha.2",
22
+ "@tanstack/react-query": "^5.102.8",
23
+ "@tanstack/react-router": "1.170.32",
24
+ "@tanstack/react-router-ssr-query": "^1.167.2",
25
+ "@tanstack/react-start": "^1.168.49",
26
+ "@tanstack/react-table": "^9.2.3",
27
+ "@tanstack/react-virtual": "^3.14.10",
28
+ "@tanstack/store": "^0.11.1",
29
+ "bcryptjs": "^3.0.3",
30
+ "class-variance-authority": "^0.7.1",
31
+ "clsx": "^2.1.1",
32
+ "date-fns": "^4.4.0",
33
+ "i18next": "^25.10.10",
34
+ "i18next-browser-languagedetector": "^8.2.1",
35
+ "lucide-react": "^1.27.0",
36
+ "motion": "^12.42.2",
37
+ "next-themes": "^0.4.6",
38
+ "radix-ui": "^1.4.3",
39
+ "react": "^19.2.4",
40
+ "react-dom": "^19.2.4",
41
+ "react-i18next": "^15.7.4",
42
+ "react-markdown": "^10.1.0",
43
+ "remark-gfm": "^4.0.1",
44
+ "sonner": "^2.0.7",
45
+ "start-authjs": "^1.0.2",
46
+ "tailwind-merge": "^3.5.0",
47
+ "zod": "^4.4.3",
48
+ "@allbluecn/database": "0.4.18",
49
+ "@allbluecn/kernel": "0.5.2",
50
+ "@allbluecn/web-app": "0.9.5"
51
+ },
52
+ "devDependencies": {
53
+ "@cloudflare/vite-plugin": "^1.40.1",
54
+ "@eslint/js": "^9.39.4",
55
+ "@tailwindcss/vite": "^4.3.2",
56
+ "@tanstack/react-query-devtools": "5.102.8",
57
+ "@tanstack/react-router-devtools": "1.167.1",
58
+ "@types/bcryptjs": "^2.4.6",
59
+ "@types/node": "^24.13.3",
60
+ "@types/react": "^19",
61
+ "@types/react-dom": "^19",
62
+ "@vitejs/plugin-react": "^6.0.1",
63
+ "@vitest/coverage-v8": "^4.1.10",
64
+ "eslint": "^9",
65
+ "nitro": "3.0.260603-beta",
66
+ "shadcn": "^4.15.0",
67
+ "tailwindcss": "^4",
68
+ "typescript": "^7",
69
+ "vite": "^8.1.5",
70
+ "vitest": "^4.1.10",
71
+ "wrangler": "^4.99.0"
72
+ },
73
+ "files": [
74
+ "src",
75
+ "public"
76
+ ],
77
+ "publishConfig": {
78
+ "access": "public"
79
+ },
80
+ "scripts": {
81
+ "dev": "NODE_OPTIONS='--import ./instrument.server.mjs' vite --port 3001",
82
+ "build": "vite build && if [ -d .output/server ]; then cp instrument.server.mjs .output/server; fi",
83
+ "start": "node --import ./.output/server/instrument.server.mjs .output/server/index.mjs",
84
+ "lint": "eslint"
85
+ }
86
+ }
package/src/client.tsx ADDED
@@ -0,0 +1,31 @@
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 './instrument.client'
19
+
20
+ import { StartClient } from '@tanstack/react-start/client'
21
+ import { StrictMode, startTransition } from 'react'
22
+ import { hydrateRoot } from 'react-dom/client'
23
+
24
+ startTransition(() => {
25
+ hydrateRoot(
26
+ document,
27
+ <StrictMode>
28
+ <StartClient />
29
+ </StrictMode>,
30
+ )
31
+ })
@@ -0,0 +1,90 @@
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 { useTranslation } from 'react-i18next'
20
+ import { LogOut } from "lucide-react";
21
+ import { Badge } from "@/components/ui/badge";
22
+ import {
23
+ SidebarMenu,
24
+ SidebarMenuAction,
25
+ SidebarMenuButton,
26
+ SidebarMenuItem,
27
+ } from "@/components/ui/sidebar";
28
+ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
29
+ import { useRouter, useMatches } from "@tanstack/react-router";
30
+ import { signOut, useSession } from "@/lib/auth-client";
31
+ import { UserAvatar } from "@allbluecn/web-app/user-avatar";
32
+ import type { AvatarConfig, ShapeType } from "@allbluecn/kernel";
33
+
34
+ export function AdminUserMenu() {
35
+ const { t } = useTranslation('common')
36
+ const router = useRouter();
37
+ const { data: clientSession } = useSession();
38
+ const matches = useMatches();
39
+
40
+ const navMatch = matches.find((m) => m.routeId === '/_nav');
41
+ const contextSession = navMatch?.context?.session as typeof clientSession | null;
42
+ const session = clientSession || contextSession;
43
+
44
+ const user = session?.user;
45
+ const userName = user?.name || user?.email || t('login.admin');
46
+ const userEmail = user?.email || "";
47
+ const userRole = (user as { role?: string })?.role || "ADMIN";
48
+ const roleLabel = t(`common.role.${userRole}`, { defaultValue: t('login.admin') });
49
+ const avatarConfig = (user as { avatarConfig?: AvatarConfig })?.avatarConfig;
50
+ const bgShape = (user as { bgShape?: string })?.bgShape as ShapeType | undefined;
51
+ const bgColor = (user as { bgColor?: string })?.bgColor;
52
+
53
+ const handleExitAdmin = async () => {
54
+ await signOut();
55
+ await router.navigate({ to: "/login" });
56
+ };
57
+
58
+ return (
59
+ <SidebarMenu>
60
+ <SidebarMenuItem>
61
+ <SidebarMenuButton size="lg" className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
62
+ <UserAvatar
63
+ avatarConfig={avatarConfig}
64
+ bgShape={bgShape}
65
+ bgColor={bgColor}
66
+ name={userName}
67
+ size="md"
68
+ />
69
+ <div className="grid flex-1 text-left text-sm leading-tight">
70
+ <div className="flex items-center gap-2">
71
+ <span className="truncate font-medium text-sm">{userName}</span>
72
+ <Badge variant="outline" className="text-[10px] bg-blue-50 text-blue-700 dark:bg-blue-950 dark:text-blue-300">
73
+ {roleLabel}
74
+ </Badge>
75
+ </div>
76
+ <span className="truncate text-xs">{userEmail}</span>
77
+ </div>
78
+ </SidebarMenuButton>
79
+ <Tooltip>
80
+ <TooltipTrigger asChild>
81
+ <SidebarMenuAction onClick={handleExitAdmin} className="hover:text-red-500 h-7 w-8 flex items-center justify-center">
82
+ <LogOut className="h-4 w-4" />
83
+ </SidebarMenuAction>
84
+ </TooltipTrigger>
85
+ <TooltipContent>{t('login.exitAdmin')}</TooltipContent>
86
+ </Tooltip>
87
+ </SidebarMenuItem>
88
+ </SidebarMenu>
89
+ );
90
+ }
@@ -0,0 +1,356 @@
1
+ import { useTranslation } from 'react-i18next'
2
+ import { useState, useMemo } from 'react'
3
+ import { Route } from '@/routes/_nav/developer-logs/route'
4
+ import type { DeveloperLogEntry } from '@allbluecn/kernel'
5
+ import { useQueryClient } from '@tanstack/react-query'
6
+ import { useServerFn } from '@tanstack/react-start'
7
+ import { toast } from 'sonner'
8
+ import {
9
+ Plus,
10
+ Pencil,
11
+ Trash2,
12
+ Search,
13
+ Loader2,
14
+ RefreshCw,
15
+ } from 'lucide-react'
16
+ import {
17
+ Dialog,
18
+ DialogContent,
19
+ DialogHeader,
20
+ DialogTitle,
21
+ DialogFooter,
22
+ } from '@/components/ui/dialog'
23
+ import {
24
+ AlertDialog,
25
+ AlertDialogAction,
26
+ AlertDialogCancel,
27
+ AlertDialogContent,
28
+ AlertDialogDescription,
29
+ AlertDialogFooter,
30
+ AlertDialogHeader,
31
+ AlertDialogTitle,
32
+ AlertDialogTrigger,
33
+ } from '@/components/ui/alert-dialog'
34
+ import { Input } from '@/components/ui/input'
35
+ import { Textarea } from '@/components/ui/textarea'
36
+ import { Label } from '@/components/ui/label'
37
+ import { Button } from '@/components/ui/button'
38
+ import { createDeveloperLogFn, updateDeveloperLogFn, deleteDeveloperLogFn } from '@/server/serverFns/developer-logs'
39
+ import { autoGenerateLogsFn, backfillLogDetailsFn } from '@/server/serverFns/developer-log-auto-generate'
40
+
41
+ const TAG_OPTIONS = ['feat', 'fix', 'improvement', 'design', 'refactor', 'breaking', 'docs', 'security', 'perf']
42
+
43
+ function DeveloperLogDialog({
44
+ open,
45
+ onOpenChange,
46
+ entry,
47
+ }: {
48
+ open: boolean
49
+ onOpenChange: (open: boolean) => void
50
+ entry?: DeveloperLogEntry
51
+ }) {
52
+ const { t } = useTranslation('common')
53
+ const isEdit = !!entry
54
+
55
+ const [date, setDate] = useState(entry?.date || '')
56
+ const [title, setTitle] = useState(entry?.title || '')
57
+ const [desc, setDesc] = useState(entry?.desc || '')
58
+ const [tagStr, setTagStr] = useState(entry?.tags?.join(', ') || '')
59
+ const [commitSha, setCommitSha] = useState(entry?.commitSha || '')
60
+
61
+ const createFn = useServerFn(createDeveloperLogFn)
62
+ const updateFn = useServerFn(updateDeveloperLogFn)
63
+ const queryClient = useQueryClient()
64
+
65
+ const handleSave = async () => {
66
+ if (!title.trim()) {
67
+ toast.error(t('developerLogs.form.fillRequired'))
68
+ return
69
+ }
70
+
71
+ const tags = tagStr.split(',').map((s) => s.trim()).filter(Boolean)
72
+
73
+ if (isEdit) {
74
+ await updateFn({ data: { id: entry!.id, date, year: date.split(',')[1]?.trim() || new Date().getFullYear().toString(), title, desc, tags, commitSha: commitSha || null } })
75
+ } else {
76
+ const year = date.split(',')[1]?.trim() || new Date().getFullYear().toString()
77
+ await createFn({ data: { date, year, title, desc, tags, commitSha: commitSha || undefined } })
78
+ }
79
+
80
+ toast.success(t('developerLogs.saveSuccess'))
81
+ queryClient.invalidateQueries({ queryKey: ['DeveloperLogPage'] })
82
+ onOpenChange(false)
83
+ }
84
+
85
+ return (
86
+ <Dialog open={open} onOpenChange={onOpenChange}>
87
+ <DialogContent className="max-w-lg">
88
+ <DialogHeader>
89
+ <DialogTitle>{isEdit ? t('developerLogs.editEntry') : t('developerLogs.addEntry')}</DialogTitle>
90
+ </DialogHeader>
91
+ <div className="grid gap-4 py-4">
92
+ <div className="grid gap-2">
93
+ <Label>{t('developerLogs.form.date.label')}</Label>
94
+ <Input value={date} onChange={(e) => setDate(e.target.value)} placeholder={t('developerLogs.form.date.placeholder')} />
95
+ </div>
96
+ <div className="grid gap-2">
97
+ <Label>{t('developerLogs.form.title.label')}</Label>
98
+ <Input value={title} onChange={(e) => setTitle(e.target.value)} placeholder={t('developerLogs.form.title.placeholder')} />
99
+ </div>
100
+ <div className="grid gap-2">
101
+ <Label>{t('developerLogs.form.desc.label')}</Label>
102
+ <Textarea value={desc} onChange={(e) => setDesc(e.target.value)} placeholder={t('developerLogs.form.desc.placeholder')} rows={3} />
103
+ </div>
104
+ <div className="grid gap-2">
105
+ <Label>{t('developerLogs.form.tags.label')}</Label>
106
+ <div className="flex gap-1 flex-wrap">
107
+ {TAG_OPTIONS.map((tag) => {
108
+ const isActive = tagStr.split(',').map((s) => s.trim()).includes(tag)
109
+ return (
110
+ <button
111
+ key={tag}
112
+ type="button"
113
+ onClick={() => {
114
+ const current = tagStr.split(',').map((s) => s.trim()).filter(Boolean)
115
+ if (isActive) {
116
+ setTagStr(current.filter((s) => s !== tag).join(', '))
117
+ } else {
118
+ setTagStr([...current, tag].join(', '))
119
+ }
120
+ }}
121
+ className={`px-2 py-1 rounded text-xs font-medium transition-colors ${isActive ? 'bg-primary/10 text-primary border border-primary/20' : 'bg-muted text-muted-foreground border border-border'}`}
122
+ >
123
+ {tag}
124
+ </button>
125
+ )
126
+ })}
127
+ </div>
128
+ </div>
129
+ <div className="grid gap-2">
130
+ <Label>{t('developerLogs.form.commitSha.label')}</Label>
131
+ <Input value={commitSha} onChange={(e) => setCommitSha(e.target.value)} placeholder={t('developerLogs.form.commitSha.placeholder')} />
132
+ </div>
133
+ </div>
134
+ <DialogFooter>
135
+ <Button variant="outline" onClick={() => onOpenChange(false)}>{t('button.cancel')}</Button>
136
+ <Button onClick={handleSave}>{t('button.save')}</Button>
137
+ </DialogFooter>
138
+ </DialogContent>
139
+ </Dialog>
140
+ )
141
+ }
142
+
143
+ function DeveloperLogPage() {
144
+ const { t } = useTranslation('common')
145
+ const { entries } = Route.useLoaderData() as { entries: DeveloperLogEntry[] }
146
+ const queryClient = useQueryClient()
147
+
148
+ const [searchQuery, setSearchQuery] = useState('')
149
+ const [activeTag, setActiveTag] = useState<string>('all')
150
+ const [isAddOpen, setIsAddOpen] = useState(false)
151
+ const [editOpen, setEditOpen] = useState(false)
152
+ const [editingEntry, setEditingEntry] = useState<DeveloperLogEntry | undefined>()
153
+ const [isGenerating, setIsGenerating] = useState(false)
154
+
155
+ const allTags = useMemo(() => {
156
+ const tagSet = new Set<string>()
157
+ entries.forEach((e) => e.tags.forEach((tag) => tagSet.add(tag)))
158
+ return Array.from(tagSet).sort()
159
+ }, [entries])
160
+
161
+ const filteredEntries = useMemo(() => {
162
+ let result = entries
163
+ if (searchQuery) {
164
+ const q = searchQuery.toLowerCase()
165
+ result = result.filter((e) =>
166
+ e.title.toLowerCase().includes(q) || e.desc.toLowerCase().includes(q)
167
+ )
168
+ }
169
+ if (activeTag !== 'all') {
170
+ result = result.filter((e) => e.tags.includes(activeTag))
171
+ }
172
+ return result
173
+ }, [entries, searchQuery, activeTag])
174
+
175
+ const deleteFn = useServerFn(deleteDeveloperLogFn)
176
+ const autoGenFn = useServerFn(autoGenerateLogsFn)
177
+ const backfillDetailsFn = useServerFn(backfillLogDetailsFn)
178
+
179
+ const handleDelete = async (id: string) => {
180
+ await deleteFn({ data: { id } })
181
+ toast.success(t('developerLogs.deleteSuccess'))
182
+ queryClient.invalidateQueries({ queryKey: ['DeveloperLogPage'] })
183
+ }
184
+
185
+ const handleAutoGenerate = async () => {
186
+ setIsGenerating(true)
187
+ try {
188
+ const result = await autoGenFn({ data: { daysBack: 60 } })
189
+ if (result.success) {
190
+ toast.success(t('developerLogs.autoGenerateSuccess', { count: result.count }))
191
+ queryClient.invalidateQueries({ queryKey: ['DeveloperLogPage'] })
192
+ } else {
193
+ toast.error(t('developerLogs.autoGenerateFailed'))
194
+ }
195
+ } catch {
196
+ toast.error(t('developerLogs.autoGenerateFailed'))
197
+ } finally {
198
+ setIsGenerating(false)
199
+ }
200
+ }
201
+
202
+ const handleEdit = (entry: DeveloperLogEntry) => {
203
+ setEditingEntry(entry)
204
+ setEditOpen(true)
205
+ }
206
+
207
+ const handleBackfill = async () => {
208
+ try {
209
+ const result = await backfillDetailsFn()
210
+ if (result.success) {
211
+ toast.success(t('developerLogs.backfillSuccess', { count: result.count }))
212
+ queryClient.invalidateQueries({ queryKey: ['DeveloperLogPage'] })
213
+ } else {
214
+ toast.error(t('developerLogs.backfillFailed'))
215
+ }
216
+ } catch {
217
+ toast.error(t('developerLogs.backfillFailed'))
218
+ }
219
+ }
220
+
221
+ return (
222
+ <div className="h-full flex flex-col p-6 gap-4">
223
+ <div className="flex items-center justify-between">
224
+ <div>
225
+ <h1 className="text-2xl font-semibold">{t('developerLogs.title')}</h1>
226
+ <p className="text-sm text-muted-foreground mt-1">{t('developerLogs.description')}</p>
227
+ </div>
228
+ <div className="flex items-center gap-2">
229
+ <Dialog open={isGenerating} onOpenChange={setIsGenerating}>
230
+ <DialogContent>
231
+ <DialogHeader>
232
+ <DialogTitle>{t('developerLogs.autoGenerate')}</DialogTitle>
233
+ </DialogHeader>
234
+ <div className="flex items-center gap-2 py-4">
235
+ <Loader2 className="size-4 animate-spin" />
236
+ <span className="text-sm text-muted-foreground">{t('developerLogs.autoGenerating')}</span>
237
+ </div>
238
+ </DialogContent>
239
+ </Dialog>
240
+ <Button variant="outline" onClick={handleAutoGenerate} disabled={isGenerating}>
241
+ <Loader2 className="size-4 mr-1.5 animate-spin" />
242
+ {t('developerLogs.autoGenerate')}
243
+ </Button>
244
+ <Button variant="outline" onClick={handleBackfill}>
245
+ <RefreshCw className="size-4 mr-1.5" />
246
+ {t('developerLogs.backfillDetails')}
247
+ </Button>
248
+ <Button onClick={() => { setEditingEntry(undefined); setEditOpen(true) }}>
249
+ <Plus className="size-4 mr-1.5" />
250
+ {t('developerLogs.addEntry')}
251
+ </Button>
252
+ </div>
253
+ </div>
254
+
255
+ <div className="flex items-center gap-3">
256
+ <div className="relative flex-1 max-w-sm">
257
+ <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground" />
258
+ <Input
259
+ value={searchQuery}
260
+ onChange={(e) => setSearchQuery(e.target.value)}
261
+ placeholder={t('developerLogs.searchPlaceholder')}
262
+ className="pl-8"
263
+ />
264
+ </div>
265
+ <div className="flex items-center gap-1 flex-wrap">
266
+ <button
267
+ onClick={() => setActiveTag('all')}
268
+ className={`px-2.5 py-1 rounded text-xs font-medium transition-colors ${activeTag === 'all' ? 'bg-primary/10 text-primary' : 'text-muted-foreground hover:text-foreground'}`}
269
+ >
270
+ {t('developerLogs.allTags')}
271
+ </button>
272
+ {allTags.map((tag) => (
273
+ <button
274
+ key={tag}
275
+ onClick={() => setActiveTag(tag === activeTag ? 'all' : tag)}
276
+ className={`px-2.5 py-1 rounded text-xs font-medium transition-colors ${activeTag === tag ? 'bg-primary/10 text-primary' : 'text-muted-foreground hover:text-foreground'}`}
277
+ >
278
+ {tag}
279
+ </button>
280
+ ))}
281
+ </div>
282
+ </div>
283
+
284
+ <div className="flex-1 min-h-0 overflow-auto border rounded-lg">
285
+ <table className="w-full">
286
+ <thead className="sticky top-0 bg-muted/50">
287
+ <tr>
288
+ <th className="text-left px-4 py-2.5 text-xs font-medium text-muted-foreground">{t('developerLogs.columns.date')}</th>
289
+ <th className="text-left px-4 py-2.5 text-xs font-medium text-muted-foreground">{t('developerLogs.columns.title')}</th>
290
+ <th className="text-left px-4 py-2.5 text-xs font-medium text-muted-foreground">{t('developerLogs.columns.desc')}</th>
291
+ <th className="text-left px-4 py-2.5 text-xs font-medium text-muted-foreground">{t('developerLogs.columns.tags')}</th>
292
+ <th className="text-right px-4 py-2.5 text-xs font-medium text-muted-foreground">{t('developerLogs.columns.actions')}</th>
293
+ </tr>
294
+ </thead>
295
+ <tbody>
296
+ {filteredEntries.length === 0 ? (
297
+ <tr>
298
+ <td colSpan={5} className="text-center py-12 text-sm text-muted-foreground">
299
+ {t('developerLogs.empty')}
300
+ </td>
301
+ </tr>
302
+ ) : (
303
+ filteredEntries.map((entry) => (
304
+ <tr key={entry.id} className="border-t border-border/50 hover:bg-muted/30 transition-colors">
305
+ <td className="px-4 py-3 text-sm whitespace-nowrap">{entry.date}</td>
306
+ <td className="px-4 py-3 text-sm font-medium max-w-[200px] truncate">{entry.title}</td>
307
+ <td className="px-4 py-3 text-sm text-muted-foreground max-w-[400px] truncate">{entry.desc}</td>
308
+ <td className="px-4 py-3">
309
+ <div className="flex flex-wrap gap-1">
310
+ {entry.tags.map((tag) => (
311
+ <span key={tag} className="px-1.5 py-0.5 rounded text-[10px] bg-muted/50 text-muted-foreground">{tag}</span>
312
+ ))}
313
+ </div>
314
+ </td>
315
+ <td className="px-4 py-3 text-right">
316
+ <div className="flex items-center justify-end gap-1">
317
+ <Button variant="ghost" size="icon" className="size-7" onClick={() => handleEdit(entry)}>
318
+ <Pencil className="size-3.5" />
319
+ </Button>
320
+ <AlertDialog>
321
+ <AlertDialogTrigger asChild>
322
+ <Button variant="ghost" size="icon" className="size-7 text-destructive">
323
+ <Trash2 className="size-3.5" />
324
+ </Button>
325
+ </AlertDialogTrigger>
326
+ <AlertDialogContent>
327
+ <AlertDialogHeader>
328
+ <AlertDialogTitle>{t('button.delete')}</AlertDialogTitle>
329
+ <AlertDialogDescription>{t('developerLogs.deleteConfirm')}</AlertDialogDescription>
330
+ </AlertDialogHeader>
331
+ <AlertDialogFooter>
332
+ <AlertDialogCancel>{t('button.cancel')}</AlertDialogCancel>
333
+ <AlertDialogAction onClick={() => handleDelete(entry.id)} className="bg-destructive hover:bg-destructive/90">{t('button.confirm')}</AlertDialogAction>
334
+ </AlertDialogFooter>
335
+ </AlertDialogContent>
336
+ </AlertDialog>
337
+ </div>
338
+ </td>
339
+ </tr>
340
+ ))
341
+ )}
342
+ </tbody>
343
+ </table>
344
+ </div>
345
+
346
+ <DeveloperLogDialog
347
+ key={editingEntry?.id || 'new'}
348
+ open={editOpen}
349
+ onOpenChange={setEditOpen}
350
+ entry={editingEntry}
351
+ />
352
+ </div>
353
+ )
354
+ }
355
+
356
+ export { DeveloperLogPage }