mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-10-04 03:40:10 +00:00
196 lines
4.4 KiB
TypeScript
196 lines
4.4 KiB
TypeScript
import {
|
|
IconBook,
|
|
IconDeviceDesktop,
|
|
IconHome,
|
|
IconLock,
|
|
IconSettings,
|
|
IconShield,
|
|
IconUser,
|
|
} from "@tabler/icons-react";
|
|
import cn from "classnames";
|
|
import React from "react";
|
|
import { HasPermission, NavLink } from "src/components";
|
|
import { intl } from "src/locale";
|
|
|
|
interface MenuItem {
|
|
label: string;
|
|
icon?: React.ElementType;
|
|
to?: string;
|
|
items?: MenuItem[];
|
|
permission?: string;
|
|
permissionType?: "view" | "manage";
|
|
}
|
|
|
|
const menuItems: MenuItem[] = [
|
|
{
|
|
to: "/",
|
|
icon: IconHome,
|
|
label: "dashboard.title",
|
|
},
|
|
{
|
|
icon: IconDeviceDesktop,
|
|
label: "hosts.title",
|
|
items: [
|
|
{
|
|
to: "/nginx/proxy",
|
|
label: "proxy-hosts.title",
|
|
permission: "proxyHosts",
|
|
permissionType: "view",
|
|
},
|
|
{
|
|
to: "/nginx/redirection",
|
|
label: "redirection-hosts.title",
|
|
permission: "redirectionHosts",
|
|
permissionType: "view",
|
|
},
|
|
{
|
|
to: "/nginx/stream",
|
|
label: "streams.title",
|
|
permission: "streams",
|
|
permissionType: "view",
|
|
},
|
|
{
|
|
to: "/nginx/404",
|
|
label: "dead-hosts.title",
|
|
permission: "deadHosts",
|
|
permissionType: "view",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
to: "/access",
|
|
icon: IconLock,
|
|
label: "access.title",
|
|
permission: "accessLists",
|
|
permissionType: "view",
|
|
},
|
|
{
|
|
to: "/certificates",
|
|
icon: IconShield,
|
|
label: "certificates.title",
|
|
permission: "certificates",
|
|
permissionType: "view",
|
|
},
|
|
{
|
|
to: "/users",
|
|
icon: IconUser,
|
|
label: "users.title",
|
|
permission: "admin",
|
|
},
|
|
{
|
|
to: "/audit-log",
|
|
icon: IconBook,
|
|
label: "auditlog.title",
|
|
permission: "admin",
|
|
},
|
|
{
|
|
to: "/settings",
|
|
icon: IconSettings,
|
|
label: "settings.title",
|
|
permission: "admin",
|
|
},
|
|
];
|
|
|
|
const getMenuItem = (item: MenuItem, onClick?: () => void) => {
|
|
if (item.items && item.items.length > 0) {
|
|
return getMenuDropown(item, onClick);
|
|
}
|
|
|
|
return (
|
|
<HasPermission
|
|
key={`item-${item.label}`}
|
|
permission={item.permission || ""}
|
|
type={item.permissionType || "view"}
|
|
hideError
|
|
>
|
|
<li className="nav-item">
|
|
<NavLink to={item.to} onClick={onClick}>
|
|
<span className="nav-link-icon d-md-none d-lg-inline-block">
|
|
{item.icon && React.createElement(item.icon, { height: 24, width: 24 })}
|
|
</span>
|
|
<span className="nav-link-title">{intl.formatMessage({ id: item.label })}</span>
|
|
</NavLink>
|
|
</li>
|
|
</HasPermission>
|
|
);
|
|
};
|
|
|
|
const getMenuDropown = (item: MenuItem, onClick?: () => void) => {
|
|
const cns = cn("nav-item", "dropdown");
|
|
return (
|
|
<HasPermission
|
|
key={`item-${item.label}`}
|
|
permission={item.permission || ""}
|
|
type={item.permissionType || "view"}
|
|
hideError
|
|
>
|
|
<li className={cns}>
|
|
<a
|
|
className="nav-link dropdown-toggle"
|
|
href={item.to}
|
|
data-bs-toggle="dropdown"
|
|
data-bs-auto-close="outside"
|
|
aria-expanded="false"
|
|
role="button"
|
|
>
|
|
<span className="nav-link-icon d-md-none d-lg-inline-block">
|
|
<IconDeviceDesktop height={24} width={24} />
|
|
</span>
|
|
<span className="nav-link-title">{intl.formatMessage({ id: item.label })}</span>
|
|
</a>
|
|
<div className="dropdown-menu">
|
|
{item.items?.map((subitem, idx) => {
|
|
return (
|
|
<HasPermission
|
|
key={`${idx}-${subitem.to}`}
|
|
permission={subitem.permission || ""}
|
|
type={subitem.permissionType || "view"}
|
|
hideError
|
|
>
|
|
<NavLink to={subitem.to} isDropdownItem onClick={onClick}>
|
|
{intl.formatMessage({ id: subitem.label })}
|
|
</NavLink>
|
|
</HasPermission>
|
|
);
|
|
})}
|
|
</div>
|
|
</li>
|
|
</HasPermission>
|
|
);
|
|
};
|
|
|
|
export function SiteMenu() {
|
|
// This is hacky AF. But that's the price of using a non-react UI kit.
|
|
const closeMenus = () => {
|
|
const navMenus = document.querySelectorAll(".nav-item.dropdown");
|
|
navMenus.forEach((menu) => {
|
|
menu.classList.remove("show");
|
|
const dropdown = menu.querySelector(".dropdown-menu");
|
|
if (dropdown) {
|
|
dropdown.classList.remove("show");
|
|
}
|
|
});
|
|
};
|
|
|
|
return (
|
|
<header className="navbar-expand-md">
|
|
<div className="collapse navbar-collapse">
|
|
<div className="navbar">
|
|
<div className="container-xl">
|
|
<div className="row flex-column flex-md-row flex-fill align-items-center">
|
|
<div className="col">
|
|
<ul className="navbar-nav">
|
|
{menuItems.length > 0 &&
|
|
menuItems.map((item) => {
|
|
return getMenuItem(item, closeMenus);
|
|
})}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|