feat: add service uptime and start style changes (#2831)

This commit is contained in:
Alex Inkin
2025-02-14 22:32:30 +04:00
committed by GitHub
parent ce2842d365
commit 1b006599cf
167 changed files with 448 additions and 629 deletions

View File

@@ -45,7 +45,6 @@
],
"styles": [
"node_modules/@taiga-ui/core/styles/taiga-ui-theme.less",
"node_modules/@taiga-ui/styles/taiga-ui-global.less",
"projects/shared/styles/taiga.scss",
"projects/shared/styles/shared.scss",
"projects/ui/src/styles.scss"

152
web/package-lock.json generated
View File

@@ -25,19 +25,18 @@
"@noble/hashes": "^1.4.0",
"@start9labs/argon2": "^0.2.2",
"@start9labs/start-sdk": "file:../sdk/baseDist",
"@taiga-ui/addon-charts": "4.21.0",
"@taiga-ui/addon-commerce": "4.21.0",
"@taiga-ui/addon-mobile": "4.21.0",
"@taiga-ui/addon-table": "4.21.0",
"@taiga-ui/cdk": "4.21.0",
"@taiga-ui/core": "4.21.0",
"@taiga-ui/event-plugins": "4.3.1",
"@taiga-ui/icons": "4.21.0",
"@taiga-ui/kit": "4.21.0",
"@taiga-ui/layout": "4.21.0",
"@taiga-ui/legacy": "4.21.0",
"@taiga-ui/addon-charts": "4.24.0",
"@taiga-ui/addon-commerce": "4.24.0",
"@taiga-ui/addon-mobile": "4.24.0",
"@taiga-ui/addon-table": "4.24.0",
"@taiga-ui/cdk": "4.24.0",
"@taiga-ui/core": "4.24.0",
"@taiga-ui/event-plugins": "4.4.0",
"@taiga-ui/icons": "4.24.0",
"@taiga-ui/kit": "4.24.0",
"@taiga-ui/layout": "4.24.0",
"@taiga-ui/legacy": "4.24.0",
"@taiga-ui/polymorpheus": "4.8.0",
"@taiga-ui/styles": "4.21.0",
"@tinkoff/ng-dompurify": "4.0.0",
"ansi-to-html": "^0.7.2",
"base64-js": "^1.5.1",
@@ -4416,9 +4415,9 @@
"link": true
},
"node_modules/@taiga-ui/addon-charts": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-4.21.0.tgz",
"integrity": "sha512-ai213WTf+VG3hYLrusWJiSj7fY+Z5HjeZLmEJIMvFswzV8+Ekiiqb95+BnBUQaie6bwu3yAhcadwgun7u2Rq3w==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-4.24.0.tgz",
"integrity": "sha512-x75l4Dj1l7ForatbIdk4O8NzM6fQlKRp9UsXzMrKSoKj9O3jGHsFyMV67FhBS9oVitU4+3MIgPLuj09qpG1gBg==",
"license": "Apache-2.0",
"dependencies": {
"tslib": ">=2.8.1"
@@ -4427,15 +4426,15 @@
"@angular/common": ">=16.0.0",
"@angular/core": ">=16.0.0",
"@ng-web-apis/common": "^4.11.1",
"@taiga-ui/cdk": "^4.21.0",
"@taiga-ui/core": "^4.21.0",
"@taiga-ui/cdk": "^4.24.0",
"@taiga-ui/core": "^4.24.0",
"@taiga-ui/polymorpheus": "^4.8.0"
}
},
"node_modules/@taiga-ui/addon-commerce": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-4.21.0.tgz",
"integrity": "sha512-CmibFfEsSW2h9k6/NcAiverxiMZDk1e15j10Uupfodn7vpZk3u4j4/c8aS4klV4Hk3lC5UCCvpYVLpx/c9u+/A==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-4.24.0.tgz",
"integrity": "sha512-YAbhTZHXnN5TT9oO+KJX/NAehuHgMZ/QoVIuzQR16HiIoKTQQWznSipThnX2hinXrLNBas0O2Teo4Ij5kGIEOQ==",
"license": "Apache-2.0",
"dependencies": {
"tslib": ">=2.8.1"
@@ -4448,18 +4447,18 @@
"@maskito/core": "^3.2.1",
"@maskito/kit": "^3.2.1",
"@ng-web-apis/common": "^4.11.1",
"@taiga-ui/cdk": "^4.21.0",
"@taiga-ui/core": "^4.21.0",
"@taiga-ui/i18n": "^4.21.0",
"@taiga-ui/kit": "^4.21.0",
"@taiga-ui/cdk": "^4.24.0",
"@taiga-ui/core": "^4.24.0",
"@taiga-ui/i18n": "^4.24.0",
"@taiga-ui/kit": "^4.24.0",
"@taiga-ui/polymorpheus": "^4.8.0",
"rxjs": ">=7.0.0"
}
},
"node_modules/@taiga-ui/addon-mobile": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-4.21.0.tgz",
"integrity": "sha512-AIklfOfgiTuCFHvGb9Ok1nNtkelrEZmoeOQsLEH0wIA0+Ou/6XeAG03BkwrIJq4yQ1AI+ZCFeVNtl17bg+EzkA==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-4.24.0.tgz",
"integrity": "sha512-YdkhhgWsmtgi85sPbjibqa/BxdqKkETav/6O0KgFmBGvS4MF/Hr2sOXWMDUfskSALee9BJBNPdf8NrV/zNpcXw==",
"license": "Apache-2.0",
"dependencies": {
"tslib": ">=2.8.1"
@@ -4469,17 +4468,18 @@
"@angular/common": ">=16.0.0",
"@angular/core": ">=16.0.0",
"@ng-web-apis/common": "^4.11.1",
"@taiga-ui/cdk": "^4.21.0",
"@taiga-ui/core": "^4.21.0",
"@taiga-ui/kit": "^4.21.0",
"@taiga-ui/cdk": "^4.24.0",
"@taiga-ui/core": "^4.24.0",
"@taiga-ui/kit": "^4.24.0",
"@taiga-ui/layout": "^4.24.0",
"@taiga-ui/polymorpheus": "^4.8.0",
"rxjs": ">=7.0.0"
}
},
"node_modules/@taiga-ui/addon-table": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-table/-/addon-table-4.21.0.tgz",
"integrity": "sha512-A3j4mfsM/S+IRQOEfZwVTFhy0zTSeeM7/ZHgQwuau7Xn8UN6S8X0HvZ2LCg6ZMJaXU1W79XMm2j1uLa/BDFcog==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-table/-/addon-table-4.24.0.tgz",
"integrity": "sha512-da/5FUmyrG6+pS9XgBY7B78EU6O1M9GPh9fxTtYTDjRgEpFdb/4TX0J79v5InSTGQV77ITVkpqbTB1moRgUVfA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": ">=2.8.1"
@@ -4488,18 +4488,18 @@
"@angular/common": ">=16.0.0",
"@angular/core": ">=16.0.0",
"@ng-web-apis/intersection-observer": "^4.11.1",
"@taiga-ui/cdk": "^4.21.0",
"@taiga-ui/core": "^4.21.0",
"@taiga-ui/i18n": "^4.21.0",
"@taiga-ui/kit": "^4.21.0",
"@taiga-ui/cdk": "^4.24.0",
"@taiga-ui/core": "^4.24.0",
"@taiga-ui/i18n": "^4.24.0",
"@taiga-ui/kit": "^4.24.0",
"@taiga-ui/polymorpheus": "^4.8.0",
"rxjs": ">=7.0.0"
}
},
"node_modules/@taiga-ui/cdk": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-4.21.0.tgz",
"integrity": "sha512-PfHMELF2OHO7GNjdyfz1LpyjrsQMEa7oqZoyqz5s4W7W/KWpds3Cm6RP2inFQsZofXrJQNDGtThb1UgNYH5AsQ==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-4.24.0.tgz",
"integrity": "sha512-vsI+Jyzj4yfMolCg42OOyDvT30g1WFoupfOGlUk8A9R2HTVhMTeFeoA186A7c5KLI3e9fxj/60Rc9LbIbacnDA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "2.8.1"
@@ -4522,15 +4522,15 @@
"@ng-web-apis/platform": "^4.11.1",
"@ng-web-apis/resize-observer": "^4.11.1",
"@ng-web-apis/screen-orientation": "^4.11.1",
"@taiga-ui/event-plugins": "^4.3.1",
"@taiga-ui/event-plugins": "^4.4.0",
"@taiga-ui/polymorpheus": "^4.8.0",
"rxjs": ">=7.0.0"
}
},
"node_modules/@taiga-ui/core": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-4.21.0.tgz",
"integrity": "sha512-O91kueQldSGCxR+IshcgwAOJukwkV2gEu/MBhw7t5fDEvW6aNbKjFl/RZcDSMguVIzOdRAyTrWm+UyVLYeOphA==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-4.24.0.tgz",
"integrity": "sha512-tHHxdwXtvMI8W2Ct1YgLZGg5UEDRPq3j3fYwLV0P/G+17TU1rVeF9/JxVe8ZdZ1Mo55LNLDflTnenC13GK0R/w==",
"license": "Apache-2.0",
"dependencies": {
"tslib": ">=2.8.1"
@@ -4544,17 +4544,17 @@
"@angular/router": ">=16.0.0",
"@ng-web-apis/common": "^4.11.1",
"@ng-web-apis/mutation-observer": "^4.11.1",
"@taiga-ui/cdk": "^4.21.0",
"@taiga-ui/event-plugins": "^4.3.1",
"@taiga-ui/i18n": "^4.21.0",
"@taiga-ui/cdk": "^4.24.0",
"@taiga-ui/event-plugins": "^4.4.0",
"@taiga-ui/i18n": "^4.24.0",
"@taiga-ui/polymorpheus": "^4.8.0",
"rxjs": ">=7.0.0"
}
},
"node_modules/@taiga-ui/event-plugins": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@taiga-ui/event-plugins/-/event-plugins-4.3.1.tgz",
"integrity": "sha512-DhD1hdOYNidngdkxQQDGKawfn6fuhPcVVsbafZIJNAgw5P1+nN9b+J/3DB+GJJ1X3ODyMDl73Qubz0lPlMdRGw==",
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/event-plugins/-/event-plugins-4.4.0.tgz",
"integrity": "sha512-Tv8C0p5EZXl7s1Vc+MrLbAblbYvyswomY/xvyFcI9NgMj6JyfsStu6jpCiRMfzojz3G70PRFsk0+WwI19lRJCQ==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.3.0"
@@ -4566,9 +4566,9 @@
}
},
"node_modules/@taiga-ui/i18n": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.23.0.tgz",
"integrity": "sha512-+7AxbAKeQYmS0uK0LqhDqCDwJhuZlNhO4vZ8FTjmgPDWKF/VeRua+v7ei1pp3b6u+n8ADn9wq7Y2oitLjrH2gg==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.24.0.tgz",
"integrity": "sha512-F3s+SGJ6QkGIWDpTcJvqiE++WHHF95o11tokQvzFq9pCLfMrxehNx38Dv/JnZK3FA2vYlVqdaQc0OsRMfMDRMw==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
@@ -4581,18 +4581,18 @@
}
},
"node_modules/@taiga-ui/icons": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-4.21.0.tgz",
"integrity": "sha512-lzUYj/LOIFVJqdhzl0mgFUMASicQb2b+0MG5YELZik69hU+QAAKjCBzqDAaOqIMA/MVnERXiOxl62yWLjBR9+Q==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-4.24.0.tgz",
"integrity": "sha512-VGWKMuVayab+/MJ60JuxRM2zTJG2WelDmUuTMdYqnh+zAWTn7FoPjDgAREiT7m84XryZdKtkUqGK6aLI+J62Sg==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.3.0"
}
},
"node_modules/@taiga-ui/kit": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-4.21.0.tgz",
"integrity": "sha512-7/V6laRXsu+CQA+LnVVQJhb0m6hH1/FbElqictaiIyIKguIELpq64XMcspEPpnkVkEMatn47YA4xPH6PCF5Mng==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-4.24.0.tgz",
"integrity": "sha512-qz+OpsAyHWUT6fcfBPd+RJTv98J5USezh028B7TpSLSSyRE6mbklKIj2gteJQVnwVS0AXwm69WgQDXMVolbJnw==",
"license": "Apache-2.0",
"dependencies": {
"tslib": ">=2.8.1"
@@ -4610,17 +4610,17 @@
"@ng-web-apis/intersection-observer": "^4.11.1",
"@ng-web-apis/mutation-observer": "^4.11.1",
"@ng-web-apis/resize-observer": "^4.11.1",
"@taiga-ui/cdk": "^4.21.0",
"@taiga-ui/core": "^4.21.0",
"@taiga-ui/i18n": "^4.21.0",
"@taiga-ui/cdk": "^4.24.0",
"@taiga-ui/core": "^4.24.0",
"@taiga-ui/i18n": "^4.24.0",
"@taiga-ui/polymorpheus": "^4.8.0",
"rxjs": ">=7.0.0"
}
},
"node_modules/@taiga-ui/layout": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-4.21.0.tgz",
"integrity": "sha512-st8CEVPZSNCC9a/6pEbzMYO0vdHSWvua/nHoNbE4100AkW593DBQD2442LMF7ENRjFrPS+Ffuv8s5GFGasE/nw==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-4.24.0.tgz",
"integrity": "sha512-xwWB2iXeQWipzw0jTvqFL6V/20bhyM5lNT2AyAo6EcI8fX1N8x/RThaz9crf1jexajynHm4qirGT1M8tblg+Pw==",
"license": "Apache-2.0",
"dependencies": {
"tslib": ">=2.8.1"
@@ -4628,17 +4628,17 @@
"peerDependencies": {
"@angular/common": ">=16.0.0",
"@angular/core": ">=16.0.0",
"@taiga-ui/cdk": "^4.21.0",
"@taiga-ui/core": "^4.21.0",
"@taiga-ui/kit": "^4.21.0",
"@taiga-ui/cdk": "^4.24.0",
"@taiga-ui/core": "^4.24.0",
"@taiga-ui/kit": "^4.24.0",
"@taiga-ui/polymorpheus": "^4.8.0",
"rxjs": ">=7.0.0"
}
},
"node_modules/@taiga-ui/legacy": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/legacy/-/legacy-4.21.0.tgz",
"integrity": "sha512-B6QeYOyJ5F8WtrL/H3Wa1tiWogH8ruS1aGysASrDSo7wZfS66AzjAdNUuHX3L3HJcqp0xAAVDKL/LNW2S/fckg==",
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/legacy/-/legacy-4.24.0.tgz",
"integrity": "sha512-J3CJwDuaVV1zMUunZWru6Syg1cFPL0uF8qWyAis+tgllfYxFpUstvJfKhqezSauXifFsDqcLJYKGu3/fMLIsmA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": ">=2.8.1"
@@ -4660,16 +4660,6 @@
"@angular/platform-browser": ">=16.0.0"
}
},
"node_modules/@taiga-ui/styles": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/styles/-/styles-4.21.0.tgz",
"integrity": "sha512-ZFbE1nIz0V3R/4ufXGJxxA+cX17dM7efFT38QKmZpsfSyGJBeASg1FQ5ENqIE3lR8kq+GmNNtro4oo+uCjH/7w==",
"peerDependencies": {
"@taiga-ui/cdk": "^4.21.0",
"@taiga-ui/core": "^4.21.0",
"tslib": ">=2.8.1"
}
},
"node_modules/@tinkoff/ng-dompurify": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@tinkoff/ng-dompurify/-/ng-dompurify-4.0.0.tgz",

