fix: finish porting minor changes to major (#2799)

This commit is contained in:
Alex Inkin
2024-12-12 03:16:46 +04:00
committed by GitHub
parent 115c599fd8
commit 89ab67e067
76 changed files with 1176 additions and 1376 deletions

486
web/package-lock.json generated
View File

@@ -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",

View File

@@ -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"

View File

@@ -1,12 +1,12 @@
<tui-root>
<main>
<img class="logo" src="assets/img/icon.png" alt="Start9" />
<section tuiCardLarge tuiSurface="elevated" class="card">
<section tuiCardLarge tuiSurface="floating" class="card">
<header class="header">
@if (selected) {
<button
tuiIconButton
appearance="flat"
appearance="flat-grayscale"
size="m"
class="back"
iconStart="@tui.chevron-left"

View File

@@ -3,10 +3,7 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { Exver, MarkdownPipeModule } from '@start9labs/shared'
import { TuiButton, TuiDialogContext, TuiLoader } from '@taiga-ui/core'
import { TuiAccordion } from '@taiga-ui/kit'
import {
POLYMORPHEUS_CONTEXT,
PolymorpheusComponent,
} from '@taiga-ui/polymorpheus'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { MarketplacePkg } from '../../src/types'
@Component({
@@ -35,7 +32,7 @@ import { MarketplacePkg } from '../../src/types'
export class ReleaseNotesComponent {
private readonly exver = inject(Exver)
private readonly pkg =
inject<TuiDialogContext<void, MarketplacePkg>>(POLYMORPHEUS_CONTEXT).data
injectContext<TuiDialogContext<void, MarketplacePkg>>().data
readonly notes = Object.entries(this.pkg.otherVersions)
.filter(

View File

@@ -22,7 +22,7 @@ import { PolymorpheusContent } from '@taiga-ui/polymorpheus'
>
<button
tuiIconButton
appearance="flat"
appearance="flat-grayscale"
iconStart="@tui.chevron-left"
title="Previous"
type="button"
@@ -60,7 +60,7 @@ import { PolymorpheusContent } from '@taiga-ui/polymorpheus'
</tui-carousel>
<button
tuiIconButton
appearance="flat"
appearance="flat-grayscale"
type="button"
iconStart="@tui.chevron-right"
title="Next"

View File

@@ -17,10 +17,7 @@ import {
} from '@taiga-ui/core'
import { TUI_VALIDATION_ERRORS, TuiFieldErrorPipe } from '@taiga-ui/kit'
import { TuiInputModule, TuiInputPasswordModule } from '@taiga-ui/legacy'
import {
POLYMORPHEUS_CONTEXT,
PolymorpheusComponent,
} from '@taiga-ui/polymorpheus'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { SERVERS, ServersResponse } from 'src/app/components/servers.component'
import { ApiService } from 'src/app/services/api.service'
@@ -108,8 +105,7 @@ export class CifsComponent {
private readonly dialogs = inject(TuiDialogService)
private readonly api = inject(ApiService)
private readonly loader = inject(LoadingService)
private readonly context =
inject<TuiDialogContext<CifsResponse>>(POLYMORPHEUS_CONTEXT)
private readonly context = injectContext<TuiDialogContext<CifsResponse>>()
readonly form = new FormGroup({
hostname: new FormControl('', {

View File

@@ -1,13 +1,10 @@
import { TuiInputPasswordModule } from '@taiga-ui/legacy'
import { Component, inject } from '@angular/core'
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'
import * as argon2 from '@start9labs/argon2'
import { ErrorService } from '@start9labs/shared'
import { TuiDialogContext, TuiError, TuiButton } from '@taiga-ui/core'
import {
POLYMORPHEUS_CONTEXT,
PolymorpheusComponent,
} from '@taiga-ui/polymorpheus'
import { TuiButton, TuiDialogContext, TuiError } from '@taiga-ui/core'
import { TuiInputPasswordModule } from '@taiga-ui/legacy'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
interface DialogData {
passwordHash?: string
@@ -67,7 +64,7 @@ interface DialogData {
export class PasswordComponent {
private readonly errorService = inject(ErrorService)
private readonly context =
inject<TuiDialogContext<string, DialogData>>(POLYMORPHEUS_CONTEXT)
injectContext<TuiDialogContext<string, DialogData>>()
readonly storageDrive = this.context.data.storageDrive
readonly password = new FormControl('', { nonNullable: true })

View File

@@ -1,10 +1,7 @@
import { Component, inject } from '@angular/core'
import { Component } from '@angular/core'
import { ServerComponent } from '@start9labs/shared'
import { TuiDialogContext } from '@taiga-ui/core'
import {
POLYMORPHEUS_CONTEXT,
PolymorpheusComponent,
} from '@taiga-ui/polymorpheus'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PasswordDirective } from 'src/app/components/password.directive'
import { StartOSDiskInfoWithId } from 'src/app/services/api.service'
@@ -27,8 +24,7 @@ export interface ServersResponse {
imports: [ServerComponent, PasswordDirective],
})
export class ServersComponent {
readonly context =
inject<TuiDialogContext<ServersResponse, Data>>(POLYMORPHEUS_CONTEXT)
readonly context = injectContext<TuiDialogContext<ServersResponse, Data>>()
select(password: string, serverId: string) {
this.context.completeWith({ serverId, password })

View File

@@ -18,7 +18,7 @@ import { StateService } from 'src/app/services/state.service'
@if (recover) {
<button
tuiIconButton
appearance="flat"
appearance="flat-grayscale"
class="back"
iconStart="@tui.chevron-left"
(click)="recover = false"

View File

@@ -40,7 +40,7 @@ import { StateService } from 'src/app/services/state.service'
<h3>You can now safely unplug your old StartOS data drive</h3>
}
<button tuiCardLarge tuiSurface="elevated" (click)="download()">
<button tuiCardLarge tuiSurface="floating" (click)="download()">
<strong class="caps">Download address info</strong>
<span>
start.local was for setup purposes only. It will no longer work.
@@ -53,7 +53,7 @@ import { StateService } from 'src/app/services/state.service'
<a
tuiCardLarge
tuiSurface="elevated"
tuiSurface="floating"
target="_blank"
[attr.href]="disableLogin ? null : lanAddress"
>

View File

@@ -1,9 +1,6 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { TuiLoader } from '@taiga-ui/core'
import {
POLYMORPHEUS_CONTEXT,
PolymorpheusContent,
} from '@taiga-ui/polymorpheus'
import { injectContext, PolymorpheusContent } from '@taiga-ui/polymorpheus'
@Component({
standalone: true,
@@ -13,6 +10,5 @@ import {
imports: [TuiLoader],
})
export class LoadingComponent {
readonly content: PolymorpheusContent =
inject(POLYMORPHEUS_CONTEXT)['content']
readonly content = injectContext<{ content: PolymorpheusContent }>().content
}

View File

@@ -1,6 +1,9 @@
import { Component, Inject } from '@angular/core'
import { TuiDialogContext } from '@taiga-ui/core'
import { POLYMORPHEUS_CONTEXT } from '@taiga-ui/polymorpheus'
import {
POLYMORPHEUS_CONTEXT,
PolymorpheusComponent,
} from '@taiga-ui/polymorpheus'
import {
catchError,
ignoreElements,
@@ -42,3 +45,5 @@ export class MarkdownComponent {
return this.context.label || ''
}
}
export const MARKDOWN = new PolymorpheusComponent(MarkdownComponent)

View File

@@ -1,7 +1,8 @@
import { inject } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
export function getPkgId({ snapshot }: ActivatedRoute): string {
const pkgId = snapshot.paramMap.get('pkgId')
export function getPkgId(): string {
const pkgId = inject(ActivatedRoute).snapshot.paramMap.get('pkgId')
if (!pkgId) {
throw new Error('pkgId is missing from route params')

View File

@@ -32,11 +32,6 @@
color: var(--tui-status-negative);
}
[tuiAppearance][data-appearance='flat'],
[tuiAppearance][data-appearance='outline'] {
color: var(--tui-text-primary);
}
[tuiAppearance][data-appearance='primary'] {
@include appearance-disabled {
background: #eaecee;

View File

@@ -36,14 +36,14 @@ import { BackupReport } from 'src/app/services/api/api.types'
})
export class BackupsReportModal {
private readonly context =
inject<TuiDialogContext<void, { report: BackupReport; timestamp: string }>>(
POLYMORPHEUS_CONTEXT,
)
inject<
TuiDialogContext<void, { content: BackupReport; timestamp: string }>
>(POLYMORPHEUS_CONTEXT)
readonly system = this.getSystem()
get report(): BackupReport {
return this.context.data.report
return this.context.data.content
}
get timestamp(): string {

View File

@@ -1,42 +0,0 @@
import { NgIf } from '@angular/common'
import {
ChangeDetectionStrategy,
Component,
inject,
Input,
} from '@angular/core'
import { TuiIconModule, TuiTitleModule } from '@taiga-ui/experimental'
import { ConfigService } from 'src/app/services/config.service'
import { StoreIconComponent } from './store-icon.component'
@Component({
standalone: true,
selector: '[registry]',
template: `
<store-icon
[url]="registry.url"
[marketplace]="marketplace"
size="40px"
></store-icon>
<div tuiTitle>
{{ registry.name }}
<div tuiSubtitle>{{ registry.url }}</div>
</div>
<tui-icon
*ngIf="registry.selected; else content"
icon="@tui.check"
[style.color]="'var(--tui-positive)'"
/>
<ng-template #content><ng-content></ng-content></ng-template>
`,
styles: [':host { border-radius: 0.25rem; width: stretch; }'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgIf, StoreIconComponent, TuiIconModule, TuiTitleModule],
})
export class MarketplaceRegistryComponent {
readonly marketplace = inject(ConfigService).marketplace
@Input()
registry!: { url: string; selected: boolean; name?: string }
}

View File

@@ -1,46 +0,0 @@
import { NgIf } from '@angular/common'
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { sameUrl } from '@start9labs/shared'
@Component({
standalone: true,
selector: 'store-icon',
template: `
<img
*ngIf="icon; else noIcon"
[style.border-radius.%]="100"
[style.max-width]="size || '100%'"
[src]="icon"
alt="Marketplace Icon"
/>
<ng-template #noIcon>
<img
[style.max-width]="size || '100%'"
[style.border-radius]="0"
src="assets/img/storefront-outline.png"
alt="Marketplace Icon"
/>
</ng-template>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [NgIf],
})
export class StoreIconComponent {
@Input()
url = ''
@Input()
size?: string
@Input()
marketplace!: any
get icon() {
const { start9, community } = this.marketplace
if (sameUrl(this.url, start9)) {
return 'assets/img/icon_transparent.png'
} else if (sameUrl(this.url, community)) {
return 'assets/img/community-store.png'
}
return null
}
}

View File

@@ -1,45 +0,0 @@
<ng-container *ngIf="actionRequests.critical.length">
<ion-item-divider>Required Actions</ion-item-divider>
<ion-item
*ngFor="let request of actionRequests.critical"
button
(click)="handleAction(request)"
>
<ion-icon slot="start" name="warning-outline" color="warning"></ion-icon>
<ion-label>
<h2 class="highlighted">{{ request.actionName }}</h2>
<p *ngIf="request.dependency" class="dependency">
<span class="light">Service:</span>
<img [src]="request.dependency.icon" alt="" />
{{ request.dependency.title }}
</p>
<p>
<span class="light">Reason:</span>
{{ request.reason || 'no reason provided' }}
</p>
</ion-label>
</ion-item>
</ng-container>
<ng-container *ngIf="actionRequests.important.length">
<ion-item-divider>Requested Actions</ion-item-divider>
<ion-item
*ngFor="let request of actionRequests.important"
button
(click)="handleAction(request)"
>
<ion-icon slot="start" name="play-outline" color="warning"></ion-icon>
<ion-label>
<h2 class="highlighted">{{ request.actionName }}</h2>
<p *ngIf="request.dependency" class="dependency">
<span class="light">Service:</span>
<img [src]="request.dependency.icon" alt="" />
{{ request.dependency.title }}
</p>
<p>
<span class="light">Reason:</span>
{{ request.reason || 'no reason provided' }}
</p>
</ion-label>
</ion-item>
</ng-container>

View File

@@ -1,16 +0,0 @@
.light {
color: var(--ion-color-dark);
}
.highlighted {
color: var(--ion-color-dark);
font-weight: bold;
}
.dependency {
display: inline-flex;
img {
max-width: 16px;
margin: 0 2px 0 5px;
}
}

View File

@@ -1,94 +0,0 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { T } from '@start9labs/start-sdk'
import { ActionService } from 'src/app/services/action.service'
import { getDepDetails } from 'src/app/util/dep-info'
@Component({
selector: 'app-show-action-requests',
templateUrl: './app-show-action-requests.component.html',
styleUrls: ['./app-show-action-requests.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppShowActionRequestsComponent {
@Input()
allPkgs!: Record<string, T.PackageDataEntry>
@Input()
pkg!: T.PackageDataEntry
@Input()
manifest!: T.Manifest
get actionRequests() {
const critical: (T.ActionRequest & {
actionName: string
dependency: {
title: string
icon: string
} | null
})[] = []
const important: (T.ActionRequest & {
actionName: string
dependency: {
title: string
icon: string
} | null
})[] = []
Object.values(this.pkg.requestedActions)
.filter(r => r.active)
.forEach(r => {
const self = r.request.packageId === this.manifest.id
const toReturn = {
...r.request,
actionName: self
? this.pkg.actions[r.request.actionId].name
: this.allPkgs[r.request.packageId]?.actions[r.request.actionId]
.name || 'Unknown Action',
dependency: self
? null
: getDepDetails(this.pkg, this.allPkgs, r.request.packageId),
}
if (r.request.severity === 'critical') {
critical.push(toReturn)
} else {
important.push(toReturn)
}
})
return { critical, important }
}
constructor(private readonly actionService: ActionService) {}
async handleAction(request: T.ActionRequest) {
const self = request.packageId === this.manifest.id
this.actionService.present({
pkgInfo: {
id: request.packageId,
title: self
? this.manifest.title
: getDepDetails(this.pkg, this.allPkgs, request.packageId).title,
mainStatus: self
? this.pkg.status.main
: this.allPkgs[request.packageId].status.main,
icon: self
? this.pkg.icon
: getDepDetails(this.pkg, this.allPkgs, request.packageId).icon,
},
actionInfo: {
id: request.actionId,
metadata:
request.packageId === this.manifest.id
? this.pkg.actions[request.actionId]
: this.allPkgs[request.packageId].actions[request.actionId],
},
requestInfo: {
request,
dependentId:
request.packageId === this.manifest.id ? undefined : this.manifest.id,
},
})
}
}

View File

@@ -1,31 +0,0 @@
<ion-item-divider>Message</ion-item-divider>
<div class="code-block ion-margin">
<code>
<ion-text color="warning">{{ error.message }}</ion-text>
</code>
</div>
<ion-item-divider>Actions</ion-item-divider>
<div class="ion-margin">
<p>
<b>Rebuild Container</b>
is harmless action that and only takes a few seconds to complete. It will
likely resolve this issue.
<b>Uninstall Service</b>
is a dangerous action that will remove the service from StartOS and wipe all
its data.
</p>
<ion-button class="ion-margin-end" (click)="rebuild()">
Rebuild Container
</ion-button>
<ion-button (click)="tryUninstall()" color="danger">
Uninstall Service
</ion-button>
</div>
<ng-container *ngIf="error.debug">
<ion-item-divider>Full Stack Trace</ion-item-divider>
<div class="code-block ion-margin">
<code>{{ error.message }}</code>
</div>
</ng-container>

View File

@@ -1,45 +0,0 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { ToastController } from '@ionic/angular'
import { copyToClipboard } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { StandardActionsService } from 'src/app/services/standard-actions.service'
@Component({
selector: 'app-show-error',
templateUrl: 'app-show-error.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppShowErrorComponent {
@Input()
manifest!: T.Manifest
@Input()
error!: T.MainStatus & { main: 'error' }
constructor(
private readonly toastCtrl: ToastController,
private readonly standardActionsService: StandardActionsService,
) {}
async copy(text: string): Promise<void> {
const success = await copyToClipboard(text)
const message = success
? 'Copied to clipboard!'
: 'Failed to copy to clipboard.'
const toast = await this.toastCtrl.create({
header: message,
position: 'bottom',
duration: 1000,
})
await toast.present()
}
async rebuild() {
return this.standardActionsService.rebuild(this.manifest.id)
}
async tryUninstall() {
return this.standardActionsService.tryUninstall(this.manifest)
}
}

View File

@@ -1,7 +1,7 @@
<div
*ngIf="!caTrusted; else trusted"
tuiCardLarge
tuiSurface="elevated"
tuiSurface="floating"
class="card"
>
<tui-icon icon="@tui.lock" [style.font-size.rem]="4" />
@@ -72,7 +72,7 @@
<button
tuiButton
size="s"
appearance="flat"
appearance="flat-grayscale"
iconEnd="@tui.external-link"
(click)="launchHttps()"
[disabled]="caTrusted"
@@ -83,7 +83,7 @@
</div>
<ng-template #trusted>
<div tuiCardLarge tuiSurface="elevated" class="card">
<div tuiCardLarge tuiSurface="floating" class="card">
<tui-icon
icon="@tui.shield"
tuiAppearance="icon-success"

View File

@@ -51,7 +51,7 @@ export interface FormContext<T> {
<button
*ngIf="button.handler; else link"
tuiButton
[appearance]="last ? 'primary' : 'flat'"
[appearance]="last ? 'primary' : 'flat-grayscale'"
[type]="last ? 'submit' : 'button'"
(click)="onClick(button.handler)"
>
@@ -60,7 +60,7 @@ export interface FormContext<T> {
<ng-template #link>
<a
tuiButton
appearance="flat"
appearance="flat-grayscale"
[routerLink]="button.link"
(click)="close()"
>

View File

@@ -22,6 +22,6 @@
[(ngModel)]="value"
(click.stop)="(0)"
/>
<tui-icon icon="@tui.paint" tuiAppearance="icon" class="icon" />
<tui-icon icon="@tui.paint-bucket" tuiAppearance="icon" class="icon" />
</div>
</ng-template>

View File

@@ -11,7 +11,7 @@
position: absolute;
height: 0.3rem;
width: 1.4rem;
bottom: 0.125rem;
bottom: -0.25rem;
background: currentColor;
border-radius: 0.125rem;
pointer-events: none;

View File

@@ -30,7 +30,7 @@
<button
tuiButton
type="button"
appearance="flat"
appearance="flat-grayscale"
size="s"
(click)="completeWith(false)"
>

View File

@@ -7,8 +7,8 @@ import {
OnDestroy,
} from '@angular/core'
import { pauseFor } from '@start9labs/shared'
import { POLYMORPHEUS_CONTEXT } from '@taiga-ui/polymorpheus'
import { TuiDialogContext, TuiButton } from '@taiga-ui/core'
import { TuiButton, TuiDialogContext } from '@taiga-ui/core'
import { injectContext } from '@taiga-ui/polymorpheus'
@Component({
standalone: true,
@@ -44,8 +44,7 @@ import { TuiDialogContext, TuiButton } from '@taiga-ui/core'
})
export class HeaderSnekComponent implements AfterViewInit, OnDestroy {
private readonly document = inject(DOCUMENT)
private readonly dialog =
inject<TuiDialogContext<number, number>>(POLYMORPHEUS_CONTEXT)
private readonly dialog = injectContext<TuiDialogContext<number, number>>()
highScore: number = this.dialog.data
score = 0

View File

@@ -25,7 +25,7 @@ import { AddressDetails } from './interface.utils'
*ngIf="network$ | async as network"
clearnetAddresses
tuiCardLarge="compact"
tuiSurface="elevated"
tuiSurface="floating"
[network]="network"
[addresses]="serviceInterface.addresses.clearnet"
>
@@ -46,7 +46,7 @@ import { AddressDetails } from './interface.utils'
<app-address-group
torAddresses
tuiCardLarge="compact"
tuiSurface="elevated"
tuiSurface="floating"
[addresses]="serviceInterface.addresses.tor"
>
<em>
@@ -66,7 +66,7 @@ import { AddressDetails } from './interface.utils'
<app-address-group
localAddresses
tuiCardLarge="compact"
tuiSurface="elevated"
tuiSurface="floating"
[addresses]="serviceInterface.addresses.local"
>
<em>

View File

@@ -49,7 +49,7 @@
<footer class="footer">
<button
tuiButton
appearance="flat"
appearance="flat-grayscale"
iconStart="@tui.circle-arrow-down"
(click)="setScroll(true); scrollToBottom()"
>
@@ -57,7 +57,7 @@
</button>
<button
tuiButton
appearance="flat"
appearance="flat-grayscale"
iconStart="@tui.download"
[logsDownload]="fetchLogs"
>

View File

@@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { TuiDialogContext } from '@taiga-ui/core'
import { POLYMORPHEUS_CONTEXT } from '@taiga-ui/polymorpheus'
import { injectContext } from '@taiga-ui/polymorpheus'
import { QrCodeModule } from 'ng-qrcode'
@Component({
@@ -11,6 +11,5 @@ import { QrCodeModule } from 'ng-qrcode'
imports: [QrCodeModule],
})
export class QRModal {
readonly context =
inject<TuiDialogContext<void, string>>(POLYMORPHEUS_CONTEXT)
readonly context = injectContext<TuiDialogContext<void, string>>()
}

View File

@@ -10,7 +10,7 @@ import { TuiLet } from '@taiga-ui/cdk'
import { TuiButton, tuiButtonOptionsProvider } from '@taiga-ui/core'
import { map } from 'rxjs'
import { UILaunchComponent } from 'src/app/routes/portal/routes/dashboard/ui.component'
import { ActionsService } from 'src/app/services/actions.service'
import { ControlsService } from 'src/app/services/controls.service'
import { DepErrorService } from 'src/app/services/dep-error.service'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { renderPkgStatus } from 'src/app/services/pkg-status-rendering.service'
@@ -26,7 +26,7 @@ const RUNNING = ['running', 'starting', 'restarting']
<button
tuiIconButton
iconStart="@tui.square"
(click)="actions.stop(manifest())"
(click)="controls.stop(manifest())"
>
Stop
</button>
@@ -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
</button>
@@ -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
</button>
<button
tuiIconButton
iconStart="@tui.wrench"
(click)="actions.configure(manifest())"
>
Configure
</button>
}
<app-ui-launch [pkg]="pkg()" />
@@ -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<PackageDataEntry>()
readonly status = computed(() => renderPkgStatus(this.pkg()))
readonly running = computed(() => RUNNING.includes(this.status().primary))
readonly manifest = computed(() => getManifest(this.pkg()))

View File

@@ -21,7 +21,7 @@ import { DepErrorService } from 'src/app/services/dep-error.service'
<th>Name</th>
<th>Version</th>
<th [style.width.rem]="13">Status</th>
<th [style.width.rem]="8" [style.text-indent.rem]="1">Controls</th>
<th [style.width.rem]="8" [style.text-indent.rem]="1.5">Controls</th>
</tr>
</thead>
<tbody>

View File

@@ -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':

View File

@@ -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: `
<tui-icon class="g-warning" [icon]="icon" />
<span tuiTitle>
<strong>{{ actionRequest.actionName }}</strong>
@if (actionRequest.dependency) {
<span tuiSubtitle>
<strong>Service:</strong>
<img
alt=""
[src]="actionRequest.dependency.icon"
[style.width.rem]="1"
/>
{{ actionRequest.dependency.title }}
</span>
}
<span tuiSubtitle>
{{ actionRequest.reason || 'no reason provided' }}
</span>
</span>
`,
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<string, PackageDataEntry>
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,
},
})
}
}

View File

@@ -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: `
<tui-icon [icon]="action.icon" />
<div>
<tui-icon [icon]="action.icon || '@tui.circle-play'" />
<div tuiTitle>
<strong>{{ action.name }}</strong>
<div>{{ action.description }}</div>
<div tuiSubtitle>{{ action.description }}</div>
@if (disabled) {
<div tuiSubtitle class="g-warning">{{ disabled }}</div>
}
</div>
`,
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
)
}
}

View File

@@ -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
</button>
}
@if (canConfigure) {
<button
tuiButton
appearance="secondary-warning"
iconStart="@tui.wrench"
(click)="actions.configure(manifest)"
>
Configure
</button>
}
`,
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)

View File

@@ -6,14 +6,8 @@ import { ServiceDependencyComponent } from './dependency.component'
selector: 'service-dependencies',
template: `
@for (dep of dependencies; track $index) {
<button
class="g-action"
[serviceDependency]="dep"
(click)="dep.action()"
></button>
}
@if (!dependencies.length) {
<button [serviceDependency]="dep"></button>
} @empty {
No dependencies
}
`,

View File

@@ -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: `
<img [src]="dep.icon" alt="" />
<span [style.flex]="1">
<span tuiTitle>
<strong>
@if (dep.errorText) {
<tui-icon icon="@tui.triangle-alert" [style.color]="color" />
}
{{ dep.title }}
</strong>
<div>{{ dep.version }}</div>
<div [style.color]="color">{{ dep.errorText || 'Satisfied' }}</div>
<span tuiSubtitle>{{ dep.version }}</span>
<span tuiSubtitle="" [style.color]="color">
{{ dep.errorText || 'Satisfied' }}
</span>
</span>
@if (dep.actionText) {
<div>
<span>
{{ dep.actionText }}
<tui-icon icon="@tui.arrow-right" />
</div>
</span>
}
`,
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' })

View File

@@ -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: `
<tui-line-clamp
style="pointer-events: none; margin: 1rem 0 -1rem; color: var(--tui-status-negative);"
[linesLimit]="2"
[content]="error?.message"
(overflownChange)="overflow = $event"
/>
<h4 class="g-title">
<span [style.display]="'flex'">
Actions
<tui-icon [style.margin-left.rem]="0.25" [tuiTooltip]="hint" />
</span>
</h4>
<ng-template #hint>
<div>
<b>Rebuild Container</b>
is harmless action that and only takes a few seconds to complete. It
will likely resolve this issue.
</div>
<b>Uninstall Service</b>
is a dangerous action that will remove the service from StartOS and wipe
all its data.
</ng-template>
<p style="display: flex; flex-wrap: wrap; gap: 1rem">
<button tuiButton (click)="rebuild()">Rebuild Container</button>
<button tuiButton appearance="negative" (click)="uninstall()">
Uninstall Service
</button>
@if (overflow) {
<button tuiButton appearance="secondary-grayscale" (click)="show()">
View full message
</button>
}
</p>
`,
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<void> {
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()
}
}

View File

@@ -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"
/>
}
<div>
<span tuiTitle>
<strong [class.tui-skeleton]="!connected">{{ check.name }}</strong>
<div [class.tui-skeleton]="!connected" [style.color]="color">
<span tuiSubtitle [class.tui-skeleton]="!connected" [style.color]="color">
{{ message }}
</div>
</div>
</span>
</span>
`,
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 })

View File

@@ -14,7 +14,6 @@ import { ConnectionService } from 'src/app/services/connection.service'
template: `
@for (check of checks; track $index) {
<service-health-check
class="g-action"
[check]="check"
[connected]="!!(connected$ | async)"
/>

View File

@@ -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 {
<tui-icon icon="@tui.circle-x" class="g-error" />
}
<div [style.flex]="1">
<span tuiTitle>
<strong>{{ info.name }}</strong>
<div>{{ info.description }}</div>
<span tuiSubtitle>{{ info.description }}</span>
@if (check) {
<div class="g-error">
<b>Health check failed:</b>
{{ check }}
</div>
<span tuiSubtitle class="g-error">
<span>
<b>Health check failed:</b>
{{ check }}
</span>
</span>
} @else {
<div [style.color]="info.color">{{ info.typeDetail }}</div>
<span tuiSubtitle [style.color]="info.color">
{{ info.typeDetail }}
</span>
}
</div>
</span>
@if (info.type === 'ui') {
<a
tuiIconButton
appearance="flat"
appearance="flat-grayscale"
iconStart="@tui.external-link"
target="_blank"
rel="noreferrer"
title="Open"
size="m"
[style.border-radius.%]="100"
[attr.href]="href"
(click.stop)="(0)"
@@ -52,7 +58,8 @@ import { ExtendedInterfaceInfo } from '../pipes/interface-info.pipe'
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [CommonModule, TuiButton, TuiLet, TuiLoader, TuiIcon],
imports: [CommonModule, TuiButton, TuiLet, TuiLoader, TuiIcon, TuiTitle],
hostDirectives: [TuiCell],
})
export class ServiceInterfaceListItemComponent {
private readonly config = inject(ConfigService)
@@ -73,7 +80,7 @@ export class ServiceInterfaceListItemComponent {
get href(): string | null {
return this.disabled
? null
? 'null'
: this.config.launchableAddress(this.info, this.pkg.hosts)
}
}

View File

@@ -10,7 +10,6 @@ import { ServiceInterfaceListItemComponent } from './interface-list-item.compone
template: `
@for (info of pkg | interfaceInfo; track $index) {
<a
class="g-action"
serviceInterfaceListItem
[info]="info"
[pkg]="pkg"

View File

@@ -1,23 +1,25 @@
import { TuiIcon } from '@taiga-ui/core'
import { TuiIcon, TuiTitle } from '@taiga-ui/core'
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { TuiCell } from '@taiga-ui/layout'
import { ServiceMenu } from '../pipes/to-menu.pipe'
@Component({
selector: '[serviceMenuItem]',
template: `
<tui-icon [icon]="menu.icon" />
<div [style.flex]="1">
<span tuiTitle [style.flex]="1">
<strong>{{ menu.name }}</strong>
<div>
<span tuiSubtitle>
{{ menu.description }}
<ng-content />
</div>
</div>
</span>
<ng-content />
</span>
<tui-icon icon="@tui.chevron-right" />
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [TuiIcon],
imports: [TuiIcon, TuiTitle],
hostDirectives: [TuiCell],
})
export class ServiceMenuItemComponent {
@Input({ required: true, alias: 'serviceMenuItem' })

View File

@@ -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) {
<a
class="g-action"
[serviceMenuItem]="menu"
[routerLink]="menu.routerLink"
[queryParams]="menu.params || {}"
></a>
} @else {
<button
class="g-action"
[serviceMenuItem]="menu"
(click)="menu.action?.()"
>
<button [serviceMenuItem]="menu" (click)="menu.action?.()">
@if (menu.name === 'Outbound Proxy') {
<div [style.color]="color">{{ pkg.outboundProxy || 'None' }}</div>
<div tuiSubtitle [style.color]="color">
{{ pkg.outboundProxy || 'None' }}
</div>
}
</button>
}

View File

@@ -7,8 +7,8 @@ import { InstallingProgressPipe } from 'src/app/routes/portal/routes/service/pip
selector: '[progress]',
template: `
<ng-content />
@if (progress | installingProgress; as decimal) {
: {{ decimal * 100 }}%
@if (progress | installingProgress; as percent) {
: {{ percent }}%
<progress
tuiProgressBar
size="xs"
@@ -17,7 +17,7 @@ import { InstallingProgressPipe } from 'src/app/routes/portal/routes/service/pip
? 'var(--tui-text-positive)'
: 'var(--tui-text-action)'
"
[value]="decimal * 100"
[value]="percent / 100"
></progress>
}
`,

View File

@@ -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: `
<label [style.flex]="1" tuiTitle>
<span tuiSubtitle>{{ label }}</span>
{{ masked ? mask : value }}
</label>
<button
tuiIconButton
appearance="flat"
[iconStart]="masked ? '@tui.eye' : '@tui.eye-off'"
(click)="masked = !masked"
>
Toggle
</button>
<button
tuiIconButton
appearance="flat"
iconStart="@tui.copy"
(click)="copyService.copy(value)"
>
Copy
</button>
`,
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)
}
}

View File

@@ -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: `
<div class="service-title">
<img [src]="pkgInfo.icon" alt="" />
<h4>{{ pkgInfo.title }}</h4>
</div>
@if (res$ | async; as res) {
@if (error) {
<tui-notification appearance="negative">
<div [innerHTML]="error"></div>
</tui-notification>
}
@if (warning) {
<tui-notification appearance="warning">
<div [innerHTML]="warning"></div>
</tui-notification>
}
@if (requestInfo) {
<action-request-info
[originalValue]="res.originalValue || {}"
[operations]="res.operations || []"
/>
}
<app-form
[spec]="res.spec"
[value]="res.originalValue || {}"
[buttons]="buttons"
[operations]="res.operations || []"
>
<button
tuiButton
appearance="flat-grayscale"
type="reset"
[style.margin-right]="'auto'"
>
Reset Defaults
</button>
</app-form>
} @else {
<tui-loader size="l" textContent="loading" />
}
`,
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<DataModel>>(PatchDB)
private readonly actionService = inject(ActionService)
private readonly context =
injectContext<TuiDialogContext<void, PackageActionData>>()
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<any>[] = [
{
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<boolean> {
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:<ul>'
const content = `${message}${breakages.map(
id => `<li><b>${getManifest(packages[id]).title}</b></li>`,
)}</ul>`
const data: TuiConfirmData = { content, yes: 'Continue', no: 'Cancel' }
return firstValueFrom(
this.dialogs.open<boolean>(TUI_CONFIRM, { data }).pipe(endWith(false)),
)
}
}

View File

@@ -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<TuiDialogOptions<PackageDataEntry>>(POLYMORPHEUS_CONTEXT).data
readonly pkg = injectContext<TuiDialogOptions<PackageDataEntry>>().data
}

View File

@@ -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) {
<tui-loader />
} @else {
@for (prop of properties | keyvalue: asIsOrder; track prop) {
<service-property [label]="prop.key" [value]="prop.value" />
} @empty {
No properties
}
}
<button tuiButton iconStart="@tui.refresh-cw" (click)="refresh()">
Refresh
</button>
`,
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<string, string> = {}
async ngOnInit() {
await this.getProperties()
}
async refresh() {
await this.getProperties()
}
private async getProperties(): Promise<void> {
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
}
}

View File

@@ -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<Array<WithId<T.ActionMetadata>>> | null {
if (!actions) return null
const noGroup = 'noGroup'
const grouped = Object.entries(actions).reduce<
Record<string, WithId<T.ActionMetadata>[]>
>((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)),
)
}
}

View File

@@ -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<string, PackageDataEntry>) {
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 }
}
}

View File

@@ -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: {

View File

@@ -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<PackageConfigData>(ConfigModal, {
// label: `${title} configuration`,
// data: { pkgId: id },
// })
}
}

View File

@@ -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 { 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 { 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 { toSignal } from '@angular/core/rxjs-interop'
import { ActivatedRoute } from '@angular/router'
import { getPkgId } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { getAllPackages, getManifest } from 'src/app/utils/get-package-data'
import { PatchDB } from 'patch-db-client'
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'
@Component({
template: `
@if (pkg$ | async; as pkg) {
@if (package(); as pkg) {
<section>
<h3 class="g-title">Standard Actions</h3>
<button
class="g-action"
[action]="action"
(click)="tryUninstall(pkg)"
[action]="rebuild"
(click)="service.rebuild(pkg.manifest.id)"
></button>
<button
class="g-action"
[action]="uninstall"
(click)="service.uninstall(pkg.manifest)"
></button>
</section>
<ng-container *ngIf="pkg.actions | groupActions as actionGroups">
<h3 *ngIf="actionGroups.length" class="g-title">
Actions for {{ (pkg | toManifest).title }}
</h3>
<div *ngFor="let group of actionGroups">
@if (pkg.actions.length) {
<h3 class="g-title">Actions for {{ pkg.manifest.title }}</h3>
}
@for (action of pkg.actions; track $index) {
@if (action.visibility !== 'hidden') {
<button
*ngFor="let action of group"
class="g-action"
[action]="{
name: action.name,
description: action.description,
icon: '@tui.circle-play',
}"
(click)="handleAction(action)"
[action]="action"
(click)="
handleAction(pkg.mainStatus, pkg.icon, pkg.manifest, action)
"
></button>
</div>
</ng-container>
}
}
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
CommonModule,
ServiceActionComponent,
GroupActionsPipe,
ToManifestPipe,
],
imports: [ServiceActionComponent],
})
export class ServiceActionsRoute {
private readonly id = getPkgId(inject(ActivatedRoute))
private readonly actions = inject(ActionService)
readonly pkg$ = this.patch
.watch$('packageData', this.id)
.pipe(filter(pkg => pkg.stateInfo.state === 'installed'))
readonly service = inject(StandardActionsService)
readonly package = toSignal(
inject<PatchDB<DataModel>>(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 action = {
icon: '@tui.trash-2',
name: 'Uninstall',
description:
'This will uninstall the service from StartOS and delete all data permanently.',
}
readonly rebuild = REBUILD
readonly uninstall = UNINSTALL
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<DataModel>,
private readonly formDialog: FormDialogService,
) {}
async handleAction(action: WithId<T.ActionMetadata>) {
// @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<void> {
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 async uninstall() {
const loader = this.loader.open(`Beginning uninstall...`).subscribe()
try {
await this.embassyApi.uninstallPackage({ id: this.id })
this.embassyApi
.setDbValue<boolean>(['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<boolean> {
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()
}
}
asIsOrder() {
return 0
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 },
})
}
}
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.',
}
const UNINSTALL = {
icon: '@tui.trash-2',
name: 'Uninstall',
description:
'Uninstalls this service from StartOS and delete all data permanently.',
}

View File

@@ -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<DataModel>>(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([

View File

@@ -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: '<logs [fetchLogs]="fetch" [followLogs]="follow" [context]="id" />',
@@ -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 })

View File

@@ -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'
<service-backups [pkg]="service.pkg" />
</section>
<section [style.grid-column]="'span 6'">
<h3>Metrics</h3>
TODO
</section>
@if (service.pkg.status.main === 'error') {
<section class="error">
<h3>Error</h3>
<service-error [pkg]="service.pkg" />
</section>
} @else {
<section [style.grid-column]="'span 6'">
<h3>Metrics</h3>
TODO
</section>
}
<section [style.grid-column]="'span 4'" [style.align-self]="'start'">
<h3>Menu</h3>
@@ -72,6 +77,34 @@ import { DependencyInfo } from '../types/dependency-info'
</section>
<div>
@if (service.pkg | toActionRequests: service.allPkgs; as requests) {
@if (requests.critical.length) {
<section>
<h3>Required Actions</h3>
@for (request of requests.critical; track $index) {
<button
[actionRequest]="request"
[pkg]="service.pkg"
[allPkgs]="service.allPkgs"
></button>
}
</section>
}
@if (requests.important.length) {
<section>
<h3>Requested Actions</h3>
@for (request of requests.important; track $index) {
<button
[actionRequest]="request"
[pkg]="service.pkg"
[allPkgs]="service.allPkgs"
></button>
}
</section>
}
}
<section>
<h3>Health Checks</h3>
<service-health-checks [checks]="(health$ | async) || []" />
@@ -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'

View File

@@ -51,7 +51,7 @@ interface Package {
}
</div>
<footer class="g-buttons">
<button tuiButton appearance="flat" (click)="toggleSelectAll()">
<button tuiButton appearance="flat-grayscale" (click)="toggleSelectAll()">
Toggle all
</button>
<button tuiButton [disabled]="!hasSelection" (click)="done()">

View File

@@ -1,26 +1,23 @@
import { toSignal } from '@angular/core/rxjs-interop'
import {
TuiWrapperModule,
TuiInputModule,
TuiInputNumberModule,
} from '@taiga-ui/legacy'
import { CommonModule } from '@angular/common'
import { Component, inject } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { FormsModule } from '@angular/forms'
import { ErrorService, LoadingService } from '@start9labs/shared'
import { TuiDialogContext, TuiDialogService, TuiButton } from '@taiga-ui/core'
import { TuiButton, TuiDialogContext, TuiDialogService } from '@taiga-ui/core'
import { TuiBadge, TuiSwitch } from '@taiga-ui/kit'
import {
POLYMORPHEUS_CONTEXT,
PolymorpheusComponent,
} from '@taiga-ui/polymorpheus'
TuiInputModule,
TuiInputNumberModule,
TuiWrapperModule,
} from '@taiga-ui/legacy'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { from, map } from 'rxjs'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { BackupJob, BackupTarget } from 'src/app/services/api/api.types'
import { TARGET, TARGET_CREATE } from './target.component'
import { BACKUP, BACKUP_OPTIONS } from './backup.component'
import { BackupJobBuilder } from '../utils/job-builder'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ToHumanCronPipe } from '../pipes/to-human-cron.pipe'
import { BackupJobBuilder } from '../utils/job-builder'
import { BACKUP, BACKUP_OPTIONS } from './backup.component'
import { TARGET, TARGET_CREATE } from './target.component'
@Component({
template: `
@@ -113,7 +110,7 @@ export class BackupsEditModal {
private readonly loader = inject(LoadingService)
private readonly dialogs = inject(TuiDialogService)
private readonly context =
inject<TuiDialogContext<BackupJob, BackupJobBuilder>>(POLYMORPHEUS_CONTEXT)
injectContext<TuiDialogContext<BackupJob, BackupJobBuilder>>()
readonly target = toSignal(
from(this.api.getBackupTargets({})).pipe(map(({ saved }) => saved)),

View File

@@ -223,13 +223,13 @@ export class BackupsHistoryModal {
}
}
showReport(run: BackupRun) {
showReport({ report, completedAt }: BackupRun) {
this.dialogs
.open(REPORT, {
label: 'Backup Report',
data: {
report: run.report,
timestamp: run.completedAt,
content: report,
timestamp: completedAt,
},
})
.subscribe()

View File

@@ -5,10 +5,7 @@ import { ErrorService, LoadingService } from '@start9labs/shared'
import { TuiMapperPipe } from '@taiga-ui/cdk'
import { TuiButton, TuiDialogContext, TuiGroup } from '@taiga-ui/core'
import { TuiBlock, TuiCheckbox } from '@taiga-ui/kit'
import {
POLYMORPHEUS_CONTEXT,
PolymorpheusComponent,
} from '@taiga-ui/polymorpheus'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PatchDB } from 'patch-db-client'
import { take } from 'rxjs'
import { PackageBackupInfo } from 'src/app/services/api/api.types'
@@ -79,7 +76,7 @@ export class BackupsRecoverModal {
private readonly loader = inject(LoadingService)
private readonly errorService = inject(ErrorService)
private readonly context =
inject<TuiDialogContext<void, RecoverData>>(POLYMORPHEUS_CONTEXT)
injectContext<TuiDialogContext<void, RecoverData>>()
readonly packageData$ = inject<PatchDB<DataModel>>(PatchDB)
.watch$('packageData')

View File

@@ -1,10 +1,7 @@
import { Component, inject } from '@angular/core'
import { Component } from '@angular/core'
import { ServerComponent, StartOSDiskInfo } from '@start9labs/shared'
import { TuiDialogContext } from '@taiga-ui/core'
import {
POLYMORPHEUS_CONTEXT,
PolymorpheusComponent,
} from '@taiga-ui/polymorpheus'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
interface Data {
servers: StartOSDiskInfo[]
@@ -23,8 +20,7 @@ interface Data {
imports: [ServerComponent],
})
export class ServersComponent {
readonly context =
inject<TuiDialogContext<StartOSDiskInfo, Data>>(POLYMORPHEUS_CONTEXT)
readonly context = injectContext<TuiDialogContext<StartOSDiskInfo, Data>>()
}
export const SERVERS = new PolymorpheusComponent(ServersComponent)

View File

@@ -15,10 +15,7 @@ import {
TuiIcon,
TuiLoader,
} from '@taiga-ui/core'
import {
POLYMORPHEUS_CONTEXT,
PolymorpheusComponent,
} from '@taiga-ui/polymorpheus'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PatchDB } from 'patch-db-client'
import { BackupTarget } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
@@ -83,9 +80,9 @@ export class BackupsTargetModal {
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
readonly context =
inject<
injectContext<
TuiDialogContext<BackupTarget & { id: string }, { type: BackupType }>
>(POLYMORPHEUS_CONTEXT)
>()
readonly loading = signal(true)
readonly text =

View File

@@ -76,7 +76,7 @@ import { ToManifestPipe } from 'src/app/routes/portal/pipes/to-manifest'
<button
tuiButton
type="button"
appearance="outline"
appearance="outline-grayscale"
(click)="showService()"
>
View Installed

View File

@@ -1,7 +1,10 @@
import { TUI_CONFIRM } from '@taiga-ui/kit'
import { TuiCell } from '@taiga-ui/layout'
import { CommonModule } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import {
MarketplaceRegistryComponent,
StoreIconComponentModule,
} from '@start9labs/marketplace'
import {
ErrorService,
LoadingService,
@@ -9,30 +12,24 @@ import {
toUrl,
} from '@start9labs/shared'
import {
StoreIconComponentModule,
MarketplaceRegistryComponent,
} from '@start9labs/marketplace'
import {
TuiButton,
TuiDialogContext,
TuiDialogService,
TuiIcon,
TuiTitle,
TuiButton,
TuiDialogContext,
} from '@taiga-ui/core'
import {
PolymorpheusComponent,
POLYMORPHEUS_CONTEXT,
} from '@taiga-ui/polymorpheus'
import { TUI_CONFIRM } from '@taiga-ui/kit'
import { TuiCell } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PatchDB } from 'patch-db-client'
import { combineLatest, filter, firstValueFrom, map, Subscription } from 'rxjs'
import { FormComponent } from 'src/app/routes/portal/components/form.component'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { DataModel, UIStore } from 'src/app/services/patch-db/data-model'
import { MarketplaceService } from 'src/app/services/marketplace.service'
import { FormDialogService } from 'src/app/services/form-dialog.service'
import { getMarketplaceValueSpec, getPromptOptions } from '../utils/registry'
import { ConfigService } from 'src/app/services/config.service'
import { ActivatedRoute, Router } from '@angular/router'
import { FormDialogService } from 'src/app/services/form-dialog.service'
import { MarketplaceService } from 'src/app/services/marketplace.service'
import { DataModel, UIStore } from 'src/app/services/patch-db/data-model'
import { getMarketplaceValueSpec, getPromptOptions } from '../utils/registry'
@Component({
standalone: true,
@@ -100,7 +97,7 @@ export class MarketplaceRegistryModal {
private readonly formDialog = inject(FormDialogService)
private readonly dialogs = inject(TuiDialogService)
private readonly marketplaceService = inject(MarketplaceService)
private readonly context = inject<TuiDialogContext>(POLYMORPHEUS_CONTEXT)
private readonly context = injectContext<TuiDialogContext>()
private readonly route = inject(ActivatedRoute)
private readonly router = inject(Router)
private readonly hosts$ = inject<PatchDB<DataModel>>(PatchDB).watch$(

View File

@@ -46,12 +46,12 @@ import { toRouterLink } from 'src/app/utils/to-router-link'
(overflownChange)="overflow = $event"
/>
@if (overflow) {
<button tuiLink (click)="service.viewFull(notificationItem)">
<button tuiLink (click)="service.viewModal(notificationItem, true)">
View Full
</button>
}
@if (notificationItem.code === 1) {
<button tuiLink (click)="service.viewReport(notificationItem)">
@if (notificationItem.code === 1 || notificationItem.code === 2) {
<button tuiLink (click)="service.viewModal(notificationItem)">
View Report
</button>
}

View File

@@ -15,44 +15,68 @@ import {
} from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { TuiLet } from '@taiga-ui/cdk'
import { TuiAlertService, TuiButton } from '@taiga-ui/core'
import { TuiButton } from '@taiga-ui/core'
import { TuiProgressBar } from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client'
import { combineLatest, map } from 'rxjs'
import { combineLatest, filter, firstValueFrom, map } from 'rxjs'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ClientStorageService } from 'src/app/services/client-storage.service'
import { DataModel } from 'src/app/services/patch-db/data-model'
import { getManifest } from 'src/app/utils/get-package-data'
import { InstallingProgressPipe } from 'src/app/routes/portal/routes/service/pipes/install-progress.pipe'
import { SideloadService } from './sideload.service'
@Component({
selector: 'sideload-package',
template: `
<div class="outer-container">
<ng-content />
<marketplace-package-hero
*tuiLet="button$ | async as button"
[pkg]="package"
>
<div class="inner-container">
@if (button !== null && button !== 'Install') {
<a
tuiButton
appearance="tertiary-solid"
[routerLink]="'/portal/service/' + package.id"
>
View installed
</a>
}
@if (button) {
<button tuiButton (click)="upload()">{{ button }}</button>
}
</div>
</marketplace-package-hero>
<!-- @TODO Matt do we want this here? How do we turn s9pk into MarketplacePkg? -->
<!-- <marketplace-about [pkg]="package" />-->
<!-- @if (!(package.dependencyMetadata | empty)) {-->
<!-- <marketplace-dependencies [pkg]="package" (open)="open($event)" />-->
<!-- }-->
<!-- <marketplace-additional [pkg]="package" />-->
@if (progress$ | async; as progress) {
@for (phase of progress.phases; track $index) {
<p>
{{ phase.name }}
@if (phase.progress | installingProgress; as progress) {
: {{ progress }}%
}
</p>
<progress
tuiProgressBar
size="xs"
[style.color]="
phase.progress === true
? 'var(--tui-text-positive)'
: 'var(--tui-text-action)'
"
[attr.value]="(phase.progress | installingProgress) / 100 || null"
></progress>
}
} @else {
<marketplace-package-hero
*tuiLet="button$ | async as button"
[pkg]="package"
>
<div class="inner-container">
@if (button !== null && button !== 'Install') {
<a
tuiButton
appearance="tertiary-solid"
[routerLink]="'/portal/service/' + package.id"
>
View installed
</a>
}
@if (button) {
<button tuiButton (click)="upload()">{{ button }}</button>
}
</div>
</marketplace-package-hero>
<!-- @TODO Matt do we want this here? How do we turn s9pk into MarketplacePkg? -->
<!-- <marketplace-about [pkg]="package" />-->
<!-- @if (!(package.dependencyMetadata | empty)) {-->
<!-- <marketplace-dependencies [pkg]="package" (open)="open($event)" />-->
<!-- }-->
<!-- <marketplace-additional [pkg]="package" />-->
}
</div>
`,
styles: [
@@ -87,6 +111,8 @@ import { getManifest } from 'src/app/utils/get-package-data'
TuiLet,
MarketplacePackageHeroComponent,
MarketplaceDependenciesComponent,
InstallingProgressPipe,
TuiProgressBar,
],
})
export class SideloadPackageComponent {
@@ -94,9 +120,10 @@ export class SideloadPackageComponent {
private readonly api = inject(ApiService)
private readonly errorService = inject(ErrorService)
private readonly router = inject(Router)
private readonly alerts = inject(TuiAlertService)
private readonly exver = inject(Exver)
private readonly sideloadService = inject(SideloadService)
readonly progress$ = this.sideloadService.progress$
readonly button$ = combineLatest([
inject(ClientStorageService).showDevTools$,
inject<PatchDB<DataModel>>(PatchDB)
@@ -133,17 +160,14 @@ export class SideloadPackageComponent {
file!: File
async upload() {
const loader = this.loader.open('Uploading package').subscribe()
const loader = this.loader.open('Starting upload').subscribe()
try {
const { upload } = await this.api.sideloadPackage()
const { upload, progress } = await this.api.sideloadPackage()
await this.api.uploadPackage(upload, this.file).catch(console.error)
await this.router.navigate(['/portal/service', this.package.id])
this.alerts
.open('Package uploaded successfully', { appearance: 'positive' })
.subscribe()
this.sideloadService.followProgress(progress)
this.api.uploadPackage(upload, this.file).catch(console.error)
await firstValueFrom(this.progress$.pipe(filter(Boolean)))
} catch (e: any) {
this.errorService.handleError(e)
} finally {

View File

@@ -1,4 +1,4 @@
import { Injectable } from '@angular/core'
import { inject, Injectable } from '@angular/core'
import { T } from '@start9labs/start-sdk'
import { endWith, ReplaySubject, shareReplay, Subject, switchMap } from 'rxjs'
import { ApiService } from 'src/app/services/api/embassy-api.service'
@@ -7,25 +7,16 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
providedIn: 'root',
})
export class SideloadService {
private readonly api = inject(ApiService)
private readonly guid$ = new Subject<string>()
readonly websocketConnected$ = new ReplaySubject()
readonly progress$ = this.guid$.pipe(
switchMap(guid =>
this.api
.openWebsocket$<T.FullProgress>(guid, {
openObserver: {
next: () => this.websocketConnected$.next(''),
},
})
.pipe(endWith(null)),
this.api.openWebsocket$<T.FullProgress>(guid).pipe(endWith(null)),
),
shareReplay(1),
)
constructor(private readonly api: ApiService) {}
followProgress(guid: string) {
this.guid$.next(guid)
}

View File

@@ -4,13 +4,13 @@ import { TuiDialogService } from '@taiga-ui/core'
import { TUI_CONFIRM } from '@taiga-ui/kit'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { filter } from 'rxjs'
import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { FormDialogService } from 'src/app/services/form-dialog.service'
import {
ActionInputModal,
PackageActionData,
} from '../modals/action-input.component'
} from 'src/app/routes/portal/routes/service/modals/action-input.component'
import { ActionSuccessPage } from 'src/app/routes/portal/routes/service/modals/action-success/action-success.page'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { FormDialogService } from 'src/app/services/form-dialog.service'
const allowedStatuses = {
'only-running': new Set(['running']),
@@ -98,11 +98,7 @@ export class ActionService {
}
}
async execute(
packageId: string,
actionId: string,
input?: object,
): Promise<boolean> {
async execute(packageId: string, actionId: string, input?: object) {
const loader = this.loader.open('Loading...').subscribe()
try {
@@ -112,7 +108,7 @@ export class ActionService {
input: input || null,
})
if (!res) return true
if (!res) return
if (res.result) {
this.dialogs
@@ -124,10 +120,8 @@ export class ActionService {
} else if (res.message) {
this.dialogs.open(res.message, { label: res.title }).subscribe()
}
return true // needed to dismiss original modal/alert
} catch (e: any) {
this.errorService.handleError(e)
return false // don't dismiss original modal/alert
} finally {
loader.unsubscribe()
}

View File

@@ -5,13 +5,7 @@ import { TuiDialogOptions, TuiDialogService } from '@taiga-ui/core'
import { TuiConfirmData, TUI_CONFIRM } from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client'
import { defaultIfEmpty, filter, firstValueFrom } from 'rxjs'
// @TODO Alex implement config
// import {
// ConfigModal,
// PackageConfigData,
// } from 'src/app/routes/portal/modals/config.component'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { FormDialogService } from 'src/app/services/form-dialog.service'
import { DataModel } from 'src/app/services/patch-db/data-model'
import { getAllPackages } from 'src/app/utils/get-package-data'
import { hasCurrentDeps } from 'src/app/utils/has-deps'
@@ -19,21 +13,13 @@ import { hasCurrentDeps } from 'src/app/utils/has-deps'
@Injectable({
providedIn: 'root',
})
export class ActionsService {
export class ControlsService {
private readonly dialogs = inject(TuiDialogService)
private readonly errorService = inject(ErrorService)
private readonly loader = inject(LoadingService)
private readonly api = inject(ApiService)
private readonly formDialog = inject(FormDialogService)
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
configure(manifest: T.Manifest): void {
// this.formDialog.open<PackageConfigData>(ConfigModal, {
// label: `${manifest.title} configuration`,
// data: { pkgId: manifest.id },
// })
}
async start(manifest: T.Manifest, unmet: boolean): Promise<void> {
const deps = `${manifest.title} has unmet dependencies. It will not work as expected.`

View File

@@ -1,14 +1,14 @@
import { inject, Injectable } from '@angular/core'
import { ErrorService } from '@start9labs/shared'
import { ErrorService, MARKDOWN } from '@start9labs/shared'
import { TuiDialogService } from '@taiga-ui/core'
import { PatchDB } from 'patch-db-client'
import { firstValueFrom, merge, shareReplay, Subject } from 'rxjs'
import { REPORT } from 'src/app/components/report.component'
import {
ServerNotification,
ServerNotifications,
} from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { REPORT } from 'src/app/components/report.component'
import { firstValueFrom, merge, shareReplay, Subject } from 'rxjs'
import { PatchDB } from 'patch-db-client'
import { DataModel } from 'src/app/services/patch-db/data-model'
@Injectable({ providedIn: 'root' })
@@ -89,19 +89,19 @@ export class NotificationService {
}
}
viewFull(notification: ServerNotification<number>) {
this.dialogs
.open(notification.message, { label: notification.title })
.subscribe()
}
viewModal(
{ data, createdAt, code, title, message }: ServerNotification<number>,
full = false,
) {
const label = full || code === 2 ? title : 'Backup Report'
const content = code === 1 ? REPORT : MARKDOWN
viewReport(notification: ServerNotification<number>) {
this.dialogs
.open(REPORT, {
label: 'Backup Report',
.open(full ? message : content, {
label,
data: {
report: notification.data,
timestamp: notification.createdAt,
content: data,
timestamp: createdAt,
},
})
.subscribe()

View File

@@ -1,32 +1,33 @@
import { Injectable } from '@angular/core'
import { T } from '@start9labs/start-sdk'
import { hasCurrentDeps } from '../util/has-deps'
import { getAllPackages } from '../util/get-package-data'
import { PatchDB } from 'patch-db-client'
import { DataModel } from './patch-db/data-model'
import { AlertController, NavController } from '@ionic/angular'
import { ApiService } from './api/embassy-api.service'
import { inject, Injectable } from '@angular/core'
import { Router } from '@angular/router'
import { ErrorService, LoadingService } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { TuiDialogService } from '@taiga-ui/core'
import { TUI_CONFIRM } from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client'
import { filter } from 'rxjs'
import { getAllPackages } from '../utils/get-package-data'
import { hasCurrentDeps } from '../utils/has-deps'
import { ApiService } from './api/embassy-api.service'
import { DataModel } from './patch-db/data-model'
@Injectable({
providedIn: 'root',
})
export class StandardActionsService {
constructor(
private readonly patch: PatchDB<DataModel>,
private readonly api: ApiService,
private readonly alertCtrl: AlertController,
private readonly errorService: ErrorService,
private readonly loader: LoadingService,
private readonly navCtrl: NavController,
) {}
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
private readonly api = inject(ApiService)
private readonly dialogs = inject(TuiDialogService)
private readonly errorService = inject(ErrorService)
private readonly loader = inject(LoadingService)
private readonly router = inject(Router)
async rebuild(id: string) {
const loader = this.loader.open(`Rebuilding Container...`).subscribe()
try {
await this.api.rebuildPackage({ id })
this.navCtrl.navigateBack('/services/' + id)
await this.router.navigate(['portal', 'services', id])
} catch (e: any) {
this.errorService.handleError(e)
} finally {
@@ -34,48 +35,38 @@ export class StandardActionsService {
}
}
async tryUninstall(manifest: T.Manifest): Promise<void> {
const { id, title, alerts } = manifest
let message =
async uninstall({ id, title, alerts }: T.Manifest): Promise<void> {
let content =
alerts.uninstall ||
`Uninstalling ${title} will permanently delete its data`
if (hasCurrentDeps(id, await getAllPackages(this.patch))) {
message = `${message}. Services that depend on ${title} will no longer work properly and may crash`
content = `${content}. Services that depend on ${title} will no longer work properly and may crash`
}
const alert = await this.alertCtrl.create({
header: 'Warning',
message,
buttons: [
{
text: 'Cancel',
role: 'cancel',
this.dialogs
.open(TUI_CONFIRM, {
label: 'Warning',
size: 's',
data: {
content,
yes: 'Uninstall',
no: 'Cancel',
},
{
text: 'Uninstall',
handler: () => {
this.uninstall(id)
},
cssClass: 'enter-click',
},
],
cssClass: 'alert-warning-message',
})
await alert.present()
})
.pipe(filter(Boolean))
.subscribe(() => this.doUninstall(id))
}
private async uninstall(id: string) {
private async doUninstall(id: string) {
const loader = this.loader.open(`Beginning uninstall...`).subscribe()
try {
await this.api.uninstallPackage({ id })
this.api
await this.api
.setDbValue<boolean>(['ackInstructions', id], false)
.catch(e => console.error('Failed to mark instructions as unseen', e))
this.navCtrl.navigateRoot('/services')
await this.router.navigate(['portal'])
} catch (e: any) {
this.errorService.handleError(e)
} finally {

View File

@@ -41,7 +41,7 @@ export function isRemoving(
}
export function isInstalling(
pkg: PackageDataEntry,
pkg: T.PackageDataEntry,
): pkg is PackageDataEntry<InstallingState> {
return pkg.stateInfo.state === 'installing'
}

View File

@@ -205,20 +205,20 @@ button.g-action {
overflow: auto !important;
}
.g-success.g-success {
color: var(--tui-status-positive);
.g-success {
color: var(--tui-status-positive) !important;
}
.g-warning.g-warning {
color: var(--tui-status-warning);
.g-warning {
color: var(--tui-status-warning) !important;
}
.g-error.g-error {
color: var(--tui-status-negative);
color: var(--tui-status-negative) !important;
}
.g-info.g-info {
color: var(--tui-status-info);
.g-info {
color: var(--tui-status-info) !important;
}
ng-component {