@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
@@ -1,6 +1,6 @@
1
1
  import { createLazyFileRoute } from '@tanstack/react-router'
2
+ import { m } from '@/paraglide/messages.js'
2
3
  import { useState, useEffect, useMemo } from 'react'
3
- import { useTranslation } from 'react-i18next'
4
4
  import { defineChart, lineY } from '@tanstack/charts'
5
5
  import { scaleLinear } from '@tanstack/charts/scales/linear'
6
6
  import { scalePoint } from '@tanstack/charts/scales/point'
@@ -61,7 +61,6 @@ function OverviewCard({ title, value, sub }: { title: string; value: string; sub
61
61
  }
62
62
 
63
63
  function AiUsagePage() {
64
- const { t } = useTranslation('common')
65
64
  const [loading, setLoading] = useState(true)
66
65
  const [data, setData] = useState<AiUsageOverview | null>(null)
67
66
  const [wsPage, setWsPage] = useState(1)
@@ -107,7 +106,7 @@ function AiUsagePage() {
107
106
  if (loading && !data) {
108
107
  return (
109
108
  <div className="flex flex-col flex-1 h-full space-y-4 my-4 overflow-hidden">
110
- <PageHeader title={t('monitoring.aiUsage.title')} description={t('monitoring.aiUsage.description')} icon={BarChart3} />
109
+ <PageHeader title={m.common_monitoring_aiUsage_title()} description={m.common_monitoring_aiUsage_description()} icon={BarChart3} />
111
110
  <div className="flex items-center justify-center h-32">
112
111
  <RefreshCw className="size-5 animate-spin text-muted-foreground" />
113
112
  </div>
@@ -118,26 +117,26 @@ function AiUsagePage() {
118
117
  return (
119
118
  <div className="flex flex-col flex-1 h-full space-y-4 my-4 overflow-hidden">
120
119
  <div className="flex items-center justify-between">
121
- <PageHeader title={t('monitoring.aiUsage.title')} description={t('monitoring.aiUsage.description')} icon={BarChart3} />
120
+ <PageHeader title={m.common_monitoring_aiUsage_title()} description={m.common_monitoring_aiUsage_description()} icon={BarChart3} />
122
121
  <button
123
122
  onClick={loadData}
124
123
  disabled={loading}
125
124
  className="inline-flex items-center gap-1 rounded-md border px-3 py-1.5 text-sm font-medium hover:bg-accent transition-colors disabled:opacity-50"
126
125
  >
127
126
  <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />
128
- {t('common.refresh')}
127
+ {m.common_common_refresh()}
129
128
  </button>
130
129
  </div>
131
130
 
132
131
  <div className="grid gap-4 md:grid-cols-3">
133
- <OverviewCard title={t('monitoring.aiUsage.totalCalls')} value={data?.totalCalls?.toLocaleString('en-US') ?? '0'} />
132
+ <OverviewCard title={m.common_monitoring_aiUsage_totalCalls()} value={data?.totalCalls?.toLocaleString('en-US') ?? '0'} />
134
133
  <OverviewCard
135
- title={t('monitoring.aiUsage.totalCost')}
134
+ title={m.common_monitoring_aiUsage_totalCost()}
136
135
  value={data?.totalCostUsd != null ? `$${data.totalCostUsd.toFixed(2)}` : '—'}
137
136
  />
138
137
  <OverviewCard
139
- title={t('monitoring.aiUsage.byokVsManaged')}
140
- value={data?.bySource.map((s) => `${s.source}: ${s.count}`).join(' / ') || '—'}
138
+ title={m.common_monitoring_aiUsage_byokVsManaged()}
139
+ value={data?.bySource.map((s: any) => `${s.source}: ${s.count}`).join(' / ') || '—'}
141
140
  />
142
141
  </div>
143
142
 
@@ -160,7 +159,7 @@ function AiUsagePage() {
160
159
  </CardHeader>
161
160
  <CardContent className="py-0 px-4">
162
161
  <div className="text-xs text-muted-foreground">
163
- {alert.metric}: {alert.value} / {t('monitoring.aiUsage.alert.threshold', { threshold: alert.threshold })} · {t('monitoring.aiUsage.alert.affected', { count: alert.count })}
162
+ {alert.metric}: {alert.value} / {m.common_monitoring_aiUsage_alert_threshold({ threshold: alert.threshold })} · {m.common_monitoring_aiUsage_alert_affected({ count: alert.count })}
164
163
  </div>
165
164
  </CardContent>
166
165
  </Card>
@@ -170,34 +169,34 @@ function AiUsagePage() {
170
169
 
171
170
  <Card>
172
171
  <CardHeader>
173
- <CardTitle>{t('monitoring.aiUsage.dailyTrend')}</CardTitle>
172
+ <CardTitle>{m.common_monitoring_aiUsage_dailyTrend()}</CardTitle>
174
173
  </CardHeader>
175
174
  <CardContent>
176
175
  {data?.trend?.length ? (
177
176
  <div className="grid gap-6 md:grid-cols-2">
178
177
  <div>
179
- <div className="text-sm font-medium text-muted-foreground mb-2">{t('monitoring.aiUsage.trend.calls')}</div>
178
+ <div className="text-sm font-medium text-muted-foreground mb-2">{m.common_monitoring_aiUsage_trend_calls()}</div>
180
179
  <TrendChart
181
180
  data={data.trend.slice(-14)}
182
181
  y="calls"
183
182
  stroke="var(--primary)"
184
- ariaLabel={t('monitoring.aiUsage.trend.callsAria')}
183
+ ariaLabel={m.common_monitoring_aiUsage_trend_callsAria()}
185
184
  />
186
185
  </div>
187
186
  <div>
188
- <div className="text-sm font-medium text-muted-foreground mb-2">{t('monitoring.aiUsage.trend.cost')}</div>
187
+ <div className="text-sm font-medium text-muted-foreground mb-2">{m.common_monitoring_aiUsage_trend_cost()}</div>
189
188
  <TrendChart
190
189
  data={data.trend.slice(-14)}
191
190
  y="cost"
192
191
  stroke="var(--muted-foreground)"
193
192
  strokeDasharray="4 2"
194
- ariaLabel={t('monitoring.aiUsage.trend.costAria')}
193
+ ariaLabel={m.common_monitoring_aiUsage_trend_costAria()}
195
194
  />
196
195
  </div>
197
196
  </div>
198
197
  ) : (
199
198
  <div className="flex items-center justify-center h-[200px] text-muted-foreground text-sm">
200
- {t('common.empty')}
199
+ {m.common_common_empty()}
201
200
  </div>
202
201
  )}
203
202
  </CardContent>
@@ -206,19 +205,19 @@ function AiUsagePage() {
206
205
  <div className="grid gap-4 md:grid-cols-2">
207
206
  <Card>
208
207
  <CardHeader>
209
- <CardTitle>{t('monitoring.aiUsage.top10Workspaces')}</CardTitle>
208
+ <CardTitle>{m.common_monitoring_aiUsage_top10Workspaces()}</CardTitle>
210
209
  </CardHeader>
211
210
  <CardContent>
212
211
  <Table>
213
212
  <TableHeader>
214
213
  <TableRow>
215
- <TableHead>{t('monitoring.aiUsage.workspace')}</TableHead>
216
- <TableHead className="text-right">{t('monitoring.aiUsage.count')}</TableHead>
217
- <TableHead className="text-right">{t('monitoring.aiUsage.cost')}</TableHead>
214
+ <TableHead>{m.common_monitoring_aiUsage_workspace()}</TableHead>
215
+ <TableHead className="text-right">{m.common_monitoring_aiUsage_count()}</TableHead>
216
+ <TableHead className="text-right">{m.common_monitoring_aiUsage_cost()}</TableHead>
218
217
  </TableRow>
219
218
  </TableHeader>
220
219
  <TableBody>
221
- {data?.topWorkspaces.map((w) => (
220
+ {data?.topWorkspaces.map((w: any) => (
222
221
  <TableRow key={w.workspaceId}>
223
222
  <TableCell>{w.workspaceName ?? w.workspaceId.slice(0, 8)}</TableCell>
224
223
  <TableCell className="text-right">{w.count}</TableCell>
@@ -230,7 +229,7 @@ function AiUsagePage() {
230
229
  </TableBody>
231
230
  </Table>
232
231
  <div className="flex items-center justify-between mt-3">
233
- <span className="text-xs text-muted-foreground">{t('monitoring.aiUsage.totalItems', { total: data?.topWorkspacesTotal ?? 0 })}</span>
232
+ <span className="text-xs text-muted-foreground">{m.common_monitoring_aiUsage_totalItems({ total: data?.topWorkspacesTotal ?? 0 })}</span>
234
233
  <div className="flex items-center gap-1">
235
234
  <button
236
235
  onClick={() => setWsPage(Math.max(1, wsPage - 1))}
@@ -254,18 +253,18 @@ function AiUsagePage() {
254
253
 
255
254
  <Card>
256
255
  <CardHeader>
257
- <CardTitle>{t('monitoring.aiUsage.top10Users')}</CardTitle>
256
+ <CardTitle>{m.common_monitoring_aiUsage_top10Users()}</CardTitle>
258
257
  </CardHeader>
259
258
  <CardContent>
260
259
  <Table>
261
260
  <TableHeader>
262
261
  <TableRow>
263
- <TableHead>{t('monitoring.aiUsage.user')}</TableHead>
264
- <TableHead className="text-right">{t('monitoring.aiUsage.count')}</TableHead>
262
+ <TableHead>{m.common_monitoring_aiUsage_user()}</TableHead>
263
+ <TableHead className="text-right">{m.common_monitoring_aiUsage_count()}</TableHead>
265
264
  </TableRow>
266
265
  </TableHeader>
267
266
  <TableBody>
268
- {data?.topUsers.map((u) => (
267
+ {data?.topUsers.map((u: any) => (
269
268
  <TableRow key={u.userId}>
270
269
  <TableCell>{u.userName ?? u.userId.slice(0, 8)}</TableCell>
271
270
  <TableCell className="text-right">{u.count}</TableCell>
@@ -274,7 +273,7 @@ function AiUsagePage() {
274
273
  </TableBody>
275
274
  </Table>
276
275
  <div className="flex items-center justify-between mt-3">
277
- <span className="text-xs text-muted-foreground">{t('monitoring.aiUsage.totalItems', { total: data?.topUsersTotal ?? 0 })}</span>
276
+ <span className="text-xs text-muted-foreground">{m.common_monitoring_aiUsage_totalItems({ total: data?.topUsersTotal ?? 0 })}</span>
278
277
  <div className="flex items-center gap-1">
279
278
  <button
280
279
  onClick={() => setUsrPage(Math.max(1, usrPage - 1))}
@@ -16,8 +16,8 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { createLazyFileRoute } from '@tanstack/react-router'
19
+ import { m } from '@/paraglide/messages.js'
19
20
  import { useState, useEffect } from 'react'
20
- import { useTranslation } from 'react-i18next'
21
21
  import { AlertTriangle, RefreshCw, Trash2 } from 'lucide-react'
22
22
  import { PageHeader } from '@/components/common/page-header'
23
23
  import { ErrorList } from '@/components/monitoring/error-list'
@@ -30,7 +30,6 @@ export const Route = createLazyFileRoute('/_nav/monitoring/errors')({
30
30
  })
31
31
 
32
32
  function MonitoringErrors() {
33
- const { t } = useTranslation('common')
34
33
  const [loading, setLoading] = useState(true)
35
34
  const [data, setData] = useState<any>(null)
36
35
 
@@ -52,17 +51,17 @@ function MonitoringErrors() {
52
51
  const handleReset = async () => {
53
52
  try {
54
53
  await resetMonitoring({ data: { type: 'errors' } })
55
- toast.success(t('monitoring.errors.clearSuccess'))
54
+ toast.success(m.common_monitoring_errors_clearSuccess())
56
55
  loadData()
57
56
  } catch {
58
- toast.error(t('monitoring.errors.clearFailed'))
57
+ toast.error(m.common_monitoring_errors_clearFailed())
59
58
  }
60
59
  }
61
60
 
62
61
  return (
63
62
  <div className="flex flex-col flex-1 h-full space-y-4 my-4 overflow-hidden">
64
63
  <div className="flex items-center justify-between">
65
- <PageHeader title={t('monitoring.errors.title')} description={t('monitoring.errors.description')} icon={AlertTriangle} />
64
+ <PageHeader title={m.common_monitoring_errors_title()} description={m.common_monitoring_errors_description()} icon={AlertTriangle} />
66
65
  <div className="flex items-center gap-2">
67
66
  <button
68
67
  onClick={loadData}
@@ -70,30 +69,30 @@ function MonitoringErrors() {
70
69
  className="inline-flex items-center gap-1 rounded-md border px-3 py-1.5 text-sm font-medium hover:bg-accent transition-colors disabled:opacity-50"
71
70
  >
72
71
  <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />
73
- {t('common.refresh')}
72
+ {m.common_common_refresh()}
74
73
  </button>
75
74
  <button
76
75
  onClick={handleReset}
77
76
  className="inline-flex items-center gap-1 rounded-md border border-red-200 text-red-600 px-3 py-1.5 text-sm font-medium hover:bg-red-50 transition-colors dark:border-red-800 dark:text-red-400 dark:hover:bg-red-950"
78
77
  >
79
78
  <Trash2 className="size-4" />
80
- {t('monitoring.errors.clearData')}
79
+ {m.common_monitoring_errors_clearData()}
81
80
  </button>
82
81
  </div>
83
82
  </div>
84
83
 
85
84
  <div className="grid gap-4 md:grid-cols-3">
86
85
  <div className="rounded-lg border p-4 bg-card">
87
- <p className="text-xs text-muted-foreground">{t('monitoring.errors.stats.errorTypes')}</p>
86
+ <p className="text-xs text-muted-foreground">{m.common_monitoring_errors_stats_errorTypes()}</p>
88
87
  <p className="text-2xl font-bold mt-1">{data?.summary?.uniqueErrors || 0}</p>
89
88
  </div>
90
89
  <div className="rounded-lg border p-4 bg-card">
91
- <p className="text-xs text-muted-foreground">{t('monitoring.errors.stats.totalOccurrences')}</p>
90
+ <p className="text-xs text-muted-foreground">{m.common_monitoring_errors_stats_totalOccurrences()}</p>
92
91
  <p className="text-2xl font-bold mt-1">{data?.summary?.totalOccurrences || 0}</p>
93
92
  </div>
94
93
  <div className="rounded-lg border p-4 bg-card">
95
- <p className="text-xs text-muted-foreground">{t('monitoring.errors.stats.recentError')}</p>
96
- <p className="text-sm font-medium mt-1">{data?.errors?.[0]?.errorName || t('common.none')}</p>
94
+ <p className="text-xs text-muted-foreground">{m.common_monitoring_errors_stats_recentError()}</p>
95
+ <p className="text-sm font-medium mt-1">{data?.errors?.[0]?.errorName || m.common_common_none()}</p>
97
96
  </div>
98
97
  </div>
99
98
 
@@ -104,7 +103,7 @@ function MonitoringErrors() {
104
103
  ) : data?.errors?.length > 0 ? (
105
104
  <ErrorList errors={data.errors} />
106
105
  ) : (
107
- <div className="flex items-center justify-center h-32 text-sm text-muted-foreground">{t('monitoring.errors.empty')}</div>
106
+ <div className="flex items-center justify-center h-32 text-sm text-muted-foreground">{m.common_monitoring_errors_empty()}</div>
108
107
  )}
109
108
  </div>
110
109
  )
@@ -16,8 +16,8 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { createLazyFileRoute } from '@tanstack/react-router'
19
+ import { m } from '@/paraglide/messages.js'
19
20
  import { useState, useEffect, useRef } from 'react'
20
- import { useTranslation } from 'react-i18next'
21
21
  import { formatTimestamp } from '@allbluecn/web-app/lib/datetime'
22
22
  import { HeartPulse, Play, Square, RefreshCw } from 'lucide-react'
23
23
  import { PageHeader } from '@/components/common/page-header'
@@ -32,7 +32,6 @@ export const Route = createLazyFileRoute('/_nav/monitoring/health')({
32
32
  })
33
33
 
34
34
  function MonitoringHealth() {
35
- const { t } = useTranslation('common')
36
35
  const [loading, setLoading] = useState(true)
37
36
  const [data, setData] = useState<any>(null)
38
37
  const [autoEnabled, setAutoEnabled] = useState(true)
@@ -87,9 +86,9 @@ function MonitoringHealth() {
87
86
  try {
88
87
  await updateHealthCheckSettings({ data: { enabled } })
89
88
  setAutoEnabled(enabled)
90
- toast.success(enabled ? t('monitoring.health.autoEnabled') : t('monitoring.health.autoDisabled'))
89
+ toast.success(enabled ? m.common_monitoring_health_autoEnabled() : m.common_monitoring_health_autoDisabled())
91
90
  } catch {
92
- toast.error(t('monitoring.health.operationFailed'))
91
+ toast.error(m.common_monitoring_health_operationFailed())
93
92
  }
94
93
  }
95
94
 
@@ -100,7 +99,7 @@ function MonitoringHealth() {
100
99
  return (
101
100
  <div className="flex flex-col flex-1 h-full space-y-4 my-4 overflow-hidden min-h-0">
102
101
  <div className="flex items-center justify-between">
103
- <PageHeader title={t('monitoring.health.title')} description={t('monitoring.health.description')} icon={HeartPulse} />
102
+ <PageHeader title={m.common_monitoring_health_title()} description={m.common_monitoring_health_description()} icon={HeartPulse} />
104
103
  <div className="flex items-center gap-3">
105
104
  <div className="flex items-center gap-2">
106
105
  {autoEnabled ? (
@@ -108,7 +107,7 @@ function MonitoringHealth() {
108
107
  ) : (
109
108
  <Play className="size-4 text-green-500" />
110
109
  )}
111
- <span className="text-sm">{t('monitoring.health.autoCheck')}</span>
110
+ <span className="text-sm">{m.common_monitoring_health_autoCheck()}</span>
112
111
  <button
113
112
  onClick={() => handleToggleAuto(!autoEnabled)}
114
113
  disabled={autoLoading}
@@ -125,7 +124,7 @@ function MonitoringHealth() {
125
124
  className="inline-flex items-center gap-1 rounded-md border px-3 py-1.5 text-sm font-medium hover:bg-accent transition-colors disabled:opacity-50"
126
125
  >
127
126
  <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />
128
- {t('monitoring.health.checkNow')}
127
+ {m.common_monitoring_health_checkNow()}
129
128
  </button>
130
129
  </div>
131
130
  </div>
@@ -144,8 +143,8 @@ function MonitoringHealth() {
144
143
  {data.current.status === 'healthy' ? '🟢 HEALTHY' : '🔴 UNHEALTHY'}
145
144
  </p>
146
145
  <p className="text-sm text-muted-foreground mt-1">
147
- {t('monitoring.health.runningTime', { hours: Math.floor(data.current.checks.uptime / 3600), minutes: Math.floor((data.current.checks.uptime % 3600) / 60) })}
148
- {autoEnabled && ` · ${t('monitoring.health.nextCheck', { countdown })}`}
146
+ {m.common_monitoring_health_runningTime({ hours: Math.floor(data.current.checks.uptime / 3600), minutes: Math.floor((data.current.checks.uptime % 3600) / 60) })}
147
+ {autoEnabled && ` · ${m.common_monitoring_health_nextCheck({ countdown })}`}
149
148
  </p>
150
149
  </div>
151
150
 
@@ -156,15 +155,15 @@ function MonitoringHealth() {
156
155
  ? 'border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950'
157
156
  : 'border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-950'
158
157
  }`}>
159
- <p className="text-sm font-medium">{t('monitoring.health.database.title')}</p>
158
+ <p className="text-sm font-medium">{m.common_monitoring_health_database_title()}</p>
160
159
  <p className="text-xs text-muted-foreground mt-1">
161
160
  {data.current.checks.database.status === 'connected'
162
- ? t('monitoring.health.database.connected', { latency: data.current.checks.database.latency })
163
- : `${t('monitoring.health.database.failed')}: ${data.current.checks.database.error || t('monitoring.health.database.unknownError')}`}
161
+ ? m.common_monitoring_health_database_connected({ latency: data.current.checks.database.latency })
162
+ : `${m.common_monitoring_health_database_failed()}: ${data.current.checks.database.error || m.common_monitoring_health_database_unknownError()}`}
164
163
  </p>
165
164
  </div>
166
165
  <div className="rounded-lg border p-4">
167
- <p className="text-sm font-medium">{t('monitoring.health.memory.title')}</p>
166
+ <p className="text-sm font-medium">{m.common_monitoring_health_memory_title()}</p>
168
167
  <p className="text-xs text-muted-foreground mt-1">
169
168
  RSS: {data.current.checks.memory.rss}B · Heap: {data.current.checks.memory.heapUsed}B / {data.current.checks.memory.heapTotal}B
170
169
  </p>
@@ -176,7 +175,7 @@ function MonitoringHealth() {
176
175
  {data?.recent?.length > 0 && (
177
176
  <div className="rounded-lg border bg-card flex-1 min-h-0 flex flex-col">
178
177
  <div className="border-b px-4 py-3 shrink-0">
179
- <h3 className="text-sm font-medium">{t('monitoring.health.history')}</h3>
178
+ <h3 className="text-sm font-medium">{m.common_monitoring_health_history()}</h3>
180
179
  </div>
181
180
  <div className="p-4 flex-1 min-h-0">
182
181
  <HistoryTable data={data.recent} />
@@ -197,10 +196,9 @@ interface HealthHistoryRow {
197
196
  }
198
197
 
199
198
  function HistoryTable({ data }: { data: HealthHistoryRow[] }) {
200
- const { t } = useTranslation('common')
201
199
  const columns = [
202
200
  {
203
- header: t('monitoring.health.historyColumns.time'),
201
+ header: m.common_monitoring_health_historyColumns_time(),
204
202
  accessorKey: 'timestamp',
205
203
  meta: { style: { width: 105 } },
206
204
  cell: ({ row }: { row: { original: HealthHistoryRow } }) => (
@@ -210,14 +208,14 @@ function HistoryTable({ data }: { data: HealthHistoryRow[] }) {
210
208
  ),
211
209
  },
212
210
  {
213
- header: t('monitoring.health.historyColumns.status'),
211
+ header: m.common_monitoring_health_historyColumns_status(),
214
212
  accessorKey: 'status',
215
213
  meta: { style: { width: 60 } },
216
214
  cell: ({ row }: { row: { original: HealthHistoryRow } }) => {
217
215
  const s = row.original.status
218
216
  return (
219
217
  <span className={s === 'healthy' ? 'text-green-600' : 'text-red-600'}>
220
- {s === 'healthy' ? '🟢 ' + t('monitoring.health.status.normal') : '🔴 ' + t('monitoring.health.status.abnormal')}
218
+ {s === 'healthy' ? '🟢 ' + m.common_monitoring_health_status_normal() : '🔴 ' + m.common_monitoring_health_status_abnormal()}
221
219
  </span>
222
220
  )
223
221
  },
@@ -236,7 +234,7 @@ function HistoryTable({ data }: { data: HealthHistoryRow[] }) {
236
234
  },
237
235
  },
238
236
  {
239
- header: t('monitoring.health.historyColumns.dbLatency'),
237
+ header: m.common_monitoring_health_historyColumns_dbLatency(),
240
238
  accessorKey: 'dbLatencyMs',
241
239
  meta: { style: { width: 65, textAlign: 'right' } },
242
240
  cell: ({ row }: { row: { original: HealthHistoryRow } }) => (
@@ -244,7 +242,7 @@ function HistoryTable({ data }: { data: HealthHistoryRow[] }) {
244
242
  ),
245
243
  },
246
244
  {
247
- header: t('monitoring.health.historyColumns.uptime'),
245
+ header: m.common_monitoring_health_historyColumns_uptime(),
248
246
  accessorKey: 'uptimeSeconds',
249
247
  meta: { style: { width: 65, textAlign: 'right' } },
250
248
  cell: ({ row }: { row: { original: HealthHistoryRow } }) => {
@@ -16,8 +16,8 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { createLazyFileRoute } from '@tanstack/react-router'
19
+ import { m } from '@/paraglide/messages.js'
19
20
  import { useState, useEffect } from 'react'
20
- import { useTranslation } from 'react-i18next'
21
21
  import { Activity, Filter, RefreshCw, Trash2 } from 'lucide-react'
22
22
  import { PageHeader } from '@/components/common/page-header'
23
23
  import { RequestLogTable } from '@/components/monitoring/request-log-table'
@@ -32,7 +32,6 @@ export const Route = createLazyFileRoute('/_nav/monitoring/logs')({
32
32
  })
33
33
 
34
34
  function MonitoringLogs() {
35
- const { t } = useTranslation('common')
36
35
  const [loading, setLoading] = useState(true)
37
36
  const [logs, setLogs] = useState<any[]>([])
38
37
  const [total, setTotal] = useState(0)
@@ -71,16 +70,16 @@ function MonitoringLogs() {
71
70
  const handleClearOld = async () => {
72
71
  try {
73
72
  await resetMonitoring({ data: { type: 'request-logs', days: 7 } })
74
- toast.success(t('monitoring.logs.clearSuccess'))
73
+ toast.success(m.common_monitoring_logs_clearSuccess())
75
74
  loadLogs()
76
75
  } catch {
77
- toast.error(t('monitoring.logs.clearFailed'))
76
+ toast.error(m.common_monitoring_logs_clearFailed())
78
77
  }
79
78
  }
80
79
 
81
80
  return (
82
81
  <div className="flex flex-col flex-1 h-full space-y-4 my-4 overflow-hidden">
83
- <PageHeader title={t('monitoring.logs.title')} description={t('monitoring.logs.description')} icon={Activity} />
82
+ <PageHeader title={m.common_monitoring_logs_title()} description={m.common_monitoring_logs_description()} icon={Activity} />
84
83
 
85
84
  <div className="flex items-center gap-3 rounded-lg border bg-card p-3 shrink-0">
86
85
  <Filter className="size-4 text-muted-foreground" />
@@ -89,7 +88,7 @@ function MonitoringLogs() {
89
88
  onChange={(e) => { setMethod(e.target.value); setPage(1) }}
90
89
  className="rounded-md border bg-background px-2 py-1 text-sm"
91
90
  >
92
- <option value="">{t('monitoring.logs.filter.allMethods')}</option>
91
+ <option value="">{m.common_monitoring_logs_filter_allMethods()}</option>
93
92
  <option value="GET">GET</option>
94
93
  <option value="POST">POST</option>
95
94
  <option value="PUT">PUT</option>
@@ -100,7 +99,7 @@ function MonitoringLogs() {
100
99
  onChange={(e) => { setStatus(e.target.value); setPage(1) }}
101
100
  className="rounded-md border bg-background px-2 py-1 text-sm"
102
101
  >
103
- <option value="">{t('monitoring.logs.filter.allStatus')}</option>
102
+ <option value="">{m.common_monitoring_logs_filter_allStatus()}</option>
104
103
  <option value="200">200</option>
105
104
  <option value="400">4xx</option>
106
105
  <option value="500">5xx</option>
@@ -109,7 +108,7 @@ function MonitoringLogs() {
109
108
  type="text"
110
109
  value={path}
111
110
  onChange={(e) => { setPath(e.target.value); setPage(1) }}
112
- placeholder={t('monitoring.logs.filter.searchPath')}
111
+ placeholder={m.common_monitoring_logs_filter_searchPath()}
113
112
  className="flex-1 rounded-md border bg-background px-2 py-1 text-sm outline-none focus:ring-2 focus:ring-ring/50"
114
113
  />
115
114
  <button
@@ -118,24 +117,24 @@ function MonitoringLogs() {
118
117
  className="inline-flex items-center gap-1 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:opacity-90 disabled:opacity-50"
119
118
  >
120
119
  <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />
121
- {t('monitoring.logs.query')}
120
+ {m.common_monitoring_logs_query()}
122
121
  </button>
123
122
  <button
124
123
  onClick={handleClearOld}
125
124
  className="inline-flex items-center gap-1 rounded-md border px-3 py-1.5 text-sm font-medium hover:bg-accent transition-colors"
126
125
  >
127
126
  <Trash2 className="size-4" />
128
- {t('monitoring.logs.clear7Days')}
127
+ {m.common_monitoring_logs_clear7Days()}
129
128
  </button>
130
129
  <label className="inline-flex items-center gap-1.5 text-sm shrink-0 cursor-pointer">
131
130
  <Checkbox
132
131
  checked={filterMonitoring}
133
- onCheckedChange={(checked) => {
132
+ onCheckedChange={(checked: boolean) => {
134
133
  setFilterMonitoring(Boolean(checked))
135
134
  setPage(1)
136
135
  }}
137
136
  />
138
- {t('monitoring.logs.filterMonitoring')}
137
+ {m.common_monitoring_logs_filterMonitoring()}
139
138
  </label>
140
139
  </div>
141
140
 
@@ -158,7 +157,7 @@ function MonitoringLogs() {
158
157
  />
159
158
  </div>
160
159
  ) : (
161
- <div className="flex items-center justify-center h-32 text-sm text-muted-foreground shrink-0">{t('monitoring.logs.empty')}</div>
160
+ <div className="flex items-center justify-center h-32 text-sm text-muted-foreground shrink-0">{m.common_monitoring_logs_empty()}</div>
162
161
  )}
163
162
  </div>
164
163
  )
@@ -16,11 +16,11 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { describeMetric } from '@/lib/monitoring-descriptions'
19
+ import { m } from '@/paraglide/messages.js'
19
20
  import { getBenchmark, getBenchmarkSource, compareDuration } from '@/lib/monitoring-benchmarks'
20
21
  import { formatTimestamp } from '@allbluecn/web-app/lib/datetime'
21
22
  import { createLazyFileRoute } from '@tanstack/react-router'
22
23
  import { useState, useEffect } from 'react'
23
- import { useTranslation } from 'react-i18next'
24
24
  import { BarChart3, RefreshCw, Trash2, MemoryStick } from 'lucide-react'
25
25
  import { ArrowDown, ArrowUp } from 'lucide-react'
26
26
  import { PageHeader } from '@/components/common/page-header'
@@ -41,7 +41,6 @@ function formatBytes(bytes: number): string {
41
41
  }
42
42
 
43
43
  function MonitoringMetrics() {
44
- const { t } = useTranslation('common')
45
44
  const [loading, setLoading] = useState(true)
46
45
  const [data, setData] = useState<any>(null)
47
46
 
@@ -63,17 +62,17 @@ function MonitoringMetrics() {
63
62
  const handleReset = async () => {
64
63
  try {
65
64
  await resetMonitoring({ data: { type: 'metrics' } })
66
- toast.success(t('monitoring.metrics.clearSuccess'))
65
+ toast.success(m.common_monitoring_metrics_clearSuccess())
67
66
  loadData()
68
67
  } catch {
69
- toast.error(t('monitoring.metrics.clearFailed'))
68
+ toast.error(m.common_monitoring_metrics_clearFailed())
70
69
  }
71
70
  }
72
71
 
73
72
  return (
74
73
  <div className="flex flex-col flex-1 h-full space-y-4 my-4 overflow-hidden">
75
74
  <div className="flex items-center justify-between">
76
- <PageHeader title={t('monitoring.metrics.title')} description={t('monitoring.metrics.description')} icon={BarChart3} />
75
+ <PageHeader title={m.common_monitoring_metrics_title()} description={m.common_monitoring_metrics_description()} icon={BarChart3} />
77
76
  <div className="flex items-center gap-2">
78
77
  <button
79
78
  onClick={loadData}
@@ -81,14 +80,14 @@ function MonitoringMetrics() {
81
80
  className="inline-flex items-center gap-1 rounded-md border px-3 py-1.5 text-sm font-medium hover:bg-accent transition-colors disabled:opacity-50"
82
81
  >
83
82
  <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />
84
- {t('common.refresh')}
83
+ {m.common_common_refresh()}
85
84
  </button>
86
85
  <button
87
86
  onClick={handleReset}
88
87
  className="inline-flex items-center gap-1 rounded-md border px-3 py-1.5 text-sm font-medium hover:bg-accent transition-colors"
89
88
  >
90
89
  <Trash2 className="size-4" />
91
- {t('monitoring.metrics.clearData')}
90
+ {m.common_monitoring_metrics_clearData()}
92
91
  </button>
93
92
  </div>
94
93
  </div>
@@ -96,7 +95,7 @@ function MonitoringMetrics() {
96
95
  <div className="rounded-lg border bg-card p-4">
97
96
  <div className="flex items-center gap-2 mb-3">
98
97
  <MemoryStick className="size-4 text-muted-foreground" />
99
- <h3 className="text-sm font-medium">{t('monitoring.metrics.memoryUsage')}</h3>
98
+ <h3 className="text-sm font-medium">{m.common_monitoring_metrics_memoryUsage()}</h3>
100
99
  </div>
101
100
  <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
102
101
  <div>
@@ -125,13 +124,13 @@ function MonitoringMetrics() {
125
124
  ) : data?.current && Object.keys(data.current).length > 0 ? (
126
125
  <MetricsStats data={data.current} />
127
126
  ) : (
128
- <div className="flex items-center justify-center h-32 text-sm text-muted-foreground">{t('monitoring.metrics.empty')}</div>
127
+ <div className="flex items-center justify-center h-32 text-sm text-muted-foreground">{m.common_monitoring_metrics_empty()}</div>
129
128
  )}
130
129
 
131
130
  {data?.history?.length > 0 && (
132
131
  <div className="rounded-lg border bg-card flex-1 min-h-0 flex flex-col">
133
132
  <div className="border-b px-4 py-3 shrink-0">
134
- <h3 className="text-sm font-medium">{t('monitoring.metrics.history')}</h3>
133
+ <h3 className="text-sm font-medium">{m.common_monitoring_metrics_history()}</h3>
135
134
  </div>
136
135
  <div className="p-4 flex-1 min-h-0">
137
136
  <MetricsHistoryTable data={data.history} />
@@ -183,10 +182,9 @@ function MetricsBenchmarkCell({ value, metricName }: { value: number; metricName
183
182
  }
184
183
 
185
184
  function MetricsHistoryTable({ data }: { data: MetricsHistoryRow[] }) {
186
- const { t } = useTranslation('common')
187
185
  const columns = [
188
186
  {
189
- header: t('monitoring.metrics.columns.time'),
187
+ header: m.common_monitoring_metrics_columns_time(),
190
188
  accessorKey: 'recordedAt',
191
189
  meta: { style: { width: 30 } },
192
190
  cell: ({ row }: { row: { original: MetricsHistoryRow } }) => (
@@ -196,7 +194,7 @@ function MetricsHistoryTable({ data }: { data: MetricsHistoryRow[] }) {
196
194
  ),
197
195
  },
198
196
  {
199
- header: t('monitoring.metrics.columns.metricName'),
197
+ header: m.common_monitoring_metrics_columns_metricName(),
200
198
  accessorKey: 'metricName',
201
199
  meta: { style: { width: 150 } },
202
200
  cell: ({ row }: { row: { original: MetricsHistoryRow } }) => (
@@ -206,12 +204,12 @@ function MetricsHistoryTable({ data }: { data: MetricsHistoryRow[] }) {
206
204
  ),
207
205
  },
208
206
  {
209
- header: t('monitoring.metrics.columns.count'),
207
+ header: m.common_monitoring_metrics_columns_count(),
210
208
  accessorKey: 'count',
211
209
  meta: { style: { width: 15, textAlign: 'right' } },
212
210
  },
213
211
  {
214
- header: t('monitoring.metrics.columns.avg'),
212
+ header: m.common_monitoring_metrics_columns_avg(),
215
213
  accessorKey: 'avg',
216
214
  meta: { style: { width: 30, textAlign: 'right' } },
217
215
  cell: ({ row }: { row: { original: MetricsHistoryRow } }) => (