View File

@@ -47,19 +47,18 @@
"@noble/hashes": "^1.4.0",
"@start9labs/argon2": "^0.2.2",
"@start9labs/start-sdk": "file:../sdk/baseDist",
"@taiga-ui/addon-charts": "4.21.0",
"@taiga-ui/addon-commerce": "4.21.0",
"@taiga-ui/addon-mobile": "4.21.0",
"@taiga-ui/addon-table": "4.21.0",
"@taiga-ui/cdk": "4.21.0",
"@taiga-ui/core": "4.21.0",
"@taiga-ui/event-plugins": "4.3.1",
"@taiga-ui/icons": "4.21.0",
"@taiga-ui/kit": "4.21.0",
"@taiga-ui/layout": "4.21.0",
"@taiga-ui/legacy": "4.21.0",
"@taiga-ui/addon-charts": "4.24.0",
"@taiga-ui/addon-commerce": "4.24.0",
"@taiga-ui/addon-mobile": "4.24.0",
"@taiga-ui/addon-table": "4.24.0",
"@taiga-ui/cdk": "4.24.0",
"@taiga-ui/core": "4.24.0",
"@taiga-ui/event-plugins": "4.4.0",
"@taiga-ui/icons": "4.24.0",
"@taiga-ui/kit": "4.24.0",
"@taiga-ui/layout": "4.24.0",
"@taiga-ui/legacy": "4.24.0",
"@taiga-ui/polymorpheus": "4.8.0",
"@taiga-ui/styles": "4.21.0",
"@tinkoff/ng-dompurify": "4.0.0",
"ansi-to-html": "^0.7.2",
"base64-js": "^1.5.1",

