@allbluecn/admin-app 0.3.2 → 0.3.4

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 (209) hide show
  1. package/allblue-ui/package.json +6 -0
  2. package/allblue-ui/src/components/confirm-dialog.tsx +94 -0
  3. package/allblue-ui/src/components/infinite-scroll-sentinel.tsx +37 -0
  4. package/allblue-ui/src/components/notion-avatar.tsx +126 -0
  5. package/allblue-ui/src/components/user-avatar.tsx +82 -0
  6. package/{src/components → allblue-ui/src}/data-table/data-table-pagination.tsx +14 -5
  7. package/{src/components → allblue-ui/src}/data-table/data-table.tsx +1 -1
  8. package/{src/components → allblue-ui/src}/data-table/table-detail-layout.tsx +5 -6
  9. package/allblue-ui/src/hooks/use-load-more.ts +57 -0
  10. package/allblue-ui/src/index.ts +31 -0
  11. package/allblue-ui/src/lib/datetime.ts +226 -0
  12. package/allblue-ui/src/lib/sanitize.ts +18 -0
  13. package/allblue-ui/src/lib/utils.ts +1 -0
  14. package/{src/components → allblue-ui/src}/ui/alert-dialog.tsx +1 -3
  15. package/{src/components → allblue-ui/src}/ui/checkbox.tsx +0 -1
  16. package/{src/components → allblue-ui/src}/ui/dialog.tsx +1 -1
  17. package/{src/components → allblue-ui/src}/ui/dropdown-menu.tsx +0 -2
  18. package/{src/components → allblue-ui/src}/ui/field.tsx +2 -2
  19. package/{src/components → allblue-ui/src}/ui/input-group.tsx +3 -4
  20. package/allblue-ui/src/ui/input.tsx +19 -0
  21. package/{src/components → allblue-ui/src}/ui/pagination.tsx +1 -1
  22. package/{src/components → allblue-ui/src}/ui/select.tsx +0 -2
  23. package/{src/components → allblue-ui/src}/ui/separator.tsx +1 -0
  24. package/{src/components → allblue-ui/src}/ui/sheet.tsx +1 -2
  25. package/{src/components → allblue-ui/src}/ui/sidebar.tsx +7 -7
  26. package/{src/components → allblue-ui/src}/ui/switch.tsx +1 -0
  27. package/{src/components → allblue-ui/src}/ui/table.tsx +0 -2
  28. package/{src/components → allblue-ui/src}/ui/toast.tsx +44 -18
  29. package/{src/components → allblue-ui/src}/ui/tooltip.tsx +1 -0
  30. package/package.json +21 -18
  31. package/src/components/admin/admin-user-menu.tsx +4 -4
  32. package/src/components/admin/developer-log.tsx +16 -32
  33. package/src/components/ai-memory/memory-facts-panel.tsx +30 -70
  34. package/src/components/ai-prompts/preview-panel.tsx +1 -1
  35. package/src/components/ai-prompts/prompt-editor.tsx +8 -9
  36. package/src/components/ai-prompts/prompt-table.tsx +6 -6
  37. package/src/components/ai-prompts/section-editor.tsx +3 -3
  38. package/src/components/ai-prompts/variable-panel.tsx +2 -2
  39. package/src/components/ai-prompts/version-history.tsx +21 -41
  40. package/src/components/ai-provider/model-list-editor.tsx +2 -3
  41. package/src/components/ai-provider/provider-form.tsx +7 -7
  42. package/src/components/ai-provider/provider-table.tsx +17 -40
  43. package/src/components/auth/admin-login-form.tsx +7 -7
  44. package/src/components/auth/icon-input.tsx +2 -2
  45. package/src/components/auth/password-input.tsx +2 -2
  46. package/src/components/component/component-form-dialog.tsx +10 -18
  47. package/src/components/component/component-import-dialog.tsx +6 -6
  48. package/src/components/component/component-preview.tsx +311 -0
  49. package/src/components/component/component-table.tsx +3 -3
  50. package/src/components/dependency/changelog-dialog.tsx +2 -2
  51. package/src/components/dependency/columns.tsx +3 -3
  52. package/src/components/dependency/dependency-table.tsx +7 -7
  53. package/src/components/dependency/edit-dialog.tsx +8 -8
  54. package/src/components/invitations/invitations-page.tsx +26 -49
  55. package/src/components/knowledge-public/bookmark-form-dialog.tsx +6 -6
  56. package/src/components/knowledge-public/compliance-form-dialog.tsx +6 -6
  57. package/src/components/knowledge-public/legal-form-dialog.tsx +7 -7
  58. package/src/components/layout/sidebar-02/app-sidebar.tsx +1 -2
  59. package/src/components/layout/sidebar-02/index.tsx +1 -1
  60. package/src/components/layout/sidebar-02/nav-main.tsx +2 -3
  61. package/src/components/layout/sidebar-02/nav-notifications.tsx +3 -3
  62. package/src/components/market/columns.tsx +4 -4
  63. package/src/components/market/market-app-form.tsx +6 -7
  64. package/src/components/market/market-app-list.tsx +14 -29
  65. package/src/components/monitoring/error-list.tsx +2 -2
  66. package/src/components/monitoring/metrics-stats.tsx +1 -1
  67. package/src/components/monitoring/request-log-table.tsx +2 -2
  68. package/src/components/oauth-providers/provider-page.tsx +20 -38
  69. package/src/components/permissions/assign-dialog.tsx +135 -0
  70. package/src/components/permissions/create-role-dialog.tsx +135 -0
  71. package/src/components/permissions/types.ts +49 -0
  72. package/src/components/user/columns.tsx +6 -6
  73. package/src/components/user/settings-activity-log.tsx +7 -7
  74. package/src/components/user/settings-profile/avatar-uploader.tsx +118 -0
  75. package/src/components/user/settings-profile/social-links.tsx +63 -0
  76. package/src/components/user/settings-profile.tsx +26 -125
  77. package/src/components/user/user-create-dialog.tsx +6 -6
  78. package/src/components/user/user-detail/error-boundary.tsx +19 -0
  79. package/src/components/user/user-detail/user-detail-dialogs.tsx +203 -0
  80. package/src/components/user/user-filters.tsx +4 -4
  81. package/src/lib/auth/page-gate.ts +28 -0
  82. package/src/lib/auth.config.ts +25 -9
  83. package/src/lib/i18n/detect.ts +1 -1
  84. package/src/lib/loader-serial.ts +14 -0
  85. package/src/providers/index.tsx +1 -1
  86. package/src/routeTree.gen.ts +67 -78
  87. package/src/routes/__root.tsx +4 -4
  88. package/src/routes/_nav/audit/route.lazy.tsx +1 -2
  89. package/src/routes/_nav/compliance/route.lazy.tsx +5 -3
  90. package/src/routes/_nav/components/route.lazy.tsx +14 -34
  91. package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +18 -38
  92. package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +18 -38
  93. package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +6 -6
  94. package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +3 -3
  95. package/src/routes/_nav/licenses/new/route.lazy.tsx +5 -5
  96. package/src/routes/_nav/licenses/route.lazy.tsx +1 -1
  97. package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +2 -2
  98. package/src/routes/_nav/monitoring/errors/route.lazy.tsx +10 -10
  99. package/src/routes/_nav/monitoring/errors/route.tsx +7 -0
  100. package/src/routes/_nav/monitoring/health/route.lazy.tsx +13 -15
  101. package/src/routes/_nav/monitoring/health/route.tsx +14 -0
  102. package/src/routes/_nav/monitoring/logs/route.lazy.tsx +24 -9
  103. package/src/routes/_nav/monitoring/logs/route.tsx +13 -0
  104. package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +12 -14
  105. package/src/routes/_nav/monitoring/metrics/route.tsx +7 -0
  106. package/src/routes/_nav/monitoring/overview/route.lazy.tsx +14 -13
  107. package/src/routes/_nav/monitoring/overview/route.tsx +17 -0
  108. package/src/routes/_nav/monitoring/route.lazy.tsx +6 -6
  109. package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +2 -2
  110. package/src/routes/_nav/permissions/route.lazy.tsx +21 -311
  111. package/src/routes/_nav/permissions/route.tsx +1 -1
  112. package/src/routes/_nav/route.tsx +5 -13
  113. package/src/routes/_nav/sso/route.lazy.tsx +2 -3
  114. package/src/routes/_nav/users/$userId/route.lazy.tsx +91 -156
  115. package/src/routes/_nav/users/-components/users-table.tsx +1 -1
  116. package/src/routes/_nav/users/route.lazy.tsx +1 -1
  117. package/src/routes/login/route.lazy.tsx +1 -1
  118. package/src/routes/setup/route.lazy.tsx +2 -2
  119. package/src/server/__tests__/admin-billing.test.ts +191 -16
  120. package/src/server/__tests__/compliance.test.ts +44 -7
  121. package/src/server/__tests__/permissions.test.ts +10 -5
  122. package/src/server/__tests__/prompt-defaults.test.ts +3 -3
  123. package/src/server/admin-ai-alerts.ts +2 -1
  124. package/src/server/admin-ai-usage.ts +2 -1
  125. package/src/server/ai-memory/store.ts +6 -2
  126. package/src/server/{serverFns/ai-prompts → ai-prompts}/prompt-defaults.ts +5 -3
  127. package/src/server/{serverFns/dependency → dependency}/helpers.ts +4 -3
  128. package/src/server/knowledge-public/github-sync.ts +2 -1
  129. package/src/server/license-issue.ts +2 -1
  130. package/src/server/middlewares/auth-rate-limit.ts +117 -0
  131. package/src/server/observability/health-check.ts +4 -1
  132. package/src/server/observability/metrics.ts +17 -15
  133. package/src/server/observability/request-middleware.ts +2 -1
  134. package/src/server/observability/timing.ts +14 -16
  135. package/src/server/serverFns/admin-billing.ts +107 -12
  136. package/src/server/serverFns/ai-memory/list-users.ts +2 -1
  137. package/src/server/serverFns/ai-prompts/activate.ts +2 -1
  138. package/src/server/serverFns/ai-prompts/get.ts +3 -2
  139. package/src/server/serverFns/ai-prompts/list.ts +3 -2
  140. package/src/server/serverFns/ai-prompts/rollback.ts +3 -1
  141. package/src/server/serverFns/ai-prompts/save.ts +5 -3
  142. package/src/server/serverFns/ai-provider/create.ts +4 -1
  143. package/src/server/serverFns/ai-provider/delete.ts +4 -1
  144. package/src/server/serverFns/ai-provider/get.ts +4 -1
  145. package/src/server/serverFns/ai-provider/list.ts +28 -1
  146. package/src/server/serverFns/ai-provider/seed-from-seed.ts +2 -1
  147. package/src/server/serverFns/ai-provider/update.ts +4 -1
  148. package/src/server/serverFns/anyhub/market.ts +8 -1
  149. package/src/server/serverFns/audit.ts +14 -1
  150. package/src/server/serverFns/auth/setup.ts +3 -1
  151. package/src/server/serverFns/compliance.ts +32 -3
  152. package/src/server/serverFns/components/import.ts +69 -14
  153. package/src/server/serverFns/components/list.ts +4 -1
  154. package/src/server/serverFns/components/mutations.ts +9 -1
  155. package/src/server/serverFns/dependency/list.ts +28 -5
  156. package/src/server/serverFns/dependency/sync.ts +7 -2
  157. package/src/server/serverFns/dependency/update.ts +4 -1
  158. package/src/server/serverFns/developer-log-auto-generate.ts +63 -32
  159. package/src/server/serverFns/developer-logs.ts +5 -1
  160. package/src/server/serverFns/invitations.ts +7 -1
  161. package/src/server/serverFns/knowledge-public/bookmarks/create.ts +2 -1
  162. package/src/server/serverFns/knowledge-public/bookmarks/delete.ts +2 -1
  163. package/src/server/serverFns/knowledge-public/bookmarks/detail.ts +2 -1
  164. package/src/server/serverFns/knowledge-public/bookmarks/list.ts +2 -1
  165. package/src/server/serverFns/knowledge-public/bookmarks/sync-all-github.ts +2 -1
  166. package/src/server/serverFns/knowledge-public/bookmarks/update.ts +2 -1
  167. package/src/server/serverFns/knowledge-public/compliance/create.ts +2 -1
  168. package/src/server/serverFns/knowledge-public/compliance/delete.ts +2 -1
  169. package/src/server/serverFns/knowledge-public/compliance/detail.ts +2 -1
  170. package/src/server/serverFns/knowledge-public/compliance/list.ts +2 -1
  171. package/src/server/serverFns/knowledge-public/compliance/update.ts +2 -1
  172. package/src/server/serverFns/knowledge-public/legal/create.ts +2 -1
  173. package/src/server/serverFns/knowledge-public/legal/delete.ts +2 -1
  174. package/src/server/serverFns/knowledge-public/legal/detail.ts +2 -1
  175. package/src/server/serverFns/knowledge-public/legal/list.ts +2 -1
  176. package/src/server/serverFns/knowledge-public/legal/update.ts +2 -1
  177. package/src/server/serverFns/licenses.ts +8 -2
  178. package/src/server/serverFns/monitoring/get-errors.ts +2 -1
  179. package/src/server/serverFns/monitoring/get-health.ts +2 -1
  180. package/src/server/serverFns/monitoring/get-metrics.ts +2 -1
  181. package/src/server/serverFns/monitoring/get-request-logs.ts +2 -1
  182. package/src/server/serverFns/monitoring/report-error.ts +2 -1
  183. package/src/server/serverFns/monitoring/reset.ts +2 -1
  184. package/src/server/serverFns/oauth-providers.ts +4 -1
  185. package/src/server/serverFns/permissions.ts +18 -11
  186. package/src/server/serverFns/sentry/config.ts +11 -8
  187. package/src/server/serverFns/sso-config.ts +17 -2
  188. package/src/server/serverFns/user/create.ts +6 -1
  189. package/src/server/serverFns/user/delete.ts +6 -1
  190. package/src/server/serverFns/user/detail.ts +5 -2
  191. package/src/server/serverFns/user/list.ts +19 -4
  192. package/src/server/serverFns/user/reset-password.ts +10 -2
  193. package/src/server/serverFns/user/update-role.ts +10 -2
  194. package/src/server/serverFns/user/update-status.ts +10 -2
  195. package/src/styles/globals.css +4 -0
  196. package/src/components/ui/input.tsx +0 -19
  197. /package/{src → allblue-ui/src}/hooks/use-mobile.ts +0 -0
  198. /package/{src → allblue-ui/src}/lib/avatar.ts +0 -0
  199. /package/{src/components → allblue-ui/src}/ui/alert.tsx +0 -0
  200. /package/{src/components → allblue-ui/src}/ui/avatar.tsx +0 -0
  201. /package/{src/components → allblue-ui/src}/ui/badge.tsx +0 -0
  202. /package/{src/components → allblue-ui/src}/ui/button.tsx +0 -0
  203. /package/{src/components → allblue-ui/src}/ui/card.tsx +0 -0
  204. /package/{src/components → allblue-ui/src}/ui/collapsible.tsx +0 -0
  205. /package/{src/components → allblue-ui/src}/ui/label.tsx +0 -0
  206. /package/{src/components → allblue-ui/src}/ui/skeleton.tsx +0 -0
  207. /package/{src/components → allblue-ui/src}/ui/textarea.tsx +0 -0
  208. /package/src/routes/{api.auth.$.ts → api/auth/$.ts} +0 -0
  209. /package/src/routes/{api.sentry-test.ts → api/sentry-test.ts} +0 -0
