From 664b1e66425cda19ab405b674b467a0699f32321 Mon Sep 17 00:00:00 2001 From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> Date: Sun, 2 Feb 2025 13:17:24 +0100 Subject: [PATCH] First template of Pf6 --- README.md | 6 + server/ui/package-lock.json | 408 +++++++++++++++++- server/ui/package.json | 10 +- server/ui/src/App.css | 42 -- server/ui/src/App.tsx | 35 -- server/ui/src/app/App.css | 22 + server/ui/src/app/App.tsx | 24 ++ server/ui/src/app/Constants.ts | 0 server/ui/src/app/api/models.ts | 42 ++ server/ui/src/app/assets/avatar.svg | 18 + .../ui/src/app/assets/patternfly_avatar.jpg | Bin 0 -> 34826 bytes server/ui/src/app/assets/pfbg-icon.svg | 1 + server/ui/src/app/common/types.ts | 9 + .../ui/src/app/components/Notifications.tsx | 36 ++ .../app/components/NotificationsContext.tsx | 66 +++ .../src/app/components/PageDrawerContext.tsx | 199 +++++++++ server/ui/src/app/layout/about.tsx | 58 +++ server/ui/src/app/layout/default-layout.tsx | 34 ++ server/ui/src/app/layout/header.tsx | 250 +++++++++++ server/ui/src/app/layout/index.ts | 1 + server/ui/src/app/layout/sidebar.tsx | 109 +++++ server/ui/src/app/pages/home/home.tsx | 9 + server/ui/src/app/pages/home/index.ts | 1 + server/ui/src/assets/react.svg | 1 - server/ui/src/index.css | 68 --- server/ui/src/main.tsx | 22 +- server/ui/tsconfig.app.json | 6 +- server/ui/vite.config.ts | 5 + 28 files changed, 1324 insertions(+), 158 deletions(-) delete mode 100644 server/ui/src/App.css delete mode 100644 server/ui/src/App.tsx create mode 100644 server/ui/src/app/App.css create mode 100644 server/ui/src/app/App.tsx create mode 100644 server/ui/src/app/Constants.ts create mode 100644 server/ui/src/app/api/models.ts create mode 100644 server/ui/src/app/assets/avatar.svg create mode 100644 server/ui/src/app/assets/patternfly_avatar.jpg create mode 100644 server/ui/src/app/assets/pfbg-icon.svg create mode 100644 server/ui/src/app/common/types.ts create mode 100644 server/ui/src/app/components/Notifications.tsx create mode 100644 server/ui/src/app/components/NotificationsContext.tsx create mode 100644 server/ui/src/app/components/PageDrawerContext.tsx create mode 100644 server/ui/src/app/layout/about.tsx create mode 100644 server/ui/src/app/layout/default-layout.tsx create mode 100644 server/ui/src/app/layout/header.tsx create mode 100644 server/ui/src/app/layout/index.ts create mode 100644 server/ui/src/app/layout/sidebar.tsx create mode 100644 server/ui/src/app/pages/home/home.tsx create mode 100644 server/ui/src/app/pages/home/index.ts delete mode 100644 server/ui/src/assets/react.svg delete mode 100644 server/ui/src/index.css diff --git a/README.md b/README.md index b8715e7d..3a99868d 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,12 @@ XMLs basados en UBL y SUNAT cargo run --bin server ``` +## Server UI + +```shell +npm run dev --prefix server/ui +``` + ## License - [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) diff --git a/server/ui/package-lock.json b/server/ui/package-lock.json index 47bfe9dd..f23a5cb5 100644 --- a/server/ui/package-lock.json +++ b/server/ui/package-lock.json @@ -8,6 +8,14 @@ "name": "openubl-ui", "version": "0.0.0", "dependencies": { + "@patternfly/patternfly": "^6.1.0", + "@patternfly/react-core": "^6.1.0", + "@patternfly/react-table": "^6.1.0", + "@patternfly/react-tokens": "^6.1.0", + "@tanstack/react-query": "^5.66.0", + "@tanstack/react-query-devtools": "^5.66.0", + "@tanstack/react-router": "^1.99.0", + "axios": "^1.7.9", "react": "^18.3.1", "react-dom": "^18.3.1" }, @@ -696,6 +704,70 @@ "node": ">= 8" } }, + "node_modules/@patternfly/patternfly": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-6.1.0.tgz", + "integrity": "sha512-w+QazL8NHKkg5j01eotblsswKxQQSYB0CN3yBXQL9ScpHdp/fK8M6TqWbKZNRpf+NqhMxcH/om8eR0N/fDCJqw==", + "license": "MIT" + }, + "node_modules/@patternfly/react-core": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-6.1.0.tgz", + "integrity": "sha512-zj0lJPZxQanXKD8ae2kYnweT0kpp1CzpHYAkaBjTrw2k6ZMfr/UPlp0/ugCjWEokBqh79RUADLkKJJPce/yoSQ==", + "license": "MIT", + "dependencies": { + "@patternfly/react-icons": "^6.1.0", + "@patternfly/react-styles": "^6.1.0", + "@patternfly/react-tokens": "^6.1.0", + "focus-trap": "7.6.2", + "react-dropzone": "^14.3.5", + "tslib": "^2.8.1" + }, + "peerDependencies": { + "react": "^17 || ^18", + "react-dom": "^17 || ^18" + } + }, + "node_modules/@patternfly/react-icons": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-6.1.0.tgz", + "integrity": "sha512-V1w/j19YmOgvh72IRRf1p07k+u4M5+9P+o/IxunlF0fWzLDX4Hf+utBI11A8cRfUzpQN7eLw/vZIS3BLM8Ge3Q==", + "license": "MIT", + "peerDependencies": { + "react": "^17 || ^18", + "react-dom": "^17 || ^18" + } + }, + "node_modules/@patternfly/react-styles": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-6.1.0.tgz", + "integrity": "sha512-JQ3zIl5SFiSB0YWVYibcUwgZdsp6Wn8hkfZ7KhtCjHFccSDdJexPOXVV1O9f2h4PfxTlY3YntZ81ZsguBx/Q7A==", + "license": "MIT" + }, + "node_modules/@patternfly/react-table": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-6.1.0.tgz", + "integrity": "sha512-eC8mKkvFR0btfv6yEOvE+J4gBXU8ZGe9i2RSezBM+MJaXEQt/CKRjV+SAB5EeE3PyBYKG8yYDdsOoNmaPxxvSA==", + "license": "MIT", + "dependencies": { + "@patternfly/react-core": "^6.1.0", + "@patternfly/react-icons": "^6.1.0", + "@patternfly/react-styles": "^6.1.0", + "@patternfly/react-tokens": "^6.1.0", + "lodash": "^4.17.21", + "tslib": "^2.8.1" + }, + "peerDependencies": { + "react": "^17 || ^18", + "react-dom": "^17 || ^18" + } + }, + "node_modules/@patternfly/react-tokens": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-6.1.0.tgz", + "integrity": "sha512-t1UcHbOa4txczTR5UlnG4XcAAdnDSfSlCaOddw/HTqRF59pn2ks2JUu9sfnFRZ8SiAAxKRiYdX5bT7Mf4R24+w==", + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.34.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.0.tgz", @@ -1188,6 +1260,142 @@ "@swc/counter": "^0.1.3" } }, + "node_modules/@tanstack/history": { + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.99.0.tgz", + "integrity": "sha512-MQS1Lg8D+1vpasEJKf4zs1sxhxbXcoejmVCZDbo0bq2wq+xVK+kRixj5Pae2kb2APzdXYga4u236GBbgCKTcnQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.66.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.66.0.tgz", + "integrity": "sha512-J+JeBtthiKxrpzUu7rfIPDzhscXF2p5zE/hVdrqkACBP8Yu0M96mwJ5m/8cPPYQE9aRNvXztXHlNwIh4FEeMZw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.65.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.65.0.tgz", + "integrity": "sha512-g5y7zc07U9D3esMdqUfTEVu9kMHoIaVBsD0+M3LPdAdD710RpTcLiNvJY1JkYXqkq9+NV+CQoemVNpQPBXVsJg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.66.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.66.0.tgz", + "integrity": "sha512-z3sYixFQJe8hndFnXgWu7C79ctL+pI0KAelYyW+khaNJ1m22lWrhJU2QrsTcRKMuVPtoZvfBYrTStIdKo+x0Xw==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.66.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.66.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.66.0.tgz", + "integrity": "sha512-uB57wA2YZaQ2fPcFW0E9O1zAGDGSbRKRx84uMk/86VyU9jWVxvJ3Uzp+zNm+nZJYsuekCIo2opTdgNuvM3cKgA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-devtools": "5.65.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.66.0", + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-router": { + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.99.0.tgz", + "integrity": "sha512-MwKQxckJMdxpYwpZsItM1SCodcAKu1bqWha2s2d5/Iy1F/9XzqT8gZXpZW5fvC7+21YOuwMxvJmwbSPkpVeWRw==", + "license": "MIT", + "dependencies": { + "@tanstack/history": "1.99.0", + "@tanstack/react-store": "^0.7.0", + "@tanstack/router-core": "^1.99.0", + "jsesc": "^3.1.0", + "tiny-invariant": "^1.3.3", + "tiny-warning": "^1.0.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=18.0.0 || >=19.0.0", + "react-dom": ">=18.0.0 || >=19.0.0" + } + }, + "node_modules/@tanstack/react-store": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.7.0.tgz", + "integrity": "sha512-S/Rq17HaGOk+tQHV/yrePMnG1xbsKZIl/VsNWnNXt4XW+tTY8dTlvpJH2ZQ3GRALsusG5K6Q3unAGJ2pd9W/Ng==", + "license": "MIT", + "dependencies": { + "@tanstack/store": "0.7.0", + "use-sync-external-store": "^1.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/router-core": { + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.99.0.tgz", + "integrity": "sha512-/9HMagUilxdjHNfgZaRSjyMvuJ+BL8/IXLTN9NtbXOJZvB2q4Vu0GIWZJfwMiz9czXqdQ/NxtPxeG/b+YTnUOg==", + "license": "MIT", + "dependencies": { + "@tanstack/history": "1.99.0", + "@tanstack/store": "^0.7.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/store": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.7.0.tgz", + "integrity": "sha512-CNIhdoUsmD2NolYuaIs8VfWM467RK6oIBAW4nPEKZhg1smZ+/CwtCdpURgp7nxSqOaV9oKkzdWD80+bC66F/Jg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1658,6 +1866,21 @@ "node": ">= 0.4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/attr-accept": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", + "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -1674,6 +1897,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1802,6 +2036,18 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1946,6 +2192,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -2493,6 +2748,18 @@ "node": ">=16.0.0" } }, + "node_modules/file-selector": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz", + "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==", + "license": "MIT", + "dependencies": { + "tslib": "^2.7.0" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2544,6 +2811,35 @@ "dev": true, "license": "ISC" }, + "node_modules/focus-trap": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.2.tgz", + "integrity": "sha512-9FhUxK1hVju2+AiQIDJ5Dd//9R2n2RAfJ0qfhF4IHGHgcoEUTMpbTeG/zbEuwaiYXfuAH6XE0/aCyxDdRM+W5w==", + "license": "MIT", + "dependencies": { + "tabbable": "^6.2.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.4.tgz", @@ -2560,6 +2856,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3311,6 +3621,18 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -3388,6 +3710,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3441,6 +3769,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3491,7 +3840,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3775,7 +4123,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -3783,6 +4130,12 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3839,11 +4192,27 @@ "react": "^18.3.1" } }, + "node_modules/react-dropzone": { + "version": "14.3.5", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.5.tgz", + "integrity": "sha512-9nDUaEEpqZLOz5v5SUcFA0CjM4vq8YbqO0WRls+EYT7+DvxUdzDPKNCPLqGfj3YL9MsniCLCD4RFA6M95V6KMQ==", + "license": "MIT", + "dependencies": { + "attr-accept": "^2.2.4", + "file-selector": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/reflect.getprototypeof": { @@ -4364,6 +4733,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4390,6 +4777,12 @@ "typescript": ">=4.8.4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4547,6 +4940,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vite": { "version": "6.0.11", "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz", diff --git a/server/ui/package.json b/server/ui/package.json index 3adc9562..3fcbfaf5 100644 --- a/server/ui/package.json +++ b/server/ui/package.json @@ -4,12 +4,20 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --host", "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview" }, "dependencies": { + "@patternfly/patternfly": "^6.1.0", + "@patternfly/react-core": "^6.1.0", + "@patternfly/react-table": "^6.1.0", + "@patternfly/react-tokens": "^6.1.0", + "@tanstack/react-query": "^5.66.0", + "@tanstack/react-query-devtools": "^5.66.0", + "@tanstack/react-router": "^1.99.0", + "axios": "^1.7.9", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/server/ui/src/App.css b/server/ui/src/App.css deleted file mode 100644 index b9d355df..00000000 --- a/server/ui/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/server/ui/src/App.tsx b/server/ui/src/App.tsx deleted file mode 100644 index 3ed854c7..00000000 --- a/server/ui/src/App.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' -import './App.css' - -function App() { - const [count, setCount] = useState(0) - - return ( - <> -
-
- Edit src/App.tsx
and save to test HMR
-
- Click on the Vite and React logos to learn more -
- > - ) -} - -export default App diff --git a/server/ui/src/app/App.css b/server/ui/src/app/App.css new file mode 100644 index 00000000..aa6a34de --- /dev/null +++ b/server/ui/src/app/App.css @@ -0,0 +1,22 @@ +.pf-c-select__toggle:before { + border-top: var(--pf-c-select__toggle--before--BorderTopWidth) solid + var(--pf-c-select__toggle--before--BorderTopColor) !important; + border-right: var(--pf-c-select__toggle--before--BorderRightWidth) solid + var(--pf-c-select__toggle--before--BorderRightColor) !important; + border-bottom: var(--pf-c-select__toggle--before--BorderBottomWidth) solid + var(--pf-c-select__toggle--before--BorderBottomColor) !important; + border-left: var(--pf-c-select__toggle--before--BorderLeftWidth) solid + var(--pf-c-select__toggle--before--BorderLeftColor) !important; +} + +table.vertical-middle-aligned-table tr td { + vertical-align: middle; +} + +.multiple-file-upload-status-item-force-blue .pf-v6-c-progress__indicator { + background-color: #0066cc !important; +} + +.multiple-file-upload-status-item-force-blue .pf-v6-c-progress__status-icon { + color: #0066cc !important; +} diff --git a/server/ui/src/app/App.tsx b/server/ui/src/app/App.tsx new file mode 100644 index 00000000..876295b9 --- /dev/null +++ b/server/ui/src/app/App.tsx @@ -0,0 +1,24 @@ +import "./App.css"; +import React from "react"; + +import { DefaultLayout } from "./layout"; +// import { AppRoutes } from "./Routes"; +import { NotificationsProvider } from "./components/NotificationsContext"; + +import "@patternfly/patternfly/patternfly.css"; +import "@patternfly/patternfly/patternfly-addons.css"; + +const App: React.FC = () => { + return ( + //fCJ!X zAl(NzA57L)OM5KxZXak8F#?PN06?kL!hnc%5O9t-UZ?4YlhYmo3=bHCyO-a9BLxTd zhdkBW z%kyo)jV3DG>)=Sqs30SaS#vZk4J9=@g^0huVwwV^-Zj2 wD>XN5CwdL4vfE8~&(mVOQo9O+y6TB@Gia5pwRHsNC&f#D_Fix@bSrpL z@_^a}q60_y*LAog#bhc_*nyYx3kLWylZ2TG5efy8PLy`##Q4h>tokq+UgNqzb*f@z z%NwagU$F?QqeB=LLf;~`eBQk*9MfktQ@;|k76h4h0TOFxO?G{wfF(EA*2kez0l)FZ z*fn<#M_`8qPdWdQ;XV>r@Rveo; v6>)nZzg;@uQ7Uz*LBvWR6R6yGlq@ z&Mf%7u(G3A=pw1I&kx*G_t)ReD0NMOWE*vE1S;y+SlsE64q1Szd|~uGDnzJ1em#FN zhk{7Cv!ON{DC(}Kx9sVWGwg%}yHu%lPW#`UZFJnm_4Sn&-WHlRuQ7u;pfAT$4J|A? zzlW)u91Wie_A7+OxLcPzSbTT1#q{rW9_?)Iprwe9;df_YPFHYypvxn>nu`m?^guo; z#9zEEFR@H;RnW-T|D=IF`!f&=YqTTaS+af<*jN1e05%lEblK~3Pg@@a_?a0Q+%{)_ z4R9{<72xalXO+x7F=l7Nvo@921pufh^=+*Y(E1U8V|8ZhnHi4rK%(Q)Fg#j|888F! zf{|12*lZ@&|7P-VzSTe`9qX(&wB^UyDzD55 zW-l?TIV1aizb#rOp5F2sneKGgpmJPN912zL)__WFp-=xh_r1%iq5WDS zDpt>rP~dpT4*&Nyf W;2R0!(>#?26WAhH8CK*z78HjS$&kQT~^L_XQL@u2*dj z6jVd^Pt}?RB8k{Rkf`Rjzv(_6{d~>hGs$Gr21Rkv#Nr6Ar7g|klP9cG2)oE2tE%3m zUwf5eFt}$sEh$cvn@#zUw?t?l&L)FbVAR;fyN603DKe_#YbCe*`9*gKtcPNz&tf+q zGkYL`<;okJ@l(B7=II>Ho0;D6JK^0an1tO+3N>1Kokb?zO8p+DHNI%mR*%5M+)qmg ze=mh~wZ*9kS3fH8fzK`#{~UQ=1;6>>Z(>0piacyrvSAxFG-yAcjzW(rSu~mL^i=1K znKUhCY! &c6b@=Y2ON_xKY4=>go7)VFm4_l++BS-LxbZU&$mK;HxYKJip7Gm!0> z#U@N+(V7b19Po)AP`7b?k~#|k3YB01!xv_EB_;yUnX5DpW4rd|uo34DDE7wYRz*sg zN1tSeL1bl%SX!I|VbaDGnl;i_ixK9dvuE`K&NIegz|MDFvpp8_KE1F7It|J18c=PZ z#8_9tS}vog=>nG5GRWE$m6_KHl(@QOCSTu67~++8&|phe4> eY(ivdbGqnr=VFvjU zX4AB#i~%6i>7?SM+{yiJ)Ky!j^HTa&MU~>wm3fgWE?mXeVaGWE{+oOcQK`4^)6Y$j z2JCmQ*XLFM!T-_t4wrHcwg5W~njn0nMiY}?KE2L2;?gF#%P)SVcUqs|p|^q9hbyV? zw`Vp1xUwtv`9BDibyf#B>Dr2x+Ls}Faf`RclNEyHWV&)GTle-#-Iwy9SO=jtGp-W6 zx(DP{JzaULF208@dg|1}VwmRJ& wfdX>VxLQlt;09^J3Xcvou>WV2iv|p$B1gS zf2DcXvKefeBruBg8FIz &KmtI z01g5U@z>kt(e*eWfyD$_!uf5Z!KVQqfvIGfsVqUJWAXSs8#AZes6GPdQZFF )#t>*Fp+=yFjskdLV{kc}T38_uQKa87M2 zsv9htZZukWDaRv|cij7U)=gc#Zx^4riwR998JrbaWV3K;9^KTKwFMBS<{6;Q4Vyh1 zA5XQREZE6O=N1=w!Q~h!qAV>OIIEF%sh+Zsid-sz<_94V3neei&n}`7Cc#Hu@(5Xm z=U%fpC!qBtd0sJ6nODAV`(n;^cBYRaInX$tMLRHzo2>A8-PSm)O(w;;7QZ25CR@(1jT!+p0D5aUw&LV1p*-RMts|k#PBs>QMK6YC=v>8} zPD-0v%V(%M=h-hp{K&%#?$17Idh0JD%t>}e5?z9Z!RFTox!!%AssfVqEPRg@aTS(K z-p}#6@r=p*cf9SA=v$GlvJ-#^vBriwtYQ&tDQGvb>&)Qt5p6I4rPZshy04OTrf>XE z$wL27$so|0>gQ5}O+A!?6L;}2)ZqHYCLyq$N6uI4yH7UMUAtE*9=Da)vsfQERK2nX zDmIzEcg`+tHXt6kr8vml(oLrOJ)SzxE8~9vyJGVQ_h7t-fYvsFI-ISJD6w*xSSsvQ zJ?VcZM+G)esBNNSV1|?Ak!oL2%^x%LKLzl1g@Z+7U{_d|Bf&}CZurCj2ZQIJzpuai zg@ym-FI;_UXY|mBY{&$7fczPY-~kCBA =vxs$+{D%Oi8UZSLi0Cx%{TAzBq@c| R=jl;ab*O?2OuSkjrl`v(1avn)O&;)x}K- z@B;|d^(W;gftnLrAVA?$tIt$Ce}@%F2!Ic1Phti_k*a_j99`uN%h3Dgw}PJg>xK^u z^JwL-0I=f03XTblYnJ_kVvu6c_v9>cXe)veu%#Ctq~C$Jjq6`)!6ZiTOU{sd(m$A| z;oq9JWVQva0nymij*&7#Nd$kud+Knxatt ~jCGs5v0z4mJS=TK2e-YT% zqyP*Dl)vyFX2N+)5CW(s=oF8V73Vm{!;IcVDCs4*sG__0GE5vDN^1COH%01`mnK^5 z#x1N7mh9YYlC!U*$QR2kHLk^M=F!f^rjBlE-{niNkE15)(|cj% zsH`lWBj4@j7a}gzYhaPJp3e$S(PJx>^Xfh8!0s8QlGwdjYK5KWmGScr*CjCoFp(c8 zNRbgRYy|=ram;?&XoC@R@v $0&VFEfF%VM3D?u9MuL_nf34Dt?+Fv@dbE5UbABcG_~)#C6V zh%$tJB0sCI=S-HFCxP`?SXj6tc}_re2Q*vNf9K%6VPcb7DUJU&^9@G$&Gv2*h&Y$`^Ne_CVyd z*NPv}3e^@Kpd#G!zla824=oID*F@0=PA_zXj_JAwK7F`xA3eThXr6Dc{WL|<(}i{C zED?vXPN=fHLH~6GenWAbho!5qblS(!#M5PT;?qXErnb2BlD`G_NH;PFRn4}^dugJ_ zOcmk4-j?U*qT1GTW)v+C+-t8 uc1Jq HH zn?*$E1>dwQuGb1R4I+ylHy6+QP@f!#I{(*hA81)>FGgszP5Y2Xd&yoyxJG5?Ump7! z&?!cbRvu8fJ71z~BzfMxMMU@7mr(^SCmYpP(C2xtrMvsm#321Qm$q%`m5S^;Njbja zM&;UGxUgxgqQJ!+orG^rtKL$K;1&zq%`;w~e#{4cTUNGSP%!^5s9;vDRn0{(cqm#% z#cXQmrhOm}0Ydk)LxusSy+V0#;cF0Ys@<9g;dT! L1L7;mfyHT8 zgpitdF5pp}7|fv<0~yzxdgFcFc~uaw `(3K zZvkk}D@$ANX*Yh@`UoHbGglDaR^9{Q=1+Rx-}UsXo;qL)`}MEg0$vXhsi3t|KJj}n z!LoqtluCG~ec)>Ar{Uex@jNEcWx9Zs;G@^1r7nwI)xqPg``heP$EUYcr$ns=CCKL? zMwMDdg!e!QbAL~bLR&^c9cpAY#Uw;p5#>XMMd_De$VEjnk2C!Ec;&nM^J01!&y}ga z{B!!h!7V^X8UzBE*9`#k`iF{Sp^N0IycQNZr66-3h@qYzA7BL?l)ZnvPsp^~87N&U zhf3*j7fQ06kF@hkNJ$OK93@94N2(`WtInQ{Nv|IW3=QofcG+QsRy}>}b{FF5@QO+O zZb@ko FdbplGuI~mu%SC& z@JmBEDGgx-l)T0s)5|Ei%uqD;*LEY4>CefI&w*JF+#Zs*OJB8PW~tkgr}Ei`hhT*4 zXS<2+24ttlq^Z_LQzDf#B=)_r(1n*LRK{+q)0r>9QjzZp@J(kN_x<_UyLW?fm3`*k zJVk}q^4EE>zpDMSWC|>>51;V6@pEMa$DyKMr>WIi>aqU8wU4_1m7PuSC{Kf5Nj{_N z{3DS4Sn3zpv_5@Z>BiQ-ZUDhmm50gC;A$cAM{xBO&m{X_*C7)6sJ}UtS5%;_t-|7_ z1v>M0&kiBaQ*i^LGShjs8*NQHFHpGQ3oR+@DVr$t(P}c$c!%2o4;z<$TT$&f9oyIv z3yW^4goWveXeC?O&*d6>Cx5yu@vjMhH~+5k{!>1InM|;n0o=vUfVKm!1F+9Ivz!4@ z5zx805K*{ZKh!)o*`a>GGs~V^$zL_&b*QpQ{Z2s{fHo%j`tyKSn&qVnXR<@&J7j3} zRc0AeMNpS!KP%yiwrpg7zv{A?RlTVe)&WY^QD1l+KFdB~85eQRf>1|R7Q|vJX0YDz zz!%*v{6Cd_cR-Wp+BeqLQ56m%qOw{jpdui$N1&|?Wfnt->?0_9L)gPskqt%m2&n=A z0t694*i;!&SwaX%AYrdC!wg~g-GTO0-*ft&@8u7;^W@=q?tQJ_@48i(uI#F+n^X%I zW;=*Zv7PBp|Fjv0<(*1PsJKphf$;gVokEmW^-9`nX`D?@B|#Kc+3y$O>Ts!Fpgf_s z0 g!OYH6Hs=xAl+;wZuyqyZ8~--qo(F;3v`<-a8_BC=nFvW9nBUNiE9x;& zk4DB $H2-%o?Qx}^QitW-e?x@RruG{2Iulu$a8^qp-U*|awacm%6G z&L^z8Wzb{#*1c{BT`dkJI|68a)h*ydGx3_tPR-JJ6`!cnde4DnOIE6G+EvW~ 7cFvEmlNDkbTW$TY(3{?A)^ 8}t@H0J_qZQM3YeOQisFy8pY_?9fnQd4 M zeg878t>LQ@lUu#J #iaPfY5}#gx3{_ #ie1pJV{{HO4PuufX+0{a!75{>%SR0|UseT_m1QWDSSkTK3E(vr8-p|FN z1&mnnhdDn=o>L9ka`{S3AjWZrCWg&?*4;Pj92T9O-|)!P$qTRO$2O`M6n!0`Uot#9 zF%|r-066|XvWcnRm>A(NaxG;ql`?Omi<{xSHfYy|3IgZT(dl&5aaWJ&4ENi;r8dMl zCfvRe(=-ExPW2~FNs=&D1s(Y5!I8$Gsm5xHo9bp1oz0u&&znhQRzdwydcNXk)4eUf zdJg@XU~})J)frOwv$LWi*GohJSSd>HTj|R{XSs6n=cjW~CTIVUsyZz5gtNfA?r<6b z?d~Sf?&cf)3^xtIydr`N8sLKeEPiEcZRf{ly;4YP5O_5uz5lDXKq*AAN^}vM?BBue z_vHG%G&;Z_VLRpff$Tb6s`N6uSd_zR@DE7C@a=@YiQ(nzp8Mi$Ose{x>btqUpGGrA z@#g%l>$aH<7D;Rz@Pi%a&xezhOj*uF H+-gZ? 7t=N?u_DuF=8>u) z=QB%<2I $e4_ zmiAWIValb&mM^1}3pcv^`hOU1jRwL{*j`xF9Jp1!;n4mMyVjr0J0ciOQ3@%h4U*d= z6MBjzV{oqZ-AK2b?Whgy7@ah~RqH4F`aQJ%8>Q=B6~Y}+@wF0RYeNg()v0DS%`$G( z>5pt2gy4+l!W9j}myB%&U*i&DdS=bhLc%XY%3fY- vJwAN)*csN-jx=l-@!jy}Pokv@?fq8^(vg2rnJ;3stzrc;o19 zZcbG9twhZcbUG91yWla8s7#-k6P)WDfEO3l8^%6VFnjpPdx3@eXK3+I`zk+(I5L}( z{$-%1ko0BsVF8r{i;fMAo4<@pUhZ8tQ&zwRSj5$q7^!sIhdnB(PKw^pcd9KN2Chmx ztpe!4JHfC;`A4E|L9Xo6A$F=3c&a&p%X6BY!%$Fvc52esr>Pwswu5vnrqw g5=hi zA_vpAy1qlli6?L4i{^+EgOWGSM*-u7{+wmtVONv;^6uH zodUyf!&CQGg5U}<$j6PRE~R8P)z2Bhi5xE}@7x0VNcvWU*nZn80aB&;OVe+G2Rq6p zWt0MY#?rh%nB}d*!B%$aN8rxB$U+xh(?WH<-G9qSSTh6G=o=Cf2J!6bu2JU Jh8$B`iEB{0Qg4UUcO5 +ewWjazjbdT;$({Y((XgTRIF@GJ&tR79fsbkO~0bVf+4z??3t9e=i>S zz?U`Foh+w}i!BbhW^sGR*zkd${9fXYnL*N{M2wR^L$Q(s8`@}U+BvBV^6Mm!F=!E}DgHhyRQ{v94*g0L z6!8Zr9iXr}#1P}~=e0#^MHr0J#sPvvPj?Ylcpp0F<6JO6-jQ%Jq3y3`7H%S{0p@DT z1(7q8?`6jORY{Bjgg2SAJZ~4}{8OjfMtf-7yP~c78IDK2HyBkXl9$FxX&28iEiW~^ zU|#OqM>IXRf<|}WdJ7jhn;vXzKS5fX+iaFo^4|zU8a6!ceg95^i^}{~rJU$Xv5I>a znlc_Xz0#Vj(*ul25tS$>zJ;A6I?utLC2)%+_}mcPK=6ef2)^6k0%Df%rU^yn7Dx;} z2!?c&eb&tlF$k+S&pXu+2$H_EfQ;NtUW1-%#k@oI)dBi3Ca88R!IlhivA?nVV*0R4 zrW!=C)=pkd8Yf>0)d(v3nO?H3yzT0KF^%*BS`?34iS(Iyy?m C O18G9BaJ*$9_s+$Pyq1t$fVS2AqR2>r?e&;dV* zX*qB*b3cB~bs7Zq0NsJ>Bp@%ae9S)q7!n0+D7M$r4 a4m^#wLoaqSQ$oZw=SMcgnRkWYQTODYn75lv@6D6E+9R)ErVvoN zQy@A_K)@1ZP*6})qdtgj*v6?haa`{%xHHo8Vd$&+{rAQ6xe+;*JK&zj!%uqZIHbKq zJ%K(Q5#BS>Zclz5lJ${oWz;fp#f4bZr-Um2;peNKr50~TUOUnf$i(rBA*pF1yF%t> z*FuR#I{qDyBsbw_g5PWZld(qob;3XQ7&X6nkii4tiw#`tZp`ridJ3epEJi?kFrqd# zK{kF1p%IpmnPRj4hMAY#-&(vLxLymn @va zdch}^{qIH2nScfmja^nVUHQoNCXgH0E ~SsZ?#$xv2W794@q>-kBX2s zZXv)BTX&YKQ^I0sAn|E*#q^R#gZ#+q(2fMa&z#bzW~VMhJz<4l0cODD5RDIDG2dQi zK?~lqAOz_kU~u5C1z3Vo5dYFB|G*ks0Ll@)D0MQ*gzF$;CJo}Xk6Qqzg$2*&`hw0P zUS7W6A5gO;H_#*xP~y{9)@gz6Sz+FmL2FKcOmQD&kYABIB18>yFi~9YDtpRy*W&so z%|9|Cc$A`?mAQ$>zzny}OPD 6xfhu{g?OZECtQy%f1{WbJqlCnP~MSiCaxML>-69W=7APuekkgQRV|{~@r8 zpJ%0^_sW|wR~b_@N`*`OO$P(&1r$qP{;qf$B)+k3(@;z>er&T=#dU~}dXMvDi00a7 z8to@F{!hh=9`t3q2jo@)8nZ;fTtN$0J%}1qMZH&L;ubJL3b~g=Rd#61OmYeR5g*n3 zl8a2%cP6|uryS4wK+d+I+;d^ZT3b%r8{IB7cL996z^cCiO{C)Pl )-%%I{I~YpGb((pg z`l`gtX1H^FFnRl%;~~wTwq7A9H<_$4n_b%@OD>una6g|>HekHbz3AWc^Llq`b<^!$ zkh@69)OQiKkXbvm?Ba9 8Na`5(CXz1M)LMEjrSatbnaK`s{c)pZf@^-*E z599lNc1)P?`p3gK8YZV|>U6+4s29nERuJeEg$_yT+jy1gdB1d`*2c=Tyv@QlZ>xFj z^>^O8tv!td#)wI8AmoztYH^I^P>;;?egWH#C?Br Sr_}x%-On2h=1SKuylI_}#oS6&k*3l|o;L4pcJr z b#O@(o+J$5X%S%ejkmN=mUKo!5H-xR%-+{`D%+E5RXa;JYy2P1p8L-eAM$rG@@ zg3TE)G~c%-0c;R!h($`h?U&pSN4Ap-j4WPmhBdMZ2_q4i_QWEW*^g{*@jyoAD6Bb8 zKIGr}$0x1VcvRJRN?F1z?M6}GReWznfo tnMqzH!rzapR7AK|49>5yV;r%qIDA z$>u=POmL9?0>T^8Km9BJ*11or{J$2Dy=`CQm@0M4EQ11!9l4ImH9#_Kz> ue2rrW$N^|oe>s5@Az zo!($XQF5U(X3Nv`ot3rwOoT-C(aE9i#jgi=w{ffAv#&+|)gxaxpfSbhERe; fLXP8Xy*&WfsBgQAjmHRF%I v#=WuV(RQ}tj&&p7&yIREdb1s<|( zBlL6w$e~*NG)D|&-rKD(ohm9S?=KcznU%@GNxKADO4H>Ks*{%4>fs`(Z8go5Pq~LD zu# i`tJWa+#b!~_;44BwcZ12$=DKa8{fD;x=$4L6QCyauhWtLvA?!jsa6{t( ztf8l(=>d$fgZyn 9D_);XtU?`83NF?^x2Ct3{I z)ORg}j~Jw{F`4tO>Mjt;`3vBUh-Gy}+l8z&(|(tsxv8<12ex4&20I_w7AYU}{0h-i zm_kfCMpRT(tWkg*-2W=IMw}wW$8gT#FCS{y9y#ecJTmsK8lG;3P19E~&8i3!EG~XK zFu*C$)%JXc9VKhmYLT*#9$X=RrS)ka-s67;G5%`u=cO;}bW<_~TUIcnmlEsllUn`w z_*w`0R%Pg^9!x*3_8Q4>BPa+S8dR&Y8MMPc +As!1?=W04;3h|j)9VO0O>z_$j@p8ge^C)R|wzp zPr=)U<8La0I{{j0sGJonXX)NL@<5?B;W}|q2+AVn0~L%Fw-o(H>a7$!NHYPL`E?f4 z1;g{^2cw-WMl70La_rs=C_R`Birpg;7RM*eyBA97Wm(qWu`N~q{#OSqpdO)4? jon{+X`G)m^SyVXc>)@d8+r~?RLd0u>w^iesW=SJU z#qRmjVzZ)k=m{EG%PK2H4{k5oX)+%b$3&~AIltyJe?IB4%sJywV@%>6x61OA8G@+? z!5fuFzMsX`k?$cP1;`-P o96+k$!ohxEOL0h+d7@7QGNa-^c(8Qt1Nz2m!21wo>DRnK zP&~g2$m1ed5V5c5bYJt|V+6F$b~&&{I+V5Ul}h;z#u@7g#`&A$h_-)q1>Z8;%Er=_ zaf 4}o&VWk?I=TI>^YhqceoR@ZAbXOQJxWl8o|J9<>M41IPA z9NkwsU1o7|?fl4neF5RTIFnPh%S&tir@i=Y=RI&xUUEx7>KjkmSY;vHdG(E&U{;KI z&*joydSGiYI0b^5q^oaVc_iyo?d4y6@L7TL8|HOscY8aar2}~UB7ny)F8k#?LJ#)( z6f4R*uDKFt47-XOLsPTf#c{kP7n{8)=q?NN9JhU`LfbcZ748>B3*2Nug#04rm?`t! zE>C~;LZ18ad)7->s4TGmvsOMU3(RGO!}Sa@Xj}yP!Qs+pQE_zcu`FXNkpu)wbK{{% zUwv)!2kg77?jleC4%R*p0Kn=7YJsox>y@bc0|B|}gVVU}`p{j~*>yu%wHfBXfzW^1 z^LwM-_Ff8AX3h4^kzvn=tg5hn1j9HSP0glp;d!G( v0TRjw>^5#X?A@}uPxe>94FB4@G@Mf5<_!8sV6n%rQ*{$2IJEJH}0sqj4bb+J@ ztV2ei0iwE0P;__*D^}*ZBR0e}z;TL>=dklA _8E^S$bSe#)sleEq-w_R5yX1IUXQMIR>bI+jGy=U>~+ zHpg#!Kif^pB_;-<=1rx!NfY{zKA^?r;GqlB9R!!EN`DCUR@8#ahkn #NR?J#KZL)n-ELzblQa#^!_ zs49KAn8NQSHz)N=+LyuBUoT&voo^fp741I< >>wV*UC$Atn8mp2zSf8HcpQ5taR NaL5*-)Qh>myj zZOT9V`j0n%RO9&V;L8l&DdZRNqQr&uib}F?!#RRwJ3mNOwz0$ifsklN)yzr-j17=B zH1jGt=xS6Pn%7Liay?@{6WM8Lmsy)F-|DtC^H`*Q4Y2w=3@=y_@cJ;vL2rc@^GmZG z8$F9rw4r9ZSmtz=k-pZ|$-LFH4)4*b(@F94E;fPX|FZaptpz$eVxzAOqMfiTS`7bc z6td uZ#ht8E5v)U`Bijxe*cak!@G$ z0o=TBb6d>qjj3!pBwj7#x45T7*Dt>Na8~FnTijDN;eP^@$Nh&w-wl2PJl>C6V$%+C zZ0MW?iX2elQ)Pg+1Z)yzC+=5D08ICjEBC{J?0 k1yO( s z)N?iL$We6>UO|pEoP$k|H+5StR@Ynz@bmO4B{^+irlV)m$&TqcgjtvRp%wHfhe*)E zfG%N8oE)bWZTm9lO3jXtNmnf0&^C$k(ooztL~NfF|EtHcqoI4e62U#G`}Ma{zZL!U z=KoYW8aAaBJ|zV!y$_x4X;9qR>JG75YD4}uhsU{^w4FieOj#Y!PU^Z1TPhxf|HUd< z^Qz7%C^E?NZO?CB-wQ&_ycE> iA5anTkz?#4J_ZAk cr$QwH)FYDUuM=ZmS-G3M>Q2XQ;XxB_fn z8=p7c1qY%KQ{FIeq=+8KN#)WVeZ(VPr{v*xhqdO%t+ I5{)~@vg32bn=P1WZ5V>05 ovOsj=dRmyIL2~gK1C^4odf{ z!xS`Tppb?OgLw_@od&00Pq=Y6 X&7SOO|Tj%Bdt;8n(c3=m0Gu}?+vc9EmuGMq>}%m_=SjM z*v~#XxKg)hHz>T-#TBqT;%Onvsu|+(6^={x@hosjy;t*(+v@t;H-$zvVyoBu7SSUS z4K*&&wz=9`DEV|6Sw&tCW4dN8H@#YMuk}I^_v453z}))}h3`K c 7Yvd4x{0<9^2MKBcJTKoI+QcGV&^-eSY&08`kpm=)M_%6KR`aW%zY=fu66iL zPFq(3Jn44kjfotOev+eOQLExIe_7XHbWvF@JWHAaxueoOcBNhYsaNs%9+#kt_(qr| zP6J_z4c!O1l6P{cvjo!mrQyI~>C~i|_22>XyvC$!6V*o%g`d^q(+?*P(hAo)&gpe% zXvZ>CDt=ja4it!T7QiO7NxE8Y)7GmNwv_D++8YX{$sL4Q%^_$n+6jF%62+hYVlRiT zz-$*>ob%rJaAPL2R4ge-JL~+Fd28Iz4sorB32mByg2X=yTTxB@cR#YZ-`)&r8JU*| z!{>MGiYfH;?CNrmhE<4b&v;pPTdE&?eJu>1w*wh>-$+#->J8<}YU5}r53zjJpstL{ zhg=Qy36ab>{JLqVH{{O>sGsbdI6i*25j#o+E3h{wMO|4f&G27et9AQl?L8>lTt_{g zraz)zvxa*t8+JVkYb%)Z*&1Bt3M;|wQ @qo@8HdI%N117k8GySdktd$dhkj%^?ImC3~h;jZo6ka3pAKG zVqdSn|8ig_(D1MO$CUOXSZx>sPr41-%Mbapzy9bD(Be=o)a;*+>e~}t-=$d|-t||v zeRkJJwo6H@+U5V-8*M=i|F3U+pN?U5@7%~?rCE qO_xylMClIp%OBY+?ZGqG zAF*x>tI7kjBvy>BY7O^kDHUWkM){<4sg676q(0MBYd_Z<-#!lU7SEr;>0x8HpcX{I zBDv9R`>K0!)o8_qD8JNa#dEbG7_6PW?I2p!ISTb$nQYh8(P1tNbJuPy7HaGk_rs)b z5E2?lfQ|BQH~$?5kdiYpGLjWMR$`5j%Uxy}{yDl{Wn$l!IxENH66tX_uNLF{po98q zZx$j1*z>QLX^|q7`?Cx{CM3P@50{mfs?2jIK27ZSEYmWhE-BFhs^ 0NRbS^Vzw2PNI#YlgjB*8e#)M}CFF1!V9djI{P+LuI#ak~2j!B68>MGTl%% zSr@9;HP07<=qR6{grbV!DE3kXu%?JLbCV >_IgPH =xV2K}6NbBNz3X+h&4AA;`+xhu+Uu*)nSmg)fQHtZ%xDxca=JY{E)kjL z%yGWcuZ)7ZjMwC>C&>(Q{L@P&HGP;fch~(!wB)?0F`-S;cl+`Z-mthWi2RZ5)kKQV z?1fZDmK7n2R0eH?U~ +hu;mGg5_xHpDsIA_RZwHPbuX`bV>VeS?lpVB*2`Tz{% zBF_kn+p*mycvJQkZPV*7<^emE(!0SMa*0VJyg{q(aBsl`yHqIDQ*G|Qy(Kj%8@dx| z<8(JJ%Ljp^DgTdbQeU(C#rwleMi&8iy4aLltGq~w%n7F{(6A_Yam_2Wj2MX H8{;Ojp(IZu0s4ldtMzrV}pj zpoNRR$(I=Y&lMmkfqAOU=KxMON@ Td)3F2Q>NS#boKML zA-R}o0dCJq(wNtzzdv+2B>3^<$WulW7u`*AD-dgQ4=#`p?k7xmEf9`Q&%ZT6`DP?U zEhl(p@l@EcQDg>=b4svAQ?^F~st>sY%&PF8b AB7Uu@Xt%WeSxy;KoS&9Q!GubsaRPdnYbbZ<0B+Ac0ZzP*a8=tpmLKB| z4*ikQZ-=$fjaB%>VjsM>Zc7>xtVn5Lm_S_p7Vt4^lfDB=+mJ$!{Gr;a2)8A-D4E`L zpf+FghfNqxD5v@Lu4i_b)q5tLTNa#K+cQZLP2JN=d~wvV V9 zK_NXHKds(Oy373PJYq?tLQAVun%j(kQl=^lSTA_*W=|QUy}Fxh|3LF%E#;l BGOTINOo< zsaiK9*#SM6UK^2%x25)I_HZ0NX= 3@g2TjcinP*Cbx}G;^ zyz-H)3SFTY12;h53iY47+mMiGrKVZCXo;5r2h#EC%Zy!fzrSXmOqRMtE|Vu{8}{3% z4r8v+Y8ZIorlH|g)Www?{A;sPvrt?FER)IPvBgq8jP# hmD~b@XBi@8NC$I%Db#3H{ilKY_|jHc3UU1{86&+T zWgWk`?<%uixly~Itc+M4vg=RNPJ{WQ6)AaM?P-BC{7#{U$3_xg;f;fBZ&a^cPB^hm z3e2gQT0S}(%$={ro?_dL5pth|4|r`@$*2i%4o|PUN*&cEmdr8TBuXrtrF))JYJDRk z$lC<&2~*rAjd0aJbbjW{m|`V@8ZboIyu~j5xkmr*4LRYl3vI4d=W>M0cG`bUHGzbX zv@K=u`Ffi~HT?Bd 9I_ZDgU7$gRASf})@`_E^f73yr%FR4rn zuotGy#u;&In-?nOzuDuB+{Nbqwha+8Mgp<}xpQ!%B~a~yr6T sY9D^JUv3|5LJ2`;`gviz^d zbN{^&e{E-@fCYmDcfC{U zX^=S9yPUvrV5PH qCXHCi!6sU*U{fYwhW@#f(jaQtgsBx(6PdIy1&!x+i$1ySS>a zOiEzp_KMUOo?;gt5bZTzVT)A=hN3fBc9A3a(2s2Q-KBI_o1Zd|pCmbhbi9|aMVwqy zZp4N ey~NO_!Xd|x zzCx4x86_GnM;|WzUAzA6dI?eAva$6cqU?Hr%Ij=X?n$GBRjX&xq@);r{ynJS{hc5y z---P@o_;4`3Rs)2+(%rblqfxt05iyT=)-0X4X!CilqKoGxJc5Fr4fz^a;ZR1lUd{0 ziFtoX6s`SATDN pdOQ?bh)T-DI*E$5=%p Yv*jfc&(q z_9a8b)^zrPr*H72p>da}?7G7y4DPa%-RaKLJ3Fzj>%9^`aPxMv+@&Ihcsrp6XbV~- zxel_2Pc~odaP+iru3Z$eU;vW5^MG34z)&47du!(zZ~tQsrcpQ!ZvC>h38WThjx7m- zR7dRD`ho_8W*jFc4&$4#+3m73oOh*mKiR&!!6R6D-=u*@+hdG8XkV&6*1EFvbMDIi zR~O9GP`2!y;6G}8GltrN?5R^K#4n3^8p_;o1p#-Rb$aDXyx4Aj^I3EM%ZDReCy!t} z!=xC{=u&;<#<&FEWvhY0`0#i31pmdYT6pX}dq7of0G|Y=O%qZcmsSav8RI4Z9JvmA zYC??DI!bc4UMyRQiRD4}?FX*W{RN{K@W^U=A(#0;0|NmfJ{c30g)qcm-+Gk2FX>RV zXos1;Bj7@ZH;G>97 axUPj#3}&_!y bvYqPtYlsWyV)Imu=`)=>*it~uW z>ZV P~qKCG*$a7Wd2cK 3ZTheWa|K D_6|I?k6N(Nz_B%AE%Eej4F%=)6GM z&vQPz)5^q+;17z5{)+1#np-Op>{Kq}HK2k;>)qwoUB)^(x>gr7G_>Bc{o!){FVpCM ze0{t#BRne2$#u$>+@xt#N%V@ufNalsLUu~c7bA1-%!i6hHXU)cyxMpx`xI$e#*e*8 zOh)Z3RUKMVoi#cJoV?x<8}0C-ouP{+o0zbT&Cr7U<52xJTy0o;Kss~HdxKBxHfjw@ zOlrye$aYn6OFM%>;J80E%=-PDKk7iqKQG_iZScE?8ETB*B%ND1CkJOs1bFGbbjG zX5v&)=*Sq9HFUASpIsWA^SwKjny6-=<`&Z%Fy2UY@9tTM^LuL1L=x`k6(`c@uWKfm zdpng#+tI;CUNyzH$5ms)PP{4SQMb@jxe#dd{OvZrEIYKzz%8COx9yvjrR@Qd_Z-K{ zwPMSXR%I7xs& |-o~91LzF;&z4mhS9eBuGcA89W%9yPUif@Lnk=4hmOks(dYc%9~)W2 zJtbwO70(XhEfL-Kp_)+j@>sHPL$b+(SZ{LJF;BN(jWLhuqQs^SLWXOg_ICjhg+}_; z-wn0^gxI3>tX@#AFV^bY{MPMNjWFLfkhDW;GOv3R@nRV%JW46Hc%F$<`?jr({=7>) z8CBr#7O3!Q;<0ofxBV2<iRV*+h#`DzdWjGEax3&IjCl7W2?qYRIyl3Qd!FzJfpN zY$^D(5_0oK#?HL_)y&Yeigf|Y6{`rCf-F_w70y?F=YCHwCS-MRaS#Vi^f(9A2)(8e zN)S}nov2_}heMBR&fq2X1a6N}f1o$!TXNe&qw-BrjWyN=)tfzn<`s(eql?XVJvBR> zf9mLSBe=>MKE |21kv}!9=MF!8&kV(D zBpdqHV=y>oYHF!dU9x-fB_@yGQmw)W-+?mqTAO>Z56-r#YvGF@jTKkB(?aWkPuS@J z_M-)Dxaqw#$R1Rn;6QI`TKy{b$xOS`u1Ur|Z3)jU6rdruuz!JgZM~K&QEey-w|MiY zpkV$aGn9${>d_f=(1X^u|6tG;dMp-=N$WKfm7A}c_GVJHU@ssQdM#c76A_St72kxg z+$lA=rH1Rn)wU^o;)SmCXq-S(R+9AY&n+aow`N`aC^%d{KFNW~yQ%Qac9<3sR(fY? zk2FoXK7GALqP>p4md|Rpl#KaGSN^a;rXG$jy7v?3*zJ#|Wph084z}VUN_6;bU&z>V zP2$o2z4iH(ww7{m60ihO@C{#Z#_GtLnf*>J \ No newline at end of file diff --git a/server/ui/src/app/common/types.ts b/server/ui/src/app/common/types.ts new file mode 100644 index 00000000..4bbf6791 --- /dev/null +++ b/server/ui/src/app/common/types.ts @@ -0,0 +1,9 @@ +export interface Page { + page: number; + perPage: number; +} + +export interface SortBy { + index: number; + direction: "asc" | "desc"; +} diff --git a/server/ui/src/app/components/Notifications.tsx b/server/ui/src/app/components/Notifications.tsx new file mode 100644 index 00000000..2d51d05f --- /dev/null +++ b/server/ui/src/app/components/Notifications.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { + Alert, + AlertActionCloseButton, + AlertGroup, +} from "@patternfly/react-core"; +import { NotificationsContext } from "./NotificationsContext"; + +export const Notifications: React.FunctionComponent = () => { + const appContext = React.useContext(NotificationsContext); + return ( + + {appContext.notifications.map((notification) => { + return ( + + ); +}; diff --git a/server/ui/src/app/components/NotificationsContext.tsx b/server/ui/src/app/components/NotificationsContext.tsx new file mode 100644 index 00000000..e21a8d35 --- /dev/null +++ b/server/ui/src/app/components/NotificationsContext.tsx @@ -0,0 +1,66 @@ +import * as React from "react"; +import { AlertProps } from "@patternfly/react-core"; + +export type INotification = { + title: string; + variant: AlertProps["variant"]; + message?: React.ReactNode; + hideCloseButton?: boolean; + timeout?: number | boolean; +}; + +interface INotificationsProvider { + children: React.ReactNode; +} + +interface INotificationsContext { + pushNotification: (notification: INotification) => void; + dismissNotification: (key: string) => void; + notifications: INotification[]; +} + +const appContextDefaultValue = {} as INotificationsContext; + +const notificationDefault: Pick{ + appContext.dismissNotification(notification.title); + }} + /> + ), + })} + timeout={notification.timeout ? notification.timeout : 4000} + > + {notification.message} + + ); + })} += { + hideCloseButton: false, +}; + +export const NotificationsContext = React.createContext ( + appContextDefaultValue +); + +export const NotificationsProvider: React.FunctionComponent< + INotificationsProvider +> = ({ children }: INotificationsProvider) => { + const [notifications, setNotifications] = React.useState ([]); + + const pushNotification = ( + notification: INotification, + clearNotificationDelay?: number + ) => { + setNotifications([ + ...notifications, + { ...notificationDefault, ...notification }, + ]); + setTimeout(() => setNotifications([]), clearNotificationDelay || 10000); + }; + + const dismissNotification = (title: string) => { + const remainingNotifications = notifications.filter( + (n) => n.title !== title + ); + setNotifications(remainingNotifications); + }; + + return ( + + {children} + + ); +}; diff --git a/server/ui/src/app/components/PageDrawerContext.tsx b/server/ui/src/app/components/PageDrawerContext.tsx new file mode 100644 index 00000000..90eb886a --- /dev/null +++ b/server/ui/src/app/components/PageDrawerContext.tsx @@ -0,0 +1,199 @@ +import * as React from "react"; +import { + Drawer, + DrawerActions, + DrawerCloseButton, + DrawerContent, + DrawerContentBody, + DrawerHead, + DrawerPanelBody, + DrawerPanelContent, + DrawerPanelContentProps, +} from "@patternfly/react-core"; +import pageStyles from "@patternfly/react-styles/css/components/Page/page"; + +const usePageDrawerState = () => { + const [isDrawerExpanded, setIsDrawerExpanded] = React.useState(false); + const [drawerPanelContent, setDrawerPanelContent] = + React.useState(null); + const [drawerPanelContentProps, setDrawerPanelContentProps] = React.useState< + Partial + >({}); + const [drawerPageKey, setDrawerPageKey] = React.useState (""); + const drawerFocusRef = React.useRef(document.createElement("span")); + return { + isDrawerExpanded, + setIsDrawerExpanded, + drawerPanelContent, + setDrawerPanelContent, + drawerPanelContentProps, + setDrawerPanelContentProps, + drawerPageKey, + setDrawerPageKey, + drawerFocusRef: drawerFocusRef as typeof drawerFocusRef | null, + }; +}; + +type PageDrawerState = ReturnType ; + +const PageDrawerContext = React.createContext ({ + isDrawerExpanded: false, + setIsDrawerExpanded: () => {}, + drawerPanelContent: null, + setDrawerPanelContent: () => {}, + drawerPanelContentProps: {}, + setDrawerPanelContentProps: () => {}, + drawerPageKey: "", + setDrawerPageKey: () => {}, + drawerFocusRef: null, +}); + +// PageContentWithDrawerProvider should only be rendered as the direct child of a PatternFly Page component. +interface IPageContentWithDrawerProviderProps { + children: React.ReactNode; // The entire content of the page. See usage in client/src/app/layout/DefaultLayout. +} +export const PageContentWithDrawerProvider: React.FC< + IPageContentWithDrawerProviderProps +> = ({ children }) => { + const pageDrawerState = usePageDrawerState(); + const { + isDrawerExpanded, + drawerFocusRef, + drawerPanelContent, + drawerPanelContentProps, + drawerPageKey, + } = pageDrawerState; + return ( + + + ); +}; + +let numPageDrawerContentInstances = 0; + +// PageDrawerContent can be rendered anywhere, but must have only one instance rendered at a time. +export interface IPageDrawerContentProps { + isExpanded: boolean; + onCloseClick: () => void; // Should be used to update local state such that `isExpanded` becomes false. + header?: React.ReactNode; + children: React.ReactNode; // The content to show in the drawer when `isExpanded` is true. + drawerPanelContentProps?: Partial++drawerFocusRef?.current?.focus()} + position="right" + > + ++ {drawerPanelContent} + + } + > + +{children} +; // Additional props for the DrawerPanelContent component. + focusKey?: string | number; // A unique key representing the object being described in the drawer. When this changes, the drawer will regain focus. + pageKey: string; // A unique key representing the page where the drawer is used. Causes the drawer to remount when changing pages. +} + +export const PageDrawerContent: React.FC = ({ + isExpanded, + onCloseClick, + header = null, + children, + drawerPanelContentProps, + pageKey: localPageKeyProp, +}) => { + const { + setIsDrawerExpanded, + drawerFocusRef, + setDrawerPanelContent, + setDrawerPanelContentProps, + setDrawerPageKey, + } = React.useContext(PageDrawerContext); + + // Warn if we are trying to render more than one PageDrawerContent (they'll fight over the same state). + React.useEffect(() => { + numPageDrawerContentInstances++; + return () => { + numPageDrawerContentInstances--; + }; + }, []); + if (numPageDrawerContentInstances > 1) { + console.warn( + `${numPageDrawerContentInstances} instances of PageDrawerContent are currently rendered! Only one instance of this component should be rendered at a time.` + ); + } + + // Lift the value of isExpanded out to the context, but derive it from local state such as a selected table row. + // This is the ONLY place where `setIsDrawerExpanded` should be called. + // To expand/collapse the drawer, use the `isExpanded` prop when rendering PageDrawerContent. + React.useEffect(() => { + setIsDrawerExpanded(isExpanded); + return () => { + setIsDrawerExpanded(false); + setDrawerPanelContent(null); + }; + }, [isExpanded, setDrawerPanelContent, setIsDrawerExpanded]); + + // Same with pageKey and drawerPanelContentProps, keep them in sync with the local prop on PageDrawerContent. + React.useEffect(() => { + setDrawerPageKey(localPageKeyProp); + return () => { + setDrawerPageKey(""); + }; + }, [localPageKeyProp, setDrawerPageKey]); + + React.useEffect(() => { + setDrawerPanelContentProps(drawerPanelContentProps || {}); + }, [drawerPanelContentProps, setDrawerPanelContentProps]); + + // If the drawer is already expanded describing app A, then the user clicks app B, we want to send focus back to the drawer. + + // TODO: This introduces a layout issue bug when clicking in between the columns of a table. + // React.useEffect(() => { + // drawerFocusRef?.current?.focus(); + // }, [drawerFocusRef, focusKey]); + + React.useEffect(() => { + const drawerHead = header === null ? children : header; + const drawerPanelBody = header === null ? null : children; + + setDrawerPanelContent( + <> + + + {drawerHead} + + ++ ++ {drawerPanelBody} + > + ); + }, [ + children, + drawerFocusRef, + header, + isExpanded, + onCloseClick, + setDrawerPanelContent, + ]); + + return null; +}; diff --git a/server/ui/src/app/layout/about.tsx b/server/ui/src/app/layout/about.tsx new file mode 100644 index 00000000..a7426452 --- /dev/null +++ b/server/ui/src/app/layout/about.tsx @@ -0,0 +1,58 @@ +import React from "react"; + +import {AboutModal, Content, ContentVariants} from "@patternfly/react-core"; + +import backgroundImage from "@app/assets/pfbg-icon.svg"; + +interface IButtonAboutAppProps { + isOpen: boolean; + onClose: () => void; +} + +const TRANSPARENT_1x1_GIF = + "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw== "; + +export const AboutApp: React.FC= ({ + isOpen, + onClose, + }) => { + return ( + + + ); +}; diff --git a/server/ui/src/app/layout/default-layout.tsx b/server/ui/src/app/layout/default-layout.tsx new file mode 100644 index 00000000..97dd74e2 --- /dev/null +++ b/server/ui/src/app/layout/default-layout.tsx @@ -0,0 +1,34 @@ +import React from "react"; + +import { Page, SkipToContent } from "@patternfly/react-core"; + +import { HeaderApp } from "./header"; +import { SidebarApp } from "./sidebar"; +import { Notifications } from "@app/components/Notifications"; +import { PageContentWithDrawerProvider } from "@app/components/PageDrawerContext"; + +interface DefaultLayoutProps { + children?: React.ReactNode; +} + +export const DefaultLayout: React.FC+ ++ OpenUBL is vendor-neutral, thought-leadering, mostly + informational collection of resources devoted to making Software + Supply Chains easier to create, manage, consume and ultimately… to + trust! + + ++ For more information refer to{" "} + ++ OpenUBL documentation + ++ ++ ++ +Version +99.0.0 += ({ children }) => { + const pageId = "main-content-page-layout-horizontal-nav"; + const PageSkipToContent = ( + Skip to content + ); + + return ( +} + sidebar={ } + isManagedSidebar + skipToContent={PageSkipToContent} + mainContainerId={pageId} + > + + {children} + + + ); +}; diff --git a/server/ui/src/app/layout/header.tsx b/server/ui/src/app/layout/header.tsx new file mode 100644 index 00000000..77811981 --- /dev/null +++ b/server/ui/src/app/layout/header.tsx @@ -0,0 +1,250 @@ +import React, { useReducer, useState } from "react"; +// import { useNavigate } from "react-router-dom"; + +import { + Avatar, + Button, + ButtonVariant, + Divider, + Dropdown, + DropdownItem, + DropdownList, + Masthead, + MastheadBrand, + MastheadContent, + MastheadLogo, + MastheadMain, + MastheadToggle, + MenuToggle, + MenuToggleElement, + PageToggleButton, + Split, + SplitItem, + Title, + ToggleGroup, + ToggleGroupItem, + Toolbar, + ToolbarContent, + ToolbarGroup, + ToolbarItem, +} from "@patternfly/react-core"; + +import { MoonIcon, SunIcon } from "@patternfly/react-icons/"; +import EllipsisVIcon from "@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon"; +import HelpIcon from "@patternfly/react-icons/dist/esm/icons/help-icon"; +import BarsIcon from "@patternfly/react-icons/dist/js/icons/bars-icon"; + +import imgAvatar from "../assets/avatar.svg"; +import { AboutApp } from "./about"; + +export const HeaderApp: React.FC = () => { + const [isDarkTheme, setIsDarkTheme] = useState( + localStorage.getItem("isDarkTheme") === "true" ? true : false + ); + + React.useEffect(() => { + if (isDarkTheme) { + document.documentElement.classList.add("pf-v6-theme-dark"); + localStorage.setItem("isDarkTheme", "true"); + } else { + document.documentElement.classList.remove("pf-v6-theme-dark"); + localStorage.setItem("isDarkTheme", "false"); + } + }, [isDarkTheme]); + + // const navigate = useNavigate(); + + const [isAboutOpen, toggleIsAboutOpen] = useReducer((state) => !state, false); + const [isKebabDropdownOpen, setIsKebabDropdownOpen] = useState(false); + const [isUserDropdownOpen, setIsUserDropdownOpen] = useState(false); + + const onKebabDropdownToggle = () => { + setIsKebabDropdownOpen(!isKebabDropdownOpen); + }; + + const onKebabDropdownSelect = () => { + setIsKebabDropdownOpen(!isKebabDropdownOpen); + }; + + return ( + <> ++ + + + + > + ); +}; diff --git a/server/ui/src/app/layout/index.ts b/server/ui/src/app/layout/index.ts new file mode 100644 index 00000000..081402f4 --- /dev/null +++ b/server/ui/src/app/layout/index.ts @@ -0,0 +1 @@ +export { DefaultLayout } from "./default-layout"; diff --git a/server/ui/src/app/layout/sidebar.tsx b/server/ui/src/app/layout/sidebar.tsx new file mode 100644 index 00000000..7b02d9b3 --- /dev/null +++ b/server/ui/src/app/layout/sidebar.tsx @@ -0,0 +1,109 @@ +import React from "react"; + +import { + Nav, + NavList, + PageSidebar, + PageSidebarBody, +} from "@patternfly/react-core"; +// import { css } from "@patternfly/react-styles"; +// +// const LINK_CLASS = "pf-v6-c-nav__link"; +// const ACTIVE_LINK_CLASS = "pf-m-current"; + +export const SidebarApp: React.FC = () => { + const renderPageNav = () => { + return ( + + ); + }; + + return ( ++ ++ ++ ++ + ++ ++ ++ {/* +*/} + + ++ OpenUBL + ++ ++ ++ {/* toolbar items to always show */} + ++ + + {/* toolbar items to show at desktop sizes */} ++ ++ +} + isSelected={!isDarkTheme} + onChange={() => setIsDarkTheme(false)} + /> + } + isSelected={isDarkTheme} + onChange={() => setIsDarkTheme(true)} + /> + + + + {/* toolbar items to show at mobile sizes */} ++ } + id="about-button" + aria-label="about button" + variant={ButtonVariant.plain} + onClick={toggleIsAboutOpen} + /> + ++ + + {/* Show the SSO menu at desktop sizes */} ++ ++ setIsKebabDropdownOpen(isOpen) + } + popperProps={{ position: "right" }} + toggle={(toggleRef: React.Ref +) => ( + + + )} + > + ++ ++ Logout + ++ + +About + + + ++ +setIsUserDropdownOpen(false)} + onOpenChange={(isOpen: boolean) => + setIsUserDropdownOpen(isOpen) + } + popperProps={{ position: "right" }} + toggle={(toggleRef: React.Ref +) => ( + + setIsUserDropdownOpen(!isUserDropdownOpen) + } + isFullHeight + isExpanded={isUserDropdownOpen} + icon={ + )} + > +} + > + username + + ++ Logout + ++ ++ {/* +*/} + + + ); +}; diff --git a/server/ui/src/app/pages/home/home.tsx b/server/ui/src/app/pages/home/home.tsx new file mode 100644 index 00000000..f2e78d34 --- /dev/null +++ b/server/ui/src/app/pages/home/home.tsx @@ -0,0 +1,9 @@ +import React from "react"; + +export const Home: React.FC = () => { + return ( + <> + hello + > + ); +}; diff --git a/server/ui/src/app/pages/home/index.ts b/server/ui/src/app/pages/home/index.ts new file mode 100644 index 00000000..484c8c12 --- /dev/null +++ b/server/ui/src/app/pages/home/index.ts @@ -0,0 +1 @@ +export { Home as default } from "./home"; diff --git a/server/ui/src/assets/react.svg b/server/ui/src/assets/react.svg deleted file mode 100644 index 6c87de9b..00000000 --- a/server/ui/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/server/ui/src/index.css b/server/ui/src/index.css deleted file mode 100644 index 6119ad9a..00000000 --- a/server/ui/src/index.css +++ /dev/null @@ -1,68 +0,0 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/server/ui/src/main.tsx b/server/ui/src/main.tsx index bef5202a..0a9c5baa 100644 --- a/server/ui/src/main.tsx +++ b/server/ui/src/main.tsx @@ -1,10 +1,18 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.tsx' +import {StrictMode} from 'react' +import {createRoot} from 'react-dom/client' + +import {QueryClient, QueryClientProvider} from "@tanstack/react-query"; +import {ReactQueryDevtools} from "@tanstack/react-query-devtools"; + +import App from "@app/App"; + +const queryClient = new QueryClient(); createRoot(document.getElementById('root')!).render( -{renderPageNav()} +- , +- + , ) diff --git a/server/ui/tsconfig.app.json b/server/ui/tsconfig.app.json index 358ca9ba..851d7d6d 100644 --- a/server/ui/tsconfig.app.json +++ b/server/ui/tsconfig.app.json @@ -20,7 +20,11 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true + "noUncheckedSideEffectImports": true, + + "paths": { + "@app/*": ["./src/app/*"] + } }, "include": ["src"] } diff --git a/server/ui/vite.config.ts b/server/ui/vite.config.ts index 2328e170..7ef8f474 100644 --- a/server/ui/vite.config.ts +++ b/server/ui/vite.config.ts @@ -4,4 +4,9 @@ import react from '@vitejs/plugin-react-swc' // https://vite.dev/config/ export default defineConfig({ plugins: [react()], + resolve: { + alias: { + "@app": "/src/app", + }, + }, })+ ++ +