You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SettingsNav.tsx 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import * as classNames from 'classnames';
  21. import * as React from 'react';
  22. import { IndexLink, Link } from 'react-router';
  23. import Dropdown from 'sonar-ui-common/components/controls/Dropdown';
  24. import DropdownIcon from 'sonar-ui-common/components/icons/DropdownIcon';
  25. import ContextNavBar from 'sonar-ui-common/components/ui/ContextNavBar';
  26. import NavBarTabs from 'sonar-ui-common/components/ui/NavBarTabs';
  27. import { translate } from 'sonar-ui-common/helpers/l10n';
  28. import { getBaseUrl } from 'sonar-ui-common/helpers/urls';
  29. import { PendingPluginResult } from '../../../../types/plugins';
  30. import { rawSizes } from '../../../theme';
  31. import PendingPluginsActionNotif from './PendingPluginsActionNotif';
  32. import SystemRestartNotif from './SystemRestartNotif';
  33. interface Props {
  34. extensions: T.Extension[];
  35. fetchPendingPlugins: () => void;
  36. fetchSystemStatus: () => void;
  37. location: {};
  38. pendingPlugins: PendingPluginResult;
  39. systemStatus: T.SysStatus;
  40. }
  41. export default class SettingsNav extends React.PureComponent<Props> {
  42. static defaultProps = {
  43. extensions: []
  44. };
  45. isSomethingActive(urls: string[]): boolean {
  46. const path = window.location.pathname;
  47. return urls.some((url: string) => path.indexOf(getBaseUrl() + url) === 0);
  48. }
  49. isSecurityActive() {
  50. const urls = [
  51. '/admin/users',
  52. '/admin/groups',
  53. '/admin/permissions',
  54. '/admin/permission_templates'
  55. ];
  56. return this.isSomethingActive(urls);
  57. }
  58. isProjectsActive() {
  59. const urls = ['/admin/projects_management', '/admin/background_tasks'];
  60. return this.isSomethingActive(urls);
  61. }
  62. isSystemActive() {
  63. const urls = ['/admin/system'];
  64. return this.isSomethingActive(urls);
  65. }
  66. isMarketplace() {
  67. const urls = ['/admin/marketplace'];
  68. return this.isSomethingActive(urls);
  69. }
  70. renderExtension = ({ key, name }: T.Extension) => {
  71. return (
  72. <li key={key}>
  73. <Link activeClassName="active" to={`/admin/extension/${key}`}>
  74. {name}
  75. </Link>
  76. </li>
  77. );
  78. };
  79. renderConfigurationTab() {
  80. const extensionsWithoutSupport = this.props.extensions.filter(
  81. extension => extension.key !== 'license/support'
  82. );
  83. return (
  84. <Dropdown
  85. overlay={
  86. <ul className="menu">
  87. <li>
  88. <IndexLink activeClassName="active" to="/admin/settings">
  89. {translate('settings.page')}
  90. </IndexLink>
  91. </li>
  92. <li>
  93. <IndexLink activeClassName="active" to="/admin/settings/encryption">
  94. {translate('property.category.security.encryption')}
  95. </IndexLink>
  96. </li>
  97. <li>
  98. <IndexLink activeClassName="active" to="/admin/webhooks">
  99. {translate('webhooks.page')}
  100. </IndexLink>
  101. </li>
  102. {extensionsWithoutSupport.map(this.renderExtension)}
  103. </ul>
  104. }
  105. tagName="li">
  106. {({ onToggleClick, open }) => (
  107. <a
  108. aria-expanded={open}
  109. aria-haspopup="menu"
  110. role="button"
  111. className={classNames('dropdown-toggle', {
  112. active:
  113. open ||
  114. (!this.isSecurityActive() &&
  115. !this.isProjectsActive() &&
  116. !this.isSystemActive() &&
  117. !this.isSomethingActive(['/admin/extension/license/support']) &&
  118. !this.isMarketplace())
  119. })}
  120. href="#"
  121. id="settings-navigation-configuration"
  122. onClick={onToggleClick}>
  123. {translate('sidebar.project_settings')}
  124. <DropdownIcon className="little-spacer-left" />
  125. </a>
  126. )}
  127. </Dropdown>
  128. );
  129. }
  130. renderProjectsTab() {
  131. return (
  132. <Dropdown
  133. overlay={
  134. <ul className="menu">
  135. <li>
  136. <IndexLink activeClassName="active" to="/admin/projects_management">
  137. {translate('management')}
  138. </IndexLink>
  139. </li>
  140. <li>
  141. <IndexLink activeClassName="active" to="/admin/background_tasks">
  142. {translate('background_tasks.page')}
  143. </IndexLink>
  144. </li>
  145. </ul>
  146. }
  147. tagName="li">
  148. {({ onToggleClick, open }) => (
  149. <a
  150. aria-expanded={open}
  151. aria-haspopup="menu"
  152. role="button"
  153. className={classNames('dropdown-toggle', { active: open || this.isProjectsActive() })}
  154. href="#"
  155. onClick={onToggleClick}>
  156. {translate('sidebar.projects')}
  157. <DropdownIcon className="little-spacer-left" />
  158. </a>
  159. )}
  160. </Dropdown>
  161. );
  162. }
  163. renderSecurityTab() {
  164. return (
  165. <Dropdown
  166. overlay={
  167. <ul className="menu">
  168. <li>
  169. <IndexLink activeClassName="active" to="/admin/users">
  170. {translate('users.page')}
  171. </IndexLink>
  172. </li>
  173. <li>
  174. <IndexLink activeClassName="active" to="/admin/groups">
  175. {translate('user_groups.page')}
  176. </IndexLink>
  177. </li>
  178. <li>
  179. <IndexLink activeClassName="active" to="/admin/permissions">
  180. {translate('global_permissions.page')}
  181. </IndexLink>
  182. </li>
  183. <li>
  184. <IndexLink activeClassName="active" to="/admin/permission_templates">
  185. {translate('permission_templates')}
  186. </IndexLink>
  187. </li>
  188. </ul>
  189. }
  190. tagName="li">
  191. {({ onToggleClick, open }) => (
  192. <a
  193. aria-expanded={open}
  194. aria-haspopup="menu"
  195. role="button"
  196. className={classNames('dropdown-toggle', { active: open || this.isSecurityActive() })}
  197. href="#"
  198. onClick={onToggleClick}>
  199. {translate('sidebar.security')}
  200. <DropdownIcon className="little-spacer-left" />
  201. </a>
  202. )}
  203. </Dropdown>
  204. );
  205. }
  206. render() {
  207. const { extensions, pendingPlugins } = this.props;
  208. const hasSupportExtension = extensions.find(extension => extension.key === 'license/support');
  209. const totalPendingPlugins =
  210. pendingPlugins.installing.length +
  211. pendingPlugins.removing.length +
  212. pendingPlugins.updating.length;
  213. const contextNavHeight = rawSizes.contextNavHeightRaw;
  214. let notifComponent;
  215. if (this.props.systemStatus === 'RESTARTING') {
  216. notifComponent = <SystemRestartNotif />;
  217. } else if (totalPendingPlugins > 0) {
  218. notifComponent = (
  219. <PendingPluginsActionNotif
  220. fetchSystemStatus={this.props.fetchSystemStatus}
  221. pending={pendingPlugins}
  222. refreshPending={this.props.fetchPendingPlugins}
  223. systemStatus={this.props.systemStatus}
  224. />
  225. );
  226. }
  227. return (
  228. <ContextNavBar
  229. height={notifComponent ? contextNavHeight + 30 : contextNavHeight}
  230. id="context-navigation"
  231. notif={notifComponent}>
  232. <header className="navbar-context-header">
  233. <h1>{translate('layout.settings')}</h1>
  234. </header>
  235. <NavBarTabs>
  236. {this.renderConfigurationTab()}
  237. {this.renderSecurityTab()}
  238. {this.renderProjectsTab()}
  239. <li>
  240. <IndexLink activeClassName="active" to="/admin/system">
  241. {translate('sidebar.system')}
  242. </IndexLink>
  243. </li>
  244. <li>
  245. <IndexLink activeClassName="active" to="/admin/marketplace">
  246. {translate('marketplace.page')}
  247. </IndexLink>
  248. </li>
  249. {hasSupportExtension && (
  250. <li>
  251. <IndexLink activeClassName="active" to="/admin/extension/license/support">
  252. {translate('support')}
  253. </IndexLink>
  254. </li>
  255. )}
  256. </NavBarTabs>
  257. </ContextNavBar>
  258. );
  259. }
  260. }