mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-02 05:23:14 +00:00
start9 marketplace config
shift not unshift move eos updates to embassy tab selected id sub roughly working keep name in sync in case of change delete commented code 64 img
This commit is contained in:
committed by
Aiden McClelland
parent
2d4ecd3096
commit
0c0cd9d0a0
@@ -15,9 +15,14 @@
|
||||
|
||||
<!-- not loading -->
|
||||
<ng-template #data>
|
||||
<h1 style="font-family: 'Montserrat'; font-size: 42px; margin: 32px 0;" class="ion-text-center">Embassy Marketplace</h1>
|
||||
<h1
|
||||
style="font-family: 'Montserrat'; font-size: 42px; margin: 32px 0"
|
||||
class="ion-text-center"
|
||||
>
|
||||
Embassy Marketplace
|
||||
</h1>
|
||||
|
||||
<ion-grid style="padding-bottom: 32px;">
|
||||
<ion-grid style="padding-bottom: 32px">
|
||||
<ion-row>
|
||||
<ion-col sizeSm="8" offset-sm="2">
|
||||
<ion-toolbar color="transparent">
|
||||
@@ -36,12 +41,18 @@
|
||||
<!-- loading -->
|
||||
<ng-container *ngIf="loading; else pageLoaded">
|
||||
<div class="scrollable ion-text-center">
|
||||
<ion-button *ngFor="let cat of ['', '', '', '', '', '', '']" fill="clear">
|
||||
<ion-skeleton-text animated style="width: 80px; border-radius: 0;"></ion-skeleton-text>
|
||||
<ion-button
|
||||
*ngFor="let cat of ['', '', '', '', '', '', '']"
|
||||
fill="clear"
|
||||
>
|
||||
<ion-skeleton-text
|
||||
animated
|
||||
style="width: 80px; border-radius: 0"
|
||||
></ion-skeleton-text>
|
||||
</ion-button>
|
||||
</div>
|
||||
|
||||
<div class="divider" style="margin: 24px 0;"></div>
|
||||
<div class="divider" style="margin: 24px 0"></div>
|
||||
</ng-container>
|
||||
|
||||
<!-- loaded -->
|
||||
@@ -57,22 +68,39 @@
|
||||
</ion-button>
|
||||
</div>
|
||||
|
||||
<div class="divider" style="margin: 24px;"></div>
|
||||
<div class="divider" style="margin: 24px"></div>
|
||||
</ng-template>
|
||||
|
||||
<!-- loading -->
|
||||
<ng-container *ngIf="loading; else pkgsLoaded">
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col *ngFor="let pkg of ['', '', '', '']" sizeXs="12" sizeSm="12" sizeMd="6">
|
||||
<ion-col
|
||||
*ngFor="let pkg of ['', '', '', '']"
|
||||
sizeXs="12"
|
||||
sizeSm="12"
|
||||
sizeMd="6"
|
||||
>
|
||||
<ion-item>
|
||||
<ion-thumbnail slot="start">
|
||||
<ion-skeleton-text style="border-radius: 100%;" animated></ion-skeleton-text>
|
||||
<ion-skeleton-text
|
||||
style="border-radius: 100%"
|
||||
animated
|
||||
></ion-skeleton-text>
|
||||
</ion-thumbnail>
|
||||
<ion-label>
|
||||
<ion-skeleton-text animated style="width: 150px; height: 18px; margin-bottom: 8px;"></ion-skeleton-text>
|
||||
<ion-skeleton-text animated style="width: 400px;"></ion-skeleton-text>
|
||||
<ion-skeleton-text animated style="width: 100px;"></ion-skeleton-text>
|
||||
<ion-skeleton-text
|
||||
animated
|
||||
style="width: 150px; height: 18px; margin-bottom: 8px"
|
||||
></ion-skeleton-text>
|
||||
<ion-skeleton-text
|
||||
animated
|
||||
style="width: 400px"
|
||||
></ion-skeleton-text>
|
||||
<ion-skeleton-text
|
||||
animated
|
||||
style="width: 100px"
|
||||
></ion-skeleton-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-col>
|
||||
@@ -85,39 +113,46 @@
|
||||
<div
|
||||
class="ion-padding"
|
||||
*ngIf="!pkgs.length && category ==='updates'"
|
||||
style="text-align: center;"
|
||||
style="text-align: center"
|
||||
>
|
||||
<h1>All services are up to date!</h1>
|
||||
</div>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col *ngIf="marketplaceService.eosUpdateAvailable && category === 'featured'" sizeXs="12" sizeSm="12" sizeMd="6">
|
||||
<ion-item button class="eos-item" (click)="updateEos()">
|
||||
<ion-thumbnail slot="start">
|
||||
<img src="assets/img/icon.png" />
|
||||
</ion-thumbnail>
|
||||
<ion-label>
|
||||
<h3>Now Available...</h3>
|
||||
<h2>Embassy OS {{ marketplaceService.eos.version }}</h2>
|
||||
<p>{{ marketplaceService.eos.headline }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-col>
|
||||
<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]="'/marketplace' + pkg.icon" />
|
||||
<img
|
||||
[src]="sanitizer.bypassSecurityTrustResourceUrl('data:image/png;base64,' + pkg.icon)"
|
||||
/>
|
||||
</ion-thumbnail>
|
||||
<ion-label>
|
||||
<h2 style="font-family: 'Montserrat'; font-weight: bold;">{{ pkg.manifest.title }}</h2>
|
||||
<h2 style="font-family: 'Montserrat'; font-weight: bold">
|
||||
{{ pkg.manifest.title }}
|
||||
</h2>
|
||||
<h3>{{ pkg.manifest.description.short }}</h3>
|
||||
<ng-container *ngIf="localPkgs[pkg.manifest.id] as localPkg; else none">
|
||||
<ng-container
|
||||
*ngIf="localPkgs[pkg.manifest.id] as localPkg; else none"
|
||||
>
|
||||
<p *ngIf="localPkg.state === PackageState.Installed">
|
||||
<ion-text *ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 0" color="success">Installed</ion-text>
|
||||
<ion-text *ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 1" color="warning">Update Available</ion-text>
|
||||
<ion-text
|
||||
*ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 0"
|
||||
color="success"
|
||||
>Installed</ion-text
|
||||
>
|
||||
<ion-text
|
||||
*ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 1"
|
||||
color="warning"
|
||||
>Update Available</ion-text
|
||||
>
|
||||
</p>
|
||||
<p *ngIf="[PackageState.Installing, PackageState.Updating] | includes : localPkg.state">
|
||||
<ion-text color="primary" *ngIf="(localPkg['install-progress'] | installProgress) as progress">
|
||||
<p
|
||||
*ngIf="[PackageState.Installing, PackageState.Updating] | includes : localPkg.state"
|
||||
>
|
||||
<ion-text
|
||||
color="primary"
|
||||
*ngIf="(localPkg['install-progress'] | installProgress) as progress"
|
||||
>
|
||||
Installing
|
||||
<span class="loading-dots"></span>{{ progress }}
|
||||
</ion-text>
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { Component, ViewChild } from '@angular/core'
|
||||
import { MarketplacePkg } from 'src/app/services/api/api.types'
|
||||
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
||||
import { AlertController, IonContent, ModalController } from '@ionic/angular'
|
||||
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||
import { PackageDataEntry, PackageState } from 'src/app/services/patch-db/data-model'
|
||||
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 { Router } from '@angular/router'
|
||||
import { filter, first } from 'rxjs/operators'
|
||||
import { DomSanitizer } from '@angular/platform-browser'
|
||||
|
||||
const defaultOps = {
|
||||
isCaseSensitive: false,
|
||||
@@ -45,145 +46,106 @@ export class MarketplaceListPage {
|
||||
@ViewChild(IonContent) content: IonContent
|
||||
|
||||
pkgs: MarketplacePkg[] = []
|
||||
hasRecoveredPackage: boolean
|
||||
categories: string[]
|
||||
localPkgs: { [id: string]: PackageDataEntry } = { }
|
||||
localPkgs: { [id: string]: PackageDataEntry } = {}
|
||||
category = 'featured'
|
||||
query: string
|
||||
loading = true
|
||||
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
private readonly modalCtrl: ModalController,
|
||||
constructor(
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly wizardBaker: WizardBaker,
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly router: Router,
|
||||
public readonly patch: PatchDbService,
|
||||
public readonly marketplaceService: MarketplaceService,
|
||||
) { }
|
||||
public readonly sanitizer: DomSanitizer,
|
||||
) {}
|
||||
|
||||
async ngOnInit () {
|
||||
async ngOnInit() {
|
||||
this.subs = [
|
||||
this.patch.watch$('package-data')
|
||||
.pipe(
|
||||
filter((data) => exists(data) && !isEmptyObject(data)),
|
||||
).subscribe(pkgs => {
|
||||
this.localPkgs = pkgs
|
||||
Object.values(this.localPkgs).forEach(pkg => {
|
||||
pkg['install-progress'] = { ...pkg['install-progress'] }
|
||||
})
|
||||
}),
|
||||
this.patch.watch$('recovered-packages').subscribe(rps => {
|
||||
this.hasRecoveredPackage = !isEmptyObject(rps)
|
||||
}),
|
||||
this.patch
|
||||
.watch$('package-data')
|
||||
.pipe(filter(data => exists(data) && !isEmptyObject(data)))
|
||||
.subscribe(pkgs => {
|
||||
this.localPkgs = pkgs
|
||||
Object.values(this.localPkgs).forEach(pkg => {
|
||||
pkg['install-progress'] = { ...pkg['install-progress'] }
|
||||
})
|
||||
}),
|
||||
]
|
||||
|
||||
this.patch.watch$('server-info')
|
||||
.pipe(
|
||||
filter((data) => exists(data) && !isEmptyObject(data)),
|
||||
first(),
|
||||
).subscribe(async _ => {
|
||||
try {
|
||||
if (!this.marketplaceService.pkgs.length) {
|
||||
await this.marketplaceService.load()
|
||||
this.patch
|
||||
.watch$('server-info')
|
||||
.pipe(
|
||||
filter(data => exists(data) && !isEmptyObject(data)),
|
||||
first(),
|
||||
)
|
||||
.subscribe(async _ => {
|
||||
try {
|
||||
if (!this.marketplaceService.pkgs.length) {
|
||||
await this.marketplaceService.load()
|
||||
}
|
||||
|
||||
// category should start as first item in array
|
||||
// remove here then add at beginning
|
||||
const filterdCategories =
|
||||
this.marketplaceService.data.categories.filter(
|
||||
cat => this.category !== cat,
|
||||
)
|
||||
this.categories = [this.category, 'updates']
|
||||
.concat(filterdCategories)
|
||||
.concat(['all'])
|
||||
|
||||
this.filterPkgs()
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
// category should start as first item in array
|
||||
// remove here then add at beginning
|
||||
const filterdCategories = this.marketplaceService.data.categories.filter(cat => this.category !== cat)
|
||||
this.categories = [this.category, 'updates'].concat(filterdCategories).concat(['all'])
|
||||
|
||||
this.filterPkgs()
|
||||
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
ngAfterViewInit () {
|
||||
ngAfterViewInit() {
|
||||
this.content.scrollToPoint(undefined, 1)
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
ngOnDestroy() {
|
||||
this.subs.forEach(sub => sub.unsubscribe())
|
||||
}
|
||||
|
||||
async search (): Promise<void> {
|
||||
search(): void {
|
||||
if (this.query) {
|
||||
this.category = undefined
|
||||
}
|
||||
await this.filterPkgs()
|
||||
this.filterPkgs()
|
||||
}
|
||||
|
||||
async switchCategory (category: string): Promise<void> {
|
||||
switchCategory(category: string): void {
|
||||
this.category = category
|
||||
this.query = undefined
|
||||
this.filterPkgs()
|
||||
}
|
||||
|
||||
async updateEos (): Promise<void> {
|
||||
if (this.hasRecoveredPackage) {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Cannot Update',
|
||||
message: 'You cannot update EmbassyOS when you have unresolved recovered services.',
|
||||
buttons: [
|
||||
{
|
||||
text: 'OK',
|
||||
role: 'cancel',
|
||||
},
|
||||
{
|
||||
text: 'Resolve',
|
||||
handler: () => {
|
||||
this.router.navigate(['/services/list'], { replaceUrl: true })
|
||||
},
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
],
|
||||
})
|
||||
await alert.present()
|
||||
return
|
||||
}
|
||||
|
||||
const { version, headline, 'release-notes': releaseNotes } = this.marketplaceService.eos
|
||||
|
||||
await wizardModal(
|
||||
this.modalCtrl,
|
||||
this.wizardBaker.updateOS({
|
||||
version,
|
||||
headline,
|
||||
releaseNotes,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
private async filterPkgs (): Promise<void> {
|
||||
private filterPkgs(): void {
|
||||
if (this.category === 'updates') {
|
||||
this.pkgs = this.marketplaceService.pkgs.filter(pkg => {
|
||||
const { id, version } = pkg.manifest
|
||||
return this.localPkgs[id] && version !== this.localPkgs[id].manifest.version
|
||||
return (
|
||||
this.localPkgs[id] && version !== this.localPkgs[id].manifest.version
|
||||
)
|
||||
})
|
||||
} else if (this.query) {
|
||||
const fuse = new Fuse(this.marketplaceService.pkgs, defaultOps)
|
||||
this.pkgs = fuse.search(this.query).map(p => p.item)
|
||||
|
||||
} else {
|
||||
const pkgsToSort = this.marketplaceService.pkgs.filter(p => {
|
||||
return this.category === 'all' || p.categories.includes(this.category)
|
||||
})
|
||||
|
||||
const opts = {
|
||||
...defaultOps,
|
||||
threshold: 1,
|
||||
}
|
||||
|
||||
const fuse = new Fuse(pkgsToSort, { ...defaultOps, threshold: 1 })
|
||||
this.pkgs = fuse.search(this.category !== 'all' ? this.category || '' : 'bit').map(p => p.item)
|
||||
this.pkgs = fuse
|
||||
.search(this.category !== 'all' ? this.category || '' : 'bit')
|
||||
.map(p => p.item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,24 @@
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
|
||||
<text-spinner *ngIf="loading; else loaded" text="Loading Package"></text-spinner>
|
||||
<text-spinner
|
||||
*ngIf="loading; else loaded"
|
||||
text="Loading Package"
|
||||
></text-spinner>
|
||||
|
||||
<ng-template #loaded>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col sizeXs="12" sizeSm="12" sizeMd="9" sizeLg="9" sizeXl="9">
|
||||
<div class="header">
|
||||
<img [src]="'/marketplace' + pkg.icon" />
|
||||
<img
|
||||
[src]="sanitizer.bypassSecurityTrustResourceUrl('data:image/png;base64,' + pkg.icon)"
|
||||
/>
|
||||
<div class="header-text">
|
||||
<h1 class="header-title">{{ pkg.manifest.title }}</h1>
|
||||
<p class="header-version">{{ pkg.manifest.version | displayEmver }}</p>
|
||||
<p class="header-version">
|
||||
{{ pkg.manifest.version | displayEmver }}
|
||||
</p>
|
||||
<div class="header-status">
|
||||
<!-- no localPkg -->
|
||||
<p *ngIf="!localPkg; else local">Not Installed</p>
|
||||
@@ -27,12 +33,25 @@
|
||||
<ng-template #local>
|
||||
<!-- installed -->
|
||||
<p *ngIf="localPkg.state === PackageState.Installed">
|
||||
<ion-text *ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 0" color="success">Installed</ion-text>
|
||||
<ion-text *ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 1" color="warning">Update Available</ion-text>
|
||||
<ion-text
|
||||
*ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 0"
|
||||
color="success"
|
||||
>Installed</ion-text
|
||||
>
|
||||
<ion-text
|
||||
*ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 1"
|
||||
color="warning"
|
||||
>Update Available</ion-text
|
||||
>
|
||||
</p>
|
||||
<!-- installing, updating -->
|
||||
<p *ngIf="[PackageState.Installing, PackageState.Updating] | includes : localPkg.state">
|
||||
<ion-text color="primary" *ngIf="(localPkg['install-progress'] | installProgress) as progress">
|
||||
<p
|
||||
*ngIf="[PackageState.Installing, PackageState.Updating] | includes : localPkg.state"
|
||||
>
|
||||
<ion-text
|
||||
color="primary"
|
||||
*ngIf="(localPkg['install-progress'] | installProgress) as progress"
|
||||
>
|
||||
Installing
|
||||
<span class="loading-dots"></span>{{ progress }}
|
||||
</ion-text>
|
||||
@@ -49,7 +68,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</ion-col>
|
||||
<ion-col sizeXl="3" sizeLg="3" sizeMd="3" sizeSm="12" sizeXs="12" class="ion-align-self-center">
|
||||
<ion-col
|
||||
sizeXl="3"
|
||||
sizeLg="3"
|
||||
sizeMd="3"
|
||||
sizeSm="12"
|
||||
sizeXs="12"
|
||||
class="ion-align-self-center"
|
||||
>
|
||||
<!-- no localPkg -->
|
||||
<ion-button *ngIf="!localPkg" expand="block" (click)="tryInstall()">
|
||||
Install
|
||||
@@ -58,10 +84,19 @@
|
||||
<ng-container *ngIf="localPkg">
|
||||
<!-- not installing, updating, or removing -->
|
||||
<ng-container *ngIf="localPkg.state === PackageState.Installed">
|
||||
<ion-button *ngIf="(localPkg.manifest.version | compareEmver : pkg.manifest.version) === -1" expand="block" (click)="presentModal('update')">
|
||||
<ion-button
|
||||
*ngIf="(localPkg.manifest.version | compareEmver : pkg.manifest.version) === -1"
|
||||
expand="block"
|
||||
(click)="presentModal('update')"
|
||||
>
|
||||
Update
|
||||
</ion-button>
|
||||
<ion-button *ngIf="(localPkg.manifest.version | compareEmver : pkg.manifest.version) === 1" expand="block" color="warning" (click)="presentModal('downgrade')">
|
||||
<ion-button
|
||||
*ngIf="(localPkg.manifest.version | compareEmver : pkg.manifest.version) === 1"
|
||||
expand="block"
|
||||
color="warning"
|
||||
(click)="presentModal('downgrade')"
|
||||
>
|
||||
Downgrade
|
||||
</ion-button>
|
||||
</ng-container>
|
||||
@@ -69,8 +104,20 @@
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<ion-row *ngIf="localPkg">
|
||||
<ion-col sizeXl="3" sizeLg="3" sizeMd="3" sizeSm="12" sizeXs="12" class="ion-align-self-center">
|
||||
<ion-button expand="block" fill="outline" color="primary" [routerLink]="['/services', pkg.manifest.id]">
|
||||
<ion-col
|
||||
sizeXl="3"
|
||||
sizeLg="3"
|
||||
sizeMd="3"
|
||||
sizeSm="12"
|
||||
sizeXs="12"
|
||||
class="ion-align-self-center"
|
||||
>
|
||||
<ion-button
|
||||
expand="block"
|
||||
fill="outline"
|
||||
color="primary"
|
||||
[routerLink]="['/services', pkg.manifest.id]"
|
||||
>
|
||||
View Service
|
||||
</ion-button>
|
||||
</ion-col>
|
||||
@@ -80,16 +127,30 @@
|
||||
<!-- auto-config -->
|
||||
<ion-item lines="none" *ngIf="dependentInfo" class="rec-item">
|
||||
<ion-label>
|
||||
<h2 style="display: flex; align-items: center;">
|
||||
<ion-text style="margin: 5px; font-family: 'Montserrat'; font-size: 18px;">{{ pkg.manifest.title }}</ion-text>
|
||||
<h2 style="display: flex; align-items: center">
|
||||
<ion-text
|
||||
style="margin: 5px; font-family: 'Montserrat'; font-size: 18px"
|
||||
>{{ pkg.manifest.title }}</ion-text
|
||||
>
|
||||
</h2>
|
||||
<p>
|
||||
<ion-text color="dark">
|
||||
{{ dependentInfo.title }} requires an install of {{ pkg.manifest.title }} satisfying {{ dependentInfo.version }}.
|
||||
{{ dependentInfo.title }} requires an install of {{
|
||||
pkg.manifest.title }} satisfying {{ dependentInfo.version }}.
|
||||
<br />
|
||||
<br />
|
||||
<span *ngIf="pkg.manifest.version | satisfiesEmver: dependentInfo.version" class="recommendation-text">{{ pkg.manifest.title }} version {{ pkg.manifest.version | displayEmver }} is compatible.</span>
|
||||
<span *ngIf="!(pkg.manifest.version | satisfiesEmver: dependentInfo.version)" class="recommendation-text recommendation-error">{{ pkg.manifest.title }} version {{ pkg.manifest.version | displayEmver }} is NOT compatible.</span>
|
||||
<span
|
||||
*ngIf="pkg.manifest.version | satisfiesEmver: dependentInfo.version"
|
||||
class="recommendation-text"
|
||||
>{{ pkg.manifest.title }} version {{ pkg.manifest.version |
|
||||
displayEmver }} is compatible.</span
|
||||
>
|
||||
<span
|
||||
*ngIf="!(pkg.manifest.version | satisfiesEmver: dependentInfo.version)"
|
||||
class="recommendation-text recommendation-error"
|
||||
>{{ pkg.manifest.title }} version {{ pkg.manifest.version |
|
||||
displayEmver }} is NOT compatible.</span
|
||||
>
|
||||
</ion-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
@@ -99,47 +160,71 @@
|
||||
<!-- release notes -->
|
||||
<ion-item-divider>
|
||||
New in {{ pkg.manifest.version | displayEmver }}
|
||||
<ion-button [routerLink]="['notes']" style="position: absolute; right: 10px;" fill="clear" color="dark">
|
||||
<ion-button
|
||||
[routerLink]="['notes']"
|
||||
style="position: absolute; right: 10px"
|
||||
fill="clear"
|
||||
color="dark"
|
||||
>
|
||||
All Release Notes
|
||||
<ion-icon slot="end" name="arrow-forward-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item-divider>
|
||||
<ion-item lines="none" color="transparent">
|
||||
<ion-label>
|
||||
<div id='release-notes' [innerHTML]="pkg.manifest['release-notes'] | markdown"></div>
|
||||
<div
|
||||
id="release-notes"
|
||||
[innerHTML]="pkg.manifest['release-notes'] | markdown"
|
||||
></div>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<!-- description -->
|
||||
<ion-item-divider>Description</ion-item-divider>
|
||||
<ion-item lines="none" color="transparent">
|
||||
<ion-label>
|
||||
<div id="release-notes" class="release-notes">{{ pkg.manifest.description.long }}</div>
|
||||
<div id="release-notes" class="release-notes">
|
||||
{{ pkg.manifest.description.long }}
|
||||
</div>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<!-- dependencies -->
|
||||
<ng-container *ngIf="!(pkg.manifest.dependencies | empty)">
|
||||
<ion-item-divider>Dependencies</ion-item-divider>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col *ngFor="let dep of pkg.manifest.dependencies | keyvalue" sizeSm="12" sizeMd="6">
|
||||
<ion-item [routerLink]="['/marketplace', dep.key]">
|
||||
<ion-thumbnail slot="start">
|
||||
<img [src]="'/marketplace' + pkg['dependency-metadata'][dep.key].icon" />
|
||||
</ion-thumbnail>
|
||||
<ion-label>
|
||||
<h2>
|
||||
{{ pkg['dependency-metadata'][dep.key].title }}
|
||||
<span *ngIf="dep.value.requirement.type === 'required'"> (required)</span>
|
||||
<span *ngIf="dep.value.requirement.type === 'opt-out'"> (required by default)</span>
|
||||
<span *ngIf="dep.value.requirement.type === 'opt-in'"> (optional)</span>
|
||||
</h2>
|
||||
<p style="font-size: small">{{ dep.value.version | displayEmver }}</p>
|
||||
<p>{{ dep.value.description }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col
|
||||
*ngFor="let dep of pkg.manifest.dependencies | keyvalue"
|
||||
sizeSm="12"
|
||||
sizeMd="6"
|
||||
>
|
||||
<ion-item [routerLink]="['/marketplace', dep.key]">
|
||||
<ion-thumbnail slot="start">
|
||||
<img
|
||||
[src]="'/marketplace' + pkg['dependency-metadata'][dep.key].icon"
|
||||
/>
|
||||
</ion-thumbnail>
|
||||
<ion-label>
|
||||
<h2>
|
||||
{{ pkg['dependency-metadata'][dep.key].title }}
|
||||
<span *ngIf="dep.value.requirement.type === 'required'">
|
||||
(required)</span
|
||||
>
|
||||
<span *ngIf="dep.value.requirement.type === 'opt-out'">
|
||||
(required by default)</span
|
||||
>
|
||||
<span *ngIf="dep.value.requirement.type === 'opt-in'">
|
||||
(optional)</span
|
||||
>
|
||||
</h2>
|
||||
<p style="font-size: small">
|
||||
{{ dep.value.version | displayEmver }}
|
||||
</p>
|
||||
<p>{{ dep.value.description }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ng-container>
|
||||
</ion-item-group>
|
||||
|
||||
@@ -156,14 +241,22 @@
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="chevron-forward-outline"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-item button detail="false" (click)="presentModalMd('license')">
|
||||
<ion-item
|
||||
button
|
||||
detail="false"
|
||||
(click)="presentModalMd('license')"
|
||||
>
|
||||
<ion-label>
|
||||
<h2>License</h2>
|
||||
<p>{{ pkg.manifest.license }}</p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="chevron-forward-outline"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-item button detail="false" (click)="presentModalMd('instructions')">
|
||||
<ion-item
|
||||
button
|
||||
detail="false"
|
||||
(click)="presentModalMd('instructions')"
|
||||
>
|
||||
<ion-label>
|
||||
<h2>Instructions</h2>
|
||||
<p>Click to view instructions</p>
|
||||
@@ -174,21 +267,36 @@
|
||||
</ion-col>
|
||||
<ion-col sizeSm="12" sizeMd="6">
|
||||
<ion-item-group>
|
||||
<ion-item [href]="pkg.manifest['upstream-repo']" target="_blank" rel="noreferrer" detail="false">
|
||||
<ion-item
|
||||
[href]="pkg.manifest['upstream-repo']"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
detail="false"
|
||||
>
|
||||
<ion-label>
|
||||
<h2>Source Repository</h2>
|
||||
<p>{{ pkg.manifest['upstream-repo'] }}</p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-item [href]="pkg.manifest['wrapper-repo']" target="_blank" rel="noreferrer" detail="false">
|
||||
<ion-item
|
||||
[href]="pkg.manifest['wrapper-repo']"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
detail="false"
|
||||
>
|
||||
<ion-label>
|
||||
<h2>Wrapper Repository</h2>
|
||||
<p>{{ pkg.manifest['wrapper-repo'] }}</p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-item [href]="pkg.manifest['support-site']" target="_blank" rel="noreferrer" detail="false">
|
||||
<ion-item
|
||||
[href]="pkg.manifest['support-site']"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
detail="false"
|
||||
>
|
||||
<ion-label>
|
||||
<h2>Support Site</h2>
|
||||
<p>{{ pkg.manifest['support-site'] }}</p>
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { Component, ViewChild } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { AlertController, IonContent, LoadingController, ModalController, NavController } from '@ionic/angular'
|
||||
import {
|
||||
AlertController,
|
||||
IonContent,
|
||||
LoadingController,
|
||||
ModalController,
|
||||
NavController,
|
||||
} 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'
|
||||
@@ -8,12 +14,16 @@ import { displayEmver } from 'src/app/pipes/emver.pipe'
|
||||
import { DependentInfo, pauseFor } from 'src/app/util/misc.util'
|
||||
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 {
|
||||
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'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { MarketplacePkg } from 'src/app/services/api/api.types'
|
||||
import { DomSanitizer } from '@angular/platform-browser'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-show',
|
||||
@@ -30,7 +40,7 @@ export class MarketplaceShowPage {
|
||||
dependentInfo: DependentInfo
|
||||
subs: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly modalCtrl: ModalController,
|
||||
@@ -42,18 +52,21 @@ export class MarketplaceShowPage {
|
||||
private readonly patch: PatchDbService,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly marketplaceService: MarketplaceService,
|
||||
) { }
|
||||
public readonly sanitizer: DomSanitizer,
|
||||
) {}
|
||||
|
||||
async ngOnInit () {
|
||||
async ngOnInit() {
|
||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
||||
this.dependentInfo = history.state && history.state.dependentInfo as DependentInfo
|
||||
this.dependentInfo =
|
||||
history.state && (history.state.dependentInfo as DependentInfo)
|
||||
|
||||
this.subs = [
|
||||
this.patch.watch$('package-data', this.pkgId)
|
||||
.subscribe(pkg => {
|
||||
this.patch.watch$('package-data', this.pkgId).subscribe(pkg => {
|
||||
if (!pkg) return
|
||||
this.localPkg = pkg
|
||||
this.localPkg['install-progress'] = { ...this.localPkg['install-progress'] }
|
||||
this.localPkg['install-progress'] = {
|
||||
...this.localPkg['install-progress'],
|
||||
}
|
||||
}),
|
||||
]
|
||||
|
||||
@@ -61,7 +74,9 @@ export class MarketplaceShowPage {
|
||||
if (!this.marketplaceService.pkgs.length) {
|
||||
await this.marketplaceService.load()
|
||||
}
|
||||
this.pkg = this.marketplaceService.pkgs.find(pkg => pkg.manifest.id === this.pkgId)
|
||||
this.pkg = this.marketplaceService.pkgs.find(
|
||||
pkg => pkg.manifest.id === this.pkgId,
|
||||
)
|
||||
if (!this.pkg) {
|
||||
throw new Error(`Service with ID "${this.pkgId}" not found.`)
|
||||
}
|
||||
@@ -72,31 +87,34 @@ export class MarketplaceShowPage {
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit () {
|
||||
ngAfterViewInit() {
|
||||
this.content.scrollToPoint(undefined, 1)
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
ngOnDestroy() {
|
||||
this.subs.forEach(sub => sub.unsubscribe())
|
||||
}
|
||||
|
||||
async presentAlertVersions () {
|
||||
async presentAlertVersions() {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Versions',
|
||||
inputs: this.pkg.versions.sort((a, b) => -1 * this.emver.compare(a, b)).map(v => {
|
||||
return {
|
||||
name: v, // for CSS
|
||||
type: 'radio',
|
||||
label: displayEmver(v), // appearance on screen
|
||||
value: v, // literal SEM version value
|
||||
checked: this.pkg.manifest.version === v,
|
||||
}
|
||||
}),
|
||||
inputs: this.pkg.versions
|
||||
.sort((a, b) => -1 * this.emver.compare(a, b))
|
||||
.map(v => {
|
||||
return {
|
||||
name: v, // for CSS
|
||||
type: 'radio',
|
||||
label: displayEmver(v), // appearance on screen
|
||||
value: v, // literal SEM version value
|
||||
checked: this.pkg.manifest.version === v,
|
||||
}
|
||||
}),
|
||||
buttons: [
|
||||
{
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
}, {
|
||||
},
|
||||
{
|
||||
text: 'Ok',
|
||||
handler: (version: string) => {
|
||||
this.getPkg(version)
|
||||
@@ -109,7 +127,7 @@ export class MarketplaceShowPage {
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
async presentModalMd (title: string) {
|
||||
async presentModalMd(title: string) {
|
||||
const modal = await this.modalCtrl.create({
|
||||
componentProps: {
|
||||
title,
|
||||
@@ -121,7 +139,7 @@ export class MarketplaceShowPage {
|
||||
await modal.present()
|
||||
}
|
||||
|
||||
async tryInstall () {
|
||||
async tryInstall() {
|
||||
const { id, title, version, alerts } = this.pkg.manifest
|
||||
|
||||
if (!alerts.install) {
|
||||
@@ -148,7 +166,7 @@ export class MarketplaceShowPage {
|
||||
}
|
||||
}
|
||||
|
||||
async presentModal (action: 'update' | 'downgrade') {
|
||||
async presentModal(action: 'update' | 'downgrade') {
|
||||
const { id, title, version, dependencies, alerts } = this.pkg.manifest
|
||||
const value = {
|
||||
id,
|
||||
@@ -160,9 +178,9 @@ export class MarketplaceShowPage {
|
||||
|
||||
const { cancelled } = await wizardModal(
|
||||
this.modalCtrl,
|
||||
action === 'update' ?
|
||||
this.wizardBaker.update(value) :
|
||||
this.wizardBaker.downgrade(value),
|
||||
action === 'update'
|
||||
? this.wizardBaker.update(value)
|
||||
: this.wizardBaker.downgrade(value),
|
||||
)
|
||||
|
||||
if (cancelled) return
|
||||
@@ -170,7 +188,7 @@ export class MarketplaceShowPage {
|
||||
this.navCtrl.back()
|
||||
}
|
||||
|
||||
private async getPkg (version?: string): Promise<void> {
|
||||
private async getPkg(version?: string): Promise<void> {
|
||||
this.loading = true
|
||||
try {
|
||||
this.pkg = await this.marketplaceService.getPkg(this.pkgId, version)
|
||||
@@ -182,7 +200,7 @@ export class MarketplaceShowPage {
|
||||
}
|
||||
}
|
||||
|
||||
private async install (id: string, version?: string): Promise<void> {
|
||||
private async install(id: string, version?: string): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Beginning Installation',
|
||||
@@ -191,7 +209,10 @@ export class MarketplaceShowPage {
|
||||
loader.present()
|
||||
|
||||
try {
|
||||
await this.embassyApi.installPackage({ id, 'version-spec': version ? `=${version}` : undefined })
|
||||
await this.embassyApi.installPackage({
|
||||
id,
|
||||
'version-spec': version ? `=${version}` : undefined,
|
||||
})
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import {
|
||||
MarketplaceData,
|
||||
MarketplaceEOS,
|
||||
MarketplacePkg,
|
||||
} from 'src/app/services/api/api.types'
|
||||
import { MarketplaceData, MarketplacePkg } from 'src/app/services/api/api.types'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { Emver } from 'src/app/services/emver.service'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
@@ -14,7 +10,6 @@ import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||
})
|
||||
export class MarketplaceService {
|
||||
data: MarketplaceData
|
||||
eos: MarketplaceEOS
|
||||
pkgs: MarketplacePkg[] = []
|
||||
releaseNotes: {
|
||||
[id: string]: {
|
||||
@@ -28,31 +23,24 @@ export class MarketplaceService {
|
||||
private readonly patch: PatchDbService,
|
||||
) {}
|
||||
|
||||
get eosUpdateAvailable() {
|
||||
return (
|
||||
this.emver.compare(
|
||||
this.eos.version,
|
||||
this.patch.data['server-info'].version,
|
||||
) === 1
|
||||
)
|
||||
}
|
||||
|
||||
async load(): Promise<void> {
|
||||
try {
|
||||
const [data, eos, pkgs] = await Promise.all([
|
||||
const [data, pkgs] = await Promise.all([
|
||||
this.api.getMarketplaceData({}),
|
||||
this.api.getEos({
|
||||
'eos-version-compat':
|
||||
this.patch.getData()['server-info']['eos-version-compat'],
|
||||
}),
|
||||
this.getPkgs(1, 100),
|
||||
])
|
||||
this.data = data
|
||||
this.eos = eos
|
||||
this.pkgs = pkgs
|
||||
const { 'selected-id': selectedId, 'known-hosts': knownHosts } =
|
||||
this.patch.getData().ui.marketplace
|
||||
if (knownHosts[selectedId].name !== this.data.name) {
|
||||
this.api.setDbValue({
|
||||
pointer: `/marketplace/known-hosts/${selectedId}/name`,
|
||||
value: this.data.name,
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
this.data = undefined
|
||||
this.eos = undefined
|
||||
this.pkgs = []
|
||||
throw e
|
||||
}
|
||||
@@ -61,10 +49,18 @@ export class MarketplaceService {
|
||||
async getUpdates(localPkgs: {
|
||||
[id: string]: PackageDataEntry
|
||||
}): Promise<MarketplacePkg[]> {
|
||||
const idAndCurrentVersions = Object.keys(localPkgs).map(key => ({
|
||||
id: key,
|
||||
version: localPkgs[key].manifest.version,
|
||||
}))
|
||||
const idAndCurrentVersions = Object.keys(localPkgs)
|
||||
.map(key => ({
|
||||
id: key,
|
||||
version: localPkgs[key].manifest.version,
|
||||
marketplaceUrl: localPkgs[key].installed['marketplace-url'],
|
||||
}))
|
||||
.filter(pkg => {
|
||||
return (
|
||||
pkg.marketplaceUrl ===
|
||||
this.patch.getData().ui.marketplace['known-hosts']['selected-id'].url
|
||||
)
|
||||
})
|
||||
const latestPkgs = await this.api.getMarketplacePkgs({
|
||||
ids: idAndCurrentVersions,
|
||||
'eos-version-compat':
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<ion-item
|
||||
[button]="mp.key !== patch.data.ui.marketplace['selected-id']"
|
||||
detail="false"
|
||||
*ngFor="let mp of patch.data.ui.marketplace.options | keyvalue"
|
||||
*ngFor="let mp of patch.data.ui.marketplace['known-hosts'] | keyvalue"
|
||||
(click)="presentAction(mp.key)"
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -12,11 +12,8 @@ import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
||||
import { PatchDbService } from '../../../services/patch-db/patch-db.service'
|
||||
import { v4 } from 'uuid'
|
||||
import { MarketplaceService } from '../../marketplace-routes/marketplace.service'
|
||||
import {
|
||||
DataModel,
|
||||
UIData,
|
||||
UIMarketplaceData,
|
||||
} from '../../../services/patch-db/data-model'
|
||||
import { UIMarketplaceData } from '../../../services/patch-db/data-model'
|
||||
import { ConfigService } from '../../../services/config.service'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplaces',
|
||||
@@ -31,6 +28,7 @@ export class MarketplacesPage {
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly actionCtrl: ActionSheetController,
|
||||
private readonly marketplaceService: MarketplaceService,
|
||||
private readonly config: ConfigService,
|
||||
public readonly patch: PatchDbService,
|
||||
) {}
|
||||
|
||||
@@ -95,10 +93,10 @@ export class MarketplacesPage {
|
||||
}
|
||||
|
||||
private async connect(id: string): Promise<void> {
|
||||
const marketplace = JSON.parse(
|
||||
const marketplace: UIMarketplaceData = JSON.parse(
|
||||
JSON.stringify(this.patch.data.ui.marketplace),
|
||||
)
|
||||
const newMarketplace = marketplace.options[id]
|
||||
const newMarketplace = marketplace['known-hosts'][id]
|
||||
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
@@ -141,7 +139,7 @@ export class MarketplacesPage {
|
||||
}
|
||||
|
||||
private async delete(id: string): Promise<void> {
|
||||
const marketplace = JSON.parse(
|
||||
const marketplace: UIMarketplaceData = JSON.parse(
|
||||
JSON.stringify(this.patch.data.ui.marketplace),
|
||||
)
|
||||
|
||||
@@ -153,7 +151,7 @@ export class MarketplacesPage {
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
delete marketplace.options[id]
|
||||
delete marketplace['known-hosts'][id]
|
||||
await this.api.setDbValue({ pointer: `/marketplace`, value: marketplace })
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
@@ -168,7 +166,7 @@ export class MarketplacesPage {
|
||||
) as UIMarketplaceData
|
||||
|
||||
// no-op on duplicates
|
||||
const currentUrls = Object.values(marketplace.options).map(
|
||||
const currentUrls = Object.values(marketplace['known-hosts']).map(
|
||||
u => new URL(u.url).hostname,
|
||||
)
|
||||
if (currentUrls.includes(new URL(url).hostname)) return
|
||||
@@ -184,7 +182,7 @@ export class MarketplacesPage {
|
||||
try {
|
||||
const id = v4()
|
||||
const { name } = await this.api.getMarketplaceData({}, url)
|
||||
marketplace.options[id] = { name, url }
|
||||
marketplace['known-hosts'][id] = { name, url }
|
||||
} catch (e) {
|
||||
this.errToast.present({ message: `Could not connect to ${url}` } as any)
|
||||
loader.dismiss()
|
||||
@@ -208,7 +206,7 @@ export class MarketplacesPage {
|
||||
) as UIMarketplaceData
|
||||
|
||||
// no-op on duplicates
|
||||
const currentUrls = Object.values(marketplace.options).map(
|
||||
const currentUrls = Object.values(marketplace['known-hosts']).map(
|
||||
u => new URL(u.url).hostname,
|
||||
)
|
||||
if (currentUrls.includes(new URL(url).hostname)) return
|
||||
@@ -223,7 +221,7 @@ export class MarketplacesPage {
|
||||
try {
|
||||
const id = v4()
|
||||
const { name } = await this.api.getMarketplaceData({}, url)
|
||||
marketplace.options[id] = { name, url }
|
||||
marketplace['known-hosts'][id] = { name, url }
|
||||
marketplace['selected-id'] = id
|
||||
} catch (e) {
|
||||
this.errToast.present({ message: `Could not connect to ${url}` } as any)
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title *ngIf="!patch.loaded">Loading<span class="loading-dots"></span></ion-title>
|
||||
<ion-title *ngIf="patch.loaded">{{ patch.data.ui.name || "Embassy-" + patch.data['server-info'].id }}</ion-title>
|
||||
<ion-title *ngIf="!patch.loaded"
|
||||
>Loading<span class="loading-dots"></span
|
||||
></ion-title>
|
||||
<ion-title *ngIf="patch.loaded"
|
||||
>{{ patch.data.ui.name || "Embassy-" + patch.data['server-info'].id
|
||||
}}</ion-title
|
||||
>
|
||||
<ion-buttons slot="end">
|
||||
<badge-menu-button></badge-menu-button>
|
||||
</ion-buttons>
|
||||
@@ -19,28 +24,59 @@
|
||||
<ng-template #data>
|
||||
<ion-item-group>
|
||||
<div *ngFor="let cat of settings | keyvalue : asIsOrder">
|
||||
<ion-item-divider><ion-text color="dark">{{ cat.key }}</ion-text></ion-item-divider>
|
||||
<ion-item button *ngFor="let button of cat.value" [detail]="button.detail" [disabled]="button.disabled | async" (click)="button.action()">
|
||||
<ion-item-divider
|
||||
><ion-text color="dark">{{ cat.key }}</ion-text></ion-item-divider
|
||||
>
|
||||
<ion-item
|
||||
button
|
||||
*ngFor="let button of cat.value"
|
||||
[detail]="button.detail"
|
||||
[disabled]="button.disabled | async"
|
||||
(click)="button.action()"
|
||||
>
|
||||
<ion-icon slot="start" [name]="button.icon"></ion-icon>
|
||||
<ion-label>
|
||||
<h2>{{ button.title }}</h2>
|
||||
<p *ngIf="button.description">{{ button.description }}</p>
|
||||
|
||||
<!-- "Create Backup" button only -->
|
||||
<p *ngIf="button.title === 'Create Backup'">
|
||||
<ng-container *ngIf="patch.data['server-info'].status as status">
|
||||
<ion-text color="warning" *ngIf="status === ServerStatus.Running">
|
||||
Last Backup: {{ patch.data['server-info']['last-backup'] ? (patch.data['server-info']['last-backup'] | date: 'short') : 'never' }}
|
||||
<ion-text
|
||||
color="warning"
|
||||
*ngIf="status === ServerStatus.Running"
|
||||
>
|
||||
Last Backup: {{ patch.data['server-info']['last-backup'] ?
|
||||
(patch.data['server-info']['last-backup'] | date: 'short') :
|
||||
'never' }}
|
||||
</ion-text>
|
||||
<span *ngIf="status === ServerStatus.BackingUp" class="inline">
|
||||
<ion-spinner color="success" style="height: 12px; width: 12px; margin-right: 6px;"></ion-spinner>
|
||||
<ion-text color="success">
|
||||
Backing up
|
||||
</ion-text>
|
||||
<ion-spinner
|
||||
color="success"
|
||||
style="height: 12px; width: 12px; margin-right: 6px"
|
||||
></ion-spinner>
|
||||
<ion-text color="success"> Backing up </ion-text>
|
||||
</span>
|
||||
</ng-container>
|
||||
</p>
|
||||
|
||||
<!-- "Software Update" button only -->
|
||||
<p *ngIf="button.title === 'Software Update'">
|
||||
<ng-container
|
||||
*ngIf="eosService.updateAvailable$ | async; else check"
|
||||
>
|
||||
<ion-text class="inline" color="success">
|
||||
<ion-icon name="repeat"></ion-icon>
|
||||
Update Available
|
||||
</ion-text>
|
||||
</ng-container>
|
||||
<ng-template #check>
|
||||
<i>Check for updates</i>
|
||||
</ng-template>
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
</ion-item-group>
|
||||
</ng-template>
|
||||
</ion-content>
|
||||
</ion-content>
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
LoadingController,
|
||||
NavController,
|
||||
IonicSafeString,
|
||||
ModalController,
|
||||
} from '@ionic/angular'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
@@ -11,7 +12,11 @@ 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 { map } from 'rxjs/operators'
|
||||
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 { EOSService } from 'src/app/services/eos.service'
|
||||
|
||||
@Component({
|
||||
selector: 'server-show',
|
||||
@@ -20,17 +25,57 @@ import { map } from 'rxjs/operators'
|
||||
})
|
||||
export class ServerShowPage {
|
||||
ServerStatus = ServerStatus
|
||||
hasRecoveredPackage: boolean
|
||||
|
||||
constructor(
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly wizardBaker: WizardBaker,
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly errToast: ErrorToastService,
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly navCtrl: NavController,
|
||||
private readonly route: ActivatedRoute,
|
||||
public readonly eosService: EOSService,
|
||||
public readonly patch: PatchDbService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.patch
|
||||
.watch$('recovered-packages')
|
||||
.pipe(filter(exists), take(1))
|
||||
.subscribe(rps => {
|
||||
this.hasRecoveredPackage = !isEmptyObject(rps)
|
||||
})
|
||||
}
|
||||
|
||||
async updateEos(): Promise<void> {
|
||||
if (this.hasRecoveredPackage) {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Cannot Update',
|
||||
message:
|
||||
'You cannot update EmbassyOS when you have unresolved recovered services.',
|
||||
buttons: ['OK'],
|
||||
})
|
||||
await alert.present()
|
||||
} else {
|
||||
const {
|
||||
version,
|
||||
headline,
|
||||
'release-notes': releaseNotes,
|
||||
} = this.eosService.eos
|
||||
|
||||
await wizardModal(
|
||||
this.modalCtrl,
|
||||
this.wizardBaker.updateOS({
|
||||
version,
|
||||
headline,
|
||||
releaseNotes,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async presentAlertRestart() {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Confirm',
|
||||
@@ -54,7 +99,6 @@ export class ServerShowPage {
|
||||
}
|
||||
|
||||
async presentAlertShutdown() {
|
||||
const sts = this.patch.data['server-info'].status
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Warning',
|
||||
message:
|
||||
@@ -81,7 +125,7 @@ export class ServerShowPage {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'System Rebuild',
|
||||
message: new IonicSafeString(
|
||||
`<ion-text color="warning">Important:</ion-text> This will tear down all service containers and rebuild them from scratch. This may take up to ${minutes} minutes to complete. During this time, you will lose all connectivity to your Embassy.`,
|
||||
`<ion-text color="warning">Warning:</ion-text> This action will tear down all service containers and rebuild them from scratch. No data will be deleted. This action is useful if your system gets into a bad state, and it should only be performed if you are experiencing general performance or reliability issues. It may take up to ${minutes} minutes to complete. During this time, you will lose all connectivity to your Embassy.`,
|
||||
),
|
||||
buttons: [
|
||||
{
|
||||
@@ -151,6 +195,23 @@ export class ServerShowPage {
|
||||
}
|
||||
}
|
||||
|
||||
private async checkForEosUpdate(): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
message: 'Checking for updates',
|
||||
cssClass: 'loader',
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.eosService.getEOS()
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
settings: ServerSettings = {
|
||||
Backups: [
|
||||
{
|
||||
@@ -179,6 +240,17 @@ export class ServerShowPage {
|
||||
},
|
||||
],
|
||||
Insights: [
|
||||
{
|
||||
title: 'Software Update',
|
||||
description: 'Get the latest version of EmbassyOS',
|
||||
icon: 'cog-outline',
|
||||
action: () =>
|
||||
this.eosService.updateAvailable$.getValue()
|
||||
? this.updateEos()
|
||||
: this.checkForEosUpdate(),
|
||||
detail: false,
|
||||
disabled: of(false),
|
||||
},
|
||||
{
|
||||
title: 'About',
|
||||
description: 'Basic information about your Embassy',
|
||||
@@ -303,12 +375,14 @@ export class ServerShowPage {
|
||||
}
|
||||
|
||||
interface ServerSettings {
|
||||
[key: string]: {
|
||||
title: string
|
||||
description: string
|
||||
icon: string
|
||||
action: Function
|
||||
detail: boolean
|
||||
disabled: Observable<boolean>
|
||||
}[]
|
||||
[key: string]: SettingBtn[]
|
||||
}
|
||||
|
||||
interface SettingBtn {
|
||||
title: string
|
||||
description: string
|
||||
icon: string
|
||||
action: Function
|
||||
detail: boolean
|
||||
disabled: Observable<boolean>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user