(ServiceConfigModal, {
label: `${title} configuration`,
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/router/primary-ip.pipe.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/router/primary-ip.pipe.ts
new file mode 100644
index 000000000..777194183
--- /dev/null
+++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/router/primary-ip.pipe.ts
@@ -0,0 +1,14 @@
+import { Pipe, PipeTransform } from '@angular/core'
+import { IpInfo } from 'src/app/services/patch-db/data-model'
+
+@Pipe({
+ standalone: true,
+ name: 'primaryIp',
+})
+export class PrimaryIpPipe implements PipeTransform {
+ transform(ipInfo: IpInfo): string {
+ return Object.values(ipInfo)
+ .filter(iface => iface.ipv4)
+ .sort((a, b) => (a.wireless ? -1 : 1))[0].ipv4!
+ }
+}
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/router/router.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/router/router.component.ts
index 0fe99fe61..b34be8390 100644
--- a/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/router/router.component.ts
+++ b/web/projects/ui/src/app/apps/portal/routes/system/settings/routes/router/router.component.ts
@@ -3,8 +3,8 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { TuiTextfieldControllerModule } from '@taiga-ui/core'
import { PatchDB } from 'patch-db-client'
import { DataModel } from 'src/app/services/patch-db/data-model'
-import { PrimaryIpPipeModule } from 'src/app/common/primary-ip/primary-ip.module'
import { RouterInfoComponent } from './info.component'
+import { PrimaryIpPipe } from './primary-ip.pipe'
import { RouterPortComponent } from './table.component'
@Component({
@@ -58,10 +58,10 @@ import { RouterPortComponent } from './table.component'
standalone: true,
imports: [
CommonModule,
- PrimaryIpPipeModule,
RouterInfoComponent,
RouterPortComponent,
TuiTextfieldControllerModule,
+ PrimaryIpPipe,
],
})
export class SettingsRouterComponent {
diff --git a/web/projects/ui/src/app/apps/portal/routes/system/updates/updates.component.ts b/web/projects/ui/src/app/apps/portal/routes/system/updates/updates.component.ts
index 1f8de4437..a1b7116b4 100644
--- a/web/projects/ui/src/app/apps/portal/routes/system/updates/updates.component.ts
+++ b/web/projects/ui/src/app/apps/portal/routes/system/updates/updates.component.ts
@@ -4,7 +4,7 @@ import {
AbstractMarketplaceService,
StoreIconComponentModule,
} from '@start9labs/marketplace'
-import { TuiForModule } from '@taiga-ui/cdk'
+import { TuiAvatarModule, TuiCellModule } from '@taiga-ui/experimental'
import { PatchDB } from 'patch-db-client'
import { combineLatest } from 'rxjs'
import { MarketplaceService } from 'src/app/services/marketplace.service'
@@ -12,66 +12,64 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
import { ConfigService } from 'src/app/services/config.service'
import { FilterUpdatesPipe } from './pipes/filter-updates.pipe'
import { UpdatesItemComponent } from './components/item.component'
-import { SkeletonListComponent } from '../../../components/skeleton-list.component'
@Component({
template: `
-
-
+ @if (data$ | async; as data) {
+ @for (host of data.hosts; track host) {
-
+
{{ host.name }}
-
- Request Failed
-
-
-
-
- All services are up to date!
-
-
-
+ @if (data.errors.includes(host.url)) {
+ Request Failed
+ }
+ @if (data.mp[host.url]?.packages | filterUpdates: data.local; as pkgs) {
+ @for (pkg of pkgs; track pkg) {
+
+ } @empty {
+ All services are up to date!
+ }
+ } @else {
+ @for (i of [0, 1, 2]; track i) {
+
+
+ Loading update item
+
+ Loading actions
+
+
+ }
+ }
+ }
+ }
`,
host: { class: 'g-page' },
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
CommonModule,
- TuiForModule,
+ TuiCellModule,
+ TuiAvatarModule,
StoreIconComponentModule,
FilterUpdatesPipe,
UpdatesItemComponent,
- SkeletonListComponent,
],
})
export default class UpdatesComponent {
- private readonly marketplace = inject(
+ private readonly service = inject(
AbstractMarketplaceService,
) as MarketplaceService
- readonly config = inject(ConfigService)
-
+ readonly mp = inject(ConfigService).marketplace
readonly data$ = combineLatest({
- hosts: this.marketplace.getKnownHosts$(true),
- mp: this.marketplace.getMarketplace$(),
+ hosts: this.service.getKnownHosts$(true),
+ mp: this.service.getMarketplace$(),
local: inject(PatchDB).watch$('package-data'),
- errors: this.marketplace.getRequestErrors$(),
+ errors: this.service.getRequestErrors$(),
})
}
diff --git a/web/projects/ui/src/app/common/install-progress/install-progress.module.ts b/web/projects/ui/src/app/common/install-progress/install-progress.module.ts
deleted file mode 100644
index 68d0330b5..000000000
--- a/web/projects/ui/src/app/common/install-progress/install-progress.module.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { NgModule } from '@angular/core'
-import { InstallProgressDisplayPipe } from './install-progress.pipe'
-
-@NgModule({
- declarations: [InstallProgressDisplayPipe],
- exports: [InstallProgressDisplayPipe],
-})
-export class InstallProgressPipeModule {}
diff --git a/web/projects/ui/src/app/common/primary-ip/primary-ip.module.ts b/web/projects/ui/src/app/common/primary-ip/primary-ip.module.ts
deleted file mode 100644
index 941518ab2..000000000
--- a/web/projects/ui/src/app/common/primary-ip/primary-ip.module.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { NgModule } from '@angular/core'
-import { PrimaryIpPipe } from './primary-ip.pipe'
-
-@NgModule({
- declarations: [PrimaryIpPipe],
- exports: [PrimaryIpPipe],
-})
-export class PrimaryIpPipeModule {}
diff --git a/web/projects/ui/src/app/common/primary-ip/primary-ip.pipe.ts b/web/projects/ui/src/app/common/primary-ip/primary-ip.pipe.ts
deleted file mode 100644
index 4cfa98552..000000000
--- a/web/projects/ui/src/app/common/primary-ip/primary-ip.pipe.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Pipe, PipeTransform } from '@angular/core'
-import { IpInfo } from '../../services/patch-db/data-model'
-
-@Pipe({
- name: 'primaryIp',
-})
-export class PrimaryIpPipe implements PipeTransform {
- transform(ipInfo: IpInfo): string {
- return getPrimaryIp(ipInfo)
- }
-}
-
-export function getPrimaryIp(ipInfo: IpInfo): string {
- return Object.values(ipInfo)
- .filter(iface => iface.ipv4)
- .sort((a, b) => (a.wireless ? -1 : 1))[0].ipv4!
-}
diff --git a/web/projects/ui/src/app/common/qr.component.ts b/web/projects/ui/src/app/common/qr.component.ts
new file mode 100644
index 000000000..dee6cbb7e
--- /dev/null
+++ b/web/projects/ui/src/app/common/qr.component.ts
@@ -0,0 +1,16 @@
+import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
+import { TuiDialogContext } from '@taiga-ui/core'
+import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus'
+import { QrCodeModule } from 'ng-qrcode'
+
+@Component({
+ standalone: true,
+ selector: 'qr',
+ template: '',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [QrCodeModule],
+})
+export class QRComponent {
+ readonly context =
+ inject>(POLYMORPHEUS_CONTEXT)
+}
diff --git a/web/projects/ui/src/app/common/qr/qr.component.ts b/web/projects/ui/src/app/common/qr/qr.component.ts
deleted file mode 100644
index a87e43863..000000000
--- a/web/projects/ui/src/app/common/qr/qr.component.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Component, Inject } from '@angular/core'
-import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus'
-import { TuiDialogContext } from '@taiga-ui/core'
-
-@Component({
- selector: 'qr',
- template: '',
-})
-export class QRComponent {
- constructor(
- @Inject(POLYMORPHEUS_CONTEXT)
- readonly context: TuiDialogContext,
- ) {}
-}
diff --git a/web/projects/ui/src/app/common/qr/qr.module.ts b/web/projects/ui/src/app/common/qr/qr.module.ts
deleted file mode 100644
index aa5086b28..000000000
--- a/web/projects/ui/src/app/common/qr/qr.module.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { NgModule } from '@angular/core'
-import { CommonModule } from '@angular/common'
-import { QrCodeModule } from 'ng-qrcode'
-
-import { QRComponent } from './qr.component'
-
-@NgModule({
- declarations: [QRComponent],
- imports: [CommonModule, QrCodeModule],
- exports: [QRComponent],
-})
-export class QRComponentModule {}
diff --git a/web/projects/ui/src/app/app/sidebar-host.component.ts b/web/projects/ui/src/app/common/sidebar-host.component.ts
similarity index 100%
rename from web/projects/ui/src/app/app/sidebar-host.component.ts
rename to web/projects/ui/src/app/common/sidebar-host.component.ts
diff --git a/web/projects/ui/src/app/common/skeleton-list/skeleton-list.component.html b/web/projects/ui/src/app/common/skeleton-list/skeleton-list.component.html
deleted file mode 100644
index 5b875fa2c..000000000
--- a/web/projects/ui/src/app/common/skeleton-list/skeleton-list.component.html
+++ /dev/null
@@ -1,51 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/web/projects/ui/src/app/common/skeleton-list/skeleton-list.component.module.ts b/web/projects/ui/src/app/common/skeleton-list/skeleton-list.component.module.ts
deleted file mode 100644
index 23e8b446e..000000000
--- a/web/projects/ui/src/app/common/skeleton-list/skeleton-list.component.module.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { NgModule } from '@angular/core'
-import { CommonModule } from '@angular/common'
-import { SkeletonListComponent } from './skeleton-list.component'
-import { IonicModule } from '@ionic/angular'
-import { RouterModule } from '@angular/router'
-
-@NgModule({
- declarations: [SkeletonListComponent],
- imports: [CommonModule, IonicModule, RouterModule],
- exports: [SkeletonListComponent],
-})
-export class SkeletonListComponentModule {}
diff --git a/web/projects/ui/src/app/common/skeleton-list/skeleton-list.component.ts b/web/projects/ui/src/app/common/skeleton-list/skeleton-list.component.ts
deleted file mode 100644
index 4f6c369d0..000000000
--- a/web/projects/ui/src/app/common/skeleton-list/skeleton-list.component.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Component, Input } from '@angular/core'
-
-@Component({
- selector: 'skeleton-list',
- templateUrl: './skeleton-list.component.html',
-})
-export class SkeletonListComponent {
- @Input() groups = 0
- @Input() rows = 3
- @Input() showAvatar = false
- groupsArr: number[] = []
- rowsArr: number[] = []
-
- ngOnInit() {
- this.groupsArr = Array(this.groups).fill(0)
- this.rowsArr = Array(this.rows).fill(0)
- }
-}
diff --git a/web/projects/ui/src/app/common/svg-definitions.component.ts b/web/projects/ui/src/app/common/svg-definitions.component.ts
new file mode 100644
index 000000000..36d6462b9
--- /dev/null
+++ b/web/projects/ui/src/app/common/svg-definitions.component.ts
@@ -0,0 +1,32 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core'
+
+@Component({
+ standalone: true,
+ selector: 'svg-definitions',
+ template: `
+
+ `,
+ styles: `
+ :host {
+ position: absolute;
+ width: 0;
+ height: 0;
+ visibility: hidden;
+ }
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class SvgDefinitionsComponent {}
diff --git a/web/projects/ui/src/app/common/toast-container/notifications-toast.component.ts b/web/projects/ui/src/app/common/toast-container/notifications-toast.component.ts
new file mode 100644
index 000000000..eceb6cc62
--- /dev/null
+++ b/web/projects/ui/src/app/common/toast-container/notifications-toast.component.ts
@@ -0,0 +1,42 @@
+import { AsyncPipe } from '@angular/common'
+import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
+import { RouterLink } from '@angular/router'
+import { TuiAlertModule } from '@taiga-ui/core'
+import { PatchDB } from 'patch-db-client'
+import { Observable, Subject, merge, pairwise, map, endWith } from 'rxjs'
+import { DataModel } from 'src/app/services/patch-db/data-model'
+
+@Component({
+ standalone: true,
+ selector: 'notifications-toast',
+ template: `
+
+ New notifications
+ View
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [TuiAlertModule, RouterLink, AsyncPipe],
+})
+export class NotificationsToastComponent {
+ private readonly dismiss$ = new Subject()
+
+ readonly visible$: Observable = merge(
+ this.dismiss$,
+ inject(PatchDB)
+ .watch$('server-info', 'unreadNotifications', 'count')
+ .pipe(
+ pairwise(),
+ map(([prev, cur]) => cur > prev),
+ endWith(false),
+ ),
+ )
+
+ onDismiss() {
+ this.dismiss$.next(false)
+ }
+}
diff --git a/web/projects/ui/src/app/common/toast-container/notifications-toast/notifications-toast.component.html b/web/projects/ui/src/app/common/toast-container/notifications-toast/notifications-toast.component.html
deleted file mode 100644
index d75364715..000000000
--- a/web/projects/ui/src/app/common/toast-container/notifications-toast/notifications-toast.component.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
- New notifications
- View
-
diff --git a/web/projects/ui/src/app/common/toast-container/notifications-toast/notifications-toast.component.ts b/web/projects/ui/src/app/common/toast-container/notifications-toast/notifications-toast.component.ts
deleted file mode 100644
index 65d4241c2..000000000
--- a/web/projects/ui/src/app/common/toast-container/notifications-toast/notifications-toast.component.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
-import { Observable, Subject, merge } from 'rxjs'
-
-import { NotificationsToastService } from './notifications-toast.service'
-
-@Component({
- selector: 'notifications-toast',
- templateUrl: './notifications-toast.component.html',
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class NotificationsToastComponent {
- private readonly dismiss$ = new Subject()
-
- readonly visible$: Observable = merge(
- this.dismiss$,
- this.notifications$,
- )
-
- constructor(
- @Inject(NotificationsToastService)
- private readonly notifications$: Observable,
- ) {}
-
- onDismiss() {
- this.dismiss$.next(false)
- }
-}
diff --git a/web/projects/ui/src/app/common/toast-container/notifications-toast/notifications-toast.service.ts b/web/projects/ui/src/app/common/toast-container/notifications-toast/notifications-toast.service.ts
deleted file mode 100644
index 9dd3671f7..000000000
--- a/web/projects/ui/src/app/common/toast-container/notifications-toast/notifications-toast.service.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Injectable } from '@angular/core'
-import { PatchDB } from 'patch-db-client'
-import { endWith, map, pairwise, Observable } from 'rxjs'
-import { DataModel } from 'src/app/services/patch-db/data-model'
-
-@Injectable({ providedIn: 'root' })
-export class NotificationsToastService extends Observable {
- private readonly stream$ = this.patch
- .watch$('server-info', 'unreadNotifications', 'count')
- .pipe(
- pairwise(),
- map(([prev, cur]) => cur > prev),
- endWith(false),
- )
-
- constructor(private readonly patch: PatchDB) {
- super(subscriber => this.stream$.subscribe(subscriber))
- }
-}
diff --git a/web/projects/ui/src/app/common/toast-container/refresh-alert.component.ts b/web/projects/ui/src/app/common/toast-container/refresh-alert.component.ts
new file mode 100644
index 000000000..49aabce15
--- /dev/null
+++ b/web/projects/ui/src/app/common/toast-container/refresh-alert.component.ts
@@ -0,0 +1,90 @@
+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 { TuiAutoFocusModule } from '@taiga-ui/cdk'
+import { TuiDialogModule } from '@taiga-ui/core'
+import { TuiButtonModule } from '@taiga-ui/experimental'
+import { PatchDB } from 'patch-db-client'
+import { debounceTime, endWith, map, merge, Subject } from 'rxjs'
+import { ConfigService } from 'src/app/services/config.service'
+import { DataModel } from 'src/app/services/patch-db/data-model'
+
+@Component({
+ standalone: true,
+ selector: 'refresh-alert',
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [TuiDialogModule, AsyncPipe, TuiButtonModule, TuiAutoFocusModule],
+})
+export class RefreshAlertComponent {
+ private readonly updates = inject(SwUpdate)
+ private readonly loader = inject(LoadingService)
+ private readonly emver = inject(Emver)
+ private readonly config = inject(ConfigService)
+ private readonly dismiss$ = new Subject()
+
+ readonly show$ = merge(
+ this.dismiss$,
+ inject(PatchDB)
+ .watch$('server-info', 'version')
+ .pipe(
+ map(version => !!this.emver.compare(this.config.version, version)),
+ endWith(false),
+ ),
+ ).pipe(debounceTime(0))
+
+ // @TODO use this like we did on 0344
+ onPwa = false
+
+ ngOnInit() {
+ this.onPwa = window.matchMedia('(display-mode: standalone)').matches
+ }
+
+ async pwaReload() {
+ const loader = this.loader.open('Reloading PWA...').subscribe()
+
+ try {
+ // attempt to update to the latest client version available
+ await this.updates.activateUpdate()
+ } catch (e) {
+ console.error('Error activating update from service worker: ', e)
+ } finally {
+ loader.unsubscribe()
+ // always reload, as this resolves most out of sync cases
+ window.location.reload()
+ }
+ }
+
+ onDismiss() {
+ this.dismiss$.next(false)
+ }
+}
diff --git a/web/projects/ui/src/app/common/toast-container/refresh-alert/refresh-alert.component.html b/web/projects/ui/src/app/common/toast-container/refresh-alert/refresh-alert.component.html
deleted file mode 100644
index cb412aae9..000000000
--- a/web/projects/ui/src/app/common/toast-container/refresh-alert/refresh-alert.component.html
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/web/projects/ui/src/app/common/toast-container/refresh-alert/refresh-alert.component.ts b/web/projects/ui/src/app/common/toast-container/refresh-alert/refresh-alert.component.ts
deleted file mode 100644
index 708b9ca22..000000000
--- a/web/projects/ui/src/app/common/toast-container/refresh-alert/refresh-alert.component.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
-import { Observable, Subject, merge, debounceTime } from 'rxjs'
-
-import { RefreshAlertService } from './refresh-alert.service'
-import { SwUpdate } from '@angular/service-worker'
-import { LoadingController } from '@ionic/angular'
-
-@Component({
- selector: 'refresh-alert',
- templateUrl: './refresh-alert.component.html',
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class RefreshAlertComponent {
- private readonly dismiss$ = new Subject()
-
- readonly show$ = merge(this.dismiss$, this.refresh$).pipe(debounceTime(0))
-
- // @TODO use this like we did on 0344
- onPwa = false
-
- constructor(
- @Inject(RefreshAlertService) private readonly refresh$: Observable,
- private readonly updates: SwUpdate,
- private readonly loadingCtrl: LoadingController,
- ) {}
-
- ngOnInit() {
- this.onPwa = window.matchMedia('(display-mode: standalone)').matches
- }
-
- async pwaReload() {
- const loader = await this.loadingCtrl.create({
- message: 'Reloading PWA...',
- })
- await loader.present()
- try {
- // attempt to update to the latest client version available
- await this.updates.activateUpdate()
- } catch (e) {
- console.error('Error activating update from service worker: ', e)
- } finally {
- loader.dismiss()
- // always reload, as this resolves most out of sync cases
- window.location.reload()
- }
- }
-
- onDismiss() {
- this.dismiss$.next(false)
- }
-}
diff --git a/web/projects/ui/src/app/common/toast-container/refresh-alert/refresh-alert.service.ts b/web/projects/ui/src/app/common/toast-container/refresh-alert/refresh-alert.service.ts
deleted file mode 100644
index 134add795..000000000
--- a/web/projects/ui/src/app/common/toast-container/refresh-alert/refresh-alert.service.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Injectable } from '@angular/core'
-import { Emver } from '@start9labs/shared'
-import { PatchDB } from 'patch-db-client'
-import { endWith, map, Observable } from 'rxjs'
-import { ConfigService } from 'src/app/services/config.service'
-import { DataModel } from 'src/app/services/patch-db/data-model'
-
-@Injectable({ providedIn: 'root' })
-export class RefreshAlertService extends Observable {
- private readonly stream$ = this.patch.watch$('server-info', 'version').pipe(
- map(version => !!this.emver.compare(this.config.version, version)),
- endWith(false),
- )
-
- constructor(
- private readonly patch: PatchDB,
- private readonly emver: Emver,
- private readonly config: ConfigService,
- ) {
- super(subscriber => this.stream$.subscribe(subscriber))
- }
-}
diff --git a/web/projects/ui/src/app/common/toast-container/toast-container.component.html b/web/projects/ui/src/app/common/toast-container/toast-container.component.html
deleted file mode 100644
index 3b7ca2cf1..000000000
--- a/web/projects/ui/src/app/common/toast-container/toast-container.component.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/web/projects/ui/src/app/common/toast-container/toast-container.component.ts b/web/projects/ui/src/app/common/toast-container/toast-container.component.ts
index 161ddc076..edf74664f 100644
--- a/web/projects/ui/src/app/common/toast-container/toast-container.component.ts
+++ b/web/projects/ui/src/app/common/toast-container/toast-container.component.ts
@@ -1,8 +1,21 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
+import { NotificationsToastComponent } from './notifications-toast.component'
+import { RefreshAlertComponent } from './refresh-alert.component'
+import { UpdateToastComponent } from './update-toast.component'
@Component({
+ standalone: true,
selector: 'toast-container',
- templateUrl: './toast-container.component.html',
+ template: `
+
+
+
+ `,
changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [
+ NotificationsToastComponent,
+ UpdateToastComponent,
+ RefreshAlertComponent,
+ ],
})
export class ToastContainerComponent {}
diff --git a/web/projects/ui/src/app/common/toast-container/toast-container.module.ts b/web/projects/ui/src/app/common/toast-container/toast-container.module.ts
deleted file mode 100644
index bf24027d3..000000000
--- a/web/projects/ui/src/app/common/toast-container/toast-container.module.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { CommonModule } from '@angular/common'
-import { NgModule } from '@angular/core'
-import { RouterModule } from '@angular/router'
-import { TuiAutoFocusModule } from '@taiga-ui/cdk'
-import { TuiAlertModule, TuiDialogModule } from '@taiga-ui/core'
-import { TuiButtonModule } from '@taiga-ui/experimental'
-
-import { ToastContainerComponent } from './toast-container.component'
-import { NotificationsToastComponent } from './notifications-toast/notifications-toast.component'
-import { RefreshAlertComponent } from './refresh-alert/refresh-alert.component'
-import { UpdateToastComponent } from './update-toast/update-toast.component'
-
-@NgModule({
- imports: [
- CommonModule,
- RouterModule,
- TuiDialogModule,
- TuiButtonModule,
- TuiAutoFocusModule,
- TuiAlertModule,
- ],
- declarations: [
- ToastContainerComponent,
- NotificationsToastComponent,
- RefreshAlertComponent,
- UpdateToastComponent,
- ],
- exports: [ToastContainerComponent],
-})
-export class ToastContainerModule {}
diff --git a/web/projects/ui/src/app/common/toast-container/update-toast.component.ts b/web/projects/ui/src/app/common/toast-container/update-toast.component.ts
new file mode 100644
index 000000000..6396398fa
--- /dev/null
+++ b/web/projects/ui/src/app/common/toast-container/update-toast.component.ts
@@ -0,0 +1,79 @@
+import { AsyncPipe } from '@angular/common'
+import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
+import { ErrorService, LoadingService } from '@start9labs/shared'
+import { TuiAlertModule } from '@taiga-ui/core'
+import { TuiButtonModule } from '@taiga-ui/experimental'
+import { PatchDB } from 'patch-db-client'
+import {
+ distinctUntilChanged,
+ endWith,
+ filter,
+ merge,
+ Observable,
+ Subject,
+} from 'rxjs'
+import { ApiService } from 'src/app/services/api/embassy-api.service'
+import { DataModel } from 'src/app/services/patch-db/data-model'
+
+@Component({
+ standalone: true,
+ selector: 'update-toast',
+ template: `
+
+ Restart your server for these updates to take effect. It can take several
+ minutes to come back online.
+
+
+
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [TuiButtonModule, TuiAlertModule, AsyncPipe],
+})
+export class UpdateToastComponent {
+ private readonly api = inject(ApiService)
+ private readonly errorService = inject(ErrorService)
+ private readonly loader = inject(LoadingService)
+ private readonly dismiss$ = new Subject()
+
+ readonly visible$: Observable = merge(
+ this.dismiss$,
+ inject(PatchDB)
+ .watch$('server-info', 'status-info', 'updated')
+ .pipe(distinctUntilChanged(), filter(Boolean), endWith(false)),
+ )
+
+ onDismiss() {
+ this.dismiss$.next(false)
+ }
+
+ async restart(): Promise {
+ this.onDismiss()
+
+ const loader = this.loader.open('Restarting...').subscribe()
+
+ try {
+ await this.api.restartServer({})
+ } catch (e: any) {
+ await this.errorService.handleError(e)
+ } finally {
+ await loader.unsubscribe()
+ }
+ }
+}
diff --git a/web/projects/ui/src/app/common/toast-container/update-toast/update-toast.component.html b/web/projects/ui/src/app/common/toast-container/update-toast/update-toast.component.html
deleted file mode 100644
index 8cd93b647..000000000
--- a/web/projects/ui/src/app/common/toast-container/update-toast/update-toast.component.html
+++ /dev/null
@@ -1,23 +0,0 @@
-
- Restart your server for these updates to take effect. It can take several
- minutes to come back online.
-
-
-
-
diff --git a/web/projects/ui/src/app/common/toast-container/update-toast/update-toast.component.ts b/web/projects/ui/src/app/common/toast-container/update-toast/update-toast.component.ts
deleted file mode 100644
index 0b02faa4e..000000000
--- a/web/projects/ui/src/app/common/toast-container/update-toast/update-toast.component.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
-import { ErrorService, LoadingService } from '@start9labs/shared'
-import { Observable, Subject, merge } from 'rxjs'
-
-import { UpdateToastService } from './update-toast.service'
-import { ApiService } from 'src/app/services/api/embassy-api.service'
-
-@Component({
- selector: 'update-toast',
- templateUrl: './update-toast.component.html',
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class UpdateToastComponent {
- private readonly dismiss$ = new Subject()
-
- readonly visible$: Observable = merge(this.dismiss$, this.update$)
-
- constructor(
- @Inject(UpdateToastService) private readonly update$: Observable,
- private readonly embassyApi: ApiService,
- private readonly errorService: ErrorService,
- private readonly loader: LoadingService,
- ) {}
-
- onDismiss() {
- this.dismiss$.next(false)
- }
-
- async restart(): Promise {
- this.onDismiss()
-
- const loader = this.loader.open('Restarting...').subscribe()
-
- try {
- await this.embassyApi.restartServer({})
- } catch (e: any) {
- await this.errorService.handleError(e)
- } finally {
- await loader.unsubscribe()
- }
- }
-}
diff --git a/web/projects/ui/src/app/common/toast-container/update-toast/update-toast.service.ts b/web/projects/ui/src/app/common/toast-container/update-toast/update-toast.service.ts
deleted file mode 100644
index 77afbe736..000000000
--- a/web/projects/ui/src/app/common/toast-container/update-toast/update-toast.service.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Injectable } from '@angular/core'
-import { distinctUntilChanged, filter, endWith, Observable } from 'rxjs'
-import { PatchDB } from 'patch-db-client'
-import { DataModel } from 'src/app/services/patch-db/data-model'
-
-@Injectable({ providedIn: 'root' })
-export class UpdateToastService extends Observable {
- private readonly stream$ = this.patch
- .watch$('server-info', 'status-info', 'updated')
- .pipe(distinctUntilChanged(), filter(Boolean), endWith(false))
-
- constructor(private readonly patch: PatchDB) {
- super(subscriber => this.stream$.subscribe(subscriber))
- }
-}
diff --git a/web/projects/ui/src/app/common/widget-list/any-link/any-link.component.html b/web/projects/ui/src/app/common/widget-list/any-link/any-link.component.html
deleted file mode 100644
index 67a2956f5..000000000
--- a/web/projects/ui/src/app/common/widget-list/any-link/any-link.component.html
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/web/projects/ui/src/app/common/widget-list/any-link/any-link.component.scss b/web/projects/ui/src/app/common/widget-list/any-link/any-link.component.scss
deleted file mode 100644
index 81759f278..000000000
--- a/web/projects/ui/src/app/common/widget-list/any-link/any-link.component.scss
+++ /dev/null
@@ -1,4 +0,0 @@
-a {
- text-decoration: none;
- color: unset;
-}
\ No newline at end of file
diff --git a/web/projects/ui/src/app/common/widget-list/any-link/any-link.component.ts b/web/projects/ui/src/app/common/widget-list/any-link/any-link.component.ts
deleted file mode 100644
index 0e8d6f67d..000000000
--- a/web/projects/ui/src/app/common/widget-list/any-link/any-link.component.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import {
- Component,
- Input,
- ChangeDetectionStrategy,
- OnInit,
-} from '@angular/core'
-
-@Component({
- selector: 'any-link',
- templateUrl: './any-link.component.html',
- styleUrls: ['./any-link.component.scss'],
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class AnyLinkComponent implements OnInit {
- @Input({ required: true }) link!: string
- @Input() qp?: Record
- externalLink = false
-
- ngOnInit() {
- try {
- const _ = new URL(this.link)
- this.externalLink = true
- } catch {
- this.externalLink = false
- }
- }
-}
diff --git a/web/projects/ui/src/app/common/widget-list/widget-card/widget-card.component.html b/web/projects/ui/src/app/common/widget-list/widget-card/widget-card.component.html
deleted file mode 100644
index dec4bc5e1..000000000
--- a/web/projects/ui/src/app/common/widget-list/widget-card/widget-card.component.html
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
- {{ cardDetails.title }}
-
-
-
-
-
- {{ cardDetails.description }}
-
-
-
-
-
diff --git a/web/projects/ui/src/app/common/widget-list/widget-card/widget-card.component.scss b/web/projects/ui/src/app/common/widget-list/widget-card/widget-card.component.scss
deleted file mode 100644
index 687370f0f..000000000
--- a/web/projects/ui/src/app/common/widget-list/widget-card/widget-card.component.scss
+++ /dev/null
@@ -1,68 +0,0 @@
-ion-card {
- background: rgba(70, 70, 70, 0.31);
- box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25);
- border-radius: 44px;
- margin: auto;
- max-height: 100%;
- max-width: 100%;
- text-align: center;
- transition: all 350ms ease;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
-
- &:hover {
- transition-property: transform;
- transform: scale(1.05);
- transition-delay: 40ms;
- }
-
- ion-card-title {
- font-family: 'Open Sans', sans-serif;
- padding: 0.6rem;
- font-weight: 600;
- height: 2.4rem;
- }
-
- ion-card-content {
- min-height: 8rem;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
-
- ion-icon {
- font-size: calc(90px + 0.4vw);
- --ionicon-stroke-width: 1rem;
- }
- }
-
- ion-footer {
- padding: 0 1rem;
- font-family: 'Open Sans';
- font-size: clamp(1rem, calc(12px + 0.5vw), 1.3rem);
- height: 4.5rem;
- width: clamp(13rem, 80%, 18rem);
- margin: 0 auto;
- * {
- max-width: 100%;
- }
- p {
- margin-top: 0;
- }
- }
-
- .footer-md::before {
- background-image: none;
- }
-}
-
-@media (max-width: 900px) {
- ion-footer {
- width: 10rem;
- }
-}
-
-@media (max-width: 1200px) {
- ion-footer {
- width: 14rem;
- }
-}
diff --git a/web/projects/ui/src/app/common/widget-list/widget-card/widget-card.component.ts b/web/projects/ui/src/app/common/widget-list/widget-card/widget-card.component.ts
deleted file mode 100644
index 5b1ba93e9..000000000
--- a/web/projects/ui/src/app/common/widget-list/widget-card/widget-card.component.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import {
- ChangeDetectionStrategy,
- Component,
- ElementRef,
- HostListener,
- Input,
- ViewChild,
-} from '@angular/core'
-
-@Component({
- selector: 'widget-card',
- templateUrl: './widget-card.component.html',
- styleUrls: ['./widget-card.component.scss'],
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class WidgetCardComponent {
- @Input({ required: true }) cardDetails!: Card
- @Input({ required: true }) containerDimensions!: Dimension
- @ViewChild('outerWrapper') outerWrapper: ElementRef =
- {} as ElementRef
- @ViewChild('innerWrapper') innerWrapper: ElementRef =
- {} as ElementRef
- @HostListener('window:resize', ['$event'])
- onResize() {
- this.resize()
- }
- maxHeight = 0
- maxWidth = 0
- innerTransform = ''
- outerWidth: any
- outerHeight: any
-
- ngAfterViewInit() {
- this.maxHeight = ( (
- this.innerWrapper.nativeElement
- )).getBoundingClientRect().height
- this.maxWidth = ( (
- this.innerWrapper.nativeElement
- )).getBoundingClientRect().width
- this.resize()
- }
-
- resize() {
- const height = this.containerDimensions.height
- const width = this.containerDimensions.width
- const isMax = width >= this.maxWidth && height >= this.maxHeight
- const scale = Math.min(width / this.maxWidth, height / this.maxHeight)
- this.innerTransform = isMax ? '' : 'scale(' + scale + ')'
- this.outerWidth = isMax ? '' : this.maxWidth * scale
- this.outerHeight = isMax ? '' : this.maxHeight * scale
- }
-}
-
-export interface Dimension {
- height: number
- width: number
-}
-
-export interface Card {
- title: string
- icon: string
- color: string
- description: string
- link: string
- qp?: Record
-}
diff --git a/web/projects/ui/src/app/common/widget-list/widget-list.component.html b/web/projects/ui/src/app/common/widget-list/widget-list.component.html
deleted file mode 100644
index 2c013d1c3..000000000
--- a/web/projects/ui/src/app/common/widget-list/widget-list.component.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/web/projects/ui/src/app/common/widget-list/widget-list.component.module.ts b/web/projects/ui/src/app/common/widget-list/widget-list.component.module.ts
deleted file mode 100644
index b75bad20b..000000000
--- a/web/projects/ui/src/app/common/widget-list/widget-list.component.module.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { NgModule } from '@angular/core'
-import { CommonModule } from '@angular/common'
-import { IonicModule } from '@ionic/angular'
-import { RouterModule } from '@angular/router'
-import { AnyLinkComponent } from './any-link/any-link.component'
-import { WidgetListComponent } from './widget-list.component'
-import { WidgetCardComponent } from './widget-card/widget-card.component'
-
-@NgModule({
- declarations: [WidgetListComponent, WidgetCardComponent, AnyLinkComponent],
- imports: [CommonModule, IonicModule, RouterModule],
- exports: [WidgetListComponent],
-})
-export class WidgetListComponentModule {}
diff --git a/web/projects/ui/src/app/common/widget-list/widget-list.component.scss b/web/projects/ui/src/app/common/widget-list/widget-list.component.scss
deleted file mode 100644
index 843e0d9a5..000000000
--- a/web/projects/ui/src/app/common/widget-list/widget-list.component.scss
+++ /dev/null
@@ -1,19 +0,0 @@
-ion-col {
- max-width: 22rem !important;
- --ion-grid-column-padding: 1rem;
-}
-
-@media (min-width: 1700px) {
- div {
- padding: 0 7%;
- }
- ion-col {
- max-width: 24rem !important;
- }
-}
-
-@media (min-width: 2000px) {
- div {
- padding: 0 12%;
- }
-}
\ No newline at end of file
diff --git a/web/projects/ui/src/app/common/widget-list/widget-list.component.ts b/web/projects/ui/src/app/common/widget-list/widget-list.component.ts
deleted file mode 100644
index f6514e4ee..000000000
--- a/web/projects/ui/src/app/common/widget-list/widget-list.component.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import {
- ChangeDetectionStrategy,
- Component,
- ElementRef,
- HostListener,
- ViewChild,
-} from '@angular/core'
-import { Card, Dimension } from './widget-card/widget-card.component'
-
-@Component({
- selector: 'widget-list',
- templateUrl: './widget-list.component.html',
- styleUrls: ['./widget-list.component.scss'],
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class WidgetListComponent {
- @ViewChild('gridContent')
- gridContent: ElementRef = {} as ElementRef
- @HostListener('window:resize', ['$event'])
- onResize() {
- this.setContainerDimensions()
- }
-
- containerDimensions: Dimension = {} as Dimension
-
- ngAfterViewInit() {
- this.setContainerDimensions()
- }
-
- setContainerDimensions() {
- this.containerDimensions.height = ( (
- this.gridContent.nativeElement
- )).getBoundingClientRect().height
- this.containerDimensions.width = ( (
- this.gridContent.nativeElement
- )).getBoundingClientRect().width
- }
-
- cards: Card[] = [
- {
- title: 'Server Info',
- icon: 'information-circle-outline',
- color: 'var(--alt-green)',
- description: 'View information about your server',
- link: '/system/specs',
- },
- {
- title: 'Browse',
- icon: 'storefront-outline',
- color: 'var(--alt-purple)',
- description: 'Browse for services to install',
- link: '/marketplace',
- qp: { back: 'true' },
- },
- {
- title: 'Create Backup',
- icon: 'duplicate-outline',
- color: 'var(--alt-blue)',
- description: 'Back up StartOS and service data',
- link: '/system/backup',
- },
- {
- title: 'Monitor',
- icon: 'pulse-outline',
- color: 'var(--alt-orange)',
- description: `View your system resource usage`,
- link: '/system/metrics',
- },
- {
- title: 'User Manual',
- icon: 'map-outline',
- color: 'var(--alt-yellow)',
- description: 'Discover what StartOS can do',
- link: 'https://docs.start9.com/0.3.5.x/user-manual/index',
- },
- {
- title: 'Contact Support',
- icon: 'chatbubbles-outline',
- color: 'var(--alt-red)',
- description: 'Get help from the Start9 community',
- link: 'https://start9.com/contact',
- },
- ]
-}
diff --git a/web/projects/ui/src/app/route-animation.ts b/web/projects/ui/src/app/route-animation.ts
deleted file mode 100644
index 17d6e2b75..000000000
--- a/web/projects/ui/src/app/route-animation.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import {
- animate,
- group,
- query,
- style,
- transition,
- trigger,
-} from '@angular/animations'
-export const slideInAnimation = trigger('routeAnimations', [
- transition('* => *', [
- query(':enter, :leave', style({ position: 'fixed', width: '100%' }), {
- optional: true,
- }),
- group([
- query(
- ':enter',
- [
- style({ transform: 'translateX(-100%)' }),
- animate('1s ease-in-out', style({ transform: 'translateX(0%)' })),
- ],
- { optional: true },
- ),
- query(
- ':leave',
- [
- style({ transform: 'translateX(0%)' }),
- animate('1s ease-in-out', style({ transform: 'translateX(100%)' })),
- ],
- { optional: true },
- ),
- ]),
- ]),
-])