mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
Feature/shared refactor (#1176)
* refactor: move most of the shared entities to @start8labs/shared library
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 { }
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
.wrapper {
|
||||
position: relative;
|
||||
margin-right: 1vh;
|
||||
}
|
||||
|
||||
.md-badge {
|
||||
background-color: var(--ion-color-danger);
|
||||
position: absolute;
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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
|
||||
}...`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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
|
||||
}`,
|
||||
),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<ion-grid style="height: 100%;">
|
||||
<ion-row class="ion-align-items-center ion-text-center" style="height: 100%;">
|
||||
<ion-col>
|
||||
<ion-spinner name="lines" color="warning"></ion-spinner>
|
||||
<p>{{ text }}</p>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
@@ -1,18 +0,0 @@
|
||||
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'
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
TextSpinnerComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild([]),
|
||||
],
|
||||
exports: [TextSpinnerComponent],
|
||||
})
|
||||
export class TextSpinnerComponentModule { }
|
||||
@@ -1,10 +0,0 @@
|
||||
import { Component, Input } from '@angular/core'
|
||||
|
||||
@Component({
|
||||
selector: 'text-spinner',
|
||||
templateUrl: './text-spinner.component.html',
|
||||
styleUrls: ['./text-spinner.component.scss'],
|
||||
})
|
||||
export class TextSpinnerComponent {
|
||||
@Input() text: string
|
||||
}
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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 {}
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'])
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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
|
||||
}),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
<ion-col *ngFor="let pkg of pkgs" sizeXs="12" sizeSm="12" sizeMd="6">
|
||||
<ion-item [routerLink]="['/marketplace', pkg.manifest.id]">
|
||||
<ion-thumbnail slot="start">
|
||||
<img [src]="('data:image/png;base64,' + pkg.icon) | sanitize" />
|
||||
<img [src]="('data:image/png;base64,' + pkg.icon) | trust" />
|
||||
</ion-thumbnail>
|
||||
<ion-label>
|
||||
<h2 style="font-family: 'Montserrat'; font-weight: bold">
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { Component, ViewChild } from '@angular/core'
|
||||
import { MarketplacePkg } from 'src/app/services/api/api.types'
|
||||
import { IonContent } from '@ionic/angular'
|
||||
import {
|
||||
PackageDataEntry,
|
||||
PackageState,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
import { MarketplaceService } from '../marketplace.service'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import Fuse from 'fuse.js/dist/fuse.min.js'
|
||||
import { exists, isEmptyObject } from 'src/app/util/misc.util'
|
||||
import { exists, isEmptyObject, PackageState } from '@start9labs/shared'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import { filter, first } from 'rxjs/operators'
|
||||
|
||||
const defaultOps = {
|
||||
@@ -46,7 +43,7 @@ export class MarketplaceListPage {
|
||||
|
||||
pkgs: MarketplacePkg[] = []
|
||||
categories: string[]
|
||||
localPkgs: { [id: string]: PackageDataEntry } = {}
|
||||
localPkgs: Record<string, PackageDataEntry> = {}
|
||||
category = 'featured'
|
||||
query: string
|
||||
loading = true
|
||||
|
||||
@@ -3,9 +3,14 @@ import { CommonModule } from '@angular/common'
|
||||
import { Routes, RouterModule } from '@angular/router'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { MarketplaceShowPage } from './marketplace-show.page'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { StatusComponentModule } from 'src/app/components/status/status.component.module'
|
||||
import {
|
||||
SharedPipesModule,
|
||||
EmverPipesModule,
|
||||
MarkdownPipeModule,
|
||||
TextSpinnerComponentModule,
|
||||
} from '@start9labs/shared'
|
||||
import { InstallWizardComponentModule } from 'src/app/components/install-wizard/install-wizard.component.module'
|
||||
import { MarketplacePipesModule } from '../pipes/marketplace-pipes.module'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -18,11 +23,14 @@ const routes: Routes = [
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
StatusComponentModule,
|
||||
RouterModule.forChild(routes),
|
||||
SharingModule,
|
||||
TextSpinnerComponentModule,
|
||||
SharedPipesModule,
|
||||
EmverPipesModule,
|
||||
MarkdownPipeModule,
|
||||
MarketplacePipesModule,
|
||||
InstallWizardComponentModule,
|
||||
],
|
||||
declarations: [MarketplaceShowPage],
|
||||
})
|
||||
export class MarketplaceShowPageModule { }
|
||||
export class MarketplaceShowPageModule {}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<ion-row>
|
||||
<ion-col sizeXs="12" sizeSm="12" sizeMd="9" sizeLg="9" sizeXl="9">
|
||||
<div class="header">
|
||||
<img [src]="('data:image/png;base64,' + pkg.icon) | sanitize" />
|
||||
<img [src]="('data:image/png;base64,' + pkg.icon) | trust" />
|
||||
<div class="header-text">
|
||||
<h1 class="header-title">{{ pkg.manifest.title }}</h1>
|
||||
<p class="header-version">
|
||||
@@ -198,7 +198,7 @@
|
||||
<ion-item [routerLink]="['/marketplace', dep.key]">
|
||||
<ion-thumbnail slot="start">
|
||||
<img
|
||||
[src]="('data:image/png;base64,' + pkg['dependency-metadata'][dep.key].icon) | sanitize"
|
||||
[src]="('data:image/png;base64,' + pkg['dependency-metadata'][dep.key].icon) | trust"
|
||||
/>
|
||||
</ion-thumbnail>
|
||||
<ion-label>
|
||||
|
||||
@@ -9,15 +9,16 @@ import {
|
||||
} from '@ionic/angular'
|
||||
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
||||
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||
import { Emver } from 'src/app/services/emver.service'
|
||||
import { displayEmver } from 'src/app/pipes/emver.pipe'
|
||||
import { DependentInfo, pauseFor } from 'src/app/util/misc.util'
|
||||
import {
|
||||
displayEmver,
|
||||
Emver,
|
||||
DependentInfo,
|
||||
pauseFor,
|
||||
PackageState,
|
||||
} from '@start9labs/shared'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
import {
|
||||
PackageDataEntry,
|
||||
PackageState,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { MarketplaceService } from '../marketplace.service'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { MarkdownPage } from 'src/app/modals/markdown/markdown.page'
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} 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 { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
|
||||
@@ -24,14 +24,14 @@ export class MarketplaceService {
|
||||
} = {}
|
||||
marketplaceUrl: string
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
private readonly api: ApiService,
|
||||
private readonly emver: Emver,
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly config: ConfigService,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
init (): Subscription {
|
||||
init(): Subscription {
|
||||
return this.patch.watch$('ui', 'marketplace').subscribe(marketplace => {
|
||||
if (!marketplace || !marketplace['selected-id']) {
|
||||
this.marketplaceUrl = this.config.marketplace.url
|
||||
@@ -42,7 +42,7 @@ export class MarketplaceService {
|
||||
})
|
||||
}
|
||||
|
||||
async load (): Promise<void> {
|
||||
async load(): Promise<void> {
|
||||
try {
|
||||
const [data, pkgs] = await Promise.all([
|
||||
this.getMarketplaceData({}),
|
||||
@@ -67,9 +67,9 @@ export class MarketplaceService {
|
||||
}
|
||||
}
|
||||
|
||||
async getUpdates (localPkgs: {
|
||||
[id: string]: PackageDataEntry
|
||||
}): Promise<MarketplacePkg[]> {
|
||||
async getUpdates(
|
||||
localPkgs: Record<string, PackageDataEntry>,
|
||||
): Promise<MarketplacePkg[]> {
|
||||
const id = this.patch.getData().ui.marketplace?.['selected-id']
|
||||
const url = id
|
||||
? this.patch.getData().ui.marketplace['known-hosts'][id].url
|
||||
@@ -95,7 +95,7 @@ export class MarketplaceService {
|
||||
})
|
||||
}
|
||||
|
||||
async getPkg (id: string, version = '*'): Promise<MarketplacePkg> {
|
||||
async getPkg(id: string, version = '*'): Promise<MarketplacePkg> {
|
||||
const pkgs = await this.getMarketplacePkgs({
|
||||
ids: [{ id, version }],
|
||||
})
|
||||
@@ -108,11 +108,11 @@ export class MarketplaceService {
|
||||
}
|
||||
}
|
||||
|
||||
async cacheReleaseNotes (id: string): Promise<void> {
|
||||
async cacheReleaseNotes(id: string): Promise<void> {
|
||||
this.releaseNotes[id] = await this.getReleaseNotes({ id })
|
||||
}
|
||||
|
||||
async getMarketplaceData (
|
||||
async getMarketplaceData(
|
||||
params: RR.GetMarketplaceDataReq,
|
||||
url?: string,
|
||||
): Promise<RR.GetMarketplaceDataRes> {
|
||||
@@ -120,7 +120,7 @@ export class MarketplaceService {
|
||||
return this.api.marketplaceProxy('/package/v0/info', params, url)
|
||||
}
|
||||
|
||||
async getMarketplacePkgs (
|
||||
async getMarketplacePkgs(
|
||||
params: Omit<RR.GetMarketplacePackagesReq, 'eos-version-compat'>,
|
||||
): Promise<RR.GetMarketplacePackagesRes> {
|
||||
if (params.query) delete params.category
|
||||
@@ -139,7 +139,7 @@ export class MarketplaceService {
|
||||
)
|
||||
}
|
||||
|
||||
async getReleaseNotes (
|
||||
async getReleaseNotes(
|
||||
params: RR.GetReleaseNotesReq,
|
||||
): Promise<RR.GetReleaseNotesRes> {
|
||||
return this.api.marketplaceProxy(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { InstallProgress } from '../services/patch-db/data-model'
|
||||
import { packageLoadingProgress } from '../util/package-loading-progress'
|
||||
import { InstallProgress, packageLoadingProgress } from '@start9labs/shared'
|
||||
|
||||
@Pipe({
|
||||
name: 'installProgress',
|
||||
@@ -0,0 +1,9 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { InstallProgressPipe } from './install-progress.pipe'
|
||||
import { TrustPipe } from './trust.pipe'
|
||||
|
||||
@NgModule({
|
||||
declarations: [InstallProgressPipe, TrustPipe],
|
||||
exports: [InstallProgressPipe, TrustPipe],
|
||||
})
|
||||
export class MarketplacePipesModule {}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'
|
||||
|
||||
@Pipe({
|
||||
name: 'trust',
|
||||
})
|
||||
export class TrustPipe implements PipeTransform {
|
||||
constructor(public readonly sanitizer: DomSanitizer) {}
|
||||
|
||||
transform(base64Icon: string): SafeResourceUrl {
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl(base64Icon)
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { NotificationsPage } from './notifications.page'
|
||||
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
import { BackupReportPageModule } from 'src/app/modals/backup-report/backup-report.module'
|
||||
|
||||
const routes: Routes = [
|
||||
@@ -20,9 +20,9 @@ const routes: Routes = [
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
BadgeMenuComponentModule,
|
||||
SharingModule,
|
||||
SharedPipesModule,
|
||||
BackupReportPageModule,
|
||||
],
|
||||
declarations: [NotificationsPage],
|
||||
})
|
||||
export class NotificationsPageModule { }
|
||||
export class NotificationsPageModule {}
|
||||
|
||||
@@ -78,14 +78,10 @@
|
||||
<span *ngIf="not['package-id']"
|
||||
>{{ not['package-id'] }} -
|
||||
</span>
|
||||
<ion-text [color]="not | notificationColor"
|
||||
>{{ not.title }}</ion-text
|
||||
>
|
||||
<ion-text [color]="getColor(not)"> {{ not.title }} </ion-text>
|
||||
</b>
|
||||
</h2>
|
||||
<h2 class="notification-message">
|
||||
{{ not.message | truncateTail: 240 }}
|
||||
</h2>
|
||||
<h2 class="notification-message">{{ truncate(not.message) }}</h2>
|
||||
<p class="view-message-tag">
|
||||
<a
|
||||
class="view-message-tag"
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ServerNotification, ServerNotifications } from 'src/app/services/api/api.types'
|
||||
import { AlertController, LoadingController, ModalController } from '@ionic/angular'
|
||||
import {
|
||||
ServerNotifications,
|
||||
NotificationLevel,
|
||||
ServerNotification,
|
||||
} from 'src/app/services/api/api.types'
|
||||
import {
|
||||
AlertController,
|
||||
LoadingController,
|
||||
ModalController,
|
||||
} from '@ionic/angular'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
import { BackupReportPage } from 'src/app/modals/backup-report/backup-report.page'
|
||||
@@ -19,31 +27,34 @@ export class NotificationsPage {
|
||||
fromToast = false
|
||||
readonly perPage = 40
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly route: ActivatedRoute,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
async ngOnInit () {
|
||||
async ngOnInit() {
|
||||
this.fromToast = !!this.route.snapshot.queryParamMap.get('toast')
|
||||
this.notifications = await this.getNotifications()
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
async doInfinite (e: any) {
|
||||
async doInfinite(e: any) {
|
||||
const notifications = await this.getNotifications()
|
||||
this.notifications = this.notifications.concat(notifications)
|
||||
e.target.complete()
|
||||
}
|
||||
|
||||
async getNotifications (): Promise<ServerNotifications> {
|
||||
async getNotifications(): Promise<ServerNotifications> {
|
||||
let notifications: ServerNotifications = []
|
||||
try {
|
||||
notifications = await this.embassyApi.getNotifications({ before: this.beforeCursor, limit: this.perPage })
|
||||
notifications = await this.embassyApi.getNotifications({
|
||||
before: this.beforeCursor,
|
||||
limit: this.perPage,
|
||||
})
|
||||
this.beforeCursor = notifications[notifications.length - 1]?.id
|
||||
this.needInfinite = notifications.length >= this.perPage
|
||||
} catch (e) {
|
||||
@@ -53,7 +64,7 @@ export class NotificationsPage {
|
||||
}
|
||||
}
|
||||
|
||||
async delete (id: number, index: number): Promise<void> {
|
||||
async delete(id: number, index: number): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Deleting...',
|
||||
@@ -72,7 +83,7 @@ export class NotificationsPage {
|
||||
}
|
||||
}
|
||||
|
||||
async presentAlertDeleteAll () {
|
||||
async presentAlertDeleteAll() {
|
||||
const alert = await this.alertCtrl.create({
|
||||
backdropDismiss: false,
|
||||
header: 'Delete All?',
|
||||
@@ -94,7 +105,7 @@ export class NotificationsPage {
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
async viewBackupReport (notification: ServerNotification<1>) {
|
||||
async viewBackupReport(notification: ServerNotification<1>) {
|
||||
const modal = await this.modalCtrl.create({
|
||||
component: BackupReportPage,
|
||||
componentProps: {
|
||||
@@ -105,7 +116,7 @@ export class NotificationsPage {
|
||||
await modal.present()
|
||||
}
|
||||
|
||||
async viewFullMessage (title: string, message: string) {
|
||||
async viewFullMessage(title: string, message: string) {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: title,
|
||||
message: message,
|
||||
@@ -123,7 +134,26 @@ export class NotificationsPage {
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
private async deleteAll (): Promise<void> {
|
||||
truncate(message: string): string {
|
||||
return message.length <= 240 ? message : '...' + message.substr(-240)
|
||||
}
|
||||
|
||||
getColor({ level }: ServerNotification<number>): string {
|
||||
switch (level) {
|
||||
case NotificationLevel.Info:
|
||||
return 'primary'
|
||||
case NotificationLevel.Success:
|
||||
return 'success'
|
||||
case NotificationLevel.Warning:
|
||||
return 'warning'
|
||||
case NotificationLevel.Error:
|
||||
return 'danger'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
private async deleteAll(): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Deleting...',
|
||||
@@ -132,7 +162,9 @@ export class NotificationsPage {
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.embassyApi.deleteAllNotifications({ before: this.notifications[0].id })
|
||||
await this.embassyApi.deleteAllNotifications({
|
||||
before: this.notifications[0].id,
|
||||
})
|
||||
this.notifications = []
|
||||
this.beforeCursor = undefined
|
||||
} catch (e) {
|
||||
@@ -142,4 +174,3 @@ export class NotificationsPage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'
|
||||
import { Routes, RouterModule } from '@angular/router'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { LANPage } from './lan.page'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -17,8 +17,8 @@ const routes: Routes = [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
SharingModule,
|
||||
SharedPipesModule,
|
||||
],
|
||||
declarations: [LANPage],
|
||||
})
|
||||
export class LANPageModule { }
|
||||
export class LANPageModule {}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { MarketplacesPage } from './marketplaces.page'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -17,7 +17,7 @@ const routes: Routes = [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
SharingModule,
|
||||
SharedPipesModule,
|
||||
],
|
||||
declarations: [MarketplacesPage],
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { PreferencesPage } from './preferences.page'
|
||||
import { Routes, RouterModule } from '@angular/router'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -17,10 +17,8 @@ const routes: Routes = [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
SharingModule,
|
||||
],
|
||||
declarations: [
|
||||
PreferencesPage,
|
||||
SharedPipesModule,
|
||||
],
|
||||
declarations: [PreferencesPage],
|
||||
})
|
||||
export class PreferencesPageModule { }
|
||||
export class PreferencesPageModule {}
|
||||
|
||||
@@ -24,7 +24,7 @@ export class PreferencesPage {
|
||||
defaultName: string
|
||||
clicks = 0
|
||||
|
||||
constructor(
|
||||
constructor (
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly api: ApiService,
|
||||
@@ -32,17 +32,17 @@ export class PreferencesPage {
|
||||
private readonly localStorageService: LocalStorageService,
|
||||
public readonly serverConfig: ServerConfigService,
|
||||
public readonly patch: PatchDbService,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
ngOnInit () {
|
||||
this.defaultName = `Embassy-${this.patch.getData()['server-info'].id}`
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
ngAfterViewInit () {
|
||||
this.content.scrollToPoint(undefined, 1)
|
||||
}
|
||||
|
||||
async presentModalName(): Promise<void> {
|
||||
async presentModalName (): Promise<void> {
|
||||
const options: GenericInputOptions = {
|
||||
title: 'Edit Device Name',
|
||||
message: 'This is for your reference only.',
|
||||
@@ -66,7 +66,7 @@ export class PreferencesPage {
|
||||
await modal.present()
|
||||
}
|
||||
|
||||
async setDbValue(key: string, value: any): Promise<void> {
|
||||
private async setDbValue (key: string, value: string): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Saving...',
|
||||
@@ -81,7 +81,7 @@ export class PreferencesPage {
|
||||
}
|
||||
}
|
||||
|
||||
async addClick() {
|
||||
async addClick () {
|
||||
this.clicks++
|
||||
if (this.clicks >= 5) {
|
||||
this.clicks = 0
|
||||
|
||||
@@ -3,7 +3,7 @@ import { RouterModule, Routes } from '@angular/router'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RestorePage } from './restore.component'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
import { BackupDrivesComponentModule } from 'src/app/components/backup-drives/backup-drives.component.module'
|
||||
import { AppRecoverSelectPageModule } from 'src/app/modals/app-recover-select/app-recover-select.module'
|
||||
|
||||
@@ -19,12 +19,10 @@ const routes: Routes = [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
SharingModule,
|
||||
SharedPipesModule,
|
||||
BackupDrivesComponentModule,
|
||||
AppRecoverSelectPageModule,
|
||||
],
|
||||
declarations: [
|
||||
RestorePage,
|
||||
],
|
||||
declarations: [RestorePage],
|
||||
})
|
||||
export class RestorePageModule { }
|
||||
export class RestorePageModule {}
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ModalController, NavController } from '@ionic/angular'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { GenericInputComponent, GenericInputOptions } from 'src/app/modals/generic-input/generic-input.component'
|
||||
import { MappedBackupTarget } from 'src/app/util/misc.util'
|
||||
import { BackupInfo, CifsBackupTarget, DiskBackupTarget } from 'src/app/services/api/api.types'
|
||||
import {
|
||||
GenericInputComponent,
|
||||
GenericInputOptions,
|
||||
} from 'src/app/modals/generic-input/generic-input.component'
|
||||
import { MappedBackupTarget } from 'src/app/types/mapped-backup-target'
|
||||
import {
|
||||
BackupInfo,
|
||||
CifsBackupTarget,
|
||||
DiskBackupTarget,
|
||||
} from 'src/app/services/api/api.types'
|
||||
import { AppRecoverSelectPage } from 'src/app/modals/app-recover-select/app-recover-select.page'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import * as argon2 from '@start9labs/argon2'
|
||||
@@ -14,18 +21,20 @@ import * as argon2 from '@start9labs/argon2'
|
||||
styleUrls: ['./restore.component.scss'],
|
||||
})
|
||||
export class RestorePage {
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly navCtrl: NavController,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly patch: PatchDbService,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
async presentModalPassword (target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>): Promise<void> {
|
||||
async presentModalPassword(
|
||||
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
|
||||
): Promise<void> {
|
||||
const options: GenericInputOptions = {
|
||||
title: 'Master Password Required',
|
||||
message: 'Enter your master password. On the next screen, you will select the individual services you want to restore.',
|
||||
message:
|
||||
'Enter your master password. On the next screen, you will select the individual services you want to restore.',
|
||||
label: 'Master Password',
|
||||
placeholder: 'Enter master password',
|
||||
useMask: true,
|
||||
@@ -43,7 +52,10 @@ export class RestorePage {
|
||||
await modal.present()
|
||||
}
|
||||
|
||||
private async decryptDrive (target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>, password: string): Promise<void> {
|
||||
private async decryptDrive(
|
||||
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
|
||||
password: string,
|
||||
): Promise<void> {
|
||||
const passwordHash = this.patch.getData()['server-info']['password-hash']
|
||||
argon2.verify(passwordHash, password)
|
||||
|
||||
@@ -55,15 +67,20 @@ export class RestorePage {
|
||||
}
|
||||
}
|
||||
|
||||
private async presentModalOldPassword (target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>, password: string): Promise<void> {
|
||||
private async presentModalOldPassword(
|
||||
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
|
||||
password: string,
|
||||
): Promise<void> {
|
||||
const options: GenericInputOptions = {
|
||||
title: 'Original Password Needed',
|
||||
message: 'This backup was created with a different password. Enter the ORIGINAL password that was used to encrypt this backup.',
|
||||
message:
|
||||
'This backup was created with a different password. Enter the ORIGINAL password that was used to encrypt this backup.',
|
||||
label: 'Original Password',
|
||||
placeholder: 'Enter original password',
|
||||
useMask: true,
|
||||
buttonText: 'Restore From Backup',
|
||||
submitFn: (oldPassword: string) => this.restoreFromBackup(target, password, oldPassword),
|
||||
submitFn: (oldPassword: string) =>
|
||||
this.restoreFromBackup(target, password, oldPassword),
|
||||
}
|
||||
|
||||
const m = await this.modalCtrl.create({
|
||||
@@ -76,7 +93,11 @@ export class RestorePage {
|
||||
await m.present()
|
||||
}
|
||||
|
||||
private async restoreFromBackup (target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>, password: string, oldPassword?: string): Promise<void> {
|
||||
private async restoreFromBackup(
|
||||
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
|
||||
password: string,
|
||||
oldPassword?: string,
|
||||
): Promise<void> {
|
||||
const backupInfo = await this.embassyApi.getBackupInfo({
|
||||
'target-id': target.id,
|
||||
password,
|
||||
@@ -84,7 +105,12 @@ export class RestorePage {
|
||||
this.presentModalSelect(target.id, backupInfo, password, oldPassword)
|
||||
}
|
||||
|
||||
private async presentModalSelect (id: string, backupInfo: BackupInfo, password: string, oldPassword?: string): Promise<void> {
|
||||
private async presentModalSelect(
|
||||
id: string,
|
||||
backupInfo: BackupInfo,
|
||||
password: string,
|
||||
oldPassword?: string,
|
||||
): Promise<void> {
|
||||
const modal = await this.modalCtrl.create({
|
||||
componentProps: {
|
||||
id,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { IonicModule } from '@ionic/angular'
|
||||
import { ServerBackupPage } from './server-backup.page'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { BackupDrivesComponentModule } from 'src/app/components/backup-drives/backup-drives.component.module'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -18,11 +18,9 @@ const routes: Routes = [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
SharingModule,
|
||||
SharedPipesModule,
|
||||
BackupDrivesComponentModule,
|
||||
],
|
||||
declarations: [
|
||||
ServerBackupPage,
|
||||
],
|
||||
declarations: [ServerBackupPage],
|
||||
})
|
||||
export class ServerBackupPageModule { }
|
||||
export class ServerBackupPageModule {}
|
||||
|
||||
@@ -10,14 +10,13 @@ import {
|
||||
GenericInputOptions,
|
||||
} from 'src/app/modals/generic-input/generic-input.component'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { take } from 'rxjs/operators'
|
||||
import { MappedBackupTarget } from 'src/app/types/mapped-backup-target'
|
||||
import {
|
||||
PackageDataEntry,
|
||||
PackageMainStatus,
|
||||
ServerStatus,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { take } from 'rxjs/operators'
|
||||
import { MappedBackupTarget } from 'src/app/util/misc.util'
|
||||
import * as argon2 from '@start9labs/argon2'
|
||||
import {
|
||||
CifsBackupTarget,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'
|
||||
import { Routes, RouterModule } from '@angular/router'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { ServerLogsPage } from './server-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: [ServerLogsPage],
|
||||
})
|
||||
export class ServerLogsPageModule { }
|
||||
export class ServerLogsPageModule {}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Routes, RouterModule } from '@angular/router'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { ServerMetricsPage } from './server-metrics.page'
|
||||
import { SkeletonListComponentModule } from 'src/app/components/skeleton-list/skeleton-list.component.module'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -19,8 +19,8 @@ const routes: Routes = [
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
SkeletonListComponentModule,
|
||||
SharingModule,
|
||||
SharedPipesModule,
|
||||
],
|
||||
declarations: [ServerMetricsPage],
|
||||
})
|
||||
export class ServerMetricsPageModule { }
|
||||
export class ServerMetricsPageModule {}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component } from '@angular/core'
|
||||
import { Metrics } 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 { pauseFor } from 'src/app/util/misc.util'
|
||||
import { pauseFor } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
selector: 'server-metrics',
|
||||
@@ -12,14 +12,14 @@ import { pauseFor } from 'src/app/util/misc.util'
|
||||
export class ServerMetricsPage {
|
||||
loading = true
|
||||
going = false
|
||||
metrics: Metrics = { }
|
||||
metrics: Metrics = {}
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly embassyApi: ApiService,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
async ngOnInit () {
|
||||
async ngOnInit() {
|
||||
await this.getMetrics()
|
||||
let headersCount = 0
|
||||
let rowsCount = 0
|
||||
@@ -36,11 +36,11 @@ export class ServerMetricsPage {
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
ngOnDestroy() {
|
||||
this.stopDaemon()
|
||||
}
|
||||
|
||||
private async startDaemon (): Promise<void> {
|
||||
private async startDaemon(): Promise<void> {
|
||||
this.going = true
|
||||
while (this.going) {
|
||||
const startTime = Date.now()
|
||||
@@ -49,20 +49,20 @@ export class ServerMetricsPage {
|
||||
}
|
||||
}
|
||||
|
||||
private stopDaemon () {
|
||||
private stopDaemon() {
|
||||
this.going = false
|
||||
}
|
||||
|
||||
private async getMetrics (): Promise<void> {
|
||||
private async getMetrics(): Promise<void> {
|
||||
try {
|
||||
this.metrics = await this.embassyApi.getServerMetrics({ })
|
||||
this.metrics = await this.embassyApi.getServerMetrics({})
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
this.stopDaemon()
|
||||
}
|
||||
}
|
||||
|
||||
asIsOrder (a: any, b: any) {
|
||||
asIsOrder(a: any, b: any) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@ import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { ServerShowPage } from './server-show.page'
|
||||
import { StatusComponentModule } from 'src/app/components/status/status.component.module'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { TextSpinnerComponentModule } from '@start9labs/shared'
|
||||
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
||||
|
||||
const routes: Routes = [
|
||||
@@ -19,12 +18,11 @@ const routes: Routes = [
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
StatusComponentModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
SharingModule,
|
||||
TextSpinnerComponentModule,
|
||||
BadgeMenuComponentModule,
|
||||
],
|
||||
declarations: [ServerShowPage],
|
||||
})
|
||||
export class ServerShowPageModule { }
|
||||
export class ServerShowPageModule {}
|
||||
|
||||
@@ -10,13 +10,13 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
import { ServerStatus } from 'src/app/services/patch-db/data-model'
|
||||
import { Observable, of } from 'rxjs'
|
||||
import { filter, map, take } from 'rxjs/operators'
|
||||
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
||||
import { exists, isEmptyObject } from 'src/app/util/misc.util'
|
||||
import { exists, isEmptyObject } from '@start9labs/shared'
|
||||
import { EOSService } from 'src/app/services/eos.service'
|
||||
import { ServerStatus } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Component({
|
||||
selector: 'server-show',
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'
|
||||
import { Routes, RouterModule } from '@angular/router'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { ServerSpecsPage } from './server-specs.page'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { EmverPipesModule } from '@start9labs/shared'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -17,8 +17,8 @@ const routes: Routes = [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
SharingModule,
|
||||
EmverPipesModule,
|
||||
],
|
||||
declarations: [ServerSpecsPage],
|
||||
})
|
||||
export class ServerSpecsPageModule { }
|
||||
export class ServerSpecsPageModule {}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { SessionsPage } from './sessions.page'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -17,8 +17,8 @@ const routes: Routes = [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
SharingModule,
|
||||
SharedPipesModule,
|
||||
],
|
||||
declarations: [SessionsPage],
|
||||
})
|
||||
export class SessionsPageModule { }
|
||||
export class SessionsPageModule {}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { SSHKeysPage } from './ssh-keys.page'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -17,8 +17,8 @@ const routes: Routes = [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
SharingModule,
|
||||
SharedPipesModule,
|
||||
],
|
||||
declarations: [SSHKeysPage],
|
||||
})
|
||||
export class SSHKeysPageModule { }
|
||||
export class SSHKeysPageModule {}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { WifiPage } from './wifi.page'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { SharedPipesModule } from '@start9labs/shared'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -17,8 +17,8 @@ const routes: Routes = [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
SharingModule,
|
||||
SharedPipesModule,
|
||||
],
|
||||
declarations: [WifiPage],
|
||||
})
|
||||
export class WifiPageModule { }
|
||||
export class WifiPageModule {}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { ActionSheetButton } from '@ionic/core'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
import { ValueSpecObject } from 'src/app/pkg-config/config-types'
|
||||
import { RR } from 'src/app/services/api/api.types'
|
||||
import { pauseFor } from 'src/app/util/misc.util'
|
||||
import { pauseFor } from '@start9labs/shared'
|
||||
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { isEmptyObject } from '../util/misc.util'
|
||||
|
||||
@Pipe({
|
||||
name: 'empty',
|
||||
})
|
||||
export class EmptyPipe implements PipeTransform {
|
||||
transform (val: object | [] = { }): boolean {
|
||||
if (Array.isArray(val)) return !val.length
|
||||
return isEmptyObject(val)
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { Emver } from '../services/emver.service'
|
||||
@Pipe({
|
||||
name: 'satisfiesEmver',
|
||||
})
|
||||
export class EmverSatisfiesPipe implements PipeTransform {
|
||||
constructor (private readonly emver: Emver) { }
|
||||
|
||||
transform (versionUnderTest: string, range: string): boolean {
|
||||
return this.emver.satisfies(versionUnderTest, range)
|
||||
}
|
||||
}
|
||||
|
||||
@Pipe({
|
||||
name: 'compareEmver',
|
||||
})
|
||||
export class EmverComparesPipe implements PipeTransform {
|
||||
constructor (private readonly emver: Emver) { }
|
||||
|
||||
transform (first: string, second: string): SemverResult {
|
||||
try {
|
||||
return this.emver.compare(first, second) as SemverResult
|
||||
} catch (e) {
|
||||
console.warn(`emver comparison failed`, e, first, second)
|
||||
return 'comparison-impossible'
|
||||
}
|
||||
}
|
||||
}
|
||||
type SemverResult = 0 | 1 | -1 | 'comparison-impossible'
|
||||
|
||||
@Pipe({
|
||||
name: 'displayEmver',
|
||||
})
|
||||
export class EmverDisplayPipe implements PipeTransform {
|
||||
constructor () { }
|
||||
|
||||
transform (version: string): string {
|
||||
return displayEmver(version)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
|
||||
@Pipe({
|
||||
name: 'includes',
|
||||
})
|
||||
export class IncludesPipe implements PipeTransform {
|
||||
transform<T> (list: T[], val: T): boolean {
|
||||
return list.includes(val)
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { PackageDataEntry } from '../services/patch-db/data-model'
|
||||
import {
|
||||
packageLoadingProgress,
|
||||
ProgressData,
|
||||
} from '../util/package-loading-progress'
|
||||
|
||||
@Pipe({
|
||||
name: 'installState',
|
||||
})
|
||||
export class InstallState implements PipeTransform {
|
||||
transform(pkg: PackageDataEntry): ProgressData | null {
|
||||
return packageLoadingProgress(pkg['install-progress'])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { LaunchablePipe } from './launchable.pipe'
|
||||
|
||||
@NgModule({
|
||||
declarations: [LaunchablePipe],
|
||||
exports: [LaunchablePipe],
|
||||
})
|
||||
export class LaunchablePipeModule {}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user