mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
Merge pull request #2618 from Start9Labs/service
refactor: change service page to the new design
This commit is contained in:
@@ -16,6 +16,7 @@ export type PackageDataEntry = {
|
|||||||
developerKey: string;
|
developerKey: string;
|
||||||
icon: DataUrl;
|
icon: DataUrl;
|
||||||
lastBackup: string | null;
|
lastBackup: string | null;
|
||||||
|
nextBackup: string | null;
|
||||||
currentDependencies: CurrentDependencies;
|
currentDependencies: CurrentDependencies;
|
||||||
actions: { [key: ActionId]: ActionMetadata };
|
actions: { [key: ActionId]: ActionMetadata };
|
||||||
serviceInterfaces: {
|
serviceInterfaces: {
|
||||||
|
|||||||
184
web/package-lock.json
generated
184
web/package-lock.json
generated
@@ -23,17 +23,17 @@
|
|||||||
"@start9labs/argon2": "^0.1.0",
|
"@start9labs/argon2": "^0.1.0",
|
||||||
"@start9labs/emver": "^0.1.5",
|
"@start9labs/emver": "^0.1.5",
|
||||||
"@start9labs/start-sdk": "file:../sdk/dist",
|
"@start9labs/start-sdk": "file:../sdk/dist",
|
||||||
"@taiga-ui/addon-charts": "3.73.0",
|
"@taiga-ui/addon-charts": "3.77.1",
|
||||||
"@taiga-ui/addon-commerce": "3.73.0",
|
"@taiga-ui/addon-commerce": "3.77.1",
|
||||||
"@taiga-ui/addon-mobile": "3.73.0",
|
"@taiga-ui/addon-mobile": "3.77.1",
|
||||||
"@taiga-ui/cdk": "3.73.0",
|
"@taiga-ui/cdk": "3.77.1",
|
||||||
"@taiga-ui/core": "3.73.0",
|
"@taiga-ui/core": "3.77.1",
|
||||||
"@taiga-ui/experimental": "3.73.0",
|
"@taiga-ui/experimental": "3.77.1",
|
||||||
"@taiga-ui/icons": "3.73.0",
|
"@taiga-ui/icons": "3.77.1",
|
||||||
"@taiga-ui/kit": "3.73.0",
|
"@taiga-ui/kit": "3.77.1",
|
||||||
"@taiga-ui/styles": "3.73.0",
|
"@taiga-ui/styles": "3.77.1",
|
||||||
"@tinkoff/ng-dompurify": "4.0.0",
|
"@tinkoff/ng-dompurify": "4.0.0",
|
||||||
"@tinkoff/ng-event-plugins": "3.1.1",
|
"@tinkoff/ng-event-plugins": "3.2.0",
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"cbor": "npm:@jprochazk/cbor@^0.4.9",
|
"cbor": "npm:@jprochazk/cbor@^0.4.9",
|
||||||
@@ -5085,72 +5085,72 @@
|
|||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/addon-charts": {
|
"node_modules/@taiga-ui/addon-charts": {
|
||||||
"version": "3.73.0",
|
"version": "3.77.1",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.73.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.77.1.tgz",
|
||||||
"integrity": "sha512-4zK0sXqMtuz2fVlsgbnBLMuzphCqLbsSR/owMzJxAsrlZV7Lv1VUaMHHe9sdhDBmvO3VZu2d1wNuv/Z1jVfpYQ==",
|
"integrity": "sha512-yk9cvMewoRRVxTv1sIorVgO9GBkleQyRzkT774RPQACQHizGS2Sq5dQMtetwD3usPmLR2GblDR6NM+iOyHhMIA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.6.2"
|
"tslib": "^2.6.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": ">=12.0.0",
|
"@angular/common": ">=12.0.0",
|
||||||
"@angular/core": ">=12.0.0",
|
"@angular/core": ">=12.0.0",
|
||||||
"@ng-web-apis/common": "3.0.6",
|
"@ng-web-apis/common": "^3.0.6",
|
||||||
"@taiga-ui/cdk": "^3.73.0",
|
"@taiga-ui/cdk": "^3.77.1",
|
||||||
"@taiga-ui/core": "^3.73.0",
|
"@taiga-ui/core": "^3.77.1",
|
||||||
"@tinkoff/ng-polymorpheus": "4.3.0"
|
"@tinkoff/ng-polymorpheus": "^4.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/addon-commerce": {
|
"node_modules/@taiga-ui/addon-commerce": {
|
||||||
"version": "3.73.0",
|
"version": "3.77.1",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-3.73.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-3.77.1.tgz",
|
||||||
"integrity": "sha512-LVK1ROrutavqIvOEHCaXRcX9ZNyznz3cCQFhbLldTIgTSXoYQNNitAgG4cRmntLioLupcVnKt0JZOHiObEx49A==",
|
"integrity": "sha512-4qNgDZfxyzTHQdKaerw28xP8d2PCuumLM9xjlRbNE5XcnhUI+rccgZOV9MtZtQoNBl++OBPmbaT2/9+zbSqbxg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.6.2"
|
"tslib": "^2.6.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": ">=12.0.0",
|
"@angular/common": ">=12.0.0",
|
||||||
"@angular/core": ">=12.0.0",
|
"@angular/core": ">=12.0.0",
|
||||||
"@angular/forms": ">=12.0.0",
|
"@angular/forms": ">=12.0.0",
|
||||||
"@maskito/angular": "1.9.0",
|
"@maskito/angular": "^1.9.0",
|
||||||
"@maskito/core": "1.9.0",
|
"@maskito/core": "^1.9.0",
|
||||||
"@maskito/kit": "1.9.0",
|
"@maskito/kit": "^1.9.0",
|
||||||
"@ng-web-apis/common": "3.0.6",
|
"@ng-web-apis/common": "^3.0.6",
|
||||||
"@taiga-ui/cdk": "^3.73.0",
|
"@taiga-ui/cdk": "^3.77.1",
|
||||||
"@taiga-ui/core": "^3.73.0",
|
"@taiga-ui/core": "^3.77.1",
|
||||||
"@taiga-ui/i18n": "^3.73.0",
|
"@taiga-ui/i18n": "^3.77.1",
|
||||||
"@taiga-ui/kit": "^3.73.0",
|
"@taiga-ui/kit": "^3.77.1",
|
||||||
"@tinkoff/ng-polymorpheus": "4.3.0",
|
"@tinkoff/ng-polymorpheus": "^4.3.0",
|
||||||
"rxjs": ">=6.0.0"
|
"rxjs": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/addon-mobile": {
|
"node_modules/@taiga-ui/addon-mobile": {
|
||||||
"version": "3.73.0",
|
"version": "3.77.1",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-3.73.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-3.77.1.tgz",
|
||||||
"integrity": "sha512-XJ8xlmnp3fU02KjqgFtUCkcMsPmdIvksI/vmK7ZCaNunsGIYVAGSqw3/yyRV+HkX1ccqynX5johv+lligD9RtA==",
|
"integrity": "sha512-kRa9uNdnY9Tyz8FdGXqgbL7cKZ1Z43K3Kjey9tX3uf4BEkODPNVuQFTNzuZvOt6/ql+XtPxi/hU19ii5rvKz3A==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.6.2"
|
"tslib": "^2.6.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/cdk": ">=12.0.0",
|
"@angular/cdk": ">=12.0.0",
|
||||||
"@angular/common": ">=12.0.0",
|
"@angular/common": ">=12.0.0",
|
||||||
"@angular/core": ">=12.0.0",
|
"@angular/core": ">=12.0.0",
|
||||||
"@ng-web-apis/common": "3.0.6",
|
"@ng-web-apis/common": "^3.0.6",
|
||||||
"@taiga-ui/cdk": "^3.73.0",
|
"@taiga-ui/cdk": "^3.77.1",
|
||||||
"@taiga-ui/core": "^3.73.0",
|
"@taiga-ui/core": "^3.77.1",
|
||||||
"@taiga-ui/kit": "^3.73.0",
|
"@taiga-ui/kit": "^3.77.1",
|
||||||
"@tinkoff/ng-polymorpheus": "4.3.0",
|
"@tinkoff/ng-polymorpheus": "^4.3.0",
|
||||||
"rxjs": ">=6.0.0"
|
"rxjs": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/cdk": {
|
"node_modules/@taiga-ui/cdk": {
|
||||||
"version": "3.73.0",
|
"version": "3.77.1",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.73.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.77.1.tgz",
|
||||||
"integrity": "sha512-bxgbetJcbB+9h6ZO0ht076A9K0UOemr5D339o8h2tQpFnjOiw7gQ0ZnpNrKXXOpb5eASnptulmwtITvagpE1lg==",
|
"integrity": "sha512-mqT1Rz/JWqah4aNVRD3iEND4XdkfsOxV100D0gbdFD5/aaiMTyL6oMEP2GGE0lSsuk1mRCixkDW0qPCbhG21yQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ng-web-apis/common": "3.0.6",
|
"@ng-web-apis/common": "3.0.6",
|
||||||
"@ng-web-apis/mutation-observer": "3.1.0",
|
"@ng-web-apis/mutation-observer": "3.1.0",
|
||||||
"@ng-web-apis/resize-observer": "3.0.6",
|
"@ng-web-apis/resize-observer": "3.0.6",
|
||||||
"@tinkoff/ng-event-plugins": "3.1.1",
|
"@tinkoff/ng-event-plugins": "3.2.0",
|
||||||
"@tinkoff/ng-polymorpheus": "4.3.0",
|
"@tinkoff/ng-polymorpheus": "4.3.0",
|
||||||
"tslib": "2.6.2"
|
"tslib": "2.6.2"
|
||||||
},
|
},
|
||||||
@@ -5172,12 +5172,12 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/core": {
|
"node_modules/@taiga-ui/core": {
|
||||||
"version": "3.73.0",
|
"version": "3.77.1",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.73.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.77.1.tgz",
|
||||||
"integrity": "sha512-zQ+qhgBxQK6+SrzfSHIotKI00xbPu0ZTBFyYGUT8zD0If4aN5oeR+Q5A9kUb/0rEPsHcg04TcAEtAsAtG2l/oA==",
|
"integrity": "sha512-RjrOZtkQbb+Hw+7wMTt8j76OkGiLrCDb2Ol1rmngFQfzPX5OlaRkBtNhCNoyBtI9rl4CCX2/ViCGnGmrIkHu6g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@taiga-ui/i18n": "^3.73.0",
|
"@taiga-ui/i18n": "^3.77.1",
|
||||||
"tslib": "2.6.2"
|
"tslib": "^2.6.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/animations": ">=12.0.0",
|
"@angular/animations": ">=12.0.0",
|
||||||
@@ -5186,68 +5186,68 @@
|
|||||||
"@angular/forms": ">=12.0.0",
|
"@angular/forms": ">=12.0.0",
|
||||||
"@angular/platform-browser": ">=12.0.0",
|
"@angular/platform-browser": ">=12.0.0",
|
||||||
"@angular/router": ">=12.0.0",
|
"@angular/router": ">=12.0.0",
|
||||||
"@ng-web-apis/common": "3.0.6",
|
"@ng-web-apis/common": "^3.0.6",
|
||||||
"@ng-web-apis/mutation-observer": "3.1.0",
|
"@ng-web-apis/mutation-observer": "^3.1.0",
|
||||||
"@taiga-ui/cdk": "^3.73.0",
|
"@taiga-ui/cdk": "^3.77.1",
|
||||||
"@taiga-ui/i18n": "^3.73.0",
|
"@taiga-ui/i18n": "^3.77.1",
|
||||||
"@tinkoff/ng-event-plugins": "3.1.1",
|
"@tinkoff/ng-event-plugins": "^3.2.0",
|
||||||
"@tinkoff/ng-polymorpheus": "4.3.0",
|
"@tinkoff/ng-polymorpheus": "^4.3.0",
|
||||||
"rxjs": ">=6.0.0"
|
"rxjs": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/experimental": {
|
"node_modules/@taiga-ui/experimental": {
|
||||||
"version": "3.73.0",
|
"version": "3.77.1",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-3.73.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-3.77.1.tgz",
|
||||||
"integrity": "sha512-FlMrasE7emFTNCN2a6xdU9ZCH7HIAU/xd+jjqxsi9Cf37juDLFtInFjFyQ9ZuEnhIJP3POTX+d8t/niRTPwpNw==",
|
"integrity": "sha512-UVmP0y1w9VvOoQoTH4VByZ3m45OcC0Lw//rsrNMicPCX9b5IJxgEjUAmJ+PZ/ecsgnDVKO0HD18cZt5uCVdy7w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.6.2"
|
"tslib": "^2.6.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": ">=12.0.0",
|
"@angular/common": ">=12.0.0",
|
||||||
"@angular/core": ">=12.0.0",
|
"@angular/core": ">=12.0.0",
|
||||||
"@taiga-ui/addon-commerce": "^3.73.0",
|
"@taiga-ui/addon-commerce": "^3.77.1",
|
||||||
"@taiga-ui/cdk": "^3.73.0",
|
"@taiga-ui/cdk": "^3.77.1",
|
||||||
"@taiga-ui/core": "^3.73.0",
|
"@taiga-ui/core": "^3.77.1",
|
||||||
"@taiga-ui/kit": "^3.73.0",
|
"@taiga-ui/kit": "^3.77.1",
|
||||||
"@tinkoff/ng-polymorpheus": "4.3.0",
|
"@tinkoff/ng-polymorpheus": "^4.3.0",
|
||||||
"rxjs": ">=6.0.0"
|
"rxjs": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/i18n": {
|
"node_modules/@taiga-ui/i18n": {
|
||||||
"version": "3.73.0",
|
"version": "3.77.1",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.73.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.77.1.tgz",
|
||||||
"integrity": "sha512-CLyYmnnL08+P5XvnKSMhZPpJyBs4199ApMX33hPBa4MvpmykUoVXQ0nXslLECSqAcfL39N9tzWKnxyLAavXjNw==",
|
"integrity": "sha512-rC2RYMFyaRHKcoU5WUKMe2DU0urRNh0daKvRIhp8t5AnTQE1MDZHRJYVCpysr8sfXvtJdr5oHoZccavzvqjqOQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.6.2"
|
"tslib": "^2.6.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/core": ">=12.0.0",
|
"@angular/core": ">=12.0.0",
|
||||||
"@ng-web-apis/common": "3.0.6",
|
"@ng-web-apis/common": "^3.0.6",
|
||||||
"rxjs": ">=6.0.0"
|
"rxjs": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/icons": {
|
"node_modules/@taiga-ui/icons": {
|
||||||
"version": "3.73.0",
|
"version": "3.77.1",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.73.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.77.1.tgz",
|
||||||
"integrity": "sha512-3/B9NW/gQ4ad+voS57TSuz7cCElh1hcqUil4wP2P8pwS3HzgScokZpvVQs6u5jZ0Xt4v7uG4pac6pwGVsC8ndA==",
|
"integrity": "sha512-e9NXwxOktKM9TVDVePVkTFAuAs9jlhZCKzpAOfrme1bHVQJ1MEhD2hdj74Yn7IGKOCFjrUDQnxJ9w9POh6H9Vg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.6.2"
|
"tslib": "^2.6.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@taiga-ui/cdk": "^3.73.0"
|
"@taiga-ui/cdk": "^3.77.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/kit": {
|
"node_modules/@taiga-ui/kit": {
|
||||||
"version": "3.73.0",
|
"version": "3.77.1",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.73.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.77.1.tgz",
|
||||||
"integrity": "sha512-AKPjxkcKnMA4TeL5f6gBjgnJff7JJfh2YmOxHPcXXcBy/BeXzY+Lfmlgqz0P8Rtz01JouCZpWIWNayb2cGlq5g==",
|
"integrity": "sha512-Pyk44wDF61VZiMQHehQ3KEEBj5AsO3et8QccKwmqr13jaQzmH6ljwbbYJOwPpEVsdZbqfiI56OVxp86mi6F8mw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@maskito/angular": "1.9.0",
|
"@maskito/angular": "1.9.0",
|
||||||
"@maskito/core": "1.9.0",
|
"@maskito/core": "1.9.0",
|
||||||
"@maskito/kit": "1.9.0",
|
"@maskito/kit": "1.9.0",
|
||||||
"@ng-web-apis/intersection-observer": "3.2.0",
|
"@ng-web-apis/intersection-observer": "3.2.0",
|
||||||
"text-mask-core": "5.1.2",
|
"text-mask-core": "5.1.2",
|
||||||
"tslib": "2.6.2"
|
"tslib": "^2.6.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": ">=12.0.0",
|
"@angular/common": ">=12.0.0",
|
||||||
@@ -5255,22 +5255,22 @@
|
|||||||
"@angular/forms": ">=12.0.0",
|
"@angular/forms": ">=12.0.0",
|
||||||
"@angular/router": ">=12.0.0",
|
"@angular/router": ">=12.0.0",
|
||||||
"@ng-web-apis/common": "3.0.6",
|
"@ng-web-apis/common": "3.0.6",
|
||||||
"@ng-web-apis/mutation-observer": "3.1.0",
|
"@ng-web-apis/mutation-observer": "^3.1.0",
|
||||||
"@ng-web-apis/resize-observer": "3.0.6",
|
"@ng-web-apis/resize-observer": "^3.0.6",
|
||||||
"@taiga-ui/cdk": "^3.73.0",
|
"@taiga-ui/cdk": "^3.77.1",
|
||||||
"@taiga-ui/core": "^3.73.0",
|
"@taiga-ui/core": "^3.77.1",
|
||||||
"@taiga-ui/i18n": "^3.73.0",
|
"@taiga-ui/i18n": "^3.77.1",
|
||||||
"@tinkoff/ng-polymorpheus": "4.3.0",
|
"@tinkoff/ng-polymorpheus": "^4.3.0",
|
||||||
"rxjs": ">=6.0.0"
|
"rxjs": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@taiga-ui/styles": {
|
"node_modules/@taiga-ui/styles": {
|
||||||
"version": "3.73.0",
|
"version": "3.77.1",
|
||||||
"resolved": "https://registry.npmjs.org/@taiga-ui/styles/-/styles-3.73.0.tgz",
|
"resolved": "https://registry.npmjs.org/@taiga-ui/styles/-/styles-3.77.1.tgz",
|
||||||
"integrity": "sha512-hFInEKxsI4fj5KZCIGzH/zY00YwE2PsUoAlzoeGqpfnOG5rY/2WFpZnLrj7yJCOkFzsFy9KUZC00d46w50ijnQ==",
|
"integrity": "sha512-YoPXqY1pz+13vYhHnE6hskjRtnDDGgjnZIPtx05ChN8/TDIJ6NiaWKI5Es/G4fq3vKJnq2yIZ9D2Ghm/aPItGw==",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@taiga-ui/cdk": "^3.73.0",
|
"@taiga-ui/cdk": "^3.77.1",
|
||||||
"tslib": "2.6.2"
|
"tslib": "^2.6.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tinkoff/ng-dompurify": {
|
"node_modules/@tinkoff/ng-dompurify": {
|
||||||
@@ -5287,9 +5287,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tinkoff/ng-event-plugins": {
|
"node_modules/@tinkoff/ng-event-plugins": {
|
||||||
"version": "3.1.1",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tinkoff/ng-event-plugins/-/ng-event-plugins-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@tinkoff/ng-event-plugins/-/ng-event-plugins-3.2.0.tgz",
|
||||||
"integrity": "sha512-JC64sdpiOHmq4rZBFzqVweQoPxN7afoCuebKayEELEODho6oYiTVyM3P9T9JWUyzieSujDP7um7fiCggrWc83A==",
|
"integrity": "sha512-n56R5xNfiytabh2WmWdQXfNU6m7dfOo3LLxlARE+DX7f5yciW2xBdDkuEHX74q8dlCuAVlW9aslSfz8c//ymwA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.2.0"
|
"tslib": "^2.2.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -45,17 +45,17 @@
|
|||||||
"@start9labs/argon2": "^0.1.0",
|
"@start9labs/argon2": "^0.1.0",
|
||||||
"@start9labs/emver": "^0.1.5",
|
"@start9labs/emver": "^0.1.5",
|
||||||
"@start9labs/start-sdk": "file:../sdk/dist",
|
"@start9labs/start-sdk": "file:../sdk/dist",
|
||||||
"@taiga-ui/addon-charts": "3.73.0",
|
"@taiga-ui/addon-charts": "3.77.1",
|
||||||
"@taiga-ui/addon-commerce": "3.73.0",
|
"@taiga-ui/addon-commerce": "3.77.1",
|
||||||
"@taiga-ui/addon-mobile": "3.73.0",
|
"@taiga-ui/addon-mobile": "3.77.1",
|
||||||
"@taiga-ui/cdk": "3.73.0",
|
"@taiga-ui/cdk": "3.77.1",
|
||||||
"@taiga-ui/core": "3.73.0",
|
"@taiga-ui/core": "3.77.1",
|
||||||
"@taiga-ui/experimental": "3.73.0",
|
"@taiga-ui/experimental": "3.77.1",
|
||||||
"@taiga-ui/icons": "3.73.0",
|
"@taiga-ui/icons": "3.77.1",
|
||||||
"@taiga-ui/kit": "3.73.0",
|
"@taiga-ui/kit": "3.77.1",
|
||||||
"@taiga-ui/styles": "3.73.0",
|
"@taiga-ui/styles": "3.77.1",
|
||||||
"@tinkoff/ng-dompurify": "4.0.0",
|
"@tinkoff/ng-dompurify": "4.0.0",
|
||||||
"@tinkoff/ng-event-plugins": "3.1.1",
|
"@tinkoff/ng-event-plugins": "3.2.0",
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"cbor": "npm:@jprochazk/cbor@^0.4.9",
|
"cbor": "npm:@jprochazk/cbor@^0.4.9",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
& > * + * {
|
& > * + * {
|
||||||
border-top-width: 1px;
|
border-top-width: 1px;
|
||||||
border-bottom-width: 0px;
|
border-bottom-width: 0;
|
||||||
border-color: rgb(113 113 122);
|
border-color: rgb(113 113 122);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
/* stylelint-disable order/order */
|
/* stylelint-disable order/order */
|
||||||
[tuiAppearance][data-appearance='secondary-warning'] {
|
[tuiAppearance][data-appearance='secondary-warning'] {
|
||||||
background: var(--tui-warning-bg);
|
background: var(--tui-warning-bg);
|
||||||
color: var(--tui-warning-fill);
|
color: var(--tui-text-01);
|
||||||
|
|
||||||
@include appearance-hover {
|
@include appearance-hover {
|
||||||
background: var(--tui-warning-bg-hover);
|
background: var(--tui-warning-bg-hover);
|
||||||
@@ -58,6 +58,7 @@
|
|||||||
|
|
||||||
@include appearance-disabled {
|
@include appearance-disabled {
|
||||||
background: #eaecee;
|
background: #eaecee;
|
||||||
|
color: #333;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +76,7 @@
|
|||||||
|
|
||||||
@include appearance-disabled {
|
@include appearance-disabled {
|
||||||
background: #eaecee;
|
background: #eaecee;
|
||||||
|
color: #333;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +94,7 @@
|
|||||||
|
|
||||||
@include appearance-disabled {
|
@include appearance-disabled {
|
||||||
background: #eaecee;
|
background: #eaecee;
|
||||||
|
color: #333;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +112,7 @@
|
|||||||
|
|
||||||
@include appearance-disabled {
|
@include appearance-disabled {
|
||||||
background: #eaecee;
|
background: #eaecee;
|
||||||
|
color: #333;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +130,7 @@
|
|||||||
|
|
||||||
@include appearance-disabled {
|
@include appearance-disabled {
|
||||||
background: #eaecee;
|
background: #eaecee;
|
||||||
|
color: #333;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
class="button"
|
class="button"
|
||||||
[class.button_open]="open"
|
[class.button_open]="open"
|
||||||
[style.border-radius.%]="100"
|
[style.border-radius.%]="100"
|
||||||
[appearance]="invalid ? 'secondary-destructive' : 'secondary'"
|
[appearance]="invalid ? 'danger-solid' : 'secondary'"
|
||||||
></button>
|
></button>
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
{{ spec.name }}
|
{{ spec.name }}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
import { TuiSvgModule } from '@taiga-ui/core'
|
import { TuiSvgModule } from '@taiga-ui/core'
|
||||||
|
import { TuiIconModule } from '@taiga-ui/experimental'
|
||||||
|
|
||||||
interface ActionItem {
|
interface ActionItem {
|
||||||
readonly icon: string
|
readonly icon: string
|
||||||
@@ -10,7 +11,7 @@ interface ActionItem {
|
|||||||
@Component({
|
@Component({
|
||||||
selector: '[action]',
|
selector: '[action]',
|
||||||
template: `
|
template: `
|
||||||
<tui-svg [src]="action.icon"></tui-svg>
|
<tui-icon [icon]="action.icon"></tui-icon>
|
||||||
<div>
|
<div>
|
||||||
<strong>{{ action.name }}</strong>
|
<strong>{{ action.name }}</strong>
|
||||||
<div>{{ action.description }}</div>
|
<div>{{ action.description }}</div>
|
||||||
@@ -18,7 +19,7 @@ interface ActionItem {
|
|||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [TuiSvgModule],
|
imports: [TuiIconModule],
|
||||||
})
|
})
|
||||||
export class ServiceActionComponent {
|
export class ServiceActionComponent {
|
||||||
@Input({ required: true })
|
@Input({ required: true })
|
||||||
|
|||||||
@@ -4,13 +4,16 @@ import {
|
|||||||
inject,
|
inject,
|
||||||
Input,
|
Input,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
|
import { Manifest } from '@startos'
|
||||||
import { tuiPure } from '@taiga-ui/cdk'
|
import { tuiPure } from '@taiga-ui/cdk'
|
||||||
import { TuiButtonModule } from '@taiga-ui/experimental'
|
import {
|
||||||
|
TuiButtonModule,
|
||||||
|
tuiButtonOptionsProvider,
|
||||||
|
} from '@taiga-ui/experimental'
|
||||||
import { DependencyInfo } from 'src/app/routes/portal/routes/service/types/dependency-info'
|
import { DependencyInfo } from 'src/app/routes/portal/routes/service/types/dependency-info'
|
||||||
import { ActionsService } from 'src/app/services/actions.service'
|
import { ActionsService } from 'src/app/services/actions.service'
|
||||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
import { getManifest } from 'src/app/utils/get-package-data'
|
import { getManifest } from 'src/app/utils/get-package-data'
|
||||||
import { Manifest } from '../../../../../../../../../../core/startos/bindings/Manifest'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'service-actions',
|
selector: 'service-actions',
|
||||||
@@ -18,7 +21,7 @@ import { Manifest } from '../../../../../../../../../../core/startos/bindings/Ma
|
|||||||
@if (pkg.status.main.status === 'running') {
|
@if (pkg.status.main.status === 'running') {
|
||||||
<button
|
<button
|
||||||
tuiButton
|
tuiButton
|
||||||
appearance="secondary-destructive"
|
appearance="danger-solid"
|
||||||
iconLeft="tuiIconSquare"
|
iconLeft="tuiIconSquare"
|
||||||
(click)="actions.stop(manifest)"
|
(click)="actions.stop(manifest)"
|
||||||
>
|
>
|
||||||
@@ -27,7 +30,6 @@ import { Manifest } from '../../../../../../../../../../core/startos/bindings/Ma
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
tuiButton
|
tuiButton
|
||||||
appearance="secondary"
|
|
||||||
iconLeft="tuiIconRotateCw"
|
iconLeft="tuiIconRotateCw"
|
||||||
(click)="actions.restart(manifest)"
|
(click)="actions.restart(manifest)"
|
||||||
>
|
>
|
||||||
@@ -56,10 +58,20 @@ import { Manifest } from '../../../../../../../../../../core/startos/bindings/Ma
|
|||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
styles: [':host { display: flex; gap: 1rem }'],
|
styles: [
|
||||||
|
`
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [TuiButtonModule],
|
imports: [TuiButtonModule],
|
||||||
|
providers: [tuiButtonOptionsProvider({ size: 's' })],
|
||||||
})
|
})
|
||||||
export class ServiceActionsComponent {
|
export class ServiceActionsComponent {
|
||||||
@Input({ required: true })
|
@Input({ required: true })
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
|
||||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
|
||||||
import { ToAdditionalPipe } from '../pipes/to-additional.pipe'
|
|
||||||
import { ServiceAdditionalItemComponent } from './additional-item.component'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'service-additional',
|
|
||||||
template: `
|
|
||||||
<h3 class="g-title">Additional Info</h3>
|
|
||||||
<ng-container *ngFor="let additional of pkg | toAdditional">
|
|
||||||
<a
|
|
||||||
*ngIf="additional.description.startsWith('http'); else button"
|
|
||||||
class="g-action"
|
|
||||||
[additionalItem]="additional"
|
|
||||||
></a>
|
|
||||||
<ng-template #button>
|
|
||||||
<button
|
|
||||||
class="g-action"
|
|
||||||
[style.pointer-events]="!additional.icon ? 'none' : null"
|
|
||||||
[additionalItem]="additional"
|
|
||||||
(click)="additional.action?.()"
|
|
||||||
></button>
|
|
||||||
</ng-template>
|
|
||||||
</ng-container>
|
|
||||||
`,
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
standalone: true,
|
|
||||||
imports: [CommonModule, ToAdditionalPipe, ServiceAdditionalItemComponent],
|
|
||||||
})
|
|
||||||
export class ServiceAdditionalComponent {
|
|
||||||
@Input({ required: true })
|
|
||||||
pkg!: PackageDataEntry
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { I18nPluralPipe } from '@angular/common'
|
||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
computed,
|
||||||
|
input,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { RouterLink } from '@angular/router'
|
||||||
|
import { PackageDataEntry } from '@startos'
|
||||||
|
import { TuiButtonModule } from '@taiga-ui/experimental'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'service-backups',
|
||||||
|
template: `
|
||||||
|
<div [style.flex]="1">
|
||||||
|
<small>Last backup</small>
|
||||||
|
{{ previous() | i18nPlural: ago }}
|
||||||
|
</div>
|
||||||
|
<div [style.flex]="1">
|
||||||
|
<small>Next backup</small>
|
||||||
|
{{ next() | i18nPlural: in }}
|
||||||
|
</div>
|
||||||
|
<div [style.min-width.%]="100">
|
||||||
|
<a
|
||||||
|
tuiButton
|
||||||
|
iconLeft="tuiIconPlusSquare"
|
||||||
|
routerLink="/portal/system/backups"
|
||||||
|
size="s"
|
||||||
|
appearance="secondary-warning"
|
||||||
|
>
|
||||||
|
Manage
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
styles: `
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
|
||||||
|
small {
|
||||||
|
display: block;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--tui-text-02);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
|
imports: [TuiButtonModule, RouterLink, I18nPluralPipe],
|
||||||
|
})
|
||||||
|
export class ServiceBackupsComponent {
|
||||||
|
pkg = input.required<PackageDataEntry>()
|
||||||
|
|
||||||
|
readonly previous = computed(() =>
|
||||||
|
daysBetween(new Date(), new Date(this.pkg().lastBackup || new Date())),
|
||||||
|
)
|
||||||
|
|
||||||
|
readonly next = computed(() =>
|
||||||
|
daysBetween(new Date(), new Date(this.pkg().nextBackup || new Date())),
|
||||||
|
)
|
||||||
|
|
||||||
|
readonly ago = {
|
||||||
|
'=0': 'Never performed',
|
||||||
|
'=1': 'day ago',
|
||||||
|
other: '# days ago',
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly in = {
|
||||||
|
'=0': 'Not scheduled',
|
||||||
|
'=1': 'Tomorrow',
|
||||||
|
other: 'In # days',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function daysBetween(one: Date, two: Date): number {
|
||||||
|
return Math.abs(
|
||||||
|
Math.round((one.valueOf() - two.valueOf()) / (1000 * 60 * 60 * 24)),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,22 +1,26 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
import { ServiceDependencyComponent } from './dependency.component'
|
|
||||||
import { DependencyInfo } from '../types/dependency-info'
|
import { DependencyInfo } from '../types/dependency-info'
|
||||||
|
import { ServiceDependencyComponent } from './dependency.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'service-dependencies',
|
selector: 'service-dependencies',
|
||||||
template: `
|
template: `
|
||||||
<h3 class="g-title">Dependencies</h3>
|
@for (dep of dependencies; track $index) {
|
||||||
<button
|
<button
|
||||||
*ngFor="let dep of dependencies"
|
class="g-action"
|
||||||
class="g-action"
|
[serviceDependency]="dep"
|
||||||
[serviceDependency]="dep"
|
(click)="dep.action()"
|
||||||
(click)="dep.action()"
|
></button>
|
||||||
></button>
|
}
|
||||||
|
|
||||||
|
@if (!dependencies.length) {
|
||||||
|
No dependencies
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
|
styles: ':host { display: block; min-height: var(--tui-height-s) }',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, ServiceDependencyComponent],
|
imports: [ServiceDependencyComponent],
|
||||||
})
|
})
|
||||||
export class ServiceDependenciesComponent {
|
export class ServiceDependenciesComponent {
|
||||||
@Input({ required: true })
|
@Input({ required: true })
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
|
import { HealthCheckResult } from '@startos'
|
||||||
import { TuiLoaderModule, TuiSvgModule } from '@taiga-ui/core'
|
import { TuiLoaderModule, TuiSvgModule } from '@taiga-ui/core'
|
||||||
import { HealthCheckResult } from '../../../../../../../../../../core/startos/bindings/HealthCheckResult'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'service-health-check',
|
selector: 'service-health-check',
|
||||||
|
|||||||
@@ -1,28 +1,33 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
import { AsyncPipe } from '@angular/common'
|
||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
inject,
|
inject,
|
||||||
Input,
|
Input,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
|
import { HealthCheckResult } from '@startos'
|
||||||
|
import { ServiceHealthCheckComponent } from 'src/app/routes/portal/routes/service/components/health-check.component'
|
||||||
import { ConnectionService } from 'src/app/services/connection.service'
|
import { ConnectionService } from 'src/app/services/connection.service'
|
||||||
import { ServiceHealthCheckComponent } from './health-check.component'
|
|
||||||
import { HealthCheckResult } from '../../../../../../../../../../core/startos/bindings/HealthCheckResult'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'service-health-checks',
|
selector: 'service-health-checks',
|
||||||
template: `
|
template: `
|
||||||
<h3 class="g-title">Health Checks</h3>
|
@for (check of checks; track $index) {
|
||||||
<service-health-check
|
<service-health-check
|
||||||
*ngFor="let check of checks"
|
class="g-action"
|
||||||
class="g-action"
|
[check]="check"
|
||||||
[check]="check"
|
[connected]="!!(connected$ | async)"
|
||||||
[connected]="!!(connected$ | async)"
|
/>
|
||||||
></service-health-check>
|
}
|
||||||
|
|
||||||
|
@if (!checks.length) {
|
||||||
|
No health checks
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
|
styles: ':host { display: block; min-height: var(--tui-height-s) }',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, ServiceHealthCheckComponent],
|
imports: [AsyncPipe, ServiceHealthCheckComponent],
|
||||||
})
|
})
|
||||||
export class ServiceHealthChecksComponent {
|
export class ServiceHealthChecksComponent {
|
||||||
@Input({ required: true })
|
@Input({ required: true })
|
||||||
|
|||||||
@@ -5,35 +5,60 @@ import {
|
|||||||
inject,
|
inject,
|
||||||
Input,
|
Input,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { TuiSvgModule } from '@taiga-ui/core'
|
import { TuiLetModule } from '@taiga-ui/cdk'
|
||||||
import { TuiButtonModule } from '@taiga-ui/experimental'
|
import { TuiLoaderModule } from '@taiga-ui/core'
|
||||||
|
import { TuiButtonModule, TuiIconModule } from '@taiga-ui/experimental'
|
||||||
|
import { map, timer } from 'rxjs'
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
import { ExtendedInterfaceInfo } from '../pipes/interface-info.pipe'
|
import { ExtendedInterfaceInfo } from '../pipes/interface-info.pipe'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'a[serviceInterfaceListItem]',
|
selector: 'a[serviceInterfaceListItem]',
|
||||||
template: `
|
template: `
|
||||||
<tui-svg [src]="info.icon" [style.color]="info.color"></tui-svg>
|
<ng-container *tuiLet="healthCheck$ | async as check">
|
||||||
<div [style.flex]="1">
|
@if (check === null) {
|
||||||
<strong>{{ info.name }}</strong>
|
<tui-loader />
|
||||||
<div>{{ info.description }}</div>
|
} @else if (check === '') {
|
||||||
<div [style.color]="info.color">{{ info.typeDetail }}</div>
|
<tui-icon [icon]="info.icon" [style.color]="info.color" />
|
||||||
</div>
|
} @else {
|
||||||
<a
|
<tui-icon icon="tuiIconXCircle" class="g-error" />
|
||||||
*ngIf="info.type === 'ui'"
|
}
|
||||||
tuiIconButton
|
<div [style.flex]="1">
|
||||||
appearance="flat"
|
<strong>{{ info.name }}</strong>
|
||||||
iconLeft="tuiIconExternalLinkLarge"
|
<div>{{ info.description }}</div>
|
||||||
target="_blank"
|
@if (check) {
|
||||||
rel="noreferrer"
|
<div class="g-error">
|
||||||
[style.border-radius.%]="100"
|
<b>Health check failed:</b>
|
||||||
[attr.href]="href"
|
{{ check }}
|
||||||
(click.stop)="(0)"
|
</div>
|
||||||
></a>
|
} @else {
|
||||||
|
<div [style.color]="info.color">{{ info.typeDetail }}</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
@if (info.type === 'ui') {
|
||||||
|
<a
|
||||||
|
tuiIconButton
|
||||||
|
appearance="flat"
|
||||||
|
iconLeft="tuiIconExternalLinkLarge"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
title="Open"
|
||||||
|
[style.border-radius.%]="100"
|
||||||
|
[attr.href]="href"
|
||||||
|
(click.stop)="(0)"
|
||||||
|
></a>
|
||||||
|
}
|
||||||
|
</ng-container>
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [TuiButtonModule, CommonModule, TuiSvgModule],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
TuiButtonModule,
|
||||||
|
TuiLetModule,
|
||||||
|
TuiLoaderModule,
|
||||||
|
TuiIconModule,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class ServiceInterfaceListItemComponent {
|
export class ServiceInterfaceListItemComponent {
|
||||||
private readonly config = inject(ConfigService)
|
private readonly config = inject(ConfigService)
|
||||||
@@ -44,6 +69,11 @@ export class ServiceInterfaceListItemComponent {
|
|||||||
@Input()
|
@Input()
|
||||||
disabled = false
|
disabled = false
|
||||||
|
|
||||||
|
// TODO: Implement real health check
|
||||||
|
readonly healthCheck$ = timer(3000).pipe(
|
||||||
|
map(() => (Math.random() > 0.5 ? '' : 'You done f***d it up...')),
|
||||||
|
)
|
||||||
|
|
||||||
get href(): string | null {
|
get href(): string | null {
|
||||||
return this.disabled ? null : this.config.launchableAddress(this.info)
|
return this.disabled ? null : this.config.launchableAddress(this.info)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,25 @@
|
|||||||
import { NgForOf } from '@angular/common'
|
|
||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
|
import { RouterLink } from '@angular/router'
|
||||||
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
import { PackageStatus } from 'src/app/services/pkg-status-rendering.service'
|
import { PackageStatus } from 'src/app/services/pkg-status-rendering.service'
|
||||||
import { InterfaceInfoPipe } from '../pipes/interface-info.pipe'
|
import { InterfaceInfoPipe } from '../pipes/interface-info.pipe'
|
||||||
import { ServiceInterfaceListItemComponent } from './interface-list-item.component'
|
import { ServiceInterfaceListItemComponent } from './interface-list-item.component'
|
||||||
import { RouterLink } from '@angular/router'
|
|
||||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'service-interface-list',
|
selector: 'service-interface-list',
|
||||||
template: `
|
template: `
|
||||||
<h3 class="g-title">Service Interfaces</h3>
|
@for (info of pkg | interfaceInfo; track $index) {
|
||||||
<a
|
<a
|
||||||
*ngFor="let info of pkg | interfaceInfo"
|
class="g-action"
|
||||||
class="g-action"
|
[serviceInterfaceListItem]="info"
|
||||||
[serviceInterfaceListItem]="info"
|
[disabled]="status.primary !== 'running'"
|
||||||
[disabled]="status.primary !== 'running'"
|
[routerLink]="info.routerLink"
|
||||||
[routerLink]="info.routerLink"
|
></a>
|
||||||
></a>
|
}
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [RouterLink, InterfaceInfoPipe, ServiceInterfaceListItemComponent],
|
||||||
NgForOf,
|
|
||||||
RouterLink,
|
|
||||||
InterfaceInfoPipe,
|
|
||||||
ServiceInterfaceListItemComponent,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class ServiceInterfaceListComponent {
|
export class ServiceInterfaceListComponent {
|
||||||
@Input({ required: true })
|
@Input({ required: true })
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
import { TuiSvgModule } from '@taiga-ui/core'
|
import { TuiIconModule } from '@taiga-ui/experimental'
|
||||||
import { ServiceMenu } from '../pipes/to-menu.pipe'
|
import { ServiceMenu } from '../pipes/to-menu.pipe'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: '[serviceMenuItem]',
|
selector: '[serviceMenuItem]',
|
||||||
template: `
|
template: `
|
||||||
<tui-svg [src]="menu.icon"></tui-svg>
|
<tui-icon [icon]="menu.icon" />
|
||||||
<div [style.flex]="1">
|
<div [style.flex]="1">
|
||||||
<strong>{{ menu.name }}</strong>
|
<strong>{{ menu.name }}</strong>
|
||||||
<div>
|
<div>
|
||||||
{{ menu.description }}
|
{{ menu.description }}
|
||||||
<ng-content></ng-content>
|
<ng-content />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<tui-svg src="tuiIconChevronRightLarge"></tui-svg>
|
<tui-icon icon="tuiIconChevronRight" />
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [TuiSvgModule],
|
imports: [TuiIconModule],
|
||||||
})
|
})
|
||||||
export class ServiceMenuItemComponent {
|
export class ServiceMenuItemComponent {
|
||||||
@Input({ required: true, alias: 'serviceMenuItem' })
|
@Input({ required: true, alias: 'serviceMenuItem' })
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { RouterLink } from '@angular/router'
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'service-menu',
|
selector: 'service-menu',
|
||||||
template: `
|
template: `
|
||||||
<h3 class="g-title">Menu</h3>
|
|
||||||
@for (menu of pkg | toMenu; track $index) {
|
@for (menu of pkg | toMenu; track $index) {
|
||||||
@if (menu.routerLink) {
|
@if (menu.routerLink) {
|
||||||
<a
|
<a
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
|
import { Progress } from '@startos'
|
||||||
import { TuiProgressModule } from '@taiga-ui/kit'
|
import { TuiProgressModule } from '@taiga-ui/kit'
|
||||||
import { InstallingProgressPipe } from '../pipes/install-progress.pipe'
|
import { InstallingProgressPipe } from 'src/app/routes/portal/routes/service/pipes/install-progress.pipe'
|
||||||
import { Progress } from '../../../../../../../../../../core/startos/bindings/Progress'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: '[progress]',
|
selector: '[progress]',
|
||||||
|
|||||||
@@ -32,9 +32,11 @@ import { UnitConversionPipesModule } from '@start9labs/shared'
|
|||||||
styles: [
|
styles: [
|
||||||
`
|
`
|
||||||
:host {
|
:host {
|
||||||
font-size: x-large;
|
|
||||||
margin: 1em 0;
|
|
||||||
display: block;
|
display: block;
|
||||||
|
font-size: x-large;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin: auto 0;
|
||||||
|
height: 2.75rem;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
import { TuiSvgModule } from '@taiga-ui/core'
|
import { TuiSvgModule } from '@taiga-ui/core'
|
||||||
import { AdditionalItem, FALLBACK_URL } from '../pipes/to-additional.pipe'
|
import {
|
||||||
|
AdditionalItem,
|
||||||
|
FALLBACK_URL,
|
||||||
|
} from 'src/app/routes/portal/routes/service/pipes/to-additional.pipe'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: '[additionalItem]',
|
selector: '[additionalItem]',
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
inject,
|
||||||
|
Input,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { TuiDialogOptions } from '@taiga-ui/core'
|
||||||
|
import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus'
|
||||||
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
|
import { ToAdditionalPipe } from 'src/app/routes/portal/routes/service/pipes/to-additional.pipe'
|
||||||
|
import { ServiceAdditionalItemComponent } from './additional-item.component'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'service-additional',
|
||||||
|
template: `
|
||||||
|
@for (additional of pkg | toAdditional; track $index) {
|
||||||
|
@if (additional.description.startsWith('http')) {
|
||||||
|
<a class="g-action" [additionalItem]="additional"></a>
|
||||||
|
} @else {
|
||||||
|
<button
|
||||||
|
class="g-action"
|
||||||
|
[style.pointer-events]="!additional.icon ? 'none' : null"
|
||||||
|
[additionalItem]="additional"
|
||||||
|
(click)="additional.action?.()"
|
||||||
|
></button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
standalone: true,
|
||||||
|
imports: [ToAdditionalPipe, ServiceAdditionalItemComponent],
|
||||||
|
})
|
||||||
|
export class ServiceAdditionalModal {
|
||||||
|
readonly pkg =
|
||||||
|
inject<TuiDialogOptions<PackageDataEntry>>(POLYMORPHEUS_CONTEXT).data
|
||||||
|
}
|
||||||
@@ -24,17 +24,17 @@ export class InterfaceInfoPipe implements PipeTransform {
|
|||||||
switch (val.type) {
|
switch (val.type) {
|
||||||
case 'ui':
|
case 'ui':
|
||||||
color = 'var(--tui-primary)'
|
color = 'var(--tui-primary)'
|
||||||
icon = 'tuiIconMonitorLarge'
|
icon = 'tuiIconMonitor'
|
||||||
typeDetail = 'User Interface (UI)'
|
typeDetail = 'User Interface (UI)'
|
||||||
break
|
break
|
||||||
case 'p2p':
|
case 'p2p':
|
||||||
color = 'var(--tui-info-fill)'
|
color = 'var(--tui-info-fill)'
|
||||||
icon = 'tuiIconUsersLarge'
|
icon = 'tuiIconUsers'
|
||||||
typeDetail = 'Peer-To-Peer Interface (P2P)'
|
typeDetail = 'Peer-To-Peer Interface (P2P)'
|
||||||
break
|
break
|
||||||
case 'api':
|
case 'api':
|
||||||
color = 'var(--tui-support-09)'
|
color = 'var(--tui-support-09)'
|
||||||
icon = 'tuiIconTerminalLarge'
|
icon = 'tuiIconTerminal'
|
||||||
typeDetail = 'Application Program Interface (API)'
|
typeDetail = 'Application Program Interface (API)'
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { inject, Pipe, PipeTransform } from '@angular/core'
|
import { inject, Pipe, PipeTransform } from '@angular/core'
|
||||||
|
import { CopyService, MarkdownComponent } from '@start9labs/shared'
|
||||||
|
import { Manifest } from '@startos'
|
||||||
import { TuiDialogService } from '@taiga-ui/core'
|
import { TuiDialogService } from '@taiga-ui/core'
|
||||||
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
|
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
|
||||||
import { CopyService, MarkdownComponent } from '@start9labs/shared'
|
|
||||||
import { from } from 'rxjs'
|
import { from } from 'rxjs'
|
||||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
import { getManifest } from 'src/app/utils/get-package-data'
|
import { getManifest } from 'src/app/utils/get-package-data'
|
||||||
import { Manifest } from '../../../../../../../../../../core/startos/bindings/Manifest'
|
|
||||||
|
|
||||||
export const FALLBACK_URL = 'Not provided'
|
export const FALLBACK_URL = 'Not provided'
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
import { inject, Pipe, PipeTransform } from '@angular/core'
|
import { inject, Pipe, PipeTransform } from '@angular/core'
|
||||||
import { Params } from '@angular/router'
|
import { Params } from '@angular/router'
|
||||||
import { MarkdownComponent } from '@start9labs/shared'
|
import { MarkdownComponent } from '@start9labs/shared'
|
||||||
|
import { Manifest } from '@startos'
|
||||||
import { TuiDialogService } from '@taiga-ui/core'
|
import { TuiDialogService } from '@taiga-ui/core'
|
||||||
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
|
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
|
||||||
import { from } from 'rxjs'
|
import { from } from 'rxjs'
|
||||||
import {
|
import {
|
||||||
PackageConfigData,
|
|
||||||
ConfigModal,
|
ConfigModal,
|
||||||
|
PackageConfigData,
|
||||||
} from 'src/app/routes/portal/modals/config.component'
|
} from 'src/app/routes/portal/modals/config.component'
|
||||||
|
import { ServiceAdditionalModal } from 'src/app/routes/portal/routes/service/modals/additional.component'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
import { ProxyService } from 'src/app/services/proxy.service'
|
import { ProxyService } from 'src/app/services/proxy.service'
|
||||||
import { ServicePropertiesModal } from '../modals/properties.component'
|
|
||||||
import { getManifest } from 'src/app/utils/get-package-data'
|
import { getManifest } from 'src/app/utils/get-package-data'
|
||||||
import { Manifest } from '../../../../../../../../../../core/startos/bindings/Manifest'
|
import { ServicePropertiesModal } from 'src/app/routes/portal/routes/service/modals/properties.component'
|
||||||
|
|
||||||
export interface ServiceMenu {
|
export interface ServiceMenu {
|
||||||
icon: string
|
icon: string
|
||||||
@@ -40,19 +41,19 @@ export class ToMenuPipe implements PipeTransform {
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
icon: 'tuiIconListLarge',
|
icon: 'tuiIconList',
|
||||||
name: 'Instructions',
|
name: 'Instructions',
|
||||||
description: `Understand how to use ${manifest.title}`,
|
description: `Understand how to use ${manifest.title}`,
|
||||||
action: () => this.showInstructions(manifest),
|
action: () => this.showInstructions(manifest),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'tuiIconSlidersLarge',
|
icon: 'tuiIconSliders',
|
||||||
name: 'Config',
|
name: 'Config',
|
||||||
description: `Customize ${manifest.title}`,
|
description: `Customize ${manifest.title}`,
|
||||||
action: () => this.openConfig(manifest),
|
action: () => this.openConfig(manifest),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'tuiIconKeyLarge',
|
icon: 'tuiIconKey',
|
||||||
name: 'Properties',
|
name: 'Properties',
|
||||||
description: `Runtime information, credentials, and other values of interest`,
|
description: `Runtime information, credentials, and other values of interest`,
|
||||||
action: () =>
|
action: () =>
|
||||||
@@ -64,13 +65,13 @@ export class ToMenuPipe implements PipeTransform {
|
|||||||
.subscribe(),
|
.subscribe(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'tuiIconZapLarge',
|
icon: 'tuiIconZap',
|
||||||
name: 'Actions',
|
name: 'Actions',
|
||||||
description: `Uninstall and other commands specific to ${manifest.title}`,
|
description: `Uninstall and other commands specific to ${manifest.title}`,
|
||||||
routerLink: `actions`,
|
routerLink: `actions`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'tuiIconShieldLarge',
|
icon: 'tuiIconShield',
|
||||||
name: 'Outbound Proxy',
|
name: 'Outbound Proxy',
|
||||||
description: `Proxy all outbound traffic from ${manifest.title}`,
|
description: `Proxy all outbound traffic from ${manifest.title}`,
|
||||||
action: () =>
|
action: () =>
|
||||||
@@ -80,21 +81,33 @@ export class ToMenuPipe implements PipeTransform {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'tuiIconFileTextLarge',
|
icon: 'tuiIconFileText',
|
||||||
name: 'Logs',
|
name: 'Logs',
|
||||||
description: `Raw, unfiltered logs`,
|
description: `Raw, unfiltered logs`,
|
||||||
routerLink: 'logs',
|
routerLink: 'logs',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: 'tuiIconInfo',
|
||||||
|
name: 'Additional Info',
|
||||||
|
description: `View package details`,
|
||||||
|
action: () =>
|
||||||
|
this.dialogs
|
||||||
|
.open(new PolymorpheusComponent(ServiceAdditionalModal), {
|
||||||
|
label: `Additional Info`,
|
||||||
|
data: pkg,
|
||||||
|
})
|
||||||
|
.subscribe(),
|
||||||
|
},
|
||||||
pkg.marketplaceUrl
|
pkg.marketplaceUrl
|
||||||
? {
|
? {
|
||||||
icon: 'tuiIconShoppingBagLarge',
|
icon: 'tuiIconShoppingBag',
|
||||||
name: 'Marketplace Listing',
|
name: 'Marketplace Listing',
|
||||||
description: `View ${manifest.title} on the Marketplace`,
|
description: `View ${manifest.title} on the Marketplace`,
|
||||||
routerLink: `/portal/system/marketplace`,
|
routerLink: `/portal/system/marketplace`,
|
||||||
params: { url: pkg.marketplaceUrl, id: manifest.id },
|
params: { url: pkg.marketplaceUrl, id: manifest.id },
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
icon: 'tuiIconShoppingBagLarge',
|
icon: 'tuiIconShoppingBag',
|
||||||
name: 'Marketplace Listing',
|
name: 'Marketplace Listing',
|
||||||
description: `This package was not installed from the marketplace`,
|
description: `This package was not installed from the marketplace`,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ import { getAllPackages, getManifest } from 'src/app/utils/get-package-data'
|
|||||||
[action]="{
|
[action]="{
|
||||||
name: action.name,
|
name: action.name,
|
||||||
description: action.description,
|
description: action.description,
|
||||||
icon: 'tuiIconPlayCircleLarge'
|
icon: 'tuiIconPlayCircle'
|
||||||
}"
|
}"
|
||||||
(click)="handleAction(action)"
|
(click)="handleAction(action)"
|
||||||
></button>
|
></button>
|
||||||
@@ -75,7 +75,7 @@ export class ServiceActionsRoute {
|
|||||||
.pipe(filter(pkg => pkg.stateInfo.state === 'installed'))
|
.pipe(filter(pkg => pkg.stateInfo.state === 'installed'))
|
||||||
|
|
||||||
readonly action = {
|
readonly action = {
|
||||||
icon: 'tuiIconTrash2Large',
|
icon: 'tuiIconTrash2',
|
||||||
name: 'Uninstall',
|
name: 'Uninstall',
|
||||||
description:
|
description:
|
||||||
'This will uninstall the service from StartOS and delete all data permanently.',
|
'This will uninstall the service from StartOS and delete all data permanently.',
|
||||||
|
|||||||
@@ -2,8 +2,15 @@ import { CommonModule } from '@angular/common'
|
|||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||||
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router'
|
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router'
|
||||||
import { isEmptyObject } from '@start9labs/shared'
|
import { isEmptyObject } from '@start9labs/shared'
|
||||||
|
import { HealthCheckResult, MainStatus, Manifest } from '@startos'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { combineLatest, map, switchMap } from 'rxjs'
|
import { combineLatest, map, switchMap } from 'rxjs'
|
||||||
|
import {
|
||||||
|
ConfigModal,
|
||||||
|
PackageConfigData,
|
||||||
|
} from 'src/app/routes/portal/modals/config.component'
|
||||||
|
import { ServiceBackupsComponent } from 'src/app/routes/portal/routes/service/components/backups.component'
|
||||||
|
import { InstallingProgressPipe } from 'src/app/routes/portal/routes/service/pipes/install-progress.pipe'
|
||||||
import { ConnectionService } from 'src/app/services/connection.service'
|
import { ConnectionService } from 'src/app/services/connection.service'
|
||||||
import {
|
import {
|
||||||
DepErrorService,
|
DepErrorService,
|
||||||
@@ -21,87 +28,122 @@ import {
|
|||||||
StatusRendering,
|
StatusRendering,
|
||||||
} from 'src/app/services/pkg-status-rendering.service'
|
} from 'src/app/services/pkg-status-rendering.service'
|
||||||
import { DependentInfo } from 'src/app/types/dependent-info'
|
import { DependentInfo } from 'src/app/types/dependent-info'
|
||||||
|
import { getManifest } from 'src/app/utils/get-package-data'
|
||||||
import { ServiceActionsComponent } from '../components/actions.component'
|
import { ServiceActionsComponent } from '../components/actions.component'
|
||||||
import { ServiceAdditionalComponent } from '../components/additional.component'
|
|
||||||
import { ServiceDependenciesComponent } from '../components/dependencies.component'
|
import { ServiceDependenciesComponent } from '../components/dependencies.component'
|
||||||
import { ServiceHealthChecksComponent } from '../components/health-checks.component'
|
import { ServiceHealthChecksComponent } from '../components/health-checks.component'
|
||||||
import { ServiceInterfaceListComponent } from '../components/interface-list.component'
|
import { ServiceInterfaceListComponent } from '../components/interface-list.component'
|
||||||
import { ServiceMenuComponent } from '../components/menu.component'
|
import { ServiceMenuComponent } from '../components/menu.component'
|
||||||
import { ServiceProgressComponent } from '../components/progress.component'
|
import { ServiceProgressComponent } from '../components/progress.component'
|
||||||
import { ServiceStatusComponent } from '../components/status.component'
|
import { ServiceStatusComponent } from '../components/status.component'
|
||||||
import {
|
|
||||||
PackageConfigData,
|
|
||||||
ConfigModal,
|
|
||||||
} from 'src/app/routes/portal/modals/config.component'
|
|
||||||
import { DependencyInfo } from '../types/dependency-info'
|
import { DependencyInfo } from '../types/dependency-info'
|
||||||
import { getManifest } from 'src/app/utils/get-package-data'
|
|
||||||
import { InstallingProgressPipe } from 'src/app/routes/portal/routes/service/pipes/install-progress.pipe'
|
|
||||||
import { Manifest } from '../../../../../../../../../../core/startos/bindings/Manifest'
|
|
||||||
import { HealthCheckResult } from '../../../../../../../../../../core/startos/bindings/HealthCheckResult'
|
|
||||||
import { MainStatus } from '../../../../../../../../../../core/startos/bindings/MainStatus'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
@if (service$ | async; as service) {
|
@if (service$ | async; as service) {
|
||||||
<h3 class="g-title">Status</h3>
|
<section [style.grid-column]="'span 3'">
|
||||||
<service-status
|
<h3>Status</h3>
|
||||||
[connected]="!!(connected$ | async)"
|
<service-status
|
||||||
[installingInfo]="service.pkg.stateInfo.installingInfo"
|
[connected]="!!(connected$ | async)"
|
||||||
[rendering]="getRendering(service.status)"
|
[installingInfo]="service.pkg.stateInfo.installingInfo"
|
||||||
[sigtermTimeout]="
|
[rendering]="getRendering(service.status)"
|
||||||
service.pkg.status.main.status === 'stopping'
|
[sigtermTimeout]="
|
||||||
? service.pkg.status.main.timeout
|
service.pkg.status.main.status === 'stopping'
|
||||||
: null
|
? service.pkg.status.main.timeout
|
||||||
"
|
: null
|
||||||
/>
|
|
||||||
|
|
||||||
@if (
|
|
||||||
service.pkg.stateInfo.state === 'installing' ||
|
|
||||||
service.pkg.stateInfo.state === 'updating' ||
|
|
||||||
service.pkg.stateInfo.state === 'restoring'
|
|
||||||
) {
|
|
||||||
<p
|
|
||||||
*ngFor="
|
|
||||||
let phase of service.pkg.stateInfo.installingInfo.progress.phases
|
|
||||||
"
|
"
|
||||||
[progress]="phase.progress"
|
/>
|
||||||
>
|
|
||||||
{{ phase.name }}
|
|
||||||
</p>
|
|
||||||
} @else {
|
|
||||||
@if (
|
|
||||||
service.pkg.stateInfo.state === 'installed' &&
|
|
||||||
service.status.primary !== 'backingUp'
|
|
||||||
) {
|
|
||||||
@if (connected$ | async) {
|
|
||||||
<service-actions
|
|
||||||
[pkg]="service.pkg"
|
|
||||||
[dependencies]="service.dependencies"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@if (isInstalled(service) && (connected$ | async)) {
|
||||||
|
<service-actions
|
||||||
|
[pkg]="service.pkg"
|
||||||
|
[dependencies]="service.dependencies"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
@if (isInstalled(service)) {
|
||||||
|
<section [style.grid-column]="'span 3'">
|
||||||
|
<h3>Backups</h3>
|
||||||
|
<service-backups [pkg]="service.pkg" />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section [style.grid-column]="'span 6'">
|
||||||
|
<h3>Metrics</h3>
|
||||||
|
TODO
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section [style.grid-column]="'span 4'" [style.align-self]="'start'">
|
||||||
|
<h3>Menu</h3>
|
||||||
|
<service-menu [pkg]="service.pkg" />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<section>
|
||||||
|
<h3>Health Checks</h3>
|
||||||
|
<service-health-checks [checks]="(health$ | async) || []" />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3>Dependencies</h3>
|
||||||
|
<service-dependencies [dependencies]="service.dependencies" />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section [style.grid-column]="'span 4'" [style.align-self]="'start'">
|
||||||
|
<h3>Service Interfaces</h3>
|
||||||
<service-interface-list
|
<service-interface-list
|
||||||
[pkg]="service.pkg"
|
[pkg]="service.pkg"
|
||||||
[status]="service.status"
|
[status]="service.status"
|
||||||
/>
|
/>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
@if (
|
@if (isInstalling(service.pkg.stateInfo.state)) {
|
||||||
service.status.primary === 'running' && (health$ | async);
|
@for (
|
||||||
as checks
|
item of service.pkg.stateInfo.installingInfo?.progress?.phases;
|
||||||
) {
|
track $index
|
||||||
<service-health-checks [checks]="checks" />
|
) {
|
||||||
}
|
<p [progress]="item.progress">{{ item.name }}</p>
|
||||||
|
|
||||||
@if (service.dependencies.length) {
|
|
||||||
<service-dependencies [dependencies]="service.dependencies" />
|
|
||||||
}
|
|
||||||
|
|
||||||
<service-menu [pkg]="service.pkg" />
|
|
||||||
<service-additional [pkg]="service.pkg" />
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
styles: `
|
||||||
|
:host {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(12, 1fr);
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
margin: 1rem -1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(tui-root._mobile) {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem 1.5rem 0.5rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
background: var(--tui-clear);
|
||||||
|
box-shadow: inset 0 7rem 0 -4rem var(--tui-clear);
|
||||||
|
clip-path: polygon(0 1.5rem, 1.5rem 0, 100% 0, 100% 100%, 0 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: inherit;
|
||||||
|
grid-column: span 4;
|
||||||
|
}
|
||||||
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
@@ -113,7 +155,7 @@ import { MainStatus } from '../../../../../../../../../../core/startos/bindings/
|
|||||||
ServiceHealthChecksComponent,
|
ServiceHealthChecksComponent,
|
||||||
ServiceDependenciesComponent,
|
ServiceDependenciesComponent,
|
||||||
ServiceMenuComponent,
|
ServiceMenuComponent,
|
||||||
ServiceAdditionalComponent,
|
ServiceBackupsComponent,
|
||||||
InstallingProgressPipe,
|
InstallingProgressPipe,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@@ -134,13 +176,11 @@ export class ServiceRoute {
|
|||||||
this.depErrorService.getPkgDepErrors$(pkgId),
|
this.depErrorService.getPkgDepErrors$(pkgId),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
map(([pkg, depErrors]) => {
|
map(([pkg, depErrors]) => ({
|
||||||
return {
|
pkg,
|
||||||
pkg,
|
dependencies: this.getDepInfo(pkg, depErrors),
|
||||||
dependencies: this.getDepInfo(pkg, depErrors),
|
status: renderPkgStatus(pkg, depErrors),
|
||||||
status: renderPkgStatus(pkg, depErrors),
|
})),
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
readonly health$ = this.pkgId$.pipe(
|
readonly health$ = this.pkgId$.pipe(
|
||||||
@@ -150,6 +190,16 @@ export class ServiceRoute {
|
|||||||
map(toHealthCheck),
|
map(toHealthCheck),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
isInstalling(state: string): boolean {
|
||||||
|
return (
|
||||||
|
state === 'installing' || state === 'updating' || state === 'restoring'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
isInstalled({ pkg, status }: any): boolean {
|
||||||
|
return pkg.stateInfo.state === 'installed' && status.primary !== 'backingUp'
|
||||||
|
}
|
||||||
|
|
||||||
getRendering({ primary }: PackageStatus): StatusRendering {
|
getRendering({ primary }: PackageStatus): StatusRendering {
|
||||||
return PrimaryRendering[primary]
|
return PrimaryRendering[primary]
|
||||||
}
|
}
|
||||||
@@ -160,9 +210,9 @@ export class ServiceRoute {
|
|||||||
): DependencyInfo[] {
|
): DependencyInfo[] {
|
||||||
const manifest = getManifest(pkg)
|
const manifest = getManifest(pkg)
|
||||||
|
|
||||||
return Object.keys(pkg.currentDependencies)
|
return Object.keys(pkg.currentDependencies).map(id =>
|
||||||
.filter(id => !!manifest.dependencies[id])
|
this.getDepValues(pkg, manifest, id, depErrors),
|
||||||
.map(id => this.getDepValues(pkg, manifest, id, depErrors))
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDepValues(
|
private getDepValues(
|
||||||
@@ -203,7 +253,7 @@ export class ServiceRoute {
|
|||||||
depId: string,
|
depId: string,
|
||||||
depErrors: PkgDependencyErrors,
|
depErrors: PkgDependencyErrors,
|
||||||
) {
|
) {
|
||||||
const depError = depErrors[pkgManifest.id]
|
const depError = depErrors[depId]
|
||||||
|
|
||||||
let errorText: string | null = null
|
let errorText: string | null = null
|
||||||
let fixText: string | null = null
|
let fixText: string | null = null
|
||||||
@@ -271,10 +321,15 @@ export class ServiceRoute {
|
|||||||
version: pkg.currentDependencies[depId].versionSpec,
|
version: pkg.currentDependencies[depId].versionSpec,
|
||||||
}
|
}
|
||||||
const navigationExtras: NavigationExtras = {
|
const navigationExtras: NavigationExtras = {
|
||||||
|
// @TODO state not being used by marketplace component. Maybe it is not important to use.
|
||||||
state: { dependentInfo },
|
state: { dependentInfo },
|
||||||
|
queryParams: { id: depId },
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.router.navigate(['marketplace', depId], navigationExtras)
|
await this.router.navigate(
|
||||||
|
['portal', 'system', 'marketplace'],
|
||||||
|
navigationExtras,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { GetBackupIconPipe } from '../pipes/get-backup-icon.pipe'
|
|||||||
Past Events
|
Past Events
|
||||||
<button
|
<button
|
||||||
tuiButton
|
tuiButton
|
||||||
appearance="secondary-destructive"
|
appearance="danger-solid"
|
||||||
[disabled]="disabled"
|
[disabled]="disabled"
|
||||||
(click)="delete()"
|
(click)="delete()"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -2,20 +2,20 @@ import { CommonModule } from '@angular/common'
|
|||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
Input,
|
|
||||||
inject,
|
inject,
|
||||||
|
Input,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { RouterLink } from '@angular/router'
|
import { RouterLink } from '@angular/router'
|
||||||
|
import { Manifest } from '@startos'
|
||||||
import { tuiPure } from '@taiga-ui/cdk'
|
import { tuiPure } from '@taiga-ui/cdk'
|
||||||
import { TuiSvgModule } from '@taiga-ui/core'
|
import { TuiSvgModule } from '@taiga-ui/core'
|
||||||
import { TuiLineClampModule } from '@taiga-ui/kit'
|
import { TuiLineClampModule } from '@taiga-ui/kit'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { Observable, first } from 'rxjs'
|
import { first, Observable } from 'rxjs'
|
||||||
import { ServerNotification } from 'src/app/services/api/api.types'
|
import { ServerNotification } from 'src/app/services/api/api.types'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
|
||||||
import { NotificationService } from 'src/app/services/notification.service'
|
import { NotificationService } from 'src/app/services/notification.service'
|
||||||
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { toRouterLink } from 'src/app/utils/to-router-link'
|
import { toRouterLink } from 'src/app/utils/to-router-link'
|
||||||
import { Manifest } from '../../../../../../../../../../core/startos/bindings/Manifest'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: '[notificationItem]',
|
selector: '[notificationItem]',
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { inject, Injectable } from '@angular/core'
|
import { inject, Injectable } from '@angular/core'
|
||||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||||
|
import { Manifest } from '@startos'
|
||||||
import { TuiDialogOptions, TuiDialogService } from '@taiga-ui/core'
|
import { TuiDialogOptions, TuiDialogService } from '@taiga-ui/core'
|
||||||
import { TUI_PROMPT, TuiPromptData } from '@taiga-ui/kit'
|
import { TUI_PROMPT, TuiPromptData } from '@taiga-ui/kit'
|
||||||
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { defaultIfEmpty, filter, firstValueFrom } from 'rxjs'
|
import { defaultIfEmpty, filter, firstValueFrom } from 'rxjs'
|
||||||
import {
|
import {
|
||||||
PackageConfigData,
|
|
||||||
ConfigModal,
|
ConfigModal,
|
||||||
|
PackageConfigData,
|
||||||
} from 'src/app/routes/portal/modals/config.component'
|
} from 'src/app/routes/portal/modals/config.component'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { hasCurrentDeps } from 'src/app/utils/has-deps'
|
|
||||||
import { getAllPackages } from 'src/app/utils/get-package-data'
|
import { getAllPackages } from 'src/app/utils/get-package-data'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { hasCurrentDeps } from 'src/app/utils/has-deps'
|
||||||
import { Manifest } from '../../../../../../core/startos/bindings/Manifest'
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
|
|||||||
@@ -913,7 +913,7 @@ export module Mock {
|
|||||||
integer: false,
|
integer: false,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
displayAs: 'I\'m {{last-name}}, {{first-name}} {{last-name}}',
|
displayAs: `I'm {{last-name}}, {{first-name}} {{last-name}}`,
|
||||||
uniqueBy: 'last-name',
|
uniqueBy: 'last-name',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -1295,6 +1295,7 @@ export module Mock {
|
|||||||
icon: '/assets/img/service-icons/bitcoind.svg',
|
icon: '/assets/img/service-icons/bitcoind.svg',
|
||||||
installedAt: new Date().toISOString(),
|
installedAt: new Date().toISOString(),
|
||||||
lastBackup: null,
|
lastBackup: null,
|
||||||
|
nextBackup: null,
|
||||||
status: {
|
status: {
|
||||||
configured: true,
|
configured: true,
|
||||||
main: {
|
main: {
|
||||||
@@ -1539,6 +1540,7 @@ export module Mock {
|
|||||||
icon: '/assets/img/service-icons/btc-rpc-proxy.png',
|
icon: '/assets/img/service-icons/btc-rpc-proxy.png',
|
||||||
installedAt: new Date().toISOString(),
|
installedAt: new Date().toISOString(),
|
||||||
lastBackup: null,
|
lastBackup: null,
|
||||||
|
nextBackup: null,
|
||||||
status: {
|
status: {
|
||||||
configured: false,
|
configured: false,
|
||||||
main: {
|
main: {
|
||||||
@@ -1681,6 +1683,7 @@ export module Mock {
|
|||||||
icon: '/assets/img/service-icons/lnd.png',
|
icon: '/assets/img/service-icons/lnd.png',
|
||||||
installedAt: new Date().toISOString(),
|
installedAt: new Date().toISOString(),
|
||||||
lastBackup: null,
|
lastBackup: null,
|
||||||
|
nextBackup: null,
|
||||||
status: {
|
status: {
|
||||||
configured: true,
|
configured: true,
|
||||||
main: {
|
main: {
|
||||||
|
|||||||
@@ -146,7 +146,8 @@ export const mockPatchData: DataModel = {
|
|||||||
},
|
},
|
||||||
icon: '/assets/img/service-icons/bitcoind.svg',
|
icon: '/assets/img/service-icons/bitcoind.svg',
|
||||||
installedAt: new Date().toISOString(),
|
installedAt: new Date().toISOString(),
|
||||||
lastBackup: null,
|
lastBackup: new Date(new Date().valueOf() - 604800001).toISOString(),
|
||||||
|
nextBackup: new Date(new Date().valueOf() + 100000000).toISOString(),
|
||||||
status: {
|
status: {
|
||||||
configured: true,
|
configured: true,
|
||||||
main: {
|
main: {
|
||||||
@@ -419,6 +420,7 @@ export const mockPatchData: DataModel = {
|
|||||||
icon: '/assets/img/service-icons/lnd.png',
|
icon: '/assets/img/service-icons/lnd.png',
|
||||||
installedAt: new Date().toISOString(),
|
installedAt: new Date().toISOString(),
|
||||||
lastBackup: null,
|
lastBackup: null,
|
||||||
|
nextBackup: null,
|
||||||
status: {
|
status: {
|
||||||
configured: true,
|
configured: true,
|
||||||
main: {
|
main: {
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ export type PackageDataEntry<T extends StateInfo = StateInfo> =
|
|||||||
stateInfo: T
|
stateInfo: T
|
||||||
installedAt: string
|
installedAt: string
|
||||||
outboundProxy: string | null
|
outboundProxy: string | null
|
||||||
|
nextBackup: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StateInfo = InstalledState | InstallingState | UpdatingState
|
export type StateInfo = InstalledState | InstallingState | UpdatingState
|
||||||
|
|||||||
@@ -23,7 +23,8 @@
|
|||||||
"paths": {
|
"paths": {
|
||||||
/* These paths are relative to each app base folder */
|
/* These paths are relative to each app base folder */
|
||||||
"@start9labs/marketplace": ["../marketplace/index"],
|
"@start9labs/marketplace": ["../marketplace/index"],
|
||||||
"@start9labs/shared": ["../shared/src/public-api"]
|
"@start9labs/shared": ["../shared/src/public-api"],
|
||||||
|
"@startos": ["../../../core/startos/bindings/index"]
|
||||||
},
|
},
|
||||||
"typeRoots": ["node_modules/@types"],
|
"typeRoots": ["node_modules/@types"],
|
||||||
"types": ["node"]
|
"types": ["node"]
|
||||||
|
|||||||
Reference in New Issue
Block a user