mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 04:01:58 +00:00
remove product key from setup flow (#1750)
* remove product key flow from setup * feat: backend turned off encryption + new Id + no package id * implement new encryption scheme in FE * decode response string * crypto not working * update setup wizard closes #1762 * feat: Get the encryption key * fix: Get to recovery * remove old code * fix build * fix: Install works for now * fix bug in config for adding new list items * dismiss action modal on success * clear button in config * wip: Currently broken in avahi mdns * include headers with req/res and refactor patchDB init and usage * fix: Can now run in the main * flatline on failed init * update patch DB * add last-wifi-region to data model even though not used by FE * chore: Fix the start. * wip: Fix wrong order for getting hostname before sql has been created * fix edge case where union keys displayed as new when not new * fix: Can start * last backup color, markdown links always new tab, fix bug with login * refactor to remove WithRevision * resolve circular dep issue * update submodule * fix patch-db * update patchDB * update patch again * escape error * decodeuricomponent * increase proxy buffer size * increase proxy buffer size * fix nginx Co-authored-by: BluJ <mogulslayer@gmail.com> Co-authored-by: BluJ <dragondef@gmail.com> Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
@@ -34,7 +34,7 @@ import { ConnectionBarComponentModule } from './components/connection-bar/connec
|
||||
storeName: '_embassykv',
|
||||
dbKey: '_embassykey',
|
||||
name: '_embassystorage',
|
||||
driverOrder: [Drivers.LocalStorage, Drivers.IndexedDB],
|
||||
driverOrder: [Drivers.LocalStorage],
|
||||
}),
|
||||
MenuModule,
|
||||
PreloaderModule,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Bootstrapper, DBCache } from 'patch-db-client'
|
||||
import { APP_INITIALIZER, ErrorHandler, Provider } from '@angular/core'
|
||||
import { APP_INITIALIZER, Provider } from '@angular/core'
|
||||
import { UntypedFormBuilder } from '@angular/forms'
|
||||
import { Router, RouteReuseStrategy } from '@angular/router'
|
||||
import { IonicRouteStrategy, IonNav } from '@ionic/angular'
|
||||
@@ -8,10 +7,8 @@ import { WorkspaceConfig } from '@start9labs/shared'
|
||||
import { ApiService } from './services/api/embassy-api.service'
|
||||
import { MockApiService } from './services/api/embassy-mock-api.service'
|
||||
import { LiveApiService } from './services/api/embassy-live-api.service'
|
||||
import { BOOTSTRAPPER, PATCH_CACHE } from './services/patch-db/patch-db.factory'
|
||||
import { AuthService } from './services/auth.service'
|
||||
import { LocalStorageService } from './services/local-storage.service'
|
||||
import { DataModel } from './services/patch-db/data-model'
|
||||
import { FilterPackagesPipe } from '../../../marketplace/src/pipes/filter-packages.pipe'
|
||||
|
||||
const { useMocks } = require('../../../../config.json') as WorkspaceConfig
|
||||
@@ -30,14 +27,7 @@ export const APP_PROVIDERS: Provider[] = [
|
||||
},
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
deps: [
|
||||
Storage,
|
||||
AuthService,
|
||||
LocalStorageService,
|
||||
Router,
|
||||
BOOTSTRAPPER,
|
||||
PATCH_CACHE,
|
||||
],
|
||||
deps: [Storage, AuthService, LocalStorageService, Router],
|
||||
useFactory: appInitializer,
|
||||
multi: true,
|
||||
},
|
||||
@@ -48,19 +38,12 @@ export function appInitializer(
|
||||
auth: AuthService,
|
||||
localStorage: LocalStorageService,
|
||||
router: Router,
|
||||
bootstrapper: Bootstrapper<DataModel>,
|
||||
cache: DBCache<DataModel>,
|
||||
): () => Promise<void> {
|
||||
return async () => {
|
||||
await storage.create()
|
||||
await auth.init()
|
||||
await localStorage.init()
|
||||
|
||||
const localCache = await bootstrapper.init()
|
||||
|
||||
cache.sequence = localCache.sequence
|
||||
cache.data = localCache.data
|
||||
|
||||
router.initialNavigation()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { heightCollapse } from '../../util/animations'
|
||||
import { PatchDbService } from '../../services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { map } from 'rxjs/operators'
|
||||
import { ServerInfo } from '../../services/patch-db/data-model'
|
||||
import { DataModel, ServerInfo } from '../../services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'footer[appFooter]',
|
||||
@@ -24,7 +24,7 @@ export class FooterComponent {
|
||||
},
|
||||
}
|
||||
|
||||
constructor(private readonly patch: PatchDbService) {}
|
||||
constructor(private readonly patch: PatchDB<DataModel>) {}
|
||||
|
||||
getProgress({
|
||||
downloaded,
|
||||
|
||||
@@ -57,6 +57,6 @@
|
||||
src="assets/img/icons/snek.png"
|
||||
[appSnekHighScore]="snekScore$ | async"
|
||||
/>
|
||||
<ion-footer class="bottom">
|
||||
<ion-footer *ngIf="sidebarOpen$ | async" class="bottom">
|
||||
<connection-bar></connection-bar>
|
||||
</ion-footer>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
|
||||
import { LocalStorageService } from '../../services/local-storage.service'
|
||||
import { EOSService } from '../../services/eos.service'
|
||||
import { PatchDbService } from '../../services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { Observable } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { SplitPaneTracker } from 'src/app/services/split-pane.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-menu',
|
||||
@@ -57,11 +59,14 @@ export class MenuComponent {
|
||||
.getUpdates()
|
||||
.pipe(map(pkgs => pkgs.length))
|
||||
|
||||
readonly sidebarOpen$ = this.splitPane.sidebarOpen$
|
||||
|
||||
constructor(
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly localStorageService: LocalStorageService,
|
||||
private readonly eosService: EOSService,
|
||||
@Inject(AbstractMarketplaceService)
|
||||
private readonly marketplaceService: MarketplaceService,
|
||||
private readonly splitPane: SplitPaneTracker,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
|
||||
<!-- 3rd party components -->
|
||||
<qr-code value="hello"></qr-code>
|
||||
<swiper>
|
||||
<ng-template swiperSlide>Slide 1</ng-template>
|
||||
</swiper>
|
||||
|
||||
<!-- Ionic components -->
|
||||
<ion-action-sheet></ion-action-sheet>
|
||||
|
||||
@@ -2,11 +2,10 @@ import { CommonModule } from '@angular/common'
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { QrCodeModule } from 'ng-qrcode'
|
||||
import { SwiperModule } from 'swiper/angular'
|
||||
import { PreloaderComponent } from './preloader.component'
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, IonicModule, QrCodeModule, SwiperModule],
|
||||
imports: [CommonModule, IonicModule, QrCodeModule],
|
||||
declarations: [PreloaderComponent],
|
||||
exports: [PreloaderComponent],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { Directive, HostListener, Input } from '@angular/core'
|
||||
import { LoadingController, ModalController } from '@ionic/angular'
|
||||
import { ErrorToastService } from '@start9labs/shared'
|
||||
|
||||
import { SnakePage } from '../../modals/snake/snake.page'
|
||||
import { PatchDbService } from '../../services/patch-db/patch-db.service'
|
||||
import { ApiService } from '../../services/api/embassy-api.service'
|
||||
|
||||
@Directive({
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<h1>
|
||||
<ion-text color="warning">Warning</ion-text>
|
||||
</h1>
|
||||
<div class="ion-text-left" [innerHTML]="params.message || '' | markdown"></div>
|
||||
@@ -1,18 +0,0 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { AlertComponent } from './alert.component'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule } from '@angular/router'
|
||||
import { MarkdownPipeModule } from '@start9labs/shared'
|
||||
|
||||
@NgModule({
|
||||
declarations: [AlertComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild([]),
|
||||
MarkdownPipeModule,
|
||||
],
|
||||
exports: [AlertComponent],
|
||||
})
|
||||
export class AlertComponentModule {}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Component, Input } from '@angular/core'
|
||||
import { BaseSlide } from '../wizard-types'
|
||||
|
||||
@Component({
|
||||
selector: 'alert',
|
||||
templateUrl: './alert.component.html',
|
||||
styleUrls: ['../app-wizard.component.scss'],
|
||||
})
|
||||
export class AlertComponent implements BaseSlide {
|
||||
@Input()
|
||||
params!: { message: string }
|
||||
|
||||
async load() {}
|
||||
|
||||
loading = false
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<div style="padding: 10px 0">
|
||||
<ion-title style="font-size: 32px">{{ params.title }}</ion-title>
|
||||
<div class="underline"></div>
|
||||
<ion-title>
|
||||
<i
|
||||
>{{ params.action | titlecase
|
||||
}}<span *ngIf="params.version"
|
||||
>: {{ params.version | displayEmver }}</span
|
||||
></i
|
||||
>
|
||||
</ion-title>
|
||||
</div>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button (click)="dismiss()">
|
||||
<ion-icon slot="icon-only" name="close"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<div style="padding: 36px; height: 100%">
|
||||
<swiper
|
||||
*ngIf="!error; else hasError"
|
||||
(swiper)="setSwiperInstance($event)"
|
||||
(slideNextTransitionStart)="loadSlide()"
|
||||
>
|
||||
<ng-template swiperSlide *ngFor="let slide of params.slides">
|
||||
<alert
|
||||
#components
|
||||
*ngIf="slide.selector === 'alert'"
|
||||
[params]="slide.params"
|
||||
></alert>
|
||||
<dependents
|
||||
#components
|
||||
*ngIf="slide.selector === 'dependents'"
|
||||
[params]="slide.params"
|
||||
(onSuccess)="next()"
|
||||
(onError)="setError($event)"
|
||||
></dependents>
|
||||
<complete
|
||||
#components
|
||||
*ngIf="slide.selector === 'complete'"
|
||||
[params]="slide.params"
|
||||
(onSuccess)="dismiss('success')"
|
||||
(onError)="setError($event)"
|
||||
></complete>
|
||||
</ng-template>
|
||||
</swiper>
|
||||
|
||||
<ng-template #hasError>
|
||||
<p>
|
||||
<ion-text color="danger">{{ error }}</ion-text>
|
||||
</p>
|
||||
</ng-template>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
<ion-footer>
|
||||
<ion-toolbar>
|
||||
<ng-container *ngIf="!initializing && swiper">
|
||||
<ion-buttons slot="end" class="ion-padding-end">
|
||||
<ion-button
|
||||
*ngIf="error; else noError"
|
||||
fill="solid"
|
||||
color="dark"
|
||||
(click)="dismiss()"
|
||||
class="enter-click btn-128"
|
||||
>
|
||||
Dismiss
|
||||
</ion-button>
|
||||
<ng-template #noError>
|
||||
<ion-button
|
||||
*ngIf="!currentSlide.loading && !swiper.isEnd"
|
||||
fill="solid"
|
||||
color="primary"
|
||||
(click)="next()"
|
||||
class="enter-click btn-128"
|
||||
[class.no-click]="currentSlide.loading"
|
||||
>
|
||||
{{
|
||||
currentIndex < swiper.slides.length - 2
|
||||
? 'Continue'
|
||||
: params.submitBtn
|
||||
}}
|
||||
</ion-button>
|
||||
</ng-template>
|
||||
</ion-buttons>
|
||||
</ng-container>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
@@ -1,26 +0,0 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { AppWizardComponent } from './app-wizard.component'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule } from '@angular/router'
|
||||
import { EmverPipesModule } from '@start9labs/shared'
|
||||
import { DependentsComponentModule } from './dependents/dependents.component.module'
|
||||
import { CompleteComponentModule } from './complete/complete.component.module'
|
||||
import { AlertComponentModule } from './alert/alert.component.module'
|
||||
import { SwiperModule } from 'swiper/angular'
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppWizardComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild([]),
|
||||
EmverPipesModule,
|
||||
DependentsComponentModule,
|
||||
CompleteComponentModule,
|
||||
AlertComponentModule,
|
||||
SwiperModule,
|
||||
],
|
||||
exports: [AppWizardComponent],
|
||||
})
|
||||
export class AppWizardComponentModule {}
|
||||
@@ -1,6 +0,0 @@
|
||||
.underline {
|
||||
margin: 6px 0 8px 16px;
|
||||
border-style: solid;
|
||||
border-width: 0px 0px 1px 0px;
|
||||
border-color: #404040;
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
QueryList,
|
||||
ViewChild,
|
||||
ViewChildren,
|
||||
} from '@angular/core'
|
||||
import { IonContent, ModalController } from '@ionic/angular'
|
||||
import { CompleteComponent } from './complete/complete.component'
|
||||
import { DependentsComponent } from './dependents/dependents.component'
|
||||
import { AlertComponent } from './alert/alert.component'
|
||||
import { WizardAction } from './wizard-types'
|
||||
import SwiperCore, { Swiper } from 'swiper'
|
||||
import { IonicSlides } from '@ionic/angular'
|
||||
import { BaseSlide } from './wizard-types'
|
||||
|
||||
SwiperCore.use([IonicSlides])
|
||||
|
||||
@Component({
|
||||
selector: 'app-wizard',
|
||||
templateUrl: './app-wizard.component.html',
|
||||
styleUrls: ['./app-wizard.component.scss'],
|
||||
})
|
||||
export class AppWizardComponent {
|
||||
@Input()
|
||||
params!: {
|
||||
action: WizardAction
|
||||
title: string
|
||||
slides: SlideDefinition[]
|
||||
submitBtn: string
|
||||
version?: string
|
||||
}
|
||||
|
||||
// content container so we can scroll to top between slide transitions
|
||||
@ViewChild(IonContent)
|
||||
content?: IonContent
|
||||
|
||||
swiper?: Swiper
|
||||
|
||||
//a slide component gives us hook into a slide. Allows us to call load when slide comes into view
|
||||
@ViewChildren('components')
|
||||
slideComponentsQL?: QueryList<BaseSlide>
|
||||
|
||||
get slideComponents(): BaseSlide[] {
|
||||
return this.slideComponentsQL?.toArray() || []
|
||||
}
|
||||
|
||||
get currentSlide(): BaseSlide {
|
||||
return this.slideComponents[this.currentIndex]
|
||||
}
|
||||
|
||||
get currentIndex(): number {
|
||||
return this.swiper?.activeIndex || NaN
|
||||
}
|
||||
|
||||
initializing = true
|
||||
error = ''
|
||||
|
||||
constructor(private readonly modalController: ModalController) {}
|
||||
|
||||
ionViewDidEnter() {
|
||||
this.initializing = false
|
||||
if (this.swiper) this.swiper.allowTouchMove = false
|
||||
this.loadSlide()
|
||||
}
|
||||
|
||||
setSwiperInstance(swiper: any) {
|
||||
this.swiper = swiper
|
||||
}
|
||||
|
||||
dismiss(role = 'cancelled') {
|
||||
this.modalController.dismiss(null, role)
|
||||
}
|
||||
|
||||
async next() {
|
||||
await this.content?.scrollToTop()
|
||||
this.swiper?.slideNext(500)
|
||||
}
|
||||
|
||||
setError(e: any) {
|
||||
this.error = e
|
||||
}
|
||||
|
||||
async loadSlide() {
|
||||
this.currentSlide.load()
|
||||
}
|
||||
}
|
||||
|
||||
export type SlideDefinition =
|
||||
| { selector: 'alert'; params: AlertComponent['params'] }
|
||||
| { selector: 'dependents'; params: DependentsComponent['params'] }
|
||||
| { selector: 'complete'; params: CompleteComponent['params'] }
|
||||
|
||||
export async function wizardModal(
|
||||
modalController: ModalController,
|
||||
params: AppWizardComponent['params'],
|
||||
): Promise<boolean> {
|
||||
const modal = await modalController.create({
|
||||
backdropDismiss: false,
|
||||
cssClass: 'wizard-modal',
|
||||
component: AppWizardComponent,
|
||||
componentProps: { params },
|
||||
})
|
||||
|
||||
await modal.present()
|
||||
return modal.onDidDismiss().then(({ role }) => role === 'success')
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
<div style="padding: 32px">
|
||||
<ion-spinner color="warning" name="lines"></ion-spinner>
|
||||
<p>{{ message }}</p>
|
||||
</div>
|
||||
@@ -1,12 +0,0 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { CompleteComponent } from './complete.component'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule } from '@angular/router'
|
||||
|
||||
@NgModule({
|
||||
declarations: [CompleteComponent],
|
||||
imports: [CommonModule, IonicModule, RouterModule.forChild([])],
|
||||
exports: [CompleteComponent],
|
||||
})
|
||||
export class CompleteComponentModule {}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core'
|
||||
import { capitalizeFirstLetter } from '@start9labs/shared'
|
||||
import { BaseSlide } from '../wizard-types'
|
||||
|
||||
@Component({
|
||||
selector: 'complete',
|
||||
templateUrl: './complete.component.html',
|
||||
styleUrls: ['../app-wizard.component.scss'],
|
||||
})
|
||||
export class CompleteComponent implements BaseSlide {
|
||||
@Input()
|
||||
params!: {
|
||||
verb: string // loader verb: '*stopping* ...'
|
||||
title: string
|
||||
Fn: () => Promise<any>
|
||||
}
|
||||
|
||||
@Output() onSuccess: EventEmitter<void> = new EventEmitter()
|
||||
@Output() onError: EventEmitter<string> = new EventEmitter()
|
||||
|
||||
message = ''
|
||||
|
||||
loading = true
|
||||
|
||||
async load() {
|
||||
this.message =
|
||||
capitalizeFirstLetter(this.params.verb || '') + ' ' + this.params.title
|
||||
try {
|
||||
await this.params.Fn()
|
||||
this.onSuccess.emit()
|
||||
} catch (e: any) {
|
||||
this.onError.emit(`Error: ${e.message || e}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<div *ngIf="loading; else loaded" style="padding: 32px">
|
||||
<ion-spinner color="warning" name="lines"></ion-spinner>
|
||||
<p>Checking for installed services which depend on {{ params.title }}...</p>
|
||||
</div>
|
||||
|
||||
<ng-template #loaded>
|
||||
<h1><ion-text color="warning">Warning</ion-text></h1>
|
||||
<p>{{ warningMessage }}</p>
|
||||
|
||||
<ng-container *ngIf="pkgs$ | async as pkgs">
|
||||
<ion-item-group>
|
||||
<ion-item-divider class="ion-padding-bottom">
|
||||
Affected Services
|
||||
</ion-item-divider>
|
||||
<ion-item *ngFor="let dep of breakages | keyvalue">
|
||||
<ion-thumbnail slot="start">
|
||||
<img alt="" [src]="pkgs[dep.key]['static-files'].icon" />
|
||||
</ion-thumbnail>
|
||||
<ion-label>
|
||||
{{ pkgs[dep.key].manifest.title }}
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
@@ -1,18 +0,0 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { DependentsComponent } from './dependents.component'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule } from '@angular/router'
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
|
||||
@NgModule({
|
||||
declarations: [DependentsComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild([]),
|
||||
SharedPipesModule,
|
||||
],
|
||||
exports: [DependentsComponent],
|
||||
})
|
||||
export class DependentsComponentModule {}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core'
|
||||
import { Breakages } from 'src/app/services/api/api.types'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { capitalizeFirstLetter, isEmptyObject } from '@start9labs/shared'
|
||||
import { BaseSlide } from '../wizard-types'
|
||||
|
||||
@Component({
|
||||
selector: 'dependents',
|
||||
templateUrl: './dependents.component.html',
|
||||
styleUrls: ['./dependents.component.scss', '../app-wizard.component.scss'],
|
||||
})
|
||||
export class DependentsComponent implements BaseSlide {
|
||||
@Input()
|
||||
params!: {
|
||||
title: string
|
||||
verb: string // *Uninstalling* will cause problems...
|
||||
Fn: () => Promise<Breakages>
|
||||
}
|
||||
|
||||
@Output() onSuccess: EventEmitter<void> = new EventEmitter()
|
||||
@Output() onError: EventEmitter<string> = new EventEmitter()
|
||||
|
||||
breakages?: Breakages
|
||||
warningMessage = ''
|
||||
|
||||
loading = true
|
||||
|
||||
readonly pkgs$ = this.patch.watch$('package-data')
|
||||
|
||||
constructor(private readonly patch: PatchDbService) {}
|
||||
|
||||
async load() {
|
||||
try {
|
||||
this.breakages = await this.params.Fn()
|
||||
if (this.breakages && !isEmptyObject(this.breakages)) {
|
||||
this.warningMessage =
|
||||
capitalizeFirstLetter(this.params.verb || '') +
|
||||
' ' +
|
||||
this.params.title +
|
||||
' will prohibit the following services from functioning properly.'
|
||||
} else {
|
||||
this.onSuccess.emit()
|
||||
}
|
||||
} catch (e: any) {
|
||||
this.onError.emit(
|
||||
`Error fetching dependent service information: ${e.message || e}`,
|
||||
)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
import { Inject, Injectable } from '@angular/core'
|
||||
import { exists } from '@start9labs/shared'
|
||||
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
||||
import { Manifest } from 'src/app/services/patch-db/data-model'
|
||||
import { ApiService } from '../../services/api/embassy-api.service'
|
||||
import { AppWizardComponent, SlideDefinition } from './app-wizard.component'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import { firstValueFrom } from 'rxjs'
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class WizardDefs {
|
||||
constructor(
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly config: ConfigService,
|
||||
@Inject(AbstractMarketplaceService)
|
||||
private readonly marketplaceService: MarketplaceService,
|
||||
) {}
|
||||
|
||||
update(values: {
|
||||
id: string
|
||||
title: string
|
||||
version: string
|
||||
installAlert?: string
|
||||
}): AppWizardComponent['params'] {
|
||||
const { id, title, version, installAlert } = values
|
||||
|
||||
const slides: Array<SlideDefinition | undefined> = [
|
||||
installAlert
|
||||
? {
|
||||
selector: 'alert',
|
||||
params: {
|
||||
message: installAlert,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
{
|
||||
selector: 'complete',
|
||||
params: {
|
||||
verb: 'beginning update for',
|
||||
title,
|
||||
Fn: () =>
|
||||
firstValueFrom(
|
||||
this.marketplaceService.installPackage({
|
||||
id,
|
||||
'version-spec': version ? `=${version}` : undefined,
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
]
|
||||
return {
|
||||
action: 'update',
|
||||
title,
|
||||
version,
|
||||
slides: slides.filter(exists),
|
||||
submitBtn: 'Begin Update',
|
||||
}
|
||||
}
|
||||
|
||||
downgrade(values: {
|
||||
id: string
|
||||
title: string
|
||||
version: string
|
||||
installAlert?: string
|
||||
}): AppWizardComponent['params'] {
|
||||
const { id, title, version, installAlert } = values
|
||||
|
||||
const slides: Array<SlideDefinition | undefined> = [
|
||||
installAlert
|
||||
? {
|
||||
selector: 'alert',
|
||||
params: {
|
||||
message: installAlert,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
{
|
||||
selector: 'complete',
|
||||
params: {
|
||||
verb: 'beginning downgrade for',
|
||||
title,
|
||||
Fn: () =>
|
||||
firstValueFrom(
|
||||
this.marketplaceService.installPackage({
|
||||
id,
|
||||
'version-spec': version ? `=${version}` : undefined,
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
return {
|
||||
action: 'downgrade',
|
||||
title,
|
||||
version,
|
||||
slides: slides.filter(exists),
|
||||
submitBtn: 'Begin Downgrade',
|
||||
}
|
||||
}
|
||||
|
||||
uninstall(values: {
|
||||
id: string
|
||||
title: string
|
||||
uninstallAlert?: string
|
||||
}): AppWizardComponent['params'] {
|
||||
const { id, title, uninstallAlert } = values
|
||||
|
||||
const slides: SlideDefinition[] = [
|
||||
{
|
||||
selector: 'alert',
|
||||
params: {
|
||||
message: uninstallAlert || defaultUninstallWarning(title),
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'complete',
|
||||
params: {
|
||||
verb: 'uninstalling',
|
||||
title,
|
||||
Fn: () => this.embassyApi.uninstallPackage({ id }),
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
return {
|
||||
action: 'uninstall',
|
||||
title,
|
||||
slides: slides.filter(exists),
|
||||
submitBtn: 'Uninstall Anyway',
|
||||
}
|
||||
}
|
||||
|
||||
stop(values: { id: string; title: string }): AppWizardComponent['params'] {
|
||||
const { title, id } = values
|
||||
|
||||
const slides: SlideDefinition[] = [
|
||||
{
|
||||
selector: 'complete',
|
||||
params: {
|
||||
verb: 'stopping',
|
||||
title,
|
||||
Fn: () => this.embassyApi.stopPackage({ id }),
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
return {
|
||||
action: 'stop',
|
||||
title,
|
||||
slides: slides.filter(exists),
|
||||
submitBtn: 'Stop Anyway',
|
||||
}
|
||||
}
|
||||
|
||||
configure(values: {
|
||||
manifest: Manifest
|
||||
config: object
|
||||
}): AppWizardComponent['params'] {
|
||||
const { manifest, config } = values
|
||||
const { id, title } = manifest
|
||||
|
||||
const slides: SlideDefinition[] = [
|
||||
{
|
||||
selector: 'dependents',
|
||||
params: {
|
||||
verb: 'saving config for',
|
||||
title,
|
||||
Fn: () => this.embassyApi.drySetPackageConfig({ id, config }),
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'complete',
|
||||
params: {
|
||||
verb: 'configuring',
|
||||
title,
|
||||
Fn: () => this.embassyApi.setPackageConfig({ id, config }),
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
return {
|
||||
action: 'configure',
|
||||
title,
|
||||
slides: slides.filter(exists),
|
||||
submitBtn: 'Configure Anyway',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const defaultUninstallWarning = (serviceName: string) =>
|
||||
`Uninstalling ${serviceName} will result in the deletion of its data.`
|
||||
@@ -1,11 +0,0 @@
|
||||
export type WizardAction =
|
||||
| 'update'
|
||||
| 'downgrade'
|
||||
| 'uninstall'
|
||||
| 'stop'
|
||||
| 'configure'
|
||||
|
||||
export interface BaseSlide {
|
||||
load: () => Promise<void>
|
||||
loading: boolean
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { SplitPaneTracker } from 'src/app/services/split-pane.service'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'badge-menu-button',
|
||||
@@ -14,6 +15,6 @@ export class BadgeMenuComponent {
|
||||
|
||||
constructor(
|
||||
private readonly splitPane: SplitPaneTracker,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
slot="end"
|
||||
[name]="connection.icon"
|
||||
class="icon"
|
||||
color="light"
|
||||
[color]="connection.iconColor"
|
||||
></ion-icon>
|
||||
<p style="margin: 8px 0; font-weight: 600">{{ connection.message }}</p>
|
||||
<ion-spinner
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { combineLatest, map, Observable, startWith, tap } from 'rxjs'
|
||||
import { Component } from '@angular/core'
|
||||
import { combineLatest, map, Observable } from 'rxjs'
|
||||
import { ConnectionService } from 'src/app/services/connection.service'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
|
||||
@Component({
|
||||
selector: 'connection-bar',
|
||||
@@ -14,8 +13,9 @@ export class ConnectionBarComponent {
|
||||
|
||||
readonly connection$: Observable<{
|
||||
message: string
|
||||
icon: string
|
||||
color: string
|
||||
icon: string
|
||||
iconColor: string
|
||||
dots: boolean
|
||||
}> = combineLatest([
|
||||
this.connectionService.networkConnected$,
|
||||
@@ -25,29 +25,29 @@ export class ConnectionBarComponent {
|
||||
if (!network)
|
||||
return {
|
||||
message: 'No Internet',
|
||||
color: 'danger',
|
||||
icon: 'cloud-offline-outline',
|
||||
color: 'dark',
|
||||
iconColor: 'dark',
|
||||
dots: false,
|
||||
}
|
||||
if (!websocket)
|
||||
return {
|
||||
message: 'Connecting',
|
||||
icon: 'cloud-offline-outline',
|
||||
color: 'warning',
|
||||
icon: 'cloud-offline-outline',
|
||||
iconColor: 'light',
|
||||
dots: true,
|
||||
}
|
||||
|
||||
return {
|
||||
message: 'Connected',
|
||||
icon: 'cloud-done',
|
||||
color: 'success',
|
||||
icon: 'cloud-done',
|
||||
iconColor: 'light',
|
||||
dots: false,
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
constructor(
|
||||
private readonly connectionService: ConnectionService,
|
||||
private readonly patch: PatchDbService,
|
||||
) {}
|
||||
constructor(private readonly connectionService: ConnectionService) {}
|
||||
}
|
||||
|
||||
@@ -214,15 +214,23 @@
|
||||
>
|
||||
<div class="nested-wrapper">
|
||||
<form-object
|
||||
[objectSpec]="
|
||||
spec.type === 'union'
|
||||
? spec.variants[$any(entry.value).controls[spec.tag.id].value]
|
||||
: spec.spec
|
||||
"
|
||||
*ngIf="spec.type === 'object'"
|
||||
[objectSpec]="spec.spec"
|
||||
[formGroup]="$any(entry.value)"
|
||||
[current]="current?.[entry.key]"
|
||||
[original]="original?.[entry.key]"
|
||||
[unionSpec]="spec.type === 'union' ? spec : undefined"
|
||||
(onExpand)="resize(entry.key)"
|
||||
(hasNewOptions)="setHasNew(entry.key)"
|
||||
></form-object>
|
||||
<form-object
|
||||
*ngIf="spec.type === 'union'"
|
||||
[objectSpec]="
|
||||
spec.variants[$any(entry.value).controls[spec.tag.id].value]
|
||||
"
|
||||
[formGroup]="$any(entry.value)"
|
||||
[current]="current?.[entry.key]"
|
||||
[original]="original?.[entry.key][spec.tag.id] === current?.[entry.key][spec.tag.id] ? original?.[entry.key] : undefined"
|
||||
[unionSpec]="spec"
|
||||
(onExpand)="resize(entry.key)"
|
||||
(hasNewOptions)="setHasNew(entry.key)"
|
||||
></form-object>
|
||||
@@ -366,6 +374,7 @@
|
||||
</ion-input>
|
||||
<ion-button
|
||||
strong
|
||||
fill="clear"
|
||||
slot="end"
|
||||
color="danger"
|
||||
(click)="presentAlertDelete(entry.key, i)"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component, Input, Output, EventEmitter } from '@angular/core'
|
||||
import {
|
||||
AbstractFormGroupDirective,
|
||||
FormArray,
|
||||
UntypedFormArray,
|
||||
UntypedFormGroup,
|
||||
} from '@angular/forms'
|
||||
@@ -105,10 +106,10 @@ export class FormObjectComponent {
|
||||
}
|
||||
|
||||
updateUnion(e: any): void {
|
||||
const primary = this.unionSpec?.tag.id
|
||||
const id = this.unionSpec?.tag.id
|
||||
|
||||
Object.keys(this.formGroup.controls).forEach(control => {
|
||||
if (control === primary) return
|
||||
if (control === id) return
|
||||
this.formGroup.removeControl(control)
|
||||
})
|
||||
|
||||
@@ -118,7 +119,7 @@ export class FormObjectComponent {
|
||||
)
|
||||
|
||||
Object.keys(unionGroup.controls).forEach(control => {
|
||||
if (control === primary) return
|
||||
if (control === id) return
|
||||
this.formGroup.addControl(control, unionGroup.controls[control])
|
||||
})
|
||||
|
||||
@@ -152,35 +153,6 @@ export class FormObjectComponent {
|
||||
this.presentAlertChangeWarning(key, spec, () => this.addListItem(key))
|
||||
}
|
||||
|
||||
addListItem(key: string, markDirty = true, val?: string): void {
|
||||
const arr = this.formGroup.get(key) as UntypedFormArray
|
||||
if (markDirty) arr.markAsDirty()
|
||||
const listSpec = this.objectSpec[key] as ValueSpecList
|
||||
const newItem = this.formService.getListItem(listSpec, val)
|
||||
|
||||
if (!newItem) return
|
||||
|
||||
const index = arr.length
|
||||
|
||||
newItem.markAllAsTouched()
|
||||
arr.insert(index, newItem)
|
||||
if (['object', 'union'].includes(listSpec.subtype)) {
|
||||
const displayAs = (listSpec.spec as ListValueSpecOf<'object'>)[
|
||||
'display-as'
|
||||
]
|
||||
this.objectListDisplay[key].push({
|
||||
height: '0px',
|
||||
expanded: false,
|
||||
displayAs: displayAs ? Mustache.render(displayAs, newItem.value) : '',
|
||||
})
|
||||
}
|
||||
|
||||
pauseFor(400).then(() => {
|
||||
const element = document.getElementById(this.getElementId(key, index))
|
||||
element?.parentElement?.scrollIntoView({ behavior: 'smooth' })
|
||||
})
|
||||
}
|
||||
|
||||
toggleExpandObject(key: string) {
|
||||
this.objectDisplay[key].expanded = !this.objectDisplay[key].expanded
|
||||
this.objectDisplay[key].height = this.objectDisplay[key].expanded
|
||||
@@ -327,6 +299,36 @@ export class FormObjectComponent {
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
private addListItem(key: string): void {
|
||||
const arr = this.formGroup.get(key) as UntypedFormArray
|
||||
const listSpec = this.objectSpec[key] as ValueSpecList
|
||||
const newItem = this.formService.getListItem(listSpec, undefined)!
|
||||
|
||||
const index = arr.length
|
||||
arr.insert(index, newItem)
|
||||
|
||||
if (['object', 'union'].includes(listSpec.subtype)) {
|
||||
const displayAs = (listSpec.spec as ListValueSpecOf<'object'>)[
|
||||
'display-as'
|
||||
]
|
||||
this.objectListDisplay[key].push({
|
||||
height: '0px',
|
||||
expanded: false,
|
||||
displayAs: displayAs ? Mustache.render(displayAs, newItem.value) : '',
|
||||
})
|
||||
}
|
||||
|
||||
this.onExpand.emit()
|
||||
|
||||
pauseFor(400).then(() => {
|
||||
const element = document.getElementById(this.getElementId(key, index))
|
||||
element?.parentElement?.scrollIntoView({ behavior: 'smooth' })
|
||||
})
|
||||
|
||||
arr.markAsDirty()
|
||||
newItem.markAllAsTouched()
|
||||
}
|
||||
|
||||
private deleteListItem(key: string, index: number, markDirty = true): void {
|
||||
if (this.objectListDisplay[key])
|
||||
this.objectListDisplay[key][index].height = '0px'
|
||||
@@ -340,19 +342,25 @@ export class FormObjectComponent {
|
||||
}
|
||||
|
||||
private updateEnumList(key: string, current: string[], updated: string[]) {
|
||||
this.formGroup.get(key)?.markAsDirty()
|
||||
const arr = this.formGroup.get(key) as FormArray
|
||||
|
||||
for (let i = current.length - 1; i >= 0; i--) {
|
||||
if (!updated.includes(current[i])) {
|
||||
this.deleteListItem(key, i, false)
|
||||
arr.removeAt(i)
|
||||
}
|
||||
}
|
||||
|
||||
const listSpec = this.objectSpec[key] as ValueSpecList
|
||||
|
||||
updated.forEach(val => {
|
||||
if (!current.includes(val)) {
|
||||
this.addListItem(key, false, val)
|
||||
const newItem = this.formService.getListItem(listSpec, val)!
|
||||
arr.insert(arr.length, newItem)
|
||||
}
|
||||
})
|
||||
|
||||
arr.markAsDirty()
|
||||
arr.markAllAsTouched()
|
||||
}
|
||||
|
||||
private getDocSize(key: string, index = 0): string {
|
||||
|
||||
@@ -2,7 +2,8 @@ import { Injectable } from '@angular/core'
|
||||
import { endWith, Observable } from 'rxjs'
|
||||
import { filter, map, pairwise } from 'rxjs/operators'
|
||||
import { exists } from '@start9labs/shared'
|
||||
import { PatchDbService } from '../../../services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class NotificationsToastService extends Observable<boolean> {
|
||||
@@ -15,7 +16,7 @@ export class NotificationsToastService extends Observable<boolean> {
|
||||
endWith(false),
|
||||
)
|
||||
|
||||
constructor(private readonly patch: PatchDbService) {
|
||||
constructor(private readonly patch: PatchDB<DataModel>) {
|
||||
super(subscriber => this.stream$.subscribe(subscriber))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Injectable } from '@angular/core'
|
||||
import { endWith, Observable } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
import { Emver } from '@start9labs/shared'
|
||||
|
||||
import { PatchDbService } from '../../../services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { ConfigService } from '../../../services/config.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
// Watch for connection status
|
||||
@Injectable({ providedIn: 'root' })
|
||||
@@ -15,7 +15,7 @@ export class RefreshAlertService extends Observable<boolean> {
|
||||
)
|
||||
|
||||
constructor(
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly emver: Emver,
|
||||
private readonly config: ConfigService,
|
||||
) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { endWith, Observable } from 'rxjs'
|
||||
import { distinctUntilChanged, filter } from 'rxjs/operators'
|
||||
import { PatchDbService } from '../../../services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class UpdateToastService extends Observable<boolean> {
|
||||
@@ -9,7 +10,7 @@ export class UpdateToastService extends Observable<boolean> {
|
||||
.watch$('server-info', 'status-info', 'updated')
|
||||
.pipe(distinctUntilChanged(), filter(Boolean), endWith(false))
|
||||
|
||||
constructor(private readonly patch: PatchDbService) {
|
||||
constructor(private readonly patch: PatchDB<DataModel>) {
|
||||
super(subscriber => this.stream$.subscribe(subscriber))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,11 @@ import {
|
||||
} from '@start9labs/shared'
|
||||
import { DependentInfo } from 'src/app/types/dependent-info'
|
||||
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import {
|
||||
DataModel,
|
||||
PackageDataEntry,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { UntypedFormGroup } from '@angular/forms'
|
||||
import {
|
||||
convertValuesRecursive,
|
||||
@@ -57,7 +60,7 @@ export class AppConfigPage {
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly formService: FormService,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
|
||||
@@ -7,8 +7,9 @@ import {
|
||||
import { getErrorMessage } from '@start9labs/shared'
|
||||
import { BackupInfo } from 'src/app/services/api/api.types'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { AppRecoverOption } from './to-options.pipe'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'app-recover-select',
|
||||
@@ -30,7 +31,7 @@ export class AppRecoverSelectPage {
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
dismiss() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ModalController } from '@ionic/angular'
|
||||
import { filter, map, take } from 'rxjs/operators'
|
||||
import { PackageState } from 'src/app/services/patch-db/data-model'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { DataModel, PackageState } from 'src/app/services/patch-db/data-model'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
|
||||
@Component({
|
||||
selector: 'backup-select',
|
||||
@@ -22,7 +22,7 @@ export class BackupSelectPage {
|
||||
|
||||
constructor(
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
@@ -7,9 +7,10 @@ import {
|
||||
ModalController,
|
||||
NavController,
|
||||
} from '@ionic/angular'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
Action,
|
||||
DataModel,
|
||||
PackageDataEntry,
|
||||
PackageMainStatus,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
@@ -36,7 +37,7 @@ export class AppActionsPage {
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly navCtrl: NavController,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
async handleAction(
|
||||
@@ -197,10 +198,10 @@ export class AppActionsPage {
|
||||
})
|
||||
|
||||
setTimeout(() => successModal.present(), 500)
|
||||
return false
|
||||
return true // needed to dismiss original modal/alert
|
||||
} catch (e: any) {
|
||||
this.errToast.present(e)
|
||||
return false
|
||||
return false // don't dismiss original modal/alert
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ import { ModalController, ToastController } from '@ionic/angular'
|
||||
import { getPkgId, copyToClipboard } from '@start9labs/shared'
|
||||
import { getUiInterfaceKey } from 'src/app/services/config.service'
|
||||
import {
|
||||
DataModel,
|
||||
InstalledPackageDataEntry,
|
||||
InterfaceDef,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { QRComponent } from 'src/app/components/qr/qr.component'
|
||||
import { getPackage } from '../../../util/get-package-data'
|
||||
|
||||
@@ -28,7 +29,7 @@ export class AppInterfacesPage {
|
||||
|
||||
constructor(
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
DataModel,
|
||||
PackageDataEntry,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { Observable } from 'rxjs'
|
||||
import { filter, map, switchMap, take, takeUntil, tap } from 'rxjs/operators'
|
||||
import { isEmptyObject, exists, DestroyService } from '@start9labs/shared'
|
||||
@@ -22,7 +25,7 @@ export class AppListPage {
|
||||
constructor(
|
||||
private readonly api: ApiService,
|
||||
private readonly destroy$: DestroyService,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
get empty(): boolean {
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { Observable } from 'rxjs'
|
||||
import { filter, map, startWith } from 'rxjs/operators'
|
||||
import { PackageDataEntry } from '../../../services/patch-db/data-model'
|
||||
import {
|
||||
DataModel,
|
||||
PackageDataEntry,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { getPackageInfo, PkgInfo } from '../../../util/get-package-info'
|
||||
import { PatchDbService } from '../../../services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
|
||||
@Pipe({
|
||||
name: 'packageInfo',
|
||||
})
|
||||
export class PackageInfoPipe implements PipeTransform {
|
||||
constructor(private readonly patch: PatchDbService) {}
|
||||
constructor(private readonly patch: PatchDB<DataModel>) {}
|
||||
|
||||
transform(pkg: PackageDataEntry): Observable<PkgInfo> {
|
||||
return this.patch
|
||||
|
||||
@@ -10,8 +10,11 @@ import {
|
||||
} from '@ionic/angular'
|
||||
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 { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
DataModel,
|
||||
PackageMainStatus,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import {
|
||||
DestroyService,
|
||||
ErrorToastService,
|
||||
@@ -52,7 +55,7 @@ export class AppPropertiesPage {
|
||||
private readonly toastCtrl: ToastController,
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly navCtrl: NavController,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly destroy$: DestroyService,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Routes, RouterModule } from '@angular/router'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { AppShowPage } from './app-show.page'
|
||||
import { EmverPipesModule } from '@start9labs/shared'
|
||||
import { AppWizardComponentModule } from 'src/app/components/app-wizard/app-wizard.component.module'
|
||||
import { StatusComponentModule } from 'src/app/components/status/status.component.module'
|
||||
import { AppConfigPageModule } from 'src/app/modals/app-config/app-config.module'
|
||||
import { LaunchablePipeModule } from 'src/app/pipes/launchable/launchable.module'
|
||||
@@ -51,7 +50,6 @@ const routes: Routes = [
|
||||
StatusComponentModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
AppWizardComponentModule,
|
||||
AppConfigPageModule,
|
||||
EmverPipesModule,
|
||||
LaunchablePipeModule,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
|
||||
import { NavController } from '@ionic/angular'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
DataModel,
|
||||
PackageDataEntry,
|
||||
PackageMainStatus,
|
||||
PackageState,
|
||||
@@ -62,7 +63,7 @@ export class AppShowPage {
|
||||
constructor(
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly navCtrl: NavController,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
@Inject(AbstractMarketplaceService)
|
||||
private readonly marketplaceService: MarketplaceService,
|
||||
) {}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
removeTrailingSlash,
|
||||
} from '@start9labs/shared'
|
||||
import {
|
||||
DataModel,
|
||||
PackageDataEntry,
|
||||
UIMarketplaceData,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
@@ -16,7 +17,8 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { from, map, Observable } from 'rxjs'
|
||||
import { Marketplace } from '@start9labs/marketplace'
|
||||
import { ActionMarketplaceComponent } from 'src/app/modals/action-marketplace/action-marketplace.component'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
|
||||
export interface Button {
|
||||
title: string
|
||||
description: string
|
||||
@@ -38,7 +40,7 @@ export class ToButtonsPipe implements PipeTransform {
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly modalService: ModalService,
|
||||
private readonly apiService: ApiService,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
transform(
|
||||
|
||||
@@ -4,12 +4,13 @@ import { NavController } from '@ionic/angular'
|
||||
import { combineLatest, Observable } from 'rxjs'
|
||||
import { filter, map, startWith } from 'rxjs/operators'
|
||||
import {
|
||||
DataModel,
|
||||
DependencyError,
|
||||
DependencyErrorType,
|
||||
PackageDataEntry,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { DependentInfo } from 'src/app/types/dependent-info'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { ModalService } from 'src/app/services/modal.service'
|
||||
|
||||
export interface DependencyInfo {
|
||||
@@ -27,7 +28,7 @@ export interface DependencyInfo {
|
||||
})
|
||||
export class ToDependenciesPipe implements PipeTransform {
|
||||
constructor(
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly navCtrl: NavController,
|
||||
private readonly modalService: ModalService,
|
||||
) {}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import {
|
||||
DataModel,
|
||||
HealthCheckResult,
|
||||
PackageDataEntry,
|
||||
PackageMainStatus,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { exists, isEmptyObject } from '@start9labs/shared'
|
||||
import { filter, map, startWith } from 'rxjs/operators'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { Observable } from 'rxjs'
|
||||
|
||||
@Pipe({
|
||||
name: 'toHealthChecks',
|
||||
})
|
||||
export class ToHealthChecksPipe implements PipeTransform {
|
||||
constructor(private readonly patch: PatchDbService) {}
|
||||
constructor(private readonly patch: PatchDB<DataModel>) {}
|
||||
|
||||
transform(
|
||||
pkg: PackageDataEntry,
|
||||
|
||||
@@ -5,9 +5,10 @@ import { debounce, ErrorToastService } from '@start9labs/shared'
|
||||
import * as yaml from 'js-yaml'
|
||||
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 { PatchDB } from 'patch-db-client'
|
||||
import { getProjectId } from 'src/app/util/get-project-id'
|
||||
import { GenericFormPage } from '../../../modals/generic-form/generic-form.page'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'dev-config',
|
||||
@@ -24,12 +25,12 @@ export class DevConfigPage {
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly patchDb: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly api: ApiService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.patchDb
|
||||
this.patch
|
||||
.watch$('ui', 'dev', this.projectId, 'config')
|
||||
.pipe(filter(Boolean), take(1))
|
||||
.subscribe(config => {
|
||||
|
||||
@@ -8,8 +8,9 @@ import {
|
||||
ErrorToastService,
|
||||
MarkdownComponent,
|
||||
} from '@start9labs/shared'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { getProjectId } from 'src/app/util/get-project-id'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'dev-instructions',
|
||||
@@ -26,12 +27,12 @@ export class DevInstructionsPage {
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly patchDb: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly api: ApiService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.patchDb
|
||||
this.patch
|
||||
.watch$('ui', 'dev', this.projectId, 'instructions')
|
||||
.pipe(filter(Boolean), take(1))
|
||||
.subscribe(config => {
|
||||
|
||||
@@ -2,8 +2,9 @@ import { Component } from '@angular/core'
|
||||
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 { PatchDB } from 'patch-db-client'
|
||||
import { getProjectId } from 'src/app/util/get-project-id'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'dev-manifest',
|
||||
@@ -17,11 +18,11 @@ export class DevManifestPage {
|
||||
|
||||
constructor(
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly patchDb: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.patchDb
|
||||
this.patch
|
||||
.watch$('ui', 'dev', this.projectId)
|
||||
.pipe(take(1))
|
||||
.subscribe(devData => {
|
||||
|
||||
@@ -10,12 +10,12 @@ import {
|
||||
GenericInputComponent,
|
||||
GenericInputOptions,
|
||||
} from 'src/app/modals/generic-input/generic-input.component'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
||||
import * as yaml from 'js-yaml'
|
||||
import { v4 } from 'uuid'
|
||||
import { DevData } from 'src/app/services/patch-db/data-model'
|
||||
import { DataModel, DevData } from 'src/app/services/patch-db/data-model'
|
||||
import { DestroyService, ErrorToastService } from '@start9labs/shared'
|
||||
import { takeUntil } from 'rxjs/operators'
|
||||
|
||||
@@ -35,7 +35,7 @@ export class DeveloperListPage {
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly destroy$: DestroyService,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly actionCtrl: ActionSheetController,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ import { ActivatedRoute } from '@angular/router'
|
||||
import { LoadingController, ModalController } from '@ionic/angular'
|
||||
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 { PatchDB } from 'patch-db-client'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ErrorToastService } from '@start9labs/shared'
|
||||
import { getProjectId } from 'src/app/util/get-project-id'
|
||||
import { DevProjectData } from 'src/app/services/patch-db/data-model'
|
||||
import { DataModel, DevProjectData } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'developer-menu',
|
||||
@@ -25,7 +25,7 @@ export class DeveloperMenuPage {
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly api: ApiService,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
async openBasicInfoModal(data: DevProjectData) {
|
||||
|
||||
@@ -6,7 +6,6 @@ ion-card-title {
|
||||
}
|
||||
|
||||
ion-item {
|
||||
--border-radius: 6px;
|
||||
--border-style: solid;
|
||||
--border-width: 1px;
|
||||
--border-color: var(--ion-color-light);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { map } from 'rxjs/operators'
|
||||
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { ConnectionService } from 'src/app/services/connection.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-list',
|
||||
@@ -23,7 +24,7 @@ export class MarketplaceListPage {
|
||||
.pipe(map(({ name }) => name))
|
||||
|
||||
constructor(
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly marketplaceService: AbstractMarketplaceService,
|
||||
private readonly connectionService: ConnectionService,
|
||||
) {}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from '@start9labs/marketplace'
|
||||
import { Emver, ErrorToastService, isEmptyObject } from '@start9labs/shared'
|
||||
import {
|
||||
DataModel,
|
||||
PackageDataEntry,
|
||||
PackageState,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
@@ -19,7 +20,7 @@ import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import { hasCurrentDeps } from 'src/app/util/has-deps'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { Breakages } from 'src/app/services/api/api.types'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { getAllPackages } from 'src/app/util/get-package-data'
|
||||
import { firstValueFrom } from 'rxjs'
|
||||
|
||||
@@ -49,7 +50,7 @@ export class MarketplaceShowControlsComponent {
|
||||
private readonly emver: Emver,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
get localVersion(): string {
|
||||
|
||||
@@ -14,8 +14,6 @@ import {
|
||||
AdditionalModule,
|
||||
DependenciesModule,
|
||||
} from '@start9labs/marketplace'
|
||||
import { AppWizardComponentModule } from 'src/app/components/app-wizard/app-wizard.component.module'
|
||||
|
||||
import { MarketplaceStatusModule } from '../marketplace-status/marketplace-status.module'
|
||||
import { MarketplaceShowPage } from './marketplace-show.page'
|
||||
import { MarketplaceShowHeaderComponent } from './marketplace-show-header/marketplace-show-header.component'
|
||||
@@ -39,7 +37,6 @@ const routes: Routes = [
|
||||
EmverPipesModule,
|
||||
MarkdownPipeModule,
|
||||
MarketplaceStatusModule,
|
||||
AppWizardComponentModule,
|
||||
PackageModule,
|
||||
AboutModule,
|
||||
DependenciesModule,
|
||||
|
||||
@@ -5,10 +5,10 @@ import {
|
||||
MarketplacePkg,
|
||||
AbstractMarketplaceService,
|
||||
} from '@start9labs/marketplace'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { BehaviorSubject, Observable, of } from 'rxjs'
|
||||
import { catchError, filter, shareReplay, switchMap } from 'rxjs/operators'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-show',
|
||||
@@ -40,7 +40,7 @@ export class MarketplaceShowPage {
|
||||
constructor(
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly marketplaceService: AbstractMarketplaceService,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ import {
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { ErrorToastService } from '@start9labs/shared'
|
||||
import { BackupReportPage } from 'src/app/modals/backup-report/backup-report.page'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'notifications',
|
||||
@@ -36,7 +37,7 @@ export class NotificationsPage {
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'lan',
|
||||
@@ -14,7 +15,7 @@ export class LANPage {
|
||||
|
||||
constructor(
|
||||
private readonly config: ConfigService,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
installCert(): void {
|
||||
|
||||
@@ -11,9 +11,12 @@ import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ValueSpecObject } from 'src/app/pkg-config/config-types'
|
||||
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
||||
import { PatchDbService } from '../../../services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { v4 } from 'uuid'
|
||||
import { UIMarketplaceData } from '../../../services/patch-db/data-model'
|
||||
import {
|
||||
DataModel,
|
||||
UIMarketplaceData,
|
||||
} from '../../../services/patch-db/data-model'
|
||||
import { ConfigService } from '../../../services/config.service'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import {
|
||||
@@ -50,7 +53,7 @@ export class MarketplacesPage {
|
||||
@Inject(AbstractMarketplaceService)
|
||||
private readonly marketplaceService: MarketplaceService,
|
||||
private readonly config: ConfigService,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly destroy$: DestroyService,
|
||||
private readonly alertCtrl: AlertController,
|
||||
) {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
LoadingController,
|
||||
ModalController,
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
ServerNameInfo,
|
||||
ServerNameService,
|
||||
} from 'src/app/services/server-name.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'preferences',
|
||||
@@ -36,7 +37,7 @@ export class PreferencesPage {
|
||||
private readonly api: ApiService,
|
||||
private readonly toastCtrl: ToastController,
|
||||
private readonly localStorageService: LocalStorageService,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly serverNameService: ServerNameService,
|
||||
readonly serverConfig: ServerConfigService,
|
||||
) {}
|
||||
|
||||
@@ -4,9 +4,12 @@ import {
|
||||
Pipe,
|
||||
PipeTransform,
|
||||
} from '@angular/core'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { filter, take } from 'rxjs/operators'
|
||||
import { PackageMainStatus } from 'src/app/services/patch-db/data-model'
|
||||
import {
|
||||
DataModel,
|
||||
PackageMainStatus,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { Observable } from 'rxjs'
|
||||
|
||||
@Component({
|
||||
@@ -26,7 +29,7 @@ export class BackingUpComponent {
|
||||
|
||||
PackageMainStatus = PackageMainStatus
|
||||
|
||||
constructor(private readonly patch: PatchDbService) {}
|
||||
constructor(private readonly patch: PatchDB<DataModel>) {}
|
||||
}
|
||||
|
||||
@Pipe({
|
||||
@@ -44,5 +47,5 @@ export class PkgMainStatusPipe implements PipeTransform {
|
||||
)
|
||||
}
|
||||
|
||||
constructor(private readonly patch: PatchDbService) {}
|
||||
constructor(private readonly patch: PatchDB<DataModel>) {}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
GenericInputComponent,
|
||||
GenericInputOptions,
|
||||
} from 'src/app/modals/generic-input/generic-input.component'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { skip, takeUntil } from 'rxjs/operators'
|
||||
import { MappedBackupTarget } from 'src/app/types/mapped-backup-target'
|
||||
import * as argon2 from '@start9labs/argon2'
|
||||
@@ -21,6 +21,7 @@ import { BackupSelectPage } from 'src/app/modals/backup-select/backup-select.pag
|
||||
import { EOSService } from 'src/app/services/eos.service'
|
||||
import { DestroyService } from '@start9labs/shared'
|
||||
import { getServerInfo } from 'src/app/util/get-server-info'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'server-backup',
|
||||
@@ -40,7 +41,7 @@ export class ServerBackupPage {
|
||||
private readonly navCtrl: NavController,
|
||||
private readonly destroy$: DestroyService,
|
||||
private readonly eosService: EOSService,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { FormsModule } from '@angular/forms'
|
||||
import { TextSpinnerComponentModule } from '@start9labs/shared'
|
||||
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
||||
import { OSUpdatePageModule } from 'src/app/modals/os-update/os-update.page.module'
|
||||
import { BackupColorPipeModule } from 'src/app/pipes/backup-color/backup-color.module'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -24,6 +25,7 @@ const routes: Routes = [
|
||||
TextSpinnerComponentModule,
|
||||
BadgeMenuComponentModule,
|
||||
OSUpdatePageModule,
|
||||
BackupColorPipeModule,
|
||||
],
|
||||
declarations: [ServerShowPage],
|
||||
})
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<p *ngIf="button.title === 'Create Backup'">
|
||||
<ng-container *ngIf="server['status-info'] as statusInfo">
|
||||
<ion-text
|
||||
color="warning"
|
||||
[color]="server['last-backup'] | backupColor"
|
||||
*ngIf="!statusInfo['backup-progress'] && !statusInfo['update-progress']"
|
||||
>
|
||||
Last Backup: {{ server['last-backup'] ? (server['last-backup']
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@ionic/angular'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { ServerNameService } from 'src/app/services/server-name.service'
|
||||
import { Observable, of } from 'rxjs'
|
||||
import { filter, take, tap } from 'rxjs/operators'
|
||||
@@ -17,6 +17,7 @@ import { LocalStorageService } from 'src/app/services/local-storage.service'
|
||||
import { OSUpdatePage } from 'src/app/modals/os-update/os-update.page'
|
||||
import { getAllPackages } from '../../../util/get-package-data'
|
||||
import { AuthService } from 'src/app/services/auth.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'server-show',
|
||||
@@ -40,7 +41,7 @@ export class ServerShowPage {
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly navCtrl: NavController,
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly eosService: EOSService,
|
||||
private readonly localStorageService: LocalStorageService,
|
||||
private readonly serverNameService: ServerNameService,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { ToastController } from '@ionic/angular'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { copyToClipboard } from '@start9labs/shared'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'server-specs',
|
||||
@@ -15,7 +16,7 @@ export class ServerSpecsPage {
|
||||
|
||||
constructor(
|
||||
private readonly toastCtrl: ToastController,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly config: ConfigService,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { BackupColorPipe } from './backup-color.pipe'
|
||||
|
||||
@NgModule({
|
||||
declarations: [BackupColorPipe],
|
||||
exports: [BackupColorPipe],
|
||||
})
|
||||
export class BackupColorPipeModule {}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
|
||||
@Pipe({
|
||||
name: 'backupColor',
|
||||
})
|
||||
export class BackupColorPipe implements PipeTransform {
|
||||
transform(lastBackup: string | null): 'success' | 'warning' | 'danger' {
|
||||
if (!lastBackup) return 'danger'
|
||||
|
||||
const currentDate = new Date().valueOf()
|
||||
const backupDate = new Date(lastBackup).valueOf()
|
||||
const diff = currentDate - backupDate
|
||||
const week = 604800000
|
||||
|
||||
if (diff <= week) {
|
||||
return 'success'
|
||||
} else if (diff > week && diff <= week * 2) {
|
||||
return 'warning'
|
||||
} else {
|
||||
return 'danger'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -679,7 +679,7 @@ export module Mock {
|
||||
manifest: {
|
||||
...Mock.MockManifestBitcoind,
|
||||
'release-notes':
|
||||
'For a complete list of changes, please visit <a href="https://bitcoincore.org/en/releases/0.21.0/">https://bitcoincore.org/en/releases/0.21.0/</a><br /><ul><li>Taproot!</li><li>New RPCs</li><li>Experimental Descriptor Wallets</li></ul>',
|
||||
'For a complete list of changes, please visit <a href="https://bitcoincore.org/en/releases/0.21.0/" target="_blank">https://bitcoincore.org/en/releases/0.21.0/</a><br />Or in [markdown](https://bitcoincore.org/en/releases/0.21.0/)<ul><li>Taproot!</li><li>New RPCs</li><li>Experimental Descriptor Wallets</li></ul>',
|
||||
},
|
||||
categories: ['bitcoin', 'cryptocurrency'],
|
||||
versions: ['0.19.0', '0.20.0', '0.21.0'],
|
||||
@@ -1469,6 +1469,14 @@ export module Mock {
|
||||
masked: false,
|
||||
copyable: true,
|
||||
},
|
||||
'private-domain': {
|
||||
name: 'Private Domain',
|
||||
type: 'string',
|
||||
description: 'the private address of the node',
|
||||
nullable: false,
|
||||
masked: true,
|
||||
copyable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1726,7 +1734,10 @@ export module Mock {
|
||||
rpcuser: '123',
|
||||
rulemakers: [],
|
||||
},
|
||||
'bitcoin-node': undefined,
|
||||
'bitcoin-node': {
|
||||
type: 'external',
|
||||
'public-domain': 'hello.com',
|
||||
},
|
||||
port: 20,
|
||||
rpcallowip: undefined,
|
||||
rpcauth: ['matt: 8273gr8qwoidm1uid91jeh8y23gdio1kskmwejkdnm'],
|
||||
|
||||
@@ -16,8 +16,8 @@ export module RR {
|
||||
|
||||
export type GetDumpRes = Dump<DataModel>
|
||||
|
||||
export type SetDBValueReq = WithExpire<{ pointer: string; value: any }> // db.put.ui
|
||||
export type SetDBValueRes = WithRevision<null>
|
||||
export type SetDBValueReq = { pointer: string; value: any } // db.put.ui
|
||||
export type SetDBValueRes = null
|
||||
|
||||
// auth
|
||||
|
||||
@@ -44,8 +44,8 @@ export module RR {
|
||||
export type GetServerMetricsReq = {} // server.metrics
|
||||
export type GetServerMetricsRes = Metrics
|
||||
|
||||
export type UpdateServerReq = WithExpire<{ 'marketplace-url': string }> // server.update
|
||||
export type UpdateServerRes = WithRevision<'updating' | 'no-updates'>
|
||||
export type UpdateServerReq = { 'marketplace-url': string } // server.update
|
||||
export type UpdateServerRes = 'updating' | 'no-updates'
|
||||
|
||||
export type RestartServerReq = {} // server.restart
|
||||
export type RestartServerRes = null
|
||||
@@ -64,8 +64,8 @@ export module RR {
|
||||
sessions: { [hash: string]: Session }
|
||||
}
|
||||
|
||||
export type KillSessionsReq = WithExpire<{ ids: string[] }> // sessions.kill
|
||||
export type KillSessionsRes = WithRevision<null>
|
||||
export type KillSessionsReq = { ids: string[] } // sessions.kill
|
||||
export type KillSessionsRes = null
|
||||
|
||||
// password
|
||||
|
||||
@@ -74,11 +74,11 @@ export module RR {
|
||||
|
||||
// notification
|
||||
|
||||
export type GetNotificationsReq = WithExpire<{
|
||||
export type GetNotificationsReq = {
|
||||
before?: number
|
||||
limit?: number
|
||||
}> // notification.list
|
||||
export type GetNotificationsRes = WithRevision<ServerNotification<number>[]>
|
||||
} // notification.list
|
||||
export type GetNotificationsRes = ServerNotification<number>[]
|
||||
|
||||
export type DeleteNotificationReq = { id: number } // notification.delete
|
||||
export type DeleteNotificationRes = null
|
||||
@@ -96,8 +96,8 @@ export module RR {
|
||||
ssids: {
|
||||
[ssid: string]: number
|
||||
}
|
||||
connected?: string
|
||||
country: string
|
||||
connected: string | null
|
||||
country: string | null
|
||||
ethernet: boolean
|
||||
'available-wifi': AvailableWifi[]
|
||||
}
|
||||
@@ -151,14 +151,14 @@ export module RR {
|
||||
export type GetBackupInfoReq = { 'target-id': string; password: string } // backup.target.info
|
||||
export type GetBackupInfoRes = BackupInfo
|
||||
|
||||
export type CreateBackupReq = WithExpire<{
|
||||
export type CreateBackupReq = {
|
||||
// backup.create
|
||||
'target-id': string
|
||||
'package-ids': string[]
|
||||
'old-password': string | null
|
||||
password: string
|
||||
}>
|
||||
export type CreateBackupRes = WithRevision<null>
|
||||
}
|
||||
export type CreateBackupRes = null
|
||||
|
||||
// package
|
||||
|
||||
@@ -175,13 +175,13 @@ export module RR {
|
||||
export type GetPackageMetricsReq = { id: string } // package.metrics
|
||||
export type GetPackageMetricsRes = Metric
|
||||
|
||||
export type InstallPackageReq = WithExpire<{
|
||||
export type InstallPackageReq = {
|
||||
id: string
|
||||
'version-spec'?: string
|
||||
'version-priority'?: 'min' | 'max'
|
||||
'marketplace-url': string
|
||||
}> // package.install
|
||||
export type InstallPackageRes = WithRevision<null>
|
||||
} // package.install
|
||||
export type InstallPackageRes = null
|
||||
|
||||
export type DryUpdatePackageReq = { id: string; version: string } // package.update.dry
|
||||
export type DryUpdatePackageRes = Breakages
|
||||
@@ -192,17 +192,17 @@ export module RR {
|
||||
export type DrySetPackageConfigReq = { id: string; config: object } // package.config.set.dry
|
||||
export type DrySetPackageConfigRes = Breakages
|
||||
|
||||
export type SetPackageConfigReq = WithExpire<DrySetPackageConfigReq> // package.config.set
|
||||
export type SetPackageConfigRes = WithRevision<null>
|
||||
export type SetPackageConfigReq = DrySetPackageConfigReq // package.config.set
|
||||
export type SetPackageConfigRes = null
|
||||
|
||||
export type RestorePackagesReq = WithExpire<{
|
||||
export type RestorePackagesReq = {
|
||||
// package.backup.restore
|
||||
ids: string[]
|
||||
'target-id': string
|
||||
'old-password': string | null
|
||||
password: string
|
||||
}>
|
||||
export type RestorePackagesRes = WithRevision<null>
|
||||
}
|
||||
export type RestorePackagesRes = null
|
||||
|
||||
export type ExecutePackageActionReq = {
|
||||
id: string
|
||||
@@ -211,20 +211,20 @@ export module RR {
|
||||
} // package.action
|
||||
export type ExecutePackageActionRes = ActionResponse
|
||||
|
||||
export type StartPackageReq = WithExpire<{ id: string }> // package.start
|
||||
export type StartPackageRes = WithRevision<null>
|
||||
export type StartPackageReq = { id: string } // package.start
|
||||
export type StartPackageRes = null
|
||||
|
||||
export type RestartPackageReq = WithExpire<{ id: string }> // package.restart
|
||||
export type RestartPackageRes = WithRevision<null>
|
||||
export type RestartPackageReq = { id: string } // package.restart
|
||||
export type RestartPackageRes = null
|
||||
|
||||
export type StopPackageReq = WithExpire<{ id: string }> // package.stop
|
||||
export type StopPackageRes = WithRevision<null>
|
||||
export type StopPackageReq = { id: string } // package.stop
|
||||
export type StopPackageRes = null
|
||||
|
||||
export type UninstallPackageReq = WithExpire<{ id: string }> // package.uninstall
|
||||
export type UninstallPackageRes = WithRevision<null>
|
||||
export type UninstallPackageReq = { id: string } // package.uninstall
|
||||
export type UninstallPackageRes = null
|
||||
|
||||
export type DeleteRecoveredPackageReq = { id: string } // package.delete-recovered
|
||||
export type DeleteRecoveredPackageRes = WithRevision<null>
|
||||
export type DeleteRecoveredPackageRes = null
|
||||
|
||||
export type DryConfigureDependencyReq = {
|
||||
'dependency-id': string
|
||||
@@ -268,9 +268,6 @@ export module RR {
|
||||
export type GetReleaseNotesRes = { [version: string]: string }
|
||||
}
|
||||
|
||||
export type WithExpire<T> = { 'expire-id'?: string } & T
|
||||
export type WithRevision<T> = { response: T | null; revision?: Revision }
|
||||
|
||||
export interface MarketplaceEOS {
|
||||
version: string
|
||||
headline: string
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Subject, Observable } from 'rxjs'
|
||||
import { Update, Operation, Revision } from 'patch-db-client'
|
||||
import { Observable, ReplaySubject } from 'rxjs'
|
||||
import { Update } from 'patch-db-client'
|
||||
import { RR } from './api.types'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { Log, RequestError } from '@start9labs/shared'
|
||||
import { Log } from '@start9labs/shared'
|
||||
import { WebSocketSubjectConfig } from 'rxjs/webSocket'
|
||||
|
||||
export abstract class ApiService {
|
||||
readonly sync$ = new Subject<Update<DataModel>>()
|
||||
readonly patchStream$ = new ReplaySubject<Update<DataModel>[]>(1)
|
||||
|
||||
// http
|
||||
|
||||
@@ -18,15 +18,7 @@ export abstract class ApiService {
|
||||
|
||||
// db
|
||||
|
||||
abstract getRevisions(since: number): Promise<RR.GetRevisionsRes>
|
||||
|
||||
abstract getDump(): Promise<RR.GetDumpRes>
|
||||
|
||||
protected abstract setDbValueRaw(
|
||||
params: RR.SetDBValueReq,
|
||||
): Promise<RR.SetDBValueRes>
|
||||
setDbValue = (params: RR.SetDBValueReq) =>
|
||||
this.syncResponse(() => this.setDbValueRaw(params))()
|
||||
abstract setDbValue(params: RR.SetDBValueReq): Promise<RR.SetDBValueRes>
|
||||
|
||||
// auth
|
||||
|
||||
@@ -72,18 +64,7 @@ export abstract class ApiService {
|
||||
params: RR.GetPackageMetricsReq,
|
||||
): Promise<RR.GetPackageMetricsRes>
|
||||
|
||||
protected abstract updateServerRaw(
|
||||
params: RR.UpdateServerReq,
|
||||
): Promise<RR.UpdateServerRes>
|
||||
updateServer = (params: RR.UpdateServerReq) =>
|
||||
this.syncResponse(() => this.updateServerWrapper(params))()
|
||||
async updateServerWrapper(params: RR.UpdateServerReq) {
|
||||
const res = await this.updateServerRaw(params)
|
||||
if (res.response === 'no-updates') {
|
||||
throw new Error('Could not find a newer version of EmbassyOS')
|
||||
}
|
||||
return res
|
||||
}
|
||||
abstract updateServer(params: RR.UpdateServerReq): Promise<RR.UpdateServerRes>
|
||||
|
||||
abstract restartServer(
|
||||
params: RR.RestartServerReq,
|
||||
@@ -116,13 +97,9 @@ export abstract class ApiService {
|
||||
|
||||
// notification
|
||||
|
||||
abstract getNotificationsRaw(
|
||||
abstract getNotifications(
|
||||
params: RR.GetNotificationsReq,
|
||||
): Promise<RR.GetNotificationsRes>
|
||||
getNotifications = (params: RR.GetNotificationsReq) =>
|
||||
this.syncResponse<RR.GetNotificationsRes['response'], any>(() =>
|
||||
this.getNotificationsRaw(params),
|
||||
)()
|
||||
|
||||
abstract deleteNotification(
|
||||
params: RR.DeleteNotificationReq,
|
||||
@@ -179,11 +156,7 @@ export abstract class ApiService {
|
||||
params: RR.GetBackupInfoReq,
|
||||
): Promise<RR.GetBackupInfoRes>
|
||||
|
||||
protected abstract createBackupRaw(
|
||||
params: RR.CreateBackupReq,
|
||||
): Promise<RR.CreateBackupRes>
|
||||
createBackup = (params: RR.CreateBackupReq) =>
|
||||
this.syncResponse(() => this.createBackupRaw(params))()
|
||||
abstract createBackup(params: RR.CreateBackupReq): Promise<RR.CreateBackupRes>
|
||||
|
||||
// package
|
||||
|
||||
@@ -199,11 +172,9 @@ export abstract class ApiService {
|
||||
params: RR.FollowPackageLogsReq,
|
||||
): Promise<RR.FollowPackageLogsRes>
|
||||
|
||||
protected abstract installPackageRaw(
|
||||
abstract installPackage(
|
||||
params: RR.InstallPackageReq,
|
||||
): Promise<RR.InstallPackageRes>
|
||||
installPackage = (params: RR.InstallPackageReq) =>
|
||||
this.syncResponse(() => this.installPackageRaw(params))()
|
||||
|
||||
abstract dryUpdatePackage(
|
||||
params: RR.DryUpdatePackageReq,
|
||||
@@ -217,85 +188,39 @@ export abstract class ApiService {
|
||||
params: RR.DrySetPackageConfigReq,
|
||||
): Promise<RR.DrySetPackageConfigRes>
|
||||
|
||||
protected abstract setPackageConfigRaw(
|
||||
abstract setPackageConfig(
|
||||
params: RR.SetPackageConfigReq,
|
||||
): Promise<RR.SetPackageConfigRes>
|
||||
setPackageConfig = (params: RR.SetPackageConfigReq) =>
|
||||
this.syncResponse(() => this.setPackageConfigRaw(params))()
|
||||
|
||||
protected abstract restorePackagesRaw(
|
||||
abstract restorePackages(
|
||||
params: RR.RestorePackagesReq,
|
||||
): Promise<RR.RestorePackagesRes>
|
||||
restorePackages = (params: RR.RestorePackagesReq) =>
|
||||
this.syncResponse(() => this.restorePackagesRaw(params))()
|
||||
|
||||
abstract executePackageAction(
|
||||
params: RR.ExecutePackageActionReq,
|
||||
): Promise<RR.ExecutePackageActionRes>
|
||||
|
||||
protected abstract startPackageRaw(
|
||||
params: RR.StartPackageReq,
|
||||
): Promise<RR.StartPackageRes>
|
||||
startPackage = (params: RR.StartPackageReq) =>
|
||||
this.syncResponse(() => this.startPackageRaw(params))()
|
||||
abstract startPackage(params: RR.StartPackageReq): Promise<RR.StartPackageRes>
|
||||
|
||||
protected abstract restartPackageRaw(
|
||||
abstract restartPackage(
|
||||
params: RR.RestartPackageReq,
|
||||
): Promise<RR.RestartPackageRes>
|
||||
restartPackage = (params: RR.RestartPackageReq) =>
|
||||
this.syncResponse(() => this.restartPackageRaw(params))()
|
||||
|
||||
protected abstract stopPackageRaw(
|
||||
params: RR.StopPackageReq,
|
||||
): Promise<RR.StopPackageRes>
|
||||
stopPackage = (params: RR.StopPackageReq) =>
|
||||
this.syncResponse(() => this.stopPackageRaw(params))()
|
||||
abstract stopPackage(params: RR.StopPackageReq): Promise<RR.StopPackageRes>
|
||||
|
||||
protected abstract uninstallPackageRaw(
|
||||
abstract uninstallPackage(
|
||||
params: RR.UninstallPackageReq,
|
||||
): Promise<RR.UninstallPackageRes>
|
||||
uninstallPackage = (params: RR.UninstallPackageReq) =>
|
||||
this.syncResponse(() => this.uninstallPackageRaw(params))()
|
||||
|
||||
abstract dryConfigureDependency(
|
||||
params: RR.DryConfigureDependencyReq,
|
||||
): Promise<RR.DryConfigureDependencyRes>
|
||||
|
||||
protected abstract deleteRecoveredPackageRaw(
|
||||
abstract deleteRecoveredPackage(
|
||||
params: RR.UninstallPackageReq,
|
||||
): Promise<RR.UninstallPackageRes>
|
||||
deleteRecoveredPackage = (params: RR.UninstallPackageReq) =>
|
||||
this.syncResponse(() => this.deleteRecoveredPackageRaw(params))()
|
||||
|
||||
abstract sideloadPackage(
|
||||
params: RR.SideloadPackageReq,
|
||||
): Promise<RR.SideloadPacakgeRes>
|
||||
|
||||
// Helper allowing quick decoration to sync the response patch and return the response contents.
|
||||
// Pass in a tempUpdate function which returns a UpdateTemp corresponding to a temporary
|
||||
// state change you'd like to enact prior to request and expired when request terminates.
|
||||
private syncResponse<
|
||||
T,
|
||||
F extends (...args: any[]) => Promise<{ response: T; revision?: Revision }>,
|
||||
>(f: F, temp?: Operation<unknown>): (...args: Parameters<F>) => Promise<T> {
|
||||
return (...a) => {
|
||||
// let expireId = undefined
|
||||
// if (temp) {
|
||||
// expireId = uuid.v4()
|
||||
// this.sync.next({ patch: [temp], expiredBy: expireId })
|
||||
// }
|
||||
|
||||
return f(a)
|
||||
.catch((e: UIRequestError) => {
|
||||
if (e.revision) this.sync$.next(e.revision)
|
||||
throw e
|
||||
})
|
||||
.then(({ response, revision }) => {
|
||||
if (revision) this.sync$.next(revision)
|
||||
return response
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type UIRequestError = RequestError & { revision: Revision }
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Inject, Injectable } from '@angular/core'
|
||||
import {
|
||||
HttpOptions,
|
||||
HttpService,
|
||||
isRpcError,
|
||||
Log,
|
||||
Method,
|
||||
RPCError,
|
||||
RpcError,
|
||||
RPCOptions,
|
||||
} from '@start9labs/shared'
|
||||
import { ApiService } from './embassy-api.service'
|
||||
@@ -11,11 +13,11 @@ import { RR } from './api.types'
|
||||
import { parsePropertiesPermissive } from 'src/app/util/properties.util'
|
||||
import { ConfigService } from '../config.service'
|
||||
import { webSocket, WebSocketSubjectConfig } from 'rxjs/webSocket'
|
||||
import { Observable, timeout } from 'rxjs'
|
||||
import { Observable } from 'rxjs'
|
||||
import { AuthService } from '../auth.service'
|
||||
import { DOCUMENT } from '@angular/common'
|
||||
import { DataModel } from '../patch-db/data-model'
|
||||
import { Update } from 'patch-db-client'
|
||||
import { PatchDB, Update } from 'patch-db-client'
|
||||
|
||||
@Injectable()
|
||||
export class LiveApiService extends ApiService {
|
||||
@@ -24,13 +26,14 @@ export class LiveApiService extends ApiService {
|
||||
private readonly http: HttpService,
|
||||
private readonly config: ConfigService,
|
||||
private readonly auth: AuthService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {
|
||||
super()
|
||||
; (window as any).rpcClient = this
|
||||
}
|
||||
|
||||
async getStatic(url: string): Promise<string> {
|
||||
return this.http.httpRequest({
|
||||
return this.httpRequest({
|
||||
method: Method.GET,
|
||||
url,
|
||||
responseType: 'text',
|
||||
@@ -38,7 +41,7 @@ export class LiveApiService extends ApiService {
|
||||
}
|
||||
|
||||
async uploadPackage(guid: string, body: ArrayBuffer): Promise<string> {
|
||||
return this.http.httpRequest({
|
||||
return this.httpRequest({
|
||||
method: Method.POST,
|
||||
body,
|
||||
url: `/rest/rpc/${guid}`,
|
||||
@@ -48,22 +51,14 @@ export class LiveApiService extends ApiService {
|
||||
|
||||
// db
|
||||
|
||||
async getRevisions(since: number): Promise<RR.GetRevisionsRes> {
|
||||
return this.rpcRequest({ method: 'db.revisions', params: { since } })
|
||||
}
|
||||
|
||||
async getDump(): Promise<RR.GetDumpRes> {
|
||||
return this.rpcRequest({ method: 'db.dump', params: {} })
|
||||
}
|
||||
|
||||
async setDbValueRaw(params: RR.SetDBValueReq): Promise<RR.SetDBValueRes> {
|
||||
async setDbValue(params: RR.SetDBValueReq): Promise<RR.SetDBValueRes> {
|
||||
return this.rpcRequest({ method: 'db.put.ui', params })
|
||||
}
|
||||
|
||||
// auth
|
||||
|
||||
async login(params: RR.LoginReq): Promise<RR.loginRes> {
|
||||
return this.rpcRequest({ method: 'auth.login', params })
|
||||
return this.rpcRequest({ method: 'auth.login', params }, false)
|
||||
}
|
||||
|
||||
async logout(params: RR.LogoutReq): Promise<RR.LogoutRes> {
|
||||
@@ -81,7 +76,7 @@ export class LiveApiService extends ApiService {
|
||||
// server
|
||||
|
||||
async echo(params: RR.EchoReq): Promise<RR.EchoRes> {
|
||||
return this.rpcRequest({ method: 'echo', params })
|
||||
return this.rpcRequest({ method: 'echo', params }, false)
|
||||
}
|
||||
|
||||
openPatchWebsocket$(): Observable<Update<DataModel>> {
|
||||
@@ -94,7 +89,7 @@ export class LiveApiService extends ApiService {
|
||||
},
|
||||
}
|
||||
|
||||
return this.openWebsocket(config).pipe(timeout({ first: 21000 }))
|
||||
return this.openWebsocket(config)
|
||||
}
|
||||
|
||||
openLogsWebsocket$(config: WebSocketSubjectConfig<Log>): Observable<Log> {
|
||||
@@ -131,10 +126,13 @@ export class LiveApiService extends ApiService {
|
||||
return this.rpcRequest({ method: 'server.metrics', params })
|
||||
}
|
||||
|
||||
async updateServerRaw(
|
||||
params: RR.UpdateServerReq,
|
||||
): Promise<RR.UpdateServerRes> {
|
||||
async updateServer(params: RR.UpdateServerReq): Promise<RR.UpdateServerRes> {
|
||||
return this.rpcRequest({ method: 'server.update', params })
|
||||
// const res = await this.updateServer(params)
|
||||
// if (res.response === 'no-updates') {
|
||||
// throw new Error('Could not find a newer version of EmbassyOS')
|
||||
// }
|
||||
// return res
|
||||
}
|
||||
|
||||
async restartServer(
|
||||
@@ -182,7 +180,7 @@ export class LiveApiService extends ApiService {
|
||||
|
||||
// notification
|
||||
|
||||
async getNotificationsRaw(
|
||||
async getNotifications(
|
||||
params: RR.GetNotificationsReq,
|
||||
): Promise<RR.GetNotificationsRes> {
|
||||
return this.rpcRequest({ method: 'notification.list', params })
|
||||
@@ -277,9 +275,7 @@ export class LiveApiService extends ApiService {
|
||||
return this.rpcRequest({ method: 'backup.target.info', params })
|
||||
}
|
||||
|
||||
async createBackupRaw(
|
||||
params: RR.CreateBackupReq,
|
||||
): Promise<RR.CreateBackupRes> {
|
||||
async createBackup(params: RR.CreateBackupReq): Promise<RR.CreateBackupRes> {
|
||||
return this.rpcRequest({ method: 'backup.create', params })
|
||||
}
|
||||
|
||||
@@ -288,9 +284,9 @@ export class LiveApiService extends ApiService {
|
||||
async getPackageProperties(
|
||||
params: RR.GetPackagePropertiesReq,
|
||||
): Promise<RR.GetPackagePropertiesRes<2>['data']> {
|
||||
return this.http
|
||||
.rpcRequest({ method: 'package.properties', params })
|
||||
.then(parsePropertiesPermissive)
|
||||
return this.rpcRequest({ method: 'package.properties', params }).then(
|
||||
parsePropertiesPermissive,
|
||||
)
|
||||
}
|
||||
|
||||
async getPackageLogs(
|
||||
@@ -311,7 +307,7 @@ export class LiveApiService extends ApiService {
|
||||
return this.rpcRequest({ method: 'package.metrics', params })
|
||||
}
|
||||
|
||||
async installPackageRaw(
|
||||
async installPackage(
|
||||
params: RR.InstallPackageReq,
|
||||
): Promise<RR.InstallPackageRes> {
|
||||
return this.rpcRequest({ method: 'package.install', params })
|
||||
@@ -335,13 +331,13 @@ export class LiveApiService extends ApiService {
|
||||
return this.rpcRequest({ method: 'package.config.set.dry', params })
|
||||
}
|
||||
|
||||
async setPackageConfigRaw(
|
||||
async setPackageConfig(
|
||||
params: RR.SetPackageConfigReq,
|
||||
): Promise<RR.SetPackageConfigRes> {
|
||||
return this.rpcRequest({ method: 'package.config.set', params })
|
||||
}
|
||||
|
||||
async restorePackagesRaw(
|
||||
async restorePackages(
|
||||
params: RR.RestorePackagesReq,
|
||||
): Promise<RR.RestorePackagesRes> {
|
||||
return this.rpcRequest({ method: 'package.backup.restore', params })
|
||||
@@ -353,29 +349,27 @@ export class LiveApiService extends ApiService {
|
||||
return this.rpcRequest({ method: 'package.action', params })
|
||||
}
|
||||
|
||||
async startPackageRaw(
|
||||
params: RR.StartPackageReq,
|
||||
): Promise<RR.StartPackageRes> {
|
||||
async startPackage(params: RR.StartPackageReq): Promise<RR.StartPackageRes> {
|
||||
return this.rpcRequest({ method: 'package.start', params })
|
||||
}
|
||||
|
||||
async restartPackageRaw(
|
||||
async restartPackage(
|
||||
params: RR.RestartPackageReq,
|
||||
): Promise<RR.RestartPackageRes> {
|
||||
return this.rpcRequest({ method: 'package.restart', params })
|
||||
}
|
||||
|
||||
async stopPackageRaw(params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
|
||||
async stopPackage(params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
|
||||
return this.rpcRequest({ method: 'package.stop', params })
|
||||
}
|
||||
|
||||
async deleteRecoveredPackageRaw(
|
||||
async deleteRecoveredPackage(
|
||||
params: RR.DeleteRecoveredPackageReq,
|
||||
): Promise<RR.DeleteRecoveredPackageRes> {
|
||||
return this.rpcRequest({ method: 'package.delete-recovered', params })
|
||||
}
|
||||
|
||||
async uninstallPackageRaw(
|
||||
async uninstallPackage(
|
||||
params: RR.UninstallPackageReq,
|
||||
): Promise<RR.UninstallPackageRes> {
|
||||
return this.rpcRequest({ method: 'package.uninstall', params })
|
||||
@@ -409,13 +403,42 @@ export class LiveApiService extends ApiService {
|
||||
return webSocket(config)
|
||||
}
|
||||
|
||||
private async rpcRequest<T>(options: RPCOptions): Promise<T> {
|
||||
return this.http.rpcRequest<T>(options).catch(e => {
|
||||
if ((e as RPCError).error.code === 34) {
|
||||
private async rpcRequest<T>(
|
||||
options: RPCOptions,
|
||||
addHeader = true,
|
||||
): Promise<T> {
|
||||
if (addHeader) {
|
||||
options.headers = {
|
||||
'x-patch-sequence': String(this.patch.cache$.value.sequence),
|
||||
...(options.headers || {}),
|
||||
}
|
||||
}
|
||||
|
||||
const res = await this.http.rpcRequest<T>(options)
|
||||
const encoded = res.headers.get('x-patch-updates')
|
||||
|
||||
if (encoded) {
|
||||
const updates: Update<DataModel>[] = JSON.parse(
|
||||
decodeURIComponent(encoded),
|
||||
)
|
||||
this.patchStream$.next(updates)
|
||||
}
|
||||
|
||||
const rpcRes = res.body
|
||||
|
||||
if (isRpcError(rpcRes)) {
|
||||
if (rpcRes.error.code === 34) {
|
||||
console.error('Unauthenticated, logging out')
|
||||
this.auth.setUnverified()
|
||||
}
|
||||
throw e
|
||||
})
|
||||
throw new RpcError(rpcRes.error)
|
||||
}
|
||||
|
||||
return rpcRes.result
|
||||
}
|
||||
|
||||
private async httpRequest<T>(opts: HttpOptions): Promise<T> {
|
||||
const res = await this.http.httpRequest<T>(opts)
|
||||
return res.body
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { pauseFor, Log, LogsRes } from '@start9labs/shared'
|
||||
import { pauseFor, Log } from '@start9labs/shared'
|
||||
import { ApiService } from './embassy-api.service'
|
||||
import { PatchOp, Update, Operation, RemoveOperation } from 'patch-db-client'
|
||||
import {
|
||||
@@ -11,11 +11,11 @@ import {
|
||||
PackageState,
|
||||
ServerStatus,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { CifsBackupTarget, RR, WithRevision } from './api.types'
|
||||
import { CifsBackupTarget, RR } from './api.types'
|
||||
import { parsePropertiesPermissive } from 'src/app/util/properties.util'
|
||||
import { Mock } from './api.fixures'
|
||||
import markdown from 'raw-loader!../../../../../../assets/markdown/md-sample.md'
|
||||
import { BehaviorSubject, interval, map, Observable, tap } from 'rxjs'
|
||||
import { BehaviorSubject, interval, map, Observable } from 'rxjs'
|
||||
import { LocalStorageBootstrap } from '../patch-db/local-storage-bootstrap'
|
||||
import { mockPatchData } from './mock-patch'
|
||||
import { WebSocketSubjectConfig } from 'rxjs/webSocket'
|
||||
@@ -32,10 +32,9 @@ const PROGRESS: InstallProgress = {
|
||||
|
||||
@Injectable()
|
||||
export class MockApiService extends ApiService {
|
||||
readonly mockPatch$ = new BehaviorSubject<Update<DataModel>>({
|
||||
readonly mockWsSource$ = new BehaviorSubject<Update<DataModel>>({
|
||||
id: 1,
|
||||
value: mockPatchData,
|
||||
expireId: null,
|
||||
})
|
||||
private readonly revertTime = 2000
|
||||
sequence = 0
|
||||
@@ -56,20 +55,7 @@ export class MockApiService extends ApiService {
|
||||
|
||||
// db
|
||||
|
||||
async getRevisions(since: number): Promise<RR.GetRevisionsRes> {
|
||||
return this.getDump()
|
||||
}
|
||||
|
||||
async getDump(): Promise<RR.GetDumpRes> {
|
||||
const cache = await this.bootstrapper.init()
|
||||
return {
|
||||
id: cache.sequence,
|
||||
value: cache.data,
|
||||
expireId: null,
|
||||
}
|
||||
}
|
||||
|
||||
async setDbValueRaw(params: RR.SetDBValueReq): Promise<RR.SetDBValueRes> {
|
||||
async setDbValue(params: RR.SetDBValueReq): Promise<RR.SetDBValueRes> {
|
||||
await pauseFor(2000)
|
||||
const patch = [
|
||||
{
|
||||
@@ -87,7 +73,7 @@ export class MockApiService extends ApiService {
|
||||
await pauseFor(2000)
|
||||
|
||||
setTimeout(() => {
|
||||
this.mockPatch$.next({ id: 1, value: mockPatchData, expireId: null })
|
||||
this.mockWsSource$.next({ id: 1, value: mockPatchData })
|
||||
}, 2000)
|
||||
|
||||
return null
|
||||
@@ -105,7 +91,7 @@ export class MockApiService extends ApiService {
|
||||
|
||||
async killSessions(params: RR.KillSessionsReq): Promise<RR.KillSessionsRes> {
|
||||
await pauseFor(2000)
|
||||
return { response: null }
|
||||
return null
|
||||
}
|
||||
|
||||
// server
|
||||
@@ -116,7 +102,7 @@ export class MockApiService extends ApiService {
|
||||
}
|
||||
|
||||
openPatchWebsocket$(): Observable<Update<DataModel>> {
|
||||
return this.mockPatch$
|
||||
return this.mockWsSource$
|
||||
}
|
||||
|
||||
openLogsWebsocket$(config: WebSocketSubjectConfig<Log>): Observable<Log> {
|
||||
@@ -198,9 +184,7 @@ export class MockApiService extends ApiService {
|
||||
return Mock.getAppMetrics()
|
||||
}
|
||||
|
||||
async updateServerRaw(
|
||||
params: RR.UpdateServerReq,
|
||||
): Promise<RR.UpdateServerRes> {
|
||||
async updateServer(params: RR.UpdateServerReq): Promise<RR.UpdateServerRes> {
|
||||
await pauseFor(2000)
|
||||
const initialProgress = {
|
||||
size: 10000,
|
||||
@@ -289,7 +273,7 @@ export class MockApiService extends ApiService {
|
||||
|
||||
// notification
|
||||
|
||||
async getNotificationsRaw(
|
||||
async getNotifications(
|
||||
params: RR.GetNotificationsReq,
|
||||
): Promise<RR.GetNotificationsRes> {
|
||||
await pauseFor(2000)
|
||||
@@ -418,9 +402,7 @@ export class MockApiService extends ApiService {
|
||||
return Mock.BackupInfo
|
||||
}
|
||||
|
||||
async createBackupRaw(
|
||||
params: RR.CreateBackupReq,
|
||||
): Promise<RR.CreateBackupRes> {
|
||||
async createBackup(params: RR.CreateBackupReq): Promise<RR.CreateBackupRes> {
|
||||
await pauseFor(2000)
|
||||
const path = '/server-info/status-info/backup-progress'
|
||||
const ids = params['package-ids']
|
||||
@@ -436,17 +418,17 @@ export class MockApiService extends ApiService {
|
||||
value: PackageMainStatus.BackingUp,
|
||||
},
|
||||
]
|
||||
this.updateMock(appPatch)
|
||||
this.mockRevision(appPatch)
|
||||
|
||||
await pauseFor(8000)
|
||||
|
||||
this.updateMock([
|
||||
this.mockRevision([
|
||||
{
|
||||
...appPatch[0],
|
||||
value: PackageMainStatus.Stopped,
|
||||
},
|
||||
])
|
||||
this.updateMock([
|
||||
this.mockRevision([
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `${path}/${id}/complete`,
|
||||
@@ -465,7 +447,7 @@ export class MockApiService extends ApiService {
|
||||
value: null,
|
||||
},
|
||||
]
|
||||
this.updateMock(lastPatch)
|
||||
this.mockRevision(lastPatch)
|
||||
}, 500)
|
||||
|
||||
const originalPatch = [
|
||||
@@ -525,7 +507,7 @@ export class MockApiService extends ApiService {
|
||||
}
|
||||
}
|
||||
|
||||
async installPackageRaw(
|
||||
async installPackage(
|
||||
params: RR.InstallPackageReq,
|
||||
): Promise<RR.InstallPackageRes> {
|
||||
await pauseFor(2000)
|
||||
@@ -582,7 +564,7 @@ export class MockApiService extends ApiService {
|
||||
return {}
|
||||
}
|
||||
|
||||
async setPackageConfigRaw(
|
||||
async setPackageConfig(
|
||||
params: RR.SetPackageConfigReq,
|
||||
): Promise<RR.SetPackageConfigRes> {
|
||||
await pauseFor(2000)
|
||||
@@ -596,7 +578,7 @@ export class MockApiService extends ApiService {
|
||||
return this.withRevision(patch)
|
||||
}
|
||||
|
||||
async restorePackagesRaw(
|
||||
async restorePackages(
|
||||
params: RR.RestorePackagesReq,
|
||||
): Promise<RR.RestorePackagesRes> {
|
||||
await pauseFor(2000)
|
||||
@@ -627,9 +609,7 @@ export class MockApiService extends ApiService {
|
||||
return Mock.ActionResponse
|
||||
}
|
||||
|
||||
async startPackageRaw(
|
||||
params: RR.StartPackageReq,
|
||||
): Promise<RR.StartPackageRes> {
|
||||
async startPackage(params: RR.StartPackageReq): Promise<RR.StartPackageRes> {
|
||||
const path = `/package-data/${params.id}/installed/status/main`
|
||||
|
||||
await pauseFor(2000)
|
||||
@@ -647,7 +627,7 @@ export class MockApiService extends ApiService {
|
||||
value: new Date().toISOString(),
|
||||
},
|
||||
]
|
||||
this.updateMock(patch2)
|
||||
this.mockRevision(patch2)
|
||||
|
||||
const patch3 = [
|
||||
{
|
||||
@@ -663,7 +643,7 @@ export class MockApiService extends ApiService {
|
||||
},
|
||||
},
|
||||
]
|
||||
this.updateMock(patch3)
|
||||
this.mockRevision(patch3)
|
||||
|
||||
await pauseFor(2000)
|
||||
|
||||
@@ -692,7 +672,7 @@ export class MockApiService extends ApiService {
|
||||
},
|
||||
},
|
||||
]
|
||||
this.updateMock(patch4)
|
||||
this.mockRevision(patch4)
|
||||
}, 2000)
|
||||
|
||||
const originalPatch = [
|
||||
@@ -706,7 +686,7 @@ export class MockApiService extends ApiService {
|
||||
return this.withRevision(originalPatch)
|
||||
}
|
||||
|
||||
async restartPackageRaw(
|
||||
async restartPackage(
|
||||
params: RR.RestartPackageReq,
|
||||
): Promise<RR.RestartPackageRes> {
|
||||
// first enact stop
|
||||
@@ -744,7 +724,7 @@ export class MockApiService extends ApiService {
|
||||
},
|
||||
} as any,
|
||||
]
|
||||
this.updateMock(patch2)
|
||||
this.mockRevision(patch2)
|
||||
}, this.revertTime)
|
||||
|
||||
const patch = [
|
||||
@@ -763,7 +743,7 @@ export class MockApiService extends ApiService {
|
||||
return this.withRevision(patch)
|
||||
}
|
||||
|
||||
async stopPackageRaw(params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
|
||||
async stopPackage(params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
|
||||
await pauseFor(2000)
|
||||
const path = `/package-data/${params.id}/installed/status/main`
|
||||
|
||||
@@ -775,7 +755,7 @@ export class MockApiService extends ApiService {
|
||||
value: PackageMainStatus.Stopped,
|
||||
},
|
||||
]
|
||||
this.updateMock(patch2)
|
||||
this.mockRevision(patch2)
|
||||
}, this.revertTime)
|
||||
|
||||
const patch = [
|
||||
@@ -794,7 +774,7 @@ export class MockApiService extends ApiService {
|
||||
return this.withRevision(patch)
|
||||
}
|
||||
|
||||
async uninstallPackageRaw(
|
||||
async uninstallPackage(
|
||||
params: RR.UninstallPackageReq,
|
||||
): Promise<RR.UninstallPackageRes> {
|
||||
await pauseFor(2000)
|
||||
@@ -806,7 +786,7 @@ export class MockApiService extends ApiService {
|
||||
path: `/package-data/${params.id}`,
|
||||
},
|
||||
]
|
||||
this.updateMock(patch2)
|
||||
this.mockRevision(patch2)
|
||||
}, this.revertTime)
|
||||
|
||||
const patch = [
|
||||
@@ -820,7 +800,7 @@ export class MockApiService extends ApiService {
|
||||
return this.withRevision(patch)
|
||||
}
|
||||
|
||||
async deleteRecoveredPackageRaw(
|
||||
async deleteRecoveredPackage(
|
||||
params: RR.DeleteRecoveredPackageReq,
|
||||
): Promise<RR.DeleteRecoveredPackageRes> {
|
||||
await pauseFor(2000)
|
||||
@@ -878,7 +858,7 @@ export class MockApiService extends ApiService {
|
||||
value: { ...progress },
|
||||
},
|
||||
]
|
||||
this.updateMock(patch)
|
||||
this.mockRevision(patch)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -898,7 +878,7 @@ export class MockApiService extends ApiService {
|
||||
path: `/recovered-packages/${id}`,
|
||||
},
|
||||
]
|
||||
this.updateMock(patch2)
|
||||
this.mockRevision(patch2)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
@@ -914,7 +894,7 @@ export class MockApiService extends ApiService {
|
||||
value: downloaded,
|
||||
},
|
||||
]
|
||||
this.updateMock(patch)
|
||||
this.mockRevision(patch)
|
||||
}
|
||||
|
||||
const patch2 = [
|
||||
@@ -924,7 +904,7 @@ export class MockApiService extends ApiService {
|
||||
value: size,
|
||||
},
|
||||
]
|
||||
this.updateMock(patch2)
|
||||
this.mockRevision(patch2)
|
||||
|
||||
setTimeout(async () => {
|
||||
const patch3: Operation<ServerStatus>[] = [
|
||||
@@ -938,7 +918,7 @@ export class MockApiService extends ApiService {
|
||||
path: '/server-info/status-info/update-progress',
|
||||
},
|
||||
]
|
||||
this.updateMock(patch3)
|
||||
this.mockRevision(patch3)
|
||||
// quickly revert server to "running" for continued testing
|
||||
await pauseFor(100)
|
||||
const patch4 = [
|
||||
@@ -948,7 +928,7 @@ export class MockApiService extends ApiService {
|
||||
value: ServerStatus.Running,
|
||||
},
|
||||
]
|
||||
this.updateMock(patch4)
|
||||
this.mockRevision(patch4)
|
||||
// set patch indicating update is complete
|
||||
await pauseFor(100)
|
||||
const patch6 = [
|
||||
@@ -958,11 +938,11 @@ export class MockApiService extends ApiService {
|
||||
value: Mock.ServerUpdated,
|
||||
},
|
||||
]
|
||||
this.updateMock(patch6)
|
||||
this.mockRevision(patch6)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
private async updateMock<T>(patch: Operation<T>[]): Promise<void> {
|
||||
private async mockRevision<T>(patch: Operation<T>[]): Promise<void> {
|
||||
if (!this.sequence) {
|
||||
const { sequence } = await this.bootstrapper.init()
|
||||
this.sequence = sequence
|
||||
@@ -970,26 +950,26 @@ export class MockApiService extends ApiService {
|
||||
const revision = {
|
||||
id: ++this.sequence,
|
||||
patch,
|
||||
expireId: null,
|
||||
}
|
||||
this.mockPatch$.next(revision)
|
||||
this.mockWsSource$.next(revision)
|
||||
}
|
||||
|
||||
private async withRevision<T>(
|
||||
patch: Operation<unknown>[],
|
||||
response: T | null = null,
|
||||
): Promise<WithRevision<T>> {
|
||||
): Promise<T> {
|
||||
if (!this.sequence) {
|
||||
const { sequence } = await this.bootstrapper.init()
|
||||
this.sequence = sequence
|
||||
}
|
||||
|
||||
const revision = {
|
||||
id: ++this.sequence,
|
||||
patch,
|
||||
expireId: null,
|
||||
}
|
||||
this.patchStream$.next([
|
||||
{
|
||||
id: ++this.sequence,
|
||||
patch,
|
||||
},
|
||||
])
|
||||
|
||||
return { response, revision }
|
||||
return response as T
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,10 @@ export const mockPatchData: DataModel = {
|
||||
'server-info': {
|
||||
id: 'abcdefgh',
|
||||
version: '0.3.1.1',
|
||||
'last-backup': null,
|
||||
'last-backup': new Date(new Date().valueOf() - 604800001).toISOString(),
|
||||
'lan-address': 'https://embassy-abcdefgh.local',
|
||||
'tor-address': 'http://myveryownspecialtoraddress.onion',
|
||||
'last-wifi-region': null,
|
||||
'unread-notification-count': 4,
|
||||
// password is asdfasdf
|
||||
'password-hash':
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable, NgZone } from '@angular/core'
|
||||
import { ReplaySubject } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
import { distinctUntilChanged, map } from 'rxjs/operators'
|
||||
import { Storage } from '@ionic/storage-angular'
|
||||
import { Router } from '@angular/router'
|
||||
|
||||
@@ -17,6 +17,7 @@ export class AuthService {
|
||||
|
||||
readonly isVerified$ = this.authState$.pipe(
|
||||
map(state => state === AuthState.VERIFIED),
|
||||
distinctUntilChanged(),
|
||||
)
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -17,6 +17,9 @@ export class ConnectionService {
|
||||
readonly websocketConnected$ = new ReplaySubject<boolean>(1)
|
||||
readonly connected$ = combineLatest([
|
||||
this.networkConnected$,
|
||||
this.websocketConnected$,
|
||||
]).pipe(map(([network, websocket]) => network && websocket))
|
||||
this.websocketConnected$.pipe(distinctUntilChanged()),
|
||||
]).pipe(
|
||||
map(([network, websocket]) => network && websocket),
|
||||
distinctUntilChanged(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ import { distinctUntilChanged, filter, map } from 'rxjs/operators'
|
||||
|
||||
import { MarketplaceEOS } from 'src/app/services/api/api.types'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { getServerInfo } from 'src/app/util/get-server-info'
|
||||
import { DataModel } from './patch-db/data-model'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -49,7 +50,7 @@ export class EOSService {
|
||||
constructor(
|
||||
private readonly api: ApiService,
|
||||
private readonly emver: Emver,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
async getEOS(): Promise<boolean> {
|
||||
|
||||
@@ -12,10 +12,11 @@ import { RR } from 'src/app/services/api/api.types'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import {
|
||||
DataModel,
|
||||
ServerInfo,
|
||||
UIMarketplaceData,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
catchError,
|
||||
distinctUntilChanged,
|
||||
@@ -119,7 +120,7 @@ export class MarketplaceService extends AbstractMarketplaceService {
|
||||
|
||||
constructor(
|
||||
private readonly api: ApiService,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly config: ConfigService,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly emver: Emver,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ModalController } from '@ionic/angular'
|
||||
import { Observable } from 'rxjs'
|
||||
import { filter, share, switchMap, take, tap } from 'rxjs/operators'
|
||||
import { exists, isEmptyObject } from '@start9labs/shared'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel, UIData } from 'src/app/services/patch-db/data-model'
|
||||
import { EOSService } from 'src/app/services/eos.service'
|
||||
import { OSWelcomePage } from 'src/app/modals/os-welcome/os-welcome.page'
|
||||
@@ -33,7 +33,7 @@ export class PatchDataService extends Observable<DataModel> {
|
||||
)
|
||||
|
||||
constructor(
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly eosService: EOSService,
|
||||
private readonly config: ConfigService,
|
||||
private readonly modalCtrl: ModalController,
|
||||
|
||||
@@ -52,6 +52,7 @@ export interface ServerInfo {
|
||||
'last-backup': string | null
|
||||
'lan-address': Url
|
||||
'tor-address': Url
|
||||
'last-wifi-region': string | null
|
||||
'unread-notification-count': number
|
||||
'status-info': ServerStatusInfo
|
||||
'eos-version-compat': string
|
||||
|
||||
@@ -1,41 +1,45 @@
|
||||
import { InjectionToken } from '@angular/core'
|
||||
import { catchError, switchMap, take, tap } from 'rxjs/operators'
|
||||
import { Bootstrapper, DBCache, Update } from 'patch-db-client'
|
||||
import { InjectionToken, Injector } from '@angular/core'
|
||||
import { bufferTime, catchError, switchMap, take, tap } from 'rxjs/operators'
|
||||
import { Update } from 'patch-db-client'
|
||||
import { DataModel } from './data-model'
|
||||
import { EMPTY, from, interval, merge, Observable } from 'rxjs'
|
||||
import { defer, EMPTY, from, interval, merge, Observable } from 'rxjs'
|
||||
import { AuthService } from '../auth.service'
|
||||
import { ConnectionService } from '../connection.service'
|
||||
import { ApiService } from '../api/embassy-api.service'
|
||||
|
||||
export const PATCH_SOURCE = new InjectionToken<Observable<Update<DataModel>>>(
|
||||
export const PATCH_SOURCE = new InjectionToken<Observable<Update<DataModel>[]>>(
|
||||
'',
|
||||
)
|
||||
export const PATCH_CACHE = new InjectionToken<DBCache<DataModel>>('', {
|
||||
factory: () => ({} as any),
|
||||
})
|
||||
export const BOOTSTRAPPER = new InjectionToken<Bootstrapper<DataModel>>('')
|
||||
|
||||
export function sourceFactory(
|
||||
api: ApiService,
|
||||
authService: AuthService,
|
||||
connectionService: ConnectionService,
|
||||
): Observable<Update<DataModel>> {
|
||||
const websocket$ = api.openPatchWebsocket$().pipe(
|
||||
catchError((_, watch$) => {
|
||||
connectionService.websocketConnected$.next(false)
|
||||
injector: Injector,
|
||||
): Observable<Update<DataModel>[]> {
|
||||
// defer() needed to avoid circular dependency with ApiService, since PatchDB is needed there
|
||||
return defer(() => {
|
||||
const api = injector.get(ApiService)
|
||||
const authService = injector.get(AuthService)
|
||||
const connectionService = injector.get(ConnectionService)
|
||||
|
||||
return interval(4000).pipe(
|
||||
switchMap(() =>
|
||||
from(api.echo({ message: 'ping' })).pipe(catchError(() => EMPTY)),
|
||||
),
|
||||
take(1),
|
||||
switchMap(() => watch$),
|
||||
)
|
||||
}),
|
||||
tap(() => connectionService.websocketConnected$.next(true)),
|
||||
)
|
||||
const websocket$ = api.openPatchWebsocket$().pipe(
|
||||
bufferTime(250),
|
||||
catchError((_, watch$) => {
|
||||
connectionService.websocketConnected$.next(false)
|
||||
|
||||
return authService.isVerified$.pipe(
|
||||
switchMap(verified => (verified ? merge(websocket$, api.sync$) : EMPTY)),
|
||||
)
|
||||
return interval(4000).pipe(
|
||||
switchMap(() =>
|
||||
from(api.echo({ message: 'ping' })).pipe(catchError(() => EMPTY)),
|
||||
),
|
||||
take(1),
|
||||
switchMap(() => watch$),
|
||||
)
|
||||
}),
|
||||
tap(() => connectionService.websocketConnected$.next(true)),
|
||||
)
|
||||
|
||||
return authService.isVerified$.pipe(
|
||||
switchMap(verified =>
|
||||
verified ? merge(websocket$, api.patchStream$) : EMPTY,
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,31 +1,18 @@
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { NgModule } from '@angular/core'
|
||||
import {
|
||||
BOOTSTRAPPER,
|
||||
PATCH_CACHE,
|
||||
PATCH_SOURCE,
|
||||
sourceFactory,
|
||||
} from './patch-db.factory'
|
||||
import { LocalStorageBootstrap } from './local-storage-bootstrap'
|
||||
import { ApiService } from '../api/embassy-api.service'
|
||||
import { AuthService } from '../auth.service'
|
||||
import { ConnectionService } from '../connection.service'
|
||||
import { Injector, NgModule } from '@angular/core'
|
||||
import { PATCH_SOURCE, sourceFactory } from './patch-db.factory'
|
||||
|
||||
// This module is purely for providers organization purposes
|
||||
@NgModule({
|
||||
providers: [
|
||||
{
|
||||
provide: BOOTSTRAPPER,
|
||||
useExisting: LocalStorageBootstrap,
|
||||
},
|
||||
{
|
||||
provide: PATCH_SOURCE,
|
||||
deps: [ApiService, AuthService, ConnectionService],
|
||||
deps: [Injector],
|
||||
useFactory: sourceFactory,
|
||||
},
|
||||
{
|
||||
provide: PatchDB,
|
||||
deps: [PATCH_SOURCE, PATCH_CACHE],
|
||||
deps: [PATCH_SOURCE],
|
||||
useClass: PatchDB,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import { Inject, Injectable } from '@angular/core'
|
||||
import { Bootstrapper, PatchDB, Store } from 'patch-db-client'
|
||||
import { Observable, of, Subscription } from 'rxjs'
|
||||
import { catchError, debounceTime, finalize, tap } from 'rxjs/operators'
|
||||
import { DataModel } from './data-model'
|
||||
import { BOOTSTRAPPER } from './patch-db.factory'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class PatchDbService {
|
||||
private sub?: Subscription
|
||||
|
||||
constructor(
|
||||
@Inject(BOOTSTRAPPER)
|
||||
private readonly bootstrapper: Bootstrapper<DataModel>,
|
||||
private readonly patchDb: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
start(): void {
|
||||
// Early return if already started
|
||||
if (this.sub) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log('patchDB: STARTING')
|
||||
this.sub = this.patchDb.cache$
|
||||
.pipe(
|
||||
debounceTime(420),
|
||||
tap(cache => {
|
||||
this.bootstrapper.update(cache)
|
||||
}),
|
||||
)
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
// Early return if already stopped
|
||||
if (!this.sub) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log('patchDB: STOPPING')
|
||||
this.patchDb.store.reset()
|
||||
this.sub.unsubscribe()
|
||||
this.sub = undefined
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
watch$: Store<DataModel>['watch$'] = (...args: (string | number)[]): Observable<DataModel> => {
|
||||
const argsString = '/' + args.join('/')
|
||||
|
||||
console.log('patchDB: WATCHING ', argsString)
|
||||
|
||||
return this.patchDb.store.watch$(...(args as [])).pipe(
|
||||
tap(data => console.log('patchDB: NEW VALUE', argsString, data)),
|
||||
catchError(e => {
|
||||
console.error('patchDB: WATCH ERROR', e)
|
||||
return of(e.message)
|
||||
}),
|
||||
finalize(() => console.log('patchDB: UNSUBSCRIBING', argsString)),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,31 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Observable } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { tap } from 'rxjs/operators'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { AuthService } from 'src/app/services/auth.service'
|
||||
import { DataModel } from './patch-db/data-model'
|
||||
import { LocalStorageBootstrap } from './patch-db/local-storage-bootstrap'
|
||||
|
||||
// Start and stop PatchDb upon verification
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class PatchMonitorService extends Observable<boolean> {
|
||||
export class PatchMonitorService extends Observable<any> {
|
||||
// @TODO not happy with Observable<void>
|
||||
private readonly stream$ = this.authService.isVerified$.pipe(
|
||||
map(verified => {
|
||||
tap(verified => {
|
||||
if (verified) {
|
||||
this.patch.start()
|
||||
return true
|
||||
this.patch.start(this.bootstrapper)
|
||||
} else {
|
||||
this.patch.stop()
|
||||
}
|
||||
this.patch.stop()
|
||||
return false
|
||||
}),
|
||||
)
|
||||
|
||||
constructor(
|
||||
private readonly authService: AuthService,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
private readonly bootstrapper: LocalStorageBootstrap,
|
||||
) {
|
||||
super(subscriber => this.stream$.subscribe(subscriber))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { PatchDbService } from './patch-db/patch-db.service'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { combineLatest, filter, map, Observable } from 'rxjs'
|
||||
import { DataModel } from './patch-db/data-model'
|
||||
|
||||
export interface ServerNameInfo {
|
||||
current: string
|
||||
@@ -26,5 +27,5 @@ export class ServerNameService {
|
||||
}),
|
||||
)
|
||||
|
||||
constructor(private readonly patch: PatchDbService) {}
|
||||
constructor(private readonly patch: PatchDB<DataModel>) {}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { UIMarketplaceData } from 'src/app/services/patch-db/data-model'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
DataModel,
|
||||
UIMarketplaceData,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { filter, firstValueFrom } from 'rxjs'
|
||||
|
||||
export function getMarketplace(
|
||||
patch: PatchDbService,
|
||||
patch: PatchDB<DataModel>,
|
||||
): Promise<UIMarketplaceData> {
|
||||
return firstValueFrom(patch.watch$('ui', 'marketplace').pipe(filter(Boolean)))
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
DataModel,
|
||||
PackageDataEntry,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { filter, firstValueFrom } from 'rxjs'
|
||||
|
||||
export function getPackage(
|
||||
patch: PatchDbService,
|
||||
patch: PatchDB<DataModel>,
|
||||
id: string,
|
||||
): Promise<PackageDataEntry> {
|
||||
return firstValueFrom(patch.watch$('package-data', id))
|
||||
}
|
||||
|
||||
export function getAllPackages(
|
||||
patch: PatchDbService,
|
||||
patch: PatchDB<DataModel>,
|
||||
): Promise<Record<string, PackageDataEntry>> {
|
||||
return firstValueFrom(patch.watch$('package-data').pipe(filter(Boolean)))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { ServerInfo } from 'src/app/services/patch-db/data-model'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel, ServerInfo } from 'src/app/services/patch-db/data-model'
|
||||
import { filter, firstValueFrom } from 'rxjs'
|
||||
|
||||
export function getServerInfo(patch: PatchDbService): Promise<ServerInfo> {
|
||||
export function getServerInfo(patch: PatchDB<DataModel>): Promise<ServerInfo> {
|
||||
return firstValueFrom(patch.watch$('server-info').pipe(filter(Boolean)))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
@import '~swiper/scss';
|
||||
@import '~@ionic/angular/css/ionic-swiper';
|
||||
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
@@ -61,12 +58,6 @@
|
||||
|
||||
$subheader-height: 48px;
|
||||
|
||||
.swiper {
|
||||
.swiper-slide {
|
||||
display: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-128 {
|
||||
min-width: 128px;
|
||||
}
|
||||
@@ -253,7 +244,7 @@ ion-item-divider {
|
||||
}
|
||||
|
||||
ion-item {
|
||||
border-radius: 6px;
|
||||
--border-radius: 6px;
|
||||
--ripple-color: transparent;
|
||||
}
|
||||
|
||||
@@ -266,12 +257,6 @@ ion-loading {
|
||||
z-index: 40000 !important;
|
||||
}
|
||||
|
||||
.swiper-pagination {
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
.rec-item {
|
||||
margin: 20px;
|
||||
border-style: solid;
|
||||
|
||||
Reference in New Issue
Block a user