@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,358 @@
|
|
|
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 {
|
|
19
|
+
Calendar,
|
|
20
|
+
Download,
|
|
21
|
+
Filter,
|
|
22
|
+
Loader2,
|
|
23
|
+
MapPin,
|
|
24
|
+
Search,
|
|
25
|
+
} from "lucide-react";
|
|
26
|
+
import { useState } from "react";
|
|
27
|
+
import { useTranslation } from 'react-i18next'
|
|
28
|
+
import { cn } from "@/lib/utils";
|
|
29
|
+
import { Badge } from "@/components/ui/badge";
|
|
30
|
+
import { Button } from "@/components/ui/button";
|
|
31
|
+
import {
|
|
32
|
+
Card,
|
|
33
|
+
CardContent,
|
|
34
|
+
CardDescription,
|
|
35
|
+
CardHeader,
|
|
36
|
+
CardTitle,
|
|
37
|
+
} from "@/components/ui/card";
|
|
38
|
+
import { FieldLabel } from "@/components/ui/field";
|
|
39
|
+
import {
|
|
40
|
+
InputGroup,
|
|
41
|
+
InputGroupInput,
|
|
42
|
+
} from "@/components/ui/input-group";
|
|
43
|
+
import {
|
|
44
|
+
Select,
|
|
45
|
+
SelectContent,
|
|
46
|
+
SelectItem,
|
|
47
|
+
SelectTrigger,
|
|
48
|
+
SelectValue,
|
|
49
|
+
} from "@/components/ui/select";
|
|
50
|
+
import { Separator } from "@/components/ui/separator";
|
|
51
|
+
|
|
52
|
+
export interface ActivityLogEntry {
|
|
53
|
+
id: string;
|
|
54
|
+
action: string;
|
|
55
|
+
type:
|
|
56
|
+
| "login"
|
|
57
|
+
| "logout"
|
|
58
|
+
| "password_change"
|
|
59
|
+
| "profile_update"
|
|
60
|
+
| "settings_change"
|
|
61
|
+
| "export"
|
|
62
|
+
| "delete"
|
|
63
|
+
| "create"
|
|
64
|
+
| "update";
|
|
65
|
+
description: string;
|
|
66
|
+
ipAddress: string;
|
|
67
|
+
location: string;
|
|
68
|
+
device: string;
|
|
69
|
+
timestamp: Date;
|
|
70
|
+
status: "success" | "failed";
|
|
71
|
+
/** 失败原因(仅 status === "failed" 时显示,位于日期右侧,dot 分隔) */
|
|
72
|
+
reason?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface SettingsActivityLogProps {
|
|
76
|
+
entries?: ActivityLogEntry[];
|
|
77
|
+
onExport?: (filters: {
|
|
78
|
+
dateRange?: { start: Date; end: Date };
|
|
79
|
+
type?: string;
|
|
80
|
+
search?: string;
|
|
81
|
+
}) => Promise<void>;
|
|
82
|
+
className?: string;
|
|
83
|
+
title?: string;
|
|
84
|
+
description?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const actionTypes = [
|
|
88
|
+
{ value: "all", key: "all" },
|
|
89
|
+
{ value: "login", key: "login" },
|
|
90
|
+
{ value: "logout", key: "logout" },
|
|
91
|
+
{ value: "password_change", key: "password_change" },
|
|
92
|
+
{ value: "profile_update", key: "profile_update" },
|
|
93
|
+
{ value: "settings_change", key: "settings_change" },
|
|
94
|
+
{ value: "export", key: "export" },
|
|
95
|
+
{ value: "delete", key: "delete" },
|
|
96
|
+
] as const
|
|
97
|
+
|
|
98
|
+
function formatRelativeTime(t: (k: string, p?: Record<string, number>) => string, date: Date): string {
|
|
99
|
+
const now = Date.now();
|
|
100
|
+
const diff = now - date.getTime();
|
|
101
|
+
const minutes = Math.floor(diff / 60_000);
|
|
102
|
+
const hours = Math.floor(minutes / 60);
|
|
103
|
+
const days = Math.floor(hours / 24);
|
|
104
|
+
|
|
105
|
+
if (minutes < 1) return t("activityLog.justNow");
|
|
106
|
+
if (minutes < 60) return t("common.timeAgo.minutes", { count: minutes });
|
|
107
|
+
if (hours < 24) return t("common.timeAgo.hours", { count: hours });
|
|
108
|
+
if (days < 7) return t("common.timeAgo.days", { count: days });
|
|
109
|
+
return new Intl.DateTimeFormat("zh-CN", {
|
|
110
|
+
month: "long",
|
|
111
|
+
day: "numeric",
|
|
112
|
+
year: "numeric",
|
|
113
|
+
hour: "numeric",
|
|
114
|
+
minute: "2-digit",
|
|
115
|
+
}).format(date);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export default function SettingsActivityLog({
|
|
119
|
+
entries = [],
|
|
120
|
+
onExport,
|
|
121
|
+
className,
|
|
122
|
+
title,
|
|
123
|
+
description,
|
|
124
|
+
}: SettingsActivityLogProps) {
|
|
125
|
+
const { t } = useTranslation('common')
|
|
126
|
+
const displayTitle = title ?? t('activityLog.title')
|
|
127
|
+
const displayDescription = description ?? t('activityLog.description')
|
|
128
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
129
|
+
const [selectedType, setSelectedType] = useState("all");
|
|
130
|
+
const [dateRange, setDateRange] = useState({
|
|
131
|
+
start: "",
|
|
132
|
+
end: "",
|
|
133
|
+
});
|
|
134
|
+
const [isExporting, setIsExporting] = useState(false);
|
|
135
|
+
|
|
136
|
+
const filteredEntries = entries.filter((entry) => {
|
|
137
|
+
if (selectedType !== "all" && entry.type !== selectedType) return false;
|
|
138
|
+
if (
|
|
139
|
+
searchQuery &&
|
|
140
|
+
!entry.description.toLowerCase().includes(searchQuery.toLowerCase()) &&
|
|
141
|
+
!entry.ipAddress.includes(searchQuery) &&
|
|
142
|
+
!entry.location.toLowerCase().includes(searchQuery.toLowerCase())
|
|
143
|
+
)
|
|
144
|
+
return false;
|
|
145
|
+
return true;
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const handleExport = async () => {
|
|
149
|
+
setIsExporting(true);
|
|
150
|
+
try {
|
|
151
|
+
const exportRange =
|
|
152
|
+
dateRange.start && dateRange.end
|
|
153
|
+
? {
|
|
154
|
+
start: new Date(dateRange.start),
|
|
155
|
+
end: new Date(dateRange.end),
|
|
156
|
+
}
|
|
157
|
+
: undefined;
|
|
158
|
+
|
|
159
|
+
await onExport?.({
|
|
160
|
+
dateRange: exportRange,
|
|
161
|
+
type: selectedType !== "all" ? selectedType : undefined,
|
|
162
|
+
search: searchQuery || undefined,
|
|
163
|
+
});
|
|
164
|
+
} finally {
|
|
165
|
+
setIsExporting(false);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<Card className={cn("w-full shadow-xs", className)}>
|
|
171
|
+
<CardHeader>
|
|
172
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
173
|
+
<div className="flex min-w-0 flex-1 flex-col gap-2">
|
|
174
|
+
<CardTitle className="wrap-break-word">{displayTitle}</CardTitle>
|
|
175
|
+
<CardDescription className="wrap-break-word">
|
|
176
|
+
{displayDescription}
|
|
177
|
+
</CardDescription>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
</CardHeader>
|
|
181
|
+
<CardContent>
|
|
182
|
+
<div className="flex flex-col gap-6">
|
|
183
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
184
|
+
<div className="flex flex-col gap-4 rounded-lg border p-4">
|
|
185
|
+
<div className="flex items-center gap-2">
|
|
186
|
+
<div className="flex size-8 items-center justify-center rounded-lg bg-muted">
|
|
187
|
+
<Search className="size-4" />
|
|
188
|
+
</div>
|
|
189
|
+
<FieldLabel className="mb-0">{t('activityLog.search')}</FieldLabel>
|
|
190
|
+
</div>
|
|
191
|
+
<InputGroup>
|
|
192
|
+
<InputGroupInput
|
|
193
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
194
|
+
placeholder={t('activityLog.searchPlaceholder')}
|
|
195
|
+
value={searchQuery}
|
|
196
|
+
/>
|
|
197
|
+
</InputGroup>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<div className="flex flex-col gap-4 rounded-lg border p-4">
|
|
201
|
+
<div className="flex items-center gap-2">
|
|
202
|
+
<div className="flex size-8 items-center justify-center rounded-lg bg-muted">
|
|
203
|
+
<Filter className="size-4" />
|
|
204
|
+
</div>
|
|
205
|
+
<FieldLabel className="mb-0" htmlFor="action-type">
|
|
206
|
+
{t('activityLog.actionType')}
|
|
207
|
+
</FieldLabel>
|
|
208
|
+
</div>
|
|
209
|
+
<Select onValueChange={setSelectedType} value={selectedType}>
|
|
210
|
+
<SelectTrigger id="action-type">
|
|
211
|
+
<SelectValue />
|
|
212
|
+
</SelectTrigger>
|
|
213
|
+
<SelectContent>
|
|
214
|
+
{actionTypes.map((type) => (
|
|
215
|
+
<SelectItem key={type.value} value={type.value}>
|
|
216
|
+
{t(`activityLog.actionTypes.${type.key}`)}
|
|
217
|
+
</SelectItem>
|
|
218
|
+
))}
|
|
219
|
+
</SelectContent>
|
|
220
|
+
</Select>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<div className="flex flex-col gap-4 rounded-lg border p-4">
|
|
224
|
+
<div className="flex items-center gap-2">
|
|
225
|
+
<div className="flex size-8 items-center justify-center rounded-lg bg-muted">
|
|
226
|
+
<Calendar className="size-4" />
|
|
227
|
+
</div>
|
|
228
|
+
<FieldLabel className="mb-0" htmlFor="date-start">
|
|
229
|
+
{t('activityLog.startDate')}
|
|
230
|
+
</FieldLabel>
|
|
231
|
+
</div>
|
|
232
|
+
<InputGroup>
|
|
233
|
+
<InputGroupInput
|
|
234
|
+
id="date-start"
|
|
235
|
+
onChange={(e) =>
|
|
236
|
+
setDateRange((prev) => ({ ...prev, start: e.target.value }))
|
|
237
|
+
}
|
|
238
|
+
type="date"
|
|
239
|
+
value={dateRange.start}
|
|
240
|
+
/>
|
|
241
|
+
</InputGroup>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
<div className="flex flex-col gap-4 rounded-lg border p-4">
|
|
245
|
+
<div className="flex items-center gap-2">
|
|
246
|
+
<div className="flex size-8 items-center justify-center rounded-lg bg-muted">
|
|
247
|
+
<Calendar className="size-4" />
|
|
248
|
+
</div>
|
|
249
|
+
<FieldLabel className="mb-0" htmlFor="date-end">
|
|
250
|
+
{t('activityLog.endDate')}
|
|
251
|
+
</FieldLabel>
|
|
252
|
+
</div>
|
|
253
|
+
<InputGroup>
|
|
254
|
+
<InputGroupInput
|
|
255
|
+
id="date-end"
|
|
256
|
+
onChange={(e) =>
|
|
257
|
+
setDateRange((prev) => ({ ...prev, end: e.target.value }))
|
|
258
|
+
}
|
|
259
|
+
type="date"
|
|
260
|
+
value={dateRange.end}
|
|
261
|
+
/>
|
|
262
|
+
</InputGroup>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
267
|
+
<p className="text-muted-foreground text-sm">
|
|
268
|
+
{t('activityLog.total', { count: filteredEntries.length })}
|
|
269
|
+
</p>
|
|
270
|
+
<Button
|
|
271
|
+
className="w-full sm:w-auto"
|
|
272
|
+
disabled={isExporting}
|
|
273
|
+
onClick={handleExport}
|
|
274
|
+
type="button"
|
|
275
|
+
variant="outline"
|
|
276
|
+
>
|
|
277
|
+
{isExporting ? (
|
|
278
|
+
<>
|
|
279
|
+
<Loader2 className="size-4 animate-spin" />
|
|
280
|
+
{t('activityLog.exporting')}
|
|
281
|
+
</>
|
|
282
|
+
) : (
|
|
283
|
+
<>
|
|
284
|
+
<Download className="size-4" />
|
|
285
|
+
{t('activityLog.exportLog')}
|
|
286
|
+
</>
|
|
287
|
+
)}
|
|
288
|
+
</Button>
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
<Separator />
|
|
292
|
+
|
|
293
|
+
{filteredEntries.length === 0 ? (
|
|
294
|
+
<div className="flex flex-col items-center justify-center gap-4 py-12 text-center">
|
|
295
|
+
<div className="flex size-12 items-center justify-center rounded-full bg-muted">
|
|
296
|
+
<Search className="size-6 text-muted-foreground" />
|
|
297
|
+
</div>
|
|
298
|
+
<div className="flex flex-col gap-2">
|
|
299
|
+
<p className="font-medium text-sm">{t('activityLog.empty')}</p>
|
|
300
|
+
<p className="text-muted-foreground text-sm">
|
|
301
|
+
{searchQuery || selectedType !== "all"
|
|
302
|
+
? t('activityLog.emptyHint.filtered')
|
|
303
|
+
: t('activityLog.emptyHint.default')}
|
|
304
|
+
</p>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
) : (
|
|
308
|
+
<div className="flex flex-col gap-3">
|
|
309
|
+
{filteredEntries.map((entry) => (
|
|
310
|
+
<div
|
|
311
|
+
className="flex items-start gap-3 rounded-lg border p-4"
|
|
312
|
+
key={entry.id}
|
|
313
|
+
>
|
|
314
|
+
<div className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted">
|
|
315
|
+
<Calendar className="size-4" />
|
|
316
|
+
</div>
|
|
317
|
+
<div className="flex min-w-0 flex-1 flex-col gap-2">
|
|
318
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
319
|
+
<span className="font-medium text-sm">
|
|
320
|
+
{entry.description}
|
|
321
|
+
</span>
|
|
322
|
+
<Badge
|
|
323
|
+
className="text-xs"
|
|
324
|
+
variant={
|
|
325
|
+
entry.status === "success" ? "default" : "destructive"
|
|
326
|
+
}
|
|
327
|
+
>
|
|
328
|
+
{entry.status === "success" ? t('activityLog.status.success') : t('activityLog.status.failed')}
|
|
329
|
+
</Badge>
|
|
330
|
+
</div>
|
|
331
|
+
<div className="flex flex-wrap items-center gap-2 text-muted-foreground text-xs">
|
|
332
|
+
<span className="flex items-center gap-1">
|
|
333
|
+
<MapPin className="size-3" />
|
|
334
|
+
{entry.location || t('activityLog.unknown')}
|
|
335
|
+
</span>
|
|
336
|
+
<span>•</span>
|
|
337
|
+
<span>{entry.ipAddress || t('activityLog.unknown')}</span>
|
|
338
|
+
<span>•</span>
|
|
339
|
+
<span>{entry.device || t('activityLog.unknown')}</span>
|
|
340
|
+
<span>•</span>
|
|
341
|
+
<span>{formatRelativeTime(t, entry.timestamp)}</span>
|
|
342
|
+
{entry.status === "failed" && entry.reason && (
|
|
343
|
+
<>
|
|
344
|
+
<span>•</span>
|
|
345
|
+
<span className="text-destructive">{entry.reason}</span>
|
|
346
|
+
</>
|
|
347
|
+
)}
|
|
348
|
+
</div>
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
))}
|
|
352
|
+
</div>
|
|
353
|
+
)}
|
|
354
|
+
</div>
|
|
355
|
+
</CardContent>
|
|
356
|
+
</Card>
|
|
357
|
+
);
|
|
358
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
export const defaultSocialPlatforms = [
|
|
19
|
+
{
|
|
20
|
+
id: "twitter",
|
|
21
|
+
label: "Twitter/X",
|
|
22
|
+
placeholder: "https://x.com/preetsuthar17",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "github",
|
|
26
|
+
label: "GitHub",
|
|
27
|
+
placeholder: "https://github.com/preetsuthar17",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "linkedin",
|
|
31
|
+
label: "LinkedIn",
|
|
32
|
+
placeholder: "https://linkedin.com/in/preetsuthar17",
|
|
33
|
+
},
|
|
34
|
+
{ id: "website", label: "Website", placeholder: "https://preetsuthar.me" },
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
export const socialPlatformLabels: Record<string, string> = {
|
|
38
|
+
twitter: "Twitter/X",
|
|
39
|
+
github: "GitHub",
|
|
40
|
+
linkedin: "LinkedIn",
|
|
41
|
+
website: "Website",
|
|
42
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import type { ReactNode } from "react";
|
|
19
|
+
|
|
20
|
+
export interface SocialLink {
|
|
21
|
+
platform: string;
|
|
22
|
+
url: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ProfileData {
|
|
26
|
+
name: string;
|
|
27
|
+
email: string;
|
|
28
|
+
bio?: string;
|
|
29
|
+
location?: string;
|
|
30
|
+
website?: string;
|
|
31
|
+
avatar?: string;
|
|
32
|
+
socialLinks?: SocialLink[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface SettingsProfileProps {
|
|
36
|
+
profile?: ProfileData;
|
|
37
|
+
onSave?: (data: ProfileData) => Promise<void>;
|
|
38
|
+
onEmailChange?: (newEmail: string, currentPassword: string) => Promise<void>;
|
|
39
|
+
onAvatarUpload?: (file: File) => Promise<string>;
|
|
40
|
+
onAvatarRemove?: () => Promise<void>;
|
|
41
|
+
className?: string;
|
|
42
|
+
showEmailVerification?: boolean;
|
|
43
|
+
readOnly?: boolean;
|
|
44
|
+
title?: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
actions?: ReactNode;
|
|
47
|
+
}
|