Feature/shared refactor (#1176)

* refactor: move most of the shared entities to @start8labs/shared library
This commit is contained in:
Alex Inkin
2022-02-15 18:13:05 +03:00
committed by GitHub
parent 1769f7e2e6
commit 7c26b18c73
164 changed files with 2908 additions and 2860 deletions

View File

@@ -389,10 +389,10 @@
}, },
"configurations": { "configurations": {
"production": { "production": {
"tsConfig": "projects/shared/tsconfig.lib.prod.json" "tsConfig": "projects/shared/tsconfig.prod.json"
}, },
"development": { "development": {
"tsConfig": "projects/shared/tsconfig.lib.json" "tsConfig": "projects/shared/tsconfig.json"
} }
}, },
"defaultConfiguration": "production" "defaultConfiguration": "production"

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"check": "npm run check:shared && npm run check:ui && npm run check:setup-wizard && npm run check:diagnostic-ui", "check": "npm run check:shared && npm run check:ui && npm run check:setup-wizard && npm run check:diagnostic-ui",
"check:shared": "tsc --project projects/shared/tsconfig.lib.json --noEmit --skipLibCheck", "check:shared": "tsc --project projects/shared/tsconfig.json --noEmit --skipLibCheck",
"check:diagnostic-ui": "tsc --project projects/diagnostic-ui/tsconfig.json --noEmit --skipLibCheck", "check:diagnostic-ui": "tsc --project projects/diagnostic-ui/tsconfig.json --noEmit --skipLibCheck",
"check:setup-wizard": "tsc --project projects/setup-wizard/tsconfig.json --noEmit --skipLibCheck", "check:setup-wizard": "tsc --project projects/setup-wizard/tsconfig.json --noEmit --skipLibCheck",
"check:ui": "tsc --project projects/ui/tsconfig.json --noEmit --skipLibCheck", "check:ui": "tsc --project projects/ui/tsconfig.json --noEmit --skipLibCheck",

View File

@@ -10,7 +10,7 @@ import { MockApiService } from './services/api/mock-api.service'
import { LiveApiService } from './services/api/live-api.service' import { LiveApiService } from './services/api/live-api.service'
import { HttpService } from './services/http.service' import { HttpService } from './services/http.service'
import { GlobalErrorHandler } from './services/global-error-handler.service' import { GlobalErrorHandler } from './services/global-error-handler.service'
import { WorkspaceConfig } from '@shared' import { WorkspaceConfig } from '@start9labs/shared'
const { useMocks } = require('../../../../config.json') as WorkspaceConfig const { useMocks } = require('../../../../config.json') as WorkspaceConfig

View File

