@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.
Files changed (87) hide show
  1. package/package.json +8 -8
  2. package/src/components/admin/admin-user-menu.tsx +12 -6
  3. package/src/components/admin/developer-log.tsx +42 -44
  4. package/src/components/ai-provider/model-list-editor.tsx +5 -4
  5. package/src/components/ai-provider/provider-form.tsx +63 -64
  6. package/src/components/ai-provider/provider-table.tsx +35 -36
  7. package/src/components/auth/admin-login-form.tsx +23 -24
  8. package/src/components/auth/oauth-section.tsx +3 -5
  9. package/src/components/auth/password-input.tsx +2 -3
  10. package/src/components/common/placeholder-page.tsx +2 -4
  11. package/src/components/component/component-form-dialog.tsx +48 -49
  12. package/src/components/component/component-import-dialog.tsx +5 -5
  13. package/src/components/component/component-table.tsx +13 -14
  14. package/src/components/data-table/data-table-pagination.tsx +4 -6
  15. package/src/components/data-table/table-detail-layout.tsx +2 -3
  16. package/src/components/dependency/changelog-dialog.tsx +3 -4
  17. package/src/components/dependency/columns.tsx +32 -24
  18. package/src/components/dependency/dependency-table.tsx +37 -29
  19. package/src/components/dependency/edit-dialog.tsx +24 -25
  20. package/src/components/knowledge-public/bookmark-form-dialog.tsx +7 -7
  21. package/src/components/knowledge-public/compliance-form-dialog.tsx +13 -13
  22. package/src/components/knowledge-public/legal-form-dialog.tsx +51 -46
  23. package/src/components/layout/sidebar-02/app-sidebar.tsx +33 -34
  24. package/src/components/layout/sidebar-02/nav-main.tsx +1 -1
  25. package/src/components/market/columns.tsx +20 -21
  26. package/src/components/market/market-app-form.tsx +45 -38
  27. package/src/components/market/market-app-list.tsx +16 -17
  28. package/src/components/monitoring/error-list.tsx +18 -19
  29. package/src/components/monitoring/health-cards.tsx +11 -13
  30. package/src/components/monitoring/metrics-stats.tsx +11 -12
  31. package/src/components/monitoring/request-log-table.tsx +8 -9
  32. package/src/components/ui/pagination.tsx +0 -2
  33. package/src/components/ui/sidebar.tsx +1 -1
  34. package/src/components/user/columns.tsx +9 -9
  35. package/src/components/user/settings-activity-log.tsx +39 -29
  36. package/src/components/user/settings-profile.tsx +36 -36
  37. package/src/components/user/user-create-dialog.tsx +4 -4
  38. package/src/components/user/user-filters.tsx +16 -18
  39. package/src/lib/i18n/config.ts +0 -32
  40. package/src/providers/index.tsx +6 -16
  41. package/src/routeTree.gen.ts +1199 -0
  42. package/src/routes/__root.tsx +9 -23
  43. package/src/routes/_nav/apps/route.lazy.tsx +2 -3
  44. package/src/routes/_nav/audit/route.lazy.tsx +10 -11
  45. package/src/routes/_nav/billing/invoices/route.lazy.tsx +6 -7
  46. package/src/routes/_nav/billing/route.lazy.tsx +3 -4
  47. package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.lazy.tsx +16 -17
  48. package/src/routes/_nav/billing/subscriptions/route.lazy.tsx +9 -10
  49. package/src/routes/_nav/compliance/route.lazy.tsx +34 -35
  50. package/src/routes/_nav/components/route.lazy.tsx +6 -6
  51. package/src/routes/_nav/dashboard/route.lazy.tsx +3 -5
  52. package/src/routes/_nav/dependencies/route.lazy.tsx +7 -8
  53. package/src/routes/_nav/docs/route.lazy.tsx +2 -3
  54. package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +40 -41
  55. package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +32 -33
  56. package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +32 -33
  57. package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +24 -31
  58. package/src/routes/_nav/licenses/new/route.lazy.tsx +25 -26
  59. package/src/routes/_nav/licenses/route.lazy.tsx +17 -18
  60. package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +26 -27
  61. package/src/routes/_nav/monitoring/errors/route.lazy.tsx +11 -12
  62. package/src/routes/_nav/monitoring/health/route.lazy.tsx +18 -20
  63. package/src/routes/_nav/monitoring/logs/route.lazy.tsx +12 -13
  64. package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +13 -15
  65. package/src/routes/_nav/monitoring/overview/route.lazy.tsx +12 -13
  66. package/src/routes/_nav/monitoring/route.lazy.tsx +8 -9
  67. package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +55 -57
  68. package/src/routes/_nav/permissions/route.lazy.tsx +48 -50
  69. package/src/routes/_nav/settings/route.lazy.tsx +2 -3
  70. package/src/routes/_nav/sso/route.lazy.tsx +20 -18
  71. package/src/routes/_nav/users/$userId/route.lazy.tsx +86 -82
  72. package/src/routes/_nav/users/route.lazy.tsx +4 -5
  73. package/src/routes/setup/route.lazy.tsx +15 -16
  74. package/src/start.ts +2 -2
  75. package/src/lib/i18n/client.ts +0 -32
  76. package/src/lib/i18n/locales/en/admin-ai.json +0 -128
  77. package/src/lib/i18n/locales/en/admin-billing.json +0 -56
  78. package/src/lib/i18n/locales/en/admin-users.json +0 -88
  79. package/src/lib/i18n/locales/en/common.json +0 -877
  80. package/src/lib/i18n/locales/en/enterprise.json +0 -208
  81. package/src/lib/i18n/locales/zh/admin-ai.json +0 -128
  82. package/src/lib/i18n/locales/zh/admin-billing.json +0 -56
  83. package/src/lib/i18n/locales/zh/admin-users.json +0 -88
  84. package/src/lib/i18n/locales/zh/common.json +0 -877
  85. package/src/lib/i18n/locales/zh/enterprise.json +0 -208
  86. package/src/lib/i18n/server.ts +0 -18
  87. 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 { useTranslation } from 'react-i18next'
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(t('market.form.fillRequired'))
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 || t('market.form.operationFailed'))
112
+ throw new Error(err.error || m.common_market_form_operationFailed())
106
113
  }
