+
+
+
+ Version
+ @if (update.hasUpdate()) {
+
+ Update Available
+
+ }
+
+ Current: {{ update.installed() ?? '—' }}
+
+ @if (update.hasUpdate()) {
+
+ } @else {
+
+ }
+
+
+
+ Change password
+
+
+
+
`,
- 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: [
- ReactiveFormsModule,
- AsyncPipe,
TuiCard,
- TuiForm,
- TuiHeader,
+ TuiCell,
TuiTitle,
- TuiTextfield,
- TuiError,
- TuiFieldErrorPipe,
TuiButton,
TuiButtonLoading,
- TuiValidator,
+ TuiBadge,
TuiAppearance,
],
})
export default class Settings {
- private readonly api = inject(ApiService)
- private readonly alerts = inject(TuiAlertService)
+ private readonly dialogs = inject(TuiDialogService)
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 update = inject(UpdateService)
+ protected readonly checking = signal(false)
+ protected readonly applying = signal(false)
- protected readonly matchValidator = toSignal(
- this.form.controls.password.valueChanges.pipe(
- map(
- (password): ValidatorFn =>
- ({ value }) =>
- value === password ? null : { match: true },
- ),
- ),
- { initialValue: Validators.nullValidator },
- )
+ protected onChangePassword(): void {
+ this.dialogs.open(CHANGE_PASSWORD, { label: 'Change Password' }).subscribe()
+ }
- protected async onSave() {
- if (this.form.invalid) {
- tuiMarkControlAsTouchedAndValidate(this.form)
-
- return
- }
-
- this.loading.set(true)
+ protected async onCheckUpdate() {
+ this.checking.set(true)
try {
- await this.api.setPassword({ password: this.form.getRawValue().password })
- this.alerts
- .open('Password changed', { label: 'Success', appearance: 'positive' })
- .subscribe()
- this.form.reset()
+ await this.update.checkUpdate()
} catch (e: any) {
this.errorService.handleError(e)
} 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)
}
}
}
diff --git a/web/projects/start-tunnel/src/app/services/update.service.ts b/web/projects/start-tunnel/src/app/services/update.service.ts
new file mode 100644
index 000000000..861b5c057
--- /dev/null
+++ b/web/projects/start-tunnel/src/app/services/update.service.ts
@@ -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: '