diff --git a/package-lock.json b/package-lock.json index 32d44c5b60f90c73acae1ed9f152551b674e5851..fd24c5cef799c39a0dc0dabd5f374de1db99bc8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -211,16 +211,16 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-sdk/client-s3": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.504.0.tgz", - "integrity": "sha512-J8xPsnk7EDwalFSaDxPFNT2+x99nG2uQTpsLXAV3bWbT1nD/JZ+fase9GqxM11v6WngzqRvTQg26ljMn5hQSKA==", + "version": "3.507.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.507.0.tgz", + "integrity": "sha512-rRLiC5Ly3e7kZVNoRsG6JhZ8Yat5uEnDeShdWNdHchyTO88AaEnHaeyiVG9ecmKI8jYl6NbWSHB8xL0l9KIr/w==", "dependencies": { "@aws-crypto/sha1-browser": "3.0.0", "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.504.0", + "@aws-sdk/client-sts": "3.507.0", "@aws-sdk/core": "3.496.0", - "@aws-sdk/credential-provider-node": "3.504.0", + "@aws-sdk/credential-provider-node": "3.507.0", "@aws-sdk/middleware-bucket-endpoint": "3.502.0", "@aws-sdk/middleware-expect-continue": "3.502.0", "@aws-sdk/middleware-flexible-checksums": "3.502.0", @@ -279,9 +279,9 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.502.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.502.0.tgz", - "integrity": "sha512-OZAYal1+PQgUUtWiHhRayDtX0OD+XpXHKAhjYgEIPbyhQaCMp3/Bq1xDX151piWXvXqXLJHFKb8DUEqzwGO9QA==", + "version": "3.507.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.507.0.tgz", + "integrity": "sha512-pFeaKwqv4tXD6QVxWC2V4N62DUoP3bPSm/mCe2SPhaNjNsmwwA53viUHz/nwxIbs8w4vV44UQsygb0AgKm+HoQ==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -326,13 +326,13 @@ } }, "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.504.0.tgz", - "integrity": "sha512-ODA33/nm2srhV08EW0KZAP577UgV0qjyr7Xp2yEo8MXWL4ZqQZprk1c+QKBhjr4Djesrm0VPmSD/np0mtYP68A==", + "version": "3.507.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.507.0.tgz", + "integrity": "sha512-ms5CH2ImhqqCIbo5irxayByuPOlVAmSiqDVfjZKwgIziqng2bVgNZMeKcT6t0bmrcgScEAVnZwY7j/iZTIw73g==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.504.0", + "@aws-sdk/client-sts": "3.507.0", "@aws-sdk/core": "3.496.0", "@aws-sdk/middleware-host-header": "3.502.0", "@aws-sdk/middleware-logger": "3.502.0", @@ -374,13 +374,13 @@ "node": ">=14.0.0" }, "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.504.0" + "@aws-sdk/credential-provider-node": "^3.507.0" } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.504.0.tgz", - "integrity": "sha512-IESs8FkL7B/uY+ml4wgoRkrr6xYo4PizcNw6JX17eveq1gRBCPKeGMjE6HTDOcIYZZ8rqz/UeuH3JD4UhrMOnA==", + "version": "3.507.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.507.0.tgz", + "integrity": "sha512-TOWBe0ApEh32QOib0R+irWGjd1F9wnhbGV5PcB9SakyRwvqwG5MKOfYxG7ocoDqLlaRwzZMidcy/PV8/OEVNKg==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", @@ -426,7 +426,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "@aws-sdk/credential-provider-node": "^3.504.0" + "@aws-sdk/credential-provider-node": "^3.507.0" } }, "node_modules/@aws-sdk/core": { @@ -479,15 +479,15 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.504.0.tgz", - "integrity": "sha512-ODICLXfr8xTUd3wweprH32Ge41yuBa+u3j0JUcLdTUO1N9ldczSMdo8zOPlP0z4doqD3xbnqMkjNQWgN/Q+5oQ==", + "version": "3.507.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.507.0.tgz", + "integrity": "sha512-2CnyduoR9COgd7qH1LPYK8UggGqVs8R4ASDMB5bwGxbg9ZerlStDiHpqvJNNg1k+VlejBr++utxfmHd236XgmQ==", "dependencies": { - "@aws-sdk/client-sts": "3.504.0", + "@aws-sdk/client-sts": "3.507.0", "@aws-sdk/credential-provider-env": "3.502.0", "@aws-sdk/credential-provider-process": "3.502.0", - "@aws-sdk/credential-provider-sso": "3.504.0", - "@aws-sdk/credential-provider-web-identity": "3.504.0", + "@aws-sdk/credential-provider-sso": "3.507.0", + "@aws-sdk/credential-provider-web-identity": "3.507.0", "@aws-sdk/types": "3.502.0", "@smithy/credential-provider-imds": "^2.2.1", "@smithy/property-provider": "^2.1.1", @@ -500,16 +500,16 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.504.0.tgz", - "integrity": "sha512-6+V5hIh+tILmUjf2ZQWQINR3atxQVgH/bFrGdSR/sHSp/tEgw3m0xWL3IRslWU1e4/GtXrfg1iYnMknXy68Ikw==", + "version": "3.507.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.507.0.tgz", + "integrity": "sha512-tkQnmOLkRBXfMLgDYHzogrqTNdtl0Im0ipzJb2IV5hfM5NoTfCf795e9A9isgwjSP/g/YEU0xQWxa4lq8LRtuA==", "dependencies": { "@aws-sdk/credential-provider-env": "3.502.0", "@aws-sdk/credential-provider-http": "3.503.1", - "@aws-sdk/credential-provider-ini": "3.504.0", + "@aws-sdk/credential-provider-ini": "3.507.0", "@aws-sdk/credential-provider-process": "3.502.0", - "@aws-sdk/credential-provider-sso": "3.504.0", - "@aws-sdk/credential-provider-web-identity": "3.504.0", + "@aws-sdk/credential-provider-sso": "3.507.0", + "@aws-sdk/credential-provider-web-identity": "3.507.0", "@aws-sdk/types": "3.502.0", "@smithy/credential-provider-imds": "^2.2.1", "@smithy/property-provider": "^2.1.1", @@ -537,12 +537,12 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.504.0.tgz", - "integrity": "sha512-4MgH2or2SjPzaxM08DCW+BjaX4DSsEGJlicHKmz6fh+w9JmLh750oXcTnbvgUeVz075jcs6qTKjvUcsdGM/t8Q==", + "version": "3.507.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.507.0.tgz", + "integrity": "sha512-6WBjou52QukFpDi4ezb19bcAx/bM8ge8qnJnRT02WVRmU6zFQ5yLD2fW1MFsbX3cwbey+wSqKd5FGE1Hukd5wQ==", "dependencies": { - "@aws-sdk/client-sso": "3.502.0", - "@aws-sdk/token-providers": "3.504.0", + "@aws-sdk/client-sso": "3.507.0", + "@aws-sdk/token-providers": "3.507.0", "@aws-sdk/types": "3.502.0", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", @@ -554,11 +554,11 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.504.0.tgz", - "integrity": "sha512-L1ljCvGpIEFdJk087ijf2ohg7HBclOeB1UgBxUBBzf4iPRZTQzd2chGaKj0hm2VVaXz7nglswJeURH5PFcS5oA==", + "version": "3.507.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.507.0.tgz", + "integrity": "sha512-f+aGMfazBimX7S06224JRYzGTaMh1uIhfj23tZylPJ05KxTVi5IO1RoqeI/uHLJ+bDOx+JHBC04g/oCdO4kHvw==", "dependencies": { - "@aws-sdk/client-sts": "3.504.0", + "@aws-sdk/client-sts": "3.507.0", "@aws-sdk/types": "3.502.0", "@smithy/property-provider": "^2.1.1", "@smithy/types": "^2.9.1", @@ -569,9 +569,9 @@ } }, "node_modules/@aws-sdk/lib-storage": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.504.0.tgz", - "integrity": "sha512-A2h/yHy+2JFhqiCL1vfSlKxLRIZyyQte58O8s0yAV/TDt7ElzeXMTVtCUvhcOrnjtdHKfh4F36jeZSh1ja/9HA==", + "version": "3.507.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.507.0.tgz", + "integrity": "sha512-cwCBGw6W71d0PrFSRizBd7WdyzsZnRWVftnOvNkyjIE5vFifdFbpdVaFidMVUZmvUQvS3Vgkx1LeXPT98EBWUQ==", "dependencies": { "@smithy/abort-controller": "^2.1.1", "@smithy/middleware-endpoint": "^2.4.1", @@ -772,9 +772,9 @@ } }, "node_modules/@aws-sdk/s3-request-presigner": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.504.0.tgz", - "integrity": "sha512-5FxVdRufiFLSUDJ/Qul5JFPHjhFFzo+C6u53bzbi7gaSshA6lLLhJ9KbVk2LmKE1mTR+nh2+JebI6y+3njtkzw==", + "version": "3.507.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.507.0.tgz", + "integrity": "sha512-A3EGvXMeOvnG+qtAsmlcQyLP7+PlCePS+PsVqLm3Pz3C16avOTxTqOZIkYCqBBX2fnASr2qUr0d3cezBfsU7PQ==", "dependencies": { "@aws-sdk/signature-v4-multi-region": "3.502.0", "@aws-sdk/types": "3.502.0", @@ -806,11 +806,11 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.504.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.504.0.tgz", - "integrity": "sha512-YIJWWsZi2ClUiILS1uh5L6VjmCUSTI6KKMuL9DkGjYqJ0aI6M8bd8fT9Wm7QmXCyjcArTgr/Atkhia4T7oKvzQ==", + "version": "3.507.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.507.0.tgz", + "integrity": "sha512-ehOINGjoGJc6Puzon7ev4bXckkaZx18WNgMTNttYJhj3vTpj5LPSQbI/5SS927bEbpGMFz1+hJ6Ra5WGfbTcEQ==", "dependencies": { - "@aws-sdk/client-sso-oidc": "3.504.0", + "@aws-sdk/client-sso-oidc": "3.507.0", "@aws-sdk/types": "3.502.0", "@smithy/property-provider": "^2.1.1", "@smithy/shared-ini-file-loader": "^2.3.1", @@ -2719,9 +2719,9 @@ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, "node_modules/@vue/compiler-sfc/node_modules/magic-string": { - "version": "0.30.6", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.6.tgz", - "integrity": "sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==", + "version": "0.30.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", + "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, @@ -2960,13 +2960,16 @@ "dev": true }, "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2982,17 +2985,18 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", - "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" }, "engines": { @@ -3126,14 +3130,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz", + "integrity": "sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg==", "dev": true, "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.3", + "set-function-length": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3354,14 +3362,15 @@ "dev": true }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.2.tgz", + "integrity": "sha512-SRtsSqsDbgpJBbW3pABMCOt6rQyeM8s8RiyeSN8jYG8sYmt/kGJejbydttUsnDs1tadr19tvhT4ShwMyoqAm4g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.2", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -3495,6 +3504,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-set-tostringtag": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", @@ -3930,9 +3948,9 @@ } }, "node_modules/fastq": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz", - "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -4120,28 +4138,32 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.1.tgz", + "integrity": "sha512-KmuibvwbWaM4BHcBRYwJfZ1JxyJeBwB8ct9YYu67SvYdbEIlcQ2e56dHxfbobqW38GXo8/zDFqJeGtHiVbWyQw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -4470,12 +4492,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/internal-slot": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", - "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" }, @@ -4484,14 +4506,16 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5483,9 +5507,9 @@ } }, "node_modules/postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.34", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.34.tgz", + "integrity": "sha512-4eLTO36woPSocqZ1zIrFD2K1v6wH7pY1uBh0JIM2KKfrVtGvPFiAku6aNOP0W1Wr9qwnaCsF0Z+CrVnryB2A8Q==", "funding": [ { "type": "opencollective", @@ -5532,9 +5556,9 @@ } }, "node_modules/prettier": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", - "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" diff --git a/src/App.vue b/src/App.vue index 5d537e2a7205226dccc4df5e617a691914a32ed8..1ae0d2eb5e6ada407cac55d717e85244856e08db 100644 --- a/src/App.vue +++ b/src/App.vue @@ -38,7 +38,8 @@ onBeforeMount(() => { query: { login_error: err.response.status === 400 ? "token_invalid" : "token_expired", - return_path: encodeURI(route.path), + return_path: + route.name != "login" ? encodeURI(route.path) : undefined, }, }); } diff --git a/src/components/modals/BootstrapModal.vue b/src/components/modals/BootstrapModal.vue index bb4940543ce0dbab9669e28f299d0a443ab74989..7f3a55ec324e5f73e427cdde92a120ba705de49d 100644 --- a/src/components/modals/BootstrapModal.vue +++ b/src/components/modals/BootstrapModal.vue @@ -5,9 +5,11 @@ const props = defineProps<{ modalId: string; modalLabel: string; staticBackdrop?: boolean; - sizeModifier?: string; // https://getbootstrap.com/docs/5.3/components/modal/#optional-sizes, e.g. sm, lg and xl + sizeModifier?: sizeModifierType; // https://getbootstrap.com/docs/5.3/components/modal/#optional-sizes, e.g. sm, lg and xl }>(); +type sizeModifierType = "sm" | "lg" | "xl"; + const modalSizeClass = computed<string>(() => { if (props.sizeModifier == undefined) { return ""; diff --git a/src/components/object-storage/modals/UploadObjectModal.vue b/src/components/object-storage/modals/UploadObjectModal.vue index 331fbf62a255527b1e1220c4d4b9db089eb554c6..f317f780615a2dd905f952431514ba75af8fe0e4 100644 --- a/src/components/object-storage/modals/UploadObjectModal.vue +++ b/src/components/object-storage/modals/UploadObjectModal.vue @@ -117,7 +117,7 @@ onMounted(() => { :static-backdrop="true" modal-label="Upload Object Modal" > - <template v-slot:header> + <template #header> <h4>Upload file to</h4> <ol class="breadcrumb"> <li class="breadcrumb-item">{{ props.bucketName }}</li> @@ -130,7 +130,7 @@ onMounted(() => { </li> </ol> </template> - <template v-slot:body> + <template #body> <div class="container-fluid"> <div class="row"> <form diff --git a/src/components/parameter-schema/ParameterSchemaFormComponent.vue b/src/components/parameter-schema/ParameterSchemaFormComponent.vue index 53e57e0d7e93b8eb615f6c171b62f16ae7c1b336..4a93a52f9669beb1b047b1a609907103feec4405 100644 --- a/src/components/parameter-schema/ParameterSchemaFormComponent.vue +++ b/src/components/parameter-schema/ParameterSchemaFormComponent.vue @@ -5,17 +5,24 @@ import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import Ajv from "ajv"; import type { ValidateFunction } from "ajv"; import ParameterStringInput from "@/components/parameter-schema/form-mode/ParameterStringInput.vue"; -import { Toast } from "bootstrap"; +import { Toast, Tooltip } from "bootstrap"; import { useBucketStore } from "@/stores/buckets"; import { useS3KeyStore } from "@/stores/s3keys"; import BootstrapToast from "@/components/BootstrapToast.vue"; import { useResourceStore } from "@/stores/resources"; import { useRoute, useRouter } from "vue-router"; import type { ClowmInfo } from "@/types/ClowmInfo"; +import UploadParameterFileModal from "@/components/parameter-schema/UploadParameterFileModal.vue"; +import type { + TemporaryParams, + WorkflowParameters, +} from "@/types/WorkflowParameters"; +import { useWorkflowExecutionStore } from "@/stores/workflowExecutions"; const bucketRepository = useBucketStore(); const resourceRepository = useResourceStore(); const keyRepository = useS3KeyStore(); +const executionRepository = useWorkflowExecutionStore(); const router = useRouter(); const route = useRoute(); @@ -47,8 +54,7 @@ const props = defineProps({ const emit = defineEmits<{ ( e: "start-workflow", - // eslint-disable-next-line @typescript-eslint/no-explicit-any - parameters: Record<string, any>, + parameters: WorkflowParameters, notes?: string, logs_s3_path?: string, debug_s3_path?: string, @@ -59,6 +65,7 @@ const emit = defineEmits<{ // Bootstrap Elements // ============================================================================= let errorToast: Toast | null = null; +let parameterLoadToast: Toast | null = null; // Types // ============================================================================= @@ -81,8 +88,7 @@ let validateSchema: ValidateFunction; const launchForm = ref<HTMLFormElement | null>(null); const formState = reactive<{ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - formInput: Record<string, any>; + formInput: WorkflowParameters; validated: boolean; pipelineNotes: string; logs_s3_path?: string; @@ -156,8 +162,7 @@ watch( /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unused-vars */ function updateSchema(schema: Record<string, any>) { validateSchema = schemaCompiler.compile(schema); - const b = Object.keys(schema["definitions"]).map((groupName) => [ - groupName, + const b = Object.keys(schema["definitions"]).map((groupName) => Object.fromEntries( Object.entries(schema["definitions"][groupName]["properties"]).map( ([parameterName, parameter]) => [ @@ -167,22 +172,19 @@ function updateSchema(schema: Record<string, any>) { ], ), ), - ]); - formState.formInput = Object.fromEntries(b); + ); + formState.formInput = b.reduce((acc, val) => { + return { ...acc, ...val }; + }); + loadParameters(executionRepository.popTemporaryParameters()); } -/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/ban-ts-comment, @typescript-eslint/no-unused-vars */ function startWorkflow() { errorToast?.hide(); formState.validated = true; formState.errorType = undefined; if (launchForm.value?.checkValidity()) { - const realInput: Record<string, any> = Object.values( - formState.formInput, - ).reduce((acc, val) => { - return { ...acc, ...val }; - }); - const schemaValid = validateSchema(realInput); + const schemaValid = validateSchema(formState.formInput); if (!schemaValid) { console.error(validateSchema.errors); @@ -190,7 +192,7 @@ function startWorkflow() { } else { emit( "start-workflow", - realInput, + formState.formInput, formState.pipelineNotes, formState.logs_s3_path, formState.debug_s3_path, @@ -203,6 +205,23 @@ function startWorkflow() { } } +function loadParameters(tempParams?: TemporaryParams) { + if (tempParams) { + for (const param in tempParams.params) { + if (param in formState.formInput) { + formState.formInput[param] = tempParams.params[param]; + } + } + formState.pipelineNotes = tempParams.metaParams.notes ?? ""; + formState.logs_s3_path = tempParams.metaParams.logs_s3_path; + formState.provenance_s3_path = tempParams.metaParams.provenance_s3_path; + formState.debug_s3_path = tempParams.metaParams.debug_s3_path; + if (Object.keys(tempParams?.params ?? {}).length > 0) { + parameterLoadToast?.show(); + } + } +} + function scroll(selectedAnchor: string) { document.querySelector(selectedAnchor)?.scrollIntoView({ behavior: "smooth", @@ -213,15 +232,23 @@ function scroll(selectedAnchor: string) { // ============================================================================= onMounted(() => { if (props.schema) updateSchema(props.schema); + if (props.clowmInfo?.exampleParameters) Tooltip.getOrCreateInstance("#exampleDataButton"); bucketRepository.fetchBuckets(); bucketRepository.fetchOwnPermissions(); keyRepository.fetchS3Keys(); resourceRepository.fetchPublicResources(); errorToast = new Toast("#workflowExecutionErrorToast"); + parameterLoadToast = new Toast("#workflowExecutionParameterLoadToast"); }); </script> <template> + <bootstrap-toast + toast-id="workflowExecutionParameterLoadToast" + color-class="success" + > + Successfully loaded parameters + </bootstrap-toast> <bootstrap-toast toast-id="workflowExecutionErrorToast" color-class="danger"> <template #default>Error starting workflow</template> <template #body> @@ -234,6 +261,16 @@ onMounted(() => { </template> </template> </bootstrap-toast> + <upload-parameter-file-modal + modal-id="parameterUploadModal" + @parameters-uploaded=" + (params: WorkflowParameters) => + loadParameters({ + params: params, + metaParams: {}, + }) + " + /> <div class="row mb-5 align-items-start"> <form v-if="props.schema" @@ -246,11 +283,9 @@ onMounted(() => { > <template v-for="(group, groupName) in parameterGroups" :key="groupName"> <parameter-group-form - :modelValue="formState.formInput[groupName]" - @update:model-value=" - (newValue) => (formState.formInput[groupName] = newValue) - " - v-if="formState.formInput[groupName]" + :modelValue="formState.formInput" + @update:model-value="(newValue) => (formState.formInput = newValue)" + v-if="formState.formInput" :parameter-group-name="groupName" :parameter-group="group" :showHidden="showHidden" @@ -397,7 +432,7 @@ onMounted(() => { > <input type="radio" - class="btn-check w-100" + class="btn-check" name="view-mode" id="view-mode-simple" autocomplete="off" @@ -490,11 +525,35 @@ onMounted(() => { ></span> </div> </nav> - <div class="d-grid gap-2 mb-2"> + <div class="d-grid gap-2 mb-2 px-2"> + <button + type="button" + class="btn btn-primary" + v-if="props.clowmInfo?.exampleParameters" + data-bs-toggle="tooltip" + id="exampleDataButton" + data-bs-title="Load example parameters/data for this workflow" + @click=" + loadParameters({ + params: props.clowmInfo?.exampleParameters, + metaParams: {}, + }) + " + > + Try it out + </button> + <button + class="btn btn-primary" + data-bs-toggle="modal" + data-bs-target="#parameterUploadModal" + > + <font-awesome-icon icon="fa-solid fa-upload" class="me-2" /> + Upload Parameters + </button> <button type="submit" form="launchWorkflowForm" - class="btn btn-success btn-lg mx-2" + class="btn btn-success btn-lg" :disabled="props.loading || !props.schema" > <font-awesome-icon icon="fa-solid fa-play" class="me-2" /> @@ -505,8 +564,9 @@ onMounted(() => { </div> </template> -<style scoped> -div.card-body { - backdrop-filter: brightness(1.2); +<style> +.was-validated *:invalid { + border-color: var(--bs-form-invalid-border-color) !important; + background: var(--bs-danger-bg-subtle) !important; } </style> diff --git a/src/components/parameter-schema/UploadParameterFileModal.vue b/src/components/parameter-schema/UploadParameterFileModal.vue new file mode 100644 index 0000000000000000000000000000000000000000..fef305d95d3697ed1b9c6f87aa24331847c746a5 --- /dev/null +++ b/src/components/parameter-schema/UploadParameterFileModal.vue @@ -0,0 +1,121 @@ +<script setup lang="ts"> +import BootstrapModal from "@/components/modals/BootstrapModal.vue"; +import { reactive, ref } from "vue"; +import type { WorkflowParameters } from "@/types/WorkflowParameters"; + +const props = defineProps<{ + modalId: string; +}>(); + +const parameterState = reactive<{ + params?: Record<string, string | number | boolean | undefined>; + error?: string; +}>({ + params: undefined, + error: undefined, +}); + +const randomIDSuffix = Math.random().toString(16).substring(2, 8); +const parameterFileInput = ref<HTMLInputElement | undefined>(undefined); + +const emit = defineEmits<{ + (e: "parameters-uploaded", params: WorkflowParameters): void; +}>(); + +function emitParameters() { + if (parameterState.params != undefined) { + emit("parameters-uploaded", parameterState.params); + parameterState.params = undefined; + if (parameterFileInput.value != undefined) { + parameterFileInput.value.value = ""; + } + } +} + +function fileChange() { + if (!parameterFileInput.value?.value?.length) return; + parameterFileInput.value?.files?.[0].text().then((str) => { + try { + parameterState.params = JSON.parse(str); + parameterState.error = undefined; + } catch (err) { + parameterState.params = undefined; + if (typeof err === "string") { + parameterState.error = err; + } else if (err instanceof Error) { + parameterState.error = err.message; + } else { + parameterState.error = "unknown error"; + } + if (parameterFileInput.value != undefined) { + parameterFileInput.value.value = ""; + } + } + }); +} +</script> + +<template> + <bootstrap-modal + :modalId="props.modalId" + :static-backdrop="true" + modal-label="Confirm Delete Modal" + size-modifier="lg" + > + <template #header>Upload Parameter File</template> + <template #body> + <label :for="'parameter-upload-input' + randomIDSuffix" class="form-label" + >Parameter File</label + > + <input + class="form-control mb-3" + type="file" + :id="'parameter-upload-input' + randomIDSuffix" + accept="application/json" + @change="fileChange" + ref="parameterFileInput" + /> + <template v-if="parameterState.error"> + <h5 class="text-danger">Error parsing JSON</h5> + <p class="text-danger">{{ parameterState.error }}</p> + </template> + <template v-else-if="parameterState.params"> + <h5>Uploaded Parameters:</h5> + <table class="table table-bordered"> + <caption> + {{ + Object.keys(parameterState.params).length + }} + Parameters + </caption> + <tbody> + <tr v-for="(value, name) in parameterState.params" :key="name"> + <th scope="row" style="width: 10%" class="text-end"> + <b>{{ name }}</b> + </th> + <td> + {{ value }} + </td> + </tr> + </tbody> + </table> + </template> + </template> + <template #footer> + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> + Close + </button> + <button + :disabled="parameterState.params == undefined" + type="button" + class="btn btn-primary" + data-bs-dismiss="modal" + @click="emitParameters" + > + Upload + </button> + </template> + </bootstrap-modal> +</template> + +<style scoped></style> diff --git a/src/components/parameter-schema/form-mode/ParameterBooleanInput.vue b/src/components/parameter-schema/form-mode/ParameterBooleanInput.vue index dbcbe47aa698410732366f1d3b5175054ddf3d8f..a30bd4eea2d05d58c521da97bb481b3c789a084d 100644 --- a/src/components/parameter-schema/form-mode/ParameterBooleanInput.vue +++ b/src/components/parameter-schema/form-mode/ParameterBooleanInput.vue @@ -26,9 +26,6 @@ const randomIDSuffix = Math.random().toString(16).substring(2, 8); const helpTextPresent = computed<boolean>(() => props.parameter["help_text"]); const iconPresent = computed<boolean>(() => props.parameter["fa_icon"]); -const defaultValue = computed<boolean>( - () => props.parameter["default"] ?? false, -); const emit = defineEmits<{ (e: "update:modelValue", value: boolean): void; @@ -50,7 +47,7 @@ const emit = defineEmits<{ :name="'inlineRadioOptions' + randomIDSuffix" :id="'trueOption' + randomIDSuffix" :value="true" - :checked="defaultValue" + :checked="props.modelValue" @input="emit('update:modelValue', true)" /> </div> @@ -62,7 +59,7 @@ const emit = defineEmits<{ :id="'falseOption' + randomIDSuffix" :value="false" @input="emit('update:modelValue', false)" - :checked="!defaultValue" + :checked="!props.modelValue" /> <label class="form-check-label" :for="'falseOption' + randomIDSuffix" >False</label diff --git a/src/components/parameter-schema/form-mode/ParameterEnumInput.vue b/src/components/parameter-schema/form-mode/ParameterEnumInput.vue index 44ce05cbe660c313e30eb7fc82a562cb77897dc9..03ec622af5cf5a20a118936cbe3718a5645531ce 100644 --- a/src/components/parameter-schema/form-mode/ParameterEnumInput.vue +++ b/src/components/parameter-schema/form-mode/ParameterEnumInput.vue @@ -22,8 +22,6 @@ const props = defineProps({ }, }); -const defaultValue = computed<string>(() => props.parameter["default"]); - const possibleValues = computed<string[]>(() => props.parameter["enum"]); const enumSelection = ref<HTMLSelectElement | undefined>(undefined); @@ -49,7 +47,7 @@ function updateValue() { <option v-for="val in possibleValues" :key="val" - :selected="defaultValue === val" + :selected="props.modelValue === val" > {{ val }} </option> diff --git a/src/components/parameter-schema/form-mode/ParameterGroupForm.vue b/src/components/parameter-schema/form-mode/ParameterGroupForm.vue index 95d09663bf78ba27591046b2398511b9a8a73c80..be65227cd15dd3b2699a1b7ef1010b5509123e95 100644 --- a/src/components/parameter-schema/form-mode/ParameterGroupForm.vue +++ b/src/components/parameter-schema/form-mode/ParameterGroupForm.vue @@ -106,19 +106,12 @@ watch( " > <code - class="p-2 rounded-top border-bottom-0 border bg-secondary-subtle" - :class="{ - 'border-warning': parameterRequired( - parameterGroup, - parameterName, - ), - 'border-secondary': !parameterRequired( - parameterGroup, - parameterName, - ), - 'border-2': parameterRequired(parameterGroup, parameterName), - }" + class="p-2 rounded-top border-bottom-0 border bg-secondary-subtle border-secondary" >--{{ parameter["name"] ?? parameterName }}</code + ><span + v-if="parameterRequired(parameterGroup, parameterName)" + class="rounded p-1 bg-warning text-light ms-2" + >required</span > <div class="input-group"> <span diff --git a/src/components/parameter-schema/form-mode/ParameterStringInput.vue b/src/components/parameter-schema/form-mode/ParameterStringInput.vue index af8713531998330ed876c919451e8b00885f107c..a41cc04f692ad9733e06bba04c1fa0ae2697c061 100644 --- a/src/components/parameter-schema/form-mode/ParameterStringInput.vue +++ b/src/components/parameter-schema/form-mode/ParameterStringInput.vue @@ -1,5 +1,5 @@ <script setup lang="ts"> -import { computed, watch, onMounted, reactive } from "vue"; +import { computed, watch, onMounted, reactive, ref } from "vue"; import { useBucketStore } from "@/stores/buckets"; import { useS3ObjectStore } from "@/stores/s3objects"; import { useResourceStore } from "@/stores/resources"; @@ -67,6 +67,7 @@ const formState = reactive<{ stringVal: undefined, }); +const stringInput = ref<HTMLInputElement | undefined>(undefined); const pattern = computed<string>(() => props.parameter["pattern"]); const dataFormat = computed<string | undefined>( () => props.parameter["format"], @@ -137,6 +138,16 @@ watch(selectedResource, () => { } }); +watch( + () => props.modelValue, + (newVal) => { + if (formState.stringVal != newVal) { + formState.stringVal = newVal; + formState.advancedInput = true; + } + }, +); + watch( () => formState.advancedInput, (newVal, oldValue) => { diff --git a/src/components/workflows/modals/ParameterModal.vue b/src/components/workflows/modals/ParameterModal.vue index fed99a85f691de4aac7032244b223bb1f9ea6001..31c33ca166d72d5ff9c0da296f864cf86456b0ec 100644 --- a/src/components/workflows/modals/ParameterModal.vue +++ b/src/components/workflows/modals/ParameterModal.vue @@ -2,15 +2,18 @@ import BootstrapModal from "@/components/modals/BootstrapModal.vue"; import { computed, onMounted, reactive, watch } from "vue"; import { useWorkflowExecutionStore } from "@/stores/workflowExecutions"; -import type { RouteParamsRaw } from "vue-router"; +import type { RouteLocationRaw, RouteParamsRaw } from "vue-router"; import { Modal } from "bootstrap"; import { useRouter } from "vue-router"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; -import type { WorkflowExecutionOut } from "@/client/workflow"; +import type { WorkflowExecutionOut, WorkflowVersion } from "@/client/workflow"; import { useNameStore } from "@/stores/names"; +import { useWorkflowStore } from "@/stores/workflows"; +import type { WorkflowParameters } from "@/types/WorkflowParameters"; const nameRepository = useNameStore(); const executionRepository = useWorkflowExecutionStore(); +const workflowRepository = useWorkflowStore(); const router = useRouter(); let parameterModal: Modal | null = null; @@ -35,6 +38,24 @@ const execution = computed<WorkflowExecutionOut | undefined>(() => { return executionRepository.executionMapping[props.executionId] ?? undefined; }); +const workflowVersion = computed<WorkflowVersion | undefined>( + () => + workflowRepository.versionMapping[ + execution.value?.workflow_version_id ?? "" + ], +); + +watch(execution, (newVal, oldVal) => { + if ( + newVal != undefined && + newVal.execution_id != oldVal?.execution_id && + newVal.workflow_id != undefined && + workflowRepository.workflowMapping[newVal.workflow_id] == undefined + ) { + workflowRepository.fetchWorkflow(newVal.workflow_id); + } +}); + const logs_s3_path = computed<string | undefined>( () => execution.value?.logs_s3_path ?? undefined, ); @@ -44,9 +65,11 @@ const debug_s3_path = computed<string | undefined>( const provenance_s3_path = computed<string | undefined>( () => execution.value?.provenance_s3_path ?? undefined, ); +const notes = computed<string | undefined>( + () => execution.value?.notes ?? undefined, +); -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const parameters = computed<Record<string, any> | undefined>(() => { +const parameters = computed<WorkflowParameters | undefined>(() => { if (props.executionId == undefined) { return undefined; } @@ -95,12 +118,23 @@ function fetchWorkflowExecutionParameters(executionId?: string) { } } -function handleBucketLinkClick(s3String: string) { +function handleLinkClick(route: RouteLocationRaw) { parameterModal?.hide(); - router.push({ - name: "bucket", - params: getS3LinkParameters(s3String), - }); + router.push(route); +} + +function saveParameters() { + if (parameters.value != undefined) { + executionRepository.pushTemporaryParameters(parameters.value, { + logs_s3_path: logs_s3_path?.value?.split("/")?.slice(0, -1)?.join("/"), + debug_s3_path: debug_s3_path?.value?.split("/")?.slice(0, -1)?.join("/"), + provenance_s3_path: provenance_s3_path?.value + ?.split("/") + ?.slice(0, -1) + ?.join("/"), + notes: notes.value, + }); + } } function getS3LinkParameters(s3String: string): RouteParamsRaw { @@ -142,6 +176,7 @@ onMounted(() => { :modalId="modalID" :static-backdrop="false" modal-label="Workflow Execution Parameters Modal" + size-modifier="lg" > <template v-slot:header >Workflow Execution Parameters @@ -198,7 +233,12 @@ onMounted(() => { name: 'bucket', params: getS3LinkParameters(logs_s3_path), }" - @click.prevent="handleBucketLinkClick(logs_s3_path)" + @click.prevent=" + handleLinkClick({ + name: 'bucket', + params: getS3LinkParameters(logs_s3_path), + }) + " >{{ logs_s3_path }} </router-link> </td> @@ -218,7 +258,12 @@ onMounted(() => { name: 'bucket', params: getS3LinkParameters(provenance_s3_path), }" - @click.prevent="handleBucketLinkClick(provenance_s3_path)" + @click.prevent=" + handleLinkClick({ + name: 'bucket', + params: getS3LinkParameters(provenance_s3_path), + }) + " >{{ provenance_s3_path }} </router-link> </td> @@ -233,7 +278,12 @@ onMounted(() => { name: 'bucket', params: getS3LinkParameters(debug_s3_path), }" - @click.prevent="handleBucketLinkClick(debug_s3_path)" + @click.prevent=" + handleLinkClick({ + name: 'bucket', + params: getS3LinkParameters(debug_s3_path), + }) + " >{{ debug_s3_path }} </router-link> </td> @@ -244,14 +294,31 @@ onMounted(() => { </th> <td> <router-link - v-if="isBucketLink(value)" + v-if="typeof value === 'string' && isBucketLink(value)" :to="{ name: 'bucket', params: getS3LinkParameters(value), }" - @click.prevent="handleBucketLinkClick(value)" + @click.prevent=" + handleLinkClick({ + name: 'bucket', + params: getS3LinkParameters(value), + }) + " >{{ value }} </router-link> + <a + v-else-if=" + typeof value === 'string' && value.startsWith('http') + " + :href="value" + target="_blank" + >{{ value }} + <font-awesome-icon + icon="fa-solid fa-arrow-up-right-from-square" + class="ms-1" + /> + </a> <template v-else>{{ value }}</template> </td> </tr> @@ -260,6 +327,39 @@ onMounted(() => { </table> </template> <template v-slot:footer> + <router-link + v-if="workflowVersion" + class="btn btn-primary" + :class="{ + disabled: parameterState.loading || parameterState.error, + }" + @click.prevent=" + saveParameters(); + handleLinkClick({ + name: 'workflow-start', + params: { + versionId: workflowVersion.workflow_version_id, + workflowId: workflowVersion.workflow_id, + }, + query: { + workflowModeId: execution?.mode_id, + }, + }); + " + :to="{ + name: 'workflow-start', + params: { + versionId: workflowVersion.workflow_version_id, + workflowId: workflowVersion.workflow_id, + }, + query: { + workflowModeId: execution?.mode_id, + }, + }" + > + Rerun Execution + <font-awesome-icon icon="fa-solid fa-arrow-rotate-right" class="me2" /> + </router-link> <a class="btn btn-primary" role="button" @@ -274,8 +374,8 @@ onMounted(() => { :aria-disabled="parameterState.loading || parameterState.error" > Download Parameters - <font-awesome-icon icon="fa-solid fa-download" class="ms-1" - /></a> + <font-awesome-icon icon="fa-solid fa-download" class="ms-1" /> + </a> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> Close </button> diff --git a/src/stores/workflowExecutions.ts b/src/stores/workflowExecutions.ts index 9633272e7dac04c2705022eea3cc81a7368638d3..e90bd1d1bb182099af42944841287ba762f5c3ce 100644 --- a/src/stores/workflowExecutions.ts +++ b/src/stores/workflowExecutions.ts @@ -13,6 +13,11 @@ import { import { useAuthStore } from "@/stores/users"; import dayjs from "dayjs"; import { set, get } from "idb-keyval"; +import type { + WorkflowParameters, + WorkflowMetaParameters, + TemporaryParams, +} from "@/types/WorkflowParameters"; export const useWorkflowExecutionStore = defineStore({ id: "workflow-executions", @@ -21,11 +26,14 @@ export const useWorkflowExecutionStore = defineStore({ executionMapping: {}, anonymizedExecutions: [], parameters: {}, + __temporaryParameters: undefined, + __temporaryMetaParameters: undefined, }) as { executionMapping: Record<string, WorkflowExecutionOut>; anonymizedExecutions: AnonymizedWorkflowExecution[]; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - parameters: Record<string, Record<string, any>>; + parameters: Record<string, WorkflowParameters>; + __temporaryParameters?: WorkflowParameters; + __temporaryMetaParameters?: Record<string, string>; }, getters: { executions(): WorkflowExecutionOut[] { @@ -33,6 +41,23 @@ export const useWorkflowExecutionStore = defineStore({ }, }, actions: { + pushTemporaryParameters( + params: WorkflowParameters, + metaParams?: WorkflowMetaParameters, + ) { + this.__temporaryParameters = params; + this.__temporaryMetaParameters = metaParams ?? {}; + }, + popTemporaryParameters(): TemporaryParams { + const params = this.__temporaryParameters; + this.__temporaryParameters = undefined; + const metaParams = this.__temporaryMetaParameters; + this.__temporaryMetaParameters = undefined; + return { + metaParams: metaParams ?? {}, + params: params ?? {}, + }; + }, fetchExecutionsForDevStatistics( onFinally?: () => void, ): Promise<AnonymizedWorkflowExecution[]> { diff --git a/src/types/ClowmInfo.ts b/src/types/ClowmInfo.ts index ebd4644e6ebc5ada66e42fbd60917aa316b803fd..09ea3ef9ad1760de9faefecd2a31c6e87c673baf 100644 --- a/src/types/ClowmInfo.ts +++ b/src/types/ClowmInfo.ts @@ -3,4 +3,5 @@ export type ClowmInfo = { outputParameters: string[]; resourceParameters?: string[]; exampleParameters?: Record<string, string | boolean | number>; + dois?: string[]; }; diff --git a/src/types/WorkflowParameters.ts b/src/types/WorkflowParameters.ts new file mode 100644 index 0000000000000000000000000000000000000000..0fd1398d7ac811443f2966fdf3bc3f24098b492f --- /dev/null +++ b/src/types/WorkflowParameters.ts @@ -0,0 +1,16 @@ +export type WorkflowParameters = Record< + string, + string | number | boolean | undefined +>; + +export type WorkflowMetaParameters = { + logs_s3_path?: string; + debug_s3_path?: string; + provenance_s3_path?: string; + notes?: string; +}; + +export type TemporaryParams = { + params: WorkflowParameters; + metaParams: WorkflowMetaParameters; +}; diff --git a/src/views/workflows/ArbitraryWorkflowView.vue b/src/views/workflows/ArbitraryWorkflowView.vue index c739aa57795aa1a3d19b78cd5395013065e31031..a32fd64a807377857fe768b161aa864dd700a078 100644 --- a/src/views/workflows/ArbitraryWorkflowView.vue +++ b/src/views/workflows/ArbitraryWorkflowView.vue @@ -11,6 +11,7 @@ import type { WorkflowIn } from "@/client/workflow"; import { useWorkflowExecutionStore } from "@/stores/workflowExecutions"; import ParameterSchemaFormComponent from "@/components/parameter-schema/ParameterSchemaFormComponent.vue"; import BootstrapToast from "@/components/BootstrapToast.vue"; +import type { WorkflowParameters } from "@/types/WorkflowParameters"; const props = defineProps<{ wid: string; @@ -92,8 +93,7 @@ watch( ); function startWorkflow( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - parameters: Record<string, any>, + parameters: WorkflowParameters, notes?: string, logs_s3_path?: string, debug_s3_path?: string, diff --git a/src/views/workflows/ListWorkflowsView.vue b/src/views/workflows/ListWorkflowsView.vue index ac39acf8aed4fe2cb96107b67b4bce36ad79274f..56ccb5a3f70b464f79001ca13175f931db7dee8d 100644 --- a/src/views/workflows/ListWorkflowsView.vue +++ b/src/views/workflows/ListWorkflowsView.vue @@ -28,7 +28,9 @@ const filterFunctionMapping: Record< (a: WorkflowOut, b: WorkflowOut) => boolean > = { name: (a: WorkflowOut, b: WorkflowOut) => - workflowsState.sortDesc ? a.name > b.name : a.name < b.name, + workflowsState.sortDesc + ? a.name.toLowerCase() > b.name.toLowerCase() + : a.name.toLowerCase() < b.name.toLowerCase(), release: (a: WorkflowOut, b: WorkflowOut) => { const a_date = dayjs.unix(a.versions[0].created_at); const b_date = dayjs.unix(b.versions[0].created_at); diff --git a/src/views/workflows/ReviewWorkflowsView.vue b/src/views/workflows/ReviewWorkflowsView.vue index 1ff20c478c5d69385d447584a9649908000d6220..850757417a41235124e5354cca2f4bce6026c1d8 100644 --- a/src/views/workflows/ReviewWorkflowsView.vue +++ b/src/views/workflows/ReviewWorkflowsView.vue @@ -7,6 +7,8 @@ import { sortedVersions } from "@/utils/Workflow"; import { useWorkflowStore } from "@/stores/workflows"; import { useAuthStore } from "@/stores/users"; import { useNameStore } from "@/stores/names"; +import dayjs from "dayjs"; +import { Tooltip } from "bootstrap"; const workflowRepository = useWorkflowStore(); const nameRepository = useNameStore(); @@ -42,6 +44,17 @@ onMounted(() => { .fetchReviewableWorkflows(() => { workflowsState.loading = false; }) + .then((workflows) => { + setTimeout(() => { + document + .querySelector("#reviewTable") + ?.querySelectorAll('[data-bs-toggle="tooltip"]') + ?.forEach((tooltipTriggerEl) => + Tooltip.getOrCreateInstance(tooltipTriggerEl), + ); + }, 1000); + return workflows; + }) .then((workflows) => workflows.map((workflow) => workflow.developer_id).filter(isDefined), ) @@ -60,6 +73,7 @@ onMounted(() => { </div> <table class="table table-striped mx-auto" + id="reviewTable" v-else-if="workflowRepository.reviewableWorkflows.length > 0" > <thead class="fs-5"> @@ -96,9 +110,6 @@ onMounted(() => { <tr> <td colspan="4" class="px-5"> <table class="table mb-0"> - <colgroup> - <col span="1" style="width: 1%" /> - </colgroup> <tbody class="text-center"> <tr v-for="version in sortedVersions(workflow.versions)" @@ -113,6 +124,17 @@ onMounted(() => { /> </td> <td class="text-start">{{ version.version }}</td> + <td> + <span + data-bs-toggle="tooltip" + :data-bs-title=" + dayjs + .unix(version?.created_at) + .format('DD.MM.YYYY HH:mm:ss') + " + >{{ dayjs.unix(version.created_at).fromNow() }}</span + > + </td> <td>{{ version.status }}</td> <td> <a diff --git a/src/views/workflows/StartWorkflowView.vue b/src/views/workflows/StartWorkflowView.vue index 0d419d447cd72d7afd609a1903d9c8683580ee69..df2f89e29afcc73b3fd3bcefb77027d39aa9eb74 100644 --- a/src/views/workflows/StartWorkflowView.vue +++ b/src/views/workflows/StartWorkflowView.vue @@ -8,6 +8,7 @@ import { Toast } from "bootstrap"; import { useWorkflowExecutionStore } from "@/stores/workflowExecutions"; import BootstrapToast from "@/components/BootstrapToast.vue"; import { useWorkflowStore } from "@/stores/workflows"; +import type { WorkflowParameters } from "@/types/WorkflowParameters"; const executionRepository = useWorkflowExecutionStore(); const workflowRepository = useWorkflowStore(); @@ -67,8 +68,7 @@ function downloadParameterSchema() { } function startWorkflow( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - parameters: Record<string, any>, + parameters: WorkflowParameters, notes?: string, logs_s3_path?: string, debug_s3_path?: string, diff --git a/src/views/workflows/WorkflowVersionView.vue b/src/views/workflows/WorkflowVersionView.vue index 072ba90b87ddba6e8df00fe3e43309baa9c70fd4..b130b670521c1d3521b76ca74c56824a02802f69 100644 --- a/src/views/workflows/WorkflowVersionView.vue +++ b/src/views/workflows/WorkflowVersionView.vue @@ -36,12 +36,16 @@ watch( () => props.workflowModeId, (newModeId, oldModeId) => { if (newModeId !== oldModeId) { - workflowRepository.fetchWorkflowDocumentation( - props.workflowId, - props.versionId, - DocumentationEnum.PARAMETER_SCHEMA, - newModeId, - ); + workflowRepository + .fetchWorkflowDocumentation( + props.workflowId, + props.versionId, + DocumentationEnum.PARAMETER_SCHEMA, + newModeId, + ) + .then((schema) => { + versionState.parameterSchema = schema; + }); } }, ); diff --git a/src/views/workflows/WorkflowView.vue b/src/views/workflows/WorkflowView.vue index b6d573f6805d7f76753aabd6f58488d61a133a82..8ad2714a3795dbeb33aef3491fb18dbb5eeae61c 100644 --- a/src/views/workflows/WorkflowView.vue +++ b/src/views/workflows/WorkflowView.vue @@ -38,16 +38,12 @@ const route = useRoute(); // ============================================================================= const workflowState = reactive<{ loading: boolean; - activeVersionId: string; initialOpen: boolean; stats: WorkflowStatistic[]; - activeModeId?: string; }>({ loading: true, - activeVersionId: props.versionId ?? "", initialOpen: true, stats: [], - activeModeId: props.workflowModeId, }); // Watchers @@ -61,67 +57,6 @@ watch( }, ); -watch( - () => props.versionId, - (newWorkflowId) => { - workflowState.activeVersionId = newWorkflowId ?? ""; - }, -); - -watch( - () => props.workflowModeId, - (newModeId) => { - if (newModeId) { - workflowState.activeModeId = newModeId; - } - }, -); - -watch( - () => workflowState.activeVersionId, - (newVersionId, oldVersionId) => { - if ( - newVersionId && - newVersionId !== oldVersionId && - route.name !== "workflow-start" - ) { - // If mode does not exist in other version, select another mode - if ( - activeVersionModeIds.value.length > 0 && - activeVersionModeIds.value.findIndex( - (modeId) => modeId === workflowState.activeModeId, - ) == -1 - ) { - workflowState.activeModeId = activeVersionModeIds.value[0]; - } else if (activeVersionModeIds.value.length == 0) { - // If new version does not has any modes, then set mode id to none - workflowState.activeModeId = undefined; - } - router.push({ - name: "workflow-version", - params: { versionId: newVersionId }, - query: { - tab: route.query.tab, - workflowModeId: workflowState.activeModeId, - }, - }); - } - }, -); - -watch( - () => workflowState.activeModeId, - (newModeId, oldModeId) => { - if (newModeId != oldModeId) { - router.push({ - name: route.name ?? undefined, - params: { versionId: workflowState.activeVersionId }, - query: { tab: route.query.tab, workflowModeId: newModeId }, - }); - } - }, -); - // Computed Properties // ============================================================================= const workflow = computed<WorkflowOut | undefined>(() => @@ -139,7 +74,7 @@ const latestVersion = computed<WorkflowVersion | undefined>(() => ); const activeVersion = computed<WorkflowVersion | undefined>(() => workflow.value?.versions.find( - (w) => w.workflow_version_id === workflowState.activeVersionId, + (w) => w.workflow_version_id === props.versionId, ), ); @@ -198,13 +133,9 @@ function updateWorkflow(workflowId: string) { workflowState.initialOpen = false; }) .then((workflow) => { - if (!workflowState.initialOpen || !route.params.versionId) { - workflowState.activeVersionId = - workflow.versions[workflow.versions.length - 1].workflow_version_id; - } else { - workflowState.activeVersionId = route.params.versionId as string; + if (props.versionId == undefined) { + updateVersion(workflow.versions[0]?.workflow_version_id); } - workflowState.activeModeId = activeVersionModeIds.value[0] ?? undefined; }); WorkflowService.workflowGetWorkflowStatistics(workflowId).then((stats) => { @@ -212,6 +143,52 @@ function updateWorkflow(workflowId: string) { }); } +function updateVersion(workflowVersionId?: string) { + if (workflowVersionId) { + const possibleModes = getModesForVersion(workflowVersionId); + let modeId: string | undefined = undefined; + if ( + (props.workflowModeId == undefined && possibleModes.length > 0) || // next version needs a mode + (props.workflowId != undefined && // next version has not the current mode + possibleModes.length > 0 && + !possibleModes.includes(props.workflowId)) + ) { + modeId = possibleModes[0]; + } else if ( + // next version has the same mode as the current mode + props.workflowId != undefined && + possibleModes.length > 0 && + possibleModes.includes(props.workflowId) + ) { + modeId = props.workflowModeId; + } + router.push({ + params: { + ...route.params, + versionId: workflowVersionId, + }, + query: { ...route.query, workflowModeId: modeId }, + }); + } +} + +function updateMode(modeId?: string) { + router.replace({ + params: { + ...route.params, + }, + query: { ...route.query, workflowModeId: modeId }, + }); +} + +function getModesForVersion(workflowVersionId: string): string[] { + return ( + workflow.value?.versions.find( + (w) => w.workflow_version_id === workflowVersionId, + )?.modes ?? [] + ); +} + function deprecateCurrentWorkflowVersion() { if (props.versionId) { workflowRepository.deprecateWorkflowVersion( @@ -272,7 +249,7 @@ onMounted(() => { <select id="workflowModeSelect" class="form-select w-fit" - v-model="workflowState.activeModeId" + @change="updateMode(($event?.target as HTMLSelectElement)?.value)" > <option v-for="modeId of activeVersionModeIds" @@ -326,7 +303,7 @@ onMounted(() => { workflowId: props.workflowId, }, query: { - workflowModeId: workflowState.activeModeId, + workflowModeId: props.workflowModeId, viewMode: 'simple', }, }" @@ -346,12 +323,15 @@ onMounted(() => { class="form-select form-select-sm" aria-label="Workflow version selection" aria-describedby="workflow-version-wrapping" - v-model="workflowState.activeVersionId" + @change=" + updateVersion(($event?.target as HTMLSelectElement)?.value) + " > <option v-for="version in sortedVersions(workflow.versions)" :key="version.workflow_version_id" :value="version.workflow_version_id" + :selected="version.workflow_version_id === props.versionId" > {{ version.version }} </option> @@ -365,7 +345,7 @@ onMounted(() => { class="text-secondary text-decoration-none mx-auto w-fit p-0" > <font-awesome-icon :icon="gitIcon" class="me-1" /> - <span class="align-middle"> {{ workflow.repository_url }}</span> + <span class="align-middle"> {{ workflow?.repository_url }}</span> <font-awesome-icon icon="fa-solid fa-arrow-up-right-from-square" class="ms-1"