@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
package/src/router.tsx
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
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 * as Sentry from '@sentry/tanstackstart-react'
|
|
19
|
+
import { createRouter as createTanstackRouter } from '@tanstack/react-router'
|
|
20
|
+
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
|
|
21
|
+
import { QueryClient } from '@tanstack/react-query'
|
|
22
|
+
import { routeTree } from './routeTree.gen'
|
|
23
|
+
|
|
24
|
+
export function getRouter() {
|
|
25
|
+
const queryClient = new QueryClient({
|
|
26
|
+
defaultOptions: {
|
|
27
|
+
queries: { staleTime: 60 * 1000, refetchOnWindowFocus: false },
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const router = createTanstackRouter({
|
|
32
|
+
routeTree,
|
|
33
|
+
context: { queryClient, locale: 'zh' as const },
|
|
34
|
+
defaultPreload: 'intent',
|
|
35
|
+
defaultPendingMs: 200,
|
|
36
|
+
scrollRestoration: true,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
if (!router.isServer) {
|
|
40
|
+
Sentry.addIntegration(
|
|
41
|
+
Sentry.tanstackRouterBrowserTracingIntegration(router),
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
setupRouterSsrQueryIntegration({ router, queryClient, wrapQueryClient: true })
|
|
46
|
+
return router
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
declare module '@tanstack/react-router' {
|
|
50
|
+
interface Register { router: ReturnType<typeof getRouter> }
|
|
51
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
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 { Link, Outlet, createRootRouteWithContext, HeadContent, Scripts } from '@tanstack/react-router'
|
|
19
|
+
import { I18nextProvider } from 'react-i18next'
|
|
20
|
+
import { useTranslation } from 'react-i18next'
|
|
21
|
+
import globalsCss from '../styles/globals.css?url'
|
|
22
|
+
import type { RouterContext } from '@/lib/router-context'
|
|
23
|
+
import type { AppLocale } from '@/lib/i18n/config'
|
|
24
|
+
import { Providers } from '@/providers'
|
|
25
|
+
import { Toaster } from '@/components/ui/sonner'
|
|
26
|
+
import { getSessionFn } from '@/server/serverFns/auth/session'
|
|
27
|
+
import { getLocaleFn } from '@/server/serverFns/locale'
|
|
28
|
+
import { reportClientError } from '@/server/serverFns/monitoring/report-error'
|
|
29
|
+
import { useEffect, useMemo } from 'react'
|
|
30
|
+
import { createServerI18n } from '@/lib/i18n/server'
|
|
31
|
+
|
|
32
|
+
export const Route = createRootRouteWithContext<RouterContext>()({
|
|
33
|
+
beforeLoad: async () => {
|
|
34
|
+
try {
|
|
35
|
+
const [session, locale] = await Promise.all([
|
|
36
|
+
getSessionFn(),
|
|
37
|
+
getLocaleFn(),
|
|
38
|
+
])
|
|
39
|
+
return { session, locale }
|
|
40
|
+
} catch {
|
|
41
|
+
return { session: null, locale: 'zh' as AppLocale }
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
head: () => ({
|
|
45
|
+
meta: [
|
|
46
|
+
{ charSet: 'utf-8' },
|
|
47
|
+
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
|
48
|
+
{ title: 'AllBlue Admin' },
|
|
49
|
+
],
|
|
50
|
+
links: [{ rel: 'stylesheet', href: globalsCss }],
|
|
51
|
+
}),
|
|
52
|
+
notFoundComponent: NotFoundPage,
|
|
53
|
+
errorComponent: ErrorBoundary,
|
|
54
|
+
component: RootComponent,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
function RootComponent() {
|
|
58
|
+
const context = Route.useRouteContext()
|
|
59
|
+
const locale = context?.locale || 'zh'
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
const errorHandler = (event: ErrorEvent) => {
|
|
63
|
+
const error = event.error || new Error(event.message || 'Unknown browser error')
|
|
64
|
+
reportClientError({
|
|
65
|
+
data: {
|
|
66
|
+
errorName: error.name,
|
|
67
|
+
errorMessage: error.message,
|
|
68
|
+
stackTrace: error.stack,
|
|
69
|
+
source: 'browser',
|
|
70
|
+
context: {
|
|
71
|
+
pathname: event.filename || window.location.pathname,
|
|
72
|
+
lineno: event.lineno,
|
|
73
|
+
colno: event.colno,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
}).catch(() => {})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const rejectionHandler = (event: PromiseRejectionEvent) => {
|
|
80
|
+
const reason = event.reason as Error
|
|
81
|
+
reportClientError({
|
|
82
|
+
data: {
|
|
83
|
+
errorName: reason?.name || 'UnhandledPromiseRejection',
|
|
84
|
+
errorMessage: reason?.message || String(event.reason),
|
|
85
|
+
stackTrace: reason?.stack,
|
|
86
|
+
source: 'browser',
|
|
87
|
+
context: { pathname: window.location.pathname },
|
|
88
|
+
},
|
|
89
|
+
}).catch(() => {})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
window.addEventListener('error', errorHandler)
|
|
93
|
+
window.addEventListener('unhandledrejection', rejectionHandler)
|
|
94
|
+
return () => {
|
|
95
|
+
window.removeEventListener('error', errorHandler)
|
|
96
|
+
window.removeEventListener('unhandledrejection', rejectionHandler)
|
|
97
|
+
}
|
|
98
|
+
}, [])
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<html lang={locale === 'en' ? 'en' : 'zh-CN'} suppressHydrationWarning>
|
|
102
|
+
<head>
|
|
103
|
+
<HeadContent />
|
|
104
|
+
</head>
|
|
105
|
+
<body className="w-full h-full min-h-screen antialiased">
|
|
106
|
+
<Providers locale={locale}>
|
|
107
|
+
<Outlet />
|
|
108
|
+
<Toaster position="top-center" />
|
|
109
|
+
</Providers>
|
|
110
|
+
<Scripts />
|
|
111
|
+
</body>
|
|
112
|
+
</html>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function NotFoundPage() {
|
|
117
|
+
const i18n = useMemo(() => createServerI18n('zh'), [])
|
|
118
|
+
return (
|
|
119
|
+
<I18nextProvider i18n={i18n}>
|
|
120
|
+
<NotFoundPageInner />
|
|
121
|
+
</I18nextProvider>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function NotFoundPageInner() {
|
|
126
|
+
const { t } = useTranslation('common')
|
|
127
|
+
return (
|
|
128
|
+
<div className="flex h-screen w-full items-center justify-center">
|
|
129
|
+
<div className="text-center">
|
|
130
|
+
<h1 className="text-6xl font-bold">404</h1>
|
|
131
|
+
<p className="mt-4 text-muted-foreground">{t('errorPage.notFound')}</p>
|
|
132
|
+
<Link to="/login" className="mt-6 inline-block text-primary hover:underline">{t('errorPage.goLogin')}</Link>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function ErrorBoundary({ error }: { error: Error }) {
|
|
139
|
+
const i18n = useMemo(() => createServerI18n('zh'), [])
|
|
140
|
+
return (
|
|
141
|
+
<I18nextProvider i18n={i18n}>
|
|
142
|
+
<ErrorBoundaryInner error={error} />
|
|
143
|
+
</I18nextProvider>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function ErrorBoundaryInner({ error }: { error: Error }) {
|
|
148
|
+
const { t } = useTranslation('common')
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
reportClientError({
|
|
151
|
+
data: {
|
|
152
|
+
errorName: error.name,
|
|
153
|
+
errorMessage: error.message,
|
|
154
|
+
stackTrace: error.stack,
|
|
155
|
+
source: 'browser',
|
|
156
|
+
context: { pathname: window.location.pathname },
|
|
157
|
+
},
|
|
158
|
+
}).catch(() => {})
|
|
159
|
+
}, [error])
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<div className="flex h-screen w-full items-center justify-center">
|
|
163
|
+
<div className="text-center">
|
|
164
|
+
<h1 className="text-4xl font-bold text-destructive">{t('errorPage.error')}</h1>
|
|
165
|
+
<p className="mt-4 text-muted-foreground">{error.message}</p>
|
|
166
|
+
<Link to="/login" className="mt-6 inline-block text-primary hover:underline">{t('errorPage.goLogin')}</Link>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
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 { createLazyFileRoute } from "@tanstack/react-router"
|
|
19
|
+
import { ProviderForm } from "@/components/ai-provider/provider-form"
|
|
20
|
+
|
|
21
|
+
export const Route = createLazyFileRoute("/_nav/ai-providers/$id/edit")({
|
|
22
|
+
component: EditProviderPage,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
function EditProviderPage() {
|
|
26
|
+
const ctx = Route.useRouteContext() as {
|
|
27
|
+
provider: {
|
|
28
|
+
id: string
|
|
29
|
+
providerId: string
|
|
30
|
+
displayName: string
|
|
31
|
+
brandName: string
|
|
32
|
+
description: string
|
|
33
|
+
category: string
|
|
34
|
+
adapterKind: string
|
|
35
|
+
defaultBaseUrl: string | null
|
|
36
|
+
keyUrl: string | null
|
|
37
|
+
docsUrl: string | null
|
|
38
|
+
keyPlaceholder: string | null
|
|
39
|
+
iconSlug: string | null
|
|
40
|
+
brandColor: string
|
|
41
|
+
isLocal: boolean
|
|
42
|
+
enabled: boolean
|
|
43
|
+
sortOrder: number
|
|
44
|
+
models: unknown
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const p = ctx.provider
|
|
48
|
+
return (
|
|
49
|
+
<div className="px-6">
|
|
50
|
+
<ProviderForm
|
|
51
|
+
mode="edit"
|
|
52
|
+
providerDbId={p.id}
|
|
53
|
+
initial={{
|
|
54
|
+
providerId: p.providerId,
|
|
55
|
+
displayName: p.displayName,
|
|
56
|
+
brandName: p.brandName,
|
|
57
|
+
description: p.description,
|
|
58
|
+
category: p.category as never,
|
|
59
|
+
adapterKind: p.adapterKind as never,
|
|
60
|
+
defaultBaseUrl: p.defaultBaseUrl ?? "",
|
|
61
|
+
keyUrl: p.keyUrl ?? "",
|
|
62
|
+
docsUrl: p.docsUrl ?? "",
|
|
63
|
+
keyPlaceholder: p.keyPlaceholder ?? "",
|
|
64
|
+
iconSlug: p.iconSlug ?? "",
|
|
65
|
+
brandColor: p.brandColor,
|
|
66
|
+
isLocal: p.isLocal,
|
|
67
|
+
enabled: p.enabled,
|
|
68
|
+
sortOrder: p.sortOrder,
|
|
69
|
+
models: p.models as never,
|
|
70
|
+
}}
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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 { createFileRoute, notFound } from "@tanstack/react-router"
|
|
19
|
+
import { z } from "zod"
|
|
20
|
+
import { getAiProviderAdminFn } from "@/server/serverFns/ai-provider/get"
|
|
21
|
+
|
|
22
|
+
export const Route = createFileRoute("/_nav/ai-providers/$id/edit")({
|
|
23
|
+
params: {
|
|
24
|
+
parse: (raw) => ({ id: z.string().parse(raw.id) }),
|
|
25
|
+
stringify: ({ id }) => ({ id }),
|
|
26
|
+
},
|
|
27
|
+
beforeLoad: async ({ params }) => {
|
|
28
|
+
try {
|
|
29
|
+
const provider = await getAiProviderAdminFn({ data: { id: params.id } })
|
|
30
|
+
return { provider }
|
|
31
|
+
} catch {
|
|
32
|
+
throw notFound()
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
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 { createLazyFileRoute } from "@tanstack/react-router"
|
|
19
|
+
import { ProviderTable } from "@/components/ai-provider/provider-table"
|
|
20
|
+
import type { AiProviderRow } from "@allbluecn/kernel"
|
|
21
|
+
|
|
22
|
+
export const Route = createLazyFileRoute("/_nav/ai-providers/")({
|
|
23
|
+
component: AiProvidersPage,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
function AiProvidersPage() {
|
|
27
|
+
const ctx = Route.useRouteContext() as { aiProviders?: AiProviderRow[] }
|
|
28
|
+
return (
|
|
29
|
+
<div className="p-6">
|
|
30
|
+
<ProviderTable data={ctx.aiProviders ?? []} />
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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 { createLazyFileRoute } from "@tanstack/react-router"
|
|
19
|
+
import { ProviderForm } from "@/components/ai-provider/provider-form"
|
|
20
|
+
|
|
21
|
+
export const Route = createLazyFileRoute("/_nav/ai-providers/new")({
|
|
22
|
+
component: NewProviderPage,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
function NewProviderPage() {
|
|
26
|
+
return (
|
|
27
|
+
<div className="px-6">
|
|
28
|
+
<ProviderForm mode="create" />
|
|
29
|
+
</div>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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 { createFileRoute } from "@tanstack/react-router"
|
|
19
|
+
|
|
20
|
+
export const Route = createFileRoute("/_nav/ai-providers/new")({})
|
|
@@ -0,0 +1,26 @@
|
|
|
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 { createLazyFileRoute, Outlet } from "@tanstack/react-router"
|
|
19
|
+
|
|
20
|
+
export const Route = createLazyFileRoute("/_nav/ai-providers")({
|
|
21
|
+
component: AiProvidersLayout,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
function AiProvidersLayout() {
|
|
25
|
+
return <Outlet />
|
|
26
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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 { createFileRoute } from "@tanstack/react-router"
|
|
19
|
+
import { listAiProvidersAdminFn } from "@/server/serverFns/ai-provider/list"
|
|
20
|
+
|
|
21
|
+
export const Route = createFileRoute("/_nav/ai-providers")({
|
|
22
|
+
beforeLoad: async () => {
|
|
23
|
+
try {
|
|
24
|
+
const providers = await listAiProvidersAdminFn({ data: {} })
|
|
25
|
+
return { aiProviders: providers }
|
|
26
|
+
} catch (e) {
|
|
27
|
+
console.error('[admin/ai-providers] failed to load', e)
|
|
28
|
+
return { aiProviders: [] }
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
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 { createLazyFileRoute } from '@tanstack/react-router'
|
|
19
|
+
import { useTranslation } from 'react-i18next'
|
|
20
|
+
import { Package2 } from 'lucide-react'
|
|
21
|
+
import { PlaceholderPage } from '@/components/common/placeholder-page'
|
|
22
|
+
|
|
23
|
+
export const Route = createLazyFileRoute('/_nav/apps')({
|
|
24
|
+
component: AppsPage,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
function AppsPage() {
|
|
28
|
+
const { t } = useTranslation('common')
|
|
29
|
+
return <PlaceholderPage title={t('apps.title')} icon={Package2} />
|
|
30
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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 { createFileRoute } from '@tanstack/react-router'
|
|
19
|
+
|
|
20
|
+
export const Route = createFileRoute('/_nav/apps')({})
|
|
@@ -0,0 +1,100 @@
|
|
|
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 { createLazyFileRoute } from '@tanstack/react-router'
|
|
19
|
+
import { useTranslation } from 'react-i18next'
|
|
20
|
+
import type React from 'react'
|
|
21
|
+
import { Badge } from '@/components/ui/badge'
|
|
22
|
+
|
|
23
|
+
export const Route = createLazyFileRoute('/_nav/audit')({
|
|
24
|
+
pendingComponent: () => (
|
|
25
|
+
<div className="flex h-64 items-center justify-center">
|
|
26
|
+
<div className="size-8 animate-spin rounded-full border-2 border-primary border-t-transparent" />
|
|
27
|
+
</div>
|
|
28
|
+
),
|
|
29
|
+
component: AuditLogPage,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
function AuditLogPage(): React.ReactElement {
|
|
33
|
+
const { t } = useTranslation('enterprise')
|
|
34
|
+
const { entries, total } = Route.useLoaderData() as {
|
|
35
|
+
entries: Array<{
|
|
36
|
+
id: string
|
|
37
|
+
action: string
|
|
38
|
+
resourceType: string
|
|
39
|
+
resourceId: string | null
|
|
40
|
+
userId: string | null
|
|
41
|
+
workspaceId: string | null
|
|
42
|
+
metadata: Record<string, unknown>
|
|
43
|
+
ip: string | null
|
|
44
|
+
userAgent: string | null
|
|
45
|
+
createdAt: string
|
|
46
|
+
}>
|
|
47
|
+
total: number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="space-y-4">
|
|
52
|
+
<div>
|
|
53
|
+
<h1 className="text-2xl font-bold">{t('audit.title')}</h1>
|
|
54
|
+
<p className="text-sm text-muted-foreground mt-1">{t('audit.subtitle', { count: total })}</p>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
{entries.length === 0 ? (
|
|
58
|
+
<div className="rounded-lg border border-border p-8 text-center text-muted-foreground">
|
|
59
|
+
{t('audit.empty')}
|
|
60
|
+
</div>
|
|
61
|
+
) : (
|
|
62
|
+
<div className="rounded-lg border border-border overflow-hidden">
|
|
63
|
+
<table className="w-full text-sm">
|
|
64
|
+
<thead>
|
|
65
|
+
<tr className="border-b border-border bg-muted/50">
|
|
66
|
+
<th className="p-3 text-left font-medium">{t('audit.table.timestamp')}</th>
|
|
67
|
+
<th className="p-3 text-left font-medium">{t('audit.table.action')}</th>
|
|
68
|
+
<th className="p-3 text-left font-medium">{t('audit.table.resource')}</th>
|
|
69
|
+
<th className="p-3 text-left font-medium">{t('audit.table.user')}</th>
|
|
70
|
+
<th className="p-3 text-left font-medium">{t('audit.table.ip')}</th>
|
|
71
|
+
<th className="p-3 text-left font-medium">{t('audit.table.ua')}</th>
|
|
72
|
+
</tr>
|
|
73
|
+
</thead>
|
|
74
|
+
<tbody>
|
|
75
|
+
{entries.map((entry) => (
|
|
76
|
+
<tr key={entry.id} className="border-b border-border last:border-0">
|
|
77
|
+
<td className="p-3 whitespace-nowrap">
|
|
78
|
+
{new Date(entry.createdAt).toLocaleString('zh-CN')}
|
|
79
|
+
</td>
|
|
80
|
+
<td className="p-3">
|
|
81
|
+
<Badge variant="secondary">{entry.action}</Badge>
|
|
82
|
+
</td>
|
|
83
|
+
<td className="p-3">
|
|
84
|
+
{entry.resourceType}
|
|
85
|
+
{entry.resourceId ? ` / ${entry.resourceId.slice(0, 8)}` : ''}
|
|
86
|
+
</td>
|
|
87
|
+
<td className="p-3">{entry.userId?.slice(0, 8) || '—'}</td>
|
|
88
|
+
<td className="p-3 text-muted-foreground">{entry.ip || '—'}</td>
|
|
89
|
+
<td className="p-3 text-muted-foreground truncate max-w-[200px]">
|
|
90
|
+
{entry.userAgent || '—'}
|
|
91
|
+
</td>
|
|
92
|
+
</tr>
|
|
93
|
+
))}
|
|
94
|
+
</tbody>
|
|
95
|
+
</table>
|
|
96
|
+
</div>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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 { createFileRoute } from '@tanstack/react-router'
|
|
19
|
+
import { queryAuditLogsFn } from '@/server/serverFns/audit'
|
|
20
|
+
|
|
21
|
+
export const Route = createFileRoute('/_nav/audit')({
|
|
22
|
+
loader: async () => {
|
|
23
|
+
return queryAuditLogsFn({ data: { page: 1, pageSize: 50 } })
|
|
24
|
+
},
|
|
25
|
+
})
|