SiteMenu.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import {
  2. IconBook,
  3. IconDeviceDesktop,
  4. IconHome,
  5. IconLock,
  6. IconSettings,
  7. IconShield,
  8. IconUser,
  9. } from "@tabler/icons-react";
  10. import cn from "classnames";
  11. import React from "react";
  12. import { HasPermission, NavLink } from "src/components";
  13. import { T } from "src/locale";
  14. interface MenuItem {
  15. label: string;
  16. icon?: React.ElementType;
  17. to?: string;
  18. items?: MenuItem[];
  19. permission?: string;
  20. permissionType?: "view" | "manage";
  21. }
  22. const menuItems: MenuItem[] = [
  23. {
  24. to: "/",
  25. icon: IconHome,
  26. label: "dashboard.title",
  27. },
  28. {
  29. icon: IconDeviceDesktop,
  30. label: "hosts.title",
  31. items: [
  32. {
  33. to: "/nginx/proxy",
  34. label: "proxy-hosts.title",
  35. permission: "proxyHosts",
  36. permissionType: "view",
  37. },
  38. {
  39. to: "/nginx/redirection",
  40. label: "redirection-hosts.title",
  41. permission: "redirectionHosts",
  42. permissionType: "view",
  43. },
  44. {
  45. to: "/nginx/stream",
  46. label: "streams.title",
  47. permission: "streams",
  48. permissionType: "view",
  49. },
  50. {
  51. to: "/nginx/404",
  52. label: "dead-hosts.title",
  53. permission: "deadHosts",
  54. permissionType: "view",
  55. },
  56. ],
  57. },
  58. {
  59. to: "/access",
  60. icon: IconLock,
  61. label: "access.title",
  62. permission: "accessLists",
  63. permissionType: "view",
  64. },
  65. {
  66. to: "/certificates",
  67. icon: IconShield,
  68. label: "certificates.title",
  69. permission: "certificates",
  70. permissionType: "view",
  71. },
  72. {
  73. to: "/users",
  74. icon: IconUser,
  75. label: "users.title",
  76. permission: "admin",
  77. },
  78. {
  79. to: "/audit-log",
  80. icon: IconBook,
  81. label: "auditlog.title",
  82. permission: "admin",
  83. },
  84. {
  85. to: "/settings",
  86. icon: IconSettings,
  87. label: "settings.title",
  88. permission: "admin",
  89. },
  90. ];
  91. const getMenuItem = (item: MenuItem, onClick?: () => void) => {
  92. if (item.items && item.items.length > 0) {
  93. return getMenuDropown(item, onClick);
  94. }
  95. return (
  96. <HasPermission
  97. key={`item-${item.label}`}
  98. permission={item.permission || ""}
  99. type={item.permissionType || "view"}
  100. hideError
  101. >
  102. <li className="nav-item">
  103. <NavLink to={item.to} onClick={onClick}>
  104. <span className="nav-link-icon d-md-none d-lg-inline-block">
  105. {item.icon && React.createElement(item.icon, { height: 24, width: 24 })}
  106. </span>
  107. <span className="nav-link-title">
  108. <T id={item.label} />
  109. </span>
  110. </NavLink>
  111. </li>
  112. </HasPermission>
  113. );
  114. };
  115. const getMenuDropown = (item: MenuItem, onClick?: () => void) => {
  116. const cns = cn("nav-item", "dropdown");
  117. return (
  118. <HasPermission
  119. key={`item-${item.label}`}
  120. permission={item.permission || ""}
  121. type={item.permissionType || "view"}
  122. hideError
  123. >
  124. <li className={cns}>
  125. <a
  126. className="nav-link dropdown-toggle"
  127. href={item.to}
  128. data-bs-toggle="dropdown"
  129. data-bs-auto-close="outside"
  130. aria-expanded="false"
  131. role="button"
  132. >
  133. <span className="nav-link-icon d-md-none d-lg-inline-block">
  134. <IconDeviceDesktop height={24} width={24} />
  135. </span>
  136. <span className="nav-link-title">
  137. <T id={item.label} />
  138. </span>
  139. </a>
  140. <div className="dropdown-menu">
  141. {item.items?.map((subitem, idx) => {
  142. return (
  143. <HasPermission
  144. key={`${idx}-${subitem.to}`}
  145. permission={subitem.permission || ""}
  146. type={subitem.permissionType || "view"}
  147. hideError
  148. >
  149. <NavLink to={subitem.to} isDropdownItem onClick={onClick}>
  150. <T id={subitem.label} />
  151. </NavLink>
  152. </HasPermission>
  153. );
  154. })}
  155. </div>
  156. </li>
  157. </HasPermission>
  158. );
  159. };
  160. export function SiteMenu() {
  161. // This is hacky AF. But that's the price of using a non-react UI kit.
  162. const closeMenus = () => {
  163. const navMenus = document.querySelectorAll(".nav-item.dropdown");
  164. navMenus.forEach((menu) => {
  165. menu.classList.remove("show");
  166. const dropdown = menu.querySelector(".dropdown-menu");
  167. if (dropdown) {
  168. dropdown.classList.remove("show");
  169. }
  170. });
  171. };
  172. return (
  173. <header className="navbar-expand-md">
  174. <div className="collapse navbar-collapse">
  175. <div className="navbar">
  176. <div className="container-xl">
  177. <div className="row flex-column flex-md-row flex-fill align-items-center">
  178. <div className="col">
  179. <ul className="navbar-nav">
  180. {menuItems.length > 0 &&
  181. menuItems.map((item) => {
  182. return getMenuItem(item, closeMenus);
  183. })}
  184. </ul>
  185. </div>
  186. </div>
  187. </div>
  188. </div>
  189. </div>
  190. </header>
  191. );
  192. }