* tell user to restart server after kiosk chnage

* remove unused import

* dont show tor address on server setup

* chore: address comments

* revert mock

* chore: remove uptime block on mobile

* utiliser le futur proche

---------

Co-authored-by: waterplea <alexander@inkin.ru>
Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>
This commit is contained in:
Matt Hill
2025-11-19 10:35:07 -07:00
committed by GitHub
parent f26791ba39
commit ad0632892e
14 changed files with 111 additions and 129 deletions

View File

@@ -39,7 +39,9 @@ import { DocsLinkDirective } from '@start9labs/shared'
" "
> >
<div> <div>
<h3 style="color: #f8546a; font-weight: bold">Important!</h3> <h2 style="font-variant-caps: all-small-caps">
Root Certificate Authority
</h2>
<p> <p>
Download your server's Root CA and Download your server's Root CA and
<a <a
@@ -47,7 +49,7 @@ import { DocsLinkDirective } from '@start9labs/shared'
path="/user-manual/trust-ca.html" path="/user-manual/trust-ca.html"
style="color: #6866cc; font-weight: bold; text-decoration: none" style="color: #6866cc; font-weight: bold; text-decoration: none"
> >
follow the instructions follow instructions
</a> </a>
to establish a secure connection with your server. to establish a secure connection with your server.
</p> </p>
@@ -84,15 +86,15 @@ import { DocsLinkDirective } from '@start9labs/shared'
" "
> >
<h2 style="font-variant-caps: all-small-caps"> <h2 style="font-variant-caps: all-small-caps">
Access from home (LAN) Permanent Local Address
</h2> </h2>
<p> <p>
Visit the address below when you are connected to the same WiFi or You must be connected to the same Local Area Network (LAN) as your
Local Area Network (LAN) as your server. server to access this address.
</p> </p>
<p <p
style=" style="
padding: 16px; padding: 16px 0;
font-weight: bold; font-weight: bold;
font-size: 1.1rem; font-size: 1.1rem;
overflow: auto; overflow: auto;
@@ -100,33 +102,6 @@ import { DocsLinkDirective } from '@start9labs/shared'
> >
<code id="lan-addr"></code> <code id="lan-addr"></code>
</p> </p>
<h2 style="font-variant-caps: all-small-caps">
Access on the go (Tor)
</h2>
<p>Visit the address below when you are away from home.</p>
<p>
<span style="font-weight: bold">Note:</span>
This address will only work from a Tor-enabled browser.
<a
docsLink
path="/user-manual/connecting-remotely/tor.html"
style="color: #6866cc; font-weight: bold; text-decoration: none"
>
Follow the instructions
</a>
to get setup.
</p>
<p
style="
padding: 16px;
font-weight: bold;
font-size: 1.1rem;
overflow: auto;
"
>
<code id="tor-addr"></code>
</p>
</section> </section>
</div> </div>
</body> </body>

View File

