mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
frontend start-tunnel updates
This commit is contained in:
@@ -3,6 +3,7 @@ import { Component, inject } from '@angular/core'
|
|||||||
import { RouterOutlet } from '@angular/router'
|
import { RouterOutlet } from '@angular/router'
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
||||||
import { PatchService } from './services/patch.service'
|
import { PatchService } from './services/patch.service'
|
||||||
|
import { UpdateService } from './services/update.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@@ -24,4 +25,6 @@ export class App {
|
|||||||
readonly subscription = inject(PatchService)
|
readonly subscription = inject(PatchService)
|
||||||
.pipe(takeUntilDestroyed())
|
.pipe(takeUntilDestroyed())
|
||||||
.subscribe()
|
.subscribe()
|
||||||
|
|
||||||
|
readonly updates = inject(UpdateService)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
|||||||
import { Router, RouterLink, RouterLinkActive } from '@angular/router'
|
import { Router, RouterLink, RouterLinkActive } from '@angular/router'
|
||||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||||
import { TuiButton } from '@taiga-ui/core'
|
import { TuiButton } from '@taiga-ui/core'
|
||||||
|
import { TuiBadgeNotification } from '@taiga-ui/kit'
|
||||||
import { ApiService } from 'src/app/services/api/api.service'
|
import { ApiService } from 'src/app/services/api/api.service'
|
||||||
import { AuthService } from 'src/app/services/auth.service'
|
import { AuthService } from 'src/app/services/auth.service'
|
||||||
import { SidebarService } from 'src/app/services/sidebar.service'
|
import { SidebarService } from 'src/app/services/sidebar.service'
|
||||||
|
import { UpdateService } from 'src/app/services/update.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'nav',
|
selector: 'nav',
|
||||||
@@ -22,6 +24,19 @@ import { SidebarService } from 'src/app/services/sidebar.service'
|
|||||||
{{ route.name }}
|
{{ route.name }}
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
|
<a
|
||||||
|
tuiButton
|
||||||
|
size="s"
|
||||||
|
appearance="flat-grayscale"
|
||||||
|
routerLinkActive="active"
|
||||||
|
iconStart="@tui.settings"
|
||||||
|
routerLink="settings"
|
||||||
|
>
|
||||||
|
Settings
|
||||||
|
@if (update.hasUpdate()) {
|
||||||
|
<tui-badge-notification size="s" appearance="positive" />
|
||||||
|
}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
tuiButton
|
tuiButton
|
||||||
@@ -57,6 +72,11 @@ import { SidebarService } from 'src/app/services/sidebar.service'
|
|||||||
&.active {
|
&.active {
|
||||||
background: var(--tui-background-neutral-1);
|
background: var(--tui-background-neutral-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tui-badge-notification {
|
||||||
|
margin-inline-start: auto;
|
||||||
|
background: var(--tui-status-positive);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@@ -78,7 +98,7 @@ import { SidebarService } from 'src/app/services/sidebar.service'
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [TuiButton, RouterLink, RouterLinkActive],
|
imports: [TuiButton, TuiBadgeNotification, RouterLink, RouterLinkActive],
|
||||||
host: {
|
host: {
|
||||||
'[class._expanded]': 'sidebars.start()',
|
'[class._expanded]': 'sidebars.start()',
|
||||||
'(document:click)': 'sidebars.start.set(false)',
|
'(document:click)': 'sidebars.start.set(false)',
|
||||||
@@ -92,6 +112,7 @@ export class Nav {
|
|||||||
protected readonly api = inject(ApiService)
|
protected readonly api = inject(ApiService)
|
||||||
private readonly loader = inject(LoadingService)
|
private readonly loader = inject(LoadingService)
|
||||||
private readonly errorService = inject(ErrorService)
|
private readonly errorService = inject(ErrorService)
|
||||||
|
protected readonly update = inject(UpdateService)
|
||||||
|
|
||||||
protected readonly routes = [
|
protected readonly routes = [
|
||||||
{
|
{
|
||||||
@@ -109,11 +130,6 @@ export class Nav {
|
|||||||
icon: '@tui.globe',
|
icon: '@tui.globe',
|
||||||
link: 'port-forwards',
|
link: 'port-forwards',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'Settings',
|
|
||||||
icon: '@tui.settings',
|
|
||||||
link: 'settings',
|
|
||||||
},
|
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
protected async logout() {
|
protected async logout() {
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
import { AsyncPipe } from '@angular/common'
|
||||||
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
inject,
|
||||||
|
signal,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { toSignal } from '@angular/core/rxjs-interop'
|
||||||
|
import {
|
||||||
|
NonNullableFormBuilder,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
ValidatorFn,
|
||||||
|
Validators,
|
||||||
|
} from '@angular/forms'
|
||||||
|
import { ErrorService } from '@start9labs/shared'
|
||||||
|
import { TuiAutoFocus, TuiValidator } from '@taiga-ui/cdk'
|
||||||
|
import {
|
||||||
|
TuiAlertService,
|
||||||
|
TuiButton,
|
||||||
|
TuiDialogContext,
|
||||||
|
TuiError,
|
||||||
|
TuiTextfield,
|
||||||
|
} from '@taiga-ui/core'
|
||||||
|
import {
|
||||||
|
TuiButtonLoading,
|
||||||
|
TuiFieldErrorPipe,
|
||||||
|
tuiValidationErrorsProvider,
|
||||||
|
} from '@taiga-ui/kit'
|
||||||
|
import { TuiForm } from '@taiga-ui/layout'
|
||||||
|
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||||
|
import { map } from 'rxjs'
|
||||||
|
import { ApiService } from 'src/app/services/api/api.service'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<form tuiForm [formGroup]="form">
|
||||||
|
<tui-textfield>
|
||||||
|
<label tuiLabel>New password</label>
|
||||||
|
<input tuiTextfield tuiAutoFocus formControlName="password" />
|
||||||
|
</tui-textfield>
|
||||||
|
<tui-error
|
||||||
|
formControlName="password"
|
||||||
|
[error]="[] | tuiFieldError | async"
|
||||||
|
/>
|
||||||
|
<tui-textfield>
|
||||||
|
<label tuiLabel>Confirm new password</label>
|
||||||
|
<input
|
||||||
|
tuiTextfield
|
||||||
|
formControlName="confirm"
|
||||||
|
[tuiValidator]="matchValidator()"
|
||||||
|
/>
|
||||||
|
</tui-textfield>
|
||||||
|
<tui-error
|
||||||
|
formControlName="confirm"
|
||||||
|
[error]="[] | tuiFieldError | async"
|
||||||
|
/>
|
||||||
|
<footer>
|
||||||
|
<button
|
||||||
|
tuiButton
|
||||||
|
(click)="onSave()"
|
||||||
|
[loading]="loading()"
|
||||||
|
[disabled]="formInvalid()"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
`,
|
||||||
|
providers: [
|
||||||
|
tuiValidationErrorsProvider({
|
||||||
|
required: 'This field is required',
|
||||||
|
minlength: 'Password must be at least 8 characters',
|
||||||
|
maxlength: 'Password cannot exceed 64 characters',
|
||||||
|
match: 'Passwords do not match',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
TuiAutoFocus,
|
||||||
|
TuiButton,
|
||||||
|
TuiButtonLoading,
|
||||||
|
TuiError,
|
||||||
|
TuiFieldErrorPipe,
|
||||||
|
TuiForm,
|
||||||
|
TuiTextfield,
|
||||||
|
TuiValidator,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ChangePasswordDialog {
|
||||||
|
private readonly context = injectContext<TuiDialogContext<void>>()
|
||||||
|
private readonly api = inject(ApiService)
|
||||||
|
private readonly alerts = inject(TuiAlertService)
|
||||||
|
private readonly errorService = inject(ErrorService)
|
||||||
|
|
||||||
|
protected readonly loading = signal(false)
|
||||||
|
protected readonly form = inject(NonNullableFormBuilder).group({
|
||||||
|
password: [
|
||||||
|
'',
|
||||||
|
[Validators.required, Validators.minLength(8), Validators.maxLength(64)],
|
||||||
|
],
|
||||||
|
confirm: [
|
||||||
|
'',
|
||||||
|
[Validators.required, Validators.minLength(8), Validators.maxLength(64)],
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
protected readonly matchValidator = toSignal(
|
||||||
|
this.form.controls.password.valueChanges.pipe(
|
||||||
|
map(
|
||||||
|
(password): ValidatorFn =>
|
||||||
|
({ value }) =>
|
||||||
|
value === password ? null : { match: true },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
{ initialValue: Validators.nullValidator },
|
||||||
|
)
|
||||||
|
|
||||||
|
protected readonly formInvalid = toSignal(
|
||||||
|
this.form.statusChanges.pipe(map(() => this.form.invalid)),
|
||||||
|
{ initialValue: this.form.invalid },
|
||||||
|
)
|
||||||
|
|
||||||
|
protected async onSave() {
|
||||||
|
this.loading.set(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.api.setPassword({ password: this.form.getRawValue().password })
|
||||||
|
this.alerts
|
||||||
|
.open('Password changed', { label: 'Success', appearance: 'positive' })
|
||||||
|
.subscribe()
|
||||||
|
this.context.$implicit.complete()
|
||||||
|
} catch (e: any) {
|
||||||
|
this.errorService.handleError(e)
|
||||||
|
} finally {
|
||||||
|
this.loading.set(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CHANGE_PASSWORD = new PolymorpheusComponent(ChangePasswordDialog)
|
||||||
@@ -1,142 +1,101 @@
|
|||||||
import { AsyncPipe } from '@angular/common'
|
|
||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
inject,
|
inject,
|
||||||
signal,
|
signal,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { toSignal } from '@angular/core/rxjs-interop'
|
|
||||||
import {
|
|
||||||
NonNullableFormBuilder,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
ValidatorFn,
|
|
||||||
Validators,
|
|
||||||
} from '@angular/forms'
|
|
||||||
import { ErrorService } from '@start9labs/shared'
|
import { ErrorService } from '@start9labs/shared'
|
||||||
import { tuiMarkControlAsTouchedAndValidate, TuiValidator } from '@taiga-ui/cdk'
|
import { TuiAppearance, TuiButton, TuiTitle } from '@taiga-ui/core'
|
||||||
import {
|
import { TuiDialogService } from '@taiga-ui/experimental'
|
||||||
TuiAlertService,
|
import { TuiBadge, TuiButtonLoading } from '@taiga-ui/kit'
|
||||||
TuiAppearance,
|
import { TuiCard, TuiCell } from '@taiga-ui/layout'
|
||||||
TuiButton,
|
import { UpdateService } from 'src/app/services/update.service'
|
||||||
TuiError,
|
|
||||||
TuiTextfield,
|
import { CHANGE_PASSWORD } from './change-password'
|
||||||
TuiTitle,
|
|
||||||
} from '@taiga-ui/core'
|
|
||||||
import {
|
|
||||||
TuiButtonLoading,
|
|
||||||
TuiFieldErrorPipe,
|
|
||||||
tuiValidationErrorsProvider,
|
|
||||||
} from '@taiga-ui/kit'
|
|
||||||
import { TuiCard, TuiForm, TuiHeader } from '@taiga-ui/layout'
|
|
||||||
import { map } from 'rxjs'
|
|
||||||
import { ApiService } from 'src/app/services/api/api.service'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
<form tuiCardLarge tuiAppearance="neutral" tuiForm [formGroup]="form">
|
<div tuiCardLarge tuiAppearance="neutral">
|
||||||
<header tuiHeader>
|
<div tuiCell>
|
||||||
<h2 tuiTitle>
|
<span tuiTitle>
|
||||||
Settings
|
<strong>
|
||||||
<span tuiSubtitle>Change password</span>
|
Version
|
||||||
</h2>
|
@if (update.hasUpdate()) {
|
||||||
</header>
|
<tui-badge appearance="positive" size="s">
|
||||||
<tui-textfield>
|
Update Available
|
||||||
<label tuiLabel>New password</label>
|
</tui-badge>
|
||||||
<input formControlName="password" tuiTextfield />
|
}
|
||||||
</tui-textfield>
|
</strong>
|
||||||
<tui-error
|
<span tuiSubtitle>Current: {{ update.installed() ?? '—' }}</span>
|
||||||
formControlName="password"
|
</span>
|
||||||
[error]="[] | tuiFieldError | async"
|
@if (update.hasUpdate()) {
|
||||||
/>
|
<button tuiButton size="s" [loading]="applying()" (click)="onApply()">
|
||||||
<tui-textfield>
|
Update to {{ update.candidate() }}
|
||||||
<label tuiLabel>Confirm new password</label>
|
</button>
|
||||||
<input
|
} @else {
|
||||||
formControlName="confirm"
|
<button
|
||||||
tuiTextfield
|
tuiButton
|
||||||
[tuiValidator]="matchValidator()"
|
size="s"
|
||||||
/>
|
appearance="secondary"
|
||||||
</tui-textfield>
|
[loading]="checking()"
|
||||||
<tui-error
|
(click)="onCheckUpdate()"
|
||||||
formControlName="confirm"
|
>
|
||||||
[error]="[] | tuiFieldError | async"
|
Check for updates
|
||||||
/>
|
</button>
|
||||||
<footer>
|
}
|
||||||
<button tuiButton (click)="onSave()" [loading]="loading()">Save</button>
|
</div>
|
||||||
</footer>
|
<div tuiCell>
|
||||||
</form>
|
<span tuiTitle>
|
||||||
|
<strong>Change password</strong>
|
||||||
|
</span>
|
||||||
|
<button tuiButton size="s" (click)="onChangePassword()">Change</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
`,
|
`,
|
||||||
providers: [
|
|
||||||
tuiValidationErrorsProvider({
|
|
||||||
required: 'This field is required',
|
|
||||||
minlength: 'Password must be at least 8 characters',
|
|
||||||
maxlength: 'Password cannot exceed 64 characters',
|
|
||||||
match: 'Passwords do not match',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [
|
imports: [
|
||||||
ReactiveFormsModule,
|
|
||||||
AsyncPipe,
|
|
||||||
TuiCard,
|
TuiCard,
|
||||||
TuiForm,
|
TuiCell,
|
||||||
TuiHeader,
|
|
||||||
TuiTitle,
|
TuiTitle,
|
||||||
TuiTextfield,
|
|
||||||
TuiError,
|
|
||||||
TuiFieldErrorPipe,
|
|
||||||
TuiButton,
|
TuiButton,
|
||||||
TuiButtonLoading,
|
TuiButtonLoading,
|
||||||
TuiValidator,
|
TuiBadge,
|
||||||
TuiAppearance,
|
TuiAppearance,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export default class Settings {
|
export default class Settings {
|
||||||
private readonly api = inject(ApiService)
|
private readonly dialogs = inject(TuiDialogService)
|
||||||
private readonly alerts = inject(TuiAlertService)
|
|
||||||
private readonly errorService = inject(ErrorService)
|
private readonly errorService = inject(ErrorService)
|
||||||
|
|
||||||
protected readonly loading = signal(false)
|
protected readonly update = inject(UpdateService)
|
||||||
protected readonly form = inject(NonNullableFormBuilder).group({
|
protected readonly checking = signal(false)
|
||||||
password: [
|
protected readonly applying = signal(false)
|
||||||
'',
|
|
||||||
[Validators.required, Validators.minLength(8), Validators.maxLength(64)],
|
|
||||||
],
|
|
||||||
confirm: [
|
|
||||||
'',
|
|
||||||
[Validators.required, Validators.minLength(8), Validators.maxLength(64)],
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
protected readonly matchValidator = toSignal(
|
protected onChangePassword(): void {
|
||||||
this.form.controls.password.valueChanges.pipe(
|
this.dialogs.open(CHANGE_PASSWORD, { label: 'Change Password' }).subscribe()
|
||||||
map(
|
}
|
||||||
(password): ValidatorFn =>
|
|
||||||
({ value }) =>
|
|
||||||
value === password ? null : { match: true },
|
|
||||||
),
|
|
||||||
),
|
|
||||||
{ initialValue: Validators.nullValidator },
|
|
||||||
)
|
|
||||||
|
|
||||||
protected async onSave() {
|
protected async onCheckUpdate() {
|
||||||
if (this.form.invalid) {
|
this.checking.set(true)
|
||||||
tuiMarkControlAsTouchedAndValidate(this.form)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loading.set(true)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.api.setPassword({ password: this.form.getRawValue().password })
|
await this.update.checkUpdate()
|
||||||
this.alerts
|
|
||||||
.open('Password changed', { label: 'Success', appearance: 'positive' })
|
|
||||||
.subscribe()
|
|
||||||
this.form.reset()
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errorService.handleError(e)
|
this.errorService.handleError(e)
|
||||||
} finally {
|
} finally {
|
||||||
this.loading.set(false)
|
this.checking.set(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async onApply() {
|
||||||
|
this.applying.set(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.update.applyUpdate()
|
||||||
|
} catch (e: any) {
|
||||||
|
this.errorService.handleError(e)
|
||||||
|
} finally {
|
||||||
|
this.applying.set(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
120
web/projects/start-tunnel/src/app/services/update.service.ts
Normal file
120
web/projects/start-tunnel/src/app/services/update.service.ts
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import { Component, computed, inject, Injectable, signal } from '@angular/core'
|
||||||
|
import { toObservable } from '@angular/core/rxjs-interop'
|
||||||
|
import { ErrorService } from '@start9labs/shared'
|
||||||
|
import { TuiLoader } from '@taiga-ui/core'
|
||||||
|
import { TuiDialogService } from '@taiga-ui/experimental'
|
||||||
|
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||||
|
import {
|
||||||
|
catchError,
|
||||||
|
EMPTY,
|
||||||
|
filter,
|
||||||
|
from,
|
||||||
|
interval,
|
||||||
|
Subscription,
|
||||||
|
switchMap,
|
||||||
|
takeWhile,
|
||||||
|
} from 'rxjs'
|
||||||
|
import { ApiService, TunnelUpdateResult } from './api/api.service'
|
||||||
|
import { AuthService } from './auth.service'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: '<tui-loader size="xl" [textContent]="text" />',
|
||||||
|
imports: [TuiLoader],
|
||||||
|
})
|
||||||
|
class UpdatingDialog {
|
||||||
|
protected readonly text = 'StartTunnel is updating...'
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class UpdateService {
|
||||||
|
private readonly api = inject(ApiService)
|
||||||
|
private readonly auth = inject(AuthService)
|
||||||
|
private readonly dialogs = inject(TuiDialogService)
|
||||||
|
private readonly errorService = inject(ErrorService)
|
||||||
|
|
||||||
|
readonly result = signal<TunnelUpdateResult | null>(null)
|
||||||
|
readonly hasUpdate = computed(
|
||||||
|
() => this.result()?.status === 'update-available',
|
||||||
|
)
|
||||||
|
readonly installed = computed(() => this.result()?.installed ?? null)
|
||||||
|
readonly candidate = computed(() => this.result()?.candidate ?? null)
|
||||||
|
|
||||||
|
private polling = false
|
||||||
|
private updatingDialog: Subscription | null = null
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
toObservable(this.auth.authenticated)
|
||||||
|
.pipe(filter(Boolean))
|
||||||
|
.subscribe(() => this.initCheck())
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkUpdate(): Promise<void> {
|
||||||
|
const result = await this.api.checkUpdate()
|
||||||
|
this.setResult(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
async applyUpdate(): Promise<void> {
|
||||||
|
const result = await this.api.applyUpdate()
|
||||||
|
this.setResult(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
private setResult(result: TunnelUpdateResult): void {
|
||||||
|
this.result.set(result)
|
||||||
|
|
||||||
|
if (result.status === 'updating') {
|
||||||
|
this.showUpdatingDialog()
|
||||||
|
this.startPolling()
|
||||||
|
} else {
|
||||||
|
this.hideUpdatingDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async initCheck(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.checkUpdate()
|
||||||
|
} catch (e: any) {
|
||||||
|
this.errorService.handleError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private startPolling(): void {
|
||||||
|
if (this.polling) return
|
||||||
|
this.polling = true
|
||||||
|
|
||||||
|
interval(5000)
|
||||||
|
.pipe(
|
||||||
|
switchMap(() =>
|
||||||
|
from(this.api.checkUpdate()).pipe(catchError(() => EMPTY)),
|
||||||
|
),
|
||||||
|
takeWhile(result => result.status === 'updating', true),
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: result => this.result.set(result),
|
||||||
|
complete: () => {
|
||||||
|
this.polling = false
|
||||||
|
this.hideUpdatingDialog()
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.polling = false
|
||||||
|
this.hideUpdatingDialog()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private showUpdatingDialog(): void {
|
||||||
|
if (this.updatingDialog) return
|
||||||
|
this.updatingDialog = this.dialogs
|
||||||
|
.open(new PolymorpheusComponent(UpdatingDialog), {
|
||||||
|
closable: false,
|
||||||
|
dismissible: false,
|
||||||
|
})
|
||||||
|
.subscribe({ complete: () => (this.updatingDialog = null) })
|
||||||
|
}
|
||||||
|
|
||||||
|
private hideUpdatingDialog(): void {
|
||||||
|
this.updatingDialog?.unsubscribe()
|
||||||
|
this.updatingDialog = null
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user