@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,55 @@
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 { createServerFn } from '@tanstack/react-start'
19
+ import { prisma } from '@allbluecn/database'
20
+ import { requireAdmin } from '@/server/auth-helpers'
21
+ import { performHealthCheck } from '@/server/observability'
22
+
23
+ export const getHealthStatus = createServerFn({ method: 'GET' })
24
+ .handler(async () => {
25
+ await requireAdmin()
26
+
27
+ const current = await performHealthCheck()
28
+ const recent = await prisma.monitoringHealthSnapshot.findMany({
29
+ orderBy: { timestamp: 'desc' },
30
+ take: 50,
31
+ })
32
+
33
+ const dbStats = await prisma.monitoringRequestLog.aggregate({
34
+ _count: { _all: true },
35
+ _avg: { duration: true },
36
+ _min: { duration: true },
37
+ _max: { duration: true },
38
+ })
39
+
40
+ const errors = await prisma.monitoringErrorReport.aggregate({
41
+ _count: { _all: true },
42
+ })
43
+
44
+ return {
45
+ current,
46
+ recent,
47
+ requestStats: {
48
+ total: dbStats._count._all,
49
+ avgDuration: dbStats._avg.duration || 0,
50
+ minDuration: dbStats._min.duration || 0,
51
+ maxDuration: dbStats._max.duration || 0,
52
+ },
53
+ errorCount: errors._count._all,
54
+ }
55
+ })
@@ -0,0 +1,46 @@
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 { createServerFn } from '@tanstack/react-start'
19
+ import { requireAdmin } from '@/server/auth-helpers'
20
+ import { metrics } from '@/server/observability'
21
+ import { prisma } from '@allbluecn/database'
22
+
23
+ export const getMetrics = createServerFn({ method: 'GET' })
24
+ .handler(async () => {
25
+ await requireAdmin()
26
+
27
+ const memory = process.memoryUsage()
28
+ const allStats = metrics.getAllStats()
29
+
30
+ const snapshots = await prisma.monitoringMetricsSnapshot.findMany({
31
+ orderBy: { recordedAt: 'desc' },
32
+ take: 100,
33
+ })
34
+
35
+ return {
36
+ memory: {
37
+ rss: memory.rss,
38
+ heapTotal: memory.heapTotal,
39
+ heapUsed: memory.heapUsed,
40
+ external: memory.external,
41
+ arrayBuffers: memory.arrayBuffers,
42
+ },
43
+ current: allStats,
44
+ history: snapshots,
45
+ }
46
+ })
@@ -0,0 +1,66 @@
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 { createServerFn } from '@tanstack/react-start'
19
+ import { prisma } from '@allbluecn/database'
20
+ import { requireAdmin } from '@/server/auth-helpers'
21
+
22
+ type GetRequestLogsInput = {
23
+ limit?: number
24
+ offset?: number
25
+ method?: string
26
+ status?: number
27
+ path?: string
28
+ excludeMonitoring?: boolean
29
+ }
30
+
31
+ const MONITORING_PREFIXES = [
32
+ '/api/observability/',
33
+ '/src/routes/',
34
+ '/node_modules/',
35
+ ]
36
+
37
+ export const getRequestLogs = createServerFn({ method: 'GET' })
38
+ .validator((data: GetRequestLogsInput) => data)
39
+ .handler(async ({ data }) => {
40
+ await requireAdmin()
41
+
42
+ const where: any = {}
43
+ if (data.method) where.method = data.method
44
+ if (data.status) where.status = data.status
45
+ if (data.path) where.path = { contains: data.path }
46
+ if (data.excludeMonitoring) {
47
+ where.NOT = MONITORING_PREFIXES.map((prefix) => ({
48
+ path: { startsWith: prefix },
49
+ }))
50
+ }
51
+
52
+ const limit = data.limit || 50
53
+ const offset = data.offset || 0
54
+
55
+ const [logs, total] = await Promise.all([
56
+ prisma.monitoringRequestLog.findMany({
57
+ where,
58
+ orderBy: { timestamp: 'desc' },
59
+ take: limit,
60
+ skip: offset,
61
+ }),
62
+ prisma.monitoringRequestLog.count({ where }),
63
+ ])
64
+
65
+ return { logs, total, limit, offset }
66
+ })
@@ -0,0 +1,55 @@
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 { createServerFn } from '@tanstack/react-start'
19
+ import { prisma } from '@allbluecn/database'
20
+ import { requireAdmin } from '@/server/auth-helpers'
21
+
22
+ type ReportErrorInput = {
23
+ errorName: string
24
+ errorMessage: string
25
+ stackTrace?: string
26
+ context?: Record<string, unknown>
27
+ source?: 'browser' | 'server'
28
+ }
29
+
30
+ export const reportClientError = createServerFn({ method: 'POST' })
31
+ .validator((data: ReportErrorInput) => data)
32
+ .handler(async ({ data }) => {
33
+ await requireAdmin()
34
+
35
+ const key = `${data.errorName}:${data.errorMessage}`
36
+ await prisma.monitoringErrorReport.upsert({
37
+ where: { id: key },
38
+ create: {
39
+ id: key,
40
+ errorName: data.errorName,
41
+ errorMessage: data.errorMessage,
42
+ stackTrace: data.stackTrace || null,
43
+ count: 1,
44
+ firstSeen: new Date(),
45
+ lastSeen: new Date(),
46
+ context: (data.context as any) ?? { source: data.source || 'browser' },
47
+ },
48
+ update: {
49
+ count: { increment: 1 },
50
+ lastSeen: new Date(),
51
+ },
52
+ })
53
+
54
+ return { ok: true }
55
+ })
@@ -0,0 +1,70 @@
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 { createServerFn } from '@tanstack/react-start'
19
+ import { requireAdmin } from '@/server/auth-helpers'
20
+ import { metrics, resetErrors } from '@/server/observability'
21
+ import { prisma } from '@allbluecn/database'
22
+
23
+ type ResetInput = {
24
+ type: 'errors' | 'metrics' | 'request-logs' | 'health' | 'all'
25
+ days?: number
26
+ }
27
+
28
+ export const resetMonitoring = createServerFn({ method: 'POST' })
29
+ .validator((data: ResetInput) => data)
30
+ .handler(async ({ data }) => {
31
+ await requireAdmin()
32
+
33
+ const cutoff = data.days ? new Date(Date.now() - data.days * 86400000) : null
34
+
35
+ if (data.type === 'errors' || data.type === 'all') {
36
+ resetErrors()
37
+ if (cutoff) {
38
+ await prisma.monitoringErrorReport.deleteMany({ where: { lastSeen: { lt: cutoff } } })
39
+ } else {
40
+ await prisma.monitoringErrorReport.deleteMany()
41
+ }
42
+ }
43
+
44
+ if (data.type === 'metrics' || data.type === 'all') {
45
+ metrics.reset()
46
+ if (cutoff) {
47
+ await prisma.monitoringMetricsSnapshot.deleteMany({ where: { recordedAt: { lt: cutoff } } })
48
+ } else {
49
+ await prisma.monitoringMetricsSnapshot.deleteMany()
50
+ }
51
+ }
52
+
53
+ if (data.type === 'request-logs' || data.type === 'all') {
54
+ if (cutoff) {
55
+ await prisma.monitoringRequestLog.deleteMany({ where: { timestamp: { lt: cutoff } } })
56
+ } else {
57
+ await prisma.monitoringRequestLog.deleteMany()
58
+ }
59
+ }
60
+
61
+ if (data.type === 'health' || data.type === 'all') {
62
+ if (cutoff) {
63
+ await prisma.monitoringHealthSnapshot.deleteMany({ where: { timestamp: { lt: cutoff } } })
64
+ } else {
65
+ await prisma.monitoringHealthSnapshot.deleteMany()
66
+ }
67
+ }
68
+
69
+ return { ok: true }
70
+ })
@@ -0,0 +1,214 @@
1
+ import { createServerFn } from '@tanstack/react-start'
2
+ import { z } from 'zod'
3
+ import { prisma } from '@allbluecn/database'
4
+ import { requireAdmin } from '@/server/auth-helpers'
5
+ import { requireFeature } from '@/server/entitlements'
6
+ import { getProService } from '@/server/pro-services'
7
+
8
+ type SystemRolePreset = {
9
+ name: string
10
+ permissions: Record<string, 'admin' | 'write' | 'read' | 'none'>
11
+ }
12
+
13
+ function buildSystemRolePresets(): SystemRolePreset[] {
14
+ const R = 'admin'
15
+ const W = 'write'
16
+ const R2 = 'read'
17
+ const N = 'none'
18
+
19
+ const base: Record<string, 'admin' | 'write' | 'read' | 'none'> = {
20
+ 'knowledge:read': R,
21
+ 'knowledge:write': R,
22
+ 'knowledge:delete': R,
23
+ 'story:read': R,
24
+ 'story:write': R,
25
+ 'story:publish': R,
26
+ 'story:delete': R,
27
+ 'user:read': R,
28
+ 'user:write': R,
29
+ 'user:delete': R,
30
+ 'setting:read': R,
31
+ 'setting:write': R,
32
+ 'billing:read': R,
33
+ 'billing:write': R,
34
+ }
35
+
36
+ return [
37
+ { name: 'owner', permissions: { ...base } },
38
+ {
39
+ name: 'admin',
40
+ permissions: { ...base, 'user:delete': N, 'billing:write': N },
41
+ },
42
+ {
43
+ name: 'member',
44
+ permissions: {
45
+ ...base,
46
+ 'knowledge:delete': N,
47
+ 'story:publish': N,
48
+ 'story:delete': N,
49
+ 'user:read': N,
50
+ 'user:write': N,
51
+ 'user:delete': N,
52
+ 'setting:write': N,
53
+ 'billing:read': N,
54
+ 'billing:write': N,
55
+ },
56
+ },
57
+ {
58
+ name: 'editor',
59
+ permissions: {
60
+ 'knowledge:read': R2,
61
+ 'knowledge:write': W,
62
+ 'knowledge:delete': N,
63
+ 'story:read': R2,
64
+ 'story:write': W,
65
+ 'story:publish': N,
66
+ 'story:delete': N,
67
+ 'user:read': N,
68
+ 'user:write': N,
69
+ 'user:delete': N,
70
+ 'setting:read': N,
71
+ 'setting:write': N,
72
+ 'billing:read': N,
73
+ 'billing:write': N,
74
+ },
75
+ },
76
+ {
77
+ name: 'viewer',
78
+ permissions: {
79
+ 'knowledge:read': R2,
80
+ 'knowledge:write': N,
81
+ 'knowledge:delete': N,
82
+ 'story:read': R2,
83
+ 'story:write': N,
84
+ 'story:publish': N,
85
+ 'story:delete': N,
86
+ 'user:read': N,
87
+ 'user:write': N,
88
+ 'user:delete': N,
89
+ 'setting:read': N,
90
+ 'setting:write': N,
91
+ 'billing:read': N,
92
+ 'billing:write': N,
93
+ },
94
+ },
95
+ ]
96
+ }
97
+
98
+ export const listRolesFn = createServerFn({ method: 'GET' })
99
+ .validator(z.object({ workspaceId: z.string() }))
100
+ .handler(async ({ data }) => {
101
+ await requireAdmin()
102
+ await requireFeature(undefined, 'advancedPermissions')
103
+
104
+ const systemCount = await prisma.customRole.count({
105
+ where: { workspaceId: data.workspaceId, isSystem: true },
106
+ })
107
+ if (systemCount === 0 && getProService('permissions')) {
108
+ const presets = buildSystemRolePresets()
109
+ for (const preset of presets) {
110
+ await prisma.customRole.create({
111
+ data: {
112
+ workspaceId: data.workspaceId,
113
+ name: preset.name,
114
+ permissions: preset.permissions as never,
115
+ isSystem: true,
116
+ },
117
+ })
118
+ }
119
+ const admins = await prisma.user.findMany({
120
+ where: { role: 'ADMIN' },
121
+ select: { id: true },
122
+ })
123
+ const ownerRole = await prisma.customRole.findFirst({
124
+ where: { workspaceId: data.workspaceId, name: 'owner', isSystem: true },
125
+ })
126
+ if (ownerRole && admins.length > 0) {
127
+ await prisma.roleAssignment.createMany({
128
+ data: admins.map(({ id }: { id: string }) => ({ userId: id, roleId: ownerRole.id })),
129
+ skipDuplicates: true,
130
+ })
131
+ }
132
+ }
133
+
134
+ const [items, total] = await Promise.all([
135
+ prisma.customRole.findMany({
136
+ where: { workspaceId: data.workspaceId },
137
+ include: { _count: { select: { assignments: true } } },
138
+ orderBy: { createdAt: 'desc' },
139
+ }),
140
+ prisma.customRole.count({ where: { workspaceId: data.workspaceId } }),
141
+ ])
142
+ return { items, total, seeded: systemCount > 0 ? 'done' : 'pending' }
143
+ })
144
+
145
+ export const createRoleFn = createServerFn({ method: 'POST' })
146
+ .validator(
147
+ z.object({
148
+ workspaceId: z.string(),
149
+ name: z.string(),
150
+ permissions: z.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null(), z.any()])).optional(),
151
+ }),
152
+ )
153
+ .handler(async ({ data }) => {
154
+ await requireAdmin()
155
+ await requireFeature(undefined, 'advancedPermissions')
156
+ return prisma.customRole.create({
157
+ data: {
158
+ workspaceId: data.workspaceId,
159
+ name: data.name,
160
+ permissions: (data.permissions ?? {}) as never,
161
+ isSystem: false,
162
+ },
163
+ })
164
+ })
165
+
166
+ export const deleteRoleFn = createServerFn({ method: 'POST' })
167
+ .validator(z.object({ roleId: z.string() }))
168
+ .handler(async ({ data }) => {
169
+ await requireAdmin()
170
+ await requireFeature(undefined, 'advancedPermissions')
171
+ await prisma.customRole.delete({ where: { id: data.roleId } })
172
+ return { success: true }
173
+ })
174
+
175
+ export const assignRoleFn = createServerFn({ method: 'POST' })
176
+ .validator(z.object({ userId: z.string(), roleId: z.string() }))
177
+ .handler(async ({ data }) => {
178
+ await requireAdmin()
179
+ await requireFeature(undefined, 'advancedPermissions')
180
+ await prisma.roleAssignment.create({ data: { userId: data.userId, roleId: data.roleId } })
181
+ return { ok: true }
182
+ })
183
+
184
+ export const unassignRoleFn = createServerFn({ method: 'POST' })
185
+ .validator(z.object({ assignmentId: z.string() }))
186
+ .handler(async ({ data }) => {
187
+ await requireAdmin()
188
+ await requireFeature(undefined, 'advancedPermissions')
189
+ await prisma.roleAssignment.delete({ where: { id: data.assignmentId } })
190
+ return { ok: true }
191
+ })
192
+
193
+ export const listRoleAssignmentsFn = createServerFn({ method: 'GET' })
194
+ .validator(z.object({ roleId: z.string() }))
195
+ .handler(async ({ data }) => {
196
+ await requireAdmin()
197
+ await requireFeature(undefined, 'advancedPermissions')
198
+ return prisma.roleAssignment.findMany({
199
+ where: { roleId: data.roleId },
200
+ include: { user: { select: { id: true, username: true, email: true } } },
201
+ orderBy: { createdAt: 'desc' },
202
+ })
203
+ })
204
+
205
+ export const listWorkspacesFn = createServerFn({ method: 'GET' })
206
+ .validator(z.object({}))
207
+ .handler(async () => {
208
+ await requireAdmin()
209
+ await requireFeature(undefined, 'advancedPermissions')
210
+ return prisma.workspace.findMany({
211
+ select: { id: true, name: true },
212
+ orderBy: { name: 'asc' },
213
+ })
214
+ })
@@ -0,0 +1,88 @@
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 { createServerFn } from '@tanstack/react-start'
19
+ import { prisma } from '@allbluecn/database'
20
+ import { requireAdmin } from '@/server/auth-helpers'
21
+
22
+ export type SentryConfig = {
23
+ dsn: string
24
+ org: string
25
+ project: string
26
+ authToken: string
27
+ }
28
+
29
+ export const getSentryConfig = createServerFn({ method: 'GET' })
30
+ .handler(async () => {
31
+ await requireAdmin()
32
+
33
+ const settings = await prisma.systemSetting.findMany({
34
+ where: { category: 'sentry' },
35
+ })
36
+
37
+ const map = new Map(settings.map((s) => [s.key, s.value]))
38
+
39
+ return {
40
+ dsn: map.get('dsn') || '',
41
+ org: map.get('org') || '',
42
+ project: map.get('project') || '',
43
+ authToken: map.get('auth_token') || '',
44
+ } satisfies SentryConfig
45
+ })
46
+
47
+ export const saveSentryConfig = createServerFn({ method: 'POST' })
48
+ .validator((data: SentryConfig) => data)
49
+ .handler(async ({ data }) => {
50
+ await requireAdmin()
51
+
52
+ const entries: { key: string; value: string; category: string; desc: string }[] = [
53
+ { key: 'dsn', value: data.dsn, category: 'sentry', desc: 'Sentry DSN(客户端/服务端共用)' },
54
+ { key: 'org', value: data.org, category: 'sentry', desc: 'Sentry 组织 slug' },
55
+ { key: 'project', value: data.project, category: 'sentry', desc: 'Sentry 项目 slug' },
56
+ { key: 'auth_token', value: data.authToken, category: 'sentry', desc: 'Sentry Auth Token(源码映射上传用)' },
57
+ ]
58
+
59
+ for (const entry of entries) {
60
+ await prisma.systemSetting.upsert({
61
+ where: { key: entry.key },
62
+ create: entry,
63
+ update: { value: entry.value },
64
+ })
65
+ }
66
+
67
+ return { ok: true }
68
+ })
69
+
70
+ export const reconfigureSentryServer = createServerFn({ method: 'POST' })
71
+ .validator((data: { dsn: string }) => data)
72
+ .handler(async ({ data }) => {
73
+ await requireAdmin()
74
+
75
+ if (!data.dsn) {
76
+ return { ok: false, error: 'DSN 为空,跳过服务端 Sentry 初始化' }
77
+ }
78
+
79
+ const Sentry = await import('@sentry/tanstackstart-react')
80
+ Sentry.init({
81
+ dsn: data.dsn,
82
+ environment: process.env.SENTRY_ENVIRONMENT || process.env.NODE_ENV,
83
+ tracesSampleRate: 1.0,
84
+ enableLogs: true,
85
+ })
86
+
87
+ return { ok: true }
88
+ })
@@ -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 { createServerFn } from '@tanstack/react-start'
19
+ import { z } from 'zod'
20
+ import { prisma } from '@allbluecn/database'
21
+ import { requireAdmin } from '@/server/auth-helpers'
22
+ import { requireFeature } from '@/server/entitlements'
23
+
24
+ export const listSsoProvidersFn = createServerFn({ method: 'GET' })
25
+ .validator(z.object({ workspaceId: z.string().optional() }))
26
+ .handler(async ({ data }) => {
27
+ await requireAdmin()
28
+ await requireFeature(undefined, 'sso')
29
+ const where: any = data.workspaceId ? { workspaceId: data.workspaceId } : {}
30
+ const [items, total] = await Promise.all([
31
+ prisma.ssoProvider.findMany({
32
+ where,
33
+ include: { _count: { select: { mappings: true } } },
34
+ orderBy: { createdAt: 'desc' },
35
+ }),
36
+ prisma.ssoProvider.count({ where }),
37
+ ])
38
+ return { items, total }
39
+ })
40
+
41
+ export const createSsoProviderFn = createServerFn({ method: 'POST' })
42
+ .validator(
43
+ z.object({
44
+ workspaceId: z.string(),
45
+ type: z.string(),
46
+ name: z.string(),
47
+ config: z.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null(), z.any()])).optional(),
48
+ domain: z.string().optional(),
49
+ }),
50
+ )
51
+ .handler(async ({ data }) => {
52
+ await requireAdmin()
53
+ await requireFeature(undefined, 'sso')
54
+ return prisma.ssoProvider.create({
55
+ data: {
56
+ workspaceId: data.workspaceId,
57
+ type: data.type,
58
+ name: data.name,
59
+ config: (data.config ?? {}) as never,
60
+ domain: data.domain,
61
+ },
62
+ })
63
+ })
64
+
65
+ export const deleteSsoProviderFn = createServerFn({ method: 'POST' })
66
+ .validator(z.object({ providerId: z.string() }))
67
+ .handler(async ({ data }) => {
68
+ await requireAdmin()
69
+ await requireFeature(undefined, 'sso')
70
+ await prisma.ssoProvider.delete({ where: { id: data.providerId } })
71
+ return { success: true }
72
+ })
73
+
74
+ export const toggleSsoProviderFn = createServerFn({ method: 'POST' })
75
+ .validator(z.object({ providerId: z.string(), enabled: z.boolean() }))
76
+ .handler(async ({ data }) => {
77
+ await requireAdmin()
78
+ await requireFeature(undefined, 'sso')
79
+ return prisma.ssoProvider.update({
80
+ where: { id: data.providerId },
81
+ data: { enabled: data.enabled },
82
+ })
83
+ })