@@ -7,7 +7,7 @@ import {
DOCUMENT, DOCUMENT,
} from '@angular/core' } from '@angular/core'
import { DownloadHTMLService, ErrorService } from '@start9labs/shared' import { DownloadHTMLService, ErrorService } from '@start9labs/shared'
import { TuiButton, TuiIcon, TuiSurface } from '@taiga-ui/core' import { TuiButton, TuiIcon, TuiLoader, TuiSurface } from '@taiga-ui/core'
import { TuiCardLarge } from '@taiga-ui/layout' import { TuiCardLarge } from '@taiga-ui/layout'
import { DocumentationComponent } from 'src/app/components/documentation.component' import { DocumentationComponent } from 'src/app/components/documentation.component'
import { MatrixComponent } from 'src/app/components/matrix.component' import { MatrixComponent } from 'src/app/components/matrix.component'
@@ -31,10 +31,16 @@ import { StateService } from 'src/app/services/state.service'
<h3>You can now safely unplug your old StartOS data drive</h3> <h3>You can now safely unplug your old StartOS data drive</h3>
} }
<h3>
http://start.local was for setup purposes only. It will no longer
work.
</h3>
<button tuiCardLarge tuiSurface="floating" (click)="download()"> <button tuiCardLarge tuiSurface="floating" (click)="download()">
<strong class="caps">Download address info</strong> <strong class="caps">Download address info</strong>
<span> <span>
start.local was for setup purposes only. It will no longer work. For future reference, this file contains your server's permanent
local address, as well as its Root Certificate Authority (Root CA).
</span> </span>
<strong class="caps"> <strong class="caps">
Download Download
@@ -48,17 +54,18 @@ import { StateService } from 'src/app/services/state.service'
target="_blank" target="_blank"
[attr.href]="disableLogin ? null : lanAddress" [attr.href]="disableLogin ? null : lanAddress"
> >
<strong class="caps">Trust your Root CA</strong>
<span> <span>
In the new tab, follow instructions to trust your server's Root CA In the new tab, follow instructions to trust your server's Root CA
and log in. and log in.
</span> </span>
<strong class="caps"> <strong class="caps">
Open Open Local Address
<tui-icon icon="@tui.external-link" /> <tui-icon icon="@tui.external-link" />
</strong> </strong>
</a> </a>
<app-documentation hidden [lanAddress]="lanAddress" /> <app-documentation hidden [lanAddress]="lanAddress" />
} @else {
<tui-loader />
} }
</section> </section>
`, `,
@@ -97,6 +104,10 @@ import { StateService } from 'src/app/services/state.service'
opacity: var(--tui-disabled-opacity); opacity: var(--tui-disabled-opacity);
pointer-events: none; pointer-events: none;
} }
h3 {
text-align: left;
}
`, `,
imports: [ imports: [
TuiCardLarge, TuiCardLarge,
@@ -105,6 +116,7 @@ import { StateService } from 'src/app/services/state.service'
TuiSurface, TuiSurface,
MatrixComponent, MatrixComponent,
DocumentationComponent, DocumentationComponent,
TuiLoader,
], ],
}) })
export default class SuccessPage implements AfterViewInit { export default class SuccessPage implements AfterViewInit {
@@ -117,7 +129,6 @@ export default class SuccessPage implements AfterViewInit {
readonly stateService = inject(StateService) readonly stateService = inject(StateService)
torAddresses?: string[]
lanAddress?: string lanAddress?: string
cert?: string cert?: string
disableLogin = this.stateService.setupType === 'fresh' disableLogin = this.stateService.setupType === 'fresh'
@@ -127,10 +138,8 @@ export default class SuccessPage implements AfterViewInit {
} }
download() { download() {
const torElem = this.document.getElementById('tor-addr')
const lanElem = this.document.getElementById('lan-addr') const lanElem = this.document.getElementById('lan-addr')
if (torElem) torElem.innerHTML = this.torAddresses?.join('\n') || ''
if (lanElem) lanElem.innerHTML = this.lanAddress || '' if (lanElem) lanElem.innerHTML = this.lanAddress || ''
this.document this.document
@@ -155,9 +164,6 @@ export default class SuccessPage implements AfterViewInit {
try { try {
const ret = await this.api.complete() const ret = await this.api.complete()
if (!this.stateService.kiosk) { if (!this.stateService.kiosk) {
this.torAddresses = ret.torAddresses.map(a =>
a.replace(/^https:/, 'http:'),
)
this.lanAddress = ret.lanAddress.replace(/^https:/, 'http:') this.lanAddress = ret.lanAddress.replace(/^https:/, 'http:')
this.cert = ret.rootCa this.cert = ret.rootCa

View File

@@ -484,7 +484,7 @@ export default {
512: 'Der Kiosk-Modus ist auf diesem Gerät nicht verfügbar', 512: 'Der Kiosk-Modus ist auf diesem Gerät nicht verfügbar',
513: 'Aktivieren', 513: 'Aktivieren',
514: 'Deaktivieren', 514: 'Deaktivieren',
515: 'Du verwendest derzeit einen Kiosk. Wenn du den Kiosk-Modus deaktivierst, wird die Verbindung zum Kiosk getrennt.', 515: 'Diese Änderung wird nach dem nächsten Neustart wirksam',
516: 'Empfohlen', 516: 'Empfohlen',
517: 'Möchten Sie diese Aufgabe wirklich verwerfen?', 517: 'Möchten Sie diese Aufgabe wirklich verwerfen?',
518: 'Verwerfen', 518: 'Verwerfen',

View File

@@ -483,7 +483,7 @@ export const ENGLISH = {
'Kiosk Mode is unavailable on this device': 512, 'Kiosk Mode is unavailable on this device': 512,
'Enable': 513, 'Enable': 513,
'Disable': 514, 'Disable': 514,
'You are currently using a kiosk. Disabling Kiosk Mode will result in the kiosk disconnecting.': 515, 'This change will take effect after the next boot': 515,
'Recommended': 516, // as in, we recommend this 'Recommended': 516, // as in, we recommend this
'Are you sure you want to dismiss this task?': 517, 'Are you sure you want to dismiss this task?': 517,
'Dismiss': 518, // as in, dismiss or delete a task 'Dismiss': 518, // as in, dismiss or delete a task

View File

@@ -484,7 +484,7 @@ export default {
512: 'El modo quiosco no está disponible en este dispositivo', 512: 'El modo quiosco no está disponible en este dispositivo',
513: 'Activar', 513: 'Activar',
514: 'Desactivar', 514: 'Desactivar',
515: 'Actualmente estás utilizando un quiosco. Desactivar el modo quiosco provocará su desconexión.', 515: 'Este cambio tendrá efecto después del próximo inicio',
516: 'Recomendado', 516: 'Recomendado',
517: '¿Estás seguro de que deseas descartar esta tarea?', 517: '¿Estás seguro de que deseas descartar esta tarea?',
518: 'Descartar', 518: 'Descartar',

View File

@@ -484,7 +484,7 @@ export default {
512: 'Le mode kiosque nest pas disponible sur cet appareil', 512: 'Le mode kiosque nest pas disponible sur cet appareil',
513: 'Activer', 513: 'Activer',
514: 'Désactiver', 514: 'Désactiver',
515: 'Vous utilisez actuellement un kiosque. Désactiver le mode kiosque entraînera sa déconnexion.', 515: 'Ce changement va prendre effet après le prochain démarrage',
516: 'Recommandé', 516: 'Recommandé',
517: 'Êtes-vous sûr de vouloir ignorer cette tâche ?', 517: 'Êtes-vous sûr de vouloir ignorer cette tâche ?',
518: 'Ignorer', 518: 'Ignorer',

View File

@@ -484,7 +484,7 @@ export default {
512: 'Tryb kiosku jest niedostępny na tym urządzeniu', 512: 'Tryb kiosku jest niedostępny na tym urządzeniu',
513: 'Włącz', 513: 'Włącz',
514: 'Wyłącz', 514: 'Wyłącz',
515: 'Obecnie używasz kiosku. Wyłączenie trybu kiosku spowoduje jego rozłączenie.', 515: 'Ta zmiana zacznie obowiązywać po następnym uruchomieniu',
516: 'Zalecane', 516: 'Zalecane',
517: 'Czy na pewno chcesz odrzucić to zadanie?', 517: 'Czy na pewno chcesz odrzucić to zadanie?',
518: 'Odrzuć', 518: 'Odrzuć',

View File

@@ -10,7 +10,7 @@ import { I18N, i18nKey } from './i18n.providers'
export class i18nPipe implements PipeTransform { export class i18nPipe implements PipeTransform {
private readonly i18n = inject(I18N) private readonly i18n = inject(I18N)
transform(englishKey: i18nKey | null | undefined): string { transform(englishKey: i18nKey | null | undefined | ''): string {
englishKey = englishKey || ('' as i18nKey) englishKey = englishKey || ('' as i18nKey)
return this.i18n()?.[ENGLISH[englishKey]] || englishKey return this.i18n()?.[ENGLISH[englishKey]] || englishKey

View File

@@ -1,17 +1,17 @@
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
inject, computed,
Input, input,
} from '@angular/core' } from '@angular/core'
import { i18nKey, i18nPipe } from '@start9labs/shared' import { i18nPipe } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { TuiLoader } from '@taiga-ui/core' import { TuiLoader } from '@taiga-ui/core'
import { ServiceUptimeComponent } from 'src/app/routes/portal/routes/services/components/uptime.component'
import { getProgressText } from 'src/app/routes/portal/routes/services/pipes/install-progress.pipe' import { getProgressText } from 'src/app/routes/portal/routes/services/pipes/install-progress.pipe'
import { InstallingInfo } from 'src/app/services/patch-db/data-model' import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { import {
getInstalledPrimaryStatus,
PrimaryRendering, PrimaryRendering,
PrimaryStatus,
} from 'src/app/services/pkg-status-rendering.service' } from 'src/app/services/pkg-status-rendering.service'
@Component({ @Component({
@@ -19,23 +19,27 @@ import {
template: ` template: `
<header>{{ 'Status' | i18n }}</header> <header>{{ 'Status' | i18n }}</header>
<div> <div>
@if (installingInfo) { @if (info()) {
<h3> <h3>
<tui-loader size="s" [inheritColor]="true" /> <tui-loader size="s" [inheritColor]="true" />
{{ 'Installing' | i18n }} {{ 'Installing' | i18n }}
<span class="loading-dots"></span> <span class="loading-dots"></span>
{{ getText(installingInfo.progress.overall) | i18n }} {{ info() | i18n }}
</h3> </h3>
} @else { } @else {
<h3 [class]="class"> <h3 [class]="class()">
{{ text | i18n }} {{ text() || 'Unknown' | i18n }}
@if (text === 'Task Required') { @if (text() === 'Task Required') {
<small>{{ 'See below' | i18n }}</small> <small>{{ 'See below' | i18n }}</small>
} }
@if (rendering?.showDots) { @if (rendering().showDots) {
<span class="loading-dots"></span> <span class="loading-dots"></span>
} }
@if ($any(pkg().status)?.started; as started) {
<service-uptime [started]="started" />
}
</h3> </h3>
} }
<ng-content /> <ng-content />
@@ -76,6 +80,12 @@ import {
margin: 0 0.25rem -0.125rem 0; margin: 0 0.25rem -0.125rem 0;
} }
service-uptime {
display: none;
width: fit-content;
margin: 0.5rem 0.125rem;
}
:host-context(tui-root._mobile) { :host-context(tui-root._mobile) {
:host { :host {
min-height: 0; min-height: 0;
@@ -94,32 +104,33 @@ import {
small { small {
text-align: left; text-align: left;
} }
service-uptime {
display: flex;
}
} }
`, `,
host: { class: 'g-card' }, host: { class: 'g-card' },
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TuiLoader, i18nPipe], imports: [TuiLoader, i18nPipe, ServiceUptimeComponent],
}) })
export class ServiceStatusComponent { export class ServiceStatusComponent {
@Input({ required: true }) readonly pkg = input.required<PackageDataEntry>()
status?: PrimaryStatus readonly connected = input(false)
@Input() protected readonly status = computed((pkg = this.pkg()) =>
installingInfo?: InstallingInfo pkg?.stateInfo.state === 'installed'
? getInstalledPrimaryStatus(pkg)
: pkg?.stateInfo.state,
)
@Input() protected readonly rendering = computed(() => PrimaryRendering[this.status()])
connected = false protected readonly text = computed(
() => this.connected() && this.rendering().display,
)
private readonly i18n = inject(i18nPipe) protected readonly class = computed(() => {
switch (this.connected() && this.rendering().color) {
get text(): i18nKey {
return this.connected ? this.rendering?.display || 'Unknown' : 'Unknown'
}
get class(): string | null {
if (!this.connected) return null
switch (this.rendering?.color) {
case 'danger': case 'danger':
return 'g-negative' return 'g-negative'
case 'warning': case 'warning':
@@ -131,13 +142,10 @@ export class ServiceStatusComponent {
default: default:
return null return null
} }
} })
get rendering() { protected readonly info = computed(
return this.status && PrimaryRendering[this.status] (progress = this.pkg().stateInfo.installingInfo?.progress.overall) =>
} progress ? getProgressText(progress) : '',
)
getText(progress: T.Progress): i18nKey {
return getProgressText(progress)
}
} }

View File

@@ -79,6 +79,10 @@ import { getManifest } from 'src/app/utils/get-package-data'
overflow: hidden; overflow: hidden;
} }
td:not(:last-child) {
padding-inline-end: 1.5rem;
}
td:last-child { td:last-child {
white-space: nowrap; white-space: nowrap;
text-align: right; text-align: right;

View File

@@ -66,7 +66,8 @@ import { distinctUntilChanged } from 'rxjs/operators'
color: var(--tui-text-primary); color: var(--tui-text-primary);
} }
:host-context(table) { :host-context(table),
:host-context(service-status) {
padding: 0; padding: 0;
header { header {

View File

@@ -39,11 +39,7 @@ import { ServiceUptimeComponent } from '../components/uptime.component'
} @else if (installing()) { } @else if (installing()) {
<service-install-progress [pkg]="pkg" /> <service-install-progress [pkg]="pkg" />
} @else if (installed()) { } @else if (installed()) {
<service-status <service-status [connected]="!!connected()" [pkg]="pkg">
[connected]="!!connected()"
[installingInfo]="pkg.stateInfo.installingInfo"
[status]="status()"
>
@if (connected()) { @if (connected()) {
<service-controls [pkg]="pkg" [status]="status()" /> <service-controls [pkg]="pkg" [status]="status()" />
} }
@@ -51,10 +47,8 @@ import { ServiceUptimeComponent } from '../components/uptime.component'
@if (status() !== 'backingUp') { @if (status() !== 'backingUp') {
<service-health-checks [checks]="health()" /> <service-health-checks [checks]="health()" />
<service-uptime <service-uptime class="g-card" [started]="$any(pkg.status).started" />
class="g-card" <service-interfaces [pkg]="pkg" [disabled]="status() !== 'running'" />
[started]="$any(pkg.status)?.started"
/>
@if (errors() | async; as errors) { @if (errors() | async; as errors) {
<service-dependencies <service-dependencies
@@ -63,7 +57,6 @@ import { ServiceUptimeComponent } from '../components/uptime.component'
[errors]="errors" [errors]="errors"
/> />
} }
<service-interfaces [pkg]="pkg" [disabled]="status() !== 'running'" />
<service-tasks <service-tasks
#tasks="elementRef" #tasks="elementRef"
@@ -91,7 +84,7 @@ import { ServiceUptimeComponent } from '../components/uptime.component'
</button> </button>
} }
} @else if (removing()) { } @else if (removing()) {
<service-status [connected]="!!connected()" [status]="status()" /> <service-status [connected]="!!connected()" [pkg]="pkg" />
} }
} }
`, `,
@@ -139,6 +132,10 @@ import { ServiceUptimeComponent } from '../components/uptime.component'
> * { > * {
grid-column: span 1; grid-column: span 1;
} }
service-uptime {
display: none;
}
} }
`, `,
host: { class: 'g-subpage' }, host: { class: 'g-subpage' },

View File

@@ -4,7 +4,6 @@ import {
Component, Component,
inject, inject,
INJECTOR, INJECTOR,
DOCUMENT,
} from '@angular/core' } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop' import { toSignal } from '@angular/core/rxjs-interop'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
@@ -151,7 +150,7 @@ import { SystemWipeComponent } from './wipe.component'
</span> </span>
</span> </span>
@if (server.kiosk !== null) { @if (server.kiosk !== null) {
<button tuiButton appearance="primary" (click)="tryToggleKiosk()"> <button tuiButton appearance="primary" (click)="toggleKiosk()">
{{ server.kiosk ? ('Disable' | i18n) : ('Enable' | i18n) }} {{ server.kiosk ? ('Disable' | i18n) : ('Enable' | i18n) }}
</button> </button>
} }
@@ -242,7 +241,6 @@ export default class SystemGeneralComponent {
private readonly patch = inject<PatchDB<DataModel>>(PatchDB) private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly isTor = inject(ConfigService).isTor() private readonly isTor = inject(ConfigService).isTor()
private readonly document = inject(DOCUMENT)
private readonly dialog = inject(DialogService) private readonly dialog = inject(DialogService)
private readonly i18n = inject(i18nPipe) private readonly i18n = inject(i18nPipe)
private readonly injector = inject(INJECTOR) private readonly injector = inject(INJECTOR)
@@ -326,28 +324,6 @@ export default class SystemGeneralComponent {
.subscribe(() => this.resetTor(this.wipe)) .subscribe(() => this.resetTor(this.wipe))
} }
async tryToggleKiosk() {
if (
this.server()?.kiosk &&
['localhost', '127.0.0.1'].includes(this.document.location.hostname)
) {
return this.dialog
.openConfirm({
label: 'Warning',
data: {
content:
'You are currently using a kiosk. Disabling Kiosk Mode will result in the kiosk disconnecting.',
yes: 'Disable',
no: 'Cancel',
},
})
.pipe(filter(Boolean))
.subscribe(async () => this.toggleKiosk())
}
this.toggleKiosk()
}
async onRepair() { async onRepair() {
this.dialog this.dialog
.openConfirm({ .openConfirm({
@@ -370,7 +346,7 @@ export default class SystemGeneralComponent {
}) })
} }
private async toggleKiosk() { async toggleKiosk() {
const kiosk = this.server()?.kiosk const kiosk = this.server()?.kiosk
const loader = this.loader const loader = this.loader
@@ -379,6 +355,11 @@ export default class SystemGeneralComponent {
try { try {
await this.api.toggleKiosk(!kiosk) await this.api.toggleKiosk(!kiosk)
this.dialog
.openAlert('This change will take effect after the next boot', {
label: 'Restart to apply',
})
.subscribe()
} catch (e: any) { } catch (e: any) {
this.errorService.handleError(e) this.errorService.handleError(e)
} finally { } finally {

View File

@@ -1,16 +1,20 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import {
ChangeDetectionStrategy,
Component,
inject,
viewChild,
} from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop' import { toSignal } from '@angular/core/rxjs-interop'
import { RouterLink } from '@angular/router' import { RouterLink } from '@angular/router'
import { verify } from '@start9labs/argon2' import { verify } from '@start9labs/argon2'
import { import {
DialogService,
ErrorService, ErrorService,
i18nKey, i18nKey,
i18nPipe, i18nPipe,
LoadingService, LoadingService,
} from '@start9labs/shared' } from '@start9labs/shared'
import { ISB } from '@start9labs/start-sdk' import { ISB } from '@start9labs/start-sdk'
import { TuiButton, TuiTitle } from '@taiga-ui/core' import { TuiAlertService, TuiButton, TuiTitle } from '@taiga-ui/core'
import { TuiHeader } from '@taiga-ui/layout' import { TuiHeader } from '@taiga-ui/layout'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { from } from 'rxjs' import { from } from 'rxjs'
@@ -70,13 +74,14 @@ import { getServerInfo } from 'src/app/utils/get-server-info'
], ],
}) })
export default class SystemPasswordComponent { export default class SystemPasswordComponent {
private readonly dialog = inject(DialogService) private readonly alerts = inject(TuiAlertService)
private readonly loader = inject(LoadingService) private readonly loader = inject(LoadingService)
private readonly errorService = inject(ErrorService) private readonly errorService = inject(ErrorService)
private readonly patch = inject<PatchDB<DataModel>>(PatchDB) private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
private readonly api = inject(ApiService) private readonly api = inject(ApiService)
private readonly i18n = inject(i18nPipe) private readonly i18n = inject(i18nPipe)
readonly form = viewChild(FormComponent)
readonly spec = toSignal(from(configBuilderToSpec(this.passwordSpec()))) readonly spec = toSignal(from(configBuilderToSpec(this.passwordSpec())))
readonly buttons = [ readonly buttons = [
{ {
@@ -119,7 +124,12 @@ export default class SystemPasswordComponent {
try { try {
await this.api.resetPassword({ oldPassword, newPassword }) await this.api.resetPassword({ oldPassword, newPassword })
this.dialog.openAlert('Password changed').subscribe() this.form()?.form.reset()
this.alerts
.open(this.i18n.transform('Password changed'), {
appearance: 'positive',
})
.subscribe()
} catch (e: any) { } catch (e: any) {
this.errorService.handleError(e) this.errorService.handleError(e)
} finally { } finally {