feat: enable strictNullChecks

feat: enable `noImplicitAny`

chore: remove sync data access

fix loading package data for affected dependencies

chore: properly get alt marketplace data

update patchdb client to allow for emit on undefined values
This commit is contained in:
waterplea
2022-05-26 18:20:31 +03:00
committed by Lucy C
parent 948fb795f2
commit 0390954a85
99 changed files with 674 additions and 535 deletions

View File

@@ -18,7 +18,7 @@ import { wizardModal } from 'src/app/components/install-wizard/install-wizard.co
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
import { Subscription } from 'rxjs'
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
import { isEmptyObject, ErrorToastService } from '@start9labs/shared'
import { isEmptyObject, ErrorToastService, getPkgId } from '@start9labs/shared'
import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page'
@Component({
@@ -28,7 +28,7 @@ import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.
})
export class AppActionsPage {
@ViewChild(IonContent) content: IonContent
pkgId: string
readonly pkgId = getPkgId(this.route)
pkg: PackageDataEntry
subs: Subscription[]
@@ -45,7 +45,6 @@ export class AppActionsPage {
) {}
ngOnInit() {
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
this.subs = [
this.patch.watch$('package-data', this.pkgId).subscribe(pkg => {
this.pkg = pkg
@@ -62,13 +61,14 @@ export class AppActionsPage {
}
async handleAction(action: { key: string; value: Action }) {
const status = this.pkg.installed.status
const status = this.pkg.installed?.status
if (
status &&
(action.value['allowed-statuses'] as PackageMainStatus[]).includes(
status.main.status,
)
) {
if (!isEmptyObject(action.value['input-spec'])) {
if (!isEmptyObject(action.value['input-spec'] || {})) {
const modal = await this.modalCtrl.create({
component: GenericFormPage,
componentProps: {
@@ -112,7 +112,7 @@ export class AppActionsPage {
const statuses = [...action.value['allowed-statuses']]
const last = statuses.pop()
let statusesStr = statuses.join(', ')
let error = null
let error = ''
if (statuses.length) {
if (statuses.length > 1) {
// oxford comma
@@ -144,7 +144,7 @@ export class AppActionsPage {
id,
title,
version,
uninstallAlert: alerts.uninstall,
uninstallAlert: alerts.uninstall || undefined,
}),
)
@@ -177,6 +177,7 @@ export class AppActionsPage {
})
setTimeout(() => successModal.present(), 400)
return true
} catch (e: any) {
this.errToast.present(e)
return false

View File

@@ -1,6 +1,7 @@
import { Component, Input, ViewChild } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { IonContent, ToastController } from '@ionic/angular'
import { getPkgId } from '@start9labs/shared'
import { getUiInterfaceKey } from 'src/app/services/config.service'
import {
InstalledPackageDataEntry,
@@ -23,7 +24,7 @@ export class AppInterfacesPage {
@ViewChild(IonContent) content: IonContent
ui: LocalInterface | null
other: LocalInterface[] = []
pkgId: string
readonly pkgId = getPkgId(this.route)
constructor(
private readonly route: ActivatedRoute,
@@ -31,11 +32,12 @@ export class AppInterfacesPage {
) {}
ngOnInit() {
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
const pkg = this.patch.getData()['package-data'][this.pkgId]
const interfaces = pkg.manifest.interfaces
const uiKey = getUiInterfaceKey(interfaces)
if (!pkg?.installed) return
const addressesMap = pkg.installed['interface-addresses']
if (uiKey) {
@@ -45,10 +47,10 @@ export class AppInterfacesPage {
addresses: {
'lan-address': uiAddresses['lan-address']
? 'https://' + uiAddresses['lan-address']
: null,
: '',
'tor-address': uiAddresses['tor-address']
? 'http://' + uiAddresses['tor-address']
: null,
: '',
},
}
}
@@ -62,10 +64,10 @@ export class AppInterfacesPage {
addresses: {
'lan-address': addresses['lan-address']
? 'https://' + addresses['lan-address']
: null,
: '',
'tor-address': addresses['tor-address']
? 'http://' + addresses['tor-address']
: null,
: '',
},
}
})

View File

@@ -21,7 +21,9 @@ export class AppListPkgComponent {
constructor(private readonly launcherService: UiLauncherService) {}
get status(): PackageMainStatus {
return this.pkg.entry.installed?.status.main.status
return (
this.pkg.entry.installed?.status.main.status || PackageMainStatus.Stopped
)
}
get manifest(): Manifest {

View File

@@ -1,12 +1,17 @@
<!-- header -->
<ion-item-divider>
{{ reordering ? "Reorder" : "Installed Services" }}
<ion-button *ngIf="pkgs.length > 1" slot="end" fill="clear" (click)="toggle()">
{{ reordering ? 'Reorder' : 'Installed Services' }}
<ion-button
*ngIf="pkgs.length > 1"
slot="end"
fill="clear"
(click)="toggle()"
>
<ion-icon
slot="start"
[name]="reordering ? 'checkmark' : 'swap-vertical'"
></ion-icon>
{{ reordering ? "Done" : "Reorder" }}
{{ reordering ? 'Done' : 'Reorder' }}
</ion-button>
</ion-item-divider>
@@ -14,11 +19,15 @@
<ion-list *ngIf="reordering; else grid">
<ion-reorder-group disabled="false" (ionItemReorder)="reorder($any($event))">
<ion-reorder *ngFor="let item of pkgs">
<ion-item color="light" *ngIf="item | packageInfo | async as pkg" class="item">
<ion-item
color="light"
*ngIf="item | packageInfo | async as pkg"
class="item"
>
<app-list-icon
slot="start"
[pkg]="pkg"
[connectionFailure]="connectionFailure$ | async"
[connectionFailure]="!!(connectionFailure$ | async)"
></app-list-icon>
<ion-thumbnail slot="start">
<img alt="" [src]="pkg.entry['static-files'].icon" />
@@ -27,7 +36,7 @@
<h2>{{ pkg.entry.manifest.title }}</h2>
<p>{{ pkg.entry.manifest.version | displayEmver }}</p>
<status
[disconnected]="connectionFailure$ | async"
[disconnected]="!!(connectionFailure$ | async)"
[rendering]="pkg.primaryRendering"
[installProgress]="pkg.installProgress?.totalProgress"
weight="bold"
@@ -47,8 +56,9 @@
<ion-row>
<ion-col *ngFor="let pkg of pkgs" sizeXs="12" sizeXl="6">
<app-list-pkg
[pkg]="pkg | packageInfo | async"
[connectionFailure]="connectionFailure$ | async"
*ngIf="pkg | packageInfo | async as info"
[pkg]="info"
[connectionFailure]="!!(connectionFailure$ | async)"
></app-list-pkg>
</ion-col>
</ion-row>

View File

@@ -1,5 +1,6 @@
import { Component } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { getPkgId } from '@start9labs/shared'
import { ApiService } from 'src/app/services/api/embassy-api.service'
@Component({
@@ -8,22 +9,22 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
styleUrls: ['./app-logs.page.scss'],
})
export class AppLogsPage {
pkgId: string
readonly pkgId = getPkgId(this.route)
loading = true
needInfinite = true
before: string
constructor (
constructor(
private readonly route: ActivatedRoute,
private readonly embassyApi: ApiService,
) { }
) {}
ngOnInit () {
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
}
fetchFetchLogs () {
return async (params: { before_flag?: boolean, limit?: number, cursor?: string }) => {
fetchFetchLogs() {
return async (params: {
before_flag?: boolean
limit?: number
cursor?: string
}) => {
return this.embassyApi.getPackageLogs({
id: this.pkgId,
before_flag: params.before_flag,

View File

@@ -5,7 +5,7 @@ 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 } from '@start9labs/shared'
import { pauseFor, ErrorToastService, getPkgId } from '@start9labs/shared'
@Component({
selector: 'app-metrics',
@@ -14,7 +14,7 @@ import { pauseFor, ErrorToastService } from '@start9labs/shared'
})
export class AppMetricsPage {
loading = true
pkgId: string
readonly pkgId = getPkgId(this.route)
mainStatus: MainStatus
going = false
metrics: Metric
@@ -29,7 +29,6 @@ export class AppMetricsPage {
) {}
ngOnInit() {
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
this.startDaemon()
}

View File

@@ -15,7 +15,7 @@ import { PackageProperties } from 'src/app/util/properties.util'
import { QRComponent } from 'src/app/components/qr/qr.component'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { PackageMainStatus } from 'src/app/services/patch-db/data-model'
import { ErrorToastService } from '@start9labs/shared'
import { ErrorToastService, getPkgId } from '@start9labs/shared'
import { getValueByPointer } from 'fast-json-patch'
@Component({
@@ -25,7 +25,7 @@ import { getValueByPointer } from 'fast-json-patch'
})
export class AppPropertiesPage {
loading = true
pkgId: string
readonly pkgId = getPkgId(this.route)
pointer: string
properties: PackageProperties
node: PackageProperties
@@ -55,8 +55,6 @@ export class AppPropertiesPage {
}
async ngOnInit() {
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
await this.getProperties()
this.subs = [
@@ -100,7 +98,7 @@ export class AppPropertiesPage {
const alert = await this.alertCtrl.create({
header: property.key,
message: property.value.description,
message: property.value.description || undefined,
})
await alert.present()
}

View File

@@ -6,7 +6,7 @@
<!-- ** status ** -->
<app-show-status
[pkg]="pkg"
[connectionFailure]="connectionFailure$ | async"
[connectionFailure]="!!(connectionFailure$ | async)"
[dependencies]="dependencies"
[status]="status"
></app-show-status>
@@ -16,7 +16,7 @@
<app-show-health-checks
*ngIf="isRunning(status)"
[pkg]="pkg"
[connectionFailure]="connectionFailure$ | async"
[connectionFailure]="!!(connectionFailure$ | async)"
></app-show-health-checks>
<!-- ** dependencies ** -->
<app-show-dependencies
@@ -27,7 +27,7 @@
<app-show-menu [buttons]="pkg | toButtons"></app-show-menu>
</ng-container>
</ion-item-group>
<!-- ** installing, updating, restoring ** -->
<ion-content *ngIf="showProgress(pkg)">
<app-show-progress

View File

@@ -13,6 +13,7 @@ import {
} from 'src/app/services/connection.service'
import { map, startWith } from 'rxjs/operators'
import { ActivatedRoute } from '@angular/router'
import { getPkgId } from '@start9labs/shared'
const STATES = [
PackageState.Installing,
@@ -26,7 +27,7 @@ const STATES = [
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppShowPage {
private readonly pkgId = this.route.snapshot.paramMap.get('pkgId')
private readonly pkgId = getPkgId(this.route)
readonly pkg$ = this.patch.watch$('package-data', this.pkgId).pipe(
map(pkg => {

View File

@@ -31,6 +31,6 @@ export class AppShowProgressComponent {
}
getColor(action: keyof InstallProgress): string {
return this.pkg['install-progress'][action] ? 'success' : 'secondary'
return this.pkg['install-progress']?.[action] ? 'success' : 'secondary'
}
}

View File

@@ -37,7 +37,7 @@
</ion-button>
<ion-button
*ngIf="!pkgStatus.configured"
*ngIf="!pkgStatus?.configured"
class="action-button"
slot="start"
color="warning"
@@ -48,7 +48,7 @@
</ion-button>
<ion-button
*ngIf="interfaces | hasUi"
*ngIf="pkgStatus && (interfaces | hasUi)"
class="action-button"
slot="start"
color="primary"

View File

@@ -61,8 +61,8 @@ export class AppShowStatusComponent {
return this.pkg.manifest.interfaces
}
get pkgStatus(): Status {
return this.pkg.installed.status
get pkgStatus(): Status | null {
return this.pkg.installed?.status || null
}
get isInstalled(): boolean {
@@ -75,7 +75,8 @@ export class AppShowStatusComponent {
get isStopped(): boolean {
return (
this.status.primary === PrimaryStatus.Stopped && this.pkgStatus.configured
this.status.primary === PrimaryStatus.Stopped &&
!!this.pkgStatus?.configured
)
}
@@ -109,7 +110,7 @@ export class AppShowStatusComponent {
async stop(): Promise<void> {
const { id, title, version } = this.pkg.manifest
const hasDependents = !!Object.keys(
this.pkg.installed['current-dependents'],
this.pkg.installed?.['current-dependents'] || {},
).filter(depId => depId !== id).length
if (!hasDependents) {

View File

@@ -134,7 +134,7 @@ export class ToButtonsPipe implements PipeTransform {
private async donate({ manifest }: PackageDataEntry): Promise<void> {
const url = manifest['donation-url']
if (url) {
this.document.defaultView.open(url, '_blank', 'noreferrer')
this.document.defaultView?.open(url, '_blank', 'noreferrer')
} else {
const alert = await this.alertCtrl.create({
header: 'Not Accepting Donations',

View File

@@ -62,7 +62,7 @@ export class ToDependenciesPipe implements PipeTransform {
private setDepValues(
pkg: PackageDataEntry,
id: string,
errors: { [id: string]: DependencyError },
errors: { [id: string]: DependencyError | null },
): DependencyInfo {
let errorText = ''
let actionText = 'View'
@@ -105,13 +105,13 @@ export class ToDependenciesPipe implements PipeTransform {
errorText = `${errorText}. ${pkg.manifest.title} will not work as expected.`
}
const depInfo = pkg.installed['dependency-info'][id]
const depInfo = pkg.installed?.['dependency-info'][id]
return {
id,
version: pkg.manifest.dependencies[id].version,
title: depInfo.manifest?.title || id,
icon: depInfo.icon,
title: depInfo?.manifest?.title || id,
icon: depInfo?.icon || '',
errorText,
actionText,
action,

View File

@@ -1,12 +1,13 @@
import { Component } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { ModalController } from '@ionic/angular'
import { debounce, exists, ErrorToastService } from '@start9labs/shared'
import * as yaml from 'js-yaml'
import { take } from 'rxjs/operators'
import { filter, take } from 'rxjs/operators'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { getProjectId } from 'src/app/util/get-project-id'
import { GenericFormPage } from '../../../modals/generic-form/generic-form.page'
import { debounce, ErrorToastService } from '@start9labs/shared'
@Component({
selector: 'dev-config',
@@ -14,7 +15,7 @@ import { debounce, ErrorToastService } from '@start9labs/shared'
styleUrls: ['dev-config.page.scss'],
})
export class DevConfigPage {
projectId: string
readonly projectId = getProjectId(this.route)
editorOptions = { theme: 'vs-dark', language: 'yaml' }
code: string = ''
saving: boolean = false
@@ -28,11 +29,9 @@ export class DevConfigPage {
) {}
ngOnInit() {
this.projectId = this.route.snapshot.paramMap.get('projectId')
this.patchDb
.watch$('ui', 'dev', this.projectId, 'config')
.pipe(take(1))
.pipe(filter(exists), take(1))
.subscribe(config => {
this.code = config
})

View File

@@ -1,14 +1,16 @@
import { Component } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { ModalController } from '@ionic/angular'
import { take } from 'rxjs/operators'
import { filter, take } from 'rxjs/operators'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import {
debounce,
exists,
ErrorToastService,
MarkdownComponent,
} from '@start9labs/shared'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { getProjectId } from 'src/app/util/get-project-id'
@Component({
selector: 'dev-instructions',
@@ -16,7 +18,7 @@ import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
styleUrls: ['dev-instructions.page.scss'],
})
export class DevInstructionsPage {
projectId: string
readonly projectId = getProjectId(this.route)
editorOptions = { theme: 'vs-dark', language: 'markdown' }
code: string = ''
saving: boolean = false
@@ -30,11 +32,9 @@ export class DevInstructionsPage {
) {}
ngOnInit() {
this.projectId = this.route.snapshot.paramMap.get('projectId')
this.patchDb
.watch$('ui', 'dev', this.projectId, 'instructions')
.pipe(take(1))
.pipe(filter(exists), take(1))
.subscribe(config => {
this.code = config
})

View File

@@ -3,6 +3,7 @@ import { ActivatedRoute } from '@angular/router'
import * as yaml from 'js-yaml'
import { take } from 'rxjs/operators'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { getProjectId } from 'src/app/util/get-project-id'
@Component({
selector: 'dev-manifest',
@@ -10,7 +11,7 @@ import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
styleUrls: ['dev-manifest.page.scss'],
})
export class DevManifestPage {
projectId: string
readonly projectId = getProjectId(this.route)
editorOptions = { theme: 'vs-dark', language: 'yaml', readOnly: true }
manifest: string = ''
@@ -20,8 +21,6 @@ export class DevManifestPage {
) {}
ngOnInit() {
this.projectId = this.route.snapshot.paramMap.get('projectId')
this.patchDb
.watch$('ui', 'dev', this.projectId)
.pipe(take(1))

View File

@@ -231,9 +231,7 @@ const SAMPLE_CONFIG: ConfigSpec = {
masked: false,
copyable: false,
// optional
warning: null,
description: 'Example description for required string input.',
default: null,
placeholder: 'Enter string value',
pattern: '^[a-zA-Z0-9! _]+$',
'pattern-description': 'Must be alphanumeric (may contain underscore).',
@@ -248,14 +246,12 @@ const SAMPLE_CONFIG: ConfigSpec = {
warning: 'Example warning to display when changing this number value.',
units: 'ms',
description: 'Example description for optional number input.',
default: null,
placeholder: 'Enter number value',
},
'sample-boolean': {
type: 'boolean',
name: 'Example Boolean Toggle',
// optional
warning: null,
description: 'Example description for boolean toggle',
default: true,
},

View File

@@ -3,7 +3,7 @@
<ion-buttons slot="start">
<ion-back-button defaultHref="/developer"></ion-back-button>
</ion-buttons>
<ion-title>{{ patchDb.data.ui.dev[projectId].name}}</ion-title>
<ion-title>{{ name }}</ion-title>
<ion-buttons slot="end">
<ion-button routerLink="manifest">View Manifest</ion-button>
</ion-buttons>

View File

@@ -5,10 +5,10 @@ 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 } from '@start9labs/shared'
import { ErrorToastService, DestroyService } from '@start9labs/shared'
import { takeUntil } from 'rxjs/operators'
import { DevProjectData } from 'src/app/services/patch-db/data-model'
import { DestroyService } from '../../../../../../shared/src/services/destroy.service'
import { getProjectId } from 'src/app/util/get-project-id'
import * as yaml from 'js-yaml'
@Component({
@@ -18,7 +18,7 @@ import * as yaml from 'js-yaml'
providers: [DestroyService],
})
export class DeveloperMenuPage {
projectId: string
readonly projectId = getProjectId(this.route)
projectData: DevProjectData
constructor(
@@ -28,12 +28,14 @@ export class DeveloperMenuPage {
private readonly api: ApiService,
private readonly errToast: ErrorToastService,
private readonly destroy$: DestroyService,
public readonly patchDb: PatchDbService,
private readonly patchDb: PatchDbService,
) {}
ngOnInit() {
this.projectId = this.route.snapshot.paramMap.get('projectId')
get name(): string {
return this.patchDb.data.ui?.dev?.[this.projectId]?.name || ''
}
ngOnInit() {
this.patchDb
.watch$('ui', 'dev', this.projectId)
.pipe(takeUntil(this.destroy$))
@@ -51,14 +53,14 @@ export class DeveloperMenuPage {
buttons: [
{
text: 'Save',
handler: basicInfo => {
handler: (basicInfo: any) => {
basicInfo.description = {
short: basicInfo.short,
long: basicInfo.long,
}
delete basicInfo.short
delete basicInfo.long
this.saveBasicInfo(basicInfo as BasicInfo)
this.saveBasicInfo(basicInfo)
},
isSubmit: true,
},

View File

@@ -9,10 +9,10 @@
<ion-content class="ion-padding">
<marketplace-list-content
*ngIf="loaded else loading"
[localPkgs]="localPkgs$ | async"
[localPkgs]="(localPkgs$ | async) || {}"
[pkgs]="pkgs$ | async"
[categories]="categories$ | async"
[name]="name$ | async"
[name]="(name$ | async) || ''"
></marketplace-list-content>
<ng-template #loading>

View File

@@ -25,7 +25,7 @@ export class MarketplaceShowControlsComponent {
pkg: MarketplacePkg
@Input()
localPkg: PackageDataEntry
localPkg: PackageDataEntry | null = null
readonly PackageState = PackageState
@@ -77,7 +77,7 @@ export class MarketplaceShowControlsComponent {
title,
version,
serviceRequirements: dependencies,
installAlert: alerts.install,
installAlert: alerts.install || undefined,
}
const { cancelled } = await wizardModal(

View File

@@ -5,9 +5,9 @@
<ng-container *ngIf="!(pkg | empty)">
<marketplace-package [pkg]="pkg">
<marketplace-status
*ngIf="localPkg$ | async as localPkg"
class="status"
[version]="pkg.manifest.version"
[localPkg]="localPkg$ | async"
[localPkg]="localPkg"
></marketplace-status>
<marketplace-show-controls
position="controls"

View File

@@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { ErrorToastService } from '@start9labs/shared'
import { ErrorToastService, getPkgId } from '@start9labs/shared'
import {
MarketplacePkg,
AbstractMarketplaceService,
@@ -8,7 +8,7 @@ import {
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { BehaviorSubject, Observable, of } from 'rxjs'
import { catchError, filter, shareReplay, switchMap, tap } from 'rxjs/operators'
import { catchError, filter, shareReplay, switchMap } from 'rxjs/operators'
@Component({
selector: 'marketplace-show',
@@ -17,7 +17,7 @@ import { catchError, filter, shareReplay, switchMap, tap } from 'rxjs/operators'
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MarketplaceShowPage {
private readonly pkgId = this.route.snapshot.paramMap.get('pkgId')
private readonly pkgId = getPkgId(this.route)
readonly loadVersion$ = new BehaviorSubject<string>('*')
@@ -28,12 +28,16 @@ export class MarketplaceShowPage {
shareReplay({ bufferSize: 1, refCount: true }),
)
readonly pkg$: Observable<MarketplacePkg> = this.loadVersion$.pipe(
readonly pkg$: Observable<MarketplacePkg | null> = this.loadVersion$.pipe(
switchMap(version =>
this.marketplaceService.getPackage(this.pkgId, version),
),
// TODO: Better fallback
catchError(e => this.errToast.present(e) && of({} as MarketplacePkg)),
catchError(e => {
this.errToast.present(e)
return of({} as MarketplacePkg)
}),
)
constructor(

View File

@@ -6,8 +6,8 @@ import { packageLoadingProgress } from 'src/app/util/package-loading-progress'
name: 'installProgress',
})
export class InstallProgressPipe implements PipeTransform {
transform(loadData: InstallProgress): string {
const { totalProgress } = packageLoadingProgress(loadData)
transform(loadData?: InstallProgress): string {
const totalProgress = packageLoadingProgress(loadData)?.totalProgress || 0
return totalProgress < 99 ? totalProgress + '%' : 'finalizing'
}

View File

@@ -1,12 +1,13 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { getPkgId } from '@start9labs/shared'
@Component({
templateUrl: './release-notes.page.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ReleaseNotesPage {
readonly href = `/marketplace/${this.route.snapshot.paramMap.get('pkgId')}`
readonly href = `/marketplace/${getPkgId(this.route)}`
constructor(private readonly route: ActivatedRoute) {}
}

View File

@@ -23,7 +23,7 @@ import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
export class NotificationsPage {
loading = true
notifications: ServerNotifications = []
beforeCursor: number
beforeCursor?: number
needInfinite = false
fromToast = false
readonly perPage = 40
@@ -51,19 +51,23 @@ export class NotificationsPage {
}
async getNotifications(): Promise<ServerNotifications> {
let notifications: ServerNotifications = []
try {
notifications = await this.embassyApi.getNotifications({
const notifications = await this.embassyApi.getNotifications({
before: this.beforeCursor,
limit: this.perPage,
})
if (!notifications) return []
this.beforeCursor = notifications[notifications.length - 1]?.id
this.needInfinite = notifications.length >= this.perPage
return notifications
} catch (e: any) {
this.errToast.present(e)
} finally {
return notifications
}
return []
}
async delete(id: number, index: number): Promise<void> {

View File

@@ -33,7 +33,7 @@
>instructions</a
>.
</h2>
<ng-container *ngIf="downloadIsDisabled && server$ | async as server">
<ng-container *ngIf="downloadIsDisabled && (server$ | async) as server">
<br />
<ion-text color="warning">
For security reasons, you must setup LAN over a

View File

@@ -8,8 +8,7 @@ import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
styleUrls: ['./lan.page.scss'],
})
export class LANPage {
downloadIsDisabled: boolean
readonly downloadIsDisabled = !this.config.isTor()
readonly server$ = this.patch.watch$('server-info')
constructor(
@@ -17,11 +16,7 @@ export class LANPage {
private readonly patch: PatchDbService,
) {}
ngOnInit() {
this.downloadIsDisabled = !this.config.isTor()
}
installCert(): void {
document.getElementById('install-cert').click()
document.getElementById('install-cert')?.click()
}
}

View File

@@ -15,7 +15,19 @@ import { v4 } from 'uuid'
import { UIMarketplaceData } from '../../../services/patch-db/data-model'
import { ConfigService } from '../../../services/config.service'
import { MarketplaceService } from 'src/app/services/marketplace.service'
import { finalize, first } from 'rxjs/operators'
import {
distinctUntilChanged,
finalize,
first,
map,
startWith,
} from 'rxjs/operators'
type Marketplaces = {
id: string | undefined
name: string
url: string
}[]
@Component({
selector: 'marketplaces',
@@ -24,7 +36,7 @@ import { finalize, first } from 'rxjs/operators'
})
export class MarketplacesPage {
selectedId: string | undefined
marketplaces: { id: string | undefined; name: string; url: string }[] = []
marketplaces: Marketplaces = []
constructor(
private readonly api: ApiService,
@@ -39,27 +51,33 @@ export class MarketplacesPage {
) {}
ngOnInit() {
this.patch.watch$('ui', 'marketplace').subscribe(mp => {
const marketplaces = [
{
id: undefined,
name: this.config.marketplace.name,
url: this.config.marketplace.url,
},
]
if (mp) {
this.selectedId = mp['selected-id']
const alts = Object.entries(mp['known-hosts']).map(([k, v]) => {
return {
id: k,
name: v.name,
url: v.url,
}
})
marketplaces.push.apply(marketplaces, alts)
}
this.marketplaces = marketplaces
})
this.patch
.watch$('ui')
.pipe(
map(ui => ui.marketplace),
distinctUntilChanged(),
)
.subscribe(mp => {
let marketplaces: Marketplaces = [
{
id: undefined,
name: this.config.marketplace.name,
url: this.config.marketplace.url,
},
]
if (mp) {
this.selectedId = mp['selected-id'] || undefined
const alts = Object.entries(mp['known-hosts']).map(([k, v]) => {
return {
id: k,
name: v.name,
url: v.url,
}
})
marketplaces = marketplaces.concat(alts)
}
this.marketplaces = marketplaces
})
}
async presentModalAdd() {
@@ -91,9 +109,10 @@ export class MarketplacesPage {
await modal.present()
}
async presentAction(id: string) {
async presentAction(id: string = '') {
// no need to view actions if is selected marketplace
if (id === this.patch.getData().ui.marketplace?.['selected-id']) return
if (!id || id === this.patch.getData().ui.marketplace?.['selected-id'])
return
const buttons: ActionSheetButton[] = [
{
@@ -200,7 +219,10 @@ export class MarketplacesPage {
? (JSON.parse(
JSON.stringify(this.patch.getData().ui.marketplace),
) as UIMarketplaceData)
: { 'selected-id': undefined, 'known-hosts': {} }
: {
'selected-id': undefined,
'known-hosts': {} as Record<string, unknown>,
}
// no-op on duplicates
const currentUrls = this.marketplaces.map(mp => mp.url)
@@ -242,7 +264,10 @@ export class MarketplacesPage {
? (JSON.parse(
JSON.stringify(this.patch.getData().ui.marketplace),
) as UIMarketplaceData)
: { 'selected-id': undefined, 'known-hosts': {} }
: {
'selected-id': undefined,
'known-hosts': {} as Record<string, unknown>,
}
// no-op on duplicates
const currentUrls = this.marketplaces.map(mp => mp.url)

View File

@@ -43,7 +43,8 @@ export class RestorePage {
useMask: true,
buttonText: 'Next',
submitFn: async (password: string) => {
argon2.verify(target.entry['embassy-os']['password-hash'], password)
const passwordHash = target.entry['embassy-os']?.['password-hash'] || ''
argon2.verify(passwordHash, password)
await this.restoreFromBackup(target, password)
},
}

View File

@@ -55,7 +55,7 @@ export class ServerBackupPage {
} else {
if (this.backingUp) {
this.backingUp = false
this.pkgs.forEach(pkg => pkg.sub.unsubscribe())
this.pkgs.forEach(pkg => pkg.sub?.unsubscribe())
this.navCtrl.navigateRoot('/embassy')
}
}
@@ -65,7 +65,7 @@ export class ServerBackupPage {
ngOnDestroy() {
this.subs.forEach(sub => sub.unsubscribe())
this.pkgs.forEach(pkg => pkg.sub.unsubscribe())
this.pkgs.forEach(pkg => pkg.sub?.unsubscribe())
}
async presentModalPassword(
@@ -98,7 +98,10 @@ export class ServerBackupPage {
// existing backup
} else {
try {
argon2.verify(target.entry['embassy-os']['password-hash'], password)
const passwordHash =
target.entry['embassy-os']?.['password-hash'] || ''
argon2.verify(passwordHash, password)
} catch {
setTimeout(
() => this.presentModalOldPassword(target, password),
@@ -133,7 +136,9 @@ export class ServerBackupPage {
useMask: true,
buttonText: 'Create Backup',
submitFn: async (oldPassword: string) => {
argon2.verify(target.entry['embassy-os']['password-hash'], oldPassword)
const passwordHash = target.entry['embassy-os']?.['password-hash'] || ''
argon2.verify(passwordHash, oldPassword)
await this.createBackup(target.id, password, oldPassword)
},
}
@@ -182,15 +187,11 @@ export class ServerBackupPage {
pkg.installed?.status.main.status === PackageMainStatus.BackingUp,
)
this.pkgs = pkgArr.map((pkg, i) => {
const pkgInfo = {
entry: pkg,
active: i === activeIndex,
complete: i < activeIndex,
sub: null,
}
return pkgInfo
})
this.pkgs = pkgArr.map((pkg, i) => ({
entry: pkg,
active: i === activeIndex,
complete: i < activeIndex,
}))
// subscribe to pkg
this.pkgs.forEach(pkg => {
@@ -220,5 +221,5 @@ interface PkgInfo {
entry: PackageDataEntry
active: boolean
complete: boolean
sub: Subscription
sub?: Subscription
}

View File

@@ -12,11 +12,11 @@ export class ServerLogsPage {
needInfinite = true
before: string
constructor (
constructor(
private readonly embassyApi: ApiService,
) { }
fetchFetchLogs () {
fetchFetchLogs() {
return async (params: { before_flag?: boolean, limit?: number, cursor?: string }) => {
return this.embassyApi.getServerLogs({
before_flag: params.before_flag,
@@ -25,4 +25,22 @@ export class ServerLogsPage {
})
}
}
async copy(): Promise<void> {
const logs = document
.getElementById('template')
?.cloneNode(true) as HTMLElement
const formatted = '```' + 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()
}
}

View File

@@ -30,7 +30,7 @@ export class ServerMetricsPage {
})
const height = headersCount * 54 + rowsCount * 50 + 24 // extra 24 for room at the bottom
const elem = document.getElementById('metricSection')
elem.style.height = `${height}px`
if (elem) elem.style.height = `${height}px`
this.startDaemon()
this.loading = false
}

View File

@@ -1,7 +1,7 @@
<ion-header>
<ion-toolbar>
<ion-title *ngIf="patch.loaded else loading">
{{ (ui$ | async).name || "Embassy-" + (server$ | async).id }}
{{ (ui$ | async)?.name || "Embassy-" + (server$ | async)?.id }}
</ion-title>
<ng-template #loading>
<ion-title>Loading<span class="loading-dots"></span></ion-title>

View File

@@ -42,7 +42,7 @@ export class WifiPage {
await this.getWifi()
}
async getWifi(timeout?: number): Promise<void> {
async getWifi(timeout: number = 0): Promise<void> {
this.loading = true
try {
this.wifi = await this.api.getWifi({}, timeout)