rework endpoints and enable setting custom marketplace urls

This commit is contained in:
Matt Hill
2021-07-14 16:43:25 -06:00
committed by Aiden McClelland
parent 7253cd002e
commit c4a5cf1c51
24 changed files with 397 additions and 439 deletions

View File

@@ -1,4 +1,8 @@
{ {
"start9Marketplace": {
"clearnet": "",
"tor": ""
},
"patchDb": { "patchDb": {
"poll": { "poll": {
"cooldown": 10000 "cooldown": 10000
@@ -14,7 +18,6 @@
"rpcPort": "5959", "rpcPort": "5959",
"wsPort": "5960", "wsPort": "5960",
"maskAs": "tor", "maskAs": "tor",
"skipStartupAlerts": true, "skipStartupAlerts": true
"registryURL": ""
} }
} }

View File

@@ -10,7 +10,6 @@ import { MarketplaceService } from '../marketplace.service'
}) })
export class AppReleaseNotes { export class AppReleaseNotes {
@ViewChild(IonContent) content: IonContent @ViewChild(IonContent) content: IonContent
error = ''
selected: string selected: string
pkgId: string pkgId: string
@@ -22,8 +21,8 @@ export class AppReleaseNotes {
ngOnInit () { ngOnInit () {
this.pkgId = this.route.snapshot.paramMap.get('pkgId') this.pkgId = this.route.snapshot.paramMap.get('pkgId')
const version = this.route.snapshot.paramMap.get('version') const version = this.route.snapshot.paramMap.get('version')
if (!this.marketplaceService.pkgs[this.pkgId]) { if (!this.marketplaceService.pkgs[this.pkgId]?.['release-notes']) {
this.marketplaceService.setPkg(this.pkgId, version) this.marketplaceService.getPkg(this.pkgId, version)
} }
} }

View File

@@ -46,17 +46,17 @@
<ion-grid> <ion-grid>
<ion-row> <ion-row>
<ion-col *ngFor="let pkg of pkgs" sizeXs="12" sizeSm="12" sizeMd="6"> <ion-col *ngFor="let pkg of pkgs" sizeXs="12" sizeSm="12" sizeMd="6">
<ion-item [routerLink]="['/marketplace', pkg.id]"> <ion-item [routerLink]="['/marketplace', pkg.manifest.id]">
<ion-thumbnail slot="start"> <ion-thumbnail slot="start">
<img [src]="pkg.icon" /> <img [src]="pkg.icon" />
</ion-thumbnail> </ion-thumbnail>
<ion-label> <ion-label>
<h2 style="font-family: 'Montserrat';">{{ pkg.title }}</h2> <h2 style="font-family: 'Montserrat';">{{ pkg.manifest.title }}</h2>
<p>{{ pkg.descriptionShort }}</p> <p>{{ pkg.manifest.description.short }}</p>
<ng-container *ngIf="patch.data['package-data'][pkg.id] as localPkg"> <ng-container *ngIf="patch.data['package-data'][pkg.id] as localPkg">
<p *ngIf="localPkg.state === PackageState.Installed"> <p *ngIf="localPkg.state === PackageState.Installed">
<ion-text *ngIf="(pkg.version | compareEmver : localPkg.manifest.version) === 0" color="success">Installed</ion-text> <ion-text *ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 0" color="success">Installed</ion-text>
<ion-text *ngIf="(pkg.version | compareEmver : localPkg.manifest.version) === 1" color="warning">Update Available</ion-text> <ion-text *ngIf="(pkg.manifest.version | compareEmver : localPkg.manifest.version) === 1" color="warning">Update Available</ion-text>
</p> </p>
<p *ngIf="localPkg.state === PackageState.Installing" style="display: flex; flex-direction: row; align-items: center;"> <p *ngIf="localPkg.state === PackageState.Installing" style="display: flex; flex-direction: row; align-items: center;">
<ion-text color="primary">Installing</ion-text> <ion-text color="primary">Installing</ion-text>

View File

@@ -1,6 +1,5 @@
import { Component, ViewChild } from '@angular/core' import { Component, ViewChild } from '@angular/core'
import { ApiService } from 'src/app/services/api/api.service' import { MarketplaceData, MarketplaceEOS, MarketplacePkg } from 'src/app/services/api/api-types'
import { MarketplaceData, MarketplaceEOS, AvailablePreview } from 'src/app/services/api/api-types'
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component' import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
import { IonContent, ModalController } from '@ionic/angular' import { IonContent, ModalController } from '@ionic/angular'
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards' import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
@@ -8,6 +7,7 @@ import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { PackageState } from 'src/app/services/patch-db/data-model' import { PackageState } from 'src/app/services/patch-db/data-model'
import { Subscription } from 'rxjs' import { Subscription } from 'rxjs'
import { ErrorToastService } from 'src/app/services/error-toast.service' import { ErrorToastService } from 'src/app/services/error-toast.service'
import { MarketplaceService } from '../marketplace.service'
@Component({ @Component({
selector: 'marketplace-list', selector: 'marketplace-list',
@@ -24,7 +24,7 @@ export class MarketplaceListPage {
data: MarketplaceData data: MarketplaceData
eos: MarketplaceEOS eos: MarketplaceEOS
pkgs: AvailablePreview[] = [] pkgs: MarketplacePkg[] = []
PackageState = PackageState PackageState = PackageState
@@ -35,7 +35,7 @@ export class MarketplaceListPage {
subs: Subscription[] = [] subs: Subscription[] = []
constructor ( constructor (
private readonly apiService: ApiService, private readonly marketplaceService: MarketplaceService,
private readonly modalCtrl: ModalController, private readonly modalCtrl: ModalController,
private readonly errToast: ErrorToastService, private readonly errToast: ErrorToastService,
private readonly wizardBaker: WizardBaker, private readonly wizardBaker: WizardBaker,
@@ -46,8 +46,8 @@ export class MarketplaceListPage {
try { try {
const [data, eos, pkgs] = await Promise.all([ const [data, eos, pkgs] = await Promise.all([
this.apiService.getMarketplaceData({ }), this.marketplaceService.getMarketplaceData(),
this.apiService.getEos({ }), this.marketplaceService.getEos(),
this.getPkgs(), this.getPkgs(),
]) ])
this.data = data this.data = data
@@ -94,14 +94,14 @@ export class MarketplaceListPage {
) )
} }
private async getPkgs (): Promise<AvailablePreview[]> { private async getPkgs (): Promise<MarketplacePkg[]> {
try { try {
const pkgs = await this.apiService.getAvailableList({ const pkgs = await this.marketplaceService.getPkgs(
category: this.category !== 'all' ? this.category : undefined, this.category !== 'all' ? this.category : undefined,
query: this.query, this.query,
page: this.page, this.page,
'per-page': this.perPage, this.perPage,
}) )
this.needInfinite = pkgs.length >= this.perPage this.needInfinite = pkgs.length >= this.perPage
this.page++ this.page++
return pkgs return pkgs

View File

@@ -1,6 +1,6 @@
import { Component, ViewChild } from '@angular/core' import { Component, ViewChild } from '@angular/core'
import { ActivatedRoute } from '@angular/router' import { ActivatedRoute } from '@angular/router'
import { AlertController, IonContent, ModalController, NavController, ToastController } from '@ionic/angular' import { AlertController, IonContent, ModalController, NavController } from '@ionic/angular'
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component' import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards' import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
import { Emver } from 'src/app/services/emver.service' import { Emver } from 'src/app/services/emver.service'
@@ -51,8 +51,9 @@ export class MarketplaceShowPage {
this.installedPkg = pkg this.installedPkg = pkg
}), }),
] ]
if (!this.marketplaceService.pkgs[this.pkgId]) {
this.getPkg() this.getPkg()
}
} }
ngAfterViewInit () { ngAfterViewInit () {
@@ -65,7 +66,7 @@ export class MarketplaceShowPage {
async getPkg (version?: string): Promise<void> { async getPkg (version?: string): Promise<void> {
try { try {
await this.marketplaceService.setPkg(this.pkgId, version) await this.marketplaceService.getPkg(this.pkgId, version)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
this.errToast.present(e.message) this.errToast.present(e.message)
@@ -117,7 +118,7 @@ export class MarketplaceShowPage {
} }
async install () { async install () {
const { id, title, version, dependencies, alerts } = this.marketplaceService.pkgs[this.pkgId].manifest const { id, title, version, alerts } = this.marketplaceService.pkgs[this.pkgId].manifest
const { cancelled } = await wizardModal( const { cancelled } = await wizardModal(
this.modalCtrl, this.modalCtrl,
this.wizardBaker.install({ this.wizardBaker.install({

View File

@@ -1,20 +1,53 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { AvailableShow } from 'src/app/services/api/api-types' import { MarketplacePkg } from 'src/app/services/api/api-types'
import { ApiService } from 'src/app/services/api/api.service' import { ApiService } from 'src/app/services/api/api.service'
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class MarketplaceService { export class MarketplaceService {
pkgs: { [id: string]: AvailableShow } = { } pkgs: { [id: string]: MarketplacePkg } = { }
additionalInfo releaseNotes: { [id: string]: {
[version: string]: string
} }
constructor ( constructor (
private readonly apiService: ApiService, private readonly apiService: ApiService,
) { } ) { }
async setPkg (id: string, version?: string): Promise<void> { async getMarketplaceData () {
this.pkgs[id] = await this.apiService.getAvailableShow({ id, version }) return this.apiService.getMarketplaceData({ })
}
async getEos () {
return this.apiService.getEos({ })
}
async getPkgs (category: string, query: string, page: number, perPage: number) : Promise<MarketplacePkg[]> {
const pkgs = await this.apiService.getMarketplacePkgs({
category: category !== 'all' ? category : undefined,
query,
page: String(page),
'per-page': String(perPage),
})
this.pkgs = pkgs.reduce((cur, val) => {
cur[val.manifest.id] = val
return cur
}, { })
return pkgs
}
async getPkg (id: string, version?: string): Promise<void> {
const pkg = (await this.apiService.getMarketplacePkgs({ id, version }))[0]
if (pkg) {
this.pkgs[id] = pkg
} else {
throw new Error(`No results for ${id}${version ? ' ' + version : ''}.`)
}
}
async getReleaseNotes (id: string): Promise<void> {
this.releaseNotes[id] = await this.apiService.getReleaseNotes({ id })
} }
} }

View File

@@ -13,10 +13,6 @@
<ion-item detail="true" button [routerLink]="['ssh-keys']"> <ion-item detail="true" button [routerLink]="['ssh-keys']">
<ion-label>SSH Keys</ion-label> <ion-label>SSH Keys</ion-label>
</ion-item> </ion-item>
<ion-item button (click)="presentModalValueEdit('registry', server.registry)">
<ion-label>Marketplace URL</ion-label>
<ion-note slot="end">{{ server.registry }}</ion-note>
</ion-item>
</ion-item-group> </ion-item-group>
</ion-content> </ion-content>

View File

@@ -1,8 +1,6 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { ServerConfigService } from 'src/app/services/server-config.service' import { ServerConfigService } from 'src/app/services/server-config.service'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service' import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { ServerInfo } from 'src/app/services/patch-db/data-model'
import { Subscription } from 'rxjs'
@Component({ @Component({
selector: 'dev-options', selector: 'dev-options',
@@ -10,7 +8,6 @@ import { Subscription } from 'rxjs'
styleUrls: ['./dev-options.page.scss'], styleUrls: ['./dev-options.page.scss'],
}) })
export class DevOptionsPage { export class DevOptionsPage {
subs: Subscription[] = []
constructor ( constructor (
private readonly serverConfigService: ServerConfigService, private readonly serverConfigService: ServerConfigService,

View File

@@ -1,19 +0,0 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<pwa-back-button></pwa-back-button>
</ion-buttons>
<ion-title>Preferences</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-top">
<ion-item-group>
<ion-item button (click)="presentModalValueEdit('autoCheckUpdates', patch.data.ui['auto-check-updates'])">
<ion-label>Auto Check for Updates</ion-label>
<ion-note slot="end">{{ patch.data.ui['auto-check-updates'] }}</ion-note>
</ion-item>
</ion-item-group>
</ion-content>

View File

@@ -1,7 +1,7 @@
import { NgModule } from '@angular/core' import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular' import { IonicModule } from '@ionic/angular'
import { PreferencesPage } from './preferences.page' import { PrivacyPage } from './privacy.page'
import { Routes, RouterModule } from '@angular/router' import { Routes, RouterModule } from '@angular/router'
import { SharingModule } from 'src/app/modules/sharing.module' import { SharingModule } from 'src/app/modules/sharing.module'
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module' import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module'
@@ -9,7 +9,7 @@ import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-b
const routes: Routes = [ const routes: Routes = [
{ {
path: '', path: '',
component: PreferencesPage, component: PrivacyPage,
}, },
] ]
@@ -22,7 +22,7 @@ const routes: Routes = [
PwaBackComponentModule, PwaBackComponentModule,
], ],
declarations: [ declarations: [
PreferencesPage, PrivacyPage,
], ],
}) })
export class PreferencesPageModule { } export class PrivacyPageModule { }

View File

@@ -0,0 +1,34 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<pwa-back-button></pwa-back-button>
</ion-buttons>
<ion-title>Privacy and Security</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-top">
<ion-item-group>
<ion-item-divider>Marketplace Settings</ion-item-divider>
<ion-item button (click)="presentModalValueEdit('eosMarketplace', patch.data['server-info']['eos-marketplace'])">
<ion-label>Use Tor</ion-label>
<ion-note slot="end">{{ patch.data['server-info']['eos-marketplace'] === config.start9Marketplace.tor }}</ion-note>
</ion-item>
<ion-item button (click)="presentModalValueEdit('packageMarketplace', patch.data['server-info']['package-marketplace'])">
<ion-label>Package Marketplace</ion-label>
<ion-note slot="end">{{ patch.data['server-info']['package-marketplace'] }}</ion-note>
</ion-item>
<ion-item button (click)="presentModalValueEdit('autoCheckUpdates', patch.data.ui['auto-check-updates'])">
<ion-label>Auto Check for Updates</ion-label>
<ion-note slot="end">{{ patch.data.ui['auto-check-updates'] }}</ion-note>
</ion-item>
<ion-item-divider></ion-item-divider>
<ion-item button (click)="presentModalValueEdit('password')">
<ion-label>Change Password</ion-label>
<ion-note slot="end">********</ion-note>
</ion-item>
</ion-item-group>
</ion-content>

View File

@@ -2,17 +2,19 @@ import { Component } from '@angular/core'
import { ServerConfigService } from 'src/app/services/server-config.service' import { ServerConfigService } from 'src/app/services/server-config.service'
import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service' import { PatchDbModel } from 'src/app/services/patch-db/patch-db.service'
import { Subscription } from 'rxjs' import { Subscription } from 'rxjs'
import { ConfigService } from 'src/app/services/config.service'
@Component({ @Component({
selector: 'preferences', selector: 'privacy',
templateUrl: './preferences.page.html', templateUrl: './privacy.page.html',
styleUrls: ['./preferences.page.scss'], styleUrls: ['./privacy.page.scss'],
}) })
export class PreferencesPage { export class PrivacyPage {
subs: Subscription[] = [] subs: Subscription[] = []
constructor ( constructor (
private readonly serverConfigService: ServerConfigService, private readonly serverConfigService: ServerConfigService,
public readonly config: ConfigService,
public readonly patch: PatchDbModel, public readonly patch: PatchDbModel,
) { } ) { }

View File

@@ -23,12 +23,8 @@ const routes: Routes = [
loadChildren: () => import('./server-logs/server-logs.module').then(m => m.ServerLogsPageModule), loadChildren: () => import('./server-logs/server-logs.module').then(m => m.ServerLogsPageModule),
}, },
{ {
path: 'preferences', path: 'privacy',
loadChildren: () => import('./preferences/preferences.module').then(m => m.PreferencesPageModule), loadChildren: () => import('./privacy/privacy.module').then(m => m.PrivacyPageModule),
},
{
path: 'preferences/edit',
loadChildren: () => import('./preferences/preferences.module').then(m => m.PreferencesPageModule),
}, },
{ {
path: 'wifi', path: 'wifi',

View File

@@ -44,10 +44,17 @@ export module RR {
export type RefreshLanReq = { } // network.lan.refresh export type RefreshLanReq = { } // network.lan.refresh
export type RefreshLanRes = null export type RefreshLanRes = null
// registry // marketplace URLs
export type SetRegistryReq = WithExpire<{ url: string }> // registry.set export type SetEosMarketplaceReq = WithExpire<{ url: string }> // marketplace.eos.set
export type SetRegistryRes = WithRevision<null> export type SetEosMarketplaceRes = WithRevision<null>
export type SetPackageMarketplaceReq = WithExpire<{ url: string }> // marketplace.package.set
export type SetPackageMarketplaceRes = WithRevision<null>
// password
export type UpdatePasswordReq = { password: string } // password.set
export type UpdatePasswordRes = null
// notification // notification
@@ -160,11 +167,11 @@ export module RR {
export type GetMarketplaceEOSReq = { } export type GetMarketplaceEOSReq = { }
export type GetMarketplaceEOSRes = MarketplaceEOS export type GetMarketplaceEOSRes = MarketplaceEOS
export type GetAvailableListReq = { category?: string, query?: string, page: number, 'per-page': number } export type GetMarketplacePackagesReq = { id?: string, version?: string, category?: string, query?: string, page?: string, 'per-page'?: string }
export type GetAvailableListRes = AvailablePreview[] export type GetMarketplacePackagesRes = MarketplacePkg[]
export type GetAvailableShowReq = { id: string, version?: string } export type GetReleaseNotesReq = { id: string }
export type GetAvailableShowRes = AvailableShow export type GetReleaseNotesRes = { [version: string]: string}
} }
export type WithExpire<T> = { 'expire-id'?: string } & T export type WithExpire<T> = { 'expire-id'?: string } & T
@@ -180,15 +187,7 @@ export interface MarketplaceEOS {
'release-notes': { [version: string]: string } 'release-notes': { [version: string]: string }
} }
export interface AvailablePreview { export interface MarketplacePkg {
id: string
title: string
version: string
icon: URL
descriptionShort: string
}
export interface AvailableShow {
icon: URL icon: URL
license: URL license: URL
instructions: URL instructions: URL
@@ -201,7 +200,6 @@ export interface AvailableShow {
icon: URL icon: URL
} }
}, },
'release-notes': { [version: string]: string }
} }
export interface Breakages { export interface Breakages {

View File

@@ -5,8 +5,8 @@ import { ConfigService } from '../config.service'
export function ApiServiceFactory (config: ConfigService, http: HttpService) { export function ApiServiceFactory (config: ConfigService, http: HttpService) {
if (config.mocks.enabled) { if (config.mocks.enabled) {
return new MockApiService(http) return new MockApiService(config, http)
} else { } else {
return new LiveApiService(http) return new LiveApiService(config, http)
} }
} }

View File

@@ -53,13 +53,21 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
abstract refreshLan (params: RR.RefreshLanReq): Promise<RR.RefreshLanRes> abstract refreshLan (params: RR.RefreshLanReq): Promise<RR.RefreshLanRes>
// registry // marketplace URLs
protected abstract setRegistryRaw (params: RR.SetRegistryReq): Promise<RR.SetRegistryRes> protected abstract setEosMarketplaceRaw (isTor: boolean): Promise<RR.SetEosMarketplaceRes>
setRegistry = (params: RR.SetRegistryReq) => this.syncResponse( setEosMarketplace = (isTor: boolean) => this.syncResponse(
() => this.setRegistryRaw(params), () => this.setEosMarketplaceRaw(isTor),
)() )()
protected abstract setPackageMarketplaceRaw (params: RR.SetPackageMarketplaceReq): Promise<RR.SetPackageMarketplaceRes>
setPackageMarketplace = (params: RR.SetPackageMarketplaceReq) => this.syncResponse(
() => this.setPackageMarketplaceRaw(params),
)()
// password
abstract updatePassword (params: RR.UpdatePasswordReq): Promise<RR.UpdatePasswordRes>
// notification // notification
abstract getNotificationsRaw (params: RR.GetNotificationsReq): Promise<RR.GetNotificationsRes> abstract getNotificationsRaw (params: RR.GetNotificationsReq): Promise<RR.GetNotificationsRes>
@@ -165,9 +173,9 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
abstract getEos (params: RR.GetMarketplaceEOSReq): Promise<RR.GetMarketplaceEOSRes> abstract getEos (params: RR.GetMarketplaceEOSReq): Promise<RR.GetMarketplaceEOSRes>
abstract getAvailableList (params: RR.GetAvailableListReq): Promise<RR.GetAvailableListRes> abstract getMarketplacePkgs (params: RR.GetMarketplacePackagesReq): Promise<RR.GetMarketplacePackagesRes>
abstract getAvailableShow (params: RR.GetAvailableShowReq): Promise<RR.GetAvailableShowRes> abstract getReleaseNotes (params: RR.GetReleaseNotesReq): Promise<RR.GetReleaseNotesRes>
// Helper allowing quick decoration to sync the response patch and return the response contents. // 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 // Pass in a tempUpdate function which returns a UpdateTemp corresponding to a temporary
@@ -187,3 +195,13 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
} }
} }
} }
export function getMarketURL (eosOrPackage: 'eos' | 'package', data: DataModel): string {
const eosMarketplace = data['server-info']['eos-marketplace']
if (eosOrPackage === 'eos') {
return eosMarketplace
} else {
const packageMarketplace = data['server-info']['package-marketplace']
return packageMarketplace || eosMarketplace
}
}

View File

@@ -1,12 +1,15 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { HttpService, Method } from '../http.service' import { HttpService, Method } from '../http.service'
import { ApiService } from './api.service' import { ApiService, getMarketURL } from './api.service'
import { RR } from './api-types' import { RR } from './api-types'
import { parsePropertiesPermissive } from 'src/app/util/properties.util' import { parsePropertiesPermissive } from 'src/app/util/properties.util'
import { ConfigService } from '../config.service'
@Injectable() @Injectable()
export class LiveApiService extends ApiService { export class LiveApiService extends ApiService {
constructor ( constructor (
private readonly config: ConfigService,
private readonly http: HttpService, private readonly http: HttpService,
) { super() } ) { super() }
@@ -66,10 +69,22 @@ export class LiveApiService extends ApiService {
return this.http.rpcRequest({ method: 'network.lan.refresh', params }) return this.http.rpcRequest({ method: 'network.lan.refresh', params })
} }
// registry // marketplace URLs
async setRegistryRaw (params: RR.SetRegistryReq): Promise<RR.SetRegistryRes> { async setEosMarketplaceRaw (isTor: boolean): Promise<RR.SetEosMarketplaceRes> {
return this.http.rpcRequest({ method: 'registry.set', params }) const params: RR.SetEosMarketplaceReq = {
url: isTor ? this.config.start9Marketplace.tor : this.config.start9Marketplace.clearnet,
}
return this.http.rpcRequest({ method: 'marketplace.eos.set', params })
}
async setPackageMarketplaceRaw (params: RR.SetPackageMarketplaceReq): Promise<RR.SetPackageMarketplaceRes> {
return this.http.rpcRequest({ method: 'marketplace.package.set', params })
}
// password
async updatePassword (params: RR.UpdatePasswordReq): Promise<RR.UpdatePasswordRes> {
return this.http.rpcRequest({ method: 'password.set', params })
} }
// notification // notification
@@ -200,18 +215,18 @@ export class LiveApiService extends ApiService {
// marketplace // marketplace
async getMarketplaceData (params: RR.GetMarketplaceDataReq): Promise<RR.GetMarketplaceDataRes> { async getMarketplaceData (params: RR.GetMarketplaceDataReq): Promise<RR.GetMarketplaceDataRes> {
return this.http.rpcRequest({ method: 'marketplace.data', params }) return this.http.simpleGet<RR.GetMarketplaceDataRes>(getMarketURL('package', this.patch.data), params)
} }
async getEos (params: RR.GetMarketplaceEOSReq): Promise<RR.GetMarketplaceEOSRes> { async getEos (params: RR.GetMarketplaceEOSReq): Promise<RR.GetMarketplaceEOSRes> {
return this.http.rpcRequest({ method: 'marketplace.eos', params }) return this.http.simpleGet<RR.GetMarketplaceEOSRes>(getMarketURL('eos', this.patch.data), params)
} }
async getAvailableList (params: RR.GetAvailableListReq): Promise<RR.GetAvailableListRes> { async getMarketplacePkgs (params: RR.GetMarketplacePackagesReq): Promise<RR.GetMarketplacePackagesRes> {
return this.http.rpcRequest({ method: 'marketplace.available.list', params }) return this.http.simpleGet<RR.GetMarketplacePackagesRes>(getMarketURL('package', this.patch.data), params)
} }
async getAvailableShow (params: RR.GetAvailableShowReq): Promise<RR.GetAvailableShowRes> { async getReleaseNotes (params: RR.GetReleaseNotesReq): Promise<RR.GetReleaseNotesRes> {
return this.http.rpcRequest({ method: 'marketplace.available', params }) return this.http.simpleGet<RR.GetReleaseNotesRes>(getMarketURL('package', this.patch.data), params)
} }
} }

View File

@@ -1,36 +1,24 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { pauseFor } from '../../util/misc.util' import { pauseFor } from '../../util/misc.util'
import { ApiService } from './api.service' import { ApiService, getMarketURL } from './api.service'
import { Observable } from 'rxjs' import { PatchOp } from 'patch-db-client'
import { PatchOp, Store, Update } from 'patch-db-client' import { PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model'
import { DataModel, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model' import { RR, WithRevision } from './api-types'
import { RR } from './api-types'
import { parsePropertiesPermissive } from 'src/app/util/properties.util' import { parsePropertiesPermissive } from 'src/app/util/properties.util'
import { Mock } from './mock-app-fixures' import { Mock } from './mock-app-fixures'
import { HttpService, Method } from '../http.service' import { HttpService } from '../http.service'
import markdown from 'raw-loader!src/assets/markdown/md-sample.md' import markdown from 'raw-loader!src/assets/markdown/md-sample.md'
import { ConfigService } from '../config.service' import { ConfigService } from '../config.service'
const configService = new ConfigService()
@Injectable() @Injectable()
export class MockApiService extends ApiService { export class MockApiService extends ApiService {
sequence = 0
welcomeAck = false welcomeAck = false
constructor ( constructor (
private readonly config: ConfigService,
private readonly http: HttpService, private readonly http: HttpService,
) { super() } ) { super() }
// every time a patch is returned from the mock, we override its sequence to be 1 more than the last sequence in the patch-db as provided by `o`.
watch$ (store: Store<DataModel>): Observable<Update<DataModel>> {
store.sequence$.subscribe(seq => {
console.log('INCOMING: ', seq)
if (this.sequence < seq) {
this.sequence = seq
}
})
return super.watch$()
}
async getStatic (url: string): Promise<string> { async getStatic (url: string): Promise<string> {
await pauseFor(2000) await pauseFor(2000)
return markdown return markdown
@@ -106,20 +94,14 @@ export class MockApiService extends ApiService {
async updateServerRaw (params: RR.UpdateServerReq): Promise<RR.UpdateServerRes> { async updateServerRaw (params: RR.UpdateServerReq): Promise<RR.UpdateServerRes> {
await pauseFor(2000) await pauseFor(2000)
return { const patch = [
response: null, {
revision: { op: PatchOp.REPLACE,
id: this.nextSequence(), path: '/server-info/status',
patch: [ value: ServerStatus.Updating,
{
op: PatchOp.REPLACE,
path: '/server-info/status',
value: ServerStatus.Updating,
},
],
expireId: null,
}, },
} ]
return this.http.rpcRequest({ method: 'db.patch', params: { patch } })
} }
async restartServer (params: RR.RestartServerReq): Promise<RR.RestartServerRes> { async restartServer (params: RR.RestartServerReq): Promise<RR.RestartServerRes> {
@@ -139,43 +121,56 @@ export class MockApiService extends ApiService {
return null return null
} }
// registry // marketplace URLs
async setRegistryRaw (params: RR.SetRegistryReq): Promise<RR.SetRegistryRes> { async setEosMarketplaceRaw (isTor: boolean): Promise<RR.SetEosMarketplaceRes> {
await pauseFor(2000) await pauseFor(2000)
return { const params: RR.SetEosMarketplaceReq = {
response: null, url: isTor ? this.config.start9Marketplace.tor : this.config.start9Marketplace.clearnet,
revision: {
id: this.nextSequence(),
patch: [
{
op: PatchOp.REPLACE,
path: '/server-info/registry',
value: params.url,
},
],
expireId: null,
},
} }
const patch = [
{
op: PatchOp.REPLACE,
path: '/server-info/eos-marketplace',
value: params.url,
},
]
return this.http.rpcRequest({ method: 'db.patch', params: { patch } })
}
async setPackageMarketplaceRaw (params: RR.SetPackageMarketplaceReq): Promise<RR.SetPackageMarketplaceRes> {
await pauseFor(2000)
const patch = [
{
op: PatchOp.REPLACE,
path: '/server-info/package-marketplace',
value: params.url,
},
]
return this.http.rpcRequest({ method: 'db.patch', params: { patch } })
}
// password
async updatePassword (params: RR.UpdatePasswordReq): Promise<RR.UpdatePasswordRes> {
await pauseFor(2000)
return null
} }
// notification // notification
async getNotificationsRaw (params: RR.GetNotificationsReq): Promise<RR.GetNotificationsRes> { async getNotificationsRaw (params: RR.GetNotificationsReq): Promise<RR.GetNotificationsRes> {
await pauseFor(2000) await pauseFor(2000)
const patch = [
{
op: PatchOp.REPLACE,
path: '/server-info/unread-notification-count',
value: 0,
},
]
const { revision } = await this.http.rpcRequest({ method: 'db.patch', params: { patch } }) as WithRevision<null>
return { return {
response: Mock.Notifications, response: Mock.Notifications,
revision: { revision,
id: this.nextSequence(),
patch: [
{
op: PatchOp.REPLACE,
path: '/server-info/unread-notification-count',
value: 0,
},
],
expireId: null,
},
} }
} }
@@ -193,48 +188,36 @@ export class MockApiService extends ApiService {
async connectWifiRaw (params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes> { async connectWifiRaw (params: RR.ConnectWifiReq): Promise<RR.ConnectWifiRes> {
await pauseFor(2000) await pauseFor(2000)
return { const patch = [
response: null, {
revision: { op: PatchOp.REPLACE,
id: this.nextSequence(), path: '/server-info/wifi/selected',
patch: [ value: params.ssid,
{
op: PatchOp.REPLACE,
path: '/server-info/wifi/selected',
value: params.ssid,
},
{
op: PatchOp.REPLACE,
path: '/server-info/wifi/connected',
value: params.ssid,
},
],
expireId: null,
}, },
} {
op: PatchOp.REPLACE,
path: '/server-info/wifi/connected',
value: params.ssid,
},
]
return this.http.rpcRequest({ method: 'db.patch', params: { patch } })
} }
async deleteWifiRaw (params: RR.DeleteWifiReq): Promise<RR.DeleteWifiRes> { async deleteWifiRaw (params: RR.DeleteWifiReq): Promise<RR.DeleteWifiRes> {
await pauseFor(2000) await pauseFor(2000)
return { const patch = [
response: null, {
revision: { op: PatchOp.REPLACE,
id: this.nextSequence(), path: '/server-info/wifi/selected',
patch: [ value: null,
{
op: PatchOp.REPLACE,
path: '/server-info/wifi/selected',
value: null,
},
{
op: PatchOp.REPLACE,
path: '/server-info/wifi/connected',
value: null,
},
],
expireId: null,
}, },
} {
op: PatchOp.REPLACE,
path: '/server-info/wifi/connected',
value: null,
},
]
return this.http.rpcRequest({ method: 'db.patch', params: { patch } })
} }
// ssh // ssh
@@ -258,20 +241,14 @@ export class MockApiService extends ApiService {
async createBackupRaw (params: RR.CreateBackupReq): Promise<RR.CreateBackupRes> { async createBackupRaw (params: RR.CreateBackupReq): Promise<RR.CreateBackupRes> {
await pauseFor(2000) await pauseFor(2000)
return { const patch = [
response: null, {
revision: { op: PatchOp.REPLACE,
id: this.nextSequence(), path: '/server-info/status',
patch: [ value: ServerStatus.BackingUp,
{
op: PatchOp.REPLACE,
path: '/server-info/status',
value: ServerStatus.BackingUp,
},
],
expireId: null,
}, },
} ]
return this.http.rpcRequest({ method: 'db.patch', params: { patch } })
} }
async restoreBackupRaw (params: RR.RestoreBackupReq): Promise<RR.RestoreBackupRes> { async restoreBackupRaw (params: RR.RestoreBackupReq): Promise<RR.RestoreBackupRes> {
@@ -308,7 +285,6 @@ export class MockApiService extends ApiService {
const pkg: PackageDataEntry = { const pkg: PackageDataEntry = {
...Mock.bitcoinproxy, ...Mock.bitcoinproxy,
state: PackageState.Installing, state: PackageState.Installing,
// installed: undefined,
'install-progress': { 'install-progress': {
size: 100, size: 100,
downloaded: 10, downloaded: 10,
@@ -319,20 +295,14 @@ export class MockApiService extends ApiService {
'read-complete': false, 'read-complete': false,
}, },
} }
return { const patch = [
response: null, {
revision: { op: PatchOp.ADD,
id: this.nextSequence(), path: `/package-data/${params.id}`,
patch: [ value: pkg,
{
op: PatchOp.ADD,
path: `/package-data/${params.id}`,
value: pkg,
},
],
expireId: null,
}, },
} ]
return this.http.rpcRequest({ method: 'db.patch', params: { patch } })
} }
async dryUpdatePackage (params: RR.DryUpdatePackageReq): Promise<RR.DryUpdatePackageRes> { async dryUpdatePackage (params: RR.DryUpdatePackageReq): Promise<RR.DryUpdatePackageRes> {
@@ -352,43 +322,31 @@ export class MockApiService extends ApiService {
async setPackageConfigRaw (params: RR.SetPackageConfigReq): Promise<RR.SetPackageConfigRes> { async setPackageConfigRaw (params: RR.SetPackageConfigReq): Promise<RR.SetPackageConfigRes> {
await pauseFor(2000) await pauseFor(2000)
return { const patch = [
response: null, {
revision: { op: PatchOp.REPLACE,
id: this.nextSequence(), path: `/package-data/${params.id}/installed/status/configured`,
patch: [ value: true,
{
op: PatchOp.REPLACE,
path: `/package-data/${params.id}/installed/status/configured`,
value: true,
},
{
op: PatchOp.REPLACE,
path: `/package-data/${params.id}/installed/status/main/status`,
value: PackageMainStatus.Running,
},
],
expireId: null,
}, },
} {
op: PatchOp.REPLACE,
path: `/package-data/${params.id}/installed/status/main/status`,
value: PackageMainStatus.Running,
},
]
return this.http.rpcRequest({ method: 'db.patch', params: { patch } })
} }
async restorePackageRaw (params: RR.RestorePackageReq): Promise<RR.RestorePackageRes> { async restorePackageRaw (params: RR.RestorePackageReq): Promise<RR.RestorePackageRes> {
await pauseFor(2000) await pauseFor(2000)
return { const patch = [
response: null, {
revision: { op: PatchOp.REPLACE,
id: this.nextSequence(), path: `/package-data/${params.id}/installed/status/main/status`,
patch: [ value: PackageMainStatus.Restoring,
{
op: PatchOp.REPLACE,
path: `/package-data/${params.id}/installed/status/main/status`,
value: PackageMainStatus.Restoring,
},
],
expireId: null,
}, },
} ]
return this.http.rpcRequest({ method: 'db.patch', params: { patch } })
} }
async executePackageAction (params: RR.ExecutePackageActionReq): Promise<RR.ExecutePackageActionRes> { async executePackageAction (params: RR.ExecutePackageActionReq): Promise<RR.ExecutePackageActionRes> {
@@ -403,20 +361,14 @@ export class MockApiService extends ApiService {
async startPackageRaw (params: RR.StartPackageReq): Promise<RR.StartPackageRes> { async startPackageRaw (params: RR.StartPackageReq): Promise<RR.StartPackageRes> {
await pauseFor(2000) await pauseFor(2000)
return { const patch = [
response: null, {
revision: { op: PatchOp.REPLACE,
id: this.nextSequence(), path: `/package-data/${params.id}/installed/status/main/status`,
patch: [ value: PackageMainStatus.Running,
{
op: PatchOp.REPLACE,
path: `/package-data/${params.id}/installed/status/main/status`,
value: PackageMainStatus.Running,
},
],
expireId: null,
}, },
} ]
return this.http.rpcRequest({ method: 'db.patch', params: { patch } })
} }
async dryStopPackage (params: RR.DryStopPackageReq): Promise<RR.DryStopPackageRes> { async dryStopPackage (params: RR.DryStopPackageReq): Promise<RR.DryStopPackageRes> {
@@ -426,20 +378,26 @@ export class MockApiService extends ApiService {
async stopPackageRaw (params: RR.StopPackageReq): Promise<RR.StopPackageRes> { async stopPackageRaw (params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
await pauseFor(2000) await pauseFor(2000)
return { const patch = [
response: null, {
revision: { op: PatchOp.REPLACE,
id: this.nextSequence(), path: `/package-data/${params.id}/installed/status/main/status`,
patch: [ value: PackageMainStatus.Stopping,
{
op: PatchOp.REPLACE,
path: `/package-data/${params.id}/installed/status/main/status`,
value: PackageMainStatus.Stopping,
},
],
expireId: null,
}, },
} ]
const res = await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
setTimeout(() => {
const patch = [
{
op: PatchOp.REPLACE,
path: `/package-data/${params.id}/installed/status/main/status`,
value: PackageMainStatus.Stopped,
},
]
this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
}, 2000)
return res
} }
async dryRemovePackage (params: RR.DryRemovePackageReq): Promise<RR.DryRemovePackageRes> { async dryRemovePackage (params: RR.DryRemovePackageReq): Promise<RR.DryRemovePackageRes> {
@@ -449,20 +407,14 @@ export class MockApiService extends ApiService {
async removePackageRaw (params: RR.RemovePackageReq): Promise<RR.RemovePackageRes> { async removePackageRaw (params: RR.RemovePackageReq): Promise<RR.RemovePackageRes> {
await pauseFor(2000) await pauseFor(2000)
return { const patch = [
response: null, {
revision: { op: PatchOp.REPLACE,
id: this.nextSequence(), path: `/package-data/${params.id}/state`,
patch: [ value: PackageState.Removing,
{
op: PatchOp.REPLACE,
path: `/package-data/${params.id}/state`,
value: PackageState.Removing,
},
],
expireId: null,
}, },
} ]
return this.http.rpcRequest({ method: 'db.patch', params: { patch } })
} }
async dryConfigureDependency (params: RR.DryConfigureDependencyReq): Promise<RR.DryConfigureDependencyRes> { async dryConfigureDependency (params: RR.DryConfigureDependencyReq): Promise<RR.DryConfigureDependencyRes> {
@@ -473,7 +425,7 @@ export class MockApiService extends ApiService {
// marketplace // marketplace
async getMarketplaceData (params: RR.GetMarketplaceDataReq): Promise<RR.GetMarketplaceDataRes> { async getMarketplaceData (params: RR.GetMarketplaceDataReq): Promise<RR.GetMarketplaceDataRes> {
const registryURL = configService.mocks.registryURL const registryURL = getMarketURL('package', this.patch.data)
if (!registryURL) { if (!registryURL) {
await pauseFor(2000) await pauseFor(2000)
return { return {
@@ -481,47 +433,36 @@ export class MockApiService extends ApiService {
} }
} }
const url = `${registryURL}/marketplace/data` const url = `${registryURL}/marketplace/data`
let md = await this.http.simpleGet(url) return this.http.simpleGet<RR.GetMarketplaceDataRes>(url)
return (md as any)
} }
async getEos (params: RR.GetMarketplaceEOSReq): Promise<RR.GetMarketplaceEOSRes> { async getEos (params: RR.GetMarketplaceEOSReq): Promise<RR.GetMarketplaceEOSRes> {
const registryURL = configService.mocks.registryURL const registryURL = getMarketURL('eos', this.patch.data)
if (!registryURL) { if (!registryURL) {
await pauseFor(2000) await pauseFor(2000)
return Mock.MarketplaceEos return Mock.MarketplaceEos
} }
const url = `${registryURL}/sys/version/eos` const url = `${registryURL}/sys/version/eos`
let eos = await this.http.simpleGet(url) return this.http.simpleGet<RR.GetMarketplaceEOSRes>(url)
return (eos as any)
} }
async getAvailableList (params: RR.GetAvailableListReq): Promise<RR.GetAvailableListRes> { async getMarketplacePkgs (params: RR.GetMarketplacePackagesReq): Promise<RR.GetMarketplacePackagesRes> {
const registryURL = configService.mocks.registryURL const registryURL = getMarketURL('package', this.patch.data)
if (!registryURL) { if (!registryURL) {
await pauseFor(2000) await pauseFor(2000)
return Mock.AvailableList return Mock.AvailableList
} }
const url = `${registryURL}/marketplace/available/list?${params.category ? `category=${params.category}` : ''}&per-page=${params['per-page'] || '20'}&page=${params.page || '1'}&query=${params.query || ''}` const url = `${registryURL}/marketplace/packages`
let av = await this.http.simpleGet(url) return this.http.simpleGet<RR.GetMarketplacePackagesRes>(url, params)
return (av as any).services
} }
async getAvailableShow (params: RR.GetAvailableShowReq): Promise<RR.GetAvailableShowRes> { async getReleaseNotes (params: RR.GetReleaseNotesReq): Promise<RR.GetReleaseNotesRes> {
const registryURL = configService.mocks.registryURL const registryURL = getMarketURL('package', this.patch.data)
if (!registryURL) { if (!registryURL) {
await pauseFor(2000) await pauseFor(2000)
return Mock.AvailableShow[params.id][params.version || 'latest'] return Mock.ReleaseNotes
} }
const url = `${registryURL}/marketplace/available?id=${params.id}` const url = `${registryURL}/marketplace/release-notes`
let res = await this.http.simpleGet(url) return this.http.simpleGet<RR.GetReleaseNotesRes>(url)
console.log('res RES RES', res)
return (res as any)
}
private nextSequence () {
console.log('next')
this.sequence++
return this.sequence
} }
} }

View File

@@ -1,5 +1,5 @@
import { DependencyErrorType, DockerIoFormat, Manifest, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model' import { DependencyErrorType, DockerIoFormat, Manifest, PackageDataEntry, PackageMainStatus, PackageState, ServerStatus } from 'src/app/services/patch-db/data-model'
import { Metric, NotificationLevel, RR, ServerNotification, ServerNotifications } from './api-types' import { MarketplacePkg, Metric, NotificationLevel, RR, ServerNotification, ServerNotifications } from './api-types'
export module Mock { export module Mock {
@@ -9,29 +9,11 @@ export module Mock {
'release-notes': { '1.0.0': 'Some **Markdown** release _notes_' }, 'release-notes': { '1.0.0': 'Some **Markdown** release _notes_' },
} }
export const AvailableList: RR.GetAvailableListRes = [ export const ReleaseNotes: RR.GetReleaseNotesRes = {
{ '0.19.2': 'Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.',
id: 'bitcoind', '0.19.1': 'release notes for Bitcoin 0.19.1',
title: 'Bitcoin Core', '0.19.0': 'release notes for Bitcoin 0.19.0',
version: '0.21.1', }
descriptionShort: 'A Bitcoin full node by Bitcoin Core.',
icon: 'assets/img/service-icons/bitcoind.png',
},
{
id: 'lnd',
title: 'LND',
version: '0.11.1',
descriptionShort: 'A BOLT-compliant, lightning network node.',
icon: 'assets/img/service-icons/lnd.png',
},
{
id: 'bitcoin-proxy',
title: 'Bitcoin Proxy',
version: '0.2.2',
descriptionShort: 'A super charger for your Bitcoin node.',
icon: 'assets/img/service-icons/bitcoin-proxy.png',
},
]
export const MockManifestBitcoind: Manifest = { export const MockManifestBitcoind: Manifest = {
id: 'bitcoind', id: 'bitcoind',
@@ -395,7 +377,7 @@ export module Mock {
export const AvailableShow: { export const AvailableShow: {
[id: string]: { [id: string]: {
[version: string]: RR.GetAvailableShowRes [version: string]: MarketplacePkg
} }
} = { } = {
'bitcoind': { 'bitcoind': {
@@ -410,11 +392,6 @@ export module Mock {
categories: ['bitcoin', 'cryptocurrency'], categories: ['bitcoin', 'cryptocurrency'],
versions: ['0.19.0', '0.20.0', '0.21.0'], versions: ['0.19.0', '0.20.0', '0.21.0'],
'dependency-metadata': { }, 'dependency-metadata': { },
'release-notes': {
'0.19.2': 'Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.',
'0.19.1': 'release notes for Bitcoin 0.19.1',
'0.19.0': 'release notes for Bitcoin 0.19.0',
},
}, },
'0.20.0': { '0.20.0': {
icon: 'assets/img/service-icons/bitcoind.png', icon: 'assets/img/service-icons/bitcoind.png',
@@ -427,11 +404,6 @@ export module Mock {
categories: ['bitcoin', 'cryptocurrency'], categories: ['bitcoin', 'cryptocurrency'],
versions: ['0.19.0', '0.20.0', '0.21.0'], versions: ['0.19.0', '0.20.0', '0.21.0'],
'dependency-metadata': { }, 'dependency-metadata': { },
'release-notes': {
'0.19.2': 'Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.',
'0.19.1': 'release notes for Bitcoin 0.19.1',
'0.19.0': 'release notes for Bitcoin 0.19.0',
},
}, },
'0.21.0': { '0.21.0': {
icon: 'assets/img/service-icons/bitcoind.png', icon: 'assets/img/service-icons/bitcoind.png',
@@ -445,11 +417,6 @@ export module Mock {
categories: ['bitcoin', 'cryptocurrency'], categories: ['bitcoin', 'cryptocurrency'],
versions: ['0.19.0', '0.20.0', '0.21.0'], versions: ['0.19.0', '0.20.0', '0.21.0'],
'dependency-metadata': { }, 'dependency-metadata': { },
'release-notes': {
'0.19.2': 'Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.',
'0.19.1': 'release notes for Bitcoin 0.19.1',
'0.19.0': 'release notes for Bitcoin 0.19.0',
},
}, },
'latest': { 'latest': {
icon: 'assets/img/service-icons/bitcoind.png', icon: 'assets/img/service-icons/bitcoind.png',
@@ -462,11 +429,6 @@ export module Mock {
categories: ['bitcoin', 'cryptocurrency'], categories: ['bitcoin', 'cryptocurrency'],
versions: ['0.19.0', '0.20.0', '0.21.0'], versions: ['0.19.0', '0.20.0', '0.21.0'],
'dependency-metadata': { }, 'dependency-metadata': { },
'release-notes': {
'0.21.0': 'Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.',
'0.20.0': 'release notes for Bitcoin 0.20.0',
'0.19.2': 'release notes for Bitcoin 0.19.2',
},
}, },
}, },
'lnd': { 'lnd': {
@@ -491,11 +453,6 @@ export module Mock {
icon: 'assets/img/service-icons/bitcoin-proxy.png', icon: 'assets/img/service-icons/bitcoin-proxy.png',
}, },
}, },
'release-notes': {
'0.19.2': 'release notes for LND 0.19.2',
'0.19.1': 'release notes for LND 0.19.1',
'0.19.0': 'release notes for LND 0.19.0',
},
}, },
'0.11.1': { '0.11.1': {
icon: 'assets/img/service-icons/lnd.png', icon: 'assets/img/service-icons/lnd.png',
@@ -518,11 +475,6 @@ export module Mock {
icon: 'assets/img/service-icons/bitcoin-proxy.png', icon: 'assets/img/service-icons/bitcoin-proxy.png',
}, },
}, },
'release-notes': {
'0.19.2': 'release notes for LND 0.19.2',
'0.19.1': 'release notes for LND 0.19.1',
'0.19.0': 'release notes for LND 0.19.0',
},
}, },
'latest': { 'latest': {
icon: 'assets/img/service-icons/lnd.png', icon: 'assets/img/service-icons/lnd.png',
@@ -541,11 +493,6 @@ export module Mock {
icon: 'assets/img/service-icons/bitcoin-proxy.png', icon: 'assets/img/service-icons/bitcoin-proxy.png',
}, },
}, },
'release-notes': {
'0.19.2': 'release notes for LND 0.19.2',
'0.19.1': 'release notes for LND 0.19.1',
'0.19.0': 'release notes for LND 0.19.0',
},
}, },
}, },
'bitcoin-proxy': { 'bitcoin-proxy': {
@@ -562,15 +509,12 @@ export module Mock {
icon: 'assets/img/service-icons/bitcoind.png', icon: 'assets/img/service-icons/bitcoind.png',
}, },
}, },
'release-notes': {
'0.19.2': 'release notes for btc proxy 0.19.2',
'0.19.1': 'release notes for btc proxy 0.19.1',
'0.19.0': 'release notes for btc proxy 0.19.0',
},
}, },
}, },
} }
export const AvailableList: RR.GetMarketplacePackagesRes = Object.values(Mock.AvailableShow).map(service => service['latest'])
export const bitcoind: PackageDataEntry = { export const bitcoind: PackageDataEntry = {
state: PackageState.Installed, state: PackageState.Installed,
'static-files': { 'static-files': {
@@ -731,8 +675,8 @@ export module Mock {
selected: 'Goosers5G', selected: 'Goosers5G',
connected: 'Goosers5G', connected: 'Goosers5G',
}, },
'package-registry': 'https://registry.start9.com', 'package-marketplace': 'https://registry.start9.com',
'system-registry': 'https://registry.start9.com', 'eos-marketplace': 'https://registry.start9.com',
'unread-notification-count': 4, 'unread-notification-count': 4,
specs: { specs: {
CPU: 'Cortex-A72: 4 Cores @1500MHz', CPU: 'Cortex-A72: 4 Cores @1500MHz',

View File

@@ -1,9 +1,13 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { InstalledPackageDataEntry, InterfaceDef, Manifest, PackageDataEntry, PackageMainStatus, PackageState } from './patch-db/data-model' import { InterfaceDef, Manifest, PackageDataEntry, PackageMainStatus, PackageState } from './patch-db/data-model'
const { patchDb, api, mocks } = require('../../../config.json') as UiConfig const { start9Marketplace, patchDb, api, mocks } = require('../../../config.json') as UiConfig
type UiConfig = { type UiConfig = {
start9Marketplace: {
clearnet: string
tor: string
}
patchDb: { patchDb: {
poll: { poll: {
cooldown: number /* in ms */ cooldown: number /* in ms */
@@ -20,7 +24,6 @@ type UiConfig = {
wsPort: number wsPort: number
maskAs: 'tor' | 'lan' maskAs: 'tor' | 'lan'
skipStartupAlerts: boolean skipStartupAlerts: boolean
registryURL: String
} }
} }
@Injectable({ @Injectable({
@@ -30,6 +33,7 @@ export class ConfigService {
origin = removePort(removeProtocol(window.origin)) origin = removePort(removeProtocol(window.origin))
version = require('../../../package.json').version version = require('../../../package.json').version
start9Marketplace = start9Marketplace
patchDb = patchDb patchDb = patchDb
api = api api = api
mocks = mocks mocks = mocks

View File

@@ -44,9 +44,11 @@ export class HttpService {
if (isRpcSuccess(res)) return res.result if (isRpcSuccess(res)) return res.result
} }
async simpleGet (url: string): Promise<object> { async simpleGet<T> (url: string, params: { [param: string]: string | string[] } = { }): Promise<T> {
const data = await this.http.get(url).toPromise() Object.keys(params).forEach(key => {
return data if (!params[key]) delete params[key]
})
return this.http.get<T>(url, { params }).toPromise()
} }
async httpRequest<T> (httpOpts: HttpOptions): Promise<T> { async httpRequest<T> (httpOpts: HttpOptions): Promise<T> {

View File

@@ -6,14 +6,19 @@ export interface DataModel {
ui: UIData ui: UIData
} }
export interface UIData {
'welcome-ack': string
'auto-check-updates': boolean
}
export interface ServerInfo { export interface ServerInfo {
id: string id: string
version: string version: string
'lan-address': URL 'lan-address': URL
'tor-address': URL 'tor-address': URL
status: ServerStatus status: ServerStatus
'package-registry': URL 'eos-marketplace': URL
'system-registry': URL 'package-marketplace': URL | null // uses EOS marketplace if null
wifi: WiFiInfo wifi: WiFiInfo
'unread-notification-count': number 'unread-notification-count': number
specs: { specs: {
@@ -376,8 +381,3 @@ export interface InterfaceInfo {
} }
export type URL = string export type URL = string
export interface UIData {
'welcome-ack': string
'auto-check-updates': boolean
}

View File

@@ -34,35 +34,25 @@ export class ServerConfigService {
} }
saveFns: { [key: string]: (val: any) => Promise<any> } = { saveFns: { [key: string]: (val: any) => Promise<any> } = {
name: async (value: string) => {
return this.apiService.setDbValue({ pointer: 'ui/name', value })
},
autoCheckUpdates: async (value: boolean) => { autoCheckUpdates: async (value: boolean) => {
return this.apiService.setDbValue({ pointer: 'ui/auto-check-updates', value }) return this.apiService.setDbValue({ pointer: 'ui/auto-check-updates', value })
}, },
ssh: async (pubkey: string) => { ssh: async (pubkey: string) => {
return this.sshService.add(pubkey) return this.sshService.add(pubkey)
}, },
registry: async (url: string) => { eosMarketplace: async (enabled: boolean) => {
return this.apiService.setRegistry({ url }) return this.apiService.setEosMarketplace(enabled)
},
packageMarketplace: async (url: string) => {
return this.apiService.setPackageMarketplace({ url })
},
password: async (password: string) => {
return this.apiService.updatePassword({ password })
}, },
// password: async (password: string) => {
// return this.apiService.updatePassword({ password })
// },
} }
} }
const serverConfig: ConfigSpec = { const serverConfig: ConfigSpec = {
name: {
type: 'string',
name: 'Device Name',
description: 'A unique label for this device.',
nullable: false,
pattern: '^.{1,40}$',
patternDescription: 'Must be less than 40 characters',
masked: false,
copyable: false,
},
autoCheckUpdates: { autoCheckUpdates: {
type: 'boolean', type: 'boolean',
name: 'Auto Check for Updates', name: 'Auto Check for Updates',
@@ -80,29 +70,33 @@ const serverConfig: ConfigSpec = {
masked: false, masked: false,
copyable: false, copyable: false,
}, },
registry: { eosMarketplace: {
type: 'boolean',
name: 'Use Tor',
description: `Use Start9's Tor Hidden Service Marketplace (instead of clearnet).`,
default: false,
},
packageMarketplace: {
type: 'string', type: 'string',
name: 'Marketplace URL', name: 'Package Marketplace',
description: 'The URL of the service marketplace. By default, your Embassy connects to the official Start9 Embassy Marketplace.', description: `Use for alternative embassy marketplace. Leave empty to use start9's marketplace.`,
nullable: true, nullable: true,
// @TODO regex for URL // @TODO regex for URL
// pattern: '', // pattern: '',
patternDescription: 'Must be a valid URL', patternDescription: 'Must be a valid URL.',
changeWarning: 'Downloading services from an alternative marketplace can result in malicious or harmful code being installed on your device.', masked: false,
default: 'https://registry.start9.com', copyable: false,
},
password: {
type: 'string',
name: 'Change Password',
description: `Your Embassy's master password, used for authentication and disk encryption.`,
nullable: false,
// @TODO regex for 12 chars
// pattern: '',
patternDescription: 'Must contain at least 12 characters.',
changeWarning: 'If you forget your master password, there is absolutely no way to recover your data. This can result in loss of money! Keep in mind, old backups will still be encrypted by the password used to encrypt them.',
masked: false, masked: false,
copyable: false, copyable: false,
}, },
// password: {
// type: 'string',
// name: 'Change Password',
// description: 'The master password for your Embassy. Must contain at least 128 bits of entropy.',
// nullable: false,
// // @TODO figure out how to confirm min entropy
// // pattern: '^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[*.!@#$%^&*\]).{12,32}$',
// patternDescription: 'Password too simple. Password must contain at least 128 bits of entroy.',
// changeWarning: 'Changing your password will have no affect on old backups. In order to restore old backups, you must provide the password that was used to create them.',
// masked: true,
// copyable: true,
// },
} }