rework modals and more

This commit is contained in:
Matt Hill
2021-07-05 20:26:42 -06:00
committed by Aiden McClelland
parent b16ef3c621
commit 01c6b91c52
84 changed files with 380 additions and 508 deletions

View File

@@ -1,34 +1,24 @@
<ion-app>
<ion-split-pane [disabled]="!showMenu" (ionSplitPaneVisible)="splitPaneVisible($event)" contentId="main-content">
<ion-menu contentId="main-content" type="overlay">
<ion-header>
<ion-toolbar style="--background: var(--ion-background-color);">
<ion-title>Menu</ion-title>
<ng-template #dots>
<ion-title><ion-spinner name="dots" color="warning"></ion-spinner></ion-title>
</ng-template>
</ion-toolbar>
</ion-header>
<ion-content scrollY="false" class="menu-style">
<ng-container>
<ion-list style="padding: 0px">
<ion-menu-toggle auto-hide="false" *ngFor="let page of appPages; let i = index">
<ion-item
button
(click)="selectedIndex = i"
routerDirection="root"
[routerLink]="[page.url]"
lines="none"
detail="false"
[class.selected]="selectedIndex === i"
>
<ion-icon slot="start" [name]="page.icon"></ion-icon>
<ion-label style="font-family: 'Montserrat';">{{ page.title }}</ion-label>
<ion-badge *ngIf="page.url === '/notifications' && unreadCount" color="danger" style="margin-right: 3%;" [class.selected-badge]="selectedIndex == i">{{ unreadCount }}</ion-badge>
</ion-item>
</ion-menu-toggle>
</ion-list>
</ng-container>
<ion-list style="padding: 0px">
<ion-menu-toggle auto-hide="false" *ngFor="let page of appPages; let i = index">
<ion-item
button
(click)="selectedIndex = i"
routerDirection="root"
[routerLink]="[page.url]"
lines="none"
detail="false"
[class.selected]="selectedIndex === i"
>
<ion-icon slot="start" [name]="page.icon"></ion-icon>
<ion-label style="font-family: 'Montserrat';">{{ page.title }}</ion-label>
<ion-badge *ngIf="page.url === '/notifications' && unreadCount" color="danger" style="margin-right: 3%;" [class.selected-badge]="selectedIndex == i">{{ unreadCount }}</ion-badge>
</ion-item>
</ion-menu-toggle>
</ion-list>
</ion-content>
<ion-footer style="padding-bottom: 16px; text-align: center;" class="menu-style">
<ion-menu-toggle auto-hide="false">

View File

