diff --git a/web/package-lock.json b/web/package-lock.json index df25ad5929..1c2f702402 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -62,7 +62,7 @@ "pbkdf2": "^3.1.2", "rxjs": "^7.5.6", "swiper": "^8.2.4", - "ts-matches": "^5.5.1", + "ts-matches": "^6.1.0", "tslib": "^2.8.1", "uuid": "^8.3.2", "zone.js": "^0.14.2" @@ -886,9 +886,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", - "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", "dev": true, "license": "MIT", "engines": { @@ -972,20 +972,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz", - "integrity": "sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", @@ -1059,14 +1045,14 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz", - "integrity": "sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", + "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", - "regexpu-core": "^6.1.1", + "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, "engines": { @@ -1247,20 +1233,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz", - "integrity": "sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", @@ -1348,13 +1320,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", - "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.26.0" + "@babel/types": "^7.26.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -1916,13 +1888,12 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz", - "integrity": "sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", + "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { @@ -2065,15 +2036,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz", - "integrity": "sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-simple-access": "^7.25.9" + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2692,17 +2662,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", - "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/parser": "^7.25.9", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9", + "@babel/types": "^7.26.3", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2711,14 +2681,14 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", - "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.2", - "@babel/types": "^7.26.0", + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -2741,9 +2711,9 @@ } }, "node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", "dev": true, "license": "MIT", "dependencies": { @@ -4030,9 +4000,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz", - "integrity": "sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz", + "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==", "cpu": [ "arm" ], @@ -4044,9 +4014,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.4.tgz", - "integrity": "sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz", + "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==", "cpu": [ "arm64" ], @@ -4058,9 +4028,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.4.tgz", - "integrity": "sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz", + "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==", "cpu": [ "arm64" ], @@ -4072,9 +4042,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.4.tgz", - "integrity": "sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz", + "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==", "cpu": [ "x64" ], @@ -4086,9 +4056,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.4.tgz", - "integrity": "sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz", + "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==", "cpu": [ "arm64" ], @@ -4100,9 +4070,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.4.tgz", - "integrity": "sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz", + "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==", "cpu": [ "x64" ], @@ -4114,9 +4084,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.4.tgz", - "integrity": "sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz", + "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==", "cpu": [ "arm" ], @@ -4128,9 +4098,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.4.tgz", - "integrity": "sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz", + "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==", "cpu": [ "arm" ], @@ -4142,9 +4112,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.4.tgz", - "integrity": "sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz", + "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==", "cpu": [ "arm64" ], @@ -4156,9 +4126,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.4.tgz", - "integrity": "sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz", + "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==", "cpu": [ "arm64" ], @@ -4169,10 +4139,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz", + "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.4.tgz", - "integrity": "sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz", + "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==", "cpu": [ "ppc64" ], @@ -4184,9 +4168,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.4.tgz", - "integrity": "sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz", + "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==", "cpu": [ "riscv64" ], @@ -4198,9 +4182,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.4.tgz", - "integrity": "sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz", + "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==", "cpu": [ "s390x" ], @@ -4212,9 +4196,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.4.tgz", - "integrity": "sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz", + "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==", "cpu": [ "x64" ], @@ -4226,9 +4210,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.4.tgz", - "integrity": "sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz", + "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==", "cpu": [ "x64" ], @@ -4240,9 +4224,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.4.tgz", - "integrity": "sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz", + "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==", "cpu": [ "arm64" ], @@ -4254,9 +4238,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.4.tgz", - "integrity": "sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz", + "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==", "cpu": [ "ia32" ], @@ -4268,9 +4252,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.4.tgz", - "integrity": "sha512-3j4jx1TppORdTAoBJRd+/wJRGCPC0ETWkXOecJ6PPZLj6SptXkrXcNqdj0oclbKML6FkQltdz7bBA3rUSirZug==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz", + "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==", "cpu": [ "x64" ], @@ -4282,9 +4266,9 @@ ] }, "node_modules/@rollup/wasm-node": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/@rollup/wasm-node/-/wasm-node-4.27.4.tgz", - "integrity": "sha512-Q1b1A1RAP4Pp4qwU59n4819nJ4v4CDgBbY1/FbC1pW5PmHHI36yyqDMB0BW/F+3lLDt0KDd+t7tBrki9oSEg/w==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/wasm-node/-/wasm-node-4.28.1.tgz", + "integrity": "sha512-t4ckEC09V3wbe0r6T4fGjq85lEbvGcGxn7QYYgjHyKNzZaQU5kFqr4FsavXYHRiVNYq8m+dRhdGjpfcC9UzzPg==", "dev": true, "license": "MIT", "dependencies": { @@ -4554,9 +4538,9 @@ } }, "node_modules/@taiga-ui/i18n": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.16.0.tgz", - "integrity": "sha512-Vdb7eyuKiOlf4vvBJVvpOso1PbQH5L3xXZQlXbhdnejfHdWKrt0TwekXTBQsJcWxYPlaKQomwOxozE1avoespg==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.17.0.tgz", + "integrity": "sha512-5uV0u6bVKq5Il2xuEQzMtx15LSNRzxVIXhUQOY+Q3dPe3V2IaE9lSEjAaR+vEp1U7uFgo0lPc+S3A1BdkEMMsA==", "license": "Apache-2.0", "peer": true, "dependencies": { @@ -4973,9 +4957,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "18.19.66", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.66.tgz", - "integrity": "sha512-14HmtUdGxFUalGRfLLn9Gc1oNWvWh5zNbsyOLo5JV6WARSeN1QcEBKRnZm9QqNfrutgsl/hY4eJW63aZ44aBCg==", + "version": "18.19.67", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.67.tgz", + "integrity": "sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5408,14 +5392,11 @@ } }, "node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", "devOptional": true, "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, "engines": { "node": ">= 14" } @@ -6094,17 +6075,16 @@ "license": "ISC" }, "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "devOptional": true, "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" }, "engines": { "node": ">= 0.4" @@ -6113,6 +6093,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.0.tgz", + "integrity": "sha512-CCKAP2tkPau7D3GE8+V8R6sQubA9R5foIzGp+85EXCVSCivuxBNAWqcpn72PKYiIcqoViv/kcUDpaEIMBVi1lQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -6133,9 +6127,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001684", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz", - "integrity": "sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==", + "version": "1.0.30001687", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz", + "integrity": "sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ==", "dev": true, "funding": [ { @@ -6245,9 +6239,9 @@ "license": "MIT" }, "node_modules/cipher-base": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.5.tgz", - "integrity": "sha512-xq7ICKB4TMHUx7Tz1L9O2SGKOhYMOTR32oir45Bq28/AQTpHogKgHcoYFSdRbMtddl+ozNXfXY9jWcgYKmde0w==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz", + "integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==", "license": "MIT", "dependencies": { "inherits": "^2.0.4", @@ -6876,9 +6870,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -7228,6 +7222,21 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dunder-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", + "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -7250,9 +7259,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.65", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.65.tgz", - "integrity": "sha512-PWVzBjghx7/wop6n22vS2MLU8tKGd4Q91aCEGhG/TYmW6PP5OcSXcdnxTe1NNt0T66N8D6jxh4kC8UsdzOGaIw==", + "version": "1.5.71", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz", + "integrity": "sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA==", "dev": true, "license": "ISC" }, @@ -7372,14 +7381,11 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "devOptional": true, "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -7624,9 +7630,9 @@ "license": "Apache-2.0" }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dev": true, "license": "MIT", "dependencies": { @@ -7649,7 +7655,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -7664,6 +7670,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/debug": { @@ -8044,17 +8054,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.5.tgz", + "integrity": "sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg==", "devOptional": true, "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -8174,13 +8187,13 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "devOptional": true, "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8238,23 +8251,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "devOptional": true, "license": "MIT", "engines": { @@ -9432,9 +9432,9 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.11.15", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.15.tgz", - "integrity": "sha512-M7+rtYi9l5RvMmHyjyoF3BHHUpXTYdJ0PezZGHNs0GyW1lO+K7jxlXpbdIb7a56h0nqLYdjIw+E+z0ciGaJP7g==", + "version": "1.11.16", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.16.tgz", + "integrity": "sha512-Noyazmt0yOvnG0OeRY45Cd1ur8G7Z0HWVkuCuKe+yysGNxPQwBAODBQQ40j0AIagi9ZWurfmmZWNlpg4h4W+XQ==", "license": "MIT", "peer": true }, @@ -10586,9 +10586,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -10852,9 +10852,9 @@ } }, "node_modules/node-gyp": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.2.0.tgz", - "integrity": "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==", + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.3.1.tgz", + "integrity": "sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==", "devOptional": true, "license": "MIT", "dependencies": { @@ -11694,9 +11694,9 @@ "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "dev": true, "license": "MIT" }, @@ -12082,9 +12082,9 @@ "license": "MIT" }, "node_modules/prettier": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.0.tgz", - "integrity": "sha512-/OXNZcLyWkfo13ofOW5M7SLh+k5pnIs07owXK2teFpnfaOEcycnSy7HQxldaVX1ZP/7Q8oO1eDuQJNwbomQq5Q==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "license": "MIT", "bin": { @@ -12829,9 +12829,9 @@ } }, "node_modules/rollup": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.4.tgz", - "integrity": "sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz", + "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==", "dev": true, "license": "MIT", "dependencies": { @@ -12845,24 +12845,25 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.27.4", - "@rollup/rollup-android-arm64": "4.27.4", - "@rollup/rollup-darwin-arm64": "4.27.4", - "@rollup/rollup-darwin-x64": "4.27.4", - "@rollup/rollup-freebsd-arm64": "4.27.4", - "@rollup/rollup-freebsd-x64": "4.27.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.27.4", - "@rollup/rollup-linux-arm-musleabihf": "4.27.4", - "@rollup/rollup-linux-arm64-gnu": "4.27.4", - "@rollup/rollup-linux-arm64-musl": "4.27.4", - "@rollup/rollup-linux-powerpc64le-gnu": "4.27.4", - "@rollup/rollup-linux-riscv64-gnu": "4.27.4", - "@rollup/rollup-linux-s390x-gnu": "4.27.4", - "@rollup/rollup-linux-x64-gnu": "4.27.4", - "@rollup/rollup-linux-x64-musl": "4.27.4", - "@rollup/rollup-win32-arm64-msvc": "4.27.4", - "@rollup/rollup-win32-ia32-msvc": "4.27.4", - "@rollup/rollup-win32-x64-msvc": "4.27.4", + "@rollup/rollup-android-arm-eabi": "4.28.1", + "@rollup/rollup-android-arm64": "4.28.1", + "@rollup/rollup-darwin-arm64": "4.28.1", + "@rollup/rollup-darwin-x64": "4.28.1", + "@rollup/rollup-freebsd-arm64": "4.28.1", + "@rollup/rollup-freebsd-x64": "4.28.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.28.1", + "@rollup/rollup-linux-arm-musleabihf": "4.28.1", + "@rollup/rollup-linux-arm64-gnu": "4.28.1", + "@rollup/rollup-linux-arm64-musl": "4.28.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.28.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1", + "@rollup/rollup-linux-riscv64-gnu": "4.28.1", + "@rollup/rollup-linux-s390x-gnu": "4.28.1", + "@rollup/rollup-linux-x64-gnu": "4.28.1", + "@rollup/rollup-linux-x64-musl": "4.28.1", + "@rollup/rollup-win32-arm64-msvc": "4.28.1", + "@rollup/rollup-win32-ia32-msvc": "4.28.1", + "@rollup/rollup-win32-x64-msvc": "4.28.1", "fsevents": "~2.3.2" } }, @@ -13365,11 +13366,14 @@ } }, "node_modules/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", "dev": true, "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -13514,13 +13518,13 @@ } }, "node_modules/socks-proxy-agent": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", - "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "devOptional": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.1", + "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" }, @@ -14182,9 +14186,9 @@ } }, "node_modules/ts-matches": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-5.6.1.tgz", - "integrity": "sha512-1QXWQUa14MCgbz7vMg7i7eVPhMKB/5w8808nkN2sfnDkbG9nWYr9IwuTxX+h99yyawHYS53DewShA2RYCbSW4Q==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ts-matches/-/ts-matches-6.2.1.tgz", + "integrity": "sha512-qdnMgTHsGCEGGK6QiaNMY2vD9eQtRp2Q+pAxcOAzxHJKDKTBYsc1ISTg1zp8H2+EmtCB0eko/1TwYUA5/mUGug==", "license": "MIT" }, "node_modules/ts-morph": { @@ -15265,18 +15269,18 @@ } }, "node_modules/webpack": { - "version": "5.96.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz", - "integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==", + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.14.0", "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", diff --git a/web/package.json b/web/package.json index 28bc3ab950..316bfadf93 100644 --- a/web/package.json +++ b/web/package.json @@ -84,7 +84,7 @@ "pbkdf2": "^3.1.2", "rxjs": "^7.5.6", "swiper": "^8.2.4", - "ts-matches": "^5.5.1", + "ts-matches": "^6.1.0", "tslib": "^2.8.1", "uuid": "^8.3.2", "zone.js": "^0.14.2" diff --git a/web/projects/install-wizard/src/app/app.component.html b/web/projects/install-wizard/src/app/app.component.html index c8150797b9..7f0e0761f0 100644 --- a/web/projects/install-wizard/src/app/app.component.html +++ b/web/projects/install-wizard/src/app/app.component.html @@ -1,12 +1,12 @@
-
+
@if (selected) { @@ -35,7 +35,7 @@ const RUNNING = ['running', 'starting', 'restarting'] tuiIconButton iconStart="@tui.rotate-cw" [disabled]="status().primary !== 'running'" - (click)="actions.restart(manifest())" + (click)="controls.restart(manifest())" > Restart @@ -45,18 +45,10 @@ const RUNNING = ['running', 'starting', 'restarting'] tuiIconButton iconStart="@tui.play" [disabled]="status().primary !== 'stopped'" - (click)="actions.start(manifest(), !!hasUnmet)" + (click)="controls.start(manifest(), !!hasUnmet)" > Start - - } @@ -80,10 +72,9 @@ const RUNNING = ['running', 'starting', 'restarting'] }) export class ControlsComponent { private readonly errors = inject(DepErrorService) - readonly actions = inject(ActionsService) + readonly controls = inject(ControlsService) readonly pkg = input.required() - readonly status = computed(() => renderPkgStatus(this.pkg())) readonly running = computed(() => RUNNING.includes(this.status().primary)) readonly manifest = computed(() => getManifest(this.pkg())) diff --git a/web/projects/ui/src/app/routes/portal/routes/dashboard/dashboard.component.ts b/web/projects/ui/src/app/routes/portal/routes/dashboard/dashboard.component.ts index f3c4861c84..a723c8b559 100644 --- a/web/projects/ui/src/app/routes/portal/routes/dashboard/dashboard.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/dashboard/dashboard.component.ts @@ -21,7 +21,7 @@ import { DepErrorService } from 'src/app/services/dep-error.service' Name Version Status - Controls + Controls diff --git a/web/projects/ui/src/app/routes/portal/routes/dashboard/status.component.ts b/web/projects/ui/src/app/routes/portal/routes/dashboard/status.component.ts index d36a3ff031..50eb562c94 100644 --- a/web/projects/ui/src/app/routes/portal/routes/dashboard/status.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/dashboard/status.component.ts @@ -58,14 +58,7 @@ export class StatusComponent { hasDepErrors = false get healthy(): boolean { - const status = this.getStatus(this.pkg) - - return ( - !this.hasDepErrors && // no deps error - // @TODO Matt how do we handle this now? - // !!this.pkg.status.configured && // no config needed - status.health !== 'failure' // no health issues - ) + return !this.hasDepErrors && this.getStatus(this.pkg).health !== 'failure' } get loading(): boolean { @@ -87,9 +80,8 @@ export class StatusComponent { return 'Running' case 'stopped': return 'Stopped' - // @TODO Matt just dropping this? - // case 'needsConfig': - // return 'Needs Config' + case 'actionRequired': + return 'Action Required' case 'updating': return 'Updating...' case 'stopping': @@ -113,9 +105,8 @@ export class StatusComponent { switch (this.getStatus(this.pkg).primary) { case 'running': return 'var(--tui-status-positive)' - // @TODO Matt just dropping this? - // case 'needsConfig': - // return 'var(--tui-status-warning)' + case 'actionRequired': + return 'var(--tui-status-warning)' case 'installing': case 'updating': case 'stopping': diff --git a/web/projects/ui/src/app/routes/portal/routes/service/components/action-request.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/components/action-request.component.ts new file mode 100644 index 0000000000..63054cb705 --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/routes/service/components/action-request.component.ts @@ -0,0 +1,97 @@ +import { + ChangeDetectionStrategy, + Component, + HostListener, + inject, + Input, +} from '@angular/core' +import { T } from '@start9labs/start-sdk' +import { TuiIcon, TuiTitle } from '@taiga-ui/core' +import { TuiCell } from '@taiga-ui/layout' +import { ActionService } from 'src/app/services/action.service' +import { PackageDataEntry } from 'src/app/services/patch-db/data-model' +import { getDepDetails } from 'src/app/utils/dep-info' +import { getManifest } from 'src/app/utils/get-package-data' + +export type ActionRequest = T.ActionRequest & { + actionName: string + dependency: { + title: string + icon: string + } | null +} + +@Component({ + standalone: true, + selector: 'button[actionRequest]', + template: ` + + + {{ actionRequest.actionName }} + @if (actionRequest.dependency) { + + Service: + + {{ actionRequest.dependency.title }} + + } + + {{ actionRequest.reason || 'no reason provided' }} + + + `, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [TuiIcon, TuiTitle], + hostDirectives: [TuiCell], +}) +export class ServiceActionRequestComponent { + private readonly actionService = inject(ActionService) + + @Input({ required: true }) + actionRequest!: ActionRequest + + @Input({ required: true }) + pkg!: PackageDataEntry + + @Input({ required: true }) + allPkgs!: Record + + get icon(): string { + return this.actionRequest.severity === 'critical' + ? '@tui.triangle-alert' + : '@tui.play' + } + + @HostListener('click') + async handleAction() { + const { id, title } = getManifest(this.pkg) + const { actionId, packageId } = this.actionRequest + const details = getDepDetails(this.pkg, this.allPkgs, packageId) + const self = packageId === id + + this.actionService.present({ + pkgInfo: { + id: packageId, + title: self ? title : details.title, + mainStatus: self + ? this.pkg.status.main + : this.allPkgs[packageId].status.main, + icon: self ? this.pkg.icon : details.icon, + }, + actionInfo: { + id: actionId, + metadata: self + ? this.pkg.actions[actionId] + : this.allPkgs[packageId].actions[actionId], + }, + requestInfo: { + request: this.actionRequest, + dependentId: self ? undefined : id, + }, + }) + } +} diff --git a/web/projects/ui/src/app/routes/portal/routes/service/components/action.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/components/action.component.ts index 7eb65fbb66..5e97ffd6df 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/components/action.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/components/action.component.ts @@ -1,26 +1,41 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { TuiIcon } from '@taiga-ui/core' +import { T } from '@start9labs/start-sdk' +import { TuiIcon, TuiTitle } from '@taiga-ui/core' interface ActionItem { - readonly icon: string readonly name: string readonly description: string + readonly icon?: string + readonly visibility?: T.ActionVisibility } @Component({ selector: '[action]', template: ` - -
+ +
{{ action.name }} -
{{ action.description }}
+
{{ action.description }}
+ @if (disabled) { +
{{ disabled }}
+ }
`, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [TuiIcon], + imports: [TuiIcon, TuiTitle], + host: { + '[disabled]': '!!disabled', + }, }) export class ServiceActionComponent { @Input({ required: true }) action!: ActionItem + + get disabled() { + return ( + typeof this.action.visibility === 'object' && + this.action.visibility.disabled + ) + } } diff --git a/web/projects/ui/src/app/routes/portal/routes/service/components/actions.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/components/actions.component.ts index 1d6bbb1894..1f474ec6dc 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/components/actions.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/components/actions.component.ts @@ -9,7 +9,7 @@ import { T } from '@start9labs/start-sdk' import { tuiPure } from '@taiga-ui/cdk' import { tuiButtonOptionsProvider } from '@taiga-ui/core' import { DependencyInfo } from 'src/app/routes/portal/routes/service/types/dependency-info' -import { ActionsService } from 'src/app/services/actions.service' +import { ControlsService } from '../../../../../services/controls.service' import { PackageDataEntry } from 'src/app/services/patch-db/data-model' import { PackageStatus } from 'src/app/services/pkg-status-rendering.service' import { getManifest } from 'src/app/utils/get-package-data' @@ -49,17 +49,6 @@ const STOPPABLE = ['running', 'starting', 'restarting'] Start } - - @if (canConfigure) { - - } `, styles: [ ` @@ -84,7 +73,7 @@ export class ServiceActionsComponent { status: PackageStatus } - readonly actions = inject(ActionsService) + readonly actions = inject(ControlsService) get manifest(): T.Manifest { return getManifest(this.service.pkg) @@ -95,19 +84,13 @@ export class ServiceActionsComponent { } get canStart(): boolean { - return this.service.status.primary === 'stopped' && !this.canConfigure + return this.service.status.primary === 'stopped' } get canRestart(): boolean { return this.service.status.primary === 'running' } - get canConfigure(): boolean { - // @TODO Matt should we just drop this? - // return !this.service.pkg.status.configured - return false - } - @tuiPure hasUnmet(dependencies: readonly DependencyInfo[]): boolean { return dependencies.some(dep => !!dep.errorText) diff --git a/web/projects/ui/src/app/routes/portal/routes/service/components/dependencies.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/components/dependencies.component.ts index 554d3d94d5..28639a843e 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/components/dependencies.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/components/dependencies.component.ts @@ -6,14 +6,8 @@ import { ServiceDependencyComponent } from './dependency.component' selector: 'service-dependencies', template: ` @for (dep of dependencies; track $index) { - - } - - @if (!dependencies.length) { + + } @empty { No dependencies } `, diff --git a/web/projects/ui/src/app/routes/portal/routes/service/components/dependency.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/components/dependency.component.ts index 8d7f1e0753..fdc81066c2 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/components/dependency.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/components/dependency.component.ts @@ -1,26 +1,29 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { TuiIcon } from '@taiga-ui/core' +import { TuiIcon, TuiTitle } from '@taiga-ui/core' +import { TuiCell } from '@taiga-ui/layout' import { DependencyInfo } from '../types/dependency-info' @Component({ selector: '[serviceDependency]', template: ` - + @if (dep.errorText) { } {{ dep.title }} -
{{ dep.version }}
-
{{ dep.errorText || 'Satisfied' }}
+ {{ dep.version }} + + {{ dep.errorText || 'Satisfied' }} +
@if (dep.actionText) { -
+ {{ dep.actionText }} -
+
} `, styles: [ @@ -38,7 +41,11 @@ import { DependencyInfo } from '../types/dependency-info' ], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [TuiIcon], + host: { + '(click)': 'dep.action()', + }, + imports: [TuiIcon, TuiTitle], + hostDirectives: [TuiCell], }) export class ServiceDependencyComponent { @Input({ required: true, alias: 'serviceDependency' }) diff --git a/web/projects/ui/src/app/routes/portal/routes/service/components/error.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/components/error.component.ts new file mode 100644 index 0000000000..0906fc6e39 --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/routes/service/components/error.component.ts @@ -0,0 +1,97 @@ +import { + ChangeDetectionStrategy, + Component, + inject, + Input, +} from '@angular/core' +import { copyToClipboard } from '@start9labs/shared' +import { + TuiAlertService, + TuiButton, + TuiDialogService, + TuiIcon, +} from '@taiga-ui/core' +import { TuiLineClamp, TuiTooltip } from '@taiga-ui/kit' +import { PackageDataEntry } from 'src/app/services/patch-db/data-model' +import { StandardActionsService } from 'src/app/services/standard-actions.service' +import { getManifest } from 'src/app/utils/get-package-data' + +@Component({ + standalone: true, + selector: 'service-error', + template: ` + +

+ + Actions + + +

+ +
+ Rebuild Container + is harmless action that and only takes a few seconds to complete. It + will likely resolve this issue. +
+ Uninstall Service + is a dangerous action that will remove the service from StartOS and wipe + all its data. +
+

+ + + @if (overflow) { + + } +

+ `, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [TuiButton, TuiIcon, TuiTooltip, TuiLineClamp], +}) +export class ServiceErrorComponent { + private readonly dialogs = inject(TuiDialogService) + private readonly alerts = inject(TuiAlertService) + private readonly service = inject(StandardActionsService) + + @Input({ required: true }) + pkg!: PackageDataEntry + + overflow = false + + get error() { + return this.pkg.status.main === 'error' ? this.pkg.status : null + } + + async copy(text: string): Promise { + const success = await copyToClipboard(text) + + this.alerts + .open(success ? 'Copied to clipboard!' : 'Failed to copy to clipboard.', { + appearance: success ? 'positive' : 'negative', + }) + .subscribe() + } + + rebuild() { + this.service.rebuild(getManifest(this.pkg).id) + } + + uninstall() { + this.service.uninstall(getManifest(this.pkg)) + } + + show() { + this.dialogs + .open(this.error?.message, { label: 'Service error' }) + .subscribe() + } +} diff --git a/web/projects/ui/src/app/routes/portal/routes/service/components/health-check.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/components/health-check.component.ts index 5293b4670c..4ee4b086c7 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/components/health-check.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/components/health-check.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { T } from '@start9labs/start-sdk' -import { TuiIcon, TuiLoader } from '@taiga-ui/core' +import { TuiIcon, TuiLoader, TuiTitle } from '@taiga-ui/core' @Component({ selector: 'service-health-check', @@ -17,12 +17,12 @@ import { TuiIcon, TuiLoader } from '@taiga-ui/core' [style.color]="color" /> } -
+ {{ check.name }} -
+ {{ message }} -
-
+ + `, styles: [ ` @@ -38,7 +38,7 @@ import { TuiIcon, TuiLoader } from '@taiga-ui/core' ], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [TuiLoader, TuiIcon], + imports: [TuiLoader, TuiIcon, TuiTitle], }) export class ServiceHealthCheckComponent { @Input({ required: true }) diff --git a/web/projects/ui/src/app/routes/portal/routes/service/components/health-checks.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/components/health-checks.component.ts index c306db8962..f0ba56aa52 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/components/health-checks.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/components/health-checks.component.ts @@ -14,7 +14,6 @@ import { ConnectionService } from 'src/app/services/connection.service' template: ` @for (check of checks; track $index) { diff --git a/web/projects/ui/src/app/routes/portal/routes/service/components/interface-list-item.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/components/interface-list-item.component.ts index 334ea7e144..1ae79a08c2 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/components/interface-list-item.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/components/interface-list-item.component.ts @@ -1,5 +1,5 @@ import { TuiLet } from '@taiga-ui/cdk' -import { TuiLoader, TuiIcon, TuiButton } from '@taiga-ui/core' +import { TuiLoader, TuiIcon, TuiButton, TuiTitle } from '@taiga-ui/core' import { CommonModule } from '@angular/common' import { ChangeDetectionStrategy, @@ -7,6 +7,7 @@ import { inject, Input, } from '@angular/core' +import { TuiCell } from '@taiga-ui/layout' import { map, timer } from 'rxjs' import { ConfigService } from 'src/app/services/config.service' import { PackageDataEntry } from 'src/app/services/patch-db/data-model' @@ -23,26 +24,31 @@ import { ExtendedInterfaceInfo } from '../pipes/interface-info.pipe' } @else { } -
+ {{ info.name }} -
{{ info.description }}
+ {{ info.description }} @if (check) { -
- Health check failed: - {{ check }} -
+ + + Health check failed: + {{ check }} + + } @else { -
{{ info.typeDetail }}
+ + {{ info.typeDetail }} + } -
+ @if (info.type === 'ui') {
-
+ {{ menu.name }} -
+ {{ menu.description }} - -
-
+ + + `, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [TuiIcon], + imports: [TuiIcon, TuiTitle], + hostDirectives: [TuiCell], }) export class ServiceMenuItemComponent { @Input({ required: true, alias: 'serviceMenuItem' }) diff --git a/web/projects/ui/src/app/routes/portal/routes/service/components/menu.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/components/menu.component.ts index 9549ac1cab..be9f3fa23b 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/components/menu.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/components/menu.component.ts @@ -1,8 +1,8 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { RouterLink } from '@angular/router' import { PackageDataEntry } from 'src/app/services/patch-db/data-model' import { ToMenuPipe } from '../pipes/to-menu.pipe' import { ServiceMenuItemComponent } from './menu-item.component' -import { RouterLink } from '@angular/router' @Component({ selector: 'service-menu', @@ -10,19 +10,16 @@ import { RouterLink } from '@angular/router' @for (menu of pkg | toMenu; track $index) { @if (menu.routerLink) {
} @else { - } diff --git a/web/projects/ui/src/app/routes/portal/routes/service/components/progress.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/components/progress.component.ts index 8146d86b7d..927c87f2c7 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/components/progress.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/components/progress.component.ts @@ -7,8 +7,8 @@ import { InstallingProgressPipe } from 'src/app/routes/portal/routes/service/pip selector: '[progress]', template: ` - @if (progress | installingProgress; as decimal) { - : {{ decimal * 100 }}% + @if (progress | installingProgress; as percent) { + : {{ percent }}% } `, diff --git a/web/projects/ui/src/app/routes/portal/routes/service/components/property.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/components/property.component.ts deleted file mode 100644 index 651bd73815..0000000000 --- a/web/projects/ui/src/app/routes/portal/routes/service/components/property.component.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - inject, - Input, -} from '@angular/core' -import { CopyService } from '@start9labs/shared' -import { TuiButton, TuiLabel, TuiTitle } from '@taiga-ui/core' -import { mask } from 'src/app/utils/mask' - -@Component({ - selector: 'service-property', - template: ` - - - - `, - styles: [ - ` - :host { - display: flex; - padding: 0.5rem 0; - - &:not(:last-of-type) { - box-shadow: 0 1px var(--tui-background-neutral-1); - } - } - `, - ], - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: true, - imports: [TuiButton, TuiLabel, TuiTitle], -}) -export class ServicePropertyComponent { - @Input() - label = '' - - @Input() - value = '' - - masked = true - - readonly copyService = inject(CopyService) - - get mask(): string { - return mask(this.value, 64) - } -} diff --git a/web/projects/ui/src/app/routes/portal/routes/service/modals/action-input.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/modals/action-input.component.ts new file mode 100644 index 0000000000..422b1b2627 --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/routes/service/modals/action-input.component.ts @@ -0,0 +1,214 @@ +import { AsyncPipe } from '@angular/common' +import { Component, inject } from '@angular/core' +import { getErrorMessage } from '@start9labs/shared' +import { T, utils } from '@start9labs/start-sdk' +import { + TuiButton, + TuiDialogContext, + TuiDialogService, + TuiLoader, + TuiNotification, +} from '@taiga-ui/core' +import { TUI_CONFIRM, TuiConfirmData } from '@taiga-ui/kit' +import { injectContext } from '@taiga-ui/polymorpheus' +import * as json from 'fast-json-patch' +import { compare } from 'fast-json-patch' +import { PatchDB } from 'patch-db-client' +import { catchError, defer, EMPTY, endWith, firstValueFrom, map } from 'rxjs' +import { + ActionButton, + FormComponent, +} from 'src/app/routes/portal/components/form.component' +import { ActionRequestInfoComponent } from 'src/app/routes/portal/modals/config-dep.component' +import { ActionService } from 'src/app/services/action.service' +import { ApiService } from 'src/app/services/api/embassy-api.service' +import { DataModel } from 'src/app/services/patch-db/data-model' +import { getAllPackages, getManifest } from 'src/app/utils/get-package-data' +import { InvalidService } from '../../../components/form/invalid.service' + +export type PackageActionData = { + pkgInfo: { + id: string + title: string + icon: string + mainStatus: T.MainStatus['main'] + } + actionInfo: { + id: string + metadata: T.ActionMetadata + } + requestInfo?: { + dependentId?: string + request: T.ActionRequest + } +} + +@Component({ + template: ` +
+ +

{{ pkgInfo.title }}

+
+ @if (res$ | async; as res) { + @if (error) { + +
+
+ } + + @if (warning) { + +
+
+ } + + @if (requestInfo) { + + } + + + + + } @else { + + } + `, + styles: [ + ` + tui-notification { + font-size: 1rem; + margin-bottom: 1.4rem; + } + .service-title { + display: inline-flex; + align-items: center; + margin-bottom: 1.4rem; + img { + height: 20px; + margin-right: 4px; + } + h4 { + margin: 0; + } + } + `, + ], + standalone: true, + imports: [ + AsyncPipe, + TuiNotification, + TuiLoader, + TuiButton, + ActionRequestInfoComponent, + FormComponent, + ], + providers: [InvalidService], +}) +export class ActionInputModal { + private readonly dialogs = inject(TuiDialogService) + private readonly api = inject(ApiService) + private readonly patch = inject>(PatchDB) + private readonly actionService = inject(ActionService) + private readonly context = + injectContext>() + + readonly actionId = this.context.data.actionInfo.id + readonly warning = this.context.data.actionInfo.metadata.warning + readonly pkgInfo = this.context.data.pkgInfo + readonly requestInfo = this.context.data.requestInfo + + buttons: ActionButton[] = [ + { + text: 'Submit', + handler: value => this.execute(value), + }, + ] + + error = '' + + res$ = defer(() => + this.api.getActionInput({ + packageId: this.pkgInfo.id, + actionId: this.actionId, + }), + ).pipe( + map(res => { + const originalValue = res.value || {} + + return { + spec: res.spec, + originalValue, + operations: this.requestInfo?.request.input + ? compare( + JSON.parse(JSON.stringify(originalValue)), + utils.deepMerge( + JSON.parse(JSON.stringify(originalValue)), + this.requestInfo.request.input.value, + ) as object, + ) + : null, + } + }), + catchError(e => { + this.error = String(getErrorMessage(e)) + return EMPTY + }), + ) + + async execute(input: object) { + if (await this.checkConflicts(input)) { + return this.actionService.execute(this.pkgInfo.id, this.actionId, input) + } + } + + private async checkConflicts(input: object): Promise { + const packages = await getAllPackages(this.patch) + + const breakages = Object.keys(packages) + .filter( + id => + id !== this.pkgInfo.id && + Object.values(packages[id].requestedActions).some( + ({ request, active }) => + !active && + request.severity === 'critical' && + request.packageId === this.pkgInfo.id && + request.actionId === this.actionId && + request.when?.condition === 'input-not-matches' && + request.input && + json + .compare(input, request.input) + .some(op => op.op === 'add' || op.op === 'replace'), + ), + ) + .map(id => id) + + if (!breakages.length) return true + + const message = + 'As a result of this change, the following services will no longer work properly and may crash:
    ' + const content = `${message}${breakages.map( + id => `
  • ${getManifest(packages[id]).title}
  • `, + )}
` + const data: TuiConfirmData = { content, yes: 'Continue', no: 'Cancel' } + + return firstValueFrom( + this.dialogs.open(TUI_CONFIRM, { data }).pipe(endWith(false)), + ) + } +} diff --git a/web/projects/ui/src/app/routes/portal/routes/service/modals/additional.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/modals/additional.component.ts index c36d8e312b..add96ef3ef 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/modals/additional.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/modals/additional.component.ts @@ -1,13 +1,8 @@ -import { - ChangeDetectionStrategy, - Component, - inject, - Input, -} from '@angular/core' +import { ChangeDetectionStrategy, Component } from '@angular/core' import { TuiDialogOptions } from '@taiga-ui/core' -import { POLYMORPHEUS_CONTEXT } from '@taiga-ui/polymorpheus' -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' +import { injectContext } from '@taiga-ui/polymorpheus' import { ToAdditionalPipe } from 'src/app/routes/portal/routes/service/pipes/to-additional.pipe' +import { PackageDataEntry } from 'src/app/services/patch-db/data-model' import { ServiceAdditionalItemComponent } from './additional-item.component' @Component({ @@ -31,6 +26,5 @@ import { ServiceAdditionalItemComponent } from './additional-item.component' imports: [ToAdditionalPipe, ServiceAdditionalItemComponent], }) export class ServiceAdditionalModal { - readonly pkg = - inject>(POLYMORPHEUS_CONTEXT).data + readonly pkg = injectContext>().data } diff --git a/web/projects/ui/src/app/routes/portal/routes/service/modals/properties.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/modals/properties.component.ts deleted file mode 100644 index d107c76149..0000000000 --- a/web/projects/ui/src/app/routes/portal/routes/service/modals/properties.component.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { TuiLoader, TuiButton } from '@taiga-ui/core' -import { CommonModule } from '@angular/common' -import { ChangeDetectionStrategy, Component, inject } from '@angular/core' -import { ErrorService } from '@start9labs/shared' -import { POLYMORPHEUS_CONTEXT } from '@taiga-ui/polymorpheus' -import { BehaviorSubject } from 'rxjs' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { ServicePropertyComponent } from '../components/property.component' - -@Component({ - template: ` - @if (loading$ | async) { - - } @else { - @for (prop of properties | keyvalue: asIsOrder; track prop) { - - } @empty { - No properties - } - } - - `, - styles: [ - ` - button { - float: right; - margin-top: 1rem; - } - `, - ], - changeDetection: ChangeDetectionStrategy.OnPush, - standalone: true, - imports: [CommonModule, TuiButton, ServicePropertyComponent, TuiLoader], -}) -export class ServicePropertiesModal { - private readonly api = inject(ApiService) - private readonly errorService = inject(ErrorService) - - readonly id = inject<{ data: string }>(POLYMORPHEUS_CONTEXT).data - readonly loading$ = new BehaviorSubject(true) - - properties: Record = {} - - async ngOnInit() { - await this.getProperties() - } - - async refresh() { - await this.getProperties() - } - - private async getProperties(): Promise { - this.loading$.next(true) - - try { - // @TODO Matt this needs complete rework, right? - // this.properties = await this.api.getPackageProperties({ id: this.id }) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - this.loading$.next(false) - } - } - - asIsOrder(a: any, b: any) { - return 0 - } -} diff --git a/web/projects/ui/src/app/routes/portal/routes/service/pipes/group-actions.pipe.ts b/web/projects/ui/src/app/routes/portal/routes/service/pipes/group-actions.pipe.ts deleted file mode 100644 index 76142eb573..0000000000 --- a/web/projects/ui/src/app/routes/portal/routes/service/pipes/group-actions.pipe.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { WithId } from '@start9labs/shared' -import { T } from '@start9labs/start-sdk' -import { PackageDataEntry } from 'src/app/services/patch-db/data-model' - -@Pipe({ - name: 'groupActions', - standalone: true, -}) -export class GroupActionsPipe implements PipeTransform { - transform( - actions: PackageDataEntry['actions'], - ): Array>> | null { - if (!actions) return null - - const noGroup = 'noGroup' - const grouped = Object.entries(actions).reduce< - Record[]> - >((groups, [id, action]) => { - const actionWithId = { id, ...action } - const groupKey = action.group || noGroup - - if (!groups[groupKey]) { - groups[groupKey] = [actionWithId] - } else { - groups[groupKey].push(actionWithId) - } - - return groups - }, {}) - - return Object.values(grouped).map(group => - group.sort((a, b) => a.name.localeCompare(b.name)), - ) - } -} diff --git a/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-action-requests.pipe.ts b/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-action-requests.pipe.ts new file mode 100644 index 0000000000..c669fc54f5 --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-action-requests.pipe.ts @@ -0,0 +1,41 @@ +import { Pipe, PipeTransform } from '@angular/core' +import { PackageDataEntry } from 'src/app/services/patch-db/data-model' +import { getDepDetails } from 'src/app/utils/dep-info' +import { getManifest } from 'src/app/utils/get-package-data' +import { ActionRequest } from '../components/action-request.component' + +@Pipe({ + standalone: true, + name: 'toActionRequests', +}) +export class ToActionRequestsPipe implements PipeTransform { + transform(pkg: PackageDataEntry, packages: Record) { + const { id } = getManifest(pkg) + const critical: ActionRequest[] = [] + const important: ActionRequest[] = [] + + Object.values(pkg.requestedActions) + .filter(r => r.active) + .forEach(r => { + const self = r.request.packageId === id + const toReturn = { + ...r.request, + actionName: self + ? pkg.actions[r.request.actionId].name + : packages[r.request.packageId]?.actions[r.request.actionId].name || + 'Unknown Action', + dependency: self + ? null + : getDepDetails(pkg, packages, r.request.packageId), + } + + if (r.request.severity === 'critical') { + critical.push(toReturn) + } else { + important.push(toReturn) + } + }) + + return { critical, important } + } +} diff --git a/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-additional.pipe.ts b/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-additional.pipe.ts index a78837f6b1..820d44532d 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-additional.pipe.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-additional.pipe.ts @@ -1,8 +1,7 @@ import { inject, Pipe, PipeTransform } from '@angular/core' -import { CopyService, MarkdownComponent } from '@start9labs/shared' +import { CopyService, MARKDOWN } from '@start9labs/shared' import { T } from '@start9labs/start-sdk' import { TuiDialogService } from '@taiga-ui/core' -import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { from } from 'rxjs' import { ApiService } from 'src/app/services/api/embassy-api.service' import { PackageDataEntry } from 'src/app/services/patch-db/data-model' @@ -70,7 +69,7 @@ export class ToAdditionalPipe implements PipeTransform { private showLicense({ id, version }: T.Manifest) { this.dialogs - .open(new PolymorpheusComponent(MarkdownComponent), { + .open(MARKDOWN, { label: 'License', size: 'l', data: { diff --git a/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-menu.pipe.ts b/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-menu.pipe.ts index 48b0a1fc26..af0998a0f7 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-menu.pipe.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-menu.pipe.ts @@ -1,22 +1,16 @@ import { inject, Pipe, PipeTransform } from '@angular/core' import { Params } from '@angular/router' -import { MarkdownComponent } from '@start9labs/shared' +import { MARKDOWN } from '@start9labs/shared' import { T } from '@start9labs/start-sdk' import { TuiDialogService } from '@taiga-ui/core' import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { from } from 'rxjs' -// @TODO Alex implement config -// import { -// ConfigModal, -// PackageConfigData, -// } from 'src/app/routes/portal/modals/config.component' import { ServiceAdditionalModal } from 'src/app/routes/portal/routes/service/modals/additional.component' import { ApiService } from 'src/app/services/api/embassy-api.service' import { FormDialogService } from 'src/app/services/form-dialog.service' import { PackageDataEntry } from 'src/app/services/patch-db/data-model' import { ProxyService } from 'src/app/services/proxy.service' import { getManifest } from 'src/app/utils/get-package-data' -import { ServicePropertiesModal } from 'src/app/routes/portal/routes/service/modals/properties.component' export interface ServiceMenu { icon: string @@ -47,24 +41,6 @@ export class ToMenuPipe implements PipeTransform { description: `Understand how to use ${manifest.title}`, action: () => this.showInstructions(manifest), }, - { - icon: '@tui.sliders-vertical', - name: 'Config', - description: `Customize ${manifest.title}`, - action: () => this.openConfig(manifest), - }, - { - icon: '@tui.key', - name: 'Properties', - description: `Runtime information, credentials, and other values of interest`, - action: () => - this.dialogs - .open(new PolymorpheusComponent(ServicePropertiesModal), { - label: `${manifest.title} credentials`, - data: manifest.id, - }) - .subscribe(), - }, { icon: '@tui.zap', name: 'Actions', @@ -121,7 +97,7 @@ export class ToMenuPipe implements PipeTransform { .catch(e => console.error('Failed to mark instructions as seen', e)) this.dialogs - .open(new PolymorpheusComponent(MarkdownComponent), { + .open(MARKDOWN, { label: `${title} instructions`, size: 'l', data: { @@ -130,11 +106,4 @@ export class ToMenuPipe implements PipeTransform { }) .subscribe() } - - private openConfig({ title, id }: T.Manifest) { - // this.formDialog.open(ConfigModal, { - // label: `${title} configuration`, - // data: { pkgId: id }, - // }) - } } diff --git a/web/projects/ui/src/app/routes/portal/routes/service/routes/actions.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/routes/actions.component.ts index 4d27795627..f8ed6c3784 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/routes/actions.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/routes/actions.component.ts @@ -1,215 +1,99 @@ -import { TUI_CONFIRM } from '@taiga-ui/kit' -import { CommonModule } from '@angular/common' import { ChangeDetectionStrategy, Component, inject } from '@angular/core' -import { ActivatedRoute, Router } from '@angular/router' -import { - isEmptyObject, - WithId, - ErrorService, - LoadingService, - getPkgId, -} from '@start9labs/shared' -import { TuiDialogService } from '@taiga-ui/core' -import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' +import { toSignal } from '@angular/core/rxjs-interop' +import { ActivatedRoute } from '@angular/router' +import { getPkgId } from '@start9labs/shared' +import { T } from '@start9labs/start-sdk' import { PatchDB } from 'patch-db-client' -import { filter, switchMap, timer } from 'rxjs' -import { FormComponent } from 'src/app/routes/portal/components/form.component' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { - DataModel, - PackageDataEntry, -} from 'src/app/services/patch-db/data-model' -import { hasCurrentDeps } from 'src/app/utils/has-deps' -import { FormDialogService } from 'src/app/services/form-dialog.service' +import { filter, map } from 'rxjs' +import { DataModel } from 'src/app/services/patch-db/data-model' +import { StandardActionsService } from 'src/app/services/standard-actions.service' +import { getManifest } from 'src/app/utils/get-package-data' +import { ActionService } from 'src/app/services/action.service' import { ServiceActionComponent } from '../components/action.component' -import { ActionSuccessPage } from '../modals/action-success/action-success.page' -import { GroupActionsPipe } from '../pipes/group-actions.pipe' -import { ToManifestPipe } from 'src/app/routes/portal/pipes/to-manifest' -import { T } from '@start9labs/start-sdk' -import { getAllPackages, getManifest } from 'src/app/utils/get-package-data' @Component({ template: ` - @if (pkg$ | async; as pkg) { + @if (package(); as pkg) {

Standard Actions

+
- -

- Actions for {{ (pkg | toManifest).title }} -

-
+ @if (pkg.actions.length) { +

Actions for {{ pkg.manifest.title }}

+ } + @for (action of pkg.actions; track $index) { + @if (action.visibility !== 'hidden') { -
-
+ } + } } `, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [ - CommonModule, - ServiceActionComponent, - GroupActionsPipe, - ToManifestPipe, - ], + imports: [ServiceActionComponent], }) export class ServiceActionsRoute { - private readonly id = getPkgId(inject(ActivatedRoute)) - - readonly pkg$ = this.patch - .watch$('packageData', this.id) - .pipe(filter(pkg => pkg.stateInfo.state === 'installed')) - - readonly action = { - icon: '@tui.trash-2', - name: 'Uninstall', - description: - 'This will uninstall the service from StartOS and delete all data permanently.', - } - - constructor( - private readonly embassyApi: ApiService, - private readonly dialogs: TuiDialogService, - private readonly errorService: ErrorService, - private readonly loader: LoadingService, - private readonly router: Router, - private readonly patch: PatchDB, - private readonly formDialog: FormDialogService, - ) {} - - async handleAction(action: WithId) { - // @TODO Matt this needs complete rework, right? - // if (action.disabled) { - // this.dialogs - // .open(action.disabled, { - // label: 'Forbidden', - // size: 's', - // }) - // .subscribe() - // } else { - // if (action.input && !isEmptyObject(action.input)) { - // this.formDialog.open(FormComponent, { - // label: action.name, - // data: { - // spec: action.input, - // buttons: [ - // { - // text: 'Execute', - // handler: async (value: any) => - // this.executeAction(action.id, value), - // }, - // ], - // }, - // }) - // } else { - // this.dialogs - // .open(TUI_CONFIRM, { - // label: 'Confirm', - // size: 's', - // data: { - // content: `Are you sure you want to execute action "${ - // action.name - // }"? ${action.warning || ''}`, - // yes: 'Execute', - // no: 'Cancel', - // }, - // }) - // .pipe(filter(Boolean)) - // .subscribe(() => this.executeAction(action.id)) - // } - // } - } - - async tryUninstall(pkg: PackageDataEntry): Promise { - const { title, alerts, id } = getManifest(pkg) - - let content = - alerts.uninstall || - `Uninstalling ${title} will permanently delete its data` - - if (hasCurrentDeps(id, await getAllPackages(this.patch))) { - content = `${content}. Services that depend on ${title} will no longer work properly and may crash` - } - - this.dialogs - .open(TUI_CONFIRM, { - label: 'Warning', - size: 's', - data: { - content, - yes: 'Uninstall', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => this.uninstall()) + private readonly actions = inject(ActionService) + + readonly service = inject(StandardActionsService) + readonly package = toSignal( + inject>(PatchDB) + .watch$('packageData', getPkgId()) + .pipe( + filter(pkg => pkg.stateInfo.state === 'installed'), + map(pkg => ({ + mainStatus: pkg.status.main, + icon: pkg.icon, + manifest: getManifest(pkg), + actions: Object.keys(pkg.actions).map(id => ({ + id, + ...pkg.actions[id], + })), + })), + ), + ) + + readonly rebuild = REBUILD + readonly uninstall = UNINSTALL + + handleAction( + mainStatus: T.MainStatus['main'], + icon: string, + manifest: T.Manifest, + action: T.ActionMetadata & { id: string }, + ) { + this.actions.present({ + pkgInfo: { id: manifest.id, title: manifest.title, icon, mainStatus }, + actionInfo: { id: action.id, metadata: action }, + }) } +} - private async uninstall() { - const loader = this.loader.open(`Beginning uninstall...`).subscribe() - - try { - await this.embassyApi.uninstallPackage({ id: this.id }) - this.embassyApi - .setDbValue(['ack-instructions', this.id], false) - .catch(e => console.error('Failed to mark instructions as unseen', e)) - this.router.navigate(['./portal/dashboard']) - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - private async executeAction( - actionId: string, - input?: object, - ): Promise { - const loader = this.loader.open('Executing action...').subscribe() - - try { - // @TODO Matt this needs complete rework, right? - // const data = await this.embassyApi.executePackageAction({ - // id: this.id, - // actionId, - // input, - // }) - - timer(500) - .pipe( - switchMap(() => - this.dialogs.open(new PolymorpheusComponent(ActionSuccessPage), { - label: 'Execution Complete', - // data, - }), - ), - ) - .subscribe() - - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } finally { - loader.unsubscribe() - } - } +const REBUILD = { + icon: '@tui.wrench', + name: 'Rebuild Service', + description: + 'Rebuilds the service container. It is harmless and only takes a few seconds to complete, but it should only be necessary if a StartOS bug is preventing dependencies, interfaces, or actions from synchronizing.', +} - asIsOrder() { - return 0 - } +const UNINSTALL = { + icon: '@tui.trash-2', + name: 'Uninstall', + description: + 'Uninstalls this service from StartOS and delete all data permanently.', } diff --git a/web/projects/ui/src/app/routes/portal/routes/service/routes/interface.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/routes/interface.component.ts index ad92d4553a..5c5be17865 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/routes/interface.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/routes/interface.component.ts @@ -21,12 +21,12 @@ import { getMultihostAddresses } from '../../../components/interfaces/interface. imports: [CommonModule, InterfaceComponent], }) export class ServiceInterfaceRoute { - private readonly route = inject(ActivatedRoute) private readonly patch = inject>(PatchDB) readonly context = { - packageId: getPkgId(this.route), - interfaceId: this.route.snapshot.paramMap.get('interfaceId') || '', + packageId: getPkgId(), + interfaceId: + inject(ActivatedRoute).snapshot.paramMap.get('interfaceId') || '', } readonly interfacesWithAddresses$ = combineLatest([ diff --git a/web/projects/ui/src/app/routes/portal/routes/service/routes/logs.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/routes/logs.component.ts index d542fcac5c..90ff05069a 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/routes/logs.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/routes/logs.component.ts @@ -1,9 +1,8 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core' -import { ActivatedRoute } from '@angular/router' import { getPkgId } from '@start9labs/shared' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { RR } from 'src/app/services/api/api.types' import { LogsComponent } from 'src/app/routes/portal/components/logs/logs.component' +import { RR } from 'src/app/services/api/api.types' +import { ApiService } from 'src/app/services/api/embassy-api.service' @Component({ template: '', @@ -15,7 +14,7 @@ import { LogsComponent } from 'src/app/routes/portal/components/logs/logs.compon export class ServiceLogsRoute { private readonly api = inject(ApiService) - readonly id = getPkgId(inject(ActivatedRoute)) + readonly id = getPkgId() readonly follow = async (params: RR.FollowServerLogsReq) => this.api.followPackageLogs({ id: this.id, ...params }) diff --git a/web/projects/ui/src/app/routes/portal/routes/service/routes/service.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/routes/service.component.ts index 2925fc2aee..1c102265df 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/routes/service.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/routes/service.component.ts @@ -5,11 +5,6 @@ import { isEmptyObject } from '@start9labs/shared' import { T } from '@start9labs/start-sdk' import { PatchDB } from 'patch-db-client' import { combineLatest, map, switchMap } from 'rxjs' -// @TODO Alex implement config -// import { -// ConfigModal, -// PackageConfigData, -// } from 'src/app/routes/portal/modals/config.component' import { ServiceBackupsComponent } from 'src/app/routes/portal/routes/service/components/backups.component' import { InstallingProgressPipe } from 'src/app/routes/portal/routes/service/pipes/install-progress.pipe' import { ConnectionService } from 'src/app/services/connection.service' @@ -30,13 +25,16 @@ import { } from 'src/app/services/pkg-status-rendering.service' import { DependentInfo } from 'src/app/types/dependent-info' import { getManifest } from 'src/app/utils/get-package-data' +import { ServiceActionRequestComponent } from '../components/action-request.component' import { ServiceActionsComponent } from '../components/actions.component' import { ServiceDependenciesComponent } from '../components/dependencies.component' +import { ServiceErrorComponent } from '../components/error.component' import { ServiceHealthChecksComponent } from '../components/health-checks.component' import { ServiceInterfaceListComponent } from '../components/interface-list.component' import { ServiceMenuComponent } from '../components/menu.component' import { ServiceProgressComponent } from '../components/progress.component' import { ServiceStatusComponent } from '../components/status.component' +import { ToActionRequestsPipe } from '../pipes/to-action-requests.pipe' import { DependencyInfo } from '../types/dependency-info' @Component({ @@ -61,10 +59,17 @@ import { DependencyInfo } from '../types/dependency-info'
-
-

Metrics

- TODO -
+ @if (service.pkg.status.main === 'error') { +
+

Error

+ +
+ } @else { +
+

Metrics

+ TODO +
+ }

Menu

@@ -72,6 +77,34 @@ import { DependencyInfo } from '../types/dependency-info'
+ @if (service.pkg | toActionRequests: service.allPkgs; as requests) { + @if (requests.critical.length) { +
+

Required Actions

+ @for (request of requests.critical; track $index) { + + } +
+ } + + @if (requests.important.length) { +
+

Requested Actions

+ @for (request of requests.important; track $index) { + + } +
+ } + } +

Health Checks

@@ -124,6 +157,24 @@ import { DependencyInfo } from '../types/dependency-info' background: var(--tui-background-neutral-1); box-shadow: inset 0 7rem 0 -4rem var(--tui-background-neutral-1); clip-path: polygon(0 1.5rem, 1.5rem 0, 100% 0, 100% 100%, 0 100%); + + &.error { + box-shadow: inset 0 7rem 0 -4rem var(--tui-status-negative-pale); + grid-column: span 6; + + h3 { + color: var(--tui-status-negative); + } + } + + ::ng-deep [tuiCell] { + width: stretch; + margin: 0 -1rem; + + &:not(:last-child) { + box-shadow: 0 0.51rem 0 -0.5rem; + } + } } h3 { @@ -153,6 +204,9 @@ import { DependencyInfo } from '../types/dependency-info' ServiceDependenciesComponent, ServiceMenuComponent, ServiceBackupsComponent, + ServiceActionRequestComponent, + ServiceErrorComponent, + ToActionRequestsPipe, InstallingProgressPipe, ], }) @@ -169,15 +223,20 @@ export class ServiceRoute { readonly service$ = this.pkgId$.pipe( switchMap(pkgId => combineLatest([ - this.patch.watch$('packageData', pkgId), + this.patch.watch$('packageData'), this.depErrorService.getPkgDepErrors$(pkgId), - ]), + ]).pipe( + map(([allPkgs, depErrors]) => { + const pkg = allPkgs[pkgId] + return { + allPkgs, + pkg, + dependencies: this.getDepInfo(pkg, depErrors), + status: renderPkgStatus(pkg, depErrors), + } + }), + ), ), - map(([pkg, depErrors]) => ({ - pkg, - dependencies: this.getDepInfo(pkg, depErrors), - status: renderPkgStatus(pkg, depErrors), - })), ) readonly health$ = this.pkgId$.pipe( @@ -263,11 +322,8 @@ export class ServiceRoute { errorText = 'Incorrect version' fixText = 'Update' fixAction = () => this.fixDep(pkg, pkgManifest, 'update', depId) - // @TODO Matt do we just remove this case? - // } else if (depError.type === 'configUnsatisfied') { - // errorText = 'Config not satisfied' - // fixText = 'Auto config' - // fixAction = () => this.fixDep(pkg, pkgManifest, 'configure', depId) + } else if (depError.type === 'actionRequired') { + errorText = 'Action Required (see above)' } else if (depError.type === 'notRunning') { errorText = 'Not running' fixText = 'Start' diff --git a/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/backup.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/backup.component.ts index 3c792878ec..56b355edac 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/backup.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/backup.component.ts @@ -51,7 +51,7 @@ interface Package { }