@allbluecn/admin-app 0.2.0 → 0.2.3
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 +10 -10
- 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
|
@@ -15,9 +15,8 @@
|
|
|
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 { useEffect, useState } from "react"
|
|
20
|
-
import {
|
|
19
|
+
import { m } from '@/paraglide/messages.js'
|
|
21
20
|
import { useTheme } from "next-themes"
|
|
22
21
|
import { MarketAppFormData } from "@/lib/anyhub/types"
|
|
23
22
|
import { CAPABILITY_GROUPS } from "@/lib/anyhub/capabilities"
|
|
@@ -49,6 +48,15 @@ interface MarketAppFormProps {
|
|
|
49
48
|
|
|
50
49
|
const CATEGORY_KEYS = ["productivity", "ai-assistant", "project-mgmt", "docs-collab", "data-analysis", "other"] as const
|
|
51
50
|
|
|
51
|
+
const CATEGORY_LABELS: Record<string, () => string> = {
|
|
52
|
+
productivity: m.common_market_categories_productivity,
|
|
53
|
+
"ai-assistant": m['common_market_categories_ai-assistant'],
|
|
54
|
+
"project-mgmt": m['common_market_categories_project-mgmt'],
|
|
55
|
+
"docs-collab": m['common_market_categories_docs-collab'],
|
|
56
|
+
"data-analysis": m['common_market_categories_data-analysis'],
|
|
57
|
+
other: m.common_market_categories_other,
|
|
58
|
+
}
|
|
59
|
+
|
|
52
60
|
const emptyForm: MarketAppFormData = {
|
|
53
61
|
name: "",
|
|
54
62
|
displayName: "",
|
|
@@ -63,7 +71,6 @@ const emptyForm: MarketAppFormData = {
|
|
|
63
71
|
export function MarketAppForm({ open, onOpenChange, editData, onSuccess }: MarketAppFormProps) {
|
|
64
72
|
const [form, setForm] = useState<MarketAppFormData>(emptyForm)
|
|
65
73
|
const [loading, setLoading] = useState(false)
|
|
66
|
-
const { t } = useTranslation('common')
|
|
67
74
|
|
|
68
75
|
useEffect(() => {
|
|
69
76
|
if (editData) {
|
|
@@ -74,11 +81,11 @@ export function MarketAppForm({ open, onOpenChange, editData, onSuccess }: Marke
|
|
|
74
81
|
}, [editData, open])
|
|
75
82
|
|
|
76
83
|
const updateField = (field: keyof MarketAppFormData, value: string) => {
|
|
77
|
-
setForm((prev) => ({ ...prev, [field]: value }))
|
|
84
|
+
setForm((prev: MarketAppFormData) => ({ ...prev, [field]: value }))
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
const toggleCapability = (capId: string) => {
|
|
81
|
-
setForm((prev) => ({
|
|
88
|
+
setForm((prev: MarketAppFormData) => ({
|
|
82
89
|
...prev,
|
|
83
90
|
capabilities: { ...prev.capabilities, [capId]: !prev.capabilities[capId] },
|
|
84
91
|
}))
|
|
@@ -86,7 +93,7 @@ export function MarketAppForm({ open, onOpenChange, editData, onSuccess }: Marke
|
|
|
86
93
|
|
|
87
94
|
const handleSubmit = async () => {
|
|
88
95
|
if (!form.name.trim() || !form.displayName.trim() || !form.entryUrl.trim()) {
|
|
89
|
-
toast.error(
|
|
96
|
+
toast.error(m.common_market_form_fillRequired())
|
|
90
97
|
return
|
|
91
98
|
}
|
|
92
99
|
|
|
@@ -102,14 +109,14 @@ export function MarketAppForm({ open, onOpenChange, editData, onSuccess }: Marke
|
|
|
102
109
|
|
|
103
110
|
if (!res.ok) {
|
|
104
111
|
const err = await res.json()
|
|
105
|
-
throw new Error(err.error ||
|
|
112
|
+
throw new Error(err.error || m.common_market_form_operationFailed())
|
|
106
113
|
}
|
|
107
114
|
|
|
108
|
-
toast.success(editData ?
|
|
115
|
+
toast.success(editData ? m.common_market_form_updated() : m.common_market_form_created())
|
|
109
116
|
onOpenChange(false)
|
|
110
117
|
onSuccess()
|
|
111
118
|
} catch (e) {
|
|
112
|
-
toast.error(e instanceof Error ? e.message :
|
|
119
|
+
toast.error(e instanceof Error ? e.message : m.common_market_form_operationFailed())
|
|
113
120
|
} finally {
|
|
114
121
|
setLoading(false)
|
|
115
122
|
}
|
|
@@ -119,67 +126,67 @@ export function MarketAppForm({ open, onOpenChange, editData, onSuccess }: Marke
|
|
|
119
126
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
120
127
|
<DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-lg">
|
|
121
128
|
<DialogHeader>
|
|
122
|
-
<DialogTitle>{editData ?
|
|
129
|
+
<DialogTitle>{editData ? m.common_market_form_edit() : m.common_market_form_create()}</DialogTitle>
|
|
123
130
|
</DialogHeader>
|
|
124
131
|
|
|
125
132
|
<div className="grid gap-4 py-4">
|
|
126
133
|
<div className="grid gap-2">
|
|
127
|
-
<Label>{
|
|
134
|
+
<Label>{m.common_market_form_name()}</Label>
|
|
128
135
|
<Input
|
|
129
136
|
value={form.name}
|
|
130
|
-
onChange={(e) => updateField("name", e.target.value)}
|
|
131
|
-
placeholder={
|
|
137
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => updateField("name", e.target.value)}
|
|
138
|
+
placeholder={m.common_market_form_namePlaceholder()}
|
|
132
139
|
disabled={!!editData}
|
|
133
140
|
/>
|
|
134
141
|
</div>
|
|
135
142
|
|
|
136
143
|
<div className="grid gap-2">
|
|
137
|
-
<Label>{
|
|
144
|
+
<Label>{m.common_market_form_displayName()}</Label>
|
|
138
145
|
<Input
|
|
139
146
|
value={form.displayName}
|
|
140
|
-
onChange={(e) => updateField("displayName", e.target.value)}
|
|
141
|
-
placeholder={
|
|
147
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => updateField("displayName", e.target.value)}
|
|
148
|
+
placeholder={m.common_market_form_displayNamePlaceholder()}
|
|
142
149
|
/>
|
|
143
150
|
</div>
|
|
144
151
|
|
|
145
152
|
<div className="grid gap-2">
|
|
146
|
-
<Label>{
|
|
153
|
+
<Label>{m.common_market_form_entryUrl()}</Label>
|
|
147
154
|
<Input
|
|
148
155
|
value={form.entryUrl}
|
|
149
|
-
onChange={(e) => updateField("entryUrl", e.target.value)}
|
|
156
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => updateField("entryUrl", e.target.value)}
|
|
150
157
|
placeholder="https://..."
|
|
151
158
|
/>
|
|
152
159
|
</div>
|
|
153
160
|
|
|
154
161
|
<div className="grid gap-2">
|
|
155
|
-
<Label>{
|
|
162
|
+
<Label>{m.common_market_form_description()}</Label>
|
|
156
163
|
<Input
|
|
157
164
|
value={form.description || ""}
|
|
158
|
-
onChange={(e) => updateField("description", e.target.value)}
|
|
159
|
-
placeholder={
|
|
165
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => updateField("description", e.target.value)}
|
|
166
|
+
placeholder={m.common_market_form_descriptionPlaceholder()}
|
|
160
167
|
/>
|
|
161
168
|
</div>
|
|
162
169
|
|
|
163
170
|
<div className="grid gap-2">
|
|
164
|
-
<Label>{
|
|
171
|
+
<Label>{m.common_market_form_icon()}</Label>
|
|
165
172
|
<Input
|
|
166
173
|
value={form.icon || ""}
|
|
167
|
-
onChange={(e) => updateField("icon", e.target.value)}
|
|
168
|
-
placeholder={
|
|
174
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => updateField("icon", e.target.value)}
|
|
175
|
+
placeholder={m.common_market_form_iconPlaceholder()}
|
|
169
176
|
/>
|
|
170
177
|
</div>
|
|
171
178
|
|
|
172
179
|
<div className="grid gap-2">
|
|
173
|
-
<Label>{
|
|
174
|
-
<Select value={form.category || ""} onValueChange={(v) => updateField("category", v === "__none__" ? "" : v)}>
|
|
180
|
+
<Label>{m.common_market_form_category()}</Label>
|
|
181
|
+
<Select value={form.category || ""} onValueChange={(v: string) => updateField("category", v === "__none__" ? "" : v)}>
|
|
175
182
|
<SelectTrigger>
|
|
176
|
-
<SelectValue placeholder={
|
|
183
|
+
<SelectValue placeholder={m.common_market_form_categoryPlaceholder()} />
|
|
177
184
|
</SelectTrigger>
|
|
178
185
|
<SelectContent>
|
|
179
|
-
<SelectItem value="__none__">{
|
|
186
|
+
<SelectItem value="__none__">{m.common_common_none()}</SelectItem>
|
|
180
187
|
{CATEGORY_KEYS.map((k) => (
|
|
181
|
-
<SelectItem key={k} value={
|
|
182
|
-
{
|
|
188
|
+
<SelectItem key={k} value={(CATEGORY_LABELS[k] ?? (() => k))()}>
|
|
189
|
+
{(CATEGORY_LABELS[k] ?? (() => k))()}
|
|
183
190
|
</SelectItem>
|
|
184
191
|
))}
|
|
185
192
|
</SelectContent>
|
|
@@ -187,22 +194,22 @@ export function MarketAppForm({ open, onOpenChange, editData, onSuccess }: Marke
|
|
|
187
194
|
</div>
|
|
188
195
|
|
|
189
196
|
<div className="grid gap-2">
|
|
190
|
-
<Label>{
|
|
197
|
+
<Label>{m.common_market_form_tags()}</Label>
|
|
191
198
|
<Input
|
|
192
199
|
value={form.tags || ""}
|
|
193
|
-
onChange={(e) => updateField("tags", e.target.value)}
|
|
194
|
-
placeholder={
|
|
200
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => updateField("tags", e.target.value)}
|
|
201
|
+
placeholder={m.common_market_form_tagsPlaceholder()}
|
|
195
202
|
/>
|
|
196
203
|
</div>
|
|
197
204
|
|
|
198
205
|
<div className="grid gap-2">
|
|
199
|
-
<Label>{
|
|
206
|
+
<Label>{m.common_market_form_capabilities()}</Label>
|
|
200
207
|
<div className="space-y-3 rounded-md border p-3">
|
|
201
|
-
{CAPABILITY_GROUPS.map((group) => (
|
|
208
|
+
{CAPABILITY_GROUPS.map((group: any) => (
|
|
202
209
|
<div key={group.id}>
|
|
203
210
|
<p className="mb-1.5 text-sm font-medium">{group.label}</p>
|
|
204
211
|
<div className="flex flex-wrap gap-3">
|
|
205
|
-
{group.capabilities.map((cap) => (
|
|
212
|
+
{group.capabilities.map((cap: any) => (
|
|
206
213
|
<label
|
|
207
214
|
key={cap.id}
|
|
208
215
|
className="flex cursor-pointer items-center gap-1.5 text-sm"
|
|
@@ -225,10 +232,10 @@ export function MarketAppForm({ open, onOpenChange, editData, onSuccess }: Marke
|
|
|
225
232
|
|
|
226
233
|
<DialogFooter>
|
|
227
234
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
228
|
-
{
|
|
235
|
+
{m.common_common_cancel()}
|
|
229
236
|
</Button>
|
|
230
237
|
<Button onClick={handleSubmit} disabled={loading}>
|
|
231
|
-
{loading ?
|
|
238
|
+
{loading ? m.common_status_loading() : editData ? m.common_button_save() : m.common_common_create()}
|
|
232
239
|
</Button>
|
|
233
240
|
</DialogFooter>
|
|
234
241
|
</DialogContent>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import { useCallback, useEffect, useState } from "react"
|
|
19
|
-
import {
|
|
19
|
+
import { m } from '@/paraglide/messages.js'
|
|
20
20
|
import { DataTable } from "@/components/data-table/data-table"
|
|
21
21
|
import { getMarketColumns, MarketAppRow } from "./columns"
|
|
22
22
|
import { MarketAppForm } from "./market-app-form"
|
|
@@ -37,7 +37,6 @@ import {
|
|
|
37
37
|
} from "@/components/ui/alert-dialog"
|
|
38
38
|
|
|
39
39
|
export function MarketAppList() {
|
|
40
|
-
const { t } = useTranslation('common')
|
|
41
40
|
const [apps, setApps] = useState<MarketAppRow[]>([])
|
|
42
41
|
const [loading, setLoading] = useState(true)
|
|
43
42
|
const [formOpen, setFormOpen] = useState(false)
|
|
@@ -47,7 +46,7 @@ export function MarketAppList() {
|
|
|
47
46
|
const fetchApps = useCallback(async () => {
|
|
48
47
|
try {
|
|
49
48
|
const data = await listMarketAppsFn({ data: {} })
|
|
50
|
-
setApps(data.items.map((app) => ({
|
|
49
|
+
setApps(data.items.map((app: any) => ({
|
|
51
50
|
...app,
|
|
52
51
|
capabilities: (app.capabilities || {}) as Record<string, boolean>,
|
|
53
52
|
createdAt: app.createdAt.toISOString(),
|
|
@@ -55,11 +54,11 @@ export function MarketAppList() {
|
|
|
55
54
|
publishedAt: app.publishedAt?.toISOString() ?? null,
|
|
56
55
|
})))
|
|
57
56
|
} catch {
|
|
58
|
-
toast.error(
|
|
57
|
+
toast.error(m.common_market_fetchFailed())
|
|
59
58
|
} finally {
|
|
60
59
|
setLoading(false)
|
|
61
60
|
}
|
|
62
|
-
}, [
|
|
61
|
+
}, [])
|
|
63
62
|
|
|
64
63
|
useEffect(() => {
|
|
65
64
|
fetchApps()
|
|
@@ -86,10 +85,10 @@ export function MarketAppList() {
|
|
|
86
85
|
const newStatus = app.status === "PUBLISHED" ? "UNPUBLISHED" : "PUBLISHED"
|
|
87
86
|
try {
|
|
88
87
|
await updateMarketAppFn({ data: { id: app.id, isPublished: newStatus === "PUBLISHED" } })
|
|
89
|
-
toast.success(
|
|
88
|
+
toast.success(m.common_market_statusUpdated({ status: newStatus === "PUBLISHED" ? 'published' : 'unpublished' }))
|
|
90
89
|
fetchApps()
|
|
91
90
|
} catch {
|
|
92
|
-
toast.error(
|
|
91
|
+
toast.error(m.common_market_operationFailed())
|
|
93
92
|
}
|
|
94
93
|
}
|
|
95
94
|
|
|
@@ -97,11 +96,11 @@ export function MarketAppList() {
|
|
|
97
96
|
if (!deleteTarget) return
|
|
98
97
|
try {
|
|
99
98
|
await deleteMarketAppFn({ data: { id: deleteTarget.id } })
|
|
100
|
-
toast.success(
|
|
99
|
+
toast.success(m.common_market_deleted())
|
|
101
100
|
setDeleteTarget(null)
|
|
102
101
|
fetchApps()
|
|
103
102
|
} catch {
|
|
104
|
-
toast.error(
|
|
103
|
+
toast.error(m.common_market_deleteFailed())
|
|
105
104
|
}
|
|
106
105
|
}
|
|
107
106
|
|
|
@@ -110,7 +109,7 @@ export function MarketAppList() {
|
|
|
110
109
|
return (
|
|
111
110
|
<div className="space-y-4">
|
|
112
111
|
<div className="flex items-center justify-between">
|
|
113
|
-
<h1 className="text-2xl font-bold">{
|
|
112
|
+
<h1 className="text-2xl font-bold">{m.common_market_title()}</h1>
|
|
114
113
|
<Button
|
|
115
114
|
onClick={() => {
|
|
116
115
|
setEditData(null)
|
|
@@ -118,12 +117,12 @@ export function MarketAppList() {
|
|
|
118
117
|
}}
|
|
119
118
|
>
|
|
120
119
|
<PlusIcon className="mr-2 size-4" />
|
|
121
|
-
{
|
|
120
|
+
{m.common_market_addApp()}
|
|
122
121
|
</Button>
|
|
123
122
|
</div>
|
|
124
123
|
|
|
125
124
|
{loading ? (
|
|
126
|
-
<p className="text-muted-foreground">{
|
|
125
|
+
<p className="text-muted-foreground">{m.common_status_loading()}</p>
|
|
127
126
|
) : (
|
|
128
127
|
<DataTable columns={columns} data={apps} />
|
|
129
128
|
)}
|
|
@@ -135,18 +134,18 @@ export function MarketAppList() {
|
|
|
135
134
|
onSuccess={fetchApps}
|
|
136
135
|
/>
|
|
137
136
|
|
|
138
|
-
<AlertDialog open={!!deleteTarget} onOpenChange={(open) => !open && setDeleteTarget(null)}>
|
|
137
|
+
<AlertDialog open={!!deleteTarget} onOpenChange={(open: boolean) => !open && setDeleteTarget(null)}>
|
|
139
138
|
<AlertDialogContent>
|
|
140
139
|
<AlertDialogHeader>
|
|
141
|
-
<AlertDialogTitle>{
|
|
140
|
+
<AlertDialogTitle>{m.common_market_deleteConfirmTitle()}</AlertDialogTitle>
|
|
142
141
|
<AlertDialogDescription>
|
|
143
|
-
{
|
|
142
|
+
{m.common_market_deleteConfirmDesc({ name: deleteTarget?.displayName ?? '' })}
|
|
144
143
|
</AlertDialogDescription>
|
|
145
144
|
</AlertDialogHeader>
|
|
146
145
|
<AlertDialogFooter>
|
|
147
|
-
<AlertDialogCancel>{
|
|
146
|
+
<AlertDialogCancel>{m.common_common_cancel()}</AlertDialogCancel>
|
|
148
147
|
<AlertDialogAction onClick={handleDelete} className="bg-destructive text-white hover:bg-destructive/90">
|
|
149
|
-
{
|
|
148
|
+
{m.common_common_delete()}
|
|
150
149
|
</AlertDialogAction>
|
|
151
150
|
</AlertDialogFooter>
|
|
152
151
|
</AlertDialogContent>
|
|
@@ -16,21 +16,21 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import { useState } 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
|
|
|
22
|
-
function formatRelativeTime(dateStr: string
|
|
22
|
+
function formatRelativeTime(dateStr: string): string {
|
|
23
23
|
const now = new Date()
|
|
24
24
|
const target = new Date(dateStr)
|
|
25
25
|
const diff = now.getTime() - target.getTime()
|
|
26
26
|
const seconds = Math.floor(diff / 1000)
|
|
27
|
-
if (seconds < 60) return
|
|
27
|
+
if (seconds < 60) return m.common_common_timeAgo_seconds({ count: seconds })
|
|
28
28
|
const minutes = Math.floor(seconds / 60)
|
|
29
|
-
if (minutes < 60) return
|
|
29
|
+
if (minutes < 60) return m.common_common_timeAgo_minutes({ count: minutes })
|
|
30
30
|
const hours = Math.floor(minutes / 60)
|
|
31
|
-
if (hours < 24) return
|
|
31
|
+
if (hours < 24) return m.common_common_timeAgo_hours({ count: hours })
|
|
32
32
|
const days = Math.floor(hours / 24)
|
|
33
|
-
return
|
|
33
|
+
return m.common_common_timeAgo_days({ count: days })
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
interface ErrorItem {
|
|
@@ -49,22 +49,21 @@ interface ErrorListProps {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export function ErrorList({ errors }: ErrorListProps) {
|
|
52
|
-
const { t } = useTranslation('common')
|
|
53
52
|
const [expandedId, setExpandedId] = useState<string | null>(null)
|
|
54
53
|
|
|
55
54
|
const columns: AdminColumnDef<ErrorItem>[] = [
|
|
56
55
|
{
|
|
57
|
-
header:
|
|
56
|
+
header: m.common_errorList_columns_lastSeen(),
|
|
58
57
|
accessorKey: 'lastSeen',
|
|
59
58
|
meta: { style: { width: 60 } },
|
|
60
59
|
cell: ({ row }: { row: { original: ErrorItem } }) => (
|
|
61
60
|
<span className="text-xs text-muted-foreground">
|
|
62
|
-
{formatRelativeTime(row.original.lastSeen
|
|
61
|
+
{formatRelativeTime(row.original.lastSeen)}
|
|
63
62
|
</span>
|
|
64
63
|
),
|
|
65
64
|
},
|
|
66
65
|
{
|
|
67
|
-
header:
|
|
66
|
+
header: m.common_errorList_columns_errorName(),
|
|
68
67
|
accessorKey: 'errorName',
|
|
69
68
|
meta: { style: { width: 120 } },
|
|
70
69
|
cell: ({ row }: { row: { original: ErrorItem } }) => (
|
|
@@ -74,7 +73,7 @@ export function ErrorList({ errors }: ErrorListProps) {
|
|
|
74
73
|
),
|
|
75
74
|
},
|
|
76
75
|
{
|
|
77
|
-
header:
|
|
76
|
+
header: m.common_errorList_columns_errorMessage(),
|
|
78
77
|
accessorKey: 'errorMessage',
|
|
79
78
|
meta: { style: { width: 280 } },
|
|
80
79
|
cell: ({ row }: { row: { original: ErrorItem } }) => {
|
|
@@ -83,7 +82,7 @@ export function ErrorList({ errors }: ErrorListProps) {
|
|
|
83
82
|
},
|
|
84
83
|
},
|
|
85
84
|
{
|
|
86
|
-
header:
|
|
85
|
+
header: m.common_errorList_columns_count(),
|
|
87
86
|
accessorKey: 'count',
|
|
88
87
|
meta: { style: { width: 60, textAlign: 'right' } },
|
|
89
88
|
cell: ({ row }: { row: { original: ErrorItem } }) => (
|
|
@@ -91,12 +90,12 @@ export function ErrorList({ errors }: ErrorListProps) {
|
|
|
91
90
|
),
|
|
92
91
|
},
|
|
93
92
|
{
|
|
94
|
-
header:
|
|
93
|
+
header: m.common_errorList_columns_firstSeen(),
|
|
95
94
|
accessorKey: 'firstSeen',
|
|
96
95
|
meta: { style: { width: 60 } },
|
|
97
96
|
cell: ({ row }: { row: { original: ErrorItem } }) => (
|
|
98
97
|
<span className="text-xs text-muted-foreground">
|
|
99
|
-
{formatRelativeTime(row.original.firstSeen
|
|
98
|
+
{formatRelativeTime(row.original.firstSeen)}
|
|
100
99
|
</span>
|
|
101
100
|
),
|
|
102
101
|
},
|
|
@@ -107,24 +106,24 @@ export function ErrorList({ errors }: ErrorListProps) {
|
|
|
107
106
|
<DataTable
|
|
108
107
|
columns={columns}
|
|
109
108
|
data={errors}
|
|
110
|
-
onRowClick={(item) => {
|
|
109
|
+
onRowClick={(item: any) => {
|
|
111
110
|
setExpandedId(expandedId === item.id ? null : item.id)
|
|
112
111
|
}}
|
|
113
112
|
/>
|
|
114
113
|
{expandedId && (
|
|
115
|
-
<ErrorDetail errors={errors} expandedId={expandedId}
|
|
114
|
+
<ErrorDetail errors={errors} expandedId={expandedId} />
|
|
116
115
|
)}
|
|
117
116
|
</div>
|
|
118
117
|
)
|
|
119
118
|
}
|
|
120
119
|
|
|
121
|
-
function ErrorDetail({ errors, expandedId
|
|
120
|
+
function ErrorDetail({ errors, expandedId }: { errors: ErrorItem[]; expandedId: string }) {
|
|
122
121
|
const error = errors.find((e) => e.id === expandedId)
|
|
123
122
|
if (!error) return null
|
|
124
123
|
|
|
125
124
|
return (
|
|
126
125
|
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
|
|
127
|
-
<div className="font-medium text-sm">{
|
|
126
|
+
<div className="font-medium text-sm">{m.common_errorList_detail_title()}</div>
|
|
128
127
|
{error.stackTrace && (
|
|
129
128
|
<pre className="text-xs bg-background rounded-md p-3 overflow-auto max-h-40 border">
|
|
130
129
|
{error.stackTrace}
|
|
@@ -132,7 +131,7 @@ function ErrorDetail({ errors, expandedId, t }: { errors: ErrorItem[]; expandedI
|
|
|
132
131
|
)}
|
|
133
132
|
{error.context && JSON.stringify(error.context, null, 2) && (
|
|
134
133
|
<div>
|
|
135
|
-
<div className="text-xs font-medium text-muted-foreground mb-1">{
|
|
134
|
+
<div className="text-xs font-medium text-muted-foreground mb-1">{m.common_errorList_detail_context()}</div>
|
|
136
135
|
<pre className="text-xs bg-background rounded-md p-3 overflow-auto border">
|
|
137
136
|
{JSON.stringify(error.context, null, 2)}
|
|
138
137
|
</pre>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { m } from '@/paraglide/messages.js'
|
|
1
2
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
3
|
// AllBlue - 理想之海
|
|
3
4
|
// Copyright (C) 2026 AllBlue Contributors
|
|
@@ -15,8 +16,6 @@
|
|
|
15
16
|
// You should have received a copy of the GNU Affero General Public License
|
|
16
17
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
18
|
|
|
18
|
-
import { useTranslation } from 'react-i18next'
|
|
19
|
-
|
|
20
19
|
interface HealthCardData {
|
|
21
20
|
status?: string
|
|
22
21
|
checks?: {
|
|
@@ -70,7 +69,6 @@ interface HealthCardsProps {
|
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
export function HealthCards({ health }: HealthCardsProps) {
|
|
73
|
-
const { t } = useTranslation('common')
|
|
74
72
|
const db = health.checks?.database
|
|
75
73
|
const mem = health.checks?.memory
|
|
76
74
|
const cpu = health.checks?.cpu
|
|
@@ -81,24 +79,24 @@ export function HealthCards({ health }: HealthCardsProps) {
|
|
|
81
79
|
return (
|
|
82
80
|
<div className="grid gap-3 md:grid-cols-3 lg:grid-cols-5">
|
|
83
81
|
<div className="rounded-lg border p-3">
|
|
84
|
-
<p className="text-xs text-muted-foreground">{
|
|
82
|
+
<p className="text-xs text-muted-foreground">{m.common_monitoring_healthCards_database()}</p>
|
|
85
83
|
<p className={`text-sm font-semibold mt-1 ${db?.status === 'connected' ? 'text-green-600' : 'text-red-600'}`}>
|
|
86
|
-
{db?.status === 'connected' ?
|
|
84
|
+
{db?.status === 'connected' ? m.common_monitoring_healthCards_connected() : m.common_monitoring_healthCards_disconnected()}
|
|
87
85
|
</p>
|
|
88
86
|
{db?.status === 'connected' && db.latency != null && (
|
|
89
|
-
<p className="text-xs text-muted-foreground mt-0.5">{
|
|
87
|
+
<p className="text-xs text-muted-foreground mt-0.5">{m.common_monitoring_healthCards_latency({ latency: db.latency })}ms</p>
|
|
90
88
|
)}
|
|
91
89
|
</div>
|
|
92
90
|
|
|
93
91
|
<div className="rounded-lg border p-3">
|
|
94
|
-
<p className="text-xs text-muted-foreground">{
|
|
92
|
+
<p className="text-xs text-muted-foreground">{m.common_monitoring_healthCards_memoryRSS()}</p>
|
|
95
93
|
<p className="text-sm font-semibold mt-1">
|
|
96
94
|
{mem?.rss ? formatBytes(mem.rss) : '-'}
|
|
97
95
|
</p>
|
|
98
96
|
</div>
|
|
99
97
|
|
|
100
98
|
<div className="rounded-lg border p-3">
|
|
101
|
-
<p className="text-xs text-muted-foreground">{
|
|
99
|
+
<p className="text-xs text-muted-foreground">{m.common_monitoring_healthCards_heapUsage()}</p>
|
|
102
100
|
<p className="text-sm font-semibold mt-1">
|
|
103
101
|
{mem?.heapUsed && mem?.heapTotal ? `${((mem.heapUsed / mem.heapTotal) * 100).toFixed(1)}%` : '-'}
|
|
104
102
|
</p>
|
|
@@ -108,7 +106,7 @@ export function HealthCards({ health }: HealthCardsProps) {
|
|
|
108
106
|
</div>
|
|
109
107
|
|
|
110
108
|
<div className="rounded-lg border p-3">
|
|
111
|
-
<p className="text-xs text-muted-foreground">{
|
|
109
|
+
<p className="text-xs text-muted-foreground">{m.common_monitoring_healthCards_cpuCores({ cores: cpu?.cores ?? 0 })}</p>
|
|
112
110
|
<p className="text-sm font-semibold mt-1">
|
|
113
111
|
{cpu?.usage != null ? `${cpu.usage.toFixed(1)}%` : '-'}
|
|
114
112
|
</p>
|
|
@@ -118,7 +116,7 @@ export function HealthCards({ health }: HealthCardsProps) {
|
|
|
118
116
|
</div>
|
|
119
117
|
|
|
120
118
|
<div className="rounded-lg border p-3">
|
|
121
|
-
<p className="text-xs text-muted-foreground">{
|
|
119
|
+
<p className="text-xs text-muted-foreground">{m.common_monitoring_healthCards_systemLoad()}</p>
|
|
122
120
|
<p className="text-sm font-semibold mt-1">
|
|
123
121
|
{load?.one != null ? `${load.one.toFixed(2)} / ${load.five?.toFixed(2) ?? '-'} / ${load.fifteen?.toFixed(2) ?? '-'}` : '-'}
|
|
124
122
|
</p>
|
|
@@ -126,19 +124,19 @@ export function HealthCards({ health }: HealthCardsProps) {
|
|
|
126
124
|
|
|
127
125
|
{disk && (
|
|
128
126
|
<div className="rounded-lg border p-3">
|
|
129
|
-
<p className="text-xs text-muted-foreground">{
|
|
127
|
+
<p className="text-xs text-muted-foreground">{m.common_monitoring_healthCards_diskUsage()}</p>
|
|
130
128
|
<p className="text-sm font-semibold mt-1">
|
|
131
129
|
{disk.usedPct != null ? `${disk.usedPct.toFixed(1)}%` : '-'}
|
|
132
130
|
</p>
|
|
133
131
|
<p className="text-xs text-muted-foreground mt-0.5">
|
|
134
|
-
{
|
|
132
|
+
{m.common_monitoring_healthCards_available({ free: disk.free != null ? formatBytes(disk.free) : '-', total: disk.total != null ? formatBytes(disk.total) : '-' })}
|
|
135
133
|
</p>
|
|
136
134
|
</div>
|
|
137
135
|
)}
|
|
138
136
|
|
|
139
137
|
{system && (
|
|
140
138
|
<div className="rounded-lg border p-3">
|
|
141
|
-
<p className="text-xs text-muted-foreground">{
|
|
139
|
+
<p className="text-xs text-muted-foreground">{m.common_monitoring_healthCards_runtime()}</p>
|
|
142
140
|
<p className="text-sm font-semibold mt-1">
|
|
143
141
|
{system.platform} {system.release}
|
|
144
142
|
</p>
|
|
@@ -15,8 +15,8 @@
|
|
|
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 { useTranslation } from 'react-i18next'
|
|
19
18
|
import { DataTable, type AdminColumnDef } from '@/components/data-table/data-table'
|
|
19
|
+
import { m } from '@/paraglide/messages.js'
|
|
20
20
|
import { describeMetric } from '@/lib/monitoring-descriptions'
|
|
21
21
|
import { getBenchmark, getBenchmarkSource, compareDuration } from '@/lib/monitoring-benchmarks'
|
|
22
22
|
import { ArrowDown, ArrowUp, Info } from 'lucide-react'
|
|
@@ -78,24 +78,23 @@ function BenchmarkCell({ value, metric }: BenchmarkCellProps) {
|
|
|
78
78
|
)
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
function BenchmarkInfoTooltip(
|
|
81
|
+
function BenchmarkInfoTooltip() {
|
|
82
82
|
return (
|
|
83
83
|
<span
|
|
84
84
|
className="inline-flex items-center gap-1 text-xs text-muted-foreground"
|
|
85
|
-
title={
|
|
85
|
+
title={m.common_metricsStats_benchmarkSource()}
|
|
86
86
|
>
|
|
87
87
|
<Info className="size-3" />
|
|
88
|
-
{
|
|
88
|
+
{m.common_metricsStats_compareBenchmark()}
|
|
89
89
|
</span>
|
|
90
90
|
)
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
export function MetricsStats({ data }: MetricsStatsProps) {
|
|
94
|
-
const { t } = useTranslation('common')
|
|
95
94
|
|
|
96
95
|
const columns: AdminColumnDef<MetricRow>[] = [
|
|
97
96
|
{
|
|
98
|
-
header:
|
|
97
|
+
header: m.common_metricsStats_columns_metricName(),
|
|
99
98
|
accessorKey: 'metric',
|
|
100
99
|
meta: { style: { width: 160 } },
|
|
101
100
|
cell: ({ row }: { row: { original: MetricRow } }) => (
|
|
@@ -105,7 +104,7 @@ export function MetricsStats({ data }: MetricsStatsProps) {
|
|
|
105
104
|
),
|
|
106
105
|
},
|
|
107
106
|
{
|
|
108
|
-
header:
|
|
107
|
+
header: m.common_metricsStats_columns_description(),
|
|
109
108
|
accessorKey: 'metric',
|
|
110
109
|
meta: { style: { width: 30 } },
|
|
111
110
|
cell: ({ row }: { row: { original: MetricRow } }) => (
|
|
@@ -115,15 +114,15 @@ export function MetricsStats({ data }: MetricsStatsProps) {
|
|
|
115
114
|
),
|
|
116
115
|
},
|
|
117
116
|
{
|
|
118
|
-
header:
|
|
117
|
+
header: m.common_metricsStats_columns_count(),
|
|
119
118
|
accessorKey: 'count',
|
|
120
119
|
meta: { style: { width: 18, textAlign: 'right' } },
|
|
121
120
|
},
|
|
122
121
|
{
|
|
123
122
|
header: () => (
|
|
124
123
|
<span className="flex items-center gap-1">
|
|
125
|
-
{
|
|
126
|
-
<BenchmarkInfoTooltip
|
|
124
|
+
{m.common_metricsStats_columns_avg()}
|
|
125
|
+
<BenchmarkInfoTooltip />
|
|
127
126
|
</span>
|
|
128
127
|
),
|
|
129
128
|
accessorKey: 'avg',
|
|
@@ -157,7 +156,7 @@ export function MetricsStats({ data }: MetricsStatsProps) {
|
|
|
157
156
|
),
|
|
158
157
|
},
|
|
159
158
|
{
|
|
160
|
-
header:
|
|
159
|
+
header: m.common_metricsStats_columns_min(),
|
|
161
160
|
accessorKey: 'min',
|
|
162
161
|
meta: { style: { width: 30, textAlign: 'right' } },
|
|
163
162
|
cell: ({ row }: { row: { original: MetricRow } }) => (
|
|
@@ -165,7 +164,7 @@ export function MetricsStats({ data }: MetricsStatsProps) {
|
|
|
165
164
|
),
|
|
166
165
|
},
|
|
167
166
|
{
|
|
168
|
-
header:
|
|
167
|
+
header: m.common_metricsStats_columns_max(),
|
|
169
168
|
accessorKey: 'max',
|
|
170
169
|
meta: { style: { width: 30, textAlign: 'right' } },
|
|
171
170
|
cell: ({ row }: { row: { original: MetricRow } }) => (
|