From 5e7d323f463e3f98e018d27885acad0135cbf7c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ostrowi=C5=84ski?= Date: Wed, 14 Jun 2023 16:25:56 +0200 Subject: [PATCH 01/39] TS: add types to components/* (#835) --- mwdb/web/package-lock.json | 604 ++++++++++++++++++ mwdb/web/package.json | 7 +- mwdb/web/src/App.jsx | 93 +-- mwdb/web/src/__tests__/helpers/index.test.tsx | 33 +- mwdb/web/src/commons/api/index.tsx | 14 +- mwdb/web/src/commons/helpers/authenticate.ts | 24 + mwdb/web/src/commons/helpers/index.ts | 7 + mwdb/web/src/commons/plugins/Extendable.tsx | 23 + mwdb/web/src/commons/plugins/Extension.tsx | 19 + mwdb/web/src/commons/plugins/index.jsx | 33 +- mwdb/web/src/commons/ui/EditableItem.tsx | 2 +- mwdb/web/src/commons/ui/HexView.tsx | 2 +- mwdb/web/src/commons/ui/LoadingSpinner.tsx | 2 +- mwdb/web/src/commons/ui/NavDropdown.tsx | 4 +- mwdb/web/src/components/AdminNav.tsx | 29 + ...tesAddModal.jsx => AttributesAddModal.tsx} | 49 +- .../Blob/Views/DiffTextBlobView.tsx | 113 ++++ .../components/Blob/Views/RecentBlobsView.tsx | 31 + .../Blob/Views/ShowTextBlobView.tsx | 79 +++ .../components/Blob/common/BlobDiffAction.tsx | 25 + .../Blob/common/RecentBlobHeader.tsx | 16 + .../components/Blob/common/RecentBlobRow.tsx | 138 ++++ .../Blob/common/TextBlobDetails.tsx | 112 ++++ .../Blob/common/TextBlobPreview.tsx | 16 + .../Views/ConfigStatsView.tsx} | 32 +- .../Config/Views/RecentConfigsView.tsx | 13 + .../Views/ShowConfigView.tsx} | 41 +- .../Config/common/ConfigDetails.tsx | 8 + .../Config/common/ConfigPreview.tsx | 26 + .../components/Config/common/ConfigRow.tsx | 116 ++++ .../components/Config/common/ConfigRows.tsx | 44 ++ .../Config/common/ConfigStatsItem.tsx | 29 + .../components/Config/common/ConfigTable.tsx | 70 ++ .../Config/common/RecentConfigHeader.tsx | 15 + .../Config/common/RecentConfigRow.tsx | 106 +++ mwdb/web/src/components/ConfigTable.jsx | 214 ------- .../{DagreD3Plot.jsx => DagreD3Plot.tsx} | 84 ++- mwdb/web/src/components/DiffTextBlob.jsx | 262 -------- .../DiffTextBlobContentPresenter.tsx | 154 +++++ .../File/Views/RecentSamplesView.tsx | 13 + .../File/common/RecentFileHeader.tsx | 14 + .../components/File/common/RecentFileRow.tsx | 115 ++++ .../{Navigation.jsx => Navigation.tsx} | 74 +-- mwdb/web/src/components/OAuth.jsx | 147 ----- .../src/components/PreviewSwitchAction.tsx | 21 + mwdb/web/src/components/ProviderButton.tsx | 26 + .../src/components/ProvidersSelectList.tsx | 39 ++ mwdb/web/src/components/RecentBlobs.jsx | 174 ----- mwdb/web/src/components/RecentConfigs.jsx | 130 ---- mwdb/web/src/components/RecentObjects.jsx | 90 --- mwdb/web/src/components/RecentSamples.jsx | 130 ---- .../RecentView/Views/RecentView.tsx | 8 +- .../RecentView/Views/RecentViewList.tsx | 8 +- .../RecentView/common/RecentInnerRow.tsx | 12 +- .../web/src/components/RelationToManyNode.tsx | 34 + mwdb/web/src/components/RelationsNode.tsx | 93 +++ mwdb/web/src/components/RemoteDropdown.tsx | 26 + .../{ShowSample.jsx => SampleDetails.tsx} | 211 ++---- mwdb/web/src/components/SamplePreview.tsx | 42 ++ mwdb/web/src/components/Search.jsx | 7 - .../ShowObject/Actions/DownloadAction.tsx | 2 +- .../ShowObject/Actions/PushAction.tsx | 2 +- .../ShowObject/common/AttributesBox.tsx | 2 +- .../ShowObject/common/LatestConfigTab.tsx | 2 +- .../ShowObject/common/ObjectBox.tsx | 2 +- .../ShowObject/common/RecentObjectHeader.tsx | 14 + .../ShowObject/common/RecentObjectRow.tsx | 67 ++ .../ShowObject/common/RecentObjects.tsx | 13 + .../ShowObject/common/RelationsTab.tsx | 7 +- .../ShowObject/common/ShowObject.tsx | 2 +- .../components/ShowObject/common/TagForm.tsx | 2 +- mwdb/web/src/components/ShowTextBlob.jsx | 192 ------ mwdb/web/src/components/UploadButton.tsx | 27 + mwdb/web/src/components/UploadDropzone.tsx | 53 ++ ...etPassword.jsx => UserSetPasswordView.tsx} | 29 +- .../{About.jsx => Views/AboutView.tsx} | 10 +- .../{Docs.jsx => Views/DocsView.tsx} | 19 +- .../components/Views/OAuthAuthorizeView.tsx | 74 +++ .../RelationsPlotView.tsx} | 158 ++--- mwdb/web/src/components/Views/SearchView.tsx | 5 + .../src/components/Views/ShowSampleView.tsx | 91 +++ .../{Upload.jsx => Views/UploadView.tsx} | 92 +-- .../UserLoginView.tsx} | 51 +- .../UserPasswordRecoverView.tsx} | 58 +- .../UserRegisterView.tsx} | 102 +-- mwdb/web/src/types/api.ts | 9 +- mwdb/web/src/types/diff-match-patch.d.ts | 1 + mwdb/web/src/types/props.ts | 4 + mwdb/web/src/types/types.ts | 17 +- 89 files changed, 3099 insertions(+), 2111 deletions(-) create mode 100644 mwdb/web/src/commons/helpers/authenticate.ts create mode 100644 mwdb/web/src/commons/plugins/Extendable.tsx create mode 100644 mwdb/web/src/commons/plugins/Extension.tsx create mode 100644 mwdb/web/src/components/AdminNav.tsx rename mwdb/web/src/components/{AttributesAddModal.jsx => AttributesAddModal.tsx} (86%) create mode 100644 mwdb/web/src/components/Blob/Views/DiffTextBlobView.tsx create mode 100644 mwdb/web/src/components/Blob/Views/RecentBlobsView.tsx create mode 100644 mwdb/web/src/components/Blob/Views/ShowTextBlobView.tsx create mode 100644 mwdb/web/src/components/Blob/common/BlobDiffAction.tsx create mode 100644 mwdb/web/src/components/Blob/common/RecentBlobHeader.tsx create mode 100644 mwdb/web/src/components/Blob/common/RecentBlobRow.tsx create mode 100644 mwdb/web/src/components/Blob/common/TextBlobDetails.tsx create mode 100644 mwdb/web/src/components/Blob/common/TextBlobPreview.tsx rename mwdb/web/src/components/{ConfigStats.tsx => Config/Views/ConfigStatsView.tsx} (71%) create mode 100644 mwdb/web/src/components/Config/Views/RecentConfigsView.tsx rename mwdb/web/src/components/{ShowConfig.jsx => Config/Views/ShowConfigView.tsx} (68%) create mode 100644 mwdb/web/src/components/Config/common/ConfigDetails.tsx create mode 100644 mwdb/web/src/components/Config/common/ConfigPreview.tsx create mode 100644 mwdb/web/src/components/Config/common/ConfigRow.tsx create mode 100644 mwdb/web/src/components/Config/common/ConfigRows.tsx create mode 100644 mwdb/web/src/components/Config/common/ConfigStatsItem.tsx create mode 100644 mwdb/web/src/components/Config/common/ConfigTable.tsx create mode 100644 mwdb/web/src/components/Config/common/RecentConfigHeader.tsx create mode 100644 mwdb/web/src/components/Config/common/RecentConfigRow.tsx delete mode 100644 mwdb/web/src/components/ConfigTable.jsx rename mwdb/web/src/components/{DagreD3Plot.jsx => DagreD3Plot.tsx} (71%) delete mode 100644 mwdb/web/src/components/DiffTextBlob.jsx create mode 100644 mwdb/web/src/components/DiffTextBlobContentPresenter.tsx create mode 100644 mwdb/web/src/components/File/Views/RecentSamplesView.tsx create mode 100644 mwdb/web/src/components/File/common/RecentFileHeader.tsx create mode 100644 mwdb/web/src/components/File/common/RecentFileRow.tsx rename mwdb/web/src/components/{Navigation.jsx => Navigation.tsx} (86%) delete mode 100644 mwdb/web/src/components/OAuth.jsx create mode 100644 mwdb/web/src/components/PreviewSwitchAction.tsx create mode 100644 mwdb/web/src/components/ProviderButton.tsx create mode 100644 mwdb/web/src/components/ProvidersSelectList.tsx delete mode 100644 mwdb/web/src/components/RecentBlobs.jsx delete mode 100644 mwdb/web/src/components/RecentConfigs.jsx delete mode 100644 mwdb/web/src/components/RecentObjects.jsx delete mode 100644 mwdb/web/src/components/RecentSamples.jsx create mode 100644 mwdb/web/src/components/RelationToManyNode.tsx create mode 100644 mwdb/web/src/components/RelationsNode.tsx create mode 100644 mwdb/web/src/components/RemoteDropdown.tsx rename mwdb/web/src/components/{ShowSample.jsx => SampleDetails.tsx} (54%) create mode 100644 mwdb/web/src/components/SamplePreview.tsx delete mode 100644 mwdb/web/src/components/Search.jsx create mode 100644 mwdb/web/src/components/ShowObject/common/RecentObjectHeader.tsx create mode 100644 mwdb/web/src/components/ShowObject/common/RecentObjectRow.tsx create mode 100644 mwdb/web/src/components/ShowObject/common/RecentObjects.tsx delete mode 100644 mwdb/web/src/components/ShowTextBlob.jsx create mode 100644 mwdb/web/src/components/UploadButton.tsx create mode 100644 mwdb/web/src/components/UploadDropzone.tsx rename mwdb/web/src/components/{UserSetPassword.jsx => UserSetPasswordView.tsx} (82%) rename mwdb/web/src/components/{About.jsx => Views/AboutView.tsx} (87%) rename mwdb/web/src/components/{Docs.jsx => Views/DocsView.tsx} (72%) create mode 100644 mwdb/web/src/components/Views/OAuthAuthorizeView.tsx rename mwdb/web/src/components/{RelationsPlot.jsx => Views/RelationsPlotView.tsx} (51%) create mode 100644 mwdb/web/src/components/Views/SearchView.tsx create mode 100644 mwdb/web/src/components/Views/ShowSampleView.tsx rename mwdb/web/src/components/{Upload.jsx => Views/UploadView.tsx} (83%) rename mwdb/web/src/components/{UserLogin.jsx => Views/UserLoginView.tsx} (78%) rename mwdb/web/src/components/{UserPasswordRecover.jsx => Views/UserPasswordRecoverView.tsx} (77%) rename mwdb/web/src/components/{UserRegister.jsx => Views/UserRegisterView.tsx} (76%) create mode 100644 mwdb/web/src/types/diff-match-patch.d.ts diff --git a/mwdb/web/package-lock.json b/mwdb/web/package-lock.json index a4a9dba6b..b1644ddd8 100644 --- a/mwdb/web/package-lock.json +++ b/mwdb/web/package-lock.json @@ -46,6 +46,8 @@ "@babel/preset-env": "^7.21.4", "@babel/preset-react": "^7.18.6", "@testing-library/react": "^14.0.0", + "@types/d3": "^7.4.0", + "@types/dagre-d3": "^0.4.39", "@types/identicon.js": "^2.3.1", "@types/jest": "^29.5.1", "@types/marked": "^4.3.0", @@ -53,10 +55,12 @@ "@types/react": "^18.0.26", "@types/react-copy-to-clipboard": "^5.0.4", "@types/react-dom": "^18.0.9", + "@types/react-google-recaptcha": "^2.1.5", "@types/react-infinite-scroller": "^1.2.3", "@types/react-js-pagination": "^3.0.4", "@types/react-modal": "^3.16.0", "@types/sha1": "^1.1.3", + "@types/swagger-ui-react": "^4.18.0", "@vitejs/plugin-react": "^3.0.0", "babel-jest": "^29.5.0", "dpdm": "^3.9.0", @@ -3895,6 +3899,281 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/d3": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz", + "integrity": "sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==", + "dev": true, + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-1.2.9.tgz", + "integrity": "sha512-E/7RgPr2ylT5dWG0CswMi9NpFcjIEDqLcUSBgNHe/EMahfqYaTx4zhcggG3khqoEB/leY4Vl6nTSbwLUPjXceA==", + "dev": true + }, + "node_modules/@types/d3-axis": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-1.0.16.tgz", + "integrity": "sha512-p7085weOmo4W+DzlRRVC/7OI/jugaKbVa6WMQGCQscaMylcbuaVEGk7abJLNyGVFLeCBNrHTdDiqRGnzvL0nXQ==", + "dev": true, + "dependencies": { + "@types/d3-selection": "^1" + } + }, + "node_modules/@types/d3-brush": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-1.1.5.tgz", + "integrity": "sha512-4zGkBafJf5zCsBtLtvDj/pNMo5X9+Ii/1hUz0GvQ+wEwelUBm2AbIDAzJnp2hLDFF307o0fhxmmocHclhXC+tw==", + "dev": true, + "dependencies": { + "@types/d3-selection": "^1" + } + }, + "node_modules/@types/d3-chord": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-1.0.11.tgz", + "integrity": "sha512-0DdfJ//bxyW3G9Nefwq/LDgazSKNN8NU0lBT3Cza6uVuInC2awMNsAcv1oKyRFLn9z7kXClH5XjwpveZjuz2eg==", + "dev": true + }, + "node_modules/@types/d3-color": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-1.4.2.tgz", + "integrity": "sha512-fYtiVLBYy7VQX+Kx7wU/uOIkGQn8aAEY8oWMoyja3N4dLd8Yf6XgSIR/4yWvMuveNOH5VShnqCgRqqh/UNanBA==", + "dev": true + }, + "node_modules/@types/d3-contour": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-1.3.3.tgz", + "integrity": "sha512-LxwmGIfVJIc1cKs7ZFRQ1FbtXpfH7QTXYRdMIJsFP71uCMdF6jJ0XZakYDX6Hn4yZkLf+7V8FgD34yCcok+5Ww==", + "dev": true, + "dependencies": { + "@types/d3-array": "^1", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz", + "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==", + "dev": true + }, + "node_modules/@types/d3-dispatch": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-1.0.9.tgz", + "integrity": "sha512-zJ44YgjqALmyps+II7b1mZLhrtfV/FOxw9owT87mrweGWcg+WK5oiJX2M3SYJ0XUAExBduarysfgbR11YxzojQ==", + "dev": true + }, + "node_modules/@types/d3-drag": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-1.2.5.tgz", + "integrity": "sha512-7NeTnfolst1Js3Vs7myctBkmJWu6DMI3k597AaHUX98saHjHWJ6vouT83UrpE+xfbSceHV+8A0JgxuwgqgmqWw==", + "dev": true, + "dependencies": { + "@types/d3-selection": "^1" + } + }, + "node_modules/@types/d3-dsv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-1.2.2.tgz", + "integrity": "sha512-GRnz9z8ypqb7OsQ/xw/BmFAp0/k3pgM1s19FTZZSlCMY0EvyVTkU8xzZKKDXzytGXPpTNC4R5pGl9oxEvVSnHQ==", + "dev": true + }, + "node_modules/@types/d3-ease": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-1.0.11.tgz", + "integrity": "sha512-wUigPL0kleGZ9u3RhzBP07lxxkMcUjL5IODP42mN/05UNL+JJCDnpEPpFbJiPvLcTeRKGIRpBBJyP/1BNwYsVA==", + "dev": true + }, + "node_modules/@types/d3-fetch": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-1.2.2.tgz", + "integrity": "sha512-rtFs92GugtV/NpiJQd0WsmGLcg52tIL0uF0bKbbJg231pR9JEb6HT4AUwrtuLq3lOeKdLBhsjV14qb0pMmd0Aw==", + "dev": true, + "dependencies": { + "@types/d3-dsv": "^1" + } + }, + "node_modules/@types/d3-force": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-1.2.4.tgz", + "integrity": "sha512-fkorLTKvt6AQbFBQwn4aq7h9rJ4c7ZVcPMGB8X6eFFveAyMZcv7t7m6wgF4Eg93rkPgPORU7sAho1QSHNcZu6w==", + "dev": true + }, + "node_modules/@types/d3-format": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.2.tgz", + "integrity": "sha512-WeGCHAs7PHdZYq6lwl/+jsl+Nfc1J2W1kNcMeIMYzQsT6mtBDBgtJ/rcdjZ0k0rVIvqEZqhhuD5TK/v3P2gFHQ==", + "dev": true + }, + "node_modules/@types/d3-geo": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-1.12.4.tgz", + "integrity": "sha512-lNDaAuOaML6w2d1XE0Txr5YOXLBQSF1q2IU6eXh/u1TTPQSm2Ah+TMIub1+CIMq8J/7DOzi5Cr8/yHqjNvqLKA==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz", + "integrity": "sha512-AbStKxNyWiMDQPGDguG2Kuhlq1Sv539pZSxYbx4UZeYkutpPwXCcgyiRrlV4YH64nIOsKx7XVnOMy9O7rJsXkg==", + "dev": true + }, + "node_modules/@types/d3-interpolate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-1.4.2.tgz", + "integrity": "sha512-ylycts6llFf8yAEs1tXzx2loxxzDZHseuhPokrqKprTQSTcD3JbJI1omZP1rphsELZO3Q+of3ff0ZS7+O6yVzg==", + "dev": true, + "dependencies": { + "@types/d3-color": "^1" + } + }, + "node_modules/@types/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==", + "dev": true + }, + "node_modules/@types/d3-polygon": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-1.0.8.tgz", + "integrity": "sha512-1TOJPXCBJC9V3+K3tGbTqD/CsqLyv/YkTXAcwdsZzxqw5cvpdnCuDl42M4Dvi8XzMxZNCT9pL4ibrK2n4VmAcw==", + "dev": true + }, + "node_modules/@types/d3-quadtree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-1.0.9.tgz", + "integrity": "sha512-5E0OJJn2QVavITFEc1AQlI8gLcIoDZcTKOD3feKFckQVmFV4CXhqRFt83tYNVNIN4ZzRkjlAMavJa1ldMhf5rA==", + "dev": true + }, + "node_modules/@types/d3-random": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-1.1.3.tgz", + "integrity": "sha512-XXR+ZbFCoOd4peXSMYJzwk0/elP37WWAzS/DG+90eilzVbUSsgKhBcWqylGWe+lA2ubgr7afWAOBaBxRgMUrBQ==", + "dev": true + }, + "node_modules/@types/d3-scale": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-2.2.6.tgz", + "integrity": "sha512-CHu34T5bGrJOeuhGxyiz9Xvaa9PlsIaQoOqjDg7zqeGj2x0rwPhGquiy03unigvcMxmvY0hEaAouT0LOFTLpIw==", + "dev": true, + "dependencies": { + "@types/d3-time": "^1" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-1.5.1.tgz", + "integrity": "sha512-7FtJYrmXTEWLykShjYhoGuDNR/Bda0+tstZMkFj4RRxUEryv16AGh3be21tqg84B6KfEwiZyEpBcTyPyU+GWjg==", + "dev": true + }, + "node_modules/@types/d3-selection": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-1.4.3.tgz", + "integrity": "sha512-GjKQWVZO6Sa96HiKO6R93VBE8DUW+DDkFpIMf9vpY5S78qZTlRRSNUsHr/afDpF7TvLDV7VxrUFOWW7vdIlYkA==", + "dev": true + }, + "node_modules/@types/d3-shape": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.3.8.tgz", + "integrity": "sha512-gqfnMz6Fd5H6GOLYixOZP/xlrMtJms9BaS+6oWxTKHNqPGZ93BkWWupQSCYm6YHqx6h9wjRupuJb90bun6ZaYg==", + "dev": true, + "dependencies": { + "@types/d3-path": "^1" + } + }, + "node_modules/@types/d3-time": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-1.1.1.tgz", + "integrity": "sha512-ULX7LoqXTCYtM+tLYOaeAJK7IwCT+4Gxlm2MaH0ErKLi07R5lh8NHCAyWcDkCCmx1AfRcBEV6H9QE9R25uP7jw==", + "dev": true + }, + "node_modules/@types/d3-time-format": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.3.1.tgz", + "integrity": "sha512-fck0Z9RGfIQn3GJIEKVrp15h9m6Vlg0d5XXeiE/6+CQiBmMDZxfR21XtjEPuDeg7gC3bBM0SdieA5XF3GW1wKA==", + "dev": true + }, + "node_modules/@types/d3-timer": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-ZnAbquVqy+4ZjdW0cY6URp+qF/AzTVNda2jYyOzpR2cPT35FTXl78s15Bomph9+ckOiI1TtkljnWkwbIGAb6rg==", + "dev": true + }, + "node_modules/@types/d3-transition": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-J+a3SuF/E7wXbOSN19p8ZieQSFIm5hU2Egqtndbc54LXaAEOpLfDx4sBu/PKAKzHOdgKK1wkMhINKqNh4aoZAg==", + "dev": true, + "dependencies": { + "@types/d3-selection": "^1" + } + }, + "node_modules/@types/d3-zoom": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-1.8.4.tgz", + "integrity": "sha512-K+6jCM9llyC5U4WvkmiXbCoOIuUX03Wi72C/L9PMPVxymWDaxTHzDgHD/HYlEyDRGiVp7D77m7XPcD/m/TRDrw==", + "dev": true, + "dependencies": { + "@types/d3-interpolate": "^1", + "@types/d3-selection": "^1" + } + }, + "node_modules/@types/dagre": { + "version": "0.7.48", + "resolved": "https://registry.npmjs.org/@types/dagre/-/dagre-0.7.48.tgz", + "integrity": "sha512-rF3yXSwHIrDxEkN6edCE4TXknb5YSEpiXfLaspw1I08grC49ZFuAVGOQCmZGIuLUGoFgcqGlUFBL/XrpgYpQgw==", + "dev": true + }, + "node_modules/@types/dagre-d3": { + "version": "0.4.39", + "resolved": "https://registry.npmjs.org/@types/dagre-d3/-/dagre-d3-0.4.39.tgz", + "integrity": "sha512-JZySpfIQPRSTx38B4P5pPGeV4Pqgb0qE/aIiS60qD+j0c3mYP/AHiiwlsrrLCVZWY7OJdvD3dp+aD1HvpjPBzA==", + "dev": true, + "dependencies": { + "@types/d3": "^3", + "@types/dagre": "*" + } + }, + "node_modules/@types/dagre-d3/node_modules/@types/d3": { + "version": "3.5.47", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-3.5.47.tgz", + "integrity": "sha512-VkWIQoZXLFdcBGe5pdBKJmTU3fmpXvo/KV6ixvTzOMl1yJ2hbTXpfvsziag0kcaerPDwas2T0vxojwQG3YwivQ==", + "dev": true + }, "node_modules/@types/fs-extra": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.1.tgz", @@ -3905,6 +4184,12 @@ "@types/node": "*" } }, + "node_modules/@types/geojson": { + "version": "7946.0.10", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", + "dev": true + }, "node_modules/@types/glob": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.0.0.tgz", @@ -4114,6 +4399,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-google-recaptcha": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/react-google-recaptcha/-/react-google-recaptcha-2.1.5.tgz", + "integrity": "sha512-iWTjmVttlNgp0teyh7eBXqNOQzVq2RWNiFROWjraOptRnb1OcHJehQnji0sjqIRAk9K0z8stjyhU+OLpPb0N6w==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-infinite-scroller": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/react-infinite-scroller/-/react-infinite-scroller-1.2.3.tgz", @@ -4169,6 +4463,15 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/swagger-ui-react": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-react/-/swagger-ui-react-4.18.0.tgz", + "integrity": "sha512-XtvFXmj46Zibe89tFQwSQknrq1NxEtOep2rZuxth7K88tyPEP00FnoA6H7ATYhocAEA4XUWaNHNFWFRl1KX8aQ==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/tough-cookie": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", @@ -16480,6 +16783,283 @@ "@babel/types": "^7.3.0" } }, + "@types/d3": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz", + "integrity": "sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==", + "dev": true, + "requires": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "@types/d3-array": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-1.2.9.tgz", + "integrity": "sha512-E/7RgPr2ylT5dWG0CswMi9NpFcjIEDqLcUSBgNHe/EMahfqYaTx4zhcggG3khqoEB/leY4Vl6nTSbwLUPjXceA==", + "dev": true + }, + "@types/d3-axis": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-1.0.16.tgz", + "integrity": "sha512-p7085weOmo4W+DzlRRVC/7OI/jugaKbVa6WMQGCQscaMylcbuaVEGk7abJLNyGVFLeCBNrHTdDiqRGnzvL0nXQ==", + "dev": true, + "requires": { + "@types/d3-selection": "^1" + } + }, + "@types/d3-brush": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-1.1.5.tgz", + "integrity": "sha512-4zGkBafJf5zCsBtLtvDj/pNMo5X9+Ii/1hUz0GvQ+wEwelUBm2AbIDAzJnp2hLDFF307o0fhxmmocHclhXC+tw==", + "dev": true, + "requires": { + "@types/d3-selection": "^1" + } + }, + "@types/d3-chord": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-1.0.11.tgz", + "integrity": "sha512-0DdfJ//bxyW3G9Nefwq/LDgazSKNN8NU0lBT3Cza6uVuInC2awMNsAcv1oKyRFLn9z7kXClH5XjwpveZjuz2eg==", + "dev": true + }, + "@types/d3-color": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-1.4.2.tgz", + "integrity": "sha512-fYtiVLBYy7VQX+Kx7wU/uOIkGQn8aAEY8oWMoyja3N4dLd8Yf6XgSIR/4yWvMuveNOH5VShnqCgRqqh/UNanBA==", + "dev": true + }, + "@types/d3-contour": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-1.3.3.tgz", + "integrity": "sha512-LxwmGIfVJIc1cKs7ZFRQ1FbtXpfH7QTXYRdMIJsFP71uCMdF6jJ0XZakYDX6Hn4yZkLf+7V8FgD34yCcok+5Ww==", + "dev": true, + "requires": { + "@types/d3-array": "^1", + "@types/geojson": "*" + } + }, + "@types/d3-delaunay": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz", + "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==", + "dev": true + }, + "@types/d3-dispatch": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-1.0.9.tgz", + "integrity": "sha512-zJ44YgjqALmyps+II7b1mZLhrtfV/FOxw9owT87mrweGWcg+WK5oiJX2M3SYJ0XUAExBduarysfgbR11YxzojQ==", + "dev": true + }, + "@types/d3-drag": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-1.2.5.tgz", + "integrity": "sha512-7NeTnfolst1Js3Vs7myctBkmJWu6DMI3k597AaHUX98saHjHWJ6vouT83UrpE+xfbSceHV+8A0JgxuwgqgmqWw==", + "dev": true, + "requires": { + "@types/d3-selection": "^1" + } + }, + "@types/d3-dsv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-1.2.2.tgz", + "integrity": "sha512-GRnz9z8ypqb7OsQ/xw/BmFAp0/k3pgM1s19FTZZSlCMY0EvyVTkU8xzZKKDXzytGXPpTNC4R5pGl9oxEvVSnHQ==", + "dev": true + }, + "@types/d3-ease": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-1.0.11.tgz", + "integrity": "sha512-wUigPL0kleGZ9u3RhzBP07lxxkMcUjL5IODP42mN/05UNL+JJCDnpEPpFbJiPvLcTeRKGIRpBBJyP/1BNwYsVA==", + "dev": true + }, + "@types/d3-fetch": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-1.2.2.tgz", + "integrity": "sha512-rtFs92GugtV/NpiJQd0WsmGLcg52tIL0uF0bKbbJg231pR9JEb6HT4AUwrtuLq3lOeKdLBhsjV14qb0pMmd0Aw==", + "dev": true, + "requires": { + "@types/d3-dsv": "^1" + } + }, + "@types/d3-force": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-1.2.4.tgz", + "integrity": "sha512-fkorLTKvt6AQbFBQwn4aq7h9rJ4c7ZVcPMGB8X6eFFveAyMZcv7t7m6wgF4Eg93rkPgPORU7sAho1QSHNcZu6w==", + "dev": true + }, + "@types/d3-format": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.2.tgz", + "integrity": "sha512-WeGCHAs7PHdZYq6lwl/+jsl+Nfc1J2W1kNcMeIMYzQsT6mtBDBgtJ/rcdjZ0k0rVIvqEZqhhuD5TK/v3P2gFHQ==", + "dev": true + }, + "@types/d3-geo": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-1.12.4.tgz", + "integrity": "sha512-lNDaAuOaML6w2d1XE0Txr5YOXLBQSF1q2IU6eXh/u1TTPQSm2Ah+TMIub1+CIMq8J/7DOzi5Cr8/yHqjNvqLKA==", + "dev": true, + "requires": { + "@types/geojson": "*" + } + }, + "@types/d3-hierarchy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz", + "integrity": "sha512-AbStKxNyWiMDQPGDguG2Kuhlq1Sv539pZSxYbx4UZeYkutpPwXCcgyiRrlV4YH64nIOsKx7XVnOMy9O7rJsXkg==", + "dev": true + }, + "@types/d3-interpolate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-1.4.2.tgz", + "integrity": "sha512-ylycts6llFf8yAEs1tXzx2loxxzDZHseuhPokrqKprTQSTcD3JbJI1omZP1rphsELZO3Q+of3ff0ZS7+O6yVzg==", + "dev": true, + "requires": { + "@types/d3-color": "^1" + } + }, + "@types/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==", + "dev": true + }, + "@types/d3-polygon": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-1.0.8.tgz", + "integrity": "sha512-1TOJPXCBJC9V3+K3tGbTqD/CsqLyv/YkTXAcwdsZzxqw5cvpdnCuDl42M4Dvi8XzMxZNCT9pL4ibrK2n4VmAcw==", + "dev": true + }, + "@types/d3-quadtree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-1.0.9.tgz", + "integrity": "sha512-5E0OJJn2QVavITFEc1AQlI8gLcIoDZcTKOD3feKFckQVmFV4CXhqRFt83tYNVNIN4ZzRkjlAMavJa1ldMhf5rA==", + "dev": true + }, + "@types/d3-random": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-1.1.3.tgz", + "integrity": "sha512-XXR+ZbFCoOd4peXSMYJzwk0/elP37WWAzS/DG+90eilzVbUSsgKhBcWqylGWe+lA2ubgr7afWAOBaBxRgMUrBQ==", + "dev": true + }, + "@types/d3-scale": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-2.2.6.tgz", + "integrity": "sha512-CHu34T5bGrJOeuhGxyiz9Xvaa9PlsIaQoOqjDg7zqeGj2x0rwPhGquiy03unigvcMxmvY0hEaAouT0LOFTLpIw==", + "dev": true, + "requires": { + "@types/d3-time": "^1" + } + }, + "@types/d3-scale-chromatic": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-1.5.1.tgz", + "integrity": "sha512-7FtJYrmXTEWLykShjYhoGuDNR/Bda0+tstZMkFj4RRxUEryv16AGh3be21tqg84B6KfEwiZyEpBcTyPyU+GWjg==", + "dev": true + }, + "@types/d3-selection": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-1.4.3.tgz", + "integrity": "sha512-GjKQWVZO6Sa96HiKO6R93VBE8DUW+DDkFpIMf9vpY5S78qZTlRRSNUsHr/afDpF7TvLDV7VxrUFOWW7vdIlYkA==", + "dev": true + }, + "@types/d3-shape": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.3.8.tgz", + "integrity": "sha512-gqfnMz6Fd5H6GOLYixOZP/xlrMtJms9BaS+6oWxTKHNqPGZ93BkWWupQSCYm6YHqx6h9wjRupuJb90bun6ZaYg==", + "dev": true, + "requires": { + "@types/d3-path": "^1" + } + }, + "@types/d3-time": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-1.1.1.tgz", + "integrity": "sha512-ULX7LoqXTCYtM+tLYOaeAJK7IwCT+4Gxlm2MaH0ErKLi07R5lh8NHCAyWcDkCCmx1AfRcBEV6H9QE9R25uP7jw==", + "dev": true + }, + "@types/d3-time-format": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.3.1.tgz", + "integrity": "sha512-fck0Z9RGfIQn3GJIEKVrp15h9m6Vlg0d5XXeiE/6+CQiBmMDZxfR21XtjEPuDeg7gC3bBM0SdieA5XF3GW1wKA==", + "dev": true + }, + "@types/d3-timer": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-ZnAbquVqy+4ZjdW0cY6URp+qF/AzTVNda2jYyOzpR2cPT35FTXl78s15Bomph9+ckOiI1TtkljnWkwbIGAb6rg==", + "dev": true + }, + "@types/d3-transition": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-J+a3SuF/E7wXbOSN19p8ZieQSFIm5hU2Egqtndbc54LXaAEOpLfDx4sBu/PKAKzHOdgKK1wkMhINKqNh4aoZAg==", + "dev": true, + "requires": { + "@types/d3-selection": "^1" + } + }, + "@types/d3-zoom": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-1.8.4.tgz", + "integrity": "sha512-K+6jCM9llyC5U4WvkmiXbCoOIuUX03Wi72C/L9PMPVxymWDaxTHzDgHD/HYlEyDRGiVp7D77m7XPcD/m/TRDrw==", + "dev": true, + "requires": { + "@types/d3-interpolate": "^1", + "@types/d3-selection": "^1" + } + }, + "@types/dagre": { + "version": "0.7.48", + "resolved": "https://registry.npmjs.org/@types/dagre/-/dagre-0.7.48.tgz", + "integrity": "sha512-rF3yXSwHIrDxEkN6edCE4TXknb5YSEpiXfLaspw1I08grC49ZFuAVGOQCmZGIuLUGoFgcqGlUFBL/XrpgYpQgw==", + "dev": true + }, + "@types/dagre-d3": { + "version": "0.4.39", + "resolved": "https://registry.npmjs.org/@types/dagre-d3/-/dagre-d3-0.4.39.tgz", + "integrity": "sha512-JZySpfIQPRSTx38B4P5pPGeV4Pqgb0qE/aIiS60qD+j0c3mYP/AHiiwlsrrLCVZWY7OJdvD3dp+aD1HvpjPBzA==", + "dev": true, + "requires": { + "@types/d3": "^3", + "@types/dagre": "*" + }, + "dependencies": { + "@types/d3": { + "version": "3.5.47", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-3.5.47.tgz", + "integrity": "sha512-VkWIQoZXLFdcBGe5pdBKJmTU3fmpXvo/KV6ixvTzOMl1yJ2hbTXpfvsziag0kcaerPDwas2T0vxojwQG3YwivQ==", + "dev": true + } + } + }, "@types/fs-extra": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.1.tgz", @@ -16490,6 +17070,12 @@ "@types/node": "*" } }, + "@types/geojson": { + "version": "7946.0.10", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", + "dev": true + }, "@types/glob": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.0.0.tgz", @@ -16692,6 +17278,15 @@ "@types/react": "*" } }, + "@types/react-google-recaptcha": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/react-google-recaptcha/-/react-google-recaptcha-2.1.5.tgz", + "integrity": "sha512-iWTjmVttlNgp0teyh7eBXqNOQzVq2RWNiFROWjraOptRnb1OcHJehQnji0sjqIRAk9K0z8stjyhU+OLpPb0N6w==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-infinite-scroller": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/react-infinite-scroller/-/react-infinite-scroller-1.2.3.tgz", @@ -16747,6 +17342,15 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "@types/swagger-ui-react": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-react/-/swagger-ui-react-4.18.0.tgz", + "integrity": "sha512-XtvFXmj46Zibe89tFQwSQknrq1NxEtOep2rZuxth7K88tyPEP00FnoA6H7ATYhocAEA4XUWaNHNFWFRl1KX8aQ==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/tough-cookie": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", diff --git a/mwdb/web/package.json b/mwdb/web/package.json index a3adb8e79..2d0b85ae9 100644 --- a/mwdb/web/package.json +++ b/mwdb/web/package.json @@ -7,7 +7,8 @@ "build": "vite build", "preview": "vite preview", "test": "jest", - "lint": "tsc --watch" + "lint": "tsc --watch", + "format": "prettier --write \"src/**/*.{ts,tsx,js,jsx}\"" }, "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.1.1", @@ -48,6 +49,8 @@ "@babel/preset-env": "^7.21.4", "@babel/preset-react": "^7.18.6", "@testing-library/react": "^14.0.0", + "@types/d3": "^7.4.0", + "@types/dagre-d3": "^0.4.39", "@types/identicon.js": "^2.3.1", "@types/jest": "^29.5.1", "@types/marked": "^4.3.0", @@ -55,10 +58,12 @@ "@types/react": "^18.0.26", "@types/react-copy-to-clipboard": "^5.0.4", "@types/react-dom": "^18.0.9", + "@types/react-google-recaptcha": "^2.1.5", "@types/react-infinite-scroller": "^1.2.3", "@types/react-js-pagination": "^3.0.4", "@types/react-modal": "^3.16.0", "@types/sha1": "^1.1.3", + "@types/swagger-ui-react": "^4.18.0", "@vitejs/plugin-react": "^3.0.0", "babel-jest": "^29.5.0", "dpdm": "^3.9.0", diff --git a/mwdb/web/src/App.jsx b/mwdb/web/src/App.jsx index de5141471..19444bbf0 100644 --- a/mwdb/web/src/App.jsx +++ b/mwdb/web/src/App.jsx @@ -7,24 +7,24 @@ import { useParams, } from "react-router-dom"; -import About from "./components/About"; -import Navigation from "./components/Navigation"; -import RecentConfigs from "./components/RecentConfigs"; -import RecentSamples from "./components/RecentSamples"; -import ConfigStats from "./components/ConfigStats"; -import RecentBlobs from "./components/RecentBlobs"; -import ShowSample from "./components/ShowSample"; -import ShowConfig from "./components/ShowConfig"; -import ShowTextBlob from "./components/ShowTextBlob"; -import DiffTextBlob from "./components/DiffTextBlob"; -import Upload from "./components/Upload"; -import UserLogin from "./components/UserLogin"; -import UserRegister from "./components/UserRegister"; -import UserSetPassword from "./components/UserSetPassword"; -import Search from "./components/Search"; -import RelationsPlot from "./components/RelationsPlot"; -import UserPasswordRecover from "./components/UserPasswordRecover"; -import Docs from "./components/Docs"; +import { AboutView } from "./components/Views/AboutView"; +import { Navigation } from "./components/Navigation"; +import { RecentSamplesView } from "./components/File/Views/RecentSamplesView"; +import { ConfigStatsView } from "./components/Config/Views/ConfigStatsView"; +import { RecentConfigsView } from "./components/Config/Views/RecentConfigsView"; +import { RecentBlobsView } from "./components/Blob/Views/RecentBlobsView"; +import { ShowSampleView } from "./components/Views/ShowSampleView"; +import { ShowConfigView } from "./components/Config/Views/ShowConfigView"; +import { ShowTextBlobView } from "./components/Blob/Views/ShowTextBlobView"; +import { DiffTextBlobView } from "./components/Blob/Views/DiffTextBlobView"; +import { UploadView } from "./components/Views/UploadView"; +import { UserLoginView } from "./components/Views/UserLoginView"; +import { UserRegisterView } from "./components/Views/UserRegisterView"; +import { UserSetPasswordView } from "./components/UserSetPasswordView"; +import { SearchView } from "./components/Views/SearchView"; +import { RelationsPlotView } from "./components/Views/RelationsPlotView"; +import { UserPasswordRecoverView } from "./components/Views/UserPasswordRecoverView"; +import { DocsView } from "./components/Views/DocsView"; import RemoteViews from "./components/Remote/RemoteViews"; import ProfileView from "./components/Profile/ProfileView"; import { SettingsView } from "./components/Settings/Views/SettingsView"; @@ -37,7 +37,7 @@ import ProfileAPIKeys from "./components/Profile/Views/ProfileAPIKeys"; import ProfileResetPassword from "./components/Profile/Views/ProfileResetPassword"; import ProfileOAuth from "./components/Profile/Views/ProfileOAuth"; -import { OAuthAuthorize } from "./components/OAuth"; +import { OAuthAuthorizeView } from "./components/Views/OAuthAuthorizeView"; import { SettingsOverviewView } from "./components/Settings/Views/SettingsOverviewView"; import { UsersPendingListView } from "./components/Settings/Views/UsersPendingListView"; @@ -98,34 +98,37 @@ function SampleRouteFallback() { function AppRoutes() { return ( - } /> - } /> - } /> - } /> - } /> + } /> + } /> + } + /> + } /> + } /> }> - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> - + } /> - } /> - } /> - } /> + } /> + } /> + } /> } /> - } /> - } /> - } /> + } /> + } /> + } /> }> } /> } /> @@ -148,20 +151,20 @@ function AppRoutes() { } + element={} /> - } /> + } /> }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> } + element={} /> { test("should return empty string when param is not typeof string", () => { @@ -82,3 +87,29 @@ describe("mapObjectType", () => { expect(result).toBe("unknown"); }); }); + +describe("negateBuffer", () => { + it("should negate the buffer contents correctly", () => { + const buffer = new ArrayBuffer(4); + const view = new Uint8Array(buffer); + view[0] = 0x11; + view[1] = 0x22; + view[2] = 0x33; + view[3] = 0x44; + + const result = negateBuffer(buffer); + const resultView = new Uint8Array(result); + expect(resultView[0]).toBe(0xee); + expect(resultView[1]).toBe(0xdd); + expect(resultView[2]).toBe(0xcc); + expect(resultView[3]).toBe(0xbb); + }); + + it("should return a new buffer", () => { + const buffer = new ArrayBuffer(4); + const result = negateBuffer(buffer); + + expect(result).not.toBe(buffer); + expect(result).toBeInstanceOf(ArrayBuffer); + }); +}); diff --git a/mwdb/web/src/commons/api/index.tsx b/mwdb/web/src/commons/api/index.tsx index 734bc3c35..2c75ce1dd 100644 --- a/mwdb/web/src/commons/api/index.tsx +++ b/mwdb/web/src/commons/api/index.tsx @@ -27,8 +27,8 @@ import { GenerateSetPasswordResponse, GetAttributeDefinitionResponse, GetAttributeDefinitionsResponse, - GetAttributePermissionsResponse, GetConfigStatsResponse, + GetAttributePermissionsResponse, GetGroupResponse, GetGroupsResponse, GetKartonAnalysesListResponse, @@ -237,7 +237,7 @@ function apiKeyAdd(login: string, name: string): ApiKeyAddResponse { return axios.post(`/user/${login}/api_key`, { name }); } -function apiKeyRemove(key_id: number | string): ApiKeyRemoveResponse { +function apiKeyRemove(key_id: string): ApiKeyRemoveResponse { return axios.delete(`/api_key/${key_id}`); } @@ -568,14 +568,14 @@ function removeAttributePermission( }); } -function downloadFile(id: number, obfuscate: number = 0): DownloadFileResponse { +function downloadFile(id: string, obfuscate: number = 0): DownloadFileResponse { return axios.get(`/file/${id}/download?obfuscate=${obfuscate}`, { responseType: "arraybuffer", responseEncoding: "binary", }); } -async function requestFileDownloadLink(id: number): Promise { +async function requestFileDownloadLink(id: string): Promise { const response = await axios.post(`/file/${id}/download`); const baseURL = getApiForEnvironment(); return `${baseURL}/file/${id}/download?token=${response.data.token}`; @@ -635,7 +635,7 @@ function pullObjectRemote( }); } -function getConfigStats(fromTime: number | string): GetConfigStatsResponse { +function getConfigStats(fromTime: string): GetConfigStatsResponse { return axios.get("/config/stats", { params: { range: fromTime, @@ -709,7 +709,7 @@ function getRemoteObjectAttributes( function downloadRemoteFile( remote: string, - id: number + id: string ): DownloadRemoteFileResponse { return axios.get(`/remote/${remote}/api/file/${id}/download`, { responseType: "arraybuffer", @@ -719,7 +719,7 @@ function downloadRemoteFile( async function requestRemoteFileDownloadLink( remote: string, - id: number + id: string ): Promise { const response = await axios.post( `/remote/${remote}/api/file/${id}/download` diff --git a/mwdb/web/src/commons/helpers/authenticate.ts b/mwdb/web/src/commons/helpers/authenticate.ts new file mode 100644 index 000000000..10cdb18f4 --- /dev/null +++ b/mwdb/web/src/commons/helpers/authenticate.ts @@ -0,0 +1,24 @@ +import { toast } from "react-toastify"; +import { api } from "../api"; +import { getErrorMessage } from "./getErrorMessage"; + +export async function authenticate(provider: string, action: string) { + try { + const response = await api.oauthAuthenticate(provider); + const expirationTime = Date.now() + 5 * 60 * 1000; + sessionStorage.setItem( + `openid_${response.data["state"]}`, + JSON.stringify({ + provider: provider, + nonce: response.data["nonce"], + action: action, + expiration: expirationTime, + }) + ); + window.location = response.data["authorization_url"]; + } catch (e) { + toast(getErrorMessage(e), { + type: "error", + }); + } +} diff --git a/mwdb/web/src/commons/helpers/index.ts b/mwdb/web/src/commons/helpers/index.ts index 99ed15087..e8305fffd 100644 --- a/mwdb/web/src/commons/helpers/index.ts +++ b/mwdb/web/src/commons/helpers/index.ts @@ -48,3 +48,10 @@ export function mapObjectType(objectType: string): string { }[objectType] || objectType ); } + +// negate the buffer contents (xor with key equal 0xff) +export function negateBuffer(buffer: ArrayBuffer) { + const uint8View = new Uint8Array(buffer); + const xored = uint8View.map((item) => item ^ 0xff); + return xored.buffer; +} diff --git a/mwdb/web/src/commons/plugins/Extendable.tsx b/mwdb/web/src/commons/plugins/Extendable.tsx new file mode 100644 index 000000000..27870af36 --- /dev/null +++ b/mwdb/web/src/commons/plugins/Extendable.tsx @@ -0,0 +1,23 @@ +import { Extension } from "./Extension"; + +type Props = { + [extensionProp: string]: any; + ident: string; + fallback?: JSX.Element; +}; + +export function Extendable({ ident, children, ...props }: Props) { + return ( + <> + {} + { + + } + {} + + ); +} diff --git a/mwdb/web/src/commons/plugins/Extension.tsx b/mwdb/web/src/commons/plugins/Extension.tsx new file mode 100644 index 000000000..58b99bcfe --- /dev/null +++ b/mwdb/web/src/commons/plugins/Extension.tsx @@ -0,0 +1,19 @@ +import { fromPlugins } from "."; + +type Props = { + [extensionProp: string]: any; + ident: string; + fallback?: JSX.Element; +}; + +export function Extension({ ident, fallback, ...props }: Props) { + const components = fromPlugins(ident); + if (components.length === 0) return fallback || <>; + return ( + <> + {components.map((ExtElement) => ( + + ))} + + ); +} diff --git a/mwdb/web/src/commons/plugins/index.jsx b/mwdb/web/src/commons/plugins/index.jsx index 005e45961..a16ce52f9 100644 --- a/mwdb/web/src/commons/plugins/index.jsx +++ b/mwdb/web/src/commons/plugins/index.jsx @@ -1,8 +1,9 @@ -import React from "react"; import _ from "lodash"; - import pluginLoaders from "@mwdb-web/plugins"; +export { Extension } from "./Extension"; +export { Extendable } from "./Extendable"; + let loadedPlugins = {}; let pluginsLoadedCallbacks = []; @@ -35,31 +36,3 @@ export function fromPlugins(element) { ) ); } - -export function Extension({ ident, fallback, ...props }) { - const components = fromPlugins(ident); - if (components.length === 0) return fallback || []; - return ( - <> - {components.map((ExtElement) => ( - - ))} - - ); -} - -export function Extendable({ ident, children, ...props }) { - return ( - - {} - { - - } - {} - - ); -} diff --git a/mwdb/web/src/commons/ui/EditableItem.tsx b/mwdb/web/src/commons/ui/EditableItem.tsx index 4dbf6321b..84e6459bf 100644 --- a/mwdb/web/src/commons/ui/EditableItem.tsx +++ b/mwdb/web/src/commons/ui/EditableItem.tsx @@ -17,7 +17,7 @@ type Props = SelectOrInput & { selective?: boolean; badge?: boolean; masked?: boolean; - children?: React.ReactNode; + children?: JSX.Element | JSX.Element[]; onSubmit: (value: Record) => void; }; diff --git a/mwdb/web/src/commons/ui/HexView.tsx b/mwdb/web/src/commons/ui/HexView.tsx index ae087ea31..9ad27b6d8 100644 --- a/mwdb/web/src/commons/ui/HexView.tsx +++ b/mwdb/web/src/commons/ui/HexView.tsx @@ -29,7 +29,7 @@ type Content = string | ArrayBuffer; type Props = { content: Content; - mode: "raw" | "hex"; + mode: string; showInvisibles: boolean; json?: boolean; }; diff --git a/mwdb/web/src/commons/ui/LoadingSpinner.tsx b/mwdb/web/src/commons/ui/LoadingSpinner.tsx index 985890256..9141c187d 100644 --- a/mwdb/web/src/commons/ui/LoadingSpinner.tsx +++ b/mwdb/web/src/commons/ui/LoadingSpinner.tsx @@ -1,7 +1,7 @@ import { useMemo } from "react"; type Props = { - type: + type?: | "primary" | "secondary" | "success" diff --git a/mwdb/web/src/commons/ui/NavDropdown.tsx b/mwdb/web/src/commons/ui/NavDropdown.tsx index c16c8d378..41d1587d4 100644 --- a/mwdb/web/src/commons/ui/NavDropdown.tsx +++ b/mwdb/web/src/commons/ui/NavDropdown.tsx @@ -2,8 +2,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; type Props = { - title: string; - icon: IconProp; + title?: string; + icon?: IconProp; elements: JSX.Element[]; badge?: string; }; diff --git a/mwdb/web/src/components/AdminNav.tsx b/mwdb/web/src/components/AdminNav.tsx new file mode 100644 index 000000000..f99be053b --- /dev/null +++ b/mwdb/web/src/components/AdminNav.tsx @@ -0,0 +1,29 @@ +import { useContext } from "react"; +import { Link } from "react-router-dom"; + +import { AuthContext } from "@mwdb-web/commons/auth"; +import { ConfigContext } from "@mwdb-web/commons/config"; + +export function AdminNav() { + const auth = useContext(AuthContext); + const config = useContext(ConfigContext); + + if (!auth.isAdmin) return <>; + return ( +
  • + + Settings + {config.pendingUsers.length ? ( + + {config.pendingUsers.length} + + ) : ( + <> + )} + +
  • + ); +} diff --git a/mwdb/web/src/components/AttributesAddModal.jsx b/mwdb/web/src/components/AttributesAddModal.tsx similarity index 86% rename from mwdb/web/src/components/AttributesAddModal.jsx rename to mwdb/web/src/components/AttributesAddModal.tsx index 016f8d85b..5fbe93632 100644 --- a/mwdb/web/src/components/AttributesAddModal.jsx +++ b/mwdb/web/src/components/AttributesAddModal.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState, useEffect } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { isEmpty } from "lodash"; import { toast } from "react-toastify"; @@ -12,30 +12,39 @@ import "ace-builds/src-noconflict/mode-text"; import "ace-builds/src-noconflict/mode-json"; import "ace-builds/src-noconflict/theme-github"; import "ace-builds/src-noconflict/ext-searchbox"; +import { AttributeDefinition } from "@mwdb-web/types/types"; -export default function AttributesAddModal({ isOpen, onAdd, onRequestClose }) { - const [attributeDefinitions, setAttributeDefinitions] = useState({}); - const [attributeKey, setAttributeKey] = useState(""); - const [richTemplate, setRichTemplate] = useState(""); - const [attributeValue, setAttributeValue] = useState(""); - const [attributeType, setAttributeType] = useState("string"); - const [invalid, setInvalid] = useState(false); - const [error, setError] = useState(null); - const attributeForm = useRef(null); +type Props = { + isOpen: boolean; + onAdd: (attributeKey: string, value: string) => void; + onRequestClose: (e: React.MouseEvent) => void; +}; + +export function AttributesAddModal({ isOpen, onAdd, onRequestClose }: Props) { + const [attributeDefinitions, setAttributeDefinitions] = useState< + Record + >({}); + const [attributeKey, setAttributeKey] = useState(""); + const [richTemplate, setRichTemplate] = useState(""); + const [attributeValue, setAttributeValue] = useState(""); + const [attributeType, setAttributeType] = useState("string"); + const [invalid, setInvalid] = useState(false); + const [error, setError] = useState(null); + const attributeForm = useRef(null); const attributesAvailable = !isEmpty(attributeDefinitions); useEffect(() => { getAttributeDefinitions(); }, []); - function handleSubmit(ev) { + function handleSubmit(ev: React.MouseEvent) { if (ev) ev.preventDefault(); - if (!attributeForm.current.reportValidity()) return; + if (!attributeForm.current?.reportValidity()) return; let value = attributeValue; if (attributeType === "object") { try { value = JSON.parse(attributeValue); - } catch (e) { + } catch (e: any) { setError(e.toString()); return; } @@ -43,7 +52,7 @@ export default function AttributesAddModal({ isOpen, onAdd, onRequestClose }) { onAdd(attributeKey, value); } - function handleKeyChange(ev) { + function handleKeyChange(ev: React.ChangeEvent) { setAttributeKey(ev.target.value); if (!ev.target.value.length) setRichTemplate(""); else { @@ -59,12 +68,12 @@ export default function AttributesAddModal({ isOpen, onAdd, onRequestClose }) { setError(null); } - function handleValueChange(ev) { + function handleValueChange(ev: React.ChangeEvent) { setAttributeValue(ev.target.value); setError(null); } - function handleTypeChange(ev) { + function handleTypeChange(ev: React.ChangeEvent) { setAttributeType(ev.target.value); setError(null); } @@ -82,7 +91,7 @@ export default function AttributesAddModal({ isOpen, onAdd, onRequestClose }) { {} ); setAttributeDefinitions(keyDefinitions); - } catch (error) { + } catch (error: any) { toast(error.toString(), { type: "error" }); } } @@ -95,7 +104,9 @@ export default function AttributesAddModal({ isOpen, onAdd, onRequestClose }) { isOpen={isOpen} onRequestClose={onRequestClose} onConfirm={handleSubmit} - confirmDisabled={!attributesAvailable || (invalid && richTemplate)} + confirmDisabled={ + !attributesAvailable || (invalid && !isEmpty(richTemplate)) + } > {!attributesAvailable ? (
    @@ -107,7 +118,7 @@ export default function AttributesAddModal({ isOpen, onAdd, onRequestClose }) { { - setChosenProvider(e.target.value); - }} - > - - {availableProviders.map((provider) => ( - - ))} - - - - ); -} - -export function OAuthAuthorize() { - const auth = useContext(AuthContext); - const navigate = useNavigate(); - // Current query set in URI path - const searchParams = useSearchParams()[0]; - const { code, state } = Object.fromEntries(searchParams); - - async function authorize() { - const stateData = sessionStorage.getItem(`openid_${state}`); - if (!stateData) { - toast("Invalid state data", { type: "error" }); - navigate("/"); - } - const { provider, nonce, action, expiration } = JSON.parse(stateData); - sessionStorage.removeItem(`openid_${state}`); - try { - const expirationTime = new Date(expiration); - if (Date.now() > expirationTime) - throw new Error("Session expired. Please try again."); - const response = await api.oauthCallback( - provider, - action, - code, - nonce, - state - ); - if (action === "bind_account") { - toast("New external identity successfully added", { - type: "success", - }); - navigate("/profile/oauth", { - replace: true, - }); - } else { - auth.updateSession(response.data); - navigate("/", { - replace: true, - }); - } - } catch (e) { - toast(getErrorMessage(e), { - type: "error", - }); - if (action === "bind_account") { - navigate("/profile/oauth", { - replace: true, - }); - } else { - navigate("/login", { - state: { - attemptedProvider: provider, - }, - replace: true, - }); - } - } - } - - useEffect(() => { - authorize(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return
    Wait for authorization...
    ; -} diff --git a/mwdb/web/src/components/PreviewSwitchAction.tsx b/mwdb/web/src/components/PreviewSwitchAction.tsx new file mode 100644 index 000000000..defb06a6f --- /dev/null +++ b/mwdb/web/src/components/PreviewSwitchAction.tsx @@ -0,0 +1,21 @@ +import { ObjectAction, useTabContext } from "./ShowObject"; + +export function PreviewSwitchAction() { + const tabContext = useTabContext(); + const mode = tabContext.subTab || "raw"; + + if (mode === "raw") { + return ( + + ); + } + return ( + + ); +} diff --git a/mwdb/web/src/components/ProviderButton.tsx b/mwdb/web/src/components/ProviderButton.tsx new file mode 100644 index 000000000..7c38794dd --- /dev/null +++ b/mwdb/web/src/components/ProviderButton.tsx @@ -0,0 +1,26 @@ +import { authenticate } from "@mwdb-web/commons/helpers/authenticate"; + +type Props = { + provider: string; + color: string; +}; + +export function ProviderButton({ provider, color }: Props) { + const chosenProvider = provider; + + return ( + + ); +} diff --git a/mwdb/web/src/components/ProvidersSelectList.tsx b/mwdb/web/src/components/ProvidersSelectList.tsx new file mode 100644 index 000000000..48a0dfaa4 --- /dev/null +++ b/mwdb/web/src/components/ProvidersSelectList.tsx @@ -0,0 +1,39 @@ +import { useState } from "react"; +import { authenticate } from "@mwdb-web/commons/helpers/authenticate"; + +type Props = { + providersList: string[]; +}; + +export function ProvidersSelectList({ providersList }: Props) { + const availableProviders = providersList; + const [chosenProvider, setChosenProvider] = useState(""); + + return ( +
    + + +
    + ); +} diff --git a/mwdb/web/src/components/RecentBlobs.jsx b/mwdb/web/src/components/RecentBlobs.jsx deleted file mode 100644 index 4607d54bf..000000000 --- a/mwdb/web/src/components/RecentBlobs.jsx +++ /dev/null @@ -1,174 +0,0 @@ -import React from "react"; -import { useSearchParams, Link } from "react-router-dom"; - -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faRandom } from "@fortawesome/free-solid-svg-icons"; - -import { RecentView, RecentRow, RecentInnerRow } from "./RecentView"; -import { TagList } from "@mwdb-web/commons/ui"; -import { DateString, ObjectLink, Hash } from "@mwdb-web/commons/ui"; -import { useRemotePath } from "@mwdb-web/commons/remotes"; - -export function RecentBlobRow(props) { - const remotePath = useRemotePath(); - const searchParams = useSearchParams()[0]; - const diffWith = searchParams.get("diff"); - const blobType = ( - { - ev.preventDefault(); - props.addToQuery("type", props.blob_type); - }} - > - {props.blob_type} - - ); - const blobId = diffWith ? ( - props.id === diffWith ? ( - - ) : ( - - - - ) - ) : ( - - ); - const blobIcon = diffWith && ( - - ); - const tags = ( - { - ev.preventDefault(); - props.addToQuery("tag", tag); - }} - tagRemove={(ev, tag) => props.addToQuery("NOT tag", tag)} - filterable - /> - ); - const firstSeen = ; - const lastSeen = ; - - return ( - - - {/* Shrinked mode */} - - - {blobId} - - {/* Wide mode */} - - - - {/* Shrinked mode */} - - {blobType} - - - {firstSeen} - - - {tags} - - {/* Wide mode */} - - {blobId} - - - - {/* Wide mode */} - - {blobType} - - - - {/* Wide mode */} - {tags} - - - {/* Wide mode */} - - {firstSeen} - - - - {/* Wide mode */} - - {lastSeen} - - - - ); -} - -export function RecentBlobHeader() { - return ( - - {/* Shrinked mode */} - Blob name/Blob ID - Type/Date/Tags - {/* Wide mode */} - Blob name - Blob ID - Blob type - Tags - First seen - Last seen - - ); -} - -export default function RecentBlobs(props) { - const searchParams = useSearchParams()[0]; - const diffWith = searchParams.get("diff"); - return ( - - {diffWith ? ( -
    -
    - Choose blob to diff with{" "} - -
    -
    - ) : ( - [] - )} - -
    - ); -} diff --git a/mwdb/web/src/components/RecentConfigs.jsx b/mwdb/web/src/components/RecentConfigs.jsx deleted file mode 100644 index 99b527109..000000000 --- a/mwdb/web/src/components/RecentConfigs.jsx +++ /dev/null @@ -1,130 +0,0 @@ -import React from "react"; - -import { RecentView, RecentRow, RecentInnerRow } from "./RecentView"; -import { TagList } from "@mwdb-web/commons/ui"; - -import { DateString, ObjectLink } from "@mwdb-web/commons/ui"; - -export function RecentConfigRow(props) { - const family = ( - { - ev.preventDefault(); - props.addToQuery("family", props.family); - }} - > - {props.family} - - ); - const configId = ; - const configType = ( - { - ev.preventDefault(); - props.addToQuery("type", props.config_type); - }} - > - {props.config_type} - - ); - const uploadTime = ; - const tags = ( - { - ev.preventDefault(); - props.addToQuery("tag", tag); - }} - tagRemove={(ev, tag) => props.addToQuery("NOT tag", tag)} - filterable - /> - ); - - return ( - - - {/* Shrinked mode */} - - {family} - - - {configId} - - {/* Wide mode */} - - {family} - - - - {/* Shrinked mode */} - - {configType} - - - {uploadTime} - - - {tags} - - {/* Wide mode */} - - {configId} - - - - - {configType} - - - - {tags} - - - - {uploadTime} - - - - ); -} - -export function RecentConfigHeader() { - return ( - - {/* Shrinked mode */} - Family/Config ID - Type/First seen/Tags - {/* Wide mode */} - Family - Config ID - Config type - Tags - First seen - - ); -} - -export default function RecentConfigs(props) { - return ( - - ); -} diff --git a/mwdb/web/src/components/RecentObjects.jsx b/mwdb/web/src/components/RecentObjects.jsx deleted file mode 100644 index 62d8fe965..000000000 --- a/mwdb/web/src/components/RecentObjects.jsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from "react"; - -import { RecentView, RecentRow, RecentInnerRow } from "./RecentView"; -import { TagList, DateString, ObjectLink } from "@mwdb-web/commons/ui"; - -export function RecentObjectRow(props) { - const objectId = ; - const uploadTime = ; - const tags = ( - { - ev.preventDefault(); - props.addToQuery("tag", tag); - }} - tagRemove={(ev, tag) => props.addToQuery("NOT tag", tag)} - filterable - /> - ); - - return ( - - - {/* All modes */} - - {objectId} - - - - {/* Shrinked mode */} - - {props.type} - - - {uploadTime} - - {tags} - {/* Wide mode */} - - {props.type} - - - - {tags} - - - - {uploadTime} - - - - ); -} - -export function RecentObjectHeader() { - return ( - - {/* Shrinked mode */} - Object ID - Type/First seen/Tags - {/* Wide mode */} - Object ID - Object type - Tags - First seen - - ); -} - -export default function RecentObjects(props) { - return ( - - ); -} diff --git a/mwdb/web/src/components/RecentSamples.jsx b/mwdb/web/src/components/RecentSamples.jsx deleted file mode 100644 index 40d40c91e..000000000 --- a/mwdb/web/src/components/RecentSamples.jsx +++ /dev/null @@ -1,130 +0,0 @@ -import React from "react"; - -import { RecentView, RecentRow, RecentInnerRow } from "./RecentView"; -import { TagList } from "@mwdb-web/commons/ui"; -import { DateString, Identicon, ObjectLink } from "@mwdb-web/commons/ui"; -import { humanFileSize } from "@mwdb-web/commons/helpers"; - -export function RecentFileRow(props) { - const identicon = ( - - ); - const uploadTime = ; - const tags = ( - { - ev.preventDefault(); - props.addToQuery("tag", tag); - }} - tagRemove={(ev, tag) => props.addToQuery("NOT tag", tag)} - filterable - /> - ); - - return ( - - - {/* Wide mode */} -
    {identicon}
    - - - - - - - - {/* Shrinked mode */} - - - - - - {uploadTime} - - - - {/* All modes */} - - {humanFileSize(props.file_size)} - - - {/* Shrink mode */} - - {tags} - - - - {/* Wide mode */} - {tags} - - - {/* Wide mode */} - - {uploadTime} - - -
    - ); -} - -export function RecentFileHeader() { - return ( - - {/* Shrinked mode */} - Name/SHA256/First seen - Size/Type/Tags - {/* Wide mode */} - Name/Hash - Size/Type - Tags - First seen - - ); -} - -export default function RecentSamples(props) { - return ( - - ); -} diff --git a/mwdb/web/src/components/RecentView/Views/RecentView.tsx b/mwdb/web/src/components/RecentView/Views/RecentView.tsx index d21eb4751..632ff3310 100644 --- a/mwdb/web/src/components/RecentView/Views/RecentView.tsx +++ b/mwdb/web/src/components/RecentView/Views/RecentView.tsx @@ -13,9 +13,9 @@ import { isEmpty } from "lodash"; type Props = { type: ObjectType; - rowComponent: JSX.Element; - headerComponent: JSX.Element; - disallowEmpty: boolean; + rowComponent: React.ComponentType; + headerComponent: React.ComponentType; + disallowEmpty?: boolean; }; export function RecentView(props: Props) { @@ -270,7 +270,7 @@ export function RecentView(props: Props) { rowComponent={props.rowComponent} headerComponent={props.headerComponent} locked={isLocked} - disallowEmpty={props.disallowEmpty} + disallowEmpty={props.disallowEmpty ?? false} setQueryError={setQueryError} addToQuery={addToQuery} /> diff --git a/mwdb/web/src/components/RecentView/Views/RecentViewList.tsx b/mwdb/web/src/components/RecentView/Views/RecentViewList.tsx index 3186bb7a7..f1d7465ce 100644 --- a/mwdb/web/src/components/RecentView/Views/RecentViewList.tsx +++ b/mwdb/web/src/components/RecentView/Views/RecentViewList.tsx @@ -75,8 +75,8 @@ type Props = { message: string; }> | null ) => void; - rowComponent: JSX.Element; - headerComponent: JSX.Element; + rowComponent: React.ComponentType; + headerComponent: React.ComponentType; locked: boolean; addToQuery: AddToQuery; }; @@ -143,10 +143,10 @@ export default function RecentViewList(props: Props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [listState.pageToLoad, listState.loadedPages]); - const Row = props.rowComponent as unknown as React.ComponentType<{ + const Row = props.rowComponent as React.ComponentType<{ addToQuery: AddToQuery; }>; - const Header = props.headerComponent as unknown as React.ComponentType; + const Header = props.headerComponent as React.ComponentType; const tableStyle: React.CSSProperties = { tableLayout: "fixed", ...(props.locked ? { pointerEvents: "none", filter: "blur(4px)" } : {}), diff --git a/mwdb/web/src/components/RecentView/common/RecentInnerRow.tsx b/mwdb/web/src/components/RecentView/common/RecentInnerRow.tsx index 8ef008dd3..cdd2036f5 100644 --- a/mwdb/web/src/components/RecentView/common/RecentInnerRow.tsx +++ b/mwdb/web/src/components/RecentView/common/RecentInnerRow.tsx @@ -3,12 +3,12 @@ import { ActionCopyToClipboard } from "@mwdb-web/commons/ui"; type Props = { narrowOnly?: boolean; wideOnly?: boolean; - copyable: boolean; + copyable?: boolean; label?: string; - labelWidth?: number; - icon: JSX.Element; - noEllipsis: boolean; - value: string; + labelWidth?: string; + icon?: JSX.Element; + noEllipsis?: boolean; + value?: string; children?: JSX.Element; }; @@ -36,7 +36,7 @@ export function RecentInnerRow(props: Props) { {props.copyable && ( diff --git a/mwdb/web/src/components/RelationToManyNode.tsx b/mwdb/web/src/components/RelationToManyNode.tsx new file mode 100644 index 000000000..d2b304546 --- /dev/null +++ b/mwdb/web/src/components/RelationToManyNode.tsx @@ -0,0 +1,34 @@ +const nodeStatuses = { + initial: "initial", + showGraph: "showGraph", + showWarning: "showWarning", +}; + +type Props = { + setNodesStatus: (graph: string) => void; + nodesLength: number; +}; + +export function RelationToManyNode({ setNodesStatus, nodesLength }: Props) { + return ( + <> +
    + The relationships for a given object will amount to{" "} + {nodesLength}{" "} + elements, displaying such a quantity of connections may affect + the application's performance. +
    +
    + +
    + + ); +} diff --git a/mwdb/web/src/components/RelationsNode.tsx b/mwdb/web/src/components/RelationsNode.tsx new file mode 100644 index 000000000..7aacc0240 --- /dev/null +++ b/mwdb/web/src/components/RelationsNode.tsx @@ -0,0 +1,93 @@ +import { capitalize } from "@mwdb-web/commons/helpers"; +import { Tag } from "@mwdb-web/commons/ui"; +import { ObjectLegacyType, ObjectType } from "@mwdb-web/types/types"; + +function nodeTypeMapping(type: ObjectLegacyType): ObjectType { + switch (type) { + case "file": { + return "file"; + } + case "text_blob": { + return "blob"; + } + default: { + return "config"; + } + } +} + +function styleMapping(type: ObjectType) { + switch (type) { + case "file": { + return "bg-danger"; + } + case "blob": { + return "bg-info"; + } + default: { + return "bg-success"; + } + } +} + +type Props = { + remotePath: string; + node: { + id: string; + expanded: boolean; + object: { + tags: { + tag: string; + }[]; + type: ObjectLegacyType; + upload_time: string; + }; + }; +}; + +export function RelationsNode(props: Props) { + const nodeType = nodeTypeMapping( + props.node.object.type as ObjectLegacyType + ); + const nodeStyle = styleMapping(nodeType); + const nodeHeaderStyle = props.node.expanded + ? "node-header-expanded" + : "node-header-active"; + + return ( +
    +
    +
    + {capitalize(nodeType)}{" "} + + {new Date( + props.node.object.upload_time + ).toLocaleDateString()} + +
    + +
    + {props.node.object.tags.map((tag) => ( + + ))} +
    +
    +
    + ); +} diff --git a/mwdb/web/src/components/RemoteDropdown.tsx b/mwdb/web/src/components/RemoteDropdown.tsx new file mode 100644 index 000000000..4be27c806 --- /dev/null +++ b/mwdb/web/src/components/RemoteDropdown.tsx @@ -0,0 +1,26 @@ +import { useContext } from "react"; +import { Link } from "react-router-dom"; + +import { faGlobe } from "@fortawesome/free-solid-svg-icons"; +import { ConfigContext } from "@mwdb-web/commons/config"; +import { NavDropdown } from "@mwdb-web/commons/ui"; + +export function RemoteDropdown() { + const config = useContext(ConfigContext); + if (!config.isReady) return <>; + + const remotes = config.config.remotes || []; + const remoteItems = remotes.map((remote) => ( + + {remote} + + )); + + return ( + + ); +} diff --git a/mwdb/web/src/components/ShowSample.jsx b/mwdb/web/src/components/SampleDetails.tsx similarity index 54% rename from mwdb/web/src/components/ShowSample.jsx rename to mwdb/web/src/components/SampleDetails.tsx index 75cef20b3..8421bc867 100644 --- a/mwdb/web/src/components/ShowSample.jsx +++ b/mwdb/web/src/components/SampleDetails.tsx @@ -1,30 +1,8 @@ -import React, { useState, useEffect, useContext } from "react"; -import { Link, useParams } from "react-router-dom"; +import { useContext } from "react"; +import { Link } from "react-router-dom"; -import { - ShowObject, - ObjectTab, - ObjectContext, - useTabContext, - LatestConfigTab, - RelationsTab, - DownloadAction, - ZipAction, - FavoriteAction, - PushAction, - PullAction, - UploadChildAction, - RemoveAction, - ObjectAction, -} from "./ShowObject"; - -import { - faFile, - faFingerprint, - faSearch, -} from "@fortawesome/free-solid-svg-icons"; +import { ObjectContext } from "./ShowObject"; -import { APIContext } from "@mwdb-web/commons/api"; import { makeSearchLink, makeSearchDateLink, @@ -36,13 +14,20 @@ import { DataTable, DateString, Hash, - HexView, } from "@mwdb-web/commons/ui"; import { useRemotePath } from "@mwdb-web/commons/remotes"; +import { ObjectData } from "@mwdb-web/types/types"; -function SampleDetails() { +export function SampleDetails() { const context = useContext(ObjectContext); const remotePath = useRemotePath(); + + if (!context) { + return <>; + } + + const object = context.object as ObjectData; + return ( @@ -52,15 +37,15 @@ function SampleDetails() { - {context.object.file_name} + {object.file_name} @@ -69,7 +54,7 @@ function SampleDetails() { Variant file names - {context.object.alt_names.map((alt_name) => ( + {object.alt_names.map((alt_name) => (
    - {humanFileSize(context.object.file_size)} + {humanFileSize(object.file_size)} @@ -116,15 +101,15 @@ function SampleDetails() { - {context.object.file_type} + {object.file_type} @@ -133,10 +118,10 @@ function SampleDetails() { md5 - + @@ -145,10 +130,10 @@ function SampleDetails() { sha1 - + @@ -157,10 +142,10 @@ function SampleDetails() { sha256 - + @@ -169,10 +154,10 @@ function SampleDetails() { sha512 - + @@ -181,10 +166,10 @@ function SampleDetails() { crc32 - {context.object.crc32} + {object.crc32} @@ -196,15 +181,15 @@ function SampleDetails() { - {context.object.ssdeep} + {object.ssdeep} @@ -214,15 +199,15 @@ function SampleDetails() { Upload time {" "} - {context.object.upload_time ? ( + {object.upload_time ? ( - + ) : ( [] @@ -233,123 +218,3 @@ function SampleDetails() { ); } - -// negate the buffer contents (xor with key equal 0xff) -function negateBuffer(buffer) { - const uint8View = new Uint8Array(buffer); - const xored = uint8View.map((item) => item ^ 0xff); - return xored.buffer; -} - -function SamplePreview() { - const [content, setContent] = useState(""); - const api = useContext(APIContext); - const objectContext = useContext(ObjectContext); - const tabContext = useTabContext(); - - async function updateSample() { - try { - const fileId = objectContext.object.id; - const obfuscate = 1; - const fileContentResponse = await api.downloadFile( - fileId, - obfuscate - ); - const fileContentResponseData = negateBuffer( - fileContentResponse.data - ); - setContent(fileContentResponseData); - } catch (e) { - objectContext.setObjectError(e); - } - } - - useEffect(() => { - updateSample(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [objectContext.object.id]); - - return ( - - ); -} - -function PreviewSwitchAction(props) { - const tabContext = useTabContext(); - const mode = tabContext.subTab || "raw"; - - if (mode === "raw") - return ( - - ); - else - return ( - - ); -} - -export default function ShowSample(props) { - const api = useContext(APIContext); - const params = useParams(); - const remotePath = useRemotePath(); - - async function downloadSample(object) { - window.location.href = await api.requestFileDownloadLink(object.id); - } - - async function zipSample(object) { - window.location.href = await api.requestZipFileDownloadLink(object.id); - } - - return ( - - , - , - , - , - , - , - , - ]} - /> - - , - , - , - , - , - , - , - ]} - /> - - - ); -} diff --git a/mwdb/web/src/components/SamplePreview.tsx b/mwdb/web/src/components/SamplePreview.tsx new file mode 100644 index 000000000..9d3f2ded9 --- /dev/null +++ b/mwdb/web/src/components/SamplePreview.tsx @@ -0,0 +1,42 @@ +import { APIContext } from "@mwdb-web/commons/api"; +import { useContext, useEffect, useState } from "react"; +import { ObjectContext, useTabContext } from "./ShowObject"; +import { negateBuffer } from "@mwdb-web/commons/helpers"; +import { HexView } from "@mwdb-web/commons/ui"; + +export function SamplePreview() { + const [content, setContent] = useState(new ArrayBuffer(0)); + const api = useContext(APIContext); + const objectContext = useContext(ObjectContext); + const tabContext = useTabContext(); + + async function updateSample() { + try { + const fileId = objectContext!.object!.id!; + const obfuscate = 1; + const fileContentResponse = await api.downloadFile( + fileId, + obfuscate + ); + const fileContentResponseData = negateBuffer( + fileContentResponse.data + ); + setContent(fileContentResponseData); + } catch (e) { + objectContext.setObjectError(e); + } + } + + useEffect(() => { + updateSample(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [objectContext?.object?.id]); + + return ( + + ); +} diff --git a/mwdb/web/src/components/Search.jsx b/mwdb/web/src/components/Search.jsx deleted file mode 100644 index ed24a8ecb..000000000 --- a/mwdb/web/src/components/Search.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -import RecentObjects from "./RecentObjects"; - -export default function Search(props) { - return ; -} diff --git a/mwdb/web/src/components/ShowObject/Actions/DownloadAction.tsx b/mwdb/web/src/components/ShowObject/Actions/DownloadAction.tsx index 0f9eeacad..9c85d6cd2 100644 --- a/mwdb/web/src/components/ShowObject/Actions/DownloadAction.tsx +++ b/mwdb/web/src/components/ShowObject/Actions/DownloadAction.tsx @@ -7,7 +7,7 @@ import { ObjectAction } from "@mwdb-web/commons/ui"; import { ObjectOrConfigOrBlobData } from "@mwdb-web/types/types"; type Props = { - download: (object?: Partial) => void; + download: (object?: Partial) => Promise; }; export function DownloadAction(props: Props) { diff --git a/mwdb/web/src/components/ShowObject/Actions/PushAction.tsx b/mwdb/web/src/components/ShowObject/Actions/PushAction.tsx index 33bc35843..a208f6c71 100644 --- a/mwdb/web/src/components/ShowObject/Actions/PushAction.tsx +++ b/mwdb/web/src/components/ShowObject/Actions/PushAction.tsx @@ -22,7 +22,7 @@ export function PushAction() { const remotes = config.config.remotes; - if (!remotes || !remotes.length || api.remote) return []; + if (!remotes || !remotes.length || api.remote) return <>; async function pushRemote() { try { diff --git a/mwdb/web/src/components/ShowObject/common/AttributesBox.tsx b/mwdb/web/src/components/ShowObject/common/AttributesBox.tsx index f1ec21ed9..a167e5a09 100644 --- a/mwdb/web/src/components/ShowObject/common/AttributesBox.tsx +++ b/mwdb/web/src/components/ShowObject/common/AttributesBox.tsx @@ -11,7 +11,7 @@ import { ConfirmationModal } from "@mwdb-web/commons/ui"; import { Capability } from "@mwdb-web/types/types"; import { Attributes } from "./Attributes"; -import AttributesAddModal from "../../AttributesAddModal"; +import { AttributesAddModal } from "../../AttributesAddModal"; import { Attribute, AttributeDefinition } from "@mwdb-web/types/types"; export function AttributesBox() { diff --git a/mwdb/web/src/components/ShowObject/common/LatestConfigTab.tsx b/mwdb/web/src/components/ShowObject/common/LatestConfigTab.tsx index 02bc7c304..982e8d7f5 100644 --- a/mwdb/web/src/components/ShowObject/common/LatestConfigTab.tsx +++ b/mwdb/web/src/components/ShowObject/common/LatestConfigTab.tsx @@ -1,6 +1,6 @@ import { useContext } from "react"; -import ConfigTable from "../../ConfigTable"; +import { ConfigTable } from "../../Config/common/ConfigTable"; import { ObjectContext } from "@mwdb-web/commons/context"; import { ObjectAction, ObjectTab } from "@mwdb-web/commons/ui"; diff --git a/mwdb/web/src/components/ShowObject/common/ObjectBox.tsx b/mwdb/web/src/components/ShowObject/common/ObjectBox.tsx index 20d557996..10c86cc7c 100644 --- a/mwdb/web/src/components/ShowObject/common/ObjectBox.tsx +++ b/mwdb/web/src/components/ShowObject/common/ObjectBox.tsx @@ -11,7 +11,7 @@ type Props = { children: React.ReactNode; }; -export default function ObjectBox({ defaultTab, children }: Props) { +export function ObjectBox({ defaultTab, children }: Props) { const remotePath = useRemotePath(); const location = useLocation(); const { Component, setComponent } = useComponentState(); diff --git a/mwdb/web/src/components/ShowObject/common/RecentObjectHeader.tsx b/mwdb/web/src/components/ShowObject/common/RecentObjectHeader.tsx new file mode 100644 index 000000000..fff4a252e --- /dev/null +++ b/mwdb/web/src/components/ShowObject/common/RecentObjectHeader.tsx @@ -0,0 +1,14 @@ +export function RecentObjectHeader() { + return ( + + {/* Shrinked mode */} + Object ID + Type/First seen/Tags + {/* Wide mode */} + Object ID + Object type + Tags + First seen + + ); +} diff --git a/mwdb/web/src/components/ShowObject/common/RecentObjectRow.tsx b/mwdb/web/src/components/ShowObject/common/RecentObjectRow.tsx new file mode 100644 index 000000000..cdbdabf1c --- /dev/null +++ b/mwdb/web/src/components/ShowObject/common/RecentObjectRow.tsx @@ -0,0 +1,67 @@ +import { TagList, DateString, ObjectLink } from "@mwdb-web/commons/ui"; +import { RecentInnerRow, RecentRow } from "@mwdb-web/components/RecentView"; +import { RecentRowProps } from "@mwdb-web/types/props"; +import { ObjectData } from "@mwdb-web/types/types"; + +export function RecentObjectRow(props: RecentRowProps) { + const objectId = ; + const uploadTime = ; + const tags = ( + { + ev.preventDefault(); + props.addToQuery("tag", tag); + }} + tagRemove={(ev, tag) => props.addToQuery("NOT tag", tag)} + filterable + /> + ); + + return ( + + <> + + {/* All modes */} + + {objectId} + + + + {/* Shrinked mode */} + + <>{props.type} + + + {uploadTime} + + {tags} + {/* Wide mode */} + + <>{props.type} + + + + {tags} + + + + {uploadTime} + + + + + ); +} diff --git a/mwdb/web/src/components/ShowObject/common/RecentObjects.tsx b/mwdb/web/src/components/ShowObject/common/RecentObjects.tsx new file mode 100644 index 000000000..4d5ca411d --- /dev/null +++ b/mwdb/web/src/components/ShowObject/common/RecentObjects.tsx @@ -0,0 +1,13 @@ +import { RecentView } from "../../RecentView"; +import { RecentObjectHeader } from "./RecentObjectHeader"; +import { RecentObjectRow } from "./RecentObjectRow"; + +export default function RecentObjects() { + return ( + + ); +} diff --git a/mwdb/web/src/components/ShowObject/common/RelationsTab.tsx b/mwdb/web/src/components/ShowObject/common/RelationsTab.tsx index 10801310b..dc83e336c 100644 --- a/mwdb/web/src/components/ShowObject/common/RelationsTab.tsx +++ b/mwdb/web/src/components/ShowObject/common/RelationsTab.tsx @@ -4,7 +4,7 @@ import { useSearchParams } from "react-router-dom"; import { faProjectDiagram, faSearch } from "@fortawesome/free-solid-svg-icons"; -import RelationsPlot from "../../RelationsPlot"; +import { RelationsPlotView } from "../../Views/RelationsPlotView"; import { ObjectContext } from "@mwdb-web/commons/context"; import { ObjectAction, ObjectTab } from "@mwdb-web/commons/ui"; @@ -24,7 +24,10 @@ export function RelationsTab() { , ]} component={() => ( - + )} /> ); diff --git a/mwdb/web/src/components/ShowObject/common/ShowObject.tsx b/mwdb/web/src/components/ShowObject/common/ShowObject.tsx index 161390c17..0e4f72961 100644 --- a/mwdb/web/src/components/ShowObject/common/ShowObject.tsx +++ b/mwdb/web/src/components/ShowObject/common/ShowObject.tsx @@ -10,7 +10,7 @@ import { toast } from "react-toastify"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import ObjectBox from "./ObjectBox"; +import { ObjectBox } from "./ObjectBox"; import { MultiRelationsBox } from "./MultiRelationsBox"; import { CommentBox } from "./CommentBox"; import { SharesBox } from "./SharesBox"; diff --git a/mwdb/web/src/components/ShowObject/common/TagForm.tsx b/mwdb/web/src/components/ShowObject/common/TagForm.tsx index c4b801e8c..b8d5d1367 100644 --- a/mwdb/web/src/components/ShowObject/common/TagForm.tsx +++ b/mwdb/web/src/components/ShowObject/common/TagForm.tsx @@ -1,4 +1,4 @@ -import { useState, useContext } from "react"; +import { useState, useContext, FormEventHandler } from "react"; import { APIContext } from "@mwdb-web/commons/api"; import { ObjectContext } from "@mwdb-web/commons/context"; diff --git a/mwdb/web/src/components/ShowTextBlob.jsx b/mwdb/web/src/components/ShowTextBlob.jsx deleted file mode 100644 index fa21569e4..000000000 --- a/mwdb/web/src/components/ShowTextBlob.jsx +++ /dev/null @@ -1,192 +0,0 @@ -import React, { useContext } from "react"; -import { Link, useParams } from "react-router-dom"; - -import { - ShowObject, - ObjectTab, - ObjectContext, - LatestConfigTab, - RelationsTab, - DownloadAction, - FavoriteAction, - RemoveAction, - ObjectAction, - PushAction, - PullAction, -} from "./ShowObject"; - -import { - faScroll, - faFingerprint, - faRandom, - faSearch, -} from "@fortawesome/free-solid-svg-icons"; - -import { - makeSearchLink, - makeSearchDateLink, - downloadData, - humanFileSize, -} from "@mwdb-web/commons/helpers"; -import { DataTable, DateString, HexView } from "@mwdb-web/commons/ui"; -import { Extendable } from "@mwdb-web/commons/plugins"; -import { useRemotePath } from "@mwdb-web/commons/remotes"; - -function TextBlobDetails() { - const context = useContext(ObjectContext); - const remotePath = useRemotePath(); - return ( - - - - Blob name - - - {context.object.blob_name} - - - - - Blob size - - - {humanFileSize(context.object.blob_size)} - - - - - Blob type - - - {context.object.blob_type} - - - - - First seen - - {" "} - {context.object.upload_time ? ( - - - - ) : ( - [] - )} - - - - Last seen - - {" "} - {context.object.last_seen ? ( - - - - ) : ( - [] - )} - - - - - ); -} - -function TextBlobPreview() { - const context = useContext(ObjectContext); - return ( - - ); -} - -function BlobDiffAction() { - const context = useContext(ObjectContext); - const remotePath = useRemotePath(); - return ( - - ); -} - -export default function ShowTextBlob(props) { - const params = useParams(); - const remotePath = useRemotePath(); - async function downloadTextBlob(object) { - downloadData(object.content, object.id, "text/plain"); - } - - return ( - - , - , - , - , - , - , - ]} - /> - - , - , - , - , - , - , - ]} - /> - - - ); -} diff --git a/mwdb/web/src/components/UploadButton.tsx b/mwdb/web/src/components/UploadButton.tsx new file mode 100644 index 000000000..7e45cfaf0 --- /dev/null +++ b/mwdb/web/src/components/UploadButton.tsx @@ -0,0 +1,27 @@ +import { faUpload } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { AuthContext } from "@mwdb-web/commons/auth"; +import { Capability } from "@mwdb-web/types/types"; +import { useContext } from "react"; +import { Link } from "react-router-dom"; + +export function UploadButton() { + const auth = useContext(AuthContext); + const buttonLink = auth.hasCapability(Capability.addingFiles) ? ( + + + Upload + + ) : ( +
    + + + Upload + +
    + ); + return buttonLink; +} diff --git a/mwdb/web/src/components/UploadDropzone.tsx b/mwdb/web/src/components/UploadDropzone.tsx new file mode 100644 index 000000000..c75dbdbd2 --- /dev/null +++ b/mwdb/web/src/components/UploadDropzone.tsx @@ -0,0 +1,53 @@ +import { useCallback } from "react"; +import { useDropzone } from "react-dropzone"; + +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faUpload } from "@fortawesome/free-solid-svg-icons"; + +type Props = { + onDrop: (data: File) => void; + file: File | null; +}; + +export function UploadDropzone(props: Props) { + const onDrop = props.onDrop; + const { getRootProps, getInputProps, isDragActive, isDragReject } = + useDropzone({ + multiple: false, + onDrop: useCallback( + (acceptedFiles: any) => onDrop(acceptedFiles[0]), + [onDrop] + ), + }); + + const dropzoneClassName = isDragActive + ? "dropzone-active" + : isDragReject + ? "dropzone-reject" + : ""; + + return ( +
    + +
    +
    +
    + +   + {props.file ? ( + + {props.file.name} - {props.file.size} bytes + + ) : ( + Click here to upload + )} +
    +
    +
    +
    + ); +} diff --git a/mwdb/web/src/components/UserSetPassword.jsx b/mwdb/web/src/components/UserSetPasswordView.tsx similarity index 82% rename from mwdb/web/src/components/UserSetPassword.jsx rename to mwdb/web/src/components/UserSetPasswordView.tsx index 3ea2369dc..e4eaca823 100644 --- a/mwdb/web/src/components/UserSetPassword.jsx +++ b/mwdb/web/src/components/UserSetPasswordView.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import { useState } from "react"; import { useForm } from "react-hook-form"; import { yupResolver } from "@hookform/resolvers/yup"; import * as Yup from "yup"; @@ -8,12 +8,17 @@ import { View } from "@mwdb-web/commons/ui"; import { getErrorMessage } from "@mwdb-web/commons/helpers"; import { api } from "@mwdb-web/commons/api"; -export default function UserSetPassword() { - const [success, setSuccess] = useState(false); +type FormValues = { + password: string; + confirmPassword: string; +}; + +export function UserSetPasswordView() { + const [success, setSuccess] = useState(false); const token = new URLSearchParams(window.location.search).get("token") || ""; - const validationSchema = Yup.object().shape({ + const validationSchema: Yup.SchemaOf = Yup.object().shape({ password: Yup.string() .required("Password is required") .min(8, "Password must be at least 8 characters"), @@ -26,10 +31,13 @@ export default function UserSetPassword() { }); const formOptions = { resolver: yupResolver(validationSchema) }; - const { register, handleSubmit, formState } = useForm(formOptions); - const { errors } = formState; + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(formOptions); - async function onSubmit(form) { + async function onSubmit(form: FormValues) { try { let response = await api.authSetPassword(token, form.password); setSuccess(true); @@ -45,17 +53,15 @@ export default function UserSetPassword() { return ( -
    e.preventDefault()}> +

    Set password

    @@ -69,11 +75,9 @@ export default function UserSetPassword() { @@ -86,7 +90,6 @@ export default function UserSetPassword() { diff --git a/mwdb/web/src/components/About.jsx b/mwdb/web/src/components/Views/AboutView.tsx similarity index 87% rename from mwdb/web/src/components/About.jsx rename to mwdb/web/src/components/Views/AboutView.tsx index 12f18cbba..4402c3f02 100644 --- a/mwdb/web/src/components/About.jsx +++ b/mwdb/web/src/components/Views/AboutView.tsx @@ -1,11 +1,11 @@ -import React, { useContext } from "react"; +import { useContext } from "react"; import { ConfigContext } from "@mwdb-web/commons/config"; import { View } from "@mwdb-web/commons/ui"; -import logo from "../assets/logo.png"; +import logo from "../../assets/logo.png"; -export default function About() { +export function AboutView() { const config = useContext(ConfigContext); return ( @@ -28,10 +28,10 @@ export default function About() { - {config.config["server_version"]} + {config.config.server_version}
    Try out our{" "} diff --git a/mwdb/web/src/components/Docs.jsx b/mwdb/web/src/components/Views/DocsView.tsx similarity index 72% rename from mwdb/web/src/components/Docs.jsx rename to mwdb/web/src/components/Views/DocsView.tsx index a433da10f..1efb55709 100644 --- a/mwdb/web/src/components/Docs.jsx +++ b/mwdb/web/src/components/Views/DocsView.tsx @@ -3,17 +3,18 @@ import React, { Suspense, useEffect, useState } from "react"; import { api } from "@mwdb-web/commons/api"; import { localStorageAuthKey } from "@mwdb-web/commons/auth"; import { View } from "@mwdb-web/commons/ui"; +import { isEmpty } from "lodash"; const SwaggerUI = React.lazy(() => import("swagger-ui-react")); -export default function Docs() { - const [apiSpec, setApiSpec] = useState({}); +export function DocsView() { + const [apiSpec, setApiSpec] = useState({}); async function updateSpec() { const spec = await api.getServerDocs(); // Server variables delivered with spec doesn't work well in swagger-ui-react - spec.data["servers"] = [ + spec.data.servers = [ { url: new URL("/", document.baseURI).href, description: "MWDB API endpoint", @@ -22,12 +23,14 @@ export default function Docs() { setApiSpec(spec.data); } - function requestInterceptor(req) { - const token = JSON.parse( - localStorage.getItem(localStorageAuthKey) - ).token; + function requestInterceptor(req: Record) { + let token: string = ""; + const authData = localStorage.getItem(localStorageAuthKey); + if (authData !== null) { + token = JSON.parse(authData).token; + } - if (token) { + if (!isEmpty(token)) { req.headers.Authorization = `Bearer ${token}`; } diff --git a/mwdb/web/src/components/Views/OAuthAuthorizeView.tsx b/mwdb/web/src/components/Views/OAuthAuthorizeView.tsx new file mode 100644 index 000000000..23ce293de --- /dev/null +++ b/mwdb/web/src/components/Views/OAuthAuthorizeView.tsx @@ -0,0 +1,74 @@ +import { useContext, useEffect } from "react"; +import { useSearchParams, useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; + +import { api } from "@mwdb-web/commons/api"; +import { AuthContext } from "@mwdb-web/commons/auth"; +import { getErrorMessage } from "@mwdb-web/commons/helpers"; + +export function OAuthAuthorizeView() { + const auth = useContext(AuthContext); + const navigate = useNavigate(); + // Current query set in URI path + const searchParams = useSearchParams()[0]; + const { code, state } = Object.fromEntries(searchParams); + + async function authorize() { + const stateData = sessionStorage.getItem(`openid_${state}`); + if (!stateData) { + toast("Invalid state data", { type: "error" }); + navigate("/"); + return; + } + const { provider, nonce, action, expiration } = JSON.parse(stateData); + sessionStorage.removeItem(`openid_${state}`); + try { + const expirationTime = new Date(expiration).getTime(); + if (Date.now() > expirationTime) + throw new Error("Session expired. Please try again."); + const response = await api.oauthCallback( + provider, + action, + code, + nonce, + state + ); + if (action === "bind_account") { + toast("New external identity successfully added", { + type: "success", + }); + navigate("/profile/oauth", { + replace: true, + }); + } else { + auth.updateSession(response.data); + navigate("/", { + replace: true, + }); + } + } catch (e) { + toast(getErrorMessage(e), { + type: "error", + }); + if (action === "bind_account") { + navigate("/profile/oauth", { + replace: true, + }); + } else { + navigate("/login", { + state: { + attemptedProvider: provider, + }, + replace: true, + }); + } + } + } + + useEffect(() => { + authorize(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return
    Wait for authorization...
    ; +} diff --git a/mwdb/web/src/components/RelationsPlot.jsx b/mwdb/web/src/components/Views/RelationsPlotView.tsx similarity index 51% rename from mwdb/web/src/components/RelationsPlot.jsx rename to mwdb/web/src/components/Views/RelationsPlotView.tsx index d35e9c16d..734400de5 100644 --- a/mwdb/web/src/components/RelationsPlot.jsx +++ b/mwdb/web/src/components/Views/RelationsPlotView.tsx @@ -9,11 +9,11 @@ import { isEmpty } from "lodash"; import { useSearchParams } from "react-router-dom"; import { APIContext } from "@mwdb-web/commons/api"; -import { capitalize } from "@mwdb-web/commons/helpers"; +import { RelationsNode } from "../RelationsNode"; +import { RelationToManyNode } from "../RelationToManyNode"; +import { Edge, NodeProp, RelatedObject } from "@mwdb-web/types/types"; -import { Tag } from "@mwdb-web/commons/ui"; - -const DagreD3Plot = React.lazy(() => import("./DagreD3Plot")); +const DagreD3Plot = React.lazy(() => import("../DagreD3Plot")); const nodeStatuses = { initial: "initial", @@ -21,106 +21,36 @@ const nodeStatuses = { showWarning: "showWarning", }; -function RelationsNode(props) { - const typeMapping = { - file: "file", - config: "config", - static_config: "config", - text_blob: "blob", - }; - - const styleMapping = { - file: "bg-danger", - config: "bg-success", - blob: "bg-info", - }; - - const nodeType = typeMapping[props.node.object.type]; - const nodeStyle = styleMapping[nodeType]; - const nodeHeaderStyle = props.node.expanded - ? "node-header-expanded" - : "node-header-active"; +type RelationUpdateType = "children" | "parent" | null; - return ( -
    -
    -
    - {capitalize(nodeType)}{" "} - - {new Date( - props.node.object.upload_time - ).toLocaleDateString()} - -
    - -
    - {props.node.object.tags.map((tag) => ( - - ))} -
    -
    -
    - ); -} +type Nodes = { + nodes: NodeProp[]; + edges: Edge[]; +}; -function RelationToManyNode({ setNodesStatus, nodesLength }) { - return ( - <> -
    - The relationships for a given object will amount to{" "} - {nodesLength}{" "} - elements, displaying such a quantity of connections may affect - the application's performance. -
    -
    - -
    - - ); -} +type Props = { + hash: string; + height: string; +}; -function RelationsPlot(props) { +export function RelationsPlotView(props: Props) { const api = useContext(APIContext); const [searchParams, setSearchParams] = useSearchParams(); const { hash, height } = props; const defaultProps = { - height: "900", + height: 900, width: "100%", }; const [nodesStatus, setNodesStatus] = useState("initial"); - const [nodes, setNodes] = useState({ + const [nodes, setNodes] = useState({ nodes: [], edges: [], }); - const convertObject = (obj, expanded) => { - let node = { + const convertObject = (obj: RelatedObject, expanded: boolean) => { + const node: NodeProp = { id: obj.id, object: { type: obj.type, @@ -132,36 +62,44 @@ function RelationsPlot(props) { return node; }; - const isNodeExpanded = (hash) => { + const isNodeExpanded = (hash: string) => { let node = nodes.nodes.find((n) => n.id === hash); return node && node.expanded; }; - const addNodes = (prevNodesState, newNode, newEdge = false) => { - const getNode = (hash) => { + const addNodes = ( + prevNodesState: Nodes, + newNode: RelatedObject, + newEdge?: Edge + ) => { + const getNode = (hash: string) => { return prevNodesState.nodes.find((n) => n.id === hash); }; - const getEdge = (edge) => { + const getEdge = (edge: Edge) => { return prevNodesState.edges.find( (e) => e.parent === edge.parent && e.child === edge.child ); }; - let newNodesState = {}; + const newNodesState = {} as Nodes; let existingNode = getNode(newNode.id); if (!existingNode) { // Append new node and mark node as expanded if no edge is added. - newNode = convertObject(newNode, !newEdge); - newNodesState.nodes = [...prevNodesState.nodes, newNode]; + newNodesState.nodes = [ + ...prevNodesState.nodes, + convertObject(newNode, !newEdge), + ]; } else { // Update node in the same place + let newConvertNode = {} as NodeProp; if ((!existingNode.expanded && !newEdge) || existingNode.expanded) - newNode = convertObject(newNode, true); - else newNode = convertObject(newNode, false); + newConvertNode = convertObject(newNode, true); + else newConvertNode = convertObject(newNode, false); newNodesState.nodes = prevNodesState.nodes.reduce( - (nodesList, node) => { - if (node.id === newNode.id) return [...nodesList, newNode]; + (nodesList: NodeProp[], node: NodeProp) => { + if (node.id === newNode.id) + return [...nodesList, newConvertNode]; else return [...nodesList, node]; }, [] @@ -175,7 +113,11 @@ function RelationsPlot(props) { return newNodesState; }; - const updateObject = (obj, type = null, edgeId = null) => { + const updateObject = ( + obj: RelatedObject, + type: RelationUpdateType = null, + edgeId: string | null = null + ) => { if (type === "parent") { setNodes((prevNodes) => addNodes(prevNodes, obj, { parent: obj.id, child: edgeId }) @@ -189,9 +131,9 @@ function RelationsPlot(props) { } }; - const expandNode = async (hash, expanded = false) => { - let objectInfo = await api.getObject("object", hash); - let obj = objectInfo.data; + const expandNode = async (hash: string) => { + const objectInfo = await api.getObject("object", hash); + const obj = objectInfo.data; updateObject(obj); if (obj.parents.length > 0) { obj.parents.forEach((o) => { @@ -205,7 +147,7 @@ function RelationsPlot(props) { } }; - const onNodeClick = (node) => { + const onNodeClick = (node: string) => { let nodes = searchParams.getAll("node"); if (isNodeExpanded(node)) return; nodes = [...(nodes || []), node]; @@ -217,8 +159,8 @@ function RelationsPlot(props) { }; useLayoutEffect(() => { - let nodes = searchParams.getAll("node"); - let expandedNodes = nodes || []; + const nodes = searchParams.getAll("node"); + const expandedNodes = nodes || []; if (hash && !expandedNodes.includes(hash)) { expandedNodes.push(hash); } @@ -246,7 +188,7 @@ function RelationsPlot(props) { {nodesStatus === nodeStatuses.showGraph && ( ); } - -export default RelationsPlot; diff --git a/mwdb/web/src/components/Views/SearchView.tsx b/mwdb/web/src/components/Views/SearchView.tsx new file mode 100644 index 000000000..dd8d14ea7 --- /dev/null +++ b/mwdb/web/src/components/Views/SearchView.tsx @@ -0,0 +1,5 @@ +import RecentObjects from "../ShowObject/common/RecentObjects"; + +export function SearchView() { + return ; +} diff --git a/mwdb/web/src/components/Views/ShowSampleView.tsx b/mwdb/web/src/components/Views/ShowSampleView.tsx new file mode 100644 index 000000000..aee8da2b2 --- /dev/null +++ b/mwdb/web/src/components/Views/ShowSampleView.tsx @@ -0,0 +1,91 @@ +import { useContext } from "react"; +import { useParams } from "react-router-dom"; + +import { + ShowObject, + ObjectTab, + LatestConfigTab, + RelationsTab, + DownloadAction, + ZipAction, + FavoriteAction, + PushAction, + PullAction, + UploadChildAction, + RemoveAction, +} from "../ShowObject"; + +import { + faFile, + faFingerprint, + faSearch, +} from "@fortawesome/free-solid-svg-icons"; + +import { APIContext } from "@mwdb-web/commons/api"; +import { useRemotePath } from "@mwdb-web/commons/remotes"; +import { SampleDetails } from "../SampleDetails"; +import { SamplePreview } from "../SamplePreview"; +import { PreviewSwitchAction } from "../PreviewSwitchAction"; +import { ObjectOrConfigOrBlobData } from "@mwdb-web/types/types"; + +export function ShowSampleView() { + const api = useContext(APIContext); + const params = useParams(); + const remotePath = useRemotePath(); + + async function downloadSample(object?: Partial) { + if (object && object.id) { + window.location.href = await api.requestFileDownloadLink(object.id); + } + } + + async function zipSample(object?: Partial) { + if (object && object.id) { + window.location.href = await api.requestZipFileDownloadLink( + object.id + ); + } + } + + return ( + + , + , + , + , + , + , + , + ]} + /> + + , + , + , + , + , + , + , + ]} + /> + + + ); +} diff --git a/mwdb/web/src/components/Upload.jsx b/mwdb/web/src/components/Views/UploadView.tsx similarity index 83% rename from mwdb/web/src/components/Upload.jsx rename to mwdb/web/src/components/Views/UploadView.tsx index f47451c9f..fb54ec356 100644 --- a/mwdb/web/src/components/Upload.jsx +++ b/mwdb/web/src/components/Views/UploadView.tsx @@ -1,11 +1,8 @@ import { useCallback, useContext, useState, useEffect } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; -import { useDropzone } from "react-dropzone"; import { toast } from "react-toastify"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faUpload } from "@fortawesome/free-solid-svg-icons"; -import AttributesAddModal from "./AttributesAddModal"; +import { AttributesAddModal } from "../AttributesAddModal"; import { api } from "@mwdb-web/commons/api"; import { AuthContext } from "@mwdb-web/commons/auth"; @@ -13,68 +10,29 @@ import { getErrorMessage } from "@mwdb-web/commons/helpers"; import { Autocomplete, DataTable, ShowIf, View } from "@mwdb-web/commons/ui"; import { ConfigContext } from "@mwdb-web/commons/config"; import { Extendable } from "@mwdb-web/commons/plugins"; +import { UploadDropzone } from "../UploadDropzone"; +import { Attribute } from "@mwdb-web/types/types"; +import { isEmpty } from "lodash"; import { Capability } from "@mwdb-web/types/types"; -function UploadDropzone(props) { - const onDrop = props.onDrop; - const { getRootProps, getInputProps, isDragActive, isDragReject } = - useDropzone({ - multiple: false, - onDrop: useCallback( - (acceptedFiles) => onDrop(acceptedFiles[0]), - [onDrop] - ), - }); - - const dropzoneClassName = isDragActive - ? "dropzone-active" - : isDragReject - ? "dropzone-reject" - : ""; - - return ( -
    - -
    -
    -
    - -   - {props.file ? ( - - {props.file.name} - {props.file.size} bytes - - ) : ( - Click here to upload - )} -
    -
    -
    -
    - ); -} - -export default function Upload() { +export function UploadView() { const auth = useContext(AuthContext); const config = useContext(ConfigContext); const fileUploadTimeout = config.config["file_upload_timeout"]; const navigate = useNavigate(); const searchParams = useSearchParams()[0]; - const [file, setFile] = useState(null); - const [shareWith, setShareWith] = useState("default"); - const [group, setGroup] = useState(""); - const [groups, setGroups] = useState([]); - const [parent, setParent] = useState(""); - const [attributes, setAttributes] = useState([]); - const [attributeModalOpen, setAttributeModalOpen] = useState(false); - const [share3rdParty, setShare3rdParty] = useState(true); + const [file, setFile] = useState(null); + const [shareWith, setShareWith] = useState("default"); + const [group, setGroup] = useState(""); + const [groups, setGroups] = useState([]); + const [parent, setParent] = useState(""); + const [attributes, setAttributes] = useState([]); + const [attributeModalOpen, setAttributeModalOpen] = + useState(false); + const [share3rdParty, setShare3rdParty] = useState(true); - const handleParentChange = (ev) => { + const handleParentChange = (ev: React.ChangeEvent) => { ev.preventDefault(); setParent(ev.target.value); }; @@ -84,7 +42,7 @@ export default function Upload() { setParent(""); }; - const updateSharingMode = (ev) => { + const updateSharingMode = (ev: React.ChangeEvent) => { setShareWith(ev.target.value); setGroup(""); }; @@ -110,11 +68,11 @@ export default function Upload() { const handleSubmit = async () => { try { let response = await api.uploadFile( - file, + file!, searchParams.get("parent") || parent, - sharingModeToUploadParam(), + sharingModeToUploadParam()!, attributes, - fileUploadTimeout, + fileUploadTimeout!, share3rdParty ); navigate("/file/" + response.data.sha256, { @@ -130,18 +88,18 @@ export default function Upload() { } }; - const onAttributeAdd = (key, value) => { + const onAttributeAdd = (key: string, value: string) => { for (let attr of attributes) if (attr.key === key && attr.value === value) { // that key, value was added yet setAttributeModalOpen(false); return; } - setAttributes([...attributes, { key, value }]); + setAttributes([...attributes, { key, value }] as Attribute[]); setAttributeModalOpen(false); }; - const onAttributeRemove = (idx) => { + const onAttributeRemove = (idx: number) => { setAttributes([ ...attributes.slice(0, idx), ...attributes.slice(idx + 1), @@ -189,8 +147,10 @@ export default function Upload() { style={{ fontSize: "medium" }} placeholder="(Optional) Type parent identifier..." value={searchParams.get("parent") || parent} - onChange={handleParentChange} - disabled={searchParams.get("parent")} + onChange={(e) => handleParentChange} + disabled={ + !isEmpty(searchParams.get("parent")) + } />
    (""); + const [password, setPassword] = useState(""); + const [providers, setProviders] = useState([]); + const [oAuthRegisterModalOpen, setOAuthRegisterModalOpen] = + useState(false); const colorsList = ["#3c5799", "#01a0f6", "#d03f30", "#b4878b", "#444444"]; const isOIDCEnabled = config.config["is_oidc_enabled"]; @@ -90,8 +93,8 @@ export default function UserLogin() {
    -

    Welcome to MWDB

    -
    Log in using mwdb credentials
    +

    Welcome to MWDB

    +
    Log in using mwdb credentials
    { ev.preventDefault(); @@ -147,19 +150,25 @@ export default function UserLogin() {
    - -
    -
    Log in using OAuth
    - {providers.length <= 5 ? ( - providers.map((provider, i) => ( - 0}> + <> +
    +
    Log in using OAuth
    + {providers.length <= 5 ? ( + providers.map((provider, i) => ( + + )) + ) : ( + - )) - ) : ( - - )} + )} +
    diff --git a/mwdb/web/src/components/UserPasswordRecover.jsx b/mwdb/web/src/components/Views/UserPasswordRecoverView.tsx similarity index 77% rename from mwdb/web/src/components/UserPasswordRecover.jsx rename to mwdb/web/src/components/Views/UserPasswordRecoverView.tsx index 23fddc92b..aadf91afd 100644 --- a/mwdb/web/src/components/UserPasswordRecover.jsx +++ b/mwdb/web/src/components/Views/UserPasswordRecoverView.tsx @@ -1,7 +1,7 @@ -import React, { useState, useContext, useRef } from "react"; +import { useState, useContext, useRef } from "react"; import ReCAPTCHA from "react-google-recaptcha"; import { toast } from "react-toastify"; -import { useForm } from "react-hook-form"; +import { UseFormProps, useForm } from "react-hook-form"; import { yupResolver } from "@hookform/resolvers/yup"; import * as Yup from "yup"; @@ -11,30 +11,28 @@ import { View, Label, FormError, LoadingSpinner } from "@mwdb-web/commons/ui"; import { getErrorMessage } from "@mwdb-web/commons/helpers"; import { useNavRedirect } from "@mwdb-web/commons/hooks"; -const formFields = { - login: "login", - email: "email", - recaptcha: "recaptcha", +type FormValues = { + login: string; + email: string; + recaptcha: string | null; }; -const validationSchema = Yup.object().shape({ - [formFields.login]: Yup.string().required("Login is required"), - [formFields.email]: Yup.string() +const validationSchema: Yup.SchemaOf = Yup.object().shape({ + login: Yup.string().required("Login is required"), + email: Yup.string() .required("Email is required") .matches(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g, "Value is not email"), - [formFields.recaptcha]: Yup.string() - .nullable() - .required("Recaptcha is required"), + recaptcha: Yup.string().nullable().required("Recaptcha is required"), }); -const formOptions = { +const formOptions: UseFormProps = { resolver: yupResolver(validationSchema), mode: "onSubmit", reValidateMode: "onSubmit", shouldFocusError: true, }; -export default function UserPasswordRecover() { +export function UserPasswordRecoverView() { const config = useContext(ConfigContext); const { redirectTo } = useNavRedirect(); const { @@ -43,18 +41,18 @@ export default function UserPasswordRecover() { formState: { errors }, reset, setValue, - } = useForm(formOptions); + } = useForm(formOptions); - const [loading, setLoading] = useState(false); - const captchaRef = useRef(null); + const [loading, setLoading] = useState(false); + const captchaRef = useRef(null); - async function recoverPassword(values) { + async function recoverPassword(values: FormValues) { try { setLoading(true); await api.authRecoverPassword( values.login, values.email, - values.recaptcha + values.recaptcha! ); toast("Password reset link has been sent to the e-mail address", { @@ -70,7 +68,7 @@ export default function UserPasswordRecover() { reset(); } finally { captchaRef.current?.reset(); - setValue(formFields.recaptcha, null); + setValue("recaptcha", null); } } @@ -85,11 +83,11 @@ export default function UserPasswordRecover() {