107
114
 
108
- toast.success(editData ? t('market.form.updated') : t('market.form.created'))
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 : t('market.form.operationFailed'))
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 ? t('market.form.edit') : t('market.form.create')}</DialogTitle>
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>{t('market.form.name')}</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={t('market.form.namePlaceholder')}
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>{t('market.form.displayName')}</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={t('market.form.displayNamePlaceholder')}
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>{t('market.form.entryUrl')}</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>{t('market.form.description')}</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={t('market.form.descriptionPlaceholder')}
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>{t('market.form.icon')}</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={t('market.form.iconPlaceholder')}
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>{t('market.form.category')}</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={t('market.form.categoryPlaceholder')} />
183
+ <SelectValue placeholder={m.common_market_form_categoryPlaceholder()} />
177
184
  </SelectTrigger>
178
185
  <SelectContent>
179
- <SelectItem value="__none__">{t('common.none')}</SelectItem>
186
+ <SelectItem value="__none__">{m.common_common_none()}</SelectItem>
180
187
  {CATEGORY_KEYS.map((k) => (
181
- <SelectItem key={k} value={t(`market.categories.${k}`)}>
182
- {t(`market.categories.${k}`)}
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>{t('market.form.tags')}</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={t('market.form.tagsPlaceholder')}
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>{t('market.form.capabilities')}</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
- {t('common.cancel')}
235
+ {m.common_common_cancel()}
229
236
  </Button>
230
237
  <Button onClick={handleSubmit} disabled={loading}>
231
- {loading ? t('status.loading') : editData ? t('common.save') : t('common.create')}
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 { useTranslation } from 'react-i18next'
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(t('market.fetchFailed'))
57
+ toast.error(m.common_market_fetchFailed())
59
58
  } finally {
60
59
  setLoading(false)
61
60
  }
62
- }, [t])
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(t('market.statusUpdated', { status: newStatus === "PUBLISHED" ? 'published' : 'unpublished' }))
88
+ toast.success(m.common_market_statusUpdated({ status: newStatus === "PUBLISHED" ? 'published' : 'unpublished' }))
90
89
  fetchApps()
