alt marketplace feature

re-arrange

use url

api proxy function

matt comments addressed

delete cache on marketplace load failure
This commit is contained in:
Drew Ansbacher
2022-01-31 19:46:41 -07:00
committed by Aiden McClelland
parent 691d567d31
commit 2d4ecd3096
16 changed files with 1311 additions and 515 deletions

View File

@@ -1,5 +1,9 @@
import { Injectable } from '@angular/core'
import { MarketplaceData, MarketplaceEOS, MarketplacePkg } from 'src/app/services/api/api.types'
import {
MarketplaceData,
MarketplaceEOS,
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'
@@ -12,38 +16,59 @@ export class MarketplaceService {
data: MarketplaceData
eos: MarketplaceEOS
pkgs: MarketplacePkg[] = []
releaseNotes: { [id: string]: {
[version: string]: string
} } = { }
releaseNotes: {
[id: string]: {
[version: string]: string
}
} = {}
constructor (
constructor(
private readonly api: ApiService,
private readonly emver: Emver,
private readonly patch: PatchDbService,
) { }
) {}
get eosUpdateAvailable () {
return this.emver.compare(this.eos.version, this.patch.data['server-info'].version) === 1
get eosUpdateAvailable() {
return (
this.emver.compare(
this.eos.version,
this.patch.data['server-info'].version,
) === 1
)
}
async load (): Promise<void> {
const [data, eos, 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
async load(): Promise<void> {
try {
const [data, eos, 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
} catch (e) {
this.data = undefined
this.eos = undefined
this.pkgs = []
throw e
}
}
async getUpdates (localPkgs: { [id: string]: PackageDataEntry }) : Promise<MarketplacePkg[]> {
const idAndCurrentVersions = Object.keys(localPkgs).map(key => ({ id: key, version: localPkgs[key].manifest.version }))
async getUpdates(localPkgs: {
[id: string]: PackageDataEntry
}): Promise<MarketplacePkg[]> {
const idAndCurrentVersions = Object.keys(localPkgs).map(key => ({
id: key,
version: localPkgs[key].manifest.version,
}))
const latestPkgs = await this.api.getMarketplacePkgs({
ids: idAndCurrentVersions,
'eos-version-compat': this.patch.getData()['server-info']['eos-version-compat'],
'eos-version-compat':
this.patch.getData()['server-info']['eos-version-compat'],
})
return latestPkgs.filter(latestPkg => {
@@ -53,10 +78,11 @@ export class MarketplaceService {
})
}
async getPkg (id: string, version = '*'): Promise<MarketplacePkg> {
async getPkg(id: string, version = '*'): Promise<MarketplacePkg> {
const pkgs = await this.api.getMarketplacePkgs({
ids: [{ id, version }],
'eos-version-compat': this.patch.getData()['server-info']['eos-version-compat'],
'eos-version-compat':
this.patch.getData()['server-info']['eos-version-compat'],
})
const pkg = pkgs.find(pkg => pkg.manifest.id == id)
@@ -67,19 +93,21 @@ export class MarketplaceService {
}
}
async getReleaseNotes (id: string): Promise<void> {
async getReleaseNotes(id: string): Promise<void> {
this.releaseNotes[id] = await this.api.getReleaseNotes({ id })
}
private async getPkgs (page: number, perPage: number) : Promise<MarketplacePkg[]> {
private async getPkgs(
page: number,
perPage: number,
): Promise<MarketplacePkg[]> {
const pkgs = await this.api.getMarketplacePkgs({
page: String(page),
'per-page': String(perPage),
'eos-version-compat': this.patch.getData()['server-info']['eos-version-compat'],
'eos-version-compat':
this.patch.getData()['server-info']['eos-version-compat'],
})
return pkgs
}
}

View File

@@ -0,0 +1,24 @@
import { NgModule } from '@angular/core'
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'
const routes: Routes = [
{
path: '',
component: MarketplacesPage,
},
]
@NgModule({
imports: [
CommonModule,
IonicModule,
RouterModule.forChild(routes),
SharingModule,
],
declarations: [MarketplacesPage],
})
export class MarketplacesPageModule {}

View File

@@ -0,0 +1,52 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button defaultHref="embassy"></ion-back-button>
</ion-buttons>
<ion-title>Marketplace Settings</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-top">
<ion-item-group>
<ion-item-divider>Saved Marketplaces</ion-item-divider>
<ion-item button detail="false" (click)="presentModalAdd()">
<ion-icon slot="start" name="add" size="large" color="dark"></ion-icon>
<ion-label>
<ion-text color="dark">
<b>Add alternative marketplace</b>
</ion-text>
</ion-label>
</ion-item>
<ion-item
[button]="mp.key !== patch.data.ui.marketplace['selected-id']"
detail="false"
*ngFor="let mp of patch.data.ui.marketplace.options | keyvalue"
(click)="presentAction(mp.key)"
>
<div
*ngIf="mp.key !== patch.data.ui.marketplace['selected-id']"
slot="start"
style="padding-right: 32px"
></div>
<ion-icon
*ngIf="mp.key === patch.data.ui.marketplace['selected-id']"
slot="start"
size="large"
name="checkmark"
color="success"
></ion-icon>
<ion-label>
<h2>{{ mp.value.name }}</h2>
<p>{{ mp.value.url }}</p>
</ion-label>
<ion-note
*ngIf="mp.key === patch.data.ui.marketplace['selected-id']"
slot="end"
>
<ion-text color="success">Selected</ion-text>
</ion-note>
</ion-item>
</ion-item-group>
</ion-content>

View File

@@ -0,0 +1,7 @@
.skeleton-parts {
ion-button::part(native) {
padding-inline-start: 0;
padding-inline-end: 0;
};
padding-bottom: 6px;
}

View File

@@ -0,0 +1,277 @@
import { Component } from '@angular/core'
import {
ActionSheetController,
LoadingController,
ModalController,
} from '@ionic/angular'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ActionSheetButton } from '@ionic/core'
import { ErrorToastService } from 'src/app/services/error-toast.service'
import { ValueSpecObject } from 'src/app/pkg-config/config-types'
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'
@Component({
selector: 'marketplaces',
templateUrl: 'marketplaces.page.html',
styleUrls: ['marketplaces.page.scss'],
})
export class MarketplacesPage {
constructor(
private readonly api: ApiService,
private readonly loadingCtrl: LoadingController,
private readonly modalCtrl: ModalController,
private readonly errToast: ErrorToastService,
private readonly actionCtrl: ActionSheetController,
private readonly marketplaceService: MarketplaceService,
public readonly patch: PatchDbService,
) {}
async presentModalAdd() {
const marketplaceSpec = getMarketplaceValueSpec()
const modal = await this.modalCtrl.create({
component: GenericFormPage,
componentProps: {
title: marketplaceSpec.name,
spec: marketplaceSpec.spec,
buttons: [
{
text: 'Save for Later',
handler: (value: { url: string }) => {
this.save(value.url)
},
},
{
text: 'Save and Connect',
handler: (value: { url: string }) => {
this.saveAndConnect(value.url)
},
isSubmit: true,
},
],
},
cssClass: 'alertlike-modal',
})
await modal.present()
}
async presentAction(id: string) {
// no need to view actions if is selected marketplace
if (id === this.patch.data.ui.marketplace['selected-id']) return
const buttons: ActionSheetButton[] = [
{
text: 'Forget',
icon: 'trash',
role: 'destructive',
handler: () => {
this.delete(id)
},
},
{
text: 'Connect to marketplace',
handler: () => {
this.connect(id)
},
},
]
const action = await this.actionCtrl.create({
header: id,
subHeader: 'Manage marketplaces',
mode: 'ios',
buttons,
})
await action.present()
}
private async connect(id: string): Promise<void> {
const marketplace = JSON.parse(
JSON.stringify(this.patch.data.ui.marketplace),
)
const newMarketplace = marketplace.options[id]
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Validating Marketplace...',
cssClass: 'loader',
})
await loader.present()
try {
await this.api.getMarketplaceData({}, newMarketplace.url)
} catch (e) {
this.errToast.present({
message: `Could not connect to ${newMarketplace.url}`,
} as any)
loader.dismiss()
return
}
loader.message = 'Changing Marketplace...'
try {
marketplace['selected-id'] = id
await this.api.setDbValue({ pointer: `/marketplace`, value: marketplace })
} catch (e) {
this.errToast.present(e)
loader.dismiss()
}
loader.message = 'Syncing store...'
try {
await this.marketplaceService.load()
} catch (e) {
this.errToast.present({
message: `Error syncing marketplace data`,
} as any)
} finally {
loader.dismiss()
}
}
private async delete(id: string): Promise<void> {
const marketplace = JSON.parse(
JSON.stringify(this.patch.data.ui.marketplace),
)
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Deleting...',
cssClass: 'loader',
})
await loader.present()
try {
delete marketplace.options[id]
await this.api.setDbValue({ pointer: `/marketplace`, value: marketplace })
} catch (e) {
this.errToast.present(e)
} finally {
loader.dismiss()
}
}
private async save(url: string): Promise<void> {
const marketplace = JSON.parse(
JSON.stringify(this.patch.data.ui.marketplace),
) as UIMarketplaceData
// no-op on duplicates
const currentUrls = Object.values(marketplace.options).map(
u => new URL(u.url).hostname,
)
if (currentUrls.includes(new URL(url).hostname)) return
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Validating Marketplace...',
cssClass: 'loader',
})
await loader.present()
try {
const id = v4()
const { name } = await this.api.getMarketplaceData({}, url)
marketplace.options[id] = { name, url }
} catch (e) {
this.errToast.present({ message: `Could not connect to ${url}` } as any)
loader.dismiss()
return
}
loader.message = 'Saving...'
try {
await this.api.setDbValue({ pointer: `/marketplace`, value: marketplace })
} catch (e) {
this.errToast.present({ message: `Error saving marketplace data` } as any)
} finally {
loader.dismiss()
}
}
private async saveAndConnect(url: string): Promise<void> {
const marketplace = JSON.parse(
JSON.stringify(this.patch.data.ui.marketplace),
) as UIMarketplaceData
// no-op on duplicates
const currentUrls = Object.values(marketplace.options).map(
u => new URL(u.url).hostname,
)
if (currentUrls.includes(new URL(url).hostname)) return
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Validating Marketplace...',
cssClass: 'loader',
})
await loader.present()
try {
const id = v4()
const { name } = await this.api.getMarketplaceData({}, url)
marketplace.options[id] = { name, url }
marketplace['selected-id'] = id
} catch (e) {
this.errToast.present({ message: `Could not connect to ${url}` } as any)
loader.dismiss()
return
}
loader.message = 'Saving...'
try {
await this.api.setDbValue({ pointer: `/marketplace`, value: marketplace })
} catch (e) {
this.errToast.present({ message: `Error saving marketplace data` } as any)
loader.dismiss()
return
}
loader.message = 'Syncing store...'
try {
await this.marketplaceService.load()
} catch (e) {
this.errToast.present({
message: `Error syncing marketplace data`,
} as any)
} finally {
loader.dismiss()
}
}
}
function getMarketplaceValueSpec(): ValueSpecObject {
return {
type: 'object',
name: 'Add Marketplace',
'unique-by': null,
spec: {
url: {
type: 'string',
name: 'URL',
description: 'The fully-qualified URL of the alternative marketplace.',
nullable: false,
masked: false,
copyable: false,
pattern: `https?:\/\/[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}`,
'pattern-description': 'Must be a valid URL',
placeholder: 'e.g. https://example.org',
},
},
}
}

View File

@@ -4,11 +4,17 @@ import { Routes, RouterModule } from '@angular/router'
const routes: Routes = [
{
path: '',
loadChildren: () => import('./server-show/server-show.module').then(m => m.ServerShowPageModule),
loadChildren: () =>
import('./server-show/server-show.module').then(
m => m.ServerShowPageModule,
),
},
{
path: 'backup',
loadChildren: () => import('./server-backup/server-backup.module').then(m => m.ServerBackupPageModule),
loadChildren: () =>
import('./server-backup/server-backup.module').then(
m => m.ServerBackupPageModule,
),
},
{
path: 'lan',
@@ -16,35 +22,60 @@ const routes: Routes = [
},
{
path: 'logs',
loadChildren: () => import('./server-logs/server-logs.module').then(m => m.ServerLogsPageModule),
loadChildren: () =>
import('./server-logs/server-logs.module').then(
m => m.ServerLogsPageModule,
),
},
{
path: 'marketplaces',
loadChildren: () =>
import('./marketplaces/marketplaces.module').then(
m => m.MarketplacesPageModule,
),
},
{
path: 'metrics',
loadChildren: () => import('./server-metrics/server-metrics.module').then(m => m.ServerMetricsPageModule),
loadChildren: () =>
import('./server-metrics/server-metrics.module').then(
m => m.ServerMetricsPageModule,
),
},
{
path: 'preferences',
loadChildren: () => import('./preferences/preferences.module').then( m => m.PreferencesPageModule),
loadChildren: () =>
import('./preferences/preferences.module').then(
m => m.PreferencesPageModule,
),
},
{
path: 'restore',
loadChildren: () => import('./restore/restore.component.module').then( m => m.RestorePageModule),
loadChildren: () =>
import('./restore/restore.component.module').then(
m => m.RestorePageModule,
),
},
{
path: 'sessions',
loadChildren: () => import('./sessions/sessions.module').then( m => m.SessionsPageModule),
loadChildren: () =>
import('./sessions/sessions.module').then(m => m.SessionsPageModule),
},
{
path: 'specs',
loadChildren: () => import('./server-specs/server-specs.module').then(m => m.ServerSpecsPageModule),
loadChildren: () =>
import('./server-specs/server-specs.module').then(
m => m.ServerSpecsPageModule,
),
},
{
path: 'ssh',
loadChildren: () => import('./ssh-keys/ssh-keys.module').then( m => m.SSHKeysPageModule),
loadChildren: () =>
import('./ssh-keys/ssh-keys.module').then(m => m.SSHKeysPageModule),
},
{
path: 'wifi',
loadChildren: () => import('./wifi/wifi.module').then(m => m.WifiPageModule),
loadChildren: () =>
import('./wifi/wifi.module').then(m => m.WifiPageModule),
},
]
@@ -52,4 +83,4 @@ const routes: Routes = [
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class ServerRoutingModule { }
export class ServerRoutingModule {}

View File

@@ -1,5 +1,10 @@
import { Component } from '@angular/core'
import { AlertController, LoadingController, NavController, IonicSafeString } from '@ionic/angular'
import {
AlertController,
LoadingController,
NavController,
IonicSafeString,
} from '@ionic/angular'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ActivatedRoute } from '@angular/router'
import { ErrorToastService } from 'src/app/services/error-toast.service'
@@ -16,7 +21,7 @@ import { map } from 'rxjs/operators'
export class ServerShowPage {
ServerStatus = ServerStatus
constructor (
constructor(
private readonly alertCtrl: AlertController,
private readonly loadingCtrl: LoadingController,
private readonly errToast: ErrorToastService,
@@ -24,12 +29,13 @@ export class ServerShowPage {
private readonly navCtrl: NavController,
private readonly route: ActivatedRoute,
public readonly patch: PatchDbService,
) { }
) {}
async presentAlertRestart () {
async presentAlertRestart() {
const alert = await this.alertCtrl.create({
header: 'Confirm',
message: 'Are you sure you want to restart your Embassy? It can take several minutes to come back online.',
message:
'Are you sure you want to restart your Embassy? It can take several minutes to come back online.',
buttons: [
{
text: 'Cancel',
@@ -47,11 +53,12 @@ export class ServerShowPage {
await alert.present()
}
async presentAlertShutdown () {
async presentAlertShutdown() {
const sts = this.patch.data['server-info'].status
const alert = await this.alertCtrl.create({
header: 'Warning',
message: 'Are you sure you want to power down your Embassy? This can take several minutes, and your Embassy will not come back online automatically. To power on again, You will need to physically unplug your Embassy and plug it back in.',
message:
'Are you sure you want to power down your Embassy? This can take several minutes, and your Embassy will not come back online automatically. To power on again, You will need to physically unplug your Embassy and plug it back in.',
buttons: [
{
text: 'Cancel',
@@ -69,11 +76,13 @@ export class ServerShowPage {
await alert.present()
}
async presentAlertSystemRebuild () {
async presentAlertSystemRebuild() {
const minutes = Object.keys(this.patch.data['package-data']).length * 2
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.`),
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.`,
),
buttons: [
{
text: 'Cancel',
@@ -91,7 +100,7 @@ export class ServerShowPage {
await alert.present()
}
private async restart () {
private async restart() {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Restarting...',
@@ -100,7 +109,7 @@ export class ServerShowPage {
await loader.present()
try {
await this.embassyApi.restartServer({ })
await this.embassyApi.restartServer({})
} catch (e) {
this.errToast.present(e)
} finally {
@@ -108,7 +117,7 @@ export class ServerShowPage {
}
}
private async shutdown () {
private async shutdown() {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Shutting down...',
@@ -117,7 +126,7 @@ export class ServerShowPage {
await loader.present()
try {
await this.embassyApi.shutdownServer({ })
await this.embassyApi.shutdownServer({})
} catch (e) {
this.errToast.present(e)
} finally {
@@ -125,7 +134,7 @@ export class ServerShowPage {
}
}
private async systemRebuild () {
private async systemRebuild() {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Hard Restarting...',
@@ -134,7 +143,7 @@ export class ServerShowPage {
await loader.present()
try {
await this.embassyApi.systemRebuild({ })
await this.embassyApi.systemRebuild({})
} catch (e) {
this.errToast.present(e)
} finally {
@@ -143,12 +152,13 @@ export class ServerShowPage {
}
settings: ServerSettings = {
'Backups': [
Backups: [
{
title: 'Create Backup',
description: 'Back up your Embassy and all its services',
icon: 'save-outline',
action: () => this.navCtrl.navigateForward(['backup'], { relativeTo: this.route }),
action: () =>
this.navCtrl.navigateForward(['backup'], { relativeTo: this.route }),
detail: true,
disabled: of(false),
},
@@ -156,17 +166,25 @@ export class ServerShowPage {
title: 'Restore From Backup',
description: 'Restore one or more services from a prior backup',
icon: 'color-wand-outline',
action: () => this.navCtrl.navigateForward(['restore'], { relativeTo: this.route }),
action: () =>
this.navCtrl.navigateForward(['restore'], { relativeTo: this.route }),
detail: true,
disabled: this.patch.watch$('server-info', 'status').pipe(map(status => [ServerStatus.Updated, ServerStatus.BackingUp].includes(status))),
disabled: this.patch
.watch$('server-info', 'status')
.pipe(
map(status =>
[ServerStatus.Updated, ServerStatus.BackingUp].includes(status),
),
),
},
],
'Insights': [
Insights: [
{
title: 'About',
description: 'Basic information about your Embassy',
icon: 'information-circle-outline',
action: () => this.navCtrl.navigateForward(['specs'], { relativeTo: this.route }),
action: () =>
this.navCtrl.navigateForward(['specs'], { relativeTo: this.route }),
detail: true,
disabled: of(false),
},
@@ -174,7 +192,8 @@ export class ServerShowPage {
title: 'Monitor',
description: 'CPU, disk, memory, and other useful metrics',
icon: 'pulse',
action: () => this.navCtrl.navigateForward(['metrics'], { relativeTo: this.route }),
action: () =>
this.navCtrl.navigateForward(['metrics'], { relativeTo: this.route }),
detail: true,
disabled: of(false),
},
@@ -182,17 +201,21 @@ export class ServerShowPage {
title: 'Logs',
description: 'Raw, unfiltered device logs',
icon: 'newspaper-outline',
action: () => this.navCtrl.navigateForward(['logs'], { relativeTo: this.route }),
action: () =>
this.navCtrl.navigateForward(['logs'], { relativeTo: this.route }),
detail: true,
disabled: of(false),
},
],
'Settings': [
Settings: [
{
title: 'Preferences',
description: 'Device name, background tasks',
icon: 'options-outline',
action: () => this.navCtrl.navigateForward(['preferences'], { relativeTo: this.route }),
action: () =>
this.navCtrl.navigateForward(['preferences'], {
relativeTo: this.route,
}),
detail: true,
disabled: of(false),
},
@@ -200,7 +223,8 @@ export class ServerShowPage {
title: 'LAN',
description: 'Access your Embassy on the Local Area Network',
icon: 'home-outline',
action: () => this.navCtrl.navigateForward(['lan'], { relativeTo: this.route }),
action: () =>
this.navCtrl.navigateForward(['lan'], { relativeTo: this.route }),
detail: true,
disabled: of(false),
},
@@ -208,16 +232,28 @@ export class ServerShowPage {
title: 'SSH',
description: 'Access your Embassy from the command line',
icon: 'terminal-outline',
action: () => this.navCtrl.navigateForward(['ssh'], { relativeTo: this.route }),
action: () =>
this.navCtrl.navigateForward(['ssh'], { relativeTo: this.route }),
detail: true,
disabled: of(false),
},
{
title: 'WiFi',
description: 'Add or remove WiFi networks',
icon: 'wifi',
action: () => this.navCtrl.navigateForward(['wifi'], { relativeTo: this.route }),
action: () =>
this.navCtrl.navigateForward(['wifi'], { relativeTo: this.route }),
detail: true,
disabled: of(false),
},
{
title: 'Marketplace Settings',
description: 'Add or remove marketplaces',
icon: 'storefront',
action: () =>
this.navCtrl.navigateForward(['marketplaces'], {
relativeTo: this.route,
}),
detail: true,
disabled: of(false),
},
@@ -225,12 +261,15 @@ export class ServerShowPage {
title: 'Active Sessions',
description: 'View and manage device access',
icon: 'desktop-outline',
action: () => this.navCtrl.navigateForward(['sessions'], { relativeTo: this.route }),
action: () =>
this.navCtrl.navigateForward(['sessions'], {
relativeTo: this.route,
}),
detail: true,
disabled: of(false),
},
],
'Power': [
Power: [
{
title: 'Restart',
description: '',
@@ -258,7 +297,7 @@ export class ServerShowPage {
],
}
asIsOrder () {
asIsOrder() {
return 0
}
}

View File

@@ -15,13 +15,19 @@
<ion-content class="ion-padding-top">
<ion-item-group>
<!-- always -->
<ion-item>
<ion-label>
<h2>
Adding WiFi credentials to your Embassy allows you to remove the Ethernet cable and move the device anywhere you want. Embassy will automatically connect to available networks.
<a href="https://docs.start9.com/user-manual/general/wifi.html" target="_blank" rel="noreferrer">View instructions</a>
Adding WiFi credentials to your Embassy allows you to remove the
Ethernet cable and move the device anywhere you want. Embassy will
automatically connect to available networks.
<a
href="https://docs.start9.com/user-manual/general/wifi.html"
target="_blank"
rel="noreferrer"
>View instructions</a
>
</h2>
</ion-label>
</ion-item>
@@ -29,9 +35,16 @@
<ion-item-divider>Country</ion-item-divider>
<!-- not loading -->
<ion-item button detail="false" (click)="presentAlertCountry()" [disabled]="loading">
<ion-item
button
detail="false"
(click)="presentAlertCountry()"
[disabled]="loading"
>
<ion-icon slot="start" name="earth-outline" size="large"></ion-icon>
<ion-label *ngIf="wifi.country">{{ wifi.country }} - {{ this.countries[wifi.country] }}</ion-label>
<ion-label *ngIf="wifi.country"
>{{ wifi.country }} - {{ this.countries[wifi.country] }}</ion-label
>
<ion-label *ngIf="!wifi.country">Select Country</ion-label>
</ion-item>
@@ -40,20 +53,26 @@
<ion-item-divider>Saved Networks</ion-item-divider>
<ion-item *ngFor="let entry of ['', '']" class="skeleton-parts">
<ion-button slot="start" fill="clear">
<ion-skeleton-text animated style="width: 30px; height: 30px; border-radius: 0;"></ion-skeleton-text>
<ion-skeleton-text
animated
style="width: 30px; height: 30px; border-radius: 0"
></ion-skeleton-text>
</ion-button>
<ion-label>
<ion-skeleton-text animated style="width: 18%;"></ion-skeleton-text>
<ion-skeleton-text animated style="width: 18%"></ion-skeleton-text>
</ion-label>
</ion-item>
<ion-item-divider>Available Networks</ion-item-divider>
<ion-item *ngFor="let entry of ['', '']" class="skeleton-parts">
<ion-button slot="start" fill="clear">
<ion-skeleton-text animated style="width: 30px; height: 30px; border-radius: 0;"></ion-skeleton-text>
<ion-skeleton-text
animated
style="width: 30px; height: 30px; border-radius: 0"
></ion-skeleton-text>
</ion-button>
<ion-label>
<ion-skeleton-text animated style="width: 18%;"></ion-skeleton-text>
<ion-skeleton-text animated style="width: 18%"></ion-skeleton-text>
</ion-label>
</ion-item>
</ng-container>
@@ -61,29 +80,88 @@
<!-- not loading -->
<ng-container *ngIf="!loading && wifi.country">
<ion-item-divider>Saved Networks</ion-item-divider>
<ion-item button detail="false" *ngFor="let ssid of wifi.ssids | keyvalue" (click)="presentAction(ssid.key)">
<div *ngIf="ssid.key !== wifi.connected" slot="start" style="padding-right: 32px;"></div>
<ion-icon *ngIf="ssid.key === wifi.connected" slot="start" size="large" name="checkmark" color="success"></ion-icon>
<ion-item
button
detail="false"
*ngFor="let ssid of wifi.ssids | keyvalue"
(click)="presentAction(ssid.key)"
>
<div
*ngIf="ssid.key !== wifi.connected"
slot="start"
style="padding-right: 32px"
></div>
<ion-icon
*ngIf="ssid.key === wifi.connected"
slot="start"
size="large"
name="checkmark"
color="success"
></ion-icon>
<ion-label>{{ ssid.key }}</ion-label>
<img *ngIf="ssid.value > 0 && ssid.value < 5" slot="end" src="assets/img/icons/wifi-1.png" style="max-width: 32px;" />
<img *ngIf="ssid.value >= 5 && ssid.value < 50" slot="end" src="assets/img/icons/wifi-1.png" style="max-width: 32px;" />
<img *ngIf="ssid.value >= 50 && ssid.value < 90" slot="end" src="assets/img/icons/wifi-2.png" style="max-width: 32px;" />
<img *ngIf="ssid.value >= 90" slot="end" src="assets/img/icons/wifi-3.png" style="max-width: 32px;" />
<img
*ngIf="ssid.value > 0 && ssid.value < 5"
slot="end"
src="assets/img/icons/wifi-1.png"
style="max-width: 32px"
/>
<img
*ngIf="ssid.value >= 5 && ssid.value < 50"
slot="end"
src="assets/img/icons/wifi-1.png"
style="max-width: 32px"
/>
<img
*ngIf="ssid.value >= 50 && ssid.value < 90"
slot="end"
src="assets/img/icons/wifi-2.png"
style="max-width: 32px"
/>
<img
*ngIf="ssid.value >= 90"
slot="end"
src="assets/img/icons/wifi-3.png"
style="max-width: 32px"
/>
</ion-item>
<ion-item-divider>Available Networks</ion-item-divider>
<ion-item button detail="false" *ngFor="let avWifi of wifi['available-wifi']" (click)="presentModalAdd(avWifi.ssid, !!avWifi.security.length)">
<ion-icon slot="start" name="add" size="large"></ion-icon>
<ion-item
button
detail="false"
*ngFor="let avWifi of wifi['available-wifi']"
(click)="presentModalAdd(avWifi.ssid, !!avWifi.security.length)"
>
<ion-label>{{ avWifi.ssid }}</ion-label>
<img *ngIf="avWifi.strength < 5" slot="end" src="assets/img/icons/wifi-1.png" style="max-width: 32px;" />
<img *ngIf="avWifi.strength >= 5 && avWifi.strength < 50" slot="end" src="assets/img/icons/wifi-1.png" style="max-width: 32px;" />
<img *ngIf="avWifi.strength >= 50 && avWifi.strength < 90" slot="end" src="assets/img/icons/wifi-2.png" style="max-width: 32px;" />
<img *ngIf="avWifi.strength >= 90" slot="end" src="assets/img/icons/wifi-3.png" style="max-width: 32px;" />
<img
*ngIf="avWifi.strength < 5"
slot="end"
src="assets/img/icons/wifi-1.png"
style="max-width: 32px"
/>
<img
*ngIf="avWifi.strength >= 5 && avWifi.strength < 50"
slot="end"
src="assets/img/icons/wifi-1.png"
style="max-width: 32px"
/>
<img
*ngIf="avWifi.strength >= 50 && avWifi.strength < 90"
slot="end"
src="assets/img/icons/wifi-2.png"
style="max-width: 32px"
/>
<img
*ngIf="avWifi.strength >= 90"
slot="end"
src="assets/img/icons/wifi-3.png"
style="max-width: 32px"
/>
</ion-item>
<ion-item button detail="false" (click)="presentModalAdd()">
<ion-icon slot="start" name="add" size="large"></ion-icon>
<ion-label>Other</ion-label>
<ion-label>Join other network</ion-label>
</ion-item>
</ng-container>
</ion-item-group>
</ion-content>
</ion-content>

View File

@@ -1,5 +1,11 @@
import { Component } from '@angular/core'
import { ActionSheetController, AlertController, LoadingController, ModalController, ToastController } from '@ionic/angular'
import {
ActionSheetController,
AlertController,
LoadingController,
ModalController,
ToastController,
} from '@ionic/angular'
import { AlertInput } from '@ionic/core'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ActionSheetButton } from '@ionic/core'
@@ -17,10 +23,12 @@ import { ConfigService } from 'src/app/services/config.service'
})
export class WifiPage {
loading = true
wifi: RR.GetWifiRes = { } as any
countries = require('../../../util/countries.json') as { [key: string]: string }
wifi: RR.GetWifiRes = {} as any
countries = require('../../../util/countries.json') as {
[key: string]: string
}
constructor (
constructor(
private readonly api: ApiService,
private readonly toastCtrl: ToastController,
private readonly alertCtrl: AlertController,
@@ -29,9 +37,9 @@ export class WifiPage {
private readonly errToast: ErrorToastService,
private readonly actionCtrl: ActionSheetController,
private readonly config: ConfigService,
) { }
) {}
async ngOnInit () {
async ngOnInit() {
try {
await this.getWifi()
} catch (e) {
@@ -41,24 +49,24 @@ export class WifiPage {
}
}
async getWifi (timeout?: number): Promise<void> {
this.wifi = await this.api.getWifi({ }, timeout)
async getWifi(timeout?: number): Promise<void> {
this.wifi = await this.api.getWifi({}, timeout)
if (!this.wifi.country) {
await this.presentAlertCountry()
}
}
async presentAlertCountry (): Promise<void> {
async presentAlertCountry(): Promise<void> {
if (!this.config.isLan) {
const alert = await this.alertCtrl.create({
header: 'Cannot Complete Action',
message: 'You must be connected to your Emassy via LAN to change the country.',
message:
'You must be connected to your Emassy via LAN to change the country.',
buttons: [
{
text: 'OK',
role: 'cancel',
},
],
cssClass: 'wide-alert enter-click',
})
@@ -66,19 +74,22 @@ export class WifiPage {
return
}
const inputs: AlertInput[] = Object.entries(this.countries).map(([country, fullName]) => {
return {
name: fullName,
type: 'radio',
label: `${country} - ${fullName}`,
value: country,
checked: country === this.wifi.country,
}
})
const inputs: AlertInput[] = Object.entries(this.countries).map(
([country, fullName]) => {
return {
name: fullName,
type: 'radio',
label: `${country} - ${fullName}`,
value: country,
checked: country === this.wifi.country,
}
},
)
const alert = await this.alertCtrl.create({
header: 'Select Country',
message: 'Warning: Changing the country will delete all saved networks from the Embassy.',
subHeader:
'Warning: Changing the country will delete all saved networks from the Embassy.',
inputs,
buttons: [
{
@@ -92,12 +103,12 @@ export class WifiPage {
},
},
],
cssClass: 'wide-alert enter-click',
cssClass: 'wide-alert enter-click select-warning',
})
await alert.present()
}
async presentModalAdd (ssid?: string, needsPW: boolean = true) {
async presentModalAdd(ssid?: string, needsPW: boolean = true) {
const wifiSpec = getWifiValueSpec(ssid, needsPW)
const modal = await this.modalCtrl.create({
component: GenericFormPage,
@@ -107,13 +118,13 @@ export class WifiPage {
buttons: [
{
text: 'Save for Later',
handler: async (value: { ssid: string, password: string }) => {
handler: async (value: { ssid: string; password: string }) => {
await this.save(value.ssid, value.password)
},
},
{
text: 'Save and Connect',
handler: async (value: { ssid: string, password: string }) => {
handler: async (value: { ssid: string; password: string }) => {
await this.saveAndConnect(value.ssid, value.password)
},
isSubmit: true,
@@ -125,7 +136,7 @@ export class WifiPage {
await modal.present()
}
async presentAction (ssid: string) {
async presentAction(ssid: string) {
const buttons: ActionSheetButton[] = [
{
text: 'Forget',
@@ -138,15 +149,13 @@ export class WifiPage {
]
if (ssid !== this.wifi.connected) {
buttons.unshift(
{
text: 'Connect',
icon: 'wifi',
handler: () => {
this.connect(ssid)
},
buttons.unshift({
text: 'Connect',
icon: 'wifi',
handler: () => {
this.connect(ssid)
},
)
})
}
const action = await this.actionCtrl.create({
@@ -159,7 +168,7 @@ export class WifiPage {
await action.present()
}
private async setCountry (country: string): Promise<void> {
private async setCountry(country: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
cssClass: 'loader',
@@ -177,7 +186,10 @@ export class WifiPage {
}
}
private async confirmWifi (ssid: string, deleteOnFailure = false): Promise<void> {
private async confirmWifi(
ssid: string,
deleteOnFailure = false,
): Promise<void> {
const timeout = 4000
const maxAttempts = 5
let attempts = 0
@@ -210,10 +222,11 @@ export class WifiPage {
}
}
private async presentAlertSuccess (ssid: string): Promise<void> {
private async presentAlertSuccess(ssid: string): Promise<void> {
const alert = await this.alertCtrl.create({
header: `Connected to "${ssid}"`,
message: 'Note. It may take several minutes to an hour for your Embassy to reconnect over Tor.',
message:
'Note. It may take several minutes to an hour for your Embassy to reconnect over Tor.',
buttons: [
{
text: 'Ok',
@@ -226,7 +239,7 @@ export class WifiPage {
await alert.present()
}
private async presentToastFail (): Promise<void> {
private async presentToastFail(): Promise<void> {
const toast = await this.toastCtrl.create({
header: 'Failed to connect:',
message: `Check credentials and try again`,
@@ -247,7 +260,7 @@ export class WifiPage {
await toast.present()
}
private async connect (ssid: string): Promise<void> {
private async connect(ssid: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Connecting. This could take a while...',
@@ -265,7 +278,7 @@ export class WifiPage {
}
}
private async delete (ssid: string): Promise<void> {
private async delete(ssid: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Deleting...',
@@ -284,7 +297,7 @@ export class WifiPage {
}
}
private async save (ssid: string, password: string): Promise<void> {
private async save(ssid: string, password: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Saving...',
@@ -307,7 +320,7 @@ export class WifiPage {
}
}
private async saveAndConnect (ssid: string, password: string): Promise<void> {
private async saveAndConnect(ssid: string, password: string): Promise<void> {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
message: 'Connecting. This could take a while...',
@@ -324,7 +337,6 @@ export class WifiPage {
})
await this.confirmWifi(ssid, true)
} catch (e) {
this.errToast.present(e)
} finally {
@@ -333,11 +345,15 @@ export class WifiPage {
}
}
function getWifiValueSpec (ssid?: string, needsPW: boolean = true): ValueSpecObject {
function getWifiValueSpec(
ssid?: string,
needsPW: boolean = true,
): ValueSpecObject {
return {
type: 'object',
name: 'WiFi Credentials',
description: 'Enter the network SSID and password. You can connect now or save the network for later.',
description:
'Enter the network SSID and password. You can connect now or save the network for later.',
'unique-by': null,
spec: {
ssid: {
@@ -358,4 +374,3 @@ function getWifiValueSpec (ssid?: string, needsPW: boolean = true): ValueSpecObj
},
}
}

View File

@@ -1,25 +1,29 @@
import { Dump, Revision } from 'patch-db-client'
import { PackagePropertiesVersioned } from 'src/app/util/properties.util'
import { ConfigSpec } from 'src/app/pkg-config/config-types'
import { DataModel, DependencyError, Manifest, URL } from 'src/app/services/patch-db/data-model'
import {
DataModel,
DependencyError,
Manifest,
URL,
} from 'src/app/services/patch-db/data-model'
export module RR {
// DB
export type GetRevisionsRes = Revision[] | Dump<DataModel>
export type GetDumpRes = Dump<DataModel>
export type SetDBValueReq = WithExpire<{ pointer: string, value: any }> // db.put.ui
export type SetDBValueReq = WithExpire<{ pointer: string; value: any }> // db.put.ui
export type SetDBValueRes = WithRevision<null>
// auth
export type LoginReq = { password: string, metadata: SessionMetadata } // auth.login - unauthed
export type LoginReq = { password: string; metadata: SessionMetadata } // auth.login - unauthed
export type loginRes = null
export type LogoutReq = { } // auth.logout
export type LogoutReq = {} // auth.logout
export type LogoutRes = null
// server
@@ -27,27 +31,31 @@ export module RR {
export type SetShareStatsReq = WithExpire<{ value: boolean }> // server.config.share-stats
export type SetShareStatsRes = WithRevision<null>
export type GetServerLogsReq = { cursor?: string, before_flag?: boolean, limit?: number }
export type GetServerLogsReq = {
cursor?: string
before_flag?: boolean
limit?: number
}
export type GetServerLogsRes = LogsRes
export type GetServerMetricsReq = { } // server.metrics
export type GetServerMetricsReq = {} // server.metrics
export type GetServerMetricsRes = Metrics
export type UpdateServerReq = WithExpire<{ }> // server.update
export type UpdateServerReq = WithExpire<{}> // server.update
export type UpdateServerRes = WithRevision<'updating' | 'no-updates'>
export type RestartServerReq = { } // server.restart
export type RestartServerReq = {} // server.restart
export type RestartServerRes = null
export type ShutdownServerReq = { } // server.shutdown
export type ShutdownServerReq = {} // server.shutdown
export type ShutdownServerRes = null
export type SystemRebuildReq = { } // server.rebuild
export type SystemRebuildReq = {} // server.rebuild
export type SystemRebuildRes = null
// sessions
export type GetSessionsReq = { } // sessions.list
export type GetSessionsReq = {} // sessions.list
export type GetSessionsRes = {
current: string
sessions: { [hash: string]: Session }
@@ -71,7 +79,10 @@ export module RR {
// notification
export type GetNotificationsReq = WithExpire<{ before?: number, limit?: number }> // notification.list
export type GetNotificationsReq = WithExpire<{
before?: number
limit?: number
}> // notification.list
export type GetNotificationsRes = WithRevision<ServerNotification<number>[]>
export type DeleteNotificationReq = { id: number } // notification.delete
@@ -85,18 +96,19 @@ export module RR {
export type SetWifiCountryReq = { country: string }
export type SetWifiCountryRes = null
export type GetWifiReq = { }
export type GetWifiReq = {}
export type GetWifiRes = {
ssids: {
[ssid: string]: number
},
connected?: string,
country: string,
ethernet: boolean,
[ssid: string]: number
}
connected?: string
country: string
ethernet: boolean
'available-wifi': AvailableWifi[]
}
}
export type AddWifiReq = { // wifi.add
export type AddWifiReq = {
// wifi.add
ssid: string
password: string
priority: number
@@ -112,7 +124,7 @@ export module RR {
// ssh
export type GetSSHKeysReq = { } // ssh.list
export type GetSSHKeysReq = {} // ssh.list
export type GetSSHKeysRes = SSHKey[]
export type AddSSHKeyReq = { key: string } // ssh.add
@@ -123,10 +135,11 @@ export module RR {
// backup
export type GetBackupTargetsReq = { } // backup.target.list
export type GetBackupTargetsReq = {} // backup.target.list
export type GetBackupTargetsRes = { [id: string]: BackupTarget }
export type AddBackupTargetReq = { // backup.target.cifs.add
export type AddBackupTargetReq = {
// backup.target.cifs.add
hostname: string
path: string
username: string
@@ -140,10 +153,11 @@ export module RR {
export type RemoveBackupTargetReq = { id: string } // backup.target.cifs.remove
export type RemoveBackupTargetRes = null
export type GetBackupInfoReq = { 'target-id': string, password: string } // backup.target.info
export type GetBackupInfoReq = { 'target-id': string; password: string } // backup.target.info
export type GetBackupInfoRes = BackupInfo
export type CreateBackupReq = WithExpire<{ // backup.create
export type CreateBackupReq = WithExpire<{
// backup.create
'target-id': string
'old-password': string | null
password: string
@@ -153,40 +167,58 @@ export module RR {
// package
export type GetPackagePropertiesReq = { id: string } // package.properties
export type GetPackagePropertiesRes<T extends number> = PackagePropertiesVersioned<T>
export type GetPackagePropertiesRes<T extends number> =
PackagePropertiesVersioned<T>
export type LogsRes = { entries: Log[], 'start-cursor'?: string, 'end-cursor'?: string }
export type LogsRes = {
entries: Log[]
'start-cursor'?: string
'end-cursor'?: string
}
export type GetPackageLogsReq = { id: string, cursor?: string, before_flag?: boolean, limit?: number } // package.logs
export type GetPackageLogsReq = {
id: string
cursor?: string
before_flag?: boolean
limit?: number
} // package.logs
export type GetPackageLogsRes = LogsRes
export type GetPackageMetricsReq = { id: string } // package.metrics
export type GetPackageMetricsRes = Metric
export type InstallPackageReq = WithExpire<{ id: string, 'version-spec'?: string }> // package.install
export type InstallPackageReq = WithExpire<{
id: string
'version-spec'?: string
}> // package.install
export type InstallPackageRes = WithRevision<null>
export type DryUpdatePackageReq = { id: string, version: string } // package.update.dry
export type DryUpdatePackageReq = { id: string; version: string } // package.update.dry
export type DryUpdatePackageRes = Breakages
export type GetPackageConfigReq = { id: string } // package.config.get
export type GetPackageConfigRes = { spec: ConfigSpec, config: object }
export type GetPackageConfigRes = { spec: ConfigSpec; config: object }
export type DrySetPackageConfigReq = { id: string, config: object } // package.config.set.dry
export type DrySetPackageConfigReq = { id: string; config: object } // package.config.set.dry
export type DrySetPackageConfigRes = Breakages
export type SetPackageConfigReq = WithExpire<DrySetPackageConfigReq> // package.config.set
export type SetPackageConfigRes = WithRevision<null>
export type RestorePackagesReq = WithExpire<{ // package.backup.restore
export type RestorePackagesReq = WithExpire<{
// package.backup.restore
ids: string[]
'target-id': string
'old-password': string | null,
'old-password': string | null
password: string
}>
export type RestorePackagesRes = WithRevision<null>
export type ExecutePackageActionReq = { id: string, 'action-id': string, input?: object } // package.action
export type ExecutePackageActionReq = {
id: string
'action-id': string
input?: object
} // package.action
export type ExecutePackageActionRes = ActionResponse
export type StartPackageReq = WithExpire<{ id: string }> // package.start
@@ -207,7 +239,10 @@ export module RR {
export type DeleteRecoveredPackageReq = { id: string } // package.delete-recovered
export type DeleteRecoveredPackageRes = WithRevision<null>
export type DryConfigureDependencyReq = { 'dependency-id': string, 'dependent-id': string } // package.dependency.configure.dry
export type DryConfigureDependencyReq = {
'dependency-id': string
'dependent-id': string
} // package.dependency.configure.dry
export type DryConfigureDependencyRes = {
'old-config': object
'new-config': object
@@ -216,7 +251,7 @@ export module RR {
// marketplace
export type GetMarketplaceDataReq = { }
export type GetMarketplaceDataReq = { url?: string }
export type GetMarketplaceDataRes = MarketplaceData
export type GetMarketplaceEOSReq = {
@@ -225,13 +260,14 @@ export module RR {
export type GetMarketplaceEOSRes = MarketplaceEOS
export type GetMarketplacePackagesReq = {
ids?: { id: string, version: string }[]
ids?: { id: string; version: string }[]
'eos-version-compat': string
// iff !ids
category?: string
query?: string
page?: string
'per-page'?: string
url?: string
}
export type GetMarketplacePackagesRes = MarketplacePkg[]
@@ -240,14 +276,14 @@ export module RR {
export type GetLatestVersionReq = { ids: string[] }
export type GetLatestVersionRes = { [id: string]: string }
}
export type WithExpire<T> = { 'expire-id'?: string } & T
export type WithRevision<T> = { response: T, revision?: Revision }
export type WithRevision<T> = { response: T; revision?: Revision }
export interface MarketplaceData {
categories: string[]
name: string
}
export interface MarketplaceEOS {
@@ -268,7 +304,7 @@ export interface MarketplacePkg {
title: string
icon: URL
}
},
}
}
export interface Breakages {
@@ -318,7 +354,22 @@ export interface SessionMetadata {
platforms: PlatformType[]
}
export type PlatformType = 'cli' | 'ios' | 'ipad' | 'iphone' | 'android' | 'phablet' | 'tablet' | 'cordova' | 'capacitor' | 'electron' | 'pwa' | 'mobile' | 'mobileweb' | 'desktop' | 'hybrid'
export type PlatformType =
| 'cli'
| 'ios'
| 'ipad'
| 'iphone'
| 'android'
| 'phablet'
| 'tablet'
| 'cordova'
| 'capacitor'
| 'electron'
| 'pwa'
| 'mobile'
| 'mobileweb'
| 'desktop'
| 'hybrid'
export type BackupTarget = DiskBackupTarget | CifsBackupTarget
@@ -387,8 +438,8 @@ export interface EmbassyOsDiskInfo {
}
export interface BackupInfo {
version: string,
timestamp: string,
version: string
timestamp: string
'package-backups': {
[id: string]: PackageBackupInfo
}
@@ -432,9 +483,11 @@ export enum NotificationLevel {
Error = 'error',
}
export type NotificationData<T> = T extends 0 ? null :
T extends 1 ? BackupReport :
any
export type NotificationData<T> = T extends 0
? null
: T extends 1
? BackupReport
: any
export interface BackupReport {
server: {
@@ -451,5 +504,5 @@ export interface BackupReport {
export interface AvailableWifi {
ssid: string
strength: number
security: string []
}
security: string[]
}

View File

@@ -1,5 +1,13 @@
import { Subject, Observable } from 'rxjs'
import { Http, Update, Operation, Revision, Source, Store, RPCResponse } from 'patch-db-client'
import {
Http,
Update,
Operation,
Revision,
Source,
Store,
RPCResponse,
} from 'patch-db-client'
import { RR } from './api.types'
import { DataModel } from 'src/app/services/patch-db/data-model'
import { RequestError } from '../http.service'
@@ -10,53 +18,63 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
/** PatchDb Source interface. Post/Patch requests provide a source of patches to the db. */
// sequenceStream '_' is not used by the live api, but is overridden by the mock
watch$ (_?: Store<DataModel>): Observable<RPCResponse<Update<DataModel>>> {
return this.sync$.asObservable().pipe(map( result => ({ result,
jsonrpc: '2.0'})))
watch$(_?: Store<DataModel>): Observable<RPCResponse<Update<DataModel>>> {
return this.sync$
.asObservable()
.pipe(map(result => ({ result, jsonrpc: '2.0' })))
}
// for getting static files: ex icons, instructions, licenses
abstract getStatic (url: string): Promise<string>
abstract getStatic(url: string): Promise<string>
// db
abstract getRevisions (since: number): Promise<RR.GetRevisionsRes>
abstract getRevisions(since: number): Promise<RR.GetRevisionsRes>
abstract getDump (): Promise<RR.GetDumpRes>
abstract getDump(): Promise<RR.GetDumpRes>
protected abstract setDbValueRaw (params: RR.SetDBValueReq): Promise<RR.SetDBValueRes>
setDbValue = (params: RR.SetDBValueReq) => this.syncResponse(
() => this.setDbValueRaw(params),
)()
protected abstract setDbValueRaw(
params: RR.SetDBValueReq,
): Promise<RR.SetDBValueRes>
setDbValue = (params: RR.SetDBValueReq) =>
this.syncResponse(() => this.setDbValueRaw(params))()
// auth
abstract login (params: RR.LoginReq): Promise<RR.loginRes>
abstract login(params: RR.LoginReq): Promise<RR.loginRes>
abstract logout (params: RR.LogoutReq): Promise<RR.LogoutRes>
abstract logout(params: RR.LogoutReq): Promise<RR.LogoutRes>
abstract getSessions (params: RR.GetSessionsReq): Promise<RR.GetSessionsRes>
abstract getSessions(params: RR.GetSessionsReq): Promise<RR.GetSessionsRes>
abstract killSessions (params: RR.KillSessionsReq): Promise<RR.KillSessionsRes>
abstract killSessions(params: RR.KillSessionsReq): Promise<RR.KillSessionsRes>
// server
protected abstract setShareStatsRaw (params: RR.SetShareStatsReq): Promise<RR.SetShareStatsRes>
setShareStats = (params: RR.SetShareStatsReq) => this.syncResponse(
() => this.setShareStatsRaw(params),
)()
protected abstract setShareStatsRaw(
params: RR.SetShareStatsReq,
): Promise<RR.SetShareStatsRes>
setShareStats = (params: RR.SetShareStatsReq) =>
this.syncResponse(() => this.setShareStatsRaw(params))()
abstract getServerLogs (params: RR.GetServerLogsReq): Promise<RR.GetServerLogsRes>
abstract getServerLogs(
params: RR.GetServerLogsReq,
): Promise<RR.GetServerLogsRes>
abstract getServerMetrics (params: RR.GetServerMetricsReq): Promise<RR.GetServerMetricsRes>
abstract getServerMetrics(
params: RR.GetServerMetricsReq,
): Promise<RR.GetServerMetricsRes>
abstract getPkgMetrics (params: RR.GetPackageMetricsReq): Promise<RR.GetPackageMetricsRes>
abstract getPkgMetrics(
params: RR.GetPackageMetricsReq,
): Promise<RR.GetPackageMetricsRes>
protected abstract updateServerRaw (params: RR.UpdateServerReq): Promise<RR.UpdateServerRes>
updateServer = (params: RR.UpdateServerReq) => this.syncResponse(
() => this.updateServerWrapper(params),
)()
async updateServerWrapper (params: RR.UpdateServerReq) {
protected abstract updateServerRaw(
params: RR.UpdateServerReq,
): Promise<RR.UpdateServerRes>
updateServer = (params: RR.UpdateServerReq) =>
this.syncResponse(() => this.updateServerWrapper(params))()
async updateServerWrapper(params: RR.UpdateServerReq) {
const res = await this.updateServerRaw(params)
if (res.response === 'no-updates') {
throw new Error('Could ont find a newer version of EmbassyOS')
@@ -64,23 +82,40 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
return res
}
abstract restartServer (params: RR.UpdateServerReq): Promise<RR.RestartServerRes>
abstract restartServer(
params: RR.UpdateServerReq,
): Promise<RR.RestartServerRes>
abstract shutdownServer (params: RR.ShutdownServerReq): Promise<RR.ShutdownServerRes>
abstract shutdownServer(
params: RR.ShutdownServerReq,
): Promise<RR.ShutdownServerRes>
abstract systemRebuild (params: RR.SystemRebuildReq): Promise<RR.SystemRebuildRes>
abstract systemRebuild(
params: RR.SystemRebuildReq,
): Promise<RR.SystemRebuildRes>
// marketplace URLs
abstract getEos (params: RR.GetMarketplaceEOSReq): Promise<RR.GetMarketplaceEOSRes>
abstract getEos(
params: RR.GetMarketplaceEOSReq,
): Promise<RR.GetMarketplaceEOSRes>
abstract getMarketplaceData (params: RR.GetMarketplaceDataReq): Promise<RR.GetMarketplaceDataRes>
abstract getMarketplaceData(
params: RR.GetMarketplaceDataReq,
url?: string,
): Promise<RR.GetMarketplaceDataRes>
abstract getMarketplacePkgs (params: RR.GetMarketplacePackagesReq): Promise<RR.GetMarketplacePackagesRes>
abstract getMarketplacePkgs(
params: RR.GetMarketplacePackagesReq,
): Promise<RR.GetMarketplacePackagesRes>
abstract getReleaseNotes (params: RR.GetReleaseNotesReq): Promise<RR.GetReleaseNotesRes>
abstract getReleaseNotes(
params: RR.GetReleaseNotesReq,
): Promise<RR.GetReleaseNotesRes>
abstract getLatestVersion (params: RR.GetLatestVersionReq): Promise<RR.GetLatestVersionRes>
abstract getLatestVersion(
params: RR.GetLatestVersionReq,
): Promise<RR.GetLatestVersionRes>
// protected abstract setPackageMarketplaceRaw (params: RR.SetPackageMarketplaceReq): Promise<RR.SetPackageMarketplaceRes>
// setPackageMarketplace = (params: RR.SetPackageMarketplaceReq) => this.syncResponse(
@@ -92,112 +127,162 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
// notification
abstract getNotificationsRaw (params: RR.GetNotificationsReq): Promise<RR.GetNotificationsRes>
getNotifications = (params: RR.GetNotificationsReq) => this.syncResponse<RR.GetNotificationsRes['response'], any>(
() => this.getNotificationsRaw(params),
)()
abstract getNotificationsRaw(
params: RR.GetNotificationsReq,
): Promise<RR.GetNotificationsRes>
getNotifications = (params: RR.GetNotificationsReq) =>
this.syncResponse<RR.GetNotificationsRes['response'], any>(() =>
this.getNotificationsRaw(params),
)()
abstract deleteNotification (params: RR.DeleteNotificationReq): Promise<RR.DeleteNotificationRes>
abstract deleteNotification(
params: RR.DeleteNotificationReq,
): Promise<RR.DeleteNotificationRes>
abstract deleteAllNotifications (params: RR.DeleteAllNotificationsReq): Promise<RR.DeleteAllNotificationsRes>
abstract deleteAllNotifications(
params: RR.DeleteAllNotificationsReq,
): Promise<RR.DeleteAllNotificationsRes>
// wifi
abstract getWifi (params: RR.GetWifiReq, timeout: number): Promise<RR.GetWifiRes>
abstract getWifi(
params: RR.GetWifiReq,
timeout: number,
): Promise<RR.GetWifiRes>
abstract setWifiCountry (params: RR.SetWifiCountryReq): Promise<RR.SetWifiCountryRes>
abstract setWifiCountry(
params: RR.SetWifiCountryReq,
): Promise<RR.SetWifiCountryRes>
abstract addWifi (params: RR.AddWifiReq): Promise<RR.AddWifiRes>
abstract addWifi(params: RR.AddWifiReq): Promise<RR.AddWifiRes>
abstract connectWifi (params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes>
abstract connectWifi(params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes>
abstract deleteWifi (params: RR.DeleteWifiReq): Promise<RR.ConnectWifiRes>
abstract deleteWifi(params: RR.DeleteWifiReq): Promise<RR.ConnectWifiRes>
// ssh
abstract getSshKeys (params: RR.GetSSHKeysReq): Promise<RR.GetSSHKeysRes>
abstract getSshKeys(params: RR.GetSSHKeysReq): Promise<RR.GetSSHKeysRes>
abstract addSshKey (params: RR.AddSSHKeyReq): Promise<RR.AddSSHKeyRes>
abstract addSshKey(params: RR.AddSSHKeyReq): Promise<RR.AddSSHKeyRes>
abstract deleteSshKey (params: RR.DeleteSSHKeyReq): Promise<RR.DeleteSSHKeyRes>
abstract deleteSshKey(params: RR.DeleteSSHKeyReq): Promise<RR.DeleteSSHKeyRes>
// backup
abstract getBackupTargets (params: RR.GetBackupTargetsReq): Promise<RR.GetBackupTargetsRes>
abstract getBackupTargets(
params: RR.GetBackupTargetsReq,
): Promise<RR.GetBackupTargetsRes>
abstract addBackupTarget (params: RR.AddBackupTargetReq): Promise<RR.AddBackupTargetRes>
abstract addBackupTarget(
params: RR.AddBackupTargetReq,
): Promise<RR.AddBackupTargetRes>
abstract updateBackupTarget (params: RR.UpdateBackupTargetReq): Promise<RR.UpdateBackupTargetRes>
abstract updateBackupTarget(
params: RR.UpdateBackupTargetReq,
): Promise<RR.UpdateBackupTargetRes>
abstract removeBackupTarget (params: RR.RemoveBackupTargetReq): Promise<RR.RemoveBackupTargetRes>
abstract removeBackupTarget(
params: RR.RemoveBackupTargetReq,
): Promise<RR.RemoveBackupTargetRes>
abstract getBackupInfo (params: RR.GetBackupInfoReq): Promise<RR.GetBackupInfoRes>
abstract getBackupInfo(
params: RR.GetBackupInfoReq,
): Promise<RR.GetBackupInfoRes>
protected abstract createBackupRaw (params: RR.CreateBackupReq): Promise<RR.CreateBackupRes>
createBackup = (params: RR.CreateBackupReq) => this.syncResponse(
() => this.createBackupRaw(params),
)()
protected abstract createBackupRaw(
params: RR.CreateBackupReq,
): Promise<RR.CreateBackupRes>
createBackup = (params: RR.CreateBackupReq) =>
this.syncResponse(() => this.createBackupRaw(params))()
// package
abstract getPackageProperties (params: RR.GetPackagePropertiesReq): Promise<RR.GetPackagePropertiesRes<2>['data']>
abstract getPackageProperties(
params: RR.GetPackagePropertiesReq,
): Promise<RR.GetPackagePropertiesRes<2>['data']>
abstract getPackageLogs (params: RR.GetPackageLogsReq): Promise<RR.GetPackageLogsRes>
abstract getPackageLogs(
params: RR.GetPackageLogsReq,
): Promise<RR.GetPackageLogsRes>
protected abstract installPackageRaw (params: RR.InstallPackageReq): Promise<RR.InstallPackageRes>
installPackage = (params: RR.InstallPackageReq) => this.syncResponse(
() => this.installPackageRaw(params),
)()
protected abstract installPackageRaw(
params: RR.InstallPackageReq,
): Promise<RR.InstallPackageRes>
installPackage = (params: RR.InstallPackageReq) =>
this.syncResponse(() => this.installPackageRaw(params))()
abstract dryUpdatePackage (params: RR.DryUpdatePackageReq): Promise<RR.DryUpdatePackageRes>
abstract dryUpdatePackage(
params: RR.DryUpdatePackageReq,
): Promise<RR.DryUpdatePackageRes>
abstract getPackageConfig (params: RR.GetPackageConfigReq): Promise<RR.GetPackageConfigRes>
abstract getPackageConfig(
params: RR.GetPackageConfigReq,
): Promise<RR.GetPackageConfigRes>
abstract drySetPackageConfig (params: RR.DrySetPackageConfigReq): Promise<RR.DrySetPackageConfigRes>
abstract drySetPackageConfig(
params: RR.DrySetPackageConfigReq,
): Promise<RR.DrySetPackageConfigRes>
protected abstract setPackageConfigRaw (params: RR.SetPackageConfigReq): Promise<RR.SetPackageConfigRes>
setPackageConfig = (params: RR.SetPackageConfigReq) => this.syncResponse(
() => this.setPackageConfigRaw(params),
)()
protected abstract setPackageConfigRaw(
params: RR.SetPackageConfigReq,
): Promise<RR.SetPackageConfigRes>
setPackageConfig = (params: RR.SetPackageConfigReq) =>
this.syncResponse(() => this.setPackageConfigRaw(params))()
protected abstract restorePackagesRaw (params: RR.RestorePackagesReq): Promise<RR.RestorePackagesRes>
restorePackages = (params: RR.RestorePackagesReq) => this.syncResponse(
() => this.restorePackagesRaw(params),
)()
protected abstract restorePackagesRaw(
params: RR.RestorePackagesReq,
): Promise<RR.RestorePackagesRes>
restorePackages = (params: RR.RestorePackagesReq) =>
this.syncResponse(() => this.restorePackagesRaw(params))()
abstract executePackageAction (params: RR.ExecutePackageActionReq): Promise<RR.ExecutePackageActionRes>
abstract executePackageAction(
params: RR.ExecutePackageActionReq,
): Promise<RR.ExecutePackageActionRes>
protected abstract startPackageRaw (params: RR.StartPackageReq): Promise<RR.StartPackageRes>
startPackage = (params: RR.StartPackageReq) => this.syncResponse(
() => this.startPackageRaw(params),
)()
protected abstract startPackageRaw(
params: RR.StartPackageReq,
): Promise<RR.StartPackageRes>
startPackage = (params: RR.StartPackageReq) =>
this.syncResponse(() => this.startPackageRaw(params))()
abstract dryStopPackage (params: RR.DryStopPackageReq): Promise<RR.DryStopPackageRes>
abstract dryStopPackage(
params: RR.DryStopPackageReq,
): Promise<RR.DryStopPackageRes>
protected abstract stopPackageRaw (params: RR.StopPackageReq): Promise<RR.StopPackageRes>
stopPackage = (params: RR.StopPackageReq) => this.syncResponse(
() => this.stopPackageRaw(params),
)()
protected abstract stopPackageRaw(
params: RR.StopPackageReq,
): Promise<RR.StopPackageRes>
stopPackage = (params: RR.StopPackageReq) =>
this.syncResponse(() => this.stopPackageRaw(params))()
abstract dryUninstallPackage (params: RR.DryUninstallPackageReq): Promise<RR.DryUninstallPackageRes>
abstract dryUninstallPackage(
params: RR.DryUninstallPackageReq,
): Promise<RR.DryUninstallPackageRes>
protected abstract uninstallPackageRaw (params: RR.UninstallPackageReq): Promise<RR.UninstallPackageRes>
uninstallPackage = (params: RR.UninstallPackageReq) => this.syncResponse(
() => this.uninstallPackageRaw(params),
)()
protected abstract uninstallPackageRaw(
params: RR.UninstallPackageReq,
): Promise<RR.UninstallPackageRes>
uninstallPackage = (params: RR.UninstallPackageReq) =>
this.syncResponse(() => this.uninstallPackageRaw(params))()
abstract dryConfigureDependency (params: RR.DryConfigureDependencyReq): Promise<RR.DryConfigureDependencyRes>
protected abstract deleteRecoveredPackageRaw (params: RR.UninstallPackageReq): Promise<RR.UninstallPackageRes>
deleteRecoveredPackage = (params: RR.UninstallPackageReq) => this.syncResponse(
() => this.deleteRecoveredPackageRaw(params),
)()
abstract dryConfigureDependency(
params: RR.DryConfigureDependencyReq,
): Promise<RR.DryConfigureDependencyRes>
protected abstract deleteRecoveredPackageRaw(
params: RR.UninstallPackageReq,
): Promise<RR.UninstallPackageRes>
deleteRecoveredPackage = (params: RR.UninstallPackageReq) =>
this.syncResponse(() => this.deleteRecoveredPackageRaw(params))()
// Helper allowing quick decoration to sync the response patch and return the response contents.
// Pass in a tempUpdate function which returns a UpdateTemp corresponding to a temporary
// state change you'd like to enact prior to request and expired when request terminates.
private syncResponse<T, F extends (...args: any[]) => Promise<{ response: T, revision?: Revision }>> (f: F, temp?: Operation): (...args: Parameters<F>) => Promise<T> {
private syncResponse<
T,
F extends (...args: any[]) => Promise<{ response: T; revision?: Revision }>,
>(f: F, temp?: Operation): (...args: Parameters<F>) => Promise<T> {
return (...a) => {
// let expireId = undefined
// if (temp) {

View File

@@ -1,20 +1,21 @@
import { Injectable } from '@angular/core'
import { HttpService, Method } from '../http.service'
import { ApiService } from './embassy-api.service'
import { ApiService } from './embassy-api.service'
import { RR } from './api.types'
import { parsePropertiesPermissive } from 'src/app/util/properties.util'
import { PatchDbService } from '../patch-db/patch-db.service'
@Injectable()
export class LiveApiService extends ApiService {
constructor (
constructor(
private readonly http: HttpService,
private readonly patch: PatchDbService,
) {
super();
(window as any).rpcClient = this
}
super()
;(window as any).rpcClient = this
}
async getStatic (url: string): Promise<string> {
async getStatic(url: string): Promise<string> {
return this.http.httpRequest({
method: Method.GET,
url,
@@ -24,69 +25,101 @@ export class LiveApiService extends ApiService {
// db
async getRevisions (since: number): Promise<RR.GetRevisionsRes> {
async getRevisions(since: number): Promise<RR.GetRevisionsRes> {
return this.http.rpcRequest({ method: 'db.revisions', params: { since } })
}
async getDump (): Promise<RR.GetDumpRes> {
async getDump(): Promise<RR.GetDumpRes> {
return this.http.rpcRequest({ method: 'db.dump' })
}
async setDbValueRaw (params: RR.SetDBValueReq): Promise<RR.SetDBValueRes> {
async setDbValueRaw(params: RR.SetDBValueReq): Promise<RR.SetDBValueRes> {
return this.http.rpcRequest({ method: 'db.put.ui', params })
}
// auth
async login (params: RR.LoginReq): Promise<RR.loginRes> {
async login(params: RR.LoginReq): Promise<RR.loginRes> {
return this.http.rpcRequest({ method: 'auth.login', params })
}
async logout (params: RR.LogoutReq): Promise<RR.LogoutRes> {
async logout(params: RR.LogoutReq): Promise<RR.LogoutRes> {
return this.http.rpcRequest({ method: 'auth.logout', params })
}
async getSessions (params: RR.GetSessionsReq): Promise<RR.GetSessionsRes> {
async getSessions(params: RR.GetSessionsReq): Promise<RR.GetSessionsRes> {
return this.http.rpcRequest({ method: 'auth.session.list', params })
}
async killSessions (params: RR.KillSessionsReq): Promise<RR.KillSessionsRes> {
async killSessions(params: RR.KillSessionsReq): Promise<RR.KillSessionsRes> {
return this.http.rpcRequest({ method: 'auth.session.kill', params })
}
// server
async setShareStatsRaw (params: RR.SetShareStatsReq): Promise<RR.SetShareStatsRes> {
return this.http.rpcRequest( { method: 'server.config.share-stats', params })
async setShareStatsRaw(
params: RR.SetShareStatsReq,
): Promise<RR.SetShareStatsRes> {
return this.http.rpcRequest({ method: 'server.config.share-stats', params })
}
async getServerLogs (params: RR.GetServerLogsReq): Promise<RR.GetServerLogsRes> {
return this.http.rpcRequest( { method: 'server.logs', params })
async getServerLogs(
params: RR.GetServerLogsReq,
): Promise<RR.GetServerLogsRes> {
return this.http.rpcRequest({ method: 'server.logs', params })
}
async getServerMetrics (params: RR.GetServerMetricsReq): Promise<RR.GetServerMetricsRes> {
async getServerMetrics(
params: RR.GetServerMetricsReq,
): Promise<RR.GetServerMetricsRes> {
return this.http.rpcRequest({ method: 'server.metrics', params })
}
async updateServerRaw (params: RR.UpdateServerReq): Promise<RR.UpdateServerRes> {
async updateServerRaw(
params: RR.UpdateServerReq,
): Promise<RR.UpdateServerRes> {
return this.http.rpcRequest({ method: 'server.update', params })
}
async restartServer (params: RR.RestartServerReq): Promise<RR.RestartServerRes> {
async restartServer(
params: RR.RestartServerReq,
): Promise<RR.RestartServerRes> {
return this.http.rpcRequest({ method: 'server.restart', params })
}
async shutdownServer (params: RR.ShutdownServerReq): Promise<RR.ShutdownServerRes> {
async shutdownServer(
params: RR.ShutdownServerReq,
): Promise<RR.ShutdownServerRes> {
return this.http.rpcRequest({ method: 'server.shutdown', params })
}
async systemRebuild (params: RR.RestartServerReq): Promise<RR.RestartServerRes> {
async systemRebuild(
params: RR.RestartServerReq,
): Promise<RR.RestartServerRes> {
return this.http.rpcRequest({ method: 'server.rebuild', params })
}
// marketplace URLs
async getEos (params: RR.GetMarketplaceEOSReq): Promise<RR.GetMarketplaceEOSRes> {
private async marketplaceProxy<T>(
path: string,
params: {},
url?: string,
): Promise<T> {
if (!url) {
const id = this.patch.data.ui.marketplace['selected-id']
url = this.patch.data.ui.marketplace.options[id].url
}
const fullURL = `${url}${path}?${new URLSearchParams(params).toString()}`
return this.http.rpcRequest({
method: 'marketplace.get',
params: { url: fullURL },
})
}
async getEos(
params: RR.GetMarketplaceEOSReq,
): Promise<RR.GetMarketplaceEOSRes> {
return this.http.httpRequest({
method: Method.GET,
url: '/marketplace/eos/latest',
@@ -94,27 +127,26 @@ export class LiveApiService extends ApiService {
})
}
async getMarketplaceData (params: RR.GetMarketplaceDataReq): Promise<RR.GetMarketplaceDataRes> {
return this.http.httpRequest({
method: Method.GET,
url: '/marketplace/package/data',
params,
})
async getMarketplaceData(
params: RR.GetMarketplaceDataReq,
url?: string,
): Promise<RR.GetMarketplaceDataRes> {
return this.marketplaceProxy('/marketplace/package/data', params, url)
}
async getMarketplacePkgs (params: RR.GetMarketplacePackagesReq): Promise<RR.GetMarketplacePackagesRes> {
async getMarketplacePkgs(
params: RR.GetMarketplacePackagesReq,
): Promise<RR.GetMarketplacePackagesRes> {
if (params.query) params.category = undefined
return this.http.httpRequest({
method: Method.GET,
url: '/marketplace/package/index',
params: {
...params,
ids: JSON.stringify(params.ids),
},
return this.marketplaceProxy('/marketplace/package/index', {
...params,
ids: JSON.stringify(params.ids),
})
}
async getReleaseNotes (params: RR.GetReleaseNotesReq): Promise<RR.GetReleaseNotesRes> {
async getReleaseNotes(
params: RR.GetReleaseNotesReq,
): Promise<RR.GetReleaseNotesRes> {
return this.http.httpRequest({
method: Method.GET,
url: '/marketplace/package/release-notes',
@@ -122,7 +154,9 @@ export class LiveApiService extends ApiService {
})
}
async getLatestVersion (params: RR.GetLatestVersionReq): Promise<RR.GetLatestVersionRes> {
async getLatestVersion(
params: RR.GetLatestVersionReq,
): Promise<RR.GetLatestVersionRes> {
return this.http.httpRequest({
method: Method.GET,
url: '/marketplace/latest-version',
@@ -141,149 +175,211 @@ export class LiveApiService extends ApiService {
// notification
async getNotificationsRaw (params: RR.GetNotificationsReq): Promise<RR.GetNotificationsRes> {
async getNotificationsRaw(
params: RR.GetNotificationsReq,
): Promise<RR.GetNotificationsRes> {
return this.http.rpcRequest({ method: 'notification.list', params })
}
async deleteNotification (params: RR.DeleteNotificationReq): Promise<RR.DeleteNotificationRes> {
async deleteNotification(
params: RR.DeleteNotificationReq,
): Promise<RR.DeleteNotificationRes> {
return this.http.rpcRequest({ method: 'notification.delete', params })
}
async deleteAllNotifications (params: RR.DeleteAllNotificationsReq): Promise<RR.DeleteAllNotificationsRes> {
return this.http.rpcRequest({ method: 'notification.delete-before', params })
async deleteAllNotifications(
params: RR.DeleteAllNotificationsReq,
): Promise<RR.DeleteAllNotificationsRes> {
return this.http.rpcRequest({
method: 'notification.delete-before',
params,
})
}
// wifi
async getWifi (params: RR.GetWifiReq, timeout?: number): Promise<RR.GetWifiRes> {
async getWifi(
params: RR.GetWifiReq,
timeout?: number,
): Promise<RR.GetWifiRes> {
return this.http.rpcRequest({ method: 'wifi.get', params, timeout })
}
async setWifiCountry (params: RR.SetWifiCountryReq): Promise<RR.SetWifiCountryRes> {
async setWifiCountry(
params: RR.SetWifiCountryReq,
): Promise<RR.SetWifiCountryRes> {
return this.http.rpcRequest({ method: 'wifi.country.set', params })
}
async addWifi (params: RR.AddWifiReq): Promise<RR.AddWifiRes> {
async addWifi(params: RR.AddWifiReq): Promise<RR.AddWifiRes> {
return this.http.rpcRequest({ method: 'wifi.add', params })
}
async connectWifi (params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes> {
async connectWifi(params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes> {
return this.http.rpcRequest({ method: 'wifi.connect', params })
}
async deleteWifi (params: RR.DeleteWifiReq): Promise<RR.DeleteWifiRes> {
async deleteWifi(params: RR.DeleteWifiReq): Promise<RR.DeleteWifiRes> {
return this.http.rpcRequest({ method: 'wifi.delete', params })
}
// ssh
async getSshKeys (params: RR.GetSSHKeysReq): Promise<RR.GetSSHKeysRes> {
async getSshKeys(params: RR.GetSSHKeysReq): Promise<RR.GetSSHKeysRes> {
return this.http.rpcRequest({ method: 'ssh.list', params })
}
async addSshKey (params: RR.AddSSHKeyReq): Promise<RR.AddSSHKeyRes> {
async addSshKey(params: RR.AddSSHKeyReq): Promise<RR.AddSSHKeyRes> {
return this.http.rpcRequest({ method: 'ssh.add', params })
}
async deleteSshKey (params: RR.DeleteSSHKeyReq): Promise<RR.DeleteSSHKeyRes> {
async deleteSshKey(params: RR.DeleteSSHKeyReq): Promise<RR.DeleteSSHKeyRes> {
return this.http.rpcRequest({ method: 'ssh.delete', params })
}
// backup
async getBackupTargets (params: RR.GetBackupTargetsReq): Promise<RR.GetBackupTargetsRes> {
async getBackupTargets(
params: RR.GetBackupTargetsReq,
): Promise<RR.GetBackupTargetsRes> {
return this.http.rpcRequest({ method: 'backup.target.list', params })
}
async addBackupTarget (params: RR.AddBackupTargetReq): Promise<RR.AddBackupTargetRes> {
async addBackupTarget(
params: RR.AddBackupTargetReq,
): Promise<RR.AddBackupTargetRes> {
params.path = params.path.replace('/\\/g', '/')
return this.http.rpcRequest({ method: 'backup.target.cifs.add', params })
}
async updateBackupTarget (params: RR.UpdateBackupTargetReq): Promise<RR.UpdateBackupTargetRes> {
async updateBackupTarget(
params: RR.UpdateBackupTargetReq,
): Promise<RR.UpdateBackupTargetRes> {
return this.http.rpcRequest({ method: 'backup.target.cifs.update', params })
}
async removeBackupTarget (params: RR.RemoveBackupTargetReq): Promise<RR.RemoveBackupTargetRes> {
async removeBackupTarget(
params: RR.RemoveBackupTargetReq,
): Promise<RR.RemoveBackupTargetRes> {
return this.http.rpcRequest({ method: 'backup.target.cifs.remove', params })
}
async getBackupInfo (params: RR.GetBackupInfoReq): Promise<RR.GetBackupInfoRes> {
async getBackupInfo(
params: RR.GetBackupInfoReq,
): Promise<RR.GetBackupInfoRes> {
return this.http.rpcRequest({ method: 'backup.target.info', params })
}
async createBackupRaw (params: RR.CreateBackupReq): Promise <RR.CreateBackupRes> {
async createBackupRaw(
params: RR.CreateBackupReq,
): Promise<RR.CreateBackupRes> {
return this.http.rpcRequest({ method: 'backup.create', params })
}
// package
async getPackageProperties (params: RR.GetPackagePropertiesReq): Promise<RR.GetPackagePropertiesRes < 2 > ['data'] > {
return this.http.rpcRequest({ method: 'package.properties', params })
async getPackageProperties(
params: RR.GetPackagePropertiesReq,
): Promise<RR.GetPackagePropertiesRes<2>['data']> {
return this.http
.rpcRequest({ method: 'package.properties', params })
.then(parsePropertiesPermissive)
}
async getPackageLogs (params: RR.GetPackageLogsReq): Promise<RR.GetPackageLogsRes> {
return this.http.rpcRequest( { method: 'package.logs', params })
async getPackageLogs(
params: RR.GetPackageLogsReq,
): Promise<RR.GetPackageLogsRes> {
return this.http.rpcRequest({ method: 'package.logs', params })
}
async getPkgMetrics (params: RR.GetPackageMetricsReq): Promise<RR.GetPackageMetricsRes> {
async getPkgMetrics(
params: RR.GetPackageMetricsReq,
): Promise<RR.GetPackageMetricsRes> {
return this.http.rpcRequest({ method: 'package.metrics', params })
}
async installPackageRaw (params: RR.InstallPackageReq): Promise<RR.InstallPackageRes> {
async installPackageRaw(
params: RR.InstallPackageReq,
): Promise<RR.InstallPackageRes> {
return this.http.rpcRequest({ method: 'package.install', params })
}
async dryUpdatePackage (params: RR.DryUpdatePackageReq): Promise<RR.DryUpdatePackageRes> {
async dryUpdatePackage(
params: RR.DryUpdatePackageReq,
): Promise<RR.DryUpdatePackageRes> {
return this.http.rpcRequest({ method: 'package.update.dry', params })
}
async getPackageConfig (params: RR.GetPackageConfigReq): Promise<RR.GetPackageConfigRes> {
async getPackageConfig(
params: RR.GetPackageConfigReq,
): Promise<RR.GetPackageConfigRes> {
return this.http.rpcRequest({ method: 'package.config.get', params })
}
async drySetPackageConfig (params: RR.DrySetPackageConfigReq): Promise<RR.DrySetPackageConfigRes> {
async drySetPackageConfig(
params: RR.DrySetPackageConfigReq,
): Promise<RR.DrySetPackageConfigRes> {
return this.http.rpcRequest({ method: 'package.config.set.dry', params })
}
async setPackageConfigRaw (params: RR.SetPackageConfigReq): Promise<RR.SetPackageConfigRes> {
async setPackageConfigRaw(
params: RR.SetPackageConfigReq,
): Promise<RR.SetPackageConfigRes> {
return this.http.rpcRequest({ method: 'package.config.set', params })
}
async restorePackagesRaw (params: RR.RestorePackagesReq): Promise<RR.RestorePackagesRes> {
async restorePackagesRaw(
params: RR.RestorePackagesReq,
): Promise<RR.RestorePackagesRes> {
return this.http.rpcRequest({ method: 'package.backup.restore', params })
}
async executePackageAction (params: RR.ExecutePackageActionReq): Promise<RR.ExecutePackageActionRes> {
async executePackageAction(
params: RR.ExecutePackageActionReq,
): Promise<RR.ExecutePackageActionRes> {
return this.http.rpcRequest({ method: 'package.action', params })
}
async startPackageRaw (params: RR.StartPackageReq): Promise<RR.StartPackageRes> {
async startPackageRaw(
params: RR.StartPackageReq,
): Promise<RR.StartPackageRes> {
return this.http.rpcRequest({ method: 'package.start', params })
}
async dryStopPackage (params: RR.DryStopPackageReq): Promise<RR.DryStopPackageRes> {
async dryStopPackage(
params: RR.DryStopPackageReq,
): Promise<RR.DryStopPackageRes> {
return this.http.rpcRequest({ method: 'package.stop.dry', params })
}
async stopPackageRaw (params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
async stopPackageRaw(params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
return this.http.rpcRequest({ method: 'package.stop', params })
}
async dryUninstallPackage (params: RR.DryUninstallPackageReq): Promise<RR.DryUninstallPackageRes> {
async dryUninstallPackage(
params: RR.DryUninstallPackageReq,
): Promise<RR.DryUninstallPackageRes> {
return this.http.rpcRequest({ method: 'package.uninstall.dry', params })
}
async deleteRecoveredPackageRaw (params: RR.DeleteRecoveredPackageReq): Promise<RR.DeleteRecoveredPackageRes> {
async deleteRecoveredPackageRaw(
params: RR.DeleteRecoveredPackageReq,
): Promise<RR.DeleteRecoveredPackageRes> {
return this.http.rpcRequest({ method: 'package.delete-recovered', params })
}
async uninstallPackageRaw (params: RR.UninstallPackageReq): Promise<RR.UninstallPackageRes> {
async uninstallPackageRaw(
params: RR.UninstallPackageReq,
): Promise<RR.UninstallPackageRes> {
return this.http.rpcRequest({ method: 'package.uninstall', params })
}
async dryConfigureDependency (params: RR.DryConfigureDependencyReq): Promise<RR.DryConfigureDependencyRes> {
return this.http.rpcRequest({ method: 'package.dependency.configure.dry', params })
async dryConfigureDependency(
params: RR.DryConfigureDependencyReq,
): Promise<RR.DryConfigureDependencyRes> {
return this.http.rpcRequest({
method: 'package.dependency.configure.dry',
params,
})
}
}

View File

@@ -25,22 +25,22 @@ export class MockApiService extends ApiService {
private readonly revertTime = 4000
sequence: number
constructor(private readonly bootstrapper: LocalStorageBootstrap) {
constructor (private readonly bootstrapper: LocalStorageBootstrap) {
super()
}
async getStatic(url: string): Promise<string> {
async getStatic (url: string): Promise<string> {
await pauseFor(2000)
return markdown
}
// db
async getRevisions(since: number): Promise<RR.GetRevisionsRes> {
async getRevisions (since: number): Promise<RR.GetRevisionsRes> {
return this.getDump()
}
async getDump(): Promise<RR.GetDumpRes> {
async getDump (): Promise<RR.GetDumpRes> {
const cache = await this.bootstrapper.init()
return {
id: cache.sequence,
@@ -49,7 +49,7 @@ export class MockApiService extends ApiService {
}
}
async setDbValueRaw(params: RR.SetDBValueReq): Promise<RR.SetDBValueRes> {
async setDbValueRaw (params: RR.SetDBValueReq): Promise<RR.SetDBValueRes> {
await pauseFor(2000)
const patch = [
{
@@ -63,7 +63,7 @@ export class MockApiService extends ApiService {
// auth
async login(params: RR.LoginReq): Promise<RR.loginRes> {
async login (params: RR.LoginReq): Promise<RR.loginRes> {
await pauseFor(2000)
setTimeout(() => {
@@ -73,24 +73,24 @@ export class MockApiService extends ApiService {
return null
}
async logout(params: RR.LogoutReq): Promise<RR.LogoutRes> {
async logout (params: RR.LogoutReq): Promise<RR.LogoutRes> {
await pauseFor(2000)
return null
}
async getSessions(params: RR.GetSessionsReq): Promise<RR.GetSessionsRes> {
async getSessions (params: RR.GetSessionsReq): Promise<RR.GetSessionsRes> {
await pauseFor(2000)
return Mock.Sessions
}
async killSessions(params: RR.KillSessionsReq): Promise<RR.KillSessionsRes> {
async killSessions (params: RR.KillSessionsReq): Promise<RR.KillSessionsRes> {
await pauseFor(2000)
return null
}
// server
async setShareStatsRaw(
async setShareStatsRaw (
params: RR.SetShareStatsReq,
): Promise<RR.SetShareStatsRes> {
await pauseFor(2000)
@@ -105,7 +105,7 @@ export class MockApiService extends ApiService {
return this.withRevision(patch)
}
async getServerLogs(
async getServerLogs (
params: RR.GetServerLogsReq,
): Promise<RR.GetServerLogsRes> {
await pauseFor(2000)
@@ -127,21 +127,21 @@ export class MockApiService extends ApiService {
}
}
async getServerMetrics(
async getServerMetrics (
params: RR.GetServerMetricsReq,
): Promise<RR.GetServerMetricsRes> {
await pauseFor(2000)
return Mock.getServerMetrics()
}
async getPkgMetrics(
async getPkgMetrics (
params: RR.GetServerMetricsReq,
): Promise<RR.GetPackageMetricsRes> {
await pauseFor(2000)
return Mock.getAppMetrics()
}
async updateServerRaw(
async updateServerRaw (
params: RR.UpdateServerReq,
): Promise<RR.UpdateServerRes> {
await pauseFor(2000)
@@ -165,21 +165,21 @@ export class MockApiService extends ApiService {
return this.withRevision(patch, 'updating')
}
async restartServer(
async restartServer (
params: RR.RestartServerReq,
): Promise<RR.RestartServerRes> {
await pauseFor(2000)
return null
}
async shutdownServer(
async shutdownServer (
params: RR.ShutdownServerReq,
): Promise<RR.ShutdownServerRes> {
await pauseFor(2000)
return null
}
async systemRebuild(
async systemRebuild (
params: RR.RestartServerReq,
): Promise<RR.RestartServerRes> {
await pauseFor(2000)
@@ -188,18 +188,20 @@ export class MockApiService extends ApiService {
// marketplace URLs
async getEos(
async getEos (
params: RR.GetMarketplaceEOSReq,
): Promise<RR.GetMarketplaceEOSRes> {
await pauseFor(2000)
return Mock.MarketplaceEos
}
async getMarketplaceData(
async getMarketplaceData (
params: RR.GetMarketplaceDataReq,
url?: string,
): Promise<RR.GetMarketplaceDataRes> {
await pauseFor(2000)
return {
name: 'Dark9',
categories: [
'featured',
'bitcoin',
@@ -212,21 +214,21 @@ export class MockApiService extends ApiService {
}
}
async getMarketplacePkgs(
async getMarketplacePkgs (
params: RR.GetMarketplacePackagesReq,
): Promise<RR.GetMarketplacePackagesRes> {
await pauseFor(2000)
return Mock.MarketplacePkgsList
}
async getReleaseNotes(
async getReleaseNotes (
params: RR.GetReleaseNotesReq,
): Promise<RR.GetReleaseNotesRes> {
await pauseFor(2000)
return Mock.ReleaseNotes
}
async getLatestVersion(
async getLatestVersion (
params: RR.GetLatestVersionReq,
): Promise<RR.GetLatestVersionRes> {
await pauseFor(2000)
@@ -244,7 +246,7 @@ export class MockApiService extends ApiService {
// notification
async getNotificationsRaw(
async getNotificationsRaw (
params: RR.GetNotificationsReq,
): Promise<RR.GetNotificationsRes> {
await pauseFor(2000)
@@ -259,14 +261,14 @@ export class MockApiService extends ApiService {
return this.withRevision(patch, Mock.Notifications)
}
async deleteNotification(
async deleteNotification (
params: RR.DeleteNotificationReq,
): Promise<RR.DeleteNotificationRes> {
await pauseFor(2000)
return null
}
async deleteAllNotifications(
async deleteAllNotifications (
params: RR.DeleteAllNotificationsReq,
): Promise<RR.DeleteAllNotificationsRes> {
await pauseFor(2000)
@@ -275,60 +277,60 @@ export class MockApiService extends ApiService {
// wifi
async getWifi(params: RR.GetWifiReq): Promise<RR.GetWifiRes> {
async getWifi (params: RR.GetWifiReq): Promise<RR.GetWifiRes> {
await pauseFor(2000)
return Mock.Wifi
}
async setWifiCountry(
async setWifiCountry (
params: RR.SetWifiCountryReq,
): Promise<RR.SetWifiCountryRes> {
await pauseFor(2000)
return null
}
async addWifi(params: RR.AddWifiReq): Promise<RR.AddWifiRes> {
async addWifi (params: RR.AddWifiReq): Promise<RR.AddWifiRes> {
await pauseFor(2000)
return null
}
async connectWifi(params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes> {
async connectWifi (params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes> {
await pauseFor(2000)
return null
}
async deleteWifi(params: RR.DeleteWifiReq): Promise<RR.DeleteWifiRes> {
async deleteWifi (params: RR.DeleteWifiReq): Promise<RR.DeleteWifiRes> {
await pauseFor(2000)
return null
}
// ssh
async getSshKeys(params: RR.GetSSHKeysReq): Promise<RR.GetSSHKeysRes> {
async getSshKeys (params: RR.GetSSHKeysReq): Promise<RR.GetSSHKeysRes> {
await pauseFor(2000)
return Mock.SshKeys
}
async addSshKey(params: RR.AddSSHKeyReq): Promise<RR.AddSSHKeyRes> {
async addSshKey (params: RR.AddSSHKeyReq): Promise<RR.AddSSHKeyRes> {
await pauseFor(2000)
return Mock.SshKey
}
async deleteSshKey(params: RR.DeleteSSHKeyReq): Promise<RR.DeleteSSHKeyRes> {
async deleteSshKey (params: RR.DeleteSSHKeyReq): Promise<RR.DeleteSSHKeyRes> {
await pauseFor(2000)
return null
}
// backup
async getBackupTargets(
async getBackupTargets (
params: RR.GetBackupTargetsReq,
): Promise<RR.GetBackupTargetsRes> {
await pauseFor(2000)
return Mock.BackupTargets
}
async addBackupTarget(
async addBackupTarget (
params: RR.AddBackupTargetReq,
): Promise<RR.AddBackupTargetRes> {
await pauseFor(2000)
@@ -345,7 +347,7 @@ export class MockApiService extends ApiService {
}
}
async updateBackupTarget(
async updateBackupTarget (
params: RR.UpdateBackupTargetReq,
): Promise<RR.UpdateBackupTargetRes> {
await pauseFor(2000)
@@ -360,21 +362,21 @@ export class MockApiService extends ApiService {
}
}
async removeBackupTarget(
async removeBackupTarget (
params: RR.RemoveBackupTargetReq,
): Promise<RR.RemoveBackupTargetRes> {
await pauseFor(2000)
return null
}
async getBackupInfo(
async getBackupInfo (
params: RR.GetBackupInfoReq,
): Promise<RR.GetBackupInfoRes> {
await pauseFor(2000)
return Mock.BackupInfo
}
async createBackupRaw(
async createBackupRaw (
params: RR.CreateBackupReq,
): Promise<RR.CreateBackupRes> {
await pauseFor(2000)
@@ -425,14 +427,14 @@ export class MockApiService extends ApiService {
// package
async getPackageProperties(
async getPackageProperties (
params: RR.GetPackagePropertiesReq,
): Promise<RR.GetPackagePropertiesRes<2>['data']> {
await pauseFor(2000)
return parsePropertiesPermissive(Mock.PackageProperties)
}
async getPackageLogs(
async getPackageLogs (
params: RR.GetPackageLogsReq,
): Promise<RR.GetPackageLogsRes> {
await pauseFor(2000)
@@ -454,7 +456,7 @@ export class MockApiService extends ApiService {
}
}
async installPackageRaw(
async installPackageRaw (
params: RR.InstallPackageReq,
): Promise<RR.InstallPackageRes> {
await pauseFor(2000)
@@ -489,14 +491,14 @@ export class MockApiService extends ApiService {
return this.withRevision(patch)
}
async dryUpdatePackage(
async dryUpdatePackage (
params: RR.DryUpdatePackageReq,
): Promise<RR.DryUpdatePackageRes> {
await pauseFor(2000)
return {}
}
async getPackageConfig(
async getPackageConfig (
params: RR.GetPackageConfigReq,
): Promise<RR.GetPackageConfigRes> {
await pauseFor(2000)
@@ -506,14 +508,14 @@ export class MockApiService extends ApiService {
}
}
async drySetPackageConfig(
async drySetPackageConfig (
params: RR.DrySetPackageConfigReq,
): Promise<RR.DrySetPackageConfigRes> {
await pauseFor(2000)
return {}
}
async setPackageConfigRaw(
async setPackageConfigRaw (
params: RR.SetPackageConfigReq,
): Promise<RR.SetPackageConfigRes> {
await pauseFor(2000)
@@ -527,7 +529,7 @@ export class MockApiService extends ApiService {
return this.withRevision(patch)
}
async restorePackagesRaw(
async restorePackagesRaw (
params: RR.RestorePackagesReq,
): Promise<RR.RestorePackagesRes> {
await pauseFor(2000)
@@ -563,14 +565,14 @@ export class MockApiService extends ApiService {
return this.withRevision(patch)
}
async executePackageAction(
async executePackageAction (
params: RR.ExecutePackageActionReq,
): Promise<RR.ExecutePackageActionRes> {
await pauseFor(2000)
return Mock.ActionResponse
}
async startPackageRaw(
async startPackageRaw (
params: RR.StartPackageReq,
): Promise<RR.StartPackageRes> {
const path = `/package-data/${params.id}/installed/status/main`
@@ -649,7 +651,7 @@ export class MockApiService extends ApiService {
return this.withRevision(originalPatch)
}
async dryStopPackage(
async dryStopPackage (
params: RR.DryStopPackageReq,
): Promise<RR.DryStopPackageRes> {
await pauseFor(2000)
@@ -663,7 +665,7 @@ export class MockApiService extends ApiService {
}
}
async stopPackageRaw(params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
async stopPackageRaw (params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
await pauseFor(2000)
const path = `/package-data/${params.id}/installed/status/main`
@@ -694,14 +696,14 @@ export class MockApiService extends ApiService {
return this.withRevision(patch)
}
async dryUninstallPackage(
async dryUninstallPackage (
params: RR.DryUninstallPackageReq,
): Promise<RR.DryUninstallPackageRes> {
await pauseFor(2000)
return {}
}
async uninstallPackageRaw(
async uninstallPackageRaw (
params: RR.UninstallPackageReq,
): Promise<RR.UninstallPackageRes> {
await pauseFor(2000)
@@ -727,7 +729,7 @@ export class MockApiService extends ApiService {
return this.withRevision(patch)
}
async deleteRecoveredPackageRaw(
async deleteRecoveredPackageRaw (
params: RR.DeleteRecoveredPackageReq,
): Promise<RR.DeleteRecoveredPackageRes> {
await pauseFor(2000)
@@ -740,7 +742,7 @@ export class MockApiService extends ApiService {
return this.withRevision(patch)
}
async dryConfigureDependency(
async dryConfigureDependency (
params: RR.DryConfigureDependencyReq,
): Promise<RR.DryConfigureDependencyRes> {
await pauseFor(2000)
@@ -751,7 +753,7 @@ export class MockApiService extends ApiService {
}
}
private async updateProgress(
private async updateProgress (
id: string,
initialProgress: InstallProgress,
): Promise<void> {
@@ -797,7 +799,7 @@ export class MockApiService extends ApiService {
}, 1000)
}
private async updateOSProgress(size: number) {
private async updateOSProgress (size: number) {
let downloaded = 0
while (downloaded < size) {
await pauseFor(250)
@@ -847,7 +849,7 @@ export class MockApiService extends ApiService {
}, 1000)
}
private async updateMock(patch: Operation[]): Promise<void> {
private async updateMock (patch: Operation[]): Promise<void> {
if (!this.sequence) {
const { sequence } = await this.bootstrapper.init()
this.sequence = sequence
@@ -860,7 +862,7 @@ export class MockApiService extends ApiService {
this.mockPatch$.next(revision)
}
private async withRevision<T>(
private async withRevision<T> (
patch: Operation[],
response: T = null,
): Promise<WithRevision<T>> {

View File

@@ -16,6 +16,15 @@ export const mockPatchData: DataModel = {
'pkg-order': [],
'ack-welcome': '1.0.0',
'ack-share-stats': false,
marketplace: {
'selected-id': 'asdfasdf',
options: {
asdfasdf: {
name: 'Start9',
url: 'start9marketplace.com',
},
},
},
},
'server-info': {
id: 'embassy-abcdefgh',

View File

@@ -13,6 +13,17 @@ export interface UIData {
'pkg-order': string[]
'ack-welcome': string // EOS version
'ack-share-stats': boolean
marketplace: UIMarketplaceData
}
export interface UIMarketplaceData {
'selected-id': string
options: {
[id: string]: {
url: string
name: string
}
}
}
export interface ServerInfo {

View File

@@ -1,5 +1,10 @@
import { Injectable } from '@angular/core'
import { AlertController, IonicSafeString, ModalController, NavController } from '@ionic/angular'
import {
AlertController,
IonicSafeString,
ModalController,
NavController,
} from '@ionic/angular'
import { wizardModal } from '../components/install-wizard/install-wizard.component'
import { WizardBaker } from '../components/install-wizard/prebaked-wizards'
import { OSWelcomePage } from '../modals/os-welcome/os-welcome.page'
@@ -15,6 +20,7 @@ import { isEmptyObject } from '../util/misc.util'
import { ApiService } from './api/embassy-api.service'
import { Subscription } from 'rxjs'
import { ServerConfigService } from './server-config.service'
import { v4 } from 'uuid'
@Injectable({
providedIn: 'root',
@@ -22,7 +28,7 @@ import { ServerConfigService } from './server-config.service'
export class StartupAlertsService {
private checks: Check<any>[]
constructor (
constructor(
private readonly alertCtrl: AlertController,
private readonly navCtrl: NavController,
private readonly config: ConfigService,
@@ -52,69 +58,74 @@ export class StartupAlertsService {
check: () => this.osUpdateCheck(),
display: pkg => this.displayOsUpdateCheck(pkg),
}
const pkgsUpdate: Check<boolean> = {
name: 'pkgsUpdate',
shouldRun: () => this.shouldRunAppsCheck(),
check: () => this.appsCheck(),
display: () => this.displayAppsCheck(),
}
this.checks = [osWelcome, shareStats, osUpdate, pkgsUpdate]
this.checks = [osWelcome, shareStats, osUpdate]
}
// This takes our three checks and filters down to those that should run.
// Then, the reduce fires, quickly iterating through yielding a promise (previousDisplay) to the next element
// Each promise fires more or less concurrently, so each c.check(server) is run concurrently
// Then, since we await previousDisplay before c.display(res), each promise executing gets hung awaiting the display of the previous run
runChecks (): Subscription {
return this.patch.watch$()
.pipe(
filter(data => !isEmptyObject(data)),
take(1),
)
.subscribe(async () => {
await this.checks
.filter(c => !this.config.skipStartupAlerts && c.shouldRun())
// returning true in the below block means to continue to next modal
// returning false means to skip all subsequent modals
.reduce(async (previousDisplay, c) => {
let checkRes: any
try {
checkRes = await c.check()
} catch (e) {
console.error(`Exception in ${c.name} check:`, e)
return true
runChecks(): Subscription {
return this.patch
.watch$()
.pipe(
filter(data => !isEmptyObject(data)),
take(1),
)
.subscribe(async data => {
if (!data.ui.marketplace) {
const uuid = v4()
const value = {
'selected-id': uuid,
options: {
[uuid]: {
url: 'marketplaceurl.com',
name: 'Start9',
},
},
}
await this.api.setDbValue({ pointer: 'marketplace', value })
}
await this.checks
.filter(c => !this.config.skipStartupAlerts && c.shouldRun())
// returning true in the below block means to continue to next modal
// returning false means to skip all subsequent modals
.reduce(async (previousDisplay, c) => {
let checkRes: any
try {
checkRes = await c.check()
} catch (e) {
console.error(`Exception in ${c.name} check:`, e)
return true
}
const displayRes = await previousDisplay
const displayRes = await previousDisplay
if (!checkRes) return true
if (displayRes) return c.display(checkRes)
}, Promise.resolve(true))
})
if (!checkRes) return true
if (displayRes) return c.display(checkRes)
}, Promise.resolve(true))
})
}
// ** should run **
private shouldRunOsWelcome (): boolean {
private shouldRunOsWelcome(): boolean {
return this.patch.getData().ui['ack-welcome'] !== this.config.version
}
private shouldRunShareStats (): boolean {
private shouldRunShareStats(): boolean {
return !this.patch.getData().ui['ack-share-stats']
}
private shouldRunOsUpdateCheck (): boolean {
return this.patch.getData().ui['auto-check-updates']
}
private shouldRunAppsCheck (): boolean {
private shouldRunOsUpdateCheck(): boolean {
return this.patch.getData().ui['auto-check-updates']
}
// ** check **
private async osUpdateCheck (): Promise<RR.GetMarketplaceEOSRes | undefined> {
private async osUpdateCheck(): Promise<RR.GetMarketplaceEOSRes | undefined> {
const res = await this.api.getEos({
'eos-version-compat': this.patch.getData()['server-info']['eos-version-compat'],
'eos-version-compat':
this.patch.getData()['server-info']['eos-version-compat'],
})
if (this.emver.compare(this.config.version, res.version) === -1) {
@@ -124,14 +135,9 @@ export class StartupAlertsService {
}
}
private async appsCheck (): Promise<boolean> {
const updates = await this.marketplaceService.getUpdates(this.patch.getData()['package-data'])
return !!updates.length
}
// ** display **
private async displayOsWelcome (): Promise<boolean> {
private async displayOsWelcome(): Promise<boolean> {
return new Promise(async resolve => {
const modal = await this.modalCtrl.create({
component: OSWelcomePage,
@@ -141,27 +147,37 @@ export class StartupAlertsService {
},
})
modal.onWillDismiss().then(() => {
this.api.setDbValue({ pointer: '/ack-welcome', value: this.config.version })
.catch()
this.api
.setDbValue({ pointer: '/ack-welcome', value: this.config.version })
.catch()
return resolve(true)
})
await modal.present()
})
}
private async displayShareStats (): Promise<boolean> {
private async displayShareStats(): Promise<boolean> {
return new Promise(async resolve => {
const alert = await this.serverConfig.presentAlert('share-stats', this.patch.getData()['server-info']['share-stats'])
const alert = await this.serverConfig.presentAlert(
'share-stats',
this.patch.getData()['server-info']['share-stats'],
)
alert.onDidDismiss().then(() => {
this.api.setDbValue({ pointer: '/ack-share-stats', value: this.config.version })
this.api
.setDbValue({
pointer: '/ack-share-stats',
value: this.config.version,
})
.catch()
return resolve(true)
})
})
}
private async displayOsUpdateCheck (eos: RR.GetMarketplaceEOSRes): Promise<boolean> {
private async displayOsUpdateCheck(
eos: RR.GetMarketplaceEOSRes,
): Promise<boolean> {
const { update } = await this.presentAlertNewOS(eos.version)
if (update) {
const { cancelled } = await wizardModal(
@@ -178,46 +194,19 @@ export class StartupAlertsService {
return true
}
private async displayAppsCheck (): Promise<boolean> {
return new Promise(async resolve => {
const alert = await this.alertCtrl.create({
header: 'Updates Available!',
message: new IonicSafeString(
`<div style="display: flex; flex-direction: column; justify-content: space-around; min-height: 100px">
<div>New service updates are available in the Marketplace.</div>
<div style="font-size:x-small">You can disable these checks in your Embassy Config</div>
</div>
`,
),
buttons: [
{
text: 'Cancel',
role: 'cancel',
handler: () => resolve(true),
},
{
text: 'View in Marketplace',
handler: () => {
this.navCtrl.navigateForward('/marketplace').then(() => resolve(false))
},
cssClass: 'enter-click',
},
],
})
await alert.present()
})
}
// more
private async presentAlertNewOS (versionLatest: string): Promise<{ cancel?: true, update?: true }> {
private async presentAlertNewOS(
versionLatest: string,
): Promise<{ cancel?: true; update?: true }> {
return new Promise(async resolve => {
const alert = await this.alertCtrl.create({
header: 'New EmbassyOS Version!',
message: new IonicSafeString(
`<div style="display: flex; flex-direction: column; justify-content: space-around; min-height: 100px">
<div>Update EmbassyOS to version ${displayEmver(versionLatest)}?</div>
<div>Update EmbassyOS to version ${displayEmver(
versionLatest,
)}?</div>
<div style="font-size:x-small">You can disable these checks in your Embassy Config</div>
</div>
`,
@@ -250,4 +239,4 @@ type Check<T> = {
display: (a: T) => Promise<boolean>
// for logging purposes
name: string
}
}