@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.
- package/allblue-ui/package.json +6 -0
- package/allblue-ui/src/components/confirm-dialog.tsx +94 -0
- package/allblue-ui/src/components/infinite-scroll-sentinel.tsx +37 -0
- package/allblue-ui/src/components/notion-avatar.tsx +126 -0
- package/allblue-ui/src/components/user-avatar.tsx +82 -0
- package/{src/components → allblue-ui/src}/data-table/data-table-pagination.tsx +14 -5
- package/{src/components → allblue-ui/src}/data-table/data-table.tsx +1 -1
- package/{src/components → allblue-ui/src}/data-table/table-detail-layout.tsx +5 -6
- package/allblue-ui/src/hooks/use-load-more.ts +57 -0
- package/allblue-ui/src/index.ts +31 -0
- package/allblue-ui/src/lib/datetime.ts +226 -0
- package/allblue-ui/src/lib/sanitize.ts +18 -0
- package/allblue-ui/src/lib/utils.ts +1 -0
- package/{src/components → allblue-ui/src}/ui/alert-dialog.tsx +1 -3
- package/{src/components → allblue-ui/src}/ui/checkbox.tsx +0 -1
- package/{src/components → allblue-ui/src}/ui/dialog.tsx +1 -1
- package/{src/components → allblue-ui/src}/ui/dropdown-menu.tsx +0 -2
- package/{src/components → allblue-ui/src}/ui/field.tsx +2 -2
- package/{src/components → allblue-ui/src}/ui/input-group.tsx +3 -4
- package/allblue-ui/src/ui/input.tsx +19 -0
- package/{src/components → allblue-ui/src}/ui/pagination.tsx +1 -1
- package/{src/components → allblue-ui/src}/ui/select.tsx +0 -2
- package/{src/components → allblue-ui/src}/ui/separator.tsx +1 -0
- package/{src/components → allblue-ui/src}/ui/sheet.tsx +1 -2
- package/{src/components → allblue-ui/src}/ui/sidebar.tsx +7 -7
- package/{src/components → allblue-ui/src}/ui/switch.tsx +1 -0
- package/{src/components → allblue-ui/src}/ui/table.tsx +0 -2
- package/{src/components → allblue-ui/src}/ui/toast.tsx +44 -18
- package/{src/components → allblue-ui/src}/ui/tooltip.tsx +1 -0
- package/package.json +21 -18
- package/src/components/admin/admin-user-menu.tsx +4 -4
- package/src/components/admin/developer-log.tsx +16 -32
- package/src/components/ai-memory/memory-facts-panel.tsx +30 -70
- package/src/components/ai-prompts/preview-panel.tsx +1 -1
- package/src/components/ai-prompts/prompt-editor.tsx +8 -9
- package/src/components/ai-prompts/prompt-table.tsx +6 -6
- package/src/components/ai-prompts/section-editor.tsx +3 -3
- package/src/components/ai-prompts/variable-panel.tsx +2 -2
- package/src/components/ai-prompts/version-history.tsx +21 -41
- package/src/components/ai-provider/model-list-editor.tsx +2 -3
- package/src/components/ai-provider/provider-form.tsx +7 -7
- package/src/components/ai-provider/provider-table.tsx +17 -40
- package/src/components/auth/admin-login-form.tsx +7 -7
- package/src/components/auth/icon-input.tsx +2 -2
- package/src/components/auth/password-input.tsx +2 -2
- package/src/components/component/component-form-dialog.tsx +10 -18
- package/src/components/component/component-import-dialog.tsx +6 -6
- package/src/components/component/component-preview.tsx +311 -0
- package/src/components/component/component-table.tsx +3 -3
- package/src/components/dependency/changelog-dialog.tsx +2 -2
- package/src/components/dependency/columns.tsx +3 -3
- package/src/components/dependency/dependency-table.tsx +7 -7
- package/src/components/dependency/edit-dialog.tsx +8 -8
- package/src/components/invitations/invitations-page.tsx +26 -49
- package/src/components/knowledge-public/bookmark-form-dialog.tsx +6 -6
- package/src/components/knowledge-public/compliance-form-dialog.tsx +6 -6
- package/src/components/knowledge-public/legal-form-dialog.tsx +7 -7
- package/src/components/layout/sidebar-02/app-sidebar.tsx +1 -2
- package/src/components/layout/sidebar-02/index.tsx +1 -1
- package/src/components/layout/sidebar-02/nav-main.tsx +2 -3
- package/src/components/layout/sidebar-02/nav-notifications.tsx +3 -3
- package/src/components/market/columns.tsx +4 -4
- package/src/components/market/market-app-form.tsx +6 -7
- package/src/components/market/market-app-list.tsx +14 -29
- package/src/components/monitoring/error-list.tsx +2 -2
- package/src/components/monitoring/metrics-stats.tsx +1 -1
- package/src/components/monitoring/request-log-table.tsx +2 -2
- package/src/components/oauth-providers/provider-page.tsx +20 -38
- package/src/components/permissions/assign-dialog.tsx +135 -0
- package/src/components/permissions/create-role-dialog.tsx +135 -0
- package/src/components/permissions/types.ts +49 -0
- package/src/components/user/columns.tsx +6 -6
- package/src/components/user/settings-activity-log.tsx +7 -7
- package/src/components/user/settings-profile/avatar-uploader.tsx +118 -0
- package/src/components/user/settings-profile/social-links.tsx +63 -0
- package/src/components/user/settings-profile.tsx +26 -125
- package/src/components/user/user-create-dialog.tsx +6 -6
- package/src/components/user/user-detail/error-boundary.tsx +19 -0
- package/src/components/user/user-detail/user-detail-dialogs.tsx +203 -0
- package/src/components/user/user-filters.tsx +4 -4
- package/src/lib/auth/page-gate.ts +28 -0
- package/src/lib/auth.config.ts +25 -9
- package/src/lib/i18n/detect.ts +1 -1
- package/src/lib/loader-serial.ts +14 -0
- package/src/providers/index.tsx +1 -1
- package/src/routeTree.gen.ts +67 -78
- package/src/routes/__root.tsx +4 -4
- package/src/routes/_nav/audit/route.lazy.tsx +1 -2
- package/src/routes/_nav/compliance/route.lazy.tsx +5 -3
- package/src/routes/_nav/components/route.lazy.tsx +14 -34
- package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +18 -38
- package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +18 -38
- package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +6 -6
- package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +3 -3
- package/src/routes/_nav/licenses/new/route.lazy.tsx +5 -5
- package/src/routes/_nav/licenses/route.lazy.tsx +1 -1
- package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +2 -2
- package/src/routes/_nav/monitoring/errors/route.lazy.tsx +10 -10
- package/src/routes/_nav/monitoring/errors/route.tsx +7 -0
- package/src/routes/_nav/monitoring/health/route.lazy.tsx +13 -15
- package/src/routes/_nav/monitoring/health/route.tsx +14 -0
- package/src/routes/_nav/monitoring/logs/route.lazy.tsx +24 -9
- package/src/routes/_nav/monitoring/logs/route.tsx +13 -0
- package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +12 -14
- package/src/routes/_nav/monitoring/metrics/route.tsx +7 -0
- package/src/routes/_nav/monitoring/overview/route.lazy.tsx +14 -13
- package/src/routes/_nav/monitoring/overview/route.tsx +17 -0
- package/src/routes/_nav/monitoring/route.lazy.tsx +6 -6
- package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +2 -2
- package/src/routes/_nav/permissions/route.lazy.tsx +21 -311
- package/src/routes/_nav/permissions/route.tsx +1 -1
- package/src/routes/_nav/route.tsx +5 -13
- package/src/routes/_nav/sso/route.lazy.tsx +2 -3
- package/src/routes/_nav/users/$userId/route.lazy.tsx +91 -156
- package/src/routes/_nav/users/-components/users-table.tsx +1 -1
- package/src/routes/_nav/users/route.lazy.tsx +1 -1
- package/src/routes/login/route.lazy.tsx +1 -1
- package/src/routes/setup/route.lazy.tsx +2 -2
- package/src/server/__tests__/admin-billing.test.ts +191 -16
- package/src/server/__tests__/compliance.test.ts +44 -7
- package/src/server/__tests__/permissions.test.ts +10 -5
- package/src/server/__tests__/prompt-defaults.test.ts +3 -3
- package/src/server/admin-ai-alerts.ts +2 -1
- package/src/server/admin-ai-usage.ts +2 -1
- package/src/server/ai-memory/store.ts +6 -2
- package/src/server/{serverFns/ai-prompts → ai-prompts}/prompt-defaults.ts +5 -3
- package/src/server/{serverFns/dependency → dependency}/helpers.ts +4 -3
- package/src/server/knowledge-public/github-sync.ts +2 -1
- package/src/server/license-issue.ts +2 -1
- package/src/server/middlewares/auth-rate-limit.ts +117 -0
- package/src/server/observability/health-check.ts +4 -1
- package/src/server/observability/metrics.ts +17 -15
- package/src/server/observability/request-middleware.ts +2 -1
- package/src/server/observability/timing.ts +14 -16
- package/src/server/serverFns/admin-billing.ts +107 -12
- package/src/server/serverFns/ai-memory/list-users.ts +2 -1
- package/src/server/serverFns/ai-prompts/activate.ts +2 -1
- package/src/server/serverFns/ai-prompts/get.ts +3 -2
- package/src/server/serverFns/ai-prompts/list.ts +3 -2
- package/src/server/serverFns/ai-prompts/rollback.ts +3 -1
- package/src/server/serverFns/ai-prompts/save.ts +5 -3
- package/src/server/serverFns/ai-provider/create.ts +4 -1
- package/src/server/serverFns/ai-provider/delete.ts +4 -1
- package/src/server/serverFns/ai-provider/get.ts +4 -1
- package/src/server/serverFns/ai-provider/list.ts +28 -1
- package/src/server/serverFns/ai-provider/seed-from-seed.ts +2 -1
- package/src/server/serverFns/ai-provider/update.ts +4 -1
- package/src/server/serverFns/anyhub/market.ts +8 -1
- package/src/server/serverFns/audit.ts +14 -1
- package/src/server/serverFns/auth/setup.ts +3 -1
- package/src/server/serverFns/compliance.ts +32 -3
- package/src/server/serverFns/components/import.ts +69 -14
- package/src/server/serverFns/components/list.ts +4 -1
- package/src/server/serverFns/components/mutations.ts +9 -1
- package/src/server/serverFns/dependency/list.ts +28 -5
- package/src/server/serverFns/dependency/sync.ts +7 -2
- package/src/server/serverFns/dependency/update.ts +4 -1
- package/src/server/serverFns/developer-log-auto-generate.ts +63 -32
- package/src/server/serverFns/developer-logs.ts +5 -1
- package/src/server/serverFns/invitations.ts +7 -1
- package/src/server/serverFns/knowledge-public/bookmarks/create.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/delete.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/detail.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/list.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/sync-all-github.ts +2 -1
- package/src/server/serverFns/knowledge-public/bookmarks/update.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/create.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/delete.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/detail.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/list.ts +2 -1
- package/src/server/serverFns/knowledge-public/compliance/update.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/create.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/delete.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/detail.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/list.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/update.ts +2 -1
- package/src/server/serverFns/licenses.ts +8 -2
- package/src/server/serverFns/monitoring/get-errors.ts +2 -1
- package/src/server/serverFns/monitoring/get-health.ts +2 -1
- package/src/server/serverFns/monitoring/get-metrics.ts +2 -1
- package/src/server/serverFns/monitoring/get-request-logs.ts +2 -1
- package/src/server/serverFns/monitoring/report-error.ts +2 -1
- package/src/server/serverFns/monitoring/reset.ts +2 -1
- package/src/server/serverFns/oauth-providers.ts +4 -1
- package/src/server/serverFns/permissions.ts +18 -11
- package/src/server/serverFns/sentry/config.ts +11 -8
- package/src/server/serverFns/sso-config.ts +17 -2
- package/src/server/serverFns/user/create.ts +6 -1
- package/src/server/serverFns/user/delete.ts +6 -1
- package/src/server/serverFns/user/detail.ts +5 -2
- package/src/server/serverFns/user/list.ts +19 -4
- package/src/server/serverFns/user/reset-password.ts +10 -2
- package/src/server/serverFns/user/update-role.ts +10 -2
- package/src/server/serverFns/user/update-status.ts +10 -2
- package/src/styles/globals.css +4 -0
- package/src/components/ui/input.tsx +0 -19
- /package/{src → allblue-ui/src}/hooks/use-mobile.ts +0 -0
- /package/{src → allblue-ui/src}/lib/avatar.ts +0 -0
- /package/{src/components → allblue-ui/src}/ui/alert.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/avatar.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/badge.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/button.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/card.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/collapsible.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/label.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/skeleton.tsx +0 -0
- /package/{src/components → allblue-ui/src}/ui/textarea.tsx +0 -0
- /package/src/routes/{api.auth.$.ts → api/auth/$.ts} +0 -0
- /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 '
|
|
21
|
-
import { Separator } from '
|
|
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
|
-
<
|
|
49
|
+
<Link
|
|
50
50
|
key={item.id}
|
|
51
|
-
|
|
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
|
-
</
|
|
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,
|
|
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 '
|
|
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 '
|
|
6
|
-
import {
|
|
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 '
|
|
17
|
-
import { Button } from '
|
|
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 '
|
|
43
|
-
|
|
44
|
-
|
|
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 {
|
|
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
|
|
19
|
-
import {
|
|
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:
|
|
23
|
-
|
|
24
|
-
|
|
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 '
|
|
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 '
|
|
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
|