mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-27 02:41:53 +00:00
frontend fixes for alpha.2 (#2919)
* rmeove icon from toggles * fix: comments * fix: more comments * always show public domains even if interface private, only show delete on domains * fix: even more comments * fix: last comments * feat: empty state for dashboard * rework welcome, dlete update-toast, minor * translation improvements --------- Co-authored-by: waterplea <alexander@inkin.ru>
This commit is contained in:
214
web/package-lock.json
generated
214
web/package-lock.json
generated
@@ -25,18 +25,18 @@
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"@start9labs/argon2": "^0.2.2",
|
||||
"@start9labs/start-sdk": "file:../sdk/baseDist",
|
||||
"@taiga-ui/addon-charts": "4.32.0",
|
||||
"@taiga-ui/addon-commerce": "4.32.0",
|
||||
"@taiga-ui/addon-mobile": "4.32.0",
|
||||
"@taiga-ui/addon-table": "4.32.0",
|
||||
"@taiga-ui/cdk": "4.32.0",
|
||||
"@taiga-ui/core": "4.32.0",
|
||||
"@taiga-ui/addon-charts": "4.36.0",
|
||||
"@taiga-ui/addon-commerce": "4.36.0",
|
||||
"@taiga-ui/addon-mobile": "4.36.0",
|
||||
"@taiga-ui/addon-table": "4.36.0",
|
||||
"@taiga-ui/cdk": "4.36.0",
|
||||
"@taiga-ui/core": "4.36.0",
|
||||
"@taiga-ui/event-plugins": "4.5.1",
|
||||
"@taiga-ui/experimental": "4.32.0",
|
||||
"@taiga-ui/icons": "4.32.0",
|
||||
"@taiga-ui/kit": "4.32.0",
|
||||
"@taiga-ui/layout": "4.32.0",
|
||||
"@taiga-ui/legacy": "4.32.0",
|
||||
"@taiga-ui/experimental": "4.36.0",
|
||||
"@taiga-ui/icons": "4.36.0",
|
||||
"@taiga-ui/kit": "4.36.0",
|
||||
"@taiga-ui/layout": "4.36.0",
|
||||
"@taiga-ui/legacy": "4.36.0",
|
||||
"@taiga-ui/polymorpheus": "4.9.0",
|
||||
"@tinkoff/ng-dompurify": "4.0.0",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
@@ -3422,9 +3422,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@maskito/angular": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@maskito/angular/-/angular-3.5.0.tgz",
|
||||
"integrity": "sha512-5uwar32qsGdZNHUgZpFnICg9tJKCXbZEGk2ZnchHzDIfN5ojNT7wKzoq8NhpRlGb3p4qQCE+PXb5GERkcWM/Sw==",
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@maskito/angular/-/angular-3.7.2.tgz",
|
||||
"integrity": "sha512-0auXz5dsS0pNuSZ3WFsQK3Fd6nucC9Um1WVlxoVbi0VEZZG68WirVQybwo7J5/J5gCDffqJn2bAw0qrbbrt0mQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -3433,35 +3433,35 @@
|
||||
"peerDependencies": {
|
||||
"@angular/core": ">=16.0.0",
|
||||
"@angular/forms": ">=16.0.0",
|
||||
"@maskito/core": "^3.5.0"
|
||||
"@maskito/core": "^3.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@maskito/core": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@maskito/core/-/core-3.5.0.tgz",
|
||||
"integrity": "sha512-zgmBjXeXc7BSBaw8jQw25dnwkFmKDvdj5rHzhEIxYhgGtnpli236F0YWPIOYzIwADjbefwDq1o7qpJfMsdDO4Q==",
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@maskito/core/-/core-3.7.2.tgz",
|
||||
"integrity": "sha512-jnX1u2HAZFy0K8Ll6AoeMpe502aVzrqgPKG7rk/qtbivW+71U3vutP3kqmjZtgsPoNzQ2wzKVWMgi7JEXDK11g==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@maskito/kit": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@maskito/kit/-/kit-3.5.0.tgz",
|
||||
"integrity": "sha512-QnpZsPTINgK4ScA4pMMJagoj+ufIXc/VGOP61AsQa/H/lmXII4pEZTLzpmMNUYmCEIEyjHR2DIbfEed04sktvQ==",
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@maskito/kit/-/kit-3.7.2.tgz",
|
||||
"integrity": "sha512-2gx/7k0iRcKWq7+2yeUEUuv13+4KibxkQrTdGSwZZVNbPsmj4b7r5KTbjfHl2ZdwuASSxJiVV8DWF91ON+cmBA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@maskito/core": "^3.5.0"
|
||||
"@maskito/core": "^3.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@maskito/phone": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@maskito/phone/-/phone-3.5.0.tgz",
|
||||
"integrity": "sha512-qh/GGRFn8cZBY/JUTLa5yeSSKSVlekggKeiCbf0eX0I53/HM2pNZ/5667S8SXwn5WjIEeB79Eltl8MNvK74yvA==",
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@maskito/phone/-/phone-3.7.2.tgz",
|
||||
"integrity": "sha512-27KfVE9fR+NiwB35z03gPZ/UM+wJR5AfRYiMh37JZCXBz+/WRhtH3mntpzxmp0CCGoFTD/c3hBkCR9p5MAaxiQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@maskito/core": "^3.5.0",
|
||||
"@maskito/kit": "^3.5.0",
|
||||
"@maskito/core": "^3.7.2",
|
||||
"@maskito/kit": "^3.7.2",
|
||||
"libphonenumber-js": ">=1.0.0"
|
||||
}
|
||||
},
|
||||
@@ -4417,9 +4417,9 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@taiga-ui/addon-charts": {
|
||||
"version": "4.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-4.32.0.tgz",
|
||||
"integrity": "sha512-VhGkBxwfra5eijSvZdXhMKOWEnFMESo5TX3OfsahIXWJXivwguvIc63rIhHYq2uC+t5sj1kINveO4yLqOeAm/Q==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-4.36.0.tgz",
|
||||
"integrity": "sha512-0lGbEPFVQfc8ntWUs+kmi1MyhFFZefIxdJmgbD1cB6Irb8T/JXxNizsJR4JXUJt3ozcE9or7Avbux2lkZaPjWw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.8.1"
|
||||
@@ -4428,15 +4428,15 @@
|
||||
"@angular/common": ">=16.0.0",
|
||||
"@angular/core": ">=16.0.0",
|
||||
"@ng-web-apis/common": "^4.12.0",
|
||||
"@taiga-ui/cdk": "^4.32.0",
|
||||
"@taiga-ui/core": "^4.32.0",
|
||||
"@taiga-ui/cdk": "^4.36.0",
|
||||
"@taiga-ui/core": "^4.36.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/addon-commerce": {
|
||||
"version": "4.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-4.32.0.tgz",
|
||||
"integrity": "sha512-AC3VU/RVTNapS8ltSAemZPeDb2CopJEj298rI3Vl4qER1oVl0zunmWVy5ncwK1F1zWKU2/QNDjjo8yKYWeU/Nw==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-4.36.0.tgz",
|
||||
"integrity": "sha512-DiUBCdvsk+3aNAHXPpGZI9KNuS0hc+T/XSz0RUPKlFWqQ1TUV4aqTE9o575EU7q+IyAn1yNXq40izmuvx/BLwg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.8.1"
|
||||
@@ -4445,22 +4445,22 @@
|
||||
"@angular/common": ">=16.0.0",
|
||||
"@angular/core": ">=16.0.0",
|
||||
"@angular/forms": ">=16.0.0",
|
||||
"@maskito/angular": "^3.5.0",
|
||||
"@maskito/core": "^3.5.0",
|
||||
"@maskito/kit": "^3.5.0",
|
||||
"@maskito/angular": "^3.7.2",
|
||||
"@maskito/core": "^3.7.2",
|
||||
"@maskito/kit": "^3.7.2",
|
||||
"@ng-web-apis/common": "^4.12.0",
|
||||
"@taiga-ui/cdk": "^4.32.0",
|
||||
"@taiga-ui/core": "^4.32.0",
|
||||
"@taiga-ui/i18n": "^4.32.0",
|
||||
"@taiga-ui/kit": "^4.32.0",
|
||||
"@taiga-ui/cdk": "^4.36.0",
|
||||
"@taiga-ui/core": "^4.36.0",
|
||||
"@taiga-ui/i18n": "^4.36.0",
|
||||
"@taiga-ui/kit": "^4.36.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/addon-mobile": {
|
||||
"version": "4.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-4.32.0.tgz",
|
||||
"integrity": "sha512-pUoHWyILPj6KIAhna1JDzz48c2nCjqYb1tb7AL3LQ3qfNwAbg9fvjBIfrgWMhW0LaDeh5+FfrS7oiO/ERcHTLg==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-4.36.0.tgz",
|
||||
"integrity": "sha512-7JvUkBkTlcBVqtwIu93WPJGs50w6UFeuG354XatzGzyAMGL7ipDkrWoxqxHl5HbKt1n/70V8wfAaBT3mgKFvBg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.8.1"
|
||||
@@ -4470,18 +4470,18 @@
|
||||
"@angular/common": ">=16.0.0",
|
||||
"@angular/core": ">=16.0.0",
|
||||
"@ng-web-apis/common": "^4.12.0",
|
||||
"@taiga-ui/cdk": "^4.32.0",
|
||||
"@taiga-ui/core": "^4.32.0",
|
||||
"@taiga-ui/kit": "^4.32.0",
|
||||
"@taiga-ui/layout": "^4.32.0",
|
||||
"@taiga-ui/cdk": "^4.36.0",
|
||||
"@taiga-ui/core": "^4.36.0",
|
||||
"@taiga-ui/kit": "^4.36.0",
|
||||
"@taiga-ui/layout": "^4.36.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/addon-table": {
|
||||
"version": "4.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-table/-/addon-table-4.32.0.tgz",
|
||||
"integrity": "sha512-8oXeqLO1wGH8RYHTYWhjCvrKWptPN1we04NRahmFY4AxSJ3u7MqaR4420RRNO4zZG9kGyktLXPjqGocMoymL8Q==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-table/-/addon-table-4.36.0.tgz",
|
||||
"integrity": "sha512-VKztLMvDo3YeEvEjLHO1DZ1y7CK9Vj+jwLyDKoDpJWBGOJfzqZfYb/lqu228NgKL+3JmGAqvq0VQnN5gx1MtSA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.8.1"
|
||||
@@ -4490,18 +4490,18 @@
|
||||
"@angular/common": ">=16.0.0",
|
||||
"@angular/core": ">=16.0.0",
|
||||
"@ng-web-apis/intersection-observer": "^4.12.0",
|
||||
"@taiga-ui/cdk": "^4.32.0",
|
||||
"@taiga-ui/core": "^4.32.0",
|
||||
"@taiga-ui/i18n": "^4.32.0",
|
||||
"@taiga-ui/kit": "^4.32.0",
|
||||
"@taiga-ui/cdk": "^4.36.0",
|
||||
"@taiga-ui/core": "^4.36.0",
|
||||
"@taiga-ui/i18n": "^4.36.0",
|
||||
"@taiga-ui/kit": "^4.36.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/cdk": {
|
||||
"version": "4.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-4.32.0.tgz",
|
||||
"integrity": "sha512-qvYe79uh6Tw2LJSEGLJYUlAidbZi6JgcuMRqWAB1JhyIGpgnaqar5v+oJJg28zJZZ81PCj59VkFNLL0dNVXRUg==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-4.36.0.tgz",
|
||||
"integrity": "sha512-JgnP7DDZsv87tVK6w9VE+mNTR3nomMgbVq1XoFh5JXbxUwC5FlEIMRKULygBW2FwjbfDehA+2Tm+OeB1HmCSZQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "2.8.1"
|
||||
@@ -4511,7 +4511,7 @@
|
||||
"@angular-devkit/schematics": ">=16.0.0",
|
||||
"@schematics/angular": ">=16.0.0",
|
||||
"ng-morph": "^4.8.4",
|
||||
"parse5": ">=7.2.1"
|
||||
"parse5": ">=7.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/animations": ">=16.0.0",
|
||||
@@ -4530,9 +4530,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/core": {
|
||||
"version": "4.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-4.32.0.tgz",
|
||||
"integrity": "sha512-e1z7YhhjePMRLTk+s83OclN45wMixCwZWMxM9WuXIyd2KXMPhJvrrgBDjoK66GuFtjZ4qaSF/H2FTIJmJ/6MiQ==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-4.36.0.tgz",
|
||||
"integrity": "sha512-BwSF/ZA0pvqqbW2UYbtwjvaSfb7uIj1gNbaMTF7uhjQkvwL22x/Opbxcs2DAoja4I/EPpT3AJMr++S1bzUriWw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.8.1"
|
||||
@@ -4546,9 +4546,9 @@
|
||||
"@angular/router": ">=16.0.0",
|
||||
"@ng-web-apis/common": "^4.12.0",
|
||||
"@ng-web-apis/mutation-observer": "^4.12.0",
|
||||
"@taiga-ui/cdk": "^4.32.0",
|
||||
"@taiga-ui/cdk": "^4.36.0",
|
||||
"@taiga-ui/event-plugins": "^4.5.1",
|
||||
"@taiga-ui/i18n": "^4.32.0",
|
||||
"@taiga-ui/i18n": "^4.36.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
@@ -4568,9 +4568,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/experimental": {
|
||||
"version": "4.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-4.32.0.tgz",
|
||||
"integrity": "sha512-sCOasTF9UlgPOW4vXSeM5M1tgGrjgofa+Qq7hejYW3BXs/4mnmdm5yiYzWfMVZd4jgTSeV5kobkIJ9Fkp2zt6g==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-4.36.0.tgz",
|
||||
"integrity": "sha512-g1TN+hcr7zF/BLw9Sj9X/uKDx8pSEWU9282og5itxsPHgans+ENuh/0dS6kShrx+zwoWLOnjI0MyIR6IktqmQg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.8.1"
|
||||
@@ -4578,18 +4578,18 @@
|
||||
"peerDependencies": {
|
||||
"@angular/common": ">=16.0.0",
|
||||
"@angular/core": ">=16.0.0",
|
||||
"@taiga-ui/addon-commerce": "^4.32.0",
|
||||
"@taiga-ui/cdk": "^4.32.0",
|
||||
"@taiga-ui/core": "^4.32.0",
|
||||
"@taiga-ui/kit": "^4.32.0",
|
||||
"@taiga-ui/addon-commerce": "^4.36.0",
|
||||
"@taiga-ui/cdk": "^4.36.0",
|
||||
"@taiga-ui/core": "^4.36.0",
|
||||
"@taiga-ui/kit": "^4.36.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/i18n": {
|
||||
"version": "4.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.32.0.tgz",
|
||||
"integrity": "sha512-PAQv9RxSgvf3RBUps9bXX2erCk9oiSt9ApM3SAIa/OuzET0TJsW6yZ4EQrtLw03bMX3wyA8PnEYva9wzoYAqxA==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.36.0.tgz",
|
||||
"integrity": "sha512-jGc+5ytZKA4zEDyt1uBxUq/4tp4hJmppKdl6GZ+AWlLl6Tx2YiYqcA3RLDaPdiXsS/UeEoJ7Vjyn6PVd/fv35A==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -4602,18 +4602,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/icons": {
|
||||
"version": "4.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-4.32.0.tgz",
|
||||
"integrity": "sha512-X2ZSiqeMKigULgX91fBZkFJRUbwzeW934yLEGhq7C1JMcC2+ppLmL/NbkD2kpKZ4OeHnGsItxKauoXu44rXeLA==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-4.36.0.tgz",
|
||||
"integrity": "sha512-mcGvUnwF7g1wcnXFJenrcfmlzE1+k2Haz2Cudws0htZQ5VnR/kC1f1pEJlhZYycqGgoJJrNEBPbHCBGrIkqfXg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/kit": {
|
||||
"version": "4.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-4.32.0.tgz",
|
||||
"integrity": "sha512-J8XoqeQHBNbAAuTz0kVACujDOb3zuh4Vps83lYl+msFIaUmkjC37muXF3eRlImH3m4DpT8yI8+ffh/T3+jky7w==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-4.36.0.tgz",
|
||||
"integrity": "sha512-jUVMa3kUrv49HvOCzVymMo/+FRTv1Yrflbjjm+V7A4z9CnZR+tg2VZ+wJzjU0qNppp/nstu+ge+XTJ5B5Hsatw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.8.1"
|
||||
@@ -4623,25 +4623,25 @@
|
||||
"@angular/core": ">=16.0.0",
|
||||
"@angular/forms": ">=16.0.0",
|
||||
"@angular/router": ">=16.0.0",
|
||||
"@maskito/angular": "^3.5.0",
|
||||
"@maskito/core": "^3.5.0",
|
||||
"@maskito/kit": "^3.5.0",
|
||||
"@maskito/phone": "^3.5.0",
|
||||
"@maskito/angular": "^3.7.2",
|
||||
"@maskito/core": "^3.7.2",
|
||||
"@maskito/kit": "^3.7.2",
|
||||
"@maskito/phone": "^3.7.2",
|
||||
"@ng-web-apis/common": "^4.12.0",
|
||||
"@ng-web-apis/intersection-observer": "^4.12.0",
|
||||
"@ng-web-apis/mutation-observer": "^4.12.0",
|
||||
"@ng-web-apis/resize-observer": "^4.12.0",
|
||||
"@taiga-ui/cdk": "^4.32.0",
|
||||
"@taiga-ui/core": "^4.32.0",
|
||||
"@taiga-ui/i18n": "^4.32.0",
|
||||
"@taiga-ui/cdk": "^4.36.0",
|
||||
"@taiga-ui/core": "^4.36.0",
|
||||
"@taiga-ui/i18n": "^4.36.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/layout": {
|
||||
"version": "4.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-4.32.0.tgz",
|
||||
"integrity": "sha512-ECaoJ3CbL+eoqL1MleaHvD9/FQ5OCaUMkjOdXId2Jg2MNbuDhtS9hqVZvSWLXRWz3XgC3aADYnPwrNvIsy5Mng==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-4.36.0.tgz",
|
||||
"integrity": "sha512-Y4s2mdYJopuVjTd38HmqaACK9RwoJAirC7PCYg8NVCWb2Q4mgqHzHtIwTcERRfxRANoSTt+Ne9rLIlxY5JOuVQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.8.1"
|
||||
@@ -4649,17 +4649,17 @@
|
||||
"peerDependencies": {
|
||||
"@angular/common": ">=16.0.0",
|
||||
"@angular/core": ">=16.0.0",
|
||||
"@taiga-ui/cdk": "^4.32.0",
|
||||
"@taiga-ui/core": "^4.32.0",
|
||||
"@taiga-ui/kit": "^4.32.0",
|
||||
"@taiga-ui/cdk": "^4.36.0",
|
||||
"@taiga-ui/core": "^4.36.0",
|
||||
"@taiga-ui/kit": "^4.36.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/legacy": {
|
||||
"version": "4.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/legacy/-/legacy-4.32.0.tgz",
|
||||
"integrity": "sha512-wEsywt6hK2NNpHddqVrL0MTd1QFzmhMdPPgtraNOieQmzrSW2jpA37KJO11cVleuRdDsk98rFtzQ3stlNNFy5Q==",
|
||||
"version": "4.36.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/legacy/-/legacy-4.36.0.tgz",
|
||||
"integrity": "sha512-x4F5ZrAhJPhlCda8daLns7tADkjLDzSNmZa8xVMHeL8nuMjx7FbpKxqzVKcKJ/SLWqF5WHs30D6S/mXjl5YvYw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.8.1"
|
||||
@@ -9526,9 +9526,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/libphonenumber-js": {
|
||||
"version": "1.12.6",
|
||||
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.6.tgz",
|
||||
"integrity": "sha512-PJiS4ETaUfCOFLpmtKzAbqZQjCCKVu2OhTV4SVNNE7c2nu/dACvtCqj4L0i/KWNnIgRv7yrILvBj5Lonv5Ncxw==",
|
||||
"version": "1.12.7",
|
||||
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.7.tgz",
|
||||
"integrity": "sha512-0nYZSNj/QEikyhcM5RZFXGlCB/mr4PVamnT1C2sKBnDDTYndrvbybYjvg+PMqAndQHlLbwQ3socolnL3WWTUFA==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
@@ -11665,12 +11665,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "7.2.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
|
||||
"integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
||||
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"entities": "^4.5.0"
|
||||
"entities": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
@@ -11715,9 +11715,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/parse5/node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
|
||||
"integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
|
||||
@@ -47,18 +47,18 @@
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"@start9labs/argon2": "^0.2.2",
|
||||
"@start9labs/start-sdk": "file:../sdk/baseDist",
|
||||
"@taiga-ui/addon-charts": "4.32.0",
|
||||
"@taiga-ui/addon-commerce": "4.32.0",
|
||||
"@taiga-ui/addon-mobile": "4.32.0",
|
||||
"@taiga-ui/addon-table": "4.32.0",
|
||||
"@taiga-ui/cdk": "4.32.0",
|
||||
"@taiga-ui/core": "4.32.0",
|
||||
"@taiga-ui/addon-charts": "4.36.0",
|
||||
"@taiga-ui/addon-commerce": "4.36.0",
|
||||
"@taiga-ui/addon-mobile": "4.36.0",
|
||||
"@taiga-ui/addon-table": "4.36.0",
|
||||
"@taiga-ui/cdk": "4.36.0",
|
||||
"@taiga-ui/core": "4.36.0",
|
||||
"@taiga-ui/event-plugins": "4.5.1",
|
||||
"@taiga-ui/experimental": "4.32.0",
|
||||
"@taiga-ui/icons": "4.32.0",
|
||||
"@taiga-ui/kit": "4.32.0",
|
||||
"@taiga-ui/layout": "4.32.0",
|
||||
"@taiga-ui/legacy": "4.32.0",
|
||||
"@taiga-ui/experimental": "4.36.0",
|
||||
"@taiga-ui/icons": "4.36.0",
|
||||
"@taiga-ui/kit": "4.36.0",
|
||||
"@taiga-ui/layout": "4.36.0",
|
||||
"@taiga-ui/legacy": "4.36.0",
|
||||
"@taiga-ui/polymorpheus": "4.9.0",
|
||||
"@tinkoff/ng-dompurify": "4.0.0",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
|
||||
@@ -441,7 +441,7 @@ export default {
|
||||
438: 'Kein Internet',
|
||||
439: 'Verbindung wird hergestellt',
|
||||
440: 'Fährt herunter',
|
||||
441: 'Versionen',
|
||||
441: 'Alle versionen',
|
||||
442: 'Neue Benachrichtigungen',
|
||||
443: 'Anzeigen',
|
||||
444: 'PWA wird neu geladen',
|
||||
@@ -498,4 +498,9 @@ export default {
|
||||
495: 'Validierung',
|
||||
496: 'in Bearbeitung',
|
||||
497: 'abgeschlossen',
|
||||
498: 'Klicken Sie hier, um alle Versionen anzuzeigen',
|
||||
499: 'Um loszulegen, besuche den Marktplatz und lade deinen ersten Dienst herunter',
|
||||
500: 'Marktplatz anzeigen',
|
||||
501: 'Willkommen bei',
|
||||
502: 'souveränes computing',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -282,7 +282,7 @@ export const ENGLISH = {
|
||||
'Donation Link': 280,
|
||||
'Standard Actions': 281,
|
||||
'Rebuild Service': 282, // as in, rebuild a software container
|
||||
'Rebuilds the service container. Only necessary in there is a bug in StartOS': 283,
|
||||
'Rebuilds the service container. Only necessary if there is a bug in StartOS': 283,
|
||||
'Uninstall': 284,
|
||||
'Uninstalls this service from StartOS and delete all data permanently.': 285,
|
||||
'Dashboard': 286,
|
||||
@@ -440,7 +440,7 @@ export const ENGLISH = {
|
||||
'No Internet': 438,
|
||||
'Connecting': 439,
|
||||
'Shutting down': 440,
|
||||
'Versions': 441,
|
||||
'All versions': 441,
|
||||
'New notifications': 442,
|
||||
'View': 443,
|
||||
'Reloading PWA': 444,
|
||||
@@ -497,4 +497,9 @@ export const ENGLISH = {
|
||||
'Validating': 495,
|
||||
'in progress': 496,
|
||||
'complete': 497,
|
||||
'Click to view all versions': 498,
|
||||
'To get started, visit the Marketplace and download your first service': 499,
|
||||
'View Marketplace': 500,
|
||||
'Welcome to': 501,
|
||||
'sovereign computing': 502,
|
||||
} as const
|
||||
|
||||
@@ -441,7 +441,7 @@ export default {
|
||||
438: 'Sin Internet',
|
||||
439: 'Conectando',
|
||||
440: 'Apagando',
|
||||
441: 'Versiones',
|
||||
441: 'Todas las versiones',
|
||||
442: 'Nuevas notificaciones',
|
||||
443: 'Ver',
|
||||
444: 'Recargando PWA',
|
||||
@@ -498,4 +498,9 @@ export default {
|
||||
495: 'Validando',
|
||||
496: 'en progreso',
|
||||
497: 'completo',
|
||||
498: 'Haga clic para ver todas las versiones',
|
||||
499: 'Para comenzar, visita el Mercado y descarga tu primer servicio',
|
||||
500: 'Ver Marketplace',
|
||||
501: 'Bienvenido a',
|
||||
502: 'computación soberana',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -441,7 +441,7 @@ export default {
|
||||
438: 'Brak Internetu',
|
||||
439: 'Łączenie',
|
||||
440: 'Wyłączanie',
|
||||
441: 'Wersje',
|
||||
441: 'Wszystkie wersje',
|
||||
442: 'Nowe powiadomienia',
|
||||
443: 'Zobacz',
|
||||
444: 'Przeładowywanie PWA',
|
||||
@@ -498,4 +498,9 @@ export default {
|
||||
495: 'Weryfikowanie',
|
||||
496: 'w toku',
|
||||
497: 'zakończono',
|
||||
498: 'Kliknij, aby zobaczyć wszystkie wersje',
|
||||
499: 'Aby rozpocząć, odwiedź Marketplace i pobierz swoją pierwszą usługę',
|
||||
500: 'Zobacz Rynek',
|
||||
501: 'Witamy w',
|
||||
502: 'suwerenne przetwarzanie',
|
||||
} satisfies i18n
|
||||
|
||||
@@ -11,9 +11,11 @@ import { I18N, i18nKey } from './i18n.providers'
|
||||
export class i18nPipe implements PipeTransform {
|
||||
private readonly i18n = inject(I18N)
|
||||
|
||||
transform(englishKey: i18nKey | null | undefined): string | undefined {
|
||||
// @TODO uncomment to make sure translations are present
|
||||
transform(englishKey: string | null | undefined): string | undefined {
|
||||
// transform(englishKey: i18nKey | null | undefined): string | undefined {
|
||||
return englishKey
|
||||
? this.i18n()?.[ENGLISH[englishKey]] || englishKey
|
||||
? this.i18n()?.[ENGLISH[englishKey as i18nKey]] || englishKey
|
||||
: undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
TuiResponsiveDialogOptions,
|
||||
TuiResponsiveDialogService,
|
||||
} from '@taiga-ui/addon-mobile'
|
||||
import { TuiAlertOptions } from '@taiga-ui/core'
|
||||
import { TUI_CONFIRM, TuiConfirmData } from '@taiga-ui/kit'
|
||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { PROMPT, PromptOptions } from '../components/prompt.component'
|
||||
@@ -63,7 +62,7 @@ export class DialogService {
|
||||
|
||||
openAlert<T = void>(
|
||||
message: i18nKey | undefined,
|
||||
options: Partial<TuiAlertOptions<any>> & {
|
||||
options: Partial<TuiResponsiveDialogOptions<any>> & {
|
||||
label?: i18nKey
|
||||
} = {},
|
||||
) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Component, inject } from '@angular/core'
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
||||
import { Title } from '@angular/platform-browser'
|
||||
import { i18nService } from '@start9labs/shared'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { merge } from 'rxjs'
|
||||
@@ -29,7 +28,6 @@ import { PatchMonitorService } from './services/patch-monitor.service'
|
||||
`,
|
||||
})
|
||||
export class AppComponent {
|
||||
private readonly title = inject(Title)
|
||||
private readonly i18n = inject(i18nService)
|
||||
|
||||
readonly subscription = merge(
|
||||
@@ -40,10 +38,9 @@ export class AppComponent {
|
||||
.subscribe()
|
||||
|
||||
readonly ui = inject<PatchDB<DataModel>>(PatchDB)
|
||||
.watch$('ui')
|
||||
.watch$('ui', 'language')
|
||||
.pipe(takeUntilDestroyed())
|
||||
.subscribe(({ name, language }) => {
|
||||
this.title.setTitle(name || 'StartOS')
|
||||
.subscribe(language => {
|
||||
this.i18n.setLanguage(language || 'english')
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import {
|
||||
TUI_DATE_FORMAT,
|
||||
TUI_DIALOGS_CLOSE,
|
||||
tuiAlertOptionsProvider,
|
||||
tuiButtonOptionsProvider,
|
||||
tuiDropdownOptionsProvider,
|
||||
tuiNumberFormatProvider,
|
||||
@@ -58,6 +59,9 @@ export const APP_PROVIDERS: Provider[] = [
|
||||
tuiButtonOptionsProvider({ size: 'm' }),
|
||||
tuiTextfieldOptionsProvider({ hintOnDisabled: true }),
|
||||
tuiDropdownOptionsProvider({ appearance: 'start-os' }),
|
||||
tuiAlertOptionsProvider({
|
||||
autoClose: appearance => (appearance === 'negative' ? 0 : 3000),
|
||||
}),
|
||||
{
|
||||
provide: TUI_DATE_FORMAT,
|
||||
useValue: of({
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { NotificationsToastComponent } from './notifications-toast.component'
|
||||
import { RefreshAlertComponent } from './refresh-alert.component'
|
||||
import { UpdateToastComponent } from './update-toast.component'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -9,13 +8,8 @@ import { UpdateToastComponent } from './update-toast.component'
|
||||
template: `
|
||||
<notifications-toast />
|
||||
<refresh-alert />
|
||||
<update-toast />
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
NotificationsToastComponent,
|
||||
UpdateToastComponent,
|
||||
RefreshAlertComponent,
|
||||
],
|
||||
imports: [NotificationsToastComponent, RefreshAlertComponent],
|
||||
})
|
||||
export class ToastContainerComponent {}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import { AsyncPipe } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { ErrorService, i18nPipe, LoadingService } from '@start9labs/shared'
|
||||
import { TuiAlert, TuiButton } from '@taiga-ui/core'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
distinctUntilChanged,
|
||||
endWith,
|
||||
filter,
|
||||
merge,
|
||||
Observable,
|
||||
Subject,
|
||||
} from 'rxjs'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'update-toast',
|
||||
template: `
|
||||
<ng-template
|
||||
[tuiAlert]="!!(visible$ | async)"
|
||||
[tuiAlertOptions]="{
|
||||
label: 'StartOS download complete' | i18n,
|
||||
appearance: 'positive',
|
||||
autoClose: 0,
|
||||
}"
|
||||
(tuiAlertChange)="onDismiss()"
|
||||
>
|
||||
{{
|
||||
'Restart your server for these updates to take effect. It can take several minutes to come back online.'
|
||||
| i18n
|
||||
}}
|
||||
<div>
|
||||
<button
|
||||
tuiButton
|
||||
appearance="secondary"
|
||||
size="s"
|
||||
style="margin-top: 8px"
|
||||
(click)="restart()"
|
||||
>
|
||||
{{ 'Restart' | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiButton, TuiAlert, AsyncPipe, i18nPipe],
|
||||
})
|
||||
export class UpdateToastComponent {
|
||||
private readonly api = inject(ApiService)
|
||||
private readonly errorService = inject(ErrorService)
|
||||
private readonly loader = inject(LoadingService)
|
||||
private readonly dismiss$ = new Subject<boolean>()
|
||||
|
||||
readonly visible$: Observable<boolean> = merge(
|
||||
this.dismiss$,
|
||||
inject<PatchDB<DataModel>>(PatchDB)
|
||||
.watch$('serverInfo', 'statusInfo', 'updated')
|
||||
.pipe(distinctUntilChanged(), filter(Boolean), endWith(false)),
|
||||
)
|
||||
|
||||
onDismiss() {
|
||||
this.dismiss$.next(false)
|
||||
}
|
||||
|
||||
async restart(): Promise<void> {
|
||||
this.onDismiss()
|
||||
|
||||
const loader = this.loader.open('Restarting').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.restartServer({})
|
||||
} catch (e: any) {
|
||||
await this.errorService.handleError(e)
|
||||
} finally {
|
||||
await loader.unsubscribe()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,11 @@ import {
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
||||
import { AbstractTuiNullableControl } from '@taiga-ui/legacy'
|
||||
import { filter } from 'rxjs'
|
||||
import { TuiDialogContext } from '@taiga-ui/core'
|
||||
import { TuiAlertService, TuiDialogContext } from '@taiga-ui/core'
|
||||
import { IST } from '@start9labs/start-sdk'
|
||||
import { ERRORS } from '../form-group/form-group.component'
|
||||
import { FORM_CONTROL_PROVIDERS } from './form-control.providers'
|
||||
import { DialogService, i18nKey } from '@start9labs/shared'
|
||||
import { DialogService, i18nKey, i18nPipe } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
selector: 'form-control',
|
||||
@@ -26,6 +26,9 @@ export class FormControlComponent<
|
||||
T extends Exclude<IST.ValueSpec, IST.ValueSpecHidden>,
|
||||
V,
|
||||
> extends AbstractTuiNullableControl<V> {
|
||||
private readonly alerts = inject(TuiAlertService)
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
|
||||
@Input({ required: true })
|
||||
spec!: T
|
||||
|
||||
@@ -35,7 +38,6 @@ export class FormControlComponent<
|
||||
warned = false
|
||||
focused = false
|
||||
readonly order = ERRORS
|
||||
private readonly dialog = inject(DialogService)
|
||||
|
||||
get immutable(): boolean {
|
||||
return 'immutable' in this.spec && this.spec.immutable
|
||||
@@ -50,9 +52,9 @@ export class FormControlComponent<
|
||||
const previous = this.value
|
||||
|
||||
if (!this.warned && this.warning) {
|
||||
this.dialog
|
||||
.openAlert<boolean>(this.warning as unknown as i18nKey, {
|
||||
label: 'Warning',
|
||||
this.alerts
|
||||
.open<boolean>(this.warning, {
|
||||
label: this.i18n.transform('Warning'),
|
||||
appearance: 'warning',
|
||||
closeable: false,
|
||||
autoClose: 0,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
type="checkbox"
|
||||
size="m"
|
||||
[disabled]="!!spec.disabled || readOnly"
|
||||
[showIcons]="false"
|
||||
[(ngModel)]="value"
|
||||
(blur)="onFocus(false)"
|
||||
/>
|
||||
|
||||
@@ -26,7 +26,7 @@ import { getMenu } from 'src/app/utils/system-utilities'
|
||||
[tuiHintShowDelay]="1000"
|
||||
[routerLink]="item.routerLink"
|
||||
[class.link_system]="item.routerLink === '/portal/system'"
|
||||
[tuiHint]="!rla.isActive ? item.name : ''"
|
||||
[tuiHint]="rla.isActive ? '' : (item.name | i18n)"
|
||||
>
|
||||
<tui-badged-content
|
||||
[style.--tui-radius.%]="50"
|
||||
|
||||
@@ -4,12 +4,8 @@ import {
|
||||
inject,
|
||||
input,
|
||||
} from '@angular/core'
|
||||
import {
|
||||
CopyService,
|
||||
DialogService,
|
||||
i18nKey,
|
||||
i18nPipe,
|
||||
} from '@start9labs/shared'
|
||||
import { CopyService, DialogService, i18nPipe } from '@start9labs/shared'
|
||||
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
|
||||
import {
|
||||
TuiButton,
|
||||
tuiButtonOptionsProvider,
|
||||
@@ -114,6 +110,7 @@ import { InterfaceComponent } from './interface.component'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class InterfaceActionsComponent {
|
||||
readonly isMobile = inject(TUI_IS_MOBILE)
|
||||
readonly dialog = inject(DialogService)
|
||||
readonly copyService = inject(CopyService)
|
||||
readonly interface = inject(InterfaceComponent)
|
||||
@@ -124,7 +121,7 @@ export class InterfaceActionsComponent {
|
||||
this.dialog
|
||||
.openComponent(new PolymorpheusComponent(QRModal), {
|
||||
size: 'auto',
|
||||
label: this.actions() as i18nKey,
|
||||
closeable: this.isMobile,
|
||||
data: this.actions(),
|
||||
})
|
||||
.subscribe()
|
||||
|
||||
@@ -37,7 +37,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { toAcmeName } from 'src/app/utils/acme'
|
||||
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
||||
import { InterfaceActionsComponent } from './actions.component'
|
||||
import { AddressDetails } from './interface.utils'
|
||||
import { ClearnetAddress } from './interface.utils'
|
||||
import { MaskPipe } from './mask.pipe'
|
||||
|
||||
type ClearnetForm = {
|
||||
@@ -85,25 +85,35 @@ type ClearnetForm = {
|
||||
<table [appTable]="['ACME', 'URL', null]">
|
||||
@for (address of clearnet(); track $index) {
|
||||
<tr>
|
||||
<td [style.width.rem]="12">{{ address.acme | acme }}</td>
|
||||
<td [style.width.rem]="12">
|
||||
{{
|
||||
interface.serviceInterface().addSsl
|
||||
? (address.acme | acme)
|
||||
: '-'
|
||||
}}
|
||||
</td>
|
||||
<td>{{ address.url | mask }}</td>
|
||||
<td [actions]="address.url">
|
||||
<button
|
||||
tuiButton
|
||||
appearance="primary-destructive"
|
||||
[style.margin-inline-end.rem]="0.5"
|
||||
(click)="remove(address)"
|
||||
>
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiOption
|
||||
tuiAppearance="action-destructive"
|
||||
iconStart="@tui.trash"
|
||||
(click)="remove(address)"
|
||||
>
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
@if (address.isDomain) {
|
||||
<button
|
||||
tuiButton
|
||||
appearance="primary-destructive"
|
||||
[style.margin-inline-end.rem]="0.5"
|
||||
(click)="remove(address)"
|
||||
>
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
}
|
||||
@if (address.isDomain) {
|
||||
<button
|
||||
tuiOption
|
||||
tuiAppearance="action-destructive"
|
||||
iconStart="@tui.trash"
|
||||
(click)="remove(address)"
|
||||
>
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@@ -144,7 +154,7 @@ export class InterfaceClearnetComponent {
|
||||
readonly interface = inject(InterfaceComponent)
|
||||
readonly isPublic = computed(() => this.interface.serviceInterface().public)
|
||||
|
||||
readonly clearnet = input.required<readonly AddressDetails[]>()
|
||||
readonly clearnet = input.required<readonly ClearnetAddress[]>()
|
||||
readonly acme = toSignal(
|
||||
inject<PatchDB<DataModel>>(PatchDB)
|
||||
.watch$('serverInfo', 'network', 'acme')
|
||||
@@ -152,7 +162,7 @@ export class InterfaceClearnetComponent {
|
||||
{ initialValue: [] },
|
||||
)
|
||||
|
||||
async remove({ url }: AddressDetails) {
|
||||
async remove({ url }: ClearnetAddress) {
|
||||
const confirm = await firstValueFrom(
|
||||
this.dialog
|
||||
.openConfirm({ label: 'Are you sure?', size: 's' })
|
||||
@@ -213,33 +223,37 @@ export class InterfaceClearnetComponent {
|
||||
}
|
||||
|
||||
async add() {
|
||||
const domain = ISB.Value.text({
|
||||
name: 'Domain',
|
||||
description: 'The domain or subdomain you want to use',
|
||||
placeholder: `e.g. 'mydomain.com' or 'sub.mydomain.com'`,
|
||||
required: true,
|
||||
default: null,
|
||||
patterns: [utils.Patterns.domain],
|
||||
})
|
||||
const acme = ISB.Value.select({
|
||||
name: 'ACME Provider',
|
||||
description:
|
||||
'Select which ACME provider to use for obtaining your SSL certificate. Add new ACME providers in the System tab. Optionally use your system Root CA. Note: only devices that have trusted your Root CA will be able to access the domain without security warnings.',
|
||||
values: this.acme().reduce(
|
||||
(obj, url) => ({
|
||||
...obj,
|
||||
[url]: toAcmeName(url),
|
||||
}),
|
||||
{ none: 'None (use system Root CA)' } as Record<string, string>,
|
||||
),
|
||||
default: '',
|
||||
})
|
||||
|
||||
this.formDialog.open<FormContext<ClearnetForm>>(FormComponent, {
|
||||
label: 'Select Domain',
|
||||
data: {
|
||||
spec: await configBuilderToSpec(
|
||||
ISB.InputSpec.of({
|
||||
domain: ISB.Value.text({
|
||||
name: 'Domain',
|
||||
description: 'The domain or subdomain you want to use',
|
||||
placeholder: `e.g. 'mydomain.com' or 'sub.mydomain.com'`,
|
||||
required: true,
|
||||
default: null,
|
||||
patterns: [utils.Patterns.domain],
|
||||
}),
|
||||
acme: ISB.Value.select({
|
||||
name: 'ACME Provider',
|
||||
description:
|
||||
'Select which ACME provider to use for obtaining your SSL certificate. Add new ACME providers in the System tab. Optionally use your system Root CA. Note: only devices that have trusted your Root CA will be able to access the domain without security warnings.',
|
||||
values: this.acme().reduce(
|
||||
(obj, url) => ({
|
||||
...obj,
|
||||
[url]: toAcmeName(url),
|
||||
}),
|
||||
{ none: 'None (use system Root CA)' } as Record<string, string>,
|
||||
),
|
||||
default: '',
|
||||
}),
|
||||
}),
|
||||
ISB.InputSpec.of(
|
||||
this.interface.serviceInterface().addSsl
|
||||
? { domain, acme }
|
||||
: { domain },
|
||||
),
|
||||
),
|
||||
buttons: [
|
||||
{
|
||||
|
||||
@@ -12,9 +12,9 @@ export function getAddresses(
|
||||
host: T.Host,
|
||||
config: ConfigService,
|
||||
): {
|
||||
clearnet: (AddressDetails & { acme: string | null })[]
|
||||
local: AddressDetails[]
|
||||
tor: AddressDetails[]
|
||||
clearnet: ClearnetAddress[]
|
||||
local: LocalAddress[]
|
||||
tor: TorAddress[]
|
||||
} {
|
||||
const addressInfo = serviceInterface.addressInfo
|
||||
const hostnames =
|
||||
@@ -46,9 +46,9 @@ export function getAddresses(
|
||||
}
|
||||
}
|
||||
|
||||
const clearnet: (AddressDetails & { acme: string | null })[] = []
|
||||
const local: AddressDetails[] = []
|
||||
const tor: AddressDetails[] = []
|
||||
const clearnet: ClearnetAddress[] = []
|
||||
const local: LocalAddress[] = []
|
||||
const tor: TorAddress[] = []
|
||||
|
||||
hostnames.forEach(h => {
|
||||
const addresses = utils.addressHostToUrl(addressInfo, h)
|
||||
@@ -56,26 +56,28 @@ export function getAddresses(
|
||||
addresses.forEach(url => {
|
||||
if (h.kind === 'onion') {
|
||||
tor.push({
|
||||
label:
|
||||
addresses.length > 1
|
||||
? new URL(url).protocol.replace(':', '').toUpperCase()
|
||||
: '',
|
||||
protocol: new URL(url).protocol.replace(':', '').toUpperCase(),
|
||||
url,
|
||||
})
|
||||
} else {
|
||||
const hostnameKind = h.hostname.kind
|
||||
|
||||
if (h.public) {
|
||||
if (
|
||||
h.public ||
|
||||
(hostnameKind === 'domain' && host.domains[h.hostname.domain]?.public)
|
||||
) {
|
||||
clearnet.push({
|
||||
url,
|
||||
disabled: !h.public,
|
||||
isDomain: hostnameKind == 'domain',
|
||||
acme:
|
||||
hostnameKind == 'domain'
|
||||
? host.domains[h.hostname.domain]?.acme || null
|
||||
: null, // @TODO Matt make sure this is handled correctly - looks like ACME settings aren't built yet anyway, but ACME settings aren't *available* for public IPs
|
||||
: null,
|
||||
})
|
||||
} else {
|
||||
local.push({
|
||||
label:
|
||||
nid:
|
||||
hostnameKind === 'local'
|
||||
? 'Local'
|
||||
: `${h.networkInterfaceId} (${hostnameKind})`,
|
||||
@@ -103,16 +105,28 @@ export function getAddresses(
|
||||
}
|
||||
|
||||
export type MappedServiceInterface = T.ServiceInterface & {
|
||||
addSsl?: T.AddSslOptions | null
|
||||
public: boolean
|
||||
addresses: {
|
||||
clearnet: AddressDetails[]
|
||||
local: AddressDetails[]
|
||||
tor: AddressDetails[]
|
||||
clearnet: ClearnetAddress[]
|
||||
local: LocalAddress[]
|
||||
tor: TorAddress[]
|
||||
}
|
||||
}
|
||||
|
||||
export type AddressDetails = {
|
||||
label?: string
|
||||
export type ClearnetAddress = {
|
||||
url: string
|
||||
acme?: string | null
|
||||
acme: string | null
|
||||
isDomain: boolean
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
export type LocalAddress = {
|
||||
url: string
|
||||
nid: string
|
||||
}
|
||||
|
||||
export type TorAddress = {
|
||||
url: string
|
||||
protocol: string
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { TuiIcon, TuiLink } from '@taiga-ui/core'
|
||||
import { TuiTooltip } from '@taiga-ui/kit'
|
||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||
import { InterfaceActionsComponent } from './actions.component'
|
||||
import { AddressDetails } from './interface.utils'
|
||||
import { LocalAddress } from './interface.utils'
|
||||
import { MaskPipe } from './mask.pipe'
|
||||
import { DocsLinkDirective, i18nPipe } from '@start9labs/shared'
|
||||
|
||||
@@ -27,7 +27,7 @@ import { DocsLinkDirective, i18nPipe } from '@start9labs/shared'
|
||||
<table [appTable]="['Network Interface', 'URL', null]">
|
||||
@for (address of local(); track $index) {
|
||||
<tr>
|
||||
<td [style.width.rem]="12">{{ address.label }}</td>
|
||||
<td [style.width.rem]="12">{{ address.nid }}</td>
|
||||
<td>{{ address.url | mask }}</td>
|
||||
<td [actions]="address.url"></td>
|
||||
</tr>
|
||||
@@ -48,5 +48,5 @@ import { DocsLinkDirective, i18nPipe } from '@start9labs/shared'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class InterfaceLocalComponent {
|
||||
readonly local = input.required<readonly AddressDetails[]>()
|
||||
readonly local = input.required<readonly LocalAddress[]>()
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
||||
import { InterfaceActionsComponent } from './actions.component'
|
||||
import { AddressDetails } from './interface.utils'
|
||||
import { TorAddress } from './interface.utils'
|
||||
import { MaskPipe } from './mask.pipe'
|
||||
|
||||
type OnionForm = {
|
||||
@@ -70,7 +70,7 @@ type OnionForm = {
|
||||
<table [appTable]="['Protocol', 'URL', null]">
|
||||
@for (address of tor(); track $index) {
|
||||
<tr>
|
||||
<td [style.width.rem]="12">{{ address.label }}</td>
|
||||
<td [style.width.rem]="12">{{ address.protocol || '-' }}</td>
|
||||
<td>
|
||||
<div [tuiFluidTypography]="[0.625, 0.8125]" tuiFade>
|
||||
{{ address.url | mask }}
|
||||
@@ -140,9 +140,9 @@ export class InterfaceTorComponent {
|
||||
private readonly interface = inject(InterfaceComponent)
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
|
||||
readonly tor = input.required<readonly AddressDetails[]>()
|
||||
readonly tor = input.required<readonly TorAddress[]>()
|
||||
|
||||
async remove({ url }: AddressDetails) {
|
||||
async remove({ url }: TorAddress) {
|
||||
const confirm = await firstValueFrom(
|
||||
this.dialog
|
||||
.openConfirm({ label: 'Are you sure?', size: 's' })
|
||||
|
||||
@@ -1,22 +1,56 @@
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
inject,
|
||||
signal,
|
||||
} from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { RouterOutlet } from '@angular/router'
|
||||
import { TuiScrollbar } from '@taiga-ui/core'
|
||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||
import { TuiButton, TuiIcon, TuiLoader, TuiScrollbar } from '@taiga-ui/core'
|
||||
import { TuiActionBar, TuiProgress } from '@taiga-ui/kit'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { TabsComponent } from 'src/app/routes/portal/components/tabs.component'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { OSService } from 'src/app/services/os.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { HeaderComponent } from './components/header/header.component'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: `
|
||||
<header appHeader>{{ name$ | async }}</header>
|
||||
<header appHeader>{{ name() }}</header>
|
||||
<main>
|
||||
<tui-scrollbar [style.max-height.%]="100">
|
||||
<router-outlet />
|
||||
</tui-scrollbar>
|
||||
</main>
|
||||
<app-tabs />
|
||||
@if (update(); as update) {
|
||||
<tui-action-bar *tuiActionBar="true">
|
||||
@if (update === true) {
|
||||
<tui-icon icon="@tui.check" class="g-positive" />
|
||||
Download complete, restart to apply changes
|
||||
} @else if (
|
||||
update.overall && update.overall !== true && update.overall.total
|
||||
) {
|
||||
<tui-progress-circle
|
||||
size="xxs"
|
||||
[style.display]="'flex'"
|
||||
[max]="100"
|
||||
[value]="getProgress(update.overall.total, update.overall.done)"
|
||||
/>
|
||||
Downloading:
|
||||
{{ getProgress(update.overall.total, update.overall.done) }}%
|
||||
} @else {
|
||||
<tui-loader />
|
||||
Calculating download size
|
||||
}
|
||||
@if (update === true) {
|
||||
<button tuiButton size="s" (click)="restart()">Restart</button>
|
||||
}
|
||||
</tui-action-bar>
|
||||
}
|
||||
`,
|
||||
styles: [
|
||||
`
|
||||
@@ -47,13 +81,39 @@ import { HeaderComponent } from './components/header/header.component'
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterOutlet,
|
||||
HeaderComponent,
|
||||
TabsComponent,
|
||||
TuiScrollbar,
|
||||
TuiActionBar,
|
||||
TuiProgress,
|
||||
TuiLoader,
|
||||
TuiIcon,
|
||||
TuiButton,
|
||||
],
|
||||
})
|
||||
export class PortalComponent {
|
||||
readonly name$ = inject<PatchDB<DataModel>>(PatchDB).watch$('ui', 'name')
|
||||
private readonly loader = inject(LoadingService)
|
||||
private readonly errorService = inject(ErrorService)
|
||||
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||
private readonly api = inject(ApiService)
|
||||
|
||||
readonly name = toSignal(this.patch.watch$('ui', 'name'))
|
||||
readonly update = toSignal(inject(OSService).updating$)
|
||||
|
||||
getProgress(size: number, downloaded: number): number {
|
||||
return Math.round((100 * downloaded) / (size || 1))
|
||||
}
|
||||
|
||||
async restart() {
|
||||
const loader = this.loader.open('Beginning restart').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.restartServer({})
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
} finally {
|
||||
loader.unsubscribe()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { ActivatedRouteSnapshot, Routes } from '@angular/router'
|
||||
import { PackageDataEntry } from '../../services/patch-db/data-model'
|
||||
import { getManifest } from '../../utils/get-package-data'
|
||||
import { SYSTEM_UTILITIES } from '../../utils/system-utilities'
|
||||
import { toRouterLink } from '../../utils/to-router-link'
|
||||
import { Routes } from '@angular/router'
|
||||
import { SYSTEM_UTILITIES } from 'src/app/utils/system-utilities'
|
||||
import { titleResolver } from 'src/app/utils/title-resolver'
|
||||
import { toRouterLink } from 'src/app/utils/to-router-link'
|
||||
import { PortalComponent } from './portal.component'
|
||||
|
||||
const ROUTES: Routes = [
|
||||
@@ -17,54 +16,56 @@ const ROUTES: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'services',
|
||||
data: { title: 'Services' },
|
||||
title: titleResolver,
|
||||
loadChildren: () => import('./routes/services/services.routes'),
|
||||
},
|
||||
// @TODO 041
|
||||
// {
|
||||
// title: systemTabResolver,
|
||||
// title: titleResolver,
|
||||
// path: 'backups',
|
||||
// loadComponent: () => import('./routes/backups/backups.component'),
|
||||
// data: toNavigationItem('/portal/backups'),
|
||||
// },
|
||||
{
|
||||
title: systemTabResolver,
|
||||
title: titleResolver,
|
||||
path: 'logs',
|
||||
loadComponent: () => import('./routes/logs/logs.component'),
|
||||
data: toNavigationItem('/portal/logs'),
|
||||
},
|
||||
{
|
||||
title: systemTabResolver,
|
||||
title: titleResolver,
|
||||
path: 'marketplace',
|
||||
loadChildren: () => import('./routes/marketplace/marketplace.routes'),
|
||||
data: toNavigationItem('/portal/marketplace'),
|
||||
},
|
||||
{
|
||||
title: systemTabResolver,
|
||||
title: titleResolver,
|
||||
path: 'system',
|
||||
loadChildren: () => import('./routes/system/system.routes'),
|
||||
data: toNavigationItem('/portal/system'),
|
||||
},
|
||||
{
|
||||
title: systemTabResolver,
|
||||
title: titleResolver,
|
||||
path: 'notifications',
|
||||
loadComponent: () =>
|
||||
import('./routes/notifications/notifications.component'),
|
||||
data: toNavigationItem('/portal/notifications'),
|
||||
},
|
||||
{
|
||||
title: systemTabResolver,
|
||||
title: titleResolver,
|
||||
path: 'sideload',
|
||||
loadComponent: () => import('./routes/sideload/sideload.component'),
|
||||
data: toNavigationItem('/portal/sideload'),
|
||||
},
|
||||
{
|
||||
title: systemTabResolver,
|
||||
title: titleResolver,
|
||||
path: 'updates',
|
||||
loadComponent: () => import('./routes/updates/updates.component'),
|
||||
data: toNavigationItem('/portal/updates'),
|
||||
},
|
||||
{
|
||||
title: systemTabResolver,
|
||||
title: titleResolver,
|
||||
path: 'metrics',
|
||||
loadComponent: () => import('./routes/metrics/metrics.component'),
|
||||
data: toNavigationItem('/portal/metrics'),
|
||||
@@ -75,26 +76,12 @@ const ROUTES: Routes = [
|
||||
|
||||
export default ROUTES
|
||||
|
||||
function systemTabResolver({ data }: ActivatedRouteSnapshot): string {
|
||||
return data['title']
|
||||
}
|
||||
function toNavigationItem(id: string) {
|
||||
const { icon, title } = SYSTEM_UTILITIES[id] || {}
|
||||
|
||||
function toNavigationItem(
|
||||
id: string,
|
||||
packages: Record<string, PackageDataEntry> = {},
|
||||
) {
|
||||
const item = SYSTEM_UTILITIES[id]
|
||||
const routerLink = toRouterLink(id)
|
||||
|
||||
return item
|
||||
? {
|
||||
icon: item.icon,
|
||||
title: item.title,
|
||||
routerLink,
|
||||
}
|
||||
: {
|
||||
icon: packages[id]?.icon,
|
||||
title: getManifest(packages[id]!).title,
|
||||
routerLink,
|
||||
}
|
||||
return {
|
||||
icon,
|
||||
title,
|
||||
routerLink: toRouterLink(id),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,13 @@ import { TARGET, TARGET_CREATE } from './target.component'
|
||||
</div>
|
||||
<div *ngIf="!job.job.id" class="g-toggle">
|
||||
Also Execute Now
|
||||
<input tuiSwitch type="checkbox" name="now" [(ngModel)]="job.now" />
|
||||
<input
|
||||
tuiSwitch
|
||||
type="checkbox"
|
||||
name="now"
|
||||
[showIcons]="false"
|
||||
[(ngModel)]="job.now"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
tuiButton
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
import {
|
||||
DialogService,
|
||||
Exver,
|
||||
i18nKey,
|
||||
i18nPipe,
|
||||
MARKDOWN,
|
||||
SharedPipesModule,
|
||||
@@ -56,22 +57,19 @@ import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
<marketplace-additional [pkg]="pkg" (static)="onStatic($event)">
|
||||
@if (versions$ | async; as versions) {
|
||||
<marketplace-additional-item
|
||||
(click)="versions.length ? selectVersion(pkg, version) : 0"
|
||||
[data]="
|
||||
versions.length
|
||||
? 'Click to view all versions'
|
||||
: 'No other versions'
|
||||
"
|
||||
(click)="selectVersion(pkg, version)"
|
||||
[data]="('Click to view all versions' | i18n) || ''"
|
||||
[icon]="versions.length > 1 ? '@tui.chevron-right' : ''"
|
||||
label="All versions"
|
||||
icon="@tui.chevron-right"
|
||||
class="versions"
|
||||
[class.versions_empty]="versions.length < 2"
|
||||
/>
|
||||
<ng-template
|
||||
#version
|
||||
let-data="data"
|
||||
let-completeWith="completeWith"
|
||||
>
|
||||
<tui-radio-list [items]="versions" [(ngModel)]="data.value" />
|
||||
<tui-radio-list [items]="versions" [(ngModel)]="data.version" />
|
||||
<footer class="buttons">
|
||||
<button
|
||||
tuiButton
|
||||
@@ -137,9 +135,14 @@ import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
border-color: rgb(113 113 122);
|
||||
border-style: solid;
|
||||
cursor: pointer;
|
||||
|
||||
::ng-deep label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&_empty {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
@@ -209,10 +212,13 @@ export class MarketplacePreviewComponent {
|
||||
this.pkg$.pipe(filter(Boolean)),
|
||||
this.flavor$,
|
||||
]).pipe(
|
||||
map(([{ otherVersions }, flavor]) =>
|
||||
Object.keys(otherVersions)
|
||||
.filter(v => this.exver.getFlavor(v) === flavor)
|
||||
.sort((a, b) => -1 * (this.exver.compareExver(a, b) || 0)),
|
||||
map(([{ otherVersions, version }, flavor]) =>
|
||||
[
|
||||
version,
|
||||
...Object.keys(otherVersions).filter(
|
||||
v => this.exver.getFlavor(v) === flavor,
|
||||
),
|
||||
].sort((a, b) => -1 * (this.exver.compareExver(a, b) || 0)),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -243,11 +249,9 @@ export class MarketplacePreviewComponent {
|
||||
) {
|
||||
this.dialog
|
||||
.openComponent<string>(template, {
|
||||
label: 'Versions',
|
||||
label: 'All versions',
|
||||
size: 's',
|
||||
data: {
|
||||
value: version,
|
||||
},
|
||||
data: { version },
|
||||
})
|
||||
.pipe(filter(Boolean))
|
||||
.subscribe(version => this.version$.next(version))
|
||||
|
||||
@@ -4,7 +4,8 @@ import {
|
||||
inject,
|
||||
signal,
|
||||
} from '@angular/core'
|
||||
import { ErrorService, i18nPipe } from '@start9labs/shared'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { ErrorService, i18nPipe, isEmptyObject } from '@start9labs/shared'
|
||||
import { TuiButton, TuiDataList, TuiDropdown } from '@taiga-ui/core'
|
||||
import { RR, ServerNotifications } from 'src/app/services/api/api.types'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
@@ -66,18 +67,23 @@ import { NotificationsTableComponent } from './table.component'
|
||||
],
|
||||
})
|
||||
export default class NotificationsComponent {
|
||||
private readonly router = inject(Router)
|
||||
private readonly route = inject(ActivatedRoute)
|
||||
|
||||
readonly service = inject(NotificationService)
|
||||
readonly api = inject(ApiService)
|
||||
readonly errorService = inject(ErrorService)
|
||||
|
||||
readonly notifications = signal<ServerNotifications | undefined>(undefined)
|
||||
readonly toast = this.route.queryParams.subscribe(params => {
|
||||
this.router.navigate([], { relativeTo: this.route, queryParams: {} })
|
||||
|
||||
if (isEmptyObject(params)) {
|
||||
this.getMore({})
|
||||
}
|
||||
})
|
||||
|
||||
open = false
|
||||
|
||||
ngOnInit() {
|
||||
this.getMore({})
|
||||
}
|
||||
|
||||
async getMore(params: RR.GetNotificationsReq) {
|
||||
try {
|
||||
this.notifications.set(undefined)
|
||||
|
||||
@@ -18,6 +18,7 @@ import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
tuiCell
|
||||
[routerLink]="services[d.key] ? ['..', d.key] : ['/portal/marketplace']"
|
||||
[queryParams]="services[d.key] ? {} : { id: d.key }"
|
||||
[class.error]="getError(d.key)"
|
||||
>
|
||||
<tui-avatar><img alt="" [src]="d.value.icon" /></tui-avatar>
|
||||
<span tuiTitle>
|
||||
@@ -42,6 +43,10 @@ import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
min-height: 12rem;
|
||||
grid-column: span 3;
|
||||
}
|
||||
|
||||
.error {
|
||||
box-shadow: inset 1.25rem 0 0 -1rem var(--tui-status-warning);
|
||||
}
|
||||
`,
|
||||
host: { class: 'g-card' },
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
@@ -82,7 +87,7 @@ export class ServiceDependenciesComponent {
|
||||
case 'notRunning':
|
||||
return 'Not running'
|
||||
case 'actionRequired':
|
||||
return 'Action required'
|
||||
return 'Task Required'
|
||||
case 'healthChecksFailed':
|
||||
return 'Required health check not passing'
|
||||
case 'transitive':
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import { RouterLink } from '@angular/router'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
import { TuiButton, TuiLink } from '@taiga-ui/core'
|
||||
import { TuiButton, TuiIcon, TuiLink } from '@taiga-ui/core'
|
||||
import { TuiBadge } from '@taiga-ui/kit'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
@@ -26,23 +26,11 @@ import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
<td class="g-secondary" [style.grid-area]="'2 / span 4'">
|
||||
{{ info.description }}
|
||||
</td>
|
||||
<td>
|
||||
<td [style.text-align]="'center'">
|
||||
@if (info.public) {
|
||||
<a
|
||||
class="hosting"
|
||||
tuiLink
|
||||
iconStart="@tui.globe"
|
||||
appearance="positive"
|
||||
[textContent]="'Public'"
|
||||
></a>
|
||||
<tui-icon class="g-positive" icon="@tui.globe" />
|
||||
} @else {
|
||||
<a
|
||||
class="hosting"
|
||||
tuiLink
|
||||
iconStart="@tui.lock"
|
||||
appearance="negative"
|
||||
[textContent]="'Private'"
|
||||
></a>
|
||||
<tui-icon class="g-negative" icon="@tui.lock" />
|
||||
}
|
||||
</td>
|
||||
<td [style.grid-area]="'span 2'">
|
||||
@@ -90,6 +78,10 @@ import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
tui-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, min-content) 1fr 2rem;
|
||||
@@ -100,15 +92,11 @@ import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.hosting {
|
||||
font-size: 0;
|
||||
}
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [TuiButton, TuiBadge, TuiLink, RouterLink, i18nPipe],
|
||||
imports: [TuiButton, TuiBadge, TuiLink, TuiIcon, RouterLink, i18nPipe],
|
||||
})
|
||||
export class ServiceInterfaceComponent {
|
||||
private readonly config = inject(ConfigService)
|
||||
@@ -138,7 +126,7 @@ export class ServiceInterfaceComponent {
|
||||
|
||||
get href(): string | null {
|
||||
return this.disabled
|
||||
? 'null'
|
||||
? null
|
||||
: this.config.launchableAddress(this.info, this.pkg.hosts)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,10 +62,12 @@ export class ServiceInterfacesComponent {
|
||||
.sort((a, b) => tuiDefaultSort(a[1], b[1]))
|
||||
.map(([id, value]) => {
|
||||
const host = hosts[value.addressInfo.hostId]
|
||||
const port = value.addressInfo.internalPort
|
||||
|
||||
return {
|
||||
...value,
|
||||
public: !!host?.bindings[value.addressInfo.internalPort]?.net.public,
|
||||
addSsl: host?.bindings[port]?.options.addSsl,
|
||||
public: !!host?.bindings[port]?.net.public,
|
||||
addresses: host ? getAddresses(value, host, this.config) : {},
|
||||
routerLink: `./interface/${id}`,
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export class ControlsComponent {
|
||||
this.errors.getPkgDepErrors$(this.manifest().id).pipe(
|
||||
map(errors =>
|
||||
Object.keys(this.pkg().currentDependencies)
|
||||
.map(id => errors[id])
|
||||
.map(id => errors?.[id])
|
||||
.some(Boolean),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { RouterLink } from '@angular/router'
|
||||
import { TuiComparator, TuiTable } from '@taiga-ui/addon-table'
|
||||
import { TuiButton, TuiLoader } from '@taiga-ui/core'
|
||||
import { ToManifestPipe } from 'src/app/routes/portal/pipes/to-manifest'
|
||||
import { DepErrorService } from 'src/app/services/dep-error.service'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
@@ -15,47 +17,73 @@ import { i18nPipe } from '@start9labs/shared'
|
||||
standalone: true,
|
||||
template: `
|
||||
<ng-container *title>{{ 'Services' | i18n }}</ng-container>
|
||||
<table tuiTable class="g-table" [(sorter)]="sorter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th [style.width.rem]="3"></th>
|
||||
<th tuiTh [requiredSort]="true" [sorter]="name">
|
||||
{{ 'Name' | i18n }}
|
||||
</th>
|
||||
<th tuiTh>{{ 'Version' | i18n }}</th>
|
||||
<th tuiTh [requiredSort]="true" [sorter]="uptime">
|
||||
{{ 'Uptime' | i18n }}
|
||||
</th>
|
||||
<th tuiTh [requiredSort]="true" [sorter]="status">
|
||||
{{ 'Status' | i18n }}
|
||||
</th>
|
||||
<th [style.width.rem]="8" [style.text-indent.rem]="1.5">
|
||||
{{ 'Controls' | i18n }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (pkg of services() | tuiTableSort; track $index) {
|
||||
<tr
|
||||
appService
|
||||
[pkg]="pkg"
|
||||
[depErrors]="errors()?.[(pkg | toManifest).id]"
|
||||
></tr>
|
||||
} @empty {
|
||||
<tr>
|
||||
<td colspan="6">
|
||||
{{
|
||||
services()
|
||||
? ('No services installed' | i18n)
|
||||
: ('Loading' | i18n)
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
@if (!services()) {
|
||||
<tui-loader [style.height.%]="100" [textContent]="'Loading' | i18n" />
|
||||
} @else {
|
||||
@if (!services()?.length) {
|
||||
<table tuiTable class="g-table" [(sorter)]="sorter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th [style.width.rem]="3"></th>
|
||||
<th tuiTh [requiredSort]="true" [sorter]="name">
|
||||
{{ 'Name' | i18n }}
|
||||
</th>
|
||||
<th tuiTh>{{ 'Version' | i18n }}</th>
|
||||
<th tuiTh [requiredSort]="true" [sorter]="uptime">
|
||||
{{ 'Uptime' | i18n }}
|
||||
</th>
|
||||
<th tuiTh [requiredSort]="true" [sorter]="status">
|
||||
{{ 'Status' | i18n }}
|
||||
</th>
|
||||
<th [style.width.rem]="8" [style.text-indent.rem]="1.5">
|
||||
{{ 'Controls' | i18n }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (pkg of services() | tuiTableSort; track $index) {
|
||||
<tr
|
||||
appService
|
||||
[pkg]="pkg"
|
||||
[depErrors]="errors()?.[(pkg | toManifest).id]"
|
||||
></tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
} @else {
|
||||
<section>
|
||||
<div>
|
||||
{{ 'Welcome to' | i18n }}
|
||||
<span>StartOS</span>
|
||||
</div>
|
||||
<p>
|
||||
{{
|
||||
'To get started, visit the Marketplace and download your first service'
|
||||
| i18n
|
||||
}}
|
||||
</p>
|
||||
<a tuiButton routerLink="../marketplace">
|
||||
{{ 'View Marketplace' | i18n }}
|
||||
</a>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
`,
|
||||
styles: `
|
||||
@keyframes slide {
|
||||
50% {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
|
||||
55% {
|
||||
margin-block-start: -1em;
|
||||
}
|
||||
|
||||
100% {
|
||||
margin-block-start: -1em;
|
||||
}
|
||||
}
|
||||
|
||||
:host {
|
||||
position: relative;
|
||||
font-size: 1rem;
|
||||
@@ -65,6 +93,31 @@ import { i18nPipe } from '@start9labs/shared'
|
||||
:host-context(tui-root._mobile) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
section {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
div {
|
||||
font-size: min(12vw, 4rem);
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #ff4961;
|
||||
}
|
||||
|
||||
a {
|
||||
margin-block-start: 1rem;
|
||||
}
|
||||
}
|
||||
`,
|
||||
host: { class: 'g-page' },
|
||||
imports: [
|
||||
@@ -73,6 +126,9 @@ import { i18nPipe } from '@start9labs/shared'
|
||||
TuiTable,
|
||||
TitleDirective,
|
||||
i18nPipe,
|
||||
TuiLoader,
|
||||
TuiButton,
|
||||
RouterLink,
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
|
||||
@@ -71,7 +71,7 @@ export class StatusComponent {
|
||||
|
||||
get status(): i18nKey {
|
||||
if (this.pkg.stateInfo.installingInfo) {
|
||||
return `${this.i18n.transform('Installing')}...${this.i18n.transform(getProgressText(this.pkg.stateInfo.installingInfo.progress.overall))}` as i18nKey
|
||||
return `${this.i18n.transform('Installing')}... ${this.i18n.transform(getProgressText(this.pkg.stateInfo.installingInfo.progress.overall))}` as i18nKey
|
||||
}
|
||||
|
||||
switch (this.getStatus(this.pkg).primary) {
|
||||
@@ -108,7 +108,6 @@ export class StatusComponent {
|
||||
case 'backingUp':
|
||||
case 'restarting':
|
||||
case 'removing':
|
||||
case 'restoring':
|
||||
return '...'
|
||||
default:
|
||||
return ''
|
||||
|
||||
@@ -96,7 +96,7 @@ export default class ServiceActionsRoute {
|
||||
readonly rebuild = {
|
||||
name: this.i18n.transform('Rebuild Service')!,
|
||||
description: this.i18n.transform(
|
||||
'Rebuilds the service container. Only necessary in there is a bug in StartOS',
|
||||
'Rebuilds the service container. Only necessary if there is a bug in StartOS',
|
||||
)!,
|
||||
}
|
||||
|
||||
|
||||
@@ -86,14 +86,16 @@ export default class ServiceInterfaceRoute {
|
||||
const item = serviceInterfaces[this.interfaceId()]
|
||||
const key = item?.addressInfo.hostId || ''
|
||||
const host = hosts[key]
|
||||
const port = item?.addressInfo.internalPort
|
||||
|
||||
if (!host || !item) {
|
||||
if (!host || !item || !port) {
|
||||
return
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
public: !!host?.bindings[item.addressInfo.internalPort]?.net.public,
|
||||
addSsl: host?.bindings[port]?.options.addSsl,
|
||||
public: !!host?.bindings[port]?.net.public,
|
||||
addresses: getAddresses(item, host, this.config),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import { MarkdownComponent } from '@start9labs/shared'
|
||||
import { defer, map, Observable, of } from 'rxjs'
|
||||
import { share } from 'rxjs/operators'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { titleResolver } from 'src/app/utils/title-resolver'
|
||||
|
||||
import { ServiceOutletComponent } from './routes/outlet.component'
|
||||
import { ServiceRoute } from './routes/service.component'
|
||||
@@ -11,6 +12,7 @@ import { ServiceRoute } from './routes/service.component'
|
||||
export const ROUTES: Routes = [
|
||||
{
|
||||
path: ':pkgId',
|
||||
title: titleResolver,
|
||||
component: ServiceOutletComponent,
|
||||
children: [
|
||||
{
|
||||
|
||||
@@ -189,18 +189,10 @@ export class BackupNetworkComponent {
|
||||
|
||||
select(target: MappedBackupTarget<CifsBackupTarget>) {
|
||||
if (!target.entry.mountable) {
|
||||
this.dialog
|
||||
.openAlert(ERROR, {
|
||||
appearance: 'negative',
|
||||
label: 'Unable to connect',
|
||||
autoClose: 0,
|
||||
})
|
||||
.subscribe()
|
||||
this.dialog.openAlert(ERROR, { label: 'Unable to connect' }).subscribe()
|
||||
} else if (this.type === 'restore' && !target.hasAnyBackup) {
|
||||
this.dialog
|
||||
.openAlert('Network Folder does not contain a valid backup', {
|
||||
appearance: 'negative',
|
||||
})
|
||||
.openAlert('Network Folder does not contain a valid backup')
|
||||
.subscribe()
|
||||
} else {
|
||||
this.networkFolders.emit(target)
|
||||
|
||||
@@ -132,9 +132,7 @@ export class BackupPhysicalComponent {
|
||||
select(target: MappedBackupTarget<DiskBackupTarget>) {
|
||||
if (this.type === 'restore' && !target.hasAnyBackup) {
|
||||
this.dialog
|
||||
.openAlert('Drive partition does not contain a valid backup', {
|
||||
appearance: 'negative',
|
||||
})
|
||||
.openAlert('Drive partition does not contain a valid backup')
|
||||
.subscribe()
|
||||
} else {
|
||||
this.physicalFolders.emit(target)
|
||||
|
||||
@@ -187,25 +187,16 @@ export default class SystemEmailComponent {
|
||||
|
||||
async sendTestEmail(value: typeof inputSpec.constants.customSmtp._TYPE) {
|
||||
const loader = this.loader.open('Sending email').subscribe()
|
||||
const success =
|
||||
`${this.i18n.transform('A test email has been sent to')} ${this.testAddress}.<br /><br /><b>${this.i18n.transform('Check your spam folder and mark as not spam.')}</b>` as i18nKey
|
||||
|
||||
try {
|
||||
await this.api.testSmtp({
|
||||
to: this.testAddress,
|
||||
...value,
|
||||
})
|
||||
await this.api.testSmtp({ to: this.testAddress, ...value })
|
||||
this.dialog.openAlert(success, { label: 'Success' }).subscribe()
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
} finally {
|
||||
loader.unsubscribe()
|
||||
}
|
||||
|
||||
this.dialog
|
||||
.openAlert(
|
||||
`${this.i18n.transform('A test email has been sent to')} ${this.testAddress}.<br /><br /><b>${this.i18n.transform('Check your spam folder and mark as not spam.')}</b>` as i18nKey,
|
||||
{
|
||||
label: 'Success',
|
||||
},
|
||||
)
|
||||
.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,7 +337,6 @@ export default class SystemGeneralComponent {
|
||||
|
||||
try {
|
||||
await this.api.resetTor({ wipeState, reason: 'User triggered' })
|
||||
|
||||
this.dialog.openAlert('Tor reset in progress').subscribe()
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
|
||||
@@ -80,12 +80,16 @@ export default class StartOsUiComponent {
|
||||
inject<PatchDB<DataModel>>(PatchDB)
|
||||
.watch$('serverInfo', 'network', 'host')
|
||||
.pipe(
|
||||
map(host => ({
|
||||
...this.iface,
|
||||
public:
|
||||
!!host.bindings[this.iface.addressInfo.internalPort]?.net.public,
|
||||
addresses: getAddresses(this.iface, host, this.config),
|
||||
})),
|
||||
map(host => {
|
||||
const port = this.iface.addressInfo.internalPort
|
||||
|
||||
return {
|
||||
...this.iface,
|
||||
addSsl: host.bindings[port]?.options.addSsl,
|
||||
public: !!host.bindings[port]?.net.public,
|
||||
addresses: getAddresses(this.iface, host, this.config),
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ import { wifiSpec } from './wifi.const'
|
||||
type="checkbox"
|
||||
tuiSwitch
|
||||
[style.margin-inline-start]="'auto'"
|
||||
[showIcons]="false"
|
||||
[ngModel]="status()?.enabled"
|
||||
(ngModelChange)="onToggle($event)"
|
||||
/>
|
||||
|
||||
@@ -89,9 +89,7 @@ export class ActionService {
|
||||
this.dialog
|
||||
.openAlert(
|
||||
`${this.i18n.transform('Action can only be executed when service is')} ${statusesStr}` as i18nKey,
|
||||
{
|
||||
label: 'Forbidden',
|
||||
},
|
||||
{ label: 'Forbidden' },
|
||||
)
|
||||
.pipe(filter(Boolean))
|
||||
.subscribe()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { inject, Injectable } from '@angular/core'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
BehaviorSubject,
|
||||
@@ -17,11 +17,14 @@ import { RR } from './api/api.types'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class OSService {
|
||||
private readonly api = inject(ApiService)
|
||||
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||
|
||||
osUpdate?: RR.CheckOsUpdateRes
|
||||
updateAvailable$ = new BehaviorSubject<boolean>(false)
|
||||
readonly updateAvailable$ = new BehaviorSubject<boolean>(false)
|
||||
|
||||
readonly updating$ = this.patch.watch$('serverInfo', 'statusInfo').pipe(
|
||||
map(status => !!status.updateProgress || status.updated),
|
||||
map(status => status.updateProgress ?? status.updated),
|
||||
distinctUntilChanged(),
|
||||
)
|
||||
|
||||
@@ -35,21 +38,12 @@ export class OSService {
|
||||
readonly updatingOrBackingUp$ = combineLatest([
|
||||
this.updating$,
|
||||
this.backingUp$,
|
||||
]).pipe(map(([updating, backingUp]) => updating || backingUp))
|
||||
]).pipe(map(([updating, backingUp]) => !!updating || backingUp))
|
||||
|
||||
readonly showUpdate$ = combineLatest([
|
||||
this.updateAvailable$,
|
||||
this.updating$,
|
||||
]).pipe(
|
||||
map(([available, updating]) => {
|
||||
return available && !updating
|
||||
}),
|
||||
)
|
||||
|
||||
constructor(
|
||||
private readonly api: ApiService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
]).pipe(map(([available, updating]) => available && !updating))
|
||||
|
||||
async loadOS(): Promise<void> {
|
||||
const { version, id } = await getServerInfo(this.patch)
|
||||
@@ -59,9 +53,11 @@ export class OSService {
|
||||
registry: startosRegistry,
|
||||
serverId: id,
|
||||
})
|
||||
const [latestVersion, _] = Object.entries(this.osUpdate).at(-1)!
|
||||
const updateAvailable =
|
||||
Version.parse(latestVersion).compare(Version.parse(version)) === 'greater'
|
||||
this.updateAvailable$.next(updateAvailable)
|
||||
|
||||
const [latest, _] = Object.entries(this.osUpdate).at(-1)!
|
||||
|
||||
this.updateAvailable$.next(
|
||||
Version.parse(latest).compare(Version.parse(version)) === 'greater',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { inject, Injectable } from '@angular/core'
|
||||
import { CanActivateFn, IsActiveMatchOptions, Router } from '@angular/router'
|
||||
import { DialogService } from '@start9labs/shared'
|
||||
import { DialogService, i18nPipe } from '@start9labs/shared'
|
||||
import { TUI_TRUE_HANDLER } from '@taiga-ui/cdk'
|
||||
import { TuiAlertService } from '@taiga-ui/core'
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
@@ -41,7 +42,8 @@ const OPTIONS: IsActiveMatchOptions = {
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class StateService extends Observable<RR.ServerState | null> {
|
||||
private readonly dialog = inject(DialogService)
|
||||
private readonly alerts = inject(TuiAlertService)
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
private readonly api = inject(ApiService)
|
||||
private readonly router = inject(Router)
|
||||
private readonly network$ = inject(NetworkService)
|
||||
@@ -91,10 +93,10 @@ export class StateService extends Observable<RR.ServerState | null> {
|
||||
.pipe(
|
||||
exhaustMap(() =>
|
||||
concat(
|
||||
this.dialog
|
||||
.openAlert('Trying to reach server', {
|
||||
label: 'State unknown',
|
||||
autoClose: 0,
|
||||
this.alerts
|
||||
.open(this.i18n.transform('Trying to reach server'), {
|
||||
label: this.i18n.transform('State unknown'),
|
||||
closeable: false,
|
||||
appearance: 'negative',
|
||||
})
|
||||
.pipe(
|
||||
@@ -104,8 +106,8 @@ export class StateService extends Observable<RR.ServerState | null> {
|
||||
),
|
||||
),
|
||||
),
|
||||
this.dialog.openAlert('Connection restored', {
|
||||
label: 'Server connected',
|
||||
this.alerts.open(this.i18n.transform('Connection restored'), {
|
||||
label: this.i18n.transform('Server connected'),
|
||||
appearance: 'positive',
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -23,7 +23,7 @@ export async function getAllPackages(
|
||||
}
|
||||
|
||||
export function getManifest(pkg: PackageDataEntry): T.Manifest {
|
||||
return isInstalling(pkg)
|
||||
return isInstalling(pkg) || isRestoring(pkg)
|
||||
? pkg.stateInfo.installingInfo.newManifest
|
||||
: pkg.stateInfo.manifest!
|
||||
}
|
||||
|
||||
26
web/projects/ui/src/app/utils/title-resolver.ts
Normal file
26
web/projects/ui/src/app/utils/title-resolver.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { inject } from '@angular/core'
|
||||
import { ActivatedRouteSnapshot } from '@angular/router'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { firstValueFrom } from 'rxjs'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { getManifest } from 'src/app/utils/get-package-data'
|
||||
|
||||
export async function titleResolver({
|
||||
data,
|
||||
params,
|
||||
}: ActivatedRouteSnapshot): Promise<string> {
|
||||
let route = inject(i18nPipe).transform(data['title'])
|
||||
|
||||
const patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||
const title = await firstValueFrom(patch.watch$('ui', 'name'))
|
||||
const id = params['pkgId']
|
||||
|
||||
if (id) {
|
||||
const service = await firstValueFrom(patch.watch$('packageData', id))
|
||||
|
||||
route = service && getManifest(service).title
|
||||
}
|
||||
|
||||
return `${title || 'StartOS'} — ${route}`
|
||||
}
|
||||
Reference in New Issue
Block a user