'use client'; import { ComponentType, useMemo } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { Bell, Database, FileText, FolderOpen, Home, Info, Layers, LayoutList, Settings, ShieldCheck, Terminal, UserRound, LayoutDashboard, Route, Server, Globe, GitBranch, ScrollText, Gauge, } from 'lucide-react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card'; import { Switch } from '@/components/ui/switch'; import services from '@/lib/services'; import type { SystemConfig } from '@/lib/services/admin'; import { toast } from 'sonner'; import { useTranslations } from 'next-intl'; interface MenuItem { path: string; labelKey: string; descKey: string; icon: ComponentType<{ className?: string }>; readOnly?: boolean; } interface MenuGroup { nameKey: string; items: MenuItem[]; } const MENU_GROUPS: MenuGroup[] = [ { nameKey: 'groupBusiness', items: [ { path: '/', labelKey: 'dashboard', descKey: 'descDashboard', icon: LayoutDashboard, readOnly: true, }, { path: '/nodes', labelKey: 'nodes', descKey: 'descNodes', icon: Server, }, { path: '/proxy-routes', labelKey: 'proxyRoutes', descKey: 'descProxyRoutes', icon: Route, }, { path: '/websites', labelKey: 'websites', descKey: 'descWebsites', icon: Globe, }, { path: '/certificates', labelKey: 'certificates', descKey: 'descCertificates', icon: ShieldCheck, }, { path: '/dns-accounts', labelKey: 'dnsAccounts', descKey: 'descDnsAccounts', icon: Settings, }, { path: '/origins', labelKey: 'origins', descKey: 'descOrigins', icon: Home, }, { path: '/waf', labelKey: 'waf', descKey: 'descWaf', icon: ShieldCheck, }, { path: '/ip-groups', labelKey: 'ipGroups', descKey: 'descIpGroups', icon: Layers, }, { path: '/pages', labelKey: 'pages', descKey: 'descPages', icon: FileText, }, { path: '/config-versions', labelKey: 'configVersions', descKey: 'descConfigVersions', icon: GitBranch, }, { path: '/access-logs', labelKey: 'accessLogs', descKey: 'descAccessLogs', icon: ScrollText, }, { path: '/performance', labelKey: 'performance', descKey: 'descPerformance', icon: Gauge, }, ], }, { nameKey: 'groupAdmin', items: [ { path: '/admin/users', labelKey: 'users', descKey: 'descUsers', icon: UserRound, }, { path: '/admin/tasks', labelKey: 'tasks', descKey: 'descTasks', icon: Layers, }, { path: '/admin/files', labelKey: 'storage', descKey: 'descStorage', icon: FolderOpen, }, { path: '/admin/database', labelKey: 'database', descKey: 'descDatabase', icon: Database, }, { path: '/admin/push', labelKey: 'push', descKey: 'descPush', icon: Bell, }, { path: '/admin/logs', labelKey: 'logs', descKey: 'descLogs', icon: Terminal, }, { path: '/admin/system', labelKey: 'system', descKey: 'descSystem', icon: ShieldCheck, }, { path: '/admin/settings', labelKey: 'adminSettings', descKey: 'descAdminSettings', icon: Settings, readOnly: true, }, ], }, { nameKey: 'groupDocs', items: [ { path: 'https://open-flare.pages.dev/', labelKey: 'usageDocs', descKey: 'descUsageDocs', icon: FileText, }, ], }, ]; interface OtherTabProps { configs: Record; } export function OtherTab({ configs }: OtherTabProps) { const queryClient = useQueryClient(); const t = useTranslations('settings.other'); const tNav = useTranslations('layout.nav'); const menuDisplayConfig = useMemo(() => { const raw = configs['menu_display_config']?.value; if (!raw) return {} as Record; try { return JSON.parse(raw) as Record; } catch { return {} as Record; } }, [configs]); const updateMenuConfigMutation = useMutation({ mutationFn: async ({ path, enabled, }: { path: string; enabled: boolean; }) => { const newConfig = { ...menuDisplayConfig, [path]: enabled }; const currentCfg = configs['menu_display_config']; await services.adminSystemConfig.updateSystemConfig( 'menu_display_config', { value: JSON.stringify(newConfig), description: currentCfg?.description || '目录显示配置(JSON 字符串,格式为 {url: enabled})', }, ); }, onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ['admin', 'system-configs'], }); await queryClient.invalidateQueries({ queryKey: ['public-config'] }); toast.success(t('menuDisplayUpdated')); }, onError: (error: Error) => { toast.error(error.message || t('updateConfigFailed')); }, }); const handleMenuToggle = (path: string, checked: boolean) => { updateMenuConfigMutation.mutate({ path, enabled: checked }); }; return (
{t('menuDisplayManagement')} {t('menuDisplayManagementDesc')}
{MENU_GROUPS.map((group) => (
{t(group.nameKey)}
{group.items.map((item) => { const Icon = item.icon; const isReadOnly = !!item.readOnly; const checked = menuDisplayConfig[item.path] !== false; return (
{Icon && ( )} {tNav(item.labelKey)} {isReadOnly && ( {t('notHideable')} )}

{t(item.descKey)}

handleMenuToggle(item.path, val) } />
); })}
))}
{t('securityTipTitle')} {t('securityTipDesc')}
); }