View File

@@ -2,12 +2,9 @@ import {
ChangeDetectionStrategy,
Component,
EventEmitter,
inject,
Input,
Output,
} from '@angular/core'
import { Router } from '@angular/router'
import { THEME } from '@start9labs/shared'
@Component({
selector: 'marketplace-search',
@@ -21,8 +18,6 @@ export class SearchComponent {
@Output()
readonly queryChange = new EventEmitter<string>()
private readonly router = inject(Router)
readonly theme$ = inject(THEME)
onModelChange(query: string) {
this.query = query

View File

@@ -18,7 +18,7 @@
tuiButton
iconEnd="@tui.external-link"
size="s"
appearance="glass"
appearance="secondary-grayscale"
target="_blank"
rel="noreferrer"
[href]="url"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 8.1 MiB

View File

@@ -46,7 +46,6 @@ export * from './types/url'
export * from './types/workspace-config'
export * from './tokens/relative-url'
export * from './tokens/theme'
export * from './util/base-64'
export * from './util/convert-ansi'

View File

@@ -1,6 +0,0 @@
import { InjectionToken } from '@angular/core'
import { EMPTY, Observable } from 'rxjs'
export const THEME = new InjectionToken<Observable<string>>('App theme', {
factory: () => EMPTY,
})

View File

@@ -1,184 +1,102 @@
@import '@taiga-ui/core/styles/taiga-ui-local';
:root {
--tui-background-accent-1: #3880ff;
--tui-background-accent-1-hover: #4c8dff;
--tui-background-accent-1-pressed: #3171e0;
[tuiTheme='dark'] {
--tui-background-base: rgba(23, 23, 23, 1);
--tui-background-base-alt: rgba(23, 24, 29, 1);
--tui-background-neutral-1: rgba(255, 255, 255, 0.06);
--tui-background-neutral-1-hover: rgba(255, 255, 255, 0.12);
--tui-background-neutral-1-pressed: rgba(255, 255, 255, 0.18);
--tui-background-neutral-2: rgba(255, 255, 255, 0.16);
--tui-background-neutral-2-hover: rgba(255, 255, 255, 0.2);
--tui-background-neutral-2-pressed: rgba(255, 255, 255, 0.24);
--tui-background-accent-1: rgba(53, 96, 240, 1);
--tui-background-accent-1-hover: rgba(50, 92, 227, 1);
--tui-background-accent-1-pressed: rgba(50, 92, 227, 1); /* update */
--tui-background-accent-2: rgba(180, 59, 201, 1);
--tui-background-accent-2-hover: rgba(166, 0, 191, 1);
--tui-background-accent-2-pressed: rgba(166, 0, 191, 1); /* update */
--tui-background-elevation-1: rgba(23, 24, 29, 1);
--tui-background-elevation-2: rgba(33, 33, 33, 1);
--tui-background-elevation-3: rgba(34, 34, 34, 1);
--tui-border-normal: rgba(255, 255, 255, 0.14);
--tui-border-hover: rgba(255, 255, 255, 0.4);
--tui-status-negative: rgba(236, 46, 52, 1);
--tui-status-negative-pale: color-mix(in hsl, var(--tui-status-negative) 12%, transparent);
--tui-status-negative-pale-hover: color-mix(in hsl, var(--tui-status-negative) 24%, transparent);
--tui-status-positive: rgba(0, 151, 0, 1); /* update */
--tui-status-positive-pale: color-mix(in hsl, var(--tui-status-positive) 12%, transparent);
--tui-status-positive-pale-hover: color-mix(in hsl, var(--tui-status-positive) 24%, transparent);
--tui-status-warning: rgba(255, 179, 0, 1);
--tui-status-warning-pale: color-mix(in hsl, var(--tui-status-warning) 12%, transparent);
--tui-status-warning-pale-hover: color-mix(in hsl, var(--tui-status-warning) 24%, transparent);
--tui-status-info: rgba(128, 89, 229, 1);
--tui-status-info-pale: color-mix(in hsl, var(--tui-status-info) 12%, transparent);
--tui-status-info-pale-hover: color-mix(in hsl, var(--tui-status-info) 24%, transparent);
--tui-status-neutral: rgba(137, 137, 137, 1);
--tui-text-primary: rgba(255, 255, 255, 1);
--tui-text-secondary: rgba(255, 255, 255, 0.7);
--tui-text-tertiary: rgba(255, 255, 255, 0.5);
--tui-text-action: rgba(53, 96, 240, 1);
--tui-text-action-hover: rgba(50, 92, 227, 1);
--tui-text-positive: rgba(0, 151, 0, 1); /* update */
--tui-text-positive-hover: rgba(0, 151, 0, 1); /* update */
--tui-text-negative: rgba(236, 46, 52, 1);
--tui-text-negative-hover: rgba(236, 46, 52, 1);
--start9-base-1: rgba(34, 36, 40, 1);
--start9-base-2: rgba(46, 47, 52, 1);
--start9-base-3: rgba(50, 51, 53, 1);
--start9-base-4: rgba(52, 54, 58, 1);
--start9-base-5: rgba(60, 62, 64, 1);
}
/* stylelint-disable order/order */
[tuiAppearance][data-appearance='secondary-warning'] {
background: var(--tui-status-warning-pale);
color: var(--tui-text-primary);
@include appearance-hover {
background: var(--tui-status-warning-pale-hover);
}
@include appearance-active {
background: var(--tui-status-warning-pale-hover);
}
}
[tuiAppearance][data-appearance='icon-success'] {
color: var(--tui-status-positive);
}
[tuiAppearance][data-appearance='icon-warning'] {
color: var(--tui-status-warning);
}
[tuiAppearance][data-appearance='icon-error'] {
color: var(--tui-status-negative);
}
[tuiAppearance][data-appearance='primary'] {
[tuiAppearance][data-appearance^='primary'] {
@include appearance-disabled {
background: #eaecee;
background: var(--tui-status-neutral);
color: #333;
}
}
[tuiAppearance][data-appearance='secondary-solid'] {
background: #3dc2ff;
color: #fff;
@include appearance-hover {
background: #50c8ff;
}
@include appearance-active {
background: #36abe0;
}
@include appearance-disabled {
background: #eaecee;
color: #333;
}
}
[tuiAppearance][data-appearance='tertiary-solid'] {
background: #5260ff;
color: #fff;
@include appearance-hover {
background: #6370ff;
}
@include appearance-active {
background: #4854e0;
}
@include appearance-disabled {
background: #eaecee;
color: #333;
}
}
[tuiAppearance][data-appearance='success-solid'] {
background: #2dd36f;
color: #fff;
@include appearance-hover {
background: #42d77d;
}
@include appearance-active {
background: #28ba62;
}
@include appearance-disabled {
background: #eaecee;
color: #333;
}
}
[tuiAppearance][data-appearance='warning-solid'] {
background: #ffc409;
color: #fff;
@include appearance-hover {
background: #ffca22;
}
@include appearance-active {
background: #e0ac08;
}
@include appearance-disabled {
background: #eaecee;
color: #333;
}
}
[tuiAppearance][data-appearance='danger-solid'] {
background: #eb445a;
color: #fff;
@include appearance-hover {
background: #ed576b;
}
@include appearance-active {
background: #cf3c4f;
}
@include appearance-disabled {
background: #eaecee;
color: #333;
}
}
[tuiAppearance][data-appearance='input-file'] {
&:hover,
&:active {
background: transparent !important;
}
tui-root._mobile &::after {
display: none;
}
}
tui-dialog {
transform: translate3d(0, 0, 0);
}
tui-opt-group[data-label^='⚠️']::before {
color: var(--tui-status-warning);
}
tui-hint[data-appearance='onDark'] {
background: white !important;
color: #222 !important;
}
[tuiLink] {
color: var(--tui-text-action) !important;
&:hover {
color: var(--tui-text-action-hover) !important;
}
}
[tuiAppearance][data-appearance='drawer'] {
// TODO: Theme
background: rgb(81 80 83 / 86%);
border-radius: 10rem;
&._focused::after {
color: var(--tui-background-accent-1);
}
}
tui-dropdown[data-appearance='start-os'][data-appearance='start-os'] {
border: 0;
border-top: 1px solid rgba(255, 255, 255, 0.1);
backdrop-filter: blur(0.25rem);
box-shadow: 0 0.25rem 0.25rem rgb(0 0 0 / 25%);
border-radius: 0.325rem;
// TODO: Replace --tui-background-elevation-2 when Taiga UI is updated
background: rgb(63 63 63 / 80%);
background-color: color-mix(
in hsl,
var(--tui-background-elevation-3) 75%,
transparent
);
background-image: linear-gradient(
to bottom,
rgba(255, 255, 255, 0.15),
transparent
),
linear-gradient(to bottom, rgba(255, 255, 255, 0.15), transparent);
background-size: 1px 100%;
background-repeat: no-repeat;
background-position:
top left,
top right;
box-shadow:
0 0.25rem 0.125rem rgba(0, 0, 0, 0.25),
0 -0.125rem 0.25rem rgba(55, 155, 255, 0.08),
0 0 0.5rem rgba(0, 0, 0, 0.3),
inset 0 -0.125rem rgba(255, 255, 255, 0.03),
inset 0 1px rgba(255, 255, 255, 0.15),
inset 0 0 1rem rgba(0, 0, 0, 0.25),
var(--tui-shadow-medium);
tui-opt-group {
&::before {
@@ -215,3 +133,7 @@ a[tuiIconButton]:not([href]) {
pointer-events: none;
opacity: var(--tui-disabled-opacity);
}
tui-badge-notification {
background: var(--tui-status-negative);
}

View File

@@ -1,4 +1,3 @@
<svg-definitions />
<tui-root tuiTheme="dark" [class.offline]="offline$ | async">
<router-outlet />
<toast-container />

Before

Width:  |  Height:  |  Size: 184 B

After

Width:  |  Height:  |  Size: 164 B

View File

@@ -1,10 +1,8 @@
import { Component, inject, OnInit } from '@angular/core'
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
import { Title } from '@angular/platform-browser'
import { THEME } from '@start9labs/shared'
import { PatchDB } from 'patch-db-client'
import { combineLatest, map, merge, startWith } from 'rxjs'
import { AuthService } from './services/auth.service'
import { ConnectionService } from './services/connection.service'
import { PatchDataService } from './services/patch-data.service'
import { DataModel } from './services/patch-db/data-model'
@@ -19,8 +17,6 @@ export class AppComponent implements OnInit {
private readonly title = inject(Title)
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
readonly auth = inject(AuthService)
readonly theme$ = inject(THEME)
readonly subscription = merge(
inject(PatchDataService),
inject(PatchMonitorService),
@@ -30,14 +26,13 @@ export class AppComponent implements OnInit {
readonly offline$ = combineLatest([
inject(ConnectionService),
this.auth.isVerified$,
this.patch
.watch$('serverInfo', 'statusInfo')
.pipe(startWith({ restarting: false, shuttingDown: false })),
]).pipe(
map(
([connected, verified, status]) =>
connected && (!verified || status.restarting || status.shuttingDown),
([connected, { restarting, shuttingDown }]) =>
connected && (restarting || shuttingDown),
),
startWith(true),
)

View File

@@ -4,7 +4,6 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { ServiceWorkerModule } from '@angular/service-worker'
import { TuiRoot } from '@taiga-ui/core'
import { SidebarHostComponent } from 'src/app/components/sidebar-host.component'
import { SvgDefinitionsComponent } from 'src/app/components/svg-definitions.component'
import { ToastContainerComponent } from 'src/app/components/toast-container.component'
import { environment } from '../environments/environment'
import { AppComponent } from './app.component'
@@ -26,7 +25,6 @@ import { RoutingModule } from './routing.module'
registrationStrategy: 'registerWhenStable:30000',
}),
SidebarHostComponent,
SvgDefinitionsComponent,
],
providers: APP_PROVIDERS,
bootstrap: [AppComponent],

View File

@@ -5,7 +5,7 @@ import {
AbstractCategoryService,
FilterPackagesPipe,
} from '@start9labs/marketplace'
import { RELATIVE_URL, THEME, WorkspaceConfig } from '@start9labs/shared'
import { RELATIVE_URL, WorkspaceConfig } from '@start9labs/shared'
import {
TUI_DATE_FORMAT,
TUI_DIALOGS_CLOSE,
@@ -35,7 +35,6 @@ import { ClientStorageService } from './services/client-storage.service'
import { DateTransformerService } from './services/date-transformer.service'
import { DatetimeTransformerService } from './services/datetime-transformer.service'
import { StorageService } from './services/storage.service'
import { ThemeSwitcherService } from './services/theme-switcher.service'
const {
useMocks,
@@ -84,10 +83,6 @@ export const APP_PROVIDERS: Provider[] = [
provide: RELATIVE_URL,
useValue: `/${api.url}/${api.version}`,
},
{
provide: THEME,
useExisting: ThemeSwitcherService,
},
{
provide: AbstractCategoryService,
useClass: CategoryService,

View File

@@ -1,46 +0,0 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
@Component({
standalone: true,
selector: 'svg-definitions',
template: `
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="bevel-light">
<feFlood flood-color="white" flood-opacity="0.1" />
<feComposite in2="SourceAlpha" operator="out" />
<feGaussianBlur stdDeviation="0.5" result="blur" />
<feOffset dx="-1" dy="1" />
<feComposite operator="atop" in2="SourceGraphic" />
</filter>
<filter id="bevel-dark">
<feFlood flood-color="black" flood-opacity="0.3" />
<feComposite in2="SourceAlpha" operator="out" />
<feGaussianBlur stdDeviation="0.5" result="blur" />
<feOffset dx="1" dy="-1" />
<feComposite operator="atop" in2="SourceGraphic" />
</filter>
<filter id="round-corners">
<feGaussianBlur in="SourceGraphic" stdDeviation="3" result="blur" />
<feColorMatrix
in="blur"
type="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -9"
result="flt_tag"
/>
<feComposite in="SourceGraphic" in2="flt_tag" operator="atop" />
</filter>
</defs>
</svg>
`,
styles: `
:host {
position: absolute;
width: 0;
height: 0;
visibility: hidden;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SvgDefinitionsComponent {}

View File

@@ -28,7 +28,6 @@
<a
tuiButton
size="s"
appearance="tertiary-solid"
iconEnd="@tui.download"
href="/static/local-root-ca.crt"
>
@@ -61,7 +60,7 @@
tuiButton
size="s"
class="refresh"
appearance="success-solid"
appearance="positive"
iconEnd="@tui.refresh-cw"
(click)="refresh()"
>
@@ -84,22 +83,13 @@
<ng-template #trusted>
<div tuiCardLarge tuiSurface="floating" class="card">
<tui-icon
icon="@tui.shield"
tuiAppearance="icon-success"
[style.font-size.rem]="4"
/>
<tui-icon icon="@tui.shield" class="g-success" [style.font-size.rem]="4" />
<h1>Root CA Trusted!</h1>
<p>
You have successfully trusted your server's Root CA and may now log in
securely.
</p>
<button
tuiButton
appearance="tertiary-solid"
iconEnd="@tui.external-link"
(click)="launchHttps()"
>
<button tuiButton iconEnd="@tui.external-link" (click)="launchHttps()">
Go to login
</button>
</div>

View File

@@ -1,7 +1,7 @@
import { CommonModule, DOCUMENT } from '@angular/common'
import { Component, inject } from '@angular/core'
import { RELATIVE_URL } from '@start9labs/shared'
import { TuiAppearance, TuiButton, TuiIcon, TuiSurface } from '@taiga-ui/core'
import { TuiButton, TuiIcon, TuiSurface } from '@taiga-ui/core'
import { TuiCardLarge } from '@taiga-ui/layout'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ConfigService } from 'src/app/services/config.service'
@@ -11,14 +11,7 @@ import { ConfigService } from 'src/app/services/config.service'
selector: 'ca-wizard',
templateUrl: './ca-wizard.component.html',
styleUrls: ['./ca-wizard.component.scss'],
imports: [
CommonModule,
TuiIcon,
TuiButton,
TuiAppearance,
TuiCardLarge,
TuiSurface,
],
imports: [CommonModule, TuiIcon, TuiButton, TuiCardLarge, TuiSurface],
})
export class CAWizardComponent {
private readonly api = inject(ApiService)

View File

@@ -16,9 +16,7 @@
Password
</tui-input-password>
<tui-error class="error" [error]="error || null" />
<button tuiButton class="button" appearance="tertiary-solid">
Login
</button>
<button tuiButton class="button">Login</button>
</form>
</div>
</ng-template>

View File

@@ -6,7 +6,7 @@
align-items: center;
text-align: center;
width: max(33%, 20rem);
background: var(--tui-background-base-alt);
background: var(--start9-base-1);
box-shadow: var(--tui-shadow-small);
}

View File

@@ -7,7 +7,7 @@
class="button"
[class.button_open]="open"
[style.border-radius.%]="100"
[appearance]="invalid ? 'danger-solid' : 'secondary'"
[appearance]="invalid ? 'primary-destructive' : 'secondary'"
></button>
<ng-content />
{{ spec.name }}

View File

@@ -54,7 +54,11 @@ import { HeaderStatusComponent } from './status.component'
border-radius: inherit;
backdrop-filter: blur(1rem);
transform: skewX(30deg);
background: rgb(75 75 75 / 65%);
background: color-mix(
in hsl,
var(--start9-base-2) 75%,
transparent
);
box-shadow: inset 0 1px rgb(255 255 255 / 25%);
z-index: -1;
}

View File

@@ -52,7 +52,7 @@ import { ABOUT } from './about.component'
<a
tuiOption
iconStart="@tui.wrench"
routerLink="/portal/system/settings"
routerLink="/portal/settings"
(click)="open = false"
>
System Settings

View File

@@ -94,7 +94,7 @@ import { getMenu } from 'src/app/utils/system-utilities'
position: absolute;
inset: 0;
transform: skewX(30deg);
background: rgb(75 75 75 / 65%);
background: color-mix(in hsl, var(--start9-base-2) 75%, transparent);
box-shadow: inset 0 1px rgb(255 255 255 / 25%);
z-index: -1;
}
@@ -129,7 +129,7 @@ import { getMenu } from 'src/app/utils/system-utilities'
&::before {
border-radius: var(--bumper);
filter: brightness(0.65);
filter: brightness(0.5);
}
span {

View File

@@ -42,7 +42,7 @@ export class ClearnetAddressesDirective implements AddressesService {
buttons: [
{
text: 'Manage domains',
link: 'portal/system/settings/domains',
link: 'portal/settings/domains',
},
{
text: 'Save',

View File

@@ -15,11 +15,7 @@ import { BadgeService } from 'src/app/services/badge.service'
import { RESOURCES } from 'src/app/utils/resources'
import { getMenu } from 'src/app/utils/system-utilities'
const FILTER = [
'/portal/services',
'/portal/system/settings',
'/portal/system/marketplace',
]
const FILTER = ['/portal/services', '/portal/settings', '/portal/marketplace']
@Component({
standalone: true,
@@ -38,7 +34,7 @@ const FILTER = [
<a
tuiTabBarItem
icon="@tui.shopping-cart"
routerLink="/portal/system/marketplace"
routerLink="/portal/marketplace"
routerLinkActive
(isActiveChange)="update()"
>
@@ -47,7 +43,7 @@ const FILTER = [
<a
tuiTabBarItem
icon="@tui.settings"
routerLink="/portal/system/settings"
routerLink="/portal/settings"
routerLinkActive
[badge]="badge()"
(isActiveChange)="update()"
@@ -145,10 +141,9 @@ export class TabsComponent {
readonly resources = RESOURCES
readonly menu = getMenu().filter(item => !FILTER.includes(item.routerLink))
readonly badge = toSignal(
inject(BadgeService).getCount('/portal/system/settings'),
{ initialValue: 0 },
)
readonly badge = toSignal(inject(BadgeService).getCount('/portal/settings'), {
initialValue: 0,
})
readonly all = computed(() =>
this.menu.reduce((acc, item) => acc + item.badge(), 0),

View File

@@ -1,4 +1,8 @@
import { Routes } from '@angular/router'
import { ActivatedRouteSnapshot, Routes } from '@angular/router'
import { PackageDataEntry } from '../../services/patch-db/data-model'
import { getManifest } from '../../utils/get-package-data'
import { SYSTEM_UTILITIES } from '../../utils/system-utilities'
import { toRouterLink } from '../../utils/to-router-link'
import { PortalComponent } from './portal.component'
const ROUTES: Routes = [
@@ -14,15 +18,85 @@ const ROUTES: Routes = [
{
path: 'services',
loadChildren: () =>
import('./routes/service/service.module').then(m => m.ServiceModule),
import('./routes/services/services.module').then(
m => m.ServicesModule,
),
},
{
path: 'system',
loadChildren: () =>
import('./routes/system/system.module').then(m => m.SystemModule),
title: systemTabResolver,
path: 'backups',
loadComponent: () => import('./routes/backups/backups.component'),
data: toNavigationItem('/portal/backups'),
},
{
title: systemTabResolver,
path: 'logs',
loadComponent: () => import('./routes/logs/logs.component'),
data: toNavigationItem('/portal/logs'),
},
{
title: systemTabResolver,
path: 'marketplace',
loadChildren: () => import('./routes/marketplace/marketplace.routes'),
data: toNavigationItem('/portal/marketplace'),
},
{
title: systemTabResolver,
path: 'settings',
loadChildren: () => import('./routes/settings/settings.routes'),
data: toNavigationItem('/portal/settings'),
},
{
title: systemTabResolver,
path: 'notifications',
loadComponent: () =>
import('./routes/notifications/notifications.component'),
data: toNavigationItem('/portal/notifications'),
},
{
title: systemTabResolver,
path: 'sideload',
loadComponent: () => import('./routes/sideload/sideload.component'),
data: toNavigationItem('/portal/sideload'),
},
// {
// title: systemTabResolver,
// path: 'updates',
// loadComponent: () => import('./routes/updates/updates.component'),
// data: toNavigationItem('/portal/updates'),
// },
{
title: systemTabResolver,
path: 'metrics',
loadComponent: () => import('./routes/metrics/metrics.component'),
data: toNavigationItem('/portal/metrics'),
},
],
},
]
export default ROUTES
function systemTabResolver({ data }: ActivatedRouteSnapshot): string {
return data['title']
}
function toNavigationItem(
id: string,
packages: Record<string, PackageDataEntry> = {},
) {
const item = SYSTEM_UTILITIES[id]
const routerLink = toRouterLink(id)
return SYSTEM_UTILITIES[id]
? {
icon: item.icon,
title: item.title,
routerLink,
}
: {
icon: packages[id]?.icon,
title: getManifest(packages[id]).title,
routerLink,
}
}

View File

@@ -26,7 +26,7 @@ import { HasErrorPipe } from '../pipes/has-error.pipe'
Past Events
<button
tuiButton
appearance="danger-solid"
appearance="primary-destructive"
[disabled]="disabled"
(click)="delete()"
>

View File

@@ -6,6 +6,6 @@ import { Pipe, PipeTransform } from '@angular/core'
})
export class DurationPipe implements PipeTransform {
transform(start: string, finish: string): number {
return (new Date(finish).valueOf() - new Date(start).valueOf()) / 100
return (new Date(finish).valueOf() - new Date(start).valueOf()) / 1000 / 60
}
}

View File

@@ -1,9 +1,9 @@
import { TuiTextfieldControllerModule, TuiSelectModule } from '@taiga-ui/legacy'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { TuiSelectModule, TuiTextfieldControllerModule } from '@taiga-ui/legacy'
import { LogsComponent } from 'src/app/routes/portal/components/logs/logs.component'
import { RR } from 'src/app/services/api/api.types'
import { LogsComponent } from '../../../components/logs/logs.component'
import { ApiService } from 'src/app/services/api/embassy-api.service'
@Component({
template: `

View File

@@ -42,7 +42,7 @@ import { ToManifestPipe } from 'src/app/routes/portal/pipes/to-manifest'
<button
tuiButton
type="button"
appearance="warning-solid"
appearance="secondary-destructive"
(click)="tryInstall()"
>
Downgrade
@@ -62,7 +62,7 @@ import { ToManifestPipe } from 'src/app/routes/portal/pipes/to-manifest'
<button
tuiButton
type="button"
appearance="tertiary-solid"
appearance="secondary-grayscale"
(click)="tryInstall()"
>
Reinstall

View File

@@ -50,6 +50,7 @@ import { MarketplaceControlsComponent } from './controls.component'
type="button"
appearance="icon"
iconStart="@tui.x"
[tuiAppearanceFocus]="false"
(click)="toggle(false)"
></button>
<marketplace-controls

View File

@@ -2,10 +2,10 @@ import { AsyncPipe } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { TuiProgress } from '@taiga-ui/kit'
import { CpuComponent } from 'src/app/routes/portal/routes/system/metrics/cpu.component'
import { TemperatureComponent } from 'src/app/routes/portal/routes/system/metrics/temperature.component'
import { MetricComponent } from 'src/app/routes/portal/routes/system/metrics/metric.component'
import { MetricsService } from 'src/app/routes/portal/routes/system/metrics/metrics.service'
import { CpuComponent } from 'src/app/routes/portal/routes/metrics/cpu.component'
import { TemperatureComponent } from 'src/app/routes/portal/routes/metrics/temperature.component'
import { MetricComponent } from 'src/app/routes/portal/routes/metrics/metric.component'
import { MetricsService } from 'src/app/routes/portal/routes/metrics/metrics.service'
import { TimeService } from 'src/app/services/time.service'
@Component({

View File

@@ -8,7 +8,7 @@ import {
import { T } from '@start9labs/start-sdk'
import { tuiPure } from '@taiga-ui/cdk'
import { tuiButtonOptionsProvider } from '@taiga-ui/core'
import { DependencyInfo } from 'src/app/routes/portal/routes/service/types/dependency-info'
import { DependencyInfo } from 'src/app/routes/portal/routes/services/types/dependency-info'
import { ControlsService } from '../../../../../services/controls.service'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { PackageStatus } from 'src/app/services/pkg-status-rendering.service'
@@ -22,7 +22,7 @@ const STOPPABLE = ['running', 'starting', 'restarting']
@if (canStop) {
<button
tuiButton
appearance="danger-solid"
appearance="primary-destructive"
iconStart="@tui.square"
(click)="actions.stop(manifest)"
>

View File

@@ -24,9 +24,9 @@ import { T } from '@start9labs/start-sdk'
<a
tuiButton
iconStart="@tui.square-plus"
routerLink="/portal/system/backups"
routerLink="/portal/backups"
size="s"
appearance="secondary-warning"
appearance="warning"
>
Manage
</a>

View File

@@ -6,7 +6,7 @@ import {
Input,
} from '@angular/core'
import { T } from '@start9labs/start-sdk'
import { ServiceHealthCheckComponent } from 'src/app/routes/portal/routes/service/components/health-check.component'
import { ServiceHealthCheckComponent } from 'src/app/routes/portal/routes/services/components/health-check.component'
import { ConnectionService } from 'src/app/services/connection.service'
@Component({

View File

@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { T } from '@start9labs/start-sdk'
import { TuiProgress } from '@taiga-ui/kit'
import { InstallingProgressPipe } from 'src/app/routes/portal/routes/service/pipes/install-progress.pipe'
import { InstallingProgressPipe } from 'src/app/routes/portal/routes/services/pipes/install-progress.pipe'
@Component({
selector: '[progress]',

View File

@@ -18,6 +18,7 @@ import { ServicesService } from './services.service'
<th [style.width.rem]="3"></th>
<th tuiTh [requiredSort]="true" [sorter]="name">Name</th>
<th tuiTh>Version</th>
<th tuiTh [requiredSort]="true" [sorter]="uptime">Uptime</th>
<th
tuiTh
[requiredSort]="true"
@@ -38,7 +39,7 @@ import { ServicesService } from './services.service'
></tr>
} @empty {
<tr>
<td colspan="5">
<td colspan="6">
{{ services() ? 'No services installed' : 'Loading...' }}
</td>
</tr>
@@ -98,5 +99,8 @@ export class DashboardComponent {
readonly status: TuiComparator<PackageDataEntry> = (a, b) =>
getInstalledPrimaryStatus(b) > getInstalledPrimaryStatus(a) ? -1 : 1
readonly uptime: TuiComparator<any> = (a, b) =>
a.startedAt || '' > b.startedAt || '' ? -1 : 1
sorter = this.name
}

View File

@@ -12,6 +12,7 @@ import { ConnectionService } from 'src/app/services/connection.service'
import { PkgDependencyErrors } from 'src/app/services/dep-error.service'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { getManifest } from 'src/app/utils/get-package-data'
import { UptimeComponent } from './uptime.component'
import { ControlsComponent } from './controls.component'
import { StatusComponent } from './status.component'
@@ -26,6 +27,7 @@ import { StatusComponent } from './status.component'
<a [routerLink]="routerLink">{{ manifest.title }}</a>
</td>
<td [style.grid-area]="'2 / 2'">{{ manifest.version }}</td>
<td [appUptime]="$any(pkg).startedAt"></td>
<td
[style.grid-area]="'3 / 2'"
appStatus
@@ -97,7 +99,13 @@ import { StatusComponent } from './status.component'
`,
hostDirectives: [RouterLink],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [RouterLink, AsyncPipe, StatusComponent, ControlsComponent],
imports: [
RouterLink,
AsyncPipe,
StatusComponent,
ControlsComponent,
UptimeComponent,
],
})
export class ServiceComponent implements OnChanges {
private readonly link = inject(RouterLink)

View File

@@ -0,0 +1,49 @@
import { Component, Input, OnChanges, OnDestroy } from '@angular/core'
import { tuiInjectElement } from '@taiga-ui/cdk'
@Component({
standalone: true,
selector: '[appUptime]',
template: '',
styles: `
:host-context(tui-root._mobile) {
display: none;
}
`,
})
export class UptimeComponent implements OnChanges, OnDestroy {
private readonly el = tuiInjectElement()
private interval: any = NaN
@Input()
appUptime = ''
ngOnChanges() {
if (!this.appUptime) {
this.el.textContent = '-'
} else {
clearInterval(this.interval)
this.el.textContent = uptime(new Date(this.appUptime))
this.interval = setInterval(() => {
this.el.textContent = uptime(new Date(this.appUptime))
}, 60 * 1000)
}
}
ngOnDestroy() {
clearInterval(this.interval)
}
}
function uptime(date: Date): string {
const delta = Date.now() - date.getTime()
const days = Math.floor(delta / (1000 * 60 * 60 * 24))
const hours = Math.floor((delta / (1000 * 60 * 60)) % 24)
const minutes = Math.floor((delta / (1000 * 60)) % 60)
if (days > 0) return `${days}d ${hours}h ${minutes}m`
if (hours > 0) return `${hours}h ${minutes}m`
return `${minutes}m`
}

Some files were not shown because too many files have changed in this diff Show More