diff --git a/ui/client/package-lock.json b/ui/client/package-lock.json index 6a730781e..c29e63250 100644 --- a/ui/client/package-lock.json +++ b/ui/client/package-lock.json @@ -20,6 +20,7 @@ "lucide-react": "^0.454.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-drag-drop-files": "^2.4.0", "react-i18next": "^15.0.2", "react-infinite-scroll-component": "^6.1.0", "react-json-pretty": "^2.2.0", @@ -1892,6 +1893,12 @@ "@types/react": "*" } }, + "node_modules/@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.8.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.0.tgz", @@ -2277,6 +2284,15 @@ "node": ">=6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001667", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz", @@ -2372,6 +2388,26 @@ "node": ">= 8" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -3379,7 +3415,6 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, "funding": [ { "type": "github", @@ -3564,6 +3599,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3640,6 +3681,20 @@ "react": "^18.3.1" } }, + "node_modules/react-drag-drop-files": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/react-drag-drop-files/-/react-drag-drop-files-2.4.0.tgz", + "integrity": "sha512-MGPV3HVVnwXEXq3gQfLtSU3jz5j5jrabvGedokpiSEMoONrDHgYl/NpIOlfsqGQ4zBv1bzzv7qbKURZNOX32PA==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2", + "styled-components": "^6.1.11" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/react-i18next": { "version": "15.0.2", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.2.tgz", @@ -3865,6 +3920,12 @@ "semver": "bin/semver.js" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3898,7 +3959,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3915,6 +3975,89 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/styled-components": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.13.tgz", + "integrity": "sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.38", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/styled-components/node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "license": "MIT" + }, + "node_modules/styled-components/node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", + "license": "MIT" + }, "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", @@ -3989,6 +4132,12 @@ "typescript": ">=4.2.0" } }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5342,6 +5491,11 @@ "@types/react": "*" } }, + "@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==" + }, "@typescript-eslint/eslint-plugin": { "version": "8.8.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.0.tgz", @@ -5576,6 +5730,11 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, + "camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==" + }, "caniuse-lite": { "version": "1.0.30001667", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz", @@ -5645,6 +5804,21 @@ "which": "^2.0.1" } }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" + }, + "css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -6380,8 +6554,7 @@ "nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" }, "natural-compare": { "version": "1.4.0", @@ -6495,6 +6668,11 @@ "source-map-js": "^1.2.1" } }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6547,6 +6725,15 @@ "scheduler": "^0.23.2" } }, + "react-drag-drop-files": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/react-drag-drop-files/-/react-drag-drop-files-2.4.0.tgz", + "integrity": "sha512-MGPV3HVVnwXEXq3gQfLtSU3jz5j5jrabvGedokpiSEMoONrDHgYl/NpIOlfsqGQ4zBv1bzzv7qbKURZNOX32PA==", + "requires": { + "prop-types": "^15.7.2", + "styled-components": "^6.1.11" + } + }, "react-i18next": { "version": "15.0.2", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.2.tgz", @@ -6691,6 +6878,11 @@ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6714,8 +6906,7 @@ "source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" }, "strip-json-comments": { "version": "3.1.1", @@ -6723,6 +6914,57 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "styled-components": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.13.tgz", + "integrity": "sha512-M0+N2xSnAtwcVAQeFEsGWFFxXDftHUD7XrKla06QbpUMmbmtFBMMTcKWvFXtWxuD5qQkB8iU5gk6QASlx2ZRMw==", + "requires": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.38", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "dependencies": { + "@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "requires": { + "@emotion/memoize": "^0.8.1" + } + }, + "@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "requires": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + } + }, + "stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" + } + } + }, "stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", @@ -6773,6 +7015,11 @@ "dev": true, "requires": {} }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/ui/client/package.json b/ui/client/package.json index 416cb4af0..1674e38f1 100644 --- a/ui/client/package.json +++ b/ui/client/package.json @@ -25,6 +25,7 @@ "lucide-react": "^0.454.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-drag-drop-files": "^2.4.0", "react-i18next": "^15.0.2", "react-infinite-scroll-component": "^6.1.0", "react-json-pretty": "^2.2.0", diff --git a/ui/client/src/App.tsx b/ui/client/src/App.tsx index 983276035..e257410d5 100644 --- a/ui/client/src/App.tsx +++ b/ui/client/src/App.tsx @@ -31,6 +31,7 @@ import { Registries } from "./views/Registries"; import { Submissions } from "./views/Submissions"; import { useEffect, useMemo, useState } from "react"; import { constants } from "./components/config"; +import { AppRoutes } from "./routes"; const queryClient = new QueryClient({ queryCache: new QueryCache({}), @@ -94,10 +95,10 @@ function App() {
- } /> - } />\ - } /> - } /> + } /> + } />\ + } /> + } /> diff --git a/ui/client/src/components/Header.tsx b/ui/client/src/components/Header.tsx index d2492c4de..691dd1f5a 100644 --- a/ui/client/src/components/Header.tsx +++ b/ui/client/src/components/Header.tsx @@ -14,31 +14,32 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { AppBar, Box, Button, Grid2, IconButton, Tab, Tabs, ToggleButton, ToggleButtonGroup, Toolbar, Tooltip, useMediaQuery, useTheme } from "@mui/material"; +import { AppBar, Box, Button, Grid2, IconButton, Tab, Tabs, Toolbar, useMediaQuery, useTheme } from "@mui/material"; import { useContext, useState } from "react"; import { useTranslation } from "react-i18next"; import { useLocation, useNavigate } from "react-router-dom"; -import Brightness4Icon from '@mui/icons-material/Brightness4'; import { ApplicationContext } from "../contexts/ApplicationContext"; -import PlayArrowIcon from '@mui/icons-material/PlayArrow'; -import PauseIcon from '@mui/icons-material/Pause'; import RefreshIcon from '@mui/icons-material/Refresh'; +import { SettingsMenu } from "../menus/Settings"; +import MenuIcon from '@mui/icons-material/Menu'; +import { AppRoutes } from "../routes"; export const Header: React.FC = () => { - const { colorMode, autoRefreshEnabled, setAutoRefreshEnabled, refreshRequired, refresh } = useContext(ApplicationContext); + const { refreshRequired, refresh } = useContext(ApplicationContext); const { t } = useTranslation(); const navigate = useNavigate(); const pathname = useLocation().pathname.toLowerCase(); const theme = useTheme(); const lessThanMedium = useMediaQuery(theme.breakpoints.down("md")); + const [anchorEl, setAnchorEl] = useState(null); const getTabFromPath = (path: string) => { - if (path.startsWith('/ui/indexer')) { + if (path.startsWith(AppRoutes.Indexer)) { return 0; - } else if (path.startsWith('/ui/submissions')) { + } else if (path.startsWith(AppRoutes.Submissions)) { return 1; - } else if (path.startsWith('/ui/registry')) { + } else if (path.startsWith(AppRoutes.Registry)) { return 2; } return 0; @@ -49,16 +50,9 @@ export const Header: React.FC = () => { const handleNavigation = (tab: number) => { setTab(tab); switch (tab) { - case 0: navigate('/ui/indexer'); break; - case 1: navigate('/ui/submissions'); break; - case 2: navigate('/ui/registry'); break; - } - }; - - const handleAutoRefreshChange = (value: 'play' | 'pause') => { - switch (value) { - case 'play': setAutoRefreshEnabled(true); break; - case 'pause': setAutoRefreshEnabled(false); break; + case 0: navigate(AppRoutes.Indexer); break; + case 1: navigate(AppRoutes.Submissions); break; + case 2: navigate(AppRoutes.Registry); break; } }; @@ -86,38 +80,16 @@ export const Header: React.FC = () => { {refreshRequired && + + + } - - } - - handleAutoRefreshChange(value)} value={autoRefreshEnabled ? 'play' : 'pause'}> - - - - - - - - - - - - - - - colorMode.toggleColorMode()}> - - - + setAnchorEl(event.currentTarget)}> + + @@ -129,6 +101,10 @@ export const Header: React.FC = () => { height: theme => lessThanMedium ? '190px' : theme.mixins.toolbar }} /> + ); diff --git a/ui/client/src/components/PaladinTransaction.tsx b/ui/client/src/components/PaladinTransaction.tsx index 3456e406b..5133b8c2b 100644 --- a/ui/client/src/components/PaladinTransaction.tsx +++ b/ui/client/src/components/PaladinTransaction.tsx @@ -27,6 +27,7 @@ import { EllapsedTime } from "./EllapsedTime"; import VisibilityIcon from '@mui/icons-material/VisibilityOutlined'; import { PaladinTransactionsDetailsDialog } from "../dialogs/TransactionDetails"; import { Captions, Tag } from 'lucide-react'; +import { formatJSONWhenApplicable } from "../utils"; daysjs.extend(relativeTime); @@ -44,14 +45,6 @@ export const PaladinTransaction: React.FC = ({ paladinTransaction }) => { return <>; } - const formatProperty = (value: any) => { - try { - const parsed = JSON.stringify(value); - return parsed.substring(1, parsed.length - 1); - } catch (err) { } - return value; - }; - return ( <> = ({ paladinTransaction }) => { .map((property) => ( ))} {Object.keys(paladinTransaction.data).length === 0 && diff --git a/ui/client/src/dialogs/ABIUpload.tsx b/ui/client/src/dialogs/ABIUpload.tsx new file mode 100644 index 000000000..004c3429d --- /dev/null +++ b/ui/client/src/dialogs/ABIUpload.tsx @@ -0,0 +1,203 @@ +// Copyright © 2024 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { + Alert, + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + FormControlLabel, + Grid2, + Radio, + RadioGroup, + TextField, + Typography +} from '@mui/material'; +import { FileUploader } from 'react-drag-drop-files'; +import { Dispatch, SetStateAction, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { UploadFile } from '@mui/icons-material'; +import { useMutation } from '@tanstack/react-query'; +import { uploadABI } from '../queries/storeABI'; + +type Props = { + dialogOpen: boolean + setDialogOpen: Dispatch> +} + +export const ABIUploadDialog: React.FC = ({ + dialogOpen, + setDialogOpen +}) => { + + const { t } = useTranslation(); + const [errorMessage, setErrorMessage] = useState(); + const [fileSelected, setFileSelected] = useState(null); + const [radioSelection, setRadioSelection] = useState<'file' | 'text'>('file'); + const [abiText, setAbiText] = useState(''); + const [abiUploadCount, setAbiUploadCount] = useState(0); + + const { mutate, data, reset } = useMutation({ + mutationFn: (value: Object) => uploadABI(value) + }); + + useEffect(() => { + if (dialogOpen) { + reset(); + setRadioSelection('file'); + setFileSelected(null); + setAbiText(''); + } + }, [dialogOpen]); + + const handleSubmit = async () => { + setErrorMessage(undefined); + reset(); + let valueToParse: string; + let parsedValue: Object; + if (radioSelection === 'file' && fileSelected !== null) { + valueToParse = await fileSelected.text(); + } else { + valueToParse = abiText; + } + try { + parsedValue = JSON.parse(valueToParse); + mutate(parsedValue); + setAbiUploadCount(abiUploadCount + 1); + } catch (err) { + if (err !== undefined) { + setErrorMessage(t('invalidABI')); + return; + } + } + }; + + const canSubmit = radioSelection === 'file' && fileSelected !== null + || radioSelection === 'text' && abiText.length > 0; + + return ( + setDialogOpen(false)} + > +
{ + event.preventDefault(); + handleSubmit(); + }}> + + {t('uploadABI')} + + {errorMessage !== undefined && + + {errorMessage} + + } + {data !== undefined && + + {t('abiHash', { hash: data })} + + } + + + + + setRadioSelection(event.target.value as 'file' || 'text')} + > + + + } label={t('uploadFile')} /> + + + { + setFileSelected(file); + }} + hoverTitle={t('dropFileHere')} + children={ + + + + {fileSelected === null + ? t('uploadABIFileDescription') + : t('abiFileSelected', { fileName: fileSelected.name })} + + + } + types={['abi']} + /> + + + + + + } label={t('pasteABI')} /> + + + setAbiText(event.target.value)} + /> + + + + + + + + +
+
+ ); +}; diff --git a/ui/client/src/interfaces.ts b/ui/client/src/interfaces.ts index 4a679cec1..c05130834 100644 --- a/ui/client/src/interfaces.ts +++ b/ui/client/src/interfaces.ts @@ -123,4 +123,6 @@ export interface IABIDecodedEntry { definition: IABIEntry; signature: string; summary?: string; // errors only -} \ No newline at end of file +} + +export type ABIUploadResponse = string \ No newline at end of file diff --git a/ui/client/src/menus/Settings.tsx b/ui/client/src/menus/Settings.tsx new file mode 100644 index 000000000..9c0f709e8 --- /dev/null +++ b/ui/client/src/menus/Settings.tsx @@ -0,0 +1,107 @@ +// Copyright © 2024 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Box, Button, Grid2, Menu, ToggleButton, ToggleButtonGroup, Typography, useTheme } from "@mui/material"; +import { t } from "i18next"; +import { useContext, useState } from "react"; +import { ApplicationContext } from "../contexts/ApplicationContext"; +import PlayArrowIcon from '@mui/icons-material/PlayArrow'; +import PauseIcon from '@mui/icons-material/Pause'; +import LightModeIcon from '@mui/icons-material/LightMode'; +import DarkModeIcon from '@mui/icons-material/DarkMode'; +import { ABIUploadDialog } from "../dialogs/ABIUpload"; + +export type Props = { + anchorEl: HTMLElement | null; + setAnchorEl: React.Dispatch> +} + +export const SettingsMenu: React.FC = ({ + anchorEl, + setAnchorEl +}) => { + + const { colorMode, autoRefreshEnabled, setAutoRefreshEnabled } = useContext(ApplicationContext); + const [abiUploadDialogOpen, setAbiUploadDialogOpen] = useState(false); + + const theme = useTheme(); + + const handleAutoRefreshChange = (value: 'play' | 'pause' | null) => { + switch (value) { + case 'play': setAutoRefreshEnabled(true); break; + case 'pause': setAutoRefreshEnabled(false); break; + } + }; + + const handleColorChange = (mode: 'light' | 'dark' | null) => { + if (mode !== null && mode !== theme.palette.mode) { + colorMode.toggleColorMode(); + } + }; + + return ( + <> + setAnchorEl(null)} + > + + + + {t('autoRefreshValue', { value: t(autoRefreshEnabled ? 'on' : 'off') })} + handleAutoRefreshChange(value)} value={autoRefreshEnabled ? 'play' : 'pause'}> + + + + + + + + + + {t('themeValue', { value: t(theme.palette.mode === 'light' ? 'light' : 'dark') })} + handleColorChange(value)} value={theme.palette.mode}> + + + + + + + + + + {t('contractAbis')} + + + + + + + + ); + +}; \ No newline at end of file diff --git a/ui/client/src/queries/rpcMethods.ts b/ui/client/src/queries/rpcMethods.ts index 226e97ed6..bdc7c0ee2 100644 --- a/ui/client/src/queries/rpcMethods.ts +++ b/ui/client/src/queries/rpcMethods.ts @@ -28,6 +28,7 @@ export const RpcMethods = { ptx_getDomainReceipt: "ptx_getDomainReceipt", ptx_decodeCall: "ptx_decodeCall", ptx_decodeEvent: "ptx_decodeEvent", + ptx_storeABI: "ptx_storeABI", reg_QueryEntriesWithProps: "reg_queryEntriesWithProps", reg_Registries: "reg_registries", }; diff --git a/ui/client/src/queries/storeABI.ts b/ui/client/src/queries/storeABI.ts new file mode 100644 index 000000000..7bdef0869 --- /dev/null +++ b/ui/client/src/queries/storeABI.ts @@ -0,0 +1,37 @@ +// Copyright © 2024 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { ABIUploadResponse } from "../interfaces"; +import { generatePostReq, returnResponse } from "./common"; +import { RpcEndpoint, RpcMethods } from "./rpcMethods"; + +export const uploadABI = async ( + abi: Object +): Promise => { + const payload = { + jsonrpc: "2.0", + id: Date.now(), + method: RpcMethods.ptx_storeABI, + params: [abi], + }; + + return >( + returnResponse( + () => fetch(RpcEndpoint, generatePostReq(JSON.stringify(payload))), "", [500] + ) + ); + +}; diff --git a/ui/client/src/routes.ts b/ui/client/src/routes.ts new file mode 100644 index 000000000..6389add80 --- /dev/null +++ b/ui/client/src/routes.ts @@ -0,0 +1,21 @@ +// Copyright © 2024 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export const AppRoutes = { + Indexer: '/ui/indexer', + Submissions: '/ui/submissions', + Registry: '/ui/registry' +} \ No newline at end of file diff --git a/ui/client/src/translations/en.json b/ui/client/src/translations/en.json index af41c06c1..52c5308f0 100644 --- a/ui/client/src/translations/en.json +++ b/ui/client/src/translations/en.json @@ -1,17 +1,25 @@ { "ISO": "ISO", + "abiFileSelected": "File {{fileName}} selected", + "abiHash": "ABI Hash: {{hash}}", + "implementation": "Implementation", + "invalidABI": "Invalid ABI", "active": "Active", "all": "All", "atomic": "Atomic", "atomicNumber": "Atomic ({{number}})", + "autoRefreshValue": "Auto-refresh: {{value}}", "autoRefreshOff": "Auto-refresh off", "autoRefreshOn": "Auto-refresh on", "block": "Block", + "cancel": "Cancel", "close": "Close", "contract": "Contract", + "contractAbis": "Contract ABIs", "copied": "Copied!", "copyToClipboard": "Copy to Clipboard", "created": "Created", + "dark": "Dark", "debug": "Debug", "decodedEvent": "Decoded event:", "decodedFunction": "Decoded function:", @@ -19,6 +27,8 @@ "dismiss": "Dismiss", "domain": "Domain", "domainReceipt": "Domain Receipt", + "dropFileHere": "Drop file here", + "endorsementType": "Endorsement Type", "entries": "Entries", "epoch": "Epoch", "errorConnectingToPaladinNode": "Error connecting to Paladin node", @@ -38,11 +48,16 @@ "evmPrivateLog": "EVM Private Log {{logIndex}}", "evmPrivateReceipt": "EVM Private Receipt", "evmPrivateTransaction": "EVM Private Transaction", + "evmVersion": "EVM Version", + "externalCallsEnabled": "External Calls Enabled", "from": "From", + "group": "Group", "hash": "Hash", "hideProperties": "Hide Properties", "id": "ID", "indexer": "Indexer", + "identityHash": "Identity Hash", + "light": "Light", "live": "Live", "localTime": "Local Time", "logIndex": "Log Index", @@ -54,8 +69,12 @@ "numberMilliseconds": "{{number}} (milliseconds)", "numberNanoseconds": "{{number}} (nanoseconds)", "numberSeconds": "{{number}} (seconds)", + "off": "Off", + "on": "On", "owner": "Owner", "paladin": "Paladin", + "parentIdentityHash": "Parent Identity Hash", + "pasteABI": "Paste ABI", "paused": "Paused", "pending": "Pending", "pendingTransactions": "Pending Transactions", @@ -72,6 +91,11 @@ "submissions": "Submissions", "success": "Success", "switchThemeMode": "Switch theme mode", + "upload": "Upload", + "uploadFile": "Upload File", + "uploadABI": "Upload ABI", + "uploadABIFileDescription": "Select or drop ABI file", + "themeValue": "Theme: {{value}}", "timestamp": "Timestamp", "to": "To", "topic": "Topic", @@ -81,5 +105,6 @@ "transactions": "Transactions", "transactionsAndEvents": "Transactions & Events", "type": "Type", + "value": "Value", "viewDetails": "View Details" } \ No newline at end of file diff --git a/ui/client/src/utils.ts b/ui/client/src/utils.ts new file mode 100644 index 000000000..56ef13f2d --- /dev/null +++ b/ui/client/src/utils.ts @@ -0,0 +1,24 @@ +// Copyright © 2024 Kaleido, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export const formatJSONWhenApplicable = (value: any) => { + if (typeof value === 'object') { + try { + return JSON.stringify(value, null, 2); + } catch (err) { } + } + return value; +}; \ No newline at end of file diff --git a/ui/client/tsconfig.app.tsbuildinfo b/ui/client/tsconfig.app.tsbuildinfo index e9a00eb23..cba796730 100644 --- a/ui/client/tsconfig.app.tsbuildinfo +++ b/ui/client/tsconfig.app.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/interfaces.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/evmprivatedetails.tsx","./src/components/ellapsedtime.tsx","./src/components/event.tsx","./src/components/events.tsx","./src/components/hash.tsx","./src/components/header.tsx","./src/components/jsonbox.tsx","./src/components/paladintransaction.tsx","./src/components/registry.tsx","./src/components/registryentry.tsx","./src/components/singlevalue.tsx","./src/components/transaction.tsx","./src/components/transactiondetails.tsx","./src/components/transactions.tsx","./src/components/config.ts","./src/contexts/applicationcontext.tsx","./src/dialogs/error.tsx","./src/dialogs/hash.tsx","./src/dialogs/timestamp.tsx","./src/dialogs/transactiondetails.tsx","./src/dialogs/transactionreceiptdetails.tsx","./src/dialogs/viewdetails.tsx","./src/queries/abidecode.ts","./src/queries/blocks.ts","./src/queries/common.ts","./src/queries/domains.ts","./src/queries/events.ts","./src/queries/registry.ts","./src/queries/rpcmethods.ts","./src/queries/states.ts","./src/queries/transactions.ts","./src/themes/default.ts","./src/views/registries.tsx","./src/views/submissions.tsx","./src/views/indexer.tsx"],"version":"5.6.2"} \ No newline at end of file +{"root":["./src/app.tsx","./src/interfaces.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/evmprivatedetails.tsx","./src/components/ellapsedtime.tsx","./src/components/event.tsx","./src/components/events.tsx","./src/components/hash.tsx","./src/components/header.tsx","./src/components/jsonbox.tsx","./src/components/paladintransaction.tsx","./src/components/registry.tsx","./src/components/registryentry.tsx","./src/components/singlevalue.tsx","./src/components/transaction.tsx","./src/components/transactiondetails.tsx","./src/components/transactions.tsx","./src/components/config.ts","./src/contexts/applicationcontext.tsx","./src/dialogs/abiupload.tsx","./src/dialogs/error.tsx","./src/dialogs/hash.tsx","./src/dialogs/timestamp.tsx","./src/dialogs/transactiondetails.tsx","./src/dialogs/transactionreceiptdetails.tsx","./src/dialogs/viewdetails.tsx","./src/menus/settings.tsx","./src/queries/abidecode.ts","./src/queries/blocks.ts","./src/queries/common.ts","./src/queries/domains.ts","./src/queries/events.ts","./src/queries/registry.ts","./src/queries/rpcmethods.ts","./src/queries/states.ts","./src/queries/storeabi.ts","./src/queries/transactions.ts","./src/themes/default.ts","./src/views/registries.tsx","./src/views/submissions.tsx","./src/views/indexer.tsx"],"version":"5.6.2"} \ No newline at end of file