@@ -3,17 +3,16 @@ import { Storage } from '@ionic/storage'
import { AuthService, AuthState } from './services/auth.service'
import { ApiService } from './services/api/api.service'
import { Router, RoutesRecognized } from '@angular/router'
import { distinctUntilChanged, filter, finalize, takeWhile } from 'rxjs/operators'
import { debounceTime, distinctUntilChanged, filter, finalize, takeWhile } from 'rxjs/operators'
import { AlertController, ToastController } from '@ionic/angular'
import { LoaderService } from './services/loader.service'
import { Emver } from './services/emver.service'
import { SplitPaneTracker } from './services/split-pane.service'
import { LoadingOptions } from '@ionic/core'
import { PatchDbModel } from './models/patch-db/patch-db-model'
import { PatchDbModel } from './services/patch-db/patch-db.service'
import { HttpService } from './services/http.service'
import { ServerStatus } from './models/patch-db/data-model'
import { ServerStatus } from './services/patch-db/data-model'
import { ConnectionFailure, ConnectionService } from './services/connection.service'
import { combineLatest, merge } from 'rxjs'
@Component({
selector: 'app-root',
@@ -29,7 +28,7 @@ export class AppComponent {
unreadCount: number
appPages = [
{
title: 'Services',
title: 'Installed Services',
url: '/services',
icon: 'grid-outline',
},
@@ -39,7 +38,7 @@ export class AppComponent {
icon: 'cube-outline',
},
{
title: 'Marketplace',
title: 'Service Marketplace',
url: '/marketplace',
icon: 'storefront-outline',
},
@@ -120,19 +119,38 @@ export class AppComponent {
}
private watchConnection (auth: AuthState): void {
this.connectionService.watch$()
this.connectionService.watchFailure$()
.pipe(
distinctUntilChanged(),
debounceTime(500),
takeWhile(() => auth === AuthState.VERIFIED),
)
.subscribe(connectionFailure => {
if (connectionFailure !== ConnectionFailure.None) {
this.presentToastOffline()
} else {
.subscribe(async connectionFailure => {
if (connectionFailure === ConnectionFailure.None) {
if (this.offlineToast) {
this.offlineToast.dismiss()
this.offlineToast = undefined
}
} else {
let message: string
switch (connectionFailure) {
case ConnectionFailure.Network:
message = 'No network'
break
case ConnectionFailure.Diagnosing:
message = 'Diagnosing'
break
case ConnectionFailure.Embassy:
message = 'Embassy is unreachable'
break
case ConnectionFailure.Tor:
message = 'Tor issues'
break
case ConnectionFailure.Internet:
message = 'No Internet'
break
}
await this.presentToastOffline(message)
}
})
}
@@ -237,10 +255,15 @@ export class AppComponent {
await toast.present()
}
private async presentToastOffline () {
private async presentToastOffline (message: string) {
if (this.offlineToast) {
this.offlineToast.message = message
return
}
this.offlineToast = await this.toastCtrl.create({
header: 'No Internet',
message: `Please check your Internet connection and try again.`,
header: 'Connection Issue',
message,
position: 'bottom',
duration: 0,
buttons: [

View File

@@ -9,16 +9,17 @@ import { AppComponent } from './app.component'
import { AppRoutingModule } from './app-routing.module'
import { ApiService } from './services/api/api.service'
import { ApiServiceFactory } from './services/api/api.service.factory'
import { PatchDbModelFactory } from './models/patch-db/patch-db-model.factory'
import { PatchDbModelFactory } from './services/patch-db/patch-db.factory'
import { HttpService } from './services/http.service'
import { ConfigService } from './services/config.service'
import { QRCodeModule } from 'angularx-qrcode'
import { APP_CONFIG_COMPONENT_MAPPING } from './modals/app-config-injectable/modal-injectable-token'
import { appConfigComponents } from './modals/app-config-injectable/modal-injectable-value'
import { appConfigComponents } from './modals/app-config-injectable'
import { OSWelcomePageModule } from './modals/os-welcome/os-welcome.module'
import { PatchDbModel } from './models/patch-db/patch-db-model'
import { LocalStorageBootstrap } from './models/patch-db/local-storage-bootstrap'
import { MarkdownPageModule } from './modals/markdown/markdown.module'
import { PatchDbModel } from './services/patch-db/patch-db.service'
import { LocalStorageBootstrap } from './services/patch-db/local-storage-bootstrap'
import { SharingModule } from './modules/sharing.module'
import { APP_CONFIG_COMPONENT_MAPPING } from './services/tracking-modal-controller.service'
@NgModule({
declarations: [AppComponent],
@@ -36,6 +37,7 @@ import { SharingModule } from './modules/sharing.module'
}),
QRCodeModule,
OSWelcomePageModule,
MarkdownPageModule,
SharingModule,
],
providers: [

View File

@@ -1,6 +1,6 @@
import { Component } from '@angular/core'
import { SplitPaneTracker } from 'src/app/services/split-pane.service'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { combineLatest, Subscription } from 'rxjs'
@Component({

View File

@@ -1,11 +0,0 @@
<div style="padding: 15px;
border-style: solid;
border-width: 1px;
background: var(--ion-color-light);
border-radius: 10px;
border-color: var(--ion-color-warning);
color: white;
font-size: small;
"
[innerHTML]="unsafeInformation">
</div>

View File

@@ -1,20 +0,0 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { InformationPopoverComponent } from './information-popover.component'
import { IonicModule } from '@ionic/angular'
import { RouterModule } from '@angular/router'
import { SharingModule } from 'src/app/modules/sharing.module'
@NgModule({
declarations: [
InformationPopoverComponent,
],
imports: [
CommonModule,
IonicModule,
RouterModule.forChild([]),
SharingModule,
],
exports: [InformationPopoverComponent],
})
export class InformationPopoverComponentModule { }

View File

@@ -1,18 +0,0 @@
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core'
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
@Component({
selector: 'app-information-popover',
templateUrl: './information-popover.component.html',
styleUrls: ['./information-popover.component.scss'],
encapsulation: ViewEncapsulation.None,
})
export class InformationPopoverComponent implements OnInit {
@Input() title: string
@Input() information: string
unsafeInformation: SafeHtml
constructor (private sanitizer: DomSanitizer) { }
ngOnInit () {
this.unsafeInformation = this.sanitizer.bypassSecurityTrustHtml(this.information)
}
}

View File

@@ -4,7 +4,6 @@ import { DependentsComponent } from './dependents.component'
import { IonicModule } from '@ionic/angular'
import { RouterModule } from '@angular/router'
import { SharingModule } from 'src/app/modules/sharing.module'
import { InformationPopoverComponentModule } from '../../information-popover/information-popover.component.module'
@NgModule({
declarations: [
@@ -15,7 +14,6 @@ import { InformationPopoverComponentModule } from '../../information-popover/inf
IonicModule,
RouterModule.forChild([]),
SharingModule,
InformationPopoverComponentModule,
],
exports: [DependentsComponent],
})

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'
import { InstalledPackageDataEntry } from 'src/app/models/patch-db/data-model'
import { InstalledPackageDataEntry } from 'src/app/services/patch-db/data-model'
import { Breakages } from 'src/app/services/api/api-types'
import { exists } from 'src/app/util/misc.util'
import { ApiService } from '../../services/api/api.service'

View File

@@ -1,9 +0,0 @@
<ion-fab id="recommendation" vertical="top" horizontal="end" slot="fixed">
<ion-fab-button [disabled]="disabled" style="border-style: solid;
border-radius: 100px;
border-color: #FFEB3B;
border-width: medium;
box-shadow: 0 0 10px white;" size="small" (click)="presentPopover($event)">
<img [src]="rec.iconURL" />
</ion-fab-button>
</ion-fab>

View File

@@ -1,20 +0,0 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { RecommendationButtonComponent } from './recommendation-button.component'
import { IonicModule } from '@ionic/angular'
import { RouterModule } from '@angular/router'
import { SharingModule } from 'src/app/modules/sharing.module'
@NgModule({
declarations: [
RecommendationButtonComponent,
],
imports: [
CommonModule,
IonicModule,
RouterModule.forChild([]),
SharingModule,
],
exports: [RecommendationButtonComponent],
})
export class RecommendationButtonComponentModule { }

View File

@@ -1,65 +0,0 @@
import { Component, Input } from '@angular/core'
import { Router } from '@angular/router'
import { PopoverController } from '@ionic/angular'
import { filter, take } from 'rxjs/operators'
import { capitalizeFirstLetter } from 'src/app/util/misc.util'
import { InformationPopoverComponent } from '../information-popover/information-popover.component'
@Component({
selector: 'recommendation-button',
templateUrl: './recommendation-button.component.html',
styleUrls: ['./recommendation-button.component.scss'],
})
export class RecommendationButtonComponent {
@Input() rec: Recommendation
@Input() raise?: { id: string }
constructor (
private readonly router: Router,
private readonly popoverController: PopoverController,
) { }
ngOnInit () {
if (!this.raise) return
const mainContent = document.getElementsByTagName('ion-app')[0]
const recButton = document.getElementById(this.raise.id)
mainContent.appendChild(recButton)
this.router.events.pipe(filter(e => !!(e as any).urlAfterRedirects, take(1))).subscribe((e: any) => {
recButton.remove()
})
}
disabled = false
async presentPopover (ev: any) {
const popover = await this.popoverController.create({
component: InformationPopoverComponent,
event: ev,
translucent: false,
showBackdrop: true,
backdropDismiss: true,
componentProps: {
information: `
<div style="font-size: medium; font-style: italic; margin: 5px 0px;">
${capitalizeFirstLetter(this.rec.dependentTitle)} Installation Recommendations
</div>
<div>
${this.rec.description}
</div>`,
},
})
popover.onWillDismiss().then(() => {
this.disabled = false
})
this.disabled = true
return await popover.present()
}
}
export type Recommendation = {
dependentId: string
dependentTitle: string
dependentIcon: string,
description: string
version?: string
}

View File

@@ -1,7 +1,6 @@
import { Component, Input } from '@angular/core'
import { PackageDataEntry } from 'src/app/models/patch-db/data-model'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { renderPkgStatus } from 'src/app/services/pkg-status-rendering.service'
import { ConnectionStatus } from 'patch-db-client'
@Component({
selector: 'status',

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@angular/core'
import { CanActivate, Router, CanActivateChild } from '@angular/router'
import { tap } from 'rxjs/operators'
import { ServerStatus } from '../models/patch-db/data-model'
import { PatchDbModel } from '../models/patch-db/patch-db-model'
import { ServerStatus } from '../services/patch-db/data-model'
import { PatchDbModel } from '../services/patch-db/patch-db.service'
@Injectable({
providedIn: 'root',

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@angular/core'
import { CanActivate, Router } from '@angular/router'
import { tap } from 'rxjs/operators'
import { ServerStatus } from '../models/patch-db/data-model'
import { PatchDbModel } from '../models/patch-db/patch-db-model'
import { ServerStatus } from '../services/patch-db/data-model'
import { PatchDbModel } from '../services/patch-db/patch-db.service'
@Injectable({
providedIn: 'root',

View File

@@ -0,0 +1,19 @@
import { AppConfigObjectPage } from './app-config-object/app-config-object.page'
import { AppConfigListPage } from './app-config-list/app-config-list.page'
import { AppConfigUnionPage } from './app-config-union/app-config-union.page'
import { AppConfigValuePage } from './app-config-value/app-config-value.page'
import { Type } from '@angular/core'
import { ValueType } from 'src/app/pkg-config/config-types'
export const appConfigComponents: AppConfigComponentMapping = {
'string': AppConfigValuePage,
'number': AppConfigValuePage,
'enum': AppConfigValuePage,
'boolean': AppConfigValuePage,
'list': AppConfigListPage,
'object': AppConfigObjectPage,
'union': AppConfigUnionPage,
'pointer': undefined,
}
export type AppConfigComponentMapping = { [k in ValueType]: Type<any> }

View File

@@ -1,3 +0,0 @@
import { InjectionToken } from '@angular/core'
export const APP_CONFIG_COMPONENT_MAPPING = new InjectionToken<string>('APP_CONFIG_COMPONENTS')

View File

@@ -1,4 +0,0 @@
import { Type } from '@angular/core'
import { ValueType } from 'src/app/pkg-config/config-types'
export type AppConfigComponentMapping = { [k in ValueType]: Type<any> }

View File

@@ -1,16 +0,0 @@
import { AppConfigObjectPage } from '../app-config-object/app-config-object.page'
import { AppConfigListPage } from '../app-config-list/app-config-list.page'
import { AppConfigUnionPage } from '../app-config-union/app-config-union.page'
import { AppConfigValuePage } from '../app-config-value/app-config-value.page'
import { AppConfigComponentMapping } from './modal-injectable-type'
export const appConfigComponents: AppConfigComponentMapping = {
'string': AppConfigValuePage,
'number': AppConfigValuePage,
'enum': AppConfigValuePage,
'boolean': AppConfigValuePage,
'list': AppConfigListPage,
'object': AppConfigObjectPage,
'union': AppConfigUnionPage,
'pointer': undefined,
}

View File

@@ -1,8 +1,7 @@
import { Component, Input } from '@angular/core'
import { getDefaultConfigValue, getDefaultDescription, Range } from 'src/app/pkg-config/config-utilities'
import { AlertController, ToastController } from '@ionic/angular'
import { AlertController, ModalController, ToastController } from '@ionic/angular'
import { LoaderService } from 'src/app/services/loader.service'
import { TrackingModalController } from 'src/app/services/tracking-modal-controller.service'
import { ConfigCursor } from 'src/app/pkg-config/config-cursor'
import { ValueSpecOf } from 'src/app/pkg-config/config-types'
import { copyToClipboard } from 'src/app/util/web.util'
@@ -31,7 +30,7 @@ export class AppConfigValuePage {
constructor (
private readonly loader: LoaderService,
private readonly trackingModalCtrl: TrackingModalController,
private readonly modalCtrl: ModalController,
private readonly alertCtrl: AlertController,
private readonly toastCtrl: ToastController,
) { }
@@ -58,7 +57,7 @@ export class AppConfigValuePage {
if ((!!this.saveFn && this.edited) || (!this.saveFn && this.error)) {
await this.presentAlert()
} else {
await this.trackingModalCtrl.dismiss()
await this.modalCtrl.dismiss()
}
}
@@ -76,7 +75,7 @@ export class AppConfigValuePage {
}),
)
await this.trackingModalCtrl.dismiss(this.value)
await this.modalCtrl.dismiss(this.value)
}
refreshDefault () {
@@ -172,7 +171,7 @@ export class AppConfigValuePage {
text: `Leave`,
cssClass: 'alert-danger',
handler: () => {
this.trackingModalCtrl.dismiss()
this.modalCtrl.dismiss()
},
},
],

View File

@@ -1,10 +1,14 @@
<!-- TODO: EJECT-DISKS, add a check box to allow a user to eject a disk on backup completion. -->
<ion-content>
<div style="height: 85%; margin: 20px; display: flex; flex-direction: column; justify-content: space-between;">
<div>
<h4><ion-text color="dark">Ready to Backup</ion-text></h4>
<div style="height: 85%; margin: 24px; display: flex; flex-direction: column; justify-content: space-between;">
<div *ngIf="type === 'backup'">
<h4><ion-text color="dark">Encrypt Backup</ion-text></h4>
<p><ion-text color="medium">Enter your master password to create an encrypted backup.</ion-text></p>
</div>
<div *ngIf="type === 'restore'">
<h4><ion-text color="dark">Decrypt Backup</ion-text></h4>
<p><ion-text color="medium">Enter the password that was originally used to encrypt this backup.</ion-text></p>
</div>
<div>
<ion-item lines="none" style="--background: var(--ion-background-color); --border-color: var(--ion-color-medium);">
<ion-label style="font-size: small" position="floating">Master Password</ion-label>
@@ -14,12 +18,13 @@
<ion-label style="font-size: small" color="danger" class="ion-text-wrap">{{ error }}</ion-label>
</ion-item>
</div>
<div style="display: flex; justify-content: flex-end; align-items: center;">
<ion-button fill="clear" color="medium" (click)="cancel()">
Cancel
</ion-button>
<ion-button fill="clear" color="primary" (click)="submit()">
Create Backup
<ion-button fill="clear" (click)="submit()">
{{ type === 'backup' ? 'Create Backup' : 'Restore Backup' }}
</ion-button>
</div>
</div>

View File

@@ -1,6 +1,5 @@
import { Component, Input } from '@angular/core'
import { ModalController } from '@ionic/angular'
import { PartitionInfo } from 'src/app/services/api/api-types'
@Component({
selector: 'backup-confirmation',
@@ -8,20 +7,15 @@ import { PartitionInfo } from 'src/app/services/api/api-types'
styleUrls: ['./backup-confirmation.component.scss'],
})
export class BackupConfirmationComponent {
@Input() name: string
@Input() type: 'backup' | 'restore'
unmasked = false
password: string
message: string
password = ''
error = ''
constructor (
private readonly modalCtrl: ModalController,
) { }
ngOnInit () {
this.message = `Enter your master password to create an encrypted backup on "${this.name}".`
}
toggleMask () {
this.unmasked = !this.unmasked
}

View File

@@ -0,0 +1,15 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { MarkdownPage } from './markdown.page'
import { SharingModule } from 'src/app/modules/sharing.module'
@NgModule({
imports: [
CommonModule,
IonicModule,
SharingModule,
],
declarations: [MarkdownPage],
})
export class MarkdownPageModule { }

View File

@@ -0,0 +1,3 @@
<ion-content>
<div [innerHTML]="content | markdown"></div>
</ion-content>

View File

@@ -0,0 +1,19 @@
import { Component, Input } from '@angular/core'
import { ModalController } from '@ionic/angular'
@Component({
selector: 'markdown',
templateUrl: './markdown.page.html',
styleUrls: ['./markdown.page.scss'],
})
export class MarkdownPage {
@Input() content: string
constructor (
private readonly modalCtrl: ModalController,
) { }
async dismiss () {
return this.modalCtrl.dismiss(true)
}
}

View File

@@ -1,6 +0,0 @@
export class StorageKeys {
static APPS_CACHE_KEY = 'apps'
static SERVER_CACHE_KEY = 'embassy'
static LOGGED_IN_KEY = 'loggedInKey'
static VIEWED_INSTRUCTIONS_KEY = 'viewedInstructions'
}

View File

@@ -4,8 +4,8 @@ import { ApiService } from 'src/app/services/api/api.service'
import { AlertController, IonContent, ModalController, NavController } from '@ionic/angular'
import { LoaderService } from 'src/app/services/loader.service'
import { HttpErrorResponse } from '@angular/common/http'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { Action, InstalledPackageDataEntry, Manifest, PackageMainStatus } from 'src/app/models/patch-db/data-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { Action, InstalledPackageDataEntry, Manifest, PackageMainStatus } from 'src/app/services/patch-db/data-model'
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
import { Subscription } from 'rxjs'

View File

@@ -10,8 +10,6 @@ import { AppConfigObjectPageModule } from 'src/app/modals/app-config-object/app-
import { AppConfigUnionPageModule } from 'src/app/modals/app-config-union/app-config-union.module'
import { AppConfigValuePageModule } from 'src/app/modals/app-config-value/app-config-value.module'
import { SharingModule } from 'src/app/modules/sharing.module'
import { RecommendationButtonComponentModule } from 'src/app/components/recommendation-button/recommendation-button.component.module'
import { InformationPopoverComponentModule } from 'src/app/components/information-popover/information-popover.component.module'
const routes: Routes = [
{
@@ -32,8 +30,6 @@ const routes: Routes = [
FormsModule,
IonicModule,
RouterModule.forChild(routes),
RecommendationButtonComponentModule,
InformationPopoverComponentModule,
],
declarations: [AppConfigPage],
})

View File

@@ -1,20 +1,18 @@
import { Component, ViewChild } from '@angular/core'
import { NavController, AlertController, ModalController, PopoverController, IonContent } from '@ionic/angular'
import { NavController, AlertController, ModalController, IonContent } from '@ionic/angular'
import { ActivatedRoute } from '@angular/router'
import { ApiService } from 'src/app/services/api/api.service'
import { isEmptyObject } from 'src/app/util/misc.util'
import { isEmptyObject, Recommendation } from 'src/app/util/misc.util'
import { LoaderService } from 'src/app/services/loader.service'
import { TrackingModalController } from 'src/app/services/tracking-modal-controller.service'
import { from, fromEvent, of, Subscription } from 'rxjs'
import { catchError, concatMap, map, take, tap } from 'rxjs/operators'
import { Recommendation } from 'src/app/components/recommendation-button/recommendation-button.component'
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
import { InformationPopoverComponent } from 'src/app/components/information-popover/information-popover.component'
import { ConfigSpec } from 'src/app/pkg-config/config-types'
import { ConfigCursor } from 'src/app/pkg-config/config-cursor'
import { InstalledPackageDataEntry, PackageState } from 'src/app/models/patch-db/data-model'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { InstalledPackageDataEntry, PackageState } from 'src/app/services/patch-db/data-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
@Component({
selector: 'app-config',
@@ -31,7 +29,7 @@ export class AppConfigPage {
pkg: InstalledPackageDataEntry
hasConfig = false
backButtonDefense = false
mocalShowing = false
packageState = PackageState
rec: Recommendation | null = null
@@ -57,7 +55,6 @@ export class AppConfigPage {
private readonly alertCtrl: AlertController,
private readonly modalController: ModalController,
private readonly trackingModalCtrl: TrackingModalController,
private readonly popoverController: PopoverController,
private readonly patch: PatchDbModel,
) { }
@@ -71,17 +68,17 @@ export class AppConfigPage {
}
}),
fromEvent(window, 'popstate').subscribe(() => {
this.backButtonDefense = false
this.mocalShowing = false
this.trackingModalCtrl.dismissAll()
}),
this.trackingModalCtrl.onCreateAny$().subscribe(() => {
if (!this.backButtonDefense) {
if (!this.mocalShowing) {
window.history.pushState(null, null, window.location.href + '/edit')
this.backButtonDefense = true
this.mocalShowing = true
}
}),
this.trackingModalCtrl.onDismissAny$().subscribe(() => {
if (!this.trackingModalCtrl.anyModals && this.backButtonDefense === true) {
if (!this.trackingModalCtrl.anyModals && this.mocalShowing === true) {
this.navCtrl.back()
}
}),
@@ -135,28 +132,6 @@ export class AppConfigPage {
this.subs.forEach(sub => sub.unsubscribe())
}
async presentPopover (title: string, description: string, ev: any) {
const information = `
<div style="font-size: medium; font-style: italic; margin: 5px 0px;">
${title}
</div>
<div>
${description}
</div>
`
const popover = await this.popoverController.create({
component: InformationPopoverComponent,
event: ev,
translucent: false,
showBackdrop: true,
backdropDismiss: true,
componentProps: {
information,
},
})
return await popover.present()
}
setConfig (spec: ConfigSpec, config: object, dependencyConfig?: object) {
this.rootCursor = dependencyConfig ? new ConfigCursor(spec, config, null, dependencyConfig) : new ConfigCursor(spec, config)
this.spec = this.rootCursor.spec().spec

View File

@@ -3,7 +3,7 @@ import { ActivatedRoute } from '@angular/router'
import { IonContent } from '@ionic/angular'
import { Subscription } from 'rxjs'
import { concatMap, take, tap } from 'rxjs/operators'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { ApiService } from 'src/app/services/api/api.service'
@Component({

View File

@@ -2,8 +2,8 @@ import { Component, ViewChild } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { IonContent, ToastController } from '@ionic/angular'
import { Subscription } from 'rxjs'
import { InstalledPackageDataEntry, PackageDataEntry } from 'src/app/models/patch-db/data-model'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { InstalledPackageDataEntry, PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { ConfigService } from 'src/app/services/config.service'
import { copyToClipboard } from 'src/app/util/web.util'

View File

@@ -1,8 +1,8 @@
import { Component } from '@angular/core'
import { ConfigService } from 'src/app/services/config.service'
import { ConnectionService } from 'src/app/services/connection.service'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { PackageDataEntry } from 'src/app/models/patch-db/data-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { Subscription } from 'rxjs'
@Component({

View File

@@ -1,8 +1,8 @@
import { Component, ViewChild } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { Subscription } from 'rxjs'
import { PackageDataEntry } from 'src/app/models/patch-db/data-model'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { getManifest } from 'src/app/services/config.service'
import * as JsonPointer from 'json-pointer'
import { IonContent } from '@ionic/angular'

View File

@@ -2,8 +2,8 @@ import { Component, ViewChild } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { IonContent } from '@ionic/angular'
import { Subscription } from 'rxjs'
import { PackageDataEntry } from 'src/app/models/patch-db/data-model'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
@Component({
selector: 'app-metrics',

View File

@@ -6,10 +6,10 @@ import { copyToClipboard } from 'src/app/util/web.util'
import { AlertController, IonContent, NavController, PopoverController, ToastController } from '@ionic/angular'
import { PackageProperties } from 'src/app/util/properties.util'
import { QRComponent } from 'src/app/components/qr/qr.component'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import * as JsonPointer from 'json-pointer'
import { FEStatus } from 'src/app/services/pkg-status-rendering.service'
import { PackageMainStatus } from 'src/app/models/patch-db/data-model'
import { PackageMainStatus } from 'src/app/services/patch-db/data-model'
@Component({
selector: 'app-properties',

View File

@@ -57,7 +57,7 @@
</ion-card-header>
<ion-card-content>
<ion-item-group>
<ion-item button *ngFor="let partition of disk.value.partitions | keyvalue" [disabled]="partition.value['is-mounted']" (click)="presentModal(partition.key, partition.value)">
<ion-item button *ngFor="let partition of disk.value.partitions | keyvalue" [disabled]="partition.value['is-mounted']" (click)="presentModal(partition.key)">
<ion-icon slot="start" name="save-outline"></ion-icon>
<ion-label>
<h2>{{ partition.value.label || partition.key }} ({{ partition.value.size || 'unknown size' }})</h2>

View File

@@ -4,7 +4,7 @@ import { ApiService } from 'src/app/services/api/api.service'
import { BackupConfirmationComponent } from 'src/app/modals/backup-confirmation/backup-confirmation.component'
import { DiskInfo, PartitionInfoEntry } from 'src/app/services/api/api-types'
import { ActivatedRoute } from '@angular/router'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { Subscription } from 'rxjs'
@Component({
@@ -69,10 +69,10 @@ export class AppRestorePage {
}
}
async presentModal (logicalname: string, partition: PartitionInfoEntry): Promise<void> {
async presentModal (logicalname: string): Promise<void> {
const m = await this.modalCtrl.create({
componentProps: {
name: partition.label || logicalname,
type: 'restore',
},
cssClass: 'alertlike-modal',
component: BackupConfirmationComponent,

View File

@@ -33,16 +33,16 @@
<div class="status-readout">
<status *ngIf="connected" size="large" weight="500" [pkg]="pkg"></status>
<ion-button *ngIf="pkg.status === FeStatus.NeedsConfig" expand="block" [routerLink]="['config']">
<ion-button *ngIf="(pkg | status) === FeStatus.NeedsConfig" expand="block" [routerLink]="['config']">
Configure
</ion-button>
<ion-button *ngIf="[FeStatus.Running, FeStatus.StartingUp, FeStatus.NeedsAttention] | includes : pkg.status" expand="block" color="danger" (click)="stop()">
<ion-button *ngIf="[FeStatus.Running, FeStatus.StartingUp, FeStatus.NeedsAttention] | includes : (pkg | status)" expand="block" color="danger" (click)="stop()">
Stop
</ion-button>
<ion-button *ngIf="pkg.status === FeStatus.DependencyIssue" expand="block" (click)="scrollToRequirements()">
<ion-button *ngIf="(pkg | status) === FeStatus.DependencyIssue" expand="block" (click)="scrollToRequirements()">
Fix
</ion-button>
<ion-button *ngIf="pkg.status === FeStatus.Stopped" expand="block" color="success" (click)="tryStart()">
<ion-button *ngIf="(pkg | status) === FeStatus.Stopped" expand="block" color="success" (click)="tryStart()">
Start
</ion-button>
</div>
@@ -86,23 +86,23 @@
<p><ion-text [color]="pkg.installed.status['dependency-errors'][dep.key] ? 'warning' : 'success'">{{ pkg.installed.status['dependency-errors'][dep.key] ? pkg.installed.status['dependency-errors'][dep.key].type : 'satisfied' }}</ion-text></p>
</ion-label>
<ion-button *ngIf="!pkg.installed.status['dependency-errors'][dep.key] || (pkg.installed.status['dependency-errors'][dep.key] && [DependencyErrorType.InterfaceHealthChecksFailed, DependencyErrorType.HealthChecksFailed] | includes : pkg.installed.status['dependency-errors'][dep.key].type)" slot="end" size="small" color="dark" [routerLink]="['/services', dep.key]" fill="outline">
<ion-button *ngIf="!pkg.installed.status['dependency-errors'][dep.key] || (pkg.installed.status['dependency-errors'][dep.key] && [DependencyErrorType.InterfaceHealthChecksFailed, DependencyErrorType.HealthChecksFailed] | includes : pkg.installed.status['dependency-errors'][dep.key].type)" slot="end" size="small" [routerLink]="['/services', dep.key]">
View
</ion-button>
<ng-container *ngIf="pkg.installed.status['dependency-errors'][dep.key]">
<ion-button *ngIf="!localDep" slot="end" size="small" color="dark" (click)="fixDep('install', dep.key)" fill="outline">
<ion-button *ngIf="!localDep" slot="end" size="small" (click)="fixDep('install', dep.key)">
Install
</ion-button>
<ng-container *ngIf="localDep && localDep.state === PackageState.Installed">
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.NotRunning" slot="end" size="small" color="dark" [routerLink]="['/services', dep.key]" fill="outline">
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.NotRunning" slot="end" size="small" [routerLink]="['/services', dep.key]">
Start
</ion-button>
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.IncorrectVersion" slot="end" size="small" color="dark" (click)="fixDep('update', dep.key)" fill="outline">
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.IncorrectVersion" slot="end" size="small" (click)="fixDep('update', dep.key)">
Update
</ion-button>
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.ConfigUnsatisfied" slot="end" size="small" color="dark" (click)="fixDep('configure', dep.key)" fill="outline">
<ion-button *ngIf="pkg.installed.status['dependency-errors'][dep.key].type === DependencyErrorType.ConfigUnsatisfied" slot="end" size="small" (click)="fixDep('configure', dep.key)">
Configure
</ion-button>
</ng-container>

View File

@@ -2,17 +2,16 @@ import { Component, ViewChild } from '@angular/core'
import { AlertController, NavController, ModalController, IonContent } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/api.service'
import { ActivatedRoute, NavigationExtras } from '@angular/router'
import { chill, isEmptyObject } from 'src/app/util/misc.util'
import { chill, isEmptyObject, Recommendation } from 'src/app/util/misc.util'
import { LoaderService } from 'src/app/services/loader.service'
import { Observable, of, Subscription } from 'rxjs'
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
import { ConfigService, getManifest } from 'src/app/services/config.service'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { DependencyErrorConfigUnsatisfied, DependencyErrorNotInstalled, DependencyErrorType, Manifest, PackageDataEntry, PackageState } from 'src/app/models/patch-db/data-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { DependencyErrorConfigUnsatisfied, DependencyErrorNotInstalled, DependencyErrorType, Manifest, PackageDataEntry, PackageState } from 'src/app/services/patch-db/data-model'
import { FEStatus } from 'src/app/services/pkg-status-rendering.service'
import { ConnectionService } from 'src/app/services/connection.service'
import { Recommendation } from 'src/app/components/recommendation-button/recommendation-button.component'
@Component({
selector: 'app-show',
@@ -80,7 +79,9 @@ export class AppShowPage {
}).displayDuringAsync(async () => {
const breakages = await this.apiService.dryStopPackage({ id })
if (isEmptyObject(breakages.length)) {
console.log('BREAKAGES', breakages)
if (!isEmptyObject(breakages)) {
const { cancelled } = await wizardModal(
this.modalCtrl,
this.wizardBaker.stop({

View File

@@ -1,6 +1,6 @@
import { Component } from '@angular/core'
import { ServerStatus } from 'src/app/models/patch-db/data-model'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { ServerStatus } from 'src/app/services/patch-db/data-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
@Component({
selector: 'Maintenance',

View File

@@ -1,6 +1,6 @@
<ion-header>
<ion-toolbar>
<ion-title>Embassy Marketplace</ion-title>
<ion-title>Service Marketplace</ion-title>
<ion-buttons slot="end">
<badge-menu-button></badge-menu-button>
</ion-buttons>

View File

@@ -4,8 +4,8 @@ import { MarketplaceData, MarketplaceEOS, AvailablePreview } from 'src/app/servi
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
import { ModalController } from '@ionic/angular'
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { PackageDataEntry, PackageState } from 'src/app/models/patch-db/data-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { PackageDataEntry, PackageState } from 'src/app/services/patch-db/data-model'
import { Subscription } from 'rxjs'
@Component({

View File

@@ -7,7 +7,6 @@ import { SharingModule } from 'src/app/modules/sharing.module'
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module'
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
import { StatusComponentModule } from 'src/app/components/status/status.component.module'
import { RecommendationButtonComponentModule } from 'src/app/components/recommendation-button/recommendation-button.component.module'
import { InstallWizardComponentModule } from 'src/app/components/install-wizard/install-wizard.component.module'
const routes: Routes = [
@@ -25,7 +24,6 @@ const routes: Routes = [
RouterModule.forChild(routes),
SharingModule,
PwaBackComponentModule,
RecommendationButtonComponentModule,
BadgeMenuComponentModule,
InstallWizardComponentModule,
],

View File

@@ -155,72 +155,65 @@
<p>{{ pkg.categories.join(', ') }}</p>
</ion-label>
</ion-item>
<ion-item detail="false">
<ion-item button detail="false" (click)="presentAlertVersions()">
<ion-label>
<h2>Other Versions</h2>
<p>Click to view other versions</p>
</ion-label>
<ion-button slot="end" fill="clear" (click)="presentAlertVersions()">
<ion-icon slot="icon-only" name="chevron-forward-outline"></ion-icon>
</ion-button>
<ion-icon slot="end" name="chevron-forward-outline"></ion-icon>
</ion-item>
<ion-item detail="false">
<ion-item button detail="false" (click)="presentModalMd(pkg.manifest.)">
<ion-label>
<h2>License</h2>
<p>{{ pkg.manifest.license }}</p>
</ion-label>
<ion-button slot="end" fill="clear" (click)="presentModalLicense()">
<ion-icon slot="icon-only" name="chevron-forward-outline"></ion-icon>
</ion-button>
<ion-icon slot="end" name="chevron-forward-outline"></ion-icon>
</ion-item>
<ion-item button detail="false" (click)="presentModalMd('instructions')">
<ion-label>
<h2>Instructions</h2>
<p>Click to view instructions</p>
</ion-label>
<ion-icon slot="end" name="chevron-forward-outline"></ion-icon>
</ion-item>
</ion-item-group>
</ion-col>
<ion-col>
<ion-col sizeSm="12" sizeMd="6">
<ion-item-group>
<ion-item detail="false">
<ion-item [href]="pkg.manifest['upstream-repo']" target="_blank" detail="false">
<ion-label>
<h2>Source Repository</h2>
<p>{{ pkg.manifest['upstream-repo'] }}</p>
</ion-label>
<ion-button slot="end" fill="clear" [href]="pkg.manifest['upstream-repo']" target="_blank">
<ion-icon slot="icon-only" name="open-outline"></ion-icon>
</ion-button>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
<ion-item detail="false">
<ion-item [href]="pkg.manifest['wrapper-repo']" target="_blank" detail="false">
<ion-label>
<h2>Wrapper Repository</h2>
<p>{{ pkg.manifest['wrapper-repo'] }}</p>
</ion-label>
<ion-button slot="end" fill="clear" [href]="pkg.manifest['wrapper-repo']" target="_blank">
<ion-icon slot="icon-only" name="open-outline"></ion-icon>
</ion-button>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
<ion-item detail="false">
<ion-item [href]="pkg.manifest['support-site']" target="_blank" detail="false">
<ion-label>
<h2>Support Site</h2>
<p>{{ pkg.manifest['support-site'] }}</p>
</ion-label>
<ion-button slot="end" fill="clear" [href]="pkg.manifest['support-site']" target="_blank">
<ion-icon slot="icon-only" name="open-outline"></ion-icon>
</ion-button>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
<ion-item detail="false">
<ion-item [href]="pkg.manifest['marketing-site']" target="_blank" detail="false">
<ion-label>
<h2>Marketing Site</h2>
<p>{{ pkg.manifest['marketing-site'] }}</p>
</ion-label>
<ion-button slot="end" fill="clear" [href]="pkg.manifest['marketing-site']" target="_blank">
<ion-icon slot="icon-only" name="open-outline"></ion-icon>
</ion-button>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
<ion-item *ngIf="pkg.manifest['donation-url'] as donationUrl" detail="false">
<ion-item *ngIf="pkg.manifest['donation-url'] as donationUrl" [href]="donationUrl" target="_blank" detail="false">
<ion-label>
<h2>Donation Site</h2>
<p>{{ donationUrl }}</p>
</ion-label>
<ion-button slot="end" fill="clear" [href]="donationUrl" target="_blank">
<ion-icon slot="icon-only" name="open-outline"></ion-icon>
</ion-button>
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-item>
</ion-item-group>
</ion-col>

View File

@@ -1,16 +1,16 @@
import { Component } from '@angular/core'
import { ActivatedRoute } from '@angular/router'
import { AlertController, ModalController, NavController } from '@ionic/angular'
import { Recommendation } from 'src/app/components/recommendation-button/recommendation-button.component'
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
import { Emver } from 'src/app/services/emver.service'
import { displayEmver } from 'src/app/pipes/emver.pipe'
import { pauseFor } from 'src/app/util/misc.util'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { PackageDataEntry, PackageState } from 'src/app/models/patch-db/data-model'
import { pauseFor, Recommendation } from 'src/app/util/misc.util'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { PackageDataEntry, PackageState } from 'src/app/services/patch-db/data-model'
import { MarketplaceService } from '../marketplace.service'
import { Subscription } from 'rxjs'
import { MarkdownPage } from 'src/app/modals/markdown/markdown.page'
@Component({
selector: 'marketplace-show',
@@ -35,7 +35,7 @@ export class MarketplaceShowPage {
private readonly navCtrl: NavController,
private readonly emver: Emver,
private readonly patch: PatchDbModel,
public marketplaceService: MarketplaceService,
public readonly marketplaceService: MarketplaceService,
) { }
async ngOnInit () {
@@ -95,6 +95,17 @@ export class MarketplaceShowPage {
await alert.present()
}
async presentModalMd (content: string) {
const modal = await this.modalCtrl.create({
componentProps: {
content,
},
component: MarkdownPage,
})
await modal.present()
}
async install () {
const { id, title, version, dependencies, alerts } = this.marketplaceService.pkgs[this.pkgId].manifest
const { cancelled } = await wizardModal(

View File

@@ -1,7 +1,7 @@
import { Component } from '@angular/core'
import { ServerConfigService } from 'src/app/services/server-config.service'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { ServerInfo } from 'src/app/models/patch-db/data-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { ServerInfo } from 'src/app/services/patch-db/data-model'
import { Subscription } from 'rxjs'
@Component({

View File

@@ -4,7 +4,7 @@ import { copyToClipboard } from 'src/app/util/web.util'
import { ConfigService } from 'src/app/services/config.service'
import { LoaderService } from 'src/app/services/loader.service'
import { ApiService } from 'src/app/services/api/api.service'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { Subscription } from 'rxjs'
@Component({

View File

@@ -10,10 +10,6 @@
<ion-content class="ion-padding-top">
<ion-item-group>
<ion-item button (click)="presentModalValueEdit('name', ui['server-name'])">
<ion-label>Embassy Name</ion-label>
<ion-note slot="end">{{ ui['server-name'] }}</ion-note>
</ion-item>
<ion-item button (click)="presentModalValueEdit('autoCheckUpdates', ui['auto-check-updates'])">
<ion-label>Auto Check for Updates</ion-label>
<ion-note slot="end">{{ ui['auto-check-updates'] }}</ion-note>

View File

@@ -1,8 +1,8 @@
import { Component } from '@angular/core'
import { ServerConfigService } from 'src/app/services/server-config.service'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { Subscription } from 'rxjs'
import { UIData } from 'src/app/models/patch-db/data-model'
import { UIData } from 'src/app/services/patch-db/data-model'
@Component({
selector: 'preferences',

View File

@@ -52,7 +52,7 @@
</ion-card-header>
<ion-card-content>
<ion-item-group>
<ion-item button *ngFor="let partition of disk.value.partitions | keyvalue" [disabled]="partition.value['is-mounted']" (click)="presentModal(partition.key, partition.value)">
<ion-item button *ngFor="let partition of disk.value.partitions | keyvalue" [disabled]="partition.value['is-mounted']" (click)="presentModal(partition.key)">
<ion-icon slot="start" name="save-outline"></ion-icon>
<ion-label>
<h2>{{ partition.value.label || partition.key }} ({{ partition.value.size || 'unknown size' }})</h2>

View File

@@ -2,7 +2,7 @@ import { Component } from '@angular/core'
import { LoadingController, ModalController } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/api.service'
import { BackupConfirmationComponent } from 'src/app/modals/backup-confirmation/backup-confirmation.component'
import { DiskInfo, PartitionInfoEntry } from 'src/app/services/api/api-types'
import { DiskInfo } from 'src/app/services/api/api-types'
@Component({
selector: 'server-backup',
@@ -42,10 +42,10 @@ export class ServerBackupPage {
}
}
async presentModal (logicalname: string, partition: PartitionInfoEntry): Promise<void> {
async presentModal (logicalname: string): Promise<void> {
const m = await this.modalCtrl.create({
componentProps: {
name: partition.label || logicalname,
type: 'backup',
},
cssClass: 'alertlike-modal',
component: BackupConfirmationComponent,

View File

@@ -26,6 +26,10 @@ const routes: Routes = [
path: 'preferences',
loadChildren: () => import('./preferences/preferences.module').then(m => m.PreferencesPageModule),
},
{
path: 'preferences/edit',
loadChildren: () => import('./preferences/preferences.module').then(m => m.PreferencesPageModule),
},
{
path: 'wifi',
loadChildren: () => import('./wifi/wifi.module').then(m => m.WifiListPageModule),

View File

@@ -1,6 +1,6 @@
<ion-header>
<ion-toolbar>
<ion-title>{{ patch.watch$('ui', 'server-name') | async }}</ion-title>
<ion-title>Embassy</ion-title>
<ion-buttons slot="end">
<badge-menu-button></badge-menu-button>
</ion-buttons>

View File

@@ -3,8 +3,6 @@ import { LoadingOptions } from '@ionic/core'
import { AlertController, NavController } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/api.service'
import { LoaderService } from 'src/app/services/loader.service'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { ServerStatus } from 'src/app/models/patch-db/data-model'
import { ActivatedRoute } from '@angular/router'
@Component({
@@ -13,7 +11,6 @@ import { ActivatedRoute } from '@angular/router'
styleUrls: ['server-show.page.scss'],
})
export class ServerShowPage {
ServerStatus = ServerStatus
settings: ServerSettings = { }
constructor (
@@ -22,7 +19,6 @@ export class ServerShowPage {
private readonly apiService: ApiService,
private readonly navCtrl: NavController,
private readonly route: ActivatedRoute,
public readonly patch: PatchDbModel,
) { }
ngOnInit () {
@@ -77,7 +73,6 @@ export class ServerShowPage {
this.loader
.of(LoadingSpinner(`Restarting...`))
.displayDuringAsync( async () => {
// this.serverModel.markUnreachable()
await this.apiService.restartServer({ })
})
.catch(console.error)
@@ -87,7 +82,6 @@ export class ServerShowPage {
this.loader
.of(LoadingSpinner(`Shutting down...`))
.displayDuringAsync( async () => {
// this.serverModel.markUnreachable()
await this.apiService.shutdownServer({ })
})
.catch(console.error)

View File

@@ -7,7 +7,7 @@
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-top">
<ion-content>
<ion-item-divider>Basic</ion-item-divider>

View File

@@ -1,8 +1,8 @@
import { Component } from '@angular/core'
import { ToastController } from '@ionic/angular'
import { copyToClipboard } from 'src/app/util/web.util'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { ServerInfo } from 'src/app/models/patch-db/data-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { ServerInfo } from 'src/app/services/patch-db/data-model'
import { Subscription } from 'rxjs'
@Component({

View File

@@ -29,7 +29,7 @@
<ng-container *ngIf="wifi">
<ion-item button detail="false" *ngFor="let ssid of wifi.ssids" (click)="presentAction(ssid, wifi)">
<ion-label>{{ ssid }}</ion-label>
<ion-icon *ngIf="ssid === wifi.connected" name="wifi" color="success"></ion-icon>
<ion-note slot="end" *ngIf="ssid === wifi.connected"><ion-text color="success">Connected</ion-text></ion-note>
</ion-item>
</ng-container>
</ion-item-group>

View File

@@ -4,8 +4,8 @@ import { ApiService } from 'src/app/services/api/api.service'
import { ActionSheetButton } from '@ionic/core'
import { WifiService } from './wifi.service'
import { LoaderService } from 'src/app/services/loader.service'
import { WiFiInfo } from 'src/app/models/patch-db/data-model'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { WiFiInfo } from 'src/app/services/patch-db/data-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { Subscription } from 'rxjs'
@Component({

View File

@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'
import { AlertController, ToastController } from '@ionic/angular'
import { merge, Observable, timer } from 'rxjs'
import { filter, map, take, tap } from 'rxjs/operators'
import { PatchDbModel } from 'src/app/models/patch-db/patch-db-model'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
@Injectable({
providedIn: 'root',

View File

@@ -1,5 +1,5 @@
import { Pipe, PipeTransform } from '@angular/core'
import { PackageDataEntry } from '../models/patch-db/data-model'
import { PackageDataEntry } from '../services/patch-db/data-model'
import { renderPkgStatus } from '../services/pkg-status-rendering.service'
@Pipe({

View File

@@ -1,5 +1,5 @@
import { Pipe, PipeTransform } from '@angular/core'
import { PackageDataEntry } from '../models/patch-db/data-model'
import { PackageDataEntry } from '../services/patch-db/data-model'
import { FEStatus, renderPkgStatus } from '../services/pkg-status-rendering.service'
@Pipe({

View File

@@ -1,5 +1,5 @@
import { Pipe, PipeTransform } from '@angular/core'
import { PackageDataEntry, Manifest } from '../models/patch-db/data-model'
import { PackageDataEntry, Manifest } from '../services/patch-db/data-model'
import { ConfigService, getManifest, hasUi } from '../services/config.service'
@Pipe({

View File

@@ -2,7 +2,10 @@ import { ConfigCursor } from './config-cursor'
import { TrackingModalController } from '../services/tracking-modal-controller.service'
export class ModalPresentable {
constructor (private readonly trackingModalCtrl: TrackingModalController) { }
constructor (
private readonly trackingModalCtrl: TrackingModalController,
) { }
async presentModal (cursor: ConfigCursor<any>, callback: () => any) {
const modal = await this.trackingModalCtrl.createConfigModal({

View File

@@ -1,7 +1,7 @@
import { Dump, Revision } from 'patch-db-client'
import { PackagePropertiesVersioned } from 'src/app/util/properties.util'
import { ConfigSpec } from 'src/app/pkg-config/config-types'
import { DataModel, DependencyError, Manifest, URL } from 'src/app/models/patch-db/data-model'
import { DataModel, DependencyError, Manifest, URL } from 'src/app/services/patch-db/data-model'
export module RR {
@@ -187,6 +187,8 @@ export interface AvailablePreview {
export interface AvailableShow {
icon: URL
license: URL
instructions: URL
manifest: Manifest
categories: string[]
versions: string[]

View File

@@ -1,22 +1,17 @@
import { Subject, Observable } from 'rxjs'
import { Http, Update, Operation, Revision } from 'patch-db-client'
import { Http, Update, Operation, Revision, Source, Store } from 'patch-db-client'
import { RR } from './api-types'
import { DataModel } from 'src/app/models/patch-db/data-model'
import { filter } from 'rxjs/operators'
import { DataModel } from 'src/app/services/patch-db/data-model'
export abstract class ApiService implements Http<DataModel> {
export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
protected readonly sync = new Subject<Update<DataModel>>()
private syncing = true
/** PatchDb Source interface. Post/Patch requests provide a source of patches to the db. */
// sequenceStream '_' is not used by the live api, but is overridden by the mock
watch$ (_?: Observable<number>): Observable<Update<DataModel>> {
return this.sync.asObservable().pipe(filter(() => this.syncing))
watch$ (_?: Store<DataModel>): Observable<Update<DataModel>> {
return this.sync.asObservable()
}
// used for determining internet connectivity
abstract echo (): Promise<string>
// for getting static files: ex icons, instructions, licenses
abstract getStatic (url: string): Promise<string>
@@ -189,18 +184,4 @@ export abstract class ApiService implements Http<DataModel> {
}) as any
}
}
// @TODO better types?
// private async process<T, F extends (args: object) => Promise<{ response: T, revision?: Revision }>> (f: F, temps: Operation[] = []): Promise<T> {
// let expireId = undefined
// if (temps.length) {
// expireId = uuid.v4()
// this.sync.next({ patch: temps, expiredBy: expireId })
// }
// const { response, revision } = await f({ ...f.arguments, expireId })
// if (revision) this.sync.next(revision)
// return response
// }
}
// used for type inference in syncResponse
type ExtractResultPromise<T extends Promise<any>> = T extends Promise<infer R> ? Promise<R> : any

View File

@@ -10,10 +10,6 @@ export class LiveApiService extends ApiService {
private readonly http: HttpService,
) { super() }
async echo (): Promise<string> {
return this.http.rpcRequest({ method: 'echo', params: { } })
}
async getStatic (url: string): Promise<string> {
return this.http.httpRequest({ method: Method.GET, url })
}

View File

@@ -2,13 +2,14 @@ import { Injectable } from '@angular/core'
import { pauseFor } from '../../util/misc.util'
import { ApiService } from './api.service'
import { Observable } from 'rxjs'
import { PatchOp, Update } from 'patch-db-client'
import { DataModel, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/models/patch-db/data-model'
import { PatchOp, Store, Update } from 'patch-db-client'
import { DataModel, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model'
import { RR } from './api-types'
import { parsePropertiesPermissive } from 'src/app/util/properties.util'
import { Mock } from './mock-app-fixures'
import { HttpService } from '../http.service'
import { ConfigService } from '../config.service'
import markdown from 'raw-loader!src/assets/markdown/md-sample.md'
import { map } from 'rxjs/operators'
@Injectable()
export class MockApiService extends ApiService {
@@ -20,18 +21,20 @@ export class MockApiService extends ApiService {
) { super() }
// every time a patch is returned from the mock, we override its sequence to be 1 more than the last sequence in the patch-db as provided by `o`.
watch$ (sequenceStream: Observable<number>): Observable<Update<DataModel>> {
sequenceStream.subscribe(i => this.sequence < i ? (this.sequence = i) : { })
watch$ (store: Store<DataModel>): Observable<Update<DataModel>> {
store.watchCache$().pipe(map(cache => cache.sequence)).subscribe(seq => {
console.log('INCOMING: ', seq)
if (this.sequence < seq) {
console.log('hererereree')
this.sequence = seq
}
})
return super.watch$()
}
async echo (): Promise<string> {
console.log('echo...echo')
return ''
}
async getStatic (url: string): Promise<string> {
return Mock.DbDump.value['package-data']['bitcoind']['static-files'].instructions
await pauseFor(2000)
return markdown
}
// db

View File

@@ -1,13 +1,12 @@
import { DependencyErrorType, DockerIoFormat, Manifest, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/models/patch-db/data-model'
import { DependencyErrorType, DockerIoFormat, Manifest, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model'
import { NotificationLevel, RR, ServerNotification, ServerNotifications } from './api-types'
import markdown from 'raw-loader!./md-sample.md'
export module Mock {
export const MarketplaceEos: RR.GetMarketplaceEOSRes = {
version: '1.0.0',
headline: 'Our biggest release ever.',
'release-notes': { '1.0.0': markdown },
'release-notes': { '1.0.0': 'Some **Markdown** release _notes_' },
}
export const AvailableList: RR.GetAvailableListRes = [
@@ -402,6 +401,8 @@ export module Mock {
'bitcoind': {
'0.19.2': {
icon: 'assets/img/service-icons/bitcoind.png',
license: 'licenseUrl',
instructions: 'instructionsUrl',
manifest: {
...Mock.MockManifestBitcoind,
version: '0.19.0',
@@ -417,6 +418,8 @@ export module Mock {
},
'0.20.0': {
icon: 'assets/img/service-icons/bitcoind.png',
license: 'licenseUrl',
instructions: 'instructionsUrl',
manifest: {
...Mock.MockManifestBitcoind,
version: '0.20.0',
@@ -432,6 +435,8 @@ export module Mock {
},
'0.21.0': {
icon: 'assets/img/service-icons/bitcoind.png',
license: 'licenseUrl',
instructions: 'instructionsUrl',
manifest: {
...Mock.MockManifestBitcoind,
version: '0.21.0',
@@ -448,6 +453,8 @@ export module Mock {
},
'latest': {
icon: 'assets/img/service-icons/bitcoind.png',
license: 'licenseUrl',
instructions: 'instructionsUrl',
manifest: {
...Mock.MockManifestBitcoind,
'release-notes': 'Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.',
@@ -465,6 +472,8 @@ export module Mock {
'lnd': {
'0.11.0': {
icon: 'assets/img/service-icons/lnd.png',
license: 'licenseUrl',
instructions: 'instructionsUrl',
manifest: {
...Mock.MockManifestLnd,
version: '0.11.0',
@@ -490,6 +499,8 @@ export module Mock {
},
'0.11.1': {
icon: 'assets/img/service-icons/lnd.png',
license: 'licenseUrl',
instructions: 'instructionsUrl',
manifest: {
...Mock.MockManifestLnd,
version: '0.11.1',
@@ -515,6 +526,8 @@ export module Mock {
},
'latest': {
icon: 'assets/img/service-icons/lnd.png',
license: 'licenseUrl',
instructions: 'instructionsUrl',
manifest: Mock.MockManifestLnd,
categories: ['bitcoin', 'lightning', 'cryptocurrency'],
versions: ['0.11.0', '0.11.1'],
@@ -538,6 +551,8 @@ export module Mock {
'bitcoin-proxy': {
'latest': {
icon: 'assets/img/service-icons/bitcoin-proxy.png',
license: 'licenseUrl',
instructions: 'instructionsUrl',
manifest: Mock.MockManifestBitcoinProxy,
categories: ['bitcoin'],
versions: ['0.2.2'],
@@ -559,9 +574,9 @@ export module Mock {
export const bitcoind: PackageDataEntry = {
state: PackageState.Installed,
'static-files': {
license: 'licenseURL',
license: 'licenseUrl', // /public/package-data/bitcoind/0.21.1/LICENSE.md,
icon: 'assets/img/service-icons/bitcoind.png',
instructions: markdown, // /public/package-data/bitcoind/0.21.1/INSTRUCTIONS.md
instructions: 'instructionsUrl', // /public/package-data/bitcoind/0.21.1/INSTRUCTIONS.md
},
'temp-manifest': undefined,
installed: {
@@ -610,9 +625,9 @@ export module Mock {
export const lnd: PackageDataEntry = {
state: PackageState.Installed,
'static-files': {
license: 'licenseURL',
license: 'licenseUrl', // /public/package-data/lnd/0.21.1/LICENSE.md,
icon: 'assets/img/service-icons/lnd.png',
instructions: markdown, // /public/package-data/bitcoind/0.21.1/INSTRUCTIONS.md
instructions: 'instructionsUrl', // /public/package-data/lnd/0.21.1/INSTRUCTIONS.md
},
'temp-manifest': undefined,
installed: {
@@ -662,9 +677,9 @@ export module Mock {
export const bitcoinproxy: PackageDataEntry = {
state: PackageState.Installed,
'static-files': {
license: 'licenseURL',
license: 'licenseUrl', // /public/package-data/bitcoinproxy/0.21.1/LICENSE.md,
icon: 'assets/img/service-icons/bitcoin-proxy.png',
instructions: '*These are some instructions.*', // /public/package-data/bitcoinproxy/0.2.2/INSTRUCTIONS.md
instructions: 'instructionsUrl', // /public/package-data/bitcoinproxy/0.2.2/INSTRUCTIONS.md
},
'temp-manifest': undefined,
installed: {
@@ -737,7 +752,6 @@ export module Mock {
'lnd': lnd,
},
ui: {
'server-name': 'My Embassy',
'welcome-ack': '1.0.0',
'auto-check-updates': true,
},

View File

@@ -3,7 +3,6 @@ import { BehaviorSubject, Observable } from 'rxjs'
import { distinctUntilChanged } from 'rxjs/operators'
import { ApiService } from './api/api.service'
import { Storage } from '@ionic/storage'
import { StorageKeys } from '../models/storage-keys'
export enum AuthState {
UNVERIFIED,
@@ -14,6 +13,7 @@ export enum AuthState {
providedIn: 'root',
})
export class AuthService {
private readonly LOGGED_IN_KEY = 'loggedInKey'
private readonly authState$: BehaviorSubject<AuthState> = new BehaviorSubject(AuthState.INITIALIZING)
constructor (
@@ -22,7 +22,7 @@ export class AuthService {
) { }
async init (): Promise<void> {
const loggedIn = await this.storage.get(StorageKeys.LOGGED_IN_KEY)
const loggedIn = await this.storage.get(this.LOGGED_IN_KEY)
this.authState$.next( loggedIn ? AuthState.VERIFIED : AuthState.UNVERIFIED)
}
@@ -32,7 +32,7 @@ export class AuthService {
async login (password: string): Promise<void> {
await this.api.login({ password })
await this.storage.set(StorageKeys.LOGGED_IN_KEY, true)
await this.storage.set(this.LOGGED_IN_KEY, true)
this.authState$.next(AuthState.VERIFIED)
}

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'
import { InstalledPackageDataEntry, InterfaceDef, Manifest, PackageDataEntry, PackageMainStatus, PackageState } from '../models/patch-db/data-model'
import { InstalledPackageDataEntry, InterfaceDef, Manifest, PackageDataEntry, PackageMainStatus, PackageState } from './patch-db/data-model'
const { patchDb, api, mocks } = require('../../../ui-config.json') as UiConfig

View File

@@ -1,9 +1,9 @@
import { Injectable } from '@angular/core'
import { BehaviorSubject, combineLatest, fromEvent, merge, Observable, Subscription } from 'rxjs'
import { ConnectionStatus } from '../../../../../patch-db/client/dist'
import { DataModel } from '../models/patch-db/data-model'
import { PatchDbModel } from '../models/patch-db/patch-db-model'
import { BehaviorSubject, combineLatest, fromEvent, merge, Subscription } from 'rxjs'
import { DataModel } from './patch-db/data-model'
import { ConnectionStatus, PatchDbModel } from './patch-db/patch-db.service'
import { HttpService, Method } from './http.service'
import { distinctUntilChanged } from 'rxjs/operators'
@Injectable({
providedIn: 'root',
@@ -19,7 +19,7 @@ export class ConnectionService {
private readonly patch: PatchDbModel,
) { }
watch$ () {
watchFailure$ () {
return this.connectionFailure$.asObservable()
}
@@ -39,22 +39,27 @@ export class ConnectionService {
this.networkState$.next(event.type === 'online')
}),
combineLatest([this.networkState$, this.patch.connectionStatus$()])
combineLatest([this.networkState$.pipe(distinctUntilChanged()), this.patch.watchConnection$().pipe(distinctUntilChanged())])
.subscribe(async ([network, connectionStatus]) => {
console.log('CONNECTION STATUS', connectionStatus)
if (connectionStatus !== ConnectionStatus.Disconnected) {
this.connectionFailure$.next(ConnectionFailure.None)
} else if (!network) {
this.connectionFailure$.next(ConnectionFailure.Network)
} else {
console.log('diagnosing')
this.connectionFailure$.next(ConnectionFailure.Diagnosing)
const torSuccess = await this.testAddrs(this.addrs.tor)
if (torSuccess) {
console.log('TOR SUCCESS, EMBASSY IS PROBLEM')
this.connectionFailure$.next(ConnectionFailure.Embassy)
} else {
const clearnetSuccess = await this.testAddrs(this.addrs.clearnet)
if (clearnetSuccess) {
console.log('CLEARNET SUCCESS, TOR IS PROBLEM')
this.connectionFailure$.next(ConnectionFailure.Tor)
} else {
console.log('INTERNET IS PROBLEM')
this.connectionFailure$.next(ConnectionFailure.Internet)
}
}

View File

@@ -379,7 +379,6 @@ export interface InterfaceInfo {
export type URL = string
export interface UIData {
'server-name': string
'welcome-ack': string
'auto-check-updates': boolean
}

View File

@@ -2,13 +2,13 @@ import { PollSource, Source, WebsocketSource } from 'patch-db-client'
import { ConfigService } from 'src/app/services/config.service'
import { DataModel } from './data-model'
import { LocalStorageBootstrap } from './local-storage-bootstrap'
import { PatchDbModel } from './patch-db-model'
import { PatchDbModel } from './patch-db.service'
import { ApiService } from 'src/app/services/api/api.service'
export function PatchDbModelFactory (
config: ConfigService,
bootstrapper: LocalStorageBootstrap,
http: ApiService,
apiService: ApiService,
): PatchDbModel {
const { mocks, patchDb: { poll }, isConsulate } = config
@@ -17,13 +17,13 @@ export function PatchDbModelFactory (
if (mocks.enabled) {
if (mocks.connection === 'poll') {
source = new PollSource({ ...poll }, http)
source = new PollSource({ ...poll }, apiService)
} else {
source = new WebsocketSource(`ws://localhost:${config.mocks.wsPort}/db`)
}
} else {
if (isConsulate) {
source = new PollSource({ ...poll }, http)
source = new PollSource({ ...poll }, apiService)
} else {
const protocol = window.location.protocol === 'http:' ? 'ws' : 'wss'
const host = window.location.host
@@ -31,5 +31,5 @@ export function PatchDbModelFactory (
}
}
return new PatchDbModel(bootstrapper, source)
return new PatchDbModel(bootstrapper, [source, apiService])
}

View File

@@ -1,27 +1,34 @@
import { Inject, Injectable, InjectionToken } from '@angular/core'
import { Bootstrapper, ConnectionStatus, PatchDB, Source, Store } from 'patch-db-client'
import { Observable, of, Subscription } from 'rxjs'
import { catchError, debounceTime, map } from 'rxjs/operators'
import { Bootstrapper, PatchDB, Source, Store } from 'patch-db-client'
import { BehaviorSubject, Observable, of, Subscription } from 'rxjs'
import { catchError, debounceTime, map, tap } from 'rxjs/operators'
import { DataModel } from './data-model'
export const BOOTSTRAPPER = new InjectionToken<Bootstrapper<DataModel>>('app.config')
export const PATCH_SOURCE = new InjectionToken<Source<DataModel>>('app.config')
export enum ConnectionStatus {
Initializing = 'initializing',
Connected = 'connected',
Disconnected = 'disconnected',
}
@Injectable({
providedIn: 'root',
})
export class PatchDbModel {
patchDb: PatchDB<DataModel>
connectionStatus$ = new BehaviorSubject(ConnectionStatus.Initializing)
private patchDb: PatchDB<DataModel>
private patchSub: Subscription
constructor (
@Inject(BOOTSTRAPPER) private readonly bootstrapper: Bootstrapper<DataModel>,
@Inject(PATCH_SOURCE) private readonly source: Source<DataModel>,
@Inject(PATCH_SOURCE) private readonly sources: Source<DataModel>[],
) { }
async init (): Promise<void> {
const cache = await this.bootstrapper.init()
this.patchDb = new PatchDB(this.source, cache)
this.patchDb = new PatchDB(this.sources, cache)
}
start (): void {
@@ -32,12 +39,14 @@ export class PatchDbModel {
.pipe(debounceTime(500))
.subscribe({
next: cache => {
console.log('saving cache: ', cache.sequence)
console.log('saving cacheee: ', cache)
this.connectionStatus$.next(ConnectionStatus.Connected)
this.bootstrapper.update(cache)
},
error: e => {
console.error('patch-db-sync sub ERROR', e)
this.start()
this.connectionStatus$.next(ConnectionStatus.Disconnected)
// this.start()
},
complete: () => {
console.error('patch-db-sync sub COMPLETE')
@@ -56,19 +65,20 @@ export class PatchDbModel {
}
connected$ (): Observable<boolean> {
return this.patchDb.connectionStatus$
return this.connectionStatus$
.pipe(
map(status => status === ConnectionStatus.Connected),
)
}
connectionStatus$ (): Observable<ConnectionStatus> {
return this.patchDb.connectionStatus$.asObservable()
watchConnection$ (): Observable<ConnectionStatus> {
return this.connectionStatus$.asObservable()
}
watch$: Store<DataModel> ['watch$'] = (...args: (string | number)[]): Observable<DataModel> => {
// console.log('WATCHING')
return this.patchDb.store.watch$(...(args as [])).pipe(
tap(cache => console.log('CHANGE IN STORE', cache)),
catchError(e => {
console.error(e)
return of(e.message)

View File

@@ -1,4 +1,4 @@
import { HealthCheckResultLoading, MainStatusRunning, PackageDataEntry, PackageMainStatus, PackageState, Status } from '../models/patch-db/data-model'
import { HealthCheckResultLoading, MainStatusRunning, PackageDataEntry, PackageMainStatus, PackageState, Status } from './patch-db/data-model'
export function renderPkgStatus (pkg: PackageDataEntry): PkgStatusRendering {
switch (pkg.state) {
@@ -15,7 +15,7 @@ function handleInstalledState (status: Status): PkgStatusRendering {
}
if (Object.keys(status['dependency-errors']).length) {
return { display: 'Needs Attention', color: 'warning', showDots: false, feStatus: FEStatus.DependencyIssue }
return { display: 'Dependency Issue', color: 'warning', showDots: false, feStatus: FEStatus.DependencyIssue }
}
switch (status.main.status) {

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core'
import { ModalController } from '@ionic/angular'
import { AppConfigValuePage } from '../modals/app-config-value/app-config-value.page'
import { ApiService } from './api/api.service'
import { ConfigSpec } from '../pkg-config/config-types'
import { ConfigCursor } from '../pkg-config/config-cursor'
import { SSHService } from '../pages/server-routes/developer-routes/dev-ssh-keys/ssh.service'
import { TrackingModalController } from './tracking-modal-controller.service'
@Injectable({
providedIn: 'root',
@@ -12,7 +12,7 @@ import { SSHService } from '../pages/server-routes/developer-routes/dev-ssh-keys
export class ServerConfigService {
constructor (
private readonly modalCtrl: ModalController,
private readonly trackingModalCtrl: TrackingModalController,
private readonly apiService: ApiService,
private readonly sshService: SSHService,
) { }
@@ -20,10 +20,10 @@ export class ServerConfigService {
async presentModalValueEdit (key: string, current?: string) {
const cursor = new ConfigCursor(serverConfig, { [key]: current }).seekNext(key)
const modal = await this.modalCtrl.create({
const modal = await this.trackingModalCtrl.create({
backdropDismiss: false,
component: AppConfigValuePage,
presentingElement: await this.modalCtrl.getTop(),
presentingElement: await this.trackingModalCtrl.getTop(),
componentProps: {
cursor,
saveFn: this.saveFns[key],

View File

@@ -1,10 +1,11 @@
import { Inject, Injectable } from '@angular/core'
import { Inject, Injectable, InjectionToken } from '@angular/core'
import { Observable, Subject } from 'rxjs'
import { ModalController } from '@ionic/angular'
import { ModalOptions } from '@ionic/core'
import { APP_CONFIG_COMPONENT_MAPPING } from '../modals/app-config-injectable/modal-injectable-token'
import { AppConfigComponentMapping } from '../modals/app-config-injectable/modal-injectable-type'
import { ValueSpec } from '../pkg-config/config-types'
import { AppConfigComponentMapping } from '../modals/app-config-injectable'
export const APP_CONFIG_COMPONENT_MAPPING = new InjectionToken<string>('APP_CONFIG_COMPONENTS')
@Injectable({
providedIn: 'root',

View File

@@ -1,6 +1,14 @@
export type Omit<ObjectType, KeysType extends keyof ObjectType> = Pick<ObjectType, Exclude<keyof ObjectType, KeysType>>
export type PromiseRes<T> = { result: 'resolve', value: T } | { result: 'reject', value: Error }
export type Recommendation = {
dependentId: string
dependentTitle: string
dependentIcon: string,
description: string
version?: string
}
import { OperatorFunction } from 'rxjs'
import { map } from 'rxjs/operators'

View File

@@ -111,7 +111,7 @@ ion-badge {
ion-item-divider {
--background: transparent;
text-transform: uppercase;
margin-top: 16px;
margin-top: 12px;
font-weight: 400;
}
@@ -161,9 +161,9 @@ ion-popover {
.modal-wrapper {
position: absolute;
height: 90%!important;
height: 90% !important;
top: 5%;
width: 90%!important;
width: 90% !important;
left: 5%;
display: block;
}
@@ -171,37 +171,30 @@ ion-popover {
@media (min-width:1000px) {
.modal-wrapper {
position: absolute;
height: 80% !important;
top: 10%;
width: 60% !important;
left: 20%;
height: 60% !important;
top: 20%;
width: 50% !important;
left: 25%;
display: block;
}
}
@media (min-width:1000px) {
.alertlike-modal {
--backdrop-opacity: 0.7 !important;
.modal-wrapper {
height: 46% !important;
width: 46% !important;
left: 26% !important;
top: 26% !important;
--box-shadow: none !important;
}
.alertlike-modal {
.modal-wrapper {
height: 50% !important;
top: 25% !important;
width: 90% !important;
left: 5% !important;
--box-shadow: none !important;
}
}
.alertlike-modal {
--backdrop-opacity: 0.7 !important;
.modal-wrapper {
height: 60% !important;
width: 80% !important;
left: 10% !important;
top: 20% !important;
--box-shadow: none !important;
@media (min-width:1000px) {
.alertlike-modal {
.modal-wrapper {
width: 50% !important;
left: 25% !important;
}
}
}
@@ -234,14 +227,9 @@ ion-loading {
}
ion-modal {
--box-shadow: 3px 4px 14px 3px rgba(255,255,255,0.3) !important;
}
ion-modal:first-of-type {
--backdrop-opacity: 0.75 !important;
}
.swiper-pagination {
position: fixed;
bottom: 0px;

View File

@@ -10,7 +10,7 @@
},
"mocks": {
"enabled": true,
"connection": "ws",
"connection": "poll",
"rpcPort": "5959",
"wsPort": "5960",
"maskAs": "tor",