@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,64 @@
|
|
|
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
|
+
|
|
22
|
+
const listInput = z.object({
|
|
23
|
+
page: z.number().default(1),
|
|
24
|
+
pageSize: z.number().default(20),
|
|
25
|
+
search: z.string().optional(),
|
|
26
|
+
category: z.string().optional(),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export const listMarketAppsFn = createServerFn({ method: 'GET' })
|
|
30
|
+
.validator(listInput)
|
|
31
|
+
.handler(async ({ data }) => {
|
|
32
|
+
const { page, pageSize, search, category } = data
|
|
33
|
+
const where: any = {}
|
|
34
|
+
if (search) where.name = { contains: search, mode: 'insensitive' }
|
|
35
|
+
if (category) where.category = category
|
|
36
|
+
const [items, total] = await Promise.all([
|
|
37
|
+
prisma.anyHubMarketApp.findMany({ where, skip: (page - 1) * pageSize, take: pageSize }),
|
|
38
|
+
prisma.anyHubMarketApp.count({ where }),
|
|
39
|
+
])
|
|
40
|
+
return { items, total }
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const updateInput = z.object({
|
|
44
|
+
id: z.string(),
|
|
45
|
+
name: z.string().optional(),
|
|
46
|
+
description: z.string().optional(),
|
|
47
|
+
category: z.string().optional(),
|
|
48
|
+
iconUrl: z.string().optional(),
|
|
49
|
+
isPublished: z.boolean().optional(),
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
export const updateMarketAppFn = createServerFn({ method: 'POST' })
|
|
53
|
+
.validator(updateInput)
|
|
54
|
+
.handler(async ({ data }) => {
|
|
55
|
+
const { id, ...update } = data
|
|
56
|
+
return prisma.anyHubMarketApp.update({ where: { id }, data: update })
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
export const deleteMarketAppFn = createServerFn({ method: 'POST' })
|
|
60
|
+
.validator(z.object({ id: z.string() }))
|
|
61
|
+
.handler(async ({ data }) => {
|
|
62
|
+
await prisma.anyHubMarketApp.delete({ where: { id: data.id } })
|
|
63
|
+
return { success: true }
|
|
64
|
+
})
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
const querySchema = z.object({
|
|
25
|
+
workspaceId: z.string().optional(),
|
|
26
|
+
userId: z.string().optional(),
|
|
27
|
+
action: z.string().optional(),
|
|
28
|
+
startDate: z.coerce.date().optional(),
|
|
29
|
+
endDate: z.coerce.date().optional(),
|
|
30
|
+
page: z.number().default(1),
|
|
31
|
+
pageSize: z.number().default(50),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export const queryAuditLogsFn = createServerFn({ method: 'GET' })
|
|
35
|
+
.validator(querySchema)
|
|
36
|
+
.handler(async ({ data }) => {
|
|
37
|
+
await requireAdmin()
|
|
38
|
+
await requireFeature(undefined, 'audit')
|
|
39
|
+
const where: any = {
|
|
40
|
+
...(data.workspaceId && { workspaceId: data.workspaceId }),
|
|
41
|
+
...(data.userId && { userId: data.userId }),
|
|
42
|
+
...(data.action && { action: { contains: data.action } }),
|
|
43
|
+
...(data.startDate && data.endDate && {
|
|
44
|
+
createdAt: { gte: data.startDate, lte: data.endDate },
|
|
45
|
+
}),
|
|
46
|
+
}
|
|
47
|
+
const [entries, total] = await Promise.all([
|
|
48
|
+
prisma.auditLog.findMany({
|
|
49
|
+
where,
|
|
50
|
+
orderBy: { createdAt: 'desc' },
|
|
51
|
+
skip: (data.page - 1) * data.pageSize,
|
|
52
|
+
take: data.pageSize,
|
|
53
|
+
}),
|
|
54
|
+
prisma.auditLog.count({ where }),
|
|
55
|
+
])
|
|
56
|
+
return { entries, total }
|
|
57
|
+
})
|
|
@@ -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 { createServerFn } from '@tanstack/react-start'
|
|
19
|
+
import { getRequest } from '@tanstack/react-start/server'
|
|
20
|
+
import { getCurrentSession } from '@/lib/session'
|
|
21
|
+
|
|
22
|
+
export const getSessionFn = createServerFn({ method: 'GET' })
|
|
23
|
+
.handler(async () => {
|
|
24
|
+
const request = getRequest()
|
|
25
|
+
return getCurrentSession(request)
|
|
26
|
+
})
|
|
@@ -0,0 +1,73 @@
|
|
|
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 bcrypt from 'bcryptjs'
|
|
22
|
+
import { getRandomConfig, presetBgColors } from '@/components/settings/avatar/avatar-config'
|
|
23
|
+
|
|
24
|
+
function pickBgColor(seed: string) {
|
|
25
|
+
let hash = 0
|
|
26
|
+
for (let i = 0; i < seed.length; i++) hash = seed.charCodeAt(i) + ((hash << 5) - hash)
|
|
27
|
+
return presetBgColors[Math.abs(hash) % presetBgColors.length]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const checkAdminExistsFn = createServerFn({ method: 'GET' }).handler(async () => {
|
|
31
|
+
const count = await prisma.user.count({
|
|
32
|
+
where: { role: { in: ['ADMIN', 'SUPER_ADMIN'] } },
|
|
33
|
+
})
|
|
34
|
+
return { hasAdmin: count > 0 }
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const setupSchema = z.object({
|
|
38
|
+
username: z.string().min(3, '用户名至少 3 个字符').max(20, '用户名最多 20 个字符'),
|
|
39
|
+
email: z.string().email('请输入有效的邮箱地址'),
|
|
40
|
+
password: z.string().min(8, '密码至少 8 个字符'),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
export const setupAdminFn = createServerFn({ method: 'POST' })
|
|
44
|
+
.validator(setupSchema)
|
|
45
|
+
.handler(async ({ data }) => {
|
|
46
|
+
const count = await prisma.user.count({
|
|
47
|
+
where: { role: 'SUPER_ADMIN' },
|
|
48
|
+
})
|
|
49
|
+
if (count > 0) throw new Error('系统已初始化,请联系管理员')
|
|
50
|
+
|
|
51
|
+
const exists = await prisma.user.findFirst({
|
|
52
|
+
where: { OR: [{ email: data.email }, { username: data.username }] },
|
|
53
|
+
select: { id: true },
|
|
54
|
+
})
|
|
55
|
+
if (exists) throw new Error('用户名或邮箱已存在')
|
|
56
|
+
|
|
57
|
+
const hashedPassword = await bcrypt.hash(data.password, 12)
|
|
58
|
+
|
|
59
|
+
const user = await prisma.user.create({
|
|
60
|
+
data: {
|
|
61
|
+
username: data.username,
|
|
62
|
+
email: data.email,
|
|
63
|
+
password: hashedPassword,
|
|
64
|
+
role: 'SUPER_ADMIN',
|
|
65
|
+
status: 'ACTIVE',
|
|
66
|
+
avatarConfig: getRandomConfig() as object,
|
|
67
|
+
bgShape: 'square',
|
|
68
|
+
bgColor: pickBgColor(data.username),
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
return { id: user.id, username: user.username, email: user.email }
|
|
73
|
+
})
|
|
@@ -0,0 +1,85 @@
|
|
|
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 { getRequest } from '@tanstack/react-start/server'
|
|
20
|
+
import { z } from 'zod'
|
|
21
|
+
import { prisma } from '@allbluecn/database'
|
|
22
|
+
import { getProService } from '@/server/pro-services'
|
|
23
|
+
import { requireAdmin } from '@/server/auth-helpers'
|
|
24
|
+
import { requireFeature } from '@/server/entitlements'
|
|
25
|
+
|
|
26
|
+
export const listComplianceExportsFn = createServerFn({ method: 'GET' })
|
|
27
|
+
.validator(z.object({ page: z.number().default(1), pageSize: z.number().default(20) }))
|
|
28
|
+
.handler(async ({ data }) => {
|
|
29
|
+
const request = getRequest()
|
|
30
|
+
await requireAdmin()
|
|
31
|
+
await requireFeature(request, 'compliance')
|
|
32
|
+
const compliance = getProService('compliance')
|
|
33
|
+
if (compliance) {
|
|
34
|
+
return compliance.listJobs({ page: data.page, pageSize: data.pageSize })
|
|
35
|
+
}
|
|
36
|
+
const [items, total] = await Promise.all([
|
|
37
|
+
prisma.complianceExport.findMany({
|
|
38
|
+
orderBy: { createdAt: 'desc' },
|
|
39
|
+
skip: (data.page - 1) * data.pageSize,
|
|
40
|
+
take: data.pageSize,
|
|
41
|
+
}),
|
|
42
|
+
prisma.complianceExport.count(),
|
|
43
|
+
])
|
|
44
|
+
return { items, total }
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
export const createComplianceExportFn = createServerFn({ method: 'POST' })
|
|
48
|
+
.validator(
|
|
49
|
+
z.object({
|
|
50
|
+
type: z.enum(['export', 'delete']),
|
|
51
|
+
scope: z.enum(['user', 'workspace']),
|
|
52
|
+
targetId: z.string(),
|
|
53
|
+
requestedBy: z.string().optional(),
|
|
54
|
+
}),
|
|
55
|
+
)
|
|
56
|
+
.handler(async ({ data }) => {
|
|
57
|
+
const request = getRequest()
|
|
58
|
+
await requireAdmin()
|
|
59
|
+
await requireFeature(request, 'compliance')
|
|
60
|
+
const compliance = getProService('compliance')
|
|
61
|
+
if (compliance) {
|
|
62
|
+
if (data.type === 'export') {
|
|
63
|
+
return compliance.createExportJob(data.scope, data.targetId, data.requestedBy)
|
|
64
|
+
}
|
|
65
|
+
return compliance.createDeleteJob(data.scope, data.targetId, data.requestedBy)
|
|
66
|
+
}
|
|
67
|
+
return prisma.complianceExport.create({ data })
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
export const executeComplianceJobFn = createServerFn({ method: 'POST' })
|
|
71
|
+
.validator(z.object({ jobId: z.string() }))
|
|
72
|
+
.handler(async ({ data }) => {
|
|
73
|
+
const request = getRequest()
|
|
74
|
+
await requireAdmin()
|
|
75
|
+
await requireFeature(request, 'compliance')
|
|
76
|
+
const job = await prisma.complianceExport.findUnique({
|
|
77
|
+
where: { id: data.jobId },
|
|
78
|
+
})
|
|
79
|
+
if (!job || job.status !== 'pending') {
|
|
80
|
+
throw new Error('任务不存在或状态不允许执行')
|
|
81
|
+
}
|
|
82
|
+
const compliance = getProService('compliance')
|
|
83
|
+
if (!compliance) throw new Error('合规模块不可用')
|
|
84
|
+
return compliance.processJob(data.jobId)
|
|
85
|
+
})
|
|
@@ -0,0 +1,197 @@
|
|
|
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 { execSync } from 'node:child_process'
|
|
22
|
+
import { writeFileSync, mkdirSync, existsSync, readFileSync } from 'node:fs'
|
|
23
|
+
import { resolve, join } from 'node:path'
|
|
24
|
+
|
|
25
|
+
const PACKAGE_NAME_RE = /^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*$/i
|
|
26
|
+
const COMPONENT_KEY_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
|
|
27
|
+
|
|
28
|
+
const importInput = z.object({
|
|
29
|
+
source: z.enum(['npm', 'local']).default('npm'),
|
|
30
|
+
packageName: z.string().regex(PACKAGE_NAME_RE, '无效的 npm 包名').optional(),
|
|
31
|
+
importPath: z.string().min(1, '不能为空').optional(),
|
|
32
|
+
exportName: z.string().default('default'),
|
|
33
|
+
componentKey: z.string().regex(COMPONENT_KEY_RE, '标识只能含小写字母、数字、连字符'),
|
|
34
|
+
displayName: z.string().min(1, '请输入显示名称'),
|
|
35
|
+
category: z.enum(['基础组件', '内容区块', '页面模版']),
|
|
36
|
+
type: z.enum(['component', 'block', 'template']).default('component'),
|
|
37
|
+
defaultWidth: z.number().int().positive().default(200),
|
|
38
|
+
defaultHeight: z.number().int().positive().default(40),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
function toPascalCase(s: string): string {
|
|
42
|
+
return s
|
|
43
|
+
.split('-')
|
|
44
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
45
|
+
.join('')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const COMPONENT_SEARCH_DIRS = [
|
|
49
|
+
'components/magicui',
|
|
50
|
+
'components/ui',
|
|
51
|
+
'components/hextaui',
|
|
52
|
+
'components/blocks-so',
|
|
53
|
+
'components',
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
function detectExportName(filePath: string, fallback: string): string {
|
|
57
|
+
const content = readFileSync(filePath, 'utf-8')
|
|
58
|
+
if (/export\s+default\b/.test(content)) return 'default'
|
|
59
|
+
const m = content.match(/export\s+(?:function|const|class)\s+(\w+)/)
|
|
60
|
+
if (m) return m[1]!
|
|
61
|
+
return fallback
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const importComponentFn = createServerFn({ method: 'POST' })
|
|
65
|
+
.validator(importInput)
|
|
66
|
+
.handler(async ({ data }) => {
|
|
67
|
+
const {
|
|
68
|
+
source,
|
|
69
|
+
packageName,
|
|
70
|
+
importPath,
|
|
71
|
+
exportName,
|
|
72
|
+
componentKey,
|
|
73
|
+
displayName,
|
|
74
|
+
category,
|
|
75
|
+
type,
|
|
76
|
+
defaultWidth,
|
|
77
|
+
defaultHeight,
|
|
78
|
+
} = data
|
|
79
|
+
|
|
80
|
+
const monorepoRoot = resolve(process.cwd(), '../..')
|
|
81
|
+
const pascalName = toPascalCase(componentKey)
|
|
82
|
+
|
|
83
|
+
let wrapperDir: string
|
|
84
|
+
let importSource: string
|
|
85
|
+
let dbSource: string
|
|
86
|
+
let actualExportName = exportName
|
|
87
|
+
|
|
88
|
+
if (source === 'npm') {
|
|
89
|
+
if (!packageName) throw new Error('npm 包名不能为空')
|
|
90
|
+
const prototypeDir = join(monorepoRoot, 'packages/prototype')
|
|
91
|
+
try {
|
|
92
|
+
execSync(`pnpm add ${packageName}`, {
|
|
93
|
+
cwd: prototypeDir,
|
|
94
|
+
stdio: 'pipe',
|
|
95
|
+
timeout: 60000,
|
|
96
|
+
})
|
|
97
|
+
} catch {
|
|
98
|
+
throw new Error(`安装包 "${packageName}" 失败,请检查包名或网络`)
|
|
99
|
+
}
|
|
100
|
+
wrapperDir = join(prototypeDir, 'src/imported')
|
|
101
|
+
importSource = packageName
|
|
102
|
+
dbSource = 'npm'
|
|
103
|
+
} else {
|
|
104
|
+
if (!importPath) throw new Error('路径不能为空')
|
|
105
|
+
const webDir = join(monorepoRoot, 'apps/web')
|
|
106
|
+
const webSrc = join(webDir, 'src')
|
|
107
|
+
let componentFilePath: string | null = null
|
|
108
|
+
|
|
109
|
+
if (importPath.startsWith('@/')) {
|
|
110
|
+
importSource = importPath
|
|
111
|
+
const relPath = importPath.slice(2)
|
|
112
|
+
const tryPath = join(webSrc, `${relPath}.tsx`)
|
|
113
|
+
if (existsSync(tryPath)) componentFilePath = tryPath
|
|
114
|
+
} else {
|
|
115
|
+
const compName = importPath.split('/').pop() ?? importPath
|
|
116
|
+
try {
|
|
117
|
+
execSync(`pnpm dlx shadcn@latest add ${importPath} --yes`, {
|
|
118
|
+
cwd: webDir,
|
|
119
|
+
stdio: 'pipe',
|
|
120
|
+
timeout: 120000,
|
|
121
|
+
})
|
|
122
|
+
} catch {
|
|
123
|
+
throw new Error(`shadcn add "${importPath}" 失败,请检查 registry 路径或网络`)
|
|
124
|
+
}
|
|
125
|
+
let found: string | null = null
|
|
126
|
+
for (const dir of COMPONENT_SEARCH_DIRS) {
|
|
127
|
+
const fp = join(webSrc, dir, `${compName}.tsx`)
|
|
128
|
+
if (existsSync(fp)) {
|
|
129
|
+
found = `@/${dir}/${compName}`
|
|
130
|
+
componentFilePath = fp
|
|
131
|
+
break
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!found || !componentFilePath) {
|
|
135
|
+
throw new Error(`组件已安装但未在约定目录找到 "${compName}.tsx",请手动指定 @/ 路径`)
|
|
136
|
+
}
|
|
137
|
+
importSource = found
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 自动检测组件的导出方式(default vs 具名)
|
|
141
|
+
if (componentFilePath) {
|
|
142
|
+
actualExportName = detectExportName(componentFilePath, exportName)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
wrapperDir = join(webSrc, 'components/prd/prototype/imported')
|
|
146
|
+
dbSource = 'custom'
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
mkdirSync(wrapperDir, { recursive: true })
|
|
150
|
+
|
|
151
|
+
const importStmt =
|
|
152
|
+
actualExportName === 'default'
|
|
153
|
+
? `import Comp from "${importSource}";`
|
|
154
|
+
: `import { ${actualExportName} as Comp } from "${importSource}";`
|
|
155
|
+
|
|
156
|
+
const wrapperContent = `// AUTO-GENERATED — do not edit manually
|
|
157
|
+
import React from "react";
|
|
158
|
+
${importStmt}
|
|
159
|
+
|
|
160
|
+
export function Imported${pascalName}({ props, style }: { props?: Record<string, any>; style?: Record<string, any> }) {
|
|
161
|
+
return <Comp {...props} style={style} />;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const config = {
|
|
165
|
+
type: "${componentKey}",
|
|
166
|
+
name: "${displayName}",
|
|
167
|
+
source: "${dbSource}" as const,
|
|
168
|
+
category: "${category}" as const,
|
|
169
|
+
defaultWidth: ${defaultWidth},
|
|
170
|
+
defaultHeight: ${defaultHeight},
|
|
171
|
+
defaultProps: {} as Record<string, any>,
|
|
172
|
+
defaultStyle: {} as Record<string, any>,
|
|
173
|
+
component: Imported${pascalName},
|
|
174
|
+
};
|
|
175
|
+
`
|
|
176
|
+
writeFileSync(join(wrapperDir, `${componentKey}.tsx`), wrapperContent, 'utf-8')
|
|
177
|
+
|
|
178
|
+
const component = await prisma.component.create({
|
|
179
|
+
data: {
|
|
180
|
+
name: displayName,
|
|
181
|
+
type,
|
|
182
|
+
category,
|
|
183
|
+
prototypeKey: componentKey,
|
|
184
|
+
source: dbSource,
|
|
185
|
+
sourceId: source === 'npm' ? packageName : importPath,
|
|
186
|
+
config: {},
|
|
187
|
+
defaultProps: {},
|
|
188
|
+
defaultStyle: {},
|
|
189
|
+
},
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
success: true,
|
|
194
|
+
componentId: component.id,
|
|
195
|
+
message: `导入成功(导出: ${actualExportName}),请重启 dev server 使组件生效`,
|
|
196
|
+
}
|
|
197
|
+
})
|
|
@@ -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 { createServerFn } from '@tanstack/react-start'
|
|
19
|
+
import { z } from 'zod'
|
|
20
|
+
import { prisma } from '@allbluecn/database'
|
|
21
|
+
|
|
22
|
+
const listInput = z.object({
|
|
23
|
+
page: z.number().default(1),
|
|
24
|
+
pageSize: z.number().default(20),
|
|
25
|
+
search: z.string().optional(),
|
|
26
|
+
category: z.string().optional(),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export const listComponentsFn = createServerFn({ method: 'GET' })
|
|
30
|
+
.validator(listInput)
|
|
31
|
+
.handler(async ({ data }) => {
|
|
32
|
+
const { page, pageSize, search, category } = data
|
|
33
|
+
const where: any = { isDeleted: false }
|
|
34
|
+
if (search) {
|
|
35
|
+
where.OR = [
|
|
36
|
+
{ name: { contains: search, mode: 'insensitive' } },
|
|
37
|
+
{ description: { contains: search, mode: 'insensitive' } },
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
if (category && category !== 'all') where.category = category
|
|
41
|
+
const [items, total] = await Promise.all([
|
|
42
|
+
prisma.component.findMany({
|
|
43
|
+
where,
|
|
44
|
+
skip: (page - 1) * pageSize,
|
|
45
|
+
take: pageSize,
|
|
46
|
+
orderBy: [{ sortOrder: 'desc' }, { name: 'asc' }],
|
|
47
|
+
}),
|
|
48
|
+
prisma.component.count({ where }),
|
|
49
|
+
])
|
|
50
|
+
return { items, total, page, pageSize }
|
|
51
|
+
})
|
|
@@ -0,0 +1,84 @@
|
|
|
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, Prisma } from '@allbluecn/database'
|
|
21
|
+
|
|
22
|
+
const createInput = z.object({
|
|
23
|
+
name: z.string().min(1),
|
|
24
|
+
type: z.string().default('component'),
|
|
25
|
+
category: z.string().default('基础组件'),
|
|
26
|
+
description: z.string().optional(),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export const createComponentFn = createServerFn({ method: 'POST' })
|
|
30
|
+
.validator(createInput)
|
|
31
|
+
.handler(async ({ data }) => {
|
|
32
|
+
return prisma.component.create({
|
|
33
|
+
data: {
|
|
34
|
+
name: data.name,
|
|
35
|
+
type: data.type,
|
|
36
|
+
category: data.category,
|
|
37
|
+
description: data.description,
|
|
38
|
+
config: {},
|
|
39
|
+
source: 'manual',
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const updateInput = z.object({
|
|
45
|
+
id: z.string(),
|
|
46
|
+
name: z.string().min(1).optional(),
|
|
47
|
+
description: z.string().optional(),
|
|
48
|
+
type: z.string().optional(),
|
|
49
|
+
category: z.string().optional(),
|
|
50
|
+
subCategory: z.string().optional(),
|
|
51
|
+
path: z.string().optional(),
|
|
52
|
+
defaultWidth: z.number().optional(),
|
|
53
|
+
defaultHeight: z.number().optional(),
|
|
54
|
+
tags: z.array(z.string()).optional(),
|
|
55
|
+
isHidden: z.boolean().optional(),
|
|
56
|
+
sortOrder: z.number().optional(),
|
|
57
|
+
defaultProps: z.record(z.string(), z.unknown()).optional(),
|
|
58
|
+
defaultStyle: z.record(z.string(), z.unknown()).optional(),
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
export const updateComponentFn = createServerFn({ method: 'POST' })
|
|
62
|
+
.validator(updateInput)
|
|
63
|
+
.handler(async ({ data }) => {
|
|
64
|
+
const { id, defaultProps, defaultStyle, ...rest } = data
|
|
65
|
+
return prisma.component.update({
|
|
66
|
+
where: { id },
|
|
67
|
+
data: {
|
|
68
|
+
...rest,
|
|
69
|
+
...(defaultProps !== undefined ? { defaultProps: defaultProps as Prisma.InputJsonValue } : {}),
|
|
70
|
+
...(defaultStyle !== undefined ? { defaultStyle: defaultStyle as Prisma.InputJsonValue } : {}),
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const deleteInput = z.object({ id: z.string() })
|
|
76
|
+
|
|
77
|
+
export const deleteComponentFn = createServerFn({ method: 'POST' })
|
|
78
|
+
.validator(deleteInput)
|
|
79
|
+
.handler(async ({ data }) => {
|
|
80
|
+
return prisma.component.update({
|
|
81
|
+
where: { id: data.id },
|
|
82
|
+
data: { isDeleted: true },
|
|
83
|
+
})
|
|
84
|
+
})
|