| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- import {
- ApiOutlined,
- AppstoreOutlined,
- CloudServerOutlined,
- DashboardOutlined,
- GlobalOutlined,
- InboxOutlined,
- KeyOutlined,
- MailOutlined,
- ReloadOutlined,
- SafetyCertificateOutlined,
- SendOutlined,
- SettingOutlined,
- UserOutlined
- } from '@ant-design/icons';
- import { Avatar, Breadcrumb, Button, Dropdown, Layout, Menu, Select, Space, Typography } from 'antd';
- import type { ReactNode } from 'react';
- import { useI18n } from '../frontend/i18n/react';
- import type { User, ViewKey } from '../frontend/types';
- const { Header, Sider, Content } = Layout;
- const navGroups: Array<{
- key: string;
- labelKey: string;
- items: Array<{ key: ViewKey; labelKey: string; icon: ReactNode; adminOnly?: boolean }>;
- }> = [
- {
- key: 'overview',
- labelKey: 'nav.group.overview',
- items: [
- { key: 'dashboard', labelKey: 'nav.dashboard', icon: <DashboardOutlined /> },
- { key: 'domains', labelKey: 'nav.domains', icon: <GlobalOutlined /> },
- { key: 'dns-api', labelKey: 'nav.dnsApi', icon: <CloudServerOutlined /> }
- ]
- },
- {
- key: 'delivery',
- labelKey: 'nav.group.delivery',
- items: [
- { key: 'smtp', labelKey: 'nav.smtp', icon: <MailOutlined /> },
- { key: 'inbox', labelKey: 'nav.inbox', icon: <InboxOutlined /> },
- { key: 'tokens', labelKey: 'nav.tokens', icon: <KeyOutlined /> },
- { key: 'logs', labelKey: 'nav.logs', icon: <SendOutlined /> },
- { key: 'webhooks', labelKey: 'nav.webhooks', icon: <ApiOutlined /> }
- ]
- },
- {
- key: 'system',
- labelKey: 'nav.group.system',
- items: [
- { key: 'admin', labelKey: 'nav.admin', icon: <SafetyCertificateOutlined />, adminOnly: true },
- { key: 'settings', labelKey: 'nav.settings', icon: <SettingOutlined /> }
- ]
- }
- ];
- interface AdminLayoutProps {
- activeView: ViewKey;
- breadcrumb: string[];
- user: User | null;
- runtimeLine: string;
- loading: boolean;
- children: ReactNode;
- onViewChange: (view: ViewKey) => void;
- onRefresh: () => void;
- onAddDomain: () => void;
- onLogout: () => void;
- }
- export function AdminLayout({
- activeView,
- breadcrumb,
- user,
- runtimeLine,
- loading,
- children,
- onViewChange,
- onRefresh,
- onAddDomain,
- onLogout
- }: AdminLayoutProps) {
- const { locale, locales, setLocale, t } = useI18n();
- const isAdmin = user?.role === 'admin';
- const menuItems = navGroups.map((group) => ({
- type: 'group' as const,
- key: group.key,
- label: t(group.labelKey),
- children: group.items
- .filter((item) => !item.adminOnly || isAdmin)
- .map((item) => ({
- key: item.key,
- icon: item.icon,
- label: t(item.labelKey)
- }))
- }));
- return (
- <Layout className="admin-layout">
- <Sider breakpoint="lg" collapsedWidth={0} width={248} className="admin-sider">
- <div className="admin-sider-inner">
- <div className="brand">
- <div className="brand-logo">MH</div>
- <div>
- <div className="brand-title">MailHub</div>
- <div className="brand-subtitle">Email Delivery</div>
- </div>
- </div>
- <Menu
- theme="dark"
- mode="inline"
- selectedKeys={[activeView]}
- items={menuItems}
- onClick={({ key }) => onViewChange(key as ViewKey)}
- className="admin-menu"
- />
- {user ? (
- <div className="admin-sider-footer">
- <Avatar size={28} icon={<UserOutlined />} className="sider-user-avatar" />
- <div className="sider-user-meta">
- <div className="sider-user-name">{user.username || t('common.user')}</div>
- <div className="sider-user-role">{user.role || '—'}</div>
- </div>
- </div>
- ) : null}
- </div>
- </Sider>
- <Layout>
- <Header className="admin-header">
- <div className="header-title">
- <Breadcrumb items={breadcrumb.map((title) => ({ title }))} className="header-breadcrumb" />
- <Typography.Text type="secondary" className="runtime-line">
- {runtimeLine}
- </Typography.Text>
- </div>
- <Space wrap className="header-actions">
- <Select
- aria-label="Language"
- value={locale}
- options={locales}
- onChange={setLocale}
- className="language-select"
- />
- <Button icon={<ReloadOutlined />} loading={loading} onClick={onRefresh}>
- {t('common.refresh')}
- </Button>
- <Button type="primary" icon={<AppstoreOutlined />} onClick={onAddDomain}>
- {t('common.addDomain')}
- </Button>
- <Dropdown
- menu={{
- items: [
- { key: 'profile', label: user?.email || user?.username || t('common.account'), disabled: true },
- { key: 'logout', label: t('common.logout'), onClick: onLogout }
- ]
- }}
- >
- <Button className="user-button">
- <Space>
- <Avatar size={24} icon={<UserOutlined />} />
- <span>{user?.username || t('common.user')}</span>
- </Space>
- </Button>
- </Dropdown>
- </Space>
- </Header>
- <Content className="admin-content">{children}</Content>
- </Layout>
- </Layout>
- );
- }
|