Просмотр исходного кода

feat:403页面新增回到登录页,新增任务日志,更新权限JSON等

韩洋 2 недель назад
Родитель
Сommit
a5834043d7

BIN
default/dist.zip


+ 21 - 0
default/docs/permission-identifiers.json

@@ -285,6 +285,27 @@
       }
     ]
   },
+  {
+    "module_name": "任务日志",
+    "module_code": "route.task-logs.view",
+    "module_label": "Task Logs",
+    "is_only_open_for_root": 0,
+    "for_role_template_type": 10,
+    "children": [
+      {
+        "perm_name": "查看(仅自己)",
+        "front_perm_code": "task-logs.view",
+        "api_perm_code": "",
+        "perm_label": "View Task Logs"
+      },
+      {
+        "perm_name": "查看(全部数据)",
+        "front_perm_code": "task-logs.all",
+        "api_perm_code": "",
+        "perm_label": "View All Task Logs"
+      }
+    ]
+  },
   {
     "module_name": "审计日志",
     "module_code": "route.audit-logs.view",

+ 1 - 0
default/rsbuild.config.ts

@@ -17,6 +17,7 @@ export default defineConfig(({ envMode }) => {
     'http://47.117.92.63:8081'
     // 'http://192.168.100.184:3000'
     // 'http://192.168.20.70:3000'
+    // 'http://192.168.20.71:13000'
 
   const isProd = envMode === 'production'
   const devProxy = Object.fromEntries(

+ 21 - 0
default/src/features/errors/forbidden.tsx

@@ -20,6 +20,7 @@ import { useNavigate, useRouter } from '@tanstack/react-router'
 import { useTranslation } from 'react-i18next'
 
 import { Button } from '@/components/ui/button'
+import { logout } from '@/features/auth/api'
 import { pickFirstAccessiblePath } from '@/lib/landing-page'
 import { useAuthStore } from '@/stores/auth-store'
 
@@ -27,6 +28,23 @@ export function ForbiddenError() {
   const { t } = useTranslation()
   const navigate = useNavigate()
   const { history } = useRouter()
+
+  const handleBackToLogin = async () => {
+    try {
+      // 调用登出接口清除服务端 session cookie
+      await logout()
+    } catch {
+      /* empty */
+    }
+    useAuthStore.getState().auth.reset()
+    try {
+      window.localStorage.removeItem('uid')
+    } catch {
+      /* empty */
+    }
+    navigate({ to: '/sign-in' })
+  }
+
   return (
     <div className='h-svh'>
       <div className='m-auto flex h-full w-full flex-col items-center justify-center gap-2'>
@@ -49,6 +67,9 @@ export function ForbiddenError() {
           >
             {t('Back to Home')}
           </Button>
+          <Button variant='outline' onClick={handleBackToLogin}>
+            {t('Back to login')}
+          </Button>
         </div>
       </div>
     </div>

+ 0 - 5
default/src/features/profile/components/sidebar-modules-card.tsx

@@ -93,11 +93,6 @@ export function SidebarModulesCard() {
           title: t('Usage Logs'),
           description: t('API usage records'),
         },
-        {
-          key: 'midjourney',
-          title: t('Drawing Logs'),
-          description: t('Drawing task records'),
-        },
         {
           key: 'task',
           title: t('Task Logs'),

+ 9 - 0
default/src/features/setup/constants.ts

@@ -49,6 +49,7 @@ export const PERMISSION_MODULES: PermissionModule[] = [
   { moduleName: 'API密钥', moduleCode: 'route.keys.view', moduleLabel: 'API Keys', isOnlyOpenForRoot: false },
   { moduleName: '模型列表', moduleCode: 'route.pricing.view', moduleLabel: 'Models', isOnlyOpenForRoot: false },
   { moduleName: '调用日志', moduleCode: 'route.usage-logs.view', moduleLabel: 'API Logs', isOnlyOpenForRoot: false },
+  { moduleName: '任务日志', moduleCode: 'route.task-logs.view', moduleLabel: 'Task Logs', isOnlyOpenForRoot: false },
   { moduleName: '审计日志', moduleCode: 'route.audit-logs.view', moduleLabel: 'Audit Logs', isOnlyOpenForRoot: false },
   { moduleName: '用户管理', moduleCode: 'route.user.view', moduleLabel: 'Users', isOnlyOpenForRoot: false },
   { moduleName: '角色管理', moduleCode: 'route.role.view', moduleLabel: 'Roles & Permissions', isOnlyOpenForRoot: false },
@@ -171,6 +172,14 @@ export const PERMISSION_TREE: PermissionModuleItem[] = [
       { permName: '查看(全部数据)', frontPermCode: 'usage-logs.all', permLabel: 'View All API Logs' },
     ],
   },
+  {
+    moduleName: '任务日志', moduleCode: 'route.task-logs.view', moduleLabel: 'Task Logs',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 10,
+    children: [
+      { permName: '查看(仅自己)', frontPermCode: 'task-logs.view', permLabel: 'View Task Logs' },
+      { permName: '查看(全部数据)', frontPermCode: 'task-logs.all', permLabel: 'View All Task Logs' },
+    ],
+  },
   {
     moduleName: '审计日志', moduleCode: 'route.audit-logs.view', moduleLabel: 'Audit Logs',
     isOnlyOpenForRoot: false, forRoleTemplateType: 10,

+ 7 - 1
default/src/features/usage-logs/components/columns/column-helpers.tsx

@@ -120,7 +120,13 @@ export function createDurationColumn<T>(config: {
   return {
     id: 'duration',
     header: ({ column }) => (
-      <DataTableColumnHeader column={column} title={headerLabel} />
+      <DataTableColumnHeader
+        column={column}
+        title={headerLabel}
+        // Override the table-wide uppercase header style so the label (e.g.
+        // "Duration") keeps its original casing.
+        className='normal-case'
+      />
     ),
     cell: ({ row }) => {
       const log = row.original as Record<string, unknown>

+ 1 - 2
default/src/features/usage-logs/components/columns/task-logs-columns.tsx

@@ -39,7 +39,6 @@ import { FailReasonDialog } from '../dialogs/fail-reason-dialog'
 import { useUsageLogsContext } from '../usage-logs-provider'
 import {
   createDurationColumn,
-  createChannelColumn,
   createProgressColumn,
 } from './column-helpers'
 
@@ -120,7 +119,7 @@ export function useTaskLogsColumns(isAdmin: boolean): ColumnDef<TaskLog>[] {
   ]
 
   if (isAdmin) {
-    columns.push(createChannelColumn<TaskLog>({ headerLabel: t('Channel') }), {
+    columns.push({
       id: 'user',
       header: t('User'),
       accessorFn: (row) => row.username || row.user_id,

+ 0 - 11
default/src/features/usage-logs/components/common-logs-filter-bar.tsx

@@ -271,7 +271,6 @@ export function CommonLogsFilterBar<TData>(
   const expandedFilterCount = [
     filters.token,
     isAdmin ? filters.username : undefined,
-    isAdmin ? filters.channel : undefined,
     filters.requestId,
     filters.upstreamRequestId,
   ].filter(Boolean).length
@@ -429,16 +428,6 @@ export function CommonLogsFilterBar<TData>(
         />
       </LogsFilterField>
       {usernameFilter}
-      {isAdmin && (
-        <LogsFilterField>
-          <LogsFilterInput
-            placeholder={t('Channel ID')}
-            value={filters.channel || ''}
-            onChange={(e) => handleChange('channel', e.target.value)}
-            onKeyDown={handleKeyDown}
-          />
-        </LogsFilterField>
-      )}
       <LogsFilterField>
         <LogsFilterInput
           placeholder={t('Request ID')}

+ 3 - 25
default/src/features/usage-logs/components/task-logs-filter-bar.tsx

@@ -31,7 +31,6 @@ import {
   LogsFilterInput,
   LogsFilterToolbar,
 } from './logs-filter-toolbar'
-import { useLogsViewScope } from './usage-logs-provider'
 
 const route = getRouteApi('/_authenticated/usage-logs/$section')
 
@@ -69,7 +68,6 @@ export function TaskLogsFilterBar<TData>(props: TaskLogsFilterBarProps<TData>) {
   const navigate = useNavigate()
   const queryClient = useQueryClient()
   const searchParams = route.useSearch()
-  const { isAdminView: isAdmin } = useLogsViewScope(props.logCategory)
   const fetchingLogs = useIsFetching({ queryKey: ['logs'] })
 
   const [filters, setFilters] = useState<TaskLogsFilters>(() => {
@@ -84,9 +82,6 @@ export function TaskLogsFilterBar<TData>(props: TaskLogsFilterBarProps<TData>) {
         ? new Date(searchParams.startTime)
         : start,
       endTime: searchParams.endTime ? new Date(searchParams.endTime) : end,
-      ...(searchParams.channel
-        ? { channel: String(searchParams.channel) }
-        : {}),
     }
     const next: TaskLogsFilters =
       props.logCategory === 'drawing'
@@ -104,7 +99,6 @@ export function TaskLogsFilterBar<TData>(props: TaskLogsFilterBarProps<TData>) {
     props.logCategory,
     searchParams.startTime,
     searchParams.endTime,
-    searchParams.channel,
     searchParams.filter,
   ])
 
@@ -164,7 +158,7 @@ export function TaskLogsFilterBar<TData>(props: TaskLogsFilterBarProps<TData>) {
     props.logCategory === 'drawing'
       ? t('Filter by MjProxy task ID')
       : t('Filter by task ID')
-  const hasAdditionalFilters = !!filterValue || !!filters.channel
+  const hasAdditionalFilters = !!filterValue
   const dateRangeFilter = (
     <LogsFilterField wide>
       <CompactDateTimeRangePicker
@@ -188,16 +182,6 @@ export function TaskLogsFilterBar<TData>(props: TaskLogsFilterBarProps<TData>) {
       />
     </LogsFilterField>
   )
-  const channelFilter = isAdmin ? (
-    <LogsFilterField>
-      <LogsFilterInput
-        placeholder={t('Channel ID')}
-        value={filters.channel || ''}
-        onChange={(e) => handleChange('channel', e.target.value)}
-        onKeyDown={handleKeyDown}
-      />
-    </LogsFilterField>
-  ) : null
 
   return (
     <LogsFilterToolbar
@@ -206,17 +190,11 @@ export function TaskLogsFilterBar<TData>(props: TaskLogsFilterBarProps<TData>) {
         <>
           {dateRangeFilter}
           {taskIdFilter}
-          {channelFilter}
         </>
       }
       mobilePinnedFilters={dateRangeFilter}
-      mobileFilters={
-        <>
-          {taskIdFilter}
-          {channelFilter}
-        </>
-      }
-      mobileFilterCount={[filterValue, filters.channel].filter(Boolean).length}
+      mobileFilters={taskIdFilter}
+      mobileFilterCount={filterValue ? 1 : 0}
       hasActiveFilters={hasAdditionalFilters}
       onSearch={handleApply}
       searchLoading={fetchingLogs > 0}

+ 11 - 3
default/src/features/usage-logs/components/usage-logs-provider.tsx

@@ -91,15 +91,23 @@ export function useUsageLogsContext() {
 }
 
 function getLogScopePermissionCodes(logCategory: LogCategory) {
-  return logCategory === 'audit'
-    ? {
+  switch (logCategory) {
+    case 'audit':
+      return {
         view: PERMISSION_CODES.AUDIT_LOGS_VIEW,
         all: PERMISSION_CODES.AUDIT_LOGS_ALL,
       }
-    : {
+    case 'task':
+      return {
+        view: PERMISSION_CODES.TASK_LOGS_VIEW,
+        all: PERMISSION_CODES.TASK_LOGS_ALL,
+      }
+    default:
+      return {
         view: PERMISSION_CODES.USAGE_LOGS_VIEW,
         all: PERMISSION_CODES.USAGE_LOGS_ALL,
       }
+  }
 }
 
 /**

+ 5 - 60
default/src/features/usage-logs/index.tsx

@@ -16,15 +16,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 
 For commercial licensing, please contact support@quantumnous.com
 */
-import { getRouteApi, useNavigate } from '@tanstack/react-router'
-import { useCallback, useMemo } from 'react'
+import { getRouteApi } from '@tanstack/react-router'
+import { useCallback } from 'react'
 import { useTranslation } from 'react-i18next'
 
 import { SectionPageLayout } from '@/components/layout'
-import type { NavGroup } from '@/components/layout/types'
 import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
 import { CacheStatsDialog } from '@/features/system-settings/general/channel-affinity/cache-stats-dialog'
-import { useSidebarConfig } from '@/hooks/use-sidebar-config'
 
 import { UserInfoDialog } from './components/dialogs/user-info-dialog'
 import {
@@ -41,7 +39,6 @@ import {
 } from './section-registry'
 
 const route = getRouteApi('/_authenticated/usage-logs/$section')
-const TASK_LOG_SECTIONS = ['drawing', 'task'] as const
 
 const SECTION_META: Record<UsageLogsSectionId, { titleKey: string }> = {
   common: {
@@ -50,9 +47,6 @@ const SECTION_META: Record<UsageLogsSectionId, { titleKey: string }> = {
   audit: {
     titleKey: 'Audit Logs',
   },
-  drawing: {
-    titleKey: 'Drawing Logs',
-  },
   task: {
     titleKey: 'Task Logs',
   },
@@ -60,7 +54,6 @@ const SECTION_META: Record<UsageLogsSectionId, { titleKey: string }> = {
 
 function UsageLogsContent() {
   const { t } = useTranslation()
-  const navigate = useNavigate()
   const params = route.useParams()
   const activeCategory: UsageLogsSectionId =
     params.section && isUsageLogsSectionId(params.section)
@@ -76,41 +69,6 @@ function UsageLogsContent() {
   } = useUsageLogsContext()
   const { canViewSelf, canViewAll, viewScope, setViewScope } =
     useLogsViewScope(activeCategory)
-  const tabNavGroups = useMemo<NavGroup[]>(
-    () => [
-      {
-        title: 'Task Logs',
-        items: TASK_LOG_SECTIONS.map((section) => ({
-          title: SECTION_META[section].titleKey,
-          url: `/usage-logs/${section}`,
-        })),
-      },
-    ],
-    []
-  )
-  const filteredTabGroups = useSidebarConfig(tabNavGroups)
-  const visibleSections = useMemo(
-    () =>
-      (filteredTabGroups[0]?.items ?? [])
-        .map((item) => {
-          if (!('url' in item) || typeof item.url !== 'string') return null
-          return item.url.split('/').pop() ?? null
-        })
-        .filter((section): section is UsageLogsSectionId =>
-          Boolean(section && isUsageLogsSectionId(section))
-        ),
-    [filteredTabGroups]
-  )
-
-  const handleSectionChange = useCallback(
-    (section: string) => {
-      void navigate({
-        to: '/usage-logs/$section',
-        params: { section: section as UsageLogsSectionId },
-      })
-    },
-    [navigate]
-  )
 
   const handleViewScopeChange = useCallback(
     (scope: string) => {
@@ -122,10 +80,6 @@ function UsageLogsContent() {
   )
 
   const pageMeta = SECTION_META[activeCategory]
-  const showTaskSwitcher =
-    activeCategory !== 'common' &&
-    activeCategory !== 'audit' &&
-    visibleSections.length > 1
 
   return (
     <>
@@ -133,7 +87,9 @@ function UsageLogsContent() {
         <SectionPageLayout.Title>
           {t(pageMeta.titleKey)}
         </SectionPageLayout.Title>
-        {(activeCategory === 'common' || activeCategory === 'audit') &&
+        {(activeCategory === 'common' ||
+          activeCategory === 'audit' ||
+          activeCategory === 'task') &&
           canViewSelf && (
             <SectionPageLayout.Actions>
               <Tabs value={viewScope} onValueChange={handleViewScopeChange}>
@@ -148,17 +104,6 @@ function UsageLogsContent() {
           )}
         <SectionPageLayout.Content>
           <div className='flex h-full min-h-0 flex-col gap-4'>
-            {showTaskSwitcher && (
-              <Tabs value={activeCategory} onValueChange={handleSectionChange}>
-                <TabsList className='max-w-full flex-wrap justify-start group-data-horizontal/tabs:h-auto'>
-                  {visibleSections.map((section) => (
-                    <TabsTrigger key={section} value={section}>
-                      {t(SECTION_META[section].titleKey)}
-                    </TabsTrigger>
-                  ))}
-                </TabsList>
-              </Tabs>
-            )}
             <div className='min-h-0 flex-1'>
               <UsageLogsTable logCategory={activeCategory} />
             </div>

+ 0 - 5
default/src/features/usage-logs/section-registry.tsx

@@ -32,11 +32,6 @@ const USAGE_LOGS_SECTIONS = [
     titleKey: 'Audit Logs',
     build: () => null, // Content is rendered directly in the page component
   },
-  {
-    id: 'drawing',
-    titleKey: 'Drawing Logs',
-    build: () => null, // Content is rendered directly in the page component
-  },
   {
     id: 'task',
     titleKey: 'Task Logs',

+ 0 - 1
default/src/hooks/use-sidebar-config.ts

@@ -103,7 +103,6 @@ const URL_TO_CONFIG_MAP: Record<string, { section: string; module: string }> = {
   '/keys': { section: 'console', module: 'token' },
   '/usage-logs': { section: 'console', module: 'log' },
   '/usage-logs/common': { section: 'console', module: 'log' },
-  '/usage-logs/drawing': { section: 'console', module: 'midjourney' },
   '/usage-logs/task': { section: 'console', module: 'task' },
   '/wallet': { section: 'personal', module: 'topup' },
   '/profile': { section: 'personal', module: 'personal' },

+ 6 - 0
default/src/hooks/use-sidebar-data.ts

@@ -172,6 +172,12 @@ export function useSidebarData(): SidebarData {
             icon: FileText,
             permissionCode: PERMISSION_CODES.USAGE_LOGS_VIEW,
           },
+          {
+            title: t('Task Logs'),
+            url: '/usage-logs/task',
+            icon: FileText,
+            permissionCode: PERMISSION_CODES.TASK_LOGS_VIEW,
+          },
           {
             title: t('Audit Logs'),
             url: '/usage-logs/audit',

+ 2 - 0
default/src/lib/admin-permissions.ts

@@ -57,6 +57,8 @@ export const PERMISSION_CODES = {
   PRICING_VIEW: 'model-pricing.view',
   USAGE_LOGS_VIEW: 'usage-logs.view',
   USAGE_LOGS_ALL: 'usage-logs.all',
+  TASK_LOGS_VIEW: 'task-logs.view',
+  TASK_LOGS_ALL: 'task-logs.all',
   AUDIT_LOGS_VIEW: 'audit-logs.view',
   AUDIT_LOGS_ALL: 'audit-logs.all',
   SETTINGS_VIEW: 'settings.view',

+ 9 - 6
default/src/routes/_authenticated/usage-logs/$section.tsx

@@ -53,9 +53,14 @@ const usageLogsSearchSchema = z.object({
 })
 
 function getSectionPermissionCode(section: string) {
-  return section === 'audit'
-    ? PERMISSION_CODES.AUDIT_LOGS_VIEW
-    : PERMISSION_CODES.USAGE_LOGS_VIEW
+  switch (section) {
+    case 'audit':
+      return PERMISSION_CODES.AUDIT_LOGS_VIEW
+    case 'task':
+      return PERMISSION_CODES.TASK_LOGS_VIEW
+    default:
+      return PERMISSION_CODES.USAGE_LOGS_VIEW
+  }
 }
 
 export const Route = createFileRoute('/_authenticated/usage-logs/$section')({
@@ -69,9 +74,7 @@ export const Route = createFileRoute('/_authenticated/usage-logs/$section')({
       })
     }
 
-    if (
-      !hasPermissionCode(auth.user, getSectionPermissionCode(params.section))
-    ) {
+    if (!hasPermissionCode(auth.user, getSectionPermissionCode(params.section))) {
       throw redirect({
         to: '/403',
       })