From a730543c76d9725db2bbc739ef247cd9246e1b97 Mon Sep 17 00:00:00 2001 From: waterplea Date: Thu, 15 Aug 2024 12:40:49 +0400 Subject: [PATCH] fix: fix build after minor merged into major --- web/package-lock.json | 1 - web/package.json | 1 - web/projects/marketplace/package.json | 2 + .../src/modals/release-notes.component.ts | 90 ++++++ .../release-notes.component.html | 51 ---- .../release-notes.component.scss | 44 --- .../release-notes/release-notes.component.ts | 70 ----- .../release-notes/release-notes.module.ts | 32 --- .../list/categories/categories.component.html | 14 +- .../list/categories/categories.component.ts | 10 +- .../src/pages/list/item/item.component.ts | 2 +- .../src/pages/show/about/about.component.html | 21 +- .../src/pages/show/about/about.component.ts | 26 +- .../src/pages/show/about/about.module.ts | 5 +- .../show/additional/additional.component.html | 18 +- .../show/additional/additional.component.ts | 7 +- .../dependencies/dependencies.component.ts | 7 +- .../dependencies/dependency-item.component.ts | 11 +- .../pages/show/flavors/flavors.component.html | 21 -- .../pages/show/flavors/flavors.component.ts | 23 +- .../src/pages/show/flavors/flavors.module.ts | 19 -- .../src/pages/show/hero/hero.component.ts | 12 +- .../show/screenshots/screenshots.component.ts | 13 +- web/projects/marketplace/src/public-api.ts | 1 - .../src/services/marketplace.service.ts | 12 +- .../src/app/components/password.directive.ts | 27 ++ .../src/app/components/servers.component.ts | 5 +- .../src/app/pages/loading.page.ts | 35 +-- .../src/app/pages/recover.page.ts | 5 +- web/projects/shared/package.json | 7 +- .../src}/components/server.component.ts | 18 +- web/projects/shared/src/public-api.ts | 2 + .../shared/src/util/format-progress.ts | 33 +++ web/projects/ui/src/app/app.component.ts | 4 +- web/projects/ui/src/app/app.providers.ts | 12 +- .../ui/src/app/components/form.component.ts | 167 ----------- .../app/components/form/control.directive.ts | 30 -- .../ui/src/app/components/form/control.ts | 35 --- .../form/form-array/form-array.component.html | 58 ---- .../form/form-array/form-array.component.scss | 50 ---- .../form/form-array/form-array.component.ts | 91 ------ .../form/form-color/form-color.component.html | 31 --- .../form/form-color/form-color.component.scss | 33 --- .../form/form-color/form-color.component.ts | 15 - .../form-control/form-control.component.html | 39 --- .../form-control/form-control.component.scss | 11 - .../form-control/form-control.component.ts | 71 ----- .../form-control/form-control.providers.ts | 25 -- .../form-datetime.component.html | 43 --- .../form-datetime/form-datetime.component.ts | 36 --- .../form/form-group/form-group.component.html | 30 -- .../form/form-group/form-group.component.scss | 35 --- .../form/form-group/form-group.component.ts | 35 --- .../form/form-group/form-group.providers.ts | 34 --- .../form-multiselect.component.html | 18 -- .../form-multiselect.component.ts | 49 ---- .../form-number/form-number.component.html | 18 -- .../form/form-number/form-number.component.ts | 11 - .../form-object/form-object.component.html | 25 -- .../form-object/form-object.component.scss | 41 --- .../form/form-object/form-object.component.ts | 38 --- .../form-select/form-select.component.html | 18 -- .../form/form-select/form-select.component.ts | 30 -- .../form/form-text/form-text.component.html | 44 --- .../form/form-text/form-text.component.scss | 8 - .../form/form-text/form-text.component.ts | 16 -- .../form-textarea.component.html | 15 - .../form-textarea/form-textarea.component.ts | 12 - .../form-toggle/form-toggle.component.html | 11 - .../form/form-toggle/form-toggle.component.ts | 10 - .../form/form-union/form-union.component.html | 11 - .../form/form-union/form-union.component.scss | 8 - .../form/form-union/form-union.component.ts | 55 ---- .../ui/src/app/components/form/form.module.ts | 107 ------- .../ui/src/app/components/form/hint.pipe.ts | 21 -- .../app/components/form/invalid.service.ts | 19 -- .../src/app/components/form/mustache.pipe.ts | 12 - .../notifications-toast.component.ts | 2 +- .../app/components/refresh-alert.component.ts | 8 +- .../app/components/update-toast.component.ts | 2 +- .../backup-server-select.module.ts | 13 - .../backup-server-select.page.html | 35 --- .../backup-server-select.page.scss | 0 .../backup-server-select.page.ts | 118 -------- .../ui/src/app/modals/config-dep.component.ts | 104 ------- .../ui/src/app/modals/config.component.ts | 261 ------------------ .../registry.component.ts | 4 +- .../app/modals/password-prompt.component.ts | 94 ------- .../ui/src/app/modals/prompt.component.ts | 123 --------- .../diagnostic-routing.module.ts | 21 -- .../pages/diagnostic-routes/home/home.page.ts | 222 --------------- .../diagnostic-routes/logs/logs.page.scss | 0 .../ui/src/app/pages/init/init.module.ts | 24 -- .../ui/src/app/pages/init/init.page.html | 18 -- .../ui/src/app/pages/init/init.page.scss | 23 -- .../ui/src/app/pages/init/init.page.ts | 11 - .../ui/src/app/pages/init/init.service.ts | 90 ------ .../src/app/pages/init/logs/logs.component.ts | 33 --- .../ui/src/app/pages/init/logs/logs.module.ts | 20 -- .../src/app/pages/init/logs/logs.service.ts | 51 ---- .../app/pages/init/logs/logs.template.html | 9 - .../routes/diagnostic/diagnostic.module.ts | 2 +- .../app/routes/diagnostic/home/home.page.html | 8 - .../app/routes/diagnostic/home/home.page.ts | 4 +- .../routes/initializing/initializing.page.ts | 55 ++++ .../ui/src/app/routes/loading/loading.page.ts | 24 -- .../portal/components/form.component.ts | 10 +- .../form/form-array/form-array.component.ts | 4 +- .../form/form-text/form-text.component.ts | 5 +- .../form/form-union/form-union.component.html | 4 +- .../form/form-union/form-union.component.ts | 9 +- .../portal/components/form/form.module.ts | 1 - .../components/header/about.component.ts | 8 +- .../components/header/connection.component.ts | 7 +- .../components/header/header.component.ts | 2 +- .../components/header/mobile.component.ts | 4 +- .../interfaces/address-item.component.ts | 4 +- .../interfaces/interface.component.ts | 5 +- .../components/interfaces/interface.utils.ts | 8 +- .../portal/components/logs/logs.pipe.ts | 2 +- .../routes/portal/modals/config.component.ts | 26 +- .../src/app/routes/portal/portal.component.ts | 2 +- .../routes/dashboard/service.component.ts | 2 +- .../routes/dashboard/services.service.ts | 2 +- .../portal/routes/dashboard/ui.component.ts | 12 +- .../components/dependency.component.ts | 10 +- .../components/health-checks.component.ts | 2 +- .../interface-list-item.component.ts | 10 +- .../components/interface-list.component.ts | 4 +- .../service/components/status.component.ts | 18 +- .../service/pipes/interface-info.pipe.ts | 2 +- .../service/pipes/to-additional.pipe.ts | 6 +- .../routes/service/pipes/to-menu.pipe.ts | 12 +- .../service/routes/interface.component.ts | 2 +- .../routes/service/routes/outlet.component.ts | 2 +- .../service/routes/service.component.ts | 15 +- .../routes/service/types/dependency-info.ts | 4 +- .../backups/components/status.component.ts | 11 +- .../backups/components/upcoming.component.ts | 2 +- .../system/backups/modals/backup.component.ts | 2 +- .../backups/modals/recover.component.ts | 5 +- .../backups/modals/servers.component.ts | 30 ++ .../backups/modals/targets.component.ts | 4 +- .../system/backups/pipes/to-options.pipe.ts | 2 +- .../backups/services/restore.service.ts | 52 ++-- .../system/backups/types/backup-config.ts | 9 +- .../system/backups/types/recover-data.ts | 1 + .../components/controls.component.ts | 24 +- .../marketplace/components/menu.component.ts | 4 +- .../marketplace/components/tile.component.ts | 8 +- .../marketplace/modals/preview.component.ts | 40 +-- .../marketplace/modals/registry.component.ts | 2 +- .../system/marketplace/pipes/to-local.pipe.ts | 2 +- .../marketplace/services/alerts.service.ts | 6 +- .../system/notifications/item.component.ts | 2 +- .../settings/components/menu.component.ts | 2 +- .../settings/modals/update.component.ts | 2 +- .../routes/domains/domains.component.ts | 14 +- .../settings/routes/email/email.component.ts | 2 +- .../experimental/experimental.component.ts | 2 +- .../routes/interfaces/ui.component.ts | 5 +- .../routes/proxies/proxies.component.ts | 2 +- .../routes/router/router.component.ts | 2 +- .../settings/routes/wifi/wifi.component.ts | 2 +- .../system/settings/settings.service.ts | 2 +- .../system/sideload/package.component.ts | 26 +- .../system/sideload/sideload.component.ts | 15 +- .../routes/system/sideload/sideload.utils.ts | 158 ----------- .../system/updates/filter-updates.pipe.ts | 12 +- .../routes/system/updates/item.component.ts | 25 +- .../system/updates/updates.component.ts | 4 +- web/projects/ui/src/app/routing.module.ts | 15 +- .../ui/src/app/services/actions.service.ts | 2 +- .../ui/src/app/services/api/api.fixures.ts | 6 +- .../ui/src/app/services/api/api.types.ts | 3 +- .../app/services/api/embassy-api.service.ts | 12 +- .../services/api/embassy-live-api.service.ts | 24 +- .../services/api/embassy-mock-api.service.ts | 18 +- .../ui/src/app/services/badge.service.ts | 16 +- .../src/app/services/breadcrumbs.service.ts | 2 +- .../ui/src/app/services/config.service.ts | 15 +- .../src/app/services/marketplace.service.ts | 20 +- .../ui/src/app/services/network.service.ts | 4 +- .../src/app/services/notification.service.ts | 2 +- .../services/patch-db/patch-db.providers.ts | 16 -- .../ui/src/app/services/state.service.ts | 12 +- .../app/services/theme-switcher.service.ts | 4 +- .../ui/src/app/services/time.service.ts | 2 +- web/projects/ui/tsconfig.json | 2 +- 189 files changed, 714 insertions(+), 3652 deletions(-) create mode 100644 web/projects/marketplace/src/modals/release-notes.component.ts delete mode 100644 web/projects/marketplace/src/modals/release-notes/release-notes.component.html delete mode 100644 web/projects/marketplace/src/modals/release-notes/release-notes.component.scss delete mode 100644 web/projects/marketplace/src/modals/release-notes/release-notes.component.ts delete mode 100644 web/projects/marketplace/src/modals/release-notes/release-notes.module.ts delete mode 100644 web/projects/marketplace/src/pages/show/flavors/flavors.component.html delete mode 100644 web/projects/marketplace/src/pages/show/flavors/flavors.module.ts create mode 100644 web/projects/setup-wizard/src/app/components/password.directive.ts rename web/projects/{setup-wizard/src/app => shared/src}/components/server.component.ts (60%) create mode 100644 web/projects/shared/src/util/format-progress.ts delete mode 100644 web/projects/ui/src/app/components/form.component.ts delete mode 100644 web/projects/ui/src/app/components/form/control.directive.ts delete mode 100644 web/projects/ui/src/app/components/form/control.ts delete mode 100644 web/projects/ui/src/app/components/form/form-array/form-array.component.html delete mode 100644 web/projects/ui/src/app/components/form/form-array/form-array.component.scss delete mode 100644 web/projects/ui/src/app/components/form/form-array/form-array.component.ts delete mode 100644 web/projects/ui/src/app/components/form/form-color/form-color.component.html delete mode 100644 web/projects/ui/src/app/components/form/form-color/form-color.component.scss delete mode 100644 web/projects/ui/src/app/components/form/form-color/form-color.component.ts delete mode 100644 web/projects/ui/src/app/components/form/form-control/form-control.component.html delete mode 100644 web/projects/ui/src/app/components/form/form-control/form-control.component.scss delete mode 100644 web/projects/ui/src/app/components/form/form-control/form-control.component.ts delete mode 100644 web/projects/ui/src/app/components/form/form-control/form-control.providers.ts delete mode 100644 web/projects/ui/src/app/components/form/form-datetime/form-datetime.component.html delete mode 100644 web/projects/ui/src/app/components/form/form-datetime/form-datetime.component.ts delete mode 100644 web/projects/ui/src/app/components/form/form-group/form-group.component.html delete mode 100644 web/projects/ui/src/app/components/form/form-group/form-group.component.scss delete mode 100644 web/projects/ui/src/app/components/form/form-group/form-group.component.ts delete mode 100644 web/projects/ui/src/app/components/form/form-group/form-group.providers.ts delete mode 100644 web/projects/ui/src/app/components/form/form-multiselect/form-multiselect.component.html delete mode 100644 web/projects/ui/src/app/components/form/form-multiselect/form-multiselect.component.ts delete mode 100644 web/projects/ui/src/app/components/form/form-number/form-number.component.html delete mode 100644 web/projects/ui/src/app/components/form/form-number/form-number.component.ts delete mode 100644 web/projects/ui/src/app/components/form/form-object/form-object.component.html delete mode 100644 web/projects/ui/src/app/components/form/form-object/form-object.component.scss delete mode 100644 web/projects/ui/src/app/components/form/form-object/form-object.component.ts delete mode 100644 web/projects/ui/src/app/components/form/form-select/form-select.component.html delete mode 100644 web/projects/ui/src/app/components/form/form-select/form-select.component.ts delete mode 100644 web/projects/ui/src/app/components/form/form-text/form-text.component.html delete mode 100644 web/projects/ui/src/app/components/form/form-text/form-text.component.scss delete mode 100644 web/projects/ui/src/app/components/form/form-text/form-text.component.ts delete mode 100644 web/projects/ui/src/app/components/form/form-textarea/form-textarea.component.html delete mode 100644 web/projects/ui/src/app/components/form/form-textarea/form-textarea.component.ts delete mode 100644 web/projects/ui/src/app/components/form/form-toggle/form-toggle.component.html delete mode 100644 web/projects/ui/src/app/components/form/form-toggle/form-toggle.component.ts delete mode 100644 web/projects/ui/src/app/components/form/form-union/form-union.component.html delete mode 100644 web/projects/ui/src/app/components/form/form-union/form-union.component.scss delete mode 100644 web/projects/ui/src/app/components/form/form-union/form-union.component.ts delete mode 100644 web/projects/ui/src/app/components/form/form.module.ts delete mode 100644 web/projects/ui/src/app/components/form/hint.pipe.ts delete mode 100644 web/projects/ui/src/app/components/form/invalid.service.ts delete mode 100644 web/projects/ui/src/app/components/form/mustache.pipe.ts delete mode 100644 web/projects/ui/src/app/modals/backup-server-select/backup-server-select.module.ts delete mode 100644 web/projects/ui/src/app/modals/backup-server-select/backup-server-select.page.html delete mode 100644 web/projects/ui/src/app/modals/backup-server-select/backup-server-select.page.scss delete mode 100644 web/projects/ui/src/app/modals/backup-server-select/backup-server-select.page.ts delete mode 100644 web/projects/ui/src/app/modals/config-dep.component.ts delete mode 100644 web/projects/ui/src/app/modals/config.component.ts delete mode 100644 web/projects/ui/src/app/modals/password-prompt.component.ts delete mode 100644 web/projects/ui/src/app/modals/prompt.component.ts delete mode 100644 web/projects/ui/src/app/pages/diagnostic-routes/diagnostic-routing.module.ts delete mode 100644 web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.ts delete mode 100644 web/projects/ui/src/app/pages/diagnostic-routes/logs/logs.page.scss delete mode 100644 web/projects/ui/src/app/pages/init/init.module.ts delete mode 100644 web/projects/ui/src/app/pages/init/init.page.html delete mode 100644 web/projects/ui/src/app/pages/init/init.page.scss delete mode 100644 web/projects/ui/src/app/pages/init/init.page.ts delete mode 100644 web/projects/ui/src/app/pages/init/init.service.ts delete mode 100644 web/projects/ui/src/app/pages/init/logs/logs.component.ts delete mode 100644 web/projects/ui/src/app/pages/init/logs/logs.module.ts delete mode 100644 web/projects/ui/src/app/pages/init/logs/logs.service.ts delete mode 100644 web/projects/ui/src/app/pages/init/logs/logs.template.html create mode 100644 web/projects/ui/src/app/routes/initializing/initializing.page.ts delete mode 100644 web/projects/ui/src/app/routes/loading/loading.page.ts create mode 100644 web/projects/ui/src/app/routes/portal/routes/system/backups/modals/servers.component.ts delete mode 100644 web/projects/ui/src/app/routes/portal/routes/system/sideload/sideload.utils.ts delete mode 100644 web/projects/ui/src/app/services/patch-db/patch-db.providers.ts diff --git a/web/package-lock.json b/web/package-lock.json index f69839842..804a514d0 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -31,7 +31,6 @@ "@taiga-ui/cdk": "4.0.0-rc.7", "@taiga-ui/core": "4.0.0-rc.7", "@taiga-ui/event-plugins": "^4.0.1", - "@taiga-ui/experimental": "4.0.0-rc.7", "@taiga-ui/icons": "4.0.0-rc.7", "@taiga-ui/kit": "4.0.0-rc.7", "@taiga-ui/layout": "4.0.0-rc.7", diff --git a/web/package.json b/web/package.json index ba0e270d8..74a8af251 100644 --- a/web/package.json +++ b/web/package.json @@ -51,7 +51,6 @@ "@taiga-ui/cdk": "4.0.0-rc.7", "@taiga-ui/core": "4.0.0-rc.7", "@taiga-ui/event-plugins": "^4.0.1", - "@taiga-ui/experimental": "4.0.0-rc.7", "@taiga-ui/icons": "4.0.0-rc.7", "@taiga-ui/kit": "4.0.0-rc.7", "@taiga-ui/layout": "4.0.0-rc.7", diff --git a/web/projects/marketplace/package.json b/web/projects/marketplace/package.json index 49c10f3b0..969f5c56a 100644 --- a/web/projects/marketplace/package.json +++ b/web/projects/marketplace/package.json @@ -6,6 +6,8 @@ "@angular/core": ">=13.2.0", "@start9labs/shared": ">=0.3.2", "@taiga-ui/cdk": "4.0.0-rc.6", + "@taiga-ui/core": "4.0.0-rc.6", + "@taiga-ui/layout": "4.0.0-rc.6", "@tinkoff/ng-dompurify": ">=4.0.0", "fuse.js": "^6.4.6" }, diff --git a/web/projects/marketplace/src/modals/release-notes.component.ts b/web/projects/marketplace/src/modals/release-notes.component.ts new file mode 100644 index 000000000..51d0190a5 --- /dev/null +++ b/web/projects/marketplace/src/modals/release-notes.component.ts @@ -0,0 +1,90 @@ +import { CommonModule } from '@angular/common' +import { + ChangeDetectionStrategy, + Component, + inject, + Input, +} from '@angular/core' +import { Exver, MarkdownPipeModule } from '@start9labs/shared' +import { TuiButton, TuiLoader } from '@taiga-ui/core' +import { TuiCardLarge } from '@taiga-ui/layout' +import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' +import { map } from 'rxjs' +import { AbstractMarketplaceService } from '../services/marketplace.service' +import { MarketplacePkg } from '../types' + +@Component({ + standalone: true, + template: ` + @if (notes$ | async; as notes) { + @for (note of notes | keyvalue: asIsOrder; track $index) { + +
+ } + } @else { + + } + `, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + CommonModule, + TuiButton, + TuiLoader, + TuiCardLarge, + MarkdownPipeModule, + ], +}) +export class ReleaseNotesComponent { + @Input() pkg!: MarketplacePkg + + private selected: string | null = null + private readonly exver = inject(Exver) + + readonly notes$ = inject(AbstractMarketplaceService) + .getSelectedStore$() + .pipe( + map(s => { + return Object.entries(this.pkg.otherVersions) + .filter( + ([v, _]) => + this.exver.getFlavor(v) === this.pkg.flavor && + this.exver.compareExver(this.pkg.version, v) === 1, + ) + .reduce( + (obj, [version, info]) => ({ + ...obj, + [version]: info.releaseNotes, + }), + { + [`${this.pkg.version} (current)`]: this.pkg.releaseNotes, + }, + ) + }), + ) + + isSelected(key: string): boolean { + return this.selected === key + } + + setSelected(selected: string) { + this.selected = this.isSelected(selected) ? null : selected + } + + getDocSize(key: string, { scrollHeight }: HTMLElement) { + return this.isSelected(key) ? scrollHeight : 0 + } + + asIsOrder(a: any, b: any) { + return 0 + } +} + +export const RELEASE_NOTES = new PolymorpheusComponent(ReleaseNotesComponent) diff --git a/web/projects/marketplace/src/modals/release-notes/release-notes.component.html b/web/projects/marketplace/src/modals/release-notes/release-notes.component.html deleted file mode 100644 index 8a10daeb2..000000000 --- a/web/projects/marketplace/src/modals/release-notes/release-notes.component.html +++ /dev/null @@ -1,51 +0,0 @@ -
-
-

What's new

-
-
-

Version {{ pkg.manifest.version }}

-

-
- -
-
-
- - - - - - {{ note.key | displayEmver }} - -

-
-
-
-
- - - - -
diff --git a/web/projects/marketplace/src/modals/release-notes/release-notes.component.scss b/web/projects/marketplace/src/modals/release-notes/release-notes.component.scss deleted file mode 100644 index f4f37b099..000000000 --- a/web/projects/marketplace/src/modals/release-notes/release-notes.component.scss +++ /dev/null @@ -1,44 +0,0 @@ -.box-container { - background-color: rgb(39 39 42); - border-radius: 0.75rem; - padding: 1.75rem; - display: grid; - grid-auto-flow: row; - align-items: center; - - &-details { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - - &-version { - line-height: 1.5rem; - font-size: 1rem; - - h3 { - font-size: 1.2rem; - margin-bottom: 1.25rem; - pointer-events: none; - } - } - - &-notes { - flex-wrap: wrap; - margin-top: 0.25rem; - pointer-events: none; - } - - button { - margin-top: 0.75rem; - place-self: end; - - @media (min-width: 768px) { - place-self: start; - } - - @media (min-width: 1024px) { - place-self: end; - } - } - } -} diff --git a/web/projects/marketplace/src/modals/release-notes/release-notes.component.ts b/web/projects/marketplace/src/modals/release-notes/release-notes.component.ts deleted file mode 100644 index 7735c7828..000000000 --- a/web/projects/marketplace/src/modals/release-notes/release-notes.component.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - ElementRef, - Input, -} from '@angular/core' -import { AbstractMarketplaceService } from '../../services/marketplace.service' -import { MarketplacePkg } from '../../types' -import { map } from 'rxjs' -import { Exver } from '@start9labs/shared' -// @TODO Alex use Taiga modal -import { ModalController } from '@ionic/angular' - -@Component({ - selector: 'release-notes', - templateUrl: './release-notes.component.html', - styleUrls: ['./release-notes.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class ReleaseNotesComponent { - @Input() pkg!: MarketplacePkg - - private selected: string | null = null - - readonly notes$ = this.marketplaceService.getSelectedStore$().pipe( - map(s => { - return Object.entries(this.pkg.otherVersions) - .filter( - ([v, _]) => - this.exver.getFlavor(v) === this.pkg.flavor && - this.exver.compareExver(this.pkg.version, v) === 1, - ) - .reduce( - (obj, [version, info]) => ({ - ...obj, - [version]: info.releaseNotes, - }), - { - [`${this.pkg.version} (current)`]: this.pkg.releaseNotes, - }, - ) - }), - ) - - constructor( - private readonly marketplaceService: AbstractMarketplaceService, - private readonly exver: Exver, - private readonly modalCtrl: ModalController, - ) {} - - async dismiss() { - return this.modalCtrl.dismiss() - } - - isSelected(key: string): boolean { - return this.selected === key - } - - setSelected(selected: string) { - this.selected = this.isSelected(selected) ? null : selected - } - - getDocSize(key: string, { nativeElement }: ElementRef) { - return this.isSelected(key) ? nativeElement.scrollHeight : 0 - } - - asIsOrder(a: any, b: any) { - return 0 - } -} diff --git a/web/projects/marketplace/src/modals/release-notes/release-notes.module.ts b/web/projects/marketplace/src/modals/release-notes/release-notes.module.ts deleted file mode 100644 index 78960211b..000000000 --- a/web/projects/marketplace/src/modals/release-notes/release-notes.module.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { TuiAccordion } from '@taiga-ui/kit' -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { - ExverPipesModule, - MarkdownPipeModule, - SafeLinksDirective, -} from '@start9labs/shared' -import { TuiLoader, TuiButton } from '@taiga-ui/core' -import { NgDompurifyModule } from '@tinkoff/ng-dompurify' -import { - FilterVersionsPipe, - ReleaseNotesComponent, -} from './release-notes.component' -import { ReleaseNotesComponent } from './release-notes.component' - -@NgModule({ - imports: [ - CommonModule, - ExverPipesModule, - MarkdownPipeModule, - NgDompurifyModule, - SafeLinksDirective, - TuiButton, - ...TuiAccordion, - TuiLoader, - FilterVersionsPipe, - ], - declarations: [ReleaseNotesComponent], - exports: [ReleaseNotesComponent], -}) -export class ReleaseNotesComponentModule {} diff --git a/web/projects/marketplace/src/pages/list/categories/categories.component.html b/web/projects/marketplace/src/pages/list/categories/categories.component.html index 5a7fea469..094238da8 100644 --- a/web/projects/marketplace/src/pages/list/categories/categories.component.html +++ b/web/projects/marketplace/src/pages/list/categories/categories.component.html @@ -1,14 +1,14 @@ diff --git a/web/projects/marketplace/src/pages/list/categories/categories.component.ts b/web/projects/marketplace/src/pages/list/categories/categories.component.ts index e0425e0f8..cd02b702e 100644 --- a/web/projects/marketplace/src/pages/list/categories/categories.component.ts +++ b/web/projects/marketplace/src/pages/list/categories/categories.component.ts @@ -15,7 +15,7 @@ import { T } from '@start9labs/start-sdk' }) export class CategoriesComponent { @Input() - categories!: Map + categories?: Record @Input() category = '' @@ -23,6 +23,14 @@ export class CategoriesComponent { @Output() readonly categoryChange = new EventEmitter() + readonly fallback: Record = { + a: { name: 'a', description: { short: 'a', long: 'a' } }, + b: { name: 'a', description: { short: 'a', long: 'a' } }, + c: { name: 'a', description: { short: 'a', long: 'a' } }, + d: { name: 'a', description: { short: 'a', long: 'a' } }, + e: { name: 'a', description: { short: 'a', long: 'a' } }, + } + switchCategory(category: string): void { this.category = category this.categoryChange.emit(category) diff --git a/web/projects/marketplace/src/pages/list/item/item.component.ts b/web/projects/marketplace/src/pages/list/item/item.component.ts index b608417ab..a3e439dcd 100644 --- a/web/projects/marketplace/src/pages/list/item/item.component.ts +++ b/web/projects/marketplace/src/pages/list/item/item.component.ts @@ -25,7 +25,7 @@ export class ItemComponent { const iconUrl = new URL(this.pkg.icon) return iconUrl.href } catch (e) { - return `${marketplace?.url}package/v0/icon/${this.pkg.manifest.id}` + return `${marketplace?.url}package/v0/icon/${this.pkg.id}` } } } diff --git a/web/projects/marketplace/src/pages/show/about/about.component.html b/web/projects/marketplace/src/pages/show/about/about.component.html index 515d04637..d26bbd709 100644 --- a/web/projects/marketplace/src/pages/show/about/about.component.html +++ b/web/projects/marketplace/src/pages/show/about/about.component.html @@ -1,8 +1,23 @@
+
+

New in {{ pkg.version }}

+

+ +

About

-

- {{ pkg.manifest.description.long }} -

+

{{ pkg.description.long }}

+ + View website +
diff --git a/web/projects/marketplace/src/pages/show/about/about.component.ts b/web/projects/marketplace/src/pages/show/about/about.component.ts index f53184788..1c73538b1 100644 --- a/web/projects/marketplace/src/pages/show/about/about.component.ts +++ b/web/projects/marketplace/src/pages/show/about/about.component.ts @@ -1,7 +1,12 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { + ChangeDetectionStrategy, + Component, + inject, + Input, +} from '@angular/core' +import { TuiDialogService } from '@taiga-ui/core' +import { RELEASE_NOTES } from '../../../modals/release-notes.component' import { MarketplacePkg } from '../../../types' -import { ModalController } from '@ionic/angular' -import { ReleaseNotesComponent } from '../../../modals/release-notes/release-notes.component' @Component({ selector: 'marketplace-about', @@ -10,17 +15,14 @@ import { ReleaseNotesComponent } from '../../../modals/release-notes/release-not changeDetection: ChangeDetectionStrategy.OnPush, }) export class AboutComponent { + private readonly dialogs = inject(TuiDialogService) + @Input({ required: true }) pkg!: MarketplacePkg - constructor(private readonly modalCtrl: ModalController) {} - - async presentModalNotes() { - const modal = await this.modalCtrl.create({ - componentProps: { pkg: this.pkg }, - component: ReleaseNotesComponent, - }) - - await modal.present() + async onPast() { + this.dialogs + .open(RELEASE_NOTES, { label: 'Past Release Notes' }) + .subscribe() } } diff --git a/web/projects/marketplace/src/pages/show/about/about.module.ts b/web/projects/marketplace/src/pages/show/about/about.module.ts index 562b29ae4..93781313f 100644 --- a/web/projects/marketplace/src/pages/show/about/about.module.ts +++ b/web/projects/marketplace/src/pages/show/about/about.module.ts @@ -1,10 +1,11 @@ +import { TuiButton } from '@taiga-ui/core' import { TuiTagModule } from '@taiga-ui/legacy' import { CommonModule } from '@angular/common' import { NgModule } from '@angular/core' import { RouterModule } from '@angular/router' import { AboutComponent } from './about.component' import { NgDompurifyModule } from '@tinkoff/ng-dompurify' -import { SafeLinksDirective } from '@start9labs/shared' +import { MarkdownPipeModule, SafeLinksDirective } from '@start9labs/shared' @NgModule({ imports: [ @@ -13,6 +14,8 @@ import { SafeLinksDirective } from '@start9labs/shared' TuiTagModule, NgDompurifyModule, SafeLinksDirective, + MarkdownPipeModule, + TuiButton, ], declarations: [AboutComponent], exports: [AboutComponent], diff --git a/web/projects/marketplace/src/pages/show/additional/additional.component.html b/web/projects/marketplace/src/pages/show/additional/additional.component.html index 7fbdc56af..389ed2586 100644 --- a/web/projects/marketplace/src/pages/show/additional/additional.component.html +++ b/web/projects/marketplace/src/pages/show/additional/additional.component.html @@ -4,11 +4,11 @@
+ /> + />
diff --git a/web/projects/marketplace/src/pages/show/additional/additional.component.ts b/web/projects/marketplace/src/pages/show/additional/additional.component.ts index 8ad54f988..5efaf5b30 100644 --- a/web/projects/marketplace/src/pages/show/additional/additional.component.ts +++ b/web/projects/marketplace/src/pages/show/additional/additional.component.ts @@ -7,7 +7,7 @@ import { import { ActivatedRoute } from '@angular/router' import { TuiDialogService } from '@taiga-ui/core' import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' -import { CopyService, Exver, MarkdownComponent } from '@start9labs/shared' +import { CopyService, MarkdownComponent } from '@start9labs/shared' import { MarketplacePkg } from '../../../types' import { AbstractMarketplaceService } from '../../../services/marketplace.service' @@ -38,9 +38,8 @@ export class AdditionalComponent { size: 'l', data: { content: this.marketplaceService.fetchStatic$( - this.pkg.id, - label.toLowerCase(), - this.url, + this.pkg, + label === 'License' ? 'LICENSE.md' : 'instructions.md', ), }, }) diff --git a/web/projects/marketplace/src/pages/show/dependencies/dependencies.component.ts b/web/projects/marketplace/src/pages/show/dependencies/dependencies.component.ts index 523e49489..8f1b6b880 100644 --- a/web/projects/marketplace/src/pages/show/dependencies/dependencies.component.ts +++ b/web/projects/marketplace/src/pages/show/dependencies/dependencies.component.ts @@ -1,14 +1,13 @@ +import { CommonModule } from '@angular/common' import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, - inject, } from '@angular/core' -import { CommonModule } from '@angular/common' -import { MarketplaceDepItemComponent } from './dependency-item.component' import { MarketplacePkg } from '../../../types' +import { MarketplaceDepItemComponent } from './dependency-item.component' @Component({ selector: 'marketplace-dependencies', @@ -17,7 +16,7 @@ import { MarketplacePkg } from '../../../types'

Dependencies

- @for (dep of pkg.manifest.dependencies | keyvalue; track $index) { + @for (dep of pkg.dependencyMetadata | keyvalue; track $index) { + dep!: KeyValue private readonly marketplaceService = inject(AbstractMarketplaceService) readonly marketplace$ = this.marketplaceService.getSelectedHost$() diff --git a/web/projects/marketplace/src/pages/show/flavors/flavors.component.html b/web/projects/marketplace/src/pages/show/flavors/flavors.component.html deleted file mode 100644 index 7a0f64aff..000000000 --- a/web/projects/marketplace/src/pages/show/flavors/flavors.component.html +++ /dev/null @@ -1,21 +0,0 @@ -Alternative Implementations - - - - - - - - -

- {{ pkg.title }} -

-

{{ pkg.version }}

-
-
-
-
-
diff --git a/web/projects/marketplace/src/pages/show/flavors/flavors.component.ts b/web/projects/marketplace/src/pages/show/flavors/flavors.component.ts index 4927f47a1..bc9c0df6f 100644 --- a/web/projects/marketplace/src/pages/show/flavors/flavors.component.ts +++ b/web/projects/marketplace/src/pages/show/flavors/flavors.component.ts @@ -1,10 +1,31 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' +import { RouterLink } from '@angular/router' +import { SharedPipesModule } from '@start9labs/shared' +import { TuiTitle } from '@taiga-ui/core' +import { TuiCell } from '@taiga-ui/layout' import { MarketplacePkg } from '../../../types' @Component({ + standalone: true, selector: 'marketplace-flavors', - templateUrl: 'flavors.component.html', + template: ` +