@@ -1,39 +1,50 @@
import { Injectable } from "@angular/core" import { Injectable } from '@angular/core'
import { pauseFor } from "../../util/misc.util" import { pauseFor } from '@start9labs/shared'
import { ApiService, GetErrorRes, GetLogsReq, GetLogsRes, Log } from "./api.service" import {
ApiService,
GetErrorRes,
GetLogsReq,
GetLogsRes,
Log,
} from './api.service'
@Injectable() @Injectable()
export class MockApiService extends ApiService { export class MockApiService extends ApiService {
constructor() {
super()
}
constructor () { super() } async getError(): Promise<GetErrorRes> {
async getError (): Promise<GetErrorRes> {
await pauseFor(1000) await pauseFor(1000)
return { return {
code: 15, code: 15,
message: 'Unknown Embassy', message: 'Unknown Embassy',
data: { details: 'Some details about the error here' } data: { details: 'Some details about the error here' },
} }
} }
async restart (): Promise<void> { async restart(): Promise<void> {
await pauseFor(1000) await pauseFor(1000)
return null return null
} }
async forgetDrive (): Promise<void> { async forgetDrive(): Promise<void> {
await pauseFor(1000) await pauseFor(1000)
return null return null
} }
async getLogs (params: GetLogsReq): Promise<GetLogsRes> { async getLogs(params: GetLogsReq): Promise<GetLogsRes> {
await pauseFor(1000) await pauseFor(1000)
let entries: Log[] let entries: Log[]
if (Math.random() < .2) { if (Math.random() < 0.2) {
entries = packageLogs entries = packageLogs
} else { } else {
const arrLength = params.limit ? Math.ceil(params.limit / packageLogs.length) : 10 const arrLength = params.limit
entries = new Array(arrLength).fill(packageLogs).reduce((acc, val) => acc.concat(val), []) ? Math.ceil(params.limit / packageLogs.length)
: 10
entries = new Array(arrLength)
.fill(packageLogs)
.reduce((acc, val) => acc.concat(val), [])
} }
return { return {
entries, entries,
@@ -56,4 +67,4 @@ const packageLogs = [
timestamp: '2019-12-26T14:22:30.872Z', timestamp: '2019-12-26T14:22:30.872Z',
message: '****** FINISH *****', message: '****** FINISH *****',
}, },
] ]

View File

@@ -1,3 +0,0 @@
export function pauseFor (ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}

View File

@@ -21,7 +21,7 @@ import { LoadingPageModule } from './pages/loading/loading.module'
import { ProdKeyModalModule } from './modals/prod-key-modal/prod-key-modal.module' import { ProdKeyModalModule } from './modals/prod-key-modal/prod-key-modal.module'
import { ProductKeyPageModule } from './pages/product-key/product-key.module' import { ProductKeyPageModule } from './pages/product-key/product-key.module'
import { RecoverPageModule } from './pages/recover/recover.module' import { RecoverPageModule } from './pages/recover/recover.module'
import { WorkspaceConfig } from '@shared' import { WorkspaceConfig } from '@start9labs/shared'
const { useMocks } = require('../../../../config.json') as WorkspaceConfig const { useMocks } = require('../../../../config.json') as WorkspaceConfig

View File

@@ -2,10 +2,10 @@ import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { UnitConversionPipesModule } from '@start9labs/shared'
import { EmbassyPage } from './embassy.page' import { EmbassyPage } from './embassy.page'
import { PasswordPageModule } from '../../modals/password/password.module' import { PasswordPageModule } from '../../modals/password/password.module'
import { EmbassyPageRoutingModule } from './embassy-routing.module' import { EmbassyPageRoutingModule } from './embassy-routing.module'
import { PipesModule } from 'src/app/pipes/pipe.module'
@NgModule({ @NgModule({
imports: [ imports: [
@@ -14,8 +14,8 @@ import { PipesModule } from 'src/app/pipes/pipe.module'
IonicModule, IonicModule,
EmbassyPageRoutingModule, EmbassyPageRoutingModule,
PasswordPageModule, PasswordPageModule,
PipesModule, UnitConversionPipesModule,
], ],
declarations: [EmbassyPage], declarations: [EmbassyPage],
}) })
export class EmbassyPageModule { } export class EmbassyPageModule {}

View File

@@ -2,11 +2,11 @@ import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { UnitConversionPipesModule } from '@start9labs/shared'
import { DriveStatusComponent, RecoverPage } from './recover.page' import { DriveStatusComponent, RecoverPage } from './recover.page'
import { PasswordPageModule } from '../../modals/password/password.module' import { PasswordPageModule } from '../../modals/password/password.module'
import { ProdKeyModalModule } from '../../modals/prod-key-modal/prod-key-modal.module' import { ProdKeyModalModule } from '../../modals/prod-key-modal/prod-key-modal.module'
import { RecoverPageRoutingModule } from './recover-routing.module' import { RecoverPageRoutingModule } from './recover-routing.module'
import { PipesModule } from 'src/app/pipes/pipe.module'
import { CifsModalModule } from 'src/app/modals/cifs-modal/cifs-modal.module' import { CifsModalModule } from 'src/app/modals/cifs-modal/cifs-modal.module'
@NgModule({ @NgModule({
@@ -18,8 +18,8 @@ import { CifsModalModule } from 'src/app/modals/cifs-modal/cifs-modal.module'
RecoverPageRoutingModule, RecoverPageRoutingModule,
PasswordPageModule, PasswordPageModule,
ProdKeyModalModule, ProdKeyModalModule,
PipesModule, UnitConversionPipesModule,
CifsModalModule, CifsModalModule,
], ],
}) })
export class RecoverPageModule { } export class RecoverPageModule {}

View File

@@ -1,18 +0,0 @@
import { Pipe, PipeTransform } from '@angular/core'
// converts bytes to gigabytes
@Pipe({
name: 'convertBytes',
})
export class ConvertBytesPipe implements PipeTransform {
transform (bytes: number): string {
if (bytes === 0) return '0 Bytes'
const k = 1024
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
}
}

View File

@@ -1,10 +0,0 @@
import { NgModule } from '@angular/core'
import { ConvertBytesPipe } from './convert-bytes.pipe'
@NgModule({
declarations: [ConvertBytesPipe],
imports: [],
exports: [ConvertBytesPipe],
})
export class PipesModule { }

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { pauseFor } from 'src/app/util/misc.util' import { pauseFor } from '@start9labs/shared'
import { ApiService, CifsRecoverySource, SetupEmbassyReq } from './api.service' import { ApiService, CifsRecoverySource, SetupEmbassyReq } from './api.service'
let tries = 0 let tries = 0

View File

@@ -1,8 +1,12 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { BehaviorSubject } from 'rxjs' import { BehaviorSubject } from 'rxjs'
import { ApiService, CifsRecoverySource, DiskRecoverySource } from './api/api.service' import {
ApiService,
CifsRecoverySource,
DiskRecoverySource,
} from './api/api.service'
import { ErrorToastService } from './error-toast.service' import { ErrorToastService } from './error-toast.service'
import { pauseFor } from '../util/misc.util' import { pauseFor } from '@start9labs/shared'
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@@ -17,7 +21,11 @@ export class StateService {
recoverySource: CifsRecoverySource | DiskRecoverySource recoverySource: CifsRecoverySource | DiskRecoverySource
recoveryPassword: string recoveryPassword: string
dataTransferProgress: { bytesTransferred: number, totalBytes: number, complete: boolean } | null dataTransferProgress: {
bytesTransferred: number
totalBytes: number
complete: boolean
} | null
dataProgress = 0 dataProgress = 0
dataCompletionSubject = new BehaviorSubject(false) dataCompletionSubject = new BehaviorSubject(false)
@@ -25,28 +33,27 @@ export class StateService {
lanAddress: string lanAddress: string
cert: string cert: string
constructor ( constructor(
private readonly apiService: ApiService, private readonly apiService: ApiService,
private readonly errorToastService: ErrorToastService, private readonly errorToastService: ErrorToastService,
) { } ) {}
async pollDataTransferProgress () { async pollDataTransferProgress() {
this.polling = true this.polling = true
await pauseFor(500) await pauseFor(500)
if ( if (this.dataTransferProgress?.complete) {
this.dataTransferProgress?.complete
) {
this.dataCompletionSubject.next(true) this.dataCompletionSubject.next(true)
return return
} }
let progress let progress
try { try {
progress = await this.apiService.getRecoveryStatus() progress = await this.apiService.getRecoveryStatus()
} catch (e) { } catch (e) {
this.errorToastService.present(`${e.message}: ${e.details}.\nRestart Embassy to try again.`) this.errorToastService.present(
`${e.message}: ${e.details}.\nRestart Embassy to try again.`,
)
} }
if (progress) { if (progress) {
this.dataTransferProgress = { this.dataTransferProgress = {
@@ -55,20 +62,25 @@ export class StateService {
complete: progress.complete, complete: progress.complete,
} }
if (this.dataTransferProgress.totalBytes) { if (this.dataTransferProgress.totalBytes) {
this.dataProgress = this.dataTransferProgress.bytesTransferred / this.dataTransferProgress.totalBytes this.dataProgress =
this.dataTransferProgress.bytesTransferred /
this.dataTransferProgress.totalBytes
} }
} }
setTimeout(() => this.pollDataTransferProgress(), 0) // prevent call stack from growing setTimeout(() => this.pollDataTransferProgress(), 0) // prevent call stack from growing
} }
async importDrive (guid: string): Promise<void> { async importDrive(guid: string): Promise<void> {
const ret = await this.apiService.importDrive(guid) const ret = await this.apiService.importDrive(guid)
this.torAddress = ret['tor-address'] this.torAddress = ret['tor-address']
this.lanAddress = ret['lan-address'] this.lanAddress = ret['lan-address']
this.cert = ret['root-ca'] this.cert = ret['root-ca']
} }
async setupEmbassy (storageLogicalname: string, password: string): Promise<void> { async setupEmbassy(
storageLogicalname: string,
password: string,
): Promise<void> {
const ret = await this.apiService.setupEmbassy({ const ret = await this.apiService.setupEmbassy({
'embassy-logicalname': storageLogicalname, 'embassy-logicalname': storageLogicalname,
'embassy-password': password, 'embassy-password': password,

View File

@@ -1,3 +0,0 @@
export const pauseFor = (ms: number) => {
return new Promise(resolve => setTimeout(resolve, ms))
}

View File

@@ -1,9 +1,10 @@
{ {
"name": "shared", "name": "@start9labs/shared",
"version": "0.0.1", "version": "0.0.1",
"peerDependencies": { "peerDependencies": {
"@angular/common": "^13.2.0", "@angular/common": "^13.2.0",
"@angular/core": "^13.2.0" "@angular/core": "^13.2.0",
"@start9labs/emver": "^0.1.5"
}, },
"dependencies": { "dependencies": {
"tslib": "^2.3.0" "tslib": "^2.3.0"

View File

@@ -1,8 +1,8 @@
<ion-grid style="height: 100%;"> <ion-grid class="full-height">
<ion-row class="ion-align-items-center ion-text-center" style="height: 100%;"> <ion-row class="ion-align-items-center ion-text-center full-height">
<ion-col> <ion-col>
<ion-spinner name="lines" color="warning"></ion-spinner> <ion-spinner name="lines" color="warning"></ion-spinner>
<p>{{ text }}</p> <p>{{ text }}</p>
</ion-col> </ion-col>
</ion-row> </ion-row>
</ion-grid> </ion-grid>

View File

@@ -1,18 +1,11 @@
import { NgModule } from '@angular/core' import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { TextSpinnerComponent } from './text-spinner.component'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { RouterModule } from '@angular/router' import { TextSpinnerComponent } from './text-spinner.component'
@NgModule({ @NgModule({
declarations: [ declarations: [TextSpinnerComponent],
TextSpinnerComponent, imports: [CommonModule, IonicModule],
],
imports: [
CommonModule,
IonicModule,
RouterModule.forChild([]),
],
exports: [TextSpinnerComponent], exports: [TextSpinnerComponent],
}) })
export class TextSpinnerComponentModule { } export class TextSpinnerComponentModule {}

View File

@@ -0,0 +1,3 @@
.full-height {
height: 100%;
}

View File

@@ -6,5 +6,5 @@ import { Component, Input } from '@angular/core'
styleUrls: ['./text-spinner.component.scss'], styleUrls: ['./text-spinner.component.scss'],
}) })
export class TextSpinnerComponent { export class TextSpinnerComponent {
@Input() text: string @Input() text = ''
} }

View File

@@ -0,0 +1,12 @@
import { NgModule } from '@angular/core'
import {
EmverComparesPipe,
EmverDisplayPipe,
EmverSatisfiesPipe,
} from './emver.pipe'
@NgModule({
declarations: [EmverComparesPipe, EmverDisplayPipe, EmverSatisfiesPipe],
exports: [EmverComparesPipe, EmverDisplayPipe, EmverSatisfiesPipe],
})
export class EmverPipesModule {}

View File

@@ -1,23 +1,24 @@
import { Pipe, PipeTransform } from '@angular/core' import { Pipe, PipeTransform } from '@angular/core'
import { Emver } from '../services/emver.service' import { Emver } from '../../services/emver.service'
@Pipe({ @Pipe({
name: 'satisfiesEmver', name: 'satisfiesEmver',
}) })
export class EmverSatisfiesPipe implements PipeTransform { export class EmverSatisfiesPipe implements PipeTransform {
constructor (private readonly emver: Emver) { } constructor(private readonly emver: Emver) {}
transform (versionUnderTest: string, range: string): boolean { transform(versionUnderTest: string, range: string): boolean {
return this.emver.satisfies(versionUnderTest, range) return this.emver.satisfies(versionUnderTest, range)
} }
} }
@Pipe({ @Pipe({
name: 'compareEmver', name: 'compareEmver',
}) })
export class EmverComparesPipe implements PipeTransform { export class EmverComparesPipe implements PipeTransform {
constructor (private readonly emver: Emver) { } constructor(private readonly emver: Emver) {}
transform (first: string, second: string): SemverResult { transform(first: string, second: string): SemverResult {
try { try {
return this.emver.compare(first, second) as SemverResult return this.emver.compare(first, second) as SemverResult
} catch (e) { } catch (e) {
@@ -29,18 +30,18 @@ export class EmverComparesPipe implements PipeTransform {
type SemverResult = 0 | 1 | -1 | 'comparison-impossible' type SemverResult = 0 | 1 | -1 | 'comparison-impossible'
@Pipe({ @Pipe({
name: 'displayEmver', name: 'displayEmver',
}) })
export class EmverDisplayPipe implements PipeTransform { export class EmverDisplayPipe implements PipeTransform {
constructor () { } constructor() {}
transform (version: string): string { transform(version: string): string {
return displayEmver(version) return displayEmver(version)
} }
} }
export function displayEmver (version: string): string { export function displayEmver(version: string): string {
const vs = version.split('.') const vs = version.split('.')
if (vs.length === 4) return `${vs[0]}.${vs[1]}.${vs[2]}~${vs[3]}` if (vs.length === 4) return `${vs[0]}.${vs[1]}.${vs[2]}~${vs[3]}`
return version return version
} }

View File

@@ -0,0 +1,8 @@
import { NgModule } from '@angular/core'
import { MarkdownPipe } from './markdown.pipe'
@NgModule({
declarations: [MarkdownPipe],
exports: [MarkdownPipe],
})
export class MarkdownPipeModule {}

View File

@@ -1,12 +1,12 @@
import { Pipe, PipeTransform } from '@angular/core' import { Pipe, PipeTransform } from '@angular/core'
import { isEmptyObject } from '../util/misc.util' import { isEmptyObject } from '../../util/misc.util'
@Pipe({ @Pipe({
name: 'empty', name: 'empty',
}) })
export class EmptyPipe implements PipeTransform { export class EmptyPipe implements PipeTransform {
transform (val: object | [] = { }): boolean { transform(val: object | [] = {}): boolean {
if (Array.isArray(val)) return !val.length if (Array.isArray(val)) return !val.length
return isEmptyObject(val) return isEmptyObject(val)
} }
} }

View File

@@ -1,10 +1,10 @@
import { Pipe, PipeTransform } from '@angular/core' import { Pipe, PipeTransform } from '@angular/core'
@Pipe({ @Pipe({
name: 'includes', name: 'includes',
}) })
export class IncludesPipe implements PipeTransform { export class IncludesPipe implements PipeTransform {
transform<T> (list: T[], val: T): boolean { transform<T>(list: T[], val: T): boolean {
return list.includes(val) return list.includes(val)
} }
} }

View File

@@ -0,0 +1,9 @@
import { NgModule } from '@angular/core'
import { IncludesPipe } from './includes.pipe'
import { EmptyPipe } from './empty.pipe'
@NgModule({
declarations: [IncludesPipe, EmptyPipe],
exports: [IncludesPipe, EmptyPipe],
})
export class SharedPipesModule {}

View File

@@ -0,0 +1,8 @@
import { NgModule } from '@angular/core'
import { ConvertBytesPipe, DurationToSecondsPipe } from './unit-conversion.pipe'
@NgModule({
declarations: [ConvertBytesPipe, DurationToSecondsPipe],
exports: [ConvertBytesPipe, DurationToSecondsPipe],
})
export class UnitConversionPipesModule {}

View File

@@ -0,0 +1,41 @@
import { Pipe, PipeTransform } from '@angular/core'
// converts bytes to gigabytes
@Pipe({
name: 'convertBytes',
})
export class ConvertBytesPipe implements PipeTransform {
transform(bytes: number): string {
if (bytes === 0) return '0 Bytes'
const k = 1024
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
}
}
@Pipe({
name: 'durationToSeconds',
})
export class DurationToSecondsPipe implements PipeTransform {
transform(duration: string | null): number {
if (!duration) return 0
const splitUnit = duration.match(/^([0-9]*(\.[0-9]+)?)(ns|µs|ms|s|m|d)$/)
const unit = splitUnit[3]
const num = splitUnit[1]
return Number(num) * unitsToSeconds[unit]
}
}
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const unitsToSeconds = {
ns: 1e-9,
µs: 1e-6,
ms: 0.001,
s: 1,
m: 60,
h: 3600,
d: 86400,
}

View File

@@ -1,5 +1,28 @@
/* /*
* Public API Surface of shared * Public API Surface of @start9labs/shared
*/ */
export * from './components/text-spinner/text-spinner.component.module'
export * from './components/text-spinner/text-spinner.component'
export * from './pipes/emver/emver.module'
export * from './pipes/emver/emver.pipe'
export * from './pipes/markdown/markdown.module'
export * from './pipes/markdown/markdown.pipe'
export * from './pipes/shared/shared.module'
export * from './pipes/shared/empty.pipe'
export * from './pipes/shared/includes.pipe'
export * from './pipes/unit-conversion/unit-conversion.module'
export * from './pipes/unit-conversion/unit-conversion.pipe'
export * from './services/destroy.service'
export * from './services/emver.service'
export * from './types/dependent-info'
export * from './types/install-progress'
export * from './types/package-state'
export * from './types/progress-data'
export * from './types/workspace-config' export * from './types/workspace-config'
export * from './util/misc.util'
export * from './util/package-loading-progress'

View File

@@ -1,5 +1,5 @@
import { Injectable, OnDestroy } from "@angular/core"; import { Injectable, OnDestroy } from '@angular/core'
import { ReplaySubject } from "rxjs"; import { ReplaySubject } from 'rxjs'
/** /**
* Observable abstraction over ngOnDestroy to use with takeUntil * Observable abstraction over ngOnDestroy to use with takeUntil
@@ -7,7 +7,7 @@ import { ReplaySubject } from "rxjs";
@Injectable() @Injectable()
export class DestroyService extends ReplaySubject<void> implements OnDestroy { export class DestroyService extends ReplaySubject<void> implements OnDestroy {
ngOnDestroy() { ngOnDestroy() {
this.next(); this.next()
this.complete(); this.complete()
} }
} }

View File

@@ -1,19 +1,18 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import * as emver from '@start9labs/emver' import * as emver from '@start9labs/emver'
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class Emver { export class Emver {
constructor () { } constructor() {}
compare (lhs: string, rhs: string): number { compare(lhs: string, rhs: string): number {
if (!lhs || !rhs) return null if (!lhs || !rhs) return null
return emver.compare(lhs, rhs) return emver.compare(lhs, rhs)
} }
satisfies (version: string, range: string): boolean { satisfies(version: string, range: string): boolean {
return emver.satisfies(version, range) return emver.satisfies(version, range)
} }
} }

View File

@@ -0,0 +1,5 @@
export interface DependentInfo {
id: string
title: string
version?: string
}

View File

@@ -0,0 +1,9 @@
export interface InstallProgress {
size: number | null
downloaded: number
'download-complete': boolean
validated: number
'validation-complete': boolean
unpacked: number
'unpack-complete': boolean
}

View File

@@ -0,0 +1,7 @@
export enum PackageState {
Installing = 'installing',
Installed = 'installed',
Updating = 'updating',
Removing = 'removing',
Restoring = 'restoring',
}

View File

@@ -0,0 +1,7 @@
export interface ProgressData {
totalProgress: number
downloadProgress: number
validateProgress: number
unpackProgress: number
isComplete: boolean
}

View File

@@ -0,0 +1,165 @@
import { OperatorFunction } from 'rxjs'
import { map } from 'rxjs/operators'
export function trace<T>(t: T): T {
console.log(`TRACE`, t)
return t
}
// curried description. This allows e.g somePromise.thentraceDesc('my result'))
export function traceDesc<T>(description: string): (t: T) => T {
return t => {
console.log(`TRACE`, description, t)
return t
}
}
// for use in observables. This allows e.g. someObservable.pipe(traceM('my result'))
// the practical equivalent of `tap(t => console.log(t, description))`
export function traceWheel<T>(description?: string): OperatorFunction<T, T> {
return description ? map(traceDesc(description)) : map(trace)
}
export function traceThrowDesc<T>(description: string, t: T | undefined): T {
if (!t) throw new Error(description)
return t
}
export function inMs(
count: number,
unit: 'days' | 'hours' | 'minutes' | 'seconds',
) {
switch (unit) {
case 'seconds':
return count * 1000
case 'minutes':
return inMs(count * 60, 'seconds')
case 'hours':
return inMs(count * 60, 'minutes')
case 'days':
return inMs(count * 24, 'hours')
}
}
// arr1 - arr2
export function diff<T>(arr1: T[], arr2: T[]): T[] {
return arr1.filter(x => !arr2.includes(x))
}
// arr1 & arr2
export function both<T>(arr1: T[], arr2: T[]): T[] {
return arr1.filter(x => arr2.includes(x))
}
export function isObject(val: any): boolean {
return val && typeof val === 'object' && !Array.isArray(val)
}
export function isEmptyObject(obj: object): boolean {
if (obj === undefined) return true
return !Object.keys(obj).length
}
export function pauseFor(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
export function toObject<T>(t: T[], map: (t0: T) => string): Record<string, T> {
return t.reduce((acc, next) => {
acc[map(next)] = next
return acc
}, {} as Record<string, T>)
}
export function update<T>(
t: Record<string, T>,
u: Record<string, T>,
): Record<string, T> {
return { ...t, ...u }
}
export function deepCloneUnknown<T>(value: T): T {
if (typeof value !== 'object' || value === null) {
return value
}
if (Array.isArray(value)) {
return deepCloneArray(value)
}
return deepCloneObject(value)
}
export function deepCloneObject<T>(source: T) {
const result = {}
Object.keys(source).forEach(key => {
const value = source[key]
result[key] = deepCloneUnknown(value)
}, {})
return result as T
}
export function deepCloneArray(collection: any) {
return collection.map(value => {
return deepCloneUnknown(value)
})
}
export function partitionArray<T>(
ts: T[],
condition: (t: T) => boolean,
): [T[], T[]] {
const yes = [] as T[]
const no = [] as T[]
ts.forEach(t => {
if (condition(t)) {
yes.push(t)
} else {
no.push(t)
}
})
return [yes, no]
}
export function uniqueBy<T>(
ts: T[],
uniqueBy: (t: T) => string,
prioritize: (t1: T, t2: T) => T,
) {
return Object.values(
ts.reduce((acc, next) => {
const previousValue = acc[uniqueBy(next)]
if (previousValue) {
acc[uniqueBy(next)] = prioritize(acc[uniqueBy(next)], previousValue)
} else {
acc[uniqueBy(next)] = previousValue
}
return acc
}, {}),
)
}
export function capitalizeFirstLetter(string: string): string {
return string.charAt(0).toUpperCase() + string.slice(1)
}
export const exists = (t: any) => {
return t !== undefined
}
export function debounce(delay: number = 300): MethodDecorator {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor,
) {
const timeoutKey = Symbol()
const original = descriptor.value
descriptor.value = function (...args) {
clearTimeout(this[timeoutKey])
this[timeoutKey] = setTimeout(() => original.apply(this, args), delay)
}
return descriptor
}
}

View File

@@ -1,5 +1,6 @@
import { InstallProgress } from 'src/app/services/patch-db/data-model'
import { isEmptyObject } from './misc.util' import { isEmptyObject } from './misc.util'
import { InstallProgress } from '../types/install-progress'
import { ProgressData } from '../types/progress-data'
export function packageLoadingProgress( export function packageLoadingProgress(
loadData: InstallProgress, loadData: InstallProgress,
@@ -46,11 +47,3 @@ export function packageLoadingProgress(
isComplete: downloadComplete && validationComplete && unpackComplete, isComplete: downloadComplete && validationComplete && unpackComplete,
} }
} }
export interface ProgressData {
totalProgress: number
downloadProgress: number
validateProgress: number
unpackProgress: number
isComplete: boolean
}

View File

@@ -2,6 +2,7 @@
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"baseUrl": "./",
"outDir": "../../out-tsc/lib", "outDir": "../../out-tsc/lib",
"declaration": true, "declaration": true,
"declarationMap": true, "declarationMap": true,

View File

@@ -16,17 +16,16 @@ import {
ModalController, ModalController,
ToastController, ToastController,
} from '@ionic/angular' } from '@ionic/angular'
import { Emver } from './services/emver.service'
import { SplitPaneTracker } from './services/split-pane.service' import { SplitPaneTracker } from './services/split-pane.service'
import { ToastButton } from '@ionic/core' import { ToastButton } from '@ionic/core'
import { PatchDbService } from './services/patch-db/patch-db.service' import { PatchDbService } from './services/patch-db/patch-db.service'
import { ServerStatus, UIData } from './services/patch-db/data-model'
import { import {
ConnectionFailure, ConnectionFailure,
ConnectionService, ConnectionService,
} from './services/connection.service' } from './services/connection.service'
import { ConfigService } from './services/config.service' import { ConfigService } from './services/config.service'
import { debounce, isEmptyObject } from './util/misc.util' import { debounce, isEmptyObject, Emver } from '@start9labs/shared'
import { ServerStatus, UIData } from 'src/app/services/patch-db/data-model'
import { ErrorToastService } from './services/error-toast.service' import { ErrorToastService } from './services/error-toast.service'
import { Subscription } from 'rxjs' import { Subscription } from 'rxjs'
import { LocalStorageService } from './services/local-storage.service' import { LocalStorageService } from './services/local-storage.service'
@@ -42,7 +41,7 @@ import { OSWelcomePage } from './modals/os-welcome/os-welcome.page'
export class AppComponent { export class AppComponent {
@HostListener('document:keydown.enter', ['$event']) @HostListener('document:keydown.enter', ['$event'])
@debounce() @debounce()
handleKeyboardEvent () { handleKeyboardEvent() {
const elems = document.getElementsByClassName('enter-click') const elems = document.getElementsByClassName('enter-click')
const elem = elems[elems.length - 1] as HTMLButtonElement const elem = elems[elems.length - 1] as HTMLButtonElement
if (!elem || elem.classList.contains('no-click') || elem.disabled) return if (!elem || elem.classList.contains('no-click') || elem.disabled) return
@@ -87,7 +86,7 @@ export class AppComponent {
}, },
] ]
constructor ( constructor(
private readonly storage: Storage, private readonly storage: Storage,
private readonly authService: AuthService, private readonly authService: AuthService,
private readonly router: Router, private readonly router: Router,
@@ -110,7 +109,7 @@ export class AppComponent {
this.init() this.init()
} }
async init () { async init() {
await this.storage.create() await this.storage.create()
await this.authService.init() await this.authService.init()
await this.localStorageService.init() await this.localStorageService.init()
@@ -181,7 +180,7 @@ export class AppComponent {
}) })
} }
async goToWebsite (): Promise<void> { async goToWebsite(): Promise<void> {
let url: string let url: string
if (this.config.isTor()) { if (this.config.isTor()) {
url = url =
@@ -192,7 +191,7 @@ export class AppComponent {
window.open(url, '_blank', 'noreferrer') window.open(url, '_blank', 'noreferrer')
} }
async presentAlertLogout () { async presentAlertLogout() {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
header: 'Caution', header: 'Caution',
message: message:
@@ -215,13 +214,13 @@ export class AppComponent {
await alert.present() await alert.present()
} }
private checkForEosUpdate (ui: UIData): void { private checkForEosUpdate(ui: UIData): void {
if (ui['auto-check-updates']) { if (ui['auto-check-updates']) {
this.eosService.getEOS() this.eosService.getEOS()
} }
} }
private async showEosWelcome (ackVersion: string): Promise<void> { private async showEosWelcome(ackVersion: string): Promise<void> {
if (!this.config.skipStartupAlerts && ackVersion !== this.config.version) { if (!this.config.skipStartupAlerts && ackVersion !== this.config.version) {
const modal = await this.modalCtrl.create({ const modal = await this.modalCtrl.create({
component: OSWelcomePage, component: OSWelcomePage,
@@ -240,12 +239,12 @@ export class AppComponent {
} }
// should wipe cache independant of actual BE logout // should wipe cache independant of actual BE logout
private async logout () { private async logout() {
this.embassyApi.logout({}) this.embassyApi.logout({})
this.authService.setUnverified() this.authService.setUnverified()
} }
private watchConnection (): Subscription { private watchConnection(): Subscription {
return this.connectionService return this.connectionService
.watchFailure$() .watchFailure$()
.pipe(distinctUntilChanged(), debounceTime(500)) .pipe(distinctUntilChanged(), debounceTime(500))
@@ -278,7 +277,7 @@ export class AppComponent {
}) })
} }
private watchRouter (): Subscription { private watchRouter(): Subscription {
return this.router.events return this.router.events
.pipe(filter((e: RoutesRecognized) => !!e.urlAfterRedirects)) .pipe(filter((e: RoutesRecognized) => !!e.urlAfterRedirects))
.subscribe(e => { .subscribe(e => {
@@ -289,7 +288,7 @@ export class AppComponent {
}) })
} }
private watchStatus (): Subscription { private watchStatus(): Subscription {
return this.patch return this.patch
.watch$('server-info', 'status-info', 'updated') .watch$('server-info', 'status-info', 'updated')
.subscribe(isUpdated => { .subscribe(isUpdated => {
@@ -300,7 +299,7 @@ export class AppComponent {
} }
m m
private watchUpdateProgress (): Subscription { private watchUpdateProgress(): Subscription {
return this.patch return this.patch
.watch$('server-info', 'status-info', 'update-progress') .watch$('server-info', 'status-info', 'update-progress')
.subscribe(progress => { .subscribe(progress => {
@@ -308,7 +307,7 @@ export class AppComponent {
}) })
} }
private watchVersion (): Subscription { private watchVersion(): Subscription {
return this.patch.watch$('server-info', 'version').subscribe(version => { return this.patch.watch$('server-info', 'version').subscribe(version => {
if (this.emver.compare(this.config.version, version) !== 0) { if (this.emver.compare(this.config.version, version) !== 0) {
this.presentAlertRefreshNeeded() this.presentAlertRefreshNeeded()
@@ -316,7 +315,7 @@ export class AppComponent {
}) })
} }
private watchNotifications (): Subscription { private watchNotifications(): Subscription {
let previous: number let previous: number
return this.patch return this.patch
.watch$('server-info', 'unread-notification-count') .watch$('server-info', 'unread-notification-count')
@@ -328,7 +327,7 @@ export class AppComponent {
}) })
} }
private async presentAlertRefreshNeeded () { private async presentAlertRefreshNeeded() {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
backdropDismiss: false, backdropDismiss: false,
header: 'Refresh Needed', header: 'Refresh Needed',
@@ -347,7 +346,7 @@ export class AppComponent {
await alert.present() await alert.present()
} }
private async presentToastUpdated () { private async presentToastUpdated() {
if (this.updateToast) return if (this.updateToast) return
this.updateToast = await this.toastCtrl.create({ this.updateToast = await this.toastCtrl.create({
@@ -377,7 +376,7 @@ export class AppComponent {
await this.updateToast.present() await this.updateToast.present()
} }
private async presentToastNotifications () { private async presentToastNotifications() {
if (this.notificationToast) return if (this.notificationToast) return
this.notificationToast = await this.toastCtrl.create({ this.notificationToast = await this.toastCtrl.create({
@@ -407,7 +406,7 @@ export class AppComponent {
await this.notificationToast.present() await this.notificationToast.present()
} }
private async presentToastOffline ( private async presentToastOffline(
message: string | IonicSafeString, message: string | IonicSafeString,
link?: string, link?: string,
) { ) {
@@ -448,7 +447,7 @@ export class AppComponent {
await this.offlineToast.present() await this.offlineToast.present()
} }
private async restart (): Promise<void> { private async restart(): Promise<void> {
const loader = await this.loadingCtrl.create({ const loader = await this.loadingCtrl.create({
spinner: 'lines', spinner: 'lines',
message: 'Restarting...', message: 'Restarting...',
@@ -465,7 +464,7 @@ export class AppComponent {
} }
} }
splitPaneVisible (e: any) { splitPaneVisible(e: any) {
this.splitPane.sidebarOpen$.next(e.detail.visible) this.splitPane.sidebarOpen$.next(e.detail.visible)
} }
} }

View File

@@ -15,15 +15,14 @@ import { OSWelcomePageModule } from './modals/os-welcome/os-welcome.module'
import { MarkdownPageModule } from './modals/markdown/markdown.module' import { MarkdownPageModule } from './modals/markdown/markdown.module'
import { PatchDbService } from './services/patch-db/patch-db.service' import { PatchDbService } from './services/patch-db/patch-db.service'
import { LocalStorageBootstrap } from './services/patch-db/local-storage-bootstrap' import { LocalStorageBootstrap } from './services/patch-db/local-storage-bootstrap'
import { SharingModule } from './modules/sharing.module'
import { FormBuilder } from '@angular/forms' import { FormBuilder } from '@angular/forms'
import { GenericInputComponentModule } from './modals/generic-input/generic-input.component.module' import { GenericInputComponentModule } from './modals/generic-input/generic-input.component.module'
import { AuthService } from './services/auth.service' import { AuthService } from './services/auth.service'
import { GlobalErrorHandler } from './services/global-error-handler.service' import { GlobalErrorHandler } from './services/global-error-handler.service'
import { MockApiService } from './services/api/embassy-mock-api.service' import { MockApiService } from './services/api/embassy-mock-api.service'
import { LiveApiService } from './services/api/embassy-live-api.service' import { LiveApiService } from './services/api/embassy-live-api.service'
import { WorkspaceConfig } from '@shared'
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor' import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'
import { SharedPipesModule, WorkspaceConfig } from '@start9labs/shared'
const { useMocks } = require('../../../../config.json') as WorkspaceConfig const { useMocks } = require('../../../../config.json') as WorkspaceConfig
@@ -47,8 +46,8 @@ const { useMocks } = require('../../../../config.json') as WorkspaceConfig
OSWelcomePageModule, OSWelcomePageModule,
MarkdownPageModule, MarkdownPageModule,
GenericInputComponentModule, GenericInputComponentModule,
SharingModule,
MonacoEditorModule, MonacoEditorModule,
SharedPipesModule,
], ],
providers: [ providers: [
FormBuilder, FormBuilder,
@@ -80,4 +79,4 @@ const { useMocks } = require('../../../../config.json') as WorkspaceConfig
bootstrap: [AppComponent], bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
}) })
export class AppModule {} export class AppModule { }

View File

@@ -1,8 +1,15 @@
import { NgModule } from '@angular/core' import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { BackupDrivesComponent, BackupDrivesHeaderComponent, BackupDrivesStatusComponent } from './backup-drives.component' import {
import { SharingModule } from '../../modules/sharing.module' BackupDrivesComponent,
BackupDrivesHeaderComponent,
BackupDrivesStatusComponent,
} from './backup-drives.component'
import {
UnitConversionPipesModule,
TextSpinnerComponentModule,
} from '@start9labs/shared'
import { GenericFormPageModule } from 'src/app/modals/generic-form/generic-form.module' import { GenericFormPageModule } from 'src/app/modals/generic-form/generic-form.module'
@NgModule({ @NgModule({
@@ -14,7 +21,8 @@ import { GenericFormPageModule } from 'src/app/modals/generic-form/generic-form.
imports: [ imports: [
CommonModule, CommonModule,
IonicModule, IonicModule,
SharingModule, UnitConversionPipesModule,
TextSpinnerComponentModule,
GenericFormPageModule, GenericFormPageModule,
], ],
exports: [ exports: [
@@ -23,4 +31,4 @@ import { GenericFormPageModule } from 'src/app/modals/generic-form/generic-form.
BackupDrivesStatusComponent, BackupDrivesStatusComponent,
], ],
}) })
export class BackupDrivesComponentModule { } export class BackupDrivesComponentModule {}

View File

@@ -1,12 +1,21 @@
import { Component, EventEmitter, Input, Output } from '@angular/core' import { Component, EventEmitter, Input, Output } from '@angular/core'
import { BackupService } from './backup.service' import { BackupService } from './backup.service'
import { CifsBackupTarget, DiskBackupTarget, RR } from 'src/app/services/api/api.types' import {
import { ActionSheetController, AlertController, LoadingController, ModalController } from '@ionic/angular' CifsBackupTarget,
DiskBackupTarget,
RR,
} from 'src/app/services/api/api.types'
import {
ActionSheetController,
AlertController,
LoadingController,
ModalController,
} from '@ionic/angular'
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page' import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
import { ConfigSpec } from 'src/app/pkg-config/config-types' import { ConfigSpec } from 'src/app/pkg-config/config-types'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ErrorToastService } from 'src/app/services/error-toast.service' import { ErrorToastService } from 'src/app/services/error-toast.service'
import { MappedBackupTarget } from 'src/app/util/misc.util' import { MappedBackupTarget } from 'src/app/types/mapped-backup-target'
@Component({ @Component({
selector: 'backup-drives', selector: 'backup-drives',
@@ -15,10 +24,12 @@ import { MappedBackupTarget } from 'src/app/util/misc.util'
}) })
export class BackupDrivesComponent { export class BackupDrivesComponent {
@Input() type: 'create' | 'restore' @Input() type: 'create' | 'restore'
@Output() onSelect: EventEmitter<MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>> = new EventEmitter() @Output() onSelect: EventEmitter<
MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>
> = new EventEmitter()
loadingText: string loadingText: string
constructor ( constructor(
private readonly loadingCtrl: LoadingController, private readonly loadingCtrl: LoadingController,
private readonly actionCtrl: ActionSheetController, private readonly actionCtrl: ActionSheetController,
private readonly alertCtrl: AlertController, private readonly alertCtrl: AlertController,
@@ -26,22 +37,30 @@ export class BackupDrivesComponent {
private readonly embassyApi: ApiService, private readonly embassyApi: ApiService,
private readonly errToast: ErrorToastService, private readonly errToast: ErrorToastService,
public readonly backupService: BackupService, public readonly backupService: BackupService,
) { } ) {}
ngOnInit () { ngOnInit() {
this.loadingText = this.type === 'create' ? 'Fetching Backup Targets' : 'Fetching Backup Sources' this.loadingText =
this.type === 'create'
? 'Fetching Backup Targets'
: 'Fetching Backup Sources'
this.backupService.getBackupTargets() this.backupService.getBackupTargets()
} }
select (target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>): void { select(
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
): void {
if (target.entry.type === 'cifs' && !target.entry.mountable) { if (target.entry.type === 'cifs' && !target.entry.mountable) {
const message = 'Unable to connect to shared folder. Ensure (1) target computer is connected to LAN, (2) target folder is being shared, and (3) hostname, path, and credentials are accurate.' const message =
'Unable to connect to shared folder. Ensure (1) target computer is connected to LAN, (2) target folder is being shared, and (3) hostname, path, and credentials are accurate.'
this.presentAlertError(message) this.presentAlertError(message)
return return
} }
if (this.type === 'restore' && !target.hasValidBackup) { if (this.type === 'restore' && !target.hasValidBackup) {
const message = `${target.entry.type === 'cifs' ? 'Shared folder' : 'Drive partition'} does not contain a valid Embassy backup.` const message = `${
target.entry.type === 'cifs' ? 'Shared folder' : 'Drive partition'
} does not contain a valid Embassy backup.`
this.presentAlertError(message) this.presentAlertError(message)
return return
} }
@@ -49,7 +68,7 @@ export class BackupDrivesComponent {
this.onSelect.emit(target) this.onSelect.emit(target)
} }
async presentModalAddCifs (): Promise<void> { async presentModalAddCifs(): Promise<void> {
const modal = await this.modalCtrl.create({ const modal = await this.modalCtrl.create({
component: GenericFormPage, component: GenericFormPage,
componentProps: { componentProps: {
@@ -69,7 +88,10 @@ export class BackupDrivesComponent {
await modal.present() await modal.present()
} }
async presentActionCifs (target: MappedBackupTarget<CifsBackupTarget>, index: number): Promise<void> { async presentActionCifs(
target: MappedBackupTarget<CifsBackupTarget>,
index: number,
): Promise<void> {
const entry = target.entry as CifsBackupTarget const entry = target.entry as CifsBackupTarget
const action = await this.actionCtrl.create({ const action = await this.actionCtrl.create({
@@ -93,8 +115,12 @@ export class BackupDrivesComponent {
}, },
}, },
{ {
text: this.type === 'create' ? 'Create Backup' : 'Restore From Backup', text:
icon: this.type === 'create' ? 'cloud-upload-outline' : 'cloud-download-outline', this.type === 'create' ? 'Create Backup' : 'Restore From Backup',
icon:
this.type === 'create'
? 'cloud-upload-outline'
: 'cloud-download-outline',
handler: () => { handler: () => {
this.select(target) this.select(target)
}, },
@@ -105,7 +131,7 @@ export class BackupDrivesComponent {
await action.present() await action.present()
} }
private async presentAlertError (message: string): Promise<void> { private async presentAlertError(message: string): Promise<void> {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
header: 'Error', header: 'Error',
message, message,
@@ -114,7 +140,7 @@ export class BackupDrivesComponent {
await alert.present() await alert.present()
} }
private async addCifs (value: RR.AddBackupTargetReq): Promise<boolean> { private async addCifs(value: RR.AddBackupTargetReq): Promise<boolean> {
const loader = await this.loadingCtrl.create({ const loader = await this.loadingCtrl.create({
spinner: 'lines', spinner: 'lines',
message: 'Testing connectivity to shared folder...', message: 'Testing connectivity to shared folder...',
@@ -139,7 +165,11 @@ export class BackupDrivesComponent {
} }
} }
private async presentModalEditCifs (id: string, entry: CifsBackupTarget, index: number): Promise<void> { private async presentModalEditCifs(
id: string,
entry: CifsBackupTarget,
index: number,
): Promise<void> {
const { hostname, path, username } = entry const { hostname, path, username } = entry
const modal = await this.modalCtrl.create({ const modal = await this.modalCtrl.create({
@@ -166,7 +196,10 @@ export class BackupDrivesComponent {
await modal.present() await modal.present()
} }
private async editCifs (value: RR.UpdateBackupTargetReq, index: number): Promise<void> { private async editCifs(
value: RR.UpdateBackupTargetReq,
index: number,
): Promise<void> {
const loader = await this.loadingCtrl.create({ const loader = await this.loadingCtrl.create({
spinner: 'lines', spinner: 'lines',
message: 'Testing connectivity to shared folder...', message: 'Testing connectivity to shared folder...',
@@ -185,7 +218,7 @@ export class BackupDrivesComponent {
} }
} }
private async deleteCifs (id: string, index: number): Promise<void> { private async deleteCifs(id: string, index: number): Promise<void> {
const loader = await this.loadingCtrl.create({ const loader = await this.loadingCtrl.create({
spinner: 'lines', spinner: 'lines',
message: 'Removing...', message: 'Removing...',
@@ -204,7 +237,6 @@ export class BackupDrivesComponent {
} }
} }
@Component({ @Component({
selector: 'backup-drives-header', selector: 'backup-drives-header',
templateUrl: './backup-drives-header.component.html', templateUrl: './backup-drives-header.component.html',
@@ -214,16 +246,13 @@ export class BackupDrivesHeaderComponent {
@Input() title: string @Input() title: string
@Output() onClose: EventEmitter<void> = new EventEmitter() @Output() onClose: EventEmitter<void> = new EventEmitter()
constructor ( constructor(public readonly backupService: BackupService) {}
public readonly backupService: BackupService,
) { }
refresh () { refresh() {
this.backupService.getBackupTargets() this.backupService.getBackupTargets()
} }
} }
@Component({ @Component({
selector: 'backup-drives-status', selector: 'backup-drives-status',
templateUrl: './backup-drives-status.component.html', templateUrl: './backup-drives-status.component.html',
@@ -238,7 +267,8 @@ const CifsSpec: ConfigSpec = {
hostname: { hostname: {
type: 'string', type: 'string',
name: 'Hostname', name: 'Hostname',
description: 'The hostname of your target device on the Local Area Network.', description:
'The hostname of your target device on the Local Area Network.',
placeholder: `e.g. 'My Computer' OR 'my-computer.local'`, placeholder: `e.g. 'My Computer' OR 'my-computer.local'`,
pattern: '^[a-zA-Z0-9._-]+( [a-zA-Z0-9]+)*$', pattern: '^[a-zA-Z0-9._-]+( [a-zA-Z0-9]+)*$',
'pattern-description': `Must be a valid hostname. e.g. 'My Computer' OR 'my-computer.local'`, 'pattern-description': `Must be a valid hostname. e.g. 'My Computer' OR 'my-computer.local'`,
@@ -249,7 +279,8 @@ const CifsSpec: ConfigSpec = {
path: { path: {
type: 'string', type: 'string',
name: 'Path', name: 'Path',
description: 'The directory path to the shared folder on your target device.', description:
'The directory path to the shared folder on your target device.',
placeholder: 'e.g. /Desktop/my-folder', placeholder: 'e.g. /Desktop/my-folder',
nullable: false, nullable: false,
masked: false, masked: false,

View File

@@ -2,9 +2,13 @@ import { Injectable } from '@angular/core'
import { IonicSafeString } from '@ionic/core' import { IonicSafeString } from '@ionic/core'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { getErrorMessage } from 'src/app/services/error-toast.service' import { getErrorMessage } from 'src/app/services/error-toast.service'
import { BackupTarget, CifsBackupTarget, DiskBackupTarget } from 'src/app/services/api/api.types' import {
import { Emver } from 'src/app/services/emver.service' BackupTarget,
import { MappedBackupTarget } from 'src/app/util/misc.util' CifsBackupTarget,
DiskBackupTarget,
} from 'src/app/services/api/api.types'
import { MappedBackupTarget } from 'src/app/types/mapped-backup-target'
import { Emver } from '@start9labs/shared'
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@@ -15,36 +19,36 @@ export class BackupService {
loading = true loading = true
loadingError: string | IonicSafeString loadingError: string | IonicSafeString
constructor ( constructor(
private readonly embassyApi: ApiService, private readonly embassyApi: ApiService,
private readonly emver: Emver, private readonly emver: Emver,
) { } ) {}
async getBackupTargets (): Promise<void> { async getBackupTargets(): Promise<void> {
this.loading = true this.loading = true
try { try {
const targets = await this.embassyApi.getBackupTargets({ }) const targets = await this.embassyApi.getBackupTargets({})
// cifs // cifs
this.cifs = Object.entries(targets) this.cifs = Object.entries(targets)
.filter(([_, target]) => target.type === 'cifs') .filter(([_, target]) => target.type === 'cifs')
.map(([id, cifs]) => { .map(([id, cifs]) => {
return { return {
id, id,
hasValidBackup: this.hasValidBackup(cifs), hasValidBackup: this.hasValidBackup(cifs),
entry: cifs as CifsBackupTarget, entry: cifs as CifsBackupTarget,
} }
}) })
// drives // drives
this.drives = Object.entries(targets) this.drives = Object.entries(targets)
.filter(([_, target]) => target.type === 'disk') .filter(([_, target]) => target.type === 'disk')
.map(([id, drive]) => { .map(([id, drive]) => {
return { return {
id, id,
hasValidBackup: this.hasValidBackup(drive), hasValidBackup: this.hasValidBackup(drive),
entry: drive as DiskBackupTarget, entry: drive as DiskBackupTarget,
} }
}) })
} catch (e) { } catch (e) {
this.loadingError = getErrorMessage(e) this.loadingError = getErrorMessage(e)
} finally { } finally {
@@ -52,7 +56,9 @@ export class BackupService {
} }
} }
hasValidBackup (target: BackupTarget): boolean { hasValidBackup(target: BackupTarget): boolean {
return [0, 1].includes(this.emver.compare(target['embassy-os']?.version, '0.3.0')) return [0, 1].includes(
this.emver.compare(target['embassy-os']?.version, '0.3.0'),
)
} }
} }

View File

@@ -1,4 +1,11 @@
<div style="position: relative; margin-right: 1vh;"> <div class="wrapper">
<ion-badge mode="md" class="md-badge" *ngIf="unreadCount && !sidebarOpen" color="danger">{{ unreadCount }}</ion-badge> <ion-badge
*ngIf="unreadCount && !sidebarOpen"
mode="md"
class="md-badge"
color="danger"
>
{{ unreadCount }}
</ion-badge>
<ion-menu-button color="dark"></ion-menu-button> <ion-menu-button color="dark"></ion-menu-button>
</div> </div>

View File

@@ -1,18 +1,11 @@
import { NgModule } from '@angular/core' import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { BadgeMenuComponent } from './badge-menu.component'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { SharingModule } from 'src/app/modules/sharing.module' import { BadgeMenuComponent } from './badge-menu.component'
@NgModule({ @NgModule({
declarations: [ declarations: [BadgeMenuComponent],
BadgeMenuComponent, imports: [CommonModule, IonicModule],
],
imports: [
CommonModule,
IonicModule,
SharingModule,
],
exports: [BadgeMenuComponent], exports: [BadgeMenuComponent],
}) })
export class BadgeMenuComponentModule { } export class BadgeMenuComponentModule {}

View File

@@ -1,3 +1,8 @@
.wrapper {
position: relative;
margin-right: 1vh;
}
.md-badge { .md-badge {
background-color: var(--ion-color-danger); background-color: var(--ion-color-danger);
position: absolute; position: absolute;

View File

@@ -1,29 +1,25 @@
import { NgModule } from '@angular/core' import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { FormObjectComponent, FormLabelComponent, FormErrorComponent } from './form-object.component' import {
FormObjectComponent,
FormLabelComponent,
FormErrorComponent,
} from './form-object.component'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { SharingModule } from 'src/app/modules/sharing.module' import { SharedPipesModule } from '@start9labs/shared'
import { EnumListPageModule } from 'src/app/modals/enum-list/enum-list.module' import { EnumListPageModule } from 'src/app/modals/enum-list/enum-list.module'
@NgModule({ @NgModule({
declarations: [ declarations: [FormObjectComponent, FormLabelComponent, FormErrorComponent],
FormObjectComponent,
FormLabelComponent,
FormErrorComponent,
],
imports: [ imports: [
CommonModule, CommonModule,
IonicModule, IonicModule,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
SharingModule, SharedPipesModule,
EnumListPageModule, EnumListPageModule,
], ],
exports: [ exports: [FormObjectComponent, FormLabelComponent, FormErrorComponent],
FormObjectComponent,
FormLabelComponent,
FormErrorComponent,
],
}) })
export class FormObjectComponentModule { } export class FormObjectComponentModule {}

View File

@@ -1,11 +1,28 @@
import { Component, Input, Output, EventEmitter } from '@angular/core' import { Component, Input, Output, EventEmitter } from '@angular/core'
import { AbstractFormGroupDirective, FormArray, FormGroup } from '@angular/forms' import {
import { AlertButton, AlertController, IonicSafeString, ModalController } from '@ionic/angular' AbstractFormGroupDirective,
import { ConfigSpec, ListValueSpecOf, ValueSpec, ValueSpecBoolean, ValueSpecList, ValueSpecListOf, ValueSpecUnion } from 'src/app/pkg-config/config-types' FormArray,
FormGroup,
} from '@angular/forms'
import {
AlertButton,
AlertController,
IonicSafeString,
ModalController,
} from '@ionic/angular'
import {
ConfigSpec,
ListValueSpecOf,
ValueSpec,
ValueSpecBoolean,
ValueSpecList,
ValueSpecListOf,
ValueSpecUnion,
} from 'src/app/pkg-config/config-types'
import { FormService } from 'src/app/services/form.service' import { FormService } from 'src/app/services/form.service'
import { Range } from 'src/app/pkg-config/config-utilities' import { Range } from 'src/app/pkg-config/config-utilities'
import { EnumListPage } from 'src/app/modals/enum-list/enum-list.page' import { EnumListPage } from 'src/app/modals/enum-list/enum-list.page'
import { pauseFor } from 'src/app/util/misc.util' import { pauseFor } from '@start9labs/shared'
import { v4 } from 'uuid' import { v4 } from 'uuid'
const Mustache = require('mustache') const Mustache = require('mustache')
@@ -22,31 +39,38 @@ export class FormObjectComponent {
@Input() showEdited: boolean = false @Input() showEdited: boolean = false
@Output() onInputChange = new EventEmitter<void>() @Output() onInputChange = new EventEmitter<void>()
@Output() onExpand = new EventEmitter<void>() @Output() onExpand = new EventEmitter<void>()
warningAck: { [key: string]: boolean } = { } warningAck: { [key: string]: boolean } = {}
unmasked: { [key: string]: boolean } = { } unmasked: { [key: string]: boolean } = {}
objectDisplay: { [key: string]: { expanded: boolean, height: string } } = { } objectDisplay: { [key: string]: { expanded: boolean; height: string } } = {}
objectListDisplay: { [key: string]: { expanded: boolean, height: string, displayAs: string }[] } = { } objectListDisplay: {
[key: string]: { expanded: boolean; height: string; displayAs: string }[]
} = {}
private objectId = v4() private objectId = v4()
Object = Object Object = Object
constructor ( constructor(
private readonly alertCtrl: AlertController, private readonly alertCtrl: AlertController,
private readonly modalCtrl: ModalController, private readonly modalCtrl: ModalController,
private readonly formService: FormService, private readonly formService: FormService,
) { } ) {}
ngOnInit () { ngOnInit() {
Object.keys(this.objectSpec).forEach(key => { Object.keys(this.objectSpec).forEach(key => {
const spec = this.objectSpec[key] const spec = this.objectSpec[key]
if (spec.type === 'list' && ['object', 'union'].includes(spec.subtype)) { if (spec.type === 'list' && ['object', 'union'].includes(spec.subtype)) {
this.objectListDisplay[key] = []; this.objectListDisplay[key] = []
(this.formGroup.get(key).value as any[]).forEach((obj, index) => { this.formGroup.get(key).value.forEach((obj, index) => {
const displayAs = (spec.spec as ListValueSpecOf<'object'>)['display-as'] const displayAs = (spec.spec as ListValueSpecOf<'object'>)[
'display-as'
]
this.objectListDisplay[key][index] = { this.objectListDisplay[key][index] = {
expanded: false, expanded: false,
height: '0px', height: '0px',
displayAs: displayAs ? (Mustache as any).render(displayAs, obj) : '', displayAs: displayAs
? (Mustache as any).render(displayAs, obj)
: '',
} }
}) })
} else if (['object', 'union'].includes(spec.type)) { } else if (['object', 'union'].includes(spec.type)) {
@@ -58,11 +82,11 @@ export class FormObjectComponent {
}) })
} }
getEnumListDisplay (arr: string[], spec: ListValueSpecOf<'enum'>): string { getEnumListDisplay(arr: string[], spec: ListValueSpecOf<'enum'>): string {
return arr.map((v: string) => spec['value-names'][v]).join(', ') return arr.map((v: string) => spec['value-names'][v]).join(', ')
} }
updateUnion (e: any): void { updateUnion(e: any): void {
const primary = this.unionSpec.tag.id const primary = this.unionSpec.tag.id
Object.keys(this.formGroup.controls).forEach(control => { Object.keys(this.formGroup.controls).forEach(control => {
@@ -70,26 +94,31 @@ export class FormObjectComponent {
this.formGroup.removeControl(control) this.formGroup.removeControl(control)
}) })
const unionGroup = this.formService.getUnionObject(this.unionSpec as ValueSpecUnion, e.detail.value) const unionGroup = this.formService.getUnionObject(
this.unionSpec as ValueSpecUnion,
e.detail.value,
)
Object.keys(unionGroup.controls).forEach(control => { Object.keys(unionGroup.controls).forEach(control => {
if (control === primary) return if (control === primary) return
this.formGroup.addControl(control, unionGroup.controls[control]) this.formGroup.addControl(control, unionGroup.controls[control])
}) })
Object.entries(this.unionSpec.variants[e.detail.value]).forEach(([key, value]) => { Object.entries(this.unionSpec.variants[e.detail.value]).forEach(
if (['object', 'union'].includes(value.type)) { ([key, value]) => {
this.objectDisplay[key] = { if (['object', 'union'].includes(value.type)) {
expanded: false, this.objectDisplay[key] = {
height: '0px', expanded: false,
height: '0px',
}
} }
} },
}) )
this.onExpand.emit() this.onExpand.emit()
} }
resize (key: string, i?: number): void { resize(key: string, i?: number): void {
setTimeout(() => { setTimeout(() => {
if (i !== undefined) { if (i !== undefined) {
this.objectListDisplay[key][i].height = this.getDocSize(key, i) this.objectListDisplay[key][i].height = this.getDocSize(key, i)
@@ -100,11 +129,11 @@ export class FormObjectComponent {
}, 250) // 250 to match transition-duration, defined in html }, 250) // 250 to match transition-duration, defined in html
} }
addListItemWrapper (key: string, spec: ValueSpec) { addListItemWrapper(key: string, spec: ValueSpec) {
this.presentAlertChangeWarning(key, spec, () => this.addListItem(key)) this.presentAlertChangeWarning(key, spec, () => this.addListItem(key))
} }
addListItem (key: string, markDirty = true, val?: string): void { addListItem(key: string, markDirty = true, val?: string): void {
const arr = this.formGroup.get(key) as FormArray const arr = this.formGroup.get(key) as FormArray
if (markDirty) arr.markAsDirty() if (markDirty) arr.markAsDirty()
const listSpec = this.objectSpec[key] as ValueSpecList const listSpec = this.objectSpec[key] as ValueSpecList
@@ -112,7 +141,9 @@ export class FormObjectComponent {
newItem.markAllAsTouched() newItem.markAllAsTouched()
arr.insert(0, newItem) arr.insert(0, newItem)
if (['object', 'union'].includes(listSpec.subtype)) { if (['object', 'union'].includes(listSpec.subtype)) {
const displayAs = (listSpec.spec as ListValueSpecOf<'object'>)['display-as'] const displayAs = (listSpec.spec as ListValueSpecOf<'object'>)[
'display-as'
]
this.objectListDisplay[key].unshift({ this.objectListDisplay[key].unshift({
height: '0px', height: '0px',
expanded: true, expanded: true,
@@ -125,30 +156,39 @@ export class FormObjectComponent {
} }
} }
toggleExpandObject (key: string) { toggleExpandObject(key: string) {
this.objectDisplay[key].expanded = !this.objectDisplay[key].expanded this.objectDisplay[key].expanded = !this.objectDisplay[key].expanded
this.objectDisplay[key].height = this.objectDisplay[key].expanded ? this.getDocSize(key) : '0px' this.objectDisplay[key].height = this.objectDisplay[key].expanded
? this.getDocSize(key)
: '0px'
this.onExpand.emit() this.onExpand.emit()
} }
toggleExpandListObject (key: string, i: number) { toggleExpandListObject(key: string, i: number) {
this.objectListDisplay[key][i].expanded = !this.objectListDisplay[key][i].expanded this.objectListDisplay[key][i].expanded =
this.objectListDisplay[key][i].height = this.objectListDisplay[key][i].expanded ? this.getDocSize(key, i) : '0px' !this.objectListDisplay[key][i].expanded
this.objectListDisplay[key][i].height = this.objectListDisplay[key][i]
.expanded
? this.getDocSize(key, i)
: '0px'
} }
updateLabel (key: string, i: number, displayAs: string) { updateLabel(key: string, i: number, displayAs: string) {
this.objectListDisplay[key][i].displayAs = displayAs ? Mustache.render(displayAs, this.formGroup.get(key).value[i]) : '' this.objectListDisplay[key][i].displayAs = displayAs
? Mustache.render(displayAs, this.formGroup.get(key).value[i])
: ''
} }
getWarningText (text: string): IonicSafeString { getWarningText(text: string): IonicSafeString {
if (text) return new IonicSafeString(`<ion-text color="warning">${text}</ion-text>`) if (text)
return new IonicSafeString(`<ion-text color="warning">${text}</ion-text>`)
} }
handleInputChange () { handleInputChange() {
this.onInputChange.emit() this.onInputChange.emit()
} }
handleBooleanChange (key: string, spec: ValueSpecBoolean) { handleBooleanChange(key: string, spec: ValueSpecBoolean) {
if (spec.warning) { if (spec.warning) {
const current = this.formGroup.get(key).value const current = this.formGroup.get(key).value
const cancelFn = () => this.formGroup.get(key).setValue(!current) const cancelFn = () => this.formGroup.get(key).setValue(!current)
@@ -156,7 +196,11 @@ export class FormObjectComponent {
} }
} }
async presentModalEnumList (key: string, spec: ValueSpecListOf<'enum'>, current: string[]) { async presentModalEnumList(
key: string,
spec: ValueSpecListOf<'enum'>,
current: string[],
) {
const modal = await this.modalCtrl.create({ const modal = await this.modalCtrl.create({
componentProps: { componentProps: {
key, key,
@@ -175,7 +219,12 @@ export class FormObjectComponent {
await modal.present() await modal.present()
} }
async presentAlertChangeWarning (key: string, spec: ValueSpec, okFn?: Function, cancelFn?: Function) { async presentAlertChangeWarning(
key: string,
spec: ValueSpec,
okFn?: Function,
cancelFn?: Function,
) {
if (!spec.warning || this.warningAck[key]) return okFn ? okFn() : null if (!spec.warning || this.warningAck[key]) return okFn ? okFn() : null
this.warningAck[key] = true this.warningAck[key] = true
@@ -207,7 +256,7 @@ export class FormObjectComponent {
await alert.present() await alert.present()
} }
async presentAlertDelete (key: string, index: number) { async presentAlertDelete(key: string, index: number) {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
header: 'Confirm', header: 'Confirm',
message: 'Are you sure you want to delete this entry?', message: 'Are you sure you want to delete this entry?',
@@ -228,17 +277,19 @@ export class FormObjectComponent {
await alert.present() await alert.present()
} }
private deleteListItem (key: string, index: number, markDirty = true): void { private deleteListItem(key: string, index: number, markDirty = true): void {
if (this.objectListDisplay[key]) this.objectListDisplay[key][index].height = '0px' if (this.objectListDisplay[key])
this.objectListDisplay[key][index].height = '0px'
const arr = this.formGroup.get(key) as FormArray const arr = this.formGroup.get(key) as FormArray
if (markDirty) arr.markAsDirty() if (markDirty) arr.markAsDirty()
pauseFor(250).then(() => { pauseFor(250).then(() => {
if (this.objectListDisplay[key]) this.objectListDisplay[key].splice(index, 1) if (this.objectListDisplay[key])
this.objectListDisplay[key].splice(index, 1)
arr.removeAt(index) arr.removeAt(index)
}) })
} }
private updateEnumList (key: string, current: string[], updated: string[]) { private updateEnumList(key: string, current: string[], updated: string[]) {
this.formGroup.get(key).markAsDirty() this.formGroup.get(key).markAsDirty()
for (let i = current.length - 1; i >= 0; i--) { for (let i = current.length - 1; i >= 0; i--) {
@@ -254,17 +305,16 @@ export class FormObjectComponent {
}) })
} }
private getDocSize (key: string, index = 0) { private getDocSize(key: string, index = 0) {
const element = document.getElementById(this.getElementId(key, index)) const element = document.getElementById(this.getElementId(key, index))
return `${element.scrollHeight}px` return `${element.scrollHeight}px`
} }
getElementId (key: string, index = 0): string { getElementId(key: string, index = 0): string {
return `${key}-${index}-${this.objectId}` return `${key}-${index}-${this.objectId}`
} }
async presentUnionTagDescription (name: string, description: string) { async presentUnionTagDescription(name: string, description: string) {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
header: name, header: name,
message: description, message: description,
@@ -272,7 +322,7 @@ export class FormObjectComponent {
await alert.present() await alert.present()
} }
asIsOrder () { asIsOrder() {
return 0 return 0
} }
} }
@@ -293,11 +343,9 @@ export class FormLabelComponent {
Range = Range Range = Range
@Input() data: HeaderData @Input() data: HeaderData
constructor ( constructor(private readonly alertCtrl: AlertController) {}
private readonly alertCtrl: AlertController,
) { }
async presentAlertDescription () { async presentAlertDescription() {
const { name, description } = this.data.spec const { name, description } = this.data.spec
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
@@ -308,7 +356,6 @@ export class FormLabelComponent {
} }
} }
@Component({ @Component({
selector: 'form-error', selector: 'form-error',
templateUrl: './form-error.component.html', templateUrl: './form-error.component.html',

View File

@@ -3,18 +3,16 @@ import { CommonModule } from '@angular/common'
import { AlertComponent } from './alert.component' import { AlertComponent } from './alert.component'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { RouterModule } from '@angular/router' import { RouterModule } from '@angular/router'
import { SharingModule } from 'src/app/modules/sharing.module' import { MarkdownPipeModule } from '@start9labs/shared'
@NgModule({ @NgModule({
declarations: [ declarations: [AlertComponent],
AlertComponent,
],
imports: [ imports: [
CommonModule, CommonModule,
IonicModule, IonicModule,
RouterModule.forChild([]), RouterModule.forChild([]),
SharingModule, MarkdownPipeModule,
], ],
exports: [AlertComponent], exports: [AlertComponent],
}) })
export class AlertComponentModule { } export class AlertComponentModule {}

View File

@@ -1,7 +1,7 @@
import { Component, Input } from '@angular/core' import { Component, Input } from '@angular/core'
import { BehaviorSubject, from, Subject } from 'rxjs' import { BehaviorSubject, from, Subject } from 'rxjs'
import { takeUntil } from 'rxjs/operators' import { takeUntil } from 'rxjs/operators'
import { capitalizeFirstLetter } from 'src/app/util/misc.util' import { capitalizeFirstLetter } from '@start9labs/shared'
import { markAsLoadingDuring$ } from '../loadable' import { markAsLoadingDuring$ } from '../loadable'
import { WizardAction } from '../wizard-types' import { WizardAction } from '../wizard-types'
@@ -30,16 +30,21 @@ export class CompleteComponent {
message: string message: string
load () { load() {
markAsLoadingDuring$(this.loading$, from(this.params.executeAction())).pipe(takeUntil(this.cancel$)).subscribe( markAsLoadingDuring$(this.loading$, from(this.params.executeAction()))
{ .pipe(takeUntil(this.cancel$))
error: e => this.transitions.error(new Error(`${this.params.action} failed: ${e.message || e}`)), .subscribe({
error: e =>
this.transitions.error(
new Error(`${this.params.action} failed: ${e.message || e}`),
),
complete: () => this.transitions.final(), complete: () => this.transitions.final(),
}, })
)
} }
ngOnInit () { ngOnInit() {
this.message = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title}...` this.message = `${capitalizeFirstLetter(this.params.verb)} ${
this.params.title
}...`
} }
} }

View File

@@ -3,18 +3,16 @@ import { CommonModule } from '@angular/common'
import { DependentsComponent } from './dependents.component' import { DependentsComponent } from './dependents.component'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { RouterModule } from '@angular/router' import { RouterModule } from '@angular/router'
import { SharingModule } from 'src/app/modules/sharing.module' import { SharedPipesModule } from '@start9labs/shared'
@NgModule({ @NgModule({
declarations: [ declarations: [DependentsComponent],
DependentsComponent,
],
imports: [ imports: [
CommonModule, CommonModule,
IonicModule, IonicModule,
RouterModule.forChild([]), RouterModule.forChild([]),
SharingModule, SharedPipesModule,
], ],
exports: [DependentsComponent], exports: [DependentsComponent],
}) })
export class DependentsComponentModule { } export class DependentsComponentModule {}

View File

@@ -3,7 +3,7 @@ import { BehaviorSubject, from, Subject } from 'rxjs'
import { takeUntil, tap } from 'rxjs/operators' import { takeUntil, tap } from 'rxjs/operators'
import { Breakages } from 'src/app/services/api/api.types' import { Breakages } from 'src/app/services/api/api.types'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { capitalizeFirstLetter, isEmptyObject } from 'src/app/util/misc.util' import { capitalizeFirstLetter, isEmptyObject } from '@start9labs/shared'
import { markAsLoadingDuring$ } from '../loadable' import { markAsLoadingDuring$ } from '../loadable'
import { WizardAction } from '../wizard-types' import { WizardAction } from '../wizard-types'
@@ -14,9 +14,9 @@ import { WizardAction } from '../wizard-types'
}) })
export class DependentsComponent { export class DependentsComponent {
@Input() params: { @Input() params: {
title: string, title: string
action: WizardAction, //Are you sure you want to *uninstall*..., action: WizardAction //Are you sure you want to *uninstall*...,
verb: string, // *Uninstalling* will cause problems... verb: string // *Uninstalling* will cause problems...
fetchBreakages: () => Promise<Breakages> fetchBreakages: () => Promise<Breakages>
} }
@Input() transitions: { @Input() transitions: {
@@ -32,27 +32,37 @@ export class DependentsComponent {
loading$ = new BehaviorSubject(false) loading$ = new BehaviorSubject(false)
cancel$ = new Subject<void>() cancel$ = new Subject<void>()
constructor ( constructor(public readonly patch: PatchDbService) {}
public readonly patch: PatchDbService,
) { }
load () { load() {
markAsLoadingDuring$(this.loading$, from(this.params.fetchBreakages())) markAsLoadingDuring$(this.loading$, from(this.params.fetchBreakages()))
.pipe( .pipe(
takeUntil(this.cancel$), takeUntil(this.cancel$),
tap(breakages => this.dependentBreakages = breakages), tap(breakages => (this.dependentBreakages = breakages)),
) )
.subscribe( .subscribe({
{
complete: () => { complete: () => {
if (this.dependentBreakages && !isEmptyObject(this.dependentBreakages)) { if (
this.dependentViolation = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title} will prohibit the following services from functioning properly and may cause them to stop if they are currently running.` this.dependentBreakages &&
!isEmptyObject(this.dependentBreakages)
) {
this.dependentViolation = `${capitalizeFirstLetter(
this.params.verb,
)} ${
this.params.title
} will prohibit the following services from functioning properly and may cause them to stop if they are currently running.`
} else { } else {
this.transitions.next() this.transitions.next()
} }
}, },
error: (e: Error) => this.transitions.error(new Error(`Fetching dependent service information failed: ${e.message || e}`)), error: (e: Error) =>
}, this.transitions.error(
) new Error(
`Fetching dependent service information failed: ${
e.message || e
}`,
),
),
})
} }
} }

View File

@@ -3,21 +3,19 @@ import { CommonModule } from '@angular/common'
import { InstallWizardComponent } from './install-wizard.component' import { InstallWizardComponent } from './install-wizard.component'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { RouterModule } from '@angular/router' import { RouterModule } from '@angular/router'
import { SharingModule } from 'src/app/modules/sharing.module' import { EmverPipesModule } from '@start9labs/shared'
import { DependentsComponentModule } from './dependents/dependents.component.module' import { DependentsComponentModule } from './dependents/dependents.component.module'
import { CompleteComponentModule } from './complete/complete.component.module' import { CompleteComponentModule } from './complete/complete.component.module'
import { NotesComponentModule } from './notes/notes.component.module' import { NotesComponentModule } from './notes/notes.component.module'
import { AlertComponentModule } from './alert/alert.component.module' import { AlertComponentModule } from './alert/alert.component.module'
@NgModule({ @NgModule({
declarations: [ declarations: [InstallWizardComponent],
InstallWizardComponent,
],
imports: [ imports: [
CommonModule, CommonModule,
IonicModule, IonicModule,
RouterModule.forChild([]), RouterModule.forChild([]),
SharingModule, EmverPipesModule,
DependentsComponentModule, DependentsComponentModule,
CompleteComponentModule, CompleteComponentModule,
NotesComponentModule, NotesComponentModule,
@@ -25,4 +23,4 @@ import { AlertComponentModule } from './alert/alert.component.module'
], ],
exports: [InstallWizardComponent], exports: [InstallWizardComponent],
}) })
export class InstallWizardComponentModule { } export class InstallWizardComponentModule {}

View File

@@ -1,6 +1,13 @@
import { Component, Input, NgZone, QueryList, ViewChild, ViewChildren } from '@angular/core' import {
Component,
Input,
NgZone,
QueryList,
ViewChild,
ViewChildren,
} from '@angular/core'
import { IonContent, IonSlides, ModalController } from '@ionic/angular' import { IonContent, IonSlides, ModalController } from '@ionic/angular'
import { capitalizeFirstLetter, pauseFor } from 'src/app/util/misc.util' import { capitalizeFirstLetter, pauseFor } from '@start9labs/shared'
import { CompleteComponent } from './complete/complete.component' import { CompleteComponent } from './complete/complete.component'
import { DependentsComponent } from './dependents/dependents.component' import { DependentsComponent } from './dependents/dependents.component'
import { AlertComponent } from './alert/alert.component' import { AlertComponent } from './alert/alert.component'
@@ -30,54 +37,70 @@ export class InstallWizardComponent {
//a slide component gives us hook into a slide. Allows us to call load when slide comes into view //a slide component gives us hook into a slide. Allows us to call load when slide comes into view
@ViewChildren('components') @ViewChildren('components')
slideComponentsQL: QueryList<Loadable> slideComponentsQL: QueryList<Loadable>
get slideComponents (): Loadable[] { return this.slideComponentsQL.toArray() } get slideComponents(): Loadable[] {
return this.slideComponentsQL.toArray()
}
private slideIndex = 0 private slideIndex = 0
get currentSlide (): Loadable { get currentSlide(): Loadable {
return this.slideComponents[this.slideIndex] return this.slideComponents[this.slideIndex]
} }
get currentBottomBar (): SlideDefinition['bottomBar'] { get currentBottomBar(): SlideDefinition['bottomBar'] {
return this.params.slideDefinitions[this.slideIndex].bottomBar return this.params.slideDefinitions[this.slideIndex].bottomBar
} }
initializing = true initializing = true
error = '' error = ''
constructor ( constructor(
private readonly modalController: ModalController, private readonly modalController: ModalController,
private readonly zone: NgZone, private readonly zone: NgZone,
) { } ) {}
ngAfterViewInit () { ngAfterViewInit() {
this.currentSlide.load() this.currentSlide.load()
this.slideContainer.update() this.slideContainer.update()
this.slideContainer.lockSwipes(true) this.slideContainer.lockSwipes(true)
} }
ionViewDidEnter () { ionViewDidEnter() {
this.initializing = false this.initializing = false
} }
// process bottom bar buttons // process bottom bar buttons
private transition = (info: { next: any } | { error: Error } | { cancelled: true } | { final: true }) => { private transition = (
const i = info as { next?: any, error?: Error, cancelled?: true, final?: true } info:
| { next: any }
| { error: Error }
| { cancelled: true }
| { final: true },
) => {
const i = info as {
next?: any
error?: Error
cancelled?: true
final?: true
}
if (i.cancelled) this.currentSlide.cancel$.next() if (i.cancelled) this.currentSlide.cancel$.next()
if (i.final || i.cancelled) return this.modalController.dismiss(i) if (i.final || i.cancelled) return this.modalController.dismiss(i)
if (i.error) return this.error = capitalizeFirstLetter(i.error.message) if (i.error) return (this.error = capitalizeFirstLetter(i.error.message))
this.moveToNextSlide(i.next) this.moveToNextSlide(i.next)
} }
// bottom bar button callbacks. Pass this into components if they need to trigger slide transitions independent of the bottom bar clicks // bottom bar button callbacks. Pass this into components if they need to trigger slide transitions independent of the bottom bar clicks
transitions = { transitions = {
next: (prevResult: any) => this.transition({ next: prevResult || this.currentSlide.result }), next: (prevResult: any) =>
this.transition({ next: prevResult || this.currentSlide.result }),
cancel: () => this.transition({ cancelled: true }), cancel: () => this.transition({ cancelled: true }),
final: () => this.transition({ final: true }), final: () => this.transition({ final: true }),
error: (e: Error) => this.transition({ error: e }), error: (e: Error) => this.transition({ error: e }),
} }
private async moveToNextSlide (prevResult?: any) { private async moveToNextSlide(prevResult?: any) {
if (this.slideComponents[this.slideIndex + 1] === undefined) { return this.transition({ final: true }) } if (this.slideComponents[this.slideIndex + 1] === undefined) {
return this.transition({ final: true })
}
this.zone.run(async () => { this.zone.run(async () => {
this.slideComponents[this.slideIndex + 1].load(prevResult) this.slideComponents[this.slideIndex + 1].load(prevResult)
await pauseFor(50) // give the load ^ opportunity to propogate into slide before sliding it into view await pauseFor(50) // give the load ^ opportunity to propogate into slide before sliding it into view
@@ -89,7 +112,7 @@ export class InstallWizardComponent {
}) })
} }
async callTransition (transition: Function) { async callTransition(transition: Function) {
this.transitioning = true this.transitioning = true
await transition() await transition()
this.transitioning = false this.transitioning = false
@@ -98,14 +121,14 @@ export class InstallWizardComponent {
export interface SlideDefinition { export interface SlideDefinition {
slide: slide:
{ selector: 'dependents', params: DependentsComponent['params'] } | | { selector: 'dependents'; params: DependentsComponent['params'] }
{ selector: 'complete', params: CompleteComponent['params'] } | | { selector: 'complete'; params: CompleteComponent['params'] }
{ selector: 'alert', params: AlertComponent['params'] } | | { selector: 'alert'; params: AlertComponent['params'] }
{ selector: 'notes', params: NotesComponent['params'] } | { selector: 'notes'; params: NotesComponent['params'] }
bottomBar: { bottomBar: {
cancel: { cancel: {
// indicates the existence of a cancel button, and whether to have text or an icon 'x' by default. // indicates the existence of a cancel button, and whether to have text or an icon 'x' by default.
afterLoading?: { text?: string }, afterLoading?: { text?: string }
whileLoading?: { text?: string } whileLoading?: { text?: string }
} }
// indicates the existence of next or finish buttons (should only have one) // indicates the existence of next or finish buttons (should only have one)
@@ -114,11 +137,16 @@ export interface SlideDefinition {
} }
} }
export type TopbarParams = { action: WizardAction, title: string, version: string } export type TopbarParams = {
action: WizardAction
title: string
version: string
}
export async function wizardModal ( export async function wizardModal(
modalController: ModalController, params: InstallWizardComponent['params'], modalController: ModalController,
): Promise<{ cancelled?: true, final?: true, modal: HTMLIonModalElement }> { params: InstallWizardComponent['params'],
): Promise<{ cancelled?: true; final?: true; modal: HTMLIonModalElement }> {
const modal = await modalController.create({ const modal = await modalController.create({
backdropDismiss: false, backdropDismiss: false,
cssClass: 'wizard-modal', cssClass: 'wizard-modal',

View File

@@ -3,18 +3,16 @@ import { CommonModule } from '@angular/common'
import { NotesComponent } from './notes.component' import { NotesComponent } from './notes.component'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { RouterModule } from '@angular/router' import { RouterModule } from '@angular/router'
import { SharingModule } from 'src/app/modules/sharing.module' import { MarkdownPipeModule } from '@start9labs/shared'
@NgModule({ @NgModule({
declarations: [ declarations: [NotesComponent],
NotesComponent,
],
imports: [ imports: [
CommonModule, CommonModule,
IonicModule, IonicModule,
RouterModule.forChild([]), RouterModule.forChild([]),
SharingModule, MarkdownPipeModule,
], ],
exports: [NotesComponent], exports: [NotesComponent],
}) })
export class NotesComponentModule { } export class NotesComponentModule {}

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model' import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { Breakages } from 'src/app/services/api/api.types' import { Breakages } from 'src/app/services/api/api.types'
import { exists } from 'src/app/util/misc.util' import { exists } from '@start9labs/shared'
import { ApiService } from '../../services/api/embassy-api.service' import { ApiService } from '../../services/api/embassy-api.service'
import { import {
InstallWizardComponent, InstallWizardComponent,

View File

@@ -2,15 +2,11 @@ import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { LogsPage } from './logs.page' import { LogsPage } from './logs.page'
import { SharingModule } from 'src/app/modules/sharing.module' import { TextSpinnerComponentModule } from '@start9labs/shared'
@NgModule({ @NgModule({
declarations: [LogsPage], declarations: [LogsPage],
imports: [ imports: [CommonModule, IonicModule, TextSpinnerComponentModule],
CommonModule,
IonicModule,
SharingModule,
],
exports: [LogsPage], exports: [LogsPage],
}) })
export class LogsPageModule { } export class LogsPageModule {}

View File

@@ -1,13 +1,21 @@
<p <p
[style.color]="disconnected ? 'gray' : 'var(--ion-color-' + rendering.color + ')'" [style.color]="
disconnected ? 'gray' : 'var(--ion-color-' + rendering.color + ')'
"
[style.font-size]="size" [style.font-size]="size"
[style.font-style]="style" [style.font-style]="style"
[style.font-weight]="weight" [style.font-weight]="weight"
> >
<span *ngIf= "!installProgress"> <span *ngIf="!installProgress">
{{ disconnected ? 'Unknown' : rendering.display }} {{ disconnected ? 'Unknown' : rendering.display }}
<span *ngIf="rendering.showDots" class="loading-dots"></span> <span *ngIf="rendering.showDots" class="loading-dots"></span>
<span *ngIf="rendering.display === PR[PS.Stopping].display && (sigtermTimeout | durationToSeconds) > 30">This may take a while.</span> <span
*ngIf="
rendering.display === PR[PS.Stopping].display &&
(sigtermTimeout | durationToSeconds) > 30
"
>This may take a while.</span
>
</span> </span>
<span *ngIf="installProgress"> <span *ngIf="installProgress">
<span *ngIf="installProgress < 99"> <span *ngIf="installProgress < 99">
@@ -19,5 +27,4 @@
<span class="loading-dots"></span> <span class="loading-dots"></span>
</span> </span>
</span> </span>
</p> </p>

View File

@@ -1,18 +1,12 @@
import { NgModule } from '@angular/core' import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { StatusComponent } from './status.component'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { SharingModule } from 'src/app/modules/sharing.module' import { UnitConversionPipesModule } from '@start9labs/shared'
import { StatusComponent } from './status.component'
@NgModule({ @NgModule({
declarations: [ declarations: [StatusComponent],
StatusComponent, imports: [CommonModule, IonicModule, UnitConversionPipesModule],
],
imports: [
CommonModule,
IonicModule,
SharingModule,
],
exports: [StatusComponent], exports: [StatusComponent],
}) })
export class StatusComponentModule { } export class StatusComponentModule {}

View File

@@ -1,5 +1,9 @@
import { Component, Input } from '@angular/core' import { Component, Input } from '@angular/core'
import { PrimaryRendering, PrimaryStatus, StatusRendering } from 'src/app/services/pkg-status-rendering.service' import {
PrimaryRendering,
PrimaryStatus,
StatusRendering,
} from 'src/app/services/pkg-status-rendering.service'
@Component({ @Component({
selector: 'status', selector: 'status',
@@ -18,4 +22,3 @@ export class StatusComponent {
@Input() installProgress?: number @Input() installProgress?: number
@Input() sigtermTimeout?: string @Input() sigtermTimeout?: string
} }

View File

@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'
import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { AppConfigPage } from './app-config.page' import { AppConfigPage } from './app-config.page'
import { SharingModule } from 'src/app/modules/sharing.module' import { TextSpinnerComponentModule } from '@start9labs/shared'
import { FormObjectComponentModule } from 'src/app/components/form-object/form-object.component.module' import { FormObjectComponentModule } from 'src/app/components/form-object/form-object.component.module'
@NgModule({ @NgModule({
@@ -12,10 +12,10 @@ import { FormObjectComponentModule } from 'src/app/components/form-object/form-o
CommonModule, CommonModule,
FormsModule, FormsModule,
IonicModule, IonicModule,
SharingModule, TextSpinnerComponentModule,
FormObjectComponentModule, FormObjectComponentModule,
ReactiveFormsModule, ReactiveFormsModule,
], ],
exports: [AppConfigPage], exports: [AppConfigPage],
}) })
export class AppConfigPageModule { } export class AppConfigPageModule {}

View File

@@ -7,7 +7,7 @@ import {
IonicSafeString, IonicSafeString,
} from '@ionic/angular' } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { DependentInfo, isEmptyObject, isObject } from 'src/app/util/misc.util' import { DependentInfo, isEmptyObject, isObject } from '@start9labs/shared'
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component' import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards' import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
import { ConfigSpec } from 'src/app/pkg-config/config-types' import { ConfigSpec } from 'src/app/pkg-config/config-types'

View File

@@ -1,9 +1,13 @@
import { Component, Input } from '@angular/core' import { Component, Input } from '@angular/core'
import { LoadingController, ModalController, IonicSafeString } from '@ionic/angular' import {
LoadingController,
ModalController,
IonicSafeString,
} from '@ionic/angular'
import { BackupInfo, PackageBackupInfo } from 'src/app/services/api/api.types' import { BackupInfo, PackageBackupInfo } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ConfigService } from 'src/app/services/config.service' import { ConfigService } from 'src/app/services/config.service'
import { Emver } from 'src/app/services/emver.service' import { Emver } from '@start9labs/shared'
import { getErrorMessage } from 'src/app/services/error-toast.service' import { getErrorMessage } from 'src/app/services/error-toast.service'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
@@ -26,39 +30,43 @@ export class AppRecoverSelectPage {
hasSelection = false hasSelection = false
error: string | IonicSafeString error: string | IonicSafeString
constructor ( constructor(
private readonly modalCtrl: ModalController, private readonly modalCtrl: ModalController,
private readonly loadingCtrl: LoadingController, private readonly loadingCtrl: LoadingController,
private readonly embassyApi: ApiService, private readonly embassyApi: ApiService,
private readonly config: ConfigService, private readonly config: ConfigService,
private readonly emver: Emver, private readonly emver: Emver,
private readonly patch: PatchDbService, private readonly patch: PatchDbService,
) { } ) {}
ngOnInit () { ngOnInit() {
this.options = Object.keys(this.backupInfo['package-backups']).map(id => { this.options = Object.keys(this.backupInfo['package-backups']).map(id => {
return { return {
...this.backupInfo['package-backups'][id], ...this.backupInfo['package-backups'][id],
id, id,
checked: false, checked: false,
installed: !!this.patch.getData()['package-data'][id], installed: !!this.patch.getData()['package-data'][id],
'newer-eos': this.emver.compare(this.backupInfo['package-backups'][id]['os-version'], this.config.version) === 1, 'newer-eos':
this.emver.compare(
this.backupInfo['package-backups'][id]['os-version'],
this.config.version,
) === 1,
} }
}) })
} }
dismiss () { dismiss() {
this.modalCtrl.dismiss() this.modalCtrl.dismiss()
} }
handleChange () { handleChange() {
this.hasSelection = this.options.some(o => o.checked) this.hasSelection = this.options.some(o => o.checked)
} }
async restore (): Promise<void> { async restore(): Promise<void> {
const ids = this.options const ids = this.options
.filter(option => !!option.checked) .filter(option => !!option.checked)
.map(option => option.id) .map(option => option.id)
const loader = await this.loadingCtrl.create({ const loader = await this.loadingCtrl.create({
spinner: 'lines', spinner: 'lines',

View File

@@ -16,11 +16,9 @@ export class BackupReportPage {
color: 'dark' | 'danger' | 'success' color: 'dark' | 'danger' | 'success'
} }
constructor ( constructor(private readonly modalCtrl: ModalController) {}
private readonly modalCtrl: ModalController,
) { }
ngOnInit () { ngOnInit() {
if (!this.report.server.attempted) { if (!this.report.server.attempted) {
this.system = { this.system = {
result: 'Not Attempted', result: 'Not Attempted',
@@ -42,7 +40,7 @@ export class BackupReportPage {
} }
} }
async dismiss () { async dismiss() {
return this.modalCtrl.dismiss(true) return this.modalCtrl.dismiss(true)
} }
} }

View File

@@ -1,6 +1,6 @@
import { Component, Input } from '@angular/core' import { Component, Input } from '@angular/core'
import { ModalController } from '@ionic/angular' import { ModalController } from '@ionic/angular'
import { ValueSpecListOf } from '../../pkg-config/config-types' import { ValueSpecListOf } from 'src/app/pkg-config/config-types'
@Component({ @Component({
selector: 'enum-list', selector: 'enum-list',
@@ -11,37 +11,37 @@ export class EnumListPage {
@Input() key: string @Input() key: string
@Input() spec: ValueSpecListOf<'enum'> @Input() spec: ValueSpecListOf<'enum'>
@Input() current: string[] @Input() current: string[]
options: { [option: string]: boolean } = { } options: { [option: string]: boolean } = {}
selectAll = true selectAll = true
constructor ( constructor(private readonly modalCtrl: ModalController) {}
private readonly modalCtrl: ModalController,
) { }
ngOnInit () { ngOnInit() {
for (let val of this.spec.spec.values) { for (let val of this.spec.spec.values) {
this.options[val] = this.current.includes(val) this.options[val] = this.current.includes(val)
} }
} }
dismiss () { dismiss() {
this.modalCtrl.dismiss() this.modalCtrl.dismiss()
} }
save () { save() {
this.modalCtrl.dismiss(Object.keys(this.options).filter(key => this.options[key])) this.modalCtrl.dismiss(
Object.keys(this.options).filter(key => this.options[key]),
)
} }
toggleSelectAll () { toggleSelectAll() {
Object.keys(this.options).forEach(k => this.options[k] = this.selectAll) Object.keys(this.options).forEach(k => (this.options[k] = this.selectAll))
this.selectAll = !this.selectAll this.selectAll = !this.selectAll
} }
toggleSelected (key: string) { toggleSelected(key: string) {
this.options[key] = !this.options[key] this.options[key] = !this.options[key]
} }
asIsOrder () { asIsOrder() {
return 0 return 0
} }
} }

View File

@@ -1,7 +1,10 @@
import { Component, Input } from '@angular/core' import { Component, Input } from '@angular/core'
import { FormGroup } from '@angular/forms' import { FormGroup } from '@angular/forms'
import { ModalController } from '@ionic/angular' import { ModalController } from '@ionic/angular'
import { convertValuesRecursive, FormService } from 'src/app/services/form.service' import {
convertValuesRecursive,
FormService,
} from 'src/app/services/form.service'
import { ConfigSpec } from 'src/app/pkg-config/config-types' import { ConfigSpec } from 'src/app/pkg-config/config-types'
export interface ActionButton { export interface ActionButton {
@@ -19,16 +22,16 @@ export class GenericFormPage {
@Input() title: string @Input() title: string
@Input() spec: ConfigSpec @Input() spec: ConfigSpec
@Input() buttons: ActionButton[] @Input() buttons: ActionButton[]
@Input() initialValue: object = { } @Input() initialValue: object = {}
submitBtn: ActionButton submitBtn: ActionButton
formGroup: FormGroup formGroup: FormGroup
constructor ( constructor(
private readonly modalCtrl: ModalController, private readonly modalCtrl: ModalController,
private readonly formService: FormService, private readonly formService: FormService,
) { } ) {}
ngOnInit () { ngOnInit() {
this.formGroup = this.formService.createForm(this.spec, this.initialValue) this.formGroup = this.formService.createForm(this.spec, this.initialValue)
this.submitBtn = this.buttons.find(btn => btn.isSubmit) || { this.submitBtn = this.buttons.find(btn => btn.isSubmit) || {
text: '', text: '',
@@ -36,16 +39,18 @@ export class GenericFormPage {
} }
} }
async dismiss (): Promise<void> { async dismiss(): Promise<void> {
this.modalCtrl.dismiss() this.modalCtrl.dismiss()
} }
async handleClick (handler: ActionButton['handler']): Promise<void> { async handleClick(handler: ActionButton['handler']): Promise<void> {
convertValuesRecursive(this.spec, this.formGroup) convertValuesRecursive(this.spec, this.formGroup)
if (this.formGroup.invalid) { if (this.formGroup.invalid) {
this.formGroup.markAllAsTouched() this.formGroup.markAllAsTouched()
document.getElementsByClassName('validation-error')[0].parentElement.parentElement.scrollIntoView({ behavior: 'smooth' }) document
.getElementsByClassName('validation-error')[0]
.parentElement.parentElement.scrollIntoView({ behavior: 'smooth' })
return return
} }

View File

@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'
import { GenericInputComponent } from './generic-input.component' import { GenericInputComponent } from './generic-input.component'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { RouterModule } from '@angular/router' import { RouterModule } from '@angular/router'
import { SharingModule } from 'src/app/modules/sharing.module' import { SharedPipesModule } from '@start9labs/shared'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
@NgModule({ @NgModule({
@@ -13,8 +13,8 @@ import { FormsModule } from '@angular/forms'
IonicModule, IonicModule,
FormsModule, FormsModule,
RouterModule.forChild([]), RouterModule.forChild([]),
SharingModule, SharedPipesModule,
], ],
exports: [GenericInputComponent], exports: [GenericInputComponent],
}) })
export class GenericInputComponentModule { } export class GenericInputComponentModule {}

View File

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

View File

@@ -2,17 +2,12 @@ import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { OSWelcomePage } from './os-welcome.page' import { OSWelcomePage } from './os-welcome.page'
import { SharingModule } from 'src/app/modules/sharing.module' import { SharedPipesModule } from '@start9labs/shared'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
@NgModule({ @NgModule({
declarations: [OSWelcomePage], declarations: [OSWelcomePage],
imports: [ imports: [CommonModule, IonicModule, FormsModule, SharedPipesModule],
CommonModule,
IonicModule,
FormsModule,
SharingModule,
],
exports: [OSWelcomePage], exports: [OSWelcomePage],
}) })
export class OSWelcomePageModule { } export class OSWelcomePageModule {}

View File

@@ -1,72 +0,0 @@
import { NgModule } from '@angular/core'
import {
EmverComparesPipe,
EmverSatisfiesPipe,
EmverDisplayPipe,
} from '../pipes/emver.pipe'
import { IncludesPipe } from '../pipes/includes.pipe'
import { TypeofPipe } from '../pipes/typeof.pipe'
import { MarkdownPipe } from '../pipes/markdown.pipe'
import {
TruncateCenterPipe,
TruncateEndPipe,
TruncateTailPipe,
} from '../pipes/truncate.pipe'
import { MaskPipe } from '../pipes/mask.pipe'
import { HasUiPipe, LaunchablePipe, SanitizePipe } from '../pipes/ui.pipe'
import { EmptyPipe } from '../pipes/empty.pipe'
import { NotificationColorPipe } from '../pipes/notification-color.pipe'
import { InstallState } from '../pipes/install-state.pipe'
import { TextSpinnerComponentModule } from '../components/text-spinner/text-spinner.component.module'
import { ConvertBytesPipe } from '../pipes/convert-bytes.pipe'
import { DurationToSecondsPipe } from '../pipes/unit-conversion.pipe'
import { InstallProgressPipe } from '../pipes/install-progress.pipe'
@NgModule({
declarations: [
EmverComparesPipe,
EmverSatisfiesPipe,
TypeofPipe,
IncludesPipe,
InstallProgressPipe,
InstallState,
MarkdownPipe,
TruncateCenterPipe,
TruncateEndPipe,
TruncateTailPipe,
MaskPipe,
EmverDisplayPipe,
HasUiPipe,
LaunchablePipe,
EmptyPipe,
NotificationColorPipe,
ConvertBytesPipe,
DurationToSecondsPipe,
SanitizePipe,
],
imports: [TextSpinnerComponentModule],
exports: [
EmverComparesPipe,
EmverSatisfiesPipe,
TypeofPipe,
IncludesPipe,
MarkdownPipe,
TruncateEndPipe,
TruncateCenterPipe,
TruncateTailPipe,
MaskPipe,
EmverDisplayPipe,
HasUiPipe,
InstallProgressPipe,
InstallState,
LaunchablePipe,
EmptyPipe,
NotificationColorPipe,
ConvertBytesPipe,
DurationToSecondsPipe,
// components
TextSpinnerComponentModule,
SanitizePipe,
],
})
export class SharingModule {}

View File

@@ -4,7 +4,7 @@ import { Routes, RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { AppActionsPage, AppActionsItemComponent } from './app-actions.page' import { AppActionsPage, AppActionsItemComponent } from './app-actions.page'
import { QRComponentModule } from 'src/app/components/qr/qr.component.module' import { QRComponentModule } from 'src/app/components/qr/qr.component.module'
import { SharingModule } from 'src/app/modules/sharing.module' import { SharedPipesModule } from '@start9labs/shared'
import { GenericFormPageModule } from 'src/app/modals/generic-form/generic-form.module' import { GenericFormPageModule } from 'src/app/modals/generic-form/generic-form.module'
import { ActionSuccessPageModule } from 'src/app/modals/action-success/action-success.module' import { ActionSuccessPageModule } from 'src/app/modals/action-success/action-success.module'
@@ -21,13 +21,10 @@ const routes: Routes = [
IonicModule, IonicModule,
RouterModule.forChild(routes), RouterModule.forChild(routes),
QRComponentModule, QRComponentModule,
SharingModule, SharedPipesModule,
GenericFormPageModule, GenericFormPageModule,
ActionSuccessPageModule, ActionSuccessPageModule,
], ],
declarations: [ declarations: [AppActionsPage, AppActionsItemComponent],
AppActionsPage,
AppActionsItemComponent,
],
}) })
export class AppActionsPageModule { } export class AppActionsPageModule {}

View File

@@ -19,7 +19,7 @@ import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
import { Subscription } from 'rxjs' import { Subscription } from 'rxjs'
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page' import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
import { ErrorToastService } from 'src/app/services/error-toast.service' import { ErrorToastService } from 'src/app/services/error-toast.service'
import { isEmptyObject } from 'src/app/util/misc.util' import { isEmptyObject } from '@start9labs/shared'
import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page' import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page'
@Component({ @Component({

View File

@@ -2,8 +2,11 @@ import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { Routes, RouterModule } from '@angular/router' import { Routes, RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { AppInterfacesItemComponent, AppInterfacesPage } from './app-interfaces.page' import {
import { SharingModule } from 'src/app/modules/sharing.module' AppInterfacesItemComponent,
AppInterfacesPage,
} from './app-interfaces.page'
import { SharedPipesModule } from '@start9labs/shared'
const routes: Routes = [ const routes: Routes = [
{ {
@@ -17,11 +20,8 @@ const routes: Routes = [
CommonModule, CommonModule,
IonicModule, IonicModule,
RouterModule.forChild(routes), RouterModule.forChild(routes),
SharingModule, SharedPipesModule,
],
declarations: [
AppInterfacesPage,
AppInterfacesItemComponent,
], ],
declarations: [AppInterfacesPage, AppInterfacesItemComponent],
}) })
export class AppInterfacesPageModule { } export class AppInterfacesPageModule {}

View File

@@ -2,7 +2,10 @@ import { Component, Input, ViewChild } from '@angular/core'
import { ActivatedRoute } from '@angular/router' import { ActivatedRoute } from '@angular/router'
import { IonContent, ToastController } from '@ionic/angular' import { IonContent, ToastController } from '@ionic/angular'
import { getUiInterfaceKey } from 'src/app/services/config.service' import { getUiInterfaceKey } from 'src/app/services/config.service'
import { InstalledPackageDataEntry, InterfaceDef } from 'src/app/services/patch-db/data-model' import {
InstalledPackageDataEntry,
InterfaceDef,
} from 'src/app/services/patch-db/data-model'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { copyToClipboard } from 'src/app/util/web.util' import { copyToClipboard } from 'src/app/util/web.util'
@@ -22,12 +25,12 @@ export class AppInterfacesPage {
other: LocalInterface[] = [] other: LocalInterface[] = []
pkgId: string pkgId: string
constructor ( constructor(
private readonly route: ActivatedRoute, private readonly route: ActivatedRoute,
public readonly patch: PatchDbService, public readonly patch: PatchDbService,
) { } ) {}
ngOnInit () { ngOnInit() {
this.pkgId = this.route.snapshot.paramMap.get('pkgId') this.pkgId = this.route.snapshot.paramMap.get('pkgId')
const pkg = this.patch.getData()['package-data'][this.pkgId] const pkg = this.patch.getData()['package-data'][this.pkgId]
const interfaces = pkg.manifest.interfaces const interfaces = pkg.manifest.interfaces
@@ -40,8 +43,12 @@ export class AppInterfacesPage {
this.ui = { this.ui = {
def: interfaces[uiKey], def: interfaces[uiKey],
addresses: { addresses: {
'lan-address': uiAddresses['lan-address'] ? 'https://' + uiAddresses['lan-address'] : null, 'lan-address': uiAddresses['lan-address']
'tor-address': uiAddresses['tor-address'] ? 'http://' + uiAddresses['tor-address'] : null, ? 'https://' + uiAddresses['lan-address']
: null,
'tor-address': uiAddresses['tor-address']
? 'http://' + uiAddresses['tor-address']
: null,
}, },
} }
} }
@@ -53,18 +60,22 @@ export class AppInterfacesPage {
return { return {
def: interfaces[key], def: interfaces[key],
addresses: { addresses: {
'lan-address': addresses['lan-address'] ? 'https://' + addresses['lan-address'] : null, 'lan-address': addresses['lan-address']
'tor-address': addresses['tor-address'] ? 'http://' + addresses['tor-address'] : null, ? 'https://' + addresses['lan-address']
: null,
'tor-address': addresses['tor-address']
? 'http://' + addresses['tor-address']
: null,
}, },
} }
}) })
} }
ngAfterViewInit () { ngAfterViewInit() {
this.content.scrollToPoint(undefined, 1) this.content.scrollToPoint(undefined, 1)
} }
asIsOrder () { asIsOrder() {
return 0 return 0
} }
} }
@@ -77,18 +88,17 @@ export class AppInterfacesPage {
export class AppInterfacesItemComponent { export class AppInterfacesItemComponent {
@Input() interface: LocalInterface @Input() interface: LocalInterface
constructor ( constructor(private readonly toastCtrl: ToastController) {}
private readonly toastCtrl: ToastController,
) { }
launch (url: string): void { launch(url: string): void {
window.open(url, '_blank', 'noreferrer') window.open(url, '_blank', 'noreferrer')
} }
async copy (address: string): Promise<void> { async copy(address: string): Promise<void> {
let message = '' let message = ''
await copyToClipboard(address || '') await copyToClipboard(address || '').then(success => {
.then(success => { message = success ? 'copied to clipboard!' : 'failed to copy' }) message = success ? 'copied to clipboard!' : 'failed to copy'
})
const toast = await this.toastCtrl.create({ const toast = await this.toastCtrl.create({
header: message, header: message,

View File

@@ -1,12 +1,6 @@
import { import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
ChangeDetectionStrategy,
Component,
Inject,
Input,
} from '@angular/core'
import { import {
PackageMainStatus, PackageMainStatus,
PackageDataEntry,
Manifest, Manifest,
} from 'src/app/services/patch-db/data-model' } from 'src/app/services/patch-db/data-model'
import { PkgInfo } from 'src/app/util/get-package-info' import { PkgInfo } from 'src/app/util/get-package-info'

View File

@@ -34,16 +34,16 @@ export class AppListReorderComponent {
readonly connectionFailure$ = this.connectionService readonly connectionFailure$ = this.connectionService
.watchFailure$() .watchFailure$()
.pipe(map((failure) => failure !== ConnectionFailure.None)) .pipe(map(failure => failure !== ConnectionFailure.None))
constructor (private readonly connectionService: ConnectionService) { } constructor(private readonly connectionService: ConnectionService) {}
toggle () { toggle() {
this.reordering = !this.reordering this.reordering = !this.reordering
this.reorderingChange.emit(this.reordering) this.reorderingChange.emit(this.reordering)
} }
reorder ({ detail }: CustomEvent<ItemReorderEventDetail>): void { reorder({ detail }: CustomEvent<ItemReorderEventDetail>): void {
this.pkgs = detail.complete([...this.pkgs]) this.pkgs = detail.complete([...this.pkgs])
this.pkgsChange.emit(this.pkgs) this.pkgsChange.emit(this.pkgs)
} }

View File

@@ -3,9 +3,14 @@ import { CommonModule } from '@angular/common'
import { Routes, RouterModule } from '@angular/router' import { Routes, RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { AppListPage } from './app-list.page' import { AppListPage } from './app-list.page'
import { StatusComponentModule } from 'src/app/components/status/status.component.module' import {
import { SharingModule } from 'src/app/modules/sharing.module' EmverPipesModule,
TextSpinnerComponentModule,
} from '@start9labs/shared'
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.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 { LaunchablePipeModule } from 'src/app/pipes/launchable/launchable.module'
import { UiPipeModule } from 'src/app/pipes/ui/ui.module'
import { AppListIconComponent } from './app-list-icon/app-list-icon.component' import { AppListIconComponent } from './app-list-icon/app-list-icon.component'
import { AppListEmptyComponent } from './app-list-empty/app-list-empty.component' import { AppListEmptyComponent } from './app-list-empty/app-list-empty.component'
import { AppListPkgComponent } from './app-list-pkg/app-list-pkg.component' import { AppListPkgComponent } from './app-list-pkg/app-list-pkg.component'
@@ -24,7 +29,10 @@ const routes: Routes = [
imports: [ imports: [
CommonModule, CommonModule,
StatusComponentModule, StatusComponentModule,
SharingModule, EmverPipesModule,
TextSpinnerComponentModule,
LaunchablePipeModule,
UiPipeModule,
IonicModule, IonicModule,
RouterModule.forChild(routes), RouterModule.forChild(routes),
BadgeMenuComponentModule, BadgeMenuComponentModule,
@@ -39,4 +47,4 @@ const routes: Routes = [
PackageInfoPipe, PackageInfoPipe,
], ],
}) })
export class AppListPageModule { } export class AppListPageModule {}

View File

@@ -3,10 +3,9 @@ import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model' import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
import { filter, map, switchMapTo, take, takeUntil, tap } from 'rxjs/operators' import { filter, map, switchMapTo, take, takeUntil, tap } from 'rxjs/operators'
import { isEmptyObject, exists } from 'src/app/util/misc.util' import { isEmptyObject, exists, DestroyService } from '@start9labs/shared'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { parseDataModel, RecoveredInfo } from 'src/app/util/parse-data-model' import { parseDataModel, RecoveredInfo } from 'src/app/util/parse-data-model'
import { DestroyService } from 'src/app/services/destroy.service'
@Component({ @Component({
selector: 'app-list', selector: 'app-list',
@@ -20,21 +19,21 @@ export class AppListPage {
order: readonly string[] = [] order: readonly string[] = []
reordering = false reordering = false
constructor ( constructor(
private readonly api: ApiService, private readonly api: ApiService,
private readonly destroy$: DestroyService, private readonly destroy$: DestroyService,
public readonly patch: PatchDbService, public readonly patch: PatchDbService,
) { } ) {}
get empty (): boolean { get empty(): boolean {
return !this.pkgs.length && isEmptyObject(this.recoveredPkgs) return !this.pkgs.length && isEmptyObject(this.recoveredPkgs)
} }
ngOnInit () { ngOnInit() {
this.patch this.patch
.watch$() .watch$()
.pipe( .pipe(
filter((data) => exists(data) && !isEmptyObject(data)), filter(data => exists(data) && !isEmptyObject(data)),
take(1), take(1),
map(parseDataModel), map(parseDataModel),
tap(({ order, pkgs, recoveredPkgs }) => { tap(({ order, pkgs, recoveredPkgs }) => {
@@ -53,7 +52,7 @@ export class AppListPage {
.subscribe() .subscribe()
} }
onReordering (reordering: boolean): void { onReordering(reordering: boolean): void {
if (!reordering) { if (!reordering) {
this.setOrder() this.setOrder()
} }
@@ -61,35 +60,33 @@ export class AppListPage {
this.reordering = reordering this.reordering = reordering
} }
deleteRecovered (rec: RecoveredInfo): void { deleteRecovered(rec: RecoveredInfo): void {
this.recoveredPkgs = this.recoveredPkgs.filter((item) => item !== rec) this.recoveredPkgs = this.recoveredPkgs.filter(item => item !== rec)
} }
private watchNewlyRecovered (): Observable<unknown> { private watchNewlyRecovered(): Observable<unknown> {
return this.patch.watch$('package-data').pipe( return this.patch.watch$('package-data').pipe(
filter((pkgs) => !!pkgs && Object.keys(pkgs).length !== this.pkgs.length), filter(pkgs => !!pkgs && Object.keys(pkgs).length !== this.pkgs.length),
tap((pkgs) => { tap(pkgs => {
const ids = Object.keys(pkgs) const ids = Object.keys(pkgs)
const newIds = ids.filter( const newIds = ids.filter(
(id) => !this.pkgs.find((pkg) => pkg.manifest.id === id), id => !this.pkgs.find(pkg => pkg.manifest.id === id),
) )
// remove uninstalled // remove uninstalled
const filtered = this.pkgs.filter((pkg) => const filtered = this.pkgs.filter(pkg => ids.includes(pkg.manifest.id))
ids.includes(pkg.manifest.id),
)
// add new entry to beginning of array // add new entry to beginning of array
const added = newIds.map((id) => pkgs[id]) const added = newIds.map(id => pkgs[id])
this.pkgs = [...added, ...filtered] this.pkgs = [...added, ...filtered]
this.recoveredPkgs = this.recoveredPkgs.filter((rec) => !pkgs[rec.id]) this.recoveredPkgs = this.recoveredPkgs.filter(rec => !pkgs[rec.id])
}), }),
) )
} }
private setOrder (): void { private setOrder(): void {
this.order = this.pkgs.map((pkg) => pkg.manifest.id) this.order = this.pkgs.map(pkg => pkg.manifest.id)
this.api.setDbValue({ pointer: '/pkg-order', value: this.order }) this.api.setDbValue({ pointer: '/pkg-order', value: this.order })
} }
} }

View File

@@ -1,21 +1,21 @@
import { Pipe, PipeTransform } from "@angular/core"; import { Pipe, PipeTransform } from '@angular/core'
import { Observable } from "rxjs"; import { Observable } from 'rxjs'
import { filter, map, startWith } from "rxjs/operators"; import { filter, map, startWith } from 'rxjs/operators'
import { PackageDataEntry } from "../../../services/patch-db/data-model"; import { PackageDataEntry } from '../../../services/patch-db/data-model'
import { getPackageInfo, PkgInfo } from "../../../util/get-package-info"; import { getPackageInfo, PkgInfo } from '../../../util/get-package-info'
import { PatchDbService } from "../../../services/patch-db/patch-db.service"; import { PatchDbService } from '../../../services/patch-db/patch-db.service'
@Pipe({ @Pipe({
name: "packageInfo", name: 'packageInfo',
}) })
export class PackageInfoPipe implements PipeTransform { export class PackageInfoPipe implements PipeTransform {
constructor(private readonly patch: PatchDbService) {} constructor(private readonly patch: PatchDbService) {}
transform(pkg: PackageDataEntry): Observable<PkgInfo> { transform(pkg: PackageDataEntry): Observable<PkgInfo> {
return this.patch.watch$("package-data", pkg.manifest.id).pipe( return this.patch.watch$('package-data', pkg.manifest.id).pipe(
filter((v) => !!v), filter(v => !!v),
map(getPackageInfo), map(getPackageInfo),
startWith(getPackageInfo(pkg)) startWith(getPackageInfo(pkg)),
); )
} }
} }

View File

@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'
import { Routes, RouterModule } from '@angular/router' import { Routes, RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { AppLogsPage } from './app-logs.page' import { AppLogsPage } from './app-logs.page'
import { SharingModule } from 'src/app/modules/sharing.module' import { SharedPipesModule } from '@start9labs/shared'
import { LogsPageModule } from 'src/app/components/logs/logs.module' import { LogsPageModule } from 'src/app/components/logs/logs.module'
const routes: Routes = [ const routes: Routes = [
@@ -18,9 +18,9 @@ const routes: Routes = [
CommonModule, CommonModule,
IonicModule, IonicModule,
RouterModule.forChild(routes), RouterModule.forChild(routes),
SharingModule, SharedPipesModule,
LogsPageModule, LogsPageModule,
], ],
declarations: [AppLogsPage], declarations: [AppLogsPage],
}) })
export class AppLogsPageModule { } export class AppLogsPageModule {}

View File

@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'
import { Routes, RouterModule } from '@angular/router' import { Routes, RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { AppMetricsPage } from './app-metrics.page' import { AppMetricsPage } from './app-metrics.page'
import { SharingModule } from 'src/app/modules/sharing.module' import { SharedPipesModule } from '@start9labs/shared'
import { SkeletonListComponentModule } from 'src/app/components/skeleton-list/skeleton-list.component.module' import { SkeletonListComponentModule } from 'src/app/components/skeleton-list/skeleton-list.component.module'
const routes: Routes = [ const routes: Routes = [
@@ -18,9 +18,9 @@ const routes: Routes = [
CommonModule, CommonModule,
IonicModule, IonicModule,
RouterModule.forChild(routes), RouterModule.forChild(routes),
SharingModule, SharedPipesModule,
SkeletonListComponentModule, SkeletonListComponentModule,
], ],
declarations: [AppMetricsPage], declarations: [AppMetricsPage],
}) })
export class AppMetricsPageModule { } export class AppMetricsPageModule {}

View File

@@ -6,7 +6,7 @@ import { Metric } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ErrorToastService } from 'src/app/services/error-toast.service' import { ErrorToastService } from 'src/app/services/error-toast.service'
import { MainStatus } from 'src/app/services/patch-db/data-model' import { MainStatus } from 'src/app/services/patch-db/data-model'
import { pauseFor } from 'src/app/util/misc.util' import { pauseFor } from '@start9labs/shared'
@Component({ @Component({
selector: 'app-metrics', selector: 'app-metrics',
@@ -23,26 +23,26 @@ export class AppMetricsPage {
@ViewChild(IonContent) content: IonContent @ViewChild(IonContent) content: IonContent
constructor ( constructor(
private readonly route: ActivatedRoute, private readonly route: ActivatedRoute,
private readonly errToast: ErrorToastService, private readonly errToast: ErrorToastService,
private readonly embassyApi: ApiService, private readonly embassyApi: ApiService,
) { } ) {}
ngOnInit () { ngOnInit() {
this.pkgId = this.route.snapshot.paramMap.get('pkgId') this.pkgId = this.route.snapshot.paramMap.get('pkgId')
this.startDaemon() this.startDaemon()
} }
ngAfterViewInit () { ngAfterViewInit() {
this.content.scrollToPoint(undefined, 1) this.content.scrollToPoint(undefined, 1)
} }
ngOnDestroy () { ngOnDestroy() {
this.stopDaemon() this.stopDaemon()
} }
async startDaemon (): Promise<void> { async startDaemon(): Promise<void> {
this.going = true this.going = true
while (this.going) { while (this.going) {
const startTime = Date.now() const startTime = Date.now()
@@ -51,13 +51,13 @@ export class AppMetricsPage {
} }
} }
stopDaemon () { stopDaemon() {
this.going = false this.going = false
} }
async getMetrics (): Promise<void> { async getMetrics(): Promise<void> {
try { try {
this.metrics = await this.embassyApi.getPkgMetrics({ id: this.pkgId}) this.metrics = await this.embassyApi.getPkgMetrics({ id: this.pkgId })
} catch (e) { } catch (e) {
this.errToast.present(e) this.errToast.present(e)
this.stopDaemon() this.stopDaemon()
@@ -66,7 +66,7 @@ export class AppMetricsPage {
} }
} }
asIsOrder (a: any, b: any) { asIsOrder(a: any, b: any) {
return 0 return 0
} }
} }

View File

@@ -4,7 +4,11 @@ import { Routes, RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { AppPropertiesPage } from './app-properties.page' import { AppPropertiesPage } from './app-properties.page'
import { QRComponentModule } from 'src/app/components/qr/qr.component.module' import { QRComponentModule } from 'src/app/components/qr/qr.component.module'
import { SharingModule } from 'src/app/modules/sharing.module' import { MaskPipeModule } from 'src/app/pipes/mask/mask.module'
import {
SharedPipesModule,
TextSpinnerComponentModule,
} from '@start9labs/shared'
const routes: Routes = [ const routes: Routes = [
{ {
@@ -19,8 +23,10 @@ const routes: Routes = [
IonicModule, IonicModule,
RouterModule.forChild(routes), RouterModule.forChild(routes),
QRComponentModule, QRComponentModule,
SharingModule, SharedPipesModule,
TextSpinnerComponentModule,
MaskPipeModule,
], ],
declarations: [AppPropertiesPage], declarations: [AppPropertiesPage],
}) })
export class AppPropertiesPageModule { } export class AppPropertiesPageModule {}

View File

@@ -3,7 +3,13 @@ import { ActivatedRoute } from '@angular/router'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { Subscription } from 'rxjs' import { Subscription } from 'rxjs'
import { copyToClipboard } from 'src/app/util/web.util' import { copyToClipboard } from 'src/app/util/web.util'
import { AlertController, IonContent, ModalController, NavController, ToastController } from '@ionic/angular' import {
AlertController,
IonContent,
ModalController,
NavController,
ToastController,
} from '@ionic/angular'
import { PackageProperties } from 'src/app/util/properties.util' import { PackageProperties } from 'src/app/util/properties.util'
import { QRComponent } from 'src/app/components/qr/qr.component' import { QRComponent } from 'src/app/components/qr/qr.component'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
@@ -22,13 +28,13 @@ export class AppPropertiesPage {
pointer: string pointer: string
properties: PackageProperties properties: PackageProperties
node: PackageProperties node: PackageProperties
unmasked: { [key: string]: boolean } = { } unmasked: { [key: string]: boolean } = {}
running = true running = true
@ViewChild(IonContent) content: IonContent @ViewChild(IonContent) content: IonContent
subs: Subscription[] = [] subs: Subscription[] = []
constructor ( constructor(
private readonly route: ActivatedRoute, private readonly route: ActivatedRoute,
private readonly embassyApi: ApiService, private readonly embassyApi: ApiService,
private readonly errToast: ErrorToastService, private readonly errToast: ErrorToastService,
@@ -37,40 +43,50 @@ export class AppPropertiesPage {
private readonly modalCtrl: ModalController, private readonly modalCtrl: ModalController,
private readonly navCtrl: NavController, private readonly navCtrl: NavController,
private readonly patch: PatchDbService, private readonly patch: PatchDbService,
) { } ) {}
async ngOnInit () { async ngOnInit() {
this.pkgId = this.route.snapshot.paramMap.get('pkgId') this.pkgId = this.route.snapshot.paramMap.get('pkgId')
await this.getProperties() await this.getProperties()
this.subs = [ this.subs = [
this.route.queryParams this.route.queryParams.subscribe(queryParams => {
.subscribe(queryParams => {
if (queryParams['pointer'] === this.pointer) return if (queryParams['pointer'] === this.pointer) return
this.pointer = queryParams['pointer'] this.pointer = queryParams['pointer']
this.node = getValueByPointer(this.properties, this.pointer || '') this.node = getValueByPointer(this.properties, this.pointer || '')
}), }),
this.patch.watch$('package-data', this.pkgId, 'installed', 'status', 'main', 'status') this.patch
.subscribe(status => { .watch$(
this.running = status === PackageMainStatus.Running 'package-data',
}), this.pkgId,
'installed',
'status',
'main',
'status',
)
.subscribe(status => {
this.running = status === PackageMainStatus.Running
}),
] ]
} }
ngAfterViewInit () { ngAfterViewInit() {
this.content.scrollToPoint(undefined, 1) this.content.scrollToPoint(undefined, 1)
} }
ngOnDestroy () { ngOnDestroy() {
this.subs.forEach(sub => sub.unsubscribe()) this.subs.forEach(sub => sub.unsubscribe())
} }
async refresh () { async refresh() {
await this.getProperties() await this.getProperties()
} }
async presentDescription (property: { key: string, value: PackageProperties[''] }, e: Event) { async presentDescription(
property: { key: string; value: PackageProperties[''] },
e: Event,
) {
e.stopPropagation() e.stopPropagation()
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
@@ -80,7 +96,7 @@ export class AppPropertiesPage {
await alert.present() await alert.present()
} }
async goToNested (key: string): Promise<any> { async goToNested(key: string): Promise<any> {
this.navCtrl.navigateForward(`/services/${this.pkgId}/properties`, { this.navCtrl.navigateForward(`/services/${this.pkgId}/properties`, {
queryParams: { queryParams: {
pointer: `${this.pointer || ''}/${key}/value`, pointer: `${this.pointer || ''}/${key}/value`,
@@ -88,9 +104,11 @@ export class AppPropertiesPage {
}) })
} }
async copy (text: string): Promise<void> { async copy(text: string): Promise<void> {
let message = '' let message = ''
await copyToClipboard(text).then(success => { message = success ? 'copied to clipboard!' : 'failed to copy'}) await copyToClipboard(text).then(success => {
message = success ? 'copied to clipboard!' : 'failed to copy'
})
const toast = await this.toastCtrl.create({ const toast = await this.toastCtrl.create({
header: message, header: message,
@@ -100,7 +118,7 @@ export class AppPropertiesPage {
await toast.present() await toast.present()
} }
async showQR (text: string): Promise<void> { async showQR(text: string): Promise<void> {
const modal = await this.modalCtrl.create({ const modal = await this.modalCtrl.create({
component: QRComponent, component: QRComponent,
componentProps: { componentProps: {
@@ -111,14 +129,16 @@ export class AppPropertiesPage {
await modal.present() await modal.present()
} }
toggleMask (key: string) { toggleMask(key: string) {
this.unmasked[key] = !this.unmasked[key] this.unmasked[key] = !this.unmasked[key]
} }
private async getProperties (): Promise<void> { private async getProperties(): Promise<void> {
this.loading = true this.loading = true
try { try {
this.properties = await this.embassyApi.getPackageProperties({ id: this.pkgId }) this.properties = await this.embassyApi.getPackageProperties({
id: this.pkgId,
})
this.node = getValueByPointer(this.properties, this.pointer || '') this.node = getValueByPointer(this.properties, this.pointer || '')
} catch (e) { } catch (e) {
this.errToast.present(e) this.errToast.present(e)
@@ -127,7 +147,7 @@ export class AppPropertiesPage {
} }
} }
asIsOrder (a: any, b: any) { asIsOrder(a: any, b: any) {
return 0 return 0
} }
} }

View File

@@ -3,10 +3,12 @@ import { CommonModule } from '@angular/common'
import { Routes, RouterModule } from '@angular/router' import { Routes, RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { AppShowPage } from './app-show.page' import { AppShowPage } from './app-show.page'
import { StatusComponentModule } from 'src/app/components/status/status.component.module' import { EmverPipesModule } from '@start9labs/shared'
import { SharingModule } from 'src/app/modules/sharing.module'
import { InstallWizardComponentModule } from 'src/app/components/install-wizard/install-wizard.component.module' import { InstallWizardComponentModule } from 'src/app/components/install-wizard/install-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 { AppConfigPageModule } from 'src/app/modals/app-config/app-config.module'
import { LaunchablePipeModule } from 'src/app/pipes/launchable/launchable.module'
import { UiPipeModule } from 'src/app/pipes/ui/ui.module'
import { AppShowHeaderComponent } from './components/app-show-header/app-show-header.component' import { AppShowHeaderComponent } from './components/app-show-header/app-show-header.component'
import { AppShowProgressComponent } from './components/app-show-progress/app-show-progress.component' import { AppShowProgressComponent } from './components/app-show-progress/app-show-progress.component'
import { AppShowStatusComponent } from './components/app-show-status/app-show-status.component' import { AppShowStatusComponent } from './components/app-show-status/app-show-status.component'
@@ -18,6 +20,7 @@ import { ToHealthChecksPipe } from './pipes/to-health-checks.pipe'
import { ToButtonsPipe } from './pipes/to-buttons.pipe' import { ToButtonsPipe } from './pipes/to-buttons.pipe'
import { ToDependenciesPipe } from './pipes/to-dependencies.pipe' import { ToDependenciesPipe } from './pipes/to-dependencies.pipe'
import { ToStatusPipe } from './pipes/to-status.pipe' import { ToStatusPipe } from './pipes/to-status.pipe'
import { InstallStatePipe } from './pipes/install-state.pipe'
const routes: Routes = [ const routes: Routes = [
{ {
@@ -30,6 +33,7 @@ const routes: Routes = [
declarations: [ declarations: [
AppShowPage, AppShowPage,
HealthColorPipe, HealthColorPipe,
InstallStatePipe,
ToHealthChecksPipe, ToHealthChecksPipe,
ToButtonsPipe, ToButtonsPipe,
ToDependenciesPipe, ToDependenciesPipe,
@@ -48,7 +52,9 @@ const routes: Routes = [
RouterModule.forChild(routes), RouterModule.forChild(routes),
InstallWizardComponentModule, InstallWizardComponentModule,
AppConfigPageModule, AppConfigPageModule,
SharingModule, EmverPipesModule,
LaunchablePipeModule,
UiPipeModule,
], ],
}) })
export class AppShowPageModule {} export class AppShowPageModule {}

View File

@@ -1,10 +1,8 @@
import { ChangeDetectionStrategy, Component } from '@angular/core' import { ChangeDetectionStrategy, Component } from '@angular/core'
import { NavController } from '@ionic/angular' import { NavController } from '@ionic/angular'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
PackageDataEntry, import { PackageState } from '@start9labs/shared'
PackageState,
} from 'src/app/services/patch-db/data-model'
import { import {
PackageStatus, PackageStatus,
PrimaryStatus, PrimaryStatus,
@@ -70,4 +68,3 @@ export class AppShowPage {
return STATES.includes(state) return STATES.includes(state)
} }
} }

View File

@@ -1,9 +1,6 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
InstallProgress, import { InstallProgress, ProgressData } from '@start9labs/shared'
PackageDataEntry,
} from 'src/app/services/patch-db/data-model'
import { ProgressData } from 'src/app/util/package-loading-progress'
@Component({ @Component({
selector: 'app-show-progress', selector: 'app-show-progress',

View File

@@ -1,17 +1,16 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { UiLauncherService } from 'src/app/services/ui-launcher.service' import { UiLauncherService } from 'src/app/services/ui-launcher.service'
import {
InterfaceDef,
PackageDataEntry,
PackageState,
Status,
} from 'src/app/services/patch-db/data-model'
import { import {
PackageStatus, PackageStatus,
PrimaryRendering, PrimaryRendering,
PrimaryStatus, PrimaryStatus,
} from 'src/app/services/pkg-status-rendering.service' } from 'src/app/services/pkg-status-rendering.service'
import { isEmptyObject } from 'src/app/util/misc.util' import {
InterfaceDef,
PackageDataEntry,
Status,
} from 'src/app/services/patch-db/data-model'
import { isEmptyObject, PackageState } from '@start9labs/shared'
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component' import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
import { import {
AlertController, AlertController,

View File

@@ -0,0 +1,12 @@
import { Pipe, PipeTransform } from '@angular/core'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { ProgressData, packageLoadingProgress } from '@start9labs/shared'
@Pipe({
name: 'installState',
})
export class InstallStatePipe implements PipeTransform {
transform(pkg: PackageDataEntry): ProgressData | null {
return packageLoadingProgress(pkg['install-progress'])
}
}

View File

@@ -84,7 +84,8 @@ export class ToButtonsPipe implements PipeTransform {
}, },
// view in marketplace // view in marketplace
{ {
action: () => this.navCtrl.navigateForward([`marketplace/${pkg.manifest.id}`]), action: () =>
this.navCtrl.navigateForward([`marketplace/${pkg.manifest.id}`]),
title: 'Marketplace', title: 'Marketplace',
description: 'View service in marketplace', description: 'View service in marketplace',
icon: 'storefront-outline', icon: 'storefront-outline',

View File

@@ -3,12 +3,12 @@ import { NavigationExtras } from '@angular/router'
import { NavController } from '@ionic/angular' import { NavController } from '@ionic/angular'
import { combineLatest, Observable } from 'rxjs' import { combineLatest, Observable } from 'rxjs'
import { filter, map, startWith } from 'rxjs/operators' import { filter, map, startWith } from 'rxjs/operators'
import { DependentInfo, exists } from 'src/app/util/misc.util'
import { import {
DependencyError, DependencyError,
DependencyErrorType, DependencyErrorType,
PackageDataEntry, PackageDataEntry,
} from 'src/app/services/patch-db/data-model' } from 'src/app/services/patch-db/data-model'
import { DependentInfo, exists } from '@start9labs/shared'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { ModalService } from 'src/app/services/modal.service' import { ModalService } from 'src/app/services/modal.service'

View File

@@ -1,10 +1,10 @@
import { Inject, Pipe, PipeTransform } from '@angular/core' import { Pipe, PipeTransform } from '@angular/core'
import { import {
HealthCheckResult, HealthCheckResult,
PackageDataEntry, PackageDataEntry,
PackageMainStatus, PackageMainStatus,
} from 'src/app/services/patch-db/data-model' } from 'src/app/services/patch-db/data-model'
import { exists, isEmptyObject } from 'src/app/util/misc.util' import { exists, isEmptyObject } from '@start9labs/shared'
import { filter, map, startWith } from 'rxjs/operators' import { filter, map, startWith } from 'rxjs/operators'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service' import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { Observable } from 'rxjs' import { Observable } from 'rxjs'
@@ -30,7 +30,8 @@ export class ToHealthChecksPipe implements PipeTransform {
map(main => { map(main => {
// Question: is this ok or do we have to use Object.keys // Question: is this ok or do we have to use Object.keys
// to maintain order and the keys initially present in pkg? // to maintain order and the keys initially present in pkg?
return main.status === PackageMainStatus.Running && !isEmptyObject(main.health) return main.status === PackageMainStatus.Running &&
!isEmptyObject(main.health)
? main.health ? main.health
: healthChecks : healthChecks
}), }),

View File

@@ -4,7 +4,6 @@ import { IonicModule } from '@ionic/angular'
import { RouterModule, Routes } from '@angular/router' import { RouterModule, Routes } from '@angular/router'
import { DevConfigPage } from './dev-config.page' import { DevConfigPage } from './dev-config.page'
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module' import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
import { SharingModule } from 'src/app/modules/sharing.module'
import { BackupReportPageModule } from 'src/app/modals/backup-report/backup-report.module' import { BackupReportPageModule } from 'src/app/modals/backup-report/backup-report.module'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor' import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'
@@ -22,7 +21,6 @@ const routes: Routes = [
IonicModule, IonicModule,
RouterModule.forChild(routes), RouterModule.forChild(routes),
BadgeMenuComponentModule, BadgeMenuComponentModule,
SharingModule,
BackupReportPageModule, BackupReportPageModule,
FormsModule, FormsModule,
MonacoEditorModule, MonacoEditorModule,

View File

@@ -4,7 +4,6 @@ import { IonicModule } from '@ionic/angular'
import { RouterModule, Routes } from '@angular/router' import { RouterModule, Routes } from '@angular/router'
import { DevInstructionsPage } from './dev-instructions.page' import { DevInstructionsPage } from './dev-instructions.page'
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module' import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
import { SharingModule } from 'src/app/modules/sharing.module'
import { BackupReportPageModule } from 'src/app/modals/backup-report/backup-report.module' import { BackupReportPageModule } from 'src/app/modals/backup-report/backup-report.module'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor' import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'
@@ -22,7 +21,6 @@ const routes: Routes = [
IonicModule, IonicModule,
RouterModule.forChild(routes), RouterModule.forChild(routes),
BadgeMenuComponentModule, BadgeMenuComponentModule,
SharingModule,
BackupReportPageModule, BackupReportPageModule,
FormsModule, FormsModule,
MonacoEditorModule, MonacoEditorModule,

View File

@@ -4,7 +4,6 @@ import { IonicModule } from '@ionic/angular'
import { RouterModule, Routes } from '@angular/router' import { RouterModule, Routes } from '@angular/router'
import { DeveloperPage } from './developer-list.page' import { DeveloperPage } from './developer-list.page'
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module' import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
import { SharingModule } from 'src/app/modules/sharing.module'
import { BackupReportPageModule } from 'src/app/modals/backup-report/backup-report.module' import { BackupReportPageModule } from 'src/app/modals/backup-report/backup-report.module'
const routes: Routes = [ const routes: Routes = [
@@ -20,7 +19,6 @@ const routes: Routes = [
IonicModule, IonicModule,
RouterModule.forChild(routes), RouterModule.forChild(routes),
BadgeMenuComponentModule, BadgeMenuComponentModule,
SharingModule,
BackupReportPageModule, BackupReportPageModule,
], ],
declarations: [DeveloperPage], declarations: [DeveloperPage],

View File

@@ -4,7 +4,7 @@ import { CommonModule } from '@angular/common'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { LoginPage } from './login.page' import { LoginPage } from './login.page'
import { SharingModule } from 'src/app/modules/sharing.module' import { SharedPipesModule } from '@start9labs/shared'
const routes: Routes = [ const routes: Routes = [
{ {
@@ -19,8 +19,8 @@ const routes: Routes = [
FormsModule, FormsModule,
IonicModule, IonicModule,
RouterModule.forChild(routes), RouterModule.forChild(routes),
SharingModule, SharedPipesModule,
], ],
declarations: [LoginPage], declarations: [LoginPage],
}) })
export class LoginPageModule { } export class LoginPageModule {}

View File

@@ -2,8 +2,13 @@ import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { Routes, RouterModule } from '@angular/router' import { Routes, RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import {
EmverPipesModule,
MarkdownPipeModule,
TextSpinnerComponentModule,
} from '@start9labs/shared'
import { AppReleaseNotes } from './app-release-notes.page' import { AppReleaseNotes } from './app-release-notes.page'
import { SharingModule } from 'src/app/modules/sharing.module' import { MarketplacePipesModule } from '../pipes/marketplace-pipes.module'
const routes: Routes = [ const routes: Routes = [
{ {
@@ -17,8 +22,11 @@ const routes: Routes = [
CommonModule, CommonModule,
IonicModule, IonicModule,
RouterModule.forChild(routes), RouterModule.forChild(routes),
SharingModule, TextSpinnerComponentModule,
EmverPipesModule,
MarkdownPipeModule,
MarketplacePipesModule,
], ],
declarations: [AppReleaseNotes], declarations: [AppReleaseNotes],
}) })
export class ReleaseNotesModule { } export class ReleaseNotesModule {}

View File

@@ -1,12 +1,16 @@
import { NgModule } from '@angular/core' import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { FormsModule } from '@angular/forms'
import { Routes, RouterModule } from '@angular/router' import { Routes, RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { MarketplaceListPage } from './marketplace-list.page' import {
import { SharingModule } from '../../../modules/sharing.module' SharedPipesModule,
EmverPipesModule,
TextSpinnerComponentModule,
} from '@start9labs/shared'
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.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 { MarketplacePipesModule } from '../pipes/marketplace-pipes.module'
import { FormsModule } from '@angular/forms' import { MarketplaceListPage } from './marketplace-list.page'
const routes: Routes = [ const routes: Routes = [
{ {
@@ -21,10 +25,12 @@ const routes: Routes = [
IonicModule, IonicModule,
FormsModule, FormsModule,
RouterModule.forChild(routes), RouterModule.forChild(routes),
StatusComponentModule, TextSpinnerComponentModule,
SharingModule, SharedPipesModule,
EmverPipesModule,
MarketplacePipesModule,
BadgeMenuComponentModule, BadgeMenuComponentModule,
], ],
declarations: [MarketplaceListPage], declarations: [MarketplaceListPage],
}) })
export class MarketplaceListPageModule { } export class MarketplaceListPageModule {}

Some files were not shown because too many files have changed in this diff Show More