diff --git a/frontend/projects/diagnostic-ui/src/app/pages/logs/logs.page.ts b/frontend/projects/diagnostic-ui/src/app/pages/logs/logs.page.ts index 49687c48d..88f706edd 100644 --- a/frontend/projects/diagnostic-ui/src/app/pages/logs/logs.page.ts +++ b/frontend/projects/diagnostic-ui/src/app/pages/logs/logs.page.ts @@ -12,15 +12,13 @@ var convert = new Convert({ styleUrls: ['./logs.page.scss'], }) export class LogsPage { - @ViewChild(IonContent) private content: IonContent + @ViewChild(IonContent) private content?: IonContent loading = true loadingMore = false - logs: string needInfinite = true - startCursor: string - endCursor: string + startCursor?: string + endCursor?: string limit = 200 - scrollToBottomButton = false isOnBottom = true constructor(private readonly api: ApiService) {} @@ -52,7 +50,7 @@ export class LogsPage { // scroll down scrollBy(0, afterContainerHeight - beforeContainerHeight) - this.content.scrollToPoint( + this.content?.scrollToPoint( 0, afterContainerHeight - beforeContainerHeight, ) @@ -117,7 +115,7 @@ export class LogsPage { } scrollToBottom() { - this.content.scrollToBottom(500) + this.content?.scrollToBottom(500) } async loadData(e: any): Promise { diff --git a/frontend/projects/marketplace/src/pages/list/item/item.component.ts b/frontend/projects/marketplace/src/pages/list/item/item.component.ts index e026b6a15..2440bad6d 100644 --- a/frontend/projects/marketplace/src/pages/list/item/item.component.ts +++ b/frontend/projects/marketplace/src/pages/list/item/item.component.ts @@ -9,5 +9,5 @@ import { MarketplacePkg } from '../../../types/marketplace-pkg' }) export class ItemComponent { @Input() - pkg: MarketplacePkg + pkg!: MarketplacePkg } diff --git a/frontend/projects/marketplace/src/pages/list/item/item.module.ts b/frontend/projects/marketplace/src/pages/list/item/item.module.ts index df6302ce3..7f3dcde4a 100644 --- a/frontend/projects/marketplace/src/pages/list/item/item.module.ts +++ b/frontend/projects/marketplace/src/pages/list/item/item.module.ts @@ -1,3 +1,4 @@ +import { CommonModule } from '@angular/common' import { NgModule } from '@angular/core' import { IonicModule } from '@ionic/angular' import { RouterModule } from '@angular/router' @@ -6,7 +7,7 @@ import { SharedPipesModule } from '@start9labs/shared' import { ItemComponent } from './item.component' @NgModule({ - imports: [IonicModule, RouterModule, SharedPipesModule], + imports: [CommonModule, IonicModule, RouterModule, SharedPipesModule], declarations: [ItemComponent], exports: [ItemComponent], }) diff --git a/frontend/projects/marketplace/src/pages/show/about/about.component.ts b/frontend/projects/marketplace/src/pages/show/about/about.component.ts index 1a681b5db..3e2c1133e 100644 --- a/frontend/projects/marketplace/src/pages/show/about/about.component.ts +++ b/frontend/projects/marketplace/src/pages/show/about/about.component.ts @@ -10,5 +10,5 @@ import { MarketplacePkg } from '../../../types/marketplace-pkg' }) export class AboutComponent { @Input() - pkg: MarketplacePkg + pkg!: MarketplacePkg } diff --git a/frontend/projects/marketplace/src/pages/show/additional/additional.component.ts b/frontend/projects/marketplace/src/pages/show/additional/additional.component.ts index d0a624da3..3c9a415b3 100644 --- a/frontend/projects/marketplace/src/pages/show/additional/additional.component.ts +++ b/frontend/projects/marketplace/src/pages/show/additional/additional.component.ts @@ -18,7 +18,7 @@ import { MarketplacePkg } from '../../../types/marketplace-pkg' }) export class AdditionalComponent { @Input() - pkg: MarketplacePkg + pkg!: MarketplacePkg @Output() version = new EventEmitter() diff --git a/frontend/projects/marketplace/src/pages/show/additional/additional.module.ts b/frontend/projects/marketplace/src/pages/show/additional/additional.module.ts index 48f474c0d..dd1814f2d 100644 --- a/frontend/projects/marketplace/src/pages/show/additional/additional.module.ts +++ b/frontend/projects/marketplace/src/pages/show/additional/additional.module.ts @@ -1,3 +1,4 @@ +import { CommonModule } from '@angular/common' import { NgModule } from '@angular/core' import { IonicModule } from '@ionic/angular' import { MarkdownModule } from '@start9labs/shared' @@ -5,7 +6,7 @@ import { MarkdownModule } from '@start9labs/shared' import { AdditionalComponent } from './additional.component' @NgModule({ - imports: [IonicModule, MarkdownModule], + imports: [CommonModule, IonicModule, MarkdownModule], declarations: [AdditionalComponent], exports: [AdditionalComponent], }) diff --git a/frontend/projects/marketplace/src/pages/show/dependencies/dependencies.component.ts b/frontend/projects/marketplace/src/pages/show/dependencies/dependencies.component.ts index 6b3781896..b383a1697 100644 --- a/frontend/projects/marketplace/src/pages/show/dependencies/dependencies.component.ts +++ b/frontend/projects/marketplace/src/pages/show/dependencies/dependencies.component.ts @@ -9,7 +9,7 @@ import { MarketplacePkg } from '../../../types/marketplace-pkg' }) export class DependenciesComponent { @Input() - pkg: MarketplacePkg + pkg!: MarketplacePkg getImg(key: string): string { return 'data:image/png;base64,' + this.pkg['dependency-metadata'][key].icon diff --git a/frontend/projects/marketplace/src/pages/show/package/package.component.ts b/frontend/projects/marketplace/src/pages/show/package/package.component.ts index 2c9a89bf5..5e4ba2530 100644 --- a/frontend/projects/marketplace/src/pages/show/package/package.component.ts +++ b/frontend/projects/marketplace/src/pages/show/package/package.component.ts @@ -10,5 +10,5 @@ import { MarketplacePkg } from '../../../types/marketplace-pkg' }) export class PackageComponent { @Input() - pkg: MarketplacePkg + pkg!: MarketplacePkg } diff --git a/frontend/projects/marketplace/src/pages/show/package/package.module.ts b/frontend/projects/marketplace/src/pages/show/package/package.module.ts index d970b3d21..ada551799 100644 --- a/frontend/projects/marketplace/src/pages/show/package/package.module.ts +++ b/frontend/projects/marketplace/src/pages/show/package/package.module.ts @@ -1,3 +1,4 @@ +import { CommonModule } from '@angular/common' import { NgModule } from '@angular/core' import { IonicModule } from '@ionic/angular' import { EmverPipesModule, SharedPipesModule } from '@start9labs/shared' @@ -5,7 +6,7 @@ import { EmverPipesModule, SharedPipesModule } from '@start9labs/shared' import { PackageComponent } from './package.component' @NgModule({ - imports: [IonicModule, SharedPipesModule, EmverPipesModule], + imports: [CommonModule, IonicModule, SharedPipesModule, EmverPipesModule], declarations: [PackageComponent], exports: [PackageComponent], }) diff --git a/frontend/projects/setup-wizard/src/app/modals/password/password.page.html b/frontend/projects/setup-wizard/src/app/modals/password/password.page.html index 22b9b8cba..c8593e02d 100644 --- a/frontend/projects/setup-wizard/src/app/modals/password/password.page.html +++ b/frontend/projects/setup-wizard/src/app/modals/password/password.page.html @@ -1,24 +1,33 @@ - {{ !!storageDrive ? 'Set Password' : 'Unlock Drive' }} + {{ storageDrive ? 'Set Password' : 'Unlock Drive' }} -
-
- -

Choose a password for your Embassy. Make it good. Write it down.

-

Losing your password can result in total loss of data.

-
-

Enter the password that was used to encrypt this drive.

+
+
+ +

+ Choose a password for your Embassy. + Make it good. Write it down. +

+

+ Losing your password can result in total loss of data. +

+
+

+ Enter the password that was used to encrypt this drive. +

-
+

Password

- + - + -
-

{{ pwError }}

+
+

+ {{ pwError }} +

- +

Confirm Password

- + - - + + -
-

{{ verError }}

+
+

+ {{ verError }} +

@@ -61,12 +88,24 @@ - + Cancel - - {{ !!storageDrive ? 'Finish' : 'Unlock' }} + + {{ storageDrive ? 'Finish' : 'Unlock' }} - diff --git a/frontend/projects/setup-wizard/src/app/modals/password/password.page.ts b/frontend/projects/setup-wizard/src/app/modals/password/password.page.ts index 1016ceb5a..2005bd02c 100644 --- a/frontend/projects/setup-wizard/src/app/modals/password/password.page.ts +++ b/frontend/projects/setup-wizard/src/app/modals/password/password.page.ts @@ -13,9 +13,9 @@ import * as argon2 from '@start9labs/argon2' styleUrls: ['password.page.scss'], }) export class PasswordPage { - @ViewChild('focusInput') elem: IonInput - @Input() target: CifsBackupTarget | DiskBackupTarget - @Input() storageDrive: DiskInfo + @ViewChild('focusInput') elem?: IonInput + @Input() target?: CifsBackupTarget | DiskBackupTarget + @Input() storageDrive?: DiskInfo pwError = '' password = '' @@ -28,7 +28,7 @@ export class PasswordPage { constructor(private modalController: ModalController) {} ngAfterViewInit() { - setTimeout(() => this.elem.setFocus(), 400) + setTimeout(() => this.elem?.setFocus(), 400) } async verifyPw() { @@ -36,7 +36,7 @@ export class PasswordPage { this.pwError = 'No recovery target' // unreachable try { - const passwordHash = this.target['embassy-os']?.['password-hash'] || '' + const passwordHash = this.target!['embassy-os']?.['password-hash'] || '' argon2.verify(passwordHash, this.password) this.modalController.dismiss({ password: this.password }, 'success') diff --git a/frontend/projects/setup-wizard/src/app/modals/prod-key-modal/prod-key-modal.page.ts b/frontend/projects/setup-wizard/src/app/modals/prod-key-modal/prod-key-modal.page.ts index 548576b6d..5bc6b9f66 100644 --- a/frontend/projects/setup-wizard/src/app/modals/prod-key-modal/prod-key-modal.page.ts +++ b/frontend/projects/setup-wizard/src/app/modals/prod-key-modal/prod-key-modal.page.ts @@ -9,8 +9,8 @@ import { HttpService } from 'src/app/services/api/http.service' styleUrls: ['prod-key-modal.page.scss'], }) export class ProdKeyModal { - @ViewChild('focusInput') elem: IonInput - @Input() target: DiskBackupTarget + @ViewChild('focusInput') elem?: IonInput + @Input() target!: DiskBackupTarget error = '' productKey = '' @@ -24,7 +24,7 @@ export class ProdKeyModal { ) {} ngAfterViewInit() { - setTimeout(() => this.elem.setFocus(), 400) + setTimeout(() => this.elem?.setFocus(), 400) } async verifyProductKey() { diff --git a/frontend/projects/setup-wizard/src/app/pages/product-key/product-key.page.ts b/frontend/projects/setup-wizard/src/app/pages/product-key/product-key.page.ts index b1de56ead..5594667f8 100644 --- a/frontend/projects/setup-wizard/src/app/pages/product-key/product-key.page.ts +++ b/frontend/projects/setup-wizard/src/app/pages/product-key/product-key.page.ts @@ -10,24 +10,24 @@ import { StateService } from 'src/app/services/state.service' styleUrls: ['product-key.page.scss'], }) export class ProductKeyPage { - @ViewChild('focusInput') elem: IonInput - productKey: string - error: string + @ViewChild('focusInput') elem?: IonInput + productKey = '' + error = '' - constructor ( + constructor( private readonly navCtrl: NavController, private readonly stateService: StateService, private readonly apiService: ApiService, private readonly loadingCtrl: LoadingController, private readonly httpService: HttpService, - ) { } + ) {} - ionViewDidEnter () { - setTimeout(() => this.elem.setFocus(), 400) + ionViewDidEnter() { + setTimeout(() => this.elem?.setFocus(), 400) } - async submit () { - if (!this.productKey) return this.error = 'Must enter product key' + async submit() { + if (!this.productKey) return (this.error = 'Must enter product key') const loader = await this.loadingCtrl.create({ message: 'Verifying Product Key', @@ -50,4 +50,3 @@ export class ProductKeyPage { } } } - diff --git a/frontend/projects/setup-wizard/src/app/pages/recover/recover.page.ts b/frontend/projects/setup-wizard/src/app/pages/recover/recover.page.ts index 23dccbd04..2f330ac9f 100644 --- a/frontend/projects/setup-wizard/src/app/pages/recover/recover.page.ts +++ b/frontend/projects/setup-wizard/src/app/pages/recover/recover.page.ts @@ -31,7 +31,7 @@ export class RecoverPage { private readonly alertCtrl: AlertController, private readonly loadingCtrl: LoadingController, private readonly errorToastService: ErrorToastService, - public readonly stateService: StateService, + private readonly stateService: StateService, ) {} async ngOnInit() { @@ -243,8 +243,8 @@ export class RecoverPage { styleUrls: ['./recover.page.scss'], }) export class DriveStatusComponent { - @Input() hasValidBackup: boolean - @Input() is02x: boolean + @Input() hasValidBackup!: boolean + @Input() is02x!: boolean } interface MappedDisk { diff --git a/frontend/projects/setup-wizard/src/app/pages/success/success.page.html b/frontend/projects/setup-wizard/src/app/pages/success/success.page.html index 18ed4b6cf..14ee4a253 100644 --- a/frontend/projects/setup-wizard/src/app/pages/success/success.page.html +++ b/frontend/projects/setup-wizard/src/app/pages/success/success.page.html @@ -13,7 +13,7 @@

You can now safely unplug your backup drive.

@@ -53,15 +53,13 @@ {{ stateService.torAddress }}{{ torAddress }} @@ -133,15 +131,13 @@ {{ stateService.lanAddress }}{{ lanAddress }} diff --git a/frontend/projects/setup-wizard/src/app/pages/success/success.page.ts b/frontend/projects/setup-wizard/src/app/pages/success/success.page.ts index 5a7ed133a..593340e8a 100644 --- a/frontend/projects/setup-wizard/src/app/pages/success/success.page.ts +++ b/frontend/projects/setup-wizard/src/app/pages/success/success.page.ts @@ -16,9 +16,21 @@ export class SuccessPage { constructor( private readonly toastCtrl: ToastController, private readonly errCtrl: ErrorToastService, - public readonly stateService: StateService, + private readonly stateService: StateService, ) {} + get recoverySource() { + return this.stateService.recoverySource + } + + get torAddress() { + return this.stateService.torAddress + } + + get lanAddress() { + return this.stateService.lanAddress + } + async ngAfterViewInit() { try { await this.stateService.completeEmbassy() diff --git a/frontend/projects/setup-wizard/src/app/services/state.service.ts b/frontend/projects/setup-wizard/src/app/services/state.service.ts index 8668191b6..1c79bdd8f 100644 --- a/frontend/projects/setup-wizard/src/app/services/state.service.ts +++ b/frontend/projects/setup-wizard/src/app/services/state.service.ts @@ -11,26 +11,26 @@ import { pauseFor, ErrorToastService } from '@start9labs/shared' providedIn: 'root', }) export class StateService { - hasProductKey: boolean - isMigrating: boolean + hasProductKey = false + isMigrating = false polling = false embassyLoaded = false - recoverySource: CifsRecoverySource | DiskRecoverySource + recoverySource?: CifsRecoverySource | DiskRecoverySource recoveryPassword?: string - dataTransferProgress: { + dataTransferProgress?: { bytesTransferred: number totalBytes: number complete: boolean - } | null + } dataProgress = 0 dataCompletionSubject = new BehaviorSubject(false) - torAddress: string - lanAddress: string - cert: string + torAddress = '' + lanAddress = '' + cert = '' constructor( private readonly apiService: ApiService, diff --git a/frontend/projects/shared/src/components/markdown/markdown.component.ts b/frontend/projects/shared/src/components/markdown/markdown.component.ts index 74aa6e2a0..e5837aa89 100644 --- a/frontend/projects/shared/src/components/markdown/markdown.component.ts +++ b/frontend/projects/shared/src/components/markdown/markdown.component.ts @@ -11,8 +11,8 @@ import { getErrorMessage } from '../../services/error-toast.service' styleUrls: ['./markdown.component.scss'], }) export class MarkdownComponent { - @Input() content?: string | Observable - @Input() title = '' + @Input() content!: string | Observable + @Input() title!: string private readonly data$ = defer(() => isObservable(this.content) ? this.content : of(this.content), diff --git a/frontend/projects/shared/src/pipes/shared/trust.pipe.ts b/frontend/projects/shared/src/pipes/shared/trust.pipe.ts index 0acd65761..76906b603 100644 --- a/frontend/projects/shared/src/pipes/shared/trust.pipe.ts +++ b/frontend/projects/shared/src/pipes/shared/trust.pipe.ts @@ -5,7 +5,7 @@ import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser' name: 'trustUrl', }) export class TrustUrlPipe implements PipeTransform { - constructor(public readonly sanitizer: DomSanitizer) {} + constructor(private readonly sanitizer: DomSanitizer) {} transform(base64Icon: string): SafeResourceUrl { return this.sanitizer.bypassSecurityTrustResourceUrl(base64Icon) diff --git a/frontend/projects/ui/src/app/app/global/services/unread-toast.service.ts b/frontend/projects/ui/src/app/app/global/services/unread-toast.service.ts index 3e2dee51e..2dad72542 100644 --- a/frontend/projects/ui/src/app/app/global/services/unread-toast.service.ts +++ b/frontend/projects/ui/src/app/app/global/services/unread-toast.service.ts @@ -10,7 +10,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model' // Watch unread notification count to display toast @Injectable() export class UnreadToastService extends Observable { - private unreadToast: HTMLIonToastElement + private unreadToast?: HTMLIonToastElement private readonly stream$ = this.patchData.pipe( switchMap>(data => { diff --git a/frontend/projects/ui/src/app/app/global/services/update-toast.service.ts b/frontend/projects/ui/src/app/app/global/services/update-toast.service.ts index 0d161664b..252a13503 100644 --- a/frontend/projects/ui/src/app/app/global/services/update-toast.service.ts +++ b/frontend/projects/ui/src/app/app/global/services/update-toast.service.ts @@ -16,7 +16,7 @@ import { PatchDataService } from './patch-data.service' // Watch status to present toast for updated state @Injectable() export class UpdateToastService extends Observable { - private updateToast: HTMLIonToastElement + private updateToast?: HTMLIonToastElement private readonly stream$ = this.patchData.pipe( switchMap(data => { diff --git a/frontend/projects/ui/src/app/app/menu/menu.component.html b/frontend/projects/ui/src/app/app/menu/menu.component.html index 5de85cec1..3bd06c372 100644 --- a/frontend/projects/ui/src/app/app/menu/menu.component.html +++ b/frontend/projects/ui/src/app/app/menu/menu.component.html @@ -49,7 +49,13 @@ -Play Snek +Play Snek
diff --git a/frontend/projects/ui/src/app/app/menu/menu.component.ts b/frontend/projects/ui/src/app/app/menu/menu.component.ts index f8f9e5949..3a45c989a 100644 --- a/frontend/projects/ui/src/app/app/menu/menu.component.ts +++ b/frontend/projects/ui/src/app/app/menu/menu.component.ts @@ -51,6 +51,8 @@ export class MenuComponent { 'unread-notification-count', ) + readonly snekScore$ = this.patch.watch$('ui', 'gaming', 'snake', 'high-score') + readonly showEOSUpdate$ = this.eosService.showUpdate$ readonly showDevTools$ = this.localStorageService.showDevTools$ diff --git a/frontend/projects/ui/src/app/app/snek/snek.directive.ts b/frontend/projects/ui/src/app/app/snek/snek.directive.ts index 3e8cd327e..81b2f8361 100644 --- a/frontend/projects/ui/src/app/app/snek/snek.directive.ts +++ b/frontend/projects/ui/src/app/app/snek/snek.directive.ts @@ -1,4 +1,4 @@ -import { Directive, HostListener } from '@angular/core' +import { Directive, HostListener, Input } from '@angular/core' import { LoadingController, ModalController } from '@ionic/angular' import { ErrorToastService } from '@start9labs/shared' @@ -10,12 +10,14 @@ import { ApiService } from '../../services/api/embassy-api.service' selector: 'img[appSnek]', }) export class SnekDirective { + @Input() + appSnekHighScore: number | null = null + constructor( private readonly modalCtrl: ModalController, private readonly loadingCtrl: LoadingController, private readonly errToast: ErrorToastService, private readonly embassyApi: ApiService, - private readonly patch: PatchDbService, ) {} @HostListener('click') @@ -24,30 +26,28 @@ export class SnekDirective { component: SnakePage, cssClass: 'snake-modal', backdropDismiss: false, + componentProps: { highScore: this.appSnekHighScore || 0 }, }) modal.onDidDismiss().then(async ({ data }) => { - const highScore = - this.patch.getData().ui.gaming?.snake?.['high-score'] || 0 + if (data?.highScore <= (this.appSnekHighScore || 0)) return - if (data?.highScore > highScore) { - const loader = await this.loadingCtrl.create({ - message: 'Saving high score...', - backdropDismiss: true, + const loader = await this.loadingCtrl.create({ + message: 'Saving high score...', + backdropDismiss: true, + }) + + await loader.present() + + try { + await this.embassyApi.setDbValue({ + pointer: '/gaming', + value: { snake: { 'high-score': data.highScore } }, }) - - await loader.present() - - try { - await this.embassyApi.setDbValue({ - pointer: '/gaming', - value: { snake: { 'high-score': data.highScore } }, - }) - } catch (e: any) { - this.errToast.present(e) - } finally { - this.loadingCtrl.dismiss() - } + } catch (e: any) { + this.errToast.present(e) + } finally { + this.loadingCtrl.dismiss() } }) diff --git a/frontend/projects/ui/src/app/components/app-wizard/alert/alert.component.html b/frontend/projects/ui/src/app/components/app-wizard/alert/alert.component.html index 2fc8bae95..b3fa9fe12 100644 --- a/frontend/projects/ui/src/app/components/app-wizard/alert/alert.component.html +++ b/frontend/projects/ui/src/app/components/app-wizard/alert/alert.component.html @@ -1,4 +1,4 @@

Warning

-
+
diff --git a/frontend/projects/ui/src/app/components/app-wizard/alert/alert.component.ts b/frontend/projects/ui/src/app/components/app-wizard/alert/alert.component.ts index ed1e1e788..7c403cc55 100644 --- a/frontend/projects/ui/src/app/components/app-wizard/alert/alert.component.ts +++ b/frontend/projects/ui/src/app/components/app-wizard/alert/alert.component.ts @@ -7,9 +7,8 @@ import { BaseSlide } from '../wizard-types' styleUrls: ['../app-wizard.component.scss'], }) export class AlertComponent implements BaseSlide { - @Input() params: { - message: string - } + @Input() + params!: { message: string } async load() {} diff --git a/frontend/projects/ui/src/app/components/app-wizard/app-wizard.component.ts b/frontend/projects/ui/src/app/components/app-wizard/app-wizard.component.ts index 9cbe58fe7..8e2a8ff91 100644 --- a/frontend/projects/ui/src/app/components/app-wizard/app-wizard.component.ts +++ b/frontend/projects/ui/src/app/components/app-wizard/app-wizard.component.ts @@ -22,7 +22,8 @@ SwiperCore.use([IonicSlides]) styleUrls: ['./app-wizard.component.scss'], }) export class AppWizardComponent { - @Input() params: { + @Input() + params!: { action: WizardAction title: string slides: SlideDefinition[] @@ -31,16 +32,17 @@ export class AppWizardComponent { } // content container so we can scroll to top between slide transitions - @ViewChild(IonContent) content: IonContent + @ViewChild(IonContent) + content?: IonContent - swiper: Swiper + swiper?: Swiper //a slide component gives us hook into a slide. Allows us to call load when slide comes into view @ViewChildren('components') - slideComponentsQL: QueryList + slideComponentsQL?: QueryList get slideComponents(): BaseSlide[] { - return this.slideComponentsQL.toArray() + return this.slideComponentsQL?.toArray() || [] } get currentSlide(): BaseSlide { @@ -48,7 +50,7 @@ export class AppWizardComponent { } get currentIndex(): number { - return this.swiper.activeIndex + return this.swiper?.activeIndex || NaN } initializing = true @@ -58,7 +60,7 @@ export class AppWizardComponent { ionViewDidEnter() { this.initializing = false - this.swiper.allowTouchMove = false + if (this.swiper) this.swiper.allowTouchMove = false this.loadSlide() } @@ -71,8 +73,8 @@ export class AppWizardComponent { } async next() { - await this.content.scrollToTop() - this.swiper.slideNext(500) + await this.content?.scrollToTop() + this.swiper?.slideNext(500) } setError(e: any) { diff --git a/frontend/projects/ui/src/app/components/app-wizard/complete/complete.component.ts b/frontend/projects/ui/src/app/components/app-wizard/complete/complete.component.ts index 6d0dfdd1d..a5fdadb50 100644 --- a/frontend/projects/ui/src/app/components/app-wizard/complete/complete.component.ts +++ b/frontend/projects/ui/src/app/components/app-wizard/complete/complete.component.ts @@ -8,7 +8,8 @@ import { BaseSlide } from '../wizard-types' styleUrls: ['../app-wizard.component.scss'], }) export class CompleteComponent implements BaseSlide { - @Input() params: { + @Input() + params!: { verb: string // loader verb: '*stopping* ...' title: string Fn: () => Promise @@ -17,13 +18,13 @@ export class CompleteComponent implements BaseSlide { @Output() onSuccess: EventEmitter = new EventEmitter() @Output() onError: EventEmitter = new EventEmitter() - message: string + message = '' loading = true async load() { this.message = - capitalizeFirstLetter(this.params.verb) + ' ' + this.params.title + capitalizeFirstLetter(this.params.verb || '') + ' ' + this.params.title try { await this.params.Fn() this.onSuccess.emit() diff --git a/frontend/projects/ui/src/app/components/app-wizard/dependents/dependents.component.ts b/frontend/projects/ui/src/app/components/app-wizard/dependents/dependents.component.ts index 2edc3ede9..0ebc53dca 100644 --- a/frontend/projects/ui/src/app/components/app-wizard/dependents/dependents.component.ts +++ b/frontend/projects/ui/src/app/components/app-wizard/dependents/dependents.component.ts @@ -10,7 +10,8 @@ import { BaseSlide } from '../wizard-types' styleUrls: ['./dependents.component.scss', '../app-wizard.component.scss'], }) export class DependentsComponent implements BaseSlide { - @Input() params: { + @Input() + params!: { title: string verb: string // *Uninstalling* will cause problems... Fn: () => Promise @@ -19,21 +20,21 @@ export class DependentsComponent implements BaseSlide { @Output() onSuccess: EventEmitter = new EventEmitter() @Output() onError: EventEmitter = new EventEmitter() - breakages: Breakages - warningMessage: string | undefined + breakages?: Breakages + warningMessage = '' loading = true readonly pkgs$ = this.patch.watch$('package-data') - constructor(public readonly patch: PatchDbService) {} + constructor(private readonly patch: PatchDbService) {} async load() { try { this.breakages = await this.params.Fn() if (this.breakages && !isEmptyObject(this.breakages)) { this.warningMessage = - capitalizeFirstLetter(this.params.verb) + + capitalizeFirstLetter(this.params.verb || '') + ' ' + this.params.title + ' will prohibit the following services from functioning properly.' diff --git a/frontend/projects/ui/src/app/components/backup-drives/backup-drives-header.component.html b/frontend/projects/ui/src/app/components/backup-drives/backup-drives-header.component.html index f59b84b1e..b88f376d5 100644 --- a/frontend/projects/ui/src/app/components/backup-drives/backup-drives-header.component.html +++ b/frontend/projects/ui/src/app/components/backup-drives/backup-drives-header.component.html @@ -7,7 +7,7 @@ type === 'create' ? 'Create Backup' : 'Restore From Backup' }} - + Refresh diff --git a/frontend/projects/ui/src/app/components/backup-drives/backup-drives.component.html b/frontend/projects/ui/src/app/components/backup-drives/backup-drives.component.html index 769794fd7..300563d23 100644 --- a/frontend/projects/ui/src/app/components/backup-drives/backup-drives.component.html +++ b/frontend/projects/ui/src/app/components/backup-drives/backup-drives.component.html @@ -3,17 +3,17 @@ - + - {{ backupService.loadingError }} + {{ loadingError }} @@ -49,7 +49,7 @@ - + Physical Drives @@ -119,7 +119,7 @@ diff --git a/frontend/projects/ui/src/app/components/backup-drives/backup-drives.component.ts b/frontend/projects/ui/src/app/components/backup-drives/backup-drives.component.ts index 89a9566d8..1b22ce124 100644 --- a/frontend/projects/ui/src/app/components/backup-drives/backup-drives.component.ts +++ b/frontend/projects/ui/src/app/components/backup-drives/backup-drives.component.ts @@ -25,11 +25,11 @@ type BackupType = 'create' | 'restore' styleUrls: ['./backup-drives.component.scss'], }) export class BackupDrivesComponent { - @Input() type: BackupType + @Input() type!: BackupType @Output() onSelect: EventEmitter< MappedBackupTarget > = new EventEmitter() - loadingText: string + loadingText = '' constructor( private readonly loadingCtrl: LoadingController, @@ -38,9 +38,25 @@ export class BackupDrivesComponent { private readonly modalCtrl: ModalController, private readonly embassyApi: ApiService, private readonly errToast: ErrorToastService, - public readonly backupService: BackupService, + private readonly backupService: BackupService, ) {} + get loading() { + return this.backupService.loading + } + + get loadingError() { + return this.backupService.loadingError + } + + get drives() { + return this.backupService.drives + } + + get cifs() { + return this.backupService.cifs + } + ngOnInit() { this.loadingText = this.type === 'create' @@ -234,10 +250,14 @@ export class BackupDrivesComponent { styleUrls: ['./backup-drives.component.scss'], }) export class BackupDrivesHeaderComponent { - @Input() type: BackupType + @Input() type!: BackupType @Output() onClose: EventEmitter = new EventEmitter() - constructor(public readonly backupService: BackupService) {} + constructor(private readonly backupService: BackupService) {} + + get loading() { + return this.backupService.loading + } refresh() { this.backupService.getBackupTargets() @@ -250,8 +270,8 @@ export class BackupDrivesHeaderComponent { styleUrls: ['./backup-drives.component.scss'], }) export class BackupDrivesStatusComponent { - @Input() type: string - @Input() hasValidBackup: boolean + @Input() type!: BackupType + @Input() hasValidBackup!: boolean } const CifsSpec: ConfigSpec = { diff --git a/frontend/projects/ui/src/app/components/backup-drives/backup.service.ts b/frontend/projects/ui/src/app/components/backup-drives/backup.service.ts index 47a46070e..381110de4 100644 --- a/frontend/projects/ui/src/app/components/backup-drives/backup.service.ts +++ b/frontend/projects/ui/src/app/components/backup-drives/backup.service.ts @@ -13,10 +13,10 @@ import { getErrorMessage, Emver } from '@start9labs/shared' providedIn: 'root', }) export class BackupService { - cifs: MappedBackupTarget[] - drives: MappedBackupTarget[] + cifs: MappedBackupTarget[] = [] + drives: MappedBackupTarget[] = [] loading = true - loadingError: string | IonicSafeString + loadingError: string | IonicSafeString = '' constructor( private readonly embassyApi: ApiService, diff --git a/frontend/projects/ui/src/app/components/badge-menu-button/badge-menu.component.ts b/frontend/projects/ui/src/app/components/badge-menu-button/badge-menu.component.ts index 507810d97..1c8f90f8e 100644 --- a/frontend/projects/ui/src/app/components/badge-menu-button/badge-menu.component.ts +++ b/frontend/projects/ui/src/app/components/badge-menu-button/badge-menu.component.ts @@ -8,32 +8,30 @@ import { combineLatest, Subscription } from 'rxjs' templateUrl: './badge-menu.component.html', styleUrls: ['./badge-menu.component.scss'], }) - export class BadgeMenuComponent { - unreadCount: number - sidebarOpen: boolean + unreadCount = 0 + sidebarOpen = false subs: Subscription[] = [] - constructor ( + constructor( private readonly splitPane: SplitPaneTracker, private readonly patch: PatchDbService, - ) { } + ) {} - ngOnInit () { + ngOnInit() { this.subs = [ combineLatest([ this.patch.watch$('server-info', 'unread-notification-count'), this.splitPane.sidebarOpen$, - ]) - .subscribe(([unread, menu]) => { + ]).subscribe(([unread, menu]) => { this.unreadCount = unread this.sidebarOpen = menu }), ] } - ngOnDestroy () { + ngOnDestroy() { this.subs.forEach(sub => sub.unsubscribe()) } } diff --git a/frontend/projects/ui/src/app/components/form-object/form-label.component.html b/frontend/projects/ui/src/app/components/form-object/form-label.component.html index ea2fbae4f..e8d8375b6 100644 --- a/frontend/projects/ui/src/app/components/form-object/form-label.component.html +++ b/frontend/projects/ui/src/app/components/form-object/form-label.component.html @@ -18,8 +18,9 @@ (['string', 'number'] | includes: data.spec.type) && !$any(data.spec).nullable " - > * +  * +  *
- + Load More - +
Promise + loading = true - loadingMore = false - logs: string + loadingNext = false needInfinite = true - startCursor: string - endCursor: string - limit = 200 - scrollToBottomButton = false + startCursor?: string + endCursor?: string + limit = 400 isOnBottom = true constructor(private readonly errToast: ErrorToastService) {} - ngOnInit() { - this.getLogs() + async ngOnInit() { + await this.getPrior() + this.loading = false } - async fetch(isBefore: boolean = true) { + async getNext() { + this.loadingNext = true + const logs = await this.fetch(false) + if (!logs?.length) return (this.loadingNext = false) + + const container = document.getElementById('container') + const newLogs = document.getElementById('template')?.cloneNode(true) + + if (!(newLogs instanceof HTMLElement)) return + + newLogs.innerHTML = + logs.map(l => `${l.timestamp} ${convert.toHtml(l.message)}`).join('\n') + + (logs.length ? '\n' : '') + container?.append(newLogs) + this.loadingNext = false + this.scrollEvent() + } + + async doInfinite(e: any): Promise { + await this.getPrior() + e.target.complete() + } + + scrollEvent() { + const buttonDiv = document.getElementById('button-div') + this.isOnBottom = + !!buttonDiv && buttonDiv.getBoundingClientRect().top < window.innerHeight + } + + scrollToBottom() { + this.content?.scrollToBottom(500) + } + + private async getPrior() { + // get logs + const logs = await this.fetch() + if (!logs?.length) return + + const container = document.getElementById('container') + const beforeContainerHeight = container?.scrollHeight || 0 + const newLogs = document.getElementById('template')?.cloneNode(true) + + if (!(newLogs instanceof HTMLElement)) return + + newLogs.innerHTML = + logs.map(l => `${l.timestamp} ${convert.toHtml(l.message)}`).join('\n') + + (logs.length ? '\n' : '') + container?.prepend(newLogs) + const afterContainerHeight = container?.scrollHeight || 0 + + // scroll down + scrollBy(0, afterContainerHeight - beforeContainerHeight) + this.content?.scrollToPoint(0, afterContainerHeight - beforeContainerHeight) + + if (logs.length < this.limit) { + this.needInfinite = false + } + } + + private async fetch(isBefore: boolean = true) { try { const cursor = isBefore ? this.startCursor : this.endCursor const logsRes = await this.fetchLogs({ @@ -55,79 +117,10 @@ export class LogsPage { if ((!isBefore || !this.endCursor) && logsRes['end-cursor']) { this.endCursor = logsRes['end-cursor'] } - this.loading = false return logsRes.entries } catch (e: any) { this.errToast.present(e) } } - - async getLogs() { - try { - // get logs - const logs = await this.fetch() - if (!logs?.length) return - - const container = document.getElementById('container') - const beforeContainerHeight = container?.scrollHeight || 0 - const newLogs = document.getElementById('template')?.cloneNode(true) - - if (!(newLogs instanceof HTMLElement)) return - - newLogs.innerHTML = - logs - .map(l => `${l.timestamp} ${convert.toHtml(l.message)}`) - .join('\n') + (logs.length ? '\n' : '') - container?.prepend(newLogs) - const afterContainerHeight = container?.scrollHeight || 0 - - // scroll down - scrollBy(0, afterContainerHeight - beforeContainerHeight) - this.content.scrollToPoint( - 0, - afterContainerHeight - beforeContainerHeight, - ) - - if (logs.length < this.limit) { - this.needInfinite = false - } - } catch (e) {} - } - - async loadMore() { - try { - this.loadingMore = true - const logs = await this.fetch(false) - if (!logs?.length) return (this.loadingMore = false) - - const container = document.getElementById('container') - const newLogs = document.getElementById('template')?.cloneNode(true) - - if (!(newLogs instanceof HTMLElement)) return - - newLogs.innerHTML = - logs - .map(l => `${l.timestamp} ${convert.toHtml(l.message)}`) - .join('\n') + (logs.length ? '\n' : '') - container?.append(newLogs) - this.loadingMore = false - this.scrollEvent() - } catch (e) {} - } - - scrollEvent() { - const buttonDiv = document.getElementById('button-div') - this.isOnBottom = - !!buttonDiv && buttonDiv.getBoundingClientRect().top < window.innerHeight - } - - scrollToBottom() { - this.content.scrollToBottom(500) - } - - async loadData(e: any): Promise { - await this.getLogs() - e.target.complete() - } } diff --git a/frontend/projects/ui/src/app/components/qr/qr.component.ts b/frontend/projects/ui/src/app/components/qr/qr.component.ts index 8a4d75c3e..7fb6108f0 100644 --- a/frontend/projects/ui/src/app/components/qr/qr.component.ts +++ b/frontend/projects/ui/src/app/components/qr/qr.component.ts @@ -6,5 +6,5 @@ import { Component, Input } from '@angular/core' styleUrls: ['./qr.component.scss'], }) export class QRComponent { - @Input() text: string + @Input() text!: string } diff --git a/frontend/projects/ui/src/app/components/skeleton-list/skeleton-list.component.ts b/frontend/projects/ui/src/app/components/skeleton-list/skeleton-list.component.ts index 9cdf215b4..7042c0d05 100644 --- a/frontend/projects/ui/src/app/components/skeleton-list/skeleton-list.component.ts +++ b/frontend/projects/ui/src/app/components/skeleton-list/skeleton-list.component.ts @@ -1,20 +1,18 @@ -import { Component, Input } from '@angular/core' +import { Component, Input, OnChanges } from '@angular/core' @Component({ selector: 'skeleton-list', templateUrl: './skeleton-list.component.html', styleUrls: ['./skeleton-list.component.scss'], }) -export class SkeletonListComponent { - @Input() groups: string - @Input() rows: string = '3' +export class SkeletonListComponent implements OnChanges { + @Input() groups = 0 + @Input() rows = 3 groupsArr: number[] = [] rowsArr: number[] = [] - ngOnInit () { - if (this.groups) { - this.groupsArr = Array(Number(this.groups)).fill(0).map((_, i) => i) - } - this.rowsArr = Array(Number(this.rows)).fill(0).map((_, i) => i) + ngOnChanges() { + this.groupsArr = Array(this.groups).fill(0) + this.rowsArr = Array(this.rows).fill(0) } } diff --git a/frontend/projects/ui/src/app/components/status/status.component.ts b/frontend/projects/ui/src/app/components/status/status.component.ts index a891612c7..d6fd54464 100644 --- a/frontend/projects/ui/src/app/components/status/status.component.ts +++ b/frontend/projects/ui/src/app/components/status/status.component.ts @@ -16,7 +16,7 @@ export class StatusComponent { PS = PrimaryStatus PR = PrimaryRendering - @Input() rendering: StatusRendering + @Input() rendering!: StatusRendering @Input() size?: string @Input() style?: string = 'regular' @Input() weight?: string = 'normal' diff --git a/frontend/projects/ui/src/app/modals/action-marketplace/action-marketplace.component.html b/frontend/projects/ui/src/app/modals/action-marketplace/action-marketplace.component.html index 24883e7a1..cf7c1ad46 100644 --- a/frontend/projects/ui/src/app/modals/action-marketplace/action-marketplace.component.html +++ b/frontend/projects/ui/src/app/modals/action-marketplace/action-marketplace.component.html @@ -1,11 +1,11 @@ - + {{ title }} + - {{ title }} @@ -17,16 +17,15 @@

But you are currently connected to:

{{ currentMarketplace }}

-
-

To switch marketplaces visit your

+

Switch to {{ packageMarketplace }} in

Marketplace Settings -

or you can

+

Or you can

-
+

{{ actionRes.value }}

- + {{ actionRes.value }} - +
diff --git a/frontend/projects/ui/src/app/modals/action-success/action-success.page.ts b/frontend/projects/ui/src/app/modals/action-success/action-success.page.ts index 56af3af86..01b7ea186 100644 --- a/frontend/projects/ui/src/app/modals/action-success/action-success.page.ts +++ b/frontend/projects/ui/src/app/modals/action-success/action-success.page.ts @@ -9,7 +9,8 @@ import { copyToClipboard } from 'src/app/util/web.util' styleUrls: ['./action-success.page.scss'], }) export class ActionSuccessPage { - @Input() actionRes: ActionResponse + @Input() + actionRes!: ActionResponse constructor( private readonly modalCtrl: ModalController, diff --git a/frontend/projects/ui/src/app/modals/app-config/app-config.page.html b/frontend/projects/ui/src/app/modals/app-config/app-config.page.html index 608ccc283..9db615976 100644 --- a/frontend/projects/ui/src/app/modals/app-config/app-config.page.html +++ b/frontend/projects/ui/src/app/modals/app-config/app-config.page.html @@ -11,10 +11,13 @@ - + - + {{ loadingError }} @@ -22,21 +25,28 @@ -

- - - {{ pkg.manifest.title }} has been automatically configured with - recommended defaults. Make whatever changes you want, then click - "Save". - - - New config options! - - -

+ + +

+ + {{ pkg.manifest.title }} has been automatically configured with + recommended defaults. Make whatever changes you want, then click + "Save". + +

+
+ +

+ + New config options! To accept the default values, click "Save". + You may also customize these new options below. + +

+
+
-
+
- - - - Reset Defaults - - - - - Save - - - Close - - + + + + + Reset Defaults + + + + + Save + + + Close + + + diff --git a/frontend/projects/ui/src/app/modals/app-config/app-config.page.ts b/frontend/projects/ui/src/app/modals/app-config/app-config.page.ts index 698340c30..0cbfb8690 100644 --- a/frontend/projects/ui/src/app/modals/app-config/app-config.page.ts +++ b/frontend/projects/ui/src/app/modals/app-config/app-config.page.ts @@ -1,8 +1,7 @@ -import { Component, Input, ViewChild } from '@angular/core' +import { Component, Input } from '@angular/core' import { AlertController, ModalController, - IonContent, LoadingController, IonicSafeString, } from '@ionic/angular' @@ -24,6 +23,7 @@ import { } from 'src/app/services/form.service' import { compare, Operation, getValueByPointer } from 'fast-json-patch' import { hasCurrentDeps } from 'src/app/util/has-deps' +import { getAllPackages, getPackage } from 'src/app/util/get-package-data' import { Breakages } from 'src/app/services/api/api.types' @Component({ @@ -32,19 +32,24 @@ import { Breakages } from 'src/app/services/api/api.types' styleUrls: ['./app-config.page.scss'], }) export class AppConfigPage { - @ViewChild(IonContent) content: IonContent - @Input() pkgId: string - @Input() dependentInfo?: DependentInfo - diff: string[] // only if dependent info - pkg: PackageDataEntry - loadingText: string | undefined - configSpec: ConfigSpec - configForm: FormGroup - original: object + @Input() pkgId!: string + + @Input() + dependentInfo?: DependentInfo + + pkg!: PackageDataEntry + loadingText!: string + configSpec!: ConfigSpec + configForm!: FormGroup + + original?: object // only if existing config + diff?: string[] // only if dependent info + + loading = true hasConfig = false hasNewOptions = false saving = false - loadingError: string | IonicSafeString + loadingError: string | IonicSafeString = '' constructor( private readonly embassyApi: ApiService, @@ -57,17 +62,15 @@ export class AppConfigPage { ) {} async ngOnInit() { - this.pkg = this.patch.getData()['package-data'][this.pkgId] - this.hasConfig = !!this.pkg?.manifest.config - - if (!this.hasConfig) return - - let oldConfig: object | null - let newConfig: object | undefined - let spec: ConfigSpec - let patch: Operation[] | undefined - try { + this.pkg = await getPackage(this.patch, this.pkgId) + this.hasConfig = !!this.pkg.manifest.config + + if (!this.hasConfig) return + + let newConfig: object | undefined + let patch: Operation[] | undefined + if (this.dependentInfo) { this.loadingText = `Setting properties to accommodate ${this.dependentInfo.title}` const { @@ -78,24 +81,22 @@ export class AppConfigPage { 'dependency-id': this.pkgId, 'dependent-id': this.dependentInfo.id, }) - oldConfig = oc + this.original = oc newConfig = nc - spec = s - patch = compare(oldConfig, newConfig) + this.configSpec = s + patch = compare(this.original, newConfig) } else { this.loadingText = 'Loading Config' const { config: c, spec: s } = await this.embassyApi.getPackageConfig({ id: this.pkgId, }) - oldConfig = c - spec = s + this.original = c + this.configSpec = s } - this.original = oldConfig - this.configSpec = spec this.configForm = this.formService.createForm( - spec, - newConfig || oldConfig, + this.configSpec, + newConfig || this.original, ) this.configForm.markAllAsTouched() @@ -106,22 +107,18 @@ export class AppConfigPage { } catch (e: any) { this.loadingError = getErrorMessage(e) } finally { - this.loadingText = undefined + this.loading = false } } - ngAfterViewInit() { - this.content.scrollToPoint(undefined, 1) - } - resetDefaults() { - this.configForm = this.formService.createForm(this.configSpec) - const patch = compare(this.original, this.configForm.value) + this.configForm = this.formService.createForm(this.configSpec!) + const patch = compare(this.original || {}, this.configForm.value) this.markDirty(patch) } async dismiss() { - if (this.configForm?.dirty) { + if (this.configForm.dirty) { this.presentAlertUnsaved() } else { this.modalCtrl.dismiss() @@ -202,7 +199,7 @@ export class AppConfigPage { private async presentAlertBreakages(breakages: Breakages): Promise { let message: string = 'As a result of this change, the following services will no longer work properly and may crash:
    ' - const localPkgs = this.patch.getData()['package-data'] + const localPkgs = await getAllPackages(this.patch) const bullets = Object.keys(breakages).map(id => { const title = localPkgs[id].manifest.title return `
  • ${title}
  • ` diff --git a/frontend/projects/ui/src/app/modals/app-recover-select/app-recover-select.module.ts b/frontend/projects/ui/src/app/modals/app-recover-select/app-recover-select.module.ts index cfab68f7d..90c9c1097 100644 --- a/frontend/projects/ui/src/app/modals/app-recover-select/app-recover-select.module.ts +++ b/frontend/projects/ui/src/app/modals/app-recover-select/app-recover-select.module.ts @@ -1,16 +1,14 @@ import { NgModule } from '@angular/core' import { CommonModule } from '@angular/common' import { IonicModule } from '@ionic/angular' -import { AppRecoverSelectPage } from './app-recover-select.page' import { FormsModule } from '@angular/forms' +import { AppRecoverSelectPage } from './app-recover-select.page' +import { ToOptionsPipe } from './to-options.pipe' + @NgModule({ - declarations: [AppRecoverSelectPage], - imports: [ - CommonModule, - IonicModule, - FormsModule, - ], + declarations: [AppRecoverSelectPage, ToOptionsPipe], + imports: [CommonModule, IonicModule, FormsModule], exports: [AppRecoverSelectPage], }) -export class AppRecoverSelectPageModule { } \ No newline at end of file +export class AppRecoverSelectPageModule {} diff --git a/frontend/projects/ui/src/app/modals/app-recover-select/app-recover-select.page.html b/frontend/projects/ui/src/app/modals/app-recover-select/app-recover-select.page.html index 365c9b7aa..b336f3406 100644 --- a/frontend/projects/ui/src/app/modals/app-recover-select/app-recover-select.page.html +++ b/frontend/projects/ui/src/app/modals/app-recover-select/app-recover-select.page.html @@ -1,62 +1,65 @@ - - - Select Services to Restore - - - - - - - + + + + Select Services to Restore + + + + + + + - -

    - Warning! Restoring a service will permanently overwrite its current - data with data from its backup. Please make selections carefully. -

    - - - -

    {{ option.title }}

    -

    Version {{ option.version }}

    -

    Backup made: {{ option.timestamp | date : 'short' }}

    -

    - Ready to restore -

    -

    - Unavailable. {{ option.title }} is already installed. -

    -

    - Unavailable. Backup was made on a newer version of - EmbassyOS. -

    -
    - -
    -
    -
    + +

    + Warning! Restoring a service will permanently overwrite its current + data with data from its backup. Please make selections carefully. +

    + + + +

    {{ option.title }}

    +

    Version {{ option.version }}

    +

    Backup made: {{ option.timestamp | date : 'medium' }}

    +

    + Ready to restore +

    +

    + + Unavailable. {{ option.title }} is already installed. + +

    +

    + + Unavailable. Backup was made on a newer version of EmbassyOS. + +

    +
    + +
    +
    +
    - - - - - Restore Selected - - - - + + + + + Restore Selected + + + + +
    diff --git a/frontend/projects/ui/src/app/modals/app-recover-select/app-recover-select.page.ts b/frontend/projects/ui/src/app/modals/app-recover-select/app-recover-select.page.ts index 710afa864..6dedd66e4 100644 --- a/frontend/projects/ui/src/app/modals/app-recover-select/app-recover-select.page.ts +++ b/frontend/projects/ui/src/app/modals/app-recover-select/app-recover-select.page.ts @@ -4,11 +4,11 @@ import { ModalController, IonicSafeString, } from '@ionic/angular' -import { BackupInfo, PackageBackupInfo } from 'src/app/services/api/api.types' +import { getErrorMessage } from '@start9labs/shared' +import { BackupInfo } from 'src/app/services/api/api.types' import { ApiService } from 'src/app/services/api/embassy-api.service' -import { ConfigService } from 'src/app/services/config.service' -import { getErrorMessage, Emver } from '@start9labs/shared' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { AppRecoverOption } from './to-options.pipe' @Component({ selector: 'app-recover-select', @@ -16,57 +16,33 @@ import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' styleUrls: ['./app-recover-select.page.scss'], }) export class AppRecoverSelectPage { - @Input() id: string - @Input() backupInfo: BackupInfo - @Input() password: string - @Input() oldPassword: string - options: (PackageBackupInfo & { - id: string - checked: boolean - installed: boolean - 'newer-eos': boolean - })[] + @Input() id!: string + @Input() backupInfo!: BackupInfo + @Input() password!: string + @Input() oldPassword?: string + + readonly packageData$ = this.patch.watch$('package-data') + hasSelection = false - error: string | IonicSafeString + error: string | IonicSafeString = '' constructor( private readonly modalCtrl: ModalController, private readonly loadingCtrl: LoadingController, private readonly embassyApi: ApiService, - private readonly config: ConfigService, - private readonly emver: Emver, private readonly patch: PatchDbService, ) {} - ngOnInit() { - this.options = Object.keys(this.backupInfo['package-backups']).map(id => { - return { - ...this.backupInfo['package-backups'][id], - id, - checked: false, - installed: !!this.patch.getData()['package-data'][id], - 'newer-eos': - this.emver.compare( - this.backupInfo['package-backups'][id]['os-version'], - this.config.version, - ) === 1, - } - }) - } - dismiss() { this.modalCtrl.dismiss() } - handleChange() { - this.hasSelection = this.options.some(o => o.checked) + handleChange(options: AppRecoverOption[]) { + this.hasSelection = options.some(o => o.checked) } - async restore(): Promise { - const ids = this.options - .filter(option => !!option.checked) - .map(option => option.id) - + async restore(options: AppRecoverOption[]): Promise { + const ids = options.filter(({ checked }) => !!checked).map(({ id }) => id) const loader = await this.loadingCtrl.create({ message: 'Initializing...', }) @@ -76,7 +52,7 @@ export class AppRecoverSelectPage { await this.embassyApi.restorePackages({ ids, 'target-id': this.id, - 'old-password': this.oldPassword, + 'old-password': this.oldPassword || null, password: this.password, }) this.modalCtrl.dismiss(undefined, 'success') diff --git a/frontend/projects/ui/src/app/modals/app-recover-select/to-options.pipe.ts b/frontend/projects/ui/src/app/modals/app-recover-select/to-options.pipe.ts new file mode 100644 index 000000000..8a6b0fbdd --- /dev/null +++ b/frontend/projects/ui/src/app/modals/app-recover-select/to-options.pipe.ts @@ -0,0 +1,46 @@ +import { Pipe, PipeTransform } from '@angular/core' +import { Emver } from '@start9labs/shared' +import { PackageBackupInfo } from 'src/app/services/api/api.types' +import { ConfigService } from 'src/app/services/config.service' +import { PackageDataEntry } from 'src/app/services/patch-db/data-model' +import { Observable } from 'rxjs' +import { map } from 'rxjs/operators' + +export interface AppRecoverOption extends PackageBackupInfo { + id: string + checked: boolean + installed: boolean + 'newer-eos': boolean +} + +@Pipe({ + name: 'toOptions', +}) +export class ToOptionsPipe implements PipeTransform { + constructor( + private readonly config: ConfigService, + private readonly emver: Emver, + ) {} + + transform( + packageData$: Observable>, + packageBackups: Record = {}, + ): Observable { + return packageData$.pipe( + map(packageData => + Object.keys(packageBackups).map(id => ({ + ...packageBackups[id], + id, + installed: !!packageData[id], + checked: false, + 'newer-eos': this.compare(packageBackups[id]['os-version']), + })), + ), + ) + } + + private compare(version: string): boolean { + // checks to see if backup was made on a newer version of EOS + return this.emver.compare(version, this.config.version) === 1 + } +} diff --git a/frontend/projects/ui/src/app/modals/backup-report/backup-report.page.html b/frontend/projects/ui/src/app/modals/backup-report/backup-report.page.html index c6513352d..f3a9e98d5 100644 --- a/frontend/projects/ui/src/app/modals/backup-report/backup-report.page.html +++ b/frontend/projects/ui/src/app/modals/backup-report/backup-report.page.html @@ -11,20 +11,35 @@ - Completed: {{ timestamp | date : 'short' }} + Completed: {{ timestamp | date : 'medium' }}

    System data

    {{ system.result }}

    - +
    - +

    {{ pkg.key }}

    -

    {{ pkg.value.error ? 'Failed: ' + pkg.value.error : 'Succeeded' }}

    +

    + {{ pkg.value.error ? 'Failed: ' + pkg.value.error : 'Succeeded' + }} +

    - +
    diff --git a/frontend/projects/ui/src/app/modals/backup-report/backup-report.page.ts b/frontend/projects/ui/src/app/modals/backup-report/backup-report.page.ts index 0e0c3afa8..662980ade 100644 --- a/frontend/projects/ui/src/app/modals/backup-report/backup-report.page.ts +++ b/frontend/projects/ui/src/app/modals/backup-report/backup-report.page.ts @@ -8,9 +8,10 @@ import { BackupReport } from 'src/app/services/api/api.types' styleUrls: ['./backup-report.page.scss'], }) export class BackupReportPage { - @Input() report: BackupReport - @Input() timestamp: string - system: { + @Input() report!: BackupReport + @Input() timestamp!: string + + system!: { result: string icon: 'remove' | 'remove-circle-outline' | 'checkmark' color: 'dark' | 'danger' | 'success' diff --git a/frontend/projects/ui/src/app/modals/enum-list/enum-list.page.ts b/frontend/projects/ui/src/app/modals/enum-list/enum-list.page.ts index 4f6e0106e..e5ddc8ed3 100644 --- a/frontend/projects/ui/src/app/modals/enum-list/enum-list.page.ts +++ b/frontend/projects/ui/src/app/modals/enum-list/enum-list.page.ts @@ -8,16 +8,17 @@ import { ValueSpecListOf } from 'src/app/pkg-config/config-types' styleUrls: ['./enum-list.page.scss'], }) export class EnumListPage { - @Input() key: string - @Input() spec: ValueSpecListOf<'enum'> - @Input() current: string[] + @Input() key!: string + @Input() spec!: ValueSpecListOf<'enum'> + @Input() current: string[] = [] + options: { [option: string]: boolean } = {} selectAll = false constructor(private readonly modalCtrl: ModalController) {} ngOnInit() { - for (let val of this.spec.spec.values) { + for (let val of this.spec.spec.values || []) { this.options[val] = this.current.includes(val) } // if none are selected, set selectAll to true diff --git a/frontend/projects/ui/src/app/modals/generic-form/generic-form.page.html b/frontend/projects/ui/src/app/modals/generic-form/generic-form.page.html index a51380520..0dcd183fa 100644 --- a/frontend/projects/ui/src/app/modals/generic-form/generic-form.page.html +++ b/frontend/projects/ui/src/app/modals/generic-form/generic-form.page.html @@ -10,11 +10,12 @@ -
    - + +
    @@ -22,7 +23,11 @@ - + {{ button.text }} diff --git a/frontend/projects/ui/src/app/modals/generic-form/generic-form.page.ts b/frontend/projects/ui/src/app/modals/generic-form/generic-form.page.ts index 26ed6f4ff..e9782ddce 100644 --- a/frontend/projects/ui/src/app/modals/generic-form/generic-form.page.ts +++ b/frontend/projects/ui/src/app/modals/generic-form/generic-form.page.ts @@ -19,12 +19,13 @@ export interface ActionButton { styleUrls: ['./generic-form.page.scss'], }) export class GenericFormPage { - @Input() title: string - @Input() spec: ConfigSpec - @Input() buttons: ActionButton[] + @Input() title!: string + @Input() spec!: ConfigSpec + @Input() buttons!: ActionButton[] @Input() initialValue: object = {} - submitBtn: ActionButton - formGroup: FormGroup + + submitBtn!: ActionButton + formGroup!: FormGroup constructor( private readonly modalCtrl: ModalController, diff --git a/frontend/projects/ui/src/app/modals/generic-input/generic-input.component.ts b/frontend/projects/ui/src/app/modals/generic-input/generic-input.component.ts index 70b291016..e585460ef 100644 --- a/frontend/projects/ui/src/app/modals/generic-input/generic-input.component.ts +++ b/frontend/projects/ui/src/app/modals/generic-input/generic-input.component.ts @@ -10,12 +10,16 @@ import { MaskPipe } from 'src/app/pipes/mask/mask.pipe' providers: [MaskPipe], }) export class GenericInputComponent { - @ViewChild('mainInput') elem: IonInput - @Input() options: GenericInputOptions - value: string - maskedValue: string - masked: boolean - error: string | IonicSafeString + @ViewChild('mainInput') elem?: IonInput + + @Input() options!: GenericInputOptions + + value!: string + masked!: boolean + + maskedValue?: string + + error: string | IonicSafeString = '' constructor( private readonly modalCtrl: ModalController, @@ -40,7 +44,7 @@ export class GenericInputComponent { } ngAfterViewInit() { - setTimeout(() => this.elem.setFocus(), 400) + setTimeout(() => this.elem?.setFocus(), 400) } toggleMask() { diff --git a/frontend/projects/ui/src/app/modals/os-update/os-update.page.ts b/frontend/projects/ui/src/app/modals/os-update/os-update.page.ts index d0041a845..6d330a861 100644 --- a/frontend/projects/ui/src/app/modals/os-update/os-update.page.ts +++ b/frontend/projects/ui/src/app/modals/os-update/os-update.page.ts @@ -2,7 +2,7 @@ import { Component, Input } from '@angular/core' import { LoadingController, ModalController } from '@ionic/angular' import { ConfigService } from '../../services/config.service' import { ApiService } from '../../services/api/embassy-api.service' -import { ErrorToastService } from '../../../../../shared/src/services/error-toast.service' +import { ErrorToastService } from '@start9labs/shared' @Component({ selector: 'os-update', @@ -10,7 +10,7 @@ import { ErrorToastService } from '../../../../../shared/src/services/error-toas styleUrls: ['./os-update.page.scss'], }) export class OSUpdatePage { - @Input() releaseNotes: { [version: string]: string } + @Input() releaseNotes!: { [version: string]: string } versions: { version: string; notes: string }[] = [] diff --git a/frontend/projects/ui/src/app/modals/os-welcome/os-welcome.page.ts b/frontend/projects/ui/src/app/modals/os-welcome/os-welcome.page.ts index 8507d26d2..3fb857eba 100644 --- a/frontend/projects/ui/src/app/modals/os-welcome/os-welcome.page.ts +++ b/frontend/projects/ui/src/app/modals/os-welcome/os-welcome.page.ts @@ -7,13 +7,11 @@ import { ModalController } from '@ionic/angular' styleUrls: ['./os-welcome.page.scss'], }) export class OSWelcomePage { - @Input() version: string + @Input() version!: string - constructor ( - private readonly modalCtrl: ModalController, - ) { } + constructor(private readonly modalCtrl: ModalController) {} - async dismiss () { + async dismiss() { return this.modalCtrl.dismiss() } } diff --git a/frontend/projects/ui/src/app/modals/snake/snake.page.html b/frontend/projects/ui/src/app/modals/snake/snake.page.html index 30e79de0d..9e037ce8d 100644 --- a/frontend/projects/ui/src/app/modals/snake/snake.page.html +++ b/frontend/projects/ui/src/app/modals/snake/snake.page.html @@ -7,7 +7,7 @@
    - +
    diff --git a/frontend/projects/ui/src/app/modals/snake/snake.page.ts b/frontend/projects/ui/src/app/modals/snake/snake.page.ts index 78d3fbe07..6296a8609 100644 --- a/frontend/projects/ui/src/app/modals/snake/snake.page.ts +++ b/frontend/projects/ui/src/app/modals/snake/snake.page.ts @@ -1,7 +1,6 @@ -import { Component, HostListener } from '@angular/core' +import { Component, HostListener, Input } from '@angular/core' import { ModalController } from '@ionic/angular' import { pauseFor } from '@start9labs/shared' -import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' @Component({ selector: 'snake', @@ -9,38 +8,30 @@ import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' styleUrls: ['./snake.page.scss'], }) export class SnakePage { - speed = 45 - width = 40 - height = 26 - grid = NaN - - startingLength = 4 - - score = 0 + @Input() highScore = 0 - xDown?: number - yDown?: number - canvas: HTMLCanvasElement - image: HTMLImageElement - context: CanvasRenderingContext2D + score = 0 - snake: any - bitcoin: { x: number; y: number } = { x: NaN, y: NaN } + private readonly speed = 45 + private readonly width = 40 + private readonly height = 26 + private grid = NaN - moveQueue: String[] = [] + private readonly startingLength = 4 - constructor( - private readonly modalCtrl: ModalController, - private readonly patch: PatchDbService, - ) {} + private xDown?: number + private yDown?: number + private canvas!: HTMLCanvasElement + private image!: HTMLImageElement + private context!: CanvasRenderingContext2D - ngOnInit() { - if (this.patch.getData().ui.gaming?.snake?.['high-score']) { - this.highScore = - this.patch.getData().ui.gaming?.snake?.['high-score'] || 0 - } - } + private snake: any + private bitcoin: { x: number; y: number } = { x: NaN, y: NaN } + + private moveQueue: String[] = [] + + constructor(private readonly modalCtrl: ModalController) {} async dismiss() { return this.modalCtrl.dismiss({ highScore: this.highScore }) @@ -77,7 +68,7 @@ export class SnakePage { } init() { - this.canvas = document.getElementById('game') as HTMLCanvasElement + this.canvas = document.querySelector('canvas#game')! this.canvas.style.border = '1px solid #e0e0e0' this.context = this.canvas.getContext('2d')! const container = document.getElementsByClassName('canvas-center')[0] @@ -224,7 +215,7 @@ export class SnakePage { // snake ate bitcoin if (cell.x === this.bitcoin.x && cell.y === this.bitcoin.y) { this.score++ - if (this.score > this.highScore) this.highScore = this.score + this.highScore = Math.max(this.score, this.highScore) this.snake.maxCells++ this.bitcoin.x = this.getRandomInt(0, this.width) * this.grid diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions-item.component.html b/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions-item.component.html index c635bc72a..71bcc6e40 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions-item.component.html +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions-item.component.html @@ -4,4 +4,4 @@

    {{ action.name }}

    {{ action.description }}

    - \ No newline at end of file + diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.html b/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.html index 610d92e58..0c28437f1 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.html +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.html @@ -8,7 +8,7 @@ - + Standard Actions @@ -32,7 +32,7 @@ description: action.value.description, icon: 'play-circle-outline' }" - (click)="handleAction(action)" + (click)="handleAction(pkg, action)" > diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts index e4564f135..8a93d69a0 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts @@ -1,9 +1,8 @@ -import { Component, Input, ViewChild } from '@angular/core' +import { Component, Input } from '@angular/core' import { ActivatedRoute } from '@angular/router' import { ApiService } from 'src/app/services/api/embassy-api.service' import { AlertController, - IonContent, LoadingController, ModalController, NavController, @@ -14,7 +13,6 @@ import { PackageDataEntry, PackageMainStatus, } from 'src/app/services/patch-db/data-model' -import { Subscription } from 'rxjs' import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page' import { isEmptyObject, ErrorToastService, getPkgId } from '@start9labs/shared' import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page' @@ -26,10 +24,8 @@ import { hasCurrentDeps } from 'src/app/util/has-deps' styleUrls: ['./app-actions.page.scss'], }) export class AppActionsPage { - @ViewChild(IonContent) content: IonContent readonly pkgId = getPkgId(this.route) - pkg: PackageDataEntry - subs: Subscription[] + readonly pkg$ = this.patch.watch$('package-data', this.pkgId) constructor( private readonly route: ActivatedRoute, @@ -42,24 +38,11 @@ export class AppActionsPage { private readonly patch: PatchDbService, ) {} - ngOnInit() { - this.subs = [ - this.patch.watch$('package-data', this.pkgId).subscribe(pkg => { - this.pkg = pkg - }), - ] - } - - ngAfterViewInit() { - this.content.scrollToPoint(undefined, 1) - } - - ngOnDestroy() { - this.subs.forEach(sub => sub.unsubscribe()) - } - - async handleAction(action: { key: string; value: Action }) { - const status = this.pkg.installed?.status + async handleAction( + pkg: PackageDataEntry, + action: { key: string; value: Action }, + ) { + const status = pkg.installed?.status if ( status && (action.value['allowed-statuses'] as PackageMainStatus[]).includes( @@ -134,14 +117,14 @@ export class AppActionsPage { } } - async tryUninstall(): Promise { - const { title, alerts } = this.pkg.manifest + async tryUninstall(pkg: PackageDataEntry): Promise { + const { title, alerts } = pkg.manifest let message = alerts.uninstall || `Uninstalling ${title} will permanently delete its data` - if (hasCurrentDeps(this.pkg)) { + if (hasCurrentDeps(pkg)) { message = `${message}. Services that depend on ${title} will no longer work properly and may crash` } @@ -233,5 +216,5 @@ interface LocalAction { styleUrls: ['./app-actions.page.scss'], }) export class AppActionsItemComponent { - @Input() action: LocalAction + @Input() action!: LocalAction } diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html b/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html index 71589eb28..932c1fd0a 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces-item.component.html @@ -1,4 +1,4 @@ - + {{ interface.def.description }} -
    +
    diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.module.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.module.ts index 67cc64f8f..a9a2ddebc 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.module.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.module.ts @@ -2,11 +2,12 @@ import { NgModule } from '@angular/core' import { CommonModule } from '@angular/common' import { Routes, RouterModule } from '@angular/router' import { IonicModule } from '@ionic/angular' +import { SharedPipesModule } from '@start9labs/shared' + import { AppInterfacesItemComponent, AppInterfacesPage, } from './app-interfaces.page' -import { SharedPipesModule } from '@start9labs/shared' const routes: Routes = [ { diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.ts index 8815fea4b..0a9e9c15c 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-interfaces/app-interfaces.page.ts @@ -1,6 +1,6 @@ -import { Component, Input, ViewChild } from '@angular/core' +import { Component, Input } from '@angular/core' import { ActivatedRoute } from '@angular/router' -import { IonContent, ModalController, ToastController } from '@ionic/angular' +import { ModalController, ToastController } from '@ionic/angular' import { getPkgId } from '@start9labs/shared' import { getUiInterfaceKey } from 'src/app/services/config.service' import { @@ -10,6 +10,7 @@ import { import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' import { copyToClipboard } from 'src/app/util/web.util' import { QRComponent } from 'src/app/components/qr/qr.component' +import { getPackage } from '../../../util/get-package-data' interface LocalInterface { def: InterfaceDef @@ -22,22 +23,21 @@ interface LocalInterface { styleUrls: ['./app-interfaces.page.scss'], }) export class AppInterfacesPage { - @ViewChild(IonContent) content: IonContent - ui: LocalInterface | null + ui?: LocalInterface other: LocalInterface[] = [] readonly pkgId = getPkgId(this.route) constructor( private readonly route: ActivatedRoute, - public readonly patch: PatchDbService, + private readonly patch: PatchDbService, ) {} - ngOnInit() { - const pkg = this.patch.getData()['package-data'][this.pkgId] + async ngOnInit() { + const pkg = await getPackage(this.patch, this.pkgId) const interfaces = pkg.manifest.interfaces const uiKey = getUiInterfaceKey(interfaces) - if (!pkg?.installed) return + if (!pkg.installed) return const addressesMap = pkg.installed['interface-addresses'] @@ -73,14 +73,6 @@ export class AppInterfacesPage { } }) } - - ngAfterViewInit() { - this.content.scrollToPoint(undefined, 1) - } - - asIsOrder() { - return 0 - } } @Component({ @@ -89,7 +81,8 @@ export class AppInterfacesPage { styleUrls: ['./app-interfaces.page.scss'], }) export class AppInterfacesItemComponent { - @Input() interface: LocalInterface + @Input() + interface!: LocalInterface constructor( private readonly toastCtrl: ToastController, diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-icon/app-list-icon.component.html b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-icon/app-list-icon.component.html index ea04afe55..085b8ee1b 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-icon/app-list-icon.component.html +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-icon/app-list-icon.component.html @@ -1,25 +1,31 @@ - - -
    + + - -
    + color="warning" + >
    + + + +
    +
    diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-icon/app-list-icon.component.scss b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-icon/app-list-icon.component.scss index a78ebb826..d189732b5 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-icon/app-list-icon.component.scss +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-icon/app-list-icon.component.scss @@ -1,20 +1,18 @@ .bulb { position: absolute !important; - top: 6px !important; + top: 9px !important; height: 14px; width: 14px; border-radius: 100%; - box-shadow: 0 0 6px 6px rgba(255, 213, 52, 0.1); } .warning-icon { position: absolute !important; - top: 6px !important; + top: 8px !important; + left: 11px !important; font-size: 12px; border-radius: 100%; padding: 1px; - background-color: rgba(255, 213, 52, 0.1); - box-shadow: 0 0 4px 4px rgba(255, 213, 52, 0.1); } .spinner { diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-icon/app-list-icon.component.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-icon/app-list-icon.component.ts index 8815e26e5..2148cea52 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-icon/app-list-icon.component.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-icon/app-list-icon.component.ts @@ -1,5 +1,4 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { map } from 'rxjs/operators' import { ConnectionService } from 'src/app/services/connection.service' import { PkgInfo } from 'src/app/util/get-package-info' @@ -11,7 +10,7 @@ import { PkgInfo } from 'src/app/util/get-package-info' }) export class AppListIconComponent { @Input() - pkg: PkgInfo + pkg!: PkgInfo disconnected$ = this.connectionService.watchDisconnected$() diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html index ad51dfad6..b33416b7d 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.html @@ -1,4 +1,9 @@ - + diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts index 71b858116..3302e182e 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-pkg/app-list-pkg.component.ts @@ -1,8 +1,5 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { - PackageMainStatus, - Manifest, -} from 'src/app/services/patch-db/data-model' +import { PackageMainStatus } from 'src/app/services/patch-db/data-model' import { PkgInfo } from 'src/app/util/get-package-info' import { UiLauncherService } from 'src/app/services/ui-launcher.service' @@ -13,7 +10,7 @@ import { UiLauncherService } from 'src/app/services/ui-launcher.service' }) export class AppListPkgComponent { @Input() - pkg: PkgInfo + pkg!: PkgInfo constructor(private readonly launcherService: UiLauncherService) {} @@ -23,10 +20,6 @@ export class AppListPkgComponent { ) } - get manifest(): Manifest { - return this.pkg.entry.manifest - } - launchUi(e: Event): void { e.stopPropagation() e.preventDefault() diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-rec/app-list-rec.component.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-rec/app-list-rec.component.ts index b336525ab..0122bc2d8 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-rec/app-list-rec.component.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-list/app-list-rec/app-list-rec.component.ts @@ -26,7 +26,7 @@ export class AppListRecComponent { readonly delete$ = new Subject() @Input() - rec: RecoveredInfo + rec!: RecoveredInfo @Output() readonly deleted = new EventEmitter() diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-logs/app-logs.page.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-logs/app-logs.page.ts index 1920bf4b1..31019d442 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-logs/app-logs.page.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-logs/app-logs.page.ts @@ -12,9 +12,6 @@ import { copyToClipboard, strip } from 'src/app/util/web.util' }) export class AppLogsPage { readonly pkgId = getPkgId(this.route) - loading = true - needInfinite = true - before: string constructor( private readonly route: ActivatedRoute, diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html b/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html index c5d99df39..5db570b23 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.html @@ -4,18 +4,21 @@ Monitor - + - - + {{ metric.key }} - {{ metric.value.value }} {{ metric.value.unit }} + {{ metric.value.value }} {{ metric.value.unit }} diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts index a2154ea09..8cf576bd7 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-metrics/app-metrics.page.ts @@ -1,10 +1,7 @@ -import { Component, ViewChild } from '@angular/core' +import { Component } from '@angular/core' import { ActivatedRoute } from '@angular/router' -import { IonContent } from '@ionic/angular' -import { Subscription } from 'rxjs' import { Metric } from 'src/app/services/api/api.types' import { ApiService } from 'src/app/services/api/embassy-api.service' -import { MainStatus } from 'src/app/services/patch-db/data-model' import { pauseFor, ErrorToastService, getPkgId } from '@start9labs/shared' @Component({ @@ -15,12 +12,8 @@ import { pauseFor, ErrorToastService, getPkgId } from '@start9labs/shared' export class AppMetricsPage { loading = true readonly pkgId = getPkgId(this.route) - mainStatus: MainStatus going = false - metrics: Metric - subs: Subscription[] = [] - - @ViewChild(IonContent) content: IonContent + metrics?: Metric constructor( private readonly route: ActivatedRoute, @@ -32,10 +25,6 @@ export class AppMetricsPage { this.startDaemon() } - ngAfterViewInit() { - this.content.scrollToPoint(undefined, 1) - } - ngOnDestroy() { this.stopDaemon() } diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.html b/frontend/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.html index 4e374ab78..8ae659abc 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.html +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-properties/app-properties.page.html @@ -21,7 +21,7 @@ - +

    status !== PackageMainStatus.Running)) @ViewChild(IonBackButtonDelegate, { static: false }) - backButton: IonBackButtonDelegate - @ViewChild(IonContent) content: IonContent - subs: Subscription[] = [] + backButton?: IonBackButtonDelegate constructor( private readonly route: ActivatedRoute, @@ -46,9 +49,11 @@ export class AppPropertiesPage { private readonly modalCtrl: ModalController, private readonly navCtrl: NavController, private readonly patch: PatchDbService, + private readonly destroy$: DestroyService, ) {} ionViewDidEnter() { + if (!this.backButton) return this.backButton.onClick = () => { history.back() } @@ -57,33 +62,13 @@ export class AppPropertiesPage { async ngOnInit() { await this.getProperties() - this.subs = [ - this.route.queryParams.subscribe(queryParams => { + this.route.queryParams + .pipe(takeUntil(this.destroy$)) + .subscribe(queryParams => { if (queryParams['pointer'] === this.pointer) return - this.pointer = queryParams['pointer'] - this.node = getValueByPointer(this.properties, this.pointer || '') - }), - this.patch - .watch$( - 'package-data', - this.pkgId, - 'installed', - 'status', - 'main', - 'status', - ) - .subscribe(status => { - this.running = status === PackageMainStatus.Running - }), - ] - } - - ngAfterViewInit() { - this.content.scrollToPoint(undefined, 1) - } - - ngOnDestroy() { - this.subs.forEach(sub => sub.unsubscribe()) + this.pointer = queryParams['pointer'] || '' + this.node = getValueByPointer(this.properties, this.pointer) + }) } async refresh() { @@ -106,7 +91,7 @@ export class AppPropertiesPage { async goToNested(key: string): Promise { this.navCtrl.navigateForward(`/services/${this.pkgId}/properties`, { queryParams: { - pointer: `${this.pointer || ''}/${key}/value`, + pointer: `${this.pointer}/${key}/value`, }, }) } @@ -148,7 +133,7 @@ export class AppPropertiesPage { this.properties = await this.embassyApi.getPackageProperties({ id: this.pkgId, }) - this.node = getValueByPointer(this.properties, this.pointer || '') + this.node = getValueByPointer(this.properties, this.pointer) } catch (e: any) { this.errToast.present(e) } finally { diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts index 8648d7968..c6d85fcbe 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts @@ -11,7 +11,7 @@ import { PackageStatus, PrimaryStatus, } from 'src/app/services/pkg-status-rendering.service' -import { map, startWith, filter } from 'rxjs/operators' +import { filter, tap } from 'rxjs/operators' import { ActivatedRoute } from '@angular/router' import { getPkgId } from '@start9labs/shared' import { MarketplaceService } from 'src/app/services/marketplace.service' @@ -36,19 +36,16 @@ export class AppShowPage { private readonly pkgId = getPkgId(this.route) readonly pkg$ = this.patch.watch$('package-data', this.pkgId).pipe( - map(pkg => { + tap(pkg => { // if package disappears, navigate to list page if (!pkg) { this.navCtrl.navigateRoot('/services') } - - return { ...pkg } }), - startWith(this.patch.getData()['package-data'][this.pkgId]), filter( - (p: PackageDataEntry | undefined) => + (p?: PackageDataEntry) => // will be undefined when sideloading - p !== undefined && + !!p && !( p.installed?.status.main.status === PackageMainStatus.Starting && p.installed?.status.main.restarting diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-header/app-show-header.component.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-header/app-show-header.component.ts index 3dbf07e0e..fd234a9e9 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-header/app-show-header.component.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-header/app-show-header.component.ts @@ -9,5 +9,5 @@ import { PackageDataEntry } from 'src/app/services/patch-db/data-model' }) export class AppShowHeaderComponent { @Input() - pkg: PackageDataEntry + pkg!: PackageDataEntry } diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-health-checks/app-show-health-checks.component.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-health-checks/app-show-health-checks.component.ts index 4b3ce13b1..bbddbffac 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-health-checks/app-show-health-checks.component.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-health-checks/app-show-health-checks.component.ts @@ -13,7 +13,7 @@ import { }) export class AppShowHealthChecksComponent { @Input() - pkg: PackageDataEntry + pkg!: PackageDataEntry HealthResult = HealthResult diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-progress/app-show-progress.component.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-progress/app-show-progress.component.ts index 572271c1e..8ee7b750a 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-progress/app-show-progress.component.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-progress/app-show-progress.component.ts @@ -13,10 +13,10 @@ import { ProgressData } from 'src/app/types/progress-data' }) export class AppShowProgressComponent { @Input() - pkg: PackageDataEntry + pkg!: PackageDataEntry @Input() - progressData: ProgressData + progressData!: ProgressData get unpackingBuffer(): number { return this.progressData.validateProgress === 100 && diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.html b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.html index effa3e0a5..e387084a2 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.html +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/components/app-show-status/app-show-status.component.html @@ -3,7 +3,7 @@ { - return this.pkg.manifest.interfaces + return this.pkg.manifest.interfaces || {} } get pkgStatus(): Status | null { @@ -74,7 +74,9 @@ export class AppShowStatusComponent { } async presentModalConfig(): Promise { - return this.modalService.presentModalConfig({ pkgId: this.pkg.manifest.id }) + return this.modalService.presentModalConfig({ + pkgId: this.id, + }) } async tryStart(): Promise { @@ -87,7 +89,7 @@ export class AppShowStatusComponent { const alertMsg = this.pkg.manifest.alerts.start - if (!!alertMsg) { + if (alertMsg) { const proceed = await this.presentAlertStart(alertMsg) if (!proceed) return @@ -180,6 +182,10 @@ export class AppShowStatusComponent { await alert.present() } + private get id(): string { + return this.pkg.manifest.id + } + private async start(): Promise { const loader = await this.loadingCtrl.create({ message: `Starting...`, @@ -187,7 +193,7 @@ export class AppShowStatusComponent { await loader.present() try { - await this.embassyApi.startPackage({ id: this.pkg.manifest.id }) + await this.embassyApi.startPackage({ id: this.id }) } catch (e: any) { this.errToast.present(e) } finally { @@ -202,7 +208,7 @@ export class AppShowStatusComponent { await loader.present() try { - await this.embassyApi.stopPackage({ id: this.pkg.manifest.id }) + await this.embassyApi.stopPackage({ id: this.id }) } catch (e: any) { this.errToast.present(e) } finally { @@ -217,7 +223,7 @@ export class AppShowStatusComponent { await loader.present() try { - await this.embassyApi.restartPackage({ id: this.pkg.manifest.id }) + await this.embassyApi.restartPackage({ id: this.id }) } catch (e: any) { this.errToast.present(e) } finally { diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts index e32bb4ec3..007a99c19 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/pipes/to-buttons.pipe.ts @@ -202,12 +202,6 @@ export class ToButtonsPipe implements PipeTransform { packageMarketplace, currentMarketplace, pkgId, - buttons: [ - { - text: 'Cancel', - role: 'cancel', - }, - ], }, cssClass: 'medium-modal', }) diff --git a/frontend/projects/ui/src/app/pages/developer-routes/developer-list/developer-list.page.ts b/frontend/projects/ui/src/app/pages/developer-routes/developer-list/developer-list.page.ts index 5ced59694..02dfb6344 100644 --- a/frontend/projects/ui/src/app/pages/developer-routes/developer-list/developer-list.page.ts +++ b/frontend/projects/ui/src/app/pages/developer-routes/developer-list/developer-list.page.ts @@ -28,7 +28,7 @@ import { takeUntil } from 'rxjs/operators' providers: [DestroyService], }) export class DeveloperListPage { - devData: DevData + devData: DevData = {} constructor( private readonly modalCtrl: ModalController, diff --git a/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/developer-menu.module.ts b/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/developer-menu.module.ts index 998e1a52c..d33ecf47f 100644 --- a/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/developer-menu.module.ts +++ b/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/developer-menu.module.ts @@ -8,7 +8,7 @@ import { BackupReportPageModule } from 'src/app/modals/backup-report/backup-repo import { GenericFormPageModule } from 'src/app/modals/generic-form/generic-form.module' import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor' import { FormsModule } from '@angular/forms' -import { SharedPipesModule } from '../../../../../../shared/src/pipes/shared/shared.module' +import { SharedPipesModule } from '@start9labs/shared' const routes: Routes = [ { diff --git a/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/developer-menu.page.html b/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/developer-menu.page.html index ab93252e6..ee10e7fc6 100644 --- a/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/developer-menu.page.html +++ b/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/developer-menu.page.html @@ -3,7 +3,7 @@ - {{ name }} + {{ (projectData$ | async)?.name || '' }} View Manifest diff --git a/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/developer-menu.page.ts b/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/developer-menu.page.ts index 9b5194655..3331375ec 100644 --- a/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/developer-menu.page.ts +++ b/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/developer-menu.page.ts @@ -5,7 +5,7 @@ import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page' import { BasicInfo, getBasicInfoSpec } from './form-info' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' import { ApiService } from 'src/app/services/api/embassy-api.service' -import { ErrorToastService, DestroyService } from '@start9labs/shared' +import { ErrorToastService } from '@start9labs/shared' import { getProjectId } from 'src/app/util/get-project-id' import { DevProjectData } from 'src/app/services/patch-db/data-model' @@ -13,11 +13,10 @@ import { DevProjectData } from 'src/app/services/patch-db/data-model' selector: 'developer-menu', templateUrl: 'developer-menu.page.html', styleUrls: ['developer-menu.page.scss'], - providers: [DestroyService], }) export class DeveloperMenuPage { readonly projectId = getProjectId(this.route) - projectData$ = this.patch.watch$('ui', 'dev', this.projectId) + readonly projectData$ = this.patch.watch$('ui', 'dev', this.projectId) constructor( private readonly route: ActivatedRoute, @@ -26,11 +25,7 @@ export class DeveloperMenuPage { private readonly api: ApiService, private readonly errToast: ErrorToastService, private readonly patch: PatchDbService, - ) { } - - get name(): string { - return this.patch.getData().ui?.dev?.[this.projectId]?.name || '' - } + ) {} async openBasicInfoModal(data: DevProjectData) { const modal = await this.modalCtrl.create({ @@ -41,13 +36,7 @@ export class DeveloperMenuPage { buttons: [ { text: 'Save', - handler: (basicInfo: any) => { - basicInfo.description = { - short: basicInfo.short, - long: basicInfo.long, - } - delete basicInfo.short - delete basicInfo.long + handler: (basicInfo: BasicInfo) => { this.saveBasicInfo(basicInfo) }, isSubmit: true, diff --git a/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/form-info.ts b/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/form-info.ts index e0627d684..5520c67e5 100644 --- a/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/form-info.ts +++ b/frontend/projects/ui/src/app/pages/developer-routes/developer-menu/form-info.ts @@ -27,19 +27,19 @@ export function getBasicInfoSpec(devData: DevProjectData): ConfigSpec { placeholder: 'e.g. bitcoind', nullable: false, masked: false, - copyable: true, + copyable: false, pattern: '^([a-z][a-z0-9]*)(-[a-z0-9]+)*$', 'pattern-description': 'Must be kebab case', default: basicInfo?.id, }, title: { type: 'string', - name: 'Title', + name: 'Service Name', description: 'A human readable service title', placeholder: 'e.g. Bitcoin Core', nullable: false, masked: false, - copyable: true, + copyable: false, default: basicInfo ? basicInfo.title : devData.name, }, 'service-version-number': { @@ -50,19 +50,51 @@ export function getBasicInfoSpec(devData: DevProjectData): ConfigSpec { placeholder: 'e.g. 0.1.2.3', nullable: false, masked: false, - copyable: true, + copyable: false, pattern: '^([0-9]+).([0-9]+).([0-9]+).([0-9]+)$', 'pattern-description': 'Must be valid Emver version', default: basicInfo?.['service-version-number'], }, + description: { + type: 'object', + name: 'Marketplace Descriptions', + spec: { + short: { + type: 'string', + name: 'Short Description', + description: + 'This is the first description visible to the user in the marketplace', + nullable: false, + masked: false, + copyable: false, + textarea: true, + default: basicInfo?.description?.short, + pattern: '^.{1,320}$', + 'pattern-description': 'Must be shorter than 320 characters', + }, + long: { + type: 'string', + name: 'Long Description', + description: `This description will display with additional details in the service's individual marketplace page`, + nullable: false, + masked: false, + copyable: false, + textarea: true, + default: basicInfo?.description?.long, + pattern: '^.{1,5000}$', + 'pattern-description': 'Must be shorter than 5000 characters', + }, + }, + }, 'release-notes': { type: 'string', name: 'Release Notes', - description: 'A human readable service title', - placeholder: 'e.g. Bitcoin Core', + description: + 'Markdown supported release notes for this version of this service.', + placeholder: 'e.g. Markdown _release notes_ for **Bitcoin Core**', nullable: false, masked: false, - copyable: true, + copyable: false, textarea: true, default: basicInfo?.['release-notes'], }, @@ -102,7 +134,7 @@ export function getBasicInfoSpec(devData: DevProjectData): ConfigSpec { placeholder: 'e.g. www.github.com/example', nullable: false, masked: false, - copyable: true, + copyable: false, default: basicInfo?.['wrapper-repo'], }, 'upstream-repo': { @@ -112,7 +144,7 @@ export function getBasicInfoSpec(devData: DevProjectData): ConfigSpec { placeholder: 'e.g. www.github.com/example', nullable: true, masked: false, - copyable: true, + copyable: false, default: basicInfo?.['upstream-repo'], }, 'support-site': { @@ -122,7 +154,7 @@ export function getBasicInfoSpec(devData: DevProjectData): ConfigSpec { placeholder: 'e.g. www.start9labs.com', nullable: true, masked: false, - copyable: true, + copyable: false, default: basicInfo?.['support-site'], }, 'marketing-site': { @@ -132,33 +164,8 @@ export function getBasicInfoSpec(devData: DevProjectData): ConfigSpec { placeholder: 'e.g. www.start9labs.com', nullable: true, masked: false, - copyable: true, + copyable: false, default: basicInfo?.['marketing-site'], }, - short: { - type: 'string', - name: 'Short Description', - description: - 'This is the first description visible to the user in the marketplace', - nullable: false, - masked: false, - copyable: false, - textarea: true, - default: basicInfo?.description?.short, - pattern: '^.{1,320}$', - 'pattern-description': 'Must be shorter than 320 characters', - }, - long: { - type: 'string', - name: 'Long Description', - description: `This description will display with additional details in the service's individual marketplace page`, - nullable: false, - masked: false, - copyable: false, - textarea: true, - default: basicInfo?.description?.long, - pattern: '^.{1,5000}$', - 'pattern-description': 'Must be shorter than 5000 characters', - }, } } diff --git a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.html b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.html index 20ae4efae..b150d2d4f 100644 --- a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.html +++ b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-controls/marketplace-show-controls.component.html @@ -25,7 +25,7 @@ > Downgrade - + { let message: string = 'As a result of this update, the following services will no longer work properly and may crash:

      ' - const localPkgs = this.patch.getData()['package-data'] + const localPkgs = await getAllPackages(this.patch) const bullets = Object.keys(breakages).map(id => { const title = localPkgs[id].manifest.title return `
    • ${title}
    • ` diff --git a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-dependent/marketplace-show-dependent.component.ts b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-dependent/marketplace-show-dependent.component.ts index 4b74cacf8..76c648867 100644 --- a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-dependent/marketplace-show-dependent.component.ts +++ b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-show/marketplace-show-dependent/marketplace-show-dependent.component.ts @@ -16,7 +16,7 @@ import { DependentInfo } from 'src/app/types/dependent-info' }) export class MarketplaceShowDependentComponent { @Input() - pkg: MarketplacePkg + pkg!: MarketplacePkg readonly dependentInfo?: DependentInfo = this.document.defaultView?.history.state?.dependentInfo @@ -24,10 +24,10 @@ export class MarketplaceShowDependentComponent { constructor(@Inject(DOCUMENT) private readonly document: Document) {} get title(): string { - return this.pkg?.manifest.title || '' + return this.pkg.manifest.title } get version(): string { - return this.pkg?.manifest.version || '' + return this.pkg.manifest.version } } diff --git a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-status/marketplace-status.component.ts b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-status/marketplace-status.component.ts index f6c767984..05e36471b 100644 --- a/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-status/marketplace-status.component.ts +++ b/frontend/projects/ui/src/app/pages/marketplace-routes/marketplace-status/marketplace-status.component.ts @@ -10,10 +10,9 @@ import { styleUrls: ['marketplace-status.component.scss'], }) export class MarketplaceStatusComponent { - @Input() - version: string - @Input() - localPkg?: PackageDataEntry + @Input() version!: string + + @Input() localPkg?: PackageDataEntry PackageState = PackageState diff --git a/frontend/projects/ui/src/app/pages/notifications/notifications.page.html b/frontend/projects/ui/src/app/pages/notifications/notifications.page.html index a08fff355..e0d9e7e63 100644 --- a/frontend/projects/ui/src/app/pages/notifications/notifications.page.html +++ b/frontend/projects/ui/src/app/pages/notifications/notifications.page.html @@ -12,7 +12,7 @@ - + - + - +
      - + +

      - - {{ patch.getData()['package-data'][not['package-id']] ? - patch.getData()['package-data'][not['package-id']].manifest.title - : not['package-id'] }} - + + {{ packageData[pkgId]?.manifest!.title || pkgId }} - {{ not.title }} @@ -101,7 +100,7 @@ View Full Message

      -

      {{ not['created-at'] | date: 'short' }}

      +

      {{ not['created-at'] | date: 'medium' }}

      - - + + diff --git a/frontend/projects/ui/src/app/pages/notifications/notifications.page.ts b/frontend/projects/ui/src/app/pages/notifications/notifications.page.ts index bf098ec71..db8f85741 100644 --- a/frontend/projects/ui/src/app/pages/notifications/notifications.page.ts +++ b/frontend/projects/ui/src/app/pages/notifications/notifications.page.ts @@ -27,6 +27,7 @@ export class NotificationsPage { needInfinite = false fromToast = false readonly perPage = 40 + readonly packageData$ = this.patch.watch$('package-data') constructor( private readonly embassyApi: ApiService, @@ -35,7 +36,7 @@ export class NotificationsPage { private readonly modalCtrl: ModalController, private readonly errToast: ErrorToastService, private readonly route: ActivatedRoute, - public readonly patch: PatchDbService, + private readonly patch: PatchDbService, ) {} async ngOnInit() { diff --git a/frontend/projects/ui/src/app/pages/server-routes/kernel-logs/kernel-logs.page.html b/frontend/projects/ui/src/app/pages/server-routes/kernel-logs/kernel-logs.page.html index 066729556..b1f001eb4 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/kernel-logs/kernel-logs.page.html +++ b/frontend/projects/ui/src/app/pages/server-routes/kernel-logs/kernel-logs.page.html @@ -4,6 +4,11 @@ Kernel Logs + + + + + diff --git a/frontend/projects/ui/src/app/pages/server-routes/kernel-logs/kernel-logs.page.ts b/frontend/projects/ui/src/app/pages/server-routes/kernel-logs/kernel-logs.page.ts index 1dee55841..7a67320b8 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/kernel-logs/kernel-logs.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/kernel-logs/kernel-logs.page.ts @@ -1,5 +1,7 @@ import { Component } from '@angular/core' +import { ToastController } from '@ionic/angular' import { ApiService } from 'src/app/services/api/embassy-api.service' +import { copyToClipboard, strip } from 'src/app/util/web.util' @Component({ selector: 'kernel-logs', @@ -7,12 +9,10 @@ import { ApiService } from 'src/app/services/api/embassy-api.service' styleUrls: ['./kernel-logs.page.scss'], }) export class KernelLogsPage { - pkgId: string - loading = true - needInfinite = true - before: string - - constructor(private readonly embassyApi: ApiService) {} + constructor( + private readonly embassyApi: ApiService, + private readonly toastCtrl: ToastController, + ) {} fetchFetchLogs() { return async (params: { @@ -27,4 +27,22 @@ export class KernelLogsPage { }) } } + + async copy(): Promise { + const logs = document + .getElementById('template') + ?.cloneNode(true) as HTMLElement + const formatted = '```' + strip(logs.innerHTML) + '```' + const success = await copyToClipboard(formatted) + const message = success + ? 'Copied to clipboard!' + : 'Failed to copy to clipboard.' + + const toast = await this.toastCtrl.create({ + header: message, + position: 'bottom', + duration: 1000, + }) + await toast.present() + } } diff --git a/frontend/projects/ui/src/app/pages/server-routes/marketplaces/marketplaces.page.ts b/frontend/projects/ui/src/app/pages/server-routes/marketplaces/marketplaces.page.ts index 992ce934c..a1aa59c20 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/marketplaces/marketplaces.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/marketplaces/marketplaces.page.ts @@ -21,9 +21,11 @@ import { first, takeUntil, } from 'rxjs/operators' +import { getServerInfo } from '../../../util/get-server-info' +import { getMarketplace } from '../../../util/get-marketplace' type Marketplaces = { - id: string | undefined + id: string | null name: string url: string }[] @@ -35,7 +37,7 @@ type Marketplaces = { providers: [DestroyService], }) export class MarketplacesPage { - selectedId: string | undefined + selectedId: string | null = null marketplaces: Marketplaces = [] constructor( @@ -47,7 +49,7 @@ export class MarketplacesPage { @Inject(AbstractMarketplaceService) private readonly marketplaceService: MarketplaceService, private readonly config: ConfigService, - public readonly patch: PatchDbService, + private readonly patch: PatchDbService, private readonly destroy$: DestroyService, ) {} @@ -58,13 +60,13 @@ export class MarketplacesPage { .subscribe((mp: UIMarketplaceData | undefined) => { let marketplaces: Marketplaces = [ { - id: undefined, + id: null, name: this.config.marketplace.name, url: this.config.marketplace.url, }, ] if (mp) { - this.selectedId = mp['selected-id'] || undefined + this.selectedId = mp['selected-id'] const alts = Object.entries(mp['known-hosts']).map(([k, v]) => { return { id: k, @@ -107,34 +109,33 @@ export class MarketplacesPage { await modal.present() } - async presentAction(id: string = '') { + async presentAction(id: string | null) { // no need to view actions if is selected marketplace - if (id === this.patch.getData().ui.marketplace?.['selected-id']) return + const marketplace = await getMarketplace(this.patch) + + if (id === marketplace['selected-id']) return const buttons: ActionSheetButton[] = [ { - text: 'Forget', - icon: 'trash', - role: 'destructive', - handler: () => { - this.delete(id) - }, - }, - { - text: 'Connect to marketplace', + text: 'Connect', handler: () => { this.connect(id) }, }, ] - if (!id) { - buttons.shift() + if (id) { + buttons.unshift({ + text: 'Delete', + role: 'destructive', + handler: () => { + this.delete(id) + }, + }) } const action = await this.actionCtrl.create({ - header: id, - subHeader: 'Manage marketplaces', + header: this.marketplaces.find(mp => mp.id === id)?.name, mode: 'ios', buttons, }) @@ -142,10 +143,8 @@ export class MarketplacesPage { await action.present() } - private async connect(id: string): Promise { - const marketplace: UIMarketplaceData = JSON.parse( - JSON.stringify(this.patch.getData().ui.marketplace), - ) + private async connect(id: string | null): Promise { + const marketplace = await getMarketplace(this.patch) const url = id ? marketplace['known-hosts'][id].url @@ -157,10 +156,8 @@ export class MarketplacesPage { await loader.present() try { - await this.marketplaceService.getMarketplaceData( - { 'server-id': this.patch.getData()['server-info'].id }, - url, - ) + const { id } = await getServerInfo(this.patch) + await this.marketplaceService.getMarketplaceData({ 'server-id': id }, url) } catch (e: any) { this.errToast.present(e) loader.dismiss() @@ -169,9 +166,13 @@ export class MarketplacesPage { loader.message = 'Changing Marketplace...' + const value: UIMarketplaceData = { + ...marketplace, + 'selected-id': id, + } + try { - marketplace['selected-id'] = id - await this.api.setDbValue({ pointer: `/marketplace`, value: marketplace }) + await this.api.setDbValue({ pointer: `/marketplace`, value }) } catch (e: any) { this.errToast.present(e) loader.dismiss() @@ -189,10 +190,8 @@ export class MarketplacesPage { } private async delete(id: string): Promise { - if (!id) return - const marketplace: UIMarketplaceData = JSON.parse( - JSON.stringify(this.patch.getData().ui.marketplace), - ) + const data = await getMarketplace(this.patch) + const marketplace: UIMarketplaceData = JSON.parse(JSON.stringify(data)) const loader = await this.loadingCtrl.create({ message: 'Deleting...', @@ -210,13 +209,12 @@ export class MarketplacesPage { } private async save(url: string): Promise { - const marketplace = this.patch.getData().ui.marketplace - ? (JSON.parse( - JSON.stringify(this.patch.getData().ui.marketplace), - ) as UIMarketplaceData) + const data = await getMarketplace(this.patch) + const marketplace: UIMarketplaceData = data + ? JSON.parse(JSON.stringify(data)) : { - 'selected-id': undefined, - 'known-hosts': {} as Record, + 'selected-id': null, + 'known-hosts': {}, } // no-op on duplicates @@ -231,8 +229,9 @@ export class MarketplacesPage { try { const id = v4() + const { id: serverId } = await getServerInfo(this.patch) const { name } = await this.marketplaceService.getMarketplaceData( - { 'server-id': this.patch.getData()['server-info'].id }, + { 'server-id': serverId }, url, ) marketplace['known-hosts'][id] = { name, url } @@ -254,13 +253,12 @@ export class MarketplacesPage { } private async saveAndConnect(url: string): Promise { - const marketplace = this.patch.getData().ui.marketplace - ? (JSON.parse( - JSON.stringify(this.patch.getData().ui.marketplace), - ) as UIMarketplaceData) + const data = await getMarketplace(this.patch) + const marketplace: UIMarketplaceData = data + ? JSON.parse(JSON.stringify(data)) : { - 'selected-id': undefined, - 'known-hosts': {} as Record, + 'selected-id': null, + 'known-hosts': {}, } // no-op on duplicates @@ -274,8 +272,9 @@ export class MarketplacesPage { try { const id = v4() + const { id: serverId } = await getServerInfo(this.patch) const { name } = await this.marketplaceService.getMarketplaceData( - { 'server-id': this.patch.getData()['server-info'].id }, + { 'server-id': serverId }, url, ) marketplace['known-hosts'][id] = { name, url } diff --git a/frontend/projects/ui/src/app/pages/server-routes/preferences/preferences.page.html b/frontend/projects/ui/src/app/pages/server-routes/preferences/preferences.page.html index 742513c94..911e8b722 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/preferences/preferences.page.html +++ b/frontend/projects/ui/src/app/pages/server-routes/preferences/preferences.page.html @@ -11,7 +11,10 @@ General - + Device Name {{ ui.name || 'Embassy-' + server.id }} diff --git a/frontend/projects/ui/src/app/pages/server-routes/preferences/preferences.page.ts b/frontend/projects/ui/src/app/pages/server-routes/preferences/preferences.page.ts index d418876e3..9a5830067 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/preferences/preferences.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/preferences/preferences.page.ts @@ -1,4 +1,4 @@ -import { Component, ViewChild } from '@angular/core' +import { Component } from '@angular/core' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' import { LoadingController, @@ -34,7 +34,10 @@ export class PreferencesPage { readonly serverConfig: ServerConfigService, ) {} - async presentModalName(placeholder: string): Promise { + async presentModalName( + placeholder: string, + initialValue: string, + ): Promise { const options: GenericInputOptions = { title: 'Edit Device Name', message: 'This is for your reference only.', @@ -42,7 +45,7 @@ export class PreferencesPage { useMask: false, placeholder, nullable: true, - initialValue: this.patch.getData().ui.name, + initialValue, buttonText: 'Save', submitFn: (value: string) => this.setDbValue('name', value || placeholder), diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-backup/backing-up/backing-up.component.ts b/frontend/projects/ui/src/app/pages/server-routes/server-backup/backing-up/backing-up.component.ts index fb50d09e4..c6a256f81 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-backup/backing-up/backing-up.component.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/server-backup/backing-up/backing-up.component.ts @@ -7,7 +7,6 @@ import { import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' import { take } from 'rxjs/operators' import { PackageMainStatus } from 'src/app/services/patch-db/data-model' -import { EOSService } from 'src/app/services/eos.service' import { Observable } from 'rxjs' @Component({ @@ -25,10 +24,7 @@ export class BackingUpComponent { PackageMainStatus = PackageMainStatus - constructor( - public readonly eosService: EOSService, - public readonly patch: PatchDbService, - ) {} + constructor(private readonly patch: PatchDbService) {} } @Pipe({ diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts b/frontend/projects/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts index 1bfd479b7..33bd51bab 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/server-backup/server-backup.page.ts @@ -20,6 +20,7 @@ import { import { BackupSelectPage } from 'src/app/modals/backup-select/backup-select.page' import { EOSService } from 'src/app/services/eos.service' import { DestroyService } from '@start9labs/shared' +import { getServerInfo } from 'src/app/util/get-server-info' @Component({ selector: 'server-backup', @@ -28,7 +29,6 @@ import { DestroyService } from '@start9labs/shared' providers: [DestroyService], }) export class ServerBackupPage { - target: MappedBackupTarget serviceIds: string[] = [] readonly backingUp$ = this.eosService.backingUp$ @@ -56,8 +56,6 @@ export class ServerBackupPage { async presentModalSelect( target: MappedBackupTarget, ) { - this.target = target - const modal = await this.modalCtrl.create({ presentingElement: await this.modalCtrl.getTop(), component: BackupSelectPage, @@ -66,14 +64,16 @@ export class ServerBackupPage { modal.onWillDismiss().then(res => { if (res.data) { this.serviceIds = res.data - this.presentModalPassword() + this.presentModalPassword(target) } }) await modal.present() } - async presentModalPassword(): Promise { + private async presentModalPassword( + target: MappedBackupTarget, + ): Promise { const options: GenericInputOptions = { title: 'Master Password Needed', message: 'Enter your master password to encrypt this backup.', @@ -83,25 +83,29 @@ export class ServerBackupPage { buttonText: 'Create Backup', submitFn: async (password: string) => { // confirm password matches current master password - const passwordHash = - this.patch.getData()['server-info']['password-hash'] + const { 'password-hash': passwordHash } = await getServerInfo( + this.patch, + ) argon2.verify(passwordHash, password) // first time backup - if (!this.target.hasValidBackup) { - await this.createBackup(password) + if (!target.hasValidBackup) { + await this.createBackup(target, password) // existing backup } else { try { const passwordHash = - this.target.entry['embassy-os']?.['password-hash'] || '' + target.entry['embassy-os']?.['password-hash'] || '' argon2.verify(passwordHash, password) } catch { - setTimeout(() => this.presentModalOldPassword(password), 500) + setTimeout( + () => this.presentModalOldPassword(target, password), + 500, + ) return } - await this.createBackup(password) + await this.createBackup(target, password) } }, } @@ -115,7 +119,10 @@ export class ServerBackupPage { await m.present() } - private async presentModalOldPassword(password: string): Promise { + private async presentModalOldPassword( + target: MappedBackupTarget, + password: string, + ): Promise { const options: GenericInputOptions = { title: 'Original Password Needed', message: @@ -125,11 +132,10 @@ export class ServerBackupPage { useMask: true, buttonText: 'Create Backup', submitFn: async (oldPassword: string) => { - const passwordHash = - this.target.entry['embassy-os']?.['password-hash'] || '' + const passwordHash = target.entry['embassy-os']?.['password-hash'] || '' argon2.verify(passwordHash, oldPassword) - await this.createBackup(password, oldPassword) + await this.createBackup(target, password, oldPassword) }, } @@ -143,6 +149,7 @@ export class ServerBackupPage { } private async createBackup( + target: MappedBackupTarget, password: string, oldPassword?: string, ): Promise { @@ -153,7 +160,7 @@ export class ServerBackupPage { try { await this.embassyApi.createBackup({ - 'target-id': this.target.id, + 'target-id': target.id, 'package-ids': this.serviceIds, 'old-password': oldPassword || null, password, diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-logs/server-logs.page.html b/frontend/projects/ui/src/app/pages/server-routes/server-logs/server-logs.page.html index e91cc3971..43a95ef78 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-logs/server-logs.page.html +++ b/frontend/projects/ui/src/app/pages/server-routes/server-logs/server-logs.page.html @@ -4,9 +4,11 @@ OS Logs - - - + + + + + diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-logs/server-logs.page.ts b/frontend/projects/ui/src/app/pages/server-routes/server-logs/server-logs.page.ts index eb7b55593..8f2c0cd69 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-logs/server-logs.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/server-logs/server-logs.page.ts @@ -9,11 +9,6 @@ import { copyToClipboard, strip } from 'src/app/util/web.util' styleUrls: ['./server-logs.page.scss'], }) export class ServerLogsPage { - pkgId: string - loading = true - needInfinite = true - before: string - constructor( private readonly embassyApi: ApiService, private readonly toastCtrl: ToastController, diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.html b/frontend/projects/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.html index 96251175b..bda576557 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.html +++ b/frontend/projects/ui/src/app/pages/server-routes/server-metrics/server-metrics.page.html @@ -4,25 +4,32 @@ Monitor - + - +
      - + {{ metricGroup.key }} - + {{ metric.key }} - {{ metric.value.value }} {{ metric.value.unit }} + {{ metric.value.value }} {{ metric.value.unit }}
      - -
      \ No newline at end of file + diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.html b/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.html index 2468a1507..08b072dcb 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.html +++ b/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.html @@ -37,7 +37,7 @@ Last Backup: {{ server['last-backup'] ? - (server['last-backup'] | date: 'short') : 'never' }} + (server['last-backup'] | date: 'medium') : 'never' }} - + Update Available diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts b/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts index a89ee89cf..066a91950 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts @@ -15,6 +15,7 @@ import { EOSService } from 'src/app/services/eos.service' import { LocalStorageService } from 'src/app/services/local-storage.service' import { RecoveredPackageDataEntry } from 'src/app/services/patch-db/data-model' import { OSUpdatePage } from 'src/app/modals/os-update/os-update.page' +import { getAllPackages } from '../../../util/get-package-data' @Component({ selector: 'server-show', @@ -22,12 +23,14 @@ import { OSUpdatePage } from 'src/app/modals/os-update/os-update.page' styleUrls: ['server-show.page.scss'], }) export class ServerShowPage { - hasRecoveredPackage: boolean + hasRecoveredPackage = false clicks = 0 readonly server$ = this.patch.watch$('server-info') readonly ui$ = this.patch.watch$('ui') readonly connected$ = this.patch.connected$ + readonly showUpdate$ = this.eosService.showUpdate$ + readonly showDiskRepair$ = this.localStorageService.showDiskRepair$ constructor( private readonly alertCtrl: AlertController, @@ -38,8 +41,8 @@ export class ServerShowPage { private readonly navCtrl: NavController, private readonly route: ActivatedRoute, private readonly patch: PatchDbService, - public readonly eosService: EOSService, - public readonly localStorageService: LocalStorageService, + private readonly eosService: EOSService, + private readonly localStorageService: LocalStorageService, ) {} ngOnInit() { @@ -63,7 +66,7 @@ export class ServerShowPage { } else { const modal = await this.modalCtrl.create({ componentProps: { - releaseNotes: this.eosService.eos['release-notes'], + releaseNotes: this.eosService.eos?.['release-notes'], }, component: OSUpdatePage, }) @@ -117,7 +120,8 @@ export class ServerShowPage { } async presentAlertSystemRebuild() { - const minutes = Object.keys(this.patch.getData()['package-data']).length * 2 + const localPkgs = await getAllPackages(this.patch) + const minutes = Object.keys(localPkgs).length * 2 const alert = await this.alertCtrl.create({ header: 'Warning', message: `This action will tear down all service containers and rebuild them from scratch. No data will be deleted. This action is useful if your system gets into a bad state, and it should only be performed if you are experiencing general performance or reliability issues. It may take up to ${minutes} minutes to complete. During this time, you will lose all connectivity to your Embassy.`, diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-specs/server-specs.page.html b/frontend/projects/ui/src/app/pages/server-routes/server-specs/server-specs.page.html index 3129d7864..9b59b23c5 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-specs/server-specs.page.html +++ b/frontend/projects/ui/src/app/pages/server-routes/server-specs/server-specs.page.html @@ -21,7 +21,7 @@

      Git Hash

      -

      {{ config.gitHash }}

      +

      {{ gitHash }}

      diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-specs/server-specs.page.ts b/frontend/projects/ui/src/app/pages/server-routes/server-specs/server-specs.page.ts index 9bfc5496c..0bdf27bba 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-specs/server-specs.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/server-specs/server-specs.page.ts @@ -1,5 +1,5 @@ -import { Component, ViewChild } from '@angular/core' -import { IonContent, ToastController } from '@ionic/angular' +import { Component } from '@angular/core' +import { ToastController } from '@ionic/angular' import { copyToClipboard } from 'src/app/util/web.util' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' import { ConfigService } from 'src/app/services/config.service' @@ -10,18 +10,16 @@ import { ConfigService } from 'src/app/services/config.service' styleUrls: ['./server-specs.page.scss'], }) export class ServerSpecsPage { - @ViewChild(IonContent) content: IonContent - readonly server$ = this.patch.watch$('server-info') constructor( private readonly toastCtrl: ToastController, private readonly patch: PatchDbService, - public readonly config: ConfigService, + private readonly config: ConfigService, ) {} - ngAfterViewInit() { - this.content.scrollToPoint(undefined, 1) + get gitHash(): string { + return this.config.gitHash } async copy(address: string) { diff --git a/frontend/projects/ui/src/app/pages/server-routes/sessions/sessions.page.html b/frontend/projects/ui/src/app/pages/server-routes/sessions/sessions.page.html index 075fe167b..2cddf45e6 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/sessions/sessions.page.html +++ b/frontend/projects/ui/src/app/pages/server-routes/sessions/sessions.page.html @@ -9,7 +9,7 @@ - +
      {{ entry }} @@ -41,60 +41,64 @@ - - Current Session - - - -

      {{ getPlatformName(currentSession.metadata.platforms) }}

      -

      - Last Active: {{ currentSession['last-active'] | date : 'medium' }} -

      -

      {{ currentSession['user-agent'] }}

      -
      -
      - - - Other Sessions - - Terminate all - - -
      + + + Current Session -

      {{ getPlatformName(session.metadata.platforms) }}

      -

      Last Active: {{ session['last-active'] | date : 'medium' }}

      -

      {{ session['user-agent'] }}

      +

      {{ getPlatformName(currentSession.metadata.platforms) }}

      +

      + Last Active: {{ currentSession['last-active'] | date : 'medium' }} +

      +

      {{ currentSession['user-agent'] }}

      +
      + + + Other Sessions - + Terminate all + +
      + + + +

      {{ getPlatformName(session.metadata.platforms) }}

      +

      Last Active: {{ session['last-active'] | date : 'medium' }}

      +

      {{ session['user-agent'] }}

      +
      + + Logout + + +
      +
      + + +

      You are not logged in anywhere else

      +
      -
      - - -

      You are not logged in anywhere else

      -
      -
      -
      + + diff --git a/frontend/projects/ui/src/app/pages/server-routes/sessions/sessions.page.ts b/frontend/projects/ui/src/app/pages/server-routes/sessions/sessions.page.ts index f14b488a7..b06deab5a 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/sessions/sessions.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/sessions/sessions.page.ts @@ -11,7 +11,7 @@ import { PlatformType, Session } from 'src/app/services/api/api.types' }) export class SessionsPage { loading = true - currentSession: Session + currentSession?: Session otherSessions: SessionWithId[] = [] constructor( @@ -67,27 +67,6 @@ export class SessionsPage { await alert.present() } - async presentAlertKill(id: string) { - const alert = await this.alertCtrl.create({ - header: 'Confirm', - message: 'Terminate other web session?', - buttons: [ - { - text: 'Cancel', - role: 'cancel', - }, - { - text: 'Terminate', - handler: () => { - this.kill([id]) - }, - cssClass: 'enter-click', - }, - ], - }) - await alert.present() - } - async kill(ids: string[]): Promise { const loader = await this.loadingCtrl.create({ message: `Terminating session${ids.length > 1 ? 's' : ''}...`, diff --git a/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.html b/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.html index f3dc66464..2baffedd0 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.html +++ b/frontend/projects/ui/src/app/pages/server-routes/sideload/sideload.page.html @@ -21,14 +21,13 @@ color="dark" style="font-size: 42px" > -

      Manually upload a service package

      +

      Upload .s9pk package file

      Tip: switch to LAN for faster uploads.

      -
      - +

      {{ ssh.hostname }}

      -

      {{ ssh['created-at'] | date: 'short' }}

      +

      {{ ssh['created-at'] | date: 'medium' }}

      {{ ssh.alg }} {{ ssh.fingerprint }}

      , ): boolean { - if (state !== PackageState.Installed) { - return false - } - return ( + state === PackageState.Installed && status === PackageMainStatus.Running && - ((hasTorUi(interfaces) && this.isTor()) || - (hasLanUi(interfaces) && !this.isTor())) + hasUi(interfaces) ) } launchableURL(pkg: PackageDataEntry): string { - return this.isTor() - ? `http://${torUiAddress(pkg)}` - : `https://${lanUiAddress(pkg)}` + if (this.isLan() && hasLanUi(pkg.manifest.interfaces)) { + return `https://${lanUiAddress(pkg)}` + } else { + return `http://${torUiAddress(pkg)}` + } } } diff --git a/frontend/projects/ui/src/app/services/eos.service.ts b/frontend/projects/ui/src/app/services/eos.service.ts index a1907291d..3d19e3452 100644 --- a/frontend/projects/ui/src/app/services/eos.service.ts +++ b/frontend/projects/ui/src/app/services/eos.service.ts @@ -1,22 +1,22 @@ import { Injectable } from '@angular/core' +import { Emver } from '@start9labs/shared' import { BehaviorSubject, combineLatest } from 'rxjs' +import { distinctUntilChanged, map } from 'rxjs/operators' + import { MarketplaceEOS } from 'src/app/services/api/api.types' import { ApiService } from 'src/app/services/api/embassy-api.service' -import { Emver } from '@start9labs/shared' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' -import { distinctUntilChanged, map } from 'rxjs/operators' +import { getServerInfo } from 'src/app/util/get-server-info' @Injectable({ providedIn: 'root', }) export class EOSService { - eos: MarketplaceEOS + eos?: MarketplaceEOS updateAvailable$ = new BehaviorSubject(false) readonly updating$ = this.patch.watch$('server-info', 'status-info').pipe( - map(status => { - return !!status['update-progress'] || status.updated - }), + map(status => !!status['update-progress'] || status.updated), distinctUntilChanged(), ) @@ -52,10 +52,9 @@ export class EOSService { ) {} async getEOS(): Promise { - const server = this.patch.getData()['server-info'] - const version = server.version + const { id, version } = await getServerInfo(this.patch) this.eos = await this.api.getEos({ - 'server-id': server.id, + 'server-id': id, 'eos-version': version, }) const updateAvailable = this.emver.compare(this.eos.version, version) === 1 diff --git a/frontend/projects/ui/src/app/services/modal.service.ts b/frontend/projects/ui/src/app/services/modal.service.ts index 147492709..c34fce9a2 100644 --- a/frontend/projects/ui/src/app/services/modal.service.ts +++ b/frontend/projects/ui/src/app/services/modal.service.ts @@ -1,5 +1,4 @@ import { Injectable } from '@angular/core' -import { ActivatedRoute } from '@angular/router' import { ModalController } from '@ionic/angular' import { DependentInfo } from 'src/app/types/dependent-info' import { AppConfigPage } from 'src/app/modals/app-config/app-config.page' @@ -8,10 +7,7 @@ import { AppConfigPage } from 'src/app/modals/app-config/app-config.page' providedIn: 'root', }) export class ModalService { - constructor( - private readonly route: ActivatedRoute, - private readonly modalCtrl: ModalController, - ) {} + constructor(private readonly modalCtrl: ModalController) {} async presentModalConfig(componentProps: ComponentProps): Promise { const modal = await this.modalCtrl.create({ diff --git a/frontend/projects/ui/src/app/services/patch-db/data-model.ts b/frontend/projects/ui/src/app/services/patch-db/data-model.ts index c3a9013bb..c289736d0 100644 --- a/frontend/projects/ui/src/app/services/patch-db/data-model.ts +++ b/frontend/projects/ui/src/app/services/patch-db/data-model.ts @@ -2,7 +2,6 @@ import { ConfigSpec } from 'src/app/pkg-config/config-types' import { Url } from '@start9labs/shared' import { MarketplaceManifest } from '@start9labs/marketplace' import { BasicInfo } from 'src/app/pages/developer-routes/developer-menu/form-info' -import { string } from 'ts-matches' export interface DataModel { 'server-info': ServerInfo diff --git a/frontend/projects/ui/src/app/services/patch-db/patch-db.service.ts b/frontend/projects/ui/src/app/services/patch-db/patch-db.service.ts index d74477eda..429ce5825 100644 --- a/frontend/projects/ui/src/app/services/patch-db/patch-db.service.ts +++ b/frontend/projects/ui/src/app/services/patch-db/patch-db.service.ts @@ -37,9 +37,9 @@ export enum PatchConnection { }) export class PatchDbService { private readonly WS_SUCCESS = 'wsSuccess' - private patchConnection$ = new ReplaySubject(1) - private wsSuccess$ = new BehaviorSubject(false) - private polling$ = new BehaviorSubject(false) + private readonly patchConnection$ = new ReplaySubject(1) + private readonly wsSuccess$ = new BehaviorSubject(false) + private readonly polling$ = new BehaviorSubject(false) private subs: Subscription[] = [] readonly connected$ = this.watchPatchConnection$().pipe( @@ -48,12 +48,6 @@ export class PatchDbService { shareReplay(), ) - errors = 0 - - getData() { - return this.patchDb.store.cache.data - } - constructor( // [wsSources, pollSources] @Inject(PATCH_SOURCE) private readonly sources: Source[], diff --git a/frontend/projects/ui/src/app/util/get-marketplace.ts b/frontend/projects/ui/src/app/util/get-marketplace.ts new file mode 100644 index 000000000..a79fe13d9 --- /dev/null +++ b/frontend/projects/ui/src/app/util/get-marketplace.ts @@ -0,0 +1,9 @@ +import { first } from 'rxjs/operators' +import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { UIMarketplaceData } from 'src/app/services/patch-db/data-model' + +export function getMarketplace( + patch: PatchDbService, +): Promise { + return patch.watch$('ui', 'marketplace').pipe(first()).toPromise() +} diff --git a/frontend/projects/ui/src/app/util/get-package-data.ts b/frontend/projects/ui/src/app/util/get-package-data.ts new file mode 100644 index 000000000..62bdd49b5 --- /dev/null +++ b/frontend/projects/ui/src/app/util/get-package-data.ts @@ -0,0 +1,16 @@ +import { first } from 'rxjs/operators' +import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { PackageDataEntry } from 'src/app/services/patch-db/data-model' + +export function getPackage( + patch: PatchDbService, + id: string, +): Promise { + return patch.watch$('package-data', id).pipe(first()).toPromise() +} + +export function getAllPackages( + patch: PatchDbService, +): Promise> { + return patch.watch$('package-data').pipe(first()).toPromise() +} diff --git a/frontend/projects/ui/src/app/util/get-server-info.ts b/frontend/projects/ui/src/app/util/get-server-info.ts new file mode 100644 index 000000000..43b00111f --- /dev/null +++ b/frontend/projects/ui/src/app/util/get-server-info.ts @@ -0,0 +1,7 @@ +import { first } from 'rxjs/operators' +import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' +import { ServerInfo } from 'src/app/services/patch-db/data-model' + +export function getServerInfo(patch: PatchDbService): Promise { + return patch.watch$('server-info').pipe(first()).toPromise() +} diff --git a/frontend/projects/ui/src/app/util/web.util.ts b/frontend/projects/ui/src/app/util/web.util.ts index 161017a4d..40d1f1c34 100644 --- a/frontend/projects/ui/src/app/util/web.util.ts +++ b/frontend/projects/ui/src/app/util/web.util.ts @@ -2,24 +2,20 @@ export async function copyToClipboard(str: string): Promise { if (window.isSecureContext) { return navigator.clipboard .writeText(str) - .then(() => { - return true - }) - .catch(err => { - return false - }) - } else { - const el = document.createElement('textarea') - el.value = str - el.setAttribute('readonly', '') - el.style.position = 'absolute' - el.style.left = '-9999px' - document.body.appendChild(el) - el.select() - const copy = document.execCommand('copy') - document.body.removeChild(el) - return copy + .then(() => true) + .catch(() => false) } + + const el = document.createElement('textarea') + el.value = str + el.setAttribute('readonly', '') + el.style.position = 'absolute' + el.style.left = '-9999px' + document.body.appendChild(el) + el.select() + const copy = document.execCommand('copy') + document.body.removeChild(el) + return copy } export function strip(html: string) { diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index e3b08335e..023b96b12 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -9,16 +9,7 @@ "noPropertyAccessFromIndexSignature": true, "noFallthroughCasesInSwitch": true, "skipLibCheck": true, - - "alwaysStrict": true, - "strictNullChecks": true, - "strictBindCallApply": true, - "strictFunctionTypes": true, - // "strictPropertyInitialization": true, - "noImplicitAny": true, - "noImplicitThis": true, - "useUnknownInCatchVariables": true, - + "strict": true, "sourceMap": true, "declaration": false, "downlevelIteration": true,