diff --git a/web/package-lock.json b/web/package-lock.json
index f77d5be5e..0a28b4f8f 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -24,14 +24,15 @@
"@start9labs/argon2": "^0.1.0",
"@start9labs/emver": "^0.1.5",
"@start9labs/start-sdk": "0.4.0-rev0.lib0.rc8.beta2",
- "@taiga-ui/addon-charts": "3.57.0",
- "@taiga-ui/addon-mobile": "3.57.0",
- "@taiga-ui/cdk": "3.57.0",
- "@taiga-ui/core": "3.57.0",
- "@taiga-ui/experimental": "3.57.0",
- "@taiga-ui/icons": "3.57.0",
- "@taiga-ui/kit": "3.57.0",
- "@taiga-ui/styles": "3.57.0",
+ "@taiga-ui/addon-charts": "^3.65.0",
+ "@taiga-ui/addon-commerce": "^3.65.0",
+ "@taiga-ui/addon-mobile": "^3.65.0",
+ "@taiga-ui/cdk": "^3.65.0",
+ "@taiga-ui/core": "^3.65.0",
+ "@taiga-ui/experimental": "^3.65.0",
+ "@taiga-ui/icons": "^3.65.0",
+ "@taiga-ui/kit": "^3.65.0",
+ "@taiga-ui/styles": "^3.65.0",
"@tinkoff/ng-dompurify": "4.0.0",
"@tinkoff/ng-event-plugins": "3.1.0",
"ansi-to-html": "^0.7.2",
@@ -3983,9 +3984,9 @@
}
},
"node_modules/@ng-web-apis/intersection-observer": {
- "version": "3.1.6",
- "resolved": "https://registry.npmjs.org/@ng-web-apis/intersection-observer/-/intersection-observer-3.1.6.tgz",
- "integrity": "sha512-Pzk0ycnYpq+EUf60kz+/A7nvCmhYzThc4ArwONwZzJqRF5xOS97CVWObs8hesorXxQdqlsrDNiu+JWuGxEvpzQ==",
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@ng-web-apis/intersection-observer/-/intersection-observer-3.2.0.tgz",
+ "integrity": "sha512-EhwqEZJFKR9pz55TWp82qyWTXdg8TZeMP6bUw26bVHz8CTkgrpzaXdtxurMTvJ/+gwuFy4JSJLjBeV9nfZ/SXA==",
"dependencies": {
"tslib": "^2.2.0"
},
@@ -3995,9 +3996,9 @@
}
},
"node_modules/@ng-web-apis/mutation-observer": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/@ng-web-apis/mutation-observer/-/mutation-observer-3.0.6.tgz",
- "integrity": "sha512-UW1qoUi2whH0uWkVz5qpdYCLs1u2T0E0QoCMQKZfLEkBpsWRTkT0PTCa9WWX/BhehaSPg23nZZm8BEixd6PI9w==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@ng-web-apis/mutation-observer/-/mutation-observer-3.1.0.tgz",
+ "integrity": "sha512-MFN0TLLBMFJJPpXkGFe9ChRCSOKvMHZRRtBq5jHWS7tv5/CtdUkqW5CU7RC9KTzZjGeMzYe0cXO4JRkjL5aZ9g==",
"dependencies": {
"tslib": "^2.2.0"
},
@@ -4671,9 +4672,9 @@
}
},
"node_modules/@taiga-ui/addon-charts": {
- "version": "3.57.0",
- "resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.57.0.tgz",
- "integrity": "sha512-/x/yVHafSmNA3GSR9cSo6KSkzDNzDGew0JCD5cCNOb6vRZOe1QiXUQwMHvdj9eZlrjZzbV67eiV13NNvWolGwg==",
+ "version": "3.65.0",
+ "resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.65.0.tgz",
+ "integrity": "sha512-HNKUeK0ippIvLRF6wsuCiyJ4d98K4uIhkGwK1fWaTVOCN26Z+AnFKk9AryTyhocEZctyc4PMpJ7BP7h3CA4dZA==",
"dependencies": {
"tslib": "2.6.2"
},
@@ -4681,16 +4682,15 @@
"@angular/common": ">=12.0.0",
"@angular/core": ">=12.0.0",
"@ng-web-apis/common": "3.0.6",
- "@taiga-ui/cdk": ">=3.57.0",
- "@taiga-ui/core": ">=3.57.0",
+ "@taiga-ui/cdk": "^3.65.0",
+ "@taiga-ui/core": "^3.65.0",
"@tinkoff/ng-polymorpheus": "4.3.0"
}
},
"node_modules/@taiga-ui/addon-commerce": {
- "version": "3.57.0",
- "resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-3.57.0.tgz",
- "integrity": "sha512-9msjwr/8tzlmbcv6MDxZTYvW9+sbURAq2Olrs4zJhHvw69tA3XYt2MCrDYUSrej/oj5jE2GFjtyio7wqQviLzA==",
- "peer": true,
+ "version": "3.65.0",
+ "resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-3.65.0.tgz",
+ "integrity": "sha512-D98M3nkPKVFz9TFiMxCmMtmJs9vDc69RlPv5M03ZF+qXHqbthfpVss/p2MSzs4Cr2vgoECaZWPLNcWBOO5mzCw==",
"dependencies": {
"tslib": "2.6.2"
},
@@ -4702,18 +4702,18 @@
"@maskito/core": "1.9.0",
"@maskito/kit": "1.9.0",
"@ng-web-apis/common": "3.0.6",
- "@taiga-ui/cdk": ">=3.57.0",
- "@taiga-ui/core": ">=3.57.0",
- "@taiga-ui/i18n": ">=3.57.0",
- "@taiga-ui/kit": ">=3.57.0",
+ "@taiga-ui/cdk": "^3.65.0",
+ "@taiga-ui/core": "^3.65.0",
+ "@taiga-ui/i18n": "^3.65.0",
+ "@taiga-ui/kit": "^3.65.0",
"@tinkoff/ng-polymorpheus": "4.3.0",
"rxjs": ">=6.0.0"
}
},
"node_modules/@taiga-ui/addon-mobile": {
- "version": "3.57.0",
- "resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-3.57.0.tgz",
- "integrity": "sha512-fxtmOqf8qcWbDeKKHukP+Iw0Ida8onKlg0L4UkeqF5AHT1f9QnpDtIQxyEUw4DXwUQdFhMT+64FWeA4acwa+kA==",
+ "version": "3.65.0",
+ "resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-3.65.0.tgz",
+ "integrity": "sha512-nKEf5Lb7yfR7vqkAIQQLoUEzSpKftdPpAsmco6FNfN4FDlvDFYTKE8MqqXAxzEqrXviDXv8/CKPv+nc6xd4VXg==",
"dependencies": {
"tslib": "2.6.2"
},
@@ -4722,27 +4722,27 @@
"@angular/common": ">=12.0.0",
"@angular/core": ">=12.0.0",
"@ng-web-apis/common": "3.0.6",
- "@taiga-ui/cdk": ">=3.57.0",
- "@taiga-ui/core": ">=3.57.0",
- "@taiga-ui/kit": ">=3.57.0",
+ "@taiga-ui/cdk": "^3.65.0",
+ "@taiga-ui/core": "^3.65.0",
+ "@taiga-ui/kit": "^3.65.0",
"@tinkoff/ng-polymorpheus": "4.3.0",
"rxjs": ">=6.0.0"
}
},
"node_modules/@taiga-ui/cdk": {
- "version": "3.57.0",
- "resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.57.0.tgz",
- "integrity": "sha512-igfgPZh7sUaElX4dehDPkbPL66LFc6qmirfEQ6f2deNnezYm4EZTTIdHebU1ibiKTqWBxWVTHKC2pQ3nxhwkNA==",
+ "version": "3.65.0",
+ "resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.65.0.tgz",
+ "integrity": "sha512-hiFC9RlRng7pUv84YPZbqieKIYsFEzsMKCjMIckHBASBBU6qQ4OY6irKszFvTGqMe9KJgBh6sJU1hkQOBwFSaA==",
"dependencies": {
"@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",
"@tinkoff/ng-event-plugins": "3.1.0",
"@tinkoff/ng-polymorpheus": "4.3.0",
"tslib": "2.6.2"
},
"optionalDependencies": {
- "ng-morph": "4.0.3",
+ "ng-morph": "4.0.5",
"parse5": "6.0.1"
},
"peerDependencies": {
@@ -4760,11 +4760,11 @@
"optional": true
},
"node_modules/@taiga-ui/core": {
- "version": "3.57.0",
- "resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.57.0.tgz",
- "integrity": "sha512-RUO//9C9+CdEPX1nV0hKRhKu3vmuAx+miszZHv5LAITK3jpTJKpd2jQ2Ib2z5O20v5vKrwkJ0DawT7EWU2v2TA==",
+ "version": "3.65.0",
+ "resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.65.0.tgz",
+ "integrity": "sha512-zNctTTsrW73fhmYirWE/mZs32UUvv6gV5CoIFm0BzVos0X7ZkN+x7PLXd9R+3CEgL6Kv/OxY92p+pJRvqc5jHg==",
"dependencies": {
- "@taiga-ui/i18n": "^3.57.0",
+ "@taiga-ui/i18n": "^3.65.0",
"tslib": "2.6.2"
},
"peerDependencies": {
@@ -4775,36 +4775,36 @@
"@angular/platform-browser": ">=12.0.0",
"@angular/router": ">=12.0.0",
"@ng-web-apis/common": "3.0.6",
- "@ng-web-apis/mutation-observer": "3.0.6",
- "@taiga-ui/cdk": ">=3.57.0",
- "@taiga-ui/i18n": ">=3.57.0",
+ "@ng-web-apis/mutation-observer": "3.1.0",
+ "@taiga-ui/cdk": "^3.65.0",
+ "@taiga-ui/i18n": "^3.65.0",
"@tinkoff/ng-event-plugins": "3.1.0",
"@tinkoff/ng-polymorpheus": "4.3.0",
"rxjs": ">=6.0.0"
}
},
"node_modules/@taiga-ui/experimental": {
- "version": "3.57.0",
- "resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-3.57.0.tgz",
- "integrity": "sha512-A0u/Cn0tHUjl6sTSfF6G9YvzuEJafBoVbHuDpw1mnSOw/TkWLXpkoYzeyJ6U9+nBIFBOSl4o+14olrYvsUg9CA==",
+ "version": "3.65.0",
+ "resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-3.65.0.tgz",
+ "integrity": "sha512-LZYR+XeJ2n+vE4AHBiIolzlqDrDGUx/bmE0ypmKO7dPgvHWu5Al8OXRrnhyqmAVO48FNpkSZ07YoqCG/aoxu6g==",
"dependencies": {
"tslib": "2.6.2"
},
"peerDependencies": {
"@angular/common": ">=12.0.0",
"@angular/core": ">=12.0.0",
- "@taiga-ui/addon-commerce": ">=3.57.0",
- "@taiga-ui/cdk": ">=3.57.0",
- "@taiga-ui/core": ">=3.57.0",
- "@taiga-ui/kit": ">=3.57.0",
+ "@taiga-ui/addon-commerce": "^3.65.0",
+ "@taiga-ui/cdk": "^3.65.0",
+ "@taiga-ui/core": "^3.65.0",
+ "@taiga-ui/kit": "^3.65.0",
"@tinkoff/ng-polymorpheus": "4.3.0",
"rxjs": ">=6.0.0"
}
},
"node_modules/@taiga-ui/i18n": {
- "version": "3.57.0",
- "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.57.0.tgz",
- "integrity": "sha512-FhE3eMD5g+i0/SbRgq4zoA7pBVY1mw4/gwndOJvCFkcWhjYxOJXJ4g/sjTyQmZ4QXmONM0OeIOMBxuMMCeJkGQ==",
+ "version": "3.65.0",
+ "resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.65.0.tgz",
+ "integrity": "sha512-lHy9VDKc5IXbm40eJnnAyOlmm3vDgmWhGbr5woGe9bV/tTqsBBDATY7Rkhz7Bu1nbX7X+MI0TDfQh9ayoCCKRQ==",
"dependencies": {
"tslib": "2.6.2"
},
@@ -4815,25 +4815,25 @@
}
},
"node_modules/@taiga-ui/icons": {
- "version": "3.57.0",
- "resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.57.0.tgz",
- "integrity": "sha512-uXch0AX8DQMCrv2ldQIYmUUjMmhP2tgOLxX08GrPZCWsz6zHq8stgUJR6kgvZYwO1JZSFlwvCgv/32MoDEdI3w==",
+ "version": "3.65.0",
+ "resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.65.0.tgz",
+ "integrity": "sha512-8iE6EuK+QBzcNiRM1ThZOOkZpal7V6dBouMXMj+QphRWiIp8Znj58mtY3L+uwQFpGnxt3DRs4p4eEA9ZuGFssw==",
"dependencies": {
"tslib": "2.6.2"
},
"peerDependencies": {
- "@taiga-ui/cdk": ">=3.57.0"
+ "@taiga-ui/cdk": "^3.65.0"
}
},
"node_modules/@taiga-ui/kit": {
- "version": "3.57.0",
- "resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.57.0.tgz",
- "integrity": "sha512-vZNMKlPku5E1wCiPdRV3GbNKba4molyd6F2Zc4EMJvHAlWybFpVPQOw67Z7VsSLGwWpzZXBC+5M5Ov3UNOUSJQ==",
+ "version": "3.65.0",
+ "resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.65.0.tgz",
+ "integrity": "sha512-Nh6pMSAFR7yScF7acj8WdCpKQUgDatW2jObqts0z4hy9BJ8gl9BAWRBgSlbp3Oen5c2WAIC316Gb9OcttC8nbw==",
"dependencies": {
"@maskito/angular": "1.9.0",
"@maskito/core": "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",
"tslib": "2.6.2"
},
@@ -4843,21 +4843,21 @@
"@angular/forms": ">=12.0.0",
"@angular/router": ">=12.0.0",
"@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",
- "@taiga-ui/cdk": ">=3.57.0",
- "@taiga-ui/core": ">=3.57.0",
- "@taiga-ui/i18n": ">=3.57.0",
+ "@taiga-ui/cdk": "^3.65.0",
+ "@taiga-ui/core": "^3.65.0",
+ "@taiga-ui/i18n": "^3.65.0",
"@tinkoff/ng-polymorpheus": "4.3.0",
"rxjs": ">=6.0.0"
}
},
"node_modules/@taiga-ui/styles": {
- "version": "3.57.0",
- "resolved": "https://registry.npmjs.org/@taiga-ui/styles/-/styles-3.57.0.tgz",
- "integrity": "sha512-jxSAZHL+QrjTHH5tEQ2EJXmNpRvKUN/8fHh5Fw+O71Y50I9IENN2CVXdB01YmjtNvn0tQE2sUy1RFausIQLyBA==",
+ "version": "3.65.0",
+ "resolved": "https://registry.npmjs.org/@taiga-ui/styles/-/styles-3.65.0.tgz",
+ "integrity": "sha512-HO2sZPxNOGj2BPQpWkrM6HgZV/QxaEMEemye3sJvsfuttvk6bmxoL8NF331I63tlp/Zx7woD8AusH5ATuUniqg==",
"peerDependencies": {
- "@taiga-ui/cdk": ">=3.57.0",
+ "@taiga-ui/cdk": "^3.65.0",
"tslib": "2.6.2"
}
},
@@ -4910,36 +4910,37 @@
}
},
"node_modules/@ts-morph/common": {
- "version": "0.21.0",
- "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.21.0.tgz",
- "integrity": "sha512-ES110Mmne5Vi4ypUKrtVQfXFDtCsDXiUiGxF6ILVlE90dDD4fdpC1LSjydl/ml7xJWKSDZwUYD2zkOePMSrPBA==",
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.22.0.tgz",
+ "integrity": "sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw==",
"optional": true,
"dependencies": {
- "fast-glob": "^3.2.12",
- "minimatch": "^7.4.3",
- "mkdirp": "^2.1.6",
+ "fast-glob": "^3.3.2",
+ "minimatch": "^9.0.3",
+ "mkdirp": "^3.0.1",
"path-browserify": "^1.0.1"
}
},
- "node_modules/@ts-morph/common/node_modules/minimatch": {
- "version": "7.4.6",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz",
- "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==",
+ "node_modules/@ts-morph/common/node_modules/fast-glob": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+ "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"optional": true,
"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": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "node": ">=8.6.0"
}
},
"node_modules/@ts-morph/common/node_modules/mkdirp": {
- "version": "2.1.6",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz",
- "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
+ "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
"optional": true,
"bin": {
"mkdirp": "dist/cjs/src/bin.js"
@@ -11981,15 +11982,15 @@
}
},
"node_modules/ng-morph": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/ng-morph/-/ng-morph-4.0.3.tgz",
- "integrity": "sha512-4voBApzsUs0/1zJsV2sSVPoOKxWt0gBd+3yqE/q8oYOX87RN3HwcJmqQLtWEU4tbmhk11gSRIFIl61+z32cnNw==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/ng-morph/-/ng-morph-4.0.5.tgz",
+ "integrity": "sha512-5tnlb5WrGKeo2E7VRcV7ZHhScyNgliYqpbXqt103kynmfj6Ic8kzhJAhHu9iLkF1yRnKv2kyCE+O7UGZx5RraQ==",
"optional": true,
"dependencies": {
"jsonc-parser": "3.2.0",
"minimatch": "9.0.3",
"multimatch": "5.0.0",
- "ts-morph": "20.0.0",
+ "ts-morph": "21.0.1",
"tslib": "2.6.2"
},
"peerDependencies": {
@@ -16605,12 +16606,12 @@
"integrity": "sha512-kXrY75F0s0WD15N2bWKDScKlKgwnusN6dTRzGs1N7LlxQRnazrsBISC1HL4sy2adsyk65Zbx3Ui3IGN8leAFOQ=="
},
"node_modules/ts-morph": {
- "version": "20.0.0",
- "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-20.0.0.tgz",
- "integrity": "sha512-JVmEJy2Wow5n/84I3igthL9sudQ8qzjh/6i4tmYCm6IqYyKFlNbJZi7oBdjyqcWSWYRu3CtL0xbT6fS03ESZIg==",
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-21.0.1.tgz",
+ "integrity": "sha512-dbDtVdEAncKctzrVZ+Nr7kHpHkv+0JDJb2MjjpBaj8bFeCkePU9rHfMklmhuLFnpeq/EJZk2IhStY6NzqgjOkg==",
"optional": true,
"dependencies": {
- "@ts-morph/common": "~0.21.0",
+ "@ts-morph/common": "~0.22.0",
"code-block-writer": "^12.0.0"
}
},
diff --git a/web/package.json b/web/package.json
index d2a401bd3..a6498057f 100644
--- a/web/package.json
+++ b/web/package.json
@@ -46,14 +46,15 @@
"@start9labs/argon2": "^0.1.0",
"@start9labs/emver": "^0.1.5",
"@start9labs/start-sdk": "0.4.0-rev0.lib0.rc8.beta2",
- "@taiga-ui/addon-charts": "3.57.0",
- "@taiga-ui/addon-mobile": "3.57.0",
- "@taiga-ui/cdk": "3.57.0",
- "@taiga-ui/core": "3.57.0",
- "@taiga-ui/experimental": "3.57.0",
- "@taiga-ui/icons": "3.57.0",
- "@taiga-ui/kit": "3.57.0",
- "@taiga-ui/styles": "3.57.0",
+ "@taiga-ui/addon-charts": "3.65.0",
+ "@taiga-ui/addon-commerce": "3.65.0",
+ "@taiga-ui/addon-mobile": "3.65.0",
+ "@taiga-ui/cdk": "3.65.0",
+ "@taiga-ui/core": "3.65.0",
+ "@taiga-ui/experimental": "3.65.0",
+ "@taiga-ui/icons": "3.65.0",
+ "@taiga-ui/kit": "3.65.0",
+ "@taiga-ui/styles": "3.65.0",
"@tinkoff/ng-dompurify": "4.0.0",
"@tinkoff/ng-event-plugins": "3.1.0",
"ansi-to-html": "^0.7.2",
diff --git a/web/projects/marketplace/package.json b/web/projects/marketplace/package.json
index 87d7c574d..8df1f559b 100644
--- a/web/projects/marketplace/package.json
+++ b/web/projects/marketplace/package.json
@@ -4,7 +4,6 @@
"peerDependencies": {
"@angular/common": ">=13.2.0",
"@angular/core": ">=13.2.0",
- "@ionic/angular": ">=6.0.0",
"@start9labs/shared": ">=0.3.2",
"@taiga-ui/cdk": ">=3.0.0",
"@tinkoff/ng-dompurify": ">=4.0.0",
diff --git a/web/projects/marketplace/src/components/store-icon/store-icon.component.html b/web/projects/marketplace/src/components/store-icon/store-icon.component.html
deleted file mode 100644
index 2000f287d..000000000
--- a/web/projects/marketplace/src/components/store-icon/store-icon.component.html
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
diff --git a/web/projects/marketplace/src/components/store-icon/store-icon.component.module.ts b/web/projects/marketplace/src/components/store-icon/store-icon.component.module.ts
index 5006663eb..e9d24c022 100644
--- a/web/projects/marketplace/src/components/store-icon/store-icon.component.module.ts
+++ b/web/projects/marketplace/src/components/store-icon/store-icon.component.module.ts
@@ -1,11 +1,11 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
-import { IonicModule } from '@ionic/angular'
+import { TuiIconModule } from '@taiga-ui/experimental'
import { StoreIconComponent } from './store-icon.component'
@NgModule({
declarations: [StoreIconComponent],
- imports: [CommonModule, IonicModule],
+ imports: [CommonModule, TuiIconModule],
exports: [StoreIconComponent],
})
export class StoreIconComponentModule {}
diff --git a/web/projects/marketplace/src/components/store-icon/store-icon.component.scss b/web/projects/marketplace/src/components/store-icon/store-icon.component.scss
deleted file mode 100644
index e69de29bb..000000000
diff --git a/web/projects/marketplace/src/components/store-icon/store-icon.component.ts b/web/projects/marketplace/src/components/store-icon/store-icon.component.ts
index b8e13c7ad..97e0088ea 100644
--- a/web/projects/marketplace/src/components/store-icon/store-icon.component.ts
+++ b/web/projects/marketplace/src/components/store-icon/store-icon.component.ts
@@ -3,8 +3,18 @@ import { MarketplaceConfig, sameUrl } from '@start9labs/shared'
@Component({
selector: 'store-icon',
- templateUrl: './store-icon.component.html',
- styleUrls: ['./store-icon.component.scss'],
+ template: `
+
+
+
+
+ `,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class StoreIconComponent {
diff --git a/web/projects/marketplace/src/pages/list/categories/categories.module.ts b/web/projects/marketplace/src/pages/list/categories/categories.module.ts
index b792ae891..567a16467 100644
--- a/web/projects/marketplace/src/pages/list/categories/categories.module.ts
+++ b/web/projects/marketplace/src/pages/list/categories/categories.module.ts
@@ -1,11 +1,10 @@
import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
-import { IonicModule } from '@ionic/angular'
import { CategoriesComponent } from './categories.component'
@NgModule({
- imports: [CommonModule, IonicModule],
+ imports: [CommonModule],
declarations: [CategoriesComponent],
exports: [CategoriesComponent],
})
diff --git a/web/projects/marketplace/src/pages/list/skeleton/skeleton.component.html b/web/projects/marketplace/src/pages/list/skeleton/skeleton.component.html
deleted file mode 100644
index 0e8fdfa2a..000000000
--- a/web/projects/marketplace/src/pages/list/skeleton/skeleton.component.html
+++ /dev/null
@@ -1,69 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/web/projects/marketplace/src/pages/list/skeleton/skeleton.component.ts b/web/projects/marketplace/src/pages/list/skeleton/skeleton.component.ts
deleted file mode 100644
index 8903a8e3e..000000000
--- a/web/projects/marketplace/src/pages/list/skeleton/skeleton.component.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { ChangeDetectionStrategy, Component } from '@angular/core'
-
-@Component({
- selector: 'marketplace-skeleton',
- templateUrl: 'skeleton.component.html',
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class SkeletonComponent {}
diff --git a/web/projects/marketplace/src/pages/list/skeleton/skeleton.module.ts b/web/projects/marketplace/src/pages/list/skeleton/skeleton.module.ts
deleted file mode 100644
index 9df2a3596..000000000
--- a/web/projects/marketplace/src/pages/list/skeleton/skeleton.module.ts
+++ /dev/null
@@ -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 {}
diff --git a/web/projects/marketplace/src/pages/release-notes/release-notes.component.html b/web/projects/marketplace/src/pages/release-notes/release-notes.component.html
index bb9e750e1..b4d6baa79 100644
--- a/web/projects/marketplace/src/pages/release-notes/release-notes.component.html
+++ b/web/projects/marketplace/src/pages/release-notes/release-notes.component.html
@@ -10,7 +10,7 @@
>
Latest Release
-
- {{ published | date : 'medium' }}
+ {{ published | date: 'medium' }}
@@ -41,7 +41,7 @@
{{ note.key | displayEmver }}
@@ -53,6 +53,6 @@
-
+
diff --git a/web/projects/marketplace/src/pages/release-notes/release-notes.module.ts b/web/projects/marketplace/src/pages/release-notes/release-notes.module.ts
index 88d5b074e..816b0c709 100644
--- a/web/projects/marketplace/src/pages/release-notes/release-notes.module.ts
+++ b/web/projects/marketplace/src/pages/release-notes/release-notes.module.ts
@@ -1,26 +1,25 @@
-import { NgModule } from "@angular/core";
-import { CommonModule } from "@angular/common";
+import { NgModule } from '@angular/core'
+import { CommonModule } from '@angular/common'
import {
EmverPipesModule,
MarkdownPipeModule,
SafeLinksDirective,
- TextSpinnerComponentModule,
-} from "@start9labs/shared";
-import { NgDompurifyModule } from "@tinkoff/ng-dompurify";
-import { ReleaseNotesComponent } from "./release-notes.component";
-import { TuiButtonModule } from "@taiga-ui/core";
-import { TuiAccordionModule } from "@taiga-ui/kit";
+} from '@start9labs/shared'
+import { TuiAccordionModule } from '@taiga-ui/kit'
+import { TuiButtonModule, TuiLoaderModule } from '@taiga-ui/core'
+import { NgDompurifyModule } from '@tinkoff/ng-dompurify'
+import { ReleaseNotesComponent } from './release-notes.component'
@NgModule({
imports: [
CommonModule,
- TextSpinnerComponentModule,
EmverPipesModule,
MarkdownPipeModule,
NgDompurifyModule,
SafeLinksDirective,
TuiButtonModule,
TuiAccordionModule,
+ TuiLoaderModule,
],
declarations: [ReleaseNotesComponent],
exports: [ReleaseNotesComponent],
diff --git a/web/projects/marketplace/src/pages/show/screenshots/screenshots.component.ts b/web/projects/marketplace/src/pages/show/screenshots/screenshots.component.ts
index 28d748f8e..a7d3c4fb2 100644
--- a/web/projects/marketplace/src/pages/show/screenshots/screenshots.component.ts
+++ b/web/projects/marketplace/src/pages/show/screenshots/screenshots.component.ts
@@ -2,9 +2,10 @@ import { CommonModule } from '@angular/common'
import {
ChangeDetectionStrategy,
Component,
- Inject,
+ inject,
Input,
} from '@angular/core'
+import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
import {
TuiButtonModule,
TuiDialogContext,
@@ -13,7 +14,6 @@ import {
import { TuiCarouselModule } from '@taiga-ui/kit'
import { MarketplacePkg } from '../../../types'
import { PolymorpheusContent } from '@tinkoff/ng-polymorpheus'
-import { isPlatform } from '@ionic/angular'
@Component({
selector: 'marketplace-package-screenshots',
@@ -77,15 +77,14 @@ import { isPlatform } from '@ionic/angular'
imports: [CommonModule, TuiCarouselModule, TuiButtonModule],
})
export class MarketplacePackageScreenshotComponent {
+ private readonly dialogs = inject(TuiDialogService)
+
@Input({ required: true })
pkg!: MarketplacePkg
- constructor(
- @Inject(TuiDialogService) private readonly dialogs: TuiDialogService,
- ) {}
index = 0
- isMobile = isPlatform(window, 'ios') || isPlatform(window, 'android')
+ isMobile = inject(TUI_IS_MOBILE)
presentModalImg(content: PolymorpheusContent) {
this.dialogs
diff --git a/web/projects/shared/src/components/initializing/initializing.component.html b/web/projects/shared/src/components/initializing/initializing.component.html
index 0722943bf..836caf38c 100644
--- a/web/projects/shared/src/components/initializing/initializing.component.html
+++ b/web/projects/shared/src/components/initializing/initializing.component.html
@@ -1,32 +1,15 @@
-
-
-
-
-
-
- Initializing StartOS
-
-
- Progress: {{ (progress * 100).toFixed(0) }}%
-
-
-
+
+
Initializing StartOS
+
+ Progress: {{ (progress * 100).toFixed(0) }}%
+
-
-
- {{ getMessage(progress) }}
-
-
+
+
{{ getMessage(progress) }}
+
-
-
-
-
-
+
diff --git a/web/projects/shared/src/components/initializing/initializing.component.scss b/web/projects/shared/src/components/initializing/initializing.component.scss
index e394fa18e..0675add7d 100644
--- a/web/projects/shared/src/components/initializing/initializing.component.scss
+++ b/web/projects/shared/src/components/initializing/initializing.component.scss
@@ -1,11 +1,22 @@
-ion-card-title {
- font-size: 42px;
+.card {
+ 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 {
- max-width: 700px;
- padding-bottom: 20px;
- margin: auto auto 40px;
+ max-width: 40rem;
+ margin: 1rem auto;
}
.logs {
@@ -13,7 +24,7 @@ ion-card-title {
flex-direction: column;
height: 18rem;
padding: 1rem;
- margin: 1.5rem 0.75rem;
+ margin: 0 1.5rem auto;
text-align: left;
overflow: hidden;
border-radius: 2rem;
diff --git a/web/projects/shared/src/components/initializing/initializing.module.ts b/web/projects/shared/src/components/initializing/initializing.module.ts
index c72a7e978..93c5c99d4 100644
--- a/web/projects/shared/src/components/initializing/initializing.module.ts
+++ b/web/projects/shared/src/components/initializing/initializing.module.ts
@@ -1,13 +1,13 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
-import { IonicModule } from '@ionic/angular'
import { TuiLetModule } from '@taiga-ui/cdk'
+import { TuiProgressModule } from '@taiga-ui/kit'
import { LogsWindowComponent } from './logs-window.component'
import { InitializingComponent } from './initializing.component'
@NgModule({
- imports: [CommonModule, IonicModule, TuiLetModule, LogsWindowComponent],
+ imports: [CommonModule, TuiLetModule, LogsWindowComponent, TuiProgressModule],
declarations: [InitializingComponent],
exports: [InitializingComponent],
})
diff --git a/web/projects/shared/src/components/markdown/markdown.component.html b/web/projects/shared/src/components/markdown/markdown.component.html
index 45271946d..8e5e7b026 100644
--- a/web/projects/shared/src/components/markdown/markdown.component.html
+++ b/web/projects/shared/src/components/markdown/markdown.component.html
@@ -1,8 +1,6 @@
-
-
- {{ error }}
-
-
+
+ {{ error }}
+
-
+
diff --git a/web/projects/shared/src/components/markdown/markdown.component.module.ts b/web/projects/shared/src/components/markdown/markdown.component.module.ts
index e643ee2e8..ddb1722bc 100644
--- a/web/projects/shared/src/components/markdown/markdown.component.module.ts
+++ b/web/projects/shared/src/components/markdown/markdown.component.module.ts
@@ -1,22 +1,21 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
-import { IonicModule } from '@ionic/angular'
+import { TuiLoaderModule, TuiNotificationModule } from '@taiga-ui/core'
import { NgDompurifyModule } from '@tinkoff/ng-dompurify'
import { MarkdownPipeModule } from '../../pipes/markdown/markdown.module'
import { SafeLinksDirective } from '../../directives/safe-links.directive'
-import { TextSpinnerComponentModule } from '../text-spinner/text-spinner.component.module'
import { MarkdownComponent } from './markdown.component'
@NgModule({
declarations: [MarkdownComponent],
imports: [
CommonModule,
- IonicModule,
MarkdownPipeModule,
- TextSpinnerComponentModule,
SafeLinksDirective,
NgDompurifyModule,
+ TuiLoaderModule,
+ TuiNotificationModule,
],
exports: [MarkdownComponent],
})
diff --git a/web/projects/shared/styles/taiga.scss b/web/projects/shared/styles/taiga.scss
index f2566adfa..ed89a5be9 100644
--- a/web/projects/shared/styles/taiga.scss
+++ b/web/projects/shared/styles/taiga.scss
@@ -40,6 +40,7 @@
[tuiAppearance][data-appearance='primary'] {
@include appearance-disabled {
background: #eaecee;
+ color: #333;
}
}
diff --git a/web/projects/ui/src/app/app.module.ts b/web/projects/ui/src/app/app.module.ts
index e573debd4..1f4ae70f1 100644
--- a/web/projects/ui/src/app/app.module.ts
+++ b/web/projects/ui/src/app/app.module.ts
@@ -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 {
TuiAlertModule,
TuiDialogModule,
@@ -5,36 +20,19 @@ import {
TuiRootModule,
TuiThemeNightModule,
} from '@taiga-ui/core'
-import { HttpClientModule } from '@angular/common/http'
-import { NgModule } from '@angular/core'
-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 { WidgetsPageModule } from 'src/app/apps/ui/pages/widgets/widgets.module'
+import { environment } from '../environments/environment'
import { AppComponent } from './app.component'
-import { RoutingModule } from './routing.module'
-import { OSWelcomePageModule } from './common/os-welcome/os-welcome.module'
-import { QRComponentModule } from './common/qr/qr.module'
-import { PreloaderModule } from './app/preloader/preloader.module'
+import { APP_PROVIDERS } from './app.providers'
+import { ConnectionBarComponentModule } from './app/connection-bar/connection-bar.component.module'
import { FooterModule } from './app/footer/footer.module'
import { MenuModule } from './app/menu/menu.module'
-import { APP_PROVIDERS } from './app.providers'
-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 { PreloaderModule } from './app/preloader/preloader.module'
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({
declarations: [AppComponent],
@@ -53,7 +51,6 @@ import { SidebarHostComponent } from './app/sidebar-host.component'
MarkdownModule,
MonacoEditorModule,
SharedPipesModule,
- PatchDbModule,
ToastContainerModule,
ConnectionBarComponentModule,
TuiRootModule,
diff --git a/web/projects/ui/src/app/app.providers.ts b/web/projects/ui/src/app/app.providers.ts
index 2c612f320..400fa0e92 100644
--- a/web/projects/ui/src/app/app.providers.ts
+++ b/web/projects/ui/src/app/app.providers.ts
@@ -1,7 +1,13 @@
import { APP_INITIALIZER, Provider } from '@angular/core'
import { UntypedFormBuilder } from '@angular/forms'
-import { Router, RouteReuseStrategy } from '@angular/router'
+import { Router } from '@angular/router'
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 {
tuiNumberFormatProvider,
@@ -12,22 +18,17 @@ import {
TUI_DATE_TIME_VALUE_TRANSFORMER,
TUI_DATE_VALUE_TRANSFORMER,
} from '@taiga-ui/kit'
-import { RELATIVE_URL, THEME, WorkspaceConfig } from '@start9labs/shared'
-import {
- AbstractCategoryService,
- AbstractMarketplaceService,
-} from '@start9labs/marketplace'
+import { PATCH_DB_PROVIDERS } from 'src/app/services/patch-db/patch-db.providers'
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 { MockApiService } from './services/api/embassy-mock-api.service'
import { AuthService } from './services/auth.service'
+import { CategoryService } from './services/category.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 { DatetimeTransformerService } from './services/datetime-transformer.service'
import { MarketplaceService } from './services/marketplace.service'
-import { CategoryService } from './services/category.service'
+import { ThemeSwitcherService } from './services/theme-switcher.service'
const {
useMocks,
@@ -35,6 +36,7 @@ const {
} = require('../../../../config.json') as WorkspaceConfig
export const APP_PROVIDERS: Provider[] = [
+ PATCH_DB_PROVIDERS,
FilterPackagesPipe,
UntypedFormBuilder,
IonNav,
diff --git a/web/projects/ui/src/app/apps/loading/loading.module.ts b/web/projects/ui/src/app/apps/loading/loading.module.ts
index 3de110846..223f8db47 100644
--- a/web/projects/ui/src/app/apps/loading/loading.module.ts
+++ b/web/projects/ui/src/app/apps/loading/loading.module.ts
@@ -1,6 +1,5 @@
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
-import { InitializingModule } from '@start9labs/shared'
import { LoadingPage } from './loading.page'
const routes: Routes = [
@@ -11,7 +10,6 @@ const routes: Routes = [
]
@NgModule({
- imports: [InitializingModule, RouterModule.forChild(routes)],
- declarations: [LoadingPage],
+ imports: [RouterModule.forChild(routes)],
})
export class LoadingPageModule {}
diff --git a/web/projects/ui/src/app/apps/loading/loading.page.html b/web/projects/ui/src/app/apps/loading/loading.page.html
deleted file mode 100644
index c4ac56866..000000000
--- a/web/projects/ui/src/app/apps/loading/loading.page.html
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/web/projects/ui/src/app/apps/loading/loading.page.ts b/web/projects/ui/src/app/apps/loading/loading.page.ts
index 14fcbeb22..da6970de8 100644
--- a/web/projects/ui/src/app/apps/loading/loading.page.ts
+++ b/web/projects/ui/src/app/apps/loading/loading.page.ts
@@ -1,6 +1,7 @@
import { Component, inject } from '@angular/core'
import { NavController } from '@ionic/angular'
import {
+ InitializingModule,
provideSetupLogsService,
provideSetupService,
} from '@start9labs/shared'
@@ -8,11 +9,18 @@ import {
import { ApiService } from 'src/app/services/api/embassy-api.service'
@Component({
- templateUrl: 'loading.page.html',
+ standalone: true,
+ template: `
+
+ `,
providers: [
provideSetupService(ApiService),
provideSetupLogsService(ApiService),
],
+ imports: [InitializingModule],
})
export class LoadingPage {
readonly navCtrl = inject(NavController)
diff --git a/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.html b/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.html
index 73f0b1e21..adf9c75ed 100644
--- a/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.html
+++ b/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.html
@@ -1,100 +1,106 @@
-
-
-
-
- Trust Your Root CA
-
- Download and trust your server's Root Certificate Authority to establish
- a secure (HTTPS) connection. You will need to repeat this on every
- device you use to connect to your server.
-
-
-
- Bookmark this page
- - Save this page so you can access it later. You can also find the
- address in the
- StartOS-info.html
- file downloaded at the end of initial setup.
-
-
- Download your server's Root CA
- - Your server uses its Root CA to generate SSL/TLS certificates for
- itself and installed services. These certificates are then used to
- encrypt network traffic with your client devices.
-
-
- Download
-
-
-
-
- Trust your server's Root CA
- - Follow instructions for your OS. By trusting your server's Root CA,
- your device can verify the authenticity of encrypted communications
- with your server.
-
-
- View Instructions
-
-
-
-
- Test
- - Refresh the page. If refreshing the page does not work, you may need
- to quit and re-open your browser, then revisit this page.
-
-
- Refresh
-
-
-
-
-
- Skip
-
-
- (not recommended)
-
-
-
-
-
-
- Root CA Trusted!
-
- You have successfully trusted your server's Root CA and may now log in
- securely.
-
-
- Go to login
-
-
-
-
+
+
+
Trust Your Root CA
+
+ Download and trust your server's Root Certificate Authority to establish a
+ secure (HTTPS) connection. You will need to repeat this on every device you
+ use to connect to your server.
+
+
+
+ Bookmark this page
+ - Save this page so you can access it later. You can also find the address
+ in the
+ StartOS-info.html
+ file downloaded at the end of initial setup.
+
+
+ Download your server's Root CA
+ - Your server uses its Root CA to generate SSL/TLS certificates for itself
+ and installed services. These certificates are then used to encrypt
+ network traffic with your client devices.
+
+
+ Download
+
+
+
+ Trust your server's Root CA
+ - Follow instructions for your OS. By trusting your server's Root CA, your
+ device can verify the authenticity of encrypted communications with your
+ server.
+
+
+ View Instructions
+
+
+
+ Test
+ - Refresh the page. If refreshing the page does not work, you may need to
+ quit and re-open your browser, then revisit this page.
+
+
+ Refresh
+
+
+
+
+ Skip
+
+
(not recommended)
-
+
+
+
+
Root CA Trusted!
+
+ You have successfully trusted your server's Root CA and may now log in
+ securely.
+
+
+ Go to login
+
+
+
diff --git a/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.scss b/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.scss
index 48b198525..9ee892c79 100644
--- a/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.scss
+++ b/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.scss
@@ -1,12 +1,4 @@
-#trusted {
- max-width: 40%;
-}
-
-#untrusted {
- max-width: 50%;
-}
-
-.center-container {
+:host {
padding: 1rem;
display: flex;
flex-direction: column;
@@ -15,69 +7,38 @@
min-height: 100vh;
}
-ion-card {
- color: var(--ion-color-dark);
- background: #414141;
- box-shadow: 0 4px 4px rgba(17, 17, 17, 0.144);
- border-radius: 35px;
- 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;
- }
+[tuiButton] {
+ text-transform: uppercase;
+ font-weight: bold;
+ border-radius: 10rem;
+ margin-top: 1rem;
}
-.text-center {
+.card {
+ max-width: max(70%, 40rem);
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 {
- font-size: 17px;
- line-height: 25px;
+ font-size: 1rem;
+ line-height: 1.5rem;
text-align: left;
-
- li {
- padding-bottom: 24px;
- }
-
- ion-button {
- margin-top: 10px;
- }
}
-.refresh {
- --background: var(--ion-color-success-shade);
+li {
+ 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%;
- }
-}
\ No newline at end of file
diff --git a/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.ts b/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.ts
index fde1c968f..0d6b71b6b 100644
--- a/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.ts
+++ b/web/projects/ui/src/app/apps/login/ca-wizard/ca-wizard.component.ts
@@ -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 { 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({
+ standalone: true,
selector: 'ca-wizard',
templateUrl: './ca-wizard.component.html',
styleUrls: ['./ca-wizard.component.scss'],
+ imports: [
+ CommonModule,
+ TuiIconModule,
+ TuiButtonModule,
+ TuiAppearanceModule,
+ TuiCardModule,
+ TuiSurfaceModule,
+ ],
})
export class CAWizardComponent {
- caTrusted = false
+ private readonly api = inject(ApiService)
+ private readonly relativeUrl = inject(RELATIVE_URL)
+ private readonly document = inject(DOCUMENT)
- constructor(
- private readonly api: ApiService,
- public readonly config: ConfigService,
- @Inject(RELATIVE_URL) private readonly relativeUrl: string,
- @Inject(DOCUMENT) public readonly document: Document,
- @Inject(WINDOW) private readonly windowRef: Window,
- ) {}
+ readonly config = inject(ConfigService)
+ caTrusted = false
async ngOnInit() {
await this.testHttps().catch(e =>
@@ -27,17 +39,12 @@ export class CAWizardComponent {
)
}
- download() {
- this.document.getElementById('install-cert')?.click()
- }
-
refresh() {
this.document.location.reload()
}
launchHttps() {
- const host = this.config.getHost()
- this.windowRef.open(`https://${host}`, '_self')
+ this.document.defaultView?.open(`https://${this.config.getHost()}`, '_self')
}
private async testHttps() {
diff --git a/web/projects/ui/src/app/apps/login/login.module.ts b/web/projects/ui/src/app/apps/login/login.module.ts
index 753bfe94e..82b683ff7 100644
--- a/web/projects/ui/src/app/apps/login/login.module.ts
+++ b/web/projects/ui/src/app/apps/login/login.module.ts
@@ -1,12 +1,17 @@
-import { NgModule } from '@angular/core'
-import { RouterModule, Routes } from '@angular/router'
import { CommonModule } from '@angular/common'
+import { NgModule } from '@angular/core'
import { FormsModule } from '@angular/forms'
-import { IonicModule } from '@ionic/angular'
-import { LoginPage } from './login.page'
+import { RouterModule, Routes } from '@angular/router'
+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 { SharedPipesModule } from '@start9labs/shared'
-import { TuiHintModule, TuiTooltipModule } from '@taiga-ui/core'
+import { LoginPage } from './login.page'
+import { LoginWarningComponent } from './warning.component'
const routes: Routes = [
{
@@ -19,12 +24,16 @@ const routes: Routes = [
imports: [
CommonModule,
FormsModule,
- IonicModule,
- SharedPipesModule,
+ CAWizardComponent,
+ LoginWarningComponent,
+ TuiButtonModule,
+ TuiCardModule,
+ TuiSurfaceModule,
+ TuiInputPasswordModule,
+ TuiTextfieldControllerModule,
+ TuiErrorModule,
RouterModule.forChild(routes),
- TuiTooltipModule,
- TuiHintModule,
],
- declarations: [LoginPage, CAWizardComponent],
+ declarations: [LoginPage],
})
export class LoginPageModule {}
diff --git a/web/projects/ui/src/app/apps/login/login.page.html b/web/projects/ui/src/app/apps/login/login.page.html
index 99f6abbe8..1343a9316 100644
--- a/web/projects/ui/src/app/apps/login/login.page.html
+++ b/web/projects/ui/src/app/apps/login/login.page.html
@@ -1,93 +1,26 @@
-
-
-
-
-
+
+
-
-
-
-
-
-
- Http detected
-
- Tor is faster over https. Your Root CA must be trusted.
-
- View instructions
-
-
-
-
- Open Https
-
-
-
-
+
+
+
-
-
-
-
-
-
- Login to StartOS
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/web/projects/ui/src/app/apps/login/login.page.scss b/web/projects/ui/src/app/apps/login/login.page.scss
index 690d97591..65bc582b1 100644
--- a/web/projects/ui/src/app/apps/login/login.page.scss
+++ b/web/projects/ui/src/app/apps/login/login.page.scss
@@ -1,76 +1,35 @@
-.content {
- --background: #333333;
+@import '@taiga-ui/core/styles/taiga-ui-local';
+
+:host {
+ background: var(--tui-base-02);
}
-.grid {
- height: 100%;
- max-width: 540px;
-}
-
-.row {
- height: 100%;
+.card {
+ @include center-all();
+ overflow: visible;
align-items: center;
text-align: center;
+ width: max(50%, 20rem);
}
-.banner {
- position: absolute;
- padding: 20px;
- width: 100%;
- 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;
+.logo {
+ @include center-left();
+ top: -17%;
+ width: 6rem;
}
.header {
- &-icon {
- width: 100px;
- position: absolute;
- 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;
+ margin: 2rem 0 1rem;
+ text-align: center;
+ font-size: 2rem;
}
.error {
- display: block;
- padding-top: 4px;
+ min-height: 2.5rem;
+}
+
+.button {
+ width: 10rem;
+ border-radius: 10rem;
+ margin-bottom: 1rem;
}
diff --git a/web/projects/ui/src/app/apps/login/login.page.ts b/web/projects/ui/src/app/apps/login/login.page.ts
index 86430ff36..b910c0c62 100644
--- a/web/projects/ui/src/app/apps/login/login.page.ts
+++ b/web/projects/ui/src/app/apps/login/login.page.ts
@@ -8,7 +8,6 @@ import { LoadingService } from '@start9labs/shared'
import { TuiDestroyService } from '@taiga-ui/cdk'
import { takeUntil } from 'rxjs'
import { DOCUMENT } from '@angular/common'
-import { WINDOW } from '@ng-web-apis/common'
@Component({
selector: 'login',
@@ -18,7 +17,6 @@ import { WINDOW } from '@ng-web-apis/common'
})
export class LoginPage {
password = ''
- unmasked = false
error = ''
constructor(
@@ -29,14 +27,8 @@ export class LoginPage {
private readonly api: ApiService,
public readonly config: ConfigService,
@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() {
this.error = ''
diff --git a/web/projects/ui/src/app/apps/login/warning.component.ts b/web/projects/ui/src/app/apps/login/warning.component.ts
new file mode 100644
index 000000000..9166a3b9a
--- /dev/null
+++ b/web/projects/ui/src/app/apps/login/warning.component.ts
@@ -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: `
+
+
+ Open Https
+
+ Http detected
+
+ Tor is faster over https. Your Root CA must be trusted.
+
+ View instructions
+
+
+
+ `,
+ 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')
+ }
+}
diff --git a/web/projects/ui/src/app/apps/portal/components/form.component.ts b/web/projects/ui/src/app/apps/portal/components/form.component.ts
new file mode 100644
index 000000000..9c7295520
--- /dev/null
+++ b/web/projects/ui/src/app/apps/portal/components/form.component.ts
@@ -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
{
+ text: string
+ handler?: (value: T) => Promise | void
+ link?: string
+}
+
+export interface FormContext {
+ spec: InputSpec
+ buttons: ActionButton[]
+ value?: T
+ patch?: Operation[]
+}
+
+@Component({
+ standalone: true,
+ selector: 'app-form',
+ template: `
+
+ `,
+ 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> implements OnInit {
+ private readonly dialogFormService = inject(TuiDialogFormService)
+ private readonly formService = inject(FormService)
+ private readonly invalidService = inject(InvalidService)
+ private readonly context = inject>>(
+ 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>['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()
+ })
+ }
+}
diff --git a/web/projects/ui/src/app/apps/portal/components/header/connection.component.ts b/web/projects/ui/src/app/apps/portal/components/header/connection.component.ts
index cdf9a4377..3bf249263 100644
--- a/web/projects/ui/src/app/apps/portal/components/header/connection.component.ts
+++ b/web/projects/ui/src/app/apps/portal/components/header/connection.component.ts
@@ -12,9 +12,12 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
template: `
@if (connection$ | async; as connection) {
+
{{ connection.message }}
}
@@ -27,6 +30,11 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
gap: 0.5rem;
padding: 0 2rem;
}
+
+ :host-context(tui-root._mobile) {
+ display: none;
+ font-size: 1rem;
+ }
`,
],
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -37,6 +45,7 @@ export class HeaderConnectionComponent {
message: string
color: string
icon: string
+ status: string
}> = combineLatest([
inject(ConnectionService).networkConnected$,
inject(ConnectionService).websocketConnected$.pipe(startWith(false)),
@@ -50,30 +59,35 @@ export class HeaderConnectionComponent {
message: 'No Internet',
color: 'var(--tui-error-fill)',
icon: 'tuiIconCloudOff',
+ status: 'error',
}
if (!websocket)
return {
message: 'Connecting',
color: 'var(--tui-warning-fill)',
icon: 'tuiIconCloudOff',
+ status: 'warning',
}
if (status['shutting-down'])
return {
message: 'Shutting Down',
color: 'var(--tui-neutral-fill)',
icon: 'tuiIconPower',
+ status: 'neutral',
}
if (status.restarting)
return {
message: 'Restarting',
color: 'var(--tui-neutral-fill)',
icon: 'tuiIconPower',
+ status: 'neutral',
}
return {
message: 'Connected',
color: 'var(--tui-success-fill)',
icon: 'tuiIconCloud',
+ status: 'success',
}
}),
)
diff --git a/web/projects/ui/src/app/apps/portal/components/header/header.component.ts b/web/projects/ui/src/app/apps/portal/components/header/header.component.ts
index 8d9599943..3da3472d6 100644
--- a/web/projects/ui/src/app/apps/portal/components/header/header.component.ts
+++ b/web/projects/ui/src/app/apps/portal/components/header/header.component.ts
@@ -1,31 +1,47 @@
import { AsyncPipe } from '@angular/common'
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 { HeaderHomeComponent } from './home.component'
import { HeaderCornerComponent } from './corner.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'
@Component({
selector: 'header[appHeader]',
template: `
-
+
@for (item of breadcrumbs$ | async; track $index) {
-
+
}
-
-
-
+
+
+
+
+
+
`,
styles: [
`
@@ -53,9 +69,43 @@ import { BreadcrumbsService } from '../../services/breadcrumbs.service'
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);
position: absolute;
inset: 0;
@@ -83,6 +133,19 @@ import { BreadcrumbsService } from '../../services/breadcrumbs.service'
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,
@@ -90,13 +153,29 @@ import { BreadcrumbsService } from '../../services/breadcrumbs.service'
imports: [
RouterLink,
RouterLinkActive,
+ AsyncPipe,
HeaderConnectionComponent,
HeaderHomeComponent,
HeaderCornerComponent,
- AsyncPipe,
+ HeaderSnekDirective,
HeaderBreadcrumbComponent,
+ HeaderMobileComponent,
],
})
export class HeaderComponent {
+ readonly options = OPTIONS
readonly breadcrumbs$ = inject(BreadcrumbsService)
+ readonly snekScore$ = inject(PatchDB).watch$(
+ 'ui',
+ 'gaming',
+ 'snake',
+ 'high-score',
+ )
+}
+
+const OPTIONS: IsActiveMatchOptions = {
+ paths: 'exact',
+ queryParams: 'ignored',
+ fragment: 'ignored',
+ matrixParams: 'ignored',
}
diff --git a/web/projects/ui/src/app/apps/portal/components/header/menu.component.ts b/web/projects/ui/src/app/apps/portal/components/header/menu.component.ts
index 253a3b0a7..0df040ae8 100644
--- a/web/projects/ui/src/app/apps/portal/components/header/menu.component.ts
+++ b/web/projects/ui/src/app/apps/portal/components/header/menu.component.ts
@@ -16,6 +16,7 @@ import { AuthService } from 'src/app/services/auth.service'
import { ABOUT } from './about.component'
import { getAllPackages } from 'src/app/util/get-package-data'
import { DataModel } from 'src/app/services/patch-db/data-model'
+import { HeaderConnectionComponent } from './connection.component'
@Component({
selector: 'header-menu',
@@ -26,7 +27,9 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
- StartOS
+
+ StartOS
+
About this server
@@ -75,9 +78,16 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
gap: 0.75rem;
}
+ .status {
+ display: flex !important;
+ font-size: 0;
+ padding: 0 0.5rem;
+ height: 2rem;
+ width: 14rem;
+ }
+
.title {
- margin: 0;
- padding: 0 0.5rem 0.25rem;
+ margin: 0 auto 0 0;
font: var(--tui-font-text-l);
font-weight: bold;
}
@@ -96,6 +106,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
TuiSvgModule,
TuiButtonModule,
TuiIconModule,
+ HeaderConnectionComponent,
],
})
export class HeaderMenuComponent {
diff --git a/web/projects/ui/src/app/apps/portal/components/header/mobile.component.ts b/web/projects/ui/src/app/apps/portal/components/header/mobile.component.ts
new file mode 100644
index 000000000..e47815656
--- /dev/null
+++ b/web/projects/ui/src/app/apps/portal/components/header/mobile.component.ts
@@ -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) {
+
+
+
+ }
+ {{ title }}
+
+ `,
+ 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'
+ )
+ }
+}
diff --git a/web/projects/ui/src/app/apps/portal/components/header/snek.component.ts b/web/projects/ui/src/app/apps/portal/components/header/snek.component.ts
new file mode 100644
index 000000000..17caa60d5
--- /dev/null
+++ b/web/projects/ui/src/app/apps/portal/components/header/snek.component.ts
@@ -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: `
+
+
+
+
+ `,
+ 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
+ }
+}
diff --git a/web/projects/ui/src/app/apps/portal/components/header/snek.directive.ts b/web/projects/ui/src/app/apps/portal/components/header/snek.directive.ts
new file mode 100644
index 000000000..d7321d508
--- /dev/null
+++ b/web/projects/ui/src/app/apps/portal/components/header/snek.directive.ts
@@ -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(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(
+ ['gaming', 'snake', 'high-score'],
+ score,
+ )
+ } catch (e: any) {
+ this.errorService.handleError(e)
+ } finally {
+ loader.unsubscribe()
+ }
+ })
+ }
+}
diff --git a/web/projects/ui/src/app/apps/portal/modals/prompt.component.ts b/web/projects/ui/src/app/apps/portal/modals/prompt.component.ts
new file mode 100644
index 000000000..b039048e5
--- /dev/null
+++ b/web/projects/ui/src/app/apps/portal/modals/prompt.component.ts
@@ -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: `
+ {{ options.message }}
+ {{ options.warning }}
+
+
+
+
+
+ `,
+ 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,
+ ) {}
+
+ 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
+}
diff --git a/web/projects/ui/src/app/apps/portal/routes/service/modals/config.component.ts b/web/projects/ui/src/app/apps/portal/routes/service/modals/config.component.ts
index f5c095f35..43f874c47 100644
--- a/web/projects/ui/src/app/apps/portal/routes/service/modals/config.component.ts
+++ b/web/projects/ui/src/app/apps/portal/routes/service/modals/config.component.ts
@@ -29,8 +29,10 @@ import { hasCurrentDeps } from 'src/app/util/has-deps'
import { getAllPackages, getPackage } from 'src/app/util/get-package-data'
import { Breakages } from 'src/app/services/api/api.types'
import { InvalidService } from 'src/app/common/form/invalid.service'
-import { ActionButton, FormPage } from 'src/app/apps/ui/modals/form/form.page'
-import { FormPageModule } from 'src/app/apps/ui/modals/form/form.module'
+import {
+ ActionButton,
+ FormComponent,
+} from 'src/app/apps/portal/components/form.component'
import { PackageConfigData } from '../types/package-config-data'
import { ConfigDepComponent } from '../components/config-dep.component'
@@ -68,7 +70,7 @@ import { ConfigDepComponent } from '../components/config-dep.component'
{{ pkg.manifest.version }}.
-
Reset Defaults
-
+
`,
styles: [
@@ -97,7 +99,7 @@ import { ConfigDepComponent } from '../components/config-dep.component'
standalone: true,
imports: [
CommonModule,
- FormPageModule,
+ FormComponent,
TuiLoaderModule,
TuiNotificationModule,
TuiButtonModule,
@@ -107,8 +109,8 @@ import { ConfigDepComponent } from '../components/config-dep.component'
providers: [InvalidService],
})
export class ServiceConfigModal {
- @ViewChild(FormPage)
- private readonly form?: FormPage>
+ @ViewChild(FormComponent)
+ private readonly form?: FormComponent>
readonly pkgId = this.context.data.pkgId
readonly dependentInfo = this.context.data.dependentInfo
diff --git a/web/projects/ui/src/app/apps/portal/routes/service/routes/actions.component.ts b/web/projects/ui/src/app/apps/portal/routes/service/routes/actions.component.ts
index 3a2856529..52e6371da 100644
--- a/web/projects/ui/src/app/apps/portal/routes/service/routes/actions.component.ts
+++ b/web/projects/ui/src/app/apps/portal/routes/service/routes/actions.component.ts
@@ -13,6 +13,7 @@ import { TUI_PROMPT } from '@taiga-ui/kit'
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
import { PatchDB } from 'patch-db-client'
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 {
Action,
@@ -22,7 +23,6 @@ import {
} from 'src/app/services/patch-db/data-model'
import { hasCurrentDeps } from 'src/app/util/has-deps'
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 { ServiceActionSuccessComponent } from '../components/action-success.component'
import { DesktopService } from '../../../services/desktop.service'
@@ -97,7 +97,7 @@ export class ServiceActionsRoute {
.subscribe()
} else {
if (action['input-spec'] && !isEmptyObject(action['input-spec'])) {
- this.formDialog.open(FormPage, {
+ this.formDialog.open(FormComponent, {
label: action.name,
data: {
spec: action['input-spec'],
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/backups/components/physical.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/backups/components/physical.component.ts
index 019db58ee..5b6357993 100644
--- a/web/projects/ui/src/app/apps/portal/routes/system/backups/components/physical.component.ts
+++ b/web/projects/ui/src/app/apps/portal/routes/system/backups/components/physical.component.ts
@@ -9,9 +9,8 @@ import {
import { TuiForModule } from '@taiga-ui/cdk'
import { TuiSvgModule } from '@taiga-ui/core'
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 { UnknownDisk } from 'src/app/services/api/api.types'
@Component({
selector: 'table[backupsPhysical]',
@@ -69,7 +68,6 @@ import { UnitConversionPipesModule } from '@start9labs/shared'
TuiForModule,
TuiSvgModule,
TuiButtonModule,
- IonicModule,
UnitConversionPipesModule,
],
})
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/backups/modals/targets.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/backups/modals/targets.component.ts
index 4c93e3a0d..ce89c9566 100644
--- a/web/projects/ui/src/app/apps/portal/routes/system/backups/modals/targets.component.ts
+++ b/web/projects/ui/src/app/apps/portal/routes/system/backups/modals/targets.component.ts
@@ -9,7 +9,7 @@ import { TuiNotificationModule } from '@taiga-ui/core'
import { TuiButtonModule, TuiFadeModule } from '@taiga-ui/experimental'
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
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 {
cifsSpec,
@@ -17,7 +17,7 @@ import {
dropboxSpec,
googleDriveSpec,
remoteBackupTargetSpec,
-} from 'src/app/apps/ui/pages/backups/types/target-types'
+} from '../types/target'
import { FormDialogService } from 'src/app/services/form-dialog.service'
import {
BackupTarget,
@@ -126,7 +126,7 @@ export class BackupsTargetsModal implements OnInit {
}
async onUpdate(value: BackupTarget) {
- this.formDialog.open(FormPage, {
+ this.formDialog.open(FormComponent, {
label: 'Update Target',
data: {
value,
@@ -147,7 +147,7 @@ export class BackupsTargetsModal implements OnInit {
}
async addPhysical(disk: UnknownDisk) {
- this.formDialog.open(FormPage, {
+ this.formDialog.open(FormComponent, {
label: 'New Physical Target',
data: {
spec: await configBuilderToSpec(diskBackupTargetSpec),
@@ -173,7 +173,7 @@ export class BackupsTargetsModal implements OnInit {
}
async addRemote() {
- this.formDialog.open(FormPage, {
+ this.formDialog.open(FormComponent, {
label: 'New Remote Target',
data: {
spec: await configBuilderToSpec(remoteBackupTargetSpec),
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/backups/services/restore.service.ts b/web/projects/ui/src/app/apps/portal/routes/system/backups/services/restore.service.ts
index b1f6b3faf..72c37c877 100644
--- a/web/projects/ui/src/app/apps/portal/routes/system/backups/services/restore.service.ts
+++ b/web/projects/ui/src/app/apps/portal/routes/system/backups/services/restore.service.ts
@@ -14,12 +14,12 @@ import {
take,
tap,
} from 'rxjs'
-import { ApiService } from 'src/app/services/api/embassy-api.service'
-import { BackupTarget } from 'src/app/services/api/api.types'
import {
PROMPT,
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 { RECOVER } from '../modals/recover.component'
import { RecoverData } from '../types/recover-data'
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/backups/types/target.ts b/web/projects/ui/src/app/apps/portal/routes/system/backups/types/target.ts
new file mode 100644
index 000000000..fa129fdef
--- /dev/null
+++ b/web/projects/ui/src/app/apps/portal/routes/system/backups/types/target.ts
@@ -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 },
+ }),
+})
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/marketplace/components/sidebars.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/marketplace/components/sidebars.component.ts
index 2a74e64b8..71439d651 100644
--- a/web/projects/ui/src/app/apps/portal/routes/system/marketplace/components/sidebars.component.ts
+++ b/web/projects/ui/src/app/apps/portal/routes/system/marketplace/components/sidebars.component.ts
@@ -13,7 +13,7 @@ import { MarketplaceSidebarService } from '../services/sidebar.service'
`
:host {
position: fixed;
- inset: 7.5rem 0 0;
+ inset: 3.5rem 0 0;
pointer-events: none;
transform: translate3d(0, 0, 0);
}
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/marketplace/modals/registry.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/marketplace/modals/registry.component.ts
index 79c1150e0..d6c656507 100644
--- a/web/projects/ui/src/app/apps/portal/routes/system/marketplace/modals/registry.component.ts
+++ b/web/projects/ui/src/app/apps/portal/routes/system/marketplace/modals/registry.component.ts
@@ -21,11 +21,11 @@ import { TUI_PROMPT } from '@taiga-ui/kit'
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
import { PatchDB } from 'patch-db-client'
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 { DataModel, UIStore } from 'src/app/services/patch-db/data-model'
import { MarketplaceService } from 'src/app/services/marketplace.service'
import { FormDialogService } from 'src/app/services/form-dialog.service'
-import { FormPage } from 'src/app/apps/ui/modals/form/form.page'
import { MarketplaceRegistryComponent } from '../components/registry.component'
import { getMarketplaceValueSpec, getPromptOptions } from '../utils/registry'
@@ -112,7 +112,7 @@ export class MarketplaceRegistryModal {
add() {
const { name, spec } = getMarketplaceValueSpec()
- this.formDialog.open(FormPage, {
+ this.formDialog.open(FormComponent, {
label: name,
data: {
spec,
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/domains/domains.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/domains/domains.component.ts
index 9bff2a680..0ec33bbf9 100644
--- a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/domains/domains.component.ts
+++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/domains/domains.component.ts
@@ -6,14 +6,16 @@ import { TuiButtonModule } from '@taiga-ui/experimental'
import { TUI_PROMPT } from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client'
import { filter, firstValueFrom, map } from 'rxjs'
-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 {
+ FormComponent,
+ FormContext,
+} from 'src/app/apps/portal/components/form.component'
import { ApiService } from 'src/app/services/api/embassy-api.service'
-import { getCustomSpec } from 'src/app/apps/ui/pages/system/domains/domain.const'
-import { getStart9ToSpec } from './constants'
-import { DomainsTableComponent } from './table.component'
+import { FormDialogService } from 'src/app/services/form-dialog.service'
+import { DataModel } from 'src/app/services/patch-db/data-model'
+import { getCustomSpec, getStart9ToSpec } from './constants'
import { DomainsInfoComponent } from './info.component'
+import { DomainsTableComponent } from './table.component'
@Component({
template: `
@@ -121,7 +123,7 @@ export class SettingsDomainsComponent {
},
}
- this.formDialog.open(FormPage, options)
+ this.formDialog.open(FormComponent, options)
}
async claim() {
@@ -146,7 +148,7 @@ export class SettingsDomainsComponent {
},
}
- this.formDialog.open(FormPage, options)
+ this.formDialog.open(FormComponent, options)
}
private getNetworkStrategy(strategy: any) {
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/email/email.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/email/email.component.ts
index f70e30f2a..c29a9213b 100644
--- a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/email/email.component.ts
+++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/email/email.component.ts
@@ -24,24 +24,22 @@ import { EmailInfoComponent } from './info.component'
template: `
-
-
`,
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/proxies/menu.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/proxies/menu.component.ts
index 66f7b2474..bff56bfcc 100644
--- a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/proxies/menu.component.ts
+++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/proxies/menu.component.ts
@@ -17,9 +17,12 @@ import {
} from '@taiga-ui/core'
import { TUI_PROMPT } from '@taiga-ui/kit'
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 { 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 { DELETE_OPTIONS, ProxyUpdate } from './constants'
@@ -132,6 +135,6 @@ export class ProxiesMenuComponent {
},
}
- this.formDialog.open(FormPage, options)
+ this.formDialog.open(FormComponent, options)
}
}
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/proxies/proxies.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/proxies/proxies.component.ts
index 6ca2f6595..3dfc3ae29 100644
--- a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/proxies/proxies.component.ts
+++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/proxies/proxies.component.ts
@@ -4,8 +4,11 @@ import { ErrorService, LoadingService } from '@start9labs/shared'
import { TuiDialogOptions, TuiDialogService } from '@taiga-ui/core'
import { TuiButtonModule } from '@taiga-ui/experimental'
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 { FormContext, FormPage } from 'src/app/apps/ui/modals/form/form.page'
import { FormDialogService } from 'src/app/services/form-dialog.service'
import { ApiService } from 'src/app/services/api/embassy-api.service'
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 {
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/ssh/table.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/ssh/table.component.ts
index e9195ccb6..729baaae1 100644
--- a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/ssh/table.component.ts
+++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/ssh/table.component.ts
@@ -12,11 +12,11 @@ import {
TuiLinkModule,
} from '@taiga-ui/core'
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 { PROMPT } from '../../../../../../ui/modals/prompt/prompt.component'
import { filter, take } from 'rxjs'
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 { TuiForModule } from '@taiga-ui/cdk'
@@ -35,7 +35,7 @@ import { TuiForModule } from '@taiga-ui/cdk'
{{ key.hostname }}
- {{ key['created-at'] | date : 'medium' }}
+ {{ key['created-at'] | date: 'medium' }}
{{ key.alg }}
{{ key.fingerprint }}
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/table.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/table.component.ts
index 8e9e0cd16..c25991f45 100644
--- a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/table.component.ts
+++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/table.component.ts
@@ -15,9 +15,12 @@ import {
TuiIconModule,
TuiTitleModule,
} 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 { 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 { SettingsWifiComponent } from './wifi.component'
@@ -142,7 +145,7 @@ export class WifiTableComponent {
},
}
- this.formDialog.open(FormPage, options)
+ this.formDialog.open(FormComponent, options)
}
}
}
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/wifi.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/wifi.component.ts
index 53c7ee378..68c8ef5ef 100644
--- a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/wifi.component.ts
+++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/wifi/wifi.component.ts
@@ -25,18 +25,17 @@ import {
} from '@taiga-ui/experimental'
import { PatchDB } from 'patch-db-client'
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 { DataModel } from 'src/app/services/patch-db/data-model'
import { WifiInfoComponent } from './info.component'
import { WifiTableComponent } from './table.component'
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 { FormDialogService } from '../../../../../../../services/form-dialog.service'
+import { FormDialogService } from 'src/app/services/form-dialog.service'
@Component({
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 {
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/settings.service.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/settings.service.ts
index e069ab3d3..f8884cd70 100644
--- a/web/projects/ui/src/app/apps/portal/routes/system/settings/settings.service.ts
+++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/settings.service.ts
@@ -6,14 +6,14 @@ import { TUI_PROMPT } from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client'
import { filter, from, take } from 'rxjs'
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 { FormPage } from 'src/app/apps/ui/modals/form/form.page'
import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec'
import { getServerInfo } from 'src/app/util/get-server-info'
import { FormDialogService } from 'src/app/services/form-dialog.service'
import { DataModel } from 'src/app/services/patch-db/data-model'
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'
@@ -160,7 +160,7 @@ export class SettingsService {
switchMap(() => from(configBuilderToSpec(passwordSpec))),
)
.subscribe(spec => {
- this.formDialog.open(FormPage, {
+ this.formDialog.open(FormComponent, {
label: 'Change Master Password',
data: {
spec,
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/snek/snek.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/snek/snek.component.ts
deleted file mode 100644
index 6eb12a532..000000000
--- a/web/projects/ui/src/app/apps/portal/routes/system/snek/snek.component.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { ChangeDetectionStrategy, Component } from '@angular/core'
-
-@Component({
- template: 'Here be snek',
- standalone: true,
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export default class SnekComponent {}
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/system.module.ts b/web/projects/ui/src/app/apps/portal/routes/system/system.module.ts
index 92691e6e2..9b0c6768c 100644
--- a/web/projects/ui/src/app/apps/portal/routes/system/system.module.ts
+++ b/web/projects/ui/src/app/apps/portal/routes/system/system.module.ts
@@ -46,12 +46,6 @@ const ROUTES: Routes = [
loadComponent: () => import('./updates/updates.component'),
data: toNavigationItem('/portal/system/updates'),
},
- {
- title: systemTabResolver,
- path: 'snek',
- loadComponent: () => import('./snek/snek.component'),
- data: toNavigationItem('/portal/system/snek'),
- },
]
@NgModule({ imports: [RouterModule.forChild(ROUTES)] })
diff --git a/web/projects/ui/src/app/apps/portal/services/breadcrumbs.service.ts b/web/projects/ui/src/app/apps/portal/services/breadcrumbs.service.ts
index 631ee0605..89593a545 100644
--- a/web/projects/ui/src/app/apps/portal/services/breadcrumbs.service.ts
+++ b/web/projects/ui/src/app/apps/portal/services/breadcrumbs.service.ts
@@ -30,7 +30,7 @@ export class BreadcrumbsService extends BehaviorSubject {
const packages = await getAllPackages(this.patch)
try {
- this.next(toBreadcrumbs(page, packages))
+ this.next(toBreadcrumbs(page.split('?')[0], packages))
} catch (e) {
this.next([])
}
diff --git a/web/projects/ui/src/app/services/patch-db/patch-db.module.ts b/web/projects/ui/src/app/services/patch-db/patch-db.module.ts
deleted file mode 100644
index 3c816e339..000000000
--- a/web/projects/ui/src/app/services/patch-db/patch-db.module.ts
+++ /dev/null
@@ -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 {}
diff --git a/web/projects/ui/src/app/services/patch-db/patch-db.providers.ts b/web/projects/ui/src/app/services/patch-db/patch-db.providers.ts
new file mode 100644
index 000000000..e47f1a9a5
--- /dev/null
+++ b/web/projects/ui/src/app/services/patch-db/patch-db.providers.ts
@@ -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,
+ },
+]
diff --git a/web/projects/ui/src/styles.scss b/web/projects/ui/src/styles.scss
index 0ebb45238..2a92038a5 100644
--- a/web/projects/ui/src/styles.scss
+++ b/web/projects/ui/src/styles.scss
@@ -369,15 +369,21 @@ ul {
}
.g-page {
+ @include customize-scroll();
+
display: block;
- height: 100%;
+ height: calc(100% - 0.375rem);
padding: 1px 2rem 3rem;
box-sizing: border-box;
overflow: auto;
isolation: isolate;
+ backdrop-filter: blur(2rem);
+ margin: 0 0.375rem;
+ border-radius: 0.375rem;
// TODO: Theme
- background: #373a3f;
+ background: rgb(55 58 63 / 90%);
+ box-shadow: inset 0 1px rgb(255 255 255 / 10%);
}
.g-edged {