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": {
"production": {
"tsConfig": "projects/shared/tsconfig.lib.prod.json"
"tsConfig": "projects/shared/tsconfig.prod.json"
},
"development": {
"tsConfig": "projects/shared/tsconfig.lib.json"
"tsConfig": "projects/shared/tsconfig.json"
}
},
"defaultConfiguration": "production"

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@
"scripts": {
"ng": "ng",
"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:setup-wizard": "tsc --project projects/setup-wizard/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 { HttpService } from './services/http.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

View File

@@ -1,39 +1,50 @@
import { Injectable } from "@angular/core"
import { pauseFor } from "../../util/misc.util"
import { ApiService, GetErrorRes, GetLogsReq, GetLogsRes, Log } from "./api.service"
import { Injectable } from '@angular/core'
import { pauseFor } from '@start9labs/shared'
import {
ApiService,
GetErrorRes,
GetLogsReq,
GetLogsRes,
Log,
} from './api.service'
@Injectable()
export class MockApiService extends ApiService {
constructor() {
super()
}
constructor () { super() }
async getError (): Promise<GetErrorRes> {
async getError(): Promise<GetErrorRes> {
await pauseFor(1000)
return {
code: 15,
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)
return null
}
async forgetDrive (): Promise<void> {
async forgetDrive(): Promise<void> {
await pauseFor(1000)
return null
}
async getLogs (params: GetLogsReq): Promise<GetLogsRes> {
async getLogs(params: GetLogsReq): Promise<GetLogsRes> {
await pauseFor(1000)
let entries: Log[]
if (Math.random() < .2) {
if (Math.random() < 0.2) {
entries = packageLogs
} else {
const arrLength = params.limit ? Math.ceil(params.limit / packageLogs.length) : 10
entries = new Array(arrLength).fill(packageLogs).reduce((acc, val) => acc.concat(val), [])
const arrLength = params.limit
? Math.ceil(params.limit / packageLogs.length)
: 10
entries = new Array(arrLength)
.fill(packageLogs)
.reduce((acc, val) => acc.concat(val), [])
}
return {
entries,
@@ -56,4 +67,4 @@ const packageLogs = [
timestamp: '2019-12-26T14:22:30.872Z',
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 { ProductKeyPageModule } from './pages/product-key/product-key.module'
import { RecoverPageModule } from './pages/recover/recover.module'
import { WorkspaceConfig } from '@shared'
import { WorkspaceConfig } from '@start9labs/shared'
const { useMocks } = require('../../../../config.json') as WorkspaceConfig

View File

@@ -2,10 +2,10 @@ import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { FormsModule } from '@angular/forms'
import { UnitConversionPipesModule } from '@start9labs/shared'
import { EmbassyPage } from './embassy.page'
import { PasswordPageModule } from '../../modals/password/password.module'
import { EmbassyPageRoutingModule } from './embassy-routing.module'
import { PipesModule } from 'src/app/pipes/pipe.module'
@NgModule({
imports: [
@@ -14,8 +14,8 @@ import { PipesModule } from 'src/app/pipes/pipe.module'
IonicModule,
EmbassyPageRoutingModule,
PasswordPageModule,
PipesModule,
UnitConversionPipesModule,
],
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 { IonicModule } from '@ionic/angular'
import { FormsModule } from '@angular/forms'
import { UnitConversionPipesModule } from '@start9labs/shared'
import { DriveStatusComponent, RecoverPage } from './recover.page'
import { PasswordPageModule } from '../../modals/password/password.module'
import { ProdKeyModalModule } from '../../modals/prod-key-modal/prod-key-modal.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'
@NgModule({
@@ -18,8 +18,8 @@ import { CifsModalModule } from 'src/app/modals/cifs-modal/cifs-modal.module'
RecoverPageRoutingModule,
PasswordPageModule,
ProdKeyModalModule,
PipesModule,
UnitConversionPipesModule,
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 { pauseFor } from 'src/app/util/misc.util'
import { pauseFor } from '@start9labs/shared'
import { ApiService, CifsRecoverySource, SetupEmbassyReq } from './api.service'
let tries = 0

View File

@@ -1,8 +1,12 @@
import { Injectable } from '@angular/core'
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 { pauseFor } from '../util/misc.util'
import { pauseFor } from '@start9labs/shared'
@Injectable({
providedIn: 'root',
@@ -17,7 +21,11 @@ export class StateService {
recoverySource: CifsRecoverySource | DiskRecoverySource
recoveryPassword: string
dataTransferProgress: { bytesTransferred: number, totalBytes: number, complete: boolean } | null
dataTransferProgress: {
bytesTransferred: number
totalBytes: number
complete: boolean
} | null
dataProgress = 0
dataCompletionSubject = new BehaviorSubject(false)
@@ -25,28 +33,27 @@ export class StateService {
lanAddress: string
cert: string
constructor (
constructor(
private readonly apiService: ApiService,
private readonly errorToastService: ErrorToastService,
) { }
) {}
async pollDataTransferProgress () {
async pollDataTransferProgress() {
this.polling = true
await pauseFor(500)
if (
this.dataTransferProgress?.complete
) {
if (this.dataTransferProgress?.complete) {
this.dataCompletionSubject.next(true)
return
}
let progress
try {
progress = await this.apiService.getRecoveryStatus()
} 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) {
this.dataTransferProgress = {
@@ -55,20 +62,25 @@ export class StateService {
complete: progress.complete,
}
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
}
async importDrive (guid: string): Promise<void> {
async importDrive(guid: string): Promise<void> {
const ret = await this.apiService.importDrive(guid)
this.torAddress = ret['tor-address']
this.lanAddress = ret['lan-address']
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({
'embassy-logicalname': storageLogicalname,
'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",
"peerDependencies": {
"@angular/common": "^13.2.0",
"@angular/core": "^13.2.0"
"@angular/core": "^13.2.0",
"@start9labs/emver": "^0.1.5"
},
"dependencies": {
"tslib": "^2.3.0"

View File

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

View File

@@ -1,18 +1,11 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { TextSpinnerComponent } from './text-spinner.component'
import { IonicModule } from '@ionic/angular'
import { RouterModule } from '@angular/router'
import { TextSpinnerComponent } from './text-spinner.component'
@NgModule({
declarations: [
TextSpinnerComponent,
],
imports: [
CommonModule,
IonicModule,
RouterModule.forChild([]),
],
declarations: [TextSpinnerComponent],
imports: [CommonModule, IonicModule],
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'],
})
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 { Emver } from '../services/emver.service'
import { Emver } from '../../services/emver.service'
@Pipe({
name: 'satisfiesEmver',
name: 'satisfiesEmver',
})
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)
}
}
@Pipe({
name: 'compareEmver',
name: 'compareEmver',
})
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 {
return this.emver.compare(first, second) as SemverResult
} catch (e) {
@@ -29,18 +30,18 @@ export class EmverComparesPipe implements PipeTransform {
type SemverResult = 0 | 1 | -1 | 'comparison-impossible'
@Pipe({
name: 'displayEmver',
name: 'displayEmver',
})
export class EmverDisplayPipe implements PipeTransform {
constructor () { }
constructor() {}
transform (version: string): string {
transform(version: string): string {
return displayEmver(version)
}
}
export function displayEmver (version: string): string {
export function displayEmver(version: string): string {
const vs = version.split('.')
if (vs.length === 4) return `${vs[0]}.${vs[1]}.${vs[2]}~${vs[3]}`
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 { isEmptyObject } from '../util/misc.util'
import { isEmptyObject } from '../../util/misc.util'
@Pipe({
name: 'empty',
})
export class EmptyPipe implements PipeTransform {
transform (val: object | [] = { }): boolean {
transform(val: object | [] = {}): boolean {
if (Array.isArray(val)) return !val.length
return isEmptyObject(val)
}
}
}

View File

@@ -1,10 +1,10 @@
import { Pipe, PipeTransform } from '@angular/core'
@Pipe({
name: 'includes',
name: 'includes',
})
export class IncludesPipe implements PipeTransform {
transform<T> (list: T[], val: T): boolean {
transform<T>(list: T[], val: T): boolean {
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 './util/misc.util'
export * from './util/package-loading-progress'

View File

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

View File

@@ -1,19 +1,18 @@
import { Injectable } from '@angular/core'
import * as emver from '@start9labs/emver'
@Injectable({
providedIn: 'root',
})
export class Emver {
constructor () { }
constructor() {}
compare (lhs: string, rhs: string): number {
compare(lhs: string, rhs: string): number {
if (!lhs || !rhs) return null
return emver.compare(lhs, rhs)
}
satisfies (version: string, range: string): boolean {
satisfies(version: string, range: string): boolean {
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 { InstallProgress } from '../types/install-progress'
import { ProgressData } from '../types/progress-data'
export function packageLoadingProgress(
loadData: InstallProgress,
@@ -46,11 +47,3 @@ export function packageLoadingProgress(
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",
"compilerOptions": {
"baseUrl": "./",
"outDir": "../../out-tsc/lib",
"declaration": true,
"declarationMap": true,

View File

@@ -16,17 +16,16 @@ import {
ModalController,
ToastController,
} from '@ionic/angular'
import { Emver } from './services/emver.service'
import { SplitPaneTracker } from './services/split-pane.service'
import { ToastButton } from '@ionic/core'
import { PatchDbService } from './services/patch-db/patch-db.service'
import { ServerStatus, UIData } from './services/patch-db/data-model'
import {
ConnectionFailure,
ConnectionService,
} from './services/connection.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 { Subscription } from 'rxjs'
import { LocalStorageService } from './services/local-storage.service'
@@ -42,7 +41,7 @@ import { OSWelcomePage } from './modals/os-welcome/os-welcome.page'
export class AppComponent {
@HostListener('document:keydown.enter', ['$event'])
@debounce()
handleKeyboardEvent () {
handleKeyboardEvent() {
const elems = document.getElementsByClassName('enter-click')
const elem = elems[elems.length - 1] as HTMLButtonElement
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 authService: AuthService,
private readonly router: Router,
@@ -110,7 +109,7 @@ export class AppComponent {
this.init()
}
async init () {
async init() {
await this.storage.create()
await this.authService.init()
await this.localStorageService.init()
@@ -181,7 +180,7 @@ export class AppComponent {
})
}
async goToWebsite (): Promise<void> {
async goToWebsite(): Promise<void> {
let url: string
if (this.config.isTor()) {
url =
@@ -192,7 +191,7 @@ export class AppComponent {
window.open(url, '_blank', 'noreferrer')
}
async presentAlertLogout () {
async presentAlertLogout() {
const alert = await this.alertCtrl.create({
header: 'Caution',
message:
@@ -215,13 +214,13 @@ export class AppComponent {
await alert.present()
}
private checkForEosUpdate (ui: UIData): void {
private checkForEosUpdate(ui: UIData): void {
if (ui['auto-check-updates']) {
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) {
const modal = await this.modalCtrl.create({
component: OSWelcomePage,
@@ -240,12 +239,12 @@ export class AppComponent {
}
// should wipe cache independant of actual BE logout
private async logout () {
private async logout() {
this.embassyApi.logout({})
this.authService.setUnverified()
}
private watchConnection (): Subscription {
private watchConnection(): Subscription {
return this.connectionService
.watchFailure$()
.pipe(distinctUntilChanged(), debounceTime(500))
@@ -278,7 +277,7 @@ export class AppComponent {
})
}
private watchRouter (): Subscription {
private watchRouter(): Subscription {
return this.router.events
.pipe(filter((e: RoutesRecognized) => !!e.urlAfterRedirects))
.subscribe(e => {
@@ -289,7 +288,7 @@ export class AppComponent {
})
}
private watchStatus (): Subscription {
private watchStatus(): Subscription {
return this.patch
.watch$('server-info', 'status-info', 'updated')
.subscribe(isUpdated => {
@@ -300,7 +299,7 @@ export class AppComponent {
}
m
private watchUpdateProgress (): Subscription {
private watchUpdateProgress(): Subscription {
return this.patch
.watch$('server-info', 'status-info', 'update-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 => {
if (this.emver.compare(this.config.version, version) !== 0) {
this.presentAlertRefreshNeeded()
@@ -316,7 +315,7 @@ export class AppComponent {
})
}
private watchNotifications (): Subscription {
private watchNotifications(): Subscription {
let previous: number
return this.patch
.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({
backdropDismiss: false,
header: 'Refresh Needed',
@@ -347,7 +346,7 @@ export class AppComponent {
await alert.present()
}
private async presentToastUpdated () {
private async presentToastUpdated() {
if (this.updateToast) return
this.updateToast = await this.toastCtrl.create({
@@ -377,7 +376,7 @@ export class AppComponent {
await this.updateToast.present()
}
private async presentToastNotifications () {
private async presentToastNotifications() {
if (this.notificationToast) return
this.notificationToast = await this.toastCtrl.create({
@@ -407,7 +406,7 @@ export class AppComponent {
await this.notificationToast.present()
}
private async presentToastOffline (
private async presentToastOffline(
message: string | IonicSafeString,
link?: string,
) {
@@ -448,7 +447,7 @@ export class AppComponent {
await this.offlineToast.present()
}
private async restart (): Promise<void> {
private async restart(): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Restarting...',
@@ -465,7 +464,7 @@ export class AppComponent {
}
}
splitPaneVisible (e: any) {
splitPaneVisible(e: any) {
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 { PatchDbService } from './services/patch-db/patch-db.service'
import { LocalStorageBootstrap } from './services/patch-db/local-storage-bootstrap'
import { SharingModule } from './modules/sharing.module'
import { FormBuilder } from '@angular/forms'
import { GenericInputComponentModule } from './modals/generic-input/generic-input.component.module'
import { AuthService } from './services/auth.service'
import { GlobalErrorHandler } from './services/global-error-handler.service'
import { MockApiService } from './services/api/embassy-mock-api.service'
import { LiveApiService } from './services/api/embassy-live-api.service'
import { WorkspaceConfig } from '@shared'
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'
import { SharedPipesModule, WorkspaceConfig } from '@start9labs/shared'
const { useMocks } = require('../../../../config.json') as WorkspaceConfig
@@ -47,8 +46,8 @@ const { useMocks } = require('../../../../config.json') as WorkspaceConfig
OSWelcomePageModule,
MarkdownPageModule,
GenericInputComponentModule,
SharingModule,
MonacoEditorModule,
SharedPipesModule,
],
providers: [
FormBuilder,
@@ -80,4 +79,4 @@ const { useMocks } = require('../../../../config.json') as WorkspaceConfig
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
export class AppModule { }

View File

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

View File

@@ -1,12 +1,21 @@
import { Component, EventEmitter, Input, Output } from '@angular/core'
import { BackupService } from './backup.service'
import { CifsBackupTarget, DiskBackupTarget, RR } from 'src/app/services/api/api.types'
import { ActionSheetController, AlertController, LoadingController, ModalController } from '@ionic/angular'
import {
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 { ConfigSpec } from 'src/app/pkg-config/config-types'
import { ApiService } from 'src/app/services/api/embassy-api.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({
selector: 'backup-drives',
@@ -15,10 +24,12 @@ import { MappedBackupTarget } from 'src/app/util/misc.util'
})
export class BackupDrivesComponent {
@Input() type: 'create' | 'restore'
@Output() onSelect: EventEmitter<MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>> = new EventEmitter()
@Output() onSelect: EventEmitter<
MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>
> = new EventEmitter()
loadingText: string
constructor (
constructor(
private readonly loadingCtrl: LoadingController,
private readonly actionCtrl: ActionSheetController,
private readonly alertCtrl: AlertController,
@@ -26,22 +37,30 @@ export class BackupDrivesComponent {
private readonly embassyApi: ApiService,
private readonly errToast: ErrorToastService,
public readonly backupService: BackupService,
) { }
) {}
ngOnInit () {
this.loadingText = this.type === 'create' ? 'Fetching Backup Targets' : 'Fetching Backup Sources'
ngOnInit() {
this.loadingText =
this.type === 'create'
? 'Fetching Backup Targets'
: 'Fetching Backup Sources'
this.backupService.getBackupTargets()
}
select (target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>): void {
select(
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
): void {
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)
return
}
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)
return
}
@@ -49,7 +68,7 @@ export class BackupDrivesComponent {
this.onSelect.emit(target)
}
async presentModalAddCifs (): Promise<void> {
async presentModalAddCifs(): Promise<void> {
const modal = await this.modalCtrl.create({
component: GenericFormPage,
componentProps: {
@@ -69,7 +88,10 @@ export class BackupDrivesComponent {
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 action = await this.actionCtrl.create({
@@ -93,8 +115,12 @@ export class BackupDrivesComponent {
},
},
{
text: this.type === 'create' ? 'Create Backup' : 'Restore From Backup',
icon: this.type === 'create' ? 'cloud-upload-outline' : 'cloud-download-outline',
text:
this.type === 'create' ? 'Create Backup' : 'Restore From Backup',
icon:
this.type === 'create'
? 'cloud-upload-outline'
: 'cloud-download-outline',
handler: () => {
this.select(target)
},
@@ -105,7 +131,7 @@ export class BackupDrivesComponent {
await action.present()
}
private async presentAlertError (message: string): Promise<void> {
private async presentAlertError(message: string): Promise<void> {
const alert = await this.alertCtrl.create({
header: 'Error',
message,
@@ -114,7 +140,7 @@ export class BackupDrivesComponent {
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({
spinner: 'lines',
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 modal = await this.modalCtrl.create({
@@ -166,7 +196,10 @@ export class BackupDrivesComponent {
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({
spinner: 'lines',
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({
spinner: 'lines',
message: 'Removing...',
@@ -204,7 +237,6 @@ export class BackupDrivesComponent {
}
}
@Component({
selector: 'backup-drives-header',
templateUrl: './backup-drives-header.component.html',
@@ -214,16 +246,13 @@ export class BackupDrivesHeaderComponent {
@Input() title: string
@Output() onClose: EventEmitter<void> = new EventEmitter()
constructor (
public readonly backupService: BackupService,
) { }
constructor(public readonly backupService: BackupService) {}
refresh () {
refresh() {
this.backupService.getBackupTargets()
}
}
@Component({
selector: 'backup-drives-status',
templateUrl: './backup-drives-status.component.html',
@@ -238,7 +267,8 @@ const CifsSpec: ConfigSpec = {
hostname: {
type: 'string',
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'`,
pattern: '^[a-zA-Z0-9._-]+( [a-zA-Z0-9]+)*$',
'pattern-description': `Must be a valid hostname. e.g. 'My Computer' OR 'my-computer.local'`,
@@ -249,7 +279,8 @@ const CifsSpec: ConfigSpec = {
path: {
type: 'string',
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',
nullable: false,
masked: false,

View File

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

View File

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

View File

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

View File

@@ -1,29 +1,25 @@
import { NgModule } from '@angular/core'
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 { 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'
@NgModule({
declarations: [
FormObjectComponent,
FormLabelComponent,
FormErrorComponent,
],
declarations: [FormObjectComponent, FormLabelComponent, FormErrorComponent],
imports: [
CommonModule,
IonicModule,
FormsModule,
ReactiveFormsModule,
SharingModule,
SharedPipesModule,
EnumListPageModule,
],
exports: [
FormObjectComponent,
FormLabelComponent,
FormErrorComponent,
],
exports: [FormObjectComponent, FormLabelComponent, FormErrorComponent],
})
export class FormObjectComponentModule { }
export class FormObjectComponentModule {}

View File

@@ -1,11 +1,28 @@
import { Component, Input, Output, EventEmitter } from '@angular/core'
import { AbstractFormGroupDirective, 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 {
AbstractFormGroupDirective,
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 { Range } from 'src/app/pkg-config/config-utilities'
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'
const Mustache = require('mustache')
@@ -22,31 +39,38 @@ export class FormObjectComponent {
@Input() showEdited: boolean = false
@Output() onInputChange = new EventEmitter<void>()
@Output() onExpand = new EventEmitter<void>()
warningAck: { [key: string]: boolean } = { }
unmasked: { [key: string]: boolean } = { }
objectDisplay: { [key: string]: { expanded: boolean, height: string } } = { }
objectListDisplay: { [key: string]: { expanded: boolean, height: string, displayAs: string }[] } = { }
warningAck: { [key: string]: boolean } = {}
unmasked: { [key: string]: boolean } = {}
objectDisplay: { [key: string]: { expanded: boolean; height: string } } = {}
objectListDisplay: {
[key: string]: { expanded: boolean; height: string; displayAs: string }[]
} = {}
private objectId = v4()
Object = Object
constructor (
constructor(
private readonly alertCtrl: AlertController,
private readonly modalCtrl: ModalController,
private readonly formService: FormService,
) { }
) {}
ngOnInit () {
ngOnInit() {
Object.keys(this.objectSpec).forEach(key => {
const spec = this.objectSpec[key]
if (spec.type === 'list' && ['object', 'union'].includes(spec.subtype)) {
this.objectListDisplay[key] = [];
(this.formGroup.get(key).value as any[]).forEach((obj, index) => {
const displayAs = (spec.spec as ListValueSpecOf<'object'>)['display-as']
this.objectListDisplay[key] = []
this.formGroup.get(key).value.forEach((obj, index) => {
const displayAs = (spec.spec as ListValueSpecOf<'object'>)[
'display-as'
]
this.objectListDisplay[key][index] = {
expanded: false,
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)) {
@@ -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(', ')
}
updateUnion (e: any): void {
updateUnion(e: any): void {
const primary = this.unionSpec.tag.id
Object.keys(this.formGroup.controls).forEach(control => {
@@ -70,26 +94,31 @@ export class FormObjectComponent {
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 => {
if (control === primary) return
this.formGroup.addControl(control, unionGroup.controls[control])
})
Object.entries(this.unionSpec.variants[e.detail.value]).forEach(([key, value]) => {
if (['object', 'union'].includes(value.type)) {
this.objectDisplay[key] = {
expanded: false,
height: '0px',
Object.entries(this.unionSpec.variants[e.detail.value]).forEach(
([key, value]) => {
if (['object', 'union'].includes(value.type)) {
this.objectDisplay[key] = {
expanded: false,
height: '0px',
}
}
}
})
},
)
this.onExpand.emit()
}
resize (key: string, i?: number): void {
resize(key: string, i?: number): void {
setTimeout(() => {
if (i !== undefined) {
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
}
addListItemWrapper (key: string, spec: ValueSpec) {
addListItemWrapper(key: string, spec: ValueSpec) {
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
if (markDirty) arr.markAsDirty()
const listSpec = this.objectSpec[key] as ValueSpecList
@@ -112,7 +141,9 @@ export class FormObjectComponent {
newItem.markAllAsTouched()
arr.insert(0, newItem)
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({
height: '0px',
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].height = this.objectDisplay[key].expanded ? this.getDocSize(key) : '0px'
this.objectDisplay[key].height = this.objectDisplay[key].expanded
? this.getDocSize(key)
: '0px'
this.onExpand.emit()
}
toggleExpandListObject (key: string, i: number) {
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'
toggleExpandListObject(key: string, i: number) {
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'
}
updateLabel (key: string, i: number, displayAs: string) {
this.objectListDisplay[key][i].displayAs = displayAs ? Mustache.render(displayAs, this.formGroup.get(key).value[i]) : ''
updateLabel(key: string, i: number, displayAs: string) {
this.objectListDisplay[key][i].displayAs = displayAs
? Mustache.render(displayAs, this.formGroup.get(key).value[i])
: ''
}
getWarningText (text: string): IonicSafeString {
if (text) return new IonicSafeString(`<ion-text color="warning">${text}</ion-text>`)
getWarningText(text: string): IonicSafeString {
if (text)
return new IonicSafeString(`<ion-text color="warning">${text}</ion-text>`)
}
handleInputChange () {
handleInputChange() {
this.onInputChange.emit()
}
handleBooleanChange (key: string, spec: ValueSpecBoolean) {
handleBooleanChange(key: string, spec: ValueSpecBoolean) {
if (spec.warning) {
const current = this.formGroup.get(key).value
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({
componentProps: {
key,
@@ -175,7 +219,12 @@ export class FormObjectComponent {
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
this.warningAck[key] = true
@@ -207,7 +256,7 @@ export class FormObjectComponent {
await alert.present()
}
async presentAlertDelete (key: string, index: number) {
async presentAlertDelete(key: string, index: number) {
const alert = await this.alertCtrl.create({
header: 'Confirm',
message: 'Are you sure you want to delete this entry?',
@@ -228,17 +277,19 @@ export class FormObjectComponent {
await alert.present()
}
private deleteListItem (key: string, index: number, markDirty = true): void {
if (this.objectListDisplay[key]) this.objectListDisplay[key][index].height = '0px'
private deleteListItem(key: string, index: number, markDirty = true): void {
if (this.objectListDisplay[key])
this.objectListDisplay[key][index].height = '0px'
const arr = this.formGroup.get(key) as FormArray
if (markDirty) arr.markAsDirty()
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)
})
}
private updateEnumList (key: string, current: string[], updated: string[]) {
private updateEnumList(key: string, current: string[], updated: string[]) {
this.formGroup.get(key).markAsDirty()
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))
return `${element.scrollHeight}px`
}
getElementId (key: string, index = 0): string {
getElementId(key: string, index = 0): string {
return `${key}-${index}-${this.objectId}`
}
async presentUnionTagDescription (name: string, description: string) {
async presentUnionTagDescription(name: string, description: string) {
const alert = await this.alertCtrl.create({
header: name,
message: description,
@@ -272,7 +322,7 @@ export class FormObjectComponent {
await alert.present()
}
asIsOrder () {
asIsOrder() {
return 0
}
}
@@ -293,11 +343,9 @@ export class FormLabelComponent {
Range = Range
@Input() data: HeaderData
constructor (
private readonly alertCtrl: AlertController,
) { }
constructor(private readonly alertCtrl: AlertController) {}
async presentAlertDescription () {
async presentAlertDescription() {
const { name, description } = this.data.spec
const alert = await this.alertCtrl.create({
@@ -308,7 +356,6 @@ export class FormLabelComponent {
}
}
@Component({
selector: 'form-error',
templateUrl: './form-error.component.html',

View File

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

View File

@@ -1,7 +1,7 @@
import { Component, Input } from '@angular/core'
import { BehaviorSubject, from, Subject } from 'rxjs'
import { takeUntil } from 'rxjs/operators'
import { capitalizeFirstLetter } from 'src/app/util/misc.util'
import { capitalizeFirstLetter } from '@start9labs/shared'
import { markAsLoadingDuring$ } from '../loadable'
import { WizardAction } from '../wizard-types'
@@ -30,16 +30,21 @@ export class CompleteComponent {
message: string
load () {
markAsLoadingDuring$(this.loading$, from(this.params.executeAction())).pipe(takeUntil(this.cancel$)).subscribe(
{
error: e => this.transitions.error(new Error(`${this.params.action} failed: ${e.message || e}`)),
load() {
markAsLoadingDuring$(this.loading$, from(this.params.executeAction()))
.pipe(takeUntil(this.cancel$))
.subscribe({
error: e =>
this.transitions.error(
new Error(`${this.params.action} failed: ${e.message || e}`),
),
complete: () => this.transitions.final(),
},
)
})
}
ngOnInit () {
this.message = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title}...`
ngOnInit() {
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 { IonicModule } from '@ionic/angular'
import { RouterModule } from '@angular/router'
import { SharingModule } from 'src/app/modules/sharing.module'
import { SharedPipesModule } from '@start9labs/shared'
@NgModule({
declarations: [
DependentsComponent,
],
declarations: [DependentsComponent],
imports: [
CommonModule,
IonicModule,
RouterModule.forChild([]),
SharingModule,
SharedPipesModule,
],
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 { Breakages } from 'src/app/services/api/api.types'
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 { WizardAction } from '../wizard-types'
@@ -14,9 +14,9 @@ import { WizardAction } from '../wizard-types'
})
export class DependentsComponent {
@Input() params: {
title: string,
action: WizardAction, //Are you sure you want to *uninstall*...,
verb: string, // *Uninstalling* will cause problems...
title: string
action: WizardAction //Are you sure you want to *uninstall*...,
verb: string // *Uninstalling* will cause problems...
fetchBreakages: () => Promise<Breakages>
}
@Input() transitions: {
@@ -32,27 +32,37 @@ export class DependentsComponent {
loading$ = new BehaviorSubject(false)
cancel$ = new Subject<void>()
constructor (
public readonly patch: PatchDbService,
) { }
constructor(public readonly patch: PatchDbService) {}
load () {
load() {
markAsLoadingDuring$(this.loading$, from(this.params.fetchBreakages()))
.pipe(
takeUntil(this.cancel$),
tap(breakages => this.dependentBreakages = breakages),
)
.subscribe(
{
.pipe(
takeUntil(this.cancel$),
tap(breakages => (this.dependentBreakages = breakages)),
)
.subscribe({
complete: () => {
if (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.`
if (
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 {
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 { IonicModule } from '@ionic/angular'
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 { CompleteComponentModule } from './complete/complete.component.module'
import { NotesComponentModule } from './notes/notes.component.module'
import { AlertComponentModule } from './alert/alert.component.module'
@NgModule({
declarations: [
InstallWizardComponent,
],
declarations: [InstallWizardComponent],
imports: [
CommonModule,
IonicModule,
RouterModule.forChild([]),
SharingModule,
EmverPipesModule,
DependentsComponentModule,
CompleteComponentModule,
NotesComponentModule,
@@ -25,4 +23,4 @@ import { AlertComponentModule } from './alert/alert.component.module'
],
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 { capitalizeFirstLetter, pauseFor } from 'src/app/util/misc.util'
import { capitalizeFirstLetter, pauseFor } from '@start9labs/shared'
import { CompleteComponent } from './complete/complete.component'
import { DependentsComponent } from './dependents/dependents.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
@ViewChildren('components')
slideComponentsQL: QueryList<Loadable>
get slideComponents (): Loadable[] { return this.slideComponentsQL.toArray() }
get slideComponents(): Loadable[] {
return this.slideComponentsQL.toArray()
}
private slideIndex = 0
get currentSlide (): Loadable {
get currentSlide(): Loadable {
return this.slideComponents[this.slideIndex]
}
get currentBottomBar (): SlideDefinition['bottomBar'] {
get currentBottomBar(): SlideDefinition['bottomBar'] {
return this.params.slideDefinitions[this.slideIndex].bottomBar
}
initializing = true
error = ''
constructor (
constructor(
private readonly modalController: ModalController,
private readonly zone: NgZone,
) { }
) {}
ngAfterViewInit () {
ngAfterViewInit() {
this.currentSlide.load()
this.slideContainer.update()
this.slideContainer.lockSwipes(true)
}
ionViewDidEnter () {
ionViewDidEnter() {
this.initializing = false
}
// process bottom bar buttons
private transition = (info: { next: any } | { error: Error } | { cancelled: true } | { final: true }) => {
const i = info as { next?: any, error?: Error, cancelled?: true, final?: true }
private transition = (
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.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)
}
// bottom bar button callbacks. Pass this into components if they need to trigger slide transitions independent of the bottom bar clicks
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 }),
final: () => this.transition({ final: true }),
error: (e: Error) => this.transition({ error: e }),
}
private async moveToNextSlide (prevResult?: any) {
if (this.slideComponents[this.slideIndex + 1] === undefined) { return this.transition({ final: true }) }
private async moveToNextSlide(prevResult?: any) {
if (this.slideComponents[this.slideIndex + 1] === undefined) {
return this.transition({ final: true })
}
this.zone.run(async () => {
this.slideComponents[this.slideIndex + 1].load(prevResult)
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
await transition()
this.transitioning = false
@@ -98,14 +121,14 @@ export class InstallWizardComponent {
export interface SlideDefinition {
slide:
{ selector: 'dependents', params: DependentsComponent['params'] } |
{ selector: 'complete', params: CompleteComponent['params'] } |
{ selector: 'alert', params: AlertComponent['params'] } |
{ selector: 'notes', params: NotesComponent['params'] }
| { selector: 'dependents'; params: DependentsComponent['params'] }
| { selector: 'complete'; params: CompleteComponent['params'] }
| { selector: 'alert'; params: AlertComponent['params'] }
| { selector: 'notes'; params: NotesComponent['params'] }
bottomBar: {
cancel: {
// 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 }
}
// 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 (
modalController: ModalController, params: InstallWizardComponent['params'],
): Promise<{ cancelled?: true, final?: true, modal: HTMLIonModalElement }> {
export async function wizardModal(
modalController: ModalController,
params: InstallWizardComponent['params'],
): Promise<{ cancelled?: true; final?: true; modal: HTMLIonModalElement }> {
const modal = await modalController.create({
backdropDismiss: false,
cssClass: 'wizard-modal',

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,21 @@
<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-style]="style"
[style.font-weight]="weight"
>
<span *ngIf= "!installProgress">
<span *ngIf="!installProgress">
{{ disconnected ? 'Unknown' : rendering.display }}
<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 *ngIf="installProgress">
<span *ngIf="installProgress < 99">
@@ -19,5 +27,4 @@
<span class="loading-dots"></span>
</span>
</span>
</p>

View File

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

View File

@@ -1,5 +1,9 @@
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({
selector: 'status',
@@ -18,4 +22,3 @@ export class StatusComponent {
@Input() installProgress?: number
@Input() sigtermTimeout?: string
}

View File

@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { IonicModule } from '@ionic/angular'
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'
@NgModule({
@@ -12,10 +12,10 @@ import { FormObjectComponentModule } from 'src/app/components/form-object/form-o
CommonModule,
FormsModule,
IonicModule,
SharingModule,
TextSpinnerComponentModule,
FormObjectComponentModule,
ReactiveFormsModule,
],
exports: [AppConfigPage],
})
export class AppConfigPageModule { }
export class AppConfigPageModule {}

View File

@@ -7,7 +7,7 @@ import {
IonicSafeString,
} from '@ionic/angular'
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 { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
import { ConfigSpec } from 'src/app/pkg-config/config-types'

View File

@@ -1,9 +1,13 @@
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 { ApiService } from 'src/app/services/api/embassy-api.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 { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
@@ -26,39 +30,43 @@ export class AppRecoverSelectPage {
hasSelection = false
error: string | IonicSafeString
constructor (
constructor(
private readonly modalCtrl: ModalController,
private readonly loadingCtrl: LoadingController,
private readonly embassyApi: ApiService,
private readonly config: ConfigService,
private readonly emver: Emver,
private readonly patch: PatchDbService,
) { }
) {}
ngOnInit () {
ngOnInit() {
this.options = Object.keys(this.backupInfo['package-backups']).map(id => {
return {
...this.backupInfo['package-backups'][id],
id,
checked: false,
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()
}
handleChange () {
handleChange() {
this.hasSelection = this.options.some(o => o.checked)
}
async restore (): Promise<void> {
async restore(): Promise<void> {
const ids = this.options
.filter(option => !!option.checked)
.map(option => option.id)
.filter(option => !!option.checked)
.map(option => option.id)
const loader = await this.loadingCtrl.create({
spinner: 'lines',

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'
import { GenericInputComponent } from './generic-input.component'
import { IonicModule } from '@ionic/angular'
import { RouterModule } from '@angular/router'
import { SharingModule } from 'src/app/modules/sharing.module'
import { SharedPipesModule } from '@start9labs/shared'
import { FormsModule } from '@angular/forms'
@NgModule({
@@ -13,8 +13,8 @@ import { FormsModule } from '@angular/forms'
IonicModule,
FormsModule,
RouterModule.forChild([]),
SharingModule,
SharedPipesModule,
],
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 { IonicModule } from '@ionic/angular'
import { MarkdownPage } from './markdown.page'
import { SharingModule } from 'src/app/modules/sharing.module'
import {
MarkdownPipeModule,
TextSpinnerComponentModule,
} from '@start9labs/shared'
@NgModule({
declarations: [MarkdownPage],
imports: [
CommonModule,
IonicModule,
SharingModule,
MarkdownPipeModule,
TextSpinnerComponentModule,
],
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 { IonicModule } from '@ionic/angular'
import { OSWelcomePage } from './os-welcome.page'
import { SharingModule } from 'src/app/modules/sharing.module'
import { SharedPipesModule } from '@start9labs/shared'
import { FormsModule } from '@angular/forms'
@NgModule({
declarations: [OSWelcomePage],
imports: [
CommonModule,
IonicModule,
FormsModule,
SharingModule,
],
imports: [CommonModule, IonicModule, FormsModule, SharedPipesModule],
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 { AppActionsPage, AppActionsItemComponent } from './app-actions.page'
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 { ActionSuccessPageModule } from 'src/app/modals/action-success/action-success.module'
@@ -21,13 +21,10 @@ const routes: Routes = [
IonicModule,
RouterModule.forChild(routes),
QRComponentModule,
SharingModule,
SharedPipesModule,
GenericFormPageModule,
ActionSuccessPageModule,
],
declarations: [
AppActionsPage,
AppActionsItemComponent,
],
declarations: [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 { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
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'
@Component({

View File

@@ -2,8 +2,11 @@ import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { Routes, RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular'
import { AppInterfacesItemComponent, AppInterfacesPage } from './app-interfaces.page'
import { SharingModule } from 'src/app/modules/sharing.module'
import {
AppInterfacesItemComponent,
AppInterfacesPage,
} from './app-interfaces.page'
import { SharedPipesModule } from '@start9labs/shared'
const routes: Routes = [
{
@@ -17,11 +20,8 @@ const routes: Routes = [
CommonModule,
IonicModule,
RouterModule.forChild(routes),
SharingModule,
],
declarations: [
AppInterfacesPage,
AppInterfacesItemComponent,
SharedPipesModule,
],
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 { IonContent, ToastController } from '@ionic/angular'
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 { copyToClipboard } from 'src/app/util/web.util'
@@ -22,12 +25,12 @@ export class AppInterfacesPage {
other: LocalInterface[] = []
pkgId: string
constructor (
constructor(
private readonly route: ActivatedRoute,
public readonly patch: PatchDbService,
) { }
) {}
ngOnInit () {
ngOnInit() {
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
const pkg = this.patch.getData()['package-data'][this.pkgId]
const interfaces = pkg.manifest.interfaces
@@ -40,8 +43,12 @@ export class AppInterfacesPage {
this.ui = {
def: interfaces[uiKey],
addresses: {
'lan-address': uiAddresses['lan-address'] ? 'https://' + uiAddresses['lan-address'] : null,
'tor-address': uiAddresses['tor-address'] ? 'http://' + uiAddresses['tor-address'] : null,
'lan-address': uiAddresses['lan-address']
? 'https://' + uiAddresses['lan-address']
: null,
'tor-address': uiAddresses['tor-address']
? 'http://' + uiAddresses['tor-address']
: null,
},
}
}
@@ -53,18 +60,22 @@ export class AppInterfacesPage {
return {
def: interfaces[key],
addresses: {
'lan-address': addresses['lan-address'] ? 'https://' + addresses['lan-address'] : null,
'tor-address': addresses['tor-address'] ? 'http://' + addresses['tor-address'] : null,
'lan-address': addresses['lan-address']
? 'https://' + addresses['lan-address']
: null,
'tor-address': addresses['tor-address']
? 'http://' + addresses['tor-address']
: null,
},
}
})
}
ngAfterViewInit () {
ngAfterViewInit() {
this.content.scrollToPoint(undefined, 1)
}
asIsOrder () {
asIsOrder() {
return 0
}
}
@@ -77,18 +88,17 @@ export class AppInterfacesPage {
export class AppInterfacesItemComponent {
@Input() interface: LocalInterface
constructor (
private readonly toastCtrl: ToastController,
) { }
constructor(private readonly toastCtrl: ToastController) {}
launch (url: string): void {
launch(url: string): void {
window.open(url, '_blank', 'noreferrer')
}
async copy (address: string): Promise<void> {
async copy(address: string): Promise<void> {
let message = ''
await copyToClipboard(address || '')
.then(success => { message = success ? 'copied to clipboard!' : 'failed to copy' })
await copyToClipboard(address || '').then(success => {
message = success ? 'copied to clipboard!' : 'failed to copy'
})
const toast = await this.toastCtrl.create({
header: message,

View File

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

View File

@@ -34,16 +34,16 @@ export class AppListReorderComponent {
readonly connectionFailure$ = this.connectionService
.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.reorderingChange.emit(this.reordering)
}
reorder ({ detail }: CustomEvent<ItemReorderEventDetail>): void {
reorder({ detail }: CustomEvent<ItemReorderEventDetail>): void {
this.pkgs = detail.complete([...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 { IonicModule } from '@ionic/angular'
import { AppListPage } from './app-list.page'
import { StatusComponentModule } from 'src/app/components/status/status.component.module'
import { SharingModule } from 'src/app/modules/sharing.module'
import {
EmverPipesModule,
TextSpinnerComponentModule,
} from '@start9labs/shared'
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 { AppListEmptyComponent } from './app-list-empty/app-list-empty.component'
import { AppListPkgComponent } from './app-list-pkg/app-list-pkg.component'
@@ -24,7 +29,10 @@ const routes: Routes = [
imports: [
CommonModule,
StatusComponentModule,
SharingModule,
EmverPipesModule,
TextSpinnerComponentModule,
LaunchablePipeModule,
UiPipeModule,
IonicModule,
RouterModule.forChild(routes),
BadgeMenuComponentModule,
@@ -39,4 +47,4 @@ const routes: Routes = [
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 { Observable } from 'rxjs'
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 { parseDataModel, RecoveredInfo } from 'src/app/util/parse-data-model'
import { DestroyService } from 'src/app/services/destroy.service'
@Component({
selector: 'app-list',
@@ -20,21 +19,21 @@ export class AppListPage {
order: readonly string[] = []
reordering = false
constructor (
constructor(
private readonly api: ApiService,
private readonly destroy$: DestroyService,
public readonly patch: PatchDbService,
) { }
) {}
get empty (): boolean {
get empty(): boolean {
return !this.pkgs.length && isEmptyObject(this.recoveredPkgs)
}
ngOnInit () {
ngOnInit() {
this.patch
.watch$()
.pipe(
filter((data) => exists(data) && !isEmptyObject(data)),
filter(data => exists(data) && !isEmptyObject(data)),
take(1),
map(parseDataModel),
tap(({ order, pkgs, recoveredPkgs }) => {
@@ -53,7 +52,7 @@ export class AppListPage {
.subscribe()
}
onReordering (reordering: boolean): void {
onReordering(reordering: boolean): void {
if (!reordering) {
this.setOrder()
}
@@ -61,35 +60,33 @@ export class AppListPage {
this.reordering = reordering
}
deleteRecovered (rec: RecoveredInfo): void {
this.recoveredPkgs = this.recoveredPkgs.filter((item) => item !== rec)
deleteRecovered(rec: RecoveredInfo): void {
this.recoveredPkgs = this.recoveredPkgs.filter(item => item !== rec)
}
private watchNewlyRecovered (): Observable<unknown> {
private watchNewlyRecovered(): Observable<unknown> {
return this.patch.watch$('package-data').pipe(
filter((pkgs) => !!pkgs && Object.keys(pkgs).length !== this.pkgs.length),
tap((pkgs) => {
filter(pkgs => !!pkgs && Object.keys(pkgs).length !== this.pkgs.length),
tap(pkgs => {
const ids = Object.keys(pkgs)
const newIds = ids.filter(
(id) => !this.pkgs.find((pkg) => pkg.manifest.id === id),
id => !this.pkgs.find(pkg => pkg.manifest.id === id),
)
// remove uninstalled
const filtered = this.pkgs.filter((pkg) =>
ids.includes(pkg.manifest.id),
)
const filtered = this.pkgs.filter(pkg => ids.includes(pkg.manifest.id))
// 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.recoveredPkgs = this.recoveredPkgs.filter((rec) => !pkgs[rec.id])
this.recoveredPkgs = this.recoveredPkgs.filter(rec => !pkgs[rec.id])
}),
)
}
private setOrder (): void {
this.order = this.pkgs.map((pkg) => pkg.manifest.id)
private setOrder(): void {
this.order = this.pkgs.map(pkg => pkg.manifest.id)
this.api.setDbValue({ pointer: '/pkg-order', value: this.order })
}
}

View File

@@ -1,21 +1,21 @@
import { Pipe, PipeTransform } from "@angular/core";
import { Observable } from "rxjs";
import { filter, map, startWith } from "rxjs/operators";
import { PackageDataEntry } from "../../../services/patch-db/data-model";
import { getPackageInfo, PkgInfo } from "../../../util/get-package-info";
import { PatchDbService } from "../../../services/patch-db/patch-db.service";
import { Pipe, PipeTransform } from '@angular/core'
import { Observable } from 'rxjs'
import { filter, map, startWith } from 'rxjs/operators'
import { PackageDataEntry } from '../../../services/patch-db/data-model'
import { getPackageInfo, PkgInfo } from '../../../util/get-package-info'
import { PatchDbService } from '../../../services/patch-db/patch-db.service'
@Pipe({
name: "packageInfo",
name: 'packageInfo',
})
export class PackageInfoPipe implements PipeTransform {
constructor(private readonly patch: PatchDbService) {}
transform(pkg: PackageDataEntry): Observable<PkgInfo> {
return this.patch.watch$("package-data", pkg.manifest.id).pipe(
filter((v) => !!v),
return this.patch.watch$('package-data', pkg.manifest.id).pipe(
filter(v => !!v),
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 { IonicModule } from '@ionic/angular'
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'
const routes: Routes = [
@@ -18,9 +18,9 @@ const routes: Routes = [
CommonModule,
IonicModule,
RouterModule.forChild(routes),
SharingModule,
SharedPipesModule,
LogsPageModule,
],
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 { IonicModule } from '@ionic/angular'
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'
const routes: Routes = [
@@ -18,9 +18,9 @@ const routes: Routes = [
CommonModule,
IonicModule,
RouterModule.forChild(routes),
SharingModule,
SharedPipesModule,
SkeletonListComponentModule,
],
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 { ErrorToastService } from 'src/app/services/error-toast.service'
import { MainStatus } from 'src/app/services/patch-db/data-model'
import { pauseFor } from 'src/app/util/misc.util'
import { pauseFor } from '@start9labs/shared'
@Component({
selector: 'app-metrics',
@@ -23,26 +23,26 @@ export class AppMetricsPage {
@ViewChild(IonContent) content: IonContent
constructor (
constructor(
private readonly route: ActivatedRoute,
private readonly errToast: ErrorToastService,
private readonly embassyApi: ApiService,
) { }
) {}
ngOnInit () {
ngOnInit() {
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
this.startDaemon()
}
ngAfterViewInit () {
ngAfterViewInit() {
this.content.scrollToPoint(undefined, 1)
}
ngOnDestroy () {
ngOnDestroy() {
this.stopDaemon()
}
async startDaemon (): Promise<void> {
async startDaemon(): Promise<void> {
this.going = true
while (this.going) {
const startTime = Date.now()
@@ -51,13 +51,13 @@ export class AppMetricsPage {
}
}
stopDaemon () {
stopDaemon() {
this.going = false
}
async getMetrics (): Promise<void> {
async getMetrics(): Promise<void> {
try {
this.metrics = await this.embassyApi.getPkgMetrics({ id: this.pkgId})
this.metrics = await this.embassyApi.getPkgMetrics({ id: this.pkgId })
} catch (e) {
this.errToast.present(e)
this.stopDaemon()
@@ -66,7 +66,7 @@ export class AppMetricsPage {
}
}
asIsOrder (a: any, b: any) {
asIsOrder(a: any, b: any) {
return 0
}
}

View File

@@ -4,7 +4,11 @@ import { Routes, RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular'
import { AppPropertiesPage } from './app-properties.page'
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 = [
{
@@ -19,8 +23,10 @@ const routes: Routes = [
IonicModule,
RouterModule.forChild(routes),
QRComponentModule,
SharingModule,
SharedPipesModule,
TextSpinnerComponentModule,
MaskPipeModule,
],
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 { Subscription } from 'rxjs'
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 { QRComponent } from 'src/app/components/qr/qr.component'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
@@ -22,13 +28,13 @@ export class AppPropertiesPage {
pointer: string
properties: PackageProperties
node: PackageProperties
unmasked: { [key: string]: boolean } = { }
unmasked: { [key: string]: boolean } = {}
running = true
@ViewChild(IonContent) content: IonContent
subs: Subscription[] = []
constructor (
constructor(
private readonly route: ActivatedRoute,
private readonly embassyApi: ApiService,
private readonly errToast: ErrorToastService,
@@ -37,40 +43,50 @@ export class AppPropertiesPage {
private readonly modalCtrl: ModalController,
private readonly navCtrl: NavController,
private readonly patch: PatchDbService,
) { }
) {}
async ngOnInit () {
async ngOnInit() {
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
await this.getProperties()
this.subs = [
this.route.queryParams
.subscribe(queryParams => {
this.route.queryParams.subscribe(queryParams => {
if (queryParams['pointer'] === this.pointer) return
this.pointer = queryParams['pointer']
this.node = getValueByPointer(this.properties, this.pointer || '')
}),
this.patch.watch$('package-data', this.pkgId, 'installed', 'status', 'main', 'status')
.subscribe(status => {
this.running = status === PackageMainStatus.Running
}),
this.patch
.watch$(
'package-data',
this.pkgId,
'installed',
'status',
'main',
'status',
)
.subscribe(status => {
this.running = status === PackageMainStatus.Running
}),
]
}
ngAfterViewInit () {
ngAfterViewInit() {
this.content.scrollToPoint(undefined, 1)
}
ngOnDestroy () {
ngOnDestroy() {
this.subs.forEach(sub => sub.unsubscribe())
}
async refresh () {
async refresh() {
await this.getProperties()
}
async presentDescription (property: { key: string, value: PackageProperties[''] }, e: Event) {
async presentDescription(
property: { key: string; value: PackageProperties[''] },
e: Event,
) {
e.stopPropagation()
const alert = await this.alertCtrl.create({
@@ -80,7 +96,7 @@ export class AppPropertiesPage {
await alert.present()
}
async goToNested (key: string): Promise<any> {
async goToNested(key: string): Promise<any> {
this.navCtrl.navigateForward(`/services/${this.pkgId}/properties`, {
queryParams: {
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 = ''
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({
header: message,
@@ -100,7 +118,7 @@ export class AppPropertiesPage {
await toast.present()
}
async showQR (text: string): Promise<void> {
async showQR(text: string): Promise<void> {
const modal = await this.modalCtrl.create({
component: QRComponent,
componentProps: {
@@ -111,14 +129,16 @@ export class AppPropertiesPage {
await modal.present()
}
toggleMask (key: string) {
toggleMask(key: string) {
this.unmasked[key] = !this.unmasked[key]
}
private async getProperties (): Promise<void> {
private async getProperties(): Promise<void> {
this.loading = true
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 || '')
} catch (e) {
this.errToast.present(e)
@@ -127,7 +147,7 @@ export class AppPropertiesPage {
}
}
asIsOrder (a: any, b: any) {
asIsOrder(a: any, b: any) {
return 0
}
}

View File

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

View File

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

View File

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

View File

@@ -1,17 +1,16 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { UiLauncherService } from 'src/app/services/ui-launcher.service'
import {
InterfaceDef,
PackageDataEntry,
PackageState,
Status,
} from 'src/app/services/patch-db/data-model'
import {
PackageStatus,
PrimaryRendering,
PrimaryStatus,
} 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 {
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
{
action: () => this.navCtrl.navigateForward([`marketplace/${pkg.manifest.id}`]),
action: () =>
this.navCtrl.navigateForward([`marketplace/${pkg.manifest.id}`]),
title: 'Marketplace',
description: 'View service in marketplace',
icon: 'storefront-outline',

View File

@@ -3,12 +3,12 @@ import { NavigationExtras } from '@angular/router'
import { NavController } from '@ionic/angular'
import { combineLatest, Observable } from 'rxjs'
import { filter, map, startWith } from 'rxjs/operators'
import { DependentInfo, exists } from 'src/app/util/misc.util'
import {
DependencyError,
DependencyErrorType,
PackageDataEntry,
} 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 { 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 {
HealthCheckResult,
PackageDataEntry,
PackageMainStatus,
} 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 { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { Observable } from 'rxjs'
@@ -30,7 +30,8 @@ export class ToHealthChecksPipe implements PipeTransform {
map(main => {
// Question: is this ok or do we have to use Object.keys
// 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
: healthChecks
}),

View File

@@ -4,7 +4,6 @@ import { IonicModule } from '@ionic/angular'
import { RouterModule, Routes } from '@angular/router'
import { DevConfigPage } from './dev-config.page'
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 { FormsModule } from '@angular/forms'
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'
@@ -22,7 +21,6 @@ const routes: Routes = [
IonicModule,
RouterModule.forChild(routes),
BadgeMenuComponentModule,
SharingModule,
BackupReportPageModule,
FormsModule,
MonacoEditorModule,

View File

@@ -4,7 +4,6 @@ import { IonicModule } from '@ionic/angular'
import { RouterModule, Routes } from '@angular/router'
import { DevInstructionsPage } from './dev-instructions.page'
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 { FormsModule } from '@angular/forms'
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'
@@ -22,7 +21,6 @@ const routes: Routes = [
IonicModule,
RouterModule.forChild(routes),
BadgeMenuComponentModule,
SharingModule,
BackupReportPageModule,
FormsModule,
MonacoEditorModule,

View File

@@ -4,7 +4,6 @@ import { IonicModule } from '@ionic/angular'
import { RouterModule, Routes } from '@angular/router'
import { DeveloperPage } from './developer-list.page'
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'
const routes: Routes = [
@@ -20,7 +19,6 @@ const routes: Routes = [
IonicModule,
RouterModule.forChild(routes),
BadgeMenuComponentModule,
SharingModule,
BackupReportPageModule,
],
declarations: [DeveloperPage],

View File

@@ -4,7 +4,7 @@ import { CommonModule } from '@angular/common'
import { FormsModule } from '@angular/forms'
import { IonicModule } from '@ionic/angular'
import { LoginPage } from './login.page'
import { SharingModule } from 'src/app/modules/sharing.module'
import { SharedPipesModule } from '@start9labs/shared'
const routes: Routes = [
{
@@ -19,8 +19,8 @@ const routes: Routes = [
FormsModule,
IonicModule,
RouterModule.forChild(routes),
SharingModule,
SharedPipesModule,
],
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 { Routes, RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular'
import {
EmverPipesModule,
MarkdownPipeModule,
TextSpinnerComponentModule,
} from '@start9labs/shared'
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 = [
{
@@ -17,8 +22,11 @@ const routes: Routes = [
CommonModule,
IonicModule,
RouterModule.forChild(routes),
SharingModule,
TextSpinnerComponentModule,
EmverPipesModule,
MarkdownPipeModule,
MarketplacePipesModule,
],
declarations: [AppReleaseNotes],
})
export class ReleaseNotesModule { }
export class ReleaseNotesModule {}

View File

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

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