mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
feat: finalize desktop and mobile design of system routes (#2855)
* feat: finalize desktop and mobile design of system routes * clean up messaging and mobile tabbar utilities --------- Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
200
web/package-lock.json
generated
200
web/package-lock.json
generated
@@ -25,17 +25,17 @@
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"@start9labs/argon2": "^0.2.2",
|
||||
"@start9labs/start-sdk": "file:../sdk/baseDist",
|
||||
"@taiga-ui/addon-charts": "4.28.0",
|
||||
"@taiga-ui/addon-commerce": "4.28.0",
|
||||
"@taiga-ui/addon-mobile": "4.28.0",
|
||||
"@taiga-ui/addon-table": "4.28.0",
|
||||
"@taiga-ui/cdk": "4.28.0",
|
||||
"@taiga-ui/core": "4.28.0",
|
||||
"@taiga-ui/event-plugins": "4.4.1",
|
||||
"@taiga-ui/icons": "4.28.0",
|
||||
"@taiga-ui/kit": "4.28.0",
|
||||
"@taiga-ui/layout": "4.28.0",
|
||||
"@taiga-ui/legacy": "4.28.0",
|
||||
"@taiga-ui/addon-charts": "4.30.0",
|
||||
"@taiga-ui/addon-commerce": "4.30.0",
|
||||
"@taiga-ui/addon-mobile": "4.30.0",
|
||||
"@taiga-ui/addon-table": "4.30.0",
|
||||
"@taiga-ui/cdk": "4.30.0",
|
||||
"@taiga-ui/core": "4.30.0",
|
||||
"@taiga-ui/event-plugins": "4.5.0",
|
||||
"@taiga-ui/icons": "4.30.0",
|
||||
"@taiga-ui/kit": "4.30.0",
|
||||
"@taiga-ui/layout": "4.30.0",
|
||||
"@taiga-ui/legacy": "4.30.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.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@maskito/angular/-/angular-3.4.0.tgz",
|
||||
"integrity": "sha512-iMFP/siEgU9Ki+g1PReZlA5+LlBMp6inqXGG5KCezhmDleZnG5lL9gxk3+ktJvKu+2kayLcwyBeUKXPwMBVt9w==",
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@maskito/angular/-/angular-3.5.0.tgz",
|
||||
"integrity": "sha512-5uwar32qsGdZNHUgZpFnICg9tJKCXbZEGk2ZnchHzDIfN5ojNT7wKzoq8NhpRlGb3p4qQCE+PXb5GERkcWM/Sw==",
|
||||
"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.4.0"
|
||||
"@maskito/core": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@maskito/core": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@maskito/core/-/core-3.4.0.tgz",
|
||||
"integrity": "sha512-gFM6qk675YwOEGhxu9Xm6/sl1TZBRab6+B3Gstqml7xJopHHZ0rUOrWXwmX0z2JI+1PsgUL/ftV/CSZ8CpIONg==",
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@maskito/core/-/core-3.5.0.tgz",
|
||||
"integrity": "sha512-zgmBjXeXc7BSBaw8jQw25dnwkFmKDvdj5rHzhEIxYhgGtnpli236F0YWPIOYzIwADjbefwDq1o7qpJfMsdDO4Q==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@maskito/kit": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@maskito/kit/-/kit-3.4.0.tgz",
|
||||
"integrity": "sha512-jkexr7wjAqFeMpyc7s0IlinL+3F9xC4BYUHDQcEqlAJisDgVFtGCZZK/RvV1C+HGDn2gtzzVrJ3G/OY66k6EXg==",
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@maskito/kit/-/kit-3.5.0.tgz",
|
||||
"integrity": "sha512-QnpZsPTINgK4ScA4pMMJagoj+ufIXc/VGOP61AsQa/H/lmXII4pEZTLzpmMNUYmCEIEyjHR2DIbfEed04sktvQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@maskito/core": "^3.4.0"
|
||||
"@maskito/core": "^3.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@maskito/phone": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@maskito/phone/-/phone-3.4.0.tgz",
|
||||
"integrity": "sha512-KR6JuuWhTumIOCUV3CzPhh1niCXcuqsogNsLW3YfdmeVo8GygS9isnHNbSaAA/b9OnmIEkh25mur6x3yEJuYjA==",
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@maskito/phone/-/phone-3.5.0.tgz",
|
||||
"integrity": "sha512-qh/GGRFn8cZBY/JUTLa5yeSSKSVlekggKeiCbf0eX0I53/HM2pNZ/5667S8SXwn5WjIEeB79Eltl8MNvK74yvA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@maskito/core": "^3.4.0",
|
||||
"@maskito/kit": "^3.4.0",
|
||||
"@maskito/core": "^3.5.0",
|
||||
"@maskito/kit": "^3.5.0",
|
||||
"libphonenumber-js": ">=1.0.0"
|
||||
}
|
||||
},
|
||||
@@ -4417,9 +4417,9 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@taiga-ui/addon-charts": {
|
||||
"version": "4.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-4.28.0.tgz",
|
||||
"integrity": "sha512-Lvi2R8Y50kBbfbru31YHon+CEpnOzAx0G4GnqjN2goTLNQ6iX7pgUeyRyiXI4ay1yLrzVIOZJhSmBwWSDocZEg==",
|
||||
"version": "4.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-4.30.0.tgz",
|
||||
"integrity": "sha512-QrM2Oh4hUcg/I0K3KWFkc/dbTCYZn2n5GU2FSpZaK6I7pwjfRoMjBU7vswPLVVdmgeWTJxxoQlbfYnbUbkMAJw==",
|
||||
"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.11.1",
|
||||
"@taiga-ui/cdk": "^4.28.0",
|
||||
"@taiga-ui/core": "^4.28.0",
|
||||
"@taiga-ui/polymorpheus": "^4.8.0"
|
||||
"@taiga-ui/cdk": "^4.30.0",
|
||||
"@taiga-ui/core": "^4.30.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/addon-commerce": {
|
||||
"version": "4.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-4.28.0.tgz",
|
||||
"integrity": "sha512-VYygBL7oySCZYLBimGJPx/VGGtUGhpes3XwBHAPBmmyiVxct0kxXzhCQdAvNMQcSvDzXDBjg3wmJiUbZA/uHGQ==",
|
||||
"version": "4.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-4.30.0.tgz",
|
||||
"integrity": "sha512-6diktxvxMpWjbEHXThS0pTrURdUiF/47jf2jdBFkMwX3BbbekisM1qkwxY24V7q8fN0IIxfO8CVEjTeLRrCw5g==",
|
||||
"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.4.0",
|
||||
"@maskito/core": "^3.4.0",
|
||||
"@maskito/kit": "^3.4.0",
|
||||
"@maskito/angular": "^3.5.0",
|
||||
"@maskito/core": "^3.5.0",
|
||||
"@maskito/kit": "^3.5.0",
|
||||
"@ng-web-apis/common": "^4.11.1",
|
||||
"@taiga-ui/cdk": "^4.28.0",
|
||||
"@taiga-ui/core": "^4.28.0",
|
||||
"@taiga-ui/i18n": "^4.28.0",
|
||||
"@taiga-ui/kit": "^4.28.0",
|
||||
"@taiga-ui/polymorpheus": "^4.8.0",
|
||||
"@taiga-ui/cdk": "^4.30.0",
|
||||
"@taiga-ui/core": "^4.30.0",
|
||||
"@taiga-ui/i18n": "^4.30.0",
|
||||
"@taiga-ui/kit": "^4.30.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/addon-mobile": {
|
||||
"version": "4.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-4.28.0.tgz",
|
||||
"integrity": "sha512-1RRaX37Ddl24q4nHrMEz6iDqHWi/mkTyXQ+kADX7+ydx9JkbU2H4R+qXrOx4+GUi93Y05HvAWCNCToBu3Ytt2A==",
|
||||
"version": "4.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-4.30.0.tgz",
|
||||
"integrity": "sha512-8cYyU0UDLUd74v+Zjs4m9S4AsSWchUojAexDLvaAHzfi0x+tdtA+ZN0h49v8AmOWHK0v69z4FMjyyc52p/jiDw==",
|
||||
"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.11.1",
|
||||
"@taiga-ui/cdk": "^4.28.0",
|
||||
"@taiga-ui/core": "^4.28.0",
|
||||
"@taiga-ui/kit": "^4.28.0",
|
||||
"@taiga-ui/layout": "^4.28.0",
|
||||
"@taiga-ui/polymorpheus": "^4.8.0",
|
||||
"@taiga-ui/cdk": "^4.30.0",
|
||||
"@taiga-ui/core": "^4.30.0",
|
||||
"@taiga-ui/kit": "^4.30.0",
|
||||
"@taiga-ui/layout": "^4.30.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/addon-table": {
|
||||
"version": "4.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-table/-/addon-table-4.28.0.tgz",
|
||||
"integrity": "sha512-C8MW6kJ3T9zy51rSxqYApll+S84oizK6C85gZyDM3gEV2RAlK2DP+r657ZlEwEgobrFCtBZe++TT7ZoKpQBBHg==",
|
||||
"version": "4.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-table/-/addon-table-4.30.0.tgz",
|
||||
"integrity": "sha512-OdCEwlrMs42Z2pINK1wvNk7OZmAlkj+mbgHTyMGdrUdA49dFZfYXNpVUCwVOqHAm2PDOeVN4ybZ8FSbzYefJyw==",
|
||||
"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.11.1",
|
||||
"@taiga-ui/cdk": "^4.28.0",
|
||||
"@taiga-ui/core": "^4.28.0",
|
||||
"@taiga-ui/i18n": "^4.28.0",
|
||||
"@taiga-ui/kit": "^4.28.0",
|
||||
"@taiga-ui/polymorpheus": "^4.8.0",
|
||||
"@taiga-ui/cdk": "^4.30.0",
|
||||
"@taiga-ui/core": "^4.30.0",
|
||||
"@taiga-ui/i18n": "^4.30.0",
|
||||
"@taiga-ui/kit": "^4.30.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/cdk": {
|
||||
"version": "4.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-4.28.0.tgz",
|
||||
"integrity": "sha512-P2vK+4WDnSt/nnilqxvDS4lyMAEH/M73z9YSzyH5mEwVTNxD3m82jJgpHqV5Re7geooAyaKqS6MJwDxaN0+9eQ==",
|
||||
"version": "4.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-4.30.0.tgz",
|
||||
"integrity": "sha512-ndfnLOnL6vriItm5lq8/0slzj03CatkGVYG8zAT3fx00Vuam5Wf8Sh6h2ObqCFAljT7WJxHqMF9A1cBfLPI/iQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "2.8.1"
|
||||
@@ -4525,14 +4525,14 @@
|
||||
"@ng-web-apis/resize-observer": "^4.11.1",
|
||||
"@ng-web-apis/screen-orientation": "^4.11.1",
|
||||
"@taiga-ui/event-plugins": "^4.4.1",
|
||||
"@taiga-ui/polymorpheus": "^4.8.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/core": {
|
||||
"version": "4.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-4.28.0.tgz",
|
||||
"integrity": "sha512-4eP6PJvmHZCrV/9apxfu6Bgj7L72yjVg1R5c4j1MsVmMESLCCRGlk0hPPvuxVQ+ZYrOZwNeWyKHPZDPL5uQawA==",
|
||||
"version": "4.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-4.30.0.tgz",
|
||||
"integrity": "sha512-IeZ6QBpSuv7k4bQx2BSDr8N3dDiMDwgnnwkkKqtJ0yJayZ/ZlCMq3nUQA0kg3VjH2spJeUbdqkDqpEuzrWJGkA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.8.1"
|
||||
@@ -4546,17 +4546,17 @@
|
||||
"@angular/router": ">=16.0.0",
|
||||
"@ng-web-apis/common": "^4.11.1",
|
||||
"@ng-web-apis/mutation-observer": "^4.11.1",
|
||||
"@taiga-ui/cdk": "^4.28.0",
|
||||
"@taiga-ui/cdk": "^4.30.0",
|
||||
"@taiga-ui/event-plugins": "^4.4.1",
|
||||
"@taiga-ui/i18n": "^4.28.0",
|
||||
"@taiga-ui/polymorpheus": "^4.8.0",
|
||||
"@taiga-ui/i18n": "^4.30.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/event-plugins": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/event-plugins/-/event-plugins-4.4.1.tgz",
|
||||
"integrity": "sha512-gwEkgyZsbAdRfmb98KlKWivYVF88eP0bOtbHwfj8Ec8DgJ5809qFqeWvJEIxZZ829iox1m8z2UuVrqN2/tI1tQ==",
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/event-plugins/-/event-plugins-4.5.0.tgz",
|
||||
"integrity": "sha512-bMW36eqr4Q+EnUM8ZNjx1Sw8POIAcyALY74xVPq9UHoQ3NqnRkeEDnZdfPhq9IYxtC3sO2BttNjWYcvBAkU2+A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
@@ -4568,9 +4568,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/i18n": {
|
||||
"version": "4.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.28.0.tgz",
|
||||
"integrity": "sha512-kM7bbqllzir4nEk3X+YMKATm23UoKJeWSGmwnjLEmhWkpNAGqfErDRbE2puf+jXy7eufGhaB7ht/mK4+HkLXbw==",
|
||||
"version": "4.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.30.0.tgz",
|
||||
"integrity": "sha512-OvtUqSRQE988XfiH1MS7Wd3Eg6dE1mkP2sqYRLw0HyE5Oc9hgHMwdPstSaoMN9aeJRVZnKXGsYmX4iaQ3x7drw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -4583,18 +4583,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/icons": {
|
||||
"version": "4.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-4.28.0.tgz",
|
||||
"integrity": "sha512-1QS7gvYHuTRUUodE58OXm+4Ree5FhFe0co0Lj+3sqeqkYb495z5q3CXBNiXD3y8IcDTjNuYkxKxEthbPnQrsVQ==",
|
||||
"version": "4.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-4.30.0.tgz",
|
||||
"integrity": "sha512-EAbvw1ii4UVDgt9+5t7NQkV0WBqkVm5SGixH0ux8Vb4qhhLJJwp5xvXOCGt5QPzviT7nFGqXD6EqB23aYcuusg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/kit": {
|
||||
"version": "4.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-4.28.0.tgz",
|
||||
"integrity": "sha512-JEHUZhWU0vgPorvO3l9POzWKPbFQA57jFh9Iv5/RlWxMI8EUI+OKH5J8z1ptX+RJE2dWB9+Yi84zasgr8TWcSA==",
|
||||
"version": "4.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-4.30.0.tgz",
|
||||
"integrity": "sha512-tCHZbsiq1u19ariarFuP9iwnNSxJGicQnYvJYy2+QojL65KsC9p8VgZv36rpggpuPEUXRXwmhyz2Qi6fwFcbLg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.8.1"
|
||||
@@ -4604,25 +4604,25 @@
|
||||
"@angular/core": ">=16.0.0",
|
||||
"@angular/forms": ">=16.0.0",
|
||||
"@angular/router": ">=16.0.0",
|
||||
"@maskito/angular": "^3.4.0",
|
||||
"@maskito/core": "^3.4.0",
|
||||
"@maskito/kit": "^3.4.0",
|
||||
"@maskito/phone": "^3.4.0",
|
||||
"@maskito/angular": "^3.5.0",
|
||||
"@maskito/core": "^3.5.0",
|
||||
"@maskito/kit": "^3.5.0",
|
||||
"@maskito/phone": "^3.5.0",
|
||||
"@ng-web-apis/common": "^4.11.1",
|
||||
"@ng-web-apis/intersection-observer": "^4.11.1",
|
||||
"@ng-web-apis/mutation-observer": "^4.11.1",
|
||||
"@ng-web-apis/resize-observer": "^4.11.1",
|
||||
"@taiga-ui/cdk": "^4.28.0",
|
||||
"@taiga-ui/core": "^4.28.0",
|
||||
"@taiga-ui/i18n": "^4.28.0",
|
||||
"@taiga-ui/polymorpheus": "^4.8.0",
|
||||
"@taiga-ui/cdk": "^4.30.0",
|
||||
"@taiga-ui/core": "^4.30.0",
|
||||
"@taiga-ui/i18n": "^4.30.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/layout": {
|
||||
"version": "4.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-4.28.0.tgz",
|
||||
"integrity": "sha512-NlXdEmXGhYvTWeSSpGlT9XS0SU1aQDuFAMFBSDVsZqLPWh2DTnNsxSf1/b6UYMmX5JKXhH/bRVvX97N5L5XZqQ==",
|
||||
"version": "4.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-4.30.0.tgz",
|
||||
"integrity": "sha512-DyIqpmXcv/OP4byt7L1f1iBKPysf3L+sj/dBpkeYvAUUnJnXnJsXav0j57d43VkXPn9lpGqz0gEBtzVDt7xxTw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.8.1"
|
||||
@@ -4630,17 +4630,17 @@
|
||||
"peerDependencies": {
|
||||
"@angular/common": ">=16.0.0",
|
||||
"@angular/core": ">=16.0.0",
|
||||
"@taiga-ui/cdk": "^4.28.0",
|
||||
"@taiga-ui/core": "^4.28.0",
|
||||
"@taiga-ui/kit": "^4.28.0",
|
||||
"@taiga-ui/polymorpheus": "^4.8.0",
|
||||
"@taiga-ui/cdk": "^4.30.0",
|
||||
"@taiga-ui/core": "^4.30.0",
|
||||
"@taiga-ui/kit": "^4.30.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/legacy": {
|
||||
"version": "4.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/legacy/-/legacy-4.28.0.tgz",
|
||||
"integrity": "sha512-mWE5w7alYsT8GMBNTfcvrf/sJjh1li2/mTykH/aoWklgYHHmSt6moY4Myi8wKdlRFBzi82eXsvJcUSCwD8Y5ew==",
|
||||
"version": "4.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/legacy/-/legacy-4.30.0.tgz",
|
||||
"integrity": "sha512-ebFJMddzlsq3TUAWxopn5Qju4REkC4bHzoYYx5OEzPq1VW1zmCvNC+X6usMnluhc9aS50UI8ZB7Xd3N4Zdgtfg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.8.1"
|
||||
|
||||
@@ -47,17 +47,17 @@
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"@start9labs/argon2": "^0.2.2",
|
||||
"@start9labs/start-sdk": "file:../sdk/baseDist",
|
||||
"@taiga-ui/addon-charts": "4.28.0",
|
||||
"@taiga-ui/addon-commerce": "4.28.0",
|
||||
"@taiga-ui/addon-mobile": "4.28.0",
|
||||
"@taiga-ui/addon-table": "4.28.0",
|
||||
"@taiga-ui/cdk": "4.28.0",
|
||||
"@taiga-ui/core": "4.28.0",
|
||||
"@taiga-ui/event-plugins": "4.4.1",
|
||||
"@taiga-ui/icons": "4.28.0",
|
||||
"@taiga-ui/kit": "4.28.0",
|
||||
"@taiga-ui/layout": "4.28.0",
|
||||
"@taiga-ui/legacy": "4.28.0",
|
||||
"@taiga-ui/addon-charts": "4.30.0",
|
||||
"@taiga-ui/addon-commerce": "4.30.0",
|
||||
"@taiga-ui/addon-mobile": "4.30.0",
|
||||
"@taiga-ui/addon-table": "4.30.0",
|
||||
"@taiga-ui/cdk": "4.30.0",
|
||||
"@taiga-ui/core": "4.30.0",
|
||||
"@taiga-ui/event-plugins": "4.5.0",
|
||||
"@taiga-ui/icons": "4.30.0",
|
||||
"@taiga-ui/kit": "4.30.0",
|
||||
"@taiga-ui/layout": "4.30.0",
|
||||
"@taiga-ui/legacy": "4.30.0",
|
||||
"@taiga-ui/polymorpheus": "4.9.0",
|
||||
"@tinkoff/ng-dompurify": "4.0.0",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
|
||||
@@ -162,20 +162,3 @@ tui-badge-notification {
|
||||
align-self: center !important;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Remove after Taiga UI update
|
||||
[tuiTitle] {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
[tuiSubtitle] {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { signal } from '@angular/core'
|
||||
import { forwardRef, signal } from '@angular/core'
|
||||
import { tuiCreateToken, tuiProvide } from '@taiga-ui/cdk'
|
||||
import {
|
||||
TuiLanguageName,
|
||||
@@ -34,5 +34,8 @@ export const I18N_PROVIDERS = [
|
||||
}
|
||||
},
|
||||
},
|
||||
tuiProvide(TuiLanguageSwitcherService, i18nService),
|
||||
tuiProvide(
|
||||
TuiLanguageSwitcherService,
|
||||
forwardRef(() => i18nService),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -87,9 +87,7 @@ import { InterfaceComponent } from './interface.component'
|
||||
:host {
|
||||
text-align: right;
|
||||
grid-area: 1 / 2 / 3 / 3;
|
||||
}
|
||||
|
||||
.desktop {
|
||||
place-content: center;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
|
||||
@@ -19,14 +19,6 @@ import { MappedServiceInterface } from './interface.utils'
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
section {
|
||||
padding-block-end: 1rem;
|
||||
}
|
||||
|
||||
:host-context(tui-root:not(._mobile)) section ::ng-deep > header {
|
||||
background: none;
|
||||
}
|
||||
`,
|
||||
providers: [tuiButtonOptionsProvider({ size: 'xs' })],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
|
||||
@@ -13,6 +13,11 @@ import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
||||
</thead>
|
||||
<tbody><ng-content /></tbody>
|
||||
`,
|
||||
styles: `
|
||||
:host:has(app-placeholder) thead {
|
||||
display: none;
|
||||
}
|
||||
`,
|
||||
host: { class: 'g-table' },
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
|
||||
@@ -10,9 +10,7 @@ import { RouterLink, RouterLinkActive } from '@angular/router'
|
||||
import { TuiResponsiveDialogService, TuiTabBar } from '@taiga-ui/addon-mobile'
|
||||
import { TuiIcon } from '@taiga-ui/core'
|
||||
import { TuiBadgeNotification } from '@taiga-ui/kit'
|
||||
import { ABOUT } from 'src/app/routes/portal/components/header/about.component'
|
||||
import { BadgeService } from 'src/app/services/badge.service'
|
||||
import { RESOURCES } from 'src/app/utils/resources'
|
||||
import { getMenu } from 'src/app/utils/system-utilities'
|
||||
|
||||
const FILTER = ['/portal/services', '/portal/system', '/portal/marketplace']
|
||||
@@ -72,20 +70,6 @@ const FILTER = ['/portal/services', '/portal/system', '/portal/marketplace']
|
||||
}
|
||||
</a>
|
||||
}
|
||||
<button class="item" (click)="about()">
|
||||
<tui-icon icon="@tui.info" />
|
||||
About this server
|
||||
</button>
|
||||
@for (link of resources; track $index) {
|
||||
<a class="item" target="_blank" rel="noreferrer" [href]="link.href">
|
||||
<tui-icon [icon]="link.icon" />
|
||||
{{ link.name }}
|
||||
<tui-icon
|
||||
icon="@tui.external-link"
|
||||
[style.margin-inline-start]="'auto'"
|
||||
/>
|
||||
</a>
|
||||
}
|
||||
</ng-template>
|
||||
</button>
|
||||
</nav>
|
||||
@@ -138,7 +122,6 @@ export class TabsComponent {
|
||||
|
||||
index = 3
|
||||
|
||||
readonly resources = RESOURCES
|
||||
readonly menu = getMenu().filter(item => !FILTER.includes(item.routerLink))
|
||||
readonly badge = toSignal(inject(BadgeService).getCount('/portal/system'), {
|
||||
initialValue: 0,
|
||||
@@ -148,10 +131,6 @@ export class TabsComponent {
|
||||
this.menu.reduce((acc, item) => acc + item.badge(), 0),
|
||||
)
|
||||
|
||||
about() {
|
||||
this.dialogs.open(ABOUT, { label: 'About this server' }).subscribe()
|
||||
}
|
||||
|
||||
more(content: TemplateRef<any>) {
|
||||
this.dialogs.open(content, { label: 'Start OS' }).subscribe({
|
||||
complete: () => this.update(),
|
||||
|
||||
@@ -55,6 +55,10 @@ import { ServicesService } from './services.service'
|
||||
font-size: 1rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
padding: 0;
|
||||
}
|
||||
`,
|
||||
host: { class: 'g-page' },
|
||||
imports: [ServiceComponent, ToManifestPipe, TuiTable, TitleDirective],
|
||||
|
||||
@@ -2,8 +2,8 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||
import { ISB, utils } from '@start9labs/start-sdk'
|
||||
import { TuiButton, TuiLoader, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiCell } from '@taiga-ui/layout'
|
||||
import { TuiButton, TuiLink, TuiLoader, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiCell, TuiHeader } from '@taiga-ui/layout'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { map } from 'rxjs'
|
||||
import { FormComponent } from 'src/app/routes/portal/components/form.component'
|
||||
@@ -12,11 +12,28 @@ import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { knownACME, toAcmeName } from 'src/app/utils/acme'
|
||||
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
||||
import { AcmeInfoComponent } from './info.component'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<acme-info />
|
||||
<header tuiHeader>
|
||||
<hgroup tuiTitle>
|
||||
<h3>ACME</h3>
|
||||
<p tuiSubtitle>
|
||||
Add ACME providers in order to generate SSL (https) certificates for
|
||||
clearnet access.
|
||||
<a
|
||||
tuiLink
|
||||
href="https://docs.start9.com/latest/user-manual/acme"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
appearance="action-grayscale"
|
||||
iconEnd="@tui.external-link"
|
||||
[pseudo]="true"
|
||||
[textContent]="'View instructions'"
|
||||
></a>
|
||||
</p>
|
||||
</hgroup>
|
||||
</header>
|
||||
<section class="g-card">
|
||||
<header>
|
||||
Saved Providers
|
||||
@@ -62,9 +79,14 @@ import { AcmeInfoComponent } from './info.component'
|
||||
}
|
||||
</section>
|
||||
`,
|
||||
styles: `
|
||||
:host {
|
||||
max-width: 40rem;
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [TuiButton, TuiLoader, TuiCell, TuiTitle, AcmeInfoComponent],
|
||||
imports: [TuiButton, TuiLoader, TuiCell, TuiTitle, TuiHeader, TuiLink],
|
||||
})
|
||||
export default class SystemAcmeComponent {
|
||||
private readonly formDialog = inject(FormDialogService)
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { TuiLink, TuiNotification } from '@taiga-ui/core'
|
||||
|
||||
@Component({
|
||||
selector: 'acme-info',
|
||||
template: `
|
||||
<tui-notification>
|
||||
Register with one or more ACME providers such as Let's Encrypt in order to
|
||||
generate SSL (https) certificates on-demand for clearnet hosting.
|
||||
<a
|
||||
tuiLink
|
||||
href="https://docs.start9.com/latest/user-manual/acme"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
iconEnd="@tui.external-link"
|
||||
[textContent]="'View instructions'"
|
||||
></a>
|
||||
</tui-notification>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [TuiNotification, TuiLink],
|
||||
})
|
||||
export class AcmeInfoComponent {}
|
||||
@@ -1,21 +1,32 @@
|
||||
import { AsyncPipe } from '@angular/common'
|
||||
import { AsyncPipe, DatePipe } from '@angular/common'
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
inject,
|
||||
OnInit,
|
||||
} from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router'
|
||||
import { UnitConversionPipesModule } from '@start9labs/shared'
|
||||
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
|
||||
import { TuiButton, TuiLink, TuiLoader } from '@taiga-ui/core'
|
||||
import { BACKUP } from 'src/app/routes/portal/routes/system/routes/backups/backup.component'
|
||||
import { TuiMapperPipe } from '@taiga-ui/cdk'
|
||||
import {
|
||||
TuiButton,
|
||||
TuiLink,
|
||||
TuiLoader,
|
||||
TuiNotification,
|
||||
TuiTitle,
|
||||
} from '@taiga-ui/core'
|
||||
import { TuiHeader } from '@taiga-ui/layout'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
CifsBackupTarget,
|
||||
DiskBackupTarget,
|
||||
} from 'src/app/services/api/api.types'
|
||||
import { EOSService } from 'src/app/services/eos.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { TitleDirective } from 'src/app/services/title.service'
|
||||
import { BACKUP } from './backup.component'
|
||||
import { BackupService, MappedBackupTarget } from './backup.service'
|
||||
import { BackupNetworkComponent } from './network.component'
|
||||
import { BackupPhysicalComponent } from './physical.component'
|
||||
@@ -29,6 +40,53 @@ import { BACKUP_RESTORE } from './restore.component'
|
||||
{{ type === 'create' ? 'Create Backup' : 'Restore Backup' }}
|
||||
</ng-container>
|
||||
|
||||
<header tuiHeader>
|
||||
<hgroup tuiTitle>
|
||||
<h3>{{ type === 'create' ? 'Create Backup' : 'Restore Backup' }}</h3>
|
||||
<p tuiSubtitle>
|
||||
@if (type === 'create') {
|
||||
Back up StartOS and service data by connecting to a device on your
|
||||
local network or a physical drive connected to your server.
|
||||
<a
|
||||
tuiLink
|
||||
href="https://docs.start9.com/0.3.5.x/user-manual/backups/backup-create"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
appearance="action-grayscale"
|
||||
iconEnd="@tui.external-link"
|
||||
[pseudo]="true"
|
||||
[textContent]="'View instructions'"
|
||||
></a>
|
||||
} @else {
|
||||
Restore StartOS and service data from a device on your local network
|
||||
or a physical drive connected to your server that contains an
|
||||
existing backup.
|
||||
<a
|
||||
tuiLink
|
||||
href="https://docs.start9.com/0.3.5.x/user-manual/backups/backup-restore"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
appearance="action-grayscale"
|
||||
iconEnd="@tui.external-link"
|
||||
[pseudo]="true"
|
||||
[textContent]="'View instructions'"
|
||||
></a>
|
||||
}
|
||||
</p>
|
||||
</hgroup>
|
||||
</header>
|
||||
|
||||
@if (type === 'create' && server(); as s) {
|
||||
<tui-notification [appearance]="s.lastBackup | tuiMapper: toAppearance">
|
||||
<div tuiTitle>
|
||||
Last Backup
|
||||
<div tuiSubtitle>
|
||||
{{ s.lastBackup ? (s.lastBackup | date: 'medium') : 'never' }}
|
||||
</div>
|
||||
</div>
|
||||
</tui-notification>
|
||||
}
|
||||
|
||||
@if (type === 'create' && (eos.backingUp$ | async)) {
|
||||
<section backupProgress></section>
|
||||
} @else {
|
||||
@@ -40,8 +98,7 @@ import { BACKUP_RESTORE } from './restore.component'
|
||||
/>
|
||||
} @else {
|
||||
<section (networkFolders)="onTarget($event)">
|
||||
{{ text }}
|
||||
a folder on another computer that is connected to the same network as
|
||||
A folder on another computer that is connected to the same network as
|
||||
your Start9 server. View the
|
||||
<a
|
||||
tuiLink
|
||||
@@ -53,17 +110,7 @@ import { BACKUP_RESTORE } from './restore.component'
|
||||
></a>
|
||||
</section>
|
||||
<section (physicalFolders)="onTarget($event)">
|
||||
{{ text }}
|
||||
a physical drive that is plugged directly into your Start9 Server.
|
||||
View the
|
||||
<a
|
||||
tuiLink
|
||||
href="https://docs.start9.com/0.3.5.x/user-manual/backups/backup-create#physical-drive"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
iconEnd="@tui.external-link"
|
||||
[textContent]="'Instructions'"
|
||||
></a>
|
||||
A physical drive that is plugged directly into your Start9 Server.
|
||||
</section>
|
||||
}
|
||||
}
|
||||
@@ -71,15 +118,20 @@ import { BACKUP_RESTORE } from './restore.component'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [
|
||||
AsyncPipe,
|
||||
DatePipe,
|
||||
RouterLink,
|
||||
TuiButton,
|
||||
TuiLoader,
|
||||
TuiLink,
|
||||
TuiHeader,
|
||||
TuiTitle,
|
||||
TuiNotification,
|
||||
TuiMapperPipe,
|
||||
TitleDirective,
|
||||
UnitConversionPipesModule,
|
||||
BackupNetworkComponent,
|
||||
BackupPhysicalComponent,
|
||||
AsyncPipe,
|
||||
BackupProgressComponent,
|
||||
],
|
||||
})
|
||||
@@ -88,11 +140,25 @@ export default class SystemBackupComponent implements OnInit {
|
||||
readonly type = inject(ActivatedRoute).snapshot.data['type']
|
||||
readonly service = inject(BackupService)
|
||||
readonly eos = inject(EOSService)
|
||||
readonly server = toSignal(
|
||||
inject<PatchDB<DataModel>>(PatchDB).watch$('serverInfo'),
|
||||
)
|
||||
|
||||
get text() {
|
||||
return this.type === 'create'
|
||||
? 'Backup server to'
|
||||
: 'Restore your services from'
|
||||
readonly toAppearance = (lastBackup: string | null) => {
|
||||
if (!lastBackup) return 'negative'
|
||||
|
||||
const currentDate = new Date().valueOf()
|
||||
const backupDate = new Date(lastBackup).valueOf()
|
||||
const diff = currentDate - backupDate
|
||||
const week = 604800000
|
||||
|
||||
if (diff <= week) {
|
||||
return 'positive'
|
||||
} else if (diff > week && diff <= week * 2) {
|
||||
return 'warning'
|
||||
} else {
|
||||
return 'negative'
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { TuiCell } from '@taiga-ui/layout'
|
||||
import { filter } from 'rxjs'
|
||||
import { FormComponent } from 'src/app/routes/portal/components/form.component'
|
||||
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||
import { CifsBackupTarget, RR } from 'src/app/services/api/api.types'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||
@@ -37,69 +38,132 @@ const ERROR =
|
||||
</button>
|
||||
</header>
|
||||
|
||||
@for (target of service.cifs(); track $index) {
|
||||
<button tuiCell (click)="select(target)">
|
||||
<tui-icon icon="@tui.folder-open" />
|
||||
<span tuiTitle>
|
||||
<strong>{{ target.entry.path.split('/').pop() }}</strong>
|
||||
@if (target.entry.mountable) {
|
||||
<span tuiSubtitle [backupStatus]="target.hasAnyBackup"></span>
|
||||
} @else {
|
||||
<span tuiSubtitle>
|
||||
<tui-icon
|
||||
icon="@tui.signal-high"
|
||||
class="g-negative"
|
||||
[style.font-size.rem]="1"
|
||||
/>
|
||||
Unable to connect
|
||||
</span>
|
||||
}
|
||||
<span tuiSubtitle>
|
||||
<b>Hostname:</b>
|
||||
{{ target.entry.hostname }}
|
||||
</span>
|
||||
<span tuiSubtitle>
|
||||
<b>Path:</b>
|
||||
{{ target.entry.path }}
|
||||
</span>
|
||||
</span>
|
||||
<button
|
||||
tuiIconButton
|
||||
appearance="action-destructive"
|
||||
iconStart="@tui.trash"
|
||||
(click.stop)="forget(target, $index)"
|
||||
<table [appTable]="['Status', 'Name', 'Hostname', 'Path', '']">
|
||||
@for (target of service.cifs(); track $index) {
|
||||
<tr
|
||||
tabindex="0"
|
||||
(click)="select(target)"
|
||||
(keydown.enter)="select(target)"
|
||||
>
|
||||
Forget
|
||||
</button>
|
||||
<button
|
||||
tuiIconButton
|
||||
appearance="icon"
|
||||
size="xs"
|
||||
iconStart="@tui.pencil"
|
||||
(click.stop)="edit(target)"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
</button>
|
||||
} @empty {
|
||||
<app-placeholder icon="@tui.folder-x">No network folders</app-placeholder>
|
||||
}
|
||||
<td>
|
||||
@if (target.entry.mountable) {
|
||||
<span [backupStatus]="target.hasAnyBackup"></span>
|
||||
} @else {
|
||||
<span>
|
||||
<tui-icon
|
||||
icon="@tui.signal-high"
|
||||
class="g-negative"
|
||||
[style.font-size.rem]="1"
|
||||
/>
|
||||
Unable to connect
|
||||
</span>
|
||||
}
|
||||
</td>
|
||||
<td class="name">{{ target.entry.path.split('/').pop() }}</td>
|
||||
<td>{{ target.entry.hostname }}</td>
|
||||
<td>{{ target.entry.path }}</td>
|
||||
<td>
|
||||
<button
|
||||
tuiIconButton
|
||||
size="s"
|
||||
appearance="action-destructive"
|
||||
iconStart="@tui.trash"
|
||||
(click.stop)="forget(target, $index)"
|
||||
>
|
||||
Forget
|
||||
</button>
|
||||
<button
|
||||
tuiIconButton
|
||||
appearance="icon"
|
||||
size="xs"
|
||||
iconStart="@tui.pencil"
|
||||
(click.stop)="edit(target)"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<app-placeholder icon="@tui.folder-x">
|
||||
No network folders
|
||||
</app-placeholder>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
`,
|
||||
styles: `
|
||||
@import '@taiga-ui/core/styles/taiga-ui-local';
|
||||
|
||||
tr {
|
||||
cursor: pointer;
|
||||
@include transition(background);
|
||||
|
||||
@media ($tui-mouse) {
|
||||
&:hover {
|
||||
background: var(--tui-background-neutral-1-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
width: 13rem;
|
||||
}
|
||||
|
||||
[tuiButton] {
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
|
||||
span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
tr {
|
||||
grid-template-columns: min-content 1fr 4rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
td {
|
||||
grid-column: span 2;
|
||||
|
||||
&:first-child {
|
||||
font-size: 0;
|
||||
width: auto;
|
||||
grid-area: 1 / 2;
|
||||
place-content: center;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
grid-area: 1 / 3 / 4 / 3;
|
||||
align-self: center;
|
||||
justify-self: end;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
color: var(--tui-text-primary);
|
||||
font: var(--tui-font-text-m);
|
||||
font-weight: bold;
|
||||
grid-column: 1;
|
||||
max-width: 12rem;
|
||||
}
|
||||
}
|
||||
`,
|
||||
host: { class: 'g-card' },
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
TuiButton,
|
||||
TuiCell,
|
||||
TuiIcon,
|
||||
TuiTitle,
|
||||
TuiTooltip,
|
||||
PlaceholderComponent,
|
||||
BackupStatusComponent,
|
||||
TableComponent,
|
||||
],
|
||||
})
|
||||
export class BackupNetworkComponent {
|
||||
|
||||
@@ -6,16 +6,10 @@ import {
|
||||
} from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { UnitConversionPipesModule } from '@start9labs/shared'
|
||||
import {
|
||||
TuiAlertService,
|
||||
TuiButton,
|
||||
TuiIcon,
|
||||
TuiNotification,
|
||||
TuiTitle,
|
||||
} from '@taiga-ui/core'
|
||||
import { TuiAlertService, TuiButton, TuiIcon } from '@taiga-ui/core'
|
||||
import { TuiTooltip } from '@taiga-ui/kit'
|
||||
import { TuiCell } from '@taiga-ui/layout'
|
||||
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||
import { DiskBackupTarget } from 'src/app/services/api/api.types'
|
||||
import { BackupService, MappedBackupTarget } from './backup.service'
|
||||
import { BackupStatusComponent } from './status.component'
|
||||
@@ -30,58 +24,97 @@ import { BackupStatusComponent } from './status.component'
|
||||
<ng-template #drives><ng-content /></ng-template>
|
||||
</header>
|
||||
|
||||
<tui-notification appearance="warning">
|
||||
Warning. Do not use this option if you are using a Raspberry Pi with an
|
||||
external SSD. The Raspberry Pi does not support more than one external
|
||||
drive without additional power and can cause data corruption.
|
||||
</tui-notification>
|
||||
|
||||
@for (target of service.drives(); track $index) {
|
||||
<button tuiCell (click)="select(target)">
|
||||
<tui-icon icon="@tui.save" />
|
||||
<span tuiTitle>
|
||||
<strong>{{ target.entry.label || target.entry.logicalname }}</strong>
|
||||
<span tuiSubtitle [backupStatus]="target.hasAnyBackup"></span>
|
||||
<span tuiSubtitle>
|
||||
<table [appTable]="['Status', 'Name', 'Model', 'Capacity']">
|
||||
@for (target of service.drives(); track $index) {
|
||||
<tr
|
||||
tabindex="0"
|
||||
(click)="select(target)"
|
||||
(keydown.enter)="select(target)"
|
||||
>
|
||||
<td><span [backupStatus]="target.hasAnyBackup"></span></td>
|
||||
<td class="name">
|
||||
{{ target.entry.label || target.entry.logicalname }}
|
||||
</td>
|
||||
<td>
|
||||
{{ target.entry.vendor || 'Unknown Vendor' }} -
|
||||
{{ target.entry.model || 'Unknown Model' }}
|
||||
</span>
|
||||
<span tuiSubtitle>
|
||||
<b>Capacity:</b>
|
||||
{{ target.entry.capacity | convertBytes }}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
} @empty {
|
||||
<app-placeholder icon="@tui.save-off">
|
||||
No drives detected
|
||||
<button
|
||||
tuiButton
|
||||
iconStart="@tui.refresh-cw"
|
||||
(click)="service.getBackupTargets()"
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
</app-placeholder>
|
||||
}
|
||||
</td>
|
||||
<td>{{ target.entry.capacity | convertBytes }}</td>
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<app-placeholder icon="@tui.save-off">
|
||||
No drives detected
|
||||
<button
|
||||
tuiButton
|
||||
iconStart="@tui.refresh-cw"
|
||||
(click)="service.getBackupTargets()"
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
</app-placeholder>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
`,
|
||||
styles: `
|
||||
tui-notification {
|
||||
margin: 0.5rem 0 0.75rem;
|
||||
@import '@taiga-ui/core/styles/taiga-ui-local';
|
||||
|
||||
tr {
|
||||
cursor: pointer;
|
||||
@include transition(background);
|
||||
|
||||
@media ($tui-mouse) {
|
||||
&:hover {
|
||||
background: var(--tui-background-neutral-1-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
width: 13rem;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
tr {
|
||||
max-width: 18rem;
|
||||
grid-template-columns: min-content 2rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
td {
|
||||
grid-column: span 2;
|
||||
|
||||
&:first-child {
|
||||
font-size: 0;
|
||||
width: auto;
|
||||
grid-area: 1 / 2;
|
||||
place-content: center;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
color: var(--tui-text-primary);
|
||||
font: var(--tui-font-text-m);
|
||||
font-weight: bold;
|
||||
grid-column: 1;
|
||||
max-width: 12rem;
|
||||
}
|
||||
}
|
||||
`,
|
||||
host: { class: 'g-card' },
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
TuiButton,
|
||||
TuiCell,
|
||||
TuiIcon,
|
||||
TuiTitle,
|
||||
TuiTooltip,
|
||||
TuiNotification,
|
||||
UnitConversionPipesModule,
|
||||
PlaceholderComponent,
|
||||
BackupStatusComponent,
|
||||
TableComponent,
|
||||
],
|
||||
})
|
||||
export class BackupPhysicalComponent {
|
||||
|
||||
@@ -26,12 +26,19 @@ import { TuiIcon } from '@taiga-ui/core'
|
||||
`,
|
||||
styles: `
|
||||
:host {
|
||||
color: var(--tui-text-primary);
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
tui-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
height: auto;
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiIcon],
|
||||
|
||||
@@ -3,8 +3,9 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { RouterLink } from '@angular/router'
|
||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||
import { IST, inputSpec } from '@start9labs/start-sdk'
|
||||
import { TuiButton, TuiDialogService } from '@taiga-ui/core'
|
||||
import { inputSpec, IST } from '@start9labs/start-sdk'
|
||||
import { TuiButton, TuiDialogService, TuiLink, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiHeader } from '@taiga-ui/layout'
|
||||
import { TuiInputModule } from '@taiga-ui/legacy'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { switchMap, tap } from 'rxjs'
|
||||
@@ -14,7 +15,6 @@ import { FormService } from 'src/app/services/form.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { TitleDirective } from 'src/app/services/title.service'
|
||||
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
||||
import { EmailInfoComponent } from './info.component'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
@@ -22,18 +22,38 @@ import { EmailInfoComponent } from './info.component'
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">Back</a>
|
||||
Email
|
||||
</ng-container>
|
||||
<email-info />
|
||||
<ng-container *ngIf="form$ | async as form">
|
||||
<form class="g-card" [formGroup]="form">
|
||||
<header>SMTP Credentials</header>
|
||||
<form-group
|
||||
*ngIf="spec | async as resolved"
|
||||
[spec]="resolved"
|
||||
></form-group>
|
||||
<header tuiHeader>
|
||||
<hgroup tuiTitle>
|
||||
<h3>Email</h3>
|
||||
<p tuiSubtitle>
|
||||
Connect to an external SMTP server for sending emails. Adding SMTP
|
||||
credentials enables StartOS and some services to send you emails.
|
||||
<a
|
||||
tuiLink
|
||||
href="https://docs.start9.com/latest/user-manual/smtp"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
appearance="action-grayscale"
|
||||
iconEnd="@tui.external-link"
|
||||
[pseudo]="true"
|
||||
[textContent]="'View instructions'"
|
||||
></a>
|
||||
</p>
|
||||
</hgroup>
|
||||
</header>
|
||||
@if (form$ | async; as form) {
|
||||
<form [formGroup]="form">
|
||||
<header tuiHeader="body-l">
|
||||
<h3 tuiTitle><b>SMTP Credentials</b></h3>
|
||||
</header>
|
||||
@if (spec | async; as resolved) {
|
||||
<form-group [spec]="resolved" />
|
||||
}
|
||||
<footer>
|
||||
@if (isSaved) {
|
||||
<button
|
||||
tuiButton
|
||||
size="l"
|
||||
appearance="secondary-destructive"
|
||||
(click)="save(null)"
|
||||
>
|
||||
@@ -42,6 +62,7 @@ import { EmailInfoComponent } from './info.component'
|
||||
}
|
||||
<button
|
||||
tuiButton
|
||||
size="l"
|
||||
[disabled]="form.invalid"
|
||||
(click)="save(form.value)"
|
||||
>
|
||||
@@ -49,19 +70,22 @@ import { EmailInfoComponent } from './info.component'
|
||||
</button>
|
||||
</footer>
|
||||
</form>
|
||||
<form class="g-card">
|
||||
<header>Send Test Email</header>
|
||||
<form>
|
||||
<header tuiHeader="body-l">
|
||||
<h3 tuiTitle><b>Send Test Email</b></h3>
|
||||
</header>
|
||||
<tui-input
|
||||
[(ngModel)]="testAddress"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
>
|
||||
Firstname Lastname <email@example.com>
|
||||
Name Lastname <email@example.com>
|
||||
<input tuiTextfieldLegacy inputmode="email" />
|
||||
</tui-input>
|
||||
<footer>
|
||||
<button
|
||||
tuiButton
|
||||
appearance="secondary"
|
||||
size="l"
|
||||
[disabled]="!testAddress || form.invalid"
|
||||
(click)="sendTestEmail(form.value)"
|
||||
>
|
||||
@@ -69,17 +93,22 @@ import { EmailInfoComponent } from './info.component'
|
||||
</button>
|
||||
</footer>
|
||||
</form>
|
||||
</ng-container>
|
||||
}
|
||||
`,
|
||||
styles: `
|
||||
:host {
|
||||
display: grid !important;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
align-items: start;
|
||||
max-width: 40rem;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
grid-template-columns: 1fr;
|
||||
form header,
|
||||
form footer {
|
||||
margin: 1rem 0;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
@@ -91,7 +120,9 @@ import { EmailInfoComponent } from './info.component'
|
||||
FormModule,
|
||||
TuiButton,
|
||||
TuiInputModule,
|
||||
EmailInfoComponent,
|
||||
TuiHeader,
|
||||
TuiTitle,
|
||||
TuiLink,
|
||||
RouterLink,
|
||||
TitleDirective,
|
||||
],
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { TuiLink, TuiNotification } from '@taiga-ui/core'
|
||||
|
||||
@Component({
|
||||
selector: 'email-info',
|
||||
template: `
|
||||
<tui-notification>
|
||||
Adding SMTP credentials to StartOS enables StartOS and some services to
|
||||
send you emails.
|
||||
<a
|
||||
tuiLink
|
||||
href="https://docs.start9.com/latest/user-manual/smtp"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
iconEnd="@tui.external-link"
|
||||
[textContent]="'View instructions'"
|
||||
></a>
|
||||
</tui-notification>
|
||||
`,
|
||||
styles: `
|
||||
:host {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
grid-column: 1;
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [TuiNotification, TuiLink],
|
||||
})
|
||||
export class EmailInfoComponent {}
|
||||
@@ -2,7 +2,8 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { RouterLink } from '@angular/router'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
import { TuiButton } from '@taiga-ui/core'
|
||||
import { TuiButton, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiHeader } from '@taiga-ui/layout'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { map } from 'rxjs'
|
||||
import { InterfaceComponent } from 'src/app/routes/portal/components/interfaces/interface.component'
|
||||
@@ -34,13 +35,29 @@ const iface: T.ServiceInterface = {
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">Back</a>
|
||||
Web Addresses
|
||||
</ng-container>
|
||||
<header tuiHeader>
|
||||
<hgroup tuiTitle>
|
||||
<h3>User Interface Addresses</h3>
|
||||
<p tuiSubtitle>
|
||||
View and manage private and public addresses for accessing your
|
||||
StartOS UI
|
||||
</p>
|
||||
</hgroup>
|
||||
</header>
|
||||
@if (ui(); as ui) {
|
||||
<app-interface [serviceInterface]="ui" />
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [InterfaceComponent, RouterLink, TuiButton, TitleDirective],
|
||||
imports: [
|
||||
InterfaceComponent,
|
||||
RouterLink,
|
||||
TuiButton,
|
||||
TitleDirective,
|
||||
TuiHeader,
|
||||
TuiTitle,
|
||||
],
|
||||
})
|
||||
export default class StartOsUiComponent {
|
||||
private readonly config = inject(ConfigService)
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
TuiNotification,
|
||||
TuiTitle,
|
||||
} from '@taiga-ui/core'
|
||||
import { TuiHeader } from '@taiga-ui/layout'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { from } from 'rxjs'
|
||||
import { FormComponent } from 'src/app/routes/portal/components/form.component'
|
||||
@@ -23,22 +24,23 @@ import { getServerInfo } from 'src/app/utils/get-server-info'
|
||||
template: `
|
||||
<ng-container *title>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">Back</a>
|
||||
Password Reset
|
||||
Change Password
|
||||
</ng-container>
|
||||
<tui-notification appearance="warning">
|
||||
<div tuiTitle>
|
||||
<strong>Warning</strong>
|
||||
<div tuiSubtitle>
|
||||
You will still need your current password to decrypt existing backups!
|
||||
</div>
|
||||
</div>
|
||||
</tui-notification>
|
||||
<section class="g-card">
|
||||
<header>Change Master Password</header>
|
||||
@if (spec(); as spec) {
|
||||
<app-form [spec]="spec" [buttons]="buttons" />
|
||||
}
|
||||
</section>
|
||||
<header tuiHeader>
|
||||
<hgroup tuiTitle>
|
||||
<h3>Change Password</h3>
|
||||
<p tuiSubtitle>
|
||||
Change your StartOS master password.
|
||||
<strong>
|
||||
You will still need your current password to decrypt existing
|
||||
backups!
|
||||
</strong>
|
||||
</p>
|
||||
</hgroup>
|
||||
</header>
|
||||
@if (spec(); as spec) {
|
||||
<app-form [spec]="spec" [buttons]="buttons" />
|
||||
}
|
||||
`,
|
||||
styles: `
|
||||
:host {
|
||||
@@ -46,6 +48,7 @@ import { getServerInfo } from 'src/app/utils/get-server-info'
|
||||
|
||||
::ng-deep footer {
|
||||
background: transparent !important;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,11 +59,11 @@ import { getServerInfo } from 'src/app/utils/get-server-info'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [
|
||||
TuiNotification,
|
||||
TuiTitle,
|
||||
FormComponent,
|
||||
RouterLink,
|
||||
TuiHeader,
|
||||
TuiTitle,
|
||||
TuiButton,
|
||||
FormComponent,
|
||||
TitleDirective,
|
||||
],
|
||||
})
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { RouterLink } from '@angular/router'
|
||||
import { TuiTable } from '@taiga-ui/addon-table'
|
||||
import { TuiLet } from '@taiga-ui/cdk'
|
||||
import { TuiButton } from '@taiga-ui/core'
|
||||
import { TuiButton, TuiTitle } from '@taiga-ui/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||
import { TuiHeader } from '@taiga-ui/layout'
|
||||
import { from, map, merge, Observable, Subject } from 'rxjs'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { Session } from 'src/app/services/api/api.types'
|
||||
@@ -17,14 +18,18 @@ import { SSHTableComponent } from './table.component'
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">Back</a>
|
||||
Active Sessions
|
||||
</ng-container>
|
||||
<header tuiHeader>
|
||||
<hgroup tuiTitle>
|
||||
<h3>Active Sessions</h3>
|
||||
<p tuiSubtitle>
|
||||
A session is a device that is currently logged into StartOS. For best
|
||||
security, terminate sessions you do not recognize or no longer use.
|
||||
</p>
|
||||
</hgroup>
|
||||
</header>
|
||||
<section class="g-card">
|
||||
<header>Current session</header>
|
||||
<table
|
||||
tuiTable
|
||||
class="g-table"
|
||||
[single]="true"
|
||||
[sessions]="current$ | async"
|
||||
></table>
|
||||
<div [single]="true" [sessions]="current$ | async"></div>
|
||||
</section>
|
||||
|
||||
<section *tuiLet="other$ | async as others" class="g-card">
|
||||
@@ -43,7 +48,7 @@ import { SSHTableComponent } from './table.component'
|
||||
</button>
|
||||
}
|
||||
</header>
|
||||
<table #table tuiTable class="g-table" [sessions]="others"></table>
|
||||
<div #table [sessions]="others"></div>
|
||||
</section>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
@@ -56,6 +61,8 @@ import { SSHTableComponent } from './table.component'
|
||||
RouterLink,
|
||||
TitleDirective,
|
||||
TuiTable,
|
||||
TuiHeader,
|
||||
TuiTitle,
|
||||
],
|
||||
})
|
||||
export default class SystemSessionsComponent {
|
||||
|
||||
@@ -6,53 +6,32 @@ import {
|
||||
OnChanges,
|
||||
} from '@angular/core'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { TuiTable } from '@taiga-ui/addon-table'
|
||||
import { TuiIcon } from '@taiga-ui/core'
|
||||
import { TuiCheckbox, TuiFade, TuiSkeleton } from '@taiga-ui/kit'
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||
import { Session } from 'src/app/services/api/api.types'
|
||||
import { PlatformInfoPipe } from './platform-info.pipe'
|
||||
|
||||
@Component({
|
||||
selector: 'table[sessions]',
|
||||
selector: '[sessions]',
|
||||
template: `
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
tuiTh
|
||||
[style.width.%]="50"
|
||||
[style.padding-left.rem]="single ? null : 2"
|
||||
>
|
||||
@if (!single) {
|
||||
<input
|
||||
tuiCheckbox
|
||||
size="s"
|
||||
type="checkbox"
|
||||
[disabled]="!sessions?.length"
|
||||
[ngModel]="all"
|
||||
(ngModelChange)="onAll($event)"
|
||||
/>
|
||||
}
|
||||
User Agent
|
||||
</th>
|
||||
<th tuiTh [style.width.%]="25">Platform</th>
|
||||
<th tuiTh [style.width.%]="25">Last Active</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<table [appTable]="['User Agent', 'Platform', 'Last Active']">
|
||||
@for (session of sessions; track $index) {
|
||||
<tr>
|
||||
<td [style.padding-left.rem]="single ? null : 2.25">
|
||||
@if (!single) {
|
||||
<input
|
||||
tuiCheckbox
|
||||
size="s"
|
||||
type="checkbox"
|
||||
[ngModel]="selected$.value.includes(session)"
|
||||
(ngModelChange)="onToggle(session)"
|
||||
/>
|
||||
}
|
||||
<span tuiFade class="agent">{{ session.userAgent }}</span>
|
||||
<td [style.padding-left.rem]="single ? null : 2.5">
|
||||
<label>
|
||||
@if (!single) {
|
||||
<input
|
||||
tuiCheckbox
|
||||
size="s"
|
||||
type="checkbox"
|
||||
[ngModel]="selected$.value.includes(session)"
|
||||
(ngModelChange)="onToggle(session)"
|
||||
/>
|
||||
}
|
||||
<span tuiFade class="agent">{{ session.userAgent }}</span>
|
||||
</label>
|
||||
</td>
|
||||
@if (session.metadata.platforms | platformInfo; as info) {
|
||||
<td class="platform">
|
||||
@@ -64,16 +43,16 @@ import { PlatformInfoPipe } from './platform-info.pipe'
|
||||
</tr>
|
||||
} @empty {
|
||||
@if (sessions) {
|
||||
<tr><td colspan="5">No sessions</td></tr>
|
||||
<tr><td colspan="3">No sessions</td></tr>
|
||||
} @else {
|
||||
@for (item of single ? [''] : ['', '']; track $index) {
|
||||
<tr>
|
||||
<td colspan="5"><div [tuiSkeleton]="true">Loading</div></td>
|
||||
<td colspan="3"><div [tuiSkeleton]="true">Loading</div></td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
`,
|
||||
styles: [
|
||||
`
|
||||
@@ -81,16 +60,37 @@ import { PlatformInfoPipe } from './platform-info.pipe'
|
||||
|
||||
td {
|
||||
position: relative;
|
||||
width: 25%;
|
||||
|
||||
&[colspan] {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0.5rem;
|
||||
left: 0.75rem;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.platform {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
tr {
|
||||
grid-template-columns: 2.5rem 1fr;
|
||||
|
||||
&:has(:checked) .platform {
|
||||
color: var(--tui-text-action);
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
@include fullsize();
|
||||
z-index: 1;
|
||||
@@ -98,8 +98,12 @@ import { PlatformInfoPipe } from './platform-info.pipe'
|
||||
transform: none;
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
padding: 0 0.25rem !important;
|
||||
td {
|
||||
width: 100%;
|
||||
|
||||
&:first-child {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.agent {
|
||||
@@ -108,11 +112,9 @@ import { PlatformInfoPipe } from './platform-info.pipe'
|
||||
}
|
||||
|
||||
.platform {
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0;
|
||||
font-size: 0;
|
||||
grid-area: 1 / 1 / 3 / 1;
|
||||
place-content: center;
|
||||
}
|
||||
|
||||
.date {
|
||||
@@ -131,7 +133,7 @@ import { PlatformInfoPipe } from './platform-info.pipe'
|
||||
TuiCheckbox,
|
||||
TuiFade,
|
||||
TuiSkeleton,
|
||||
TuiTable,
|
||||
TableComponent,
|
||||
],
|
||||
})
|
||||
export class SSHTableComponent<T extends Session> implements OnChanges {
|
||||
@@ -143,26 +145,10 @@ export class SSHTableComponent<T extends Session> implements OnChanges {
|
||||
@Input()
|
||||
single = false
|
||||
|
||||
get all(): boolean | null {
|
||||
if (!this.sessions?.length || !this.selected$.value.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.sessions?.length === this.selected$.value.length) {
|
||||
return true
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this.selected$.next([])
|
||||
}
|
||||
|
||||
onAll(selected: boolean) {
|
||||
this.selected$.next((selected && this.sessions) || [])
|
||||
}
|
||||
|
||||
onToggle(session: T) {
|
||||
const selected = this.selected$.value
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { TuiLink, TuiNotification } from '@taiga-ui/core'
|
||||
|
||||
@Component({
|
||||
selector: 'ssh-info',
|
||||
template: `
|
||||
<tui-notification>
|
||||
Adding domains to StartOS enables you to access your server and service
|
||||
interfaces over clearnet.
|
||||
<a
|
||||
tuiLink
|
||||
href="https://docs.start9.com/0.3.5.x/user-manual/ssh"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
View instructions
|
||||
</a>
|
||||
</tui-notification>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [TuiNotification, TuiLink],
|
||||
})
|
||||
export class SSHInfoComponent {}
|
||||
@@ -1,13 +1,13 @@
|
||||
import { RouterLink } from '@angular/router'
|
||||
import { TuiTable } from '@taiga-ui/addon-table'
|
||||
import { TuiButton } from '@taiga-ui/core'
|
||||
import { TuiButton, TuiLink, TuiTitle } from '@taiga-ui/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { ErrorService } from '@start9labs/shared'
|
||||
import { TuiHeader } from '@taiga-ui/layout'
|
||||
import { catchError, defer, of } from 'rxjs'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { TitleDirective } from 'src/app/services/title.service'
|
||||
import { SSHInfoComponent } from './info.component'
|
||||
import { SSHTableComponent } from './table.component'
|
||||
|
||||
@Component({
|
||||
@@ -16,7 +16,24 @@ import { SSHTableComponent } from './table.component'
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">Back</a>
|
||||
SSH
|
||||
</ng-container>
|
||||
<ssh-info />
|
||||
<header tuiHeader>
|
||||
<hgroup tuiTitle>
|
||||
<h3>SSH</h3>
|
||||
<p tuiSubtitle>
|
||||
Manage your SSH keys to access your server from the command line
|
||||
<a
|
||||
tuiLink
|
||||
href="https://docs.start9.com/latest/user-manual/ssh"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
appearance="action-grayscale"
|
||||
iconEnd="@tui.external-link"
|
||||
[pseudo]="true"
|
||||
[textContent]="'View instructions'"
|
||||
></a>
|
||||
</p>
|
||||
</hgroup>
|
||||
</header>
|
||||
<section class="g-card">
|
||||
<header>
|
||||
Saved Keys
|
||||
@@ -30,7 +47,7 @@ import { SSHTableComponent } from './table.component'
|
||||
Add Key
|
||||
</button>
|
||||
</header>
|
||||
<table #table tuiTable class="g-table" [keys]="keys$ | async"></table>
|
||||
<div #table [keys]="keys$ | async"></div>
|
||||
</section>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
@@ -39,10 +56,12 @@ import { SSHTableComponent } from './table.component'
|
||||
CommonModule,
|
||||
TuiButton,
|
||||
SSHTableComponent,
|
||||
SSHInfoComponent,
|
||||
RouterLink,
|
||||
TitleDirective,
|
||||
TuiTable,
|
||||
TuiHeader,
|
||||
TuiTitle,
|
||||
TuiLink,
|
||||
],
|
||||
})
|
||||
export default class SystemSSHComponent {
|
||||
|
||||
@@ -7,32 +7,25 @@ import {
|
||||
Input,
|
||||
} from '@angular/core'
|
||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||
import { TuiTable } from '@taiga-ui/addon-table'
|
||||
import { TuiDialogOptions, TuiDialogService, TuiButton } from '@taiga-ui/core'
|
||||
import { TuiButton, TuiDialogOptions, TuiDialogService } from '@taiga-ui/core'
|
||||
import {
|
||||
TUI_CONFIRM,
|
||||
TuiConfirmData,
|
||||
TuiFade,
|
||||
TUI_CONFIRM,
|
||||
TuiSkeleton,
|
||||
} from '@taiga-ui/kit'
|
||||
import { filter, take } from 'rxjs'
|
||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||
import { PROMPT } from 'src/app/routes/portal/modals/prompt.component'
|
||||
import { SSHKey } from 'src/app/services/api/api.types'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
|
||||
@Component({
|
||||
selector: 'table[keys]',
|
||||
selector: '[keys]',
|
||||
template: `
|
||||
<thead>
|
||||
<tr>
|
||||
<th tuiTh>Hostname</th>
|
||||
<th tuiTh>Created At</th>
|
||||
<th tuiTh>Algorithm</th>
|
||||
<th tuiTh>Fingerprint</th>
|
||||
<th tuiTh></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<table
|
||||
[appTable]="['Hostname', 'Created At', 'Algorithm', 'Fingerprint', '']"
|
||||
>
|
||||
@for (key of keys; track $index) {
|
||||
<tr>
|
||||
<td class="title">{{ key.hostname }}</td>
|
||||
@@ -62,7 +55,7 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
}
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
`,
|
||||
styles: `
|
||||
:host-context(tui-root._mobile) {
|
||||
@@ -109,7 +102,7 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
`,
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [CommonModule, TuiButton, TuiFade, TuiSkeleton, TuiTable],
|
||||
imports: [CommonModule, TuiButton, TuiFade, TuiSkeleton, TableComponent],
|
||||
})
|
||||
export class SSHTableComponent {
|
||||
private readonly loader = inject(LoadingService)
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { TuiLink, TuiNotification } from '@taiga-ui/core'
|
||||
|
||||
@Component({
|
||||
selector: 'wifi-info',
|
||||
template: `
|
||||
<tui-notification>
|
||||
Adding WiFi credentials to StartOS allows you to remove the Ethernet cable
|
||||
and move the device anywhere you want. StartOS will automatically connect
|
||||
to available networks.
|
||||
<a
|
||||
tuiLink
|
||||
href="https://docs.start9.com/latest/user-manual/wifi"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
iconEnd="@tui.external-link"
|
||||
[textContent]="'View instructions'"
|
||||
></a>
|
||||
</tui-notification>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [TuiNotification, TuiLink],
|
||||
})
|
||||
export class WifiInfoComponent {}
|
||||
@@ -24,27 +24,26 @@ import SystemWifiComponent from './wifi.component'
|
||||
template: `
|
||||
@for (network of wifi; track $index) {
|
||||
@if (network.ssid) {
|
||||
<div tuiCell [style.padding]="0">
|
||||
<button
|
||||
tuiCell
|
||||
[disabled]="network.connected"
|
||||
(click)="prompt(network)"
|
||||
>
|
||||
<div tuiTitle>
|
||||
<strong tuiFade>
|
||||
{{ network.ssid }}
|
||||
@if (network.connected) {
|
||||
<tui-badge appearance="success">Connected</tui-badge>
|
||||
<tui-badge appearance="positive">Connected</tui-badge>
|
||||
}
|
||||
</strong>
|
||||
</div>
|
||||
@if (!network.connected) {
|
||||
<button tuiButton size="xs" (click)="prompt(network)">
|
||||
Connect
|
||||
</button>
|
||||
}
|
||||
@if (network.connected !== undefined) {
|
||||
<button
|
||||
tuiIconButton
|
||||
size="s"
|
||||
appearance="icon"
|
||||
iconStart="@tui.trash-2"
|
||||
(click)="forget(network)"
|
||||
(click.stop)="forget(network)"
|
||||
>
|
||||
Forget
|
||||
</button>
|
||||
@@ -63,14 +62,22 @@ import SystemWifiComponent from './wifi.component'
|
||||
} @else {
|
||||
<tui-icon icon="@tui.wifi-off" />
|
||||
}
|
||||
</div>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
`,
|
||||
styles: `
|
||||
:host {
|
||||
align-items: stretch;
|
||||
white-space: nowrap;
|
||||
padding: 0.5rem !important;
|
||||
}
|
||||
|
||||
[tuiCell] {
|
||||
padding-inline: 1rem !important;
|
||||
|
||||
&:disabled > * {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
tui-icon {
|
||||
|
||||
@@ -13,10 +13,12 @@ import {
|
||||
TuiAppearance,
|
||||
TuiButton,
|
||||
TuiDialogOptions,
|
||||
TuiLink,
|
||||
TuiLoader,
|
||||
TuiTitle,
|
||||
} from '@taiga-ui/core'
|
||||
import { TuiSwitch } from '@taiga-ui/kit'
|
||||
import { TuiCardLarge } from '@taiga-ui/layout'
|
||||
import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { catchError, defer, map, merge, Observable, of, Subject } from 'rxjs'
|
||||
import {
|
||||
@@ -28,7 +30,6 @@ 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 { TitleDirective } from 'src/app/services/title.service'
|
||||
import { WifiInfoComponent } from './info.component'
|
||||
import { WifiTableComponent } from './table.component'
|
||||
import { parseWifi, WifiData, WiFiForm } from './utils'
|
||||
import { wifiSpec } from './wifi.const'
|
||||
@@ -39,7 +40,26 @@ import { wifiSpec } from './wifi.const'
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">Back</a>
|
||||
WiFi
|
||||
</ng-container>
|
||||
<wifi-info />
|
||||
<header tuiHeader>
|
||||
<hgroup tuiTitle>
|
||||
<h3>WiFi</h3>
|
||||
<p tuiSubtitle>
|
||||
Adding WiFi credentials to StartOS allows you to remove the Ethernet
|
||||
cable and move the device anywhere you want. StartOS will
|
||||
automatically connect to available networks.
|
||||
<a
|
||||
tuiLink
|
||||
href="https://docs.start9.com/latest/user-manual/wifi"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
appearance="action-grayscale"
|
||||
iconEnd="@tui.external-link"
|
||||
[pseudo]="true"
|
||||
[textContent]="'View instructions'"
|
||||
></a>
|
||||
</p>
|
||||
</hgroup>
|
||||
</header>
|
||||
@if (status()?.interface) {
|
||||
<section class="g-card">
|
||||
<header>
|
||||
@@ -60,7 +80,6 @@ import { wifiSpec } from './wifi.const'
|
||||
tuiCardLarge="compact"
|
||||
tuiAppearance="neutral"
|
||||
[wifi]="data.known"
|
||||
[style.padding-block.rem]="0.5"
|
||||
></div>
|
||||
}
|
||||
@if (data.available.length) {
|
||||
@@ -69,11 +88,10 @@ import { wifiSpec } from './wifi.const'
|
||||
tuiCardLarge="compact"
|
||||
tuiAppearance="neutral"
|
||||
[wifi]="data.available"
|
||||
[style.padding-block.rem]="0.5"
|
||||
></div>
|
||||
}
|
||||
<p>
|
||||
<button tuiButton size="s" (click)="other(data)">Add</button>
|
||||
<button tuiButton (click)="other(data)">Add</button>
|
||||
</p>
|
||||
} @else {
|
||||
<tui-loader [style.height.rem]="5" />
|
||||
@@ -88,6 +106,11 @@ import { wifiSpec } from './wifi.const'
|
||||
</app-placeholder>
|
||||
}
|
||||
`,
|
||||
styles: `
|
||||
:host {
|
||||
max-width: 40rem;
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [
|
||||
@@ -97,11 +120,13 @@ import { wifiSpec } from './wifi.const'
|
||||
TuiCardLarge,
|
||||
TuiLoader,
|
||||
TuiAppearance,
|
||||
WifiInfoComponent,
|
||||
WifiTableComponent,
|
||||
TitleDirective,
|
||||
RouterLink,
|
||||
PlaceholderComponent,
|
||||
TuiHeader,
|
||||
TuiTitle,
|
||||
TuiLink,
|
||||
],
|
||||
})
|
||||
export default class SystemWifiComponent {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { SYSTEM_MENU } from './system.const'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<span *title>{{ 'system.outlet.general' | i18n }}</span>
|
||||
<span *title>{{ 'system.outlet.system' | i18n }}</span>
|
||||
<aside class="g-aside">
|
||||
@for (cat of menu; track $index) {
|
||||
@if ($index) {
|
||||
@@ -45,6 +45,10 @@ import { SYSTEM_MENU } from './system.const'
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
tui-badge-notification {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[tuiCell] {
|
||||
color: var(--tui-text-secondary);
|
||||
|
||||
@@ -94,6 +98,10 @@ import { SYSTEM_MENU } from './system.const'
|
||||
hr {
|
||||
background: var(--tui-border-normal);
|
||||
}
|
||||
|
||||
::ng-deep hgroup h3 {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`,
|
||||
],
|
||||
|
||||
@@ -16,6 +16,7 @@ export const SYSTEM_UTILITIES: Record<string, { icon: string; title: string }> =
|
||||
icon: '@tui.upload',
|
||||
title: 'Sideload',
|
||||
},
|
||||
// @TODO 040
|
||||
// '/portal/updates': {
|
||||
// icon: '@tui.globe',
|
||||
// title: 'Updates',
|
||||
|
||||
@@ -121,6 +121,14 @@ hr {
|
||||
padding-top: 4rem;
|
||||
}
|
||||
|
||||
tui-root:not(._mobile) &:has(.g-table:not([tuiTable])) {
|
||||
padding-block-end: 1rem;
|
||||
|
||||
> header {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
[tuiCell] {
|
||||
margin: 0 -0.625rem;
|
||||
border-radius: var(--tui-radius-s);
|
||||
@@ -179,7 +187,6 @@ hr {
|
||||
th {
|
||||
position: relative;
|
||||
font: var(--tui-font-text-s);
|
||||
height: 2rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid var(--tui-background-neutral-1);
|
||||
border-left: 0;
|
||||
@@ -193,6 +200,11 @@ hr {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
tr:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: inset 0 0 0 0.125rem var(--tui-border-focus);
|
||||
}
|
||||
|
||||
tui-root._mobile & {
|
||||
min-width: 0;
|
||||
border: none;
|
||||
@@ -215,10 +227,6 @@ hr {
|
||||
}
|
||||
}
|
||||
|
||||
tr:has(:checked) {
|
||||
box-shadow: inset 0 0 0 0.125rem var(--tui-background-accent-1);
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
position: static;
|
||||
|
||||
Reference in New Issue
Block a user