diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e8966910e5..68cd00e034 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -44,20 +44,13 @@ // Set *default* container specific settings.json values on container create. "settings": { "terminal.integrated.defaultProfile.linux": "bash", + "editor.formatOnPaste": true, + "editor.formatOnSave": true, "python.pythonPath": "/usr/local/bin/python", - "python.linting.enabled": true, - "python.linting.pylintEnabled": false, - "python.linting.flake8Enabled": true, "python.formatting.provider": "black", "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", "python.formatting.blackPath": "/usr/local/py-utils/bin/black", "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", - "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", - "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", - "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", - "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", - "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", - "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint", "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, "python.testing.pytestArgs": [ @@ -276,6 +269,7 @@ "ms-python.python", "ms-python.pylance", "ms-python.flake8", + "nwgh.bandit", "hashicorp.terraform", "github.vscode-pull-request-github", "gitHub.copilot", @@ -295,5 +289,7 @@ ], // Run commands after the container is created. "postCreateCommand": "./.devcontainer/scripts/post-create.sh", - "initializeCommand": ["./.devcontainer/scripts/initialize"] + "initializeCommand": [ + "./.devcontainer/scripts/initialize" + ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index e015c950ff..c596b48c02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ENHANCEMENTS: * Subnet definitions are now inline in the `azurerm_virtual_network` resource, and NSG associations are set using `security_group` in each subnet block (no separate `azurerm_subnet_network_security_group_association` needed). ([[#4255](https://github.com/microsoft/AzureTRE/pull/4255/)]) * Azure Cosmos DB should disable public network access ([#4322](https://github.com/microsoft/AzureTRE/issues/4322)) * Add bundle target to Makefile for handling different bundle types in single command ([#4372](https://github.com/microsoft/AzureTRE/issues/4372)) +* Migrate UI to Vite build engine and update dependencies ([#4368](https://github.com/microsoft/AzureTRE/pull/4368)) * Add Windows image field to the Admin VM template ([#4274](https://github.com/microsoft/AzureTRE/pull/4274)) * Update TLS to the latest version for web apps / function apps (([#4351](https://github.com/microsoft/AzureTRE/issues/4351)) diff --git a/docs/tre-developers/ui.md b/docs/tre-developers/ui.md index e9b4a04441..872a883d5f 100644 --- a/docs/tre-developers/ui.md +++ b/docs/tre-developers/ui.md @@ -4,7 +4,7 @@ This project contains a React-based web UI which covers the core aspects of a TR ## Chosen UI Stack + Components The UI is built upon several popular web frameworks: -- React v18 (created via create-react-app, with all build configurations left as defaults) +- React v18 (with Vite) - Typescript - React Router v6 for client side routing - Fluent UI [Fluent UI Docs](https://developer.microsoft.com/en-us/fluentui#/controls/web) @@ -54,4 +54,53 @@ The UI is deployed as part of the `tre-deploy` make target (unless you set `depl To re-deploy _just_ the UI (after an initial deploy), run `make build-and-deploy-ui` from the root of the dev container. This will: - Use the environment variables from your deployment to create a `config.json` file for the UI - Build the source code, via `yarn build` -- Deploy the code to Azure blob storage, where it will be statically served behind the App Gateway that also fronts the APi. +- Deploy the code to Azure blob storage, where it will be statically served behind the App Gateway that also fronts the API. + +## Run the UI +- Ensure `deploy_ui=false` is not set in your `./config.yaml` file +- In the root of the repo, run `make tre-deploy`. This will provision the necessary resources in Azure, build and deploy the UI to Azure blob storage, behind the App Gateway used for the API. The deployment process will also create the necessary `config.json`, using the `config.source.json` as a template. +- In Microsoft Entra ID, locate the TRE Client Apps app (possibly called Swagger App). In the Authentication section add reply URIs for: + - `http://localhost:3000` (if wanting to run locally) + - Your deployed App Url - `https://{TRE_ID}.{LOCATION}.cloudapp.azure.com`. + +At this point you should be able to navigate to the web app in Azure, log in, and see your workspaces. + +## Available Scripts + +In the UI directory, you can run: + +### `yarn start` + +Runs the app in the development mode.
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.
+You will also see any lint errors in the console. + +### `yarn test` + +Launches the test runner in the interactive watch mode.
+ +### `yarn run build` + +Builds the app for production to the `build` folder.
+It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.
+Your app is ready to be deployed! + +### `yarn run serve` + +Serves the production build from the `build` folder.
+ +### `yarn run test:coverage` + +Runs the tests and generates a coverage report.
+ +### `yarn lint` + +Runs the linter on the project.
+ +### `yarn format` + +Runs the formatter on the project.
diff --git a/ui/README.md b/ui/README.md deleted file mode 100644 index b605e4bc71..0000000000 --- a/ui/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# TRE UI - -Please see the docs for a full overview and deployment instructions. - -The UI was built using Create React App and Microsoft Fluent UI. Further details on this in the ./app/README. - -## Run the UI -- Ensure `deploy_ui=false` is not set in your `./config.yaml` file -- In the root of the repo, run `make tre-deploy`. This will provision the necessary resources in Azure, build and deploy the UI to Azure blob storage, behind the App Gateway used for the API. The deployment process will also create the necessary `config.json`, using the `config.source.json` as a template. -- In Microsoft Entra ID, locate the TRE Client Apps app (possibly called Swagger App). In the Authentication section add reply URIs for: - - `http://localhost:3000` (if wanting to run locally) - - Your deployed App Url - `https://{TRE_ID}.{LOCATION}.cloudapp.azure.com`. - -At this point you should be able to navigate to the web app in Azure, log in, and see your workspaces. - -### To run locally -- `cd ./ui/app` -- `yarn start` - -After making changes to the code, redeploy to Azure by running `make build-and-deploy-ui` in the root of the dev container. diff --git a/ui/app/.prettierrc b/ui/app/.prettierrc new file mode 100644 index 0000000000..dc6958febb --- /dev/null +++ b/ui/app/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": false, + "semi": true +} diff --git a/ui/app/README.md b/ui/app/README.md deleted file mode 100644 index 387b475e1e..0000000000 --- a/ui/app/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# Getting Started with Create React App and Fluent UI - -This is a [Create React App](https://github.com/facebook/create-react-app) based repo that comes with Fluent UI pre-installed! - -## Available Scripts - -In the project directory, you can run: - -### `yarn start` - -Runs the app in the development mode.
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser. - -The page will reload if you make edits.
-You will also see any lint errors in the console. - -### `yarn test` - -Launches the test runner in the interactive watch mode.
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. - -### `yarn build` - -Builds the app for production to the `build` folder.
-It correctly bundles React in production mode and optimizes the build for the best performance. - -The build is minified and the filenames include the hashes.
-Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `yarn eject` - -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** - -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. - -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. - -## Learn More - -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). - -## Contributing - -This project welcomes contributions and suggestions. Most contributions require you to agree to a -Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us -the rights to use your contribution. For details, visit [CLA](https://cla.microsoft.com). - -When you submit a pull request, a CLA-bot will automatically determine whether you need to provide -a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions -provided by the bot. You will only need to do this once across all repos using our CLA. - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/ui/app/eslint.config.js b/ui/app/eslint.config.js new file mode 100644 index 0000000000..c61fac8c72 --- /dev/null +++ b/ui/app/eslint.config.js @@ -0,0 +1,24 @@ +import eslintConfigPrettier from "eslint-config-prettier"; +import typescriptEslint from "@typescript-eslint/eslint-plugin"; +import typescriptParser from "@typescript-eslint/parser"; + +export default [ + { + files: ["**/*.{ts,tsx}"], + languageOptions: { + parser: typescriptParser, + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + }, + plugins: { + "@typescript-eslint": typescriptEslint, + }, + rules: {}, + }, + eslintConfigPrettier, +]; diff --git a/ui/app/index.html b/ui/app/index.html new file mode 100644 index 0000000000..119f64be91 --- /dev/null +++ b/ui/app/index.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + Azure TRE + + + +
+ + + diff --git a/ui/app/package.json b/ui/app/package.json index eec9d31ead..9d8321c24a 100644 --- a/ui/app/package.json +++ b/ui/app/package.json @@ -2,6 +2,7 @@ "name": "tre-ui", "version": "0.7.0", "private": true, + "type": "module", "dependencies": { "@azure/msal-browser": "^2.35.0", "@azure/msal-react": "^1.5.12", @@ -12,16 +13,11 @@ "@rjsf/fluent-ui": "^5.24.3", "@rjsf/utils": "^5.24.3", "@rjsf/validator-ajv8": "^5.24.3", - "@testing-library/dom": "^7.21.4", - "@testing-library/jest-dom": "^6.2.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.6.1", - "@types/jest": "^29.5.0", "@types/node": "^20.17.14", "@types/react": "^18.3.16", "@types/react-dom": "^18.2.6", + "@vitejs/plugin-react-swc": "latest", "moment": "^2.29.4", - "node-sass": "^8.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-markdown": "^8.0.3", @@ -29,26 +25,43 @@ "react-router-dom": "6.28.2", "remark-gfm": "^3.0.1", "typescript": "^5.6.3", + "vite": "latest", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-svgr": "latest", + "vite-tsconfig-paths": "latest", "web-vitals": "^3.3.0" }, "devDependencies": { - "@babel/core": "^7.23.7", - "@babel/plugin-proposal-private-property-in-object": "^7.21.11", - "@babel/plugin-syntax-flow": "^7.23.3", - "@babel/plugin-transform-react-jsx": "^7.23.4", - "react-scripts": "5.0.1" + "@testing-library/dom": "^7.21.4", + "@testing-library/jest-dom": "^6.2.0", + "@testing-library/react": "^14.0.0", + "@types/jest": "^29.5.0", + "@types/node": "^20.17.14", + "@types/react": "^18.3.16", + "@types/react-dom": "^18.2.6", + "@typescript-eslint/eslint-plugin": "^8.24.0", + "@typescript-eslint/parser": "^8.24.0", + "@vitest/coverage-v8": "latest", + "eslint": "^9.20.1", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.3", + "eslint-plugin-react": "^7.37.4", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^15.14.0", + "jsdom": "latest", + "prettier": "3.5.0", + "sass-embedded": "^1.83.4", + "vitest": "latest" }, "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" - }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] + "start": "vite", + "build": "tsc && vite build", + "serve": "vite preview", + "test": "vitest", + "test:coverage": "vitest run --coverage --watch=false", + "lint": "eslint .", + "format": "prettier --write ." }, "browserslist": { "production": [ diff --git a/ui/app/public/index.html b/ui/app/public/index.html index 3412482b4e..493346824c 100644 --- a/ui/app/public/index.html +++ b/ui/app/public/index.html @@ -1,15 +1,18 @@ - + - + - + - + Azure TRE diff --git a/ui/app/src/App.scss b/ui/app/src/App.scss index 704e6dfeb0..0f7c87cd8b 100644 --- a/ui/app/src/App.scss +++ b/ui/app/src/App.scss @@ -1,7 +1,8 @@ body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', - 'Droid Sans', 'Helvetica Neue', sans-serif; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", + "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } @@ -16,7 +17,8 @@ h2 { } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; + font-family: + source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } .tre-logout-message { @@ -88,7 +90,7 @@ ul.tre-notifications-steps-list li { .tre-home-link { color: #fff; text-decoration: none; - font-size:1.2rem; + font-size: 1.2rem; } .tre-user-menu { @@ -118,7 +120,7 @@ ul.tre-notifications-steps-list li { } } -.tre-hide-chevron i[data-icon-name=ChevronDown] { +.tre-hide-chevron i[data-icon-name="ChevronDown"] { display: none; } @@ -174,50 +176,50 @@ ul.tre-notifications-steps-list li { margin-bottom: 10px; } -input[readonly]{ - background-color:#efefef; +input[readonly] { + background-color: #efefef; } -.tre-badge{ - border-radius:4px; +.tre-badge { + border-radius: 4px; background-color: #efefef; - padding:2px 6px; + padding: 2px 6px; text-transform: capitalize; - display:inline-block; - font-size:12px; + display: inline-block; + font-size: 12px; } -.tre-badge-in-progress{ +.tre-badge-in-progress { background-color: #ce7b00; color: #fff; } -.tre-badge-failed{ +.tre-badge-failed { background-color: #990000; color: #fff; padding-top: 4px; padding-left: 7px; font-size: 16px; } -.tre-badge-success{ +.tre-badge-success { background-color: #006600; color: #fff; } -.tre-complex-list{ +.tre-complex-list { list-style: none; - padding:0 0 0 20px; - margin:0; + padding: 0 0 0 20px; + margin: 0; } -.tre-complex-list-border{ +.tre-complex-list-border { border-bottom: 1px #ccc solid; - margin-left:-15px; + margin-left: -15px; } -.tre-complex-list-string{ - padding-left:20px; +.tre-complex-list-string { + padding-left: 20px; } -.tre-complex-list .ms-Icon{ - font-size:12px!important; +.tre-complex-list .ms-Icon { + font-size: 12px !important; font-weight: bold; position: relative; - top:2px; + top: 2px; } // Classes for rendering power state badges @@ -226,7 +228,8 @@ input[readonly]{ color: #636262; margin: 6px; - .tre-power-on, .tre-power-off { + .tre-power-on, + .tre-power-off { height: 8px; width: 8px; background-color: #006600; @@ -267,28 +270,81 @@ input[readonly]{ } /* border around sub-blocks */ -.ms-Panel-content .rjsf > .ms-Grid-col > .ms-Grid > .ms-Grid-row > .field-object, -.ms-Panel-content .rjsf > .ms-Grid-col > .ms-Grid > .ms-Grid-row > .field-array { +.ms-Panel-content + .rjsf + > .ms-Grid-col + > .ms-Grid + > .ms-Grid-row + > .field-object, +.ms-Panel-content + .rjsf + > .ms-Grid-col + > .ms-Grid + > .ms-Grid-row + > .field-array { border: 1px #ccc dashed; padding: 10px; background-color: #fcfcfc; } /* sub titles and sub-sub titles */ -.ms-Panel-content .rjsf > .ms-Grid-col > .ms-Grid > .ms-Grid-row > .field-object > label.ms-Label, -.ms-Panel-content .rjsf > .ms-Grid-col > .ms-Grid > .ms-Grid-row > .field-array > label.ms-Label { +.ms-Panel-content + .rjsf + > .ms-Grid-col + > .ms-Grid + > .ms-Grid-row + > .field-object + > label.ms-Label, +.ms-Panel-content + .rjsf + > .ms-Grid-col + > .ms-Grid + > .ms-Grid-row + > .field-array + > label.ms-Label { font-size: 20px; } -.ms-Panel-content .rjsf > .ms-Grid-col > .ms-Grid > .ms-Grid-row > .field-object > .ms-Grid > .ms-Grid-row > .ms-Grid-col > label.ms-Label, -.ms-Panel-content .rjsf > .ms-Grid-col > .ms-Grid > .ms-Grid-row > .field-array > .ms-Grid > .ms-Grid-row > .ms-Grid-col > label.ms-Label { +.ms-Panel-content + .rjsf + > .ms-Grid-col + > .ms-Grid + > .ms-Grid-row + > .field-object + > .ms-Grid + > .ms-Grid-row + > .ms-Grid-col + > label.ms-Label, +.ms-Panel-content + .rjsf + > .ms-Grid-col + > .ms-Grid + > .ms-Grid-row + > .field-array + > .ms-Grid + > .ms-Grid-row + > .ms-Grid-col + > label.ms-Label { font-size: 16px; } /* remove secondary template description at the bottom of each template + sub blocks */ .rjsf > .ms-Grid-col > span:last-of-type, -.rjsf > .ms-Grid-col > .ms-Grid > .ms-Grid-row > .field-object > span:last-of-type, -.rjsf > .ms-Grid-col > .ms-Grid > .ms-Grid-row > .field-object > .ms-Grid > .ms-Grid-row > .ms-Grid-col > span:last-of-type { +.rjsf + > .ms-Grid-col + > .ms-Grid + > .ms-Grid-row + > .field-object + > span:last-of-type, +.rjsf + > .ms-Grid-col + > .ms-Grid + > .ms-Grid-row + > .field-object + > .ms-Grid + > .ms-Grid-row + > .ms-Grid-col + > span:last-of-type { display: none; } diff --git a/ui/app/src/App.test.tsx b/ui/app/src/App.test.tsx index ac873a8bce..d55194710b 100644 --- a/ui/app/src/App.test.tsx +++ b/ui/app/src/App.test.tsx @@ -1,6 +1,6 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import { App } from './App'; +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { App } from "./App"; it('renders "Welcome to Your Fluent UI App"', () => { render(); diff --git a/ui/app/src/App.tsx b/ui/app/src/App.tsx index 8f21c2d260..24d0b5220d 100644 --- a/ui/app/src/App.tsx +++ b/ui/app/src/App.tsx @@ -1,45 +1,65 @@ -import React, { useEffect, useState } from 'react'; -import { DefaultPalette, IStackStyles, MessageBar, MessageBarType, Stack } from '@fluentui/react'; -import './App.scss'; -import { TopNav } from './components/shared/TopNav'; -import { Routes, Route } from 'react-router-dom'; -import { RootLayout } from './components/root/RootLayout'; -import { WorkspaceProvider } from './components/workspaces/WorkspaceProvider'; -import { MsalAuthenticationTemplate } from '@azure/msal-react'; -import { InteractionType } from '@azure/msal-browser'; -import { Workspace } from './models/workspace'; -import { AppRolesContext } from './contexts/AppRolesContext'; -import { WorkspaceContext } from './contexts/WorkspaceContext'; -import { GenericErrorBoundary } from './components/shared/GenericErrorBoundary'; -import { HttpMethod, ResultType, useAuthApiCall } from './hooks/useAuthApiCall'; -import { ApiEndpoint } from './models/apiEndpoints'; -import { CreateUpdateResource } from './components/shared/create-update-resource/CreateUpdateResource'; -import { CreateUpdateResourceContext } from './contexts/CreateUpdateResourceContext'; -import { CreateFormResource, ResourceType } from './models/resourceType'; -import { Footer } from './components/shared/Footer'; -import { initializeFileTypeIcons } from '@fluentui/react-file-type-icons'; -import { CostResource } from './models/costs'; -import { CostsContext } from './contexts/CostsContext'; -import { LoadingState } from './models/loadingState'; +import React, { useEffect, useState } from "react"; +import { + DefaultPalette, + IStackStyles, + MessageBar, + MessageBarType, + Stack, +} from "@fluentui/react"; +import "./App.scss"; +import { TopNav } from "./components/shared/TopNav"; +import { Routes, Route } from "react-router-dom"; +import { RootLayout } from "./components/root/RootLayout"; +import { WorkspaceProvider } from "./components/workspaces/WorkspaceProvider"; +import { MsalAuthenticationTemplate } from "@azure/msal-react"; +import { InteractionType } from "@azure/msal-browser"; +import { Workspace } from "./models/workspace"; +import { AppRolesContext } from "./contexts/AppRolesContext"; +import { WorkspaceContext } from "./contexts/WorkspaceContext"; +import { GenericErrorBoundary } from "./components/shared/GenericErrorBoundary"; +import { HttpMethod, ResultType, useAuthApiCall } from "./hooks/useAuthApiCall"; +import { ApiEndpoint } from "./models/apiEndpoints"; +import { CreateUpdateResource } from "./components/shared/create-update-resource/CreateUpdateResource"; +import { CreateUpdateResourceContext } from "./contexts/CreateUpdateResourceContext"; +import { CreateFormResource, ResourceType } from "./models/resourceType"; +import { Footer } from "./components/shared/Footer"; +import { initializeFileTypeIcons } from "@fluentui/react-file-type-icons"; +import { CostResource } from "./models/costs"; +import { CostsContext } from "./contexts/CostsContext"; +import { LoadingState } from "./models/loadingState"; export const App: React.FunctionComponent = () => { const [appRoles, setAppRoles] = useState([] as Array); const [selectedWorkspace, setSelectedWorkspace] = useState({} as Workspace); const [workspaceRoles, setWorkspaceRoles] = useState([] as Array); - const [workspaceCosts, setWorkspaceCosts] = useState([] as Array); + const [workspaceCosts, setWorkspaceCosts] = useState( + [] as Array, + ); const [costs, setCosts] = useState([] as Array); - const [costsLoadingState, setCostsLoadingState] = useState(LoadingState.Loading); + const [costsLoadingState, setCostsLoadingState] = useState( + LoadingState.Loading, + ); const [createFormOpen, setCreateFormOpen] = useState(false); - const [createFormResource, setCreateFormResource] = useState({ resourceType: ResourceType.Workspace } as CreateFormResource); + const [createFormResource, setCreateFormResource] = useState({ + resourceType: ResourceType.Workspace, + } as CreateFormResource); const apiCall = useAuthApiCall(); // set the app roles useEffect(() => { const setAppRolesOnLoad = async () => { - await apiCall(ApiEndpoint.Workspaces, HttpMethod.Get, undefined, undefined, ResultType.JSON, (roles: Array) => { - setAppRoles(roles); - }, true); + await apiCall( + ApiEndpoint.Workspaces, + HttpMethod.Get, + undefined, + undefined, + ResultType.JSON, + (roles: Array) => { + setAppRoles(roles); + }, + true, + ); }; setAppRolesOnLoad(); }, [apiCall]); @@ -49,77 +69,113 @@ export const App: React.FunctionComponent = () => { return ( <> - - ) => { setAppRoles(roles) } - }}> - { - setCreateFormResource(createFormResource); - setCreateFormOpen(true); - } - }} > - - setCreateFormOpen(false)} - resourceType={createFormResource.resourceType} - parentResource={createFormResource.resourceParent} - onAddResource={createFormResource.onAdd} - workspaceApplicationIdURI={createFormResource.workspaceApplicationIdURI} - updateResource={createFormResource.updateResource} - /> - - - - - - - ) => {setCosts(costs)}, - setLoadingState: (loadingState: LoadingState) => {setCostsLoadingState(loadingState)} - }}> - - } /> - ) => {setWorkspaceRoles(roles)}, - costs: workspaceCosts, - setCosts: (costs: Array) => {setWorkspaceCosts(costs)}, - workspace: selectedWorkspace, - setWorkspace: (w: Workspace) => {setSelectedWorkspace(w)}, - workspaceApplicationIdURI: selectedWorkspace.properties?.scope_id - }}> - - - } /> - - - - - -