App.tsx 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. /*
  2. Copyright (C) 2017 Cloudbase Solutions SRL
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. import { hot } from "react-hot-loader/root";
  15. import React from "react";
  16. import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
  17. import IdleTimer from "react-idle-timer";
  18. import styled, { createGlobalStyle } from "styled-components";
  19. import { observe } from "mobx";
  20. import Fonts from "@src/components/ui/Fonts";
  21. import NotificationsModule from "@src/components/modules/NotificationsModule";
  22. import LoginPage from "@src/components/smart/LoginPage";
  23. import TransfersPage from "@src/components/smart/TransfersPage";
  24. import MessagePage from "@src/components/smart/MessagePage";
  25. import TransferDetailsPage from "@src/components/smart/TransferDetailsPage";
  26. import DeploymentsPage from "@src/components/smart/DeploymentsPage";
  27. import DeploymentDetailsPage from "@src/components/smart/DeploymentDetailsPage";
  28. import MetalHubServersPage from "@src/components/smart/MetalHubServersPage";
  29. import EndpointsPage from "@src/components/smart/EndpointsPage";
  30. import EndpointDetailsPage from "@src/components/smart/EndpointDetailsPage";
  31. import UsersPage from "@src/components/smart/UsersPage";
  32. import UserDetailsPage from "@src/components/smart/UserDetailsPage";
  33. import ProjectsPage from "@src/components/smart/ProjectsPage";
  34. import ProjectDetailsPage from "@src/components/smart/ProjectDetailsPage";
  35. import DashboardPage from "@src/components/smart/DashboardPage";
  36. import LogsPage from "@src/components/smart/LogsPage";
  37. import LogStreamPage from "@src/components/smart/LogStreamPage";
  38. import WizardPage from "@src/components/smart/WizardPage";
  39. import Tooltip from "@src/components/ui/Tooltip";
  40. import MinionPoolsPage from "@src/components/smart/MinionPoolsPage";
  41. import MinionPoolDetailsPage from "@src/components/smart/MinionPoolDetailsPage";
  42. import { ThemePalette, ThemeProps } from "@src/components/Theme";
  43. import configLoader from "@src/utils/Config";
  44. import { navigationMenu } from "@src/constants";
  45. import userStore from "@src/stores/UserStore";
  46. import SetupPage from "@src/components/smart/SetupPage";
  47. import MetalHubServerDetailsPage from "@src/components/smart/MetalHubServerDetailsPage";
  48. const GlobalStyle = createGlobalStyle`
  49. ${Fonts}
  50. html, body, main {
  51. height: 100%;
  52. display: flex;
  53. flex-direction: column;
  54. min-height: 0;
  55. }
  56. body {
  57. margin: 0;
  58. color: ${ThemePalette.black};
  59. font-family: Rubik;
  60. font-size: 14px;
  61. font-weight: ${ThemeProps.fontWeights.regular};
  62. -webkit-font-smoothing: antialiased;
  63. -moz-osx-font-smoothing: grayscale;
  64. }
  65. `;
  66. const Wrapper = styled.div`
  67. height: 100%;
  68. min-height: 0;
  69. display: flex;
  70. flex-direction: column;
  71. > div:first-child {
  72. height: 100%;
  73. }
  74. `;
  75. type State = {
  76. isConfigReady: boolean;
  77. };
  78. class App extends React.Component<Record<string, unknown>, State> {
  79. state = {
  80. isConfigReady: false,
  81. };
  82. onIdle() {
  83. userStore.logout();
  84. }
  85. async componentDidMount() {
  86. observe(userStore, "loggedUser", () => {
  87. this.setState({});
  88. });
  89. await configLoader.load();
  90. if (configLoader.isFirstLaunch && window.location.pathname !== "/login") {
  91. if (window.location.pathname !== "/") {
  92. window.location.href = "/";
  93. return;
  94. }
  95. } else {
  96. userStore.tokenLogin();
  97. }
  98. this.setState({ isConfigReady: true });
  99. }
  100. render() {
  101. if (!this.state.isConfigReady) {
  102. return null;
  103. }
  104. const renderMessagePage = (options: {
  105. path: string;
  106. exact?: boolean;
  107. title: string;
  108. subtitle: string;
  109. showAuthAnimation?: boolean;
  110. showDenied?: boolean;
  111. }) => (
  112. <Route
  113. path={options.path}
  114. // @ts-ignore
  115. exact={options.exact}
  116. render={() => (
  117. <MessagePage
  118. title={options.title}
  119. subtitle={options.subtitle}
  120. showAuthAnimation={options.showAuthAnimation}
  121. showDenied={options.showDenied}
  122. />
  123. )}
  124. />
  125. );
  126. const renderRoute = (
  127. path: string,
  128. component: React.ReactNode,
  129. exact?: boolean
  130. ) => {
  131. if (!userStore.loggedUser) {
  132. return renderMessagePage({
  133. path,
  134. exact,
  135. title: "Authenticating...",
  136. subtitle: "Please wait while authenticating user.",
  137. showAuthAnimation: true,
  138. });
  139. }
  140. // @ts-ignore
  141. return <Route path={path} component={component} exact={exact} />;
  142. };
  143. const renderOptionalRoute = (opts: {
  144. name: string;
  145. component: React.ReactNode;
  146. path?: string;
  147. exact?: boolean;
  148. }) => {
  149. const { name, component, path, exact } = opts;
  150. if (configLoader.config.disabledPages.find(p => p === name)) {
  151. return null;
  152. }
  153. const actualPath = `${path || `/${name}`}`;
  154. const requiresAdmin = Boolean(
  155. navigationMenu.find(n => n.value === name && n.requiresAdmin)
  156. );
  157. if (!requiresAdmin) {
  158. return renderRoute(actualPath, component, exact);
  159. }
  160. if (!userStore.loggedUser || userStore.loggedUser.isAdmin == null) {
  161. return renderMessagePage({
  162. path: actualPath,
  163. exact,
  164. title: "Checking permissions...",
  165. subtitle: "Please wait while checking user's permissions.",
  166. showAuthAnimation: true,
  167. });
  168. }
  169. if (userStore.loggedUser?.isAdmin === false) {
  170. return renderMessagePage({
  171. path: actualPath,
  172. exact,
  173. title: "User doesn't have permissions to view this page",
  174. subtitle:
  175. "Please login in with an administrator acount to view this page.",
  176. showDenied: true,
  177. });
  178. }
  179. if (userStore.loggedUser?.isAdmin) {
  180. // @ts-ignore
  181. return <Route path={actualPath} exact={exact} component={component} />;
  182. }
  183. return null;
  184. };
  185. return (
  186. <Wrapper>
  187. <GlobalStyle />
  188. {configLoader.config.inactiveSessionTimeout > 0 ? (
  189. <IdleTimer
  190. timeout={configLoader.config.inactiveSessionTimeout}
  191. throttle={500}
  192. onIdle={this.onIdle}
  193. />
  194. ) : null}
  195. <Router>
  196. <Switch>
  197. {configLoader.isFirstLaunch ? (
  198. // @ts-ignore
  199. <Route path="/" component={SetupPage} exact />
  200. ) : (
  201. // @ts-ignore
  202. renderRoute("/", DashboardPage, true)
  203. )}
  204. {
  205. // @ts-ignore
  206. <Route path="/login" component={LoginPage} />
  207. }
  208. {renderRoute("/dashboard", DashboardPage)}
  209. {renderRoute("/transfers", TransfersPage, true)}
  210. {renderRoute("/transfers/:id", TransferDetailsPage, true)}
  211. {renderRoute("/transfers/:id/:page", TransferDetailsPage)}
  212. {renderRoute("/deployments", DeploymentsPage, true)}
  213. {renderRoute("/deployments/:id", DeploymentDetailsPage, true)}
  214. {renderRoute("/deployments/:id/:page", DeploymentDetailsPage)}
  215. {renderRoute("/endpoints", EndpointsPage, true)}
  216. {renderRoute("/endpoints/:id", EndpointDetailsPage)}
  217. {renderRoute("/minion-pools", MinionPoolsPage, true)}
  218. {renderRoute("/minion-pools/:id", MinionPoolDetailsPage, true)}
  219. {renderRoute("/minion-pools/:id/:page", MinionPoolDetailsPage)}
  220. {renderRoute("/bare-metal-servers", MetalHubServersPage, true)}
  221. {renderRoute("/bare-metal-servers/:id", MetalHubServerDetailsPage)}
  222. {renderRoute("/wizard/:type", WizardPage)}
  223. {renderOptionalRoute({
  224. name: "users",
  225. component: UsersPage,
  226. exact: true,
  227. })}
  228. {renderOptionalRoute({
  229. name: "users",
  230. component: UserDetailsPage,
  231. path: "/users/:id",
  232. })}
  233. {renderOptionalRoute({
  234. name: "projects",
  235. component: ProjectsPage,
  236. exact: true,
  237. })}
  238. {renderOptionalRoute({
  239. name: "projects",
  240. component: ProjectDetailsPage,
  241. path: "/projects/:id",
  242. })}
  243. {renderOptionalRoute({ name: "logging", component: LogsPage })}
  244. {renderRoute("/streamlog", LogStreamPage)}
  245. {
  246. // @ts-ignore
  247. <Route component={MessagePage} />
  248. }
  249. </Switch>
  250. </Router>
  251. <NotificationsModule />
  252. <Tooltip />
  253. </Wrapper>
  254. );
  255. }
  256. }
  257. export default hot(App);