@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,55 @@
|
|
|
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 { createServerFn } from '@tanstack/react-start'
|
|
19
|
+
import { prisma } from '@allbluecn/database'
|
|
20
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
21
|
+
import { performHealthCheck } from '@/server/observability'
|
|
22
|
+
|
|
23
|
+
export const getHealthStatus = createServerFn({ method: 'GET' })
|
|
24
|
+
.handler(async () => {
|
|
25
|
+
await requireAdmin()
|
|
26
|
+
|
|
27
|
+
const current = await performHealthCheck()
|
|
28
|
+
const recent = await prisma.monitoringHealthSnapshot.findMany({
|
|
29
|
+
orderBy: { timestamp: 'desc' },
|
|
30
|
+
take: 50,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const dbStats = await prisma.monitoringRequestLog.aggregate({
|
|
34
|
+
_count: { _all: true },
|
|
35
|
+
_avg: { duration: true },
|
|
36
|
+
_min: { duration: true },
|
|
37
|
+
_max: { duration: true },
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const errors = await prisma.monitoringErrorReport.aggregate({
|
|
41
|
+
_count: { _all: true },
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
current,
|
|
46
|
+
recent,
|
|
47
|
+
requestStats: {
|
|
48
|
+
total: dbStats._count._all,
|
|
49
|
+
avgDuration: dbStats._avg.duration || 0,
|
|
50
|
+
minDuration: dbStats._min.duration || 0,
|
|
51
|
+
maxDuration: dbStats._max.duration || 0,
|
|
52
|
+
},
|
|
53
|
+
errorCount: errors._count._all,
|
|
54
|
+
}
|
|
55
|
+
})
|
|
@@ -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
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
19
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
20
|
+
import { metrics } from '@/server/observability'
|
|
21
|
+
import { prisma } from '@allbluecn/database'
|
|
22
|
+
|
|
23
|
+
export const getMetrics = createServerFn({ method: 'GET' })
|
|
24
|
+
.handler(async () => {
|
|
25
|
+
await requireAdmin()
|
|
26
|
+
|
|
27
|
+
const memory = process.memoryUsage()
|
|
28
|
+
const allStats = metrics.getAllStats()
|
|
29
|
+
|
|
30
|
+
const snapshots = await prisma.monitoringMetricsSnapshot.findMany({
|
|
31
|
+
orderBy: { recordedAt: 'desc' },
|
|
32
|
+
take: 100,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
memory: {
|
|
37
|
+
rss: memory.rss,
|
|
38
|
+
heapTotal: memory.heapTotal,
|
|
39
|
+
heapUsed: memory.heapUsed,
|
|
40
|
+
external: memory.external,
|
|
41
|
+
arrayBuffers: memory.arrayBuffers,
|
|
42
|
+
},
|
|
43
|
+
current: allStats,
|
|
44
|
+
history: snapshots,
|
|
45
|
+
}
|
|
46
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
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 { createServerFn } from '@tanstack/react-start'
|
|
19
|
+
import { prisma } from '@allbluecn/database'
|
|
20
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
21
|
+
|
|
22
|
+
type GetRequestLogsInput = {
|
|
23
|
+
limit?: number
|
|
24
|
+
offset?: number
|
|
25
|
+
method?: string
|
|
26
|
+
status?: number
|
|
27
|
+
path?: string
|
|
28
|
+
excludeMonitoring?: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const MONITORING_PREFIXES = [
|
|
32
|
+
'/api/observability/',
|
|
33
|
+
'/src/routes/',
|
|
34
|
+
'/node_modules/',
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
export const getRequestLogs = createServerFn({ method: 'GET' })
|
|
38
|
+
.validator((data: GetRequestLogsInput) => data)
|
|
39
|
+
.handler(async ({ data }) => {
|
|
40
|
+
await requireAdmin()
|
|
41
|
+
|
|
42
|
+
const where: any = {}
|
|
43
|
+
if (data.method) where.method = data.method
|
|
44
|
+
if (data.status) where.status = data.status
|
|
45
|
+
if (data.path) where.path = { contains: data.path }
|
|
46
|
+
if (data.excludeMonitoring) {
|
|
47
|
+
where.NOT = MONITORING_PREFIXES.map((prefix) => ({
|
|
48
|
+
path: { startsWith: prefix },
|
|
49
|
+
}))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const limit = data.limit || 50
|
|
53
|
+
const offset = data.offset || 0
|
|
54
|
+
|
|
55
|
+
const [logs, total] = await Promise.all([
|
|
56
|
+
prisma.monitoringRequestLog.findMany({
|
|
57
|
+
where,
|
|
58
|
+
orderBy: { timestamp: 'desc' },
|
|
59
|
+
take: limit,
|
|
60
|
+
skip: offset,
|
|
61
|
+
}),
|
|
62
|
+
prisma.monitoringRequestLog.count({ where }),
|
|
63
|
+
])
|
|
64
|
+
|
|
65
|
+
return { logs, total, limit, offset }
|
|
66
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
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 { createServerFn } from '@tanstack/react-start'
|
|
19
|
+
import { prisma } from '@allbluecn/database'
|
|
20
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
21
|
+
|
|
22
|
+
type ReportErrorInput = {
|
|
23
|
+
errorName: string
|
|
24
|
+
errorMessage: string
|
|
25
|
+
stackTrace?: string
|
|
26
|
+
context?: Record<string, unknown>
|
|
27
|
+
source?: 'browser' | 'server'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const reportClientError = createServerFn({ method: 'POST' })
|
|
31
|
+
.validator((data: ReportErrorInput) => data)
|
|
32
|
+
.handler(async ({ data }) => {
|
|
33
|
+
await requireAdmin()
|
|
34
|
+
|
|
35
|
+
const key = `${data.errorName}:${data.errorMessage}`
|
|
36
|
+
await prisma.monitoringErrorReport.upsert({
|
|
37
|
+
where: { id: key },
|
|
38
|
+
create: {
|
|
39
|
+
id: key,
|
|
40
|
+
errorName: data.errorName,
|
|
41
|
+
errorMessage: data.errorMessage,
|
|
42
|
+
stackTrace: data.stackTrace || null,
|
|
43
|
+
count: 1,
|
|
44
|
+
firstSeen: new Date(),
|
|
45
|
+
lastSeen: new Date(),
|
|
46
|
+
context: (data.context as any) ?? { source: data.source || 'browser' },
|
|
47
|
+
},
|
|
48
|
+
update: {
|
|
49
|
+
count: { increment: 1 },
|
|
50
|
+
lastSeen: new Date(),
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return { ok: true }
|
|
55
|
+
})
|
|
@@ -0,0 +1,70 @@
|
|
|
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 { createServerFn } from '@tanstack/react-start'
|
|
19
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
20
|
+
import { metrics, resetErrors } from '@/server/observability'
|
|
21
|
+
import { prisma } from '@allbluecn/database'
|
|
22
|
+
|
|
23
|
+
type ResetInput = {
|
|
24
|
+
type: 'errors' | 'metrics' | 'request-logs' | 'health' | 'all'
|
|
25
|
+
days?: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const resetMonitoring = createServerFn({ method: 'POST' })
|
|
29
|
+
.validator((data: ResetInput) => data)
|
|
30
|
+
.handler(async ({ data }) => {
|
|
31
|
+
await requireAdmin()
|
|
32
|
+
|
|
33
|
+
const cutoff = data.days ? new Date(Date.now() - data.days * 86400000) : null
|
|
34
|
+
|
|
35
|
+
if (data.type === 'errors' || data.type === 'all') {
|
|
36
|
+
resetErrors()
|
|
37
|
+
if (cutoff) {
|
|
38
|
+
await prisma.monitoringErrorReport.deleteMany({ where: { lastSeen: { lt: cutoff } } })
|
|
39
|
+
} else {
|
|
40
|
+
await prisma.monitoringErrorReport.deleteMany()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (data.type === 'metrics' || data.type === 'all') {
|
|
45
|
+
metrics.reset()
|
|
46
|
+
if (cutoff) {
|
|
47
|
+
await prisma.monitoringMetricsSnapshot.deleteMany({ where: { recordedAt: { lt: cutoff } } })
|
|
48
|
+
} else {
|
|
49
|
+
await prisma.monitoringMetricsSnapshot.deleteMany()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (data.type === 'request-logs' || data.type === 'all') {
|
|
54
|
+
if (cutoff) {
|
|
55
|
+
await prisma.monitoringRequestLog.deleteMany({ where: { timestamp: { lt: cutoff } } })
|
|
56
|
+
} else {
|
|
57
|
+
await prisma.monitoringRequestLog.deleteMany()
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (data.type === 'health' || data.type === 'all') {
|
|
62
|
+
if (cutoff) {
|
|
63
|
+
await prisma.monitoringHealthSnapshot.deleteMany({ where: { timestamp: { lt: cutoff } } })
|
|
64
|
+
} else {
|
|
65
|
+
await prisma.monitoringHealthSnapshot.deleteMany()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { ok: true }
|
|
70
|
+
})
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { prisma } from '@allbluecn/database'
|
|
4
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
5
|
+
import { requireFeature } from '@/server/entitlements'
|
|
6
|
+
import { getProService } from '@/server/pro-services'
|
|
7
|
+
|
|
8
|
+
type SystemRolePreset = {
|
|
9
|
+
name: string
|
|
10
|
+
permissions: Record<string, 'admin' | 'write' | 'read' | 'none'>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function buildSystemRolePresets(): SystemRolePreset[] {
|
|
14
|
+
const R = 'admin'
|
|
15
|
+
const W = 'write'
|
|
16
|
+
const R2 = 'read'
|
|
17
|
+
const N = 'none'
|
|
18
|
+
|
|
19
|
+
const base: Record<string, 'admin' | 'write' | 'read' | 'none'> = {
|
|
20
|
+
'knowledge:read': R,
|
|
21
|
+
'knowledge:write': R,
|
|
22
|
+
'knowledge:delete': R,
|
|
23
|
+
'story:read': R,
|
|
24
|
+
'story:write': R,
|
|
25
|
+
'story:publish': R,
|
|
26
|
+
'story:delete': R,
|
|
27
|
+
'user:read': R,
|
|
28
|
+
'user:write': R,
|
|
29
|
+
'user:delete': R,
|
|
30
|
+
'setting:read': R,
|
|
31
|
+
'setting:write': R,
|
|
32
|
+
'billing:read': R,
|
|
33
|
+
'billing:write': R,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return [
|
|
37
|
+
{ name: 'owner', permissions: { ...base } },
|
|
38
|
+
{
|
|
39
|
+
name: 'admin',
|
|
40
|
+
permissions: { ...base, 'user:delete': N, 'billing:write': N },
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'member',
|
|
44
|
+
permissions: {
|
|
45
|
+
...base,
|
|
46
|
+
'knowledge:delete': N,
|
|
47
|
+
'story:publish': N,
|
|
48
|
+
'story:delete': N,
|
|
49
|
+
'user:read': N,
|
|
50
|
+
'user:write': N,
|
|
51
|
+
'user:delete': N,
|
|
52
|
+
'setting:write': N,
|
|
53
|
+
'billing:read': N,
|
|
54
|
+
'billing:write': N,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'editor',
|
|
59
|
+
permissions: {
|
|
60
|
+
'knowledge:read': R2,
|
|
61
|
+
'knowledge:write': W,
|
|
62
|
+
'knowledge:delete': N,
|
|
63
|
+
'story:read': R2,
|
|
64
|
+
'story:write': W,
|
|
65
|
+
'story:publish': N,
|
|
66
|
+
'story:delete': N,
|
|
67
|
+
'user:read': N,
|
|
68
|
+
'user:write': N,
|
|
69
|
+
'user:delete': N,
|
|
70
|
+
'setting:read': N,
|
|
71
|
+
'setting:write': N,
|
|
72
|
+
'billing:read': N,
|
|
73
|
+
'billing:write': N,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'viewer',
|
|
78
|
+
permissions: {
|
|
79
|
+
'knowledge:read': R2,
|
|
80
|
+
'knowledge:write': N,
|
|
81
|
+
'knowledge:delete': N,
|
|
82
|
+
'story:read': R2,
|
|
83
|
+
'story:write': N,
|
|
84
|
+
'story:publish': N,
|
|
85
|
+
'story:delete': N,
|
|
86
|
+
'user:read': N,
|
|
87
|
+
'user:write': N,
|
|
88
|
+
'user:delete': N,
|
|
89
|
+
'setting:read': N,
|
|
90
|
+
'setting:write': N,
|
|
91
|
+
'billing:read': N,
|
|
92
|
+
'billing:write': N,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const listRolesFn = createServerFn({ method: 'GET' })
|
|
99
|
+
.validator(z.object({ workspaceId: z.string() }))
|
|
100
|
+
.handler(async ({ data }) => {
|
|
101
|
+
await requireAdmin()
|
|
102
|
+
await requireFeature(undefined, 'advancedPermissions')
|
|
103
|
+
|
|
104
|
+
const systemCount = await prisma.customRole.count({
|
|
105
|
+
where: { workspaceId: data.workspaceId, isSystem: true },
|
|
106
|
+
})
|
|
107
|
+
if (systemCount === 0 && getProService('permissions')) {
|
|
108
|
+
const presets = buildSystemRolePresets()
|
|
109
|
+
for (const preset of presets) {
|
|
110
|
+
await prisma.customRole.create({
|
|
111
|
+
data: {
|
|
112
|
+
workspaceId: data.workspaceId,
|
|
113
|
+
name: preset.name,
|
|
114
|
+
permissions: preset.permissions as never,
|
|
115
|
+
isSystem: true,
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
const admins = await prisma.user.findMany({
|
|
120
|
+
where: { role: 'ADMIN' },
|
|
121
|
+
select: { id: true },
|
|
122
|
+
})
|
|
123
|
+
const ownerRole = await prisma.customRole.findFirst({
|
|
124
|
+
where: { workspaceId: data.workspaceId, name: 'owner', isSystem: true },
|
|
125
|
+
})
|
|
126
|
+
if (ownerRole && admins.length > 0) {
|
|
127
|
+
await prisma.roleAssignment.createMany({
|
|
128
|
+
data: admins.map(({ id }: { id: string }) => ({ userId: id, roleId: ownerRole.id })),
|
|
129
|
+
skipDuplicates: true,
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const [items, total] = await Promise.all([
|
|
135
|
+
prisma.customRole.findMany({
|
|
136
|
+
where: { workspaceId: data.workspaceId },
|
|
137
|
+
include: { _count: { select: { assignments: true } } },
|
|
138
|
+
orderBy: { createdAt: 'desc' },
|
|
139
|
+
}),
|
|
140
|
+
prisma.customRole.count({ where: { workspaceId: data.workspaceId } }),
|
|
141
|
+
])
|
|
142
|
+
return { items, total, seeded: systemCount > 0 ? 'done' : 'pending' }
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
export const createRoleFn = createServerFn({ method: 'POST' })
|
|
146
|
+
.validator(
|
|
147
|
+
z.object({
|
|
148
|
+
workspaceId: z.string(),
|
|
149
|
+
name: z.string(),
|
|
150
|
+
permissions: z.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null(), z.any()])).optional(),
|
|
151
|
+
}),
|
|
152
|
+
)
|
|
153
|
+
.handler(async ({ data }) => {
|
|
154
|
+
await requireAdmin()
|
|
155
|
+
await requireFeature(undefined, 'advancedPermissions')
|
|
156
|
+
return prisma.customRole.create({
|
|
157
|
+
data: {
|
|
158
|
+
workspaceId: data.workspaceId,
|
|
159
|
+
name: data.name,
|
|
160
|
+
permissions: (data.permissions ?? {}) as never,
|
|
161
|
+
isSystem: false,
|
|
162
|
+
},
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
export const deleteRoleFn = createServerFn({ method: 'POST' })
|
|
167
|
+
.validator(z.object({ roleId: z.string() }))
|
|
168
|
+
.handler(async ({ data }) => {
|
|
169
|
+
await requireAdmin()
|
|
170
|
+
await requireFeature(undefined, 'advancedPermissions')
|
|
171
|
+
await prisma.customRole.delete({ where: { id: data.roleId } })
|
|
172
|
+
return { success: true }
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
export const assignRoleFn = createServerFn({ method: 'POST' })
|
|
176
|
+
.validator(z.object({ userId: z.string(), roleId: z.string() }))
|
|
177
|
+
.handler(async ({ data }) => {
|
|
178
|
+
await requireAdmin()
|
|
179
|
+
await requireFeature(undefined, 'advancedPermissions')
|
|
180
|
+
await prisma.roleAssignment.create({ data: { userId: data.userId, roleId: data.roleId } })
|
|
181
|
+
return { ok: true }
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
export const unassignRoleFn = createServerFn({ method: 'POST' })
|
|
185
|
+
.validator(z.object({ assignmentId: z.string() }))
|
|
186
|
+
.handler(async ({ data }) => {
|
|
187
|
+
await requireAdmin()
|
|
188
|
+
await requireFeature(undefined, 'advancedPermissions')
|
|
189
|
+
await prisma.roleAssignment.delete({ where: { id: data.assignmentId } })
|
|
190
|
+
return { ok: true }
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
export const listRoleAssignmentsFn = createServerFn({ method: 'GET' })
|
|
194
|
+
.validator(z.object({ roleId: z.string() }))
|
|
195
|
+
.handler(async ({ data }) => {
|
|
196
|
+
await requireAdmin()
|
|
197
|
+
await requireFeature(undefined, 'advancedPermissions')
|
|
198
|
+
return prisma.roleAssignment.findMany({
|
|
199
|
+
where: { roleId: data.roleId },
|
|
200
|
+
include: { user: { select: { id: true, username: true, email: true } } },
|
|
201
|
+
orderBy: { createdAt: 'desc' },
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
export const listWorkspacesFn = createServerFn({ method: 'GET' })
|
|
206
|
+
.validator(z.object({}))
|
|
207
|
+
.handler(async () => {
|
|
208
|
+
await requireAdmin()
|
|
209
|
+
await requireFeature(undefined, 'advancedPermissions')
|
|
210
|
+
return prisma.workspace.findMany({
|
|
211
|
+
select: { id: true, name: true },
|
|
212
|
+
orderBy: { name: 'asc' },
|
|
213
|
+
})
|
|
214
|
+
})
|
|
@@ -0,0 +1,88 @@
|
|
|
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 { createServerFn } from '@tanstack/react-start'
|
|
19
|
+
import { prisma } from '@allbluecn/database'
|
|
20
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
21
|
+
|
|
22
|
+
export type SentryConfig = {
|
|
23
|
+
dsn: string
|
|
24
|
+
org: string
|
|
25
|
+
project: string
|
|
26
|
+
authToken: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const getSentryConfig = createServerFn({ method: 'GET' })
|
|
30
|
+
.handler(async () => {
|
|
31
|
+
await requireAdmin()
|
|
32
|
+
|
|
33
|
+
const settings = await prisma.systemSetting.findMany({
|
|
34
|
+
where: { category: 'sentry' },
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const map = new Map(settings.map((s) => [s.key, s.value]))
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
dsn: map.get('dsn') || '',
|
|
41
|
+
org: map.get('org') || '',
|
|
42
|
+
project: map.get('project') || '',
|
|
43
|
+
authToken: map.get('auth_token') || '',
|
|
44
|
+
} satisfies SentryConfig
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
export const saveSentryConfig = createServerFn({ method: 'POST' })
|
|
48
|
+
.validator((data: SentryConfig) => data)
|
|
49
|
+
.handler(async ({ data }) => {
|
|
50
|
+
await requireAdmin()
|
|
51
|
+
|
|
52
|
+
const entries: { key: string; value: string; category: string; desc: string }[] = [
|
|
53
|
+
{ key: 'dsn', value: data.dsn, category: 'sentry', desc: 'Sentry DSN(客户端/服务端共用)' },
|
|
54
|
+
{ key: 'org', value: data.org, category: 'sentry', desc: 'Sentry 组织 slug' },
|
|
55
|
+
{ key: 'project', value: data.project, category: 'sentry', desc: 'Sentry 项目 slug' },
|
|
56
|
+
{ key: 'auth_token', value: data.authToken, category: 'sentry', desc: 'Sentry Auth Token(源码映射上传用)' },
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
await prisma.systemSetting.upsert({
|
|
61
|
+
where: { key: entry.key },
|
|
62
|
+
create: entry,
|
|
63
|
+
update: { value: entry.value },
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { ok: true }
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
export const reconfigureSentryServer = createServerFn({ method: 'POST' })
|
|
71
|
+
.validator((data: { dsn: string }) => data)
|
|
72
|
+
.handler(async ({ data }) => {
|
|
73
|
+
await requireAdmin()
|
|
74
|
+
|
|
75
|
+
if (!data.dsn) {
|
|
76
|
+
return { ok: false, error: 'DSN 为空,跳过服务端 Sentry 初始化' }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const Sentry = await import('@sentry/tanstackstart-react')
|
|
80
|
+
Sentry.init({
|
|
81
|
+
dsn: data.dsn,
|
|
82
|
+
environment: process.env.SENTRY_ENVIRONMENT || process.env.NODE_ENV,
|
|
83
|
+
tracesSampleRate: 1.0,
|
|
84
|
+
enableLogs: true,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
return { ok: true }
|
|
88
|
+
})
|
|
@@ -0,0 +1,83 @@
|
|
|
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 { createServerFn } from '@tanstack/react-start'
|
|
19
|
+
import { z } from 'zod'
|
|
20
|
+
import { prisma } from '@allbluecn/database'
|
|
21
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
22
|
+
import { requireFeature } from '@/server/entitlements'
|
|
23
|
+
|
|
24
|
+
export const listSsoProvidersFn = createServerFn({ method: 'GET' })
|
|
25
|
+
.validator(z.object({ workspaceId: z.string().optional() }))
|
|
26
|
+
.handler(async ({ data }) => {
|
|
27
|
+
await requireAdmin()
|
|
28
|
+
await requireFeature(undefined, 'sso')
|
|
29
|
+
const where: any = data.workspaceId ? { workspaceId: data.workspaceId } : {}
|
|
30
|
+
const [items, total] = await Promise.all([
|
|
31
|
+
prisma.ssoProvider.findMany({
|
|
32
|
+
where,
|
|
33
|
+
include: { _count: { select: { mappings: true } } },
|
|
34
|
+
orderBy: { createdAt: 'desc' },
|
|
35
|
+
}),
|
|
36
|
+
prisma.ssoProvider.count({ where }),
|
|
37
|
+
])
|
|
38
|
+
return { items, total }
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
export const createSsoProviderFn = createServerFn({ method: 'POST' })
|
|
42
|
+
.validator(
|
|
43
|
+
z.object({
|
|
44
|
+
workspaceId: z.string(),
|
|
45
|
+
type: z.string(),
|
|
46
|
+
name: z.string(),
|
|
47
|
+
config: z.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null(), z.any()])).optional(),
|
|
48
|
+
domain: z.string().optional(),
|
|
49
|
+
}),
|
|
50
|
+
)
|
|
51
|
+
.handler(async ({ data }) => {
|
|
52
|
+
await requireAdmin()
|
|
53
|
+
await requireFeature(undefined, 'sso')
|
|
54
|
+
return prisma.ssoProvider.create({
|
|
55
|
+
data: {
|
|
56
|
+
workspaceId: data.workspaceId,
|
|
57
|
+
type: data.type,
|
|
58
|
+
name: data.name,
|
|
59
|
+
config: (data.config ?? {}) as never,
|
|
60
|
+
domain: data.domain,
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
export const deleteSsoProviderFn = createServerFn({ method: 'POST' })
|
|
66
|
+
.validator(z.object({ providerId: z.string() }))
|
|
67
|
+
.handler(async ({ data }) => {
|
|
68
|
+
await requireAdmin()
|
|
69
|
+
await requireFeature(undefined, 'sso')
|
|
70
|
+
await prisma.ssoProvider.delete({ where: { id: data.providerId } })
|
|
71
|
+
return { success: true }
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
export const toggleSsoProviderFn = createServerFn({ method: 'POST' })
|
|
75
|
+
.validator(z.object({ providerId: z.string(), enabled: z.boolean() }))
|
|
76
|
+
.handler(async ({ data }) => {
|
|
77
|
+
await requireAdmin()
|
|
78
|
+
await requireFeature(undefined, 'sso')
|
|
79
|
+
return prisma.ssoProvider.update({
|
|
80
|
+
where: { id: data.providerId },
|
|
81
|
+
data: { enabled: data.enabled },
|
|
82
|
+
})
|
|
83
|
+
})
|