use hostname from patchDB as default server name (#1758)

* replace offline toast with global indicator

* use hostname from patchDB as default server name

* add alert to marketplace delete and reword logout alert
This commit is contained in:
Matt Hill
2022-08-29 14:59:09 -06:00
committed by GitHub
parent 8cd2fac9b9
commit 705653465a
27 changed files with 247 additions and 184 deletions

View File

@@ -18,5 +18,10 @@
<ion-footer> <ion-footer>
<footer appFooter></footer> <footer appFooter></footer>
</ion-footer> </ion-footer>
<ion-footer
*ngIf="(authService.isVerified$ | async) && !(sidebarOpen$ | async)"
>
<connection-bar></connection-bar>
</ion-footer>
<toast-container></toast-container> <toast-container></toast-container>
</ion-app> </ion-app>

View File

@@ -12,6 +12,7 @@ import { PatchMonitorService } from './services/patch-monitor.service'
}) })
export class AppComponent implements OnDestroy { export class AppComponent implements OnDestroy {
readonly subscription = merge(this.patchData, this.patchMonitor).subscribe() readonly subscription = merge(this.patchData, this.patchMonitor).subscribe()
readonly sidebarOpen$ = this.splitPane.sidebarOpen$
constructor( constructor(
private readonly patchData: PatchDataService, private readonly patchData: PatchDataService,

View File

@@ -19,6 +19,7 @@ import { EnterModule } from './app/enter/enter.module'
import { APP_PROVIDERS } from './app.providers' import { APP_PROVIDERS } from './app.providers'
import { PatchDbModule } from './services/patch-db/patch-db.module' import { PatchDbModule } from './services/patch-db/patch-db.module'
import { ToastContainerModule } from './components/toast-container/toast-container.module' import { ToastContainerModule } from './components/toast-container/toast-container.module'
import { ConnectionBarComponentModule } from './components/connection-bar/connection-bar.component.module'
@NgModule({ @NgModule({
declarations: [AppComponent], declarations: [AppComponent],
@@ -47,6 +48,7 @@ import { ToastContainerModule } from './components/toast-container/toast-contain
MarketplaceModule, MarketplaceModule,
PatchDbModule, PatchDbModule,
ToastContainerModule, ToastContainerModule,
ConnectionBarComponentModule,
], ],
providers: APP_PROVIDERS, providers: APP_PROVIDERS,
bootstrap: [AppComponent], bootstrap: [AppComponent],

View File

@@ -49,6 +49,7 @@
</ion-item> </ion-item>
</ion-menu-toggle> </ion-menu-toggle>
</ion-item-group> </ion-item-group>
<img <img
appSnek appSnek
class="snek" class="snek"
@@ -56,21 +57,6 @@
src="assets/img/icons/snek.png" src="assets/img/icons/snek.png"
[appSnekHighScore]="snekScore$ | async" [appSnekHighScore]="snekScore$ | async"
/> />
<div class="bottom"> <ion-footer class="bottom">
<div class="divider" style="margin-bottom: 10px"></div> <connection-bar></connection-bar>
<ion-menu-toggle auto-hide="false"> </ion-footer>
<ion-item
button
lines="none"
style="--background: transparent; margin-bottom: 86px; text-align: center"
fill="clear"
(click)="presentAlertLogout()"
>
<ion-label class="inline">
<h2>Log Out</h2>
&nbsp;
<ion-icon name="log-out-outline"></ion-icon>
</ion-label>
</ion-item>
</ion-menu-toggle>
</div>

View File

@@ -27,8 +27,8 @@
.snek { .snek {
position: absolute; position: absolute;
bottom: 90px; bottom: 56px;
left: 20px; right: 20px;
width: 20px; width: 20px;
cursor: pointer; cursor: pointer;
} }
@@ -36,8 +36,4 @@
.bottom { .bottom {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
right: 0;
width: 100%;
height: 75px;
text-align: center;
} }

View File

@@ -1,9 +1,6 @@
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core' import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
import { AlertController } from '@ionic/angular'
import { LocalStorageService } from '../../services/local-storage.service' import { LocalStorageService } from '../../services/local-storage.service'
import { EOSService } from '../../services/eos.service' import { EOSService } from '../../services/eos.service'
import { ApiService } from '../../services/api/embassy-api.service'
import { AuthService } from '../../services/auth.service'
import { PatchDbService } from '../../services/patch-db/patch-db.service' import { PatchDbService } from '../../services/patch-db/patch-db.service'
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import { map } from 'rxjs/operators' import { map } from 'rxjs/operators'
@@ -61,40 +58,10 @@ export class MenuComponent {
.pipe(map(pkgs => pkgs.length)) .pipe(map(pkgs => pkgs.length))
constructor( constructor(
private readonly alertCtrl: AlertController,
private readonly embassyApi: ApiService,
private readonly authService: AuthService,
private readonly patch: PatchDbService, private readonly patch: PatchDbService,
private readonly localStorageService: LocalStorageService, private readonly localStorageService: LocalStorageService,
private readonly eosService: EOSService, private readonly eosService: EOSService,
@Inject(AbstractMarketplaceService) @Inject(AbstractMarketplaceService)
private readonly marketplaceService: MarketplaceService, private readonly marketplaceService: MarketplaceService,
) {} ) {}
async presentAlertLogout() {
const alert = await this.alertCtrl.create({
header: 'Caution',
message:
'Do you know your password? If you log out and forget your password, you may permanently lose access to your Embassy.',
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Logout',
handler: () => this.logout(),
cssClass: 'enter-click',
},
],
})
await alert.present()
}
// should wipe cache independent of actual BE logout
private logout() {
this.embassyApi.logout({}).catch(e => console.error('Failed to log out', e))
this.authService.setUnverified()
}
} }

View File

@@ -4,9 +4,16 @@ import { RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { MenuComponent } from './menu.component' import { MenuComponent } from './menu.component'
import { SnekModule } from '../snek/snek.module' import { SnekModule } from '../snek/snek.module'
import { ConnectionBarComponentModule } from 'src/app/components/connection-bar/connection-bar.component.module'
@NgModule({ @NgModule({
imports: [CommonModule, IonicModule, RouterModule, SnekModule], imports: [
CommonModule,
IonicModule,
RouterModule,
SnekModule,
ConnectionBarComponentModule,
],
declarations: [MenuComponent], declarations: [MenuComponent],
exports: [MenuComponent], exports: [MenuComponent],
}) })

View File

@@ -19,6 +19,7 @@ const ICONS = [
'chevron-forward', 'chevron-forward',
'close', 'close',
'cloud-outline', 'cloud-outline',
'cloud-done',
'cloud-done-outline', 'cloud-done-outline',
'cloud-download-outline', 'cloud-download-outline',
'cloud-offline-outline', 'cloud-offline-outline',

View File

@@ -0,0 +1,21 @@
<ion-toolbar
*ngIf="connection$ | async as connection"
class="connection-toolbar"
[color]="connection.color"
>
<div class="inline" slot="start">
<ion-icon
slot="end"
[name]="connection.icon"
class="icon"
color="light"
></ion-icon>
<p style="margin: 8px 0; font-weight: 600">{{ connection.message }}</p>
<ion-spinner
*ngIf="connection.dots"
name="dots"
color="light"
class="ion-margin-start"
></ion-spinner>
</div>
</ion-toolbar>

View File

@@ -0,0 +1,11 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { ConnectionBarComponent } from './connection-bar.component'
@NgModule({
declarations: [ConnectionBarComponent],
imports: [CommonModule, IonicModule],
exports: [ConnectionBarComponent],
})
export class ConnectionBarComponentModule {}

View File

@@ -0,0 +1,9 @@
.connection-toolbar {
padding: 0 24px;
--min-height: 36px;
}
.icon {
font-size: 23px;
padding-right: 12px;
}

View File

@@ -0,0 +1,53 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { combineLatest, map, Observable, startWith, tap } from 'rxjs'
import { ConnectionService } from 'src/app/services/connection.service'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
@Component({
selector: 'connection-bar',
templateUrl: './connection-bar.component.html',
styleUrls: ['./connection-bar.component.scss'],
// changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ConnectionBarComponent {
private readonly websocket$ = this.connectionService.websocketConnected$
readonly connection$: Observable<{
message: string
icon: string
color: string
dots: boolean
}> = combineLatest([
this.connectionService.networkConnected$,
this.websocket$,
]).pipe(
map(([network, websocket]) => {
if (!network)
return {
message: 'No Internet',
icon: 'cloud-offline-outline',
color: 'dark',
dots: false,
}
if (!websocket)
return {
message: 'Connecting',
icon: 'cloud-offline-outline',
color: 'warning',
dots: true,
}
return {
message: 'Connected',
icon: 'cloud-done',
color: 'success',
dots: false,
}
}),
)
constructor(
private readonly connectionService: ConnectionService,
private readonly patch: PatchDbService,
) {}
}

View File

@@ -1,19 +0,0 @@
<toast
*ngIf="content$ | async as content"
class="warning-toast"
header="Unable to Connect"
(dismiss)="onDismiss()"
>
{{ content.message }}
<button toastButton icon="close" side="start" (click)="onDismiss()"></button>
<a
*ngIf="content.link"
toastButton
side="end"
target="_blank"
rel="noreferrer"
[href]="content.link"
>
View solutions
</a>
</toast>

View File

@@ -1,24 +0,0 @@
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
import { Observable, Subject, merge, tap, map } from 'rxjs'
import { OfflineMessage, OfflineToastService } from './offline-toast.service'
@Component({
selector: 'offline-toast',
templateUrl: './offline-toast.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OfflineToastComponent {
private readonly dismiss$ = new Subject<null>()
readonly content$ = merge(this.dismiss$, this.failure$)
constructor(
@Inject(OfflineToastService)
private readonly failure$: Observable<OfflineMessage | null>,
) {}
onDismiss() {
this.dismiss$.next(null)
}
}

View File

@@ -1,40 +0,0 @@
import { Injectable } from '@angular/core'
import { combineLatest, Observable, of } from 'rxjs'
import { map, switchMap } from 'rxjs/operators'
import { AuthService } from 'src/app/services/auth.service'
import { ConnectionService } from 'src/app/services/connection.service'
export interface OfflineMessage {
readonly message: string
readonly link?: string
}
// Watch for connection status
@Injectable({ providedIn: 'root' })
export class OfflineToastService extends Observable<OfflineMessage | null> {
private readonly stream$ = this.authService.isVerified$.pipe(
switchMap(verified => (verified ? this.failure$ : of(null))),
)
private readonly failure$ = combineLatest([
this.connectionService.networkConnected$,
this.connectionService.websocketConnected$,
]).pipe(
map(([network, websocket]) => {
if (!network) return { message: 'No Internet' }
if (!websocket)
return {
message: 'Connecting to Embassy...',
link: 'https://start9.com/latest/support/common-issues',
}
return null
}),
)
constructor(
private readonly authService: AuthService,
private readonly connectionService: ConnectionService,
) {
super(subscriber => this.stream$.subscribe(subscriber))
}
}

View File

@@ -1,4 +1,3 @@
<notifications-toast></notifications-toast> <notifications-toast></notifications-toast>
<offline-toast></offline-toast>
<refresh-alert></refresh-alert> <refresh-alert></refresh-alert>
<update-toast></update-toast> <update-toast></update-toast>

View File

@@ -5,7 +5,6 @@ import { AlertModule, ToastModule } from '@start9labs/shared'
import { ToastContainerComponent } from './toast-container.component' import { ToastContainerComponent } from './toast-container.component'
import { NotificationsToastComponent } from './notifications-toast/notifications-toast.component' import { NotificationsToastComponent } from './notifications-toast/notifications-toast.component'
import { OfflineToastComponent } from './offline-toast/offline-toast.component'
import { RefreshAlertComponent } from './refresh-alert/refresh-alert.component' import { RefreshAlertComponent } from './refresh-alert/refresh-alert.component'
import { UpdateToastComponent } from './update-toast/update-toast.component' import { UpdateToastComponent } from './update-toast/update-toast.component'
@@ -14,7 +13,6 @@ import { UpdateToastComponent } from './update-toast/update-toast.component'
declarations: [ declarations: [
ToastContainerComponent, ToastContainerComponent,
NotificationsToastComponent, NotificationsToastComponent,
OfflineToastComponent,
RefreshAlertComponent, RefreshAlertComponent,
UpdateToastComponent, UpdateToastComponent,
], ],

View File

@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component } from '@angular/core' import { Component } from '@angular/core'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { import {
ServerNotifications, ServerNotifications,

View File

@@ -1,6 +1,7 @@
import { Component, Inject } from '@angular/core' import { Component, Inject } from '@angular/core'
import { import {
ActionSheetController, ActionSheetController,
AlertController,
LoadingController, LoadingController,
ModalController, ModalController,
} from '@ionic/angular' } from '@ionic/angular'
@@ -51,6 +52,7 @@ export class MarketplacesPage {
private readonly config: ConfigService, private readonly config: ConfigService,
private readonly patch: PatchDbService, private readonly patch: PatchDbService,
private readonly destroy$: DestroyService, private readonly destroy$: DestroyService,
private readonly alertCtrl: AlertController,
) {} ) {}
ngOnInit() { ngOnInit() {
@@ -129,7 +131,7 @@ export class MarketplacesPage {
text: 'Delete', text: 'Delete',
role: 'destructive', role: 'destructive',
handler: () => { handler: () => {
this.delete(id) this.presentAlertDelete(id)
}, },
}) })
} }
@@ -189,6 +191,28 @@ export class MarketplacesPage {
.subscribe() .subscribe()
} }
private async presentAlertDelete(id: string) {
const name = this.marketplaces.find(m => m.id === id)?.name
const alert = await this.alertCtrl.create({
header: 'Confirm',
message: `Are you sure you want to delete ${name}?`,
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Delete',
handler: () => this.delete(id),
cssClass: 'enter-click',
},
],
})
await alert.present()
}
private async delete(id: string): Promise<void> { private async delete(id: string): Promise<void> {
const data = await getMarketplace(this.patch) const data = await getMarketplace(this.patch)
const marketplace: UIMarketplaceData = JSON.parse(JSON.stringify(data)) const marketplace: UIMarketplaceData = JSON.parse(JSON.stringify(data))

View File

@@ -8,24 +8,21 @@
</ion-header> </ion-header>
<ion-content class="ion-padding-top"> <ion-content class="ion-padding-top">
<ng-container *ngIf="ui$ | async as ui"> <ion-item-group *ngIf="name$ | async as name">
<ion-item-group *ngIf="server$ | async as server"> <ion-item-divider>General</ion-item-divider>
<ion-item-divider>General</ion-item-divider> <ion-item button (click)="presentModalName(name)">
<ion-item button (click)="presentModalName('My Embassy', ui.name)"> <ion-label>Device Name</ion-label>
<ion-label>Device Name</ion-label> <ion-note slot="end">{{ name.current }}</ion-note>
<ion-note slot="end">{{ ui.name || 'My Embassy' }}</ion-note> </ion-item>
</ion-item>
<ion-item-divider>Marketplace</ion-item-divider> <ion-item-divider>Marketplace</ion-item-divider>
<ion-item <ion-item
button *ngIf="autoCheck$ | async as auto"
(click)="serverConfig.presentAlert('auto-check-updates', ui['auto-check-updates'] !== false)" button
> (click)="serverConfig.presentAlert('auto-check-updates', auto)"
<ion-label>Auto Check for Updates</ion-label> >
<ion-note slot="end"> <ion-label>Auto Check for Updates</ion-label>
{{ ui['auto-check-updates'] !== false ? 'Enabled' : 'Disabled' }} <ion-note slot="end"> {{ auto ? 'Enabled' : 'Disabled' }} </ion-note>
</ion-note> </ion-item>
</ion-item> </ion-item-group>
</ion-item-group>
</ng-container>
</ion-content> </ion-content>

View File

@@ -12,6 +12,10 @@ import {
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ServerConfigService } from 'src/app/services/server-config.service' import { ServerConfigService } from 'src/app/services/server-config.service'
import { LocalStorageService } from '../../../services/local-storage.service' import { LocalStorageService } from '../../../services/local-storage.service'
import {
ServerNameInfo,
ServerNameService,
} from 'src/app/services/server-name.service'
@Component({ @Component({
selector: 'preferences', selector: 'preferences',
@@ -22,8 +26,9 @@ import { LocalStorageService } from '../../../services/local-storage.service'
export class PreferencesPage { export class PreferencesPage {
clicks = 0 clicks = 0
readonly ui$ = this.patch.watch$('ui') readonly autoCheck$ = this.patch.watch$('ui', 'auto-check-updates')
readonly server$ = this.patch.watch$('server-info') readonly server$ = this.patch.watch$('server-info')
readonly name$ = this.serverNameService.name$
constructor( constructor(
private readonly loadingCtrl: LoadingController, private readonly loadingCtrl: LoadingController,
@@ -32,24 +37,22 @@ export class PreferencesPage {
private readonly toastCtrl: ToastController, private readonly toastCtrl: ToastController,
private readonly localStorageService: LocalStorageService, private readonly localStorageService: LocalStorageService,
private readonly patch: PatchDbService, private readonly patch: PatchDbService,
private readonly serverNameService: ServerNameService,
readonly serverConfig: ServerConfigService, readonly serverConfig: ServerConfigService,
) {} ) {}
async presentModalName( async presentModalName(name: ServerNameInfo): Promise<void> {
placeholder: string,
initialValue: string,
): Promise<void> {
const options: GenericInputOptions = { const options: GenericInputOptions = {
title: 'Edit Device Name', title: 'Edit Device Name',
message: 'This is for your reference only.', message: 'This is for your reference only.',
label: 'Device Name', label: 'Device Name',
useMask: false, useMask: false,
placeholder, placeholder: name.default,
nullable: true, nullable: true,
initialValue, initialValue: name.current,
buttonText: 'Save', buttonText: 'Save',
submitFn: (value: string) => submitFn: (value: string) =>
this.setDbValue('name', value || placeholder), this.setDbValue('name', value || name.default),
} }
const modal = await this.modalCtrl.create({ const modal = await this.modalCtrl.create({

View File

@@ -1,7 +1,7 @@
<ion-header> <ion-header>
<ion-toolbar> <ion-toolbar>
<ion-title *ngIf="ui$ | async as ui; else loadingTitle"> <ion-title *ngIf="name$ | async as name; else loadingTitle">
{{ ui.name || "My Embassy" }} {{ name.current }}
</ion-title> </ion-title>
<ng-template #loadingTitle> <ng-template #loadingTitle>
<ion-title> <ion-title>

View File

@@ -8,6 +8,7 @@ import {
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ActivatedRoute } from '@angular/router' import { ActivatedRoute } from '@angular/router'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { ServerNameService } from 'src/app/services/server-name.service'
import { Observable, of } from 'rxjs' import { Observable, of } from 'rxjs'
import { filter, take, tap } from 'rxjs/operators' import { filter, take, tap } from 'rxjs/operators'
import { isEmptyObject, ErrorToastService } from '@start9labs/shared' import { isEmptyObject, ErrorToastService } from '@start9labs/shared'
@@ -15,6 +16,7 @@ import { EOSService } from 'src/app/services/eos.service'
import { LocalStorageService } from 'src/app/services/local-storage.service' import { LocalStorageService } from 'src/app/services/local-storage.service'
import { OSUpdatePage } from 'src/app/modals/os-update/os-update.page' import { OSUpdatePage } from 'src/app/modals/os-update/os-update.page'
import { getAllPackages } from '../../../util/get-package-data' import { getAllPackages } from '../../../util/get-package-data'
import { AuthService } from 'src/app/services/auth.service'
@Component({ @Component({
selector: 'server-show', selector: 'server-show',
@@ -26,7 +28,7 @@ export class ServerShowPage {
clicks = 0 clicks = 0
readonly server$ = this.patch.watch$('server-info') readonly server$ = this.patch.watch$('server-info')
readonly ui$ = this.patch.watch$('ui') readonly name$ = this.serverNameService.name$
readonly showUpdate$ = this.eosService.showUpdate$ readonly showUpdate$ = this.eosService.showUpdate$
readonly showDiskRepair$ = this.localStorageService.showDiskRepair$ readonly showDiskRepair$ = this.localStorageService.showDiskRepair$
@@ -41,6 +43,8 @@ export class ServerShowPage {
private readonly patch: PatchDbService, private readonly patch: PatchDbService,
private readonly eosService: EOSService, private readonly eosService: EOSService,
private readonly localStorageService: LocalStorageService, private readonly localStorageService: LocalStorageService,
private readonly serverNameService: ServerNameService,
private readonly authService: AuthService,
) {} ) {}
ngOnInit() { ngOnInit() {
@@ -74,6 +78,26 @@ export class ServerShowPage {
} }
} }
async presentAlertLogout() {
const alert = await this.alertCtrl.create({
header: 'Confirm',
message: 'Are you sure you want to log out?',
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Logout',
handler: () => this.logout(),
cssClass: 'enter-click',
},
],
})
await alert.present()
}
async presentAlertRestart() { async presentAlertRestart() {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
header: 'Restart', header: 'Restart',
@@ -171,6 +195,12 @@ export class ServerShowPage {
await alert.present() await alert.present()
} }
// should wipe cache independent of actual BE logout
private logout() {
this.embassyApi.logout({}).catch(e => console.error('Failed to log out', e))
this.authService.setUnverified()
}
private async restart() { private async restart() {
const action = 'Restart' const action = 'Restart'
@@ -456,6 +486,14 @@ export class ServerShowPage {
}, },
], ],
Power: [ Power: [
{
title: 'Log Out',
description: '',
icon: 'log-out-outline',
action: () => this.presentAlertLogout(),
detail: false,
disabled$: of(false),
},
{ {
title: 'Restart', title: 'Restart',
description: '', description: '',

View File

@@ -95,18 +95,14 @@ export class SideloadPage {
manifest: this.toUpload.manifest!, manifest: this.toUpload.manifest!,
icon: this.toUpload.icon!, icon: this.toUpload.icon!,
}) })
this.api const buffer = await blobToBuffer(this.toUpload.file!)
.uploadPackage(guid, await blobToBuffer(this.toUpload.file!)) this.api.uploadPackage(guid, buffer).catch(e => console.error(e))
.catch(e => {
this.errToast.present(e) this.navCtrl.navigateRoot('/services')
})
} catch (e: any) { } catch (e: any) {
this.errToast.present(e) this.errToast.present(e)
} finally { } finally {
loader.dismiss() loader.dismiss()
await this.navCtrl.navigateForward(
`/services/${this.toUpload.manifest!.id}`,
)
this.clearToUpload() this.clearToUpload()
} }
} }

View File

@@ -41,6 +41,7 @@ export const mockPatchData: DataModel = {
updated: false, updated: false,
'update-progress': null, 'update-progress': null,
}, },
hostname: 'random-words',
}, },
'recovered-packages': { 'recovered-packages': {
'btc-rpc-proxy': { 'btc-rpc-proxy': {

View File

@@ -56,6 +56,7 @@ export interface ServerInfo {
'status-info': ServerStatusInfo 'status-info': ServerStatusInfo
'eos-version-compat': string 'eos-version-compat': string
'password-hash': string 'password-hash': string
hostname: string
} }
export interface ServerStatusInfo { export interface ServerStatusInfo {

View File

@@ -0,0 +1,30 @@
import { Injectable } from '@angular/core'
import { PatchDbService } from './patch-db/patch-db.service'
import { combineLatest, filter, map, Observable } from 'rxjs'
export interface ServerNameInfo {
current: string
default: string
}
@Injectable({ providedIn: 'root' })
export class ServerNameService {
private readonly chosenName$ = this.patch.watch$('ui', 'name')
private readonly hostname$ = this.patch
.watch$('server-info', 'hostname')
.pipe(filter(Boolean))
readonly name$: Observable<ServerNameInfo> = combineLatest([
this.chosenName$,
this.hostname$,
]).pipe(
map(([chosen, hostname]) => {
return {
current: chosen || hostname,
default: hostname,
}
}),
)
constructor(private readonly patch: PatchDbService) {}
}