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