@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
@@ -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, RefreshCw } from 'lucide-react'
22
22
  import { PageHeader } from '@/components/common/page-header'
23
23
  import { HealthCards } from '@/components/monitoring/health-cards'
@@ -32,7 +32,6 @@ export const Route = createLazyFileRoute('/_nav/monitoring/overview')({
32
32
  })
33
33
 
34
34
  function MonitoringOverview() {
35
- const { t } = useTranslation('common')
36
35
  const [loading, setLoading] = useState(true)
37
36
  const [health, setHealth] = useState<any>(null)
38
37
  const [errors, setErrors] = useState<any>(null)
@@ -70,13 +69,13 @@ function MonitoringOverview() {
70
69
  return (
71
70
  <div className="space-y-4 my-4">
72
71
  <div className="flex items-center justify-between">
73
- <PageHeader title={t('monitoring.overview.title')} description={t('monitoring.overview.description')} icon={Activity} />
72
+ <PageHeader title={m.common_monitoring_overview_title()} description={m.common_monitoring_overview_description()} icon={Activity} />
74
73
  <button
75
74
  onClick={loadData}
76
75
  className="inline-flex items-center gap-1.5 rounded-lg border px-3 py-1.5 text-sm font-medium hover:bg-accent transition-colors"
77
76
  >
78
77
  <RefreshCw className="size-4" />
79
- {t('common.refresh')}
78
+ {m.common_common_refresh()}
80
79
  </button>
81
80
  </div>
82
81
 
@@ -85,23 +84,23 @@ function MonitoringOverview() {
85
84
  <div className="grid gap-4 md:grid-cols-2">
86
85
  <div className="rounded-lg border bg-card">
87
86
  <div className="border-b px-4 py-3">
88
- <h3 className="text-sm font-medium">{t('monitoring.overview.requestStats.title')}</h3>
87
+ <h3 className="text-sm font-medium">{m.common_monitoring_overview_requestStats_title()}</h3>
89
88
  </div>
90
89
  <div className="p-4 grid grid-cols-2 gap-3">
91
90
  <div>
92
- <p className="text-xs text-muted-foreground">{t('monitoring.overview.requestStats.totalRequests')}</p>
91
+ <p className="text-xs text-muted-foreground">{m.common_monitoring_overview_requestStats_totalRequests()}</p>
93
92
  <p className="text-lg font-semibold">{health?.requestStats?.total || 0}</p>
94
93
  </div>
95
94
  <div>
96
- <p className="text-xs text-muted-foreground">{t('monitoring.overview.requestStats.avgDuration')}</p>
95
+ <p className="text-xs text-muted-foreground">{m.common_monitoring_overview_requestStats_avgDuration()}</p>
97
96
  <p className="text-lg font-semibold">{health?.requestStats?.avgDuration?.toFixed(1) || 0}ms</p>
98
97
  </div>
99
98
  <div>
100
- <p className="text-xs text-muted-foreground">{t('monitoring.overview.requestStats.maxDuration')}</p>
99
+ <p className="text-xs text-muted-foreground">{m.common_monitoring_overview_requestStats_maxDuration()}</p>
101
100
  <p className="text-lg font-semibold">{health?.requestStats?.maxDuration || 0}ms</p>
102
101
  </div>
103
102
  <div>
104
- <p className="text-xs text-muted-foreground">{t('monitoring.overview.requestStats.errorTypes')}</p>
103
+ <p className="text-xs text-muted-foreground">{m.common_monitoring_overview_requestStats_errorTypes()}</p>
105
104
  <p className="text-lg font-semibold">{health?.errorCount || 0}</p>
106
105
  </div>
107
106
  </div>
@@ -109,13 +108,13 @@ function MonitoringOverview() {
109
108
 
110
109
  <div className="rounded-lg border bg-card">
111
110
  <div className="border-b px-4 py-3">
112
- <h3 className="text-sm font-medium">{t('monitoring.overview.recentErrors.title')}</h3>
111
+ <h3 className="text-sm font-medium">{m.common_monitoring_overview_recentErrors_title()}</h3>
113
112
  </div>
114
113
  <div className="p-4">
115
114
  {errors?.errors?.slice(0, 5).length > 0 ? (
116
115
  <ErrorList errors={errors.errors.slice(0, 5)} />
117
116
  ) : (
118
- <p className="text-sm text-muted-foreground text-center py-4">{t('monitoring.overview.recentErrors.empty')}</p>
117
+ <p className="text-sm text-muted-foreground text-center py-4">{m.common_monitoring_overview_recentErrors_empty()}</p>
119
118
  )}
120
119
  </div>
121
120
  </div>
@@ -123,13 +122,13 @@ function MonitoringOverview() {
123
122
 
124
123
  <div className="rounded-lg border bg-card">
125
124
  <div className="border-b px-4 py-3">
126
- <h3 className="text-sm font-medium">{t('monitoring.overview.metricsSnapshot.title')}</h3>
125
+ <h3 className="text-sm font-medium">{m.common_monitoring_overview_metricsSnapshot_title()}</h3>
127
126
  </div>
128
127
  <div className="p-4">
129
128
  {metrics?.current && Object.keys(metrics.current).length > 0 ? (
130
129
  <MetricsStats data={metrics.current} />
131
130
  ) : (
132
- <p className="text-sm text-muted-foreground text-center py-4">{t('monitoring.overview.metricsSnapshot.empty')}</p>
131
+ <p className="text-sm text-muted-foreground text-center py-4">{m.common_monitoring_overview_metricsSnapshot_empty()}</p>
133
132
  )}
134
133
  </div>
135
134
  </div>
@@ -16,7 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { createLazyFileRoute, Outlet, useLocation } from '@tanstack/react-router'
19
- import { useTranslation } from 'react-i18next'
19
+ import { m } from '@/paraglide/messages.js'
20
20
  import { SidebarTrigger } from '@/components/ui/sidebar'
21
21
  import { Separator } from '@/components/ui/separator'
22
22
 
@@ -25,17 +25,16 @@ export const Route = createLazyFileRoute('/_nav/monitoring')({
25
25
  })
26
26
 
27
27
  function MonitoringLayout() {
28
- const { t } = useTranslation('common')
29
28
  const location = useLocation()
30
29
 
31
30
  const subNavItems = [
32
- { id: 'overview', label: t('monitoring.nav.overview'), path: '/monitoring/overview' },
33
- { id: 'logs', label: t('monitoring.nav.logs'), path: '/monitoring/logs' },
34
- { id: 'errors', label: t('monitoring.nav.errors'), path: '/monitoring/errors' },
35
- { id: 'metrics', label: t('monitoring.nav.metrics'), path: '/monitoring/metrics' },
36
- { id: 'health', label: t('monitoring.nav.health'), path: '/monitoring/health' },
37
- { id: 'sentry', label: t('monitoring.nav.sentry'), path: '/monitoring/sentry' },
38
- { id: 'ai-usage', label: t('monitoring.nav.aiUsage'), path: '/monitoring/ai-usage' },
31
+ { id: 'overview', label: m.common_monitoring_nav_overview(), path: '/monitoring/overview' },
32
+ { id: 'logs', label: m.common_monitoring_nav_logs(), path: '/monitoring/logs' },
33
+ { id: 'errors', label: m.common_monitoring_nav_errors(), path: '/monitoring/errors' },
34
+ { id: 'metrics', label: m.common_monitoring_nav_metrics(), path: '/monitoring/metrics' },
35
+ { id: 'health', label: m.common_monitoring_nav_health(), path: '/monitoring/health' },
36
+ { id: 'sentry', label: m.common_monitoring_nav_sentry(), path: '/monitoring/sentry' },
37
+ { id: 'ai-usage', label: m.common_monitoring_nav_aiUsage(), path: '/monitoring/ai-usage' },
39
38
  ]
40
39
 
41
40
  const currentId = subNavItems.find((item) => location.pathname.startsWith(item.path))?.id || 'overview'
@@ -16,7 +16,7 @@
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  import { createLazyFileRoute, useRouter } from '@tanstack/react-router'
19
- import { useTranslation } from 'react-i18next'
19
+ import { m } from '@/paraglide/messages.js'
20
20
  import * as Sentry from '@sentry/tanstackstart-react'
21
21
  import { BugPlay, ExternalLink, Bug, Activity, AlertTriangle, Eye, EyeOff, Save, RotateCcw, CheckCircle2, XCircle, RefreshCw, ShieldCheck } from 'lucide-react'
22
22
  import { IconBrandSentry } from '@tabler/icons-react';
@@ -38,7 +38,6 @@ type ConfigField = {
38
38
  }
39
39
 
40
40
  function SentryPage() {
41
- const { t } = useTranslation('common')
42
41
  const router = useRouter()
43
42
  const [config, setConfig] = useState<SentryConfig>({ dsn: '', org: '', project: '', authToken: '' })
44
43
  const [loading, setLoading] = useState(true)
@@ -55,32 +54,32 @@ function SentryPage() {
55
54
  placeholder: 'https://<key>@o<orgId>.ingest.sentry.io/<projectId>',
56
55
  secret: false,
57
56
  instruction: [
58
- t('monitoring.sentry.fields.dsn.instruction.step1'),
59
- t('monitoring.sentry.fields.dsn.instruction.step2'),
60
- t('monitoring.sentry.fields.dsn.instruction.step3'),
61
- t('monitoring.sentry.fields.dsn.instruction.step4'),
57
+ m.common_monitoring_sentry_fields_dsn_instruction_step1(),
58
+ m.common_monitoring_sentry_fields_dsn_instruction_step2(),
59
+ m.common_monitoring_sentry_fields_dsn_instruction_step3(),
60
+ m.common_monitoring_sentry_fields_dsn_instruction_step4(),
62
61
  ],
63
62
  },
64
63
  {
65
64
  key: 'org',
66
- label: t('monitoring.sentry.fields.org.label'),
65
+ label: m.common_monitoring_sentry_fields_org_label(),
67
66
  placeholder: 'my-organization',
68
67
  secret: false,
69
68
  instruction: [
70
- t('monitoring.sentry.fields.org.instruction.step1'),
71
- t('monitoring.sentry.fields.org.instruction.step2'),
72
- t('monitoring.sentry.fields.org.instruction.step3'),
69
+ m.common_monitoring_sentry_fields_org_instruction_step1(),
70
+ m.common_monitoring_sentry_fields_org_instruction_step2(),
71
+ m.common_monitoring_sentry_fields_org_instruction_step3(),
73
72
  ],
74
73
  },
75
74
  {
76
75
  key: 'project',
77
- label: t('monitoring.sentry.fields.project.label'),
76
+ label: m.common_monitoring_sentry_fields_project_label(),
78
77
  placeholder: 'my-project',
79
78
  secret: false,
80
79
  instruction: [
81
- t('monitoring.sentry.fields.project.instruction.step1'),
82
- t('monitoring.sentry.fields.project.instruction.step2'),
83
- t('monitoring.sentry.fields.project.instruction.step3'),
80
+ m.common_monitoring_sentry_fields_project_instruction_step1(),
81
+ m.common_monitoring_sentry_fields_project_instruction_step2(),
82
+ m.common_monitoring_sentry_fields_project_instruction_step3(),
84
83
  ],
85
84
  },
86
85
  {
@@ -89,11 +88,11 @@ function SentryPage() {
89
88
  placeholder: 'sntrys_********************',
90
89
  secret: true,
91
90
  instruction: [
92
- t('monitoring.sentry.fields.authToken.instruction.step1'),
93
- t('monitoring.sentry.fields.authToken.instruction.step2'),
94
- t('monitoring.sentry.fields.authToken.instruction.step3'),
95
- t('monitoring.sentry.fields.authToken.instruction.step4'),
96
- t('monitoring.sentry.fields.authToken.instruction.step5'),
91
+ m.common_monitoring_sentry_fields_authToken_instruction_step1(),
92
+ m.common_monitoring_sentry_fields_authToken_instruction_step2(),
93
+ m.common_monitoring_sentry_fields_authToken_instruction_step3(),
94
+ m.common_monitoring_sentry_fields_authToken_instruction_step4(),
95
+ m.common_monitoring_sentry_fields_authToken_instruction_step5(),
97
96
  ],
98
97
  },
99
98
  ]
@@ -104,7 +103,7 @@ function SentryPage() {
104
103
  const cfg = await getSentryConfig()
105
104
  setConfig(cfg)
106
105
  } catch {
107
- toast.error(t('monitoring.sentry.loadFailed'))
106
+ toast.error(m.common_monitoring_sentry_loadFailed())
108
107
  }
109
108
  setLoading(false)
110
109
  }
@@ -116,7 +115,7 @@ function SentryPage() {
116
115
  const dsnConfigured = !!config.dsn || !!import.meta.env.VITE_SENTRY_DSN
117
116
 
118
117
  const updateField = (key: keyof SentryConfig, value: string) => {
119
- setConfig((prev) => ({ ...prev, [key]: value }))
118
+ setConfig((prev: any) => ({ ...prev, [key]: value }))
120
119
  setChanged(true)
121
120
  }
122
121
 
@@ -148,23 +147,23 @@ function SentryPage() {
148
147
  await saveSentryConfig({ data: config })
149
148
  reconfigure(config.dsn)
150
149
  await reconfigureSentryServer({ data: { dsn: config.dsn } })
151
- toast.success(t('monitoring.sentry.saveSuccess'))
150
+ toast.success(m.common_monitoring_sentry_saveSuccess())
152
151
  setChanged(false)
153
152
  router.invalidate()
154
153
  } catch {
155
- toast.error(t('monitoring.sentry.saveFailed'))
154
+ toast.error(m.common_monitoring_sentry_saveFailed())
156
155
  }
157
156
  setSaving(false)
158
157
  }
159
158
 
160
159
  const validateDsn = (dsn: string) => {
161
- if (!dsn) return { ok: false, detail: t('monitoring.sentry.validate.empty') }
162
- if (!dsn.startsWith('https://')) return { ok: false, detail: t('monitoring.sentry.validate.httpsPrefix') }
163
- if (!dsn.includes('.sentry.io')) return { ok: false, detail: t('monitoring.sentry.validate.notSentryIo') }
164
- if (!dsn.includes('@')) return { ok: false, detail: t('monitoring.sentry.validate.missingAt') }
160
+ if (!dsn) return { ok: false, detail: m.common_monitoring_sentry_validate_empty() }
161
+ if (!dsn.startsWith('https://')) return { ok: false, detail: m.common_monitoring_sentry_validate_httpsPrefix() }
162
+ if (!dsn.includes('.sentry.io')) return { ok: false, detail: m.common_monitoring_sentry_validate_notSentryIo() }
163
+ if (!dsn.includes('@')) return { ok: false, detail: m.common_monitoring_sentry_validate_missingAt() }
165
164
  const projectId = dsn.split('/').pop()
166
- if (!projectId || !/^\d+$/.test(projectId)) return { ok: false, detail: t('monitoring.sentry.validate.missingProjectId') }
167
- return { ok: true, detail: t('monitoring.sentry.validate.valid') }
165
+ if (!projectId || !/^\d+$/.test(projectId)) return { ok: false, detail: m.common_monitoring_sentry_validate_missingProjectId() }
166
+ return { ok: true, detail: m.common_monitoring_sentry_validate_valid() }
168
167
  }
169
168
 
170
169
  const verifyConfig = async () => {
@@ -185,18 +184,18 @@ function SentryPage() {
185
184
 
186
185
  const eventId = Sentry.captureException(new Error('Sentry config validation - test error'))
187
186
  if (!eventId) {
188
- setVerifyResult({ ok: false, detail: t('monitoring.sentry.verify.notInitialized') })
187
+ setVerifyResult({ ok: false, detail: m.common_monitoring_sentry_verify_notInitialized() })
189
188
  setVerifying(false)
190
189
  return
191
190
  }
192
191
  const flushed = await Sentry.flush(5000)
193
192
  if (flushed) {
194
- setVerifyResult({ ok: true, detail: t('monitoring.sentry.verify.success', { eventId }) })
193
+ setVerifyResult({ ok: true, detail: m.common_monitoring_sentry_verify_success({ eventId }) })
195
194
  } else {
196
- setVerifyResult({ ok: false, detail: t('monitoring.sentry.verify.timeout') })
195
+ setVerifyResult({ ok: false, detail: m.common_monitoring_sentry_verify_timeout() })
197
196
  }
198
197
  } catch (e) {
199
- setVerifyResult({ ok: false, detail: t('monitoring.sentry.verify.error', { message: (e as Error).message }) })
198
+ setVerifyResult({ ok: false, detail: m.common_monitoring_sentry_verify_error({ message: (e as Error).message }) })
200
199
  }
201
200
  setVerifying(false)
202
201
  }
@@ -207,9 +206,9 @@ function SentryPage() {
207
206
  <div>
208
207
  <h2 className="text-2xl font-bold flex items-center gap-2">
209
208
  <IconBrandSentry stroke={2} />
210
- {t('monitoring.sentry.title')}
209
+ {m.common_monitoring_sentry_title()}
211
210
  </h2>
212
- <p className="mt-1 text-muted-foreground">{t('monitoring.sentry.subtitle')}</p>
211
+ <p className="mt-1 text-muted-foreground">{m.common_monitoring_sentry_subtitle()}</p>
213
212
  </div>
214
213
  <a
215
214
  href="https://sentry.io"
@@ -218,53 +217,53 @@ function SentryPage() {
218
217
  className="inline-flex items-center gap-1.5 rounded-lg border bg-card px-4 py-2 text-sm font-medium hover:bg-accent transition-colors"
219
218
  >
220
219
  <ExternalLink className="size-4" />
221
- {t('monitoring.sentry.openSentry')}
220
+ {m.common_monitoring_sentry_openSentry()}
222
221
  </a>
223
222
  </div>
224
223
 
225
224
  <div className="grid gap-4 md:grid-cols-4">
226
225
  <StatusCard
227
- title={t('monitoring.sentry.statusCards.dsn.title')}
226
+ title={m.common_monitoring_sentry_statusCards_dsn_title()}
228
227
  status={dsnConfigured ? 'active' : 'inactive'}
229
228
  icon={dsnConfigured ? CheckCircle2 : XCircle}
230
- description={dsnConfigured ? t('monitoring.sentry.statusCards.dsn.configured') : t('monitoring.sentry.statusCards.dsn.notConfigured')}
229
+ description={dsnConfigured ? m.common_monitoring_sentry_statusCards_dsn_configured() : m.common_monitoring_sentry_statusCards_dsn_notConfigured()}
231
230
  />
232
231
  <StatusCard
233
- title={t('monitoring.sentry.statusCards.errors.title')}
232
+ title={m.common_monitoring_sentry_statusCards_errors_title()}
234
233
  status={dsnConfigured ? 'active' : 'inactive'}
235
234
  icon={Bug}
236
- description={dsnConfigured ? t('monitoring.sentry.statusCards.errors.enabled') : t('monitoring.sentry.statusCards.errors.disabled')}
235
+ description={dsnConfigured ? m.common_monitoring_sentry_statusCards_errors_enabled() : m.common_monitoring_sentry_statusCards_errors_disabled()}
237
236
  />
238
237
  <StatusCard
239
- title={t('monitoring.sentry.statusCards.performance.title')}
238
+ title={m.common_monitoring_sentry_statusCards_performance_title()}
240
239
  status={dsnConfigured ? 'active' : 'inactive'}
241
240
  icon={Activity}
242
- description={dsnConfigured ? t('monitoring.sentry.statusCards.performance.enabled') : t('monitoring.sentry.statusCards.performance.disabled')}
241
+ description={dsnConfigured ? m.common_monitoring_sentry_statusCards_performance_enabled() : m.common_monitoring_sentry_statusCards_performance_disabled()}
243
242
  />
244
243
  <StatusCard
245
244
  title="Session Replay"
246
245
  status={dsnConfigured ? 'active' : 'inactive'}
247
246
  icon={BugPlay}
248
- description={dsnConfigured ? t('monitoring.sentry.statusCards.replay.enabled') : t('monitoring.sentry.statusCards.replay.disabled')}
247
+ description={dsnConfigured ? m.common_monitoring_sentry_statusCards_replay_enabled() : m.common_monitoring_sentry_statusCards_replay_disabled()}
249
248
  />
250
249
  </div>
251
250
 
252
251
  <div className="rounded-lg border bg-card">
253
252
  <div className="border-b px-6 py-4">
254
- <h3 className="text-lg font-semibold">{t('monitoring.sentry.config.title')}</h3>
253
+ <h3 className="text-lg font-semibold">{m.common_monitoring_sentry_config_title()}</h3>
255
254
  <p className="text-sm text-muted-foreground mt-1">
256
- {t('monitoring.sentry.config.description')}
255
+ {m.common_monitoring_sentry_config_description()}
257
256
  </p>
258
257
  </div>
259
258
  <div className="divide-y">
260
259
  {CONFIG_FIELDS.map((field) => (
261
260
  <ConfigFieldCard
262
- key={field.key}
261
+ key={String(field.key)}
263
262
  field={field}
264
263
  value={config[field.key]}
265
- visible={visibleFields.has(field.key)}
266
- onChange={(v) => updateField(field.key, v)}
267
- onToggleVisible={() => toggleVisible(field.key)}
264
+ visible={visibleFields.has(String(field.key))}
265
+ onChange={(v: string) => updateField(field.key as any, v)}
266
+ onToggleVisible={() => toggleVisible(String(field.key))}
268
267
  />
269
268
  ))}
270
269
  </div>
@@ -276,10 +275,10 @@ function SentryPage() {
276
275
  className="inline-flex items-center gap-1.5 rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:opacity-90 transition-opacity disabled:opacity-50"
277
276
  >
278
277
  {saving ? <RefreshCw className="size-4 animate-spin" /> : <Save className="size-4" />}
279
- {t('monitoring.sentry.saveConfig')}
278
+ {m.common_monitoring_sentry_saveConfig()}
280
279
  </button>
281
280
  {changed && (
282
- <span className="text-xs text-muted-foreground">{t('monitoring.sentry.unsaved')}</span>
281
+ <span className="text-xs text-muted-foreground">{m.common_monitoring_sentry_unsaved()}</span>
283
282
  )}
284
283
  </div>
285
284
  </div>
@@ -299,12 +298,12 @@ function SentryPage() {
299
298
  <div className="rounded-lg border bg-card p-6">
300
299
  <h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
301
300
  <ShieldCheck className="size-5" />
302
- {t('monitoring.sentry.verify.title')}
301
+ {m.common_monitoring_sentry_verify_title()}
303
302
  </h3>
304
303
  <p className="text-sm text-muted-foreground mb-4">
305
- {t('monitoring.sentry.verify.description')}
304
+ {m.common_monitoring_sentry_verify_description()}
306
305
  <h4 className="text-xs text-muted-foreground mb-4">
307
- {t('monitoring.sentry.verify.note')}
306
+ {m.common_monitoring_sentry_verify_note()}
308
307
  </h4>
309
308
  </p>
310
309
  <button
@@ -314,7 +313,7 @@ function SentryPage() {
314
313
  className="inline-flex items-center gap-1.5 rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:opacity-90 transition-opacity disabled:opacity-40"
315
314
  >
316
315
  {verifying ? <RefreshCw className="size-4 animate-spin" /> : <ShieldCheck className="size-4" />}
317
- {t('monitoring.sentry.verify.button')}
316
+ {m.common_monitoring_sentry_verify_button()}
318
317
  </button>
319
318
  </div>
320
319
  </div>
@@ -334,7 +333,6 @@ function ConfigFieldCard({
334
333
  onChange: (v: string) => void
335
334
  onToggleVisible: () => void
336
335
  }) {
337
- const { t } = useTranslation('common')
338
336
  return (
339
337
  <div className="px-6 py-5 space-y-3">
340
338
  <div className="flex items-center justify-between">
@@ -361,7 +359,7 @@ function ConfigFieldCard({
361
359
  </div>
362
360
 
363
361
  <div className="rounded-lg bg-muted/50 p-3">
364
- <p className="text-xs font-medium text-muted-foreground mb-1.5">{t('monitoring.sentry.fields.obtainMethod')}</p>
362
+ <p className="text-xs font-medium text-muted-foreground mb-1.5">{m.common_monitoring_sentry_fields_obtainMethod()}</p>
365
363
  <ol className="list-decimal list-inside space-y-0.5">
366
364
  {field.instruction.map((step, i) => (
367
365
  <li key={i} className="text-xs text-muted-foreground">{step}</li>