mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 19:25:05 +03:00
refactor(frontend): read the panel's navigation from one route manifest (#6922)
The router, the sidebar, the page titles and the command palette each kept their own list of the panel's pages, so a page added to one could go missing from another without any test noticing. They all read src/routes/panelRoutes.tsx now, and a contract test holds the manifest to the router, the translation bundle and the palette keywords. The unused useZodForm helper goes, and CONTRIBUTING, frontend/CLAUDE.md and the frontend README describe the useForm + rhfZodValidate pattern the panel's forms actually use.
This commit is contained in:
@@ -5,21 +5,15 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Drawer, Layout, Menu, Tooltip } from 'antd';
|
||||
import type { MenuProps } from 'antd';
|
||||
import {
|
||||
ApiOutlined,
|
||||
ApartmentOutlined,
|
||||
CloseOutlined,
|
||||
CloudServerOutlined,
|
||||
ClusterOutlined,
|
||||
CodeOutlined,
|
||||
CrownOutlined,
|
||||
DashboardOutlined,
|
||||
DatabaseOutlined,
|
||||
DiscordOutlined,
|
||||
ExportOutlined,
|
||||
GithubOutlined,
|
||||
GlobalOutlined,
|
||||
HeartOutlined,
|
||||
ImportOutlined,
|
||||
LogoutOutlined,
|
||||
MailOutlined,
|
||||
MenuOutlined,
|
||||
@@ -32,11 +26,7 @@ import {
|
||||
SearchOutlined,
|
||||
SettingOutlined,
|
||||
SunOutlined,
|
||||
SwapOutlined,
|
||||
TagsOutlined,
|
||||
TeamOutlined,
|
||||
TelegramFilled,
|
||||
ToolOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
import { HttpUtil } from '@/utils';
|
||||
@@ -44,6 +34,7 @@ import { formatPanelVersion } from '@/lib/panel-version';
|
||||
import { pauseAnimationsUntilLeave, useTheme } from '@/hooks/useTheme';
|
||||
import { useAllSettings } from '@/api/queries/useAllSettings';
|
||||
import { useCommandPalette } from '@/components/command-palette/useCommandPalette';
|
||||
import { PANEL_ROUTES } from '@/routes/panelRoutes';
|
||||
import './AppSidebar.css';
|
||||
|
||||
const DONATE_URL = 'https://donate.sanaei.dev/';
|
||||
@@ -59,37 +50,6 @@ const SIDEBAR_PINNED_KEY = 'sidebar-pinned';
|
||||
|
||||
let hoveredAcrossRemounts = false;
|
||||
|
||||
type IconName =
|
||||
| 'dashboard'
|
||||
| 'inbound'
|
||||
| 'team'
|
||||
| 'groups'
|
||||
| 'setting'
|
||||
| 'tool'
|
||||
| 'cluster'
|
||||
| 'hosts'
|
||||
| 'logout'
|
||||
| 'sponsors'
|
||||
| 'apidocs'
|
||||
| 'outbound'
|
||||
| 'routing';
|
||||
|
||||
const iconByName: Record<IconName, ComponentType> = {
|
||||
dashboard: DashboardOutlined,
|
||||
inbound: ImportOutlined,
|
||||
team: TeamOutlined,
|
||||
groups: TagsOutlined,
|
||||
setting: SettingOutlined,
|
||||
tool: ToolOutlined,
|
||||
cluster: ClusterOutlined,
|
||||
hosts: GlobalOutlined,
|
||||
logout: LogoutOutlined,
|
||||
sponsors: CrownOutlined,
|
||||
apidocs: ApiOutlined,
|
||||
outbound: ExportOutlined,
|
||||
routing: SwapOutlined,
|
||||
};
|
||||
|
||||
function DonateButton({ ariaLabel }: { ariaLabel: string }) {
|
||||
return (
|
||||
<a
|
||||
@@ -228,27 +188,20 @@ export default function AppSidebar() {
|
||||
const currentTheme: 'light' | 'dark' = isDark ? 'dark' : 'light';
|
||||
const panelVersion = window.X_UI_CUR_VER || '';
|
||||
|
||||
const tabs = useMemo<{ key: string; icon: IconName; title: string }[]>(
|
||||
const tabs = useMemo<{ key: string; icon: ComponentType; title: string }[]>(
|
||||
() => [
|
||||
{ key: '/', icon: 'dashboard', title: t('menu.dashboard') },
|
||||
{ key: '/inbounds', icon: 'inbound', title: t('menu.inbounds') },
|
||||
{ key: '/clients', icon: 'team', title: t('menu.clients') },
|
||||
{ key: '/groups', icon: 'groups', title: t('menu.groups') },
|
||||
{ key: '/nodes', icon: 'cluster', title: t('menu.nodes') },
|
||||
{ key: '/hosts', icon: 'hosts', title: t('menu.hosts') },
|
||||
{ key: '/outbound', icon: 'outbound', title: t('menu.outbounds') },
|
||||
{ key: '/routing', icon: 'routing', title: t('menu.routing') },
|
||||
{ key: '/settings', icon: 'setting', title: t('menu.settings') },
|
||||
{ key: '/xray', icon: 'tool', title: t('menu.xray') },
|
||||
{ key: '/api-docs', icon: 'apidocs', title: t('menu.apiDocs') },
|
||||
{ key: '/sponsors', icon: 'sponsors', title: t('menu.sponsors') },
|
||||
{ key: LOGOUT_KEY, icon: 'logout', title: t('logout') },
|
||||
...PANEL_ROUTES.map((route) => ({
|
||||
key: route.path,
|
||||
icon: route.icon,
|
||||
title: t(route.titleKey),
|
||||
})),
|
||||
{ key: LOGOUT_KEY, icon: LogoutOutlined, title: t('logout') },
|
||||
],
|
||||
[t],
|
||||
);
|
||||
|
||||
const navItems = useMemo(() => tabs.filter((tab) => tab.icon !== 'logout'), [tabs]);
|
||||
const utilItems = useMemo(() => tabs.filter((tab) => tab.icon === 'logout'), [tabs]);
|
||||
const navItems = useMemo(() => tabs.filter((tab) => tab.key !== LOGOUT_KEY), [tabs]);
|
||||
const utilItems = useMemo(() => tabs.filter((tab) => tab.key === LOGOUT_KEY), [tabs]);
|
||||
|
||||
const settingsChildren = useMemo<NonNullable<MenuProps['items']>>(() => {
|
||||
const children: NonNullable<MenuProps['items']> = [
|
||||
@@ -341,14 +294,24 @@ export default function AppSidebar() {
|
||||
const toMenuItems = useCallback(
|
||||
(items: typeof tabs): MenuProps['items'] =>
|
||||
items.map((tab) => {
|
||||
const Icon = iconByName[tab.icon];
|
||||
const Icon = tab.icon;
|
||||
if (tab.key === '/settings') {
|
||||
return { key: tab.key, icon: <Icon />, label: tab.title, children: settingsChildren };
|
||||
return {
|
||||
key: tab.key,
|
||||
icon: <Icon aria-hidden />,
|
||||
label: tab.title,
|
||||
children: settingsChildren,
|
||||
};
|
||||
}
|
||||
if (tab.key === '/xray') {
|
||||
return { key: tab.key, icon: <Icon />, label: tab.title, children: xrayChildren };
|
||||
return {
|
||||
key: tab.key,
|
||||
icon: <Icon aria-hidden />,
|
||||
label: tab.title,
|
||||
children: xrayChildren,
|
||||
};
|
||||
}
|
||||
return { key: tab.key, icon: <Icon />, label: tab.title, title: '' };
|
||||
return { key: tab.key, icon: <Icon aria-hidden />, label: tab.title, title: '' };
|
||||
}),
|
||||
[settingsChildren, xrayChildren],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user