@allbluecn/admin-app 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -9
- package/src/components/admin/admin-user-menu.tsx +12 -6
- package/src/components/admin/developer-log.tsx +42 -44
- package/src/components/ai-provider/model-list-editor.tsx +5 -4
- package/src/components/ai-provider/provider-form.tsx +63 -64
- package/src/components/ai-provider/provider-table.tsx +35 -36
- package/src/components/auth/admin-login-form.tsx +23 -24
- package/src/components/auth/oauth-section.tsx +3 -5
- package/src/components/auth/password-input.tsx +2 -3
- package/src/components/common/placeholder-page.tsx +2 -4
- package/src/components/component/component-form-dialog.tsx +48 -49
- package/src/components/component/component-import-dialog.tsx +5 -5
- package/src/components/component/component-table.tsx +13 -14
- package/src/components/data-table/data-table-pagination.tsx +4 -6
- package/src/components/data-table/table-detail-layout.tsx +2 -3
- package/src/components/dependency/changelog-dialog.tsx +3 -4
- package/src/components/dependency/columns.tsx +32 -24
- package/src/components/dependency/dependency-table.tsx +37 -29
- package/src/components/dependency/edit-dialog.tsx +24 -25
- package/src/components/knowledge-public/bookmark-form-dialog.tsx +7 -7
- package/src/components/knowledge-public/compliance-form-dialog.tsx +13 -13
- package/src/components/knowledge-public/legal-form-dialog.tsx +51 -46
- package/src/components/layout/sidebar-02/app-sidebar.tsx +33 -34
- package/src/components/layout/sidebar-02/nav-main.tsx +1 -1
- package/src/components/market/columns.tsx +20 -21
- package/src/components/market/market-app-form.tsx +45 -38
- package/src/components/market/market-app-list.tsx +16 -17
- package/src/components/monitoring/error-list.tsx +18 -19
- package/src/components/monitoring/health-cards.tsx +11 -13
- package/src/components/monitoring/metrics-stats.tsx +11 -12
- package/src/components/monitoring/request-log-table.tsx +8 -9
- package/src/components/ui/pagination.tsx +0 -2
- package/src/components/ui/sidebar.tsx +1 -1
- package/src/components/user/columns.tsx +9 -9
- package/src/components/user/settings-activity-log.tsx +39 -29
- package/src/components/user/settings-profile.tsx +36 -36
- package/src/components/user/user-create-dialog.tsx +4 -4
- package/src/components/user/user-filters.tsx +16 -18
- package/src/lib/i18n/config.ts +0 -32
- package/src/providers/index.tsx +6 -16
- package/src/routes/__root.tsx +9 -23
- package/src/routes/_nav/apps/route.lazy.tsx +2 -3
- package/src/routes/_nav/audit/route.lazy.tsx +10 -11
- package/src/routes/_nav/billing/invoices/route.lazy.tsx +6 -7
- package/src/routes/_nav/billing/route.lazy.tsx +3 -4
- package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.lazy.tsx +16 -17
- package/src/routes/_nav/billing/subscriptions/route.lazy.tsx +9 -10
- package/src/routes/_nav/compliance/route.lazy.tsx +34 -35
- package/src/routes/_nav/components/route.lazy.tsx +6 -6
- package/src/routes/_nav/dashboard/route.lazy.tsx +3 -5
- package/src/routes/_nav/dependencies/route.lazy.tsx +7 -8
- package/src/routes/_nav/docs/route.lazy.tsx +2 -3
- package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +40 -41
- package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +32 -33
- package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +32 -33
- package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +24 -31
- package/src/routes/_nav/licenses/new/route.lazy.tsx +25 -26
- package/src/routes/_nav/licenses/route.lazy.tsx +17 -18
- package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +26 -27
- package/src/routes/_nav/monitoring/errors/route.lazy.tsx +11 -12
- package/src/routes/_nav/monitoring/health/route.lazy.tsx +18 -20
- package/src/routes/_nav/monitoring/logs/route.lazy.tsx +12 -13
- package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +13 -15
- package/src/routes/_nav/monitoring/overview/route.lazy.tsx +12 -13
- package/src/routes/_nav/monitoring/route.lazy.tsx +8 -9
- package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +55 -57
- package/src/routes/_nav/permissions/route.lazy.tsx +48 -50
- package/src/routes/_nav/settings/route.lazy.tsx +2 -3
- package/src/routes/_nav/sso/route.lazy.tsx +20 -18
- package/src/routes/_nav/users/$userId/route.lazy.tsx +86 -82
- package/src/routes/_nav/users/route.lazy.tsx +4 -5
- package/src/routes/setup/route.lazy.tsx +15 -16
- package/src/start.ts +2 -2
- package/src/lib/i18n/client.ts +0 -32
- package/src/lib/i18n/locales/en/admin-ai.json +0 -128
- package/src/lib/i18n/locales/en/admin-billing.json +0 -56
- package/src/lib/i18n/locales/en/admin-users.json +0 -88
- package/src/lib/i18n/locales/en/common.json +0 -877
- package/src/lib/i18n/locales/en/enterprise.json +0 -208
- package/src/lib/i18n/locales/zh/admin-ai.json +0 -128
- package/src/lib/i18n/locales/zh/admin-billing.json +0 -56
- package/src/lib/i18n/locales/zh/admin-users.json +0 -88
- package/src/lib/i18n/locales/zh/common.json +0 -877
- package/src/lib/i18n/locales/zh/enterprise.json +0 -208
- package/src/lib/i18n/server.ts +0 -18
- package/src/lib/i18n/types.ts +0 -5
|
@@ -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={
|
|
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={
|
|
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
|
-
{
|
|
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={
|
|
132
|
+
<OverviewCard title={m.common_monitoring_aiUsage_totalCalls()} value={data?.totalCalls?.toLocaleString('en-US') ?? '0'} />
|
|
134
133
|
<OverviewCard
|
|
135
|
-
title={
|
|
134
|
+
title={m.common_monitoring_aiUsage_totalCost()}
|
|
136
135
|
value={data?.totalCostUsd != null ? `$${data.totalCostUsd.toFixed(2)}` : '—'}
|
|
137
136
|
/>
|
|
138
137
|
<OverviewCard
|
|
139
|
-
title={
|
|
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} / {
|
|
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>{
|
|
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">{
|
|
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={
|
|
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">{
|
|
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={
|
|
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
|
-
{
|
|
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>{
|
|
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>{
|
|
216
|
-
<TableHead className="text-right">{
|
|
217
|
-
<TableHead className="text-right">{
|
|
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">{
|
|
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>{
|
|
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>{
|
|
264
|
-
<TableHead className="text-right">{
|
|
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">{
|
|
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(
|
|
54
|
+
toast.success(m.common_monitoring_errors_clearSuccess())
|
|
56
55
|
loadData()
|
|
57
56
|
} catch {
|
|
58
|
-
toast.error(
|
|
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={
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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">{
|
|
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">{
|
|
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">{
|
|
96
|
-
<p className="text-sm font-medium mt-1">{data?.errors?.[0]?.errorName ||
|
|
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">{
|
|
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 ?
|
|
89
|
+
toast.success(enabled ? m.common_monitoring_health_autoEnabled() : m.common_monitoring_health_autoDisabled())
|
|
91
90
|
} catch {
|
|
92
|
-
toast.error(
|
|
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={
|
|
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">{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
148
|
-
{autoEnabled && ` · ${
|
|
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">{
|
|
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
|
-
?
|
|
163
|
-
: `${
|
|
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">{
|
|
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">{
|
|
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:
|
|
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:
|
|
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' ? '🟢 ' +
|
|
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:
|
|
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:
|
|
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(
|
|
73
|
+
toast.success(m.common_monitoring_logs_clearSuccess())
|
|
75
74
|
loadLogs()
|
|
76
75
|
} catch {
|
|
77
|
-
toast.error(
|
|
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={
|
|
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="">{
|
|
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="">{
|
|
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={
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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">{
|
|
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(
|
|
65
|
+
toast.success(m.common_monitoring_metrics_clearSuccess())
|
|
67
66
|
loadData()
|
|
68
67
|
} catch {
|
|
69
|
-
toast.error(
|
|
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={
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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">{
|
|
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">{
|
|
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">{
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 } }) => (
|