@@ -15,10 +15,10 @@
15
15
  // You should have received a copy of the GNU Affero General Public License
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
- import { createLazyFileRoute, Outlet, useLocation } from '@tanstack/react-router'
18
+ import { createLazyFileRoute, Link, Outlet, useLocation } from '@tanstack/react-router'
19
19
  import { m } from '@/paraglide/messages.js'
20
- import { SidebarTrigger } from '@/components/ui/sidebar'
21
- import { Separator } from '@/components/ui/separator'
20
+ import { SidebarTrigger } from '@allbluecn/ui/sidebar'
21
+ import { Separator } from '@allbluecn/ui/separator'
22
22
 
23
23
  export const Route = createLazyFileRoute('/_nav/monitoring')({
24
24
  component: MonitoringLayout,
@@ -46,16 +46,16 @@ function MonitoringLayout() {
46
46
  <Separator orientation="vertical" className="mr-2 h-4" />
47
47
  <nav className="flex gap-1">
48
48
  {subNavItems.map((item) => (
49
- <a
49
+ <Link
50
50
  key={item.id}
51
- href={item.path}
51
+ to={item.path}
52
52
  className={`px-3 py-1.5 rounded-md text-sm font-medium transition-colors ${currentId === item.id
53
53
  ? 'bg-accent text-accent-foreground'
54
54
  : 'text-muted-foreground hover:text-foreground hover:bg-accent/50'
55
55
  }`}
56
56
  >
57
57
  {item.label}
58
- </a>
58
+ </Link>
59
59
  ))}
60
60
  </nav>
61
61
  </header>
@@ -18,10 +18,10 @@
18
18
  import { createLazyFileRoute, useRouter } from '@tanstack/react-router'
19
19
  import { m } from '@/paraglide/messages.js'
20
20
  import * as Sentry from '@sentry/tanstackstart-react'
21
- import { BugPlay, ExternalLink, Bug, Activity, AlertTriangle, Eye, EyeOff, Save, RotateCcw, CheckCircle2, XCircle, RefreshCw, ShieldCheck } from 'lucide-react'
21
+ import { BugPlay, ExternalLink, Bug, Activity, Eye, EyeOff, Save, CheckCircle2, XCircle, RefreshCw, ShieldCheck } from 'lucide-react'
22
22
  import { IconBrandSentry } from '@tabler/icons-react';
23
23
  import { useEffect, useState } from 'react'
24
- import { toast } from '@/components/ui/toast'
24
+ import { toast } from '@allbluecn/ui/toast'
25
25
  import { getSentryConfig, saveSentryConfig, reconfigureSentryServer } from '@/server/serverFns/sentry/config'
26
26
  import type { SentryConfig } from '@/server/serverFns/sentry/config'
27
27
 
@@ -2,36 +2,15 @@ import { createLazyFileRoute, Link, useLocation } from '@tanstack/react-router'
2
2
  import { m } from '@/paraglide/messages.js'
3
3
  import { getLocale } from '@/paraglide/runtime.js'
4
4
  import { useServerFn } from '@tanstack/react-start'
5
- import { toast } from '@/components/ui/toast'
6
- import { useCallback, useEffect, useState } from 'react'
5
+ import { toast } from '@allbluecn/ui/toast'
6
+ import { useEffect, useState } from 'react'
7
7
  import type React from 'react'
8
8
  import {
9
9
  listRolesFn,
10
10
  deleteRoleFn,
11
- createRoleFn,
12
- assignRoleFn,
13
- unassignRoleFn,
14
- listRoleAssignmentsFn,
15
11
  } from '@/server/serverFns/permissions'
16
- import { Badge } from '@/components/ui/badge'
17
- import { Button } from '@/components/ui/button'
18
- import {
19
- Dialog,
20
- DialogContent,
21
- DialogDescription,
22
- DialogFooter,
23
- DialogHeader,
24
- DialogTitle,
25
- } from '@/components/ui/dialog'
26
- import { Input } from '@/components/ui/input'
27
- import { Label } from '@/components/ui/label'
28
- import {
29
- Select,
30
- SelectContent,
31
- SelectItem,
32
- SelectTrigger,
33
- SelectValue,
34
- } from '@/components/ui/select'
12
+ import { Badge } from '@allbluecn/ui/badge'
13
+ import { Button } from '@allbluecn/ui/button'
35
14
  import {
36
15
  Table,
37
16
  TableBody,
@@ -39,57 +18,10 @@ import {
39
18
  TableHead,
40
19
  TableHeader,
41
20
  TableRow,
42
- } from '@/components/ui/table'
43
-
44
- type ResourcePermission = Record<string, 'admin' | 'write' | 'read' | 'none'>
45
-
46
- type RoleItem = {
47
- id: string
48
- name: string
49
- permissions: ResourcePermission
50
- isSystem: boolean
51
- _count: { assignments: number }
52
- }
53
-
54
- type AssignmentItem = {
55
- id: string
56
- roleId: string
57
- userId: string
58
- user: {
59
- id: string
60
- username: string
61
- email: string
62
- }
63
- createdAt: string | Date
64
- }
65
-
66
- type SearchUser = {
67
- id: string
68
- username: string
69
- email: string
70
- }
71
-
72
- const ALL_RESOURCES: { key: string; labelZh: string; labelEn: string }[] = [
73
- { key: 'knowledge', labelZh: '知识库', labelEn: 'Knowledge' },
74
- { key: 'story', labelZh: '故事', labelEn: 'Story' },
75
- { key: 'user', labelZh: '用户', labelEn: 'User' },
76
- { key: 'setting', labelZh: '设置', labelEn: 'Setting' },
77
- { key: 'billing', labelZh: '计费', labelEn: 'Billing' },
78
- ]
79
-
80
- const ALL_ACTIONS: { key: string; labelZh: string; labelEn: string }[] = [
81
- { key: 'read', labelZh: '读', labelEn: 'Read' },
82
- { key: 'write', labelZh: '写', labelEn: 'Write' },
83
- { key: 'delete', labelZh: '删', labelEn: 'Delete' },
84
- { key: 'publish', labelZh: '发布', labelEn: 'Publish' },
85
- ]
86
-
87
- const LEVELS: { value: 'none' | 'read' | 'write' | 'admin'; labelZh: string; labelEn: string }[] = [
88
- { value: 'none', labelZh: '无', labelEn: 'None' },
89
- { value: 'read', labelZh: '读', labelEn: 'Read' },
90
- { value: 'write', labelZh: '写', labelEn: 'Write' },
91
- { value: 'admin', labelZh: '管理', labelEn: 'Admin' },
92
- ]
21
+ } from '@allbluecn/ui/table'
22
+ import { CreateRoleDialog } from '@/components/permissions/create-role-dialog'
23
+ import { AssignDialog } from '@/components/permissions/assign-dialog'
24
+ import type { RoleItem } from '@/components/permissions/types'
93
25
 
94
26
  export const Route = createLazyFileRoute('/_nav/permissions')({
95
27
  pendingComponent: () => (
@@ -100,222 +32,6 @@ export const Route = createLazyFileRoute('/_nav/permissions')({
100
32
  component: PermissionsPage,
101
33
  })
102
34
 
103
- function CreateRoleDialog({
104
- open,
105
- onOpenChange,
106
- workspaceId,
107
- i18nLang,
108
- onCreated,
109
- }: {
110
- open: boolean
111
- onOpenChange: (v: boolean) => void
112
- workspaceId: string
113
- i18nLang: string
114
- onCreated: () => void
115
- }): React.ReactElement {
116
- const createRole = useServerFn(createRoleFn)
117
- const [name, setName] = useState('')
118
- const [permissions, setPermissions] = useState<ResourcePermission>({})
119
- const isZh = i18nLang === 'zh'
120
-
121
- useEffect(() => {
122
- if (open) {
123
- setName('')
124
- setPermissions({})
125
- }
126
- }, [open])
127
-
128
- const handleLevelChange = useCallback((resource: string, action: string, value: string) => {
129
- const level = (value as 'none' | 'read' | 'write' | 'admin') || 'none'
130
- setPermissions((prev) => ({ ...prev, [`${resource}:${action}`]: level }))
131
- }, [])
132
-
133
- const handleSubmit = async () => {
134
- if (!name.trim()) {
135
- toast.warning(m.enterprise_permissions_toast_nameRequired())
136
- return
137
- }
138
- await createRole({
139
- data: {
140
- workspaceId,
141
- name: name.trim(),
142
- permissions,
143
- },
144
- })
145
- toast.success(m.enterprise_permissions_toast_created({ name: name.trim() }))
146
- onOpenChange(false)
147
- onCreated()
148
- }
149
-
150
- return (
151
- <Dialog open={open} onOpenChange={onOpenChange}>
152
- <DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
153
- <DialogHeader>
154
- <DialogTitle>{m.enterprise_permissions_createRole()}</DialogTitle>
155
- <DialogDescription>{m.enterprise_permissions_createRoleDesc()}</DialogDescription>
156
- </DialogHeader>
157
-
158
- <div className="space-y-4">
159
- <div className="grid gap-2">
160
- <Label htmlFor="role-name">{m.enterprise_permissions_roleName()}</Label>
161
- <Input
162
- id="role-name"
163
- value={name}
164
- onChange={(e: React.ChangeEvent<HTMLInputElement>) => setName(e.target.value)}
165
- placeholder={m.enterprise_permissions_roleNamePlaceholder()}
166
- autoFocus
167
- />
168
- </div>
169
-
170
- <div className="space-y-3">
171
- <Label>{m.enterprise_permissions_permissionsMatrix()}</Label>
172
- {ALL_RESOURCES.map((resource) => (
173
- <div key={resource.key} className="space-y-2">
174
- <span className="text-sm font-medium">{isZh ? resource.labelZh : resource.labelEn}</span>
175
- <div className="grid grid-cols-4 gap-2">
176
- {ALL_ACTIONS.map((action) => (
177
- <Select
178
- key={action.key}
179
- value={permissions[`${resource.key}:${action.key}`] || 'none'}
180
- onValueChange={(value) => handleLevelChange(resource.key, action.key, value ?? "")}
181
- >
182
- <SelectTrigger className="h-8">
183
- <SelectValue>
184
- {isZh ? resource.labelEn : resource.labelZh} / {isZh ? action.labelZh : action.labelEn}
185
- </SelectValue>
186
- </SelectTrigger>
187
- <SelectContent>
188
- {LEVELS.map((level) => (
189
- <SelectItem key={level.value} value={level.value}>
190
- {isZh ? level.labelZh : level.labelEn}
191
- </SelectItem>
192
- ))}
193
- </SelectContent>
194
- </Select>
195
- ))}
196
- </div>
197
- </div>
198
- ))}
199
- </div>
200
- </div>
201
-
202
- <DialogFooter>
203
- <Button variant="outline" onClick={() => onOpenChange(false)}>
204
- {m.enterprise_permissions_dialog_cancel()}
205
- </Button>
206
- <Button onClick={handleSubmit}>{m.enterprise_permissions_dialog_create()}</Button>
207
- </DialogFooter>
208
- </DialogContent>
209
- </Dialog>
210
- )
211
- }
212
-
213
- function AssignDialog({
214
- open,
215
- onOpenChange,
216
- role,
217
- i18nLang,
218
- onAssigned,
219
- }: {
220
- open: boolean
221
- onOpenChange: (v: boolean) => void
222
- role: RoleItem | null
223
- i18nLang: string
224
- onAssigned: () => void
225
- }): React.ReactElement {
226
- const assignRole = useServerFn(assignRoleFn)
227
- const unassignRole = useServerFn(unassignRoleFn)
228
- const listAssignments = useServerFn(listRoleAssignmentsFn)
229
- const [assignments, setAssignments] = useState<AssignmentItem[]>([])
230
- const [searchUser, setSearchUser] = useState('')
231
- const isZh = i18nLang === 'zh'
232
-
233
- useEffect(() => {
234
- if (open && role) {
235
- listAssignments({ data: { roleId: role.id } })
236
- .then((result: any) => setAssignments(result))
237
- .catch(() => setAssignments([]))
238
- }
239
- }, [open, role, listAssignments])
240
-
241
- const handleAssign = async () => {
242
- if (!searchUser.trim() || !role) return
243
- await assignRole({ data: { userId: searchUser.trim(), roleId: role.id } })
244
- toast.success(m.enterprise_permissions_toast_assigned())
245
- setSearchUser('')
246
- onAssigned()
247
- }
248
-
249
- const handleUnassign = async (assignmentId: string) => {
250
- await unassignRole({ data: { assignmentId } })
251
- toast.success(m.enterprise_permissions_toast_unassigned())
252
- setAssignments(assignments.filter((a) => a.id !== assignmentId))
253
- }
254
-
255
- return (
256
- <Dialog open={open} onOpenChange={onOpenChange}>
257
- <DialogContent className="max-w-lg">
258
- <DialogHeader>
259
- <DialogTitle>
260
- {m.enterprise_permissions_assign({ name: role?.name ?? '' })}
261
- </DialogTitle>
262
- <DialogDescription>{m.enterprise_permissions_assignDesc()}</DialogDescription>
263
- </DialogHeader>
264
-
265
- <div className="space-y-4">
266
- <div className="flex gap-2">
267
- <Input
268
- value={searchUser}
269
- onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchUser(e.target.value)}
270
- placeholder={m.enterprise_permissions_searchUserPlaceholder()}
271
- />
272
- <Button onClick={handleAssign} disabled={!searchUser.trim()}>
273
- {m.enterprise_permissions_assignUser()}
274
- </Button>
275
- </div>
276
-
277
- {assignments.length === 0 ? (
278
- <p className="text-sm text-muted-foreground">{m.enterprise_permissions_noAssignments()}</p>
279
- ) : (
280
- <Table>
281
- <TableHeader>
282
- <TableRow>
283
- <TableHead>{isZh ? '用户' : 'User'}</TableHead>
284
- <TableHead>{isZh ? '邮箱' : 'Email'}</TableHead>
285
- <TableHead>{isZh ? '操作' : 'Actions'}</TableHead>
286
- </TableRow>
287
- </TableHeader>
288
- <TableBody>
289
- {assignments.map((a) => (
290
- <TableRow key={a.id}>
291
- <TableCell className="font-medium">{a.user?.username || a.userId}</TableCell>
292
- <TableCell>{a.user?.email || '-'}</TableCell>
293
- <TableCell>
294
- <button
295
- type="button"
296
- className="text-destructive hover:underline"
297
- onClick={() => handleUnassign(a.id)}
298
- >
299
- {m.enterprise_permissions_unassign()}
300
- </button>
301
- </TableCell>
302
- </TableRow>
303
- ))}
304
- </TableBody>
305
- </Table>
306
- )}
307
- </div>
308
-
309
- <DialogFooter>
310
- <Button variant="outline" onClick={() => onOpenChange(false)}>
311
- {m.enterprise_permissions_dialog_close()}
312
- </Button>
313
- </DialogFooter>
314
- </DialogContent>
315
- </Dialog>
316
- )
317
- }
318
-
319
35
  function PermissionsPage(): React.ReactElement {
320
36
  const location = useLocation()
321
37
  const selectedWorkspaceId =
@@ -358,6 +74,16 @@ function PermissionsPage(): React.ReactElement {
358
74
  setAssignDialogOpen(true)
359
75
  }
360
76
 
77
+ const refetchRoles = () => {
78
+ if (selectedWorkspaceId) {
79
+ listRoles({ data: { workspaceId: selectedWorkspaceId } })
80
+ .then((result: any) => {
81
+ const data = result as { items: RoleItem[] }
82
+ setRoles(data.items)
83
+ })
84
+ }
85
+ }
86
+
361
87
  const PERMISSION_LEVELS: Record<string, () => string> = {
362
88
  admin: () => m.enterprise_permissions_level_admin(),
363
89
  write: () => m.enterprise_permissions_level_write(),
@@ -471,15 +197,7 @@ function PermissionsPage(): React.ReactElement {
471
197
  onOpenChange={setCreateDialogOpen}
472
198
  workspaceId={selectedWorkspaceId}
473
199
  i18nLang={getLocale()}
474
- onCreated={() => {
475
- if (selectedWorkspaceId) {
476
- listRoles({ data: { workspaceId: selectedWorkspaceId } })
477
- .then((result: any) => {
478
- const data = result as { items: RoleItem[] }
479
- setRoles(data.items)
480
- })
481
- }
482
- }}
200
+ onCreated={refetchRoles}
483
201
  />
484
202
 
485
203
  <AssignDialog
@@ -487,16 +205,8 @@ function PermissionsPage(): React.ReactElement {
487
205
  onOpenChange={setAssignDialogOpen}
488
206
  role={selectedRole}
489
207
  i18nLang={getLocale()}
490
- onAssigned={() => {
491
- if (selectedWorkspaceId) {
492
- listRoles({ data: { workspaceId: selectedWorkspaceId } })
493
- .then((result: any) => {
494
- const data = result as { items: RoleItem[] }
495
- setRoles(data.items)
496
- })
497
- }
498
- }}
208
+ onAssigned={refetchRoles}
499
209
  />
500
210
  </div>
501
211
  )
502
- }
212
+ }
@@ -16,7 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { createFileRoute } from '@tanstack/react-router'
19
- import { listRolesFn, listWorkspacesFn } from '@/server/serverFns/permissions'
19
+ import { listWorkspacesFn } from '@/server/serverFns/permissions'
20
20
 
21
21
  export const Route = createFileRoute('/_nav/permissions')({
22
22
  loader: async () => {
@@ -15,20 +15,12 @@
15
15
  // You should have received a copy of the GNU Affero General Public License
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
- import { createFileRoute, redirect } from '@tanstack/react-router'
19
- import { getSessionFn } from '@/server/serverFns/auth/session'
18
+ import { createFileRoute } from '@tanstack/react-router'
19
+ import { requireAdminPage } from '@/lib/auth/page-gate'
20
20
 
21
21
  export const Route = createFileRoute('/_nav')({
22
- beforeLoad: async ({ location }) => {
23
- const session = await getSessionFn()
24
- if (!session?.user?.id) {
25
- throw redirect({
26
- to: '/login',
27
- search: { redirect: location.pathname + location.searchStr },
28
- })
29
- }
30
- if (session.user.role !== 'ADMIN' && session.user.role !== 'SUPER_ADMIN') {
31
- throw redirect({ to: '/login' })
32
- }
22
+ beforeLoad: ({ context, location }) => {
23
+ // session 已由 __root beforeLoad 注入 context,无需重复调 getSessionFn(每次导航多一次 RPC)
24
+ requireAdminPage({ context, location })
33
25
  },
34
26
  })
@@ -18,10 +18,10 @@
18
18
  import { createLazyFileRoute } from '@tanstack/react-router'
19
19
  import { m } from '@/paraglide/messages.js'
20
20
  import { useServerFn } from '@tanstack/react-start'
21
- import { toast } from '@/components/ui/toast'
21
+ import { toast } from '@allbluecn/ui/toast'
22
22
  import type React from 'react'
23
23
  import { deleteSsoProviderFn, toggleSsoProviderFn } from '@/server/serverFns/sso-config'
24
- import { Badge } from '@/components/ui/badge'
24
+ import { Badge } from '@allbluecn/ui/badge'
25
25
 
26
26
  export const Route = createLazyFileRoute('/_nav/sso')({
27
27
  pendingComponent: () => (
@@ -39,7 +39,6 @@ function SsoConfigPage(): React.ReactElement {
39
39
  workspaceId: string
40
40
  type: string
41
41
  name: string
42
- config: Record<string, unknown>
43
42
  domain: string | null
44
43
  enabled: boolean
45
44
  createdAt: string