mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
: '',
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user