Alternative Implementations

+ @for (pkg of pkgs; track $index) { + + + + {{ pkg.title }} + {{ pkg.version }} + + + } + `, changeDetection: ChangeDetectionStrategy.OnPush, + imports: [RouterLink, TuiCell, TuiTitle, SharedPipesModule], }) export class FlavorsComponent { @Input() diff --git a/web/projects/marketplace/src/pages/show/flavors/flavors.module.ts b/web/projects/marketplace/src/pages/show/flavors/flavors.module.ts deleted file mode 100644 index 662a914fd..000000000 --- a/web/projects/marketplace/src/pages/show/flavors/flavors.module.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { CommonModule } from '@angular/common' -import { NgModule } from '@angular/core' -import { RouterModule } from '@angular/router' -import { IonicModule } from '@ionic/angular' -import { ResponsiveColModule, SharedPipesModule } from '@start9labs/shared' -import { FlavorsComponent } from './flavors.component' - -@NgModule({ - imports: [ - CommonModule, - RouterModule, - IonicModule, - SharedPipesModule, - ResponsiveColModule, - ], - declarations: [FlavorsComponent], - exports: [FlavorsComponent], -}) -export class FlavorsModule {} diff --git a/web/projects/marketplace/src/pages/show/hero/hero.component.ts b/web/projects/marketplace/src/pages/show/hero/hero.component.ts index 16167e181..04af1a98c 100644 --- a/web/projects/marketplace/src/pages/show/hero/hero.component.ts +++ b/web/projects/marketplace/src/pages/show/hero/hero.component.ts @@ -18,26 +18,26 @@ import { MarketplacePkg, StoreIdentity } from '../../../types' {{ pkg.manifest.title }} Icon
{{ pkg.manifest.title }} background image

- {{ pkg.manifest.title }} + {{ pkg.title }}

- {{ pkg.manifest.version }} + {{ pkg.version }}

- {{ pkg.manifest.description.short }} + {{ pkg.description.short }}

@@ -175,7 +175,7 @@ export class MarketplacePackageHeroComponent { const iconUrl = new URL(this.pkg.icon) return iconUrl.href } catch (e) { - return `${marketplace?.url}package/v0/icon/${this.pkg.manifest.id}` + return `${marketplace?.url}package/v0/icon/${this.pkg.id}` } } } diff --git a/web/projects/marketplace/src/pages/show/screenshots/screenshots.component.ts b/web/projects/marketplace/src/pages/show/screenshots/screenshots.component.ts index 8d5827637..b40c967e6 100644 --- a/web/projects/marketplace/src/pages/show/screenshots/screenshots.component.ts +++ b/web/projects/marketplace/src/pages/show/screenshots/screenshots.component.ts @@ -14,11 +14,16 @@ import { PolymorpheusContent } from '@taiga-ui/polymorpheus' @Component({ selector: 'marketplace-package-screenshots', template: ` -
+ +
diff --git a/web/projects/marketplace/src/public-api.ts b/web/projects/marketplace/src/public-api.ts index b935bc495..17d76eee3 100644 --- a/web/projects/marketplace/src/public-api.ts +++ b/web/projects/marketplace/src/public-api.ts @@ -19,7 +19,6 @@ export * from './pages/show/dependencies/dependency-item.component' export * from './pages/show/screenshots/screenshots.component' export * from './pages/show/hero/hero.component' export * from './pages/show/flavors/flavors.component' -export * from './pages/show/flavors/flavors.module' export * from './pipes/filter-packages.pipe' diff --git a/web/projects/marketplace/src/services/marketplace.service.ts b/web/projects/marketplace/src/services/marketplace.service.ts index 5d296b6a1..fb5225c93 100644 --- a/web/projects/marketplace/src/services/marketplace.service.ts +++ b/web/projects/marketplace/src/services/marketplace.service.ts @@ -1,11 +1,5 @@ import { Observable } from 'rxjs' -import { - Marketplace, - MarketplacePkg, - StoreData, - StoreIdentity, - StoreIdentityWithData, -} from '../types' +import { Marketplace, MarketplacePkg, StoreData, StoreIdentity } from '../types' export abstract class AbstractMarketplaceService { abstract getKnownHosts$(): Observable @@ -16,7 +10,9 @@ export abstract class AbstractMarketplaceService { abstract getSelectedStore$(): Observable - abstract getSelectedStoreWithCategories$(): Observable + abstract getSelectedStoreWithCategories$(): Observable< + StoreIdentity & StoreData + > abstract getPackage$( id: string, diff --git a/web/projects/setup-wizard/src/app/components/password.directive.ts b/web/projects/setup-wizard/src/app/components/password.directive.ts new file mode 100644 index 000000000..70d9c7d0e --- /dev/null +++ b/web/projects/setup-wizard/src/app/components/password.directive.ts @@ -0,0 +1,27 @@ +import { Directive, ElementRef, inject, input, Output } from '@angular/core' +import { StartOSDiskInfo } from '@start9labs/shared' +import { TuiDialogService } from '@taiga-ui/core' +import { filter, fromEvent, switchMap } from 'rxjs' +import { PASSWORD } from 'src/app/components/password.component' + +@Directive({ + standalone: true, + selector: 'button[server][password]', +}) +export class PasswordDirective { + private readonly dialogs = inject(TuiDialogService) + + readonly server = input.required() + + @Output() + readonly password = fromEvent(inject(ElementRef).nativeElement, 'click').pipe( + switchMap(() => + this.dialogs.open(PASSWORD, { + label: 'Unlock Drive', + size: 's', + data: { passwordHash: this.server().passwordHash }, + }), + ), + filter(Boolean), + ) +} diff --git a/web/projects/setup-wizard/src/app/components/servers.component.ts b/web/projects/setup-wizard/src/app/components/servers.component.ts index b8b98ca02..dd056bfd3 100644 --- a/web/projects/setup-wizard/src/app/components/servers.component.ts +++ b/web/projects/setup-wizard/src/app/components/servers.component.ts @@ -1,10 +1,11 @@ import { Component, inject } from '@angular/core' +import { ServerComponent } from '@start9labs/shared' import { TuiDialogContext } from '@taiga-ui/core' import { POLYMORPHEUS_CONTEXT, PolymorpheusComponent, } from '@taiga-ui/polymorpheus' -import { ServerComponent } from 'src/app/components/server.component' +import { PasswordDirective } from 'src/app/components/password.directive' import { StartOSDiskInfoWithId } from 'src/app/services/api.service' interface Data { @@ -23,7 +24,7 @@ export interface ServersResponse { } `, - imports: [ServerComponent], + imports: [ServerComponent, PasswordDirective], }) export class ServersComponent { readonly context = diff --git a/web/projects/setup-wizard/src/app/pages/loading.page.ts b/web/projects/setup-wizard/src/app/pages/loading.page.ts index c367968d5..c5bc0ceae 100644 --- a/web/projects/setup-wizard/src/app/pages/loading.page.ts +++ b/web/projects/setup-wizard/src/app/pages/loading.page.ts @@ -1,7 +1,11 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { toSignal } from '@angular/core/rxjs-interop' import { Router } from '@angular/router' -import { ErrorService, InitializingComponent } from '@start9labs/shared' +import { + ErrorService, + formatProgress, + InitializingComponent, +} from '@start9labs/shared' import { T } from '@start9labs/start-sdk' import { catchError, @@ -40,9 +44,8 @@ export default class LoadingPage { startWith(progress), catchError((_, watch$) => interval(2000).pipe( - switchMap(() => - from(this.api.getStatus()).pipe(catchError(() => EMPTY)), - ), + switchMap(() => from(this.api.getStatus())), + catchError(() => EMPTY), take(1), switchMap(() => watch$), ), @@ -54,13 +57,7 @@ export default class LoadingPage { }), ), ), - map(({ phases, overall }) => ({ - total: getDecimal(overall), - message: phases - .filter(p => p.progress !== true && p.progress !== null) - .map(p => `${p.name}${getPhaseBytes(p.progress)}`) - .join(','), - })), + map(formatProgress), catchError(e => { this.errorService.handleError(e) return EMPTY @@ -87,19 +84,3 @@ export default class LoadingPage { } } } - -function getDecimal(progress: T.Progress): number { - if (progress === true) { - return 1 - } else if (!progress || !progress.total) { - return 0 - } else { - return progress.total && progress.done / progress.total - } -} - -function getPhaseBytes(progress: T.Progress): string { - return progress === true || !progress - ? '' - : `: (${progress.done}/${progress.total})` -} diff --git a/web/projects/setup-wizard/src/app/pages/recover.page.ts b/web/projects/setup-wizard/src/app/pages/recover.page.ts index 480e7269b..95d0878a4 100644 --- a/web/projects/setup-wizard/src/app/pages/recover.page.ts +++ b/web/projects/setup-wizard/src/app/pages/recover.page.ts @@ -1,7 +1,7 @@ import { DatePipe } from '@angular/common' import { Component, inject } from '@angular/core' import { Router } from '@angular/router' -import { ErrorService } from '@start9labs/shared' +import { ErrorService, ServerComponent } from '@start9labs/shared' import { TuiButton, TuiDialogService, @@ -11,7 +11,7 @@ import { } from '@taiga-ui/core' import { TuiCardLarge, TuiCell } from '@taiga-ui/layout' import { CIFS, CifsResponse } from 'src/app/components/cifs.component' -import { ServerComponent } from 'src/app/components/server.component' +import { PasswordDirective } from 'src/app/components/password.directive' import { ApiService, StartOSDiskInfoFull } from 'src/app/services/api.service' import { StateService } from 'src/app/services/state.service' @@ -66,6 +66,7 @@ import { StateService } from 'src/app/services/state.service' TuiTitle, DatePipe, ServerComponent, + PasswordDirective, ], }) export default class RecoverPage { diff --git a/web/projects/shared/package.json b/web/projects/shared/package.json index 5fe3dba47..0a8f52d9a 100644 --- a/web/projects/shared/package.json +++ b/web/projects/shared/package.json @@ -7,10 +7,9 @@ "@angular/router": "^17.0.6", "@ng-web-apis/mutation-observer": ">=4.0.0", "@ng-web-apis/resize-observer": ">=4.0.0", - "@start9labs/emver": "^0.1.5", - "@taiga-ui/cdk": "4.0.0-rc.6", - "@taiga-ui/core": "4.0.0-rc.6", - "@taiga-ui/experimental": "4.0.0-rc.6", + "@taiga-ui/cdk": "4.0.0-rc.7", + "@taiga-ui/core": "4.0.0-rc.7", + "@taiga-ui/layout": "4.0.0-rc.7", "@tinkoff/ng-dompurify": ">=4.0.0", "ansi-to-html": "^0.7.2" }, diff --git a/web/projects/setup-wizard/src/app/components/server.component.ts b/web/projects/shared/src/components/server.component.ts similarity index 60% rename from web/projects/setup-wizard/src/app/components/server.component.ts rename to web/projects/shared/src/components/server.component.ts index 2a5440232..d42a67941 100644 --- a/web/projects/setup-wizard/src/app/components/server.component.ts +++ b/web/projects/shared/src/components/server.component.ts @@ -1,10 +1,8 @@ import { DatePipe } from '@angular/common' -import { Component, ElementRef, inject, input, Output } from '@angular/core' -import { StartOSDiskInfo } from '@start9labs/shared' +import { Component, inject, input } from '@angular/core' import { TuiDialogService, TuiIcon, TuiTitle } from '@taiga-ui/core' import { TuiCell } from '@taiga-ui/layout' -import { filter, fromEvent, switchMap } from 'rxjs' -import { PASSWORD } from 'src/app/components/password.component' +import { StartOSDiskInfo } from '../types/api' @Component({ standalone: true, @@ -31,16 +29,4 @@ export class ServerComponent { private readonly dialogs = inject(TuiDialogService) readonly server = input.required() - - @Output() - readonly password = fromEvent(inject(ElementRef).nativeElement, 'click').pipe( - switchMap(() => - this.dialogs.open(PASSWORD, { - label: 'Unlock Drive', - size: 's', - data: { passwordHash: this.server().passwordHash }, - }), - ), - filter(Boolean), - ) } diff --git a/web/projects/shared/src/public-api.ts b/web/projects/shared/src/public-api.ts index 4728e2b9a..e2ab9bff1 100644 --- a/web/projects/shared/src/public-api.ts +++ b/web/projects/shared/src/public-api.ts @@ -15,6 +15,7 @@ export * from './components/markdown/markdown.component.module' export * from './components/ticker/ticker.component' export * from './components/ticker/ticker.module' export * from './components/drive.component' +export * from './components/server.component' export * from './directives/drag-scroller.directive' export * from './directives/safe-links.directive' @@ -50,6 +51,7 @@ export * from './tokens/theme' export * from './util/base-64' export * from './util/convert-ansi' export * from './util/copy-to-clipboard' +export * from './util/format-progress' export * from './util/get-new-entries' export * from './util/get-pkg-id' export * from './util/invert' diff --git a/web/projects/shared/src/util/format-progress.ts b/web/projects/shared/src/util/format-progress.ts new file mode 100644 index 000000000..8007657f8 --- /dev/null +++ b/web/projects/shared/src/util/format-progress.ts @@ -0,0 +1,33 @@ +// @TODO Matt this is T.FullProgress but shared does not depend on sdk +type Progress = null | boolean | { done: number; total: number | null } +type NamedProgress = { name: string; progress: Progress } +type FullProgress = { overall: Progress; phases: Array } + +export function formatProgress({ phases, overall }: FullProgress): { + total: number + message: string +} { + return { + total: getDecimal(overall), + message: phases + .filter(p => p.progress !== true && p.progress !== null) + .map(p => `${p.name}${getPhaseBytes(p.progress)}`) + .join(', '), + } +} + +function getDecimal(progress: Progress): number { + if (progress === true) { + return 1 + } else if (!progress || !progress.total) { + return 0 + } else { + return progress.total && progress.done / progress.total + } +} + +function getPhaseBytes(progress: Progress): string { + return progress === true || !progress + ? '' + : `: (${progress.done}/${progress.total})` +} diff --git a/web/projects/ui/src/app/app.component.ts b/web/projects/ui/src/app/app.component.ts index 6b7624d2d..522f3c497 100644 --- a/web/projects/ui/src/app/app.component.ts +++ b/web/projects/ui/src/app/app.component.ts @@ -17,7 +17,7 @@ import { PatchMonitorService } from './services/patch-monitor.service' }) export class AppComponent implements OnInit { private readonly title = inject(Title) - private readonly patch = inject(PatchDB) + private readonly patch = inject>(PatchDB) readonly auth = inject(AuthService) readonly theme$ = inject(THEME) @@ -29,7 +29,7 @@ export class AppComponent implements OnInit { .subscribe() readonly offline$ = combineLatest([ - inject(ConnectionService).connected$, + inject(ConnectionService), this.auth.isVerified$, this.patch .watch$('serverInfo', 'statusInfo') diff --git a/web/projects/ui/src/app/app.providers.ts b/web/projects/ui/src/app/app.providers.ts index 6b97cdec9..6389eb76d 100644 --- a/web/projects/ui/src/app/app.providers.ts +++ b/web/projects/ui/src/app/app.providers.ts @@ -9,6 +9,7 @@ import { import { RELATIVE_URL, THEME, WorkspaceConfig } from '@start9labs/shared' import { TUI_DATE_FORMAT, + TUI_DIALOGS_CLOSE, tuiButtonOptionsProvider, tuiDropdownOptionsProvider, tuiNumberFormatProvider, @@ -19,7 +20,13 @@ import { TUI_DATE_VALUE_TRANSFORMER, } from '@taiga-ui/kit' import { tuiTextfieldOptionsProvider } from '@taiga-ui/legacy' -import { PATCH_DB_PROVIDERS } from 'src/app/services/patch-db/patch-db.providers' +import { PatchDB } from 'patch-db-client' +import { filter, pairwise } from 'rxjs' +import { + PATCH_CACHE, + PatchDbSource, +} from 'src/app/services/patch-db/patch-db-source' +import { StateService } from 'src/app/services/state.service' import { ApiService } from './services/api/embassy-api.service' import { LiveApiService } from './services/api/embassy-live-api.service' import { MockApiService } from './services/api/embassy-mock-api.service' @@ -29,8 +36,8 @@ import { ClientStorageService } from './services/client-storage.service' import { DateTransformerService } from './services/date-transformer.service' import { DatetimeTransformerService } from './services/datetime-transformer.service' import { MarketplaceService } from './services/marketplace.service' -import { ThemeSwitcherService } from './services/theme-switcher.service' import { StorageService } from './services/storage.service' +import { ThemeSwitcherService } from './services/theme-switcher.service' const { useMocks, @@ -38,7 +45,6 @@ const { } = require('../../../../config.json') as WorkspaceConfig export const APP_PROVIDERS: Provider[] = [ - PATCH_DB_PROVIDERS, NG_EVENT_PLUGINS, FilterPackagesPipe, UntypedFormBuilder, diff --git a/web/projects/ui/src/app/components/form.component.ts b/web/projects/ui/src/app/components/form.component.ts deleted file mode 100644 index 830e8de1e..000000000 --- a/web/projects/ui/src/app/components/form.component.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { CommonModule } from '@angular/common' -import { - ChangeDetectionStrategy, - Component, - inject, - Input, - OnInit, -} from '@angular/core' -import { FormGroup, ReactiveFormsModule } from '@angular/forms' -import { RouterModule } from '@angular/router' -import { CT } from '@start9labs/start-sdk' - -import { - tuiMarkControlAsTouchedAndValidate, - TuiValueChangesModule, -} from '@taiga-ui/cdk' -import { TuiDialogContext, TuiModeModule } from '@taiga-ui/core' -import { TuiButtonModule } from '@taiga-ui/experimental' -import { TuiDialogFormService } from '@taiga-ui/kit' -import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus' -import { compare, Operation } from 'fast-json-patch' -import { FormModule } from 'src/app/components/form/form.module' -import { InvalidService } from 'src/app/components/form/invalid.service' -import { FormService } from 'src/app/services/form.service' - -export interface ActionButton { - text: string - handler?: (value: T) => Promise | void - link?: string -} - -export interface FormContext { - spec: CT.InputSpec - buttons: ActionButton[] - value?: T - patch?: Operation[] -} - -@Component({ - standalone: true, - selector: 'app-form', - template: ` -
- - -
- `, - styles: [ - ` - footer { - position: sticky; - bottom: 0; - z-index: 10; - display: flex; - justify-content: flex-end; - padding: 1rem 0; - margin: 1rem 0 -1rem; - gap: 1rem; - background: var(--tui-elevation-01); - border-top: 1px solid var(--tui-base-02); - } - `, - ], - imports: [ - CommonModule, - ReactiveFormsModule, - RouterModule, - TuiValueChangesModule, - TuiButtonModule, - TuiModeModule, - FormModule, - ], - providers: [InvalidService], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class FormComponent> implements OnInit { - private readonly dialogFormService = inject(TuiDialogFormService) - private readonly formService = inject(FormService) - private readonly invalidService = inject(InvalidService) - private readonly context = inject>>( - POLYMORPHEUS_CONTEXT, - { optional: true }, - ) - - @Input() spec = this.context?.data.spec || {} - @Input() buttons = this.context?.data.buttons || [] - @Input() patch = this.context?.data.patch || [] - @Input() value?: T = this.context?.data.value - - form = new FormGroup({}) - - ngOnInit() { - this.dialogFormService.markAsPristine() - this.form = this.formService.createForm(this.spec, this.value) - this.process(this.patch) - } - - onReset() { - const { value } = this.form - - this.form = this.formService.createForm(this.spec) - this.process(compare(this.form.value, value)) - tuiMarkControlAsTouchedAndValidate(this.form) - this.markAsDirty() - } - - async onClick(handler: Required>['handler']) { - tuiMarkControlAsTouchedAndValidate(this.form) - this.invalidService.scrollIntoView() - - if (this.form.valid && (await handler(this.form.value as T))) { - this.close() - } - } - - markAsDirty() { - this.dialogFormService.markAsDirty() - } - - close() { - this.context?.$implicit.complete() - } - - private process(patch: Operation[]) { - patch.forEach(({ op, path }) => { - const control = this.form.get(path.substring(1).split('/')) - - if (!control || !control.parent) return - - if (op !== 'remove') { - control.markAsDirty() - control.markAsTouched() - } - - control.parent.markAsDirty() - control.parent.markAsTouched() - }) - } -} diff --git a/web/projects/ui/src/app/components/form/control.directive.ts b/web/projects/ui/src/app/components/form/control.directive.ts deleted file mode 100644 index 327eb8255..000000000 --- a/web/projects/ui/src/app/components/form/control.directive.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Directive, ElementRef, inject, OnDestroy, OnInit } from '@angular/core' -import { ControlContainer, NgControl } from '@angular/forms' -import { InvalidService } from './invalid.service' - -@Directive({ - selector: 'form-control, form-array, form-object', -}) -export class ControlDirective implements OnInit, OnDestroy { - private readonly invalidService = inject(InvalidService, { optional: true }) - private readonly element: ElementRef = inject(ElementRef) - private readonly control = - inject(NgControl, { optional: true }) || - inject(ControlContainer, { optional: true }) - - get invalid(): boolean { - return !!this.control?.invalid - } - - scrollIntoView() { - this.element.nativeElement.scrollIntoView({ behavior: 'smooth' }) - } - - ngOnInit() { - this.invalidService?.add(this) - } - - ngOnDestroy() { - this.invalidService?.remove(this) - } -} diff --git a/web/projects/ui/src/app/components/form/control.ts b/web/projects/ui/src/app/components/form/control.ts deleted file mode 100644 index 476826194..000000000 --- a/web/projects/ui/src/app/components/form/control.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { inject } from '@angular/core' -import { FormControlComponent } from './form-control/form-control.component' -import { CT } from '@start9labs/start-sdk' - -export abstract class Control { - private readonly control: FormControlComponent = - inject(FormControlComponent) - - get invalid(): boolean { - return this.control.touched && this.control.invalid - } - - get spec(): Spec { - return this.control.spec - } - - // TODO: Properly handle already set immutable value - get readOnly(): boolean { - return ( - !!this.value && !!this.control.control?.pristine && this.control.immutable - ) - } - - get value(): Value | null { - return this.control.value - } - - set value(value: Value | null) { - this.control.onInput(value) - } - - onFocus(focused: boolean) { - this.control.onFocus(focused) - } -} diff --git a/web/projects/ui/src/app/components/form/form-array/form-array.component.html b/web/projects/ui/src/app/components/form/form-array/form-array.component.html deleted file mode 100644 index d66387fb5..000000000 --- a/web/projects/ui/src/app/components/form/form-array/form-array.component.html +++ /dev/null @@ -1,58 +0,0 @@ -
- {{ spec.name }} - - -
- - - - - {{ item.value | mustache : $any(spec.spec).displayAs }} - - - - - - - - - diff --git a/web/projects/ui/src/app/components/form/form-array/form-array.component.scss b/web/projects/ui/src/app/components/form/form-array/form-array.component.scss deleted file mode 100644 index 9b6415ff7..000000000 --- a/web/projects/ui/src/app/components/form/form-array/form-array.component.scss +++ /dev/null @@ -1,50 +0,0 @@ -@import '@taiga-ui/core/styles/taiga-ui-local'; - -:host { - display: block; - margin: 2rem 0; -} - -.label { - display: flex; - font-size: 1.25rem; - font-weight: bold; -} - -.add { - font-size: 1rem; - padding: 0 1rem; - margin-left: auto; -} - -.object { - display: block; - position: relative; - - &_open::after, - &:last-child::after { - opacity: 0; - } - - &:after { - @include transition(opacity); - - content: ''; - position: absolute; - bottom: -0.5rem; - height: 1px; - left: 3rem; - right: 1rem; - background: var(--tui-clear); - } -} - -.remove { - margin-left: auto; - pointer-events: auto; -} - -.control { - display: block; - margin: 0.5rem 0; -} diff --git a/web/projects/ui/src/app/components/form/form-array/form-array.component.ts b/web/projects/ui/src/app/components/form/form-array/form-array.component.ts deleted file mode 100644 index 11495d510..000000000 --- a/web/projects/ui/src/app/components/form/form-array/form-array.component.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Component, HostBinding, inject, Input } from '@angular/core' -import { AbstractControl, FormArrayName } from '@angular/forms' -import { TUI_PARENT_ANIMATION, TuiDestroyService } from '@taiga-ui/cdk' -import { - TUI_ANIMATION_OPTIONS, - TuiDialogService, - tuiFadeIn, - tuiHeightCollapse, -} from '@taiga-ui/core' -import { TUI_PROMPT } from '@taiga-ui/kit' -import { CT } from '@start9labs/start-sdk' -import { filter, takeUntil } from 'rxjs' -import { FormService } from 'src/app/services/form.service' -import { ERRORS } from '../form-group/form-group.component' - -@Component({ - selector: 'form-array', - templateUrl: './form-array.component.html', - styleUrls: ['./form-array.component.scss'], - animations: [tuiFadeIn, tuiHeightCollapse, TUI_PARENT_ANIMATION], - providers: [TuiDestroyService], -}) -export class FormArrayComponent { - @Input() - spec!: CT.ValueSpecList - - @HostBinding('@tuiParentAnimation') - readonly animation = { value: '', ...inject(TUI_ANIMATION_OPTIONS) } - readonly order = ERRORS - readonly array = inject(FormArrayName) - readonly open = new Map() - - private warned = false - private readonly formService = inject(FormService) - private readonly dialogs = inject(TuiDialogService) - private readonly destroy$ = inject(TuiDestroyService) - - get canAdd(): boolean { - return ( - !this.spec.disabled && - (!this.spec.maxLength || - this.spec.maxLength >= this.array.control.controls.length) - ) - } - - add() { - if (!this.warned && this.spec.warning) { - this.dialogs - .open(TUI_PROMPT, { - label: 'Warning', - size: 's', - data: { content: this.spec.warning, yes: 'Ok', no: 'Cancel' }, - }) - .pipe(filter(Boolean), takeUntil(this.destroy$)) - .subscribe(() => { - this.addItem() - }) - } else { - this.addItem() - } - - this.warned = true - } - - removeAt(index: number) { - this.dialogs - .open(TUI_PROMPT, { - label: 'Confirm', - size: 's', - data: { - content: 'Are you sure you want to delete this entry?', - yes: 'Delete', - no: 'Cancel', - }, - }) - .pipe(filter(Boolean), takeUntil(this.destroy$)) - .subscribe(() => { - this.removeItem(index) - }) - } - - private removeItem(index: number) { - this.open.delete(this.array.control.at(index)) - this.array.control.removeAt(index) - } - - private addItem() { - this.array.control.insert(0, this.formService.getListItem(this.spec)) - this.open.set(this.array.control.at(0), true) - } -} diff --git a/web/projects/ui/src/app/components/form/form-color/form-color.component.html b/web/projects/ui/src/app/components/form/form-color/form-color.component.html deleted file mode 100644 index 19cf4051d..000000000 --- a/web/projects/ui/src/app/components/form/form-color/form-color.component.html +++ /dev/null @@ -1,31 +0,0 @@ - - {{ spec.name }} - * - - -
- - -
-
diff --git a/web/projects/ui/src/app/components/form/form-color/form-color.component.scss b/web/projects/ui/src/app/components/form/form-color/form-color.component.scss deleted file mode 100644 index 49496946e..000000000 --- a/web/projects/ui/src/app/components/form/form-color/form-color.component.scss +++ /dev/null @@ -1,33 +0,0 @@ -@import '@taiga-ui/core/styles/taiga-ui-local'; - -.wrapper { - position: relative; - width: 1.5rem; - height: 1.5rem; - pointer-events: auto; - - &::after { - content: ''; - position: absolute; - height: 0.3rem; - width: 1.4rem; - bottom: 0.125rem; - background: currentColor; - border-radius: 0.125rem; - pointer-events: none; - } -} - -.color { - @include fullsize(); - opacity: 0; -} - -.icon { - @include fullsize(); - pointer-events: none; - - input:hover + & { - opacity: 1; - } -} diff --git a/web/projects/ui/src/app/components/form/form-color/form-color.component.ts b/web/projects/ui/src/app/components/form/form-color/form-color.component.ts deleted file mode 100644 index 32a7c1c04..000000000 --- a/web/projects/ui/src/app/components/form/form-color/form-color.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component } from '@angular/core' -import { CT } from '@start9labs/start-sdk' -import { Control } from '../control' -import { MaskitoOptions } from '@maskito/core' - -@Component({ - selector: 'form-color', - templateUrl: './form-color.component.html', - styleUrls: ['./form-color.component.scss'], -}) -export class FormColorComponent extends Control { - readonly mask: MaskitoOptions = { - mask: ['#', ...Array(6).fill(/[0-9a-f]/i)], - } -} diff --git a/web/projects/ui/src/app/components/form/form-control/form-control.component.html b/web/projects/ui/src/app/components/form/form-control/form-control.component.html deleted file mode 100644 index 731d64a63..000000000 --- a/web/projects/ui/src/app/components/form/form-control/form-control.component.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - {{ spec.warning }} -

This value cannot be changed once set!

-
- - -
-
diff --git a/web/projects/ui/src/app/components/form/form-control/form-control.component.scss b/web/projects/ui/src/app/components/form/form-control/form-control.component.scss deleted file mode 100644 index 844651118..000000000 --- a/web/projects/ui/src/app/components/form/form-control/form-control.component.scss +++ /dev/null @@ -1,11 +0,0 @@ -:host { - display: block; -} - -.buttons { - margin-top: 0.5rem; - - :first-child { - margin-right: 0.5rem; - } -} diff --git a/web/projects/ui/src/app/components/form/form-control/form-control.component.ts b/web/projects/ui/src/app/components/form/form-control/form-control.component.ts deleted file mode 100644 index ec49bd084..000000000 --- a/web/projects/ui/src/app/components/form/form-control/form-control.component.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - inject, - Input, - TemplateRef, - ViewChild, -} from '@angular/core' -import { AbstractTuiNullableControl } from '@taiga-ui/cdk' -import { - TuiAlertService, - TuiDialogContext, - TuiNotification, -} from '@taiga-ui/core' -import { filter, takeUntil } from 'rxjs' -import { CT } from '@start9labs/start-sdk' -import { ERRORS } from '../form-group/form-group.component' -import { FORM_CONTROL_PROVIDERS } from './form-control.providers' - -@Component({ - selector: 'form-control', - templateUrl: './form-control.component.html', - styleUrls: ['./form-control.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - providers: FORM_CONTROL_PROVIDERS, -}) -export class FormControlComponent< - T extends CT.ValueSpec, - V, -> extends AbstractTuiNullableControl { - @Input() - spec!: T - - @ViewChild('warning') - warning?: TemplateRef> - - warned = false - focused = false - readonly order = ERRORS - private readonly alerts = inject(TuiAlertService) - - get immutable(): boolean { - return 'immutable' in this.spec && this.spec.immutable - } - - onFocus(focused: boolean) { - this.focused = focused - this.updateFocused(focused) - } - - onInput(value: V | null) { - const previous = this.value - - if (!this.warned && this.warning) { - this.alerts - .open(this.warning, { - label: 'Warning', - status: TuiNotification.Warning, - hasCloseButton: false, - autoClose: false, - }) - .pipe(filter(Boolean), takeUntil(this.destroy$)) - .subscribe(() => { - this.value = previous - }) - } - - this.warned = true - this.value = value === '' ? null : value - } -} diff --git a/web/projects/ui/src/app/components/form/form-control/form-control.providers.ts b/web/projects/ui/src/app/components/form/form-control/form-control.providers.ts deleted file mode 100644 index f065f86cb..000000000 --- a/web/projects/ui/src/app/components/form/form-control/form-control.providers.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { forwardRef, Provider } from '@angular/core' -import { TUI_VALIDATION_ERRORS } from '@taiga-ui/kit' -import { CT } from '@start9labs/start-sdk' -import { FormControlComponent } from './form-control.component' - -interface ValidatorsPatternError { - actualValue: string - requiredPattern: string | RegExp -} - -export const FORM_CONTROL_PROVIDERS: Provider[] = [ - { - provide: TUI_VALIDATION_ERRORS, - deps: [forwardRef(() => FormControlComponent)], - useFactory: (control: FormControlComponent) => ({ - required: 'Required', - pattern: ({ requiredPattern }: ValidatorsPatternError) => - ('patterns' in control.spec && - control.spec.patterns.find( - ({ regex }) => String(regex) === String(requiredPattern), - )?.description) || - 'Invalid format', - }), - }, -] diff --git a/web/projects/ui/src/app/components/form/form-datetime/form-datetime.component.html b/web/projects/ui/src/app/components/form/form-datetime/form-datetime.component.html deleted file mode 100644 index 37387a338..000000000 --- a/web/projects/ui/src/app/components/form/form-datetime/form-datetime.component.html +++ /dev/null @@ -1,43 +0,0 @@ - - - {{ spec.name }} - * - - - {{ spec.name }} - * - - - {{ spec.name }} - * - - diff --git a/web/projects/ui/src/app/components/form/form-datetime/form-datetime.component.ts b/web/projects/ui/src/app/components/form/form-datetime/form-datetime.component.ts deleted file mode 100644 index e09b22d24..000000000 --- a/web/projects/ui/src/app/components/form/form-datetime/form-datetime.component.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Component } from '@angular/core' -import { - TUI_FIRST_DAY, - TUI_LAST_DAY, - TuiDay, - tuiPure, - TuiTime, -} from '@taiga-ui/cdk' -import { CT } from '@start9labs/start-sdk' -import { Control } from '../control' - -@Component({ - selector: 'form-datetime', - templateUrl: './form-datetime.component.html', -}) -export class FormDatetimeComponent extends Control< - CT.ValueSpecDatetime, - string -> { - readonly min = TUI_FIRST_DAY - readonly max = TUI_LAST_DAY - - @tuiPure - getTime(value: string | null) { - return value ? TuiTime.fromString(value) : null - } - - getLimit(limit: string): [TuiDay, TuiTime] { - return [ - TuiDay.jsonParse(limit.slice(0, 10)), - limit.length === 10 - ? new TuiTime(0, 0) - : TuiTime.fromString(limit.slice(-5)), - ] - } -} diff --git a/web/projects/ui/src/app/components/form/form-group/form-group.component.html b/web/projects/ui/src/app/components/form/form-group/form-group.component.html deleted file mode 100644 index 1c4f8301a..000000000 --- a/web/projects/ui/src/app/components/form/form-group/form-group.component.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - diff --git a/web/projects/ui/src/app/components/form/form-group/form-group.component.scss b/web/projects/ui/src/app/components/form/form-group/form-group.component.scss deleted file mode 100644 index ce5665fc0..000000000 --- a/web/projects/ui/src/app/components/form/form-group/form-group.component.scss +++ /dev/null @@ -1,35 +0,0 @@ -form-group .g-form-control:not(:first-child) { - margin-top: 1rem; -} - -form-group .g-form-group { - position: relative; - padding-left: var(--tui-height-m); - - &::before, - &::after { - content: ''; - position: absolute; - background: var(--tui-clear); - } - - &::before { - top: 0; - left: calc(1rem - 1px); - bottom: 0.5rem; - width: 2px; - } - - &::after { - left: 0.75rem; - bottom: 0; - width: 0.5rem; - height: 0.5rem; - border-radius: 100%; - } -} - -form-group tui-tooltip { - z-index: 1; - margin-left: 0.25rem; -} diff --git a/web/projects/ui/src/app/components/form/form-group/form-group.component.ts b/web/projects/ui/src/app/components/form/form-group/form-group.component.ts deleted file mode 100644 index d9d28c8df..000000000 --- a/web/projects/ui/src/app/components/form/form-group/form-group.component.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - Input, - ViewEncapsulation, -} from '@angular/core' -import { CT } from '@start9labs/start-sdk' -import { FORM_GROUP_PROVIDERS } from './form-group.providers' - -export const ERRORS = [ - 'required', - 'pattern', - 'notNumber', - 'numberNotInteger', - 'numberNotInRange', - 'listNotUnique', - 'listNotInRange', - 'listItemIssue', -] - -@Component({ - selector: 'form-group', - templateUrl: './form-group.component.html', - styleUrls: ['./form-group.component.scss'], - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, - viewProviders: [FORM_GROUP_PROVIDERS], -}) -export class FormGroupComponent { - @Input() spec: CT.InputSpec = {} - - asIsOrder() { - return 0 - } -} diff --git a/web/projects/ui/src/app/components/form/form-group/form-group.providers.ts b/web/projects/ui/src/app/components/form/form-group/form-group.providers.ts deleted file mode 100644 index 5c9039f40..000000000 --- a/web/projects/ui/src/app/components/form/form-group/form-group.providers.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Provider, SkipSelf } from '@angular/core' -import { - TUI_ARROW_MODE, - tuiInputDateOptionsProvider, - tuiInputTimeOptionsProvider, -} from '@taiga-ui/kit' -import { TUI_DEFAULT_ERROR_MESSAGE } from '@taiga-ui/core' -import { ControlContainer } from '@angular/forms' -import { identity, of } from 'rxjs' - -export const FORM_GROUP_PROVIDERS: Provider[] = [ - { - provide: TUI_DEFAULT_ERROR_MESSAGE, - useValue: of('Unknown error'), - }, - { - provide: ControlContainer, - deps: [[new SkipSelf(), ControlContainer]], - useFactory: identity, - }, - { - provide: TUI_ARROW_MODE, - useValue: { - interactive: null, - disabled: null, - }, - }, - tuiInputDateOptionsProvider({ - nativePicker: true, - }), - tuiInputTimeOptionsProvider({ - nativePicker: true, - }), -] diff --git a/web/projects/ui/src/app/components/form/form-multiselect/form-multiselect.component.html b/web/projects/ui/src/app/components/form/form-multiselect/form-multiselect.component.html deleted file mode 100644 index 0e2a47cc2..000000000 --- a/web/projects/ui/src/app/components/form/form-multiselect/form-multiselect.component.html +++ /dev/null @@ -1,18 +0,0 @@ - - {{ spec.name }} - - diff --git a/web/projects/ui/src/app/components/form/form-multiselect/form-multiselect.component.ts b/web/projects/ui/src/app/components/form/form-multiselect/form-multiselect.component.ts deleted file mode 100644 index 7134eb1f6..000000000 --- a/web/projects/ui/src/app/components/form/form-multiselect/form-multiselect.component.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Component } from '@angular/core' -import { CT } from '@start9labs/start-sdk' -import { Control } from '../control' -import { tuiPure } from '@taiga-ui/cdk' -import { invert } from '@start9labs/shared' - -@Component({ - selector: 'form-multiselect', - templateUrl: './form-multiselect.component.html', -}) -export class FormMultiselectComponent extends Control< - CT.ValueSpecMultiselect, - readonly string[] -> { - private readonly inverted = invert(this.spec.values) - - private readonly isDisabled = (item: string) => - Array.isArray(this.spec.disabled) && - this.spec.disabled.includes(this.inverted[item]) - - private readonly isExceedingLimit = (item: string) => - !!this.spec.maxLength && - this.selected.length >= this.spec.maxLength && - !this.selected.includes(item) - - readonly disabledItemHandler = (item: string): boolean => - this.isDisabled(item) || this.isExceedingLimit(item) - - readonly items = Object.values(this.spec.values) - - get disabled(): boolean { - return typeof this.spec.disabled === 'string' - } - - get selected(): string[] { - return this.memoize(this.value) - } - - set selected(value: string[]) { - this.value = Object.entries(this.spec.values) - .filter(([_, v]) => value.includes(v)) - .map(([k]) => k) - } - - @tuiPure - private memoize(value: null | readonly string[]): string[] { - return value?.map(key => this.spec.values[key]) || [] - } -} diff --git a/web/projects/ui/src/app/components/form/form-number/form-number.component.html b/web/projects/ui/src/app/components/form/form-number/form-number.component.html deleted file mode 100644 index c205b2bb8..000000000 --- a/web/projects/ui/src/app/components/form/form-number/form-number.component.html +++ /dev/null @@ -1,18 +0,0 @@ - - {{ spec.name }} - * - - diff --git a/web/projects/ui/src/app/components/form/form-number/form-number.component.ts b/web/projects/ui/src/app/components/form/form-number/form-number.component.ts deleted file mode 100644 index a930b1614..000000000 --- a/web/projects/ui/src/app/components/form/form-number/form-number.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Component } from '@angular/core' -import { CT } from '@start9labs/start-sdk' -import { Control } from '../control' - -@Component({ - selector: 'form-number', - templateUrl: './form-number.component.html', -}) -export class FormNumberComponent extends Control { - protected readonly Infinity = Infinity -} diff --git a/web/projects/ui/src/app/components/form/form-object/form-object.component.html b/web/projects/ui/src/app/components/form/form-object/form-object.component.html deleted file mode 100644 index 589019c15..000000000 --- a/web/projects/ui/src/app/components/form/form-object/form-object.component.html +++ /dev/null @@ -1,25 +0,0 @@ -

- - - {{ spec.name }} - -

- - -
- -
-
diff --git a/web/projects/ui/src/app/components/form/form-object/form-object.component.scss b/web/projects/ui/src/app/components/form/form-object/form-object.component.scss deleted file mode 100644 index c167c89fa..000000000 --- a/web/projects/ui/src/app/components/form/form-object/form-object.component.scss +++ /dev/null @@ -1,41 +0,0 @@ -@import '@taiga-ui/core/styles/taiga-ui-local'; - -:host { - display: flex; - flex-direction: column; - align-items: flex-start; -} - -.title { - position: relative; - height: var(--tui-height-l); - display: flex; - align-items: center; - cursor: pointer; - font: var(--tui-font-text-l); - font-weight: bold; - margin: 0 0 -0.75rem; -} - -.button { - @include transition(transform); - - margin-right: 1rem; - - &_open { - transform: rotate(180deg); - } -} - -.expand { - align-self: stretch; -} - -.g-form-group { - padding-top: 0.75rem; - - &_invalid::before, - &_invalid::after { - background: var(--tui-error-bg); - } -} diff --git a/web/projects/ui/src/app/components/form/form-object/form-object.component.ts b/web/projects/ui/src/app/components/form/form-object/form-object.component.ts deleted file mode 100644 index b1aa507cf..000000000 --- a/web/projects/ui/src/app/components/form/form-object/form-object.component.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - EventEmitter, - inject, - Input, - Output, -} from '@angular/core' -import { ControlContainer } from '@angular/forms' -import { CT } from '@start9labs/start-sdk' - -@Component({ - selector: 'form-object', - templateUrl: './form-object.component.html', - styleUrls: ['./form-object.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class FormObjectComponent { - @Input() - spec!: CT.ValueSpecObject - - @Input() - open = false - - @Output() - readonly openChange = new EventEmitter() - - private readonly container = inject(ControlContainer) - - get invalid() { - return !this.container.valid && this.container.touched - } - - toggle() { - this.open = !this.open - this.openChange.emit(this.open) - } -} diff --git a/web/projects/ui/src/app/components/form/form-select/form-select.component.html b/web/projects/ui/src/app/components/form/form-select/form-select.component.html deleted file mode 100644 index fe2b561c7..000000000 --- a/web/projects/ui/src/app/components/form/form-select/form-select.component.html +++ /dev/null @@ -1,18 +0,0 @@ - - {{ spec.name }} - * - - diff --git a/web/projects/ui/src/app/components/form/form-select/form-select.component.ts b/web/projects/ui/src/app/components/form/form-select/form-select.component.ts deleted file mode 100644 index ccbbccae8..000000000 --- a/web/projects/ui/src/app/components/form/form-select/form-select.component.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Component } from '@angular/core' -import { CT } from '@start9labs/start-sdk' -import { invert } from '@start9labs/shared' -import { Control } from '../control' - -@Component({ - selector: 'form-select', - templateUrl: './form-select.component.html', -}) -export class FormSelectComponent extends Control { - private readonly inverted = invert(this.spec.values) - - readonly items = Object.values(this.spec.values) - - readonly disabledItemHandler = (item: string) => - Array.isArray(this.spec.disabled) && - this.spec.disabled.includes(this.inverted[item]) - - get disabled(): boolean { - return typeof this.spec.disabled === 'string' - } - - get selected(): string | null { - return (this.value && this.spec.values[this.value]) || null - } - - set selected(value: string | null) { - this.value = (value && this.inverted[value]) || null - } -} diff --git a/web/projects/ui/src/app/components/form/form-text/form-text.component.html b/web/projects/ui/src/app/components/form/form-text/form-text.component.html deleted file mode 100644 index 0db900238..000000000 --- a/web/projects/ui/src/app/components/form/form-text/form-text.component.html +++ /dev/null @@ -1,44 +0,0 @@ - - {{ spec.name }} - * - - - - - - diff --git a/web/projects/ui/src/app/components/form/form-text/form-text.component.scss b/web/projects/ui/src/app/components/form/form-text/form-text.component.scss deleted file mode 100644 index 05e47885b..000000000 --- a/web/projects/ui/src/app/components/form/form-text/form-text.component.scss +++ /dev/null @@ -1,8 +0,0 @@ -.button { - pointer-events: auto; - margin-left: 0.25rem; -} - -.masked { - -webkit-text-security: disc; -} diff --git a/web/projects/ui/src/app/components/form/form-text/form-text.component.ts b/web/projects/ui/src/app/components/form/form-text/form-text.component.ts deleted file mode 100644 index 8a5ac86f0..000000000 --- a/web/projects/ui/src/app/components/form/form-text/form-text.component.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Component } from '@angular/core' -import { CT, utils } from '@start9labs/start-sdk' -import { Control } from '../control' - -@Component({ - selector: 'form-text', - templateUrl: './form-text.component.html', - styleUrls: ['./form-text.component.scss'], -}) -export class FormTextComponent extends Control { - masked = true - - generate() { - this.value = utils.getDefaultString(this.spec.generate || '') - } -} diff --git a/web/projects/ui/src/app/components/form/form-textarea/form-textarea.component.html b/web/projects/ui/src/app/components/form/form-textarea/form-textarea.component.html deleted file mode 100644 index 1be4a67a2..000000000 --- a/web/projects/ui/src/app/components/form/form-textarea/form-textarea.component.html +++ /dev/null @@ -1,15 +0,0 @@ - - {{ spec.name }} - * - - diff --git a/web/projects/ui/src/app/components/form/form-textarea/form-textarea.component.ts b/web/projects/ui/src/app/components/form/form-textarea/form-textarea.component.ts deleted file mode 100644 index b32685c21..000000000 --- a/web/projects/ui/src/app/components/form/form-textarea/form-textarea.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component } from '@angular/core' -import { CT } from '@start9labs/start-sdk' -import { Control } from '../control' - -@Component({ - selector: 'form-textarea', - templateUrl: './form-textarea.component.html', -}) -export class FormTextareaComponent extends Control< - CT.ValueSpecTextarea, - string -> {} diff --git a/web/projects/ui/src/app/components/form/form-toggle/form-toggle.component.html b/web/projects/ui/src/app/components/form/form-toggle/form-toggle.component.html deleted file mode 100644 index 73d116f92..000000000 --- a/web/projects/ui/src/app/components/form/form-toggle/form-toggle.component.html +++ /dev/null @@ -1,11 +0,0 @@ -{{ spec.name }} - - diff --git a/web/projects/ui/src/app/components/form/form-toggle/form-toggle.component.ts b/web/projects/ui/src/app/components/form/form-toggle/form-toggle.component.ts deleted file mode 100644 index 6a3c0196f..000000000 --- a/web/projects/ui/src/app/components/form/form-toggle/form-toggle.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Component } from '@angular/core' -import { CT } from '@start9labs/start-sdk' -import { Control } from '../control' - -@Component({ - selector: 'form-toggle', - templateUrl: './form-toggle.component.html', - host: { class: 'g-toggle' }, -}) -export class FormToggleComponent extends Control {} diff --git a/web/projects/ui/src/app/components/form/form-union/form-union.component.html b/web/projects/ui/src/app/components/form/form-union/form-union.component.html deleted file mode 100644 index 1cb5bfe57..000000000 --- a/web/projects/ui/src/app/components/form/form-union/form-union.component.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/web/projects/ui/src/app/components/form/form-union/form-union.component.scss b/web/projects/ui/src/app/components/form/form-union/form-union.component.scss deleted file mode 100644 index cfb2f95e8..000000000 --- a/web/projects/ui/src/app/components/form/form-union/form-union.component.scss +++ /dev/null @@ -1,8 +0,0 @@ -:host { - display: block; -} - -.group { - display: block; - margin-top: 1rem; -} diff --git a/web/projects/ui/src/app/components/form/form-union/form-union.component.ts b/web/projects/ui/src/app/components/form/form-union/form-union.component.ts deleted file mode 100644 index 2c164be48..000000000 --- a/web/projects/ui/src/app/components/form/form-union/form-union.component.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - inject, - Input, - OnChanges, -} from '@angular/core' -import { ControlContainer, FormGroupName } from '@angular/forms' -import { CT } from '@start9labs/start-sdk' -import { FormService } from 'src/app/services/form.service' -import { tuiPure } from '@taiga-ui/cdk' - -@Component({ - selector: 'form-union', - templateUrl: './form-union.component.html', - styleUrls: ['./form-union.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - viewProviders: [ - { - provide: ControlContainer, - useExisting: FormGroupName, - }, - ], -}) -export class FormUnionComponent implements OnChanges { - @Input() - spec!: CT.ValueSpecUnion - - selectSpec!: CT.ValueSpecSelect - - private readonly form = inject(FormGroupName) - private readonly formService = inject(FormService) - - get union(): string { - return this.form.value.selection - } - - @tuiPure - onUnion(union: string) { - this.form.control.setControl( - 'value', - this.formService.getFormGroup( - union ? this.spec.variants[union].spec : {}, - ), - { - emitEvent: false, - }, - ) - } - - ngOnChanges() { - this.selectSpec = this.formService.getUnionSelectSpec(this.spec, this.union) - if (this.union) this.onUnion(this.union) - } -} diff --git a/web/projects/ui/src/app/components/form/form.module.ts b/web/projects/ui/src/app/components/form/form.module.ts deleted file mode 100644 index 5417309a9..000000000 --- a/web/projects/ui/src/app/components/form/form.module.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { FormsModule, ReactiveFormsModule } from '@angular/forms' -import { MaskitoModule } from '@maskito/angular' -import { TuiMapperPipeModule, TuiValueChangesModule } from '@taiga-ui/cdk' -import { - TuiErrorModule, - TuiExpandModule, - TuiHintModule, - TuiLinkModule, - TuiModeModule, - TuiTextfieldControllerModule, - TuiTooltipModule, -} from '@taiga-ui/core' -import { - TuiAppearanceModule, - TuiButtonModule, - TuiIconModule, -} from '@taiga-ui/experimental' -import { - TuiElasticContainerModule, - TuiFieldErrorPipeModule, - TuiInputDateModule, - TuiInputDateTimeModule, - TuiInputFilesModule, - TuiInputModule, - TuiInputNumberModule, - TuiInputTimeModule, - TuiMultiSelectModule, - TuiPromptModule, - TuiSelectModule, - TuiTagModule, - TuiTextareaModule, - TuiToggleModule, -} from '@taiga-ui/kit' - -import { FormGroupComponent } from './form-group/form-group.component' -import { FormTextComponent } from './form-text/form-text.component' -import { FormToggleComponent } from './form-toggle/form-toggle.component' -import { FormTextareaComponent } from './form-textarea/form-textarea.component' -import { FormNumberComponent } from './form-number/form-number.component' -import { FormSelectComponent } from './form-select/form-select.component' -import { FormMultiselectComponent } from './form-multiselect/form-multiselect.component' -import { FormUnionComponent } from './form-union/form-union.component' -import { FormObjectComponent } from './form-object/form-object.component' -import { FormArrayComponent } from './form-array/form-array.component' -import { FormControlComponent } from './form-control/form-control.component' -import { MustachePipe } from './mustache.pipe' -import { ControlDirective } from './control.directive' -import { FormColorComponent } from './form-color/form-color.component' -import { FormDatetimeComponent } from './form-datetime/form-datetime.component' -import { HintPipe } from './hint.pipe' - -@NgModule({ - imports: [ - CommonModule, - FormsModule, - ReactiveFormsModule, - TuiInputModule, - TuiInputNumberModule, - TuiInputFilesModule, - TuiTextareaModule, - TuiSelectModule, - TuiMultiSelectModule, - TuiToggleModule, - TuiTooltipModule, - TuiHintModule, - TuiModeModule, - TuiTagModule, - TuiButtonModule, - TuiExpandModule, - TuiTextfieldControllerModule, - TuiLinkModule, - TuiPromptModule, - TuiErrorModule, - TuiFieldErrorPipeModule, - TuiValueChangesModule, - TuiElasticContainerModule, - MaskitoModule, - TuiIconModule, - TuiAppearanceModule, - TuiInputDateModule, - TuiInputTimeModule, - TuiInputDateTimeModule, - TuiMapperPipeModule, - ], - declarations: [ - FormGroupComponent, - FormControlComponent, - FormColorComponent, - FormDatetimeComponent, - FormTextComponent, - FormToggleComponent, - FormTextareaComponent, - FormNumberComponent, - FormSelectComponent, - FormMultiselectComponent, - FormUnionComponent, - FormObjectComponent, - FormArrayComponent, - MustachePipe, - HintPipe, - ControlDirective, - ], - exports: [FormGroupComponent], -}) -export class FormModule {} diff --git a/web/projects/ui/src/app/components/form/hint.pipe.ts b/web/projects/ui/src/app/components/form/hint.pipe.ts deleted file mode 100644 index b5a730661..000000000 --- a/web/projects/ui/src/app/components/form/hint.pipe.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' -import { CT } from '@start9labs/start-sdk' - -@Pipe({ - name: 'hint', -}) -export class HintPipe implements PipeTransform { - transform(spec: CT.ValueSpec): string { - const hint = [] - - if (spec.description) { - hint.push(spec.description) - } - - if ('disabled' in spec && typeof spec.disabled === 'string') { - hint.push(`Disabled: ${spec.disabled}`) - } - - return hint.join('\n\n') - } -} diff --git a/web/projects/ui/src/app/components/form/invalid.service.ts b/web/projects/ui/src/app/components/form/invalid.service.ts deleted file mode 100644 index 9f474e853..000000000 --- a/web/projects/ui/src/app/components/form/invalid.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable } from '@angular/core' -import { ControlDirective } from './control.directive' - -@Injectable() -export class InvalidService { - private readonly controls: ControlDirective[] = [] - - scrollIntoView() { - this.controls.find(({ invalid }) => invalid)?.scrollIntoView() - } - - add(control: ControlDirective) { - this.controls.push(control) - } - - remove(control: ControlDirective) { - this.controls.splice(this.controls.indexOf(control), 1) - } -} diff --git a/web/projects/ui/src/app/components/form/mustache.pipe.ts b/web/projects/ui/src/app/components/form/mustache.pipe.ts deleted file mode 100644 index ec04b0104..000000000 --- a/web/projects/ui/src/app/components/form/mustache.pipe.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core' - -const Mustache = require('mustache') - -@Pipe({ - name: 'mustache', -}) -export class MustachePipe implements PipeTransform { - transform(value: any, displayAs: string): string { - return displayAs && Mustache.render(displayAs, value) - } -} diff --git a/web/projects/ui/src/app/components/notifications-toast.component.ts b/web/projects/ui/src/app/components/notifications-toast.component.ts index 0fef23d3b..0920b3393 100644 --- a/web/projects/ui/src/app/components/notifications-toast.component.ts +++ b/web/projects/ui/src/app/components/notifications-toast.component.ts @@ -27,7 +27,7 @@ export class NotificationsToastComponent { readonly visible$: Observable = merge( this.dismiss$, - inject(PatchDB) + inject>(PatchDB) .watch$('serverInfo', 'unreadNotifications', 'count') .pipe( pairwise(), diff --git a/web/projects/ui/src/app/components/refresh-alert.component.ts b/web/projects/ui/src/app/components/refresh-alert.component.ts index 57e7f9f0a..f591f79f1 100644 --- a/web/projects/ui/src/app/components/refresh-alert.component.ts +++ b/web/projects/ui/src/app/components/refresh-alert.component.ts @@ -1,7 +1,7 @@ import { AsyncPipe } from '@angular/common' import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { SwUpdate } from '@angular/service-worker' -import { Emver, LoadingService } from '@start9labs/shared' +import { Exver, LoadingService } from '@start9labs/shared' import { TuiAutoFocus } from '@taiga-ui/cdk' import { TuiButton, TuiDialog } from '@taiga-ui/core' import { PatchDB } from 'patch-db-client' @@ -47,16 +47,16 @@ import { DataModel } from 'src/app/services/patch-db/data-model' export class RefreshAlertComponent { private readonly updates = inject(SwUpdate) private readonly loader = inject(LoadingService) - private readonly emver = inject(Emver) + private readonly exver = inject(Exver) private readonly config = inject(ConfigService) private readonly dismiss$ = new Subject() readonly show$ = merge( this.dismiss$, - inject(PatchDB) + inject>(PatchDB) .watch$('serverInfo', 'version') .pipe( - map(version => !!this.emver.compare(this.config.version, version)), + map(version => !!this.exver.compareExver(this.config.version, version)), endWith(false), ), ).pipe(debounceTime(0)) diff --git a/web/projects/ui/src/app/components/update-toast.component.ts b/web/projects/ui/src/app/components/update-toast.component.ts index 91f7e83cd..d304542fa 100644 --- a/web/projects/ui/src/app/components/update-toast.component.ts +++ b/web/projects/ui/src/app/components/update-toast.component.ts @@ -53,7 +53,7 @@ export class UpdateToastComponent { readonly visible$: Observable = merge( this.dismiss$, - inject(PatchDB) + inject>(PatchDB) .watch$('serverInfo', 'statusInfo', 'updated') .pipe(distinctUntilChanged(), filter(Boolean), endWith(false)), ) diff --git a/web/projects/ui/src/app/modals/backup-server-select/backup-server-select.module.ts b/web/projects/ui/src/app/modals/backup-server-select/backup-server-select.module.ts deleted file mode 100644 index 958b98dff..000000000 --- a/web/projects/ui/src/app/modals/backup-server-select/backup-server-select.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NgModule } from '@angular/core' -import { CommonModule } from '@angular/common' -import { IonicModule } from '@ionic/angular' -import { FormsModule } from '@angular/forms' -import { BackupServerSelectModal } from './backup-server-select.page' -import { AppRecoverSelectPageModule } from 'src/app/modals/app-recover-select/app-recover-select.module' - -@NgModule({ - declarations: [BackupServerSelectModal], - imports: [CommonModule, FormsModule, IonicModule, AppRecoverSelectPageModule], - exports: [BackupServerSelectModal], -}) -export class BackupServerSelectModule {} diff --git a/web/projects/ui/src/app/modals/backup-server-select/backup-server-select.page.html b/web/projects/ui/src/app/modals/backup-server-select/backup-server-select.page.html deleted file mode 100644 index e5b2369e5..000000000 --- a/web/projects/ui/src/app/modals/backup-server-select/backup-server-select.page.html +++ /dev/null @@ -1,35 +0,0 @@ - - - Select Server Backup - - - - - - - - - - - - -

- Local Hostname - : {{ server.value.hostname }}.local -

-

- StartOS Version - : {{ server.value.version }} -

-

- Created - : {{ server.value.timestamp | date : 'medium' }} -

-
-
-
-
diff --git a/web/projects/ui/src/app/modals/backup-server-select/backup-server-select.page.scss b/web/projects/ui/src/app/modals/backup-server-select/backup-server-select.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/modals/backup-server-select/backup-server-select.page.ts b/web/projects/ui/src/app/modals/backup-server-select/backup-server-select.page.ts deleted file mode 100644 index a10067044..000000000 --- a/web/projects/ui/src/app/modals/backup-server-select/backup-server-select.page.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { Component, Input } from '@angular/core' -import { ModalController, NavController } from '@ionic/angular' -import * as argon2 from '@start9labs/argon2' -import { - ErrorService, - LoadingService, - StartOSDiskInfo, -} from '@start9labs/shared' -import { - PasswordPromptComponent, - PromptOptions, -} from 'src/app/modals/password-prompt.component' -import { - BackupInfo, - CifsBackupTarget, - DiskBackupTarget, -} from 'src/app/services/api/api.types' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { MappedBackupTarget } from 'src/app/types/mapped-backup-target' -import { AppRecoverSelectPage } from '../app-recover-select/app-recover-select.page' - -@Component({ - selector: 'backup-server-select', - templateUrl: 'backup-server-select.page.html', - styleUrls: ['backup-server-select.page.scss'], -}) -export class BackupServerSelectModal { - @Input() target!: MappedBackupTarget - - constructor( - private readonly modalCtrl: ModalController, - private readonly loader: LoadingService, - private readonly api: ApiService, - private readonly navCtrl: NavController, - private readonly errorService: ErrorService, - ) {} - - dismiss() { - this.modalCtrl.dismiss() - } - - async presentModalPassword( - serverId: string, - { passwordHash }: StartOSDiskInfo, - ): Promise { - const options: PromptOptions = { - title: 'Password Required', - message: - 'Enter the password that was used to encrypt this backup. On the next screen, you will select the individual services you want to restore.', - label: 'Decrypt Backup', - placeholder: 'Enter password', - buttonText: 'Next', - } - const modal = await this.modalCtrl.create({ - component: PasswordPromptComponent, - componentProps: { options }, - canDismiss: async password => { - if (password === null) { - return true - } - - try { - argon2.verify(passwordHash!, password) - await this.restoreFromBackup(serverId, password) - return true - } catch (e: any) { - this.errorService.handleError(e) - return false - } - }, - }) - modal.present() - } - - private async restoreFromBackup( - serverId: string, - password: string, - ): Promise { - const loader = this.loader.open('Decrypting drive...').subscribe() - - try { - const backupInfo = await this.api.getBackupInfo({ - targetId: this.target.id, - serverId, - password, - }) - this.presentModalSelect(serverId, backupInfo, password) - } finally { - loader.unsubscribe() - } - } - - private async presentModalSelect( - serverId: string, - backupInfo: BackupInfo, - password: string, - ): Promise { - const modal = await this.modalCtrl.create({ - componentProps: { - targetId: this.target.id, - serverId, - backupInfo, - password, - }, - presentingElement: await this.modalCtrl.getTop(), - component: AppRecoverSelectPage, - }) - - modal.onDidDismiss().then(res => { - if (res.role === 'success') { - this.modalCtrl.dismiss(undefined, 'success') - this.navCtrl.navigateRoot('/services') - } - }) - - await modal.present() - } -} diff --git a/web/projects/ui/src/app/modals/config-dep.component.ts b/web/projects/ui/src/app/modals/config-dep.component.ts deleted file mode 100644 index 14becf2e8..000000000 --- a/web/projects/ui/src/app/modals/config-dep.component.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { - ChangeDetectionStrategy, - Component, - Input, - OnChanges, -} from '@angular/core' -import { compare, getValueByPointer, Operation } from 'fast-json-patch' -import { isObject } from '@start9labs/shared' -import { tuiIsNumber } from '@taiga-ui/cdk' -import { CommonModule } from '@angular/common' -import { TuiNotificationModule } from '@taiga-ui/core' - -@Component({ - selector: 'config-dep', - template: ` - -

- {{ package }} -

- The following modifications have been made to {{ package }} to satisfy - {{ dep }}: -
    -
  • -
- To accept these modifications, click "Save". -
- `, - standalone: true, - imports: [CommonModule, TuiNotificationModule], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class ConfigDepComponent implements OnChanges { - @Input() - package = '' - - @Input() - dep = '' - - @Input() - original: object = {} - - @Input() - value: object = {} - - diff: string[] = [] - - ngOnChanges() { - this.diff = compare(this.original, this.value).map( - op => `${this.getPath(op)}: ${this.getMessage(op)}`, - ) - } - - private getPath(operation: Operation): string { - const path = operation.path - .substring(1) - .split('/') - .map(node => { - const num = Number(node) - return isNaN(num) ? node : num - }) - - if (tuiIsNumber(path[path.length - 1])) { - path.pop() - } - - return path.join(' → ') - } - - private getMessage(operation: Operation): string { - switch (operation.op) { - case 'add': - return `Added ${this.getNewValue(operation.value)}` - case 'remove': - return `Removed ${this.getOldValue(operation.path)}` - case 'replace': - return `Changed from ${this.getOldValue( - operation.path, - )} to ${this.getNewValue(operation.value)}` - default: - return `Unknown operation` - } - } - - private getOldValue(path: any): string { - const val = getValueByPointer(this.original, path) - if (['string', 'number', 'boolean'].includes(typeof val)) { - return val - } else if (isObject(val)) { - return 'entry' - } else { - return 'list' - } - } - - private getNewValue(val: any): string { - if (['string', 'number', 'boolean'].includes(typeof val)) { - return val - } else if (isObject(val)) { - return 'new entry' - } else { - return 'new list' - } - } -} diff --git a/web/projects/ui/src/app/modals/config.component.ts b/web/projects/ui/src/app/modals/config.component.ts deleted file mode 100644 index d0e8afa4d..000000000 --- a/web/projects/ui/src/app/modals/config.component.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { CommonModule } from '@angular/common' -import { Component, Inject, ViewChild } from '@angular/core' -import { - ErrorService, - getErrorMessage, - isEmptyObject, - LoadingService, -} from '@start9labs/shared' -import { CT, T } from '@start9labs/start-sdk' -import { TuiButtonModule } from '@taiga-ui/experimental' -import { - TuiDialogContext, - TuiDialogService, - TuiLoaderModule, - TuiModeModule, - TuiNotificationModule, -} from '@taiga-ui/core' -import { TUI_PROMPT, TuiPromptData } from '@taiga-ui/kit' -import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus' -import { compare, Operation } from 'fast-json-patch' -import { PatchDB } from 'patch-db-client' -import { endWith, firstValueFrom, Subscription } from 'rxjs' -import { ActionButton, FormComponent } from 'src/app/components/form.component' -import { InvalidService } from 'src/app/components/form/invalid.service' -import { ConfigDepComponent } from 'src/app/modals/config-dep.component' -import { UiPipeModule } from 'src/app/pipes/ui/ui.module' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { - DataModel, - PackageDataEntry, -} from 'src/app/services/patch-db/data-model' -import { - getAllPackages, - getManifest, - getPackage, -} from 'src/app/util/get-package-data' -import { hasCurrentDeps } from 'src/app/util/has-deps' -import { Breakages } from 'src/app/services/api/api.types' -import { DependentInfo } from 'src/app/types/dependent-info' - -export interface PackageConfigData { - readonly pkgId: string - readonly dependentInfo?: DependentInfo -} - -@Component({ - template: ` - - - -
-
- - - - {{ manifest.title }} has been automatically configured with recommended - defaults. Make whatever changes you want, then click "Save". - - - - - - No config options for {{ manifest.title }} {{ manifest.version }}. - - - - - - - `, - styles: [ - ` - tui-notification { - font-size: 1rem; - margin-bottom: 1rem; - } - `, - ], - standalone: true, - imports: [ - CommonModule, - FormComponent, - TuiLoaderModule, - TuiNotificationModule, - TuiButtonModule, - TuiModeModule, - ConfigDepComponent, - UiPipeModule, - ], - providers: [InvalidService], -}) -export class ConfigModal { - @ViewChild(FormComponent) - private readonly form?: FormComponent> - - readonly pkgId = this.context.data.pkgId - readonly dependentInfo = this.context.data.dependentInfo - - loadingError = '' - loadingText = this.dependentInfo - ? `Setting properties to accommodate ${this.dependentInfo.title}` - : 'Loading Config' - - pkg?: PackageDataEntry - spec: CT.InputSpec = {} - patch: Operation[] = [] - buttons: ActionButton[] = [ - { - text: 'Save', - handler: value => this.save(value), - }, - ] - - original: object | null = null - value: object | null = null - - constructor( - @Inject(POLYMORPHEUS_CONTEXT) - private readonly context: TuiDialogContext, - private readonly dialogs: TuiDialogService, - private readonly errorService: ErrorService, - private readonly loader: LoadingService, - private readonly embassyApi: ApiService, - private readonly patchDb: PatchDB, - ) {} - - get success(): boolean { - return ( - !!this.form && - !this.form.form.dirty && - !this.original && - !this.pkg?.status?.configured - ) - } - - async ngOnInit() { - try { - this.pkg = await getPackage(this.patchDb, this.pkgId) - - if (!this.pkg) { - this.loadingError = 'This service does not exist' - - return - } - - if (this.dependentInfo) { - const depConfig = await this.embassyApi.dryConfigureDependency({ - dependencyId: this.pkgId, - dependentId: this.dependentInfo.id, - }) - - this.original = depConfig.oldConfig - this.value = depConfig.newConfig || this.original - this.spec = depConfig.spec - this.patch = compare(this.original, this.value) - } else { - const { config, spec } = await this.embassyApi.getPackageConfig({ - id: this.pkgId, - }) - - this.original = config - this.value = config - this.spec = spec - } - } catch (e: any) { - this.loadingError = String(getErrorMessage(e)) - } finally { - this.loadingText = '' - } - } - - private async save(config: any) { - const loader = new Subscription() - - try { - if (hasCurrentDeps(this.pkgId, await getAllPackages(this.patchDb))) { - await this.configureDeps(config, loader) - } else { - await this.configure(config, loader) - } - } catch (e: any) { - this.errorService.handleError(e) - } finally { - loader.unsubscribe() - } - } - - private async configureDeps( - config: Record, - loader: Subscription, - ) { - loader.unsubscribe() - loader.closed = false - loader.add(this.loader.open('Checking dependent services...').subscribe()) - - const breakages = await this.embassyApi.drySetPackageConfig({ - id: this.pkgId, - config, - }) - - loader.unsubscribe() - loader.closed = false - - if (isEmptyObject(breakages) || (await this.approveBreakages(breakages))) { - await this.configure(config, loader) - } - } - - private async configure(config: Record, loader: Subscription) { - loader.unsubscribe() - loader.closed = false - loader.add(this.loader.open('Saving...').subscribe()) - - await this.embassyApi.setPackageConfig({ id: this.pkgId, config }) - this.context.$implicit.complete() - } - - private async approveBreakages(breakages: T.PackageId[]): Promise { - const packages = await getAllPackages(this.patchDb) - const message = - 'As a result of this change, the following services will no longer work properly and may crash:
    ' - const content = `${message}${breakages.map( - id => `
  • ${getManifest(packages[id]).title}
  • `, - )}
` - const data: TuiPromptData = { content, yes: 'Continue', no: 'Cancel' } - - return firstValueFrom( - this.dialogs.open(TUI_PROMPT, { data }).pipe(endWith(false)), - ) - } -} diff --git a/web/projects/ui/src/app/modals/marketplace-settings/registry.component.ts b/web/projects/ui/src/app/modals/marketplace-settings/registry.component.ts index 0441ae18f..84b064413 100644 --- a/web/projects/ui/src/app/modals/marketplace-settings/registry.component.ts +++ b/web/projects/ui/src/app/modals/marketplace-settings/registry.component.ts @@ -25,9 +25,9 @@ import { StoreIconComponent } from './store-icon.component'
+ /> `, styles: [':host { border-radius: 0.25rem; width: stretch; }'], diff --git a/web/projects/ui/src/app/modals/password-prompt.component.ts b/web/projects/ui/src/app/modals/password-prompt.component.ts deleted file mode 100644 index a9fec75cd..000000000 --- a/web/projects/ui/src/app/modals/password-prompt.component.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { - AfterViewInit, - Component, - ElementRef, - Input, - ViewChild, -} from '@angular/core' -import { FormsModule } from '@angular/forms' -import { IonicModule, ModalController } from '@ionic/angular' -import { TuiTextfieldComponent } from '@taiga-ui/core' -import { TuiInputPasswordModule } from '@taiga-ui/kit' - -export interface PromptOptions { - title: string - message: string - label: string - placeholder: string - buttonText: string -} - -@Component({ - standalone: true, - template: ` - - - {{ options.title }} - - - - - - - - - -

{{ options.message }}

-

- - {{ options.label }} - - -

-
- - - - - Cancel - - - {{ options.buttonText }} - - - - `, - imports: [IonicModule, FormsModule, TuiInputPasswordModule], -}) -export class PasswordPromptComponent implements AfterViewInit { - @ViewChild(TuiTextfieldComponent, { read: ElementRef }) - input?: ElementRef - - @Input() - options!: PromptOptions - - password = '' - - constructor(private modalCtrl: ModalController) {} - - ngAfterViewInit() { - setTimeout(() => { - this.input?.nativeElement.focus({ preventScroll: true }) - }, 300) - } - - cancel() { - return this.modalCtrl.dismiss(null, 'cancel') - } - - confirm() { - return this.modalCtrl.dismiss(this.password, 'confirm') - } -} diff --git a/web/projects/ui/src/app/modals/prompt.component.ts b/web/projects/ui/src/app/modals/prompt.component.ts deleted file mode 100644 index e2a2765f5..000000000 --- a/web/projects/ui/src/app/modals/prompt.component.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { CommonModule } from '@angular/common' -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core' -import { FormsModule } from '@angular/forms' -import { TuiAutoFocusModule } from '@taiga-ui/cdk' -import { TuiDialogContext, TuiTextfieldControllerModule } from '@taiga-ui/core' -import { TuiButtonModule } from '@taiga-ui/experimental' -import { TuiInputModule } from '@taiga-ui/kit' -import { - POLYMORPHEUS_CONTEXT, - PolymorpheusComponent, -} from '@tinkoff/ng-polymorpheus' - -@Component({ - standalone: true, - template: ` -

{{ options.message }}

-

{{ options.warning }}

-
- - {{ options.label }} - * - - -
- - -
-
- - - - - `, - styles: [ - ` - .warning { - color: var(--tui-warning-fill); - } - - .button { - pointer-events: auto; - margin-left: 0.25rem; - } - - .masked { - -webkit-text-security: disc; - } - `, - ], - imports: [ - CommonModule, - FormsModule, - TuiInputModule, - TuiButtonModule, - TuiTextfieldControllerModule, - TuiAutoFocusModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class PromptModal { - masked = this.options.useMask - value = this.options.initialValue || '' - - constructor( - @Inject(POLYMORPHEUS_CONTEXT) - private readonly context: TuiDialogContext, - ) {} - - get options(): PromptOptions { - return this.context.data - } - - cancel() { - this.context.$implicit.complete() - } - - submit(value: string) { - if (value || !this.options.required) { - this.context.$implicit.next(value) - } - } -} - -export const PROMPT = new PolymorpheusComponent(PromptModal) - -export interface PromptOptions { - message: string - label?: string - warning?: string - buttonText?: string - placeholder?: string - required?: boolean - useMask?: boolean - initialValue?: string | null -} diff --git a/web/projects/ui/src/app/pages/diagnostic-routes/diagnostic-routing.module.ts b/web/projects/ui/src/app/pages/diagnostic-routes/diagnostic-routing.module.ts deleted file mode 100644 index 4409288c1..000000000 --- a/web/projects/ui/src/app/pages/diagnostic-routes/diagnostic-routing.module.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { NgModule } from '@angular/core' -import { RouterModule, Routes } from '@angular/router' - -const ROUTES: Routes = [ - { - path: '', - loadChildren: () => - import('./home/home.module').then(m => m.HomePageModule), - }, - { - path: 'logs', - loadChildren: () => - import('./logs/logs.module').then(m => m.LogsPageModule), - }, -] - -@NgModule({ - imports: [RouterModule.forChild(ROUTES)], - exports: [RouterModule], -}) -export class DiagnosticModule {} diff --git a/web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.ts b/web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.ts deleted file mode 100644 index 9040dc069..000000000 --- a/web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.ts +++ /dev/null @@ -1,222 +0,0 @@ -<<<<<<<< HEAD:web/projects/ui/src/app/routes/diagnostic/home/home.page.ts -import { TUI_CONFIRM } from '@taiga-ui/kit' -import { Component, Inject } from '@angular/core' -import { WINDOW } from '@ng-web-apis/common' -import { LoadingService } from '@start9labs/shared' -import { TuiDialogService } from '@taiga-ui/core' -import { filter } from 'rxjs' -import { DiagnosticService } from '../services/diagnostic.service' -======== -import { Component } from '@angular/core' -import { AlertController } from '@ionic/angular' -import { LoadingService } from '@start9labs/shared' -import { ApiService } from 'src/app/services/api/embassy-api.service' ->>>>>>>> 94a5075b6daa1375433420abf5d121171dae72cb:web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.ts - -@Component({ - selector: 'diagnostic-home', - templateUrl: 'home.page.html', - styleUrls: ['home.page.scss'], -}) -export class HomePage { - restarted = false - error?: { - code: number - problem: string - solution: string - details?: string - } - - constructor( - private readonly loader: LoadingService, -<<<<<<<< HEAD:web/projects/ui/src/app/routes/diagnostic/home/home.page.ts - private readonly api: DiagnosticService, - private readonly dialogs: TuiDialogService, - @Inject(WINDOW) private readonly window: Window, -======== - private readonly api: ApiService, - private readonly alertCtrl: AlertController, ->>>>>>>> 94a5075b6daa1375433420abf5d121171dae72cb:web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.ts - ) {} - - async ngOnInit() { - try { - const error = await this.api.diagnosticGetError() - // incorrect drive - if (error.code === 15) { - this.error = { - code: 15, - problem: 'Unknown storage drive detected', - solution: - 'To use a different storage drive, replace the current one and click RESTART SERVER below. To use the current storage drive, click USE CURRENT DRIVE below, then follow instructions. No data will be erased during this process.', - details: error.data?.details, - } - // no drive - } else if (error.code === 20) { - this.error = { - code: 20, - problem: 'Storage drive not found', - solution: - 'Insert your StartOS storage drive and click RESTART SERVER below.', - details: error.data?.details, - } - // drive corrupted - } else if (error.code === 25) { - this.error = { - code: 25, - problem: - 'Storage drive corrupted. This could be the result of data corruption or physical damage.', - solution: - 'It may or may not be possible to re-use this drive by reformatting and recovering from backup. To enter recovery mode, click ENTER RECOVERY MODE below, then follow instructions. No data will be erased during this step.', - details: error.data?.details, - } - // filesystem I/O error - disk needs repair - } else if (error.code === 2) { - this.error = { - code: 2, - problem: 'Filesystem I/O error.', - solution: - 'Repairing the disk could help resolve this issue. Please DO NOT unplug the drive or server during this time or the situation will become worse.', - details: error.data?.details, - } - // disk management error - disk needs repair - } else if (error.code === 48) { - this.error = { - code: 48, - problem: 'Disk management error.', - solution: - 'Repairing the disk could help resolve this issue. Please DO NOT unplug the drive or server during this time or the situation will become worse.', - details: error.data?.details, - } - } else { - this.error = { - code: error.code, - problem: error.message, - solution: 'Please contact support.', - details: error.data?.details, - } - } - } catch (e) { - console.error(e) - } - } - - async restart(): Promise { -<<<<<<<< HEAD:web/projects/ui/src/app/routes/diagnostic/home/home.page.ts - const loader = this.loader.open('').subscribe() -======== - const loader = this.loader.open('Loading...').subscribe() ->>>>>>>> 94a5075b6daa1375433420abf5d121171dae72cb:web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.ts - - try { - await this.api.diagnosticRestart() - this.restarted = true - } catch (e) { - console.error(e) - } finally { - loader.unsubscribe() - } - } - - async forgetDrive(): Promise { -<<<<<<<< HEAD:web/projects/ui/src/app/routes/diagnostic/home/home.page.ts - const loader = this.loader.open('').subscribe() -======== - const loader = this.loader.open('Loading...').subscribe() ->>>>>>>> 94a5075b6daa1375433420abf5d121171dae72cb:web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.ts - - try { - await this.api.diagnosticForgetDrive() - await this.api.diagnosticRestart() - this.restarted = true - } catch (e) { - console.error(e) - } finally { - loader.unsubscribe() - } - } - -<<<<<<<< HEAD:web/projects/ui/src/app/routes/diagnostic/home/home.page.ts - async presentAlertSystemRebuild() { - this.dialogs - .open(TUI_CONFIRM, { - label: 'Warning', - size: 's', - data: { - no: 'Cancel', - yes: 'Rebuild', - content: - '

This action will tear down all service containers and rebuild them from scratch. No data will be deleted.

A system rebuild can be useful if your system gets into a bad state, and it should only be performed if you are experiencing general performance or reliability issues.

It may take up to an hour to complete. During this time, you will lose all connectivity to your Start9 server.

', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => { - try { - this.systemRebuild() - } catch (e) { - console.error(e) - } - }) - } - -======== ->>>>>>>> 94a5075b6daa1375433420abf5d121171dae72cb:web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.ts - async presentAlertRepairDisk() { - this.dialogs - .open(TUI_CONFIRM, { - label: 'Warning', - size: 's', - data: { - no: 'Cancel', - yes: 'Repair', - content: - '

This action should only be executed if directed by a Start9 support specialist.

If anything happens to the device during the reboot, such as losing power or unplugging the drive, the filesystem will be in an unrecoverable state. Please proceed with caution.

', - }, - }) - .pipe(filter(Boolean)) - .subscribe(() => { - try { - this.repairDisk() - } catch (e) { - console.error(e) - } - }) - } - - refreshPage(): void { - this.window.location.reload() - } - -<<<<<<<< HEAD:web/projects/ui/src/app/routes/diagnostic/home/home.page.ts - private async systemRebuild(): Promise { - const loader = this.loader.open('').subscribe() - - try { - await this.api.systemRebuild() - await this.api.restart() - this.restarted = true - } catch (e) { - console.error(e) - } finally { - loader.unsubscribe() - } - } - - private async repairDisk(): Promise { - const loader = this.loader.open('').subscribe() -======== - private async repairDisk(): Promise { - const loader = this.loader.open('Loading...').subscribe() ->>>>>>>> 94a5075b6daa1375433420abf5d121171dae72cb:web/projects/ui/src/app/pages/diagnostic-routes/home/home.page.ts - - try { - await this.api.diagnosticRepairDisk() - await this.api.diagnosticRestart() - this.restarted = true - } catch (e) { - console.error(e) - } finally { - loader.unsubscribe() - } - } -} diff --git a/web/projects/ui/src/app/pages/diagnostic-routes/logs/logs.page.scss b/web/projects/ui/src/app/pages/diagnostic-routes/logs/logs.page.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/projects/ui/src/app/pages/init/init.module.ts b/web/projects/ui/src/app/pages/init/init.module.ts deleted file mode 100644 index 07dd71185..000000000 --- a/web/projects/ui/src/app/pages/init/init.module.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { CommonModule } from '@angular/common' -import { NgModule } from '@angular/core' -import { RouterModule, Routes } from '@angular/router' -import { TuiProgressModule } from '@taiga-ui/kit' -import { LogsModule } from 'src/app/pages/init/logs/logs.module' -import { InitPage } from './init.page' - -const routes: Routes = [ - { - path: '', - component: InitPage, - }, -] - -@NgModule({ - imports: [ - CommonModule, - LogsModule, - TuiProgressModule, - RouterModule.forChild(routes), - ], - declarations: [InitPage], -}) -export class InitPageModule {} diff --git a/web/projects/ui/src/app/pages/init/init.page.html b/web/projects/ui/src/app/pages/init/init.page.html deleted file mode 100644 index 5cd21bb07..000000000 --- a/web/projects/ui/src/app/pages/init/init.page.html +++ /dev/null @@ -1,18 +0,0 @@ -
-

- Initializing StartOS -

-
- Progress: {{ (progress.total * 100).toFixed(0) }}% -
- - -

-
- diff --git a/web/projects/ui/src/app/pages/init/init.page.scss b/web/projects/ui/src/app/pages/init/init.page.scss deleted file mode 100644 index 9fbf7098a..000000000 --- a/web/projects/ui/src/app/pages/init/init.page.scss +++ /dev/null @@ -1,23 +0,0 @@ -section { - border-radius: 0.25rem; - padding: 1rem; - margin: 1.5rem; - text-align: center; - /* TODO: Theme */ - background: #e0e0e0; - color: #333; - --tui-clear-inverse: rgba(0, 0, 0, 0.1); -} - -logs-window { - display: flex; - flex-direction: column; - height: 18rem; - padding: 1rem; - margin: 0 1.5rem auto; - text-align: left; - overflow: hidden; - border-radius: 2rem; - /* TODO: Theme */ - background: #181818; -} diff --git a/web/projects/ui/src/app/pages/init/init.page.ts b/web/projects/ui/src/app/pages/init/init.page.ts deleted file mode 100644 index 318881223..000000000 --- a/web/projects/ui/src/app/pages/init/init.page.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Component, inject } from '@angular/core' -import { InitService } from 'src/app/pages/init/init.service' - -@Component({ - selector: 'init-page', - templateUrl: 'init.page.html', - styleUrls: ['init.page.scss'], -}) -export class InitPage { - readonly progress$ = inject(InitService) -} diff --git a/web/projects/ui/src/app/pages/init/init.service.ts b/web/projects/ui/src/app/pages/init/init.service.ts deleted file mode 100644 index c98c0b11c..000000000 --- a/web/projects/ui/src/app/pages/init/init.service.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { inject, Injectable } from '@angular/core' -import { ErrorService } from '@start9labs/shared' -import { T } from '@start9labs/start-sdk' -import { - catchError, - defer, - EMPTY, - from, - map, - Observable, - startWith, - switchMap, - tap, -} from 'rxjs' -import { ApiService } from 'src/app/services/api/embassy-api.service' -import { StateService } from 'src/app/services/state.service' - -interface MappedProgress { - readonly total: number | null - readonly message: string -} - -@Injectable({ providedIn: 'root' }) -export class InitService extends Observable { - private readonly state = inject(StateService) - private readonly api = inject(ApiService) - private readonly errorService = inject(ErrorService) - private readonly progress$ = defer(() => - from(this.api.initGetProgress()), - ).pipe( - switchMap(({ guid, progress }) => - this.api - .openWebsocket$(guid, {}) - .pipe(startWith(progress)), - ), - map(({ phases, overall }) => { - return { - total: getOverallDecimal(overall), - message: phases - .filter( - ( - p, - ): p is { - name: string - progress: { - done: number - total: number | null - } - } => p.progress !== true && p.progress !== null, - ) - .map(p => `${p.name}${getPhaseBytes(p.progress)}`) - .join(', '), - } - }), - tap(({ total }) => { - if (total === 1) { - this.state.syncState() - } - }), - catchError(e => { - console.error(e) - return EMPTY - }), - ) - - constructor() { - super(subscriber => this.progress$.subscribe(subscriber)) - } -} - -function getOverallDecimal(progress: T.Progress): number { - if (progress === true) { - return 1 - } else if (!progress || !progress.total) { - return 0 - } else { - return progress.total && progress.done / progress.total - } -} - -function getPhaseBytes( - progress: - | false - | { - done: number - total: number | null - }, -): string { - return progress === false ? '' : `: (${progress.done}/${progress.total})` -} diff --git a/web/projects/ui/src/app/pages/init/logs/logs.component.ts b/web/projects/ui/src/app/pages/init/logs/logs.component.ts deleted file mode 100644 index de05d33bd..000000000 --- a/web/projects/ui/src/app/pages/init/logs/logs.component.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Component, ElementRef, inject } from '@angular/core' -import { INTERSECTION_ROOT } from '@ng-web-apis/intersection-observer' -import { LogsService } from 'src/app/pages/init/logs/logs.service' - -@Component({ - selector: 'logs-window', - templateUrl: 'logs.template.html', - styles: [ - ` - pre { - margin: 0; - } - `, - ], - providers: [ - { - provide: INTERSECTION_ROOT, - useExisting: ElementRef, - }, - ], -}) -export class LogsComponent { - readonly logs$ = inject(LogsService) - scroll = true - - scrollTo(bottom: HTMLElement) { - if (this.scroll) bottom.scrollIntoView() - } - - onBottom(entries: readonly IntersectionObserverEntry[]) { - this.scroll = entries[entries.length - 1].isIntersecting - } -} diff --git a/web/projects/ui/src/app/pages/init/logs/logs.module.ts b/web/projects/ui/src/app/pages/init/logs/logs.module.ts deleted file mode 100644 index ee4a1bc1d..000000000 --- a/web/projects/ui/src/app/pages/init/logs/logs.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { CommonModule } from '@angular/common' -import { NgModule } from '@angular/core' -import { IntersectionObserverModule } from '@ng-web-apis/intersection-observer' -import { MutationObserverModule } from '@ng-web-apis/mutation-observer' -import { TuiScrollbarModule } from '@taiga-ui/core' -import { NgDompurifyModule } from '@tinkoff/ng-dompurify' -import { LogsComponent } from './logs.component' - -@NgModule({ - imports: [ - CommonModule, - MutationObserverModule, - IntersectionObserverModule, - NgDompurifyModule, - TuiScrollbarModule, - ], - declarations: [LogsComponent], - exports: [LogsComponent], -}) -export class LogsModule {} diff --git a/web/projects/ui/src/app/pages/init/logs/logs.service.ts b/web/projects/ui/src/app/pages/init/logs/logs.service.ts deleted file mode 100644 index 2553e9872..000000000 --- a/web/projects/ui/src/app/pages/init/logs/logs.service.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { inject, Injectable } from '@angular/core' -import { Log, toLocalIsoString } from '@start9labs/shared' -import { - bufferTime, - defer, - filter, - map, - Observable, - scan, - switchMap, -} from 'rxjs' -import { ApiService } from 'src/app/services/api/embassy-api.service' - -var Convert = require('ansi-to-html') -var convert = new Convert({ - newline: true, - bg: 'transparent', - colors: { - 4: 'Cyan', - }, - escapeXML: true, -}) - -function convertAnsi(entries: readonly any[]): string { - return entries - .map( - ({ timestamp, message }) => - `${toLocalIsoString( - new Date(timestamp), - )}  ${convert.toHtml(message)}`, - ) - .join('
') -} - -@Injectable({ providedIn: 'root' }) -export class LogsService extends Observable { - private readonly api = inject(ApiService) - private readonly log$ = defer(() => - this.api.initFollowLogs({ boot: 0 }), - ).pipe( - switchMap(({ guid }) => this.api.openWebsocket$(guid, {})), - bufferTime(500), - filter(logs => !!logs.length), - map(convertAnsi), - scan((logs: readonly string[], log) => [...logs, log], []), - ) - - constructor() { - super(subscriber => this.log$.subscribe(subscriber)) - } -} diff --git a/web/projects/ui/src/app/pages/init/logs/logs.template.html b/web/projects/ui/src/app/pages/init/logs/logs.template.html deleted file mode 100644 index 24ea6d0c1..000000000 --- a/web/projects/ui/src/app/pages/init/logs/logs.template.html +++ /dev/null @@ -1,9 +0,0 @@ - -

-  
-
diff --git a/web/projects/ui/src/app/routes/diagnostic/diagnostic.module.ts b/web/projects/ui/src/app/routes/diagnostic/diagnostic.module.ts index c18f0f011..8acd99578 100644 --- a/web/projects/ui/src/app/routes/diagnostic/diagnostic.module.ts +++ b/web/projects/ui/src/app/routes/diagnostic/diagnostic.module.ts @@ -17,4 +17,4 @@ const ROUTES: Routes = [ @NgModule({ imports: [RouterModule.forChild(ROUTES)], }) -export class DiagnosticModule {} +export default class DiagnosticModule {} diff --git a/web/projects/ui/src/app/routes/diagnostic/home/home.page.html b/web/projects/ui/src/app/routes/diagnostic/home/home.page.html index 9accfe6ce..c4fc493c0 100644 --- a/web/projects/ui/src/app/routes/diagnostic/home/home.page.html +++ b/web/projects/ui/src/app/routes/diagnostic/home/home.page.html @@ -25,14 +25,6 @@ {{ error.code === 15 ? 'Setup Current Drive' : 'Enter Recovery Mode'}} - -
@@ -50,10 +50,10 @@ import { ConfigService } from 'src/app/services/config.service' styles: ['[tuiCell] { padding-inline: 0 }'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [CommonModule, EmverPipesModule, TuiTitle, TuiButton, TuiCell], + imports: [CommonModule, ExverPipesModule, TuiTitle, TuiButton, TuiCell], }) export class AboutComponent { - readonly server$ = inject(PatchDB).watch$('serverInfo') + readonly server$ = inject>(PatchDB).watch$('serverInfo') readonly copyService = inject(CopyService) readonly gitHash = inject(ConfigService).gitHash } diff --git a/web/projects/ui/src/app/routes/portal/components/header/connection.component.ts b/web/projects/ui/src/app/routes/portal/components/header/connection.component.ts index 0e8f65156..c2bf57508 100644 --- a/web/projects/ui/src/app/routes/portal/components/header/connection.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/header/connection.component.ts @@ -4,6 +4,7 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { PatchDB } from 'patch-db-client' import { combineLatest, map, Observable, startWith } from 'rxjs' import { ConnectionService } from 'src/app/services/connection.service' +import { NetworkService } from 'src/app/services/network.service' import { DataModel } from 'src/app/services/patch-db/data-model' @Component({ @@ -47,9 +48,9 @@ export class HeaderConnectionComponent { icon: string status: string }> = combineLatest([ - inject(ConnectionService).networkConnected$, - inject(ConnectionService).websocketConnected$.pipe(startWith(false)), - inject(PatchDB) + inject(NetworkService), + inject(ConnectionService), + inject>(PatchDB) .watch$('serverInfo', 'statusInfo') .pipe(startWith({ restarting: false, shuttingDown: false })), ]).pipe( diff --git a/web/projects/ui/src/app/routes/portal/components/header/header.component.ts b/web/projects/ui/src/app/routes/portal/components/header/header.component.ts index 0e663eca8..800e207c3 100644 --- a/web/projects/ui/src/app/routes/portal/components/header/header.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/header/header.component.ts @@ -166,7 +166,7 @@ import { BreadcrumbsService } from 'src/app/services/breadcrumbs.service' export class HeaderComponent { readonly options = OPTIONS readonly breadcrumbs$ = inject(BreadcrumbsService) - readonly snekScore$ = inject(PatchDB).watch$( + readonly snekScore$ = inject>(PatchDB).watch$( 'ui', 'gaming', 'snake', diff --git a/web/projects/ui/src/app/routes/portal/components/header/mobile.component.ts b/web/projects/ui/src/app/routes/portal/components/header/mobile.component.ts index 8a4b1d3df..433d615f5 100644 --- a/web/projects/ui/src/app/routes/portal/components/header/mobile.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/header/mobile.component.ts @@ -6,7 +6,7 @@ import { Input, } from '@angular/core' import { RouterLink } from '@angular/router' -import { WINDOW } from '@ng-web-apis/common' +import { WA_WINDOW } from '@ng-web-apis/common' import { Breadcrumb } from 'src/app/services/breadcrumbs.service' @Component({ @@ -68,7 +68,7 @@ import { Breadcrumb } from 'src/app/services/breadcrumbs.service' imports: [TuiIcon, RouterLink], }) export class HeaderMobileComponent { - private readonly win = inject(WINDOW) + private readonly win = inject(WA_WINDOW) @Input() headerMobile: readonly Breadcrumb[] | null = [] diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/address-item.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/address-item.component.ts index ed6c35bf1..70eafc03e 100644 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/address-item.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/address-item.component.ts @@ -7,7 +7,7 @@ import { inject, Input, } from '@angular/core' -import { WINDOW } from '@ng-web-apis/common' +import { WA_WINDOW } from '@ng-web-apis/common' import { CopyService } from '@start9labs/shared' import { TuiDialogService, TuiTitle, TuiButton } from '@taiga-ui/core' import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' @@ -68,7 +68,7 @@ import { AddressesService } from './interface.utils' changeDetection: ChangeDetectionStrategy.OnPush, }) export class AddressItemComponent { - private readonly window = inject(WINDOW) + private readonly window = inject(WA_WINDOW) private readonly dialogs = inject(TuiDialogService) readonly service = inject(AddressesService) diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/interface.component.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/interface.component.ts index f6435d9b0..dd0a8d86d 100644 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/interface.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/interface.component.ts @@ -95,7 +95,10 @@ import { AddressDetails } from './interface.utils' ], }) export class InterfaceComponent { - readonly network$ = inject(PatchDB).watch$('serverInfo', 'network') + readonly network$ = inject>(PatchDB).watch$( + 'serverInfo', + 'network', + ) @Input() packageContext?: { packageId: string diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/interface.utils.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/interface.utils.ts index 881978a33..356c11ac8 100644 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/interface.utils.ts +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/interface.utils.ts @@ -53,9 +53,9 @@ export type AddressDetails = { url: string } -export function getAddresses( - serviceInterface: T.ServiceInterfaceWithHostInfo, -): { +// @TODO Matt these types have change significantly +export function getAddresses(serviceInterface: any): { + // T.ServiceInterface): { clearnet: AddressDetails[] local: AddressDetails[] tor: AddressDetails[] @@ -76,7 +76,7 @@ export function getAddresses( const local: AddressDetails[] = [] const tor: AddressDetails[] = [] - hostnames.forEach(h => { + hostnames.forEach((h: any) => { let scheme = '' let port = '' diff --git a/web/projects/ui/src/app/routes/portal/components/logs/logs.pipe.ts b/web/projects/ui/src/app/routes/portal/components/logs/logs.pipe.ts index 55f7042f4..8fdd43169 100644 --- a/web/projects/ui/src/app/routes/portal/components/logs/logs.pipe.ts +++ b/web/projects/ui/src/app/routes/portal/components/logs/logs.pipe.ts @@ -51,7 +51,7 @@ export class LogsPipe implements PipeTransform { ), ).pipe( catchError(() => - this.connection.connected$.pipe( + this.connection.pipe( tap(v => this.logs.status$.next(v ? 'reconnecting' : 'disconnected')), filter(Boolean), take(1), diff --git a/web/projects/ui/src/app/routes/portal/modals/config.component.ts b/web/projects/ui/src/app/routes/portal/modals/config.component.ts index 521d958c4..b48362783 100644 --- a/web/projects/ui/src/app/routes/portal/modals/config.component.ts +++ b/web/projects/ui/src/app/routes/portal/modals/config.component.ts @@ -6,7 +6,7 @@ import { isEmptyObject, LoadingService, } from '@start9labs/shared' -import { CT } from '@start9labs/start-sdk' +import { CT, T } from '@start9labs/start-sdk' import { TuiDialogContext, TuiDialogService, @@ -199,8 +199,6 @@ export class ConfigModal { const loader = new Subscription() try { - await this.uploadFiles(config, loader) - if (hasCurrentDeps(this.pkgId, await getAllPackages(this.patchDb))) { await this.configureDeps(config, loader) } else { @@ -213,24 +211,6 @@ export class ConfigModal { } } - private async uploadFiles(config: Record, loader: Subscription) { - loader.unsubscribe() - loader.closed = false - - // TODO: Could be nested files - const keys = Object.keys(config).filter(key => config[key] instanceof File) - const message = `Uploading File${keys.length > 1 ? 's' : ''}...` - - if (!keys.length) return - - loader.add(this.loader.open(message).subscribe()) - - const hashes = await Promise.all( - keys.map(key => this.embassyApi.uploadFile(config[key])), - ) - keys.forEach((key, i) => (config[key] = hashes[i])) - } - private async configureDeps( config: Record, loader: Subscription, @@ -261,11 +241,11 @@ export class ConfigModal { this.context.$implicit.complete() } - private async approveBreakages(breakages: Breakages): Promise { + private async approveBreakages(breakages: T.PackageId[]): Promise { const packages = await getAllPackages(this.patchDb) const message = 'As a result of this change, the following services will no longer work properly and may crash:
    ' - const content = `${message}${Object.keys(breakages).map( + const content = `${message}${breakages.map( id => `
  • ${getManifest(packages[id]).title}
  • `, )}
` const data: TuiConfirmData = { content, yes: 'Continue', no: 'Cancel' } diff --git a/web/projects/ui/src/app/routes/portal/portal.component.ts b/web/projects/ui/src/app/routes/portal/portal.component.ts index d57b31d40..ef0bd8477 100644 --- a/web/projects/ui/src/app/routes/portal/portal.component.ts +++ b/web/projects/ui/src/app/routes/portal/portal.component.ts @@ -58,5 +58,5 @@ export class PortalComponent { this.breadcrumbs.update(e.url.replace('/portal/service/', '')) }) - readonly name$ = inject(PatchDB).watch$('ui', 'name') + readonly name$ = inject>(PatchDB).watch$('ui', 'name') } diff --git a/web/projects/ui/src/app/routes/portal/routes/dashboard/service.component.ts b/web/projects/ui/src/app/routes/portal/routes/dashboard/service.component.ts index 7589e1066..71cdd1cc6 100644 --- a/web/projects/ui/src/app/routes/portal/routes/dashboard/service.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/dashboard/service.component.ts @@ -108,7 +108,7 @@ export class ServiceComponent implements OnChanges { @Input() depErrors?: PkgDependencyErrors - readonly connected$ = inject(ConnectionService).connected$ + readonly connected$ = inject(ConnectionService) get installed(): boolean { return this.pkg.stateInfo.state !== 'installed' diff --git a/web/projects/ui/src/app/routes/portal/routes/dashboard/services.service.ts b/web/projects/ui/src/app/routes/portal/routes/dashboard/services.service.ts index 6a50eacd4..7351e72f2 100644 --- a/web/projects/ui/src/app/routes/portal/routes/dashboard/services.service.ts +++ b/web/projects/ui/src/app/routes/portal/routes/dashboard/services.service.ts @@ -11,7 +11,7 @@ import { getManifest } from 'src/app/utils/get-package-data' providedIn: 'root', }) export class ServicesService extends Observable { - private readonly services$ = inject(PatchDB) + private readonly services$ = inject>(PatchDB) .watch$('packageData') .pipe( map(pkgs => diff --git a/web/projects/ui/src/app/routes/portal/routes/dashboard/ui.component.ts b/web/projects/ui/src/app/routes/portal/routes/dashboard/ui.component.ts index b35df52ef..b679d19b2 100644 --- a/web/projects/ui/src/app/routes/portal/routes/dashboard/ui.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/dashboard/ui.component.ts @@ -64,7 +64,7 @@ export class UILaunchComponent { @Input() pkg!: PackageDataEntry - get interfaces(): readonly T.ServiceInterfaceWithHostInfo[] { + get interfaces(): readonly T.ServiceInterface[] { return this.getInterfaces(this.pkg) } @@ -72,18 +72,20 @@ export class UILaunchComponent { return this.pkg.status.main.status === 'running' } - get first(): T.ServiceInterfaceWithHostInfo | undefined { + get first(): T.ServiceInterface | undefined { return this.interfaces[0] } @tuiPure - getInterfaces(pkg?: PackageDataEntry): T.ServiceInterfaceWithHostInfo[] { + getInterfaces(pkg?: PackageDataEntry): T.ServiceInterface[] { return pkg ? Object.values(pkg.serviceInterfaces).filter(({ type }) => type === 'ui') : [] } - getHref(info?: T.ServiceInterfaceWithHostInfo): string | null { - return info && this.isRunning ? this.config.launchableAddress(info) : null + getHref(info?: T.ServiceInterface): string | null { + return info && this.isRunning + ? this.config.launchableAddress(info, this.pkg.hosts) + : null } } diff --git a/web/projects/ui/src/app/routes/portal/routes/service/components/dependency.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/components/dependency.component.ts index 00f139c96..d888e6e97 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/components/dependency.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/components/dependency.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { EmverPipesModule } from '@start9labs/shared' +import { ExverPipesModule } from '@start9labs/shared' import { TuiIcon } from '@taiga-ui/core' import { DependencyInfo } from '../types/dependency-info' @@ -14,10 +14,8 @@ import { DependencyInfo } from '../types/dependency-info' } {{ dep.title }} -
{{ dep.version | displayEmver }}
-
- {{ dep.errorText || 'Satisfied' }} -
+
{{ dep.version }}
+
{{ dep.errorText || 'Satisfied' }}
@if (dep.actionText) {
@@ -41,7 +39,7 @@ import { DependencyInfo } from '../types/dependency-info' ], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [EmverPipesModule, TuiIcon], + imports: [ExverPipesModule, TuiIcon], }) export class ServiceDependencyComponent { @Input({ required: true, alias: 'serviceDependency' }) diff --git a/web/projects/ui/src/app/routes/portal/routes/service/components/health-checks.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/components/health-checks.component.ts index b82d127a4..e559e8a28 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/components/health-checks.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/components/health-checks.component.ts @@ -33,5 +33,5 @@ export class ServiceHealthChecksComponent { @Input({ required: true }) checks: readonly T.HealthCheckResult[] = [] - readonly connected$ = inject(ConnectionService).connected$ + readonly connected$ = inject(ConnectionService) } diff --git a/web/projects/ui/src/app/routes/portal/routes/service/components/interface-list-item.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/components/interface-list-item.component.ts index 26d64536d..334ea7e14 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/components/interface-list-item.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/components/interface-list-item.component.ts @@ -9,6 +9,7 @@ import { } from '@angular/core' import { map, timer } from 'rxjs' import { ConfigService } from 'src/app/services/config.service' +import { PackageDataEntry } from 'src/app/services/patch-db/data-model' import { ExtendedInterfaceInfo } from '../pipes/interface-info.pipe' @Component({ @@ -56,9 +57,12 @@ import { ExtendedInterfaceInfo } from '../pipes/interface-info.pipe' export class ServiceInterfaceListItemComponent { private readonly config = inject(ConfigService) - @Input({ required: true, alias: 'serviceInterfaceListItem' }) + @Input({ required: true }) info!: ExtendedInterfaceInfo + @Input({ required: true }) + pkg!: PackageDataEntry + @Input() disabled = false @@ -68,6 +72,8 @@ export class ServiceInterfaceListItemComponent { ) get href(): string | null { - return this.disabled ? null : this.config.launchableAddress(this.info) + return this.disabled + ? null + : this.config.launchableAddress(this.info, this.pkg.hosts) } } diff --git a/web/projects/ui/src/app/routes/portal/routes/service/components/interface-list.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/components/interface-list.component.ts index acd13ec08..de14745d6 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/components/interface-list.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/components/interface-list.component.ts @@ -11,7 +11,9 @@ import { ServiceInterfaceListItemComponent } from './interface-list-item.compone @for (info of pkg | interfaceInfo; track $index) { diff --git a/web/projects/ui/src/app/routes/portal/routes/service/components/status.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/components/status.component.ts index ed24b70a6..196115c91 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/components/status.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/components/status.component.ts @@ -1,4 +1,3 @@ -import { TuiLoader, TuiIcon } from '@taiga-ui/core' import { CommonModule } from '@angular/common' import { ChangeDetectionStrategy, @@ -6,10 +5,10 @@ import { HostBinding, Input, } from '@angular/core' +import { TuiIcon, TuiLoader } from '@taiga-ui/core' +import { InstallingInfo } from 'src/app/services/patch-db/data-model' import { StatusRendering } from 'src/app/services/pkg-status-rendering.service' import { InstallingProgressDisplayPipe } from '../pipes/install-progress.pipe' -import { InstallingInfo } from 'src/app/services/patch-db/data-model' -import { UnitConversionPipesModule } from '@start9labs/shared' @Component({ selector: 'service-status', @@ -27,9 +26,6 @@ import { UnitConversionPipesModule } from '@start9labs/shared' @if (rendering.showDots) { } - @if (sigtermTimeout && (sigtermTimeout | durationToSeconds) > 30) { -
This may take a while
- } } `, styles: [ @@ -58,13 +54,7 @@ import { UnitConversionPipesModule } from '@start9labs/shared' ], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [ - CommonModule, - InstallingProgressDisplayPipe, - UnitConversionPipesModule, - TuiIcon, - TuiLoader, - ], + imports: [CommonModule, InstallingProgressDisplayPipe, TuiIcon, TuiLoader], }) export class ServiceStatusComponent { @Input({ required: true }) @@ -76,8 +66,6 @@ export class ServiceStatusComponent { @Input() connected = false - @Input() sigtermTimeout?: string | null = null - @HostBinding('class') get class(): string | null { if (!this.connected) return null diff --git a/web/projects/ui/src/app/routes/portal/routes/service/pipes/interface-info.pipe.ts b/web/projects/ui/src/app/routes/portal/routes/service/pipes/interface-info.pipe.ts index 9a831ad7a..2f1604732 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/pipes/interface-info.pipe.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/pipes/interface-info.pipe.ts @@ -2,7 +2,7 @@ import { Pipe, PipeTransform } from '@angular/core' import { T } from '@start9labs/start-sdk' import { PackageDataEntry } from 'src/app/services/patch-db/data-model' -export interface ExtendedInterfaceInfo extends T.ServiceInterfaceWithHostInfo { +export interface ExtendedInterfaceInfo extends T.ServiceInterface { id: string icon: string color: string diff --git a/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-additional.pipe.ts b/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-additional.pipe.ts index dfaa39140..a78837f6b 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-additional.pipe.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-additional.pipe.ts @@ -74,11 +74,7 @@ export class ToAdditionalPipe implements PipeTransform { label: 'License', size: 'l', data: { - content: from( - this.api.getStatic( - `/public/package-data/${id}/${version}/LICENSE.md`, - ), - ), + content: from(this.api.getStaticInstalled(id, 'LICENSE.md')), }, }) .subscribe() diff --git a/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-menu.pipe.ts b/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-menu.pipe.ts index cd438ddcc..132cbc860 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-menu.pipe.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/pipes/to-menu.pipe.ts @@ -98,13 +98,13 @@ export class ToMenuPipe implements PipeTransform { }) .subscribe(), }, - pkg.marketplaceUrl + pkg.registry ? { icon: '@tui.shopping-bag', name: 'Marketplace Listing', description: `View ${manifest.title} on the Marketplace`, routerLink: `/portal/system/marketplace`, - params: { url: pkg.marketplaceUrl, id: manifest.id }, + params: { url: pkg.registry, id: manifest.id }, } : { icon: '@tui.shopping-bag', @@ -114,7 +114,7 @@ export class ToMenuPipe implements PipeTransform { ] } - private showInstructions({ title, id, version }: T.Manifest) { + private showInstructions({ title, id }: T.Manifest) { this.api .setDbValue(['ack-instructions', id], true) .catch(e => console.error('Failed to mark instructions as seen', e)) @@ -124,11 +124,7 @@ export class ToMenuPipe implements PipeTransform { label: `${title} instructions`, size: 'l', data: { - content: from( - this.api.getStatic( - `/public/package-data/${id}/${version}/INSTRUCTIONS.md`, - ), - ), + content: from(this.api.getStaticInstalled(id, 'instructions.md')), }, }) .subscribe() diff --git a/web/projects/ui/src/app/routes/portal/routes/service/routes/interface.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/routes/interface.component.ts index 48d0a8f05..530aa886c 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/routes/interface.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/routes/interface.component.ts @@ -28,7 +28,7 @@ export class ServiceInterfaceRoute { interfaceId: this.route.snapshot.paramMap.get('interfaceId') || '', } - readonly interfaceInfo$ = inject(PatchDB) + readonly interfaceInfo$ = inject>(PatchDB) .watch$( 'packageData', this.context.packageId, diff --git a/web/projects/ui/src/app/routes/portal/routes/service/routes/outlet.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/routes/outlet.component.ts index 66fdd7c08..0193747d7 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/routes/outlet.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/routes/outlet.component.ts @@ -16,7 +16,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model' imports: [CommonModule, RouterOutlet], }) export class ServiceOutletComponent { - private readonly patch = inject(PatchDB) + private readonly patch = inject>(PatchDB) private readonly route = inject(ActivatedRoute) private readonly router = inject(Router) diff --git a/web/projects/ui/src/app/routes/portal/routes/service/routes/service.component.ts b/web/projects/ui/src/app/routes/portal/routes/service/routes/service.component.ts index 3372dc146..d35320cb1 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/routes/service.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/routes/service.component.ts @@ -47,11 +47,6 @@ import { DependencyInfo } from '../types/dependency-info' [connected]="!!(connected$ | async)" [installingInfo]="service.pkg.stateInfo.installingInfo" [rendering]="getRendering(service.status)" - [sigtermTimeout]=" - service.pkg.status.main.status === 'stopping' - ? service.pkg.status.main.timeout - : null - " /> @if (isInstalled(service) && (connected$ | async)) { @@ -164,14 +159,14 @@ import { DependencyInfo } from '../types/dependency-info' ], }) export class ServiceRoute { - private readonly patch = inject(PatchDB) + private readonly patch = inject>(PatchDB) private readonly pkgId$ = inject(ActivatedRoute).paramMap.pipe( map(params => params.get('pkgId')!), ) private readonly depErrorService = inject(DepErrorService) private readonly router = inject(Router) private readonly formDialog = inject(FormDialogService) - readonly connected$ = inject(ConnectionService).connected$ + readonly connected$ = inject(ConnectionService) readonly service$ = this.pkgId$.pipe( switchMap(pkgId => @@ -232,11 +227,11 @@ export class ServiceRoute { depErrors, ) - const { title, icon, versionSpec } = pkg.currentDependencies[depId] + const { title, icon, versionRange } = pkg.currentDependencies[depId] return { id: depId, - version: versionSpec, + version: versionRange, title, icon, errorText: errorText @@ -322,7 +317,7 @@ export class ServiceRoute { const dependentInfo: DependentInfo = { id: manifest.id, title: manifest.title, - version: pkg.currentDependencies[depId].versionSpec, + version: pkg.currentDependencies[depId].versionRange, } const navigationExtras: NavigationExtras = { // @TODO state not being used by marketplace component. Maybe it is not important to use. diff --git a/web/projects/ui/src/app/routes/portal/routes/service/types/dependency-info.ts b/web/projects/ui/src/app/routes/portal/routes/service/types/dependency-info.ts index a28c44a24..cedf01ebc 100644 --- a/web/projects/ui/src/app/routes/portal/routes/service/types/dependency-info.ts +++ b/web/projects/ui/src/app/routes/portal/routes/service/types/dependency-info.ts @@ -1,7 +1,7 @@ export interface DependencyInfo { id: string - title: string - icon: string + title: string | null + icon: string | null version: string errorText: string actionText: string diff --git a/web/projects/ui/src/app/routes/portal/routes/system/backups/components/status.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/backups/components/status.component.ts index db296a08f..769ad69d1 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/backups/components/status.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/backups/components/status.component.ts @@ -4,7 +4,7 @@ import { inject, Input, } from '@angular/core' -import { Emver } from '@start9labs/shared' +import { Exver } from '@start9labs/shared' import { TuiIcon } from '@taiga-ui/core' import { BackupTarget } from 'src/app/services/api/api.types' import { BackupType } from '../types/backup-type' @@ -21,7 +21,7 @@ import { BackupType } from '../types/backup-type' imports: [TuiIcon], }) export class BackupsStatusComponent { - private readonly emver = inject(Emver) + private readonly exver = inject(Exver) @Input({ required: true }) type!: BackupType @Input({ required: true }) target!: BackupTarget @@ -61,9 +61,8 @@ export class BackupsStatusComponent { } private get hasBackup(): boolean { - return ( - !!this.target.startOs && - this.emver.compare(this.target.startOs.version, '0.3.0') !== -1 - ) + return !!this.target.startOs + // @TODO Matt types changed + // && this.exver.compareExver(this.target.startOs.version, '0.3.0') !== -1 } } diff --git a/web/projects/ui/src/app/routes/portal/routes/system/backups/components/upcoming.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/backups/components/upcoming.component.ts index 375a0b827..af5978170 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/backups/components/upcoming.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/backups/components/upcoming.component.ts @@ -95,7 +95,7 @@ import { GetBackupIconPipe } from '../pipes/get-backup-icon.pipe' }) export class BackupsUpcomingComponent { readonly current = toSignal( - inject(PatchDB) + inject>(PatchDB) .watch$('serverInfo', 'statusInfo', 'currentBackup', 'job') .pipe(map(job => job || {})), ) diff --git a/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/backup.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/backup.component.ts index 7387f2361..3c792878e 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/backup.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/backup.component.ts @@ -77,7 +77,7 @@ interface Package { imports: [FormsModule, TuiButton, TuiGroup, TuiLoader, TuiBlock, TuiCheckbox], }) export class BackupsBackupModal { - private readonly patch = inject(PatchDB) + private readonly patch = inject>(PatchDB) readonly context = inject>( POLYMORPHEUS_CONTEXT, diff --git a/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/recover.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/recover.component.ts index 6017365f0..77c726154 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/recover.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/recover.component.ts @@ -81,7 +81,7 @@ export class BackupsRecoverModal { private readonly context = inject>(POLYMORPHEUS_CONTEXT) - readonly packageData$ = inject(PatchDB) + readonly packageData$ = inject>(PatchDB) .watch$('packageData') .pipe(take(1)) @@ -118,12 +118,13 @@ export class BackupsRecoverModal { const ids = options.filter(({ checked }) => !!checked).map(({ id }) => id) const loader = this.loader.open('Initializing...').subscribe() - const { targetId, password } = this.context.data + const { targetId, serverId, password } = this.context.data try { await this.api.restorePackages({ ids, targetId, + serverId, password, }) diff --git a/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/servers.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/servers.component.ts new file mode 100644 index 000000000..aee420d52 --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/servers.component.ts @@ -0,0 +1,30 @@ +import { Component, inject } from '@angular/core' +import { ServerComponent, StartOSDiskInfo } from '@start9labs/shared' +import { TuiDialogContext } from '@taiga-ui/core' +import { + POLYMORPHEUS_CONTEXT, + PolymorpheusComponent, +} from '@taiga-ui/polymorpheus' + +interface Data { + servers: StartOSDiskInfo[] +} + +@Component({ + standalone: true, + template: ` + @for (server of context.data.servers; track $index) { + + } + `, + imports: [ServerComponent], +}) +export class ServersComponent { + readonly context = + inject>(POLYMORPHEUS_CONTEXT) +} + +export const SERVERS = new PolymorpheusComponent(ServersComponent) diff --git a/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/targets.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/targets.component.ts index 28e68eb45..4d9fdc98a 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/targets.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/backups/modals/targets.component.ts @@ -171,8 +171,8 @@ export class BackupsTargetsModal implements OnInit { text: 'Save', handler: ({ type }: BackupConfig) => this.add( - type[CT.unionSelectKey] === 'cifs' ? 'cifs' : 'cloud', - type[CT.unionValueKey], + type.selection === 'cifs' ? 'cifs' : 'cloud', + type.value, ), }, ], diff --git a/web/projects/ui/src/app/routes/portal/routes/system/backups/pipes/to-options.pipe.ts b/web/projects/ui/src/app/routes/portal/routes/system/backups/pipes/to-options.pipe.ts index a7fdbd99e..d9d5f900d 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/backups/pipes/to-options.pipe.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/backups/pipes/to-options.pipe.ts @@ -26,7 +26,7 @@ export class ToOptionsPipe implements PipeTransform { id, installed: !!packageData[id], checked: false, - newerOS: this.compare(packageBackups[id].osVersion), + newerStartOs: this.compare(packageBackups[id].osVersion), })) .sort((a, b) => b.title.toLowerCase() > a.title.toLowerCase() ? -1 : 1, diff --git a/web/projects/ui/src/app/routes/portal/routes/system/backups/services/restore.service.ts b/web/projects/ui/src/app/routes/portal/routes/system/backups/services/restore.service.ts index 6574737bd..84534cc69 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/backups/services/restore.service.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/backups/services/restore.service.ts @@ -1,7 +1,11 @@ import { inject, Injectable } from '@angular/core' import { Router } from '@angular/router' import * as argon2 from '@start9labs/argon2' -import { ErrorService, LoadingService } from '@start9labs/shared' +import { + ErrorService, + LoadingService, + StartOSDiskInfo, +} from '@start9labs/shared' import { TuiDialogOptions, TuiDialogService } from '@taiga-ui/core' import { catchError, @@ -18,10 +22,11 @@ import { PROMPT, PromptOptions, } from 'src/app/routes/portal/modals/prompt.component' -import { ApiService } from 'src/app/services/api/embassy-api.service' import { BackupTarget } from 'src/app/services/api/api.types' -import { TARGET, TARGET_RESTORE } from '../modals/target.component' +import { ApiService } from 'src/app/services/api/embassy-api.service' import { RECOVER } from '../modals/recover.component' +import { SERVERS } from '../modals/servers.component' +import { TARGET, TARGET_RESTORE } from '../modals/target.component' import { RecoverData } from '../types/recover-data' @Injectable({ @@ -38,23 +43,33 @@ export class BackupsRestoreService { this.dialogs .open(TARGET, TARGET_RESTORE) .pipe( + // @TODO Alex implement servers switchMap(target => - this.dialogs.open(PROMPT, PROMPT_OPTIONS).pipe( - exhaustMap(password => - this.getRecoverData( - target.id, - password, - target.startOs?.passwordHash || '', + this.dialogs + .open(SERVERS, { + data: { servers: [] }, + }) + .pipe( + switchMap(({ id, passwordHash }) => + this.dialogs.open(PROMPT, PROMPT_OPTIONS).pipe( + exhaustMap(password => + this.getRecoverData( + target.id, + id, + password, + passwordHash || '', + ), + ), + take(1), + switchMap(data => + this.dialogs.open(RECOVER, { + label: 'Select Services to Restore', + data, + }), + ), + ), ), ), - take(1), - switchMap(data => - this.dialogs.open(RECOVER, { - label: 'Select Services to Restore', - data, - }), - ), - ), ), ) .subscribe(() => { @@ -64,6 +79,7 @@ export class BackupsRestoreService { private getRecoverData( targetId: string, + serverId: string, password: string, hash: string, ): Observable { @@ -81,7 +97,7 @@ export class BackupsRestoreService { return EMPTY }), - map(backupInfo => ({ targetId, password, backupInfo })), + map(backupInfo => ({ targetId, password, backupInfo, serverId })), ) } } diff --git a/web/projects/ui/src/app/routes/portal/routes/system/backups/types/backup-config.ts b/web/projects/ui/src/app/routes/portal/routes/system/backups/types/backup-config.ts index a3c3ea0b3..3ccfd0aaf 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/backups/types/backup-config.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/backups/types/backup-config.ts @@ -1,16 +1,15 @@ -import { CT } from '@start9labs/start-sdk' import { RR } from 'src/app/services/api/api.types' export type BackupConfig = | { type: { - [CT.unionSelectKey]: 'dropbox' | 'google-drive' - [CT.unionValueKey]: RR.AddCloudBackupTargetReq + selection: 'dropbox' | 'google-drive' + value: RR.AddCloudBackupTargetReq } } | { type: { - [CT.unionSelectKey]: 'cifs' - [CT.unionValueKey]: RR.AddCifsBackupTargetReq + selection: 'cifs' + value: RR.AddCifsBackupTargetReq } } diff --git a/web/projects/ui/src/app/routes/portal/routes/system/backups/types/recover-data.ts b/web/projects/ui/src/app/routes/portal/routes/system/backups/types/recover-data.ts index 7823451ac..71103a0c3 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/backups/types/recover-data.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/backups/types/recover-data.ts @@ -2,6 +2,7 @@ import { BackupInfo } from 'src/app/services/api/api.types' export interface RecoverData { targetId: string + serverId: string backupInfo: BackupInfo password: string } diff --git a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/components/controls.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/components/controls.component.ts index 2e4717016..2587691a5 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/components/controls.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/components/controls.component.ts @@ -12,12 +12,12 @@ import { MarketplacePkg, } from '@start9labs/marketplace' import { - Emver, + Exver, ErrorService, isEmptyObject, LoadingService, sameUrl, - EmverPipesModule, + ExverPipesModule, } from '@start9labs/shared' import { PatchDB } from 'patch-db-client' import { firstValueFrom } from 'rxjs' @@ -41,7 +41,7 @@ import { ToManifestPipe } from 'src/app/routes/portal/pipes/to-manifest' localPkg.stateInfo.state === 'installed' && (localPkg | toManifest); as localManifest ) { - @switch (localManifest.version | compareEmver: pkg.manifest.version) { + @switch (localManifest.version | compareExver: pkg.version) { @case (1) { diff --git a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/components/tile.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/components/tile.component.ts index 8e6e51996..5e3fb7da9 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/components/tile.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/components/tile.component.ts @@ -22,11 +22,11 @@ import { MarketplaceControlsComponent } from './controls.component' @@ -45,7 +45,7 @@ import { MarketplaceControlsComponent } from './controls.component' slot="controls" class="controls-wrapper" [pkg]="pkg" - [localPkg]="pkg.manifest.id | toLocal | async" + [localPkg]="pkg.id | toLocal | async" /> @@ -125,7 +125,7 @@ export class MarketplaceTileComponent { toggle(open: boolean) { this.router.navigate([], { - queryParams: { id: open ? this.pkg.manifest.id : null }, + queryParams: { id: open ? this.pkg.id : null }, }) } } diff --git a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/modals/preview.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/modals/preview.component.ts index 5f90da116..6fd9cc1c1 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/marketplace/modals/preview.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/marketplace/modals/preview.component.ts @@ -6,6 +6,8 @@ import { Input, TemplateRef, } from '@angular/core' +import { FormsModule } from '@angular/forms' +import { Router } from '@angular/router' import { AboutModule, AbstractMarketplaceService, @@ -14,21 +16,17 @@ import { MarketplaceDependenciesComponent, MarketplacePackageHeroComponent, MarketplacePkg, - ReleaseNotesModule, - StoreIdentity, } from '@start9labs/marketplace' -import { displayEmver, Emver, SharedPipesModule } from '@start9labs/shared' -import { BehaviorSubject, filter, switchMap, tap } from 'rxjs' +import { Exver, SharedPipesModule } from '@start9labs/shared' import { + TuiButton, TuiDialogContext, TuiDialogService, - TuiLoader, TuiIcon, - TuiButton, + TuiLoader, } from '@taiga-ui/core' import { TuiRadioList, TuiStringifyContentPipe } from '@taiga-ui/kit' -import { FormsModule } from '@angular/forms' -import { Router } from '@angular/router' +import { BehaviorSubject, filter, switchMap, tap } from 'rxjs' @Component({ selector: 'marketplace-preview', @@ -44,13 +42,12 @@ import { Router } from '@angular/router'
- @if (!(pkg.manifest.dependencies | empty)) { + @if (!(pkg.dependencyMetadata | empty)) { } - - +
- @if (!(package.manifest.dependencies | empty)) { + @if (!(package.dependencyMetadata | empty)) { } @@ -93,18 +93,18 @@ export class SideloadPackageComponent { private readonly errorService = inject(ErrorService) private readonly router = inject(Router) private readonly alerts = inject(TuiAlertService) - private readonly emver = inject(Emver) + private readonly exver = inject(Exver) readonly button$ = combineLatest([ inject(ClientStorageService).showDevTools$, - inject(PatchDB) + inject>(PatchDB) .watch$('packageData') .pipe( map(local => - local[this.package.manifest.id] - ? this.emver.compare( - getManifest(local[this.package.manifest.id]).version, - this.package.manifest.version, + local[this.package.id] + ? this.exver.compareExver( + getManifest(local[this.package.id]).version, + this.package.version, ) : null, ), @@ -132,14 +132,12 @@ export class SideloadPackageComponent { async upload() { const loader = this.loader.open('Uploading package').subscribe() - const { manifest, icon } = this.package - const { size } = this.file try { - const pkg = await this.api.sideloadPackage({ manifest, icon, size }) + const { upload } = await this.api.sideloadPackage() - await this.api.uploadPackage(pkg, this.file) - await this.router.navigate(['/portal/service', manifest.id]) + await this.api.uploadPackage(upload, this.file).catch(console.error) + await this.router.navigate(['/portal/service', this.package.id]) this.alerts .open('Package uploaded successfully', { status: 'success' }) diff --git a/web/projects/ui/src/app/routes/portal/routes/system/sideload/sideload.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/sideload/sideload.component.ts index 24aece782..932cd9d19 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/sideload/sideload.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/sideload/sideload.component.ts @@ -12,8 +12,6 @@ import { Subject } from 'rxjs' import { ConfigService } from 'src/app/services/config.service' import { SideloadPackageComponent } from './package.component' -import { parseS9pk, validateS9pk } from './sideload.utils' - @Component({ template: ` @@ -105,13 +103,14 @@ export default class SideloadComponent { this.package = null } + // @TODO Alex refactor sideload async onFile(file: File | null) { - if (!file || !(await validateS9pk(file))) { - this.invalid = true - } else { - this.package = await parseS9pk(file) - this.file = file - } + // if (!file || !(await validateS9pk(file))) { + // this.invalid = true + // } else { + // this.package = await parseS9pk(file) + // this.file = file + // } this.refresh$.next() } diff --git a/web/projects/ui/src/app/routes/portal/routes/system/sideload/sideload.utils.ts b/web/projects/ui/src/app/routes/portal/routes/system/sideload/sideload.utils.ts deleted file mode 100644 index 027cb52fd..000000000 --- a/web/projects/ui/src/app/routes/portal/routes/system/sideload/sideload.utils.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { MarketplacePkg } from '@start9labs/marketplace' -import cbor from 'cbor' - -interface Positions { - [key: string]: [bigint, bigint] // [position, length] -} - -const MAGIC = new Uint8Array([59, 59]) -const VERSION = new Uint8Array([1]) - -export async function validateS9pk(file: File): Promise { - const magic = new Uint8Array(await blobToBuffer(file.slice(0, 2))) - const version = new Uint8Array(await blobToBuffer(file.slice(2, 3))) - - return compare(magic, MAGIC) && compare(version, VERSION) -} - -export async function parseS9pk(file: File): Promise { - const positions: Positions = {} - // magic=2bytes, version=1bytes, pubkey=32bytes, signature=64bytes, toc_length=4bytes = 103byte is starting point - let start = 103 - let end = start + 1 // 104 - const tocLength = new DataView( - await blobToBuffer(file.slice(99, 103) ?? new Blob()), - ).getUint32(0, false) - await getPositions(start, end, file, positions, tocLength as any) - - const manifest = await getAsset(positions, file, 'manifest') - const [icon] = await Promise.all([ - await getIcon(positions, file), - // getAsset(positions, file, 'license'), - // getAsset(positions, file, 'instructions'), - ]) - - return { - manifest, - icon, - license: '', - instructions: '', - categories: [], - versions: [], - dependencyMetadata: {}, - publishedAt: '', - } -} - -async function getPositions( - initialStart: number, - initialEnd: number, - file: Blob, - positions: Positions, - tocLength: number, -) { - let start = initialStart - let end = initialEnd - const titleLength = new Uint8Array( - await blobToBuffer(file.slice(start, end)), - )[0] - const tocTitle = await file.slice(end, end + titleLength).text() - start = end + titleLength - end = start + 8 - const chapterPosition = new DataView( - await blobToBuffer(file.slice(start, end)), - ).getBigUint64(0, false) - start = end - end = start + 8 - const chapterLength = new DataView( - await blobToBuffer(file.slice(start, end)), - ).getBigUint64(0, false) - - positions[tocTitle] = [chapterPosition, chapterLength] - start = end - end = start + 1 - if (end <= tocLength + (initialStart - 1)) { - await getPositions(start, end, file, positions, tocLength) - } -} - -async function readBlobAsDataURL( - f: Blob | File, -): Promise { - const reader = new FileReader() - return new Promise((resolve, reject) => { - reader.onloadend = () => { - resolve(reader.result) - } - reader.readAsDataURL(f) - reader.onerror = _ => reject(new Error('error reading blob')) - }) -} - -async function blobToDataURL(data: Blob | File): Promise { - const res = await readBlobAsDataURL(data) - if (res instanceof ArrayBuffer) { - throw new Error('readBlobAsDataURL response should not be an array buffer') - } - if (res == null) { - throw new Error('readBlobAsDataURL response should not be null') - } - if (typeof res === 'string') return res - throw new Error('no possible blob to data url resolution found') -} - -async function blobToBuffer(data: Blob | File): Promise { - const res = await readBlobToArrayBuffer(data) - if (res instanceof String) { - throw new Error('readBlobToArrayBuffer response should not be a string') - } - if (res == null) { - throw new Error('readBlobToArrayBuffer response should not be null') - } - if (res instanceof ArrayBuffer) return res - throw new Error('no possible blob to array buffer resolution found') -} - -async function readBlobToArrayBuffer( - f: Blob | File, -): Promise { - const reader = new FileReader() - return new Promise((resolve, reject) => { - reader.onloadend = () => { - resolve(reader.result) - } - reader.readAsArrayBuffer(f) - reader.onerror = _ => reject(new Error('error reading blob')) - }) -} - -function compare(a: Uint8Array, b: Uint8Array) { - for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) return false - } - return true -} - -async function getAsset( - positions: Positions, - file: Blob, - asset: 'manifest' | 'license' | 'instructions', -): Promise { - const data = await blobToBuffer( - file.slice( - Number(positions[asset][0]), - Number(positions[asset][0]) + Number(positions[asset][1]), - ), - ) - return cbor.decode(data, true) -} - -async function getIcon(positions: Positions, file: Blob): Promise { - const contentType = '' // @TODO - const data = file.slice( - Number(positions['icon'][0]), - Number(positions['icon'][0]) + Number(positions['icon'][1]), - contentType, - ) - return blobToDataURL(data) -} diff --git a/web/projects/ui/src/app/routes/portal/routes/system/updates/filter-updates.pipe.ts b/web/projects/ui/src/app/routes/portal/routes/system/updates/filter-updates.pipe.ts index af244f801..0e257a1d8 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/updates/filter-updates.pipe.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/updates/filter-updates.pipe.ts @@ -1,5 +1,5 @@ import { inject, Pipe, PipeTransform } from '@angular/core' -import { Emver } from '@start9labs/shared' +import { Exver } from '@start9labs/shared' import { MarketplacePkg } from '@start9labs/marketplace' import { InstalledState, @@ -12,7 +12,7 @@ import { standalone: true, }) export class FilterUpdatesPipe implements PipeTransform { - private readonly emver = inject(Emver) + private readonly exver = inject(Exver) transform( pkgs?: MarketplacePkg[], @@ -20,10 +20,10 @@ export class FilterUpdatesPipe implements PipeTransform { ): MarketplacePkg[] | null { return ( pkgs?.filter( - ({ manifest }) => - this.emver.compare( - manifest.version, - local?.[manifest.id]?.stateInfo.manifest.version, + ({ version, id }) => + this.exver.compareExver( + version, + local?.[id]?.stateInfo.manifest.version || '', ) === 1, ) || null ) diff --git a/web/projects/ui/src/app/routes/portal/routes/system/updates/item.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/updates/item.component.ts index 0b72e3344..60e30260a 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/updates/item.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/updates/item.component.ts @@ -5,7 +5,6 @@ import { MarketplacePkg, } from '@start9labs/marketplace' import { - EmverPipesModule, MarkdownPipeModule, SafeLinksDirective, SharedPipesModule, @@ -45,12 +44,12 @@ import { hasCurrentDeps } from 'src/app/utils/has-deps'
- {{ marketplacePkg.manifest.title }} + {{ marketplacePkg.title }}
- {{ localPkg.stateInfo.manifest.version | displayEmver }} + {{ localPkg.stateInfo.manifest.version }} - {{ marketplacePkg.manifest.version | displayEmver }} + {{ marketplacePkg.version }}
{{ errors }}
@@ -84,16 +83,13 @@ import { hasCurrentDeps } from 'src/app/utils/has-deps' What's new

View listing @@ -115,7 +111,6 @@ import { hasCurrentDeps } from 'src/app/utils/has-deps' standalone: true, imports: [ RouterLink, - EmverPipesModule, MarkdownPipeModule, NgDompurifyModule, SafeLinksDirective, @@ -132,7 +127,7 @@ import { hasCurrentDeps } from 'src/app/utils/has-deps' }) export class UpdatesItemComponent { private readonly dialogs = inject(TuiDialogService) - private readonly patch = inject(PatchDB) + private readonly patch = inject>(PatchDB) private readonly marketplace = inject( AbstractMarketplaceService, ) as MarketplaceService @@ -147,7 +142,7 @@ export class UpdatesItemComponent { url!: string get pkgId(): string { - return this.marketplacePkg.manifest.id + return this.marketplacePkg.id } get errors(): string { @@ -159,7 +154,7 @@ export class UpdatesItemComponent { } async onClick() { - const { id } = this.marketplacePkg.manifest + const { id } = this.marketplacePkg delete this.marketplace.updateErrors[id] this.marketplace.updateQueue[id] = true @@ -178,7 +173,7 @@ export class UpdatesItemComponent { } private async update() { - const { id, version } = this.marketplacePkg.manifest + const { id, version } = this.marketplacePkg try { await this.marketplace.installPackage(id, version, this.url) diff --git a/web/projects/ui/src/app/routes/portal/routes/system/updates/updates.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/updates/updates.component.ts index 00e2f0f81..774976c0f 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/updates/updates.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/updates/updates.component.ts @@ -35,7 +35,7 @@ import { isInstalled, isUpdating } from 'src/app/utils/get-package-data' @for (pkg of pkgs; track pkg) { } @empty { @@ -76,7 +76,7 @@ export default class UpdatesComponent { readonly data$ = combineLatest({ hosts: this.service.getKnownHosts$(true), mp: this.service.getMarketplace$(), - local: inject(PatchDB) + local: inject>(PatchDB) .watch$('packageData') .pipe( map(pkgs => diff --git a/web/projects/ui/src/app/routing.module.ts b/web/projects/ui/src/app/routing.module.ts index cea864793..0c6c0c89a 100644 --- a/web/projects/ui/src/app/routing.module.ts +++ b/web/projects/ui/src/app/routing.module.ts @@ -1,19 +1,19 @@ import { NgModule } from '@angular/core' import { PreloadAllModules, RouterModule, Routes } from '@angular/router' +import { stateNot } from 'src/app/services/state.service' import { AuthGuard } from './guards/auth.guard' import { UnauthGuard } from './guards/unauth.guard' const routes: Routes = [ { path: 'diagnostic', - loadChildren: () => - import('./routes/diagnostic/diagnostic.module').then( - m => m.DiagnosticModule, - ), + canActivate: [stateNot(['initializing', 'running'])], + loadChildren: () => import('./routes/diagnostic/diagnostic.module'), }, { - path: 'loading', - loadComponent: () => import('./routes/loading/loading.page'), + path: 'initializing', + canActivate: [stateNot(['error', 'running'])], + loadComponent: () => import('./routes/initializing/initializing.page'), }, { path: 'login', @@ -23,8 +23,7 @@ const routes: Routes = [ }, { path: 'portal', - canActivate: [AuthGuard], - canActivateChild: [AuthGuard], + canActivate: [AuthGuard, stateNot(['error', 'initializing'])], loadChildren: () => import('./routes/portal/portal.routes'), }, { diff --git a/web/projects/ui/src/app/services/actions.service.ts b/web/projects/ui/src/app/services/actions.service.ts index 8bc2e2ebc..d2228ef4b 100644 --- a/web/projects/ui/src/app/services/actions.service.ts +++ b/web/projects/ui/src/app/services/actions.service.ts @@ -24,7 +24,7 @@ export class ActionsService { private readonly loader = inject(LoadingService) private readonly api = inject(ApiService) private readonly formDialog = inject(FormDialogService) - private readonly patch = inject(PatchDB) + private readonly patch = inject>(PatchDB) configure(manifest: T.Manifest): void { this.formDialog.open(ConfigModal, { diff --git a/web/projects/ui/src/app/services/api/api.fixures.ts b/web/projects/ui/src/app/services/api/api.fixures.ts index a2c9c7d94..05c4770ca 100644 --- a/web/projects/ui/src/app/services/api/api.fixures.ts +++ b/web/projects/ui/src/app/services/api/api.fixures.ts @@ -1021,7 +1021,7 @@ export module Mock { username: 'TestUser', mountable: false, // @TODO Matt Provide mock for startOs - startOs: null, + startOs: {}, }, { id: 'ftcvewdnkemfksdm', @@ -1030,7 +1030,7 @@ export module Mock { provider: 'dropbox', path: '/Home/backups', mountable: true, - startOs: null, + startOs: {}, }, { id: 'csgashbdjkasnd', @@ -1040,7 +1040,7 @@ export module Mock { path: '/Desktop/embassy-backups-2', username: 'TestUser', mountable: true, - startOs: null, + startOs: {}, }, { id: 'powjefhjbnwhdva', diff --git a/web/projects/ui/src/app/services/api/api.types.ts b/web/projects/ui/src/app/services/api/api.types.ts index f1bf71b9c..70cd7a34e 100644 --- a/web/projects/ui/src/app/services/api/api.types.ts +++ b/web/projects/ui/src/app/services/api/api.types.ts @@ -418,7 +418,8 @@ export module RR { // marketplace export type GetMarketplaceInfoReq = { serverId: string } - export type GetMarketplaceInfoRes = StoreInfo + // @TODO Matt fix type + export type GetMarketplaceInfoRes = any export type GetMarketplaceEosReq = { serverId: string } // @TODO Matt fix type diff --git a/web/projects/ui/src/app/services/api/embassy-api.service.ts b/web/projects/ui/src/app/services/api/embassy-api.service.ts index 103620697..e10f60592 100644 --- a/web/projects/ui/src/app/services/api/embassy-api.service.ts +++ b/web/projects/ui/src/app/services/api/embassy-api.service.ts @@ -3,10 +3,11 @@ import { GetPackagesRes, MarketplacePkg, } from '@start9labs/marketplace' -import { RPCOptions } from '@start9labs/shared' +import { Log, RPCOptions } from '@start9labs/shared' import { T } from '@start9labs/start-sdk' import { Observable } from 'rxjs' -import { BackupTargetType, RR } from './api.types' +import { WebSocketSubjectConfig } from 'rxjs/webSocket' +import { BackupTargetType, Metrics, RR } from './api.types' export abstract class ApiService { // http @@ -84,6 +85,13 @@ export abstract class ApiService { ): Promise // server + abstract openLogsWebsocket$( + config: WebSocketSubjectConfig, + ): Observable + + abstract openMetricsWebsocket$( + config: WebSocketSubjectConfig, + ): Observable abstract getSystemTime( params: RR.GetSystemTimeReq, diff --git a/web/projects/ui/src/app/services/api/embassy-live-api.service.ts b/web/projects/ui/src/app/services/api/embassy-live-api.service.ts index e45d7867a..4b12c0f38 100644 --- a/web/projects/ui/src/app/services/api/embassy-live-api.service.ts +++ b/web/projects/ui/src/app/services/api/embassy-live-api.service.ts @@ -3,15 +3,16 @@ import { HttpOptions, HttpService, isRpcError, + Log, Method, RpcError, RPCOptions, } from '@start9labs/shared' import { PATCH_CACHE } from 'src/app/services/patch-db/patch-db-source' import { ApiService } from './embassy-api.service' -import { BackupTargetType, RR } from './api.types' +import { BackupTargetType, Metrics, RR } from './api.types' import { ConfigService } from '../config.service' -import { webSocket } from 'rxjs/webSocket' +import { webSocket, WebSocketSubjectConfig } from 'rxjs/webSocket' import { Observable, filter, firstValueFrom } from 'rxjs' import { AuthService } from '../auth.service' import { DOCUMENT } from '@angular/common' @@ -92,6 +93,16 @@ export class LiveApiService extends ApiService { } // websocket + // @TODO Matt which of these 2 APIs should we use? + private openWebsocket(config: WebSocketSubjectConfig): Observable { + const { location } = this.document.defaultView! + const protocol = location.protocol === 'http:' ? 'ws' : 'wss' + const host = location.host + + config.url = `${protocol}://${host}/ws${config.url}` + + return webSocket(config) + } openWebsocket$( guid: string, @@ -210,6 +221,15 @@ export class LiveApiService extends ApiService { } // server + openLogsWebsocket$(config: WebSocketSubjectConfig): Observable { + return this.openWebsocket(config) + } + + openMetricsWebsocket$( + config: WebSocketSubjectConfig, + ): Observable { + return this.openWebsocket(config) + } async getSystemTime( params: RR.GetSystemTimeReq, diff --git a/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts b/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts index 19c46ea5e..6a7a56606 100644 --- a/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts +++ b/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts @@ -1,10 +1,5 @@ import { Injectable } from '@angular/core' -import { - pauseFor, - Log, - RPCErrorDetails, - RPCOptions, -} from '@start9labs/shared' +import { pauseFor, Log, RPCErrorDetails, RPCOptions } from '@start9labs/shared' import { ApiService } from './embassy-api.service' import { Operation, @@ -281,6 +276,17 @@ export class MockApiService extends ApiService { // server + openLogsWebsocket$(config: WebSocketSubjectConfig): Observable { + return interval(50).pipe( + map((_, index) => { + // mock fire open observer + if (index === 0) config.openObserver?.next(new Event('')) + if (index === 100) throw new Error('HAAHHA') + return Mock.ServerLogs[0] + }), + ) + } + openMetricsWebsocket$( config: WebSocketSubjectConfig, ): Observable { diff --git a/web/projects/ui/src/app/services/badge.service.ts b/web/projects/ui/src/app/services/badge.service.ts index c71163292..28917c6e8 100644 --- a/web/projects/ui/src/app/services/badge.service.ts +++ b/web/projects/ui/src/app/services/badge.service.ts @@ -1,6 +1,6 @@ import { inject, Injectable } from '@angular/core' import { AbstractMarketplaceService } from '@start9labs/marketplace' -import { Emver } from '@start9labs/shared' +import { Exver } from '@start9labs/shared' import { PatchDB } from 'patch-db-client' import { combineLatest, @@ -26,8 +26,8 @@ import { getManifest } from 'src/app/utils/get-package-data' }) export class BadgeService { private readonly notifications = inject(NotificationService) - private readonly emver = inject(Emver) - private readonly patch = inject(PatchDB) + private readonly exver = inject(Exver) + private readonly patch = inject>(PatchDB) private readonly settings$ = combineLatest([ this.patch.watch$('serverInfo', 'ntpSynced'), inject(EOSService).updateAvailable$, @@ -36,7 +36,7 @@ export class BadgeService { AbstractMarketplaceService, ) as MarketplaceService - private readonly local$ = inject(ConnectionService).connected$.pipe( + private readonly local$ = inject(ConnectionService).pipe( filter(Boolean), switchMap(() => this.patch.watch$('packageData').pipe(first())), switchMap(outer => @@ -67,10 +67,12 @@ export class BadgeService { Object.entries(marketplace).reduce( (list, [_, store]) => store?.packages.reduce( - (result, { manifest: { id, version } }) => + (result, { id, version }) => local[id] && - this.emver.compare(version, getManifest(local[id]).version) === - 1 + this.exver.compareExver( + version, + getManifest(local[id]).version, + ) === 1 ? result.add(id) : result, list, diff --git a/web/projects/ui/src/app/services/breadcrumbs.service.ts b/web/projects/ui/src/app/services/breadcrumbs.service.ts index 193ebf0c0..d499ea158 100644 --- a/web/projects/ui/src/app/services/breadcrumbs.service.ts +++ b/web/projects/ui/src/app/services/breadcrumbs.service.ts @@ -20,7 +20,7 @@ export interface Breadcrumb { providedIn: 'root', }) export class BreadcrumbsService extends BehaviorSubject { - private readonly patch = inject(PatchDB) + private readonly patch = inject>(PatchDB) constructor() { super([]) diff --git a/web/projects/ui/src/app/services/config.service.ts b/web/projects/ui/src/app/services/config.service.ts index efb0b5dcd..fc69f6fa2 100644 --- a/web/projects/ui/src/app/services/config.service.ts +++ b/web/projects/ui/src/app/services/config.service.ts @@ -82,17 +82,14 @@ export class ConfigService { /** ${scheme}://${username}@${host}:${externalPort}${suffix} */ launchableAddress( - interfaces: PackageDataEntry['serviceInterfaces'], + ui: T.ServiceInterface, hosts: PackageDataEntry['hosts'], ): string { - const ui = Object.values(interfaces).find( - i => - i.type === 'ui' && - (i.addressInfo.scheme === 'http' || - i.addressInfo.sslScheme === 'https'), - ) // TODO select if multiple - - if (!ui) return '' + if ( + ui.type !== 'ui' || + (ui.addressInfo.scheme !== 'http' && ui.addressInfo.sslScheme !== 'https') + ) + return '' const hostnameInfo = hosts[ui.addressInfo.hostId]?.hostnameInfo[ui.addressInfo.internalPort] diff --git a/web/projects/ui/src/app/services/marketplace.service.ts b/web/projects/ui/src/app/services/marketplace.service.ts index a3f5bffb7..21c7ee09f 100644 --- a/web/projects/ui/src/app/services/marketplace.service.ts +++ b/web/projects/ui/src/app/services/marketplace.service.ts @@ -168,21 +168,11 @@ export class MarketplaceService implements AbstractMarketplaceService { this.marketplace$.pipe( map(m => m[url]), filter(Boolean), - map(({ info, packages }) => { - const categories = new Set() - if (info.categories.includes('featured')) categories.add('featured') - categories.add('all') - info.categories.forEach(c => categories.add(c)) - - return { - url, - info: { - ...info, - categories: Array.from(categories), - }, - packages, - } - }), + map(({ info, packages }) => ({ + url, + info, + packages, + })), ), ), ) diff --git a/web/projects/ui/src/app/services/network.service.ts b/web/projects/ui/src/app/services/network.service.ts index e1568603d..9623a5216 100644 --- a/web/projects/ui/src/app/services/network.service.ts +++ b/web/projects/ui/src/app/services/network.service.ts @@ -1,11 +1,11 @@ import { inject, Injectable } from '@angular/core' -import { WINDOW } from '@ng-web-apis/common' +import { WA_WINDOW } from '@ng-web-apis/common' import { fromEvent, merge, Observable, shareReplay } from 'rxjs' import { distinctUntilChanged, map, startWith } from 'rxjs/operators' @Injectable({ providedIn: 'root' }) export class NetworkService extends Observable { - private readonly win = inject(WINDOW) + private readonly win = inject(WA_WINDOW) private readonly stream$ = merge( fromEvent(this.win, 'online'), fromEvent(this.win, 'offline'), diff --git a/web/projects/ui/src/app/services/notification.service.ts b/web/projects/ui/src/app/services/notification.service.ts index f3fd635d8..a1c421426 100644 --- a/web/projects/ui/src/app/services/notification.service.ts +++ b/web/projects/ui/src/app/services/notification.service.ts @@ -14,7 +14,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model' @Injectable({ providedIn: 'root' }) export class NotificationService { - private readonly patch = inject(PatchDB) + private readonly patch = inject>(PatchDB) private readonly errorService = inject(ErrorService) private readonly api = inject(ApiService) private readonly dialogs = inject(TuiDialogService) diff --git a/web/projects/ui/src/app/services/patch-db/patch-db.providers.ts b/web/projects/ui/src/app/services/patch-db/patch-db.providers.ts deleted file mode 100644 index e47f1a9a5..000000000 --- a/web/projects/ui/src/app/services/patch-db/patch-db.providers.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { PatchDB } from 'patch-db-client' -import { Injector } from '@angular/core' -import { PATCH_SOURCE, sourceFactory } from './patch-db.factory' - -export const PATCH_DB_PROVIDERS = [ - { - provide: PATCH_SOURCE, - deps: [Injector], - useFactory: sourceFactory, - }, - { - provide: PatchDB, - deps: [PATCH_SOURCE], - useClass: PatchDB, - }, -] diff --git a/web/projects/ui/src/app/services/state.service.ts b/web/projects/ui/src/app/services/state.service.ts index 9eb8caf0a..bffa373f2 100644 --- a/web/projects/ui/src/app/services/state.service.ts +++ b/web/projects/ui/src/app/services/state.service.ts @@ -1,7 +1,7 @@ import { inject, Injectable } from '@angular/core' import { CanActivateFn, IsActiveMatchOptions, Router } from '@angular/router' -import { ALWAYS_TRUE_HANDLER } from '@taiga-ui/cdk' -import { TuiAlertService, TuiNotification } from '@taiga-ui/core' +import { TUI_TRUE_HANDLER } from '@taiga-ui/cdk' +import { TuiAlertService } from '@taiga-ui/core' import { BehaviorSubject, combineLatest, @@ -94,8 +94,8 @@ export class StateService extends Observable { this.alerts .open('Trying to reach server', { label: 'State unknown', - autoClose: false, - status: TuiNotification.Error, + autoClose: 0, + status: 'error', }) .pipe( takeUntil( @@ -106,7 +106,7 @@ export class StateService extends Observable { ), this.alerts.open('Connection restored', { label: 'Server reached', - status: TuiNotification.Success, + status: 'success', }), ), ), @@ -131,6 +131,6 @@ export function stateNot(state: RR.ServerState[]): CanActivateFn { return () => inject(StateService).pipe( filter(current => !current || !state.includes(current)), - map(ALWAYS_TRUE_HANDLER), + map(TUI_TRUE_HANDLER), ) } diff --git a/web/projects/ui/src/app/services/theme-switcher.service.ts b/web/projects/ui/src/app/services/theme-switcher.service.ts index cdb215457..30cc3e16f 100644 --- a/web/projects/ui/src/app/services/theme-switcher.service.ts +++ b/web/projects/ui/src/app/services/theme-switcher.service.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@angular/core' -import { WINDOW } from '@ng-web-apis/common' +import { WA_WINDOW } from '@ng-web-apis/common' import { PatchDB } from 'patch-db-client' import { filter, take, BehaviorSubject } from 'rxjs' import { ApiService } from './api/embassy-api.service' @@ -12,7 +12,7 @@ export class ThemeSwitcherService extends BehaviorSubject { constructor( private readonly patch: PatchDB, private readonly embassyApi: ApiService, - @Inject(WINDOW) private readonly windowRef: Window, + @Inject(WA_WINDOW) private readonly windowRef: Window, ) { super('Dark') diff --git a/web/projects/ui/src/app/services/time.service.ts b/web/projects/ui/src/app/services/time.service.ts index 3ca9d286f..2f30ae5f3 100644 --- a/web/projects/ui/src/app/services/time.service.ts +++ b/web/projects/ui/src/app/services/time.service.ts @@ -16,7 +16,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model' providedIn: 'root', }) export class TimeService { - private readonly patch = inject(PatchDB) + private readonly patch = inject>(PatchDB) private readonly time$ = defer(() => inject(ApiService).getSystemTime({}), ).pipe( diff --git a/web/projects/ui/tsconfig.json b/web/projects/ui/tsconfig.json index 6978bd46f..ea09595fb 100644 --- a/web/projects/ui/tsconfig.json +++ b/web/projects/ui/tsconfig.json @@ -3,6 +3,6 @@ "compilerOptions": { "baseUrl": "./" }, - "files": ["src/main.ts"], + "files": ["src/main.ts", "src/polyfills.ts"], "include": ["src/**/*.d.ts"] }