@allbluecn/admin-app 0.1.0
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 +86 -0
- package/src/client.tsx +31 -0
- package/src/components/admin/admin-user-menu.tsx +90 -0
- package/src/components/admin/developer-log.tsx +356 -0
- package/src/components/ai-provider/model-list-editor.tsx +147 -0
- package/src/components/ai-provider/provider-form.tsx +434 -0
- package/src/components/ai-provider/provider-icon.tsx +43 -0
- package/src/components/ai-provider/provider-logo.tsx +87 -0
- package/src/components/ai-provider/provider-table.tsx +258 -0
- package/src/components/auth/admin-login-form.tsx +214 -0
- package/src/components/auth/auth-brand-panel.tsx +60 -0
- package/src/components/auth/auth-header.tsx +38 -0
- package/src/components/auth/auth-layout.tsx +66 -0
- package/src/components/auth/auth-providers.tsx +34 -0
- package/src/components/auth/icon-input.tsx +46 -0
- package/src/components/auth/oauth-section.tsx +51 -0
- package/src/components/auth/password-input.tsx +67 -0
- package/src/components/common/page-header.tsx +42 -0
- package/src/components/common/placeholder-page.tsx +42 -0
- package/src/components/component/component-form-dialog.tsx +348 -0
- package/src/components/component/component-import-dialog.tsx +235 -0
- package/src/components/component/component-table.tsx +153 -0
- package/src/components/data-table/data-table-pagination.tsx +114 -0
- package/src/components/data-table/data-table.tsx +218 -0
- package/src/components/data-table/table-detail-layout.tsx +99 -0
- package/src/components/dependency/changelog-dialog.tsx +108 -0
- package/src/components/dependency/columns.tsx +232 -0
- package/src/components/dependency/dependency-table.tsx +368 -0
- package/src/components/dependency/edit-dialog.tsx +200 -0
- package/src/components/knowledge-public/bookmark-form-dialog.tsx +215 -0
- package/src/components/knowledge-public/compliance-form-dialog.tsx +308 -0
- package/src/components/knowledge-public/legal-form-dialog.tsx +315 -0
- package/src/components/layout/sidebar-02/app-sidebar.tsx +160 -0
- package/src/components/layout/sidebar-02/index.tsx +33 -0
- package/src/components/layout/sidebar-02/logo.tsx +40 -0
- package/src/components/layout/sidebar-02/nav-main.tsx +153 -0
- package/src/components/layout/sidebar-02/nav-notifications.tsx +78 -0
- package/src/components/layout/sidebar-02/team-switcher.tsx +106 -0
- package/src/components/magicui/animated-theme-toggler.tsx +272 -0
- package/src/components/magicui/rainbow-button.tsx +78 -0
- package/src/components/market/columns.tsx +151 -0
- package/src/components/market/market-app-form.tsx +237 -0
- package/src/components/market/market-app-list.tsx +156 -0
- package/src/components/monitoring/error-list.tsx +143 -0
- package/src/components/monitoring/health-cards.tsx +152 -0
- package/src/components/monitoring/metrics-stats.tsx +184 -0
- package/src/components/monitoring/request-log-table.tsx +179 -0
- package/src/components/providers/theme-toggle.tsx +59 -0
- package/src/components/settings/avatar/avatar-config.ts +46 -0
- package/src/components/ui/alert-dialog.tsx +216 -0
- package/src/components/ui/alert.tsx +76 -0
- package/src/components/ui/avatar.tsx +129 -0
- package/src/components/ui/badge.tsx +66 -0
- package/src/components/ui/button.tsx +84 -0
- package/src/components/ui/card.tsx +120 -0
- package/src/components/ui/checkbox.tsx +50 -0
- package/src/components/ui/collapsible.tsx +50 -0
- package/src/components/ui/dialog.tsx +185 -0
- package/src/components/ui/dropdown-menu.tsx +286 -0
- package/src/components/ui/field.tsx +255 -0
- package/src/components/ui/input-group.tsx +171 -0
- package/src/components/ui/input.tsx +36 -0
- package/src/components/ui/label.tsx +41 -0
- package/src/components/ui/pagination.tsx +146 -0
- package/src/components/ui/select.tsx +209 -0
- package/src/components/ui/separator.tsx +45 -0
- package/src/components/ui/sheet.tsx +164 -0
- package/src/components/ui/sidebar.tsx +719 -0
- package/src/components/ui/skeleton.tsx +30 -0
- package/src/components/ui/sonner.tsx +37 -0
- package/src/components/ui/switch.tsx +48 -0
- package/src/components/ui/table.tsx +133 -0
- package/src/components/ui/textarea.tsx +35 -0
- package/src/components/ui/tooltip.tsx +74 -0
- package/src/components/user/columns.tsx +153 -0
- package/src/components/user/settings-activity-log.tsx +358 -0
- package/src/components/user/settings-profile/constants.ts +42 -0
- package/src/components/user/settings-profile/types.ts +47 -0
- package/src/components/user/settings-profile.tsx +664 -0
- package/src/components/user/user-create-dialog.tsx +137 -0
- package/src/components/user/user-filters.tsx +117 -0
- package/src/hooks/use-mobile.ts +36 -0
- package/src/instrument.client.ts +34 -0
- package/src/lib/anyhub/capabilities.ts +59 -0
- package/src/lib/anyhub/types.ts +43 -0
- package/src/lib/auth-client.ts +83 -0
- package/src/lib/auth-utils.ts +24 -0
- package/src/lib/auth.config.ts +264 -0
- package/src/lib/avatar.ts +41 -0
- package/src/lib/i18n/client.ts +32 -0
- package/src/lib/i18n/config.ts +40 -0
- package/src/lib/i18n/detect.ts +43 -0
- package/src/lib/i18n/locales/en/admin-ai.json +128 -0
- package/src/lib/i18n/locales/en/admin-billing.json +56 -0
- package/src/lib/i18n/locales/en/admin-users.json +88 -0
- package/src/lib/i18n/locales/en/common.json +877 -0
- package/src/lib/i18n/locales/en/enterprise.json +208 -0
- package/src/lib/i18n/locales/zh/admin-ai.json +128 -0
- package/src/lib/i18n/locales/zh/admin-billing.json +56 -0
- package/src/lib/i18n/locales/zh/admin-users.json +88 -0
- package/src/lib/i18n/locales/zh/common.json +877 -0
- package/src/lib/i18n/locales/zh/enterprise.json +208 -0
- package/src/lib/i18n/server.ts +18 -0
- package/src/lib/i18n/types.ts +5 -0
- package/src/lib/monitoring-benchmarks.ts +85 -0
- package/src/lib/monitoring-descriptions.ts +78 -0
- package/src/lib/prisma.ts +18 -0
- package/src/lib/router-context.ts +38 -0
- package/src/lib/session.ts +38 -0
- package/src/lib/utils.ts +23 -0
- package/src/providers/index.tsx +45 -0
- package/src/routeTree.gen.ts +1199 -0
- package/src/router.tsx +51 -0
- package/src/routes/__root.tsx +170 -0
- package/src/routes/_nav/ai-providers/$id/edit/route.lazy.tsx +74 -0
- package/src/routes/_nav/ai-providers/$id/edit/route.tsx +35 -0
- package/src/routes/_nav/ai-providers/index/route.lazy.tsx +33 -0
- package/src/routes/_nav/ai-providers/new/route.lazy.tsx +31 -0
- package/src/routes/_nav/ai-providers/new/route.tsx +20 -0
- package/src/routes/_nav/ai-providers/route.lazy.tsx +26 -0
- package/src/routes/_nav/ai-providers/route.tsx +31 -0
- package/src/routes/_nav/apps/route.lazy.tsx +30 -0
- package/src/routes/_nav/apps/route.tsx +20 -0
- package/src/routes/_nav/audit/route.lazy.tsx +100 -0
- package/src/routes/_nav/audit/route.tsx +25 -0
- package/src/routes/_nav/billing/invoices/route.lazy.tsx +86 -0
- package/src/routes/_nav/billing/invoices/route.tsx +25 -0
- package/src/routes/_nav/billing/route.lazy.tsx +48 -0
- package/src/routes/_nav/billing/route.tsx +27 -0
- package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.lazy.tsx +160 -0
- package/src/routes/_nav/billing/subscriptions/$subscriptionId/route.tsx +25 -0
- package/src/routes/_nav/billing/subscriptions/route.lazy.tsx +113 -0
- package/src/routes/_nav/billing/subscriptions/route.tsx +28 -0
- package/src/routes/_nav/compliance/route.lazy.tsx +184 -0
- package/src/routes/_nav/compliance/route.tsx +25 -0
- package/src/routes/_nav/components/route.lazy.tsx +184 -0
- package/src/routes/_nav/components/route.tsx +40 -0
- package/src/routes/_nav/dashboard/route.lazy.tsx +33 -0
- package/src/routes/_nav/dashboard/route.tsx +20 -0
- package/src/routes/_nav/dependencies/route.lazy.tsx +66 -0
- package/src/routes/_nav/dependencies/route.tsx +32 -0
- package/src/routes/_nav/developer-logs/route.lazy.tsx +6 -0
- package/src/routes/_nav/developer-logs/route.tsx +14 -0
- package/src/routes/_nav/docs/route.lazy.tsx +30 -0
- package/src/routes/_nav/docs/route.tsx +20 -0
- package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +355 -0
- package/src/routes/_nav/knowledge-public/bookmarks/route.tsx +40 -0
- package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +245 -0
- package/src/routes/_nav/knowledge-public/compliance/route.tsx +40 -0
- package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +242 -0
- package/src/routes/_nav/knowledge-public/legal/route.tsx +40 -0
- package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +235 -0
- package/src/routes/_nav/licenses/$licenseId/route.tsx +25 -0
- package/src/routes/_nav/licenses/new/route.lazy.tsx +237 -0
- package/src/routes/_nav/licenses/route.lazy.tsx +143 -0
- package/src/routes/_nav/licenses/route.tsx +25 -0
- package/src/routes/_nav/market/route.lazy.tsx +31 -0
- package/src/routes/_nav/market/route.tsx +20 -0
- package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +305 -0
- package/src/routes/_nav/monitoring/ai-usage/route.tsx +3 -0
- package/src/routes/_nav/monitoring/errors/route.lazy.tsx +111 -0
- package/src/routes/_nav/monitoring/health/route.lazy.tsx +260 -0
- package/src/routes/_nav/monitoring/logs/route.lazy.tsx +165 -0
- package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +240 -0
- package/src/routes/_nav/monitoring/overview/route.lazy.tsx +138 -0
- package/src/routes/_nav/monitoring/route.lazy.tsx +68 -0
- package/src/routes/_nav/monitoring/route.tsx +20 -0
- package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +399 -0
- package/src/routes/_nav/monitoring/sentry/route.tsx +20 -0
- package/src/routes/_nav/permissions/route.lazy.tsx +504 -0
- package/src/routes/_nav/permissions/route.tsx +26 -0
- package/src/routes/_nav/route.lazy.tsx +31 -0
- package/src/routes/_nav/route.tsx +34 -0
- package/src/routes/_nav/settings/route.lazy.tsx +30 -0
- package/src/routes/_nav/settings/route.tsx +20 -0
- package/src/routes/_nav/sso/route.lazy.tsx +145 -0
- package/src/routes/_nav/sso/route.tsx +25 -0
- package/src/routes/_nav/users/$userId/route.lazy.tsx +432 -0
- package/src/routes/_nav/users/$userId/route.tsx +27 -0
- package/src/routes/_nav/users/-components/users-table.tsx +38 -0
- package/src/routes/_nav/users/route.lazy.tsx +74 -0
- package/src/routes/_nav/users/route.tsx +43 -0
- package/src/routes/api/compliance-download/$jobId.ts +35 -0
- package/src/routes/api/observability/errors.ts +41 -0
- package/src/routes/api/observability/health.ts +30 -0
- package/src/routes/api.auth.$.ts +31 -0
- package/src/routes/api.sentry-test.ts +33 -0
- package/src/routes/index/route.lazy.tsx +22 -0
- package/src/routes/index/route.tsx +24 -0
- package/src/routes/login/route.lazy.tsx +46 -0
- package/src/routes/login/route.tsx +28 -0
- package/src/routes/sentry-tunnel.ts +61 -0
- package/src/routes/setup/route.lazy.tsx +94 -0
- package/src/routes/setup/route.tsx +28 -0
- package/src/server/__tests__/admin-ai-alerts.test.ts +113 -0
- package/src/server/__tests__/licenses-issue.test.ts +98 -0
- package/src/server/admin-ai-alerts.ts +177 -0
- package/src/server/admin-ai-usage.ts +134 -0
- package/src/server/auth-helpers.ts +51 -0
- package/src/server/entitlements.ts +24 -0
- package/src/server/knowledge-public/canonical-id.ts +55 -0
- package/src/server/knowledge-public/github-sync.ts +86 -0
- package/src/server/license-issue.ts +78 -0
- package/src/server/middlewares/request.ts +26 -0
- package/src/server/observability/debug-middleware.ts +39 -0
- package/src/server/observability/error-reporter.ts +99 -0
- package/src/server/observability/health-check.ts +140 -0
- package/src/server/observability/index.ts +33 -0
- package/src/server/observability/logger.ts +49 -0
- package/src/server/observability/metrics.ts +105 -0
- package/src/server/observability/request-middleware.ts +125 -0
- package/src/server/observability/timing.ts +42 -0
- package/src/server/pro-services.ts +32 -0
- package/src/server/serverFns/admin-billing.ts +94 -0
- package/src/server/serverFns/ai-provider/create.ts +92 -0
- package/src/server/serverFns/ai-provider/delete.ts +42 -0
- package/src/server/serverFns/ai-provider/get.ts +35 -0
- package/src/server/serverFns/ai-provider/list.ts +92 -0
- package/src/server/serverFns/ai-provider/update.ts +73 -0
- package/src/server/serverFns/anyhub/market.ts +64 -0
- package/src/server/serverFns/audit.ts +57 -0
- package/src/server/serverFns/auth/session.ts +26 -0
- package/src/server/serverFns/auth/setup.ts +73 -0
- package/src/server/serverFns/compliance.ts +85 -0
- package/src/server/serverFns/components/import.ts +197 -0
- package/src/server/serverFns/components/list.ts +51 -0
- package/src/server/serverFns/components/mutations.ts +84 -0
- package/src/server/serverFns/dependency/helpers.ts +245 -0
- package/src/server/serverFns/dependency/list.ts +127 -0
- package/src/server/serverFns/dependency/sync.ts +123 -0
- package/src/server/serverFns/dependency/update.ts +51 -0
- package/src/server/serverFns/developer-log-auto-generate.ts +176 -0
- package/src/server/serverFns/developer-logs.ts +132 -0
- package/src/server/serverFns/knowledge-public/bookmarks/create.ts +53 -0
- package/src/server/serverFns/knowledge-public/bookmarks/delete.ts +31 -0
- package/src/server/serverFns/knowledge-public/bookmarks/detail.ts +32 -0
- package/src/server/serverFns/knowledge-public/bookmarks/list.ts +66 -0
- package/src/server/serverFns/knowledge-public/bookmarks/sync-all-github.ts +56 -0
- package/src/server/serverFns/knowledge-public/bookmarks/update.ts +50 -0
- package/src/server/serverFns/knowledge-public/compliance/create.ts +58 -0
- package/src/server/serverFns/knowledge-public/compliance/delete.ts +31 -0
- package/src/server/serverFns/knowledge-public/compliance/detail.ts +32 -0
- package/src/server/serverFns/knowledge-public/compliance/list.ts +59 -0
- package/src/server/serverFns/knowledge-public/compliance/update.ts +53 -0
- package/src/server/serverFns/knowledge-public/legal/create.ts +62 -0
- package/src/server/serverFns/knowledge-public/legal/delete.ts +31 -0
- package/src/server/serverFns/knowledge-public/legal/detail.ts +32 -0
- package/src/server/serverFns/knowledge-public/legal/list.ts +60 -0
- package/src/server/serverFns/knowledge-public/legal/update.ts +55 -0
- package/src/server/serverFns/licenses.ts +92 -0
- package/src/server/serverFns/locale.ts +10 -0
- package/src/server/serverFns/monitoring/check-ai-alerts.ts +13 -0
- package/src/server/serverFns/monitoring/get-ai-usage-overview.ts +25 -0
- package/src/server/serverFns/monitoring/get-errors.ts +43 -0
- package/src/server/serverFns/monitoring/get-health-settings.ts +35 -0
- package/src/server/serverFns/monitoring/get-health.ts +55 -0
- package/src/server/serverFns/monitoring/get-metrics.ts +46 -0
- package/src/server/serverFns/monitoring/get-request-logs.ts +66 -0
- package/src/server/serverFns/monitoring/report-error.ts +55 -0
- package/src/server/serverFns/monitoring/reset.ts +70 -0
- package/src/server/serverFns/permissions.ts +214 -0
- package/src/server/serverFns/sentry/config.ts +88 -0
- package/src/server/serverFns/sso-config.ts +83 -0
- package/src/server/serverFns/user/create.ts +40 -0
- package/src/server/serverFns/user/delete.ts +29 -0
- package/src/server/serverFns/user/detail.ts +45 -0
- package/src/server/serverFns/user/list.ts +56 -0
- package/src/server/serverFns/user/reset-password.ts +28 -0
- package/src/server/serverFns/user/update-role.ts +26 -0
- package/src/server/serverFns/user/update-status.ts +26 -0
- package/src/server.ts +27 -0
- package/src/start.ts +85 -0
- package/src/styles/_keyframe-animations.scss +91 -0
- package/src/styles/_variables.scss +296 -0
- package/src/styles/globals.css +200 -0
- package/src/types/start-authjs.d.ts +27 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import { useTranslation } from 'react-i18next'
|
|
19
|
+
import { DataTable, type AdminColumnDef } from '@/components/data-table/data-table'
|
|
20
|
+
import { describeMetric } from '@/lib/monitoring-descriptions'
|
|
21
|
+
import { getBenchmark, getBenchmarkSource, compareDuration } from '@/lib/monitoring-benchmarks'
|
|
22
|
+
import { ArrowDown, ArrowUp, Info } from 'lucide-react'
|
|
23
|
+
|
|
24
|
+
interface MetricRow {
|
|
25
|
+
metric: string
|
|
26
|
+
path: string
|
|
27
|
+
count: number
|
|
28
|
+
avg: number
|
|
29
|
+
p50: number
|
|
30
|
+
p95: number
|
|
31
|
+
p99: number
|
|
32
|
+
min: number
|
|
33
|
+
max: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface MetricsStatsProps {
|
|
37
|
+
data: Record<
|
|
38
|
+
string,
|
|
39
|
+
{ count: number; avg: number; p50: number; p95: number; p99: number; min: number; max: number }
|
|
40
|
+
>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface BenchmarkCellProps {
|
|
44
|
+
value: number
|
|
45
|
+
metric: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function BenchmarkCell({ value, metric }: BenchmarkCellProps) {
|
|
49
|
+
const path = metric.replace(/^request\./, '')
|
|
50
|
+
const benchmark = getBenchmark(path)
|
|
51
|
+
const comp = compareDuration(value, benchmark)
|
|
52
|
+
|
|
53
|
+
const isGood = value <= benchmark
|
|
54
|
+
const diff = Math.abs(value - benchmark)
|
|
55
|
+
const pct = Math.round((diff / benchmark) * 100)
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<span className="inline-flex items-center gap-1">
|
|
59
|
+
{value.toFixed(1)}ms
|
|
60
|
+
{isGood ? (
|
|
61
|
+
<span
|
|
62
|
+
className="inline-flex items-center gap-0.5 text-green-600 dark:text-green-400"
|
|
63
|
+
title={`${getBenchmarkSource(path)}: ${benchmark}ms P95`}
|
|
64
|
+
>
|
|
65
|
+
<ArrowUp className="size-3" />
|
|
66
|
+
<span className="text-[10px]">{pct}%</span>
|
|
67
|
+
</span>
|
|
68
|
+
) : (
|
|
69
|
+
<span
|
|
70
|
+
className="inline-flex items-center gap-0.5 text-red-600 dark:text-red-400"
|
|
71
|
+
title={`${getBenchmarkSource(path)}: ${benchmark}ms P95`}
|
|
72
|
+
>
|
|
73
|
+
<ArrowDown className="size-3" strokeWidth={3} />
|
|
74
|
+
<span className="text-[10px] font-bold">{pct}%</span>
|
|
75
|
+
</span>
|
|
76
|
+
)}
|
|
77
|
+
</span>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function BenchmarkInfoTooltip({ t }: { t: (key: string) => string }) {
|
|
82
|
+
return (
|
|
83
|
+
<span
|
|
84
|
+
className="inline-flex items-center gap-1 text-xs text-muted-foreground"
|
|
85
|
+
title={t('metricsStats.benchmarkSource')}
|
|
86
|
+
>
|
|
87
|
+
<Info className="size-3" />
|
|
88
|
+
{t('metricsStats.compareBenchmark')}
|
|
89
|
+
</span>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function MetricsStats({ data }: MetricsStatsProps) {
|
|
94
|
+
const { t } = useTranslation('common')
|
|
95
|
+
|
|
96
|
+
const columns: AdminColumnDef<MetricRow>[] = [
|
|
97
|
+
{
|
|
98
|
+
header: t('metricsStats.columns.metricName'),
|
|
99
|
+
accessorKey: 'metric',
|
|
100
|
+
meta: { style: { width: 160 } },
|
|
101
|
+
cell: ({ row }: { row: { original: MetricRow } }) => (
|
|
102
|
+
<span className="font-mono text-xs block truncate">
|
|
103
|
+
{row.original.metric}
|
|
104
|
+
</span>
|
|
105
|
+
),
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
header: t('metricsStats.columns.description'),
|
|
109
|
+
accessorKey: 'metric',
|
|
110
|
+
meta: { style: { width: 30 } },
|
|
111
|
+
cell: ({ row }: { row: { original: MetricRow } }) => (
|
|
112
|
+
<span className="text-xs text-muted-foreground block truncate">
|
|
113
|
+
{describeMetric(row.original.metric)}
|
|
114
|
+
</span>
|
|
115
|
+
),
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
header: t('metricsStats.columns.count'),
|
|
119
|
+
accessorKey: 'count',
|
|
120
|
+
meta: { style: { width: 18, textAlign: 'right' } },
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
header: () => (
|
|
124
|
+
<span className="flex items-center gap-1">
|
|
125
|
+
{t('metricsStats.columns.avg')}
|
|
126
|
+
<BenchmarkInfoTooltip t={t} />
|
|
127
|
+
</span>
|
|
128
|
+
),
|
|
129
|
+
accessorKey: 'avg',
|
|
130
|
+
meta: { style: { width: 40, textAlign: 'right' } },
|
|
131
|
+
cell: ({ row }: { row: { original: MetricRow } }) => (
|
|
132
|
+
<BenchmarkCell value={row.original.avg} metric={row.original.metric} />
|
|
133
|
+
),
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
header: 'P50',
|
|
137
|
+
accessorKey: 'p50',
|
|
138
|
+
meta: { style: { width: 40, textAlign: 'right' } },
|
|
139
|
+
cell: ({ row }: { row: { original: MetricRow } }) => (
|
|
140
|
+
<BenchmarkCell value={row.original.p50} metric={row.original.metric} />
|
|
141
|
+
),
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
header: 'P95',
|
|
145
|
+
accessorKey: 'p95',
|
|
146
|
+
meta: { style: { width: 40, textAlign: 'right' } },
|
|
147
|
+
cell: ({ row }: { row: { original: MetricRow } }) => (
|
|
148
|
+
<BenchmarkCell value={row.original.p95} metric={row.original.metric} />
|
|
149
|
+
),
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
header: 'P99',
|
|
153
|
+
accessorKey: 'p99',
|
|
154
|
+
meta: { style: { width: 40, textAlign: 'right' } },
|
|
155
|
+
cell: ({ row }: { row: { original: MetricRow } }) => (
|
|
156
|
+
<BenchmarkCell value={row.original.p99} metric={row.original.metric} />
|
|
157
|
+
),
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
header: t('metricsStats.columns.min'),
|
|
161
|
+
accessorKey: 'min',
|
|
162
|
+
meta: { style: { width: 30, textAlign: 'right' } },
|
|
163
|
+
cell: ({ row }: { row: { original: MetricRow } }) => (
|
|
164
|
+
<span>{row.original.min.toFixed(1)}ms</span>
|
|
165
|
+
),
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
header: t('metricsStats.columns.max'),
|
|
169
|
+
accessorKey: 'max',
|
|
170
|
+
meta: { style: { width: 30, textAlign: 'right' } },
|
|
171
|
+
cell: ({ row }: { row: { original: MetricRow } }) => (
|
|
172
|
+
<span>{row.original.max.toFixed(1)}ms</span>
|
|
173
|
+
),
|
|
174
|
+
},
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
const rows: MetricRow[] = Object.entries(data).map(([metric, stats]) => ({
|
|
178
|
+
metric,
|
|
179
|
+
path: metric.replace(/^request\./, ''),
|
|
180
|
+
...stats,
|
|
181
|
+
}))
|
|
182
|
+
|
|
183
|
+
return <DataTable columns={columns} data={rows} />
|
|
184
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import type React from 'react'
|
|
19
|
+
import { useTranslation } from 'react-i18next'
|
|
20
|
+
import { DataTable, type AdminColumnDef } from '@/components/data-table/data-table'
|
|
21
|
+
import { describePath } from '@/lib/monitoring-descriptions'
|
|
22
|
+
import { getBenchmark, getBenchmarkSource, compareDuration } from '@/lib/monitoring-benchmarks'
|
|
23
|
+
import { ArrowDown, ArrowUp, Info } from 'lucide-react'
|
|
24
|
+
|
|
25
|
+
interface RequestLogEntry {
|
|
26
|
+
id: string
|
|
27
|
+
timestamp: string
|
|
28
|
+
method: string
|
|
29
|
+
path: string
|
|
30
|
+
status: number
|
|
31
|
+
duration: number
|
|
32
|
+
ip?: string | null
|
|
33
|
+
userAgent?: string | null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface RequestLogTableProps {
|
|
37
|
+
logs: RequestLogEntry[]
|
|
38
|
+
actions?: React.ReactNode
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const methodColors: Record<string, string> = {
|
|
42
|
+
GET: 'text-green-600 dark:text-green-400',
|
|
43
|
+
POST: 'text-blue-600 dark:text-blue-400',
|
|
44
|
+
PUT: 'text-yellow-600 dark:text-yellow-400',
|
|
45
|
+
DELETE: 'text-red-600 dark:text-red-400',
|
|
46
|
+
PATCH: 'text-purple-600 dark:text-purple-400',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function DurationCell({ path, duration }: { path: string; duration: number }) {
|
|
50
|
+
const benchmark = getBenchmark(path)
|
|
51
|
+
const comp = compareDuration(duration, benchmark)
|
|
52
|
+
|
|
53
|
+
const isGood = duration <= benchmark
|
|
54
|
+
const pct = Math.round((Math.abs(duration - benchmark) / benchmark) * 100)
|
|
55
|
+
const color =
|
|
56
|
+
duration > 1000 ? 'text-red-600' : duration > 300 ? 'text-yellow-600' : 'text-green-600'
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<span className="inline-flex items-center gap-1">
|
|
60
|
+
<span className={`text-xs font-medium ${color}`}>{duration}ms</span>
|
|
61
|
+
{isGood ? (
|
|
62
|
+
<span
|
|
63
|
+
className="inline-flex items-center gap-0.5 text-green-600 dark:text-green-400"
|
|
64
|
+
title={`${getBenchmarkSource(path)}: ${benchmark}ms P95`}
|
|
65
|
+
>
|
|
66
|
+
<ArrowUp className="size-3" />
|
|
67
|
+
<span className="text-[10px]">{pct}%</span>
|
|
68
|
+
</span>
|
|
69
|
+
) : (
|
|
70
|
+
<span
|
|
71
|
+
className="inline-flex items-center gap-0.5 text-red-600 dark:text-red-400"
|
|
72
|
+
title={`${getBenchmarkSource(path)}: ${benchmark}ms P95`}
|
|
73
|
+
>
|
|
74
|
+
<ArrowDown className="size-3" strokeWidth={3} />
|
|
75
|
+
<span className="text-[10px] font-bold">{pct}%</span>
|
|
76
|
+
</span>
|
|
77
|
+
)}
|
|
78
|
+
</span>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function RequestLogTable({ logs, actions }: RequestLogTableProps) {
|
|
83
|
+
const { t } = useTranslation('common')
|
|
84
|
+
|
|
85
|
+
const columns: AdminColumnDef<RequestLogEntry>[] = [
|
|
86
|
+
{
|
|
87
|
+
header: t('requestLogTable.columns.time'),
|
|
88
|
+
accessorKey: 'timestamp',
|
|
89
|
+
meta: { style: { width: 20, textAlign: 'center' } },
|
|
90
|
+
cell: ({ row }: { row: { original: RequestLogEntry } }) => (
|
|
91
|
+
<span className="text-xs">
|
|
92
|
+
{new Date(row.original.timestamp).toLocaleTimeString('zh-CN')}
|
|
93
|
+
</span>
|
|
94
|
+
),
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
header: t('requestLogTable.columns.path'),
|
|
98
|
+
accessorKey: 'path',
|
|
99
|
+
meta: { style: { width: 160 } },
|
|
100
|
+
cell: ({ row }: { row: { original: RequestLogEntry } }) => (
|
|
101
|
+
<span className="font-mono text-xs block truncate">
|
|
102
|
+
{row.original.path}
|
|
103
|
+
</span>
|
|
104
|
+
),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
header: t('requestLogTable.columns.description'),
|
|
108
|
+
accessorKey: 'path',
|
|
109
|
+
meta: { style: { width: 28 } },
|
|
110
|
+
cell: ({ row }: { row: { original: RequestLogEntry } }) => (
|
|
111
|
+
<span className="text-xs text-muted-foreground block truncate">
|
|
112
|
+
{describePath(row.original.path)}
|
|
113
|
+
</span>
|
|
114
|
+
),
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
header: 'User-Agent',
|
|
118
|
+
accessorKey: 'userAgent',
|
|
119
|
+
meta: { style: { width: 48 } },
|
|
120
|
+
cell: ({ row }: { row: { original: RequestLogEntry } }) => (
|
|
121
|
+
<span className="text-xs block truncate">
|
|
122
|
+
{row.original.userAgent || '-'}
|
|
123
|
+
</span>
|
|
124
|
+
),
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
header: t('requestLogTable.columns.method'),
|
|
128
|
+
accessorKey: 'method',
|
|
129
|
+
meta: { style: { width: 14, textAlign: 'right' } },
|
|
130
|
+
cell: ({ row }: { row: { original: RequestLogEntry } }) => {
|
|
131
|
+
const method = row.original.method
|
|
132
|
+
return (
|
|
133
|
+
<span className={`font-mono text-xs font-medium ${methodColors[method] || ''}`}>
|
|
134
|
+
{method}
|
|
135
|
+
</span>
|
|
136
|
+
)
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
header: t('requestLogTable.columns.status'),
|
|
141
|
+
accessorKey: 'status',
|
|
142
|
+
meta: { style: { width: 14, textAlign: 'right' } },
|
|
143
|
+
cell: ({ row }: { row: { original: RequestLogEntry } }) => {
|
|
144
|
+
const status = row.original.status
|
|
145
|
+
const color =
|
|
146
|
+
status >= 500 ? 'text-red-600' : status >= 400 ? 'text-yellow-600' : 'text-green-600'
|
|
147
|
+
return <span className={`text-xs font-medium ${color}`}>{status}</span>
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
header: () => (
|
|
152
|
+
<span className="flex items-center gap-1">
|
|
153
|
+
{t('requestLogTable.columns.duration')}
|
|
154
|
+
<Info
|
|
155
|
+
className="size-3 text-muted-foreground"
|
|
156
|
+
data-tooltip={t('requestLogTable.compareBenchmark')}
|
|
157
|
+
/>
|
|
158
|
+
</span>
|
|
159
|
+
),
|
|
160
|
+
accessorKey: 'duration',
|
|
161
|
+
meta: { style: { width: 28, textAlign: 'right' } },
|
|
162
|
+
cell: ({ row }: { row: { original: RequestLogEntry } }) => (
|
|
163
|
+
<DurationCell path={row.original.path} duration={row.original.duration} />
|
|
164
|
+
),
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
header: 'IP',
|
|
168
|
+
accessorKey: 'ip',
|
|
169
|
+
meta: { style: { width: 20, textAlign: 'right' } },
|
|
170
|
+
cell: ({ row }: { row: { original: RequestLogEntry } }) => (
|
|
171
|
+
<span className="font-mono text-xs text-muted-foreground">
|
|
172
|
+
{row.original.ip || '-'}
|
|
173
|
+
</span>
|
|
174
|
+
),
|
|
175
|
+
},
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
return <DataTable columns={columns} data={logs} actions={actions} />
|
|
179
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
import { useTheme } from "next-themes";
|
|
20
|
+
import { Moon, Sun } from "lucide-react";
|
|
21
|
+
import { Button } from "@/components/ui/button";
|
|
22
|
+
import { useEffect, useState } from "react";
|
|
23
|
+
|
|
24
|
+
export function ThemeToggle() {
|
|
25
|
+
const { theme, setTheme } = useTheme();
|
|
26
|
+
const [mounted, setMounted] = useState(false);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
setMounted(true);
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
if (!mounted) {
|
|
33
|
+
return (
|
|
34
|
+
<Button variant="ghost" size="icon" className="h-8 w-8 rounded-full">
|
|
35
|
+
<Sun className="h-4 w-4" />
|
|
36
|
+
</Button>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const toggleTheme = () => {
|
|
41
|
+
setTheme(theme === "dark" ? "light" : "dark");
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Button
|
|
46
|
+
variant="ghost"
|
|
47
|
+
size="icon"
|
|
48
|
+
className="h-8 w-8 rounded-full"
|
|
49
|
+
onClick={toggleTheme}
|
|
50
|
+
>
|
|
51
|
+
{theme === "dark" ? (
|
|
52
|
+
<Moon className="size-5" />
|
|
53
|
+
) : (
|
|
54
|
+
<Sun className="size-5" />
|
|
55
|
+
)}
|
|
56
|
+
<span className="sr-only">Toggle theme</span>
|
|
57
|
+
</Button>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
export type { AvatarPart, AvatarConfig, ShapeType } from "@allbluecn/kernel"
|
|
19
|
+
|
|
20
|
+
export const partMax: Record<import("@allbluecn/kernel").AvatarPart, number> = {
|
|
21
|
+
face: 15,
|
|
22
|
+
eyes: 13,
|
|
23
|
+
eyebrows: 15,
|
|
24
|
+
mouth: 19,
|
|
25
|
+
nose: 13,
|
|
26
|
+
glasses: 14,
|
|
27
|
+
hair: 58,
|
|
28
|
+
accessories: 14,
|
|
29
|
+
details: 13,
|
|
30
|
+
beard: 16,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const presetBgColors = [
|
|
34
|
+
"#fff", "#fa541c", "#faad14", "#fadb14", "#a0d911",
|
|
35
|
+
"#52c41a", "#1890ff", "#2f54eb", "#722ed1", "#eb2f96",
|
|
36
|
+
"#bfbfbf", "#f0d5c0", "#debaba", "#babae1", "#e1bae1",
|
|
37
|
+
"#bae1e1", "#c0d5f0", "#d5f0c0", "#e0e0e0", "#f5c6c6",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
export function getRandomConfig(): import("@allbluecn/kernel").AvatarConfig {
|
|
41
|
+
const config: Record<string, number> = {}
|
|
42
|
+
for (const key of Object.keys(partMax) as import("@allbluecn/kernel").AvatarPart[]) {
|
|
43
|
+
config[key] = Math.floor(Math.random() * (partMax[key] + 1))
|
|
44
|
+
}
|
|
45
|
+
return config as import("@allbluecn/kernel").AvatarConfig
|
|
46
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
"use client"
|
|
19
|
+
|
|
20
|
+
import * as React from "react"
|
|
21
|
+
import { AlertDialog as AlertDialogPrimitive } from "radix-ui"
|
|
22
|
+
|
|
23
|
+
import { cn } from "@/lib/utils"
|
|
24
|
+
import { Button } from "@/components/ui/button"
|
|
25
|
+
|
|
26
|
+
function AlertDialog({
|
|
27
|
+
...props
|
|
28
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
|
|
29
|
+
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function AlertDialogTrigger({
|
|
33
|
+
...props
|
|
34
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
|
|
35
|
+
return (
|
|
36
|
+
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function AlertDialogPortal({
|
|
41
|
+
...props
|
|
42
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
|
|
43
|
+
return (
|
|
44
|
+
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function AlertDialogOverlay({
|
|
49
|
+
className,
|
|
50
|
+
...props
|
|
51
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
|
|
52
|
+
return (
|
|
53
|
+
<AlertDialogPrimitive.Overlay
|
|
54
|
+
data-slot="alert-dialog-overlay"
|
|
55
|
+
className={cn(
|
|
56
|
+
"fixed inset-0 z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
|
57
|
+
className
|
|
58
|
+
)}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function AlertDialogContent({
|
|
65
|
+
className,
|
|
66
|
+
size = "default",
|
|
67
|
+
...props
|
|
68
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {
|
|
69
|
+
size?: "default" | "sm"
|
|
70
|
+
}) {
|
|
71
|
+
return (
|
|
72
|
+
<AlertDialogPortal>
|
|
73
|
+
<AlertDialogOverlay />
|
|
74
|
+
<AlertDialogPrimitive.Content
|
|
75
|
+
data-slot="alert-dialog-content"
|
|
76
|
+
data-size={size}
|
|
77
|
+
className={cn(
|
|
78
|
+
"group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
79
|
+
className
|
|
80
|
+
)}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
</AlertDialogPortal>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function AlertDialogHeader({
|
|
88
|
+
className,
|
|
89
|
+
...props
|
|
90
|
+
}: React.ComponentProps<"div">) {
|
|
91
|
+
return (
|
|
92
|
+
<div
|
|
93
|
+
data-slot="alert-dialog-header"
|
|
94
|
+
className={cn(
|
|
95
|
+
"grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
|
|
96
|
+
className
|
|
97
|
+
)}
|
|
98
|
+
{...props}
|
|
99
|
+
/>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function AlertDialogFooter({
|
|
104
|
+
className,
|
|
105
|
+
...props
|
|
106
|
+
}: React.ComponentProps<"div">) {
|
|
107
|
+
return (
|
|
108
|
+
<div
|
|
109
|
+
data-slot="alert-dialog-footer"
|
|
110
|
+
className={cn(
|
|
111
|
+
"-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
|
|
112
|
+
className
|
|
113
|
+
)}
|
|
114
|
+
{...props}
|
|
115
|
+
/>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function AlertDialogMedia({
|
|
120
|
+
className,
|
|
121
|
+
...props
|
|
122
|
+
}: React.ComponentProps<"div">) {
|
|
123
|
+
return (
|
|
124
|
+
<div
|
|
125
|
+
data-slot="alert-dialog-media"
|
|
126
|
+
className={cn(
|
|
127
|
+
"mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6",
|
|
128
|
+
className
|
|
129
|
+
)}
|
|
130
|
+
{...props}
|
|
131
|
+
/>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function AlertDialogTitle({
|
|
136
|
+
className,
|
|
137
|
+
...props
|
|
138
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
|
|
139
|
+
return (
|
|
140
|
+
<AlertDialogPrimitive.Title
|
|
141
|
+
data-slot="alert-dialog-title"
|
|
142
|
+
className={cn(
|
|
143
|
+
"text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
|
|
144
|
+
className
|
|
145
|
+
)}
|
|
146
|
+
{...props}
|
|
147
|
+
/>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function AlertDialogDescription({
|
|
152
|
+
className,
|
|
153
|
+
...props
|
|
154
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
|
|
155
|
+
return (
|
|
156
|
+
<AlertDialogPrimitive.Description
|
|
157
|
+
data-slot="alert-dialog-description"
|
|
158
|
+
className={cn(
|
|
159
|
+
"text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
|
160
|
+
className
|
|
161
|
+
)}
|
|
162
|
+
{...props}
|
|
163
|
+
/>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function AlertDialogAction({
|
|
168
|
+
className,
|
|
169
|
+
variant = "default",
|
|
170
|
+
size = "default",
|
|
171
|
+
...props
|
|
172
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Action> &
|
|
173
|
+
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
|
|
174
|
+
return (
|
|
175
|
+
<Button variant={variant} size={size} asChild>
|
|
176
|
+
<AlertDialogPrimitive.Action
|
|
177
|
+
data-slot="alert-dialog-action"
|
|
178
|
+
className={cn(className)}
|
|
179
|
+
{...props}
|
|
180
|
+
/>
|
|
181
|
+
</Button>
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function AlertDialogCancel({
|
|
186
|
+
className,
|
|
187
|
+
variant = "outline",
|
|
188
|
+
size = "default",
|
|
189
|
+
...props
|
|
190
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel> &
|
|
191
|
+
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
|
|
192
|
+
return (
|
|
193
|
+
<Button variant={variant} size={size} asChild>
|
|
194
|
+
<AlertDialogPrimitive.Cancel
|
|
195
|
+
data-slot="alert-dialog-cancel"
|
|
196
|
+
className={cn(className)}
|
|
197
|
+
{...props}
|
|
198
|
+
/>
|
|
199
|
+
</Button>
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export {
|
|
204
|
+
AlertDialog,
|
|
205
|
+
AlertDialogAction,
|
|
206
|
+
AlertDialogCancel,
|
|
207
|
+
AlertDialogContent,
|
|
208
|
+
AlertDialogDescription,
|
|
209
|
+
AlertDialogFooter,
|
|
210
|
+
AlertDialogHeader,
|
|
211
|
+
AlertDialogMedia,
|
|
212
|
+
AlertDialogOverlay,
|
|
213
|
+
AlertDialogPortal,
|
|
214
|
+
AlertDialogTitle,
|
|
215
|
+
AlertDialogTrigger,
|
|
216
|
+
}
|