mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
feat: implement mobile header (#2559)
* feat: implement mobile header * chore: remove remaining ties to old ui project * chore: remove ionic from login page * chore: address comments
This commit is contained in:
195
web/package-lock.json
generated
195
web/package-lock.json
generated
@@ -24,14 +24,15 @@
|
|||||||
"@start9labs/argon2": "^0.1.0",
|
"@start9labs/argon2": "^0.1.0",
|
||||||
"@start9labs/emver": "^0.1.5",
|
"@start9labs/emver": "^0.1.5",
|
||||||
"@start9labs/start-sdk": "0.4.0-rev0.lib0.rc8.beta2",
|
"@start9labs/start-sdk": "0.4.0-rev0.lib0.rc8.beta2",
|
||||||
"@taiga-ui/addon-charts": "3.57.0",
|
"@taiga-ui/addon-charts": "^3.65.0",
|
||||||
"@taiga-ui/addon-mobile": "3.57.0",
|
"@taiga-ui/addon-commerce": "^3.65.0",
|
||||||
"@taiga-ui/cdk": "3.57.0",
|
"@taiga-ui/addon-mobile": "^3.65.0",
|
||||||
"@taiga-ui/core": "3.57.0",
|
"@taiga-ui/cdk": "^3.65.0",
|
||||||
"@taiga-ui/experimental": "3.57.0",
|
"@taiga-ui/core": "^3.65.0",
|
||||||
"@taiga-ui/icons": "3.57.0",
|
"@taiga-ui/experimental": "^3.65.0",
|
||||||
"@taiga-ui/kit": "3.57.0",
|
"@taiga-ui/icons": "^3.65.0",
|
||||||
"@taiga-ui/styles": "3.57.0",
|
"@taiga-ui/kit": "^3.65.0",
|
||||||
|
"@taiga-ui/styles": "^3.65.0",
|
||||||
"@tinkoff/ng-dompurify": "4.0.0",
|
"@tinkoff/ng-dompurify": "4.0.0",
|
||||||
"@tinkoff/ng-event-plugins": "3.1.0",
|
"@tinkoff/ng-event-plugins": "3.1.0",
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
@@ -3983,9 +3984,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ng-web-apis/intersection-observer": {
|
"node_modules/@ng-web-apis/intersection-observer": {
|
||||||
"version": "3.1.6",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ng-web-apis/intersection-observer/-/intersection-observer-3.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@ng-web-apis/intersection-observer/-/intersection-observer-3.2.0.tgz",
|
||||||
"integrity": "sha512-Pzk0ycnYpq+EUf60kz+/A7nvCmhYzThc4ArwONwZzJqRF5xOS97CVWObs8hesorXxQdqlsrDNiu+JWuGxEvpzQ==",
|
"integrity": "sha512-EhwqEZJFKR9pz55TWp82qyWTXdg8TZeMP6bUw26bVHz8CTkgrpzaXdtxurMTvJ/+gwuFy4JSJLjBeV9nfZ/SXA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.2.0"
|
"tslib": "^2.2.0"
|
||||||
},
|
},
|
||||||
@@ -3995,9 +3996,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ng-web-apis/mutation-observer": {
|
"node_modules/@ng-web-apis/mutation-observer": {
|
||||||
"version": "3.0.6",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ng-web-apis/mutation-observer/-/mutation-observer-3.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@ng-web-apis/mutation-observer/-/mutation-observer-3.1.0.tgz",
|
||||||
"integrity": "sha512-UW1qoUi2whH0uWkVz5qpdYCLs1u2T0E0QoCMQKZfLEkBpsWRTkT0PTCa9WWX/BhehaSPg23nZZm8BEixd6PI9w==",
|
"integrity": "sha512-MFN0TLLBMFJJPpXkGFe9ChRCSOKvMHZRRtBq5jHWS7tv5/CtdUkqW5CU7RC9KTzZjGeMzYe0cXO4JRkjL5aZ9g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.2.0"
|
"tslib": "^2.2.0"
|
||||||
},
|
},
|
||||||
@@ -4671,9 +4672,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/addon-charts": {
|
"node_modules/@taiga-ui/addon-charts": {
|
||||||
"version": "3.57.0",
|
"version": "3.65.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.65.0.tgz",
|
||||||
"integrity": "sha512-/x/yVHafSmNA3GSR9cSo6KSkzDNzDGew0JCD5cCNOb6vRZOe1QiXUQwMHvdj9eZlrjZzbV67eiV13NNvWolGwg==",
|
"integrity": "sha512-HNKUeK0ippIvLRF6wsuCiyJ4d98K4uIhkGwK1fWaTVOCN26Z+AnFKk9AryTyhocEZctyc4PMpJ7BP7h3CA4dZA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.6.2"
|
"tslib": "2.6.2"
|
||||||
},
|
},
|
||||||
@@ -4681,16 +4682,15 @@
|
|||||||
"@angular/common": ">=12.0.0",
|
"@angular/common": ">=12.0.0",
|
||||||
"@angular/core": ">=12.0.0",
|
"@angular/core": ">=12.0.0",
|
||||||
"@ng-web-apis/common": "3.0.6",
|
"@ng-web-apis/common": "3.0.6",
|
||||||
"@taiga-ui/cdk": ">=3.57.0",
|
"@taiga-ui/cdk": "^3.65.0",
|
||||||
"@taiga-ui/core": ">=3.57.0",
|
"@taiga-ui/core": "^3.65.0",
|
||||||
"@tinkoff/ng-polymorpheus": "4.3.0"
|
"@tinkoff/ng-polymorpheus": "4.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/addon-commerce": {
|
"node_modules/@taiga-ui/addon-commerce": {
|
||||||
"version": "3.57.0",
|
"version": "3.65.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-3.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-3.65.0.tgz",
|
||||||
"integrity": "sha512-9msjwr/8tzlmbcv6MDxZTYvW9+sbURAq2Olrs4zJhHvw69tA3XYt2MCrDYUSrej/oj5jE2GFjtyio7wqQviLzA==",
|
"integrity": "sha512-D98M3nkPKVFz9TFiMxCmMtmJs9vDc69RlPv5M03ZF+qXHqbthfpVss/p2MSzs4Cr2vgoECaZWPLNcWBOO5mzCw==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.6.2"
|
"tslib": "2.6.2"
|
||||||
},
|
},
|
||||||
@@ -4702,18 +4702,18 @@
|
|||||||
"@maskito/core": "1.9.0",
|
"@maskito/core": "1.9.0",
|
||||||
"@maskito/kit": "1.9.0",
|
"@maskito/kit": "1.9.0",
|
||||||
"@ng-web-apis/common": "3.0.6",
|
"@ng-web-apis/common": "3.0.6",
|
||||||
"@taiga-ui/cdk": ">=3.57.0",
|
"@taiga-ui/cdk": "^3.65.0",
|
||||||
"@taiga-ui/core": ">=3.57.0",
|
"@taiga-ui/core": "^3.65.0",
|
||||||
"@taiga-ui/i18n": ">=3.57.0",
|
"@taiga-ui/i18n": "^3.65.0",
|
||||||
"@taiga-ui/kit": ">=3.57.0",
|
"@taiga-ui/kit": "^3.65.0",
|
||||||
"@tinkoff/ng-polymorpheus": "4.3.0",
|
"@tinkoff/ng-polymorpheus": "4.3.0",
|
||||||
"rxjs": ">=6.0.0"
|
"rxjs": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/addon-mobile": {
|
"node_modules/@taiga-ui/addon-mobile": {
|
||||||
"version": "3.57.0",
|
"version": "3.65.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-3.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-3.65.0.tgz",
|
||||||
"integrity": "sha512-fxtmOqf8qcWbDeKKHukP+Iw0Ida8onKlg0L4UkeqF5AHT1f9QnpDtIQxyEUw4DXwUQdFhMT+64FWeA4acwa+kA==",
|
"integrity": "sha512-nKEf5Lb7yfR7vqkAIQQLoUEzSpKftdPpAsmco6FNfN4FDlvDFYTKE8MqqXAxzEqrXviDXv8/CKPv+nc6xd4VXg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.6.2"
|
"tslib": "2.6.2"
|
||||||
},
|
},
|
||||||
@@ -4722,27 +4722,27 @@
|
|||||||
"@angular/common": ">=12.0.0",
|
"@angular/common": ">=12.0.0",
|
||||||
"@angular/core": ">=12.0.0",
|
"@angular/core": ">=12.0.0",
|
||||||
"@ng-web-apis/common": "3.0.6",
|
"@ng-web-apis/common": "3.0.6",
|
||||||
"@taiga-ui/cdk": ">=3.57.0",
|
"@taiga-ui/cdk": "^3.65.0",
|
||||||
"@taiga-ui/core": ">=3.57.0",
|
"@taiga-ui/core": "^3.65.0",
|
||||||
"@taiga-ui/kit": ">=3.57.0",
|
"@taiga-ui/kit": "^3.65.0",
|
||||||
"@tinkoff/ng-polymorpheus": "4.3.0",
|
"@tinkoff/ng-polymorpheus": "4.3.0",
|
||||||
"rxjs": ">=6.0.0"
|
"rxjs": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/cdk": {
|
"node_modules/@taiga-ui/cdk": {
|
||||||
"version": "3.57.0",
|
"version": "3.65.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.65.0.tgz",
|
||||||
"integrity": "sha512-igfgPZh7sUaElX4dehDPkbPL66LFc6qmirfEQ6f2deNnezYm4EZTTIdHebU1ibiKTqWBxWVTHKC2pQ3nxhwkNA==",
|
"integrity": "sha512-hiFC9RlRng7pUv84YPZbqieKIYsFEzsMKCjMIckHBASBBU6qQ4OY6irKszFvTGqMe9KJgBh6sJU1hkQOBwFSaA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ng-web-apis/common": "3.0.6",
|
"@ng-web-apis/common": "3.0.6",
|
||||||
"@ng-web-apis/mutation-observer": "3.0.6",
|
"@ng-web-apis/mutation-observer": "3.1.0",
|
||||||
"@ng-web-apis/resize-observer": "3.0.6",
|
"@ng-web-apis/resize-observer": "3.0.6",
|
||||||
"@tinkoff/ng-event-plugins": "3.1.0",
|
"@tinkoff/ng-event-plugins": "3.1.0",
|
||||||
"@tinkoff/ng-polymorpheus": "4.3.0",
|
"@tinkoff/ng-polymorpheus": "4.3.0",
|
||||||
"tslib": "2.6.2"
|
"tslib": "2.6.2"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"ng-morph": "4.0.3",
|
"ng-morph": "4.0.5",
|
||||||
"parse5": "6.0.1"
|
"parse5": "6.0.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -4760,11 +4760,11 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/core": {
|
"node_modules/@taiga-ui/core": {
|
||||||
"version": "3.57.0",
|
"version": "3.65.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.65.0.tgz",
|
||||||
"integrity": "sha512-RUO//9C9+CdEPX1nV0hKRhKu3vmuAx+miszZHv5LAITK3jpTJKpd2jQ2Ib2z5O20v5vKrwkJ0DawT7EWU2v2TA==",
|
"integrity": "sha512-zNctTTsrW73fhmYirWE/mZs32UUvv6gV5CoIFm0BzVos0X7ZkN+x7PLXd9R+3CEgL6Kv/OxY92p+pJRvqc5jHg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@taiga-ui/i18n": "^3.57.0",
|
"@taiga-ui/i18n": "^3.65.0",
|
||||||
"tslib": "2.6.2"
|
"tslib": "2.6.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -4775,36 +4775,36 @@
|
|||||||
"@angular/platform-browser": ">=12.0.0",
|
"@angular/platform-browser": ">=12.0.0",
|
||||||
"@angular/router": ">=12.0.0",
|
"@angular/router": ">=12.0.0",
|
||||||
"@ng-web-apis/common": "3.0.6",
|
"@ng-web-apis/common": "3.0.6",
|
||||||
"@ng-web-apis/mutation-observer": "3.0.6",
|
"@ng-web-apis/mutation-observer": "3.1.0",
|
||||||
"@taiga-ui/cdk": ">=3.57.0",
|
"@taiga-ui/cdk": "^3.65.0",
|
||||||
"@taiga-ui/i18n": ">=3.57.0",
|
"@taiga-ui/i18n": "^3.65.0",
|
||||||
"@tinkoff/ng-event-plugins": "3.1.0",
|
"@tinkoff/ng-event-plugins": "3.1.0",
|
||||||
"@tinkoff/ng-polymorpheus": "4.3.0",
|
"@tinkoff/ng-polymorpheus": "4.3.0",
|
||||||
"rxjs": ">=6.0.0"
|
"rxjs": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/experimental": {
|
"node_modules/@taiga-ui/experimental": {
|
||||||
"version": "3.57.0",
|
"version": "3.65.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-3.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-3.65.0.tgz",
|
||||||
"integrity": "sha512-A0u/Cn0tHUjl6sTSfF6G9YvzuEJafBoVbHuDpw1mnSOw/TkWLXpkoYzeyJ6U9+nBIFBOSl4o+14olrYvsUg9CA==",
|
"integrity": "sha512-LZYR+XeJ2n+vE4AHBiIolzlqDrDGUx/bmE0ypmKO7dPgvHWu5Al8OXRrnhyqmAVO48FNpkSZ07YoqCG/aoxu6g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.6.2"
|
"tslib": "2.6.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": ">=12.0.0",
|
"@angular/common": ">=12.0.0",
|
||||||
"@angular/core": ">=12.0.0",
|
"@angular/core": ">=12.0.0",
|
||||||
"@taiga-ui/addon-commerce": ">=3.57.0",
|
"@taiga-ui/addon-commerce": "^3.65.0",
|
||||||
"@taiga-ui/cdk": ">=3.57.0",
|
"@taiga-ui/cdk": "^3.65.0",
|
||||||
"@taiga-ui/core": ">=3.57.0",
|
"@taiga-ui/core": "^3.65.0",
|
||||||
"@taiga-ui/kit": ">=3.57.0",
|
"@taiga-ui/kit": "^3.65.0",
|
||||||
"@tinkoff/ng-polymorpheus": "4.3.0",
|
"@tinkoff/ng-polymorpheus": "4.3.0",
|
||||||
"rxjs": ">=6.0.0"
|
"rxjs": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/i18n": {
|
"node_modules/@taiga-ui/i18n": {
|
||||||
"version": "3.57.0",
|
"version": "3.65.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.65.0.tgz",
|
||||||
"integrity": "sha512-FhE3eMD5g+i0/SbRgq4zoA7pBVY1mw4/gwndOJvCFkcWhjYxOJXJ4g/sjTyQmZ4QXmONM0OeIOMBxuMMCeJkGQ==",
|
"integrity": "sha512-lHy9VDKc5IXbm40eJnnAyOlmm3vDgmWhGbr5woGe9bV/tTqsBBDATY7Rkhz7Bu1nbX7X+MI0TDfQh9ayoCCKRQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.6.2"
|
"tslib": "2.6.2"
|
||||||
},
|
},
|
||||||
@@ -4815,25 +4815,25 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/icons": {
|
"node_modules/@taiga-ui/icons": {
|
||||||
"version": "3.57.0",
|
"version": "3.65.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.65.0.tgz",
|
||||||
"integrity": "sha512-uXch0AX8DQMCrv2ldQIYmUUjMmhP2tgOLxX08GrPZCWsz6zHq8stgUJR6kgvZYwO1JZSFlwvCgv/32MoDEdI3w==",
|
"integrity": "sha512-8iE6EuK+QBzcNiRM1ThZOOkZpal7V6dBouMXMj+QphRWiIp8Znj58mtY3L+uwQFpGnxt3DRs4p4eEA9ZuGFssw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.6.2"
|
"tslib": "2.6.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@taiga-ui/cdk": ">=3.57.0"
|
"@taiga-ui/cdk": "^3.65.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/kit": {
|
"node_modules/@taiga-ui/kit": {
|
||||||
"version": "3.57.0",
|
"version": "3.65.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.65.0.tgz",
|
||||||
"integrity": "sha512-vZNMKlPku5E1wCiPdRV3GbNKba4molyd6F2Zc4EMJvHAlWybFpVPQOw67Z7VsSLGwWpzZXBC+5M5Ov3UNOUSJQ==",
|
"integrity": "sha512-Nh6pMSAFR7yScF7acj8WdCpKQUgDatW2jObqts0z4hy9BJ8gl9BAWRBgSlbp3Oen5c2WAIC316Gb9OcttC8nbw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@maskito/angular": "1.9.0",
|
"@maskito/angular": "1.9.0",
|
||||||
"@maskito/core": "1.9.0",
|
"@maskito/core": "1.9.0",
|
||||||
"@maskito/kit": "1.9.0",
|
"@maskito/kit": "1.9.0",
|
||||||
"@ng-web-apis/intersection-observer": "3.1.6",
|
"@ng-web-apis/intersection-observer": "3.2.0",
|
||||||
"text-mask-core": "5.1.2",
|
"text-mask-core": "5.1.2",
|
||||||
"tslib": "2.6.2"
|
"tslib": "2.6.2"
|
||||||
},
|
},
|
||||||
@@ -4843,21 +4843,21 @@
|
|||||||
"@angular/forms": ">=12.0.0",
|
"@angular/forms": ">=12.0.0",
|
||||||
"@angular/router": ">=12.0.0",
|
"@angular/router": ">=12.0.0",
|
||||||
"@ng-web-apis/common": "3.0.6",
|
"@ng-web-apis/common": "3.0.6",
|
||||||
"@ng-web-apis/mutation-observer": "3.0.6",
|
"@ng-web-apis/mutation-observer": "3.1.0",
|
||||||
"@ng-web-apis/resize-observer": "3.0.6",
|
"@ng-web-apis/resize-observer": "3.0.6",
|
||||||
"@taiga-ui/cdk": ">=3.57.0",
|
"@taiga-ui/cdk": "^3.65.0",
|
||||||
"@taiga-ui/core": ">=3.57.0",
|
"@taiga-ui/core": "^3.65.0",
|
||||||
"@taiga-ui/i18n": ">=3.57.0",
|
"@taiga-ui/i18n": "^3.65.0",
|
||||||
"@tinkoff/ng-polymorpheus": "4.3.0",
|
"@tinkoff/ng-polymorpheus": "4.3.0",
|
||||||
"rxjs": ">=6.0.0"
|
"rxjs": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/styles": {
|
"node_modules/@taiga-ui/styles": {
|
||||||
"version": "3.57.0",
|
"version": "3.65.0",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/styles/-/styles-3.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/styles/-/styles-3.65.0.tgz",
|
||||||
"integrity": "sha512-jxSAZHL+QrjTHH5tEQ2EJXmNpRvKUN/8fHh5Fw+O71Y50I9IENN2CVXdB01YmjtNvn0tQE2sUy1RFausIQLyBA==",
|
"integrity": "sha512-HO2sZPxNOGj2BPQpWkrM6HgZV/QxaEMEemye3sJvsfuttvk6bmxoL8NF331I63tlp/Zx7woD8AusH5ATuUniqg==",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@taiga-ui/cdk": ">=3.57.0",
|
"@taiga-ui/cdk": "^3.65.0",
|
||||||
"tslib": "2.6.2"
|
"tslib": "2.6.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -4910,36 +4910,37 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ts-morph/common": {
|
"node_modules/@ts-morph/common": {
|
||||||
"version": "0.21.0",
|
"version": "0.22.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.22.0.tgz",
|
||||||
"integrity": "sha512-ES110Mmne5Vi4ypUKrtVQfXFDtCsDXiUiGxF6ILVlE90dDD4fdpC1LSjydl/ml7xJWKSDZwUYD2zkOePMSrPBA==",
|
"integrity": "sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-glob": "^3.2.12",
|
"fast-glob": "^3.3.2",
|
||||||
"minimatch": "^7.4.3",
|
"minimatch": "^9.0.3",
|
||||||
"mkdirp": "^2.1.6",
|
"mkdirp": "^3.0.1",
|
||||||
"path-browserify": "^1.0.1"
|
"path-browserify": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ts-morph/common/node_modules/minimatch": {
|
"node_modules/@ts-morph/common/node_modules/fast-glob": {
|
||||||
"version": "7.4.6",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||||
"integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==",
|
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^2.0.1"
|
"@nodelib/fs.stat": "^2.0.2",
|
||||||
|
"@nodelib/fs.walk": "^1.2.3",
|
||||||
|
"glob-parent": "^5.1.2",
|
||||||
|
"merge2": "^1.3.0",
|
||||||
|
"micromatch": "^4.0.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=8.6.0"
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ts-morph/common/node_modules/mkdirp": {
|
"node_modules/@ts-morph/common/node_modules/mkdirp": {
|
||||||
"version": "2.1.6",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
|
||||||
"integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==",
|
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"mkdirp": "dist/cjs/src/bin.js"
|
"mkdirp": "dist/cjs/src/bin.js"
|
||||||
@@ -11981,15 +11982,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ng-morph": {
|
"node_modules/ng-morph": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/ng-morph/-/ng-morph-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/ng-morph/-/ng-morph-4.0.5.tgz",
|
||||||
"integrity": "sha512-4voBApzsUs0/1zJsV2sSVPoOKxWt0gBd+3yqE/q8oYOX87RN3HwcJmqQLtWEU4tbmhk11gSRIFIl61+z32cnNw==",
|
"integrity": "sha512-5tnlb5WrGKeo2E7VRcV7ZHhScyNgliYqpbXqt103kynmfj6Ic8kzhJAhHu9iLkF1yRnKv2kyCE+O7UGZx5RraQ==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jsonc-parser": "3.2.0",
|
"jsonc-parser": "3.2.0",
|
||||||
"minimatch": "9.0.3",
|
"minimatch": "9.0.3",
|
||||||
"multimatch": "5.0.0",
|
"multimatch": "5.0.0",
|
||||||
"ts-morph": "20.0.0",
|
"ts-morph": "21.0.1",
|
||||||
"tslib": "2.6.2"
|
"tslib": "2.6.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -16605,12 +16606,12 @@
|
|||||||
"integrity": "sha512-kXrY75F0s0WD15N2bWKDScKlKgwnusN6dTRzGs1N7LlxQRnazrsBISC1HL4sy2adsyk65Zbx3Ui3IGN8leAFOQ=="
|
"integrity": "sha512-kXrY75F0s0WD15N2bWKDScKlKgwnusN6dTRzGs1N7LlxQRnazrsBISC1HL4sy2adsyk65Zbx3Ui3IGN8leAFOQ=="
|
||||||
},
|
},
|
||||||
"node_modules/ts-morph": {
|
"node_modules/ts-morph": {
|
||||||
"version": "20.0.0",
|
"version": "21.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-20.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-21.0.1.tgz",
|
||||||
"integrity": "sha512-JVmEJy2Wow5n/84I3igthL9sudQ8qzjh/6i4tmYCm6IqYyKFlNbJZi7oBdjyqcWSWYRu3CtL0xbT6fS03ESZIg==",
|
"integrity": "sha512-dbDtVdEAncKctzrVZ+Nr7kHpHkv+0JDJb2MjjpBaj8bFeCkePU9rHfMklmhuLFnpeq/EJZk2IhStY6NzqgjOkg==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ts-morph/common": "~0.21.0",
|
"@ts-morph/common": "~0.22.0",
|
||||||
"code-block-writer": "^12.0.0"
|
"code-block-writer": "^12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -46,14 +46,15 @@
|
|||||||
"@start9labs/argon2": "^0.1.0",
|
"@start9labs/argon2": "^0.1.0",
|
||||||
"@start9labs/emver": "^0.1.5",
|
"@start9labs/emver": "^0.1.5",
|
||||||
"@start9labs/start-sdk": "0.4.0-rev0.lib0.rc8.beta2",
|
"@start9labs/start-sdk": "0.4.0-rev0.lib0.rc8.beta2",
|
||||||
"@taiga-ui/addon-charts": "3.57.0",
|
"@taiga-ui/addon-charts": "3.65.0",
|
||||||
"@taiga-ui/addon-mobile": "3.57.0",
|
"@taiga-ui/addon-commerce": "3.65.0",
|
||||||
"@taiga-ui/cdk": "3.57.0",
|
"@taiga-ui/addon-mobile": "3.65.0",
|
||||||
"@taiga-ui/core": "3.57.0",
|
"@taiga-ui/cdk": "3.65.0",
|
||||||
"@taiga-ui/experimental": "3.57.0",
|
"@taiga-ui/core": "3.65.0",
|
||||||
"@taiga-ui/icons": "3.57.0",
|
"@taiga-ui/experimental": "3.65.0",
|
||||||
"@taiga-ui/kit": "3.57.0",
|
"@taiga-ui/icons": "3.65.0",
|
||||||
"@taiga-ui/styles": "3.57.0",
|
"@taiga-ui/kit": "3.65.0",
|
||||||
|
"@taiga-ui/styles": "3.65.0",
|
||||||
"@tinkoff/ng-dompurify": "4.0.0",
|
"@tinkoff/ng-dompurify": "4.0.0",
|
||||||
"@tinkoff/ng-event-plugins": "3.1.0",
|
"@tinkoff/ng-event-plugins": "3.1.0",
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": ">=13.2.0",
|
"@angular/common": ">=13.2.0",
|
||||||
"@angular/core": ">=13.2.0",
|
"@angular/core": ">=13.2.0",
|
||||||
"@ionic/angular": ">=6.0.0",
|
|
||||||
"@start9labs/shared": ">=0.3.2",
|
"@start9labs/shared": ">=0.3.2",
|
||||||
"@taiga-ui/cdk": ">=3.0.0",
|
"@taiga-ui/cdk": ">=3.0.0",
|
||||||
"@tinkoff/ng-dompurify": ">=4.0.0",
|
"@tinkoff/ng-dompurify": ">=4.0.0",
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
<img
|
|
||||||
*ngIf="icon; else noIcon"
|
|
||||||
class="rounded-full"
|
|
||||||
[style.max-width]="size || '100%'"
|
|
||||||
[src]="icon"
|
|
||||||
alt="Service Icon"
|
|
||||||
/>
|
|
||||||
<ng-template #noIcon>
|
|
||||||
<ion-icon name="storefront-outline" [style.font-size]="size"></ion-icon>
|
|
||||||
</ng-template>
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { IonicModule } from '@ionic/angular'
|
import { TuiIconModule } from '@taiga-ui/experimental'
|
||||||
import { StoreIconComponent } from './store-icon.component'
|
import { StoreIconComponent } from './store-icon.component'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [StoreIconComponent],
|
declarations: [StoreIconComponent],
|
||||||
imports: [CommonModule, IonicModule],
|
imports: [CommonModule, TuiIconModule],
|
||||||
exports: [StoreIconComponent],
|
exports: [StoreIconComponent],
|
||||||
})
|
})
|
||||||
export class StoreIconComponentModule {}
|
export class StoreIconComponentModule {}
|
||||||
|
|||||||
@@ -3,8 +3,18 @@ import { MarketplaceConfig, sameUrl } from '@start9labs/shared'
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'store-icon',
|
selector: 'store-icon',
|
||||||
templateUrl: './store-icon.component.html',
|
template: `
|
||||||
styleUrls: ['./store-icon.component.scss'],
|
<img
|
||||||
|
*ngIf="icon; else noIcon"
|
||||||
|
[style.border-radius.%]="100"
|
||||||
|
[style.max-width]="size || '100%'"
|
||||||
|
[src]="icon"
|
||||||
|
alt="Service Icon"
|
||||||
|
/>
|
||||||
|
<ng-template #noIcon>
|
||||||
|
<tui-icon icon="tuiIconShoppingCart" [style.font-size]="size" />
|
||||||
|
</ng-template>
|
||||||
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class StoreIconComponent {
|
export class StoreIconComponent {
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { IonicModule } from '@ionic/angular'
|
|
||||||
|
|
||||||
import { CategoriesComponent } from './categories.component'
|
import { CategoriesComponent } from './categories.component'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, IonicModule],
|
imports: [CommonModule],
|
||||||
declarations: [CategoriesComponent],
|
declarations: [CategoriesComponent],
|
||||||
exports: [CategoriesComponent],
|
exports: [CategoriesComponent],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
<div class="">
|
|
||||||
<ion-button *ngFor="let cat of ['', '', '', '', '', '', '']" fill="clear">
|
|
||||||
<ion-skeleton-text
|
|
||||||
animated
|
|
||||||
style="width: 80px; border-radius: 0"
|
|
||||||
></ion-skeleton-text>
|
|
||||||
</ion-button>
|
|
||||||
</div>
|
|
||||||
<div class="hidden sm:flex flex-col mx-6 pb-3 items-center">
|
|
||||||
<div class="pb-3">
|
|
||||||
<ion-skeleton-text style="border-radius: 100%" animated></ion-skeleton-text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ng-content select="[slot=desktop]"></ng-content>
|
|
||||||
</div>
|
|
||||||
<ion-row>
|
|
||||||
<ion-col
|
|
||||||
*ngFor="let cat of ['', '', '', '', '', '', '']"
|
|
||||||
fill="clear"
|
|
||||||
responsiveCol
|
|
||||||
sizeXs="12"
|
|
||||||
sizeSm="12"
|
|
||||||
sizeMd="3"
|
|
||||||
>
|
|
||||||
<ion-item>
|
|
||||||
<ion-thumbnail slot="start">
|
|
||||||
<ion-skeleton-text
|
|
||||||
style="border-radius: 100%"
|
|
||||||
animated
|
|
||||||
></ion-skeleton-text>
|
|
||||||
</ion-thumbnail>
|
|
||||||
<ion-label>
|
|
||||||
<ion-skeleton-text
|
|
||||||
animated
|
|
||||||
style="width: 150px; height: 18px; margin-bottom: 8px"
|
|
||||||
></ion-skeleton-text>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
</ion-col>
|
|
||||||
</ion-row>
|
|
||||||
|
|
||||||
<ion-grid class="p-20">
|
|
||||||
<ion-row>
|
|
||||||
<ion-col
|
|
||||||
*ngFor="let pkg of ['', '', '', '']"
|
|
||||||
responsiveCol
|
|
||||||
sizeXs="12"
|
|
||||||
sizeSm="12"
|
|
||||||
sizeMd="3"
|
|
||||||
>
|
|
||||||
<ion-item>
|
|
||||||
<ion-thumbnail slot="start">
|
|
||||||
<ion-skeleton-text
|
|
||||||
style="border-radius: 100%"
|
|
||||||
animated
|
|
||||||
></ion-skeleton-text>
|
|
||||||
</ion-thumbnail>
|
|
||||||
<ion-label>
|
|
||||||
<ion-skeleton-text
|
|
||||||
animated
|
|
||||||
style="width: 150px; height: 18px; margin-bottom: 8px"
|
|
||||||
></ion-skeleton-text>
|
|
||||||
<ion-skeleton-text animated style="width: 400px"></ion-skeleton-text>
|
|
||||||
<ion-skeleton-text animated style="width: 100px"></ion-skeleton-text>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
</ion-col>
|
|
||||||
</ion-row>
|
|
||||||
</ion-grid>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'marketplace-skeleton',
|
|
||||||
templateUrl: 'skeleton.component.html',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class SkeletonComponent {}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { IonicModule } from '@ionic/angular'
|
|
||||||
import { ResponsiveColDirective } from '@start9labs/shared'
|
|
||||||
|
|
||||||
import { SkeletonComponent } from './skeleton.component'
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [CommonModule, IonicModule, ResponsiveColDirective],
|
|
||||||
declarations: [SkeletonComponent],
|
|
||||||
exports: [SkeletonComponent],
|
|
||||||
})
|
|
||||||
export class SkeletonModule {}
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
>
|
>
|
||||||
<span class="small-caps">Latest Release</span>
|
<span class="small-caps">Latest Release</span>
|
||||||
-
|
-
|
||||||
<span class="text-sm">{{ published | date : 'medium' }}</span>
|
<span class="text-sm">{{ published | date: 'medium' }}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap justify-between gap-6">
|
<div class="flex flex-wrap justify-between gap-6">
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
<tui-accordion
|
<tui-accordion
|
||||||
class="max-w-lg"
|
class="max-w-lg"
|
||||||
[closeOthers]="false"
|
[closeOthers]="false"
|
||||||
*ngFor="let note of notes | keyvalue : asIsOrder"
|
*ngFor="let note of notes | keyvalue: asIsOrder"
|
||||||
>
|
>
|
||||||
<tui-accordion-item class="my-1">
|
<tui-accordion-item class="my-1">
|
||||||
{{ note.key | displayEmver }}
|
{{ note.key | displayEmver }}
|
||||||
@@ -53,6 +53,6 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-template #loading>
|
<ng-template #loading>
|
||||||
<text-spinner text="Loading Release Notes"></text-spinner>
|
<tui-loader textContent="Loading Release Notes" />
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -1,26 +1,25 @@
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from '@angular/core'
|
||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from '@angular/common'
|
||||||
import {
|
import {
|
||||||
EmverPipesModule,
|
EmverPipesModule,
|
||||||
MarkdownPipeModule,
|
MarkdownPipeModule,
|
||||||
SafeLinksDirective,
|
SafeLinksDirective,
|
||||||
TextSpinnerComponentModule,
|
} from '@start9labs/shared'
|
||||||
} from "@start9labs/shared";
|
import { TuiAccordionModule } from '@taiga-ui/kit'
|
||||||
import { NgDompurifyModule } from "@tinkoff/ng-dompurify";
|
import { TuiButtonModule, TuiLoaderModule } from '@taiga-ui/core'
|
||||||
import { ReleaseNotesComponent } from "./release-notes.component";
|
import { NgDompurifyModule } from '@tinkoff/ng-dompurify'
|
||||||
import { TuiButtonModule } from "@taiga-ui/core";
|
import { ReleaseNotesComponent } from './release-notes.component'
|
||||||
import { TuiAccordionModule } from "@taiga-ui/kit";
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
TextSpinnerComponentModule,
|
|
||||||
EmverPipesModule,
|
EmverPipesModule,
|
||||||
MarkdownPipeModule,
|
MarkdownPipeModule,
|
||||||
NgDompurifyModule,
|
NgDompurifyModule,
|
||||||
SafeLinksDirective,
|
SafeLinksDirective,
|
||||||
TuiButtonModule,
|
TuiButtonModule,
|
||||||
TuiAccordionModule,
|
TuiAccordionModule,
|
||||||
|
TuiLoaderModule,
|
||||||
],
|
],
|
||||||
declarations: [ReleaseNotesComponent],
|
declarations: [ReleaseNotesComponent],
|
||||||
exports: [ReleaseNotesComponent],
|
exports: [ReleaseNotesComponent],
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import { CommonModule } from '@angular/common'
|
|||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
Inject,
|
inject,
|
||||||
Input,
|
Input,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
|
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
|
||||||
import {
|
import {
|
||||||
TuiButtonModule,
|
TuiButtonModule,
|
||||||
TuiDialogContext,
|
TuiDialogContext,
|
||||||
@@ -13,7 +14,6 @@ import {
|
|||||||
import { TuiCarouselModule } from '@taiga-ui/kit'
|
import { TuiCarouselModule } from '@taiga-ui/kit'
|
||||||
import { MarketplacePkg } from '../../../types'
|
import { MarketplacePkg } from '../../../types'
|
||||||
import { PolymorpheusContent } from '@tinkoff/ng-polymorpheus'
|
import { PolymorpheusContent } from '@tinkoff/ng-polymorpheus'
|
||||||
import { isPlatform } from '@ionic/angular'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplace-package-screenshots',
|
selector: 'marketplace-package-screenshots',
|
||||||
@@ -77,15 +77,14 @@ import { isPlatform } from '@ionic/angular'
|
|||||||
imports: [CommonModule, TuiCarouselModule, TuiButtonModule],
|
imports: [CommonModule, TuiCarouselModule, TuiButtonModule],
|
||||||
})
|
})
|
||||||
export class MarketplacePackageScreenshotComponent {
|
export class MarketplacePackageScreenshotComponent {
|
||||||
|
private readonly dialogs = inject(TuiDialogService)
|
||||||
|
|
||||||
@Input({ required: true })
|
@Input({ required: true })
|
||||||
pkg!: MarketplacePkg
|
pkg!: MarketplacePkg
|
||||||
|
|
||||||
constructor(
|
|
||||||
@Inject(TuiDialogService) private readonly dialogs: TuiDialogService,
|
|
||||||
) {}
|
|
||||||
index = 0
|
index = 0
|
||||||
|
|
||||||
isMobile = isPlatform(window, 'ios') || isPlatform(window, 'android')
|
isMobile = inject(TUI_IS_MOBILE)
|
||||||
|
|
||||||
presentModalImg(content: PolymorpheusContent<TuiDialogContext>) {
|
presentModalImg(content: PolymorpheusContent<TuiDialogContext>) {
|
||||||
this.dialogs
|
this.dialogs
|
||||||
|
|||||||
@@ -1,32 +1,15 @@
|
|||||||
<ion-content>
|
<div *tuiLet="progress$ | async as progress" class="card">
|
||||||
<ion-grid>
|
<h1 class="title">Initializing StartOS</h1>
|
||||||
<ion-row class="ion-align-items-center">
|
<div *ngIf="progress" class="center-wrapper">
|
||||||
<ion-col class="ion-text-center">
|
Progress: {{ (progress * 100).toFixed(0) }}%
|
||||||
<ion-card *tuiLet="progress$ | async as progress" color="dark">
|
</div>
|
||||||
<ion-card-header>
|
|
||||||
<ion-card-title>Initializing StartOS</ion-card-title>
|
|
||||||
<div class="center-wrapper">
|
|
||||||
<ion-card-subtitle *ngIf="progress">
|
|
||||||
Progress: {{ (progress * 100).toFixed(0) }}%
|
|
||||||
</ion-card-subtitle>
|
|
||||||
</div>
|
|
||||||
</ion-card-header>
|
|
||||||
|
|
||||||
<ion-card-content class="ion-margin">
|
<progress
|
||||||
<ion-progress-bar
|
tuiProgressBar
|
||||||
color="tertiary"
|
class="progress"
|
||||||
class="progress"
|
[attr.value]="progress && progress < 1 ? progress : null"
|
||||||
[type]="
|
></progress>
|
||||||
progress && progress < 1 ? 'determinate' : 'indeterminate'
|
<p>{{ getMessage(progress) }}</p>
|
||||||
"
|
</div>
|
||||||
[value]="progress || 0"
|
|
||||||
></ion-progress-bar>
|
|
||||||
<p>{{ getMessage(progress) }}</p>
|
|
||||||
</ion-card-content>
|
|
||||||
</ion-card>
|
|
||||||
|
|
||||||
<logs-window class="logs" />
|
<logs-window class="logs" />
|
||||||
</ion-col>
|
|
||||||
</ion-row>
|
|
||||||
</ion-grid>
|
|
||||||
</ion-content>
|
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
ion-card-title {
|
.card {
|
||||||
font-size: 42px;
|
border-radius: 0.25rem;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
// TODO: Theme
|
||||||
|
background: #e0e0e0;
|
||||||
|
color: #333;
|
||||||
|
--tui-clear-inverse: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
max-width: 700px;
|
max-width: 40rem;
|
||||||
padding-bottom: 20px;
|
margin: 1rem auto;
|
||||||
margin: auto auto 40px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs {
|
.logs {
|
||||||
@@ -13,7 +24,7 @@ ion-card-title {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 18rem;
|
height: 18rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin: 1.5rem 0.75rem;
|
margin: 0 1.5rem auto;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 2rem;
|
border-radius: 2rem;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { IonicModule } from '@ionic/angular'
|
|
||||||
import { TuiLetModule } from '@taiga-ui/cdk'
|
import { TuiLetModule } from '@taiga-ui/cdk'
|
||||||
|
import { TuiProgressModule } from '@taiga-ui/kit'
|
||||||
|
|
||||||
import { LogsWindowComponent } from './logs-window.component'
|
import { LogsWindowComponent } from './logs-window.component'
|
||||||
import { InitializingComponent } from './initializing.component'
|
import { InitializingComponent } from './initializing.component'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, IonicModule, TuiLetModule, LogsWindowComponent],
|
imports: [CommonModule, TuiLetModule, LogsWindowComponent, TuiProgressModule],
|
||||||
declarations: [InitializingComponent],
|
declarations: [InitializingComponent],
|
||||||
exports: [InitializingComponent],
|
exports: [InitializingComponent],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<ion-item *ngIf="error$ | async as error">
|
<tui-notification *ngIf="error$ | async as error" status="error" safeLinks>
|
||||||
<ion-label>
|
{{ error }}
|
||||||
<ion-text safeLinks color="danger">{{ error }}</ion-text>
|
</tui-notification>
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
*ngIf="content$ | async as result; else loading"
|
*ngIf="content$ | async as result; else loading"
|
||||||
@@ -12,5 +10,5 @@
|
|||||||
></div>
|
></div>
|
||||||
|
|
||||||
<ng-template #loading>
|
<ng-template #loading>
|
||||||
<text-spinner [text]="'Loading ' + title | titlecase"></text-spinner>
|
<tui-loader [textContent]="'Loading ' + title | titlecase" />
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|||||||
@@ -1,22 +1,21 @@
|
|||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { IonicModule } from '@ionic/angular'
|
import { TuiLoaderModule, TuiNotificationModule } from '@taiga-ui/core'
|
||||||
import { NgDompurifyModule } from '@tinkoff/ng-dompurify'
|
import { NgDompurifyModule } from '@tinkoff/ng-dompurify'
|
||||||
|
|
||||||
import { MarkdownPipeModule } from '../../pipes/markdown/markdown.module'
|
import { MarkdownPipeModule } from '../../pipes/markdown/markdown.module'
|
||||||
import { SafeLinksDirective } from '../../directives/safe-links.directive'
|
import { SafeLinksDirective } from '../../directives/safe-links.directive'
|
||||||
import { TextSpinnerComponentModule } from '../text-spinner/text-spinner.component.module'
|
|
||||||
import { MarkdownComponent } from './markdown.component'
|
import { MarkdownComponent } from './markdown.component'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [MarkdownComponent],
|
declarations: [MarkdownComponent],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
IonicModule,
|
|
||||||
MarkdownPipeModule,
|
MarkdownPipeModule,
|
||||||
TextSpinnerComponentModule,
|
|
||||||
SafeLinksDirective,
|
SafeLinksDirective,
|
||||||
NgDompurifyModule,
|
NgDompurifyModule,
|
||||||
|
TuiLoaderModule,
|
||||||
|
TuiNotificationModule,
|
||||||
],
|
],
|
||||||
exports: [MarkdownComponent],
|
exports: [MarkdownComponent],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
[tuiAppearance][data-appearance='primary'] {
|
[tuiAppearance][data-appearance='primary'] {
|
||||||
@include appearance-disabled {
|
@include appearance-disabled {
|
||||||
background: #eaecee;
|
background: #eaecee;
|
||||||
|
color: #333;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
import { HttpClientModule } from '@angular/common/http'
|
||||||
|
import { NgModule } from '@angular/core'
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
|
||||||
|
import { ServiceWorkerModule } from '@angular/service-worker'
|
||||||
|
import { IonicModule } from '@ionic/angular'
|
||||||
|
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'
|
||||||
|
import {
|
||||||
|
DarkThemeModule,
|
||||||
|
EnterModule,
|
||||||
|
LightThemeModule,
|
||||||
|
LoadingModule,
|
||||||
|
MarkdownModule,
|
||||||
|
ResponsiveColViewportDirective,
|
||||||
|
SharedPipesModule,
|
||||||
|
} from '@start9labs/shared'
|
||||||
import {
|
import {
|
||||||
TuiAlertModule,
|
TuiAlertModule,
|
||||||
TuiDialogModule,
|
TuiDialogModule,
|
||||||
@@ -5,36 +20,19 @@ import {
|
|||||||
TuiRootModule,
|
TuiRootModule,
|
||||||
TuiThemeNightModule,
|
TuiThemeNightModule,
|
||||||
} from '@taiga-ui/core'
|
} from '@taiga-ui/core'
|
||||||
import { HttpClientModule } from '@angular/common/http'
|
import { WidgetsPageModule } from 'src/app/apps/ui/pages/widgets/widgets.module'
|
||||||
import { NgModule } from '@angular/core'
|
import { environment } from '../environments/environment'
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
|
|
||||||
import { IonicModule } from '@ionic/angular'
|
|
||||||
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'
|
|
||||||
import {
|
|
||||||
DarkThemeModule,
|
|
||||||
SharedPipesModule,
|
|
||||||
LightThemeModule,
|
|
||||||
LoadingModule,
|
|
||||||
ResponsiveColViewportDirective,
|
|
||||||
EnterModule,
|
|
||||||
MarkdownModule,
|
|
||||||
} from '@start9labs/shared'
|
|
||||||
|
|
||||||
import { AppComponent } from './app.component'
|
import { AppComponent } from './app.component'
|
||||||
import { RoutingModule } from './routing.module'
|
import { APP_PROVIDERS } from './app.providers'
|
||||||
import { OSWelcomePageModule } from './common/os-welcome/os-welcome.module'
|
import { ConnectionBarComponentModule } from './app/connection-bar/connection-bar.component.module'
|
||||||
import { QRComponentModule } from './common/qr/qr.module'
|
|
||||||
import { PreloaderModule } from './app/preloader/preloader.module'
|
|
||||||
import { FooterModule } from './app/footer/footer.module'
|
import { FooterModule } from './app/footer/footer.module'
|
||||||
import { MenuModule } from './app/menu/menu.module'
|
import { MenuModule } from './app/menu/menu.module'
|
||||||
import { APP_PROVIDERS } from './app.providers'
|
import { PreloaderModule } from './app/preloader/preloader.module'
|
||||||
import { PatchDbModule } from './services/patch-db/patch-db.module'
|
|
||||||
import { ToastContainerModule } from './common/toast-container/toast-container.module'
|
|
||||||
import { ConnectionBarComponentModule } from './app/connection-bar/connection-bar.component.module'
|
|
||||||
import { WidgetsPageModule } from 'src/app/apps/ui/pages/widgets/widgets.module'
|
|
||||||
import { ServiceWorkerModule } from '@angular/service-worker'
|
|
||||||
import { environment } from '../environments/environment'
|
|
||||||
import { SidebarHostComponent } from './app/sidebar-host.component'
|
import { SidebarHostComponent } from './app/sidebar-host.component'
|
||||||
|
import { OSWelcomePageModule } from './common/os-welcome/os-welcome.module'
|
||||||
|
import { QRComponentModule } from './common/qr/qr.module'
|
||||||
|
import { ToastContainerModule } from './common/toast-container/toast-container.module'
|
||||||
|
import { RoutingModule } from './routing.module'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AppComponent],
|
declarations: [AppComponent],
|
||||||
@@ -53,7 +51,6 @@ import { SidebarHostComponent } from './app/sidebar-host.component'
|
|||||||
MarkdownModule,
|
MarkdownModule,
|
||||||
MonacoEditorModule,
|
MonacoEditorModule,
|
||||||
SharedPipesModule,
|
SharedPipesModule,
|
||||||
PatchDbModule,
|
|
||||||
ToastContainerModule,
|
ToastContainerModule,
|
||||||
ConnectionBarComponentModule,
|
ConnectionBarComponentModule,
|
||||||
TuiRootModule,
|
TuiRootModule,
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import { APP_INITIALIZER, Provider } from '@angular/core'
|
import { APP_INITIALIZER, Provider } from '@angular/core'
|
||||||
import { UntypedFormBuilder } from '@angular/forms'
|
import { UntypedFormBuilder } from '@angular/forms'
|
||||||
import { Router, RouteReuseStrategy } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
import { IonNav } from '@ionic/angular'
|
import { IonNav } from '@ionic/angular'
|
||||||
|
import {
|
||||||
|
AbstractCategoryService,
|
||||||
|
AbstractMarketplaceService,
|
||||||
|
FilterPackagesPipe,
|
||||||
|
} from '@start9labs/marketplace'
|
||||||
|
import { RELATIVE_URL, THEME, WorkspaceConfig } from '@start9labs/shared'
|
||||||
import { TUI_DATE_FORMAT, TUI_DATE_SEPARATOR } from '@taiga-ui/cdk'
|
import { TUI_DATE_FORMAT, TUI_DATE_SEPARATOR } from '@taiga-ui/cdk'
|
||||||
import {
|
import {
|
||||||
tuiNumberFormatProvider,
|
tuiNumberFormatProvider,
|
||||||
@@ -12,22 +18,17 @@ import {
|
|||||||
TUI_DATE_TIME_VALUE_TRANSFORMER,
|
TUI_DATE_TIME_VALUE_TRANSFORMER,
|
||||||
TUI_DATE_VALUE_TRANSFORMER,
|
TUI_DATE_VALUE_TRANSFORMER,
|
||||||
} from '@taiga-ui/kit'
|
} from '@taiga-ui/kit'
|
||||||
import { RELATIVE_URL, THEME, WorkspaceConfig } from '@start9labs/shared'
|
import { PATCH_DB_PROVIDERS } from 'src/app/services/patch-db/patch-db.providers'
|
||||||
import {
|
|
||||||
AbstractCategoryService,
|
|
||||||
AbstractMarketplaceService,
|
|
||||||
} from '@start9labs/marketplace'
|
|
||||||
import { ApiService } from './services/api/embassy-api.service'
|
import { ApiService } from './services/api/embassy-api.service'
|
||||||
import { MockApiService } from './services/api/embassy-mock-api.service'
|
|
||||||
import { LiveApiService } from './services/api/embassy-live-api.service'
|
import { LiveApiService } from './services/api/embassy-live-api.service'
|
||||||
|
import { MockApiService } from './services/api/embassy-mock-api.service'
|
||||||
import { AuthService } from './services/auth.service'
|
import { AuthService } from './services/auth.service'
|
||||||
|
import { CategoryService } from './services/category.service'
|
||||||
import { ClientStorageService } from './services/client-storage.service'
|
import { ClientStorageService } from './services/client-storage.service'
|
||||||
import { FilterPackagesPipe } from '../../../marketplace/src/pipes/filter-packages.pipe'
|
|
||||||
import { ThemeSwitcherService } from './services/theme-switcher.service'
|
|
||||||
import { DateTransformerService } from './services/date-transformer.service'
|
import { DateTransformerService } from './services/date-transformer.service'
|
||||||
import { DatetimeTransformerService } from './services/datetime-transformer.service'
|
import { DatetimeTransformerService } from './services/datetime-transformer.service'
|
||||||
import { MarketplaceService } from './services/marketplace.service'
|
import { MarketplaceService } from './services/marketplace.service'
|
||||||
import { CategoryService } from './services/category.service'
|
import { ThemeSwitcherService } from './services/theme-switcher.service'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
useMocks,
|
useMocks,
|
||||||
@@ -35,6 +36,7 @@ const {
|
|||||||
} = require('../../../../config.json') as WorkspaceConfig
|
} = require('../../../../config.json') as WorkspaceConfig
|
||||||
|
|
||||||
export const APP_PROVIDERS: Provider[] = [
|
export const APP_PROVIDERS: Provider[] = [
|
||||||
|
PATCH_DB_PROVIDERS,
|
||||||
FilterPackagesPipe,
|
FilterPackagesPipe,
|
||||||
UntypedFormBuilder,
|
UntypedFormBuilder,
|
||||||
IonNav,
|
IonNav,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { RouterModule, Routes } from '@angular/router'
|
import { RouterModule, Routes } from '@angular/router'
|
||||||
import { InitializingModule } from '@start9labs/shared'
|
|
||||||
import { LoadingPage } from './loading.page'
|
import { LoadingPage } from './loading.page'
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
@@ -11,7 +10,6 @@ const routes: Routes = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [InitializingModule, RouterModule.forChild(routes)],
|
imports: [RouterModule.forChild(routes)],
|
||||||
declarations: [LoadingPage],
|
|
||||||
})
|
})
|
||||||
export class LoadingPageModule {}
|
export class LoadingPageModule {}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
<app-initializing
|
|
||||||
class="ion-page"
|
|
||||||
(finished)="navCtrl.navigateForward('/login')"
|
|
||||||
></app-initializing>
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Component, inject } from '@angular/core'
|
import { Component, inject } from '@angular/core'
|
||||||
import { NavController } from '@ionic/angular'
|
import { NavController } from '@ionic/angular'
|
||||||
import {
|
import {
|
||||||
|
InitializingModule,
|
||||||
provideSetupLogsService,
|
provideSetupLogsService,
|
||||||
provideSetupService,
|
provideSetupService,
|
||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
@@ -8,11 +9,18 @@ import {
|
|||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: 'loading.page.html',
|
standalone: true,
|
||||||
|
template: `
|
||||||
|
<app-initializing
|
||||||
|
class="ion-page"
|
||||||
|
(finished)="navCtrl.navigateForward('/login')"
|
||||||
|
></app-initializing>
|
||||||
|
`,
|
||||||
providers: [
|
providers: [
|
||||||
provideSetupService(ApiService),
|
provideSetupService(ApiService),
|
||||||
provideSetupLogsService(ApiService),
|
provideSetupLogsService(ApiService),
|
||||||
],
|
],
|
||||||
|
imports: [InitializingModule],
|
||||||
})
|
})
|
||||||
export class LoadingPage {
|
export class LoadingPage {
|
||||||
readonly navCtrl = inject(NavController)
|
readonly navCtrl = inject(NavController)
|
||||||
|
|||||||
@@ -1,100 +1,106 @@
|
|||||||
<div class="center-container">
|
<div
|
||||||
<ng-container *ngIf="!caTrusted; else trusted">
|
*ngIf="!caTrusted; else trusted"
|
||||||
<ion-card id="untrusted" class="text-center">
|
tuiCardLarge
|
||||||
<ion-icon name="lock-closed-outline" class="wiz-icon"></ion-icon>
|
tuiSurface="elevated"
|
||||||
<h1>Trust Your Root CA</h1>
|
class="card"
|
||||||
<p>
|
>
|
||||||
Download and trust your server's Root Certificate Authority to establish
|
<tui-icon icon="tuiIconLock" [style.font-size.rem]="4" />
|
||||||
a secure (HTTPS) connection. You will need to repeat this on every
|
<h1>Trust Your Root CA</h1>
|
||||||
device you use to connect to your server.
|
<p>
|
||||||
</p>
|
Download and trust your server's Root Certificate Authority to establish a
|
||||||
<ol>
|
secure (HTTPS) connection. You will need to repeat this on every device you
|
||||||
<li>
|
use to connect to your server.
|
||||||
<b>Bookmark this page</b>
|
</p>
|
||||||
- Save this page so you can access it later. You can also find the
|
<ol>
|
||||||
address in the
|
<li>
|
||||||
<code>StartOS-info.html</code>
|
<b>Bookmark this page</b>
|
||||||
file downloaded at the end of initial setup.
|
- Save this page so you can access it later. You can also find the address
|
||||||
</li>
|
in the
|
||||||
<li>
|
<code>StartOS-info.html</code>
|
||||||
<b>Download your server's Root CA</b>
|
file downloaded at the end of initial setup.
|
||||||
- Your server uses its Root CA to generate SSL/TLS certificates for
|
</li>
|
||||||
itself and installed services. These certificates are then used to
|
<li>
|
||||||
encrypt network traffic with your client devices.
|
<b>Download your server's Root CA</b>
|
||||||
<br />
|
- Your server uses its Root CA to generate SSL/TLS certificates for itself
|
||||||
<ion-button
|
and installed services. These certificates are then used to encrypt
|
||||||
strong
|
network traffic with your client devices.
|
||||||
size="small"
|
<br />
|
||||||
shape="round"
|
<a
|
||||||
color="tertiary"
|
tuiButton
|
||||||
(click)="download()"
|
size="s"
|
||||||
>
|
appearance="tertiary-solid"
|
||||||
Download
|
iconRight="tuiIconDownload"
|
||||||
<ion-icon slot="end" name="download-outline"></ion-icon>
|
href="/eos/local.crt"
|
||||||
</ion-button>
|
>
|
||||||
</li>
|
Download
|
||||||
<li>
|
</a>
|
||||||
<b>Trust your server's Root CA</b>
|
</li>
|
||||||
- Follow instructions for your OS. By trusting your server's Root CA,
|
<li>
|
||||||
your device can verify the authenticity of encrypted communications
|
<b>Trust your server's Root CA</b>
|
||||||
with your server.
|
- Follow instructions for your OS. By trusting your server's Root CA, your
|
||||||
<br />
|
device can verify the authenticity of encrypted communications with your
|
||||||
<ion-button
|
server.
|
||||||
strong
|
<br />
|
||||||
size="small"
|
<a
|
||||||
shape="round"
|
tuiButton
|
||||||
color="primary"
|
size="s"
|
||||||
href="https://docs.start9.com/0.3.5.x/user-manual/trust-ca"
|
href="https://docs.start9.com/0.3.5.x/user-manual/trust-ca"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
noreferrer
|
rel="noreferrer"
|
||||||
>
|
iconRight="tuiIconExternalLink"
|
||||||
View Instructions
|
>
|
||||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
View Instructions
|
||||||
</ion-button>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<b>Test</b>
|
<b>Test</b>
|
||||||
- Refresh the page. If refreshing the page does not work, you may need
|
- Refresh the page. If refreshing the page does not work, you may need to
|
||||||
to quit and re-open your browser, then revisit this page.
|
quit and re-open your browser, then revisit this page.
|
||||||
<br />
|
<br />
|
||||||
<ion-button
|
<button
|
||||||
strong
|
tuiButton
|
||||||
size="small"
|
size="s"
|
||||||
shape="round"
|
class="refresh"
|
||||||
class="refresh"
|
appearance="success-solid"
|
||||||
(click)="refresh()"
|
iconRight="tuiIconRefreshCw"
|
||||||
>
|
(click)="refresh()"
|
||||||
Refresh
|
>
|
||||||
<ion-icon slot="end" name="refresh"></ion-icon>
|
Refresh
|
||||||
</ion-button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
<ion-button fill="clear" (click)="launchHttps()" [disabled]="caTrusted">
|
<button
|
||||||
Skip
|
tuiButton
|
||||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
size="s"
|
||||||
</ion-button>
|
appearance="flat"
|
||||||
<span class="skip_detail">(not recommended)</span>
|
iconRight="tuiIconExternalLink"
|
||||||
</ion-card>
|
(click)="launchHttps()"
|
||||||
</ng-container>
|
[disabled]="caTrusted"
|
||||||
|
>
|
||||||
<ng-template #trusted>
|
Skip
|
||||||
<ion-card id="trusted" class="text-center">
|
</button>
|
||||||
<ion-icon
|
<div><small>(not recommended)</small></div>
|
||||||
name="shield-checkmark-outline"
|
|
||||||
class="wiz-icon"
|
|
||||||
color="success"
|
|
||||||
></ion-icon>
|
|
||||||
<h1>Root CA Trusted!</h1>
|
|
||||||
<p>
|
|
||||||
You have successfully trusted your server's Root CA and may now log in
|
|
||||||
securely.
|
|
||||||
</p>
|
|
||||||
<ion-button strong (click)="launchHttps()" color="tertiary" shape="round">
|
|
||||||
Go to login
|
|
||||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
</ion-card>
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a id="install-cert" href="/eos/local.crt"></a>
|
<ng-template #trusted>
|
||||||
|
<div tuiCardLarge tuiSurface="elevated" class="card">
|
||||||
|
<tui-icon
|
||||||
|
icon="tuiIconShield"
|
||||||
|
tuiAppearance="icon-success"
|
||||||
|
[style.font-size.rem]="4"
|
||||||
|
/>
|
||||||
|
<h1>Root CA Trusted!</h1>
|
||||||
|
<p>
|
||||||
|
You have successfully trusted your server's Root CA and may now log in
|
||||||
|
securely.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
tuiButton
|
||||||
|
appearance="tertiary-solid"
|
||||||
|
iconRight="tuiIconExternalLink"
|
||||||
|
(click)="launchHttps()"
|
||||||
|
>
|
||||||
|
Go to login
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|||||||
@@ -1,12 +1,4 @@
|
|||||||
#trusted {
|
:host {
|
||||||
max-width: 40%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#untrusted {
|
|
||||||
max-width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center-container {
|
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -15,69 +7,38 @@
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-card {
|
[tuiButton] {
|
||||||
color: var(--ion-color-dark);
|
text-transform: uppercase;
|
||||||
background: #414141;
|
font-weight: bold;
|
||||||
box-shadow: 0 4px 4px rgba(17, 17, 17, 0.144);
|
border-radius: 10rem;
|
||||||
border-radius: 35px;
|
margin-top: 1rem;
|
||||||
padding: 1.5rem;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
padding-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 21px;
|
|
||||||
line-height: 25px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-center {
|
.card {
|
||||||
|
max-width: max(70%, 40rem);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
margin: 0 0 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
ol {
|
ol {
|
||||||
font-size: 17px;
|
font-size: 1rem;
|
||||||
line-height: 25px;
|
line-height: 1.5rem;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
li {
|
|
||||||
padding-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-button {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.refresh {
|
li {
|
||||||
--background: var(--ion-color-success-shade);
|
padding-bottom: 1.5rem;
|
||||||
}
|
|
||||||
|
|
||||||
.wiz-icon {
|
|
||||||
font-size: 64px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skip_detail {
|
|
||||||
display: block;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
margin-top: -13px;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 700px) {
|
|
||||||
#trusted, #untrusted {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 701px) and (max-width: 1200px) {
|
|
||||||
#trusted, #untrusted {
|
|
||||||
max-width: 75%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,25 +1,37 @@
|
|||||||
import { Component, Inject } from '@angular/core'
|
import { CommonModule, DOCUMENT } from '@angular/common'
|
||||||
|
import { Component, inject } from '@angular/core'
|
||||||
|
import { RELATIVE_URL } from '@start9labs/shared'
|
||||||
|
import {
|
||||||
|
TuiAppearanceModule,
|
||||||
|
TuiButtonModule,
|
||||||
|
TuiCardModule,
|
||||||
|
TuiIconModule,
|
||||||
|
TuiSurfaceModule,
|
||||||
|
} from '@taiga-ui/experimental'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
import { RELATIVE_URL } from '@start9labs/shared'
|
|
||||||
import { DOCUMENT } from '@angular/common'
|
|
||||||
import { WINDOW } from '@ng-web-apis/common'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
standalone: true,
|
||||||
selector: 'ca-wizard',
|
selector: 'ca-wizard',
|
||||||
templateUrl: './ca-wizard.component.html',
|
templateUrl: './ca-wizard.component.html',
|
||||||
styleUrls: ['./ca-wizard.component.scss'],
|
styleUrls: ['./ca-wizard.component.scss'],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
TuiIconModule,
|
||||||
|
TuiButtonModule,
|
||||||
|
TuiAppearanceModule,
|
||||||
|
TuiCardModule,
|
||||||
|
TuiSurfaceModule,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class CAWizardComponent {
|
export class CAWizardComponent {
|
||||||
caTrusted = false
|
private readonly api = inject(ApiService)
|
||||||
|
private readonly relativeUrl = inject(RELATIVE_URL)
|
||||||
|
private readonly document = inject(DOCUMENT)
|
||||||
|
|
||||||
constructor(
|
readonly config = inject(ConfigService)
|
||||||
private readonly api: ApiService,
|
caTrusted = false
|
||||||
public readonly config: ConfigService,
|
|
||||||
@Inject(RELATIVE_URL) private readonly relativeUrl: string,
|
|
||||||
@Inject(DOCUMENT) public readonly document: Document,
|
|
||||||
@Inject(WINDOW) private readonly windowRef: Window,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
await this.testHttps().catch(e =>
|
await this.testHttps().catch(e =>
|
||||||
@@ -27,17 +39,12 @@ export class CAWizardComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
download() {
|
|
||||||
this.document.getElementById('install-cert')?.click()
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
this.document.location.reload()
|
this.document.location.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
launchHttps() {
|
launchHttps() {
|
||||||
const host = this.config.getHost()
|
this.document.defaultView?.open(`https://${this.config.getHost()}`, '_self')
|
||||||
this.windowRef.open(`https://${host}`, '_self')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async testHttps() {
|
private async testHttps() {
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { RouterModule, Routes } from '@angular/router'
|
|
||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
|
import { NgModule } from '@angular/core'
|
||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { IonicModule } from '@ionic/angular'
|
import { RouterModule, Routes } from '@angular/router'
|
||||||
import { LoginPage } from './login.page'
|
import { TuiErrorModule, TuiTextfieldControllerModule } from '@taiga-ui/core'
|
||||||
|
import {
|
||||||
|
TuiButtonModule,
|
||||||
|
TuiCardModule,
|
||||||
|
TuiSurfaceModule,
|
||||||
|
} from '@taiga-ui/experimental'
|
||||||
|
import { TuiInputPasswordModule } from '@taiga-ui/kit'
|
||||||
import { CAWizardComponent } from './ca-wizard/ca-wizard.component'
|
import { CAWizardComponent } from './ca-wizard/ca-wizard.component'
|
||||||
import { SharedPipesModule } from '@start9labs/shared'
|
import { LoginPage } from './login.page'
|
||||||
import { TuiHintModule, TuiTooltipModule } from '@taiga-ui/core'
|
import { LoginWarningComponent } from './warning.component'
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@@ -19,12 +24,16 @@ const routes: Routes = [
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
IonicModule,
|
CAWizardComponent,
|
||||||
SharedPipesModule,
|
LoginWarningComponent,
|
||||||
|
TuiButtonModule,
|
||||||
|
TuiCardModule,
|
||||||
|
TuiSurfaceModule,
|
||||||
|
TuiInputPasswordModule,
|
||||||
|
TuiTextfieldControllerModule,
|
||||||
|
TuiErrorModule,
|
||||||
RouterModule.forChild(routes),
|
RouterModule.forChild(routes),
|
||||||
TuiTooltipModule,
|
|
||||||
TuiHintModule,
|
|
||||||
],
|
],
|
||||||
declarations: [LoginPage, CAWizardComponent],
|
declarations: [LoginPage],
|
||||||
})
|
})
|
||||||
export class LoginPageModule {}
|
export class LoginPageModule {}
|
||||||
|
|||||||
@@ -1,93 +1,26 @@
|
|||||||
<ion-content class="content">
|
<!-- Local HTTP -->
|
||||||
<!-- Local HTTP -->
|
<ca-wizard *ngIf="config.isLanHttp(); else notLanHttp"></ca-wizard>
|
||||||
<ng-container *ngIf="config.isLanHttp(); else notLanHttp">
|
|
||||||
<ca-wizard></ca-wizard>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<!-- not Local HTTP -->
|
<!-- not Local HTTP -->
|
||||||
<ng-template #notLanHttp>
|
<ng-template #notLanHttp>
|
||||||
<div *ngIf="config.isTorHttp()" class="banner">
|
<login-warning />
|
||||||
<ion-item color="warning">
|
|
||||||
<ion-icon slot="start" name="warning-outline"></ion-icon>
|
|
||||||
<ion-label>
|
|
||||||
<h2 style="font-weight: bold">Http detected</h2>
|
|
||||||
<p style="font-weight: 600">
|
|
||||||
Tor is faster over https. Your Root CA must be trusted.
|
|
||||||
<a
|
|
||||||
href="https://docs.start9.com/0.3.5.x/user-manual/trust-ca"
|
|
||||||
target="_blank"
|
|
||||||
noreferrer
|
|
||||||
style="color: black"
|
|
||||||
>
|
|
||||||
View instructions
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</ion-label>
|
|
||||||
<ion-button slot="end" color="light" (click)="launchHttps()">
|
|
||||||
Open Https
|
|
||||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
</ion-item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ion-grid class="grid">
|
<div tuiCardLarge tuiSurface="elevated" class="card">
|
||||||
<ion-row class="row">
|
<img alt="StartOS Icon" class="logo" src="assets/img/icon.png" />
|
||||||
<ion-col>
|
<h1 class="header">Login to StartOS</h1>
|
||||||
<ion-card>
|
<form (submit)="submit()">
|
||||||
<img
|
<tui-input-password
|
||||||
alt="StartOS Icon"
|
tuiTextfieldIconLeft="tuiIconKeyLarge"
|
||||||
class="header-icon"
|
[ngModelOptions]="{ standalone: true }"
|
||||||
src="assets/img/icon.png"
|
[(ngModel)]="password"
|
||||||
/>
|
(ngModelChange)="error = ''"
|
||||||
<ion-card-header>
|
>
|
||||||
<ion-card-title class="title">Login to StartOS</ion-card-title>
|
Password
|
||||||
</ion-card-header>
|
</tui-input-password>
|
||||||
<ion-card-content class="ion-margin">
|
<tui-error class="error" [error]="error || null" />
|
||||||
<form (submit)="submit()">
|
<button tuiButton class="button" appearance="tertiary-solid">
|
||||||
<ion-item color="dark" fill="solid">
|
Login
|
||||||
<ion-icon
|
</button>
|
||||||
slot="start"
|
</form>
|
||||||
size="small"
|
</div>
|
||||||
color="base"
|
</ng-template>
|
||||||
name="key-outline"
|
|
||||||
style="margin-right: 16px"
|
|
||||||
></ion-icon>
|
|
||||||
<ion-input
|
|
||||||
name="password"
|
|
||||||
placeholder="Password"
|
|
||||||
[type]="unmasked ? 'text' : 'password'"
|
|
||||||
[(ngModel)]="password"
|
|
||||||
(ionChange)="error = ''"
|
|
||||||
></ion-input>
|
|
||||||
<ion-button
|
|
||||||
slot="end"
|
|
||||||
fill="clear"
|
|
||||||
color="dark"
|
|
||||||
(click)="unmasked = !unmasked"
|
|
||||||
>
|
|
||||||
<ion-icon
|
|
||||||
slot="icon-only"
|
|
||||||
size="small"
|
|
||||||
[name]="unmasked ? 'eye-off-outline' : 'eye-outline'"
|
|
||||||
></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
</ion-item>
|
|
||||||
<p class="error ion-text-center">
|
|
||||||
<ion-text color="danger">{{ error }}</ion-text>
|
|
||||||
</p>
|
|
||||||
<ion-button
|
|
||||||
class="login-button"
|
|
||||||
type="submit"
|
|
||||||
expand="block"
|
|
||||||
color="tertiary"
|
|
||||||
>
|
|
||||||
Login
|
|
||||||
</ion-button>
|
|
||||||
</form>
|
|
||||||
</ion-card-content>
|
|
||||||
</ion-card>
|
|
||||||
</ion-col>
|
|
||||||
</ion-row>
|
|
||||||
</ion-grid>
|
|
||||||
</ng-template>
|
|
||||||
</ion-content>
|
|
||||||
|
|||||||
@@ -1,76 +1,35 @@
|
|||||||
.content {
|
@import '@taiga-ui/core/styles/taiga-ui-local';
|
||||||
--background: #333333;
|
|
||||||
|
:host {
|
||||||
|
background: var(--tui-base-02);
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid {
|
.card {
|
||||||
height: 100%;
|
@include center-all();
|
||||||
max-width: 540px;
|
overflow: visible;
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
height: 100%;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
width: max(50%, 20rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.banner {
|
.logo {
|
||||||
position: absolute;
|
@include center-left();
|
||||||
padding: 20px;
|
top: -17%;
|
||||||
width: 100%;
|
width: 6rem;
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
ion-item {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-card {
|
|
||||||
background: #414141;
|
|
||||||
box-shadow: 0 4px 4px rgba(17, 17, 17, 0.144);
|
|
||||||
border-radius: 35px;
|
|
||||||
min-height: 16rem;
|
|
||||||
contain: unset;
|
|
||||||
overflow: unset;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-item {
|
|
||||||
--background: transparent;
|
|
||||||
--border-radius: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
padding-top: 55px;
|
|
||||||
color: #e0e0e0;
|
|
||||||
font-size: 1.3rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
&-icon {
|
margin: 2rem 0 1rem;
|
||||||
width: 100px;
|
text-align: center;
|
||||||
position: absolute;
|
font-size: 2rem;
|
||||||
left: 50%;
|
|
||||||
margin-left: -50px;
|
|
||||||
top: -17%;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-button {
|
|
||||||
height: 45px;
|
|
||||||
width: 120px;
|
|
||||||
--border-radius: 50px;
|
|
||||||
margin: 0 auto;
|
|
||||||
margin-top: 27px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-interactive {
|
|
||||||
--highlight-background: #5260ff !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
display: block;
|
min-height: 2.5rem;
|
||||||
padding-top: 4px;
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
width: 10rem;
|
||||||
|
border-radius: 10rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { LoadingService } from '@start9labs/shared'
|
|||||||
import { TuiDestroyService } from '@taiga-ui/cdk'
|
import { TuiDestroyService } from '@taiga-ui/cdk'
|
||||||
import { takeUntil } from 'rxjs'
|
import { takeUntil } from 'rxjs'
|
||||||
import { DOCUMENT } from '@angular/common'
|
import { DOCUMENT } from '@angular/common'
|
||||||
import { WINDOW } from '@ng-web-apis/common'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'login',
|
selector: 'login',
|
||||||
@@ -18,7 +17,6 @@ import { WINDOW } from '@ng-web-apis/common'
|
|||||||
})
|
})
|
||||||
export class LoginPage {
|
export class LoginPage {
|
||||||
password = ''
|
password = ''
|
||||||
unmasked = false
|
|
||||||
error = ''
|
error = ''
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -29,14 +27,8 @@ export class LoginPage {
|
|||||||
private readonly api: ApiService,
|
private readonly api: ApiService,
|
||||||
public readonly config: ConfigService,
|
public readonly config: ConfigService,
|
||||||
@Inject(DOCUMENT) public readonly document: Document,
|
@Inject(DOCUMENT) public readonly document: Document,
|
||||||
@Inject(WINDOW) private readonly windowRef: Window,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
launchHttps() {
|
|
||||||
const host = this.config.getHost()
|
|
||||||
this.windowRef.open(`https://${host}`, '_self')
|
|
||||||
}
|
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
this.error = ''
|
this.error = ''
|
||||||
|
|
||||||
|
|||||||
60
web/projects/ui/src/app/apps/login/warning.component.ts
Normal file
60
web/projects/ui/src/app/apps/login/warning.component.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { NgIf } from '@angular/common'
|
||||||
|
import { Component, inject } from '@angular/core'
|
||||||
|
import { WINDOW } from '@ng-web-apis/common'
|
||||||
|
import { TuiNotificationModule } from '@taiga-ui/core'
|
||||||
|
import { TuiButtonModule, TuiIconsModule } from '@taiga-ui/experimental'
|
||||||
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'login-warning',
|
||||||
|
template: `
|
||||||
|
<tui-notification *ngIf="config.isTorHttp()" status="warning">
|
||||||
|
<button
|
||||||
|
tuiButton
|
||||||
|
size="s"
|
||||||
|
appearance="neutral"
|
||||||
|
iconRight="tuiIconExternalLink"
|
||||||
|
(click)="launchHttps()"
|
||||||
|
>
|
||||||
|
Open Https
|
||||||
|
</button>
|
||||||
|
<h2><strong>Http detected</strong></h2>
|
||||||
|
<p>
|
||||||
|
Tor is faster over https. Your Root CA must be trusted.
|
||||||
|
<a
|
||||||
|
href="https://docs.start9.com/0.3.5.x/user-manual/trust-ca"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
View instructions
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</tui-notification>
|
||||||
|
`,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
@import '@taiga-ui/core/styles/taiga-ui-local';
|
||||||
|
|
||||||
|
:host {
|
||||||
|
@include center-left();
|
||||||
|
top: 1rem;
|
||||||
|
width: max(50%, 20rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
float: right;
|
||||||
|
margin: 0.5rem 0 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
imports: [NgIf, TuiButtonModule, TuiIconsModule, TuiNotificationModule],
|
||||||
|
})
|
||||||
|
export class LoginWarningComponent {
|
||||||
|
private readonly windowRef = inject(WINDOW)
|
||||||
|
readonly config = inject(ConfigService)
|
||||||
|
|
||||||
|
launchHttps() {
|
||||||
|
this.windowRef.open(`https://${this.config.getHost()}`, '_self')
|
||||||
|
}
|
||||||
|
}
|
||||||
166
web/projects/ui/src/app/apps/portal/components/form.component.ts
Normal file
166
web/projects/ui/src/app/apps/portal/components/form.component.ts
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
import { CommonModule } from '@angular/common'
|
||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
inject,
|
||||||
|
Input,
|
||||||
|
OnInit,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { FormGroup, ReactiveFormsModule } from '@angular/forms'
|
||||||
|
import { RouterModule } from '@angular/router'
|
||||||
|
import { InputSpec } from '@start9labs/start-sdk/lib/config/configTypes'
|
||||||
|
import {
|
||||||
|
tuiMarkControlAsTouchedAndValidate,
|
||||||
|
TuiValueChangesModule,
|
||||||
|
} from '@taiga-ui/cdk'
|
||||||
|
import { TuiDialogContext, TuiModeModule } from '@taiga-ui/core'
|
||||||
|
import { TuiButtonModule } from '@taiga-ui/experimental'
|
||||||
|
import { TuiDialogFormService } from '@taiga-ui/kit'
|
||||||
|
import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus'
|
||||||
|
import { compare, Operation } from 'fast-json-patch'
|
||||||
|
import { FormModule } from 'src/app/common/form/form.module'
|
||||||
|
import { InvalidService } from 'src/app/common/form/invalid.service'
|
||||||
|
import { FormService } from 'src/app/services/form.service'
|
||||||
|
|
||||||
|
export interface ActionButton<T> {
|
||||||
|
text: string
|
||||||
|
handler?: (value: T) => Promise<boolean | void> | void
|
||||||
|
link?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormContext<T> {
|
||||||
|
spec: InputSpec
|
||||||
|
buttons: ActionButton<T>[]
|
||||||
|
value?: T
|
||||||
|
patch?: Operation[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'app-form',
|
||||||
|
template: `
|
||||||
|
<form
|
||||||
|
[formGroup]="form"
|
||||||
|
(submit.capture.prevent)="(0)"
|
||||||
|
(reset.capture.prevent.stop)="onReset()"
|
||||||
|
(tuiValueChanges)="markAsDirty()"
|
||||||
|
>
|
||||||
|
<form-group [spec]="spec" />
|
||||||
|
<footer tuiMode="onDark">
|
||||||
|
<ng-content />
|
||||||
|
<ng-container *ngFor="let button of buttons; let last = last">
|
||||||
|
<button
|
||||||
|
*ngIf="button.handler; else link"
|
||||||
|
tuiButton
|
||||||
|
[appearance]="last ? 'primary' : 'flat'"
|
||||||
|
[type]="last ? 'submit' : 'button'"
|
||||||
|
(click)="onClick(button.handler)"
|
||||||
|
>
|
||||||
|
{{ button.text }}
|
||||||
|
</button>
|
||||||
|
<ng-template #link>
|
||||||
|
<a
|
||||||
|
tuiButton
|
||||||
|
appearance="flat"
|
||||||
|
[routerLink]="button.link"
|
||||||
|
(click)="close()"
|
||||||
|
>
|
||||||
|
{{ button.text }}
|
||||||
|
</a>
|
||||||
|
</ng-template>
|
||||||
|
</ng-container>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
`,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
footer {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 1rem 0;
|
||||||
|
margin: 1rem 0 -1rem;
|
||||||
|
gap: 1rem;
|
||||||
|
background: var(--tui-elevation-01);
|
||||||
|
border-top: 1px solid var(--tui-base-02);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
RouterModule,
|
||||||
|
TuiValueChangesModule,
|
||||||
|
TuiButtonModule,
|
||||||
|
TuiModeModule,
|
||||||
|
FormModule,
|
||||||
|
],
|
||||||
|
providers: [InvalidService],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class FormComponent<T extends Record<string, any>> implements OnInit {
|
||||||
|
private readonly dialogFormService = inject(TuiDialogFormService)
|
||||||
|
private readonly formService = inject(FormService)
|
||||||
|
private readonly invalidService = inject(InvalidService)
|
||||||
|
private readonly context = inject<TuiDialogContext<void, FormContext<T>>>(
|
||||||
|
POLYMORPHEUS_CONTEXT,
|
||||||
|
{ optional: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
@Input() spec = this.context?.data.spec || {}
|
||||||
|
@Input() buttons = this.context?.data.buttons || []
|
||||||
|
@Input() patch = this.context?.data.patch || []
|
||||||
|
@Input() value?: T = this.context?.data.value
|
||||||
|
|
||||||
|
form = new FormGroup({})
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.dialogFormService.markAsPristine()
|
||||||
|
this.form = this.formService.createForm(this.spec, this.value)
|
||||||
|
this.process(this.patch)
|
||||||
|
}
|
||||||
|
|
||||||
|
onReset() {
|
||||||
|
const { value } = this.form
|
||||||
|
|
||||||
|
this.form = this.formService.createForm(this.spec)
|
||||||
|
this.process(compare(this.form.value, value))
|
||||||
|
tuiMarkControlAsTouchedAndValidate(this.form)
|
||||||
|
this.markAsDirty()
|
||||||
|
}
|
||||||
|
|
||||||
|
async onClick(handler: Required<ActionButton<T>>['handler']) {
|
||||||
|
tuiMarkControlAsTouchedAndValidate(this.form)
|
||||||
|
this.invalidService.scrollIntoView()
|
||||||
|
|
||||||
|
if (this.form.valid && (await handler(this.form.value as T))) {
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
markAsDirty() {
|
||||||
|
this.dialogFormService.markAsDirty()
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.context?.$implicit.complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
private process(patch: Operation[]) {
|
||||||
|
patch.forEach(({ op, path }) => {
|
||||||
|
const control = this.form.get(path.substring(1).split('/'))
|
||||||
|
|
||||||
|
if (!control || !control.parent) return
|
||||||
|
|
||||||
|
if (op !== 'remove') {
|
||||||
|
control.markAsDirty()
|
||||||
|
control.markAsTouched()
|
||||||
|
}
|
||||||
|
|
||||||
|
control.parent.markAsDirty()
|
||||||
|
control.parent.markAsTouched()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,9 +12,12 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
|||||||
template: `
|
template: `
|
||||||
<ng-content />
|
<ng-content />
|
||||||
@if (connection$ | async; as connection) {
|
@if (connection$ | async; as connection) {
|
||||||
|
<!-- data-connection is used to display color indicator in the header through :has() -->
|
||||||
<tui-icon
|
<tui-icon
|
||||||
[icon]="connection.icon"
|
[icon]="connection.icon"
|
||||||
[style.color]="connection.color"
|
[style.color]="connection.color"
|
||||||
|
[style.font-size.em]="1.5"
|
||||||
|
[attr.data-connection]="connection.status"
|
||||||
></tui-icon>
|
></tui-icon>
|
||||||
{{ connection.message }}
|
{{ connection.message }}
|
||||||
}
|
}
|
||||||
@@ -27,6 +30,11 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
padding: 0 2rem;
|
padding: 0 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host-context(tui-root._mobile) {
|
||||||
|
display: none;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
],
|
],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
@@ -37,6 +45,7 @@ export class HeaderConnectionComponent {
|
|||||||
message: string
|
message: string
|
||||||
color: string
|
color: string
|
||||||
icon: string
|
icon: string
|
||||||
|
status: string
|
||||||
}> = combineLatest([
|
}> = combineLatest([
|
||||||
inject(ConnectionService).networkConnected$,
|
inject(ConnectionService).networkConnected$,
|
||||||
inject(ConnectionService).websocketConnected$.pipe(startWith(false)),
|
inject(ConnectionService).websocketConnected$.pipe(startWith(false)),
|
||||||
@@ -50,30 +59,35 @@ export class HeaderConnectionComponent {
|
|||||||
message: 'No Internet',
|
message: 'No Internet',
|
||||||
color: 'var(--tui-error-fill)',
|
color: 'var(--tui-error-fill)',
|
||||||
icon: 'tuiIconCloudOff',
|
icon: 'tuiIconCloudOff',
|
||||||
|
status: 'error',
|
||||||
}
|
}
|
||||||
if (!websocket)
|
if (!websocket)
|
||||||
return {
|
return {
|
||||||
message: 'Connecting',
|
message: 'Connecting',
|
||||||
color: 'var(--tui-warning-fill)',
|
color: 'var(--tui-warning-fill)',
|
||||||
icon: 'tuiIconCloudOff',
|
icon: 'tuiIconCloudOff',
|
||||||
|
status: 'warning',
|
||||||
}
|
}
|
||||||
if (status['shutting-down'])
|
if (status['shutting-down'])
|
||||||
return {
|
return {
|
||||||
message: 'Shutting Down',
|
message: 'Shutting Down',
|
||||||
color: 'var(--tui-neutral-fill)',
|
color: 'var(--tui-neutral-fill)',
|
||||||
icon: 'tuiIconPower',
|
icon: 'tuiIconPower',
|
||||||
|
status: 'neutral',
|
||||||
}
|
}
|
||||||
if (status.restarting)
|
if (status.restarting)
|
||||||
return {
|
return {
|
||||||
message: 'Restarting',
|
message: 'Restarting',
|
||||||
color: 'var(--tui-neutral-fill)',
|
color: 'var(--tui-neutral-fill)',
|
||||||
icon: 'tuiIconPower',
|
icon: 'tuiIconPower',
|
||||||
|
status: 'neutral',
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: 'Connected',
|
message: 'Connected',
|
||||||
color: 'var(--tui-success-fill)',
|
color: 'var(--tui-success-fill)',
|
||||||
icon: 'tuiIconCloud',
|
icon: 'tuiIconCloud',
|
||||||
|
status: 'success',
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,31 +1,47 @@
|
|||||||
import { AsyncPipe } from '@angular/common'
|
import { AsyncPipe } from '@angular/common'
|
||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||||
import { RouterLink, RouterLinkActive } from '@angular/router'
|
import {
|
||||||
|
IsActiveMatchOptions,
|
||||||
|
RouterLink,
|
||||||
|
RouterLinkActive,
|
||||||
|
} from '@angular/router'
|
||||||
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { HeaderConnectionComponent } from './connection.component'
|
import { HeaderConnectionComponent } from './connection.component'
|
||||||
import { HeaderHomeComponent } from './home.component'
|
import { HeaderHomeComponent } from './home.component'
|
||||||
import { HeaderCornerComponent } from './corner.component'
|
import { HeaderCornerComponent } from './corner.component'
|
||||||
import { HeaderBreadcrumbComponent } from './breadcrumb.component'
|
import { HeaderBreadcrumbComponent } from './breadcrumb.component'
|
||||||
|
import { HeaderSnekDirective } from './snek.directive'
|
||||||
|
import { HeaderMobileComponent } from './mobile.component'
|
||||||
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { BreadcrumbsService } from '../../services/breadcrumbs.service'
|
import { BreadcrumbsService } from '../../services/breadcrumbs.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'header[appHeader]',
|
selector: 'header[appHeader]',
|
||||||
template: `
|
template: `
|
||||||
<a headerHome routerLink="/portal/desktop" routerLinkActive="active">
|
<a headerHome routerLink="/portal/desktop" routerLinkActive="active">
|
||||||
<div class="plank"></div>
|
<div class="plaque"></div>
|
||||||
</a>
|
</a>
|
||||||
@for (item of breadcrumbs$ | async; track $index) {
|
@for (item of breadcrumbs$ | async; track $index) {
|
||||||
<a
|
<a
|
||||||
routerLinkActive="active"
|
routerLinkActive="active"
|
||||||
[routerLink]="item.routerLink"
|
[routerLink]="item.routerLink"
|
||||||
[routerLinkActiveOptions]="{ exact: true }"
|
[routerLinkActiveOptions]="options"
|
||||||
[headerBreadcrumb]="item"
|
[headerBreadcrumb]="item"
|
||||||
>
|
>
|
||||||
<div class="plank"></div>
|
<div class="plaque"></div>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
<div [style.flex]="1"><div class="plank"></div></div>
|
<div [style.flex]="1" [headerMobile]="breadcrumbs$ | async">
|
||||||
<header-connection><div class="plank"></div></header-connection>
|
<div class="plaque"></div>
|
||||||
<header-corner><div class="plank"></div></header-corner>
|
<img
|
||||||
|
[appSnek]="(snekScore$ | async) || 0"
|
||||||
|
class="snek"
|
||||||
|
alt="Play Snake"
|
||||||
|
src="assets/img/icons/snek.png"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<header-connection><div class="plaque"></div></header-connection>
|
||||||
|
<header-corner><div class="plaque"></div></header-corner>
|
||||||
`,
|
`,
|
||||||
styles: [
|
styles: [
|
||||||
`
|
`
|
||||||
@@ -53,9 +69,43 @@ import { BreadcrumbsService } from '../../services/breadcrumbs.service'
|
|||||||
backdrop-filter: blur(2rem) brightness(0.75) saturate(0.75);
|
backdrop-filter: blur(2rem) brightness(0.75) saturate(0.75);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:has([data-connection='error']) {
|
||||||
|
--status: var(--tui-error-fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has([data-connection='warning']) {
|
||||||
|
--status: var(--tui-warning-fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has([data-connection='neutral']) {
|
||||||
|
--status: var(--tui-neutral-fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has([data-connection='success']) {
|
||||||
|
--status: var(--tui-success-fill);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.plank {
|
header-connection .plaque::before {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px rgba(255, 255, 255, 0.25),
|
||||||
|
inset 0 -0.25rem var(--tui-success-fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(tui-root._mobile) {
|
||||||
|
a {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
header-corner .plaque::before {
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px rgb(255 255 255 / 25%),
|
||||||
|
inset -0.375rem 0 var(--status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.plaque {
|
||||||
@include transition(opacity);
|
@include transition(opacity);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@@ -83,6 +133,19 @@ import { BreadcrumbsService } from '../../services/breadcrumbs.service'
|
|||||||
box-shadow: inset 0 1px rgb(255 255 255 / 25%);
|
box-shadow: inset 0 1px rgb(255 255 255 / 25%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.snek {
|
||||||
|
@include center-top();
|
||||||
|
@include transition(opacity);
|
||||||
|
right: 2rem;
|
||||||
|
width: 1rem;
|
||||||
|
opacity: 0.2;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@@ -90,13 +153,29 @@ import { BreadcrumbsService } from '../../services/breadcrumbs.service'
|
|||||||
imports: [
|
imports: [
|
||||||
RouterLink,
|
RouterLink,
|
||||||
RouterLinkActive,
|
RouterLinkActive,
|
||||||
|
AsyncPipe,
|
||||||
HeaderConnectionComponent,
|
HeaderConnectionComponent,
|
||||||
HeaderHomeComponent,
|
HeaderHomeComponent,
|
||||||
HeaderCornerComponent,
|
HeaderCornerComponent,
|
||||||
AsyncPipe,
|
HeaderSnekDirective,
|
||||||
HeaderBreadcrumbComponent,
|
HeaderBreadcrumbComponent,
|
||||||
|
HeaderMobileComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class HeaderComponent {
|
export class HeaderComponent {
|
||||||
|
readonly options = OPTIONS
|
||||||
readonly breadcrumbs$ = inject(BreadcrumbsService)
|
readonly breadcrumbs$ = inject(BreadcrumbsService)
|
||||||
|
readonly snekScore$ = inject(PatchDB<DataModel>).watch$(
|
||||||
|
'ui',
|
||||||
|
'gaming',
|
||||||
|
'snake',
|
||||||
|
'high-score',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const OPTIONS: IsActiveMatchOptions = {
|
||||||
|
paths: 'exact',
|
||||||
|
queryParams: 'ignored',
|
||||||
|
fragment: 'ignored',
|
||||||
|
matrixParams: 'ignored',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { AuthService } from 'src/app/services/auth.service'
|
|||||||
import { ABOUT } from './about.component'
|
import { ABOUT } from './about.component'
|
||||||
import { getAllPackages } from 'src/app/util/get-package-data'
|
import { getAllPackages } from 'src/app/util/get-package-data'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
|
import { HeaderConnectionComponent } from './connection.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'header-menu',
|
selector: 'header-menu',
|
||||||
@@ -26,7 +27,9 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
|||||||
</button>
|
</button>
|
||||||
<ng-template #content>
|
<ng-template #content>
|
||||||
<tui-data-list>
|
<tui-data-list>
|
||||||
<h3 class="title">StartOS</h3>
|
<header-connection class="status">
|
||||||
|
<h3 class="title">StartOS</h3>
|
||||||
|
</header-connection>
|
||||||
<button tuiOption class="item" (click)="about()">
|
<button tuiOption class="item" (click)="about()">
|
||||||
<tui-icon icon="tuiIconInfo" />
|
<tui-icon icon="tuiIconInfo" />
|
||||||
About this server
|
About this server
|
||||||
@@ -75,9 +78,16 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
|||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
display: flex !important;
|
||||||
|
font-size: 0;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
height: 2rem;
|
||||||
|
width: 14rem;
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin: 0;
|
margin: 0 auto 0 0;
|
||||||
padding: 0 0.5rem 0.25rem;
|
|
||||||
font: var(--tui-font-text-l);
|
font: var(--tui-font-text-l);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
@@ -96,6 +106,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
|||||||
TuiSvgModule,
|
TuiSvgModule,
|
||||||
TuiButtonModule,
|
TuiButtonModule,
|
||||||
TuiIconModule,
|
TuiIconModule,
|
||||||
|
HeaderConnectionComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class HeaderMenuComponent {
|
export class HeaderMenuComponent {
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
|
import { TuiIconModule } from '@taiga-ui/experimental'
|
||||||
|
import { Breadcrumb } from '../../services/breadcrumbs.service'
|
||||||
|
import { RouterLink } from '@angular/router'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: '[headerMobile]',
|
||||||
|
template: `
|
||||||
|
@if (headerMobile?.length) {
|
||||||
|
<a [routerLink]="back" [style.padding.rem]="0.75">
|
||||||
|
<tui-icon icon="tuiIconArrowLeft" />
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
<span class="title">{{ title }}</span>
|
||||||
|
<ng-content />
|
||||||
|
`,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
@import '@taiga-ui/core/styles/taiga-ui-local';
|
||||||
|
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(tui-root._mobile) {
|
||||||
|
margin: 0;
|
||||||
|
--clip-path: polygon(
|
||||||
|
0% 0%,
|
||||||
|
calc(100% - 1.75rem) 0%,
|
||||||
|
100% 100%,
|
||||||
|
0% 100%
|
||||||
|
);
|
||||||
|
|
||||||
|
> * {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
@include text-overflow();
|
||||||
|
max-width: calc(100% - 5rem);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
imports: [TuiIconModule, RouterLink],
|
||||||
|
})
|
||||||
|
export class HeaderMobileComponent {
|
||||||
|
@Input() headerMobile: readonly Breadcrumb[] | null = []
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return this.headerMobile?.[this.headerMobile?.length - 1]?.title || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
get back() {
|
||||||
|
return (
|
||||||
|
this.headerMobile?.[this.headerMobile?.length - 2]?.routerLink ||
|
||||||
|
'/portal/desktop'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
import { DOCUMENT } from '@angular/common'
|
||||||
|
import {
|
||||||
|
AfterViewInit,
|
||||||
|
Component,
|
||||||
|
HostListener,
|
||||||
|
inject,
|
||||||
|
OnDestroy,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { pauseFor } from '@start9labs/shared'
|
||||||
|
import { TuiButtonModule } from '@taiga-ui/experimental'
|
||||||
|
import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus'
|
||||||
|
import { TuiDialogContext } from '@taiga-ui/core'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
template: `
|
||||||
|
<div class="canvas-center">
|
||||||
|
<canvas id="game"></canvas>
|
||||||
|
</div>
|
||||||
|
<footer class="footer">
|
||||||
|
<strong>Score: {{ score }}</strong>
|
||||||
|
<span>High Score: {{ highScore }}</span>
|
||||||
|
<button tuiButton (click)="dismiss()">Save and Quit</button>
|
||||||
|
</footer>
|
||||||
|
`,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
.canvas-center {
|
||||||
|
min-height: 50vh;
|
||||||
|
padding-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-top: 32px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
imports: [TuiButtonModule],
|
||||||
|
})
|
||||||
|
export class HeaderSnekComponent implements AfterViewInit, OnDestroy {
|
||||||
|
private readonly document = inject(DOCUMENT)
|
||||||
|
private readonly dialog = inject(POLYMORPHEUS_CONTEXT) as TuiDialogContext<
|
||||||
|
number,
|
||||||
|
number
|
||||||
|
>
|
||||||
|
|
||||||
|
highScore: number = this.dialog.data
|
||||||
|
score = 0
|
||||||
|
|
||||||
|
private readonly speed = 45
|
||||||
|
private readonly width = 40
|
||||||
|
private readonly height = 26
|
||||||
|
private grid = NaN
|
||||||
|
|
||||||
|
private readonly startingLength = 4
|
||||||
|
|
||||||
|
private xDown?: number
|
||||||
|
private yDown?: number
|
||||||
|
private canvas!: HTMLCanvasElement
|
||||||
|
private image!: HTMLImageElement
|
||||||
|
private context!: CanvasRenderingContext2D
|
||||||
|
|
||||||
|
private snake: any
|
||||||
|
private bitcoin: { x: number; y: number } = { x: NaN, y: NaN }
|
||||||
|
|
||||||
|
private moveQueue: String[] = []
|
||||||
|
private destroyed = false
|
||||||
|
|
||||||
|
dismiss() {
|
||||||
|
this.dialog.completeWith(this.highScore)
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('document:keydown', ['$event'])
|
||||||
|
keyEvent(e: KeyboardEvent) {
|
||||||
|
this.moveQueue.push(e.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('touchstart', ['$event'])
|
||||||
|
touchStart(e: TouchEvent) {
|
||||||
|
this.handleTouchStart(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('touchmove', ['$event'])
|
||||||
|
touchMove(e: TouchEvent) {
|
||||||
|
this.handleTouchMove(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('window:resize')
|
||||||
|
sizeChange() {
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.destroyed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.init()
|
||||||
|
|
||||||
|
this.image = new Image()
|
||||||
|
this.image.onload = () => {
|
||||||
|
requestAnimationFrame(async () => await this.loop())
|
||||||
|
}
|
||||||
|
this.image.src = '../../../../../../assets/img/icons/bitcoin.svg'
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.canvas = this.document.querySelector('canvas#game')!
|
||||||
|
this.canvas.style.border = '1px solid #e0e0e0'
|
||||||
|
this.context = this.canvas.getContext('2d')!
|
||||||
|
const container = this.document.querySelector('.canvas-center')!
|
||||||
|
this.grid = Math.min(
|
||||||
|
Math.floor(container.clientWidth / this.width),
|
||||||
|
Math.floor(container.clientHeight / this.height),
|
||||||
|
)
|
||||||
|
this.snake = {
|
||||||
|
x: this.grid * (Math.floor(this.width / 2) - this.startingLength),
|
||||||
|
y: this.grid * Math.floor(this.height / 2),
|
||||||
|
// snake velocity. moves one grid length every frame in either the x or y direction
|
||||||
|
dx: this.grid,
|
||||||
|
dy: 0,
|
||||||
|
// keep track of all grids the snake body occupies
|
||||||
|
cells: [],
|
||||||
|
// length of the snake. grows when eating an bitcoin
|
||||||
|
maxCells: this.startingLength,
|
||||||
|
}
|
||||||
|
this.bitcoin = {
|
||||||
|
x: this.getRandomInt(0, this.width) * this.grid,
|
||||||
|
y: this.getRandomInt(0, this.height) * this.grid,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.canvas.width = this.grid * this.width
|
||||||
|
this.canvas.height = this.grid * this.height
|
||||||
|
this.context.imageSmoothingEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
getTouches(evt: TouchEvent) {
|
||||||
|
return evt.touches
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTouchStart(evt: TouchEvent) {
|
||||||
|
const firstTouch = this.getTouches(evt)[0]
|
||||||
|
this.xDown = firstTouch.clientX
|
||||||
|
this.yDown = firstTouch.clientY
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTouchMove(evt: TouchEvent) {
|
||||||
|
if (!this.xDown || !this.yDown) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var xUp = evt.touches[0].clientX
|
||||||
|
var yUp = evt.touches[0].clientY
|
||||||
|
|
||||||
|
var xDiff = this.xDown - xUp
|
||||||
|
var yDiff = this.yDown - yUp
|
||||||
|
|
||||||
|
if (Math.abs(xDiff) > Math.abs(yDiff)) {
|
||||||
|
/*most significant*/
|
||||||
|
if (xDiff > 0) {
|
||||||
|
this.moveQueue.push('ArrowLeft')
|
||||||
|
} else {
|
||||||
|
this.moveQueue.push('ArrowRight')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (yDiff > 0) {
|
||||||
|
this.moveQueue.push('ArrowUp')
|
||||||
|
} else {
|
||||||
|
this.moveQueue.push('ArrowDown')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* reset values */
|
||||||
|
this.xDown = undefined
|
||||||
|
this.yDown = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// game loop
|
||||||
|
async loop() {
|
||||||
|
if (this.destroyed) return
|
||||||
|
|
||||||
|
await pauseFor(this.speed)
|
||||||
|
|
||||||
|
requestAnimationFrame(async () => await this.loop())
|
||||||
|
|
||||||
|
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
||||||
|
|
||||||
|
// move snake by its velocity
|
||||||
|
this.snake.x += this.snake.dx
|
||||||
|
this.snake.y += this.snake.dy
|
||||||
|
|
||||||
|
if (this.moveQueue.length) {
|
||||||
|
const move = this.moveQueue.shift()
|
||||||
|
// left arrow key
|
||||||
|
if (move === 'ArrowLeft' && this.snake.dx === 0) {
|
||||||
|
this.snake.dx = -this.grid
|
||||||
|
this.snake.dy = 0
|
||||||
|
}
|
||||||
|
// up arrow key
|
||||||
|
else if (move === 'ArrowUp' && this.snake.dy === 0) {
|
||||||
|
this.snake.dy = -this.grid
|
||||||
|
this.snake.dx = 0
|
||||||
|
}
|
||||||
|
// right arrow key
|
||||||
|
else if (move === 'ArrowRight' && this.snake.dx === 0) {
|
||||||
|
this.snake.dx = this.grid
|
||||||
|
this.snake.dy = 0
|
||||||
|
}
|
||||||
|
// down arrow key
|
||||||
|
else if (move === 'ArrowDown' && this.snake.dy === 0) {
|
||||||
|
this.snake.dy = this.grid
|
||||||
|
this.snake.dx = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// edge death
|
||||||
|
if (
|
||||||
|
this.snake.x < 0 ||
|
||||||
|
this.snake.y < 0 ||
|
||||||
|
this.snake.x >= this.canvas.width ||
|
||||||
|
this.snake.y >= this.canvas.height
|
||||||
|
) {
|
||||||
|
this.death()
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep track of where snake has been. front of the array is always the head
|
||||||
|
this.snake.cells.unshift({ x: this.snake.x, y: this.snake.y })
|
||||||
|
|
||||||
|
// remove cells as we move away from them
|
||||||
|
if (this.snake.cells.length > this.snake.maxCells) {
|
||||||
|
this.snake.cells.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw bitcoin
|
||||||
|
this.context.fillStyle = '#ff4961'
|
||||||
|
this.context.drawImage(
|
||||||
|
this.image,
|
||||||
|
this.bitcoin.x - 1,
|
||||||
|
this.bitcoin.y - 1,
|
||||||
|
this.grid + 2,
|
||||||
|
this.grid + 2,
|
||||||
|
)
|
||||||
|
|
||||||
|
// draw snake one cell at a time
|
||||||
|
this.context.fillStyle = '#2fdf75'
|
||||||
|
|
||||||
|
const firstCell = this.snake.cells[0]
|
||||||
|
|
||||||
|
for (let index = 0; index < this.snake.cells.length; index++) {
|
||||||
|
const cell = this.snake.cells[index]
|
||||||
|
|
||||||
|
// drawing 1 px smaller than the grid creates a grid effect in the snake body so you can see how long it is
|
||||||
|
this.context.fillRect(cell.x, cell.y, this.grid - 1, this.grid - 1)
|
||||||
|
|
||||||
|
// snake ate bitcoin
|
||||||
|
if (cell.x === this.bitcoin.x && cell.y === this.bitcoin.y) {
|
||||||
|
this.score++
|
||||||
|
this.highScore = Math.max(this.score, this.highScore)
|
||||||
|
this.snake.maxCells++
|
||||||
|
|
||||||
|
this.bitcoin.x = this.getRandomInt(0, this.width) * this.grid
|
||||||
|
this.bitcoin.y = this.getRandomInt(0, this.height) * this.grid
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index > 0) {
|
||||||
|
// check collision with all cells after this one (modified bubble sort)
|
||||||
|
// snake occupies same space as a body part. reset game
|
||||||
|
if (
|
||||||
|
firstCell.x === this.snake.cells[index].x &&
|
||||||
|
firstCell.y === this.snake.cells[index].y
|
||||||
|
) {
|
||||||
|
this.death()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
death() {
|
||||||
|
this.snake.x =
|
||||||
|
this.grid * (Math.floor(this.width / 2) - this.startingLength)
|
||||||
|
this.snake.y = this.grid * Math.floor(this.height / 2)
|
||||||
|
this.snake.cells = []
|
||||||
|
this.snake.maxCells = this.startingLength
|
||||||
|
this.snake.dx = this.grid
|
||||||
|
this.snake.dy = 0
|
||||||
|
|
||||||
|
this.bitcoin.x = this.getRandomInt(0, 25) * this.grid
|
||||||
|
this.bitcoin.y = this.getRandomInt(0, 25) * this.grid
|
||||||
|
this.score = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
getRandomInt(min: number, max: number) {
|
||||||
|
return Math.floor(Math.random() * (max - min)) + min
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { Directive, HostListener, inject, Input } from '@angular/core'
|
||||||
|
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||||
|
import { TuiDialogService } from '@taiga-ui/core'
|
||||||
|
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
|
||||||
|
import { filter } from 'rxjs'
|
||||||
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
|
import { HeaderSnekComponent } from './snek.component'
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'img[appSnek]',
|
||||||
|
})
|
||||||
|
export class HeaderSnekDirective {
|
||||||
|
private readonly dialogs = inject(TuiDialogService)
|
||||||
|
private readonly loader = inject(LoadingService)
|
||||||
|
private readonly errorService = inject(ErrorService)
|
||||||
|
private readonly api = inject(ApiService)
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
appSnek = 0
|
||||||
|
|
||||||
|
@HostListener('click')
|
||||||
|
async onClick() {
|
||||||
|
this.dialogs
|
||||||
|
.open<number>(new PolymorpheusComponent(HeaderSnekComponent), {
|
||||||
|
label: 'Snake!',
|
||||||
|
closeable: false,
|
||||||
|
dismissible: false,
|
||||||
|
data: this.appSnek,
|
||||||
|
})
|
||||||
|
.pipe(filter(score => score > this.appSnek))
|
||||||
|
.subscribe(async score => {
|
||||||
|
const loader = this.loader.open('Saving high score...').subscribe()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.api.setDbValue<number>(
|
||||||
|
['gaming', 'snake', 'high-score'],
|
||||||
|
score,
|
||||||
|
)
|
||||||
|
} catch (e: any) {
|
||||||
|
this.errorService.handleError(e)
|
||||||
|
} finally {
|
||||||
|
loader.unsubscribe()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
124
web/projects/ui/src/app/apps/portal/modals/prompt.component.ts
Normal file
124
web/projects/ui/src/app/apps/portal/modals/prompt.component.ts
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { CommonModule } from '@angular/common'
|
||||||
|
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
|
import { TuiAutoFocusModule } from '@taiga-ui/cdk'
|
||||||
|
import { TuiDialogContext, TuiTextfieldControllerModule } from '@taiga-ui/core'
|
||||||
|
import { TuiButtonModule } from '@taiga-ui/experimental'
|
||||||
|
import { TuiInputModule } from '@taiga-ui/kit'
|
||||||
|
import {
|
||||||
|
POLYMORPHEUS_CONTEXT,
|
||||||
|
PolymorpheusComponent,
|
||||||
|
} from '@tinkoff/ng-polymorpheus'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
template: `
|
||||||
|
<p>{{ options.message }}</p>
|
||||||
|
<p *ngIf="options.warning" class="warning">{{ options.warning }}</p>
|
||||||
|
<form (ngSubmit)="submit(value.trim())">
|
||||||
|
<tui-input
|
||||||
|
tuiAutoFocus
|
||||||
|
[tuiTextfieldLabelOutside]="!options.label"
|
||||||
|
[tuiTextfieldCustomContent]="options.useMask ? toggle : ''"
|
||||||
|
[ngModelOptions]="{ standalone: true }"
|
||||||
|
[(ngModel)]="value"
|
||||||
|
>
|
||||||
|
{{ options.label }}
|
||||||
|
<span *ngIf="options.required !== false && options.label">*</span>
|
||||||
|
<input
|
||||||
|
tuiTextfield
|
||||||
|
[class.masked]="options.useMask && masked && value"
|
||||||
|
[placeholder]="options.placeholder || ''"
|
||||||
|
/>
|
||||||
|
</tui-input>
|
||||||
|
<footer class="g-buttons">
|
||||||
|
<button
|
||||||
|
tuiButton
|
||||||
|
type="button"
|
||||||
|
appearance="secondary"
|
||||||
|
(click)="cancel()"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button tuiButton [disabled]="!value && options.required !== false">
|
||||||
|
{{ options.buttonText || 'Submit' }}
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ng-template #toggle>
|
||||||
|
<button
|
||||||
|
tuiIconButton
|
||||||
|
type="button"
|
||||||
|
appearance="icon"
|
||||||
|
title="Toggle masking"
|
||||||
|
size="xs"
|
||||||
|
class="button"
|
||||||
|
[iconLeft]="masked ? 'tuiIconEye' : 'tuiIconEyeOff'"
|
||||||
|
(click)="masked = !masked"
|
||||||
|
></button>
|
||||||
|
</ng-template>
|
||||||
|
`,
|
||||||
|
styles: [
|
||||||
|
`
|
||||||
|
.warning {
|
||||||
|
color: var(--tui-warning-fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
pointer-events: auto;
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.masked {
|
||||||
|
font-family: text-security-disc;
|
||||||
|
-webkit-text-security: disc;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
TuiInputModule,
|
||||||
|
TuiButtonModule,
|
||||||
|
TuiTextfieldControllerModule,
|
||||||
|
TuiAutoFocusModule,
|
||||||
|
],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class PromptComponent {
|
||||||
|
masked = this.options.useMask
|
||||||
|
value = this.options.initialValue || ''
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(POLYMORPHEUS_CONTEXT)
|
||||||
|
private readonly context: TuiDialogContext<string, PromptOptions>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
get options(): PromptOptions {
|
||||||
|
return this.context.data
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.context.$implicit.complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
submit(value: string) {
|
||||||
|
if (value || !this.options.required) {
|
||||||
|
this.context.$implicit.next(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PROMPT = new PolymorpheusComponent(PromptComponent)
|
||||||
|
|
||||||
|
export interface PromptOptions {
|
||||||
|
message: string
|
||||||
|
label?: string
|
||||||
|
warning?: string
|
||||||
|
buttonText?: string
|
||||||
|
placeholder?: string
|
||||||
|
required?: boolean
|
||||||
|
useMask?: boolean
|
||||||
|
initialValue?: string | null
|
||||||
|
}
|
||||||
@@ -29,8 +29,10 @@ import { hasCurrentDeps } from 'src/app/util/has-deps'
|
|||||||
import { getAllPackages, getPackage } from 'src/app/util/get-package-data'
|
import { getAllPackages, getPackage } from 'src/app/util/get-package-data'
|
||||||
import { Breakages } from 'src/app/services/api/api.types'
|
import { Breakages } from 'src/app/services/api/api.types'
|
||||||
import { InvalidService } from 'src/app/common/form/invalid.service'
|
import { InvalidService } from 'src/app/common/form/invalid.service'
|
||||||
import { ActionButton, FormPage } from 'src/app/apps/ui/modals/form/form.page'
|
import {
|
||||||
import { FormPageModule } from 'src/app/apps/ui/modals/form/form.module'
|
ActionButton,
|
||||||
|
FormComponent,
|
||||||
|
} from 'src/app/apps/portal/components/form.component'
|
||||||
import { PackageConfigData } from '../types/package-config-data'
|
import { PackageConfigData } from '../types/package-config-data'
|
||||||
import { ConfigDepComponent } from '../components/config-dep.component'
|
import { ConfigDepComponent } from '../components/config-dep.component'
|
||||||
|
|
||||||
@@ -68,7 +70,7 @@ import { ConfigDepComponent } from '../components/config-dep.component'
|
|||||||
{{ pkg.manifest.version }}.
|
{{ pkg.manifest.version }}.
|
||||||
</tui-notification>
|
</tui-notification>
|
||||||
|
|
||||||
<form-page
|
<app-form
|
||||||
tuiMode="onDark"
|
tuiMode="onDark"
|
||||||
[spec]="spec"
|
[spec]="spec"
|
||||||
[value]="value || {}"
|
[value]="value || {}"
|
||||||
@@ -83,7 +85,7 @@ import { ConfigDepComponent } from '../components/config-dep.component'
|
|||||||
>
|
>
|
||||||
Reset Defaults
|
Reset Defaults
|
||||||
</button>
|
</button>
|
||||||
</form-page>
|
</app-form>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
`,
|
`,
|
||||||
styles: [
|
styles: [
|
||||||
@@ -97,7 +99,7 @@ import { ConfigDepComponent } from '../components/config-dep.component'
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FormPageModule,
|
FormComponent,
|
||||||
TuiLoaderModule,
|
TuiLoaderModule,
|
||||||
TuiNotificationModule,
|
TuiNotificationModule,
|
||||||
TuiButtonModule,
|
TuiButtonModule,
|
||||||
@@ -107,8 +109,8 @@ import { ConfigDepComponent } from '../components/config-dep.component'
|
|||||||
providers: [InvalidService],
|
providers: [InvalidService],
|
||||||
})
|
})
|
||||||
export class ServiceConfigModal {
|
export class ServiceConfigModal {
|
||||||
@ViewChild(FormPage)
|
@ViewChild(FormComponent)
|
||||||
private readonly form?: FormPage<Record<string, any>>
|
private readonly form?: FormComponent<Record<string, any>>
|
||||||
|
|
||||||
readonly pkgId = this.context.data.pkgId
|
readonly pkgId = this.context.data.pkgId
|
||||||
readonly dependentInfo = this.context.data.dependentInfo
|
readonly dependentInfo = this.context.data.dependentInfo
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { TUI_PROMPT } from '@taiga-ui/kit'
|
|||||||
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
|
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { filter, switchMap, timer } from 'rxjs'
|
import { filter, switchMap, timer } from 'rxjs'
|
||||||
|
import { FormComponent } from 'src/app/apps/portal/components/form.component'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
@@ -22,7 +23,6 @@ import {
|
|||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { hasCurrentDeps } from 'src/app/util/has-deps'
|
import { hasCurrentDeps } from 'src/app/util/has-deps'
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
import { FormPage } from 'src/app/apps/ui/modals/form/form.page'
|
|
||||||
import { ServiceActionComponent } from '../components/action.component'
|
import { ServiceActionComponent } from '../components/action.component'
|
||||||
import { ServiceActionSuccessComponent } from '../components/action-success.component'
|
import { ServiceActionSuccessComponent } from '../components/action-success.component'
|
||||||
import { DesktopService } from '../../../services/desktop.service'
|
import { DesktopService } from '../../../services/desktop.service'
|
||||||
@@ -97,7 +97,7 @@ export class ServiceActionsRoute {
|
|||||||
.subscribe()
|
.subscribe()
|
||||||
} else {
|
} else {
|
||||||
if (action['input-spec'] && !isEmptyObject(action['input-spec'])) {
|
if (action['input-spec'] && !isEmptyObject(action['input-spec'])) {
|
||||||
this.formDialog.open(FormPage, {
|
this.formDialog.open(FormComponent, {
|
||||||
label: action.name,
|
label: action.name,
|
||||||
data: {
|
data: {
|
||||||
spec: action['input-spec'],
|
spec: action['input-spec'],
|
||||||
|
|||||||
@@ -9,9 +9,8 @@ import {
|
|||||||
import { TuiForModule } from '@taiga-ui/cdk'
|
import { TuiForModule } from '@taiga-ui/cdk'
|
||||||
import { TuiSvgModule } from '@taiga-ui/core'
|
import { TuiSvgModule } from '@taiga-ui/core'
|
||||||
import { TuiButtonModule } from '@taiga-ui/experimental'
|
import { TuiButtonModule } from '@taiga-ui/experimental'
|
||||||
import { UnknownDisk } from 'src/app/services/api/api.types'
|
|
||||||
import { IonicModule } from '@ionic/angular'
|
|
||||||
import { UnitConversionPipesModule } from '@start9labs/shared'
|
import { UnitConversionPipesModule } from '@start9labs/shared'
|
||||||
|
import { UnknownDisk } from 'src/app/services/api/api.types'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'table[backupsPhysical]',
|
selector: 'table[backupsPhysical]',
|
||||||
@@ -69,7 +68,6 @@ import { UnitConversionPipesModule } from '@start9labs/shared'
|
|||||||
TuiForModule,
|
TuiForModule,
|
||||||
TuiSvgModule,
|
TuiSvgModule,
|
||||||
TuiButtonModule,
|
TuiButtonModule,
|
||||||
IonicModule,
|
|
||||||
UnitConversionPipesModule,
|
UnitConversionPipesModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { TuiNotificationModule } from '@taiga-ui/core'
|
|||||||
import { TuiButtonModule, TuiFadeModule } from '@taiga-ui/experimental'
|
import { TuiButtonModule, TuiFadeModule } from '@taiga-ui/experimental'
|
||||||
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
|
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
|
||||||
import { BehaviorSubject } from 'rxjs'
|
import { BehaviorSubject } from 'rxjs'
|
||||||
import { FormPage } from 'src/app/apps/ui/modals/form/form.page'
|
import { FormComponent } from 'src/app/apps/portal/components/form.component'
|
||||||
import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec'
|
import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec'
|
||||||
import {
|
import {
|
||||||
cifsSpec,
|
cifsSpec,
|
||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
dropboxSpec,
|
dropboxSpec,
|
||||||
googleDriveSpec,
|
googleDriveSpec,
|
||||||
remoteBackupTargetSpec,
|
remoteBackupTargetSpec,
|
||||||
} from 'src/app/apps/ui/pages/backups/types/target-types'
|
} from '../types/target'
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
import {
|
import {
|
||||||
BackupTarget,
|
BackupTarget,
|
||||||
@@ -126,7 +126,7 @@ export class BackupsTargetsModal implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onUpdate(value: BackupTarget) {
|
async onUpdate(value: BackupTarget) {
|
||||||
this.formDialog.open(FormPage, {
|
this.formDialog.open(FormComponent, {
|
||||||
label: 'Update Target',
|
label: 'Update Target',
|
||||||
data: {
|
data: {
|
||||||
value,
|
value,
|
||||||
@@ -147,7 +147,7 @@ export class BackupsTargetsModal implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async addPhysical(disk: UnknownDisk) {
|
async addPhysical(disk: UnknownDisk) {
|
||||||
this.formDialog.open(FormPage, {
|
this.formDialog.open(FormComponent, {
|
||||||
label: 'New Physical Target',
|
label: 'New Physical Target',
|
||||||
data: {
|
data: {
|
||||||
spec: await configBuilderToSpec(diskBackupTargetSpec),
|
spec: await configBuilderToSpec(diskBackupTargetSpec),
|
||||||
@@ -173,7 +173,7 @@ export class BackupsTargetsModal implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async addRemote() {
|
async addRemote() {
|
||||||
this.formDialog.open(FormPage, {
|
this.formDialog.open(FormComponent, {
|
||||||
label: 'New Remote Target',
|
label: 'New Remote Target',
|
||||||
data: {
|
data: {
|
||||||
spec: await configBuilderToSpec(remoteBackupTargetSpec),
|
spec: await configBuilderToSpec(remoteBackupTargetSpec),
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ import {
|
|||||||
take,
|
take,
|
||||||
tap,
|
tap,
|
||||||
} from 'rxjs'
|
} from 'rxjs'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|
||||||
import { BackupTarget } from 'src/app/services/api/api.types'
|
|
||||||
import {
|
import {
|
||||||
PROMPT,
|
PROMPT,
|
||||||
PromptOptions,
|
PromptOptions,
|
||||||
} from 'src/app/apps/ui/modals/prompt/prompt.component'
|
} from 'src/app/apps/portal/modals/prompt.component'
|
||||||
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
|
import { BackupTarget } from 'src/app/services/api/api.types'
|
||||||
import { TARGET, TARGET_RESTORE } from '../modals/target.component'
|
import { TARGET, TARGET_RESTORE } from '../modals/target.component'
|
||||||
import { RECOVER } from '../modals/recover.component'
|
import { RECOVER } from '../modals/recover.component'
|
||||||
import { RecoverData } from '../types/recover-data'
|
import { RecoverData } from '../types/recover-data'
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import { Config } from '@start9labs/start-sdk/lib/config/builder/config'
|
||||||
|
import { Value } from '@start9labs/start-sdk/lib/config/builder/value'
|
||||||
|
import { Variants } from '@start9labs/start-sdk/lib/config/builder/variants'
|
||||||
|
|
||||||
|
export const dropboxSpec = Config.of({
|
||||||
|
name: Value.text({
|
||||||
|
name: 'Name',
|
||||||
|
description: 'A friendly name for this Dropbox target',
|
||||||
|
placeholder: 'My Dropbox',
|
||||||
|
required: { default: null },
|
||||||
|
}),
|
||||||
|
token: Value.text({
|
||||||
|
name: 'Access Token',
|
||||||
|
description: 'The secret access token for your custom Dropbox app',
|
||||||
|
required: { default: null },
|
||||||
|
masked: true,
|
||||||
|
}),
|
||||||
|
path: Value.text({
|
||||||
|
name: 'Path',
|
||||||
|
description: 'The fully qualified path to the backup directory',
|
||||||
|
placeholder: 'e.g. /Desktop/my-folder',
|
||||||
|
required: { default: null },
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const googleDriveSpec = Config.of({
|
||||||
|
name: Value.text({
|
||||||
|
name: 'Name',
|
||||||
|
description: 'A friendly name for this Google Drive target',
|
||||||
|
placeholder: 'My Google Drive',
|
||||||
|
required: { default: null },
|
||||||
|
}),
|
||||||
|
path: Value.text({
|
||||||
|
name: 'Path',
|
||||||
|
description: 'The fully qualified path to the backup directory',
|
||||||
|
placeholder: 'e.g. /Desktop/my-folder',
|
||||||
|
required: { default: null },
|
||||||
|
}),
|
||||||
|
key: Value.file({
|
||||||
|
name: 'Private Key File',
|
||||||
|
description:
|
||||||
|
'Your Google Drive service account private key file (.json file)',
|
||||||
|
required: { default: null },
|
||||||
|
extensions: ['json'],
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const cifsSpec = Config.of({
|
||||||
|
name: Value.text({
|
||||||
|
name: 'Name',
|
||||||
|
description: 'A friendly name for this Network Folder',
|
||||||
|
placeholder: 'My Network Folder',
|
||||||
|
required: { default: null },
|
||||||
|
}),
|
||||||
|
hostname: Value.text({
|
||||||
|
name: 'Hostname',
|
||||||
|
description:
|
||||||
|
'The hostname of your target device on the Local Area Network.',
|
||||||
|
warning: null,
|
||||||
|
placeholder: `e.g. 'My Computer' OR 'my-computer.local'`,
|
||||||
|
required: { default: null },
|
||||||
|
patterns: [],
|
||||||
|
}),
|
||||||
|
path: Value.text({
|
||||||
|
name: 'Path',
|
||||||
|
description: `On Windows, this is the fully qualified path to the shared folder, (e.g. /Desktop/my-folder).\n\n On Linux and Mac, this is the literal name of the shared folder (e.g. my-shared-folder).`,
|
||||||
|
placeholder: 'e.g. my-shared-folder or /Desktop/my-folder',
|
||||||
|
required: { default: null },
|
||||||
|
}),
|
||||||
|
username: Value.text({
|
||||||
|
name: 'Username',
|
||||||
|
description: `On Linux, this is the samba username you created when sharing the folder.\n\n On Mac and Windows, this is the username of the user who is sharing the folder.`,
|
||||||
|
required: { default: null },
|
||||||
|
placeholder: 'My Network Folder',
|
||||||
|
}),
|
||||||
|
password: Value.text({
|
||||||
|
name: 'Password',
|
||||||
|
description: `On Linux, this is the samba password you created when sharing the folder.\n\n On Mac and Windows, this is the password of the user who is sharing the folder.`,
|
||||||
|
required: false,
|
||||||
|
masked: true,
|
||||||
|
placeholder: 'My Network Folder',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const remoteBackupTargetSpec = Config.of({
|
||||||
|
type: Value.union(
|
||||||
|
{
|
||||||
|
name: 'Target Type',
|
||||||
|
required: { default: 'dropbox' },
|
||||||
|
},
|
||||||
|
Variants.of({
|
||||||
|
dropbox: {
|
||||||
|
name: 'Dropbox',
|
||||||
|
spec: dropboxSpec,
|
||||||
|
},
|
||||||
|
'google-drive': {
|
||||||
|
name: 'Google Drive',
|
||||||
|
spec: googleDriveSpec,
|
||||||
|
},
|
||||||
|
cifs: {
|
||||||
|
name: 'Network Folder',
|
||||||
|
spec: cifsSpec,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const diskBackupTargetSpec = Config.of({
|
||||||
|
name: Value.text({
|
||||||
|
name: 'Name',
|
||||||
|
description: 'A friendly name for this physical target',
|
||||||
|
placeholder: 'My Physical Target',
|
||||||
|
required: { default: null },
|
||||||
|
}),
|
||||||
|
path: Value.text({
|
||||||
|
name: 'Path',
|
||||||
|
description: 'The fully qualified path to the backup directory',
|
||||||
|
placeholder: 'e.g. /Backups/my-folder',
|
||||||
|
required: { default: null },
|
||||||
|
}),
|
||||||
|
})
|
||||||
@@ -13,7 +13,7 @@ import { MarketplaceSidebarService } from '../services/sidebar.service'
|
|||||||
`
|
`
|
||||||
:host {
|
:host {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 7.5rem 0 0;
|
inset: 3.5rem 0 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transform: translate3d(0, 0, 0);
|
transform: translate3d(0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ import { TUI_PROMPT } from '@taiga-ui/kit'
|
|||||||
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
|
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { combineLatest, filter, firstValueFrom, map, Subscription } from 'rxjs'
|
import { combineLatest, filter, firstValueFrom, map, Subscription } from 'rxjs'
|
||||||
|
import { FormComponent } from 'src/app/apps/portal/components/form.component'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { DataModel, UIStore } from 'src/app/services/patch-db/data-model'
|
import { DataModel, UIStore } from 'src/app/services/patch-db/data-model'
|
||||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
import { FormPage } from 'src/app/apps/ui/modals/form/form.page'
|
|
||||||
import { MarketplaceRegistryComponent } from '../components/registry.component'
|
import { MarketplaceRegistryComponent } from '../components/registry.component'
|
||||||
import { getMarketplaceValueSpec, getPromptOptions } from '../utils/registry'
|
import { getMarketplaceValueSpec, getPromptOptions } from '../utils/registry'
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ export class MarketplaceRegistryModal {
|
|||||||
add() {
|
add() {
|
||||||
const { name, spec } = getMarketplaceValueSpec()
|
const { name, spec } = getMarketplaceValueSpec()
|
||||||
|
|
||||||
this.formDialog.open(FormPage, {
|
this.formDialog.open(FormComponent, {
|
||||||
label: name,
|
label: name,
|
||||||
data: {
|
data: {
|
||||||
spec,
|
spec,
|
||||||
|
|||||||
@@ -6,14 +6,16 @@ import { TuiButtonModule } from '@taiga-ui/experimental'
|
|||||||
import { TUI_PROMPT } from '@taiga-ui/kit'
|
import { TUI_PROMPT } from '@taiga-ui/kit'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { filter, firstValueFrom, map } from 'rxjs'
|
import { filter, firstValueFrom, map } from 'rxjs'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import {
|
||||||
import { FormContext, FormPage } from 'src/app/apps/ui/modals/form/form.page'
|
FormComponent,
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
FormContext,
|
||||||
|
} from 'src/app/apps/portal/components/form.component'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { getCustomSpec } from 'src/app/apps/ui/pages/system/domains/domain.const'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
import { getStart9ToSpec } from './constants'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { DomainsTableComponent } from './table.component'
|
import { getCustomSpec, getStart9ToSpec } from './constants'
|
||||||
import { DomainsInfoComponent } from './info.component'
|
import { DomainsInfoComponent } from './info.component'
|
||||||
|
import { DomainsTableComponent } from './table.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
@@ -121,7 +123,7 @@ export class SettingsDomainsComponent {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
this.formDialog.open(FormPage, options)
|
this.formDialog.open(FormComponent, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
async claim() {
|
async claim() {
|
||||||
@@ -146,7 +148,7 @@ export class SettingsDomainsComponent {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
this.formDialog.open(FormPage, options)
|
this.formDialog.open(FormComponent, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
private getNetworkStrategy(strategy: any) {
|
private getNetworkStrategy(strategy: any) {
|
||||||
|
|||||||
@@ -24,24 +24,22 @@ import { EmailInfoComponent } from './info.component'
|
|||||||
template: `
|
template: `
|
||||||
<email-info />
|
<email-info />
|
||||||
<ng-container *ngIf="form$ | async as form">
|
<ng-container *ngIf="form$ | async as form">
|
||||||
<form [formGroup]="form">
|
<form [formGroup]="form" [style.text-align]="'right'">
|
||||||
<h3 class="g-title">SMTP Credentials</h3>
|
<h3 class="g-title">SMTP Credentials</h3>
|
||||||
<form-group
|
<form-group
|
||||||
*ngIf="spec | async as resolved"
|
*ngIf="spec | async as resolved"
|
||||||
[spec]="resolved"
|
[spec]="resolved"
|
||||||
></form-group>
|
></form-group>
|
||||||
<div class="ion-text-right ion-padding-top">
|
<button
|
||||||
<button
|
tuiButton
|
||||||
tuiButton
|
[style.margin-top.rem]="1"
|
||||||
size="m"
|
[disabled]="form.invalid"
|
||||||
[disabled]="form.invalid"
|
(click)="save(form.value)"
|
||||||
(click)="save(form.value)"
|
>
|
||||||
>
|
Save
|
||||||
Save
|
</button>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
<form>
|
<form [style.text-align]="'right'">
|
||||||
<h3 class="g-title">Test Email</h3>
|
<h3 class="g-title">Test Email</h3>
|
||||||
<tui-input
|
<tui-input
|
||||||
[(ngModel)]="testAddress"
|
[(ngModel)]="testAddress"
|
||||||
@@ -50,17 +48,15 @@ import { EmailInfoComponent } from './info.component'
|
|||||||
Firstname Lastname <email@example.com>
|
Firstname Lastname <email@example.com>
|
||||||
<input tuiTextfield inputmode="email" />
|
<input tuiTextfield inputmode="email" />
|
||||||
</tui-input>
|
</tui-input>
|
||||||
<div class="ion-text-right ion-padding-top">
|
<button
|
||||||
<button
|
tuiButton
|
||||||
tuiButton
|
appearance="secondary"
|
||||||
appearance="secondary"
|
[style.margin-top.rem]="1"
|
||||||
size="m"
|
[disabled]="!testAddress || form.invalid"
|
||||||
[disabled]="!testAddress || form.invalid"
|
(click)="sendTestEmail(form)"
|
||||||
(click)="sendTestEmail(form)"
|
>
|
||||||
>
|
Send Test Email
|
||||||
Send Test Email
|
</button>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -17,9 +17,12 @@ import {
|
|||||||
} from '@taiga-ui/core'
|
} from '@taiga-ui/core'
|
||||||
import { TUI_PROMPT } from '@taiga-ui/kit'
|
import { TUI_PROMPT } from '@taiga-ui/kit'
|
||||||
import { filter } from 'rxjs'
|
import { filter } from 'rxjs'
|
||||||
|
import {
|
||||||
|
FormComponent,
|
||||||
|
FormContext,
|
||||||
|
} from 'src/app/apps/portal/components/form.component'
|
||||||
import { Proxy } from 'src/app/services/patch-db/data-model'
|
import { Proxy } from 'src/app/services/patch-db/data-model'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { FormContext, FormPage } from 'src/app/apps/ui/modals/form/form.page'
|
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
import { DELETE_OPTIONS, ProxyUpdate } from './constants'
|
import { DELETE_OPTIONS, ProxyUpdate } from './constants'
|
||||||
|
|
||||||
@@ -132,6 +135,6 @@ export class ProxiesMenuComponent {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
this.formDialog.open(FormPage, options)
|
this.formDialog.open(FormComponent, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ import { ErrorService, LoadingService } from '@start9labs/shared'
|
|||||||
import { TuiDialogOptions, TuiDialogService } from '@taiga-ui/core'
|
import { TuiDialogOptions, TuiDialogService } from '@taiga-ui/core'
|
||||||
import { TuiButtonModule } from '@taiga-ui/experimental'
|
import { TuiButtonModule } from '@taiga-ui/experimental'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
|
import {
|
||||||
|
FormComponent,
|
||||||
|
FormContext,
|
||||||
|
} from 'src/app/apps/portal/components/form.component'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { FormContext, FormPage } from 'src/app/apps/ui/modals/form/form.page'
|
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { ProxiesTableComponent } from './table.component'
|
import { ProxiesTableComponent } from './table.component'
|
||||||
@@ -58,7 +61,7 @@ export class SettingsProxiesComponent {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
this.formDialog.open(FormPage, options)
|
this.formDialog.open(FormComponent, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async save({ name, config }: WireguardSpec): Promise<boolean> {
|
private async save({ name, config }: WireguardSpec): Promise<boolean> {
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ import {
|
|||||||
TuiLinkModule,
|
TuiLinkModule,
|
||||||
} from '@taiga-ui/core'
|
} from '@taiga-ui/core'
|
||||||
import { TuiButtonModule } from '@taiga-ui/experimental'
|
import { TuiButtonModule } from '@taiga-ui/experimental'
|
||||||
|
import { PROMPT } from 'src/app/apps/portal/modals/prompt.component'
|
||||||
import { SSHKey } from 'src/app/services/api/api.types'
|
import { SSHKey } from 'src/app/services/api/api.types'
|
||||||
import { PROMPT } from '../../../../../../ui/modals/prompt/prompt.component'
|
|
||||||
import { filter, take } from 'rxjs'
|
import { filter, take } from 'rxjs'
|
||||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||||
import { ApiService } from '../../../../../../../services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { TUI_PROMPT, TuiPromptData } from '@taiga-ui/kit'
|
import { TUI_PROMPT, TuiPromptData } from '@taiga-ui/kit'
|
||||||
import { TuiForModule } from '@taiga-ui/cdk'
|
import { TuiForModule } from '@taiga-ui/cdk'
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ import { TuiForModule } from '@taiga-ui/cdk'
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let key of keys; else: loading">
|
<tr *ngFor="let key of keys; else: loading">
|
||||||
<td>{{ key.hostname }}</td>
|
<td>{{ key.hostname }}</td>
|
||||||
<td>{{ key['created-at'] | date : 'medium' }}</td>
|
<td>{{ key['created-at'] | date: 'medium' }}</td>
|
||||||
<td>{{ key.alg }}</td>
|
<td>{{ key.alg }}</td>
|
||||||
<td>{{ key.fingerprint }}</td>
|
<td>{{ key.fingerprint }}</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -15,9 +15,12 @@ import {
|
|||||||
TuiIconModule,
|
TuiIconModule,
|
||||||
TuiTitleModule,
|
TuiTitleModule,
|
||||||
} from '@taiga-ui/experimental'
|
} from '@taiga-ui/experimental'
|
||||||
|
import {
|
||||||
|
FormComponent,
|
||||||
|
FormContext,
|
||||||
|
} from 'src/app/apps/portal/components/form.component'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
import { FormContext, FormPage } from 'src/app/apps/ui/modals/form/form.page'
|
|
||||||
import { Wifi, WiFiForm, wifiSpec } from './utils'
|
import { Wifi, WiFiForm, wifiSpec } from './utils'
|
||||||
import { SettingsWifiComponent } from './wifi.component'
|
import { SettingsWifiComponent } from './wifi.component'
|
||||||
|
|
||||||
@@ -142,7 +145,7 @@ export class WifiTableComponent {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
this.formDialog.open(FormPage, options)
|
this.formDialog.open(FormComponent, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,18 +25,17 @@ import {
|
|||||||
} from '@taiga-ui/experimental'
|
} from '@taiga-ui/experimental'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { catchError, defer, merge, Observable, of, Subject, map } from 'rxjs'
|
import { catchError, defer, merge, Observable, of, Subject, map } from 'rxjs'
|
||||||
|
import {
|
||||||
|
FormComponent,
|
||||||
|
FormContext,
|
||||||
|
} from 'src/app/apps/portal/components/form.component'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { WifiInfoComponent } from './info.component'
|
import { WifiInfoComponent } from './info.component'
|
||||||
import { WifiTableComponent } from './table.component'
|
import { WifiTableComponent } from './table.component'
|
||||||
import { parseWifi, WifiData, WiFiForm } from './utils'
|
import { parseWifi, WifiData, WiFiForm } from './utils'
|
||||||
import { RR } from '../../../../../../../services/api/api.types'
|
|
||||||
import {
|
|
||||||
FormContext,
|
|
||||||
FormPage,
|
|
||||||
} from '../../../../../../ui/modals/form/form.page'
|
|
||||||
import { wifiSpec } from '../../../../../../ui/pages/system/wifi/wifi.const'
|
import { wifiSpec } from '../../../../../../ui/pages/system/wifi/wifi.const'
|
||||||
import { FormDialogService } from '../../../../../../../services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
@@ -143,7 +142,7 @@ export class SettingsWifiComponent {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
this.formDialog.open(FormPage, options)
|
this.formDialog.open(FormComponent, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveAndConnect(ssid: string, password?: string): Promise<boolean> {
|
async saveAndConnect(ssid: string, password?: string): Promise<boolean> {
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ import { TUI_PROMPT } from '@taiga-ui/kit'
|
|||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { filter, from, take } from 'rxjs'
|
import { filter, from, take } from 'rxjs'
|
||||||
import { switchMap } from 'rxjs/operators'
|
import { switchMap } from 'rxjs/operators'
|
||||||
|
import { FormComponent } from 'src/app/apps/portal/components/form.component'
|
||||||
|
import { PROMPT } from 'src/app/apps/portal/modals/prompt.component'
|
||||||
import { ProxyService } from 'src/app/services/proxy.service'
|
import { ProxyService } from 'src/app/services/proxy.service'
|
||||||
import { FormPage } from 'src/app/apps/ui/modals/form/form.page'
|
|
||||||
import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec'
|
import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec'
|
||||||
import { getServerInfo } from 'src/app/util/get-server-info'
|
import { getServerInfo } from 'src/app/util/get-server-info'
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { PROMPT } from 'src/app/apps/ui/modals/prompt/prompt.component'
|
|
||||||
|
|
||||||
import { passwordSpec, PasswordSpec, SettingBtn } from './settings.types'
|
import { passwordSpec, PasswordSpec, SettingBtn } from './settings.types'
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ export class SettingsService {
|
|||||||
switchMap(() => from(configBuilderToSpec(passwordSpec))),
|
switchMap(() => from(configBuilderToSpec(passwordSpec))),
|
||||||
)
|
)
|
||||||
.subscribe(spec => {
|
.subscribe(spec => {
|
||||||
this.formDialog.open(FormPage, {
|
this.formDialog.open(FormComponent, {
|
||||||
label: 'Change Master Password',
|
label: 'Change Master Password',
|
||||||
data: {
|
data: {
|
||||||
spec,
|
spec,
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
template: 'Here be snek',
|
|
||||||
standalone: true,
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export default class SnekComponent {}
|
|
||||||
@@ -46,12 +46,6 @@ const ROUTES: Routes = [
|
|||||||
loadComponent: () => import('./updates/updates.component'),
|
loadComponent: () => import('./updates/updates.component'),
|
||||||
data: toNavigationItem('/portal/system/updates'),
|
data: toNavigationItem('/portal/system/updates'),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: systemTabResolver,
|
|
||||||
path: 'snek',
|
|
||||||
loadComponent: () => import('./snek/snek.component'),
|
|
||||||
data: toNavigationItem('/portal/system/snek'),
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@NgModule({ imports: [RouterModule.forChild(ROUTES)] })
|
@NgModule({ imports: [RouterModule.forChild(ROUTES)] })
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export class BreadcrumbsService extends BehaviorSubject<readonly Breadcrumb[]> {
|
|||||||
const packages = await getAllPackages(this.patch)
|
const packages = await getAllPackages(this.patch)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.next(toBreadcrumbs(page, packages))
|
this.next(toBreadcrumbs(page.split('?')[0], packages))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.next([])
|
this.next([])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import { PatchDB } from 'patch-db-client'
|
|
||||||
import { Injector, NgModule } from '@angular/core'
|
|
||||||
import { PATCH_SOURCE, sourceFactory } from './patch-db.factory'
|
|
||||||
|
|
||||||
// This module is purely for providers organization purposes
|
|
||||||
@NgModule({
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: PATCH_SOURCE,
|
|
||||||
deps: [Injector],
|
|
||||||
useFactory: sourceFactory,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: PatchDB,
|
|
||||||
deps: [PATCH_SOURCE],
|
|
||||||
useClass: PatchDB,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
export class PatchDbModule {}
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { PatchDB } from 'patch-db-client'
|
||||||
|
import { Injector } from '@angular/core'
|
||||||
|
import { PATCH_SOURCE, sourceFactory } from './patch-db.factory'
|
||||||
|
|
||||||
|
export const PATCH_DB_PROVIDERS = [
|
||||||
|
{
|
||||||
|
provide: PATCH_SOURCE,
|
||||||
|
deps: [Injector],
|
||||||
|
useFactory: sourceFactory,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: PatchDB,
|
||||||
|
deps: [PATCH_SOURCE],
|
||||||
|
useClass: PatchDB,
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -369,15 +369,21 @@ ul {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.g-page {
|
.g-page {
|
||||||
|
@include customize-scroll();
|
||||||
|
|
||||||
display: block;
|
display: block;
|
||||||
height: 100%;
|
height: calc(100% - 0.375rem);
|
||||||
padding: 1px 2rem 3rem;
|
padding: 1px 2rem 3rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
isolation: isolate;
|
isolation: isolate;
|
||||||
|
backdrop-filter: blur(2rem);
|
||||||
|
margin: 0 0.375rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
|
||||||
// TODO: Theme
|
// TODO: Theme
|
||||||
background: #373a3f;
|
background: rgb(55 58 63 / 90%);
|
||||||
|
box-shadow: inset 0 1px rgb(255 255 255 / 10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.g-edged {
|
.g-edged {
|
||||||
|
|||||||
Reference in New Issue
Block a user