diff --git a/src/Frontend/package-lock.json b/src/Frontend/package-lock.json index 2fe418e69..a4987b58e 100644 --- a/src/Frontend/package-lock.json +++ b/src/Frontend/package-lock.json @@ -64,7 +64,7 @@ "prettier": "3.8.3", "typescript": "6.0.3", "typescript-eslint": "8.60.0", - "vite": "8.0.14", + "vite": "8.0.16", "vite-plugin-checker": "0.14.1", "vite-plugin-vue-devtools": "8.1.2", "vitest": "4.1.7", @@ -1016,24 +1016,22 @@ "license": "MIT" }, "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "license": "MIT", "optional": true, - "peer": true, "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -1559,13 +1557,13 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@tybys/wasm-util": "^0.10.2" }, "funding": { "type": "github", @@ -1609,9 +1607,9 @@ "license": "MIT" }, "node_modules/@oxc-project/types": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", - "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", "devOptional": true, "license": "MIT", "funding": { @@ -1662,9 +1660,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", - "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", "cpu": [ "arm64" ], @@ -1678,9 +1676,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", - "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", "cpu": [ "arm64" ], @@ -1694,9 +1692,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", - "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", "cpu": [ "x64" ], @@ -1710,9 +1708,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", - "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", "cpu": [ "x64" ], @@ -1726,9 +1724,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", - "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", "cpu": [ "arm" ], @@ -1742,12 +1740,15 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", - "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1758,12 +1759,15 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", - "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1774,12 +1778,15 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", - "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", "cpu": [ "ppc64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1790,12 +1797,15 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", - "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", "cpu": [ "s390x" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1806,12 +1816,15 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", - "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1822,12 +1835,15 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", - "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1838,9 +1854,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", - "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", "cpu": [ "arm64" ], @@ -1854,9 +1870,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", - "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", "cpu": [ "wasm32" ], @@ -1871,31 +1887,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", - "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", "cpu": [ "arm64" ], @@ -1909,9 +1904,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", - "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", "cpu": [ "x64" ], @@ -2074,9 +2069,9 @@ "license": "MIT" }, "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "license": "MIT", "optional": true, "dependencies": { @@ -3348,9 +3343,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -5488,14 +5483,11 @@ } }, "node_modules/js-cookie": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", - "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz", + "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } + "license": "MIT" }, "node_modules/js-tokens": { "version": "4.0.0", @@ -6846,13 +6838,13 @@ "license": "MIT" }, "node_modules/rolldown": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz", - "integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", "devOptional": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.132.0", + "@oxc-project/types": "=0.133.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -6862,21 +6854,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.2", - "@rolldown/binding-darwin-arm64": "1.0.2", - "@rolldown/binding-darwin-x64": "1.0.2", - "@rolldown/binding-freebsd-x64": "1.0.2", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", - "@rolldown/binding-linux-arm64-gnu": "1.0.2", - "@rolldown/binding-linux-arm64-musl": "1.0.2", - "@rolldown/binding-linux-ppc64-gnu": "1.0.2", - "@rolldown/binding-linux-s390x-gnu": "1.0.2", - "@rolldown/binding-linux-x64-gnu": "1.0.2", - "@rolldown/binding-linux-x64-musl": "1.0.2", - "@rolldown/binding-openharmony-arm64": "1.0.2", - "@rolldown/binding-wasm32-wasi": "1.0.2", - "@rolldown/binding-win32-arm64-msvc": "1.0.2", - "@rolldown/binding-win32-x64-msvc": "1.0.2" + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" } }, "node_modules/run-applescript": { @@ -7285,9 +7277,9 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -7468,9 +7460,9 @@ "license": "MIT" }, "node_modules/undici": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", - "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", "dev": true, "license": "MIT", "engines": { @@ -7586,17 +7578,17 @@ "license": "MIT" }, "node_modules/vite": { - "version": "8.0.14", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz", - "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", "devOptional": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.15", - "rolldown": "1.0.2", - "tinyglobby": "^0.2.16" + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" diff --git a/src/Frontend/package.json b/src/Frontend/package.json index 81bdfa7fd..94d3cf9dc 100644 --- a/src/Frontend/package.json +++ b/src/Frontend/package.json @@ -76,7 +76,7 @@ "prettier": "3.8.3", "typescript": "6.0.3", "typescript-eslint": "8.60.0", - "vite": "8.0.14", + "vite": "8.0.16", "vite-plugin-checker": "0.14.1", "vite-plugin-vue-devtools": "8.1.2", "vitest": "4.1.7", diff --git a/src/Frontend/src/App.vue b/src/Frontend/src/App.vue index de55293cb..567f2973e 100644 --- a/src/Frontend/src/App.vue +++ b/src/Frontend/src/App.vue @@ -1,5 +1,5 @@ + + + + diff --git a/src/Frontend/src/components/serviceControlClient.ts b/src/Frontend/src/components/serviceControlClient.ts index 40ad24c53..9db92f3f7 100644 --- a/src/Frontend/src/components/serviceControlClient.ts +++ b/src/Frontend/src/components/serviceControlClient.ts @@ -29,8 +29,13 @@ class ServiceControlClient { } } - public async fetchTypedFromServiceControl(suffix: string, signal?: AbortSignal): Promise<[Response, T]> { - const response = await authFetch(`${this.url}${suffix}`, { signal }); + public fetchTypedFromServiceControl(suffix: string, signal?: AbortSignal): Promise<[Response, T]> { + return this.fetchTypedFromUrl(`${this.url}${suffix}`, signal); + } + + // Fetch from an absolute URL, e.g. one discovered from the ServiceControl root document. + public async fetchTypedFromUrl(url: string, signal?: AbortSignal): Promise<[Response, T]> { + const response = await authFetch(url, { signal }); if (!response.ok) throw new HttpError(response.status, response.statusText ?? "No response"); const data = await response.json(); diff --git a/src/Frontend/src/composables/usePermissionGate.spec.ts b/src/Frontend/src/composables/usePermissionGate.spec.ts new file mode 100644 index 000000000..a76ed96c6 --- /dev/null +++ b/src/Frontend/src/composables/usePermissionGate.spec.ts @@ -0,0 +1,75 @@ +import { describe, test, expect, beforeEach } from "vitest"; +import { createTestingPinia } from "@pinia/testing"; +import { setActivePinia } from "pinia"; +import usePermissionGate from "@/composables/usePermissionGate"; +import { useAuthStore } from "@/stores/AuthStore"; +import { useUserPermissionsStore, type PermissionsSummary } from "@/stores/UserPermissionsStore"; + +const summaryWith = (overrides: Partial = {}): PermissionsSummary => ({ + failed_messages_read: false, + failed_messages_write: false, + auditing_read: false, + monitoring_read: false, + monitoring_write: false, + admin_read: false, + admin_write: false, + ...overrides, +}); + +describe("usePermissionGate", () => { + beforeEach(() => { + setActivePinia(createTestingPinia({ stubActions: false })); + }); + + function withState(opts: { authEnabled: boolean; isAuthenticated: boolean; summary: PermissionsSummary | null }) { + const auth = useAuthStore(); + auth.authEnabled = opts.authEnabled; + auth.isAuthenticated = opts.isAuthenticated; + useUserPermissionsStore().summary = opts.summary; + return usePermissionGate(); + } + + test("fails open (shows everything) when authorization is disabled", () => { + const { has, shouldGate } = withState({ authEnabled: false, isAuthenticated: true, summary: summaryWith() }); + + expect(shouldGate.value).toBe(false); + expect(has("admin_read")).toBe(true); + expect(has("failed_messages_read")).toBe(true); + }); + + test("fails open when the user is not authenticated", () => { + const { has, shouldGate } = withState({ authEnabled: true, isAuthenticated: false, summary: summaryWith() }); + + expect(shouldGate.value).toBe(false); + expect(has("admin_read")).toBe(true); + }); + + test("fails open while the permission summary has not loaded yet", () => { + const { has, shouldGate } = withState({ authEnabled: true, isAuthenticated: true, summary: null }); + + expect(shouldGate.value).toBe(false); + expect(has("admin_read")).toBe(true); + }); + + test("gates per flag once enabled, authenticated and loaded", () => { + const { has, shouldGate } = withState({ authEnabled: true, isAuthenticated: true, summary: summaryWith({ admin_read: true }) }); + + expect(shouldGate.value).toBe(true); + expect(has("admin_read")).toBe(true); + expect(has("failed_messages_read")).toBe(false); + }); + + test("reacts to the summary being updated", () => { + const auth = useAuthStore(); + auth.authEnabled = true; + auth.isAuthenticated = true; + const permissions = useUserPermissionsStore(); + permissions.summary = summaryWith(); + + const { has } = usePermissionGate(); + expect(has("monitoring_read")).toBe(false); + + permissions.summary = summaryWith({ monitoring_read: true }); + expect(has("monitoring_read")).toBe(true); + }); +}); diff --git a/src/Frontend/src/composables/usePermissionGate.ts b/src/Frontend/src/composables/usePermissionGate.ts new file mode 100644 index 000000000..516a3abee --- /dev/null +++ b/src/Frontend/src/composables/usePermissionGate.ts @@ -0,0 +1,22 @@ +import { computed } from "vue"; +import { storeToRefs } from "pinia"; +import { useAuthStore } from "@/stores/AuthStore"; +import { useUserPermissionsStore, type PermissionsSummary } from "@/stores/UserPermissionsStore"; + +// Centralises the "should this UI element be shown for the current user" decision. +// Gating only kicks in once authorization is enabled, the user is authenticated and +// the permission summary has loaded; otherwise everything is shown (fail-open) so the +// UI is unchanged for unauthenticated/older-ServiceControl setups. Server-side checks +// remain the real enforcement — this only hides UI the user cannot use anyway. +export default function usePermissionGate() { + const { authEnabled, isAuthenticated } = storeToRefs(useAuthStore()); + const { summary } = storeToRefs(useUserPermissionsStore()); + + const shouldGate = computed(() => authEnabled.value && isAuthenticated.value && summary.value !== null); + + function has(flag: keyof PermissionsSummary): boolean { + return !shouldGate.value || summary.value?.[flag] === true; + } + + return { has, shouldGate }; +} diff --git a/src/Frontend/src/resources/RootUrls.ts b/src/Frontend/src/resources/RootUrls.ts index 5c18ca5c6..7826ba27a 100644 --- a/src/Frontend/src/resources/RootUrls.ts +++ b/src/Frontend/src/resources/RootUrls.ts @@ -17,4 +17,6 @@ export default interface RootUrls { event_log_items: string; archived_groups_url: string; get_archive_group: string; + mypermissions_all?: string; + mypermissions_summary?: string; } diff --git a/src/Frontend/src/router/config.ts b/src/Frontend/src/router/config.ts index 9e4f579db..10ed24d8e 100644 --- a/src/Frontend/src/router/config.ts +++ b/src/Frontend/src/router/config.ts @@ -206,6 +206,11 @@ const config: RouteItem[] = [ path: routeLinks.configuration.endpointConnection.template, component: () => import("@/components/configuration/EndpointConnection.vue"), }, + { + title: "User Permissions", + path: routeLinks.configuration.userPermissions.template, + component: () => import("@/components/configuration/UserPermissions.vue"), + }, { title: "Usage Setup", path: routeLinks.throughput.setup.root, diff --git a/src/Frontend/src/router/routeLinks.ts b/src/Frontend/src/router/routeLinks.ts index 64ee77620..f2e957c9b 100644 --- a/src/Frontend/src/router/routeLinks.ts +++ b/src/Frontend/src/router/routeLinks.ts @@ -51,6 +51,7 @@ const configurationLinks = (root: string) => { retryRedirects: createLink("retry-redirects"), connections: createLink("connections"), endpointConnection: createLink("endpoint-connection"), + userPermissions: createLink("user-permissions"), }; }; diff --git a/src/Frontend/src/stores/EnvironmentAndVersionsStore.ts b/src/Frontend/src/stores/EnvironmentAndVersionsStore.ts index 0f15264f9..1ce6b4f09 100644 --- a/src/Frontend/src/stores/EnvironmentAndVersionsStore.ts +++ b/src/Frontend/src/stores/EnvironmentAndVersionsStore.ts @@ -17,6 +17,9 @@ export const useEnvironmentAndVersionsStore = defineStore("EnvironmentAndVersion is_compatible_with_sc: true, sp_version: window.defaultConfig && window.defaultConfig.version ? window.defaultConfig.version : "1.2.0", supportsArchiveGroups: false, + supportsUserPermissions: false, + mypermissions_all_url: "", + mypermissions_summary_url: "", endpoints_error_url: "", known_endpoints_url: "", endpoints_message_search_url: "", @@ -56,6 +59,9 @@ export const useEnvironmentAndVersionsStore = defineStore("EnvironmentAndVersion const [products, scVer] = await Promise.all([productsResult, scResult, mResult]); if (scVer) { environment.supportsArchiveGroups = !!scVer.archived_groups_url; + environment.supportsUserPermissions = !!scVer.mypermissions_all && !!scVer.mypermissions_summary; + environment.mypermissions_all_url = scVer.mypermissions_all ?? ""; + environment.mypermissions_summary_url = scVer.mypermissions_summary ?? ""; environment.is_compatible_with_sc = isSupported(environment.sc_version, environment.minimum_supported_sc_version); environment.endpoints_error_url = scVer && scVer.endpoints_error_url; environment.known_endpoints_url = scVer && scVer.known_endpoints_url; diff --git a/src/Frontend/src/stores/UserPermissionsStore.spec.ts b/src/Frontend/src/stores/UserPermissionsStore.spec.ts new file mode 100644 index 000000000..e73270c92 --- /dev/null +++ b/src/Frontend/src/stores/UserPermissionsStore.spec.ts @@ -0,0 +1,89 @@ +import { describe, test, expect, beforeEach, vi } from "vitest"; +import { createTestingPinia } from "@pinia/testing"; +import { setActivePinia } from "pinia"; + +vi.mock("@/components/serviceControlClient", () => ({ + default: { fetchTypedFromUrl: vi.fn() }, +})); + +import serviceControlClient from "@/components/serviceControlClient"; +import logger from "@/logger"; +import { useUserPermissionsStore } from "@/stores/UserPermissionsStore"; +import { useEnvironmentAndVersionsStore } from "@/stores/EnvironmentAndVersionsStore"; + +const summaryUrl = "http://localhost/my/permissions"; +const allUrl = "http://localhost/my/permissions/all"; + +const fetchTypedFromUrl = vi.mocked(serviceControlClient.fetchTypedFromUrl); + +const summaryData = { failed_messages_read: true, failed_messages_write: false, auditing_read: true, monitoring_read: false, monitoring_write: false, admin_read: false, admin_write: false }; +const descriptorData = { user: "alice", permissions: ["error:messages:view", "audit:view"] }; + +function ok(data: T): [Response, T] { + return [{} as Response, data]; +} + +function setup() { + setActivePinia(createTestingPinia({ stubActions: false })); + const environment = useEnvironmentAndVersionsStore().environment; + environment.mypermissions_summary_url = summaryUrl; + environment.mypermissions_all_url = allUrl; + return useUserPermissionsStore(); +} + +describe("UserPermissionsStore", () => { + beforeEach(() => { + fetchTypedFromUrl.mockReset(); + }); + + test("refresh fetches the discovered URLs and populates summary and descriptor", async () => { + const store = setup(); + fetchTypedFromUrl.mockImplementation((url: string) => Promise.resolve(url === summaryUrl ? ok(summaryData) : ok(descriptorData))); + + await store.refresh(); + + expect(fetchTypedFromUrl).toHaveBeenCalledWith(summaryUrl); + expect(fetchTypedFromUrl).toHaveBeenCalledWith(allUrl); + expect(store.summary).toEqual(summaryData); + expect(store.descriptor).toEqual(descriptorData); + expect(store.error).toBeNull(); + expect(store.loading).toBe(false); + }); + + test("refresh records an error and logs when a request fails", async () => { + const store = setup(); + const loggerError = vi.spyOn(logger, "error").mockImplementation(() => {}); + const failure = new Error("boom"); + fetchTypedFromUrl.mockRejectedValue(failure); + + await store.refresh(); + + expect(store.summary).toBeNull(); + expect(store.error).toBe("Failed to load user permissions"); + expect(store.loading).toBe(false); + expect(loggerError).toHaveBeenCalledWith("Failed to load user permissions", failure); + loggerError.mockRestore(); + }); + + test("concurrent refreshes share a single in-flight request", async () => { + const store = setup(); + let resolveSummary: (value: [Response, typeof summaryData]) => void = () => {}; + const pendingSummary = new Promise<[Response, typeof summaryData]>((resolve) => (resolveSummary = resolve)); + fetchTypedFromUrl.mockImplementation((url: string) => (url === summaryUrl ? pendingSummary : Promise.resolve(ok(descriptorData)))); + + const first = store.refresh(); + const second = store.refresh(); + + // One load in flight => two endpoint calls (summary + descriptor), not four. + expect(fetchTypedFromUrl).toHaveBeenCalledTimes(2); + + resolveSummary(ok(summaryData)); + await Promise.all([first, second]); + + expect(store.summary).toEqual(summaryData); + + // Once settled the in-flight slot is released, so a later refresh fetches again. + await store.refresh(); + expect(fetchTypedFromUrl).toHaveBeenCalledTimes(4); + }); +}); diff --git a/src/Frontend/src/stores/UserPermissionsStore.ts b/src/Frontend/src/stores/UserPermissionsStore.ts new file mode 100644 index 000000000..434adaa32 --- /dev/null +++ b/src/Frontend/src/stores/UserPermissionsStore.ts @@ -0,0 +1,63 @@ +import { acceptHMRUpdate, defineStore } from "pinia"; +import { ref } from "vue"; +import serviceControlClient from "@/components/serviceControlClient"; +import { useEnvironmentAndVersionsStore } from "@/stores/EnvironmentAndVersionsStore"; +import logger from "@/logger"; + +interface PermissionsSummary { + failed_messages_read: boolean; + failed_messages_write: boolean; + auditing_read: boolean; + monitoring_read: boolean; + monitoring_write: boolean; + admin_read: boolean; + admin_write: boolean; +} + +interface PermissionsDescriptor { + user: string; + permissions: string[]; +} + +export const useUserPermissionsStore = defineStore("UserPermissionsStore", () => { + const summary = ref(null); + const descriptor = ref(null); + const loading = ref(false); + const error = ref(null); + + // Multiple callers can request a refresh in the same tick (the App-level watch + // and the User Permissions view's onMounted). Share a single in-flight request + // so they don't trigger duplicate fetches. + let inFlight: Promise | null = null; + + function refresh() { + return (inFlight ??= load().finally(() => (inFlight = null))); + } + + async function load() { + loading.value = true; + error.value = null; + const { environment } = useEnvironmentAndVersionsStore(); + try { + const [summaryResult, descriptorResult] = await Promise.all([ + serviceControlClient.fetchTypedFromUrl(environment.mypermissions_summary_url), + serviceControlClient.fetchTypedFromUrl(environment.mypermissions_all_url), + ]); + summary.value = summaryResult[1]; + descriptor.value = descriptorResult[1]; + } catch (err) { + logger.error("Failed to load user permissions", err); + error.value = "Failed to load user permissions"; + } finally { + loading.value = false; + } + } + + return { summary, descriptor, loading, error, refresh }; +}); + +export type { PermissionsSummary, PermissionsDescriptor }; + +if (import.meta.hot) { + import.meta.hot.accept(acceptHMRUpdate(useUserPermissionsStore, import.meta.hot)); +} diff --git a/src/Frontend/src/views/ConfigurationView.vue b/src/Frontend/src/views/ConfigurationView.vue index ecb4b8e43..0dd4f7ea6 100644 --- a/src/Frontend/src/views/ConfigurationView.vue +++ b/src/Frontend/src/views/ConfigurationView.vue @@ -11,6 +11,9 @@ import useThroughputStoreAutoRefresh from "@/composables/useThroughputStoreAutoR import useConnectionsAndStatsAutoRefresh from "@/composables/useConnectionsAndStatsAutoRefresh"; import { useRedirectsStore } from "@/stores/RedirectsStore"; import { useLicenseStore } from "@/stores/LicenseStore"; +import { useAuthStore } from "@/stores/AuthStore"; +import usePermissionGate from "@/composables/usePermissionGate"; +import { useEnvironmentAndVersionsStore } from "@/stores/EnvironmentAndVersionsStore"; const { store: throughputStore } = useThroughputStoreAutoRefresh(); const { hasErrors } = storeToRefs(throughputStore); @@ -19,6 +22,11 @@ const connectionState = connectionStore.connectionState; const redirectsStore = useRedirectsStore(); const licenseStore = useLicenseStore(); const { licenseStatus } = licenseStore; +const authStore = useAuthStore(); +const environmentStore = useEnvironmentAndVersionsStore(); + +const { has } = usePermissionGate(); +const hasAdminRead = computed(() => has("admin_read")); onMounted(async () => { if (notConnected.value) { @@ -54,11 +62,12 @@ function preventIfDisabled(e: Event) {