@allbluecn/admin-app 0.2.0 → 0.2.1
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 +8 -8
- package/src/components/admin/admin-user-menu.tsx +12 -6
- package/src/components/admin/developer-log.tsx +42 -44
- package/src/components/ai-provider/model-list-editor.tsx +5 -4
- package/src/components/ai-provider/provider-form.tsx +63 -64
- package/src/components/ai-provider/provider-table.tsx +35 -36
- package/src/components/auth/admin-login-form.tsx +23 -24
- package/src/components/auth/oauth-section.tsx +3 -5
- package/src/components/auth/password-input.tsx +2 -3
- package/src/components/common/placeholder-page.tsx +2 -4
- package/src/components/component/component-form-dialog.tsx +48 -49
- package/src/components/component/component-import-dialog.tsx +5 -5
- package/src/components/component/component-table.tsx +13 -14
- package/src/components/data-table/data-table-pagination.tsx +4 -6
- package/src/components/data-table/table-detail-layout.tsx +2 -3
- package/src/components/dependency/changelog-dialog.tsx +3 -4
- package/src/components/dependency/columns.tsx +32 -24
- package/src/components/dependency/dependency-table.tsx +37 -29
- package/src/components/dependency/edit-dialog.tsx +24 -25
- package/src/components/knowledge-public/bookmark-form-dialog.tsx +7 -7
- package/src/components/knowledge-public/compliance-form-dialog.tsx +13 -13
- package/src/components/knowledge-public/legal-form-dialog.tsx +51 -46
- package/src/components/layout/sidebar-02/app-sidebar.tsx +33 -34
- package/src/components/layout/sidebar-02/nav-main.tsx +1 -1
- package/src/components/market/columns.tsx +20 -21
- package/src/components/market/market-app-form.tsx +45 -38
- package/src/components/market/market-app-list.tsx +16 -17
- package/src/components/monitoring/error-list.tsx +18 -19
- package/src/components/monitoring/health-cards.tsx +11 -13
- package/src/components/monitoring/metrics-stats.tsx +11 -12
- package/src/components/monitoring/request-log-table.tsx +8 -9
- package/src/components/ui/pagination.tsx +0 -2
- package/src/components/ui/sidebar.tsx +1 -1
- package/src/components/user/columns.tsx +9 -9
- package/src/components/user/settings-activity-log.tsx +39 -29
- package/src/components/user/settings-profile.tsx +36 -36
- package/src/components/user/user-create-dialog.tsx +4 -4
- package/src/components/user/user-filters.tsx +16 -18
- package/src/lib/i18n/config.ts +0 -32
- package/src/providers/index.tsx +6 -16
- package/src/routeTree.gen.ts +1199 -0
- package/src/routes/__root.tsx +9 -23
- package/src/routes/_nav/apps/route.lazy.tsx +2 -3
- package/src/routes/_nav/audit/route.lazy.tsx +10 -11
- package/src/routes/_nav/billing/invoices/route.lazy.tsx +6 -7
- package/src/routes/_nav/billing/route.lazy.tsx +3 -4
- package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.lazy.tsx +16 -17
- package/src/routes/_nav/billing/subscriptions/route.lazy.tsx +9 -10
- package/src/routes/_nav/compliance/route.lazy.tsx +34 -35
- package/src/routes/_nav/components/route.lazy.tsx +6 -6
- package/src/routes/_nav/dashboard/route.lazy.tsx +3 -5
- package/src/routes/_nav/dependencies/route.lazy.tsx +7 -8
- package/src/routes/_nav/docs/route.lazy.tsx +2 -3
- package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +40 -41
- package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +32 -33
- package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +32 -33
- package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +24 -31
- package/src/routes/_nav/licenses/new/route.lazy.tsx +25 -26
- package/src/routes/_nav/licenses/route.lazy.tsx +17 -18
- package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +26 -27
- package/src/routes/_nav/monitoring/errors/route.lazy.tsx +11 -12
- package/src/routes/_nav/monitoring/health/route.lazy.tsx +18 -20
- package/src/routes/_nav/monitoring/logs/route.lazy.tsx +12 -13
- package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +13 -15
- package/src/routes/_nav/monitoring/overview/route.lazy.tsx +12 -13
- package/src/routes/_nav/monitoring/route.lazy.tsx +8 -9
- package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +55 -57
- package/src/routes/_nav/permissions/route.lazy.tsx +48 -50
- package/src/routes/_nav/settings/route.lazy.tsx +2 -3
- package/src/routes/_nav/sso/route.lazy.tsx +20 -18
- package/src/routes/_nav/users/$userId/route.lazy.tsx +86 -82
- package/src/routes/_nav/users/route.lazy.tsx +4 -5
- package/src/routes/setup/route.lazy.tsx +15 -16
- package/src/start.ts +2 -2
- package/src/lib/i18n/client.ts +0 -32
- package/src/lib/i18n/locales/en/admin-ai.json +0 -128
- package/src/lib/i18n/locales/en/admin-billing.json +0 -56
- package/src/lib/i18n/locales/en/admin-users.json +0 -88
- package/src/lib/i18n/locales/en/common.json +0 -877
- package/src/lib/i18n/locales/en/enterprise.json +0 -208
- package/src/lib/i18n/locales/zh/admin-ai.json +0 -128
- package/src/lib/i18n/locales/zh/admin-billing.json +0 -56
- package/src/lib/i18n/locales/zh/admin-users.json +0 -88
- package/src/lib/i18n/locales/zh/common.json +0 -877
- package/src/lib/i18n/locales/zh/enterprise.json +0 -208
- package/src/lib/i18n/server.ts +0 -18
- package/src/lib/i18n/types.ts +0 -5
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import type React from 'react'
|
|
19
|
-
import {
|
|
19
|
+
import { m } from '@/paraglide/messages.js'
|
|
20
20
|
import { DataTable, type AdminColumnDef } from '@/components/data-table/data-table'
|
|
21
21
|
import { describePath } from '@/lib/monitoring-descriptions'
|
|
22
22
|
import { getBenchmark, getBenchmarkSource, compareDuration } from '@/lib/monitoring-benchmarks'
|
|
@@ -80,11 +80,10 @@ function DurationCell({ path, duration }: { path: string; duration: number }) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
export function RequestLogTable({ logs, actions }: RequestLogTableProps) {
|
|
83
|
-
const { t } = useTranslation('common')
|
|
84
83
|
|
|
85
84
|
const columns: AdminColumnDef<RequestLogEntry>[] = [
|
|
86
85
|
{
|
|
87
|
-
header:
|
|
86
|
+
header: m.common_requestLogTable_columns_time(),
|
|
88
87
|
accessorKey: 'timestamp',
|
|
89
88
|
meta: { style: { width: 20, textAlign: 'center' } },
|
|
90
89
|
cell: ({ row }: { row: { original: RequestLogEntry } }) => (
|
|
@@ -94,7 +93,7 @@ export function RequestLogTable({ logs, actions }: RequestLogTableProps) {
|
|
|
94
93
|
),
|
|
95
94
|
},
|
|
96
95
|
{
|
|
97
|
-
header:
|
|
96
|
+
header: m.common_requestLogTable_columns_path(),
|
|
98
97
|
accessorKey: 'path',
|
|
99
98
|
meta: { style: { width: 160 } },
|
|
100
99
|
cell: ({ row }: { row: { original: RequestLogEntry } }) => (
|
|
@@ -104,7 +103,7 @@ export function RequestLogTable({ logs, actions }: RequestLogTableProps) {
|
|
|
104
103
|
),
|
|
105
104
|
},
|
|
106
105
|
{
|
|
107
|
-
header:
|
|
106
|
+
header: m.common_requestLogTable_columns_description(),
|
|
108
107
|
accessorKey: 'path',
|
|
109
108
|
meta: { style: { width: 28 } },
|
|
110
109
|
cell: ({ row }: { row: { original: RequestLogEntry } }) => (
|
|
@@ -124,7 +123,7 @@ export function RequestLogTable({ logs, actions }: RequestLogTableProps) {
|
|
|
124
123
|
),
|
|
125
124
|
},
|
|
126
125
|
{
|
|
127
|
-
header:
|
|
126
|
+
header: m.common_requestLogTable_columns_method(),
|
|
128
127
|
accessorKey: 'method',
|
|
129
128
|
meta: { style: { width: 14, textAlign: 'right' } },
|
|
130
129
|
cell: ({ row }: { row: { original: RequestLogEntry } }) => {
|
|
@@ -137,7 +136,7 @@ export function RequestLogTable({ logs, actions }: RequestLogTableProps) {
|
|
|
137
136
|
},
|
|
138
137
|
},
|
|
139
138
|
{
|
|
140
|
-
header:
|
|
139
|
+
header: m.common_requestLogTable_columns_status(),
|
|
141
140
|
accessorKey: 'status',
|
|
142
141
|
meta: { style: { width: 14, textAlign: 'right' } },
|
|
143
142
|
cell: ({ row }: { row: { original: RequestLogEntry } }) => {
|
|
@@ -150,10 +149,10 @@ export function RequestLogTable({ logs, actions }: RequestLogTableProps) {
|
|
|
150
149
|
{
|
|
151
150
|
header: () => (
|
|
152
151
|
<span className="flex items-center gap-1">
|
|
153
|
-
{
|
|
152
|
+
{m.common_requestLogTable_columns_duration()}
|
|
154
153
|
<Info
|
|
155
154
|
className="size-3 text-muted-foreground"
|
|
156
|
-
data-tooltip={
|
|
155
|
+
data-tooltip={m.common_requestLogTable_compareBenchmark()}
|
|
157
156
|
/>
|
|
158
157
|
</span>
|
|
159
158
|
),
|
|
@@ -86,7 +86,6 @@ function PaginationPrevious({
|
|
|
86
86
|
return (
|
|
87
87
|
<PaginationLink
|
|
88
88
|
aria-label="Go to previous page"
|
|
89
|
-
size="default"
|
|
90
89
|
className={cn("pl-1.5!", className)}
|
|
91
90
|
{...props}
|
|
92
91
|
>
|
|
@@ -104,7 +103,6 @@ function PaginationNext({
|
|
|
104
103
|
return (
|
|
105
104
|
<PaginationLink
|
|
106
105
|
aria-label="Go to next page"
|
|
107
|
-
size="default"
|
|
108
106
|
className={cn("pr-1.5!", className)}
|
|
109
107
|
{...props}
|
|
110
108
|
>
|
|
@@ -73,7 +73,7 @@ export function getUserColumns(onView: (user: UserRow) => void): AdminColumnDef<
|
|
|
73
73
|
{
|
|
74
74
|
accessorKey: "username",
|
|
75
75
|
header: "用户",
|
|
76
|
-
cell: ({ row }) => {
|
|
76
|
+
cell: ({ row }: any) => {
|
|
77
77
|
const user = row.original;
|
|
78
78
|
return (
|
|
79
79
|
<div className="flex items-center gap-2">
|
|
@@ -96,7 +96,7 @@ export function getUserColumns(onView: (user: UserRow) => void): AdminColumnDef<
|
|
|
96
96
|
{
|
|
97
97
|
accessorKey: "role",
|
|
98
98
|
header: "角色",
|
|
99
|
-
cell: ({ row }) => {
|
|
99
|
+
cell: ({ row }: any) => {
|
|
100
100
|
const role = row.original.role;
|
|
101
101
|
return <Badge variant={roleBadgeVariant[role] || "outline"}>{roleLabel[role] || role}</Badge>;
|
|
102
102
|
},
|
|
@@ -104,7 +104,7 @@ export function getUserColumns(onView: (user: UserRow) => void): AdminColumnDef<
|
|
|
104
104
|
{
|
|
105
105
|
accessorKey: "status",
|
|
106
106
|
header: "状态",
|
|
107
|
-
cell: ({ row }) => {
|
|
107
|
+
cell: ({ row }: any) => {
|
|
108
108
|
const status = row.original.status;
|
|
109
109
|
return <Badge variant={statusBadgeVariant[status] || "outline"}>{statusLabel[status] || status}</Badge>;
|
|
110
110
|
},
|
|
@@ -112,22 +112,22 @@ export function getUserColumns(onView: (user: UserRow) => void): AdminColumnDef<
|
|
|
112
112
|
{
|
|
113
113
|
id: "prdCount",
|
|
114
114
|
header: "PRD",
|
|
115
|
-
cell: ({ row }) => <span className="text-muted-foreground">{row.original._count.prds}</span>,
|
|
115
|
+
cell: ({ row }: any) => <span className="text-muted-foreground">{row.original._count.prds}</span>,
|
|
116
116
|
},
|
|
117
117
|
{
|
|
118
118
|
id: "pluginCount",
|
|
119
119
|
header: "市场应用",
|
|
120
|
-
cell: ({ row }) => <span className="text-muted-foreground">{row.original._count.anyhubMarketApps}</span>,
|
|
120
|
+
cell: ({ row }: any) => <span className="text-muted-foreground">{row.original._count.anyhubMarketApps}</span>,
|
|
121
121
|
},
|
|
122
122
|
{
|
|
123
123
|
accessorKey: "createdAt",
|
|
124
124
|
header: "注册时间",
|
|
125
|
-
cell: ({ row }) => new Date(row.original.createdAt).toLocaleDateString("zh-CN"),
|
|
125
|
+
cell: ({ row }: any) => new Date(row.original.createdAt).toLocaleDateString("zh-CN"),
|
|
126
126
|
},
|
|
127
127
|
{
|
|
128
128
|
accessorKey: "lastLoginAt",
|
|
129
129
|
header: "最后登录",
|
|
130
|
-
cell: ({ row }) => {
|
|
130
|
+
cell: ({ row }: any) => {
|
|
131
131
|
const val = row.original.lastLoginAt;
|
|
132
132
|
return <span className="text-muted-foreground">{val ? new Date(val).toLocaleDateString("zh-CN") : "从未"}</span>;
|
|
133
133
|
},
|
|
@@ -135,13 +135,13 @@ export function getUserColumns(onView: (user: UserRow) => void): AdminColumnDef<
|
|
|
135
135
|
{
|
|
136
136
|
id: "actions",
|
|
137
137
|
header: "",
|
|
138
|
-
cell: ({ row }) => {
|
|
138
|
+
cell: ({ row }: any) => {
|
|
139
139
|
const user = row.original;
|
|
140
140
|
return (
|
|
141
141
|
<Button
|
|
142
142
|
variant="ghost"
|
|
143
143
|
size="sm"
|
|
144
|
-
onClick={(e) => { e.stopPropagation(); onView(user); }}
|
|
144
|
+
onClick={(e: React.MouseEvent) => { e.stopPropagation(); onView(user); }}
|
|
145
145
|
>
|
|
146
146
|
<EyeIcon className="mr-1.5 h-4 w-4" />
|
|
147
147
|
详情
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
Search,
|
|
25
25
|
} from "lucide-react";
|
|
26
26
|
import { useState } from "react";
|
|
27
|
-
import {
|
|
27
|
+
import { m } from '@/paraglide/messages.js'
|
|
28
28
|
import { cn } from "@/lib/utils";
|
|
29
29
|
import { Badge } from "@/components/ui/badge";
|
|
30
30
|
import { Button } from "@/components/ui/button";
|
|
@@ -95,17 +95,28 @@ const actionTypes = [
|
|
|
95
95
|
{ value: "delete", key: "delete" },
|
|
96
96
|
] as const
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
const ACTION_TYPE_LABELS: Record<string, () => string> = {
|
|
99
|
+
all: m.common_activityLog_actionTypes_all,
|
|
100
|
+
login: m.common_activityLog_actionTypes_login,
|
|
101
|
+
logout: m.common_activityLog_actionTypes_logout,
|
|
102
|
+
password_change: m.common_activityLog_actionTypes_password_change,
|
|
103
|
+
profile_update: m.common_activityLog_actionTypes_profile_update,
|
|
104
|
+
settings_change: m.common_activityLog_actionTypes_settings_change,
|
|
105
|
+
export: m.common_activityLog_actionTypes_export,
|
|
106
|
+
delete: m.common_activityLog_actionTypes_delete,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function formatRelativeTime(date: Date): string {
|
|
99
110
|
const now = Date.now();
|
|
100
111
|
const diff = now - date.getTime();
|
|
101
112
|
const minutes = Math.floor(diff / 60_000);
|
|
102
113
|
const hours = Math.floor(minutes / 60);
|
|
103
114
|
const days = Math.floor(hours / 24);
|
|
104
115
|
|
|
105
|
-
if (minutes < 1) return
|
|
106
|
-
if (minutes < 60) return
|
|
107
|
-
if (hours < 24) return
|
|
108
|
-
if (days < 7) return
|
|
116
|
+
if (minutes < 1) return m.common_activityLog_justNow();
|
|
117
|
+
if (minutes < 60) return m.common_common_timeAgo_minutes({ count: minutes });
|
|
118
|
+
if (hours < 24) return m.common_common_timeAgo_hours({ count: hours });
|
|
119
|
+
if (days < 7) return m.common_common_timeAgo_days({ count: days });
|
|
109
120
|
return new Intl.DateTimeFormat("zh-CN", {
|
|
110
121
|
month: "long",
|
|
111
122
|
day: "numeric",
|
|
@@ -122,9 +133,8 @@ export default function SettingsActivityLog({
|
|
|
122
133
|
title,
|
|
123
134
|
description,
|
|
124
135
|
}: SettingsActivityLogProps) {
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
const displayDescription = description ?? t('activityLog.description')
|
|
136
|
+
const displayTitle = title ?? m.common_activityLog_title()
|
|
137
|
+
const displayDescription = description ?? m.common_activityLog_description()
|
|
128
138
|
const [searchQuery, setSearchQuery] = useState("");
|
|
129
139
|
const [selectedType, setSelectedType] = useState("all");
|
|
130
140
|
const [dateRange, setDateRange] = useState({
|
|
@@ -186,12 +196,12 @@ export default function SettingsActivityLog({
|
|
|
186
196
|
<div className="flex size-8 items-center justify-center rounded-lg bg-muted">
|
|
187
197
|
<Search className="size-4" />
|
|
188
198
|
</div>
|
|
189
|
-
<FieldLabel className="mb-0">{
|
|
199
|
+
<FieldLabel className="mb-0">{m.common_activityLog_search()}</FieldLabel>
|
|
190
200
|
</div>
|
|
191
201
|
<InputGroup>
|
|
192
202
|
<InputGroupInput
|
|
193
|
-
onChange={(e) => setSearchQuery(e.target.value)}
|
|
194
|
-
placeholder={
|
|
203
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSearchQuery(e.target.value)}
|
|
204
|
+
placeholder={m.common_activityLog_searchPlaceholder()}
|
|
195
205
|
value={searchQuery}
|
|
196
206
|
/>
|
|
197
207
|
</InputGroup>
|
|
@@ -203,7 +213,7 @@ export default function SettingsActivityLog({
|
|
|
203
213
|
<Filter className="size-4" />
|
|
204
214
|
</div>
|
|
205
215
|
<FieldLabel className="mb-0" htmlFor="action-type">
|
|
206
|
-
{
|
|
216
|
+
{m.common_activityLog_actionType()}
|
|
207
217
|
</FieldLabel>
|
|
208
218
|
</div>
|
|
209
219
|
<Select onValueChange={setSelectedType} value={selectedType}>
|
|
@@ -213,7 +223,7 @@ export default function SettingsActivityLog({
|
|
|
213
223
|
<SelectContent>
|
|
214
224
|
{actionTypes.map((type) => (
|
|
215
225
|
<SelectItem key={type.value} value={type.value}>
|
|
216
|
-
{
|
|
226
|
+
{(ACTION_TYPE_LABELS[type.key] ?? (() => type.key))()}
|
|
217
227
|
</SelectItem>
|
|
218
228
|
))}
|
|
219
229
|
</SelectContent>
|
|
@@ -226,13 +236,13 @@ export default function SettingsActivityLog({
|
|
|
226
236
|
<Calendar className="size-4" />
|
|
227
237
|
</div>
|
|
228
238
|
<FieldLabel className="mb-0" htmlFor="date-start">
|
|
229
|
-
{
|
|
239
|
+
{m.common_activityLog_startDate()}
|
|
230
240
|
</FieldLabel>
|
|
231
241
|
</div>
|
|
232
242
|
<InputGroup>
|
|
233
243
|
<InputGroupInput
|
|
234
244
|
id="date-start"
|
|
235
|
-
onChange={(e) =>
|
|
245
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
236
246
|
setDateRange((prev) => ({ ...prev, start: e.target.value }))
|
|
237
247
|
}
|
|
238
248
|
type="date"
|
|
@@ -247,13 +257,13 @@ export default function SettingsActivityLog({
|
|
|
247
257
|
<Calendar className="size-4" />
|
|
248
258
|
</div>
|
|
249
259
|
<FieldLabel className="mb-0" htmlFor="date-end">
|
|
250
|
-
{
|
|
260
|
+
{m.common_activityLog_endDate()}
|
|
251
261
|
</FieldLabel>
|
|
252
262
|
</div>
|
|
253
263
|
<InputGroup>
|
|
254
264
|
<InputGroupInput
|
|
255
265
|
id="date-end"
|
|
256
|
-
onChange={(e) =>
|
|
266
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
257
267
|
setDateRange((prev) => ({ ...prev, end: e.target.value }))
|
|
258
268
|
}
|
|
259
269
|
type="date"
|
|
@@ -265,7 +275,7 @@ export default function SettingsActivityLog({
|
|
|
265
275
|
|
|
266
276
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
267
277
|
<p className="text-muted-foreground text-sm">
|
|
268
|
-
{
|
|
278
|
+
{m.common_activityLog_total({ count: filteredEntries.length })}
|
|
269
279
|
</p>
|
|
270
280
|
<Button
|
|
271
281
|
className="w-full sm:w-auto"
|
|
@@ -277,12 +287,12 @@ export default function SettingsActivityLog({
|
|
|
277
287
|
{isExporting ? (
|
|
278
288
|
<>
|
|
279
289
|
<Loader2 className="size-4 animate-spin" />
|
|
280
|
-
{
|
|
290
|
+
{m.common_activityLog_exporting()}
|
|
281
291
|
</>
|
|
282
292
|
) : (
|
|
283
293
|
<>
|
|
284
294
|
<Download className="size-4" />
|
|
285
|
-
{
|
|
295
|
+
{m.common_activityLog_exportLog()}
|
|
286
296
|
</>
|
|
287
297
|
)}
|
|
288
298
|
</Button>
|
|
@@ -296,11 +306,11 @@ export default function SettingsActivityLog({
|
|
|
296
306
|
<Search className="size-6 text-muted-foreground" />
|
|
297
307
|
</div>
|
|
298
308
|
<div className="flex flex-col gap-2">
|
|
299
|
-
<p className="font-medium text-sm">{
|
|
309
|
+
<p className="font-medium text-sm">{m.common_activityLog_empty()}</p>
|
|
300
310
|
<p className="text-muted-foreground text-sm">
|
|
301
311
|
{searchQuery || selectedType !== "all"
|
|
302
|
-
?
|
|
303
|
-
:
|
|
312
|
+
? m.common_activityLog_emptyHint_filtered()
|
|
313
|
+
: m.common_activityLog_emptyHint_default()}
|
|
304
314
|
</p>
|
|
305
315
|
</div>
|
|
306
316
|
</div>
|
|
@@ -325,20 +335,20 @@ export default function SettingsActivityLog({
|
|
|
325
335
|
entry.status === "success" ? "default" : "destructive"
|
|
326
336
|
}
|
|
327
337
|
>
|
|
328
|
-
{entry.status === "success" ?
|
|
338
|
+
{entry.status === "success" ? m.common_activityLog_status_success() : m.common_activityLog_status_failed()}
|
|
329
339
|
</Badge>
|
|
330
340
|
</div>
|
|
331
341
|
<div className="flex flex-wrap items-center gap-2 text-muted-foreground text-xs">
|
|
332
342
|
<span className="flex items-center gap-1">
|
|
333
343
|
<MapPin className="size-3" />
|
|
334
|
-
{entry.location ||
|
|
344
|
+
{entry.location || m.common_activityLog_unknown()}
|
|
335
345
|
</span>
|
|
336
346
|
<span>•</span>
|
|
337
|
-
<span>{entry.ipAddress ||
|
|
347
|
+
<span>{entry.ipAddress || m.common_activityLog_unknown()}</span>
|
|
338
348
|
<span>•</span>
|
|
339
|
-
<span>{entry.device ||
|
|
349
|
+
<span>{entry.device || m.common_activityLog_unknown()}</span>
|
|
340
350
|
<span>•</span>
|
|
341
|
-
<span>{formatRelativeTime(
|
|
351
|
+
<span>{formatRelativeTime(entry.timestamp)}</span>
|
|
342
352
|
{entry.status === "failed" && entry.reason && (
|
|
343
353
|
<>
|
|
344
354
|
<span>•</span>
|
|
@@ -426,7 +426,7 @@ export default function SettingsProfile({
|
|
|
426
426
|
<InputGroup>
|
|
427
427
|
<InputGroupInput
|
|
428
428
|
id="name"
|
|
429
|
-
onChange={(e) =>
|
|
429
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
430
430
|
setFormData((prev) => ({ ...prev, name: e.target.value }))
|
|
431
431
|
}
|
|
432
432
|
placeholder="请输入姓名"
|
|
@@ -446,7 +446,7 @@ export default function SettingsProfile({
|
|
|
446
446
|
<InputGroupInput
|
|
447
447
|
disabled={showEmailChangeForm}
|
|
448
448
|
id="email"
|
|
449
|
-
onChange={(e) =>
|
|
449
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
450
450
|
setFormData((prev) => ({
|
|
451
451
|
...prev,
|
|
452
452
|
email: e.target.value,
|
|
@@ -483,13 +483,13 @@ export default function SettingsProfile({
|
|
|
483
483
|
</FieldLabel>
|
|
484
484
|
<FieldContent>
|
|
485
485
|
<InputGroup>
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
486
|
+
<InputGroupInput
|
|
487
|
+
id="new-email"
|
|
488
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
489
|
+
setEmailChangeData((prev) => ({
|
|
490
|
+
...prev,
|
|
491
|
+
newEmail: e.target.value,
|
|
492
|
+
}))
|
|
493
493
|
}
|
|
494
494
|
placeholder="new.email@example.com"
|
|
495
495
|
type="email"
|
|
@@ -508,13 +508,13 @@ export default function SettingsProfile({
|
|
|
508
508
|
</FieldLabel>
|
|
509
509
|
<FieldContent>
|
|
510
510
|
<InputGroup>
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
511
|
+
<InputGroupInput
|
|
512
|
+
id="current-password"
|
|
513
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
514
|
+
setEmailChangeData((prev) => ({
|
|
515
|
+
...prev,
|
|
516
|
+
currentPassword: e.target.value,
|
|
517
|
+
}))
|
|
518
518
|
}
|
|
519
519
|
placeholder="请输入当前密码"
|
|
520
520
|
type="password"
|
|
@@ -554,7 +554,7 @@ export default function SettingsProfile({
|
|
|
554
554
|
<FieldContent>
|
|
555
555
|
<Textarea
|
|
556
556
|
id="bio"
|
|
557
|
-
onChange={(e) =>
|
|
557
|
+
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
|
558
558
|
setFormData((prev) => ({ ...prev, bio: e.target.value }))
|
|
559
559
|
}
|
|
560
560
|
placeholder="介绍一下自己…"
|
|
@@ -576,13 +576,13 @@ export default function SettingsProfile({
|
|
|
576
576
|
<FieldLabel htmlFor="location">所在地</FieldLabel>
|
|
577
577
|
<FieldContent>
|
|
578
578
|
<InputGroup>
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
579
|
+
<InputGroupInput
|
|
580
|
+
id="location"
|
|
581
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
582
|
+
setFormData((prev) => ({
|
|
583
|
+
...prev,
|
|
584
|
+
location: e.target.value,
|
|
585
|
+
}))
|
|
586
586
|
}
|
|
587
587
|
placeholder="城市, 国家"
|
|
588
588
|
readOnly={readOnly}
|
|
@@ -598,13 +598,13 @@ export default function SettingsProfile({
|
|
|
598
598
|
<FieldLabel htmlFor="website">网站</FieldLabel>
|
|
599
599
|
<FieldContent>
|
|
600
600
|
<InputGroup>
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
601
|
+
<InputGroupInput
|
|
602
|
+
id="website"
|
|
603
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
604
|
+
setFormData((prev) => ({
|
|
605
|
+
...prev,
|
|
606
|
+
website: e.target.value,
|
|
607
|
+
}))
|
|
608
608
|
}
|
|
609
609
|
placeholder="https://example.com"
|
|
610
610
|
readOnly={readOnly}
|
|
@@ -640,11 +640,11 @@ export default function SettingsProfile({
|
|
|
640
640
|
</FieldLabel>
|
|
641
641
|
<FieldContent>
|
|
642
642
|
<InputGroup>
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
643
|
+
<InputGroupInput
|
|
644
|
+
id={`social-${platform.id}`}
|
|
645
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
646
|
+
updateSocialLink(platform.id, e.target.value)
|
|
647
|
+
}
|
|
648
648
|
placeholder={platform.placeholder}
|
|
649
649
|
readOnly={readOnly}
|
|
650
650
|
type="url"
|
|
@@ -87,7 +87,7 @@ export function UserCreateDialog({ open, onOpenChange }: UserCreateDialogProps)
|
|
|
87
87
|
type="email"
|
|
88
88
|
required
|
|
89
89
|
value={form.email}
|
|
90
|
-
onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))}
|
|
90
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setForm((f) => ({ ...f, email: e.target.value }))}
|
|
91
91
|
/>
|
|
92
92
|
</div>
|
|
93
93
|
<div className="grid gap-2">
|
|
@@ -96,7 +96,7 @@ export function UserCreateDialog({ open, onOpenChange }: UserCreateDialogProps)
|
|
|
96
96
|
id="username"
|
|
97
97
|
required
|
|
98
98
|
value={form.username}
|
|
99
|
-
onChange={(e) => setForm((f) => ({ ...f, username: e.target.value }))}
|
|
99
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setForm((f) => ({ ...f, username: e.target.value }))}
|
|
100
100
|
/>
|
|
101
101
|
</div>
|
|
102
102
|
<div className="grid gap-2">
|
|
@@ -106,12 +106,12 @@ export function UserCreateDialog({ open, onOpenChange }: UserCreateDialogProps)
|
|
|
106
106
|
type="password"
|
|
107
107
|
required
|
|
108
108
|
value={form.password}
|
|
109
|
-
onChange={(e) => setForm((f) => ({ ...f, password: e.target.value }))}
|
|
109
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setForm((f) => ({ ...f, password: e.target.value }))}
|
|
110
110
|
/>
|
|
111
111
|
</div>
|
|
112
112
|
<div className="grid gap-2">
|
|
113
113
|
<Label>角色</Label>
|
|
114
|
-
<Select value={form.role}
|
|
114
|
+
<Select value={form.role} onValueChange={(v: string) => setForm((f) => ({ ...f, role: v as typeof form.role }))}>
|
|
115
115
|
<SelectTrigger>
|
|
116
116
|
<SelectValue />
|
|
117
117
|
</SelectTrigger>
|
|
@@ -15,10 +15,9 @@
|
|
|
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
|
-
|
|
19
18
|
import { useNavigate, useSearch } from "@tanstack/react-router";
|
|
19
|
+
import { m } from '@/paraglide/messages.js'
|
|
20
20
|
import { useCallback } from "react";
|
|
21
|
-
import { useTranslation } from 'react-i18next'
|
|
22
21
|
import { Input } from "@/components/ui/input";
|
|
23
22
|
import { Button } from "@/components/ui/button";
|
|
24
23
|
import {
|
|
@@ -37,7 +36,6 @@ interface UserFiltersProps {
|
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
export function UserFilters({ search, role, status }: UserFiltersProps) {
|
|
40
|
-
const { t } = useTranslation('common')
|
|
41
39
|
const navigate = useNavigate()
|
|
42
40
|
|
|
43
41
|
const updateParam = useCallback(
|
|
@@ -64,10 +62,10 @@ export function UserFilters({ search, role, status }: UserFiltersProps) {
|
|
|
64
62
|
<div className="relative flex-1 max-w-sm">
|
|
65
63
|
<SearchIcon className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
|
66
64
|
<Input
|
|
67
|
-
placeholder={
|
|
65
|
+
placeholder={m.common_userFilters_searchPlaceholder()}
|
|
68
66
|
className="pl-8"
|
|
69
67
|
defaultValue={search || ""}
|
|
70
|
-
onChange={(e) => {
|
|
68
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
71
69
|
const value = e.target.value || undefined
|
|
72
70
|
const next: Record<string, string | undefined> = { search: value, role, status }
|
|
73
71
|
if (!value) delete next.search
|
|
@@ -78,38 +76,38 @@ export function UserFilters({ search, role, status }: UserFiltersProps) {
|
|
|
78
76
|
|
|
79
77
|
<Select
|
|
80
78
|
value={role || "all"}
|
|
81
|
-
onValueChange={(v) => updateParam("role", v)}
|
|
79
|
+
onValueChange={(v: string) => updateParam("role", v)}
|
|
82
80
|
>
|
|
83
81
|
<SelectTrigger className="w-32.5">
|
|
84
|
-
<SelectValue placeholder={
|
|
82
|
+
<SelectValue placeholder={m.common_userFilters_rolePlaceholder()} />
|
|
85
83
|
</SelectTrigger>
|
|
86
84
|
<SelectContent>
|
|
87
|
-
<SelectItem value="all">{
|
|
88
|
-
<SelectItem value="SUPER_ADMIN">{
|
|
89
|
-
<SelectItem value="ADMIN">{
|
|
90
|
-
<SelectItem value="USER">{
|
|
85
|
+
<SelectItem value="all">{m.common_userFilters_role_all()}</SelectItem>
|
|
86
|
+
<SelectItem value="SUPER_ADMIN">{m.common_userFilters_role_SUPER_ADMIN()}</SelectItem>
|
|
87
|
+
<SelectItem value="ADMIN">{m.common_userFilters_role_ADMIN()}</SelectItem>
|
|
88
|
+
<SelectItem value="USER">{m.common_userFilters_role_USER()}</SelectItem>
|
|
91
89
|
</SelectContent>
|
|
92
90
|
</Select>
|
|
93
91
|
|
|
94
92
|
<Select
|
|
95
93
|
value={status || "all"}
|
|
96
|
-
onValueChange={(v) => updateParam("status", v)}
|
|
94
|
+
onValueChange={(v: string) => updateParam("status", v)}
|
|
97
95
|
>
|
|
98
96
|
<SelectTrigger className="w-32.5">
|
|
99
|
-
<SelectValue placeholder={
|
|
97
|
+
<SelectValue placeholder={m.common_userFilters_statusPlaceholder()} />
|
|
100
98
|
</SelectTrigger>
|
|
101
99
|
<SelectContent>
|
|
102
|
-
<SelectItem value="all">{
|
|
103
|
-
<SelectItem value="ACTIVE">{
|
|
104
|
-
<SelectItem value="INACTIVE">{
|
|
105
|
-
<SelectItem value="BANNED">{
|
|
100
|
+
<SelectItem value="all">{m.common_userFilters_status_all()}</SelectItem>
|
|
101
|
+
<SelectItem value="ACTIVE">{m.common_userFilters_status_ACTIVE()}</SelectItem>
|
|
102
|
+
<SelectItem value="INACTIVE">{m.common_userFilters_status_INACTIVE()}</SelectItem>
|
|
103
|
+
<SelectItem value="BANNED">{m.common_userFilters_status_BANNED()}</SelectItem>
|
|
106
104
|
</SelectContent>
|
|
107
105
|
</Select>
|
|
108
106
|
|
|
109
107
|
{hasFilters && (
|
|
110
108
|
<Button variant="ghost" size="sm" onClick={clearFilters}>
|
|
111
109
|
<XIcon className="mr-1 h-4 w-4" />
|
|
112
|
-
{
|
|
110
|
+
{m.common_userFilters_clearFilters()}
|
|
113
111
|
</Button>
|
|
114
112
|
)}
|
|
115
113
|
</div>
|
package/src/lib/i18n/config.ts
CHANGED
|
@@ -1,39 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
-
import zhCommon from './locales/zh/common.json'
|
|
3
|
-
import enCommon from './locales/en/common.json'
|
|
4
|
-
import zhUsers from './locales/zh/admin-users.json'
|
|
5
|
-
import enUsers from './locales/en/admin-users.json'
|
|
6
|
-
import zhAi from './locales/zh/admin-ai.json'
|
|
7
|
-
import enAi from './locales/en/admin-ai.json'
|
|
8
|
-
import zhBilling from './locales/zh/admin-billing.json'
|
|
9
|
-
import enBilling from './locales/en/admin-billing.json'
|
|
10
|
-
import zhEnterprise from './locales/zh/enterprise.json'
|
|
11
|
-
import enEnterprise from './locales/en/enterprise.json'
|
|
12
|
-
|
|
13
2
|
export const supportedLngs = ['zh', 'en'] as const
|
|
14
3
|
export type AppLocale = (typeof supportedLngs)[number]
|
|
15
4
|
export const fallbackLng: AppLocale = 'zh'
|
|
16
|
-
export const defaultNs = 'common'
|
|
17
|
-
|
|
18
|
-
export const resources = {
|
|
19
|
-
zh: {
|
|
20
|
-
common: zhCommon,
|
|
21
|
-
'admin-users': zhUsers,
|
|
22
|
-
'admin-ai': zhAi,
|
|
23
|
-
'admin-billing': zhBilling,
|
|
24
|
-
enterprise: zhEnterprise,
|
|
25
|
-
},
|
|
26
|
-
en: {
|
|
27
|
-
common: enCommon,
|
|
28
|
-
'admin-users': enUsers,
|
|
29
|
-
'admin-ai': enAi,
|
|
30
|
-
'admin-billing': enBilling,
|
|
31
|
-
enterprise: enEnterprise,
|
|
32
|
-
},
|
|
33
|
-
} as const
|
|
34
|
-
|
|
35
|
-
export const namespaces = Object.keys(resources.zh) as Array<keyof typeof resources.zh>
|
|
36
|
-
export type NamespaceKey = (typeof namespaces)[number]
|
|
37
5
|
|
|
38
6
|
export function isSupportedLocale(lng: string): lng is AppLocale {
|
|
39
7
|
return (supportedLngs as readonly string[]).includes(lng)
|