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 &&
+
+ } variant="outlined" sx={{ textTransform: 'none', borderRadius: '20px' }}
+ onClick={() => refresh()}>
+ {t('newData')}
+
+ }
- } variant="outlined" sx={{ textTransform: 'none', borderRadius: '20px'}}
- onClick={() => refresh()}>
- {t('newData')}
-
- }
-
- 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 (
+
+ );
+};
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 (
+ <>
+
+
+ >
+ );
+
+};
\ 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