91
90
  } catch {
92
- toast.error(t('market.operationFailed'))
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(t('market.deleted'))
99
+ toast.success(m.common_market_deleted())
101
100
  setDeleteTarget(null)
102
101
  fetchApps()
103
102
  } catch {
104
- toast.error(t('market.deleteFailed'))
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">{t('market.title')}</h1>
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
- {t('market.addApp')}
120
+ {m.common_market_addApp()}
122
121
  </Button>
123
122
  </div>
124
123
 
125
124
  {loading ? (
126
- <p className="text-muted-foreground">{t('common.loading')}</p>
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>{t('market.deleteConfirmTitle')}</AlertDialogTitle>
140
+ <AlertDialogTitle>{m.common_market_deleteConfirmTitle()}</AlertDialogTitle>
142
141
  <AlertDialogDescription>
143
- {t('market.deleteConfirmDesc', { name: deleteTarget?.displayName })}
142
+ {m.common_market_deleteConfirmDesc({ name: deleteTarget?.displayName ?? '' })}
144
143
  </AlertDialogDescription>
145
144
  </AlertDialogHeader>
146
145
  <AlertDialogFooter>
147
- <AlertDialogCancel>{t('common.cancel')}</AlertDialogCancel>
146
+ <AlertDialogCancel>{m.common_common_cancel()}</AlertDialogCancel>
148
147
  <AlertDialogAction onClick={handleDelete} className="bg-destructive text-white hover:bg-destructive/90">
149
- {t('common.delete')}
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 { useTranslation } from 'react-i18next'
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, t: (key: string, params?: Record<string, number | string>) => string): 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 t('common.timeAgo.seconds', { count: seconds })
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 t('common.timeAgo.minutes', { count: minutes })
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 t('common.timeAgo.hours', { count: hours })
31
+ if (hours < 24) return m.common_common_timeAgo_hours({ count: hours })
32
32
  const days = Math.floor(hours / 24)
33
- return t('common.timeAgo.days', { count: days })
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: t('errorList.columns.lastSeen'),
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, t)}
61
+ {formatRelativeTime(row.original.lastSeen)}
63
62
  </span>
64
63
  ),
65
64
  },
66
65
  {
67
- header: t('errorList.columns.errorName'),
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: t('errorList.columns.errorMessage'),
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: t('errorList.columns.count'),
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: t('errorList.columns.firstSeen'),
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, t)}
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} t={t} />
114
+ <ErrorDetail errors={errors} expandedId={expandedId} />
116
115
  )}
117
116
  </div>
118
117
  )
119
118
  }
120
119
 
121
- function ErrorDetail({ errors, expandedId, t }: { errors: ErrorItem[]; expandedId: string; t: (key: string) => string }) {
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">{t('errorList.detail.title')}</div>
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">{t('errorList.detail.context')}</div>
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">{t('monitoring.healthCards.database')}</p>
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' ? t('monitoring.healthCards.connected') : t('monitoring.healthCards.disconnected')}
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">{t('monitoring.healthCards.latency', { latency: db.latency })}ms</p>
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">{t('monitoring.healthCards.memoryRSS')}</p>
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">{t('monitoring.healthCards.heapUsage')}</p>
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">{t('monitoring.healthCards.cpuCores', { cores: cpu?.cores })}</p>
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">{t('monitoring.healthCards.systemLoad')}</p>
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">{t('monitoring.healthCards.diskUsage')}</p>
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
- {t('monitoring.healthCards.available', { free: disk.free != null ? formatBytes(disk.free) : '-', total: disk.total != null ? formatBytes(disk.total) : '-' })}
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">{t('monitoring.healthCards.runtime')}</p>
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({ t }: { t: (key: string) => string }) {
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={t('metricsStats.benchmarkSource')}
85
+ title={m.common_metricsStats_benchmarkSource()}
86
86
  >
87
87
  <Info className="size-3" />
88
- {t('metricsStats.compareBenchmark')}
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: t('metricsStats.columns.metricName'),
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: t('metricsStats.columns.description'),
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: t('metricsStats.columns.count'),
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
- {t('metricsStats.columns.avg')}
126
- <BenchmarkInfoTooltip t={t} />
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: t('metricsStats.columns.min'),
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: t('metricsStats.columns.max'),
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 } }) => (