mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
chore: enable strict mode (#1569)
* chore: enable strict mode * refactor: remove sync data access from PatchDbService * launchable even when no LAN url Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
This commit is contained in:
@@ -4,4 +4,4 @@
|
||||
<h1>{{ action.name }}</h1>
|
||||
<p>{{ action.description }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding-top">
|
||||
<ion-item-group *ngIf="pkg">
|
||||
<ion-item-group *ngIf="pkg$ | async as pkg">
|
||||
<!-- ** standard actions ** -->
|
||||
<ion-item-divider>Standard Actions</ion-item-divider>
|
||||
<app-actions-item
|
||||
@@ -17,7 +17,7 @@
|
||||
description: 'This will uninstall the service from your Embassy and delete all data permanently.',
|
||||
icon: 'trash-outline'
|
||||
}"
|
||||
(click)="tryUninstall()"
|
||||
(click)="tryUninstall(pkg)"
|
||||
>
|
||||
</app-actions-item>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
description: action.value.description,
|
||||
icon: 'play-circle-outline'
|
||||
}"
|
||||
(click)="handleAction(action)"
|
||||
(click)="handleAction(pkg, action)"
|
||||
>
|
||||
</app-actions-item>
|
||||
</ion-item-group>
|
||||
|
||||
@@ -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<void> {
|
||||
const { title, alerts } = this.pkg.manifest
|
||||
async tryUninstall(pkg: PackageDataEntry): Promise<void> {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<ion-item>
|
||||
<ion-item *ngIf="interface">
|
||||
<ion-icon
|
||||
slot="start"
|
||||
size="large"
|
||||
@@ -9,7 +9,7 @@
|
||||
<h2>{{ interface.def.description }}</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<div style="padding-left: 64px">
|
||||
<div *ngIf="interface" style="padding-left: 64px">
|
||||
<!-- has tor -->
|
||||
<ion-item *ngIf="interface.addresses['tor-address'] as tor">
|
||||
<ion-label>
|
||||
|
||||
@@ -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 = [
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
<ion-icon
|
||||
*ngIf="pkg.error; else noError"
|
||||
class="warning-icon"
|
||||
name="warning-outline"
|
||||
size="small"
|
||||
color="warning"
|
||||
></ion-icon>
|
||||
<ng-template #noError>
|
||||
<ion-spinner
|
||||
*ngIf="pkg.transitioning; else bulb"
|
||||
class="spinner"
|
||||
<div
|
||||
*ngIf="disconnected$ | async; else connected"
|
||||
class="bulb"
|
||||
[style.background-color]="'var(--ion-color-dark)'"
|
||||
></div>
|
||||
<ng-template #connected>
|
||||
<ion-icon
|
||||
*ngIf="pkg.error; else noError"
|
||||
class="warning-icon"
|
||||
name="warning-outline"
|
||||
size="small"
|
||||
color="primary"
|
||||
></ion-spinner>
|
||||
<ng-template #bulb>
|
||||
<div
|
||||
class="bulb"
|
||||
[style.background-color]="
|
||||
(disconnected$ | async)
|
||||
? 'var(--ion-color-dark)'
|
||||
: 'var(--ion-color-' + this.pkg.primaryRendering.color + ')'
|
||||
"
|
||||
></div>
|
||||
color="warning"
|
||||
></ion-icon>
|
||||
<ng-template #noError>
|
||||
<ion-spinner
|
||||
*ngIf="pkg.transitioning; else bulb"
|
||||
class="spinner"
|
||||
size="small"
|
||||
color="primary"
|
||||
></ion-spinner>
|
||||
<ng-template #bulb>
|
||||
<div
|
||||
class="bulb"
|
||||
[style.background-color]="
|
||||
'var(--ion-color-' + pkg.primaryRendering.color + ')'
|
||||
"
|
||||
[style.color]="'var(--ion-color-' + pkg.primaryRendering.color + ')'"
|
||||
></div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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$()
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
<ion-item button detail="false" [routerLink]="['/services', manifest.id]">
|
||||
<ion-item
|
||||
button
|
||||
*ngIf="pkg.entry.manifest as manifest"
|
||||
detail="false"
|
||||
[routerLink]="['/services', manifest.id]"
|
||||
>
|
||||
<app-list-icon slot="start" [pkg]="pkg"></app-list-icon>
|
||||
<ion-thumbnail slot="start">
|
||||
<img alt="" [src]="pkg.entry['static-files'].icon" />
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -26,7 +26,7 @@ export class AppListRecComponent {
|
||||
readonly delete$ = new Subject<RecoveredInfo>()
|
||||
|
||||
@Input()
|
||||
rec: RecoveredInfo
|
||||
rec!: RecoveredInfo
|
||||
|
||||
@Output()
|
||||
readonly deleted = new EventEmitter<void>()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -4,18 +4,21 @@
|
||||
<ion-back-button [defaultHref]="'/services/' + pkgId"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Monitor</ion-title>
|
||||
<ion-title slot="end"><ion-spinner name="dots" class="fader"></ion-spinner></ion-title>
|
||||
<ion-title slot="end"
|
||||
><ion-spinner name="dots" class="fader"></ion-spinner
|
||||
></ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
|
||||
<skeleton-list *ngIf="loading" rows="3"></skeleton-list>
|
||||
<skeleton-list *ngIf="loading" [rows]="3"></skeleton-list>
|
||||
<ion-item-group *ngIf="!loading">
|
||||
<ion-item *ngFor="let metric of metrics | keyvalue : asIsOrder">
|
||||
<ion-label>{{ metric.key }}</ion-label>
|
||||
<ion-note *ngIf="metric.value" slot="end" class="metric-note">
|
||||
<ion-text style="color: white;">{{ metric.value.value }} {{ metric.value.unit }}</ion-text>
|
||||
<ion-text style="color: white"
|
||||
>{{ metric.value.value }} {{ metric.value.unit }}</ion-text
|
||||
>
|
||||
</ion-note>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
<ng-template #loaded>
|
||||
<!-- not running -->
|
||||
<ion-item *ngIf="!running" class="ion-margin-bottom">
|
||||
<ion-item *ngIf="notRunning$ | async" class="ion-margin-bottom">
|
||||
<ion-label>
|
||||
<p>
|
||||
<ion-text color="warning"
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { Component, ViewChild } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { copyToClipboard } from 'src/app/util/web.util'
|
||||
import {
|
||||
AlertController,
|
||||
IonBackButtonDelegate,
|
||||
IonContent,
|
||||
ModalController,
|
||||
NavController,
|
||||
ToastController,
|
||||
@@ -15,27 +13,32 @@ 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, getPkgId } from '@start9labs/shared'
|
||||
import { DestroyService, ErrorToastService, getPkgId } from '@start9labs/shared'
|
||||
import { getValueByPointer } from 'fast-json-patch'
|
||||
import { map, takeUntil } from 'rxjs/operators'
|
||||
|
||||
@Component({
|
||||
selector: 'app-properties',
|
||||
templateUrl: './app-properties.page.html',
|
||||
styleUrls: ['./app-properties.page.scss'],
|
||||
providers: [DestroyService],
|
||||
})
|
||||
export class AppPropertiesPage {
|
||||
loading = true
|
||||
readonly pkgId = getPkgId(this.route)
|
||||
pointer: string
|
||||
properties: PackageProperties
|
||||
node: PackageProperties
|
||||
|
||||
pointer = ''
|
||||
node: PackageProperties = {}
|
||||
|
||||
properties: PackageProperties = {}
|
||||
unmasked: { [key: string]: boolean } = {}
|
||||
running = true
|
||||
|
||||
notRunning$ = this.patch
|
||||
.watch$('package-data', this.pkgId, 'installed', 'status', 'main', 'status')
|
||||
.pipe(map(status => 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<any> {
|
||||
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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,5 +9,5 @@ import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
})
|
||||
export class AppShowHeaderComponent {
|
||||
@Input()
|
||||
pkg: PackageDataEntry
|
||||
pkg!: PackageDataEntry
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
})
|
||||
export class AppShowHealthChecksComponent {
|
||||
@Input()
|
||||
pkg: PackageDataEntry
|
||||
pkg!: PackageDataEntry
|
||||
|
||||
HealthResult = HealthResult
|
||||
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<ion-label class="label">
|
||||
<status
|
||||
size="x-large"
|
||||
weight="500"
|
||||
weight="600"
|
||||
[installProgress]="pkg['install-progress']"
|
||||
[rendering]="PR[status.primary]"
|
||||
[sigtermTimeout]="pkg.manifest.main['sigterm-timeout']"
|
||||
|
||||
@@ -27,10 +27,10 @@ import { ConnectionService } from 'src/app/services/connection.service'
|
||||
})
|
||||
export class AppShowStatusComponent {
|
||||
@Input()
|
||||
pkg: PackageDataEntry
|
||||
pkg!: PackageDataEntry
|
||||
|
||||
@Input()
|
||||
status: PackageStatus
|
||||
status!: PackageStatus
|
||||
|
||||
@Input()
|
||||
dependencies: DependencyInfo[] = []
|
||||
@@ -50,7 +50,7 @@ export class AppShowStatusComponent {
|
||||
) {}
|
||||
|
||||
get interfaces(): Record<string, InterfaceDef> {
|
||||
return this.pkg.manifest.interfaces
|
||||
return this.pkg.manifest.interfaces || {}
|
||||
}
|
||||
|
||||
get pkgStatus(): Status | null {
|
||||
@@ -74,7 +74,9 @@ export class AppShowStatusComponent {
|
||||
}
|
||||
|
||||
async presentModalConfig(): Promise<void> {
|
||||
return this.modalService.presentModalConfig({ pkgId: this.pkg.manifest.id })
|
||||
return this.modalService.presentModalConfig({
|
||||
pkgId: this.id,
|
||||
})
|
||||
}
|
||||
|
||||
async tryStart(): Promise<void> {
|
||||
@@ -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<void> {
|
||||
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 {
|
||||
|
||||
@@ -202,12 +202,6 @@ export class ToButtonsPipe implements PipeTransform {
|
||||
packageMarketplace,
|
||||
currentMarketplace,
|
||||
pkgId,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
},
|
||||
],
|
||||
},
|
||||
cssClass: 'medium-modal',
|
||||
})
|
||||
|
||||
@@ -28,7 +28,7 @@ import { takeUntil } from 'rxjs/operators'
|
||||
providers: [DestroyService],
|
||||
})
|
||||
export class DeveloperListPage {
|
||||
devData: DevData
|
||||
devData: DevData = {}
|
||||
|
||||
constructor(
|
||||
private readonly modalCtrl: ModalController,
|
||||
|
||||
@@ -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 = [
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button defaultHref="/developer"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ name }}</ion-title>
|
||||
<ion-title>{{ (projectData$ | async)?.name || '' }}</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button routerLink="manifest">View Manifest</ion-button>
|
||||
</ion-buttons>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
>
|
||||
Downgrade
|
||||
</ion-button>
|
||||
<ng-container *ngIf="localStorageService.showDevTools$ | async">
|
||||
<ng-container *ngIf="showDevTools$ | async">
|
||||
<ion-button
|
||||
*ngIf="(localVersion | compareEmver: pkg.manifest.version) === 0"
|
||||
expand="block"
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
AbstractMarketplaceService,
|
||||
MarketplacePkg,
|
||||
} from '@start9labs/marketplace'
|
||||
import { Emver, ErrorToastService, isEmptyObject } from '@start9labs/shared'
|
||||
import {
|
||||
PackageDataEntry,
|
||||
PackageState,
|
||||
@@ -16,12 +17,10 @@ import {
|
||||
import { LocalStorageService } from 'src/app/services/local-storage.service'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import { hasCurrentDeps } from 'src/app/util/has-deps'
|
||||
import { Emver } from '../../../../../../../shared/src/services/emver.service'
|
||||
import { ErrorToastService } from '../../../../../../../shared/src/services/error-toast.service'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { isEmptyObject } from '../../../../../../../shared/src/util/misc.util'
|
||||
import { Breakages } from 'src/app/services/api/api.types'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { getAllPackages } from 'src/app/util/get-package-data'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-show-controls',
|
||||
@@ -31,16 +30,18 @@ import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
})
|
||||
export class MarketplaceShowControlsComponent {
|
||||
@Input()
|
||||
pkg: MarketplacePkg
|
||||
pkg!: MarketplacePkg
|
||||
|
||||
@Input()
|
||||
localPkg: PackageDataEntry | null = null
|
||||
localPkg!: PackageDataEntry | null
|
||||
|
||||
readonly showDevTools$ = this.localStorageService.showDevTools$
|
||||
|
||||
readonly PackageState = PackageState
|
||||
|
||||
constructor(
|
||||
private readonly alertCtrl: AlertController,
|
||||
public readonly localStorageService: LocalStorageService,
|
||||
private readonly localStorageService: LocalStorageService,
|
||||
@Inject(AbstractMarketplaceService)
|
||||
private readonly marketplaceService: MarketplaceService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
@@ -151,7 +152,7 @@ export class MarketplaceShowControlsComponent {
|
||||
private async presentAlertBreakages(breakages: Breakages): Promise<boolean> {
|
||||
let message: string =
|
||||
'As a result of this update, the following services will no longer work properly and may crash:<ul>'
|
||||
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 `<li><b>${title}</b></li>`
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<ion-content>
|
||||
<!-- loading -->
|
||||
<ion-item-group *ngIf="loading">
|
||||
<ion-item-group *ngIf="loading; else loaded">
|
||||
<ion-item-divider>
|
||||
<ion-button slot="end" fill="clear">
|
||||
<ion-skeleton-text
|
||||
@@ -43,9 +43,9 @@
|
||||
</ion-item-group>
|
||||
|
||||
<!-- not loading -->
|
||||
<ng-container *ngIf="!loading">
|
||||
<ng-template #loaded>
|
||||
<!-- no notifications -->
|
||||
<ion-item-group *ngIf="!notifications.length">
|
||||
<ion-item-group *ngIf="!notifications.length; else hasNotifications">
|
||||
<div
|
||||
style="
|
||||
text-align: center;
|
||||
@@ -64,8 +64,11 @@
|
||||
</ion-item-group>
|
||||
|
||||
<!-- has notifications -->
|
||||
<ng-container *ngIf="notifications.length">
|
||||
<ion-item-group style="margin-bottom: 16px">
|
||||
<ng-template #hasNotifications>
|
||||
<ion-item-group
|
||||
*ngIf="packageData$ | async as packageData"
|
||||
style="margin-bottom: 16px"
|
||||
>
|
||||
<ion-item-divider>
|
||||
<ion-button
|
||||
slot="end"
|
||||
@@ -80,12 +83,8 @@
|
||||
<ion-label>
|
||||
<h2>
|
||||
<b>
|
||||
<span
|
||||
*ngIf="not['package-id'] && patch.getData()['package-data']"
|
||||
>
|
||||
{{ patch.getData()['package-data'][not['package-id']] ?
|
||||
patch.getData()['package-data'][not['package-id']].manifest.title
|
||||
: not['package-id'] }} -
|
||||
<span *ngIf="not['package-id'] as pkgId">
|
||||
{{ packageData[pkgId]?.manifest!.title || pkgId }} -
|
||||
</span>
|
||||
<ion-text [color]="getColor(not)"> {{ not.title }} </ion-text>
|
||||
</b>
|
||||
@@ -101,7 +100,7 @@
|
||||
View Full Message
|
||||
</a>
|
||||
</p>
|
||||
<p>{{ not['created-at'] | date: 'short' }}</p>
|
||||
<p>{{ not['created-at'] | date: 'medium' }}</p>
|
||||
</ion-label>
|
||||
<ion-button
|
||||
*ngIf="not.code === 1"
|
||||
@@ -135,6 +134,6 @@
|
||||
loadingSpinner="lines"
|
||||
></ion-infinite-scroll-content>
|
||||
</ion-infinite-scroll>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</ion-content>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
<ion-back-button defaultHref="embassy"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Kernel Logs</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button (click)="copy()">
|
||||
<ion-icon name="copy-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
|
||||
@@ -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<void> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<void> {
|
||||
const marketplace: UIMarketplaceData = JSON.parse(
|
||||
JSON.stringify(this.patch.getData().ui.marketplace),
|
||||
)
|
||||
private async connect(id: string | null): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<string, unknown>,
|
||||
'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<void> {
|
||||
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<string, unknown>,
|
||||
'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 }
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
<ng-container *ngIf="ui$ | async as ui">
|
||||
<ion-item-group *ngIf="server$ | async as server">
|
||||
<ion-item-divider>General</ion-item-divider>
|
||||
<ion-item button (click)="presentModalName('Embassy-' + server.id)">
|
||||
<ion-item
|
||||
button
|
||||
(click)="presentModalName('Embassy-' + server.id, ui.name)"
|
||||
>
|
||||
<ion-label>Device Name</ion-label>
|
||||
<ion-note slot="end">{{ ui.name || 'Embassy-' + server.id }}</ion-note>
|
||||
</ion-item>
|
||||
|
||||
@@ -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<void> {
|
||||
async presentModalName(
|
||||
placeholder: string,
|
||||
initialValue: string,
|
||||
): Promise<void> {
|
||||
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),
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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<CifsBackupTarget | DiskBackupTarget>
|
||||
serviceIds: string[] = []
|
||||
|
||||
readonly backingUp$ = this.eosService.backingUp$
|
||||
@@ -56,8 +56,6 @@ export class ServerBackupPage {
|
||||
async presentModalSelect(
|
||||
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
|
||||
) {
|
||||
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<void> {
|
||||
private async presentModalPassword(
|
||||
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
|
||||
): Promise<void> {
|
||||
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<void> {
|
||||
private async presentModalOldPassword(
|
||||
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
|
||||
password: string,
|
||||
): Promise<void> {
|
||||
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<CifsBackupTarget | DiskBackupTarget>,
|
||||
password: string,
|
||||
oldPassword?: string,
|
||||
): Promise<void> {
|
||||
@@ -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,
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
<ion-back-button defaultHref="embassy"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>OS Logs</ion-title>
|
||||
<ion-button slot="end" fill="clear" size="small" (click)="copy()">
|
||||
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button (click)="copy()">
|
||||
<ion-icon name="copy-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -4,25 +4,32 @@
|
||||
<ion-back-button defaultHref="embassy"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Monitor</ion-title>
|
||||
<ion-title slot="end"><ion-spinner name="dots" class="fader"></ion-spinner></ion-title>
|
||||
<ion-title slot="end"
|
||||
><ion-spinner name="dots" class="fader"></ion-spinner
|
||||
></ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<skeleton-list *ngIf="loading" groups="2"></skeleton-list>
|
||||
<skeleton-list *ngIf="loading" [groups]="2"></skeleton-list>
|
||||
|
||||
<div id="metricSection">
|
||||
<ng-container *ngIf="!loading">
|
||||
<ion-item-group *ngFor="let metricGroup of metrics | keyvalue : asIsOrder">
|
||||
<ion-item-group
|
||||
*ngFor="let metricGroup of metrics | keyvalue : asIsOrder"
|
||||
>
|
||||
<ion-item-divider>{{ metricGroup.key }}</ion-item-divider>
|
||||
<ion-item *ngFor="let metric of metricGroup.value | keyvalue : asIsOrder">
|
||||
<ion-item
|
||||
*ngFor="let metric of metricGroup.value | keyvalue : asIsOrder"
|
||||
>
|
||||
<ion-label>{{ metric.key }}</ion-label>
|
||||
<ion-note *ngIf="metric.value" slot="end" class="metric-note">
|
||||
<ion-text style="color: white;">{{ metric.value.value }} {{ metric.value.unit }}</ion-text>
|
||||
<ion-text style="color: white"
|
||||
>{{ metric.value.value }} {{ metric.value.unit }}</ion-text
|
||||
>
|
||||
</ion-note>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
</ion-content>
|
||||
</ion-content>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<ng-container *ngFor="let button of cat.value">
|
||||
<ion-item
|
||||
button
|
||||
[style.display]="(button.title === 'Repair Disk' && !(localStorageService.showDiskRepair$ | async)) ? 'none' : 'block'"
|
||||
[style.display]="(button.title === 'Repair Disk' && !(showDiskRepair$ | async)) ? 'none' : 'block'"
|
||||
[detail]="button.detail"
|
||||
[disabled]="button.disabled | async"
|
||||
(click)="button.action()"
|
||||
@@ -55,7 +55,7 @@
|
||||
*ngIf="!statusInfo['backup-progress'] && !statusInfo['update-progress']"
|
||||
>
|
||||
Last Backup: {{ server['last-backup'] ?
|
||||
(server['last-backup'] | date: 'short') : 'never' }}
|
||||
(server['last-backup'] | date: 'medium') : 'never' }}
|
||||
</ion-text>
|
||||
<span *ngIf="!!statusInfo['backup-progress']" class="inline">
|
||||
<ion-spinner
|
||||
@@ -76,9 +76,7 @@
|
||||
Update Complete. Restart to apply changes
|
||||
</ion-text>
|
||||
<ng-template #notUpdated>
|
||||
<ng-container
|
||||
*ngIf="eosService.showUpdate$ | async; else check"
|
||||
>
|
||||
<ng-container *ngIf="showUpdate$ | async; else check">
|
||||
<ion-text class="inline" color="success">
|
||||
<ion-icon name="rocket-outline"></ion-icon>
|
||||
Update Available
|
||||
|
||||
@@ -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.`,
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<h2>Git Hash</h2>
|
||||
<p>{{ config.gitHash }}</p>
|
||||
<p>{{ gitHash }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<ion-content class="ion-padding-top">
|
||||
<!-- loading -->
|
||||
<ion-item-group *ngIf="loading">
|
||||
<ion-item-group *ngIf="loading; else notLoading">
|
||||
<div *ngFor="let entry of ['This Session', 'Other Sessions']">
|
||||
<ion-item-divider>{{ entry }}</ion-item-divider>
|
||||
<ion-item style="padding-bottom: 6px">
|
||||
@@ -41,60 +41,64 @@
|
||||
</ion-item-group>
|
||||
|
||||
<!-- not loading -->
|
||||
<ion-item-group *ngIf="!loading">
|
||||
<ion-item-divider>Current Session</ion-item-divider>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
size="large"
|
||||
[name]="getPlatformIcon(currentSession.metadata.platforms)"
|
||||
></ion-icon>
|
||||
<ion-label>
|
||||
<h1>{{ getPlatformName(currentSession.metadata.platforms) }}</h1>
|
||||
<h2>
|
||||
Last Active: {{ currentSession['last-active'] | date : 'medium' }}
|
||||
</h2>
|
||||
<p>{{ currentSession['user-agent'] }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-divider>
|
||||
Other Sessions
|
||||
<ion-button
|
||||
*ngIf="otherSessions.length"
|
||||
slot="end"
|
||||
fill="clear"
|
||||
strong
|
||||
(click)="presentAlertKillAll()"
|
||||
>
|
||||
Terminate all
|
||||
</ion-button>
|
||||
</ion-item-divider>
|
||||
<div *ngFor="let session of otherSessions">
|
||||
<ng-template #notLoading>
|
||||
<ion-item-group *ngIf="currentSession">
|
||||
<ion-item-divider>Current Session</ion-item-divider>
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
size="large"
|
||||
[name]="getPlatformIcon(session.metadata.platforms)"
|
||||
[name]="getPlatformIcon(currentSession.metadata.platforms)"
|
||||
></ion-icon>
|
||||
<ion-label>
|
||||
<h1>{{ getPlatformName(session.metadata.platforms) }}</h1>
|
||||
<h2>Last Active: {{ session['last-active'] | date : 'medium' }}</h2>
|
||||
<p>{{ session['user-agent'] }}</p>
|
||||
<h1>{{ getPlatformName(currentSession.metadata.platforms) }}</h1>
|
||||
<h2>
|
||||
Last Active: {{ currentSession['last-active'] | date : 'medium' }}
|
||||
</h2>
|
||||
<p>{{ currentSession['user-agent'] }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item-divider>
|
||||
Other Sessions
|
||||
<ion-button
|
||||
*ngIf="otherSessions.length"
|
||||
slot="end"
|
||||
fill="clear"
|
||||
(click)="presentAlertKill(session.id)"
|
||||
strong
|
||||
(click)="presentAlertKillAll()"
|
||||
>
|
||||
<ion-icon slot="icon-only" name="log-out-outline"></ion-icon>
|
||||
Terminate all
|
||||
</ion-button>
|
||||
</ion-item-divider>
|
||||
<div *ngFor="let session of otherSessions">
|
||||
<ion-item>
|
||||
<ion-icon
|
||||
slot="start"
|
||||
size="large"
|
||||
[name]="getPlatformIcon(session.metadata.platforms)"
|
||||
></ion-icon>
|
||||
<ion-label>
|
||||
<h1>{{ getPlatformName(session.metadata.platforms) }}</h1>
|
||||
<h2>Last Active: {{ session['last-active'] | date : 'medium' }}</h2>
|
||||
<p>{{ session['user-agent'] }}</p>
|
||||
</ion-label>
|
||||
<ion-button
|
||||
slot="end"
|
||||
fill="clear"
|
||||
color="danger"
|
||||
(click)="kill([session.id])"
|
||||
>
|
||||
Logout
|
||||
<ion-icon slot="start" name="log-out-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
</div>
|
||||
<ion-item *ngIf="!otherSessions.length">
|
||||
<ion-label>
|
||||
<p>You are not logged in anywhere else</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
<ion-item *ngIf="!otherSessions.length">
|
||||
<ion-label>
|
||||
<p>You are not logged in anywhere else</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</ion-item-group>
|
||||
</ng-template>
|
||||
</ion-content>
|
||||
|
||||
@@ -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<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
message: `Terminating session${ids.length > 1 ? 's' : ''}...`,
|
||||
|
||||
@@ -21,14 +21,13 @@
|
||||
color="dark"
|
||||
style="font-size: 42px"
|
||||
></ion-icon>
|
||||
<h4>Manually upload a service package</h4>
|
||||
<h4>Upload .s9pk package file</h4>
|
||||
<p *ngIf="onTor">
|
||||
<ion-text color="success"
|
||||
>Tip: switch to LAN for faster uploads.</ion-text
|
||||
>
|
||||
</p>
|
||||
<br />
|
||||
<ion-button color="primary" type="file">
|
||||
<ion-button color="primary" type="file" class="ion-margin-top">
|
||||
<label for="upload-photo">Browse</label>
|
||||
<input
|
||||
type="file"
|
||||
|
||||
@@ -58,7 +58,6 @@
|
||||
min-width: 200px;
|
||||
max-width: 200px;
|
||||
height: auto;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
@@ -89,4 +88,4 @@
|
||||
p {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export class SideloadPage {
|
||||
file: null,
|
||||
}
|
||||
onTor = this.config.isTor()
|
||||
uploadState: {
|
||||
uploadState?: {
|
||||
invalid: boolean
|
||||
message: string
|
||||
}
|
||||
@@ -52,6 +52,7 @@ export class SideloadPage {
|
||||
const files = e.target.files
|
||||
this.setFile(files)
|
||||
}
|
||||
|
||||
async setFile(files?: File[]) {
|
||||
if (!files || !files.length) return
|
||||
const file = files[0]
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
<ion-icon slot="start" name="key-outline" size="large"></ion-icon>
|
||||
<ion-label>
|
||||
<h1>{{ ssh.hostname }}</h1>
|
||||
<h2>{{ ssh['created-at'] | date: 'short' }}</h2>
|
||||
<h2>{{ ssh['created-at'] | date: 'medium' }}</h2>
|
||||
<p>{{ ssh.alg }} {{ ssh.fingerprint }}</p>
|
||||
</ion-label>
|
||||
<ion-button
|
||||
|
||||
Reference in New Issue
Block a user