mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
feat: enable strictNullChecks
feat: enable `noImplicitAny` chore: remove sync data access fix loading package data for affected dependencies chore: properly get alt marketplace data update patchdb client to allow for emit on undefined values
This commit is contained in:
@@ -23,40 +23,47 @@ export class LogsPage {
|
|||||||
scrollToBottomButton = false
|
scrollToBottomButton = false
|
||||||
isOnBottom = true
|
isOnBottom = true
|
||||||
|
|
||||||
constructor (
|
constructor(private readonly api: ApiService) {}
|
||||||
private readonly api: ApiService,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit() {
|
||||||
this.getLogs()
|
this.getLogs()
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLogs () {
|
async getLogs() {
|
||||||
try {
|
try {
|
||||||
// get logs
|
// get logs
|
||||||
const logs = await this.fetch()
|
const logs = await this.fetch()
|
||||||
if (!logs.length) return
|
|
||||||
|
if (!logs?.length) return
|
||||||
|
|
||||||
const container = document.getElementById('container')
|
const container = document.getElementById('container')
|
||||||
const beforeContainerHeight = container.scrollHeight
|
const beforeContainerHeight = container?.scrollHeight || 0
|
||||||
const newLogs = document.getElementById('template').cloneNode(true) as HTMLElement
|
const newLogs = document.getElementById('template')?.cloneNode(true)
|
||||||
newLogs.innerHTML = logs.map(l => `${l.timestamp} ${convert.toHtml(l.message)}`).join('\n') + (logs.length ? '\n' : '')
|
|
||||||
|
|
||||||
container.prepend(newLogs)
|
if (!(newLogs instanceof HTMLElement)) return
|
||||||
const afterContainerHeight = container.scrollHeight
|
|
||||||
|
newLogs.innerHTML =
|
||||||
|
logs
|
||||||
|
.map(l => `${l.timestamp} ${convert.toHtml(l.message)}`)
|
||||||
|
.join('\n') + (logs.length ? '\n' : '')
|
||||||
|
container?.prepend(newLogs)
|
||||||
|
|
||||||
|
const afterContainerHeight = container?.scrollHeight || 0
|
||||||
|
|
||||||
// scroll down
|
// scroll down
|
||||||
scrollBy(0, afterContainerHeight - beforeContainerHeight)
|
scrollBy(0, afterContainerHeight - beforeContainerHeight)
|
||||||
this.content.scrollToPoint(0, afterContainerHeight - beforeContainerHeight)
|
this.content.scrollToPoint(
|
||||||
|
0,
|
||||||
|
afterContainerHeight - beforeContainerHeight,
|
||||||
|
)
|
||||||
|
|
||||||
if (logs.length < this.limit) {
|
if (logs.length < this.limit) {
|
||||||
this.needInfinite = false
|
this.needInfinite = false
|
||||||
}
|
}
|
||||||
|
} catch (e) {}
|
||||||
} catch (e) { }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetch (isBefore: boolean = true) {
|
async fetch(isBefore: boolean = true) {
|
||||||
try {
|
try {
|
||||||
const cursor = isBefore ? this.startCursor : this.endCursor
|
const cursor = isBefore ? this.startCursor : this.endCursor
|
||||||
|
|
||||||
@@ -81,33 +88,40 @@ export class LogsPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadMore () {
|
async loadMore() {
|
||||||
try {
|
try {
|
||||||
this.loadingMore = true
|
this.loadingMore = true
|
||||||
const logs = await this.fetch(false)
|
const logs = await this.fetch(false)
|
||||||
if (!logs.length) return this.loadingMore = false
|
|
||||||
|
if (!logs?.length) return (this.loadingMore = false)
|
||||||
|
|
||||||
const container = document.getElementById('container')
|
const container = document.getElementById('container')
|
||||||
const newLogs = document.getElementById('template').cloneNode(true) as HTMLElement
|
const newLogs = document.getElementById('template')?.cloneNode(true)
|
||||||
newLogs.innerHTML = logs.map(l => `${l.timestamp} ${convert.toHtml(l.message)}`).join('\n') + (logs.length ? '\n' : '')
|
|
||||||
container.append(newLogs)
|
if (!(newLogs instanceof HTMLElement)) return
|
||||||
|
|
||||||
|
newLogs.innerHTML =
|
||||||
|
logs
|
||||||
|
.map(l => `${l.timestamp} ${convert.toHtml(l.message)}`)
|
||||||
|
.join('\n') + (logs.length ? '\n' : '')
|
||||||
|
container?.append(newLogs)
|
||||||
this.loadingMore = false
|
this.loadingMore = false
|
||||||
this.scrollEvent()
|
this.scrollEvent()
|
||||||
} catch (e) { }
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollEvent () {
|
scrollEvent() {
|
||||||
const buttonDiv = document.getElementById('button-div')
|
const buttonDiv = document.getElementById('button-div')
|
||||||
this.isOnBottom = buttonDiv.getBoundingClientRect().top < window.innerHeight
|
this.isOnBottom =
|
||||||
|
!!buttonDiv && buttonDiv.getBoundingClientRect().top < window.innerHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToBottom () {
|
scrollToBottom() {
|
||||||
this.content.scrollToBottom(500)
|
this.content.scrollToBottom(500)
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadData (e: any): Promise<void> {
|
async loadData(e: any): Promise<void> {
|
||||||
await this.getLogs()
|
await this.getLogs()
|
||||||
e.target.complete()
|
e.target.complete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,17 +25,14 @@ export class MockApiService extends ApiService {
|
|||||||
|
|
||||||
async restart(): Promise<void> {
|
async restart(): Promise<void> {
|
||||||
await pauseFor(1000)
|
await pauseFor(1000)
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async forgetDrive(): Promise<void> {
|
async forgetDrive(): Promise<void> {
|
||||||
await pauseFor(1000)
|
await pauseFor(1000)
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async repairDisk(): Promise<void> {
|
async repairDisk(): Promise<void> {
|
||||||
await pauseFor(1000)
|
await pauseFor(1000)
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLogs(params: GetLogsReq): Promise<GetLogsRes> {
|
async getLogs(params: GetLogsReq): Promise<GetLogsRes> {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http'
|
import { HttpClient } from '@angular/common/http'
|
||||||
import { HttpError, RpcError } from '@start9labs/shared'
|
import { HttpError, RpcError } from '@start9labs/shared'
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@@ -12,6 +12,8 @@ export class HttpService {
|
|||||||
const res = await this.httpRequest<RPCResponse<T>>(options)
|
const res = await this.httpRequest<RPCResponse<T>>(options)
|
||||||
if (isRpcError(res)) throw new RpcError(res.error)
|
if (isRpcError(res)) throw new RpcError(res.error)
|
||||||
if (isRpcSuccess(res)) return res.result
|
if (isRpcSuccess(res)) return res.result
|
||||||
|
|
||||||
|
throw new Error('Unknown RPC response')
|
||||||
}
|
}
|
||||||
|
|
||||||
async httpRequest<T>(body: RPCOptions): Promise<T> {
|
async httpRequest<T>(body: RPCOptions): Promise<T> {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ChangeDetectionStrategy, Component, ElementRef } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, ElementRef } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
|
import { getPkgId } from '@start9labs/shared'
|
||||||
import { AbstractMarketplaceService } from '../../services/marketplace.service'
|
import { AbstractMarketplaceService } from '../../services/marketplace.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -9,7 +10,7 @@ import { AbstractMarketplaceService } from '../../services/marketplace.service'
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class ReleaseNotesComponent {
|
export class ReleaseNotesComponent {
|
||||||
private readonly pkgId = this.route.snapshot.paramMap.get('pkgId')
|
private readonly pkgId = getPkgId(this.route)
|
||||||
|
|
||||||
private selected: string | null = null
|
private selected: string | null = null
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export class AdditionalComponent {
|
|||||||
const alert = await this.alertCtrl.create({
|
const alert = await this.alertCtrl.create({
|
||||||
header: 'Versions',
|
header: 'Versions',
|
||||||
inputs: this.pkg.versions
|
inputs: this.pkg.versions
|
||||||
.sort((a, b) => -1 * this.emver.compare(a, b))
|
.sort((a, b) => -1 * (this.emver.compare(a, b) || 0))
|
||||||
.map(v => ({
|
.map(v => ({
|
||||||
name: v, // for CSS
|
name: v, // for CSS
|
||||||
type: 'radio',
|
type: 'radio',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NgModule, Pipe, PipeTransform } from '@angular/core'
|
import { NgModule, Pipe, PipeTransform } from '@angular/core'
|
||||||
import Fuse from 'fuse.js/dist/fuse.min.js'
|
import Fuse from 'fuse.js'
|
||||||
|
|
||||||
import { MarketplacePkg } from '../types/marketplace-pkg'
|
import { MarketplacePkg } from '../types/marketplace-pkg'
|
||||||
import { MarketplaceManifest } from '../types/marketplace-manifest'
|
import { MarketplaceManifest } from '../types/marketplace-manifest'
|
||||||
|
|||||||
@@ -15,5 +15,8 @@ export abstract class AbstractMarketplaceService {
|
|||||||
|
|
||||||
abstract getPackageMarkdown(type: string, pkgId: string): Observable<string>
|
abstract getPackageMarkdown(type: string, pkgId: string): Observable<string>
|
||||||
|
|
||||||
abstract getPackage(id: string, version: string): Observable<MarketplacePkg>
|
abstract getPackage(
|
||||||
|
id: string,
|
||||||
|
version: string,
|
||||||
|
): Observable<MarketplacePkg | null>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { Component, Input, ViewChild } from '@angular/core'
|
import { Component, Input, ViewChild } from '@angular/core'
|
||||||
import { IonInput, ModalController } from '@ionic/angular'
|
import { IonInput, ModalController } from '@ionic/angular'
|
||||||
import { DiskInfo, CifsBackupTarget, DiskBackupTarget } from 'src/app/services/api/api.service'
|
import {
|
||||||
|
DiskInfo,
|
||||||
|
CifsBackupTarget,
|
||||||
|
DiskBackupTarget,
|
||||||
|
} from 'src/app/services/api/api.service'
|
||||||
import * as argon2 from '@start9labs/argon2'
|
import * as argon2 from '@start9labs/argon2'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -21,26 +25,27 @@ export class PasswordPage {
|
|||||||
passwordVer = ''
|
passwordVer = ''
|
||||||
unmasked2 = false
|
unmasked2 = false
|
||||||
|
|
||||||
constructor (
|
constructor(private modalController: ModalController) {}
|
||||||
private modalController: ModalController,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngAfterViewInit () {
|
ngAfterViewInit() {
|
||||||
setTimeout(() => this.elem.setFocus(), 400)
|
setTimeout(() => this.elem.setFocus(), 400)
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyPw () {
|
async verifyPw() {
|
||||||
if (!this.target || !this.target['embassy-os']) this.pwError = 'No recovery target' // unreachable
|
if (!this.target || !this.target['embassy-os'])
|
||||||
|
this.pwError = 'No recovery target' // unreachable
|
||||||
|
|
||||||
try {
|
try {
|
||||||
argon2.verify(this.target['embassy-os']['password-hash'], this.password)
|
const passwordHash = this.target['embassy-os']?.['password-hash'] || ''
|
||||||
|
|
||||||
|
argon2.verify(passwordHash, this.password)
|
||||||
this.modalController.dismiss({ password: this.password }, 'success')
|
this.modalController.dismiss({ password: this.password }, 'success')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.pwError = 'Incorrect password provided'
|
this.pwError = 'Incorrect password provided'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async submitPw () {
|
async submitPw() {
|
||||||
this.validate()
|
this.validate()
|
||||||
if (this.password !== this.passwordVer) {
|
if (this.password !== this.passwordVer) {
|
||||||
this.verError = '*passwords do not match'
|
this.verError = '*passwords do not match'
|
||||||
@@ -50,8 +55,8 @@ export class PasswordPage {
|
|||||||
this.modalController.dismiss({ password: this.password }, 'success')
|
this.modalController.dismiss({ password: this.password }, 'success')
|
||||||
}
|
}
|
||||||
|
|
||||||
validate () {
|
validate() {
|
||||||
if (!!this.target) return this.pwError = ''
|
if (!!this.target) return (this.pwError = '')
|
||||||
|
|
||||||
if (this.passwordVer) {
|
if (this.passwordVer) {
|
||||||
this.checkVer()
|
this.checkVer()
|
||||||
@@ -64,11 +69,12 @@ export class PasswordPage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkVer () {
|
checkVer() {
|
||||||
this.verError = this.password !== this.passwordVer ? 'Passwords do not match' : ''
|
this.verError =
|
||||||
|
this.password !== this.passwordVer ? 'Passwords do not match' : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel () {
|
cancel() {
|
||||||
this.modalController.dismiss()
|
this.modalController.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,19 +16,19 @@ export class ProdKeyModal {
|
|||||||
productKey = ''
|
productKey = ''
|
||||||
unmasked = false
|
unmasked = false
|
||||||
|
|
||||||
constructor (
|
constructor(
|
||||||
private readonly modalController: ModalController,
|
private readonly modalController: ModalController,
|
||||||
private readonly apiService: ApiService,
|
private readonly apiService: ApiService,
|
||||||
private readonly loadingCtrl: LoadingController,
|
private readonly loadingCtrl: LoadingController,
|
||||||
private readonly httpService: HttpService,
|
private readonly httpService: HttpService,
|
||||||
) { }
|
) {}
|
||||||
|
|
||||||
ngAfterViewInit () {
|
ngAfterViewInit() {
|
||||||
setTimeout(() => this.elem.setFocus(), 400)
|
setTimeout(() => this.elem.setFocus(), 400)
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyProductKey () {
|
async verifyProductKey() {
|
||||||
if (!this.productKey) return
|
if (!this.productKey || !this.target.logicalname) return
|
||||||
|
|
||||||
const loader = await this.loadingCtrl.create({
|
const loader = await this.loadingCtrl.create({
|
||||||
message: 'Verifying Product Key',
|
message: 'Verifying Product Key',
|
||||||
@@ -48,7 +48,7 @@ export class ProdKeyModal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel () {
|
cancel() {
|
||||||
this.modalController.dismiss()
|
this.modalController.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,8 +68,8 @@ export class RecoverPage {
|
|||||||
'embassy-os': p['embassy-os'],
|
'embassy-os': p['embassy-os'],
|
||||||
}
|
}
|
||||||
this.mappedDrives.push({
|
this.mappedDrives.push({
|
||||||
hasValidBackup: p['embassy-os']?.full,
|
hasValidBackup: !!p['embassy-os']?.full,
|
||||||
is02x: drive['embassy-os']?.version.startsWith('0.2'),
|
is02x: !!drive['embassy-os']?.version.startsWith('0.2'),
|
||||||
drive,
|
drive,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -111,7 +111,8 @@ export class RecoverPage {
|
|||||||
{
|
{
|
||||||
text: 'Use Drive',
|
text: 'Use Drive',
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
await this.importDrive(importableDrive.guid)
|
if (importableDrive.guid)
|
||||||
|
await this.importDrive(importableDrive.guid)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -148,11 +149,14 @@ export class RecoverPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async select(target: DiskBackupTarget) {
|
async select(target: DiskBackupTarget) {
|
||||||
const is02x = target['embassy-os'].version.startsWith('0.2')
|
const is02x = target['embassy-os']?.version.startsWith('0.2')
|
||||||
|
const { logicalname } = target
|
||||||
|
|
||||||
|
if (!logicalname) return
|
||||||
|
|
||||||
if (this.stateService.hasProductKey) {
|
if (this.stateService.hasProductKey) {
|
||||||
if (is02x) {
|
if (is02x) {
|
||||||
this.selectRecoverySource(target.logicalname)
|
this.selectRecoverySource(logicalname)
|
||||||
} else {
|
} else {
|
||||||
const modal = await this.modalController.create({
|
const modal = await this.modalController.create({
|
||||||
component: PasswordPage,
|
component: PasswordPage,
|
||||||
@@ -160,8 +164,8 @@ export class RecoverPage {
|
|||||||
cssClass: 'alertlike-modal',
|
cssClass: 'alertlike-modal',
|
||||||
})
|
})
|
||||||
modal.onDidDismiss().then(res => {
|
modal.onDidDismiss().then(res => {
|
||||||
if (res.data && res.data.password) {
|
if (res.data?.password) {
|
||||||
this.selectRecoverySource(target.logicalname, res.data.password)
|
this.selectRecoverySource(logicalname, res.data.password)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
await modal.present()
|
await modal.present()
|
||||||
@@ -188,8 +192,8 @@ export class RecoverPage {
|
|||||||
cssClass: 'alertlike-modal',
|
cssClass: 'alertlike-modal',
|
||||||
})
|
})
|
||||||
modal.onDidDismiss().then(res => {
|
modal.onDidDismiss().then(res => {
|
||||||
if (res.data && res.data.productKey) {
|
if (res.data?.productKey) {
|
||||||
this.selectRecoverySource(target.logicalname)
|
this.selectRecoverySource(logicalname)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
await modal.present()
|
await modal.present()
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export class SuccessPage {
|
|||||||
await this.stateService.completeEmbassy()
|
await this.stateService.completeEmbassy()
|
||||||
document
|
document
|
||||||
.getElementById('install-cert')
|
.getElementById('install-cert')
|
||||||
.setAttribute(
|
?.setAttribute(
|
||||||
'href',
|
'href',
|
||||||
'data:application/x-x509-ca-cert;base64,' +
|
'data:application/x-x509-ca-cert;base64,' +
|
||||||
encodeURIComponent(this.stateService.cert),
|
encodeURIComponent(this.stateService.cert),
|
||||||
@@ -56,20 +56,24 @@ export class SuccessPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
installCert() {
|
installCert() {
|
||||||
document.getElementById('install-cert').click()
|
document.getElementById('install-cert')?.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
download() {
|
download() {
|
||||||
document.getElementById('tor-addr').innerHTML = this.stateService.torAddress
|
const torAddress = document.getElementById('tor-addr')
|
||||||
document.getElementById('lan-addr').innerHTML = this.stateService.lanAddress
|
const lanAddress = document.getElementById('lan-addr')
|
||||||
|
|
||||||
|
if (torAddress) torAddress.innerHTML = this.stateService.torAddress
|
||||||
|
if (lanAddress) lanAddress.innerHTML = this.stateService.lanAddress
|
||||||
|
|
||||||
document
|
document
|
||||||
.getElementById('cert')
|
.getElementById('cert')
|
||||||
.setAttribute(
|
?.setAttribute(
|
||||||
'href',
|
'href',
|
||||||
'data:application/x-x509-ca-cert;base64,' +
|
'data:application/x-x509-ca-cert;base64,' +
|
||||||
encodeURIComponent(this.stateService.cert),
|
encodeURIComponent(this.stateService.cert),
|
||||||
)
|
)
|
||||||
let html = document.getElementById('downloadable').innerHTML
|
let html = document.getElementById('downloadable')?.innerHTML || ''
|
||||||
const filename = 'embassy-info.html'
|
const filename = 'embassy-info.html'
|
||||||
|
|
||||||
const elem = document.createElement('a')
|
const elem = document.createElement('a')
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { HttpError, RpcError } from '@start9labs/shared'
|
|||||||
})
|
})
|
||||||
export class HttpService {
|
export class HttpService {
|
||||||
fullUrl: string
|
fullUrl: string
|
||||||
productKey: string
|
productKey?: string
|
||||||
|
|
||||||
constructor(private readonly http: HttpClient) {
|
constructor(private readonly http: HttpClient) {
|
||||||
const port = window.location.port
|
const port = window.location.port
|
||||||
@@ -43,6 +43,8 @@ export class HttpService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isRpcSuccess(res)) return res.result
|
if (isRpcSuccess(res)) return res.result
|
||||||
|
|
||||||
|
throw new Error('Unknown RPC response')
|
||||||
}
|
}
|
||||||
|
|
||||||
async encryptedHttpRequest<T>(httpOpts: {
|
async encryptedHttpRequest<T>(httpOpts: {
|
||||||
@@ -53,7 +55,7 @@ export class HttpService {
|
|||||||
const url = urlIsRelative ? this.fullUrl + httpOpts.url : httpOpts.url
|
const url = urlIsRelative ? this.fullUrl + httpOpts.url : httpOpts.url
|
||||||
|
|
||||||
const encryptedBody = await AES_CTR.encryptPbkdf2(
|
const encryptedBody = await AES_CTR.encryptPbkdf2(
|
||||||
this.productKey,
|
this.productKey || '',
|
||||||
encodeUtf8(JSON.stringify(httpOpts.body)),
|
encodeUtf8(JSON.stringify(httpOpts.body)),
|
||||||
)
|
)
|
||||||
const options = {
|
const options = {
|
||||||
@@ -74,7 +76,7 @@ export class HttpService {
|
|||||||
.toPromise()
|
.toPromise()
|
||||||
.then(res =>
|
.then(res =>
|
||||||
AES_CTR.decryptPbkdf2(
|
AES_CTR.decryptPbkdf2(
|
||||||
this.productKey,
|
this.productKey || '',
|
||||||
(res as any).body as ArrayBuffer,
|
(res as any).body as ArrayBuffer,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -206,7 +208,7 @@ type AES_CTR = {
|
|||||||
secretKey: string,
|
secretKey: string,
|
||||||
messageBuffer: Uint8Array,
|
messageBuffer: Uint8Array,
|
||||||
) => Promise<Uint8Array>
|
) => Promise<Uint8Array>
|
||||||
decryptPbkdf2: (secretKey, arr: ArrayBuffer) => Promise<string>
|
decryptPbkdf2: (secretKey: string, arr: ArrayBuffer) => Promise<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AES_CTR: AES_CTR = {
|
export const AES_CTR: AES_CTR = {
|
||||||
@@ -243,8 +245,10 @@ export const AES_CTR: AES_CTR = {
|
|||||||
|
|
||||||
export const encode16 = (buffer: Uint8Array) =>
|
export const encode16 = (buffer: Uint8Array) =>
|
||||||
buffer.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '')
|
buffer.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '')
|
||||||
export const decode16 = hexString =>
|
export const decode16 = (hexString: string) =>
|
||||||
new Uint8Array(hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16)))
|
new Uint8Array(
|
||||||
|
hexString.match(/.{1,2}/g)?.map(byte => parseInt(byte, 16)) || [],
|
||||||
|
)
|
||||||
|
|
||||||
export function encodeUtf8(str: string): Uint8Array {
|
export function encodeUtf8(str: string): Uint8Array {
|
||||||
const encoder = new TextEncoder()
|
const encoder = new TextEncoder()
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export class LiveApiService extends ApiService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async set02XDrive(logicalname) {
|
async set02XDrive(logicalname: string) {
|
||||||
return this.http.rpcRequest<void>(
|
return this.http.rpcRequest<void>(
|
||||||
{
|
{
|
||||||
method: 'setup.recovery.v2.set',
|
method: 'setup.recovery.v2.set',
|
||||||
@@ -124,7 +124,7 @@ export class LiveApiService extends ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isCifsSource(
|
function isCifsSource(
|
||||||
source: CifsRecoverySource | DiskRecoverySource | undefined,
|
source: CifsRecoverySource | DiskRecoverySource | null,
|
||||||
): source is CifsRecoverySource {
|
): source is CifsRecoverySource {
|
||||||
return !!(source as CifsRecoverySource)?.hostname
|
return !!(source as CifsRecoverySource)?.hostname
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export class StateService {
|
|||||||
embassyLoaded = false
|
embassyLoaded = false
|
||||||
|
|
||||||
recoverySource: CifsRecoverySource | DiskRecoverySource
|
recoverySource: CifsRecoverySource | DiskRecoverySource
|
||||||
recoveryPassword: string
|
recoveryPassword?: string
|
||||||
|
|
||||||
dataTransferProgress: {
|
dataTransferProgress: {
|
||||||
bytesTransferred: number
|
bytesTransferred: number
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": "^13.2.0",
|
"@angular/common": "^13.2.0",
|
||||||
"@angular/core": "^13.2.0",
|
"@angular/core": "^13.2.0",
|
||||||
|
"@angular/router": "^13.2.0",
|
||||||
"@ionic/angular": "^6.0.3",
|
"@ionic/angular": "^6.0.3",
|
||||||
"@start9labs/emver": "^0.1.5"
|
"@start9labs/emver": "^0.1.5"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export class RpcError<T> {
|
|||||||
return `${this.error.message}\n\n${this.error.data}`
|
return `${this.error.message}\n\n${this.error.data}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.error.data.details
|
return this.error.data?.details
|
||||||
? `${this.error.message}\n\n${this.error.data.details}`
|
? `${this.error.message}\n\n${this.error.data.details}`
|
||||||
: this.error.message
|
: this.error.message
|
||||||
}
|
}
|
||||||
@@ -20,6 +20,6 @@ export class RpcError<T> {
|
|||||||
private getRevision(): T | null {
|
private getRevision(): T | null {
|
||||||
return typeof this.error.data === 'string'
|
return typeof this.error.data === 'string'
|
||||||
? null
|
? null
|
||||||
: this.error.data.revision || null
|
: this.error.data?.revision || null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,12 @@ import { Emver } from '../../services/emver.service'
|
|||||||
export class EmverSatisfiesPipe implements PipeTransform {
|
export class EmverSatisfiesPipe implements PipeTransform {
|
||||||
constructor(private readonly emver: Emver) {}
|
constructor(private readonly emver: Emver) {}
|
||||||
|
|
||||||
transform(versionUnderTest: string, range: string): boolean {
|
transform(versionUnderTest?: string, range?: string): boolean {
|
||||||
return this.emver.satisfies(versionUnderTest, range)
|
return (
|
||||||
|
!!versionUnderTest &&
|
||||||
|
!!range &&
|
||||||
|
this.emver.satisfies(versionUnderTest, range)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,18 +19,17 @@ export class ConvertBytesPipe implements PipeTransform {
|
|||||||
name: 'durationToSeconds',
|
name: 'durationToSeconds',
|
||||||
})
|
})
|
||||||
export class DurationToSecondsPipe implements PipeTransform {
|
export class DurationToSecondsPipe implements PipeTransform {
|
||||||
transform(duration: string | null): number {
|
transform(duration?: string | null): number {
|
||||||
if (!duration) return 0
|
if (!duration) return 0
|
||||||
const splitUnit = duration.match(/^([0-9]*(\.[0-9]+)?)(ns|µs|ms|s|m|d)$/)
|
const [, num, , unit] =
|
||||||
const unit = splitUnit[3]
|
duration.match(/^([0-9]*(\.[0-9]+)?)(ns|µs|ms|s|m|d)$/) || []
|
||||||
const num = splitUnit[1]
|
|
||||||
return Number(num) * unitsToSeconds[unit]
|
return Number(num) * unitsToSeconds[unit]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||||
|
|
||||||
const unitsToSeconds = {
|
const unitsToSeconds: Record<string, number> = {
|
||||||
ns: 1e-9,
|
ns: 1e-9,
|
||||||
µs: 1e-6,
|
µs: 1e-6,
|
||||||
ms: 0.001,
|
ms: 0.001,
|
||||||
|
|||||||
@@ -34,5 +34,6 @@ export * from './types/rpc-error-details'
|
|||||||
export * from './types/url'
|
export * from './types/url'
|
||||||
export * from './types/workspace-config'
|
export * from './types/workspace-config'
|
||||||
|
|
||||||
|
export * from './util/get-pkg-id'
|
||||||
export * from './util/misc.util'
|
export * from './util/misc.util'
|
||||||
export * from './util/unused'
|
export * from './util/unused'
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as emver from '@start9labs/emver'
|
|||||||
export class Emver {
|
export class Emver {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
compare(lhs: string, rhs: string): number {
|
compare(lhs: string, rhs: string): number | null {
|
||||||
if (!lhs || !rhs) return null
|
if (!lhs || !rhs) return null
|
||||||
return emver.compare(lhs, rhs)
|
return emver.compare(lhs, rhs)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { IonicSafeString, ToastController } from '@ionic/angular'
|
|||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class ErrorToastService {
|
export class ErrorToastService {
|
||||||
private toast: HTMLIonToastElement
|
private toast?: HTMLIonToastElement
|
||||||
|
|
||||||
constructor(private readonly toastCtrl: ToastController) {}
|
constructor(private readonly toastCtrl: ToastController) {}
|
||||||
|
|
||||||
|
|||||||
11
frontend/projects/shared/src/util/get-pkg-id.ts
Normal file
11
frontend/projects/shared/src/util/get-pkg-id.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { ActivatedRoute } from '@angular/router'
|
||||||
|
|
||||||
|
export function getPkgId({ snapshot }: ActivatedRoute): string {
|
||||||
|
const pkgId = snapshot.paramMap.get('pkgId')
|
||||||
|
|
||||||
|
if (!pkgId) {
|
||||||
|
throw new Error('pkgId is missing from route params')
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkgId
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ export function debounce(delay: number = 300): MethodDecorator {
|
|||||||
|
|
||||||
const original = descriptor.value
|
const original = descriptor.value
|
||||||
|
|
||||||
descriptor.value = function (...args) {
|
descriptor.value = function (this: any, ...args: any[]) {
|
||||||
clearTimeout(this[timeoutKey])
|
clearTimeout(this[timeoutKey])
|
||||||
this[timeoutKey] = setTimeout(() => original.apply(this, args), delay)
|
this[timeoutKey] = setTimeout(() => original.apply(this, args), delay)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function traceThrowDesc<T>(description: string, t: T | undefined): T {
|
|||||||
export function inMs(
|
export function inMs(
|
||||||
count: number,
|
count: number,
|
||||||
unit: 'days' | 'hours' | 'minutes' | 'seconds',
|
unit: 'days' | 'hours' | 'minutes' | 'seconds',
|
||||||
) {
|
): number {
|
||||||
switch (unit) {
|
switch (unit) {
|
||||||
case 'seconds':
|
case 'seconds':
|
||||||
return count * 1000
|
return count * 1000
|
||||||
@@ -63,31 +63,6 @@ export function toObject<T>(t: T[], map: (t0: T) => string): Record<string, T> {
|
|||||||
}, {} as Record<string, T>)
|
}, {} as Record<string, T>)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deepCloneUnknown<T>(value: T): T {
|
|
||||||
if (typeof value !== 'object' || value === null) {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return deepCloneArray(value)
|
|
||||||
}
|
|
||||||
return deepCloneObject(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deepCloneObject<T>(source: T) {
|
|
||||||
const result = {}
|
|
||||||
Object.keys(source).forEach(key => {
|
|
||||||
const value = source[key]
|
|
||||||
result[key] = deepCloneUnknown(value)
|
|
||||||
}, {})
|
|
||||||
return result as T
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deepCloneArray(collection: any) {
|
|
||||||
return collection.map(value => {
|
|
||||||
return deepCloneUnknown(value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function partitionArray<T>(
|
export function partitionArray<T>(
|
||||||
ts: T[],
|
ts: T[],
|
||||||
condition: (t: T) => boolean,
|
condition: (t: T) => boolean,
|
||||||
@@ -110,21 +85,3 @@ export function update<T>(
|
|||||||
): Record<string, T> {
|
): Record<string, T> {
|
||||||
return { ...t, ...u }
|
return { ...t, ...u }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function uniqueBy<T>(
|
|
||||||
ts: T[],
|
|
||||||
uniqueBy: (t: T) => string,
|
|
||||||
prioritize: (t1: T, t2: T) => T,
|
|
||||||
) {
|
|
||||||
return Object.values(
|
|
||||||
ts.reduce((acc, next) => {
|
|
||||||
const previousValue = acc[uniqueBy(next)]
|
|
||||||
if (previousValue) {
|
|
||||||
acc[uniqueBy(next)] = prioritize(acc[uniqueBy(next)], previousValue)
|
|
||||||
} else {
|
|
||||||
acc[uniqueBy(next)] = previousValue
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
}, {}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export class FooterComponent {
|
|||||||
getProgress({
|
getProgress({
|
||||||
downloaded,
|
downloaded,
|
||||||
size,
|
size,
|
||||||
}: ServerInfo['status-info']['update-progress']): number {
|
}: NonNullable<ServerInfo['status-info']['update-progress']>): number {
|
||||||
return Math.round((100 * (downloaded || 1)) / (size || 1))
|
return Math.round((100 * (downloaded || 1)) / (size || 1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ function getMessage(failure: ConnectionFailure): OfflineMessage {
|
|||||||
message: 'Embassy not found on Local Area Network.',
|
message: 'Embassy not found on Local Area Network.',
|
||||||
link: 'https://start9.com/latest/support/common-issues',
|
link: 'https://start9.com/latest/support/common-issues',
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
return { message: '' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { mapTo, share, switchMap } from 'rxjs/operators'
|
|||||||
|
|
||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||||
import { AuthService } from 'src/app/services/auth.service'
|
import { AuthService } from 'src/app/services/auth.service'
|
||||||
import { ConnectionService } from 'src/app/services/connection.service'
|
|
||||||
|
|
||||||
// Start and stop PatchDb upon verification
|
// Start and stop PatchDb upon verification
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@@ -27,7 +26,6 @@ export class PatchMonitorService extends Observable<boolean> {
|
|||||||
)
|
)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly connectionService: ConnectionService,
|
|
||||||
private readonly authService: AuthService,
|
private readonly authService: AuthService,
|
||||||
private readonly patch: PatchDbService,
|
private readonly patch: PatchDbService,
|
||||||
private readonly storage: Storage,
|
private readonly storage: Storage,
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export class UnreadToastService extends Observable<unknown> {
|
|||||||
await this.unreadToast?.dismiss()
|
await this.unreadToast?.dismiss()
|
||||||
|
|
||||||
this.unreadToast = await this.toastCtrl.create(TOAST)
|
this.unreadToast = await this.toastCtrl.create(TOAST)
|
||||||
this.unreadToast.buttons.push({
|
this.unreadToast.buttons?.push({
|
||||||
side: 'end',
|
side: 'end',
|
||||||
text: 'View',
|
text: 'View',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export class UpdateToastService extends Observable<unknown> {
|
|||||||
await this.updateToast?.dismiss()
|
await this.updateToast?.dismiss()
|
||||||
|
|
||||||
this.updateToast = await this.toastCtrl.create(TOAST)
|
this.updateToast = await this.toastCtrl.create(TOAST)
|
||||||
this.updateToast.buttons.push({
|
this.updateToast.buttons?.push({
|
||||||
side: 'end',
|
side: 'end',
|
||||||
text: 'Restart',
|
text: 'Restart',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
|
|||||||
@@ -26,13 +26,15 @@
|
|||||||
{{ page.title }}
|
{{ page.title }}
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
*ngIf="page.url === '/embassy' && eosService.updateAvailable$ | async"
|
*ngIf="page.url === '/embassy' && (eosService.updateAvailable$ | async)"
|
||||||
color="success"
|
color="success"
|
||||||
size="small"
|
size="small"
|
||||||
name="rocket-outline"
|
name="rocket-outline"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
<ion-badge
|
<ion-badge
|
||||||
*ngIf="page.url === '/notifications' && notification$ | async as count"
|
*ngIf="
|
||||||
|
page.url === '/notifications' && (notification$ | async) as count
|
||||||
|
"
|
||||||
color="danger"
|
color="danger"
|
||||||
class="badge"
|
class="badge"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
<div [hidden]="!control.dirty && !control.touched" class="validation-error">
|
<div [hidden]="!control.dirty && !control.touched" class="validation-error">
|
||||||
<!-- primitive -->
|
<!-- primitive -->
|
||||||
<p *ngIf="control.hasError('required')">
|
<p *ngIf="control.hasError('required')">{{ spec.name }} is required</p>
|
||||||
{{ spec.name }} is required
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- string -->
|
<!-- string -->
|
||||||
<p *ngIf="control.hasError('pattern')">
|
<p *ngIf="control.hasError('pattern')">
|
||||||
{{ spec['pattern-description'] }}
|
{{ $any(spec)['pattern-description'] }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- number -->
|
<!-- number -->
|
||||||
@@ -15,7 +13,7 @@
|
|||||||
{{ spec.name }} must be an integer
|
{{ spec.name }} must be an integer
|
||||||
</p>
|
</p>
|
||||||
<p *ngIf="control.hasError('numberNotInRange')">
|
<p *ngIf="control.hasError('numberNotInRange')">
|
||||||
{{ control.errors['numberNotInRange'].value }}
|
{{ control.errors?.['numberNotInRange']?.value }}
|
||||||
</p>
|
</p>
|
||||||
<p *ngIf="control.hasError('notNumber')">
|
<p *ngIf="control.hasError('notNumber')">
|
||||||
{{ spec.name }} must be a number
|
{{ spec.name }} must be a number
|
||||||
@@ -25,13 +23,13 @@
|
|||||||
<!-- list -->
|
<!-- list -->
|
||||||
<ng-container *ngIf="spec.type === 'list'">
|
<ng-container *ngIf="spec.type === 'list'">
|
||||||
<p *ngIf="control.hasError('listNotInRange')">
|
<p *ngIf="control.hasError('listNotInRange')">
|
||||||
{{ control.errors['listNotInRange'].value }}
|
{{ control.errors?.['listNotInRange']?.value }}
|
||||||
</p>
|
</p>
|
||||||
<p *ngIf="control.hasError('listNotUnique')">
|
<p *ngIf="control.hasError('listNotUnique')">
|
||||||
{{ control.errors['listNotUnique'].value }}
|
{{ control.errors?.['listNotUnique']?.value }}
|
||||||
</p>
|
</p>
|
||||||
<p *ngIf="control.hasError('listItemIssue')">
|
<p *ngIf="control.hasError('listItemIssue')">
|
||||||
{{ control.errors['listItemIssue'].value }}
|
{{ control.errors?.['listItemIssue']?.value }}
|
||||||
</p>
|
</p>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
<form-label
|
<form-label
|
||||||
[data]="{
|
[data]="{
|
||||||
spec: spec,
|
spec: spec,
|
||||||
new: current && current[entry.key] === undefined,
|
new: !!current && current[entry.key] === undefined,
|
||||||
edited: entry.value.dirty
|
edited: entry.value.dirty
|
||||||
}"
|
}"
|
||||||
></form-label>
|
></form-label>
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
<!-- enum -->
|
<!-- enum -->
|
||||||
<!-- class enter-click disables the enter click on the modal behind the select -->
|
<!-- class enter-click disables the enter click on the modal behind the select -->
|
||||||
<ion-select
|
<ion-select
|
||||||
*ngIf="spec.type === 'enum'"
|
*ngIf="spec.type === 'enum' && formGroup.get(entry.key) as control"
|
||||||
[interfaceOptions]="{
|
[interfaceOptions]="{
|
||||||
message: getWarningText(spec.warning),
|
message: getWarningText(spec.warning),
|
||||||
cssClass: 'enter-click'
|
cssClass: 'enter-click'
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
slot="end"
|
slot="end"
|
||||||
placeholder="Select"
|
placeholder="Select"
|
||||||
[formControlName]="entry.key"
|
[formControlName]="entry.key"
|
||||||
[selectedText]="spec['value-names'][formGroup.get(entry.key).value]"
|
[selectedText]="spec['value-names'][control.value]"
|
||||||
>
|
>
|
||||||
<ion-select-option
|
<ion-select-option
|
||||||
*ngFor="let option of spec.values"
|
*ngFor="let option of spec.values"
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
<form-label
|
<form-label
|
||||||
[data]="{
|
[data]="{
|
||||||
spec: spec,
|
spec: spec,
|
||||||
new: current && current[entry.key] === undefined,
|
new: !!current && current[entry.key] === undefined,
|
||||||
edited: entry.value.dirty
|
edited: entry.value.dirty
|
||||||
}"
|
}"
|
||||||
></form-label>
|
></form-label>
|
||||||
@@ -208,7 +208,7 @@
|
|||||||
<form-label
|
<form-label
|
||||||
[data]="{
|
[data]="{
|
||||||
spec: spec,
|
spec: spec,
|
||||||
new: current && current[entry.key] === undefined,
|
new: !!current && current[entry.key] === undefined,
|
||||||
edited: entry.value.dirty
|
edited: entry.value.dirty
|
||||||
}"
|
}"
|
||||||
></form-label>
|
></form-label>
|
||||||
@@ -281,16 +281,12 @@
|
|||||||
: $any(spec.spec).spec
|
: $any(spec.spec).spec
|
||||||
"
|
"
|
||||||
[formGroup]="abstractControl"
|
[formGroup]="abstractControl"
|
||||||
[current]="
|
[current]="current?.[entry.key]?.[i]"
|
||||||
current && current[entry.key]
|
|
||||||
? current[entry.key][i]
|
|
||||||
: undefined
|
|
||||||
"
|
|
||||||
[unionSpec]="
|
[unionSpec]="
|
||||||
spec.subtype === 'union' ? $any(spec.spec) : undefined
|
spec.subtype === 'union' ? $any(spec.spec) : undefined
|
||||||
"
|
"
|
||||||
(onInputChange)="
|
(onInputChange)="
|
||||||
updateLabel(entry.key, i, spec.spec['display-as'])
|
updateLabel(entry.key, i, $any(spec.spec)['display-as'])
|
||||||
"
|
"
|
||||||
(onExpand)="resize(entry.key, i)"
|
(onExpand)="resize(entry.key, i)"
|
||||||
></form-object>
|
></form-object>
|
||||||
@@ -350,7 +346,7 @@
|
|||||||
<form-label
|
<form-label
|
||||||
[data]="{
|
[data]="{
|
||||||
spec: spec,
|
spec: spec,
|
||||||
new: current && current[entry.key] === undefined,
|
new: !!current && current[entry.key] === undefined,
|
||||||
edited: entry.value.dirty
|
edited: entry.value.dirty
|
||||||
}"
|
}"
|
||||||
></form-label>
|
></form-label>
|
||||||
@@ -372,7 +368,7 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<form-error
|
<form-error
|
||||||
*ngIf="formGroup.get(entry.key).errors"
|
*ngIf="formGroup.get(entry.key)?.errors"
|
||||||
[control]="$any(formGroup.get(entry.key))"
|
[control]="$any(formGroup.get(entry.key))"
|
||||||
[spec]="spec"
|
[spec]="spec"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ const Mustache = require('mustache')
|
|||||||
export class FormObjectComponent {
|
export class FormObjectComponent {
|
||||||
@Input() objectSpec: ConfigSpec
|
@Input() objectSpec: ConfigSpec
|
||||||
@Input() formGroup: FormGroup
|
@Input() formGroup: FormGroup
|
||||||
@Input() unionSpec: ValueSpecUnion
|
@Input() unionSpec?: ValueSpecUnion
|
||||||
@Input() current: { [key: string]: any }
|
@Input() current?: { [key: string]: any }
|
||||||
@Input() showEdited: boolean = false
|
@Input() showEdited: boolean = false
|
||||||
@Output() onInputChange = new EventEmitter<void>()
|
@Output() onInputChange = new EventEmitter<void>()
|
||||||
@Output() onExpand = new EventEmitter<void>()
|
@Output() onExpand = new EventEmitter<void>()
|
||||||
@@ -61,7 +61,7 @@ export class FormObjectComponent {
|
|||||||
|
|
||||||
if (spec.type === 'list' && ['object', 'union'].includes(spec.subtype)) {
|
if (spec.type === 'list' && ['object', 'union'].includes(spec.subtype)) {
|
||||||
this.objectListDisplay[key] = []
|
this.objectListDisplay[key] = []
|
||||||
this.formGroup.get(key).value.forEach((obj, index) => {
|
this.formGroup.get(key)?.value.forEach((obj: any, index: number) => {
|
||||||
const displayAs = (spec.spec as ListValueSpecOf<'object'>)[
|
const displayAs = (spec.spec as ListValueSpecOf<'object'>)[
|
||||||
'display-as'
|
'display-as'
|
||||||
]
|
]
|
||||||
@@ -87,7 +87,7 @@ export class FormObjectComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateUnion(e: any): void {
|
updateUnion(e: any): void {
|
||||||
const primary = this.unionSpec.tag.id
|
const primary = this.unionSpec?.tag.id
|
||||||
|
|
||||||
Object.keys(this.formGroup.controls).forEach(control => {
|
Object.keys(this.formGroup.controls).forEach(control => {
|
||||||
if (control === primary) return
|
if (control === primary) return
|
||||||
@@ -104,7 +104,7 @@ export class FormObjectComponent {
|
|||||||
this.formGroup.addControl(control, unionGroup.controls[control])
|
this.formGroup.addControl(control, unionGroup.controls[control])
|
||||||
})
|
})
|
||||||
|
|
||||||
Object.entries(this.unionSpec.variants[e.detail.value]).forEach(
|
Object.entries(this.unionSpec?.variants[e.detail.value] || {}).forEach(
|
||||||
([key, value]) => {
|
([key, value]) => {
|
||||||
if (['object', 'union'].includes(value.type)) {
|
if (['object', 'union'].includes(value.type)) {
|
||||||
this.objectDisplay[key] = {
|
this.objectDisplay[key] = {
|
||||||
@@ -138,6 +138,9 @@ export class FormObjectComponent {
|
|||||||
if (markDirty) arr.markAsDirty()
|
if (markDirty) arr.markAsDirty()
|
||||||
const listSpec = this.objectSpec[key] as ValueSpecList
|
const listSpec = this.objectSpec[key] as ValueSpecList
|
||||||
const newItem = this.formService.getListItem(listSpec, val)
|
const newItem = this.formService.getListItem(listSpec, val)
|
||||||
|
|
||||||
|
if (!newItem) return
|
||||||
|
|
||||||
newItem.markAllAsTouched()
|
newItem.markAllAsTouched()
|
||||||
arr.insert(0, newItem)
|
arr.insert(0, newItem)
|
||||||
if (['object', 'union'].includes(listSpec.subtype)) {
|
if (['object', 'union'].includes(listSpec.subtype)) {
|
||||||
@@ -177,13 +180,14 @@ export class FormObjectComponent {
|
|||||||
|
|
||||||
updateLabel(key: string, i: number, displayAs: string) {
|
updateLabel(key: string, i: number, displayAs: string) {
|
||||||
this.objectListDisplay[key][i].displayAs = displayAs
|
this.objectListDisplay[key][i].displayAs = displayAs
|
||||||
? Mustache.render(displayAs, this.formGroup.get(key).value[i])
|
? Mustache.render(displayAs, this.formGroup.get(key)?.value[i])
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
getWarningText(text: string): IonicSafeString {
|
getWarningText(text: string = ''): IonicSafeString | string {
|
||||||
if (text)
|
return text
|
||||||
return new IonicSafeString(`<ion-text color="warning">${text}</ion-text>`)
|
? new IonicSafeString(`<ion-text color="warning">${text}</ion-text>`)
|
||||||
|
: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInputChange() {
|
handleInputChange() {
|
||||||
@@ -192,8 +196,8 @@ export class FormObjectComponent {
|
|||||||
|
|
||||||
handleBooleanChange(key: string, spec: ValueSpecBoolean) {
|
handleBooleanChange(key: string, spec: ValueSpecBoolean) {
|
||||||
if (spec.warning) {
|
if (spec.warning) {
|
||||||
const current = this.formGroup.get(key).value
|
const current = this.formGroup.get(key)?.value
|
||||||
const cancelFn = () => this.formGroup.get(key).setValue(!current)
|
const cancelFn = () => this.formGroup.get(key)?.setValue(!current)
|
||||||
this.presentAlertChangeWarning(key, spec, undefined, cancelFn)
|
this.presentAlertChangeWarning(key, spec, undefined, cancelFn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -307,7 +311,7 @@ export class FormObjectComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateEnumList(key: string, current: string[], updated: string[]) {
|
private updateEnumList(key: string, current: string[], updated: string[]) {
|
||||||
this.formGroup.get(key).markAsDirty()
|
this.formGroup.get(key)?.markAsDirty()
|
||||||
|
|
||||||
for (let i = current.length - 1; i >= 0; i--) {
|
for (let i = current.length - 1; i >= 0; i--) {
|
||||||
if (!updated.includes(current[i])) {
|
if (!updated.includes(current[i])) {
|
||||||
@@ -322,9 +326,9 @@ export class FormObjectComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDocSize(key: string, index = 0) {
|
private getDocSize(key: string, index = 0): string {
|
||||||
const element = document.getElementById(this.getElementId(key, index))
|
const element = document.getElementById(this.getElementId(key, index))
|
||||||
return `${element.scrollHeight}px`
|
return `${element?.scrollHeight}px`
|
||||||
}
|
}
|
||||||
|
|
||||||
getElementId(key: string, index = 0): string {
|
getElementId(key: string, index = 0): string {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
{{ dependentViolation }}
|
{{ dependentViolation }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="patch.data['package-data']" class="items">
|
<div *ngIf="pkgs$ | async as pkgs" class="items">
|
||||||
<div class="affected">
|
<div class="affected">
|
||||||
<ion-text color="warning">Affected Services</ion-text>
|
<ion-text color="warning">Affected Services</ion-text>
|
||||||
</div>
|
</div>
|
||||||
@@ -26,13 +26,10 @@
|
|||||||
*ngFor="let dep of dependentBreakages | keyvalue"
|
*ngFor="let dep of dependentBreakages | keyvalue"
|
||||||
>
|
>
|
||||||
<ion-thumbnail class="thumbnail" slot="start">
|
<ion-thumbnail class="thumbnail" slot="start">
|
||||||
<img
|
<img alt="" [src]="pkgs[dep.key]['static-files'].icon" />
|
||||||
alt=""
|
|
||||||
[src]="patch.data['package-data'][dep.key]['static-files'].icon"
|
|
||||||
/>
|
|
||||||
</ion-thumbnail>
|
</ion-thumbnail>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h5>{{ patch.data['package-data'][dep.key].manifest.title }}</h5>
|
<h5>{{ pkgs[dep.key].manifest.title }}</h5>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ export class DependentsComponent {
|
|||||||
loading$ = new BehaviorSubject(false)
|
loading$ = new BehaviorSubject(false)
|
||||||
cancel$ = new Subject<void>()
|
cancel$ = new Subject<void>()
|
||||||
|
|
||||||
|
readonly pkgs$ = this.patch.watch$('package-data')
|
||||||
|
|
||||||
constructor(public readonly patch: PatchDbService) {}
|
constructor(public readonly patch: PatchDbService) {}
|
||||||
|
|
||||||
load() {
|
load() {
|
||||||
@@ -45,6 +47,7 @@ export class DependentsComponent {
|
|||||||
)
|
)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
complete: () => {
|
complete: () => {
|
||||||
|
console.log('DEP BREAKS, ', this.dependentBreakages)
|
||||||
if (
|
if (
|
||||||
this.dependentBreakages &&
|
this.dependentBreakages &&
|
||||||
!isEmptyObject(this.dependentBreakages)
|
!isEmptyObject(this.dependentBreakages)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export class WizardBaker {
|
|||||||
const action = 'update'
|
const action = 'update'
|
||||||
const toolbar: TopbarParams = { action, title, version }
|
const toolbar: TopbarParams = { action, title, version }
|
||||||
|
|
||||||
const slideDefinitions: SlideDefinition[] = [
|
const slideDefinitions: Array<SlideDefinition | undefined> = [
|
||||||
installAlert
|
installAlert
|
||||||
? {
|
? {
|
||||||
slide: {
|
slide: {
|
||||||
@@ -170,7 +170,7 @@ export class WizardBaker {
|
|||||||
const action = 'downgrade'
|
const action = 'downgrade'
|
||||||
const toolbar: TopbarParams = { action, title, version }
|
const toolbar: TopbarParams = { action, title, version }
|
||||||
|
|
||||||
const slideDefinitions: SlideDefinition[] = [
|
const slideDefinitions: Array<SlideDefinition | undefined> = [
|
||||||
installAlert
|
installAlert
|
||||||
? {
|
? {
|
||||||
slide: {
|
slide: {
|
||||||
|
|||||||
@@ -66,19 +66,20 @@ export class LogsPage {
|
|||||||
try {
|
try {
|
||||||
// get logs
|
// get logs
|
||||||
const logs = await this.fetch()
|
const logs = await this.fetch()
|
||||||
if (!logs.length) return
|
if (!logs?.length) return
|
||||||
|
|
||||||
const container = document.getElementById('container')
|
const container = document.getElementById('container')
|
||||||
const beforeContainerHeight = container.scrollHeight
|
const beforeContainerHeight = container?.scrollHeight || 0
|
||||||
const newLogs = document
|
const newLogs = document.getElementById('template')?.cloneNode(true)
|
||||||
.getElementById('template')
|
|
||||||
.cloneNode(true) as HTMLElement
|
if (!(newLogs instanceof HTMLElement)) return
|
||||||
|
|
||||||
newLogs.innerHTML =
|
newLogs.innerHTML =
|
||||||
logs
|
logs
|
||||||
.map(l => `${l.timestamp} ${convert.toHtml(l.message)}`)
|
.map(l => `${l.timestamp} ${convert.toHtml(l.message)}`)
|
||||||
.join('\n') + (logs.length ? '\n' : '')
|
.join('\n') + (logs.length ? '\n' : '')
|
||||||
container.prepend(newLogs)
|
container?.prepend(newLogs)
|
||||||
const afterContainerHeight = container.scrollHeight
|
const afterContainerHeight = container?.scrollHeight || 0
|
||||||
|
|
||||||
// scroll down
|
// scroll down
|
||||||
scrollBy(0, afterContainerHeight - beforeContainerHeight)
|
scrollBy(0, afterContainerHeight - beforeContainerHeight)
|
||||||
@@ -97,17 +98,18 @@ export class LogsPage {
|
|||||||
try {
|
try {
|
||||||
this.loadingMore = true
|
this.loadingMore = true
|
||||||
const logs = await this.fetch(false)
|
const logs = await this.fetch(false)
|
||||||
if (!logs.length) return (this.loadingMore = false)
|
if (!logs?.length) return (this.loadingMore = false)
|
||||||
|
|
||||||
const container = document.getElementById('container')
|
const container = document.getElementById('container')
|
||||||
const newLogs = document
|
const newLogs = document.getElementById('template')?.cloneNode(true)
|
||||||
.getElementById('template')
|
|
||||||
.cloneNode(true) as HTMLElement
|
if (!(newLogs instanceof HTMLElement)) return
|
||||||
|
|
||||||
newLogs.innerHTML =
|
newLogs.innerHTML =
|
||||||
logs
|
logs
|
||||||
.map(l => `${l.timestamp} ${convert.toHtml(l.message)}`)
|
.map(l => `${l.timestamp} ${convert.toHtml(l.message)}`)
|
||||||
.join('\n') + (logs.length ? '\n' : '')
|
.join('\n') + (logs.length ? '\n' : '')
|
||||||
container.append(newLogs)
|
container?.append(newLogs)
|
||||||
this.loadingMore = false
|
this.loadingMore = false
|
||||||
this.scrollEvent()
|
this.scrollEvent()
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
@@ -116,7 +118,7 @@ export class LogsPage {
|
|||||||
scrollEvent() {
|
scrollEvent() {
|
||||||
const buttonDiv = document.getElementById('button-div')
|
const buttonDiv = document.getElementById('button-div')
|
||||||
this.isOnBottom =
|
this.isOnBottom =
|
||||||
buttonDiv && buttonDiv.getBoundingClientRect().top < window.innerHeight
|
!!buttonDiv && buttonDiv.getBoundingClientRect().top < window.innerHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToBottom() {
|
scrollToBottom() {
|
||||||
|
|||||||
@@ -20,5 +20,5 @@ export class StatusComponent {
|
|||||||
@Input() weight?: string = 'normal'
|
@Input() weight?: string = 'normal'
|
||||||
@Input() disconnected?: boolean = false
|
@Input() disconnected?: boolean = false
|
||||||
@Input() installProgress?: number
|
@Input() installProgress?: number
|
||||||
@Input() sigtermTimeout?: string
|
@Input() sigtermTimeout?: string | null = null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
<ng-template #noError>
|
<ng-template #noError>
|
||||||
<ion-item
|
<ion-item
|
||||||
*ngIf="hasConfig && !pkg.installed.status.configured && !configForm.dirty"
|
*ngIf="hasConfig && !pkg.installed?.status?.configured && !configForm.dirty"
|
||||||
>
|
>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<ion-text color="success"
|
<ion-text color="success"
|
||||||
|
|||||||
@@ -62,11 +62,12 @@ export class AppConfigPage {
|
|||||||
|
|
||||||
if (!this.hasConfig) return
|
if (!this.hasConfig) return
|
||||||
|
|
||||||
|
let oldConfig: object
|
||||||
|
let newConfig: object | undefined
|
||||||
|
let spec: ConfigSpec
|
||||||
|
let patch: Operation[] | undefined
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let oldConfig: object
|
|
||||||
let newConfig: object
|
|
||||||
let spec: ConfigSpec
|
|
||||||
let patch: Operation[]
|
|
||||||
if (this.dependentInfo) {
|
if (this.dependentInfo) {
|
||||||
this.loadingText = `Setting properties to accommodate ${this.dependentInfo.title}`
|
this.loadingText = `Setting properties to accommodate ${this.dependentInfo.title}`
|
||||||
const {
|
const {
|
||||||
@@ -133,7 +134,7 @@ export class AppConfigPage {
|
|||||||
if (this.configForm.invalid) {
|
if (this.configForm.invalid) {
|
||||||
document
|
document
|
||||||
.getElementsByClassName('validation-error')[0]
|
.getElementsByClassName('validation-error')[0]
|
||||||
?.parentElement.parentElement.scrollIntoView({ behavior: 'smooth' })
|
?.parentElement?.parentElement?.scrollIntoView({ behavior: 'smooth' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +154,7 @@ export class AppConfigPage {
|
|||||||
config,
|
config,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!isEmptyObject(breakages['length'])) {
|
if (!isEmptyObject(breakages['length']) && this.pkg) {
|
||||||
const { cancelled } = await wizardModal(
|
const { cancelled } = await wizardModal(
|
||||||
this.modalCtrl,
|
this.modalCtrl,
|
||||||
this.wizardBaker.configure({
|
this.wizardBaker.configure({
|
||||||
@@ -247,11 +248,11 @@ export class AppConfigPage {
|
|||||||
return isNaN(num) ? node : num
|
return isNaN(num) ? node : num
|
||||||
})
|
})
|
||||||
|
|
||||||
if (op.op !== 'remove') this.configForm.get(arrPath).markAsDirty()
|
if (op.op !== 'remove') this.configForm.get(arrPath)?.markAsDirty()
|
||||||
|
|
||||||
if (typeof arrPath[arrPath.length - 1] === 'number') {
|
if (typeof arrPath[arrPath.length - 1] === 'number') {
|
||||||
const prevPath = arrPath.slice(0, arrPath.length - 1)
|
const prevPath = arrPath.slice(0, arrPath.length - 1)
|
||||||
this.configForm.get(prevPath).markAsDirty()
|
this.configForm.get(prevPath)?.markAsDirty()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export class GenericFormPage {
|
|||||||
this.formGroup.markAllAsTouched()
|
this.formGroup.markAllAsTouched()
|
||||||
document
|
document
|
||||||
.getElementsByClassName('validation-error')[0]
|
.getElementsByClassName('validation-error')[0]
|
||||||
?.parentElement.parentElement.scrollIntoView({ behavior: 'smooth' })
|
?.parentElement?.parentElement?.scrollIntoView({ behavior: 'smooth' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export class GenericInputComponent {
|
|||||||
...this.options,
|
...this.options,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.value = this.options.initialValue
|
this.value = this.options.initialValue || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
|
|||||||
@@ -12,21 +12,21 @@ export class SnakePage {
|
|||||||
speed = 45
|
speed = 45
|
||||||
width = 40
|
width = 40
|
||||||
height = 26
|
height = 26
|
||||||
grid
|
grid = NaN
|
||||||
|
|
||||||
startingLength = 4
|
startingLength = 4
|
||||||
|
|
||||||
score = 0
|
score = 0
|
||||||
highScore = 0
|
highScore = 0
|
||||||
|
|
||||||
xDown: number
|
xDown?: number
|
||||||
yDown: number
|
yDown?: number
|
||||||
canvas: HTMLCanvasElement
|
canvas: HTMLCanvasElement
|
||||||
image: HTMLImageElement
|
image: HTMLImageElement
|
||||||
context
|
context: CanvasRenderingContext2D
|
||||||
|
|
||||||
snake
|
snake: any
|
||||||
bitcoin
|
bitcoin: { x: number; y: number } = { x: NaN, y: NaN }
|
||||||
|
|
||||||
moveQueue: String[] = []
|
moveQueue: String[] = []
|
||||||
|
|
||||||
@@ -37,7 +37,8 @@ export class SnakePage {
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (this.patch.getData().ui.gaming?.snake?.['high-score']) {
|
if (this.patch.getData().ui.gaming?.snake?.['high-score']) {
|
||||||
this.highScore = this.patch.getData().ui.gaming?.snake?.['high-score']
|
this.highScore =
|
||||||
|
this.patch.getData().ui.gaming?.snake?.['high-score'] || 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,8 +61,8 @@ export class SnakePage {
|
|||||||
this.handleTouchMove(e)
|
this.handleTouchMove(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize')
|
||||||
sizeChange(event) {
|
sizeChange() {
|
||||||
this.init()
|
this.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +79,7 @@ export class SnakePage {
|
|||||||
init() {
|
init() {
|
||||||
this.canvas = document.getElementById('game') as HTMLCanvasElement
|
this.canvas = document.getElementById('game') as HTMLCanvasElement
|
||||||
this.canvas.style.border = '1px solid #e0e0e0'
|
this.canvas.style.border = '1px solid #e0e0e0'
|
||||||
this.context = this.canvas.getContext('2d')
|
this.context = this.canvas.getContext('2d')!
|
||||||
const container = document.getElementsByClassName('canvas-center')[0]
|
const container = document.getElementsByClassName('canvas-center')[0]
|
||||||
this.grid = Math.min(
|
this.grid = Math.min(
|
||||||
Math.floor(container.clientWidth / this.width),
|
Math.floor(container.clientWidth / this.width),
|
||||||
@@ -109,13 +110,13 @@ export class SnakePage {
|
|||||||
return evt.touches
|
return evt.touches
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTouchStart(evt) {
|
handleTouchStart(evt: TouchEvent) {
|
||||||
const firstTouch = this.getTouches(evt)[0]
|
const firstTouch = this.getTouches(evt)[0]
|
||||||
this.xDown = firstTouch.clientX
|
this.xDown = firstTouch.clientX
|
||||||
this.yDown = firstTouch.clientY
|
this.yDown = firstTouch.clientY
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTouchMove(evt) {
|
handleTouchMove(evt: TouchEvent) {
|
||||||
if (!this.xDown || !this.yDown) {
|
if (!this.xDown || !this.yDown) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -141,8 +142,8 @@ export class SnakePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* reset values */
|
/* reset values */
|
||||||
this.xDown = null
|
this.xDown = undefined
|
||||||
this.yDown = null
|
this.yDown = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
// game loop
|
// game loop
|
||||||
@@ -257,7 +258,7 @@ export class SnakePage {
|
|||||||
this.score = 0
|
this.score = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
getRandomInt(min, max) {
|
getRandomInt(min: number, max: number) {
|
||||||
return Math.floor(Math.random() * (max - min)) + min
|
return Math.floor(Math.random() * (max - min)) + min
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { wizardModal } from 'src/app/components/install-wizard/install-wizard.co
|
|||||||
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
||||||
import { isEmptyObject, ErrorToastService } from '@start9labs/shared'
|
import { isEmptyObject, ErrorToastService, getPkgId } from '@start9labs/shared'
|
||||||
import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page'
|
import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.page'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -28,7 +28,7 @@ import { ActionSuccessPage } from 'src/app/modals/action-success/action-success.
|
|||||||
})
|
})
|
||||||
export class AppActionsPage {
|
export class AppActionsPage {
|
||||||
@ViewChild(IonContent) content: IonContent
|
@ViewChild(IonContent) content: IonContent
|
||||||
pkgId: string
|
readonly pkgId = getPkgId(this.route)
|
||||||
pkg: PackageDataEntry
|
pkg: PackageDataEntry
|
||||||
subs: Subscription[]
|
subs: Subscription[]
|
||||||
|
|
||||||
@@ -45,7 +45,6 @@ export class AppActionsPage {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
|
||||||
this.subs = [
|
this.subs = [
|
||||||
this.patch.watch$('package-data', this.pkgId).subscribe(pkg => {
|
this.patch.watch$('package-data', this.pkgId).subscribe(pkg => {
|
||||||
this.pkg = pkg
|
this.pkg = pkg
|
||||||
@@ -62,13 +61,14 @@ export class AppActionsPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleAction(action: { key: string; value: Action }) {
|
async handleAction(action: { key: string; value: Action }) {
|
||||||
const status = this.pkg.installed.status
|
const status = this.pkg.installed?.status
|
||||||
if (
|
if (
|
||||||
|
status &&
|
||||||
(action.value['allowed-statuses'] as PackageMainStatus[]).includes(
|
(action.value['allowed-statuses'] as PackageMainStatus[]).includes(
|
||||||
status.main.status,
|
status.main.status,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (!isEmptyObject(action.value['input-spec'])) {
|
if (!isEmptyObject(action.value['input-spec'] || {})) {
|
||||||
const modal = await this.modalCtrl.create({
|
const modal = await this.modalCtrl.create({
|
||||||
component: GenericFormPage,
|
component: GenericFormPage,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
@@ -112,7 +112,7 @@ export class AppActionsPage {
|
|||||||
const statuses = [...action.value['allowed-statuses']]
|
const statuses = [...action.value['allowed-statuses']]
|
||||||
const last = statuses.pop()
|
const last = statuses.pop()
|
||||||
let statusesStr = statuses.join(', ')
|
let statusesStr = statuses.join(', ')
|
||||||
let error = null
|
let error = ''
|
||||||
if (statuses.length) {
|
if (statuses.length) {
|
||||||
if (statuses.length > 1) {
|
if (statuses.length > 1) {
|
||||||
// oxford comma
|
// oxford comma
|
||||||
@@ -144,7 +144,7 @@ export class AppActionsPage {
|
|||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
version,
|
version,
|
||||||
uninstallAlert: alerts.uninstall,
|
uninstallAlert: alerts.uninstall || undefined,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -177,6 +177,7 @@ export class AppActionsPage {
|
|||||||
})
|
})
|
||||||
|
|
||||||
setTimeout(() => successModal.present(), 400)
|
setTimeout(() => successModal.present(), 400)
|
||||||
|
return true
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errToast.present(e)
|
this.errToast.present(e)
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Component, Input, ViewChild } from '@angular/core'
|
import { Component, Input, ViewChild } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { IonContent, ToastController } from '@ionic/angular'
|
import { IonContent, ToastController } from '@ionic/angular'
|
||||||
|
import { getPkgId } from '@start9labs/shared'
|
||||||
import { getUiInterfaceKey } from 'src/app/services/config.service'
|
import { getUiInterfaceKey } from 'src/app/services/config.service'
|
||||||
import {
|
import {
|
||||||
InstalledPackageDataEntry,
|
InstalledPackageDataEntry,
|
||||||
@@ -23,7 +24,7 @@ export class AppInterfacesPage {
|
|||||||
@ViewChild(IonContent) content: IonContent
|
@ViewChild(IonContent) content: IonContent
|
||||||
ui: LocalInterface | null
|
ui: LocalInterface | null
|
||||||
other: LocalInterface[] = []
|
other: LocalInterface[] = []
|
||||||
pkgId: string
|
readonly pkgId = getPkgId(this.route)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly route: ActivatedRoute,
|
private readonly route: ActivatedRoute,
|
||||||
@@ -31,11 +32,12 @@ export class AppInterfacesPage {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
|
||||||
const pkg = this.patch.getData()['package-data'][this.pkgId]
|
const pkg = this.patch.getData()['package-data'][this.pkgId]
|
||||||
const interfaces = pkg.manifest.interfaces
|
const interfaces = pkg.manifest.interfaces
|
||||||
const uiKey = getUiInterfaceKey(interfaces)
|
const uiKey = getUiInterfaceKey(interfaces)
|
||||||
|
|
||||||
|
if (!pkg?.installed) return
|
||||||
|
|
||||||
const addressesMap = pkg.installed['interface-addresses']
|
const addressesMap = pkg.installed['interface-addresses']
|
||||||
|
|
||||||
if (uiKey) {
|
if (uiKey) {
|
||||||
@@ -45,10 +47,10 @@ export class AppInterfacesPage {
|
|||||||
addresses: {
|
addresses: {
|
||||||
'lan-address': uiAddresses['lan-address']
|
'lan-address': uiAddresses['lan-address']
|
||||||
? 'https://' + uiAddresses['lan-address']
|
? 'https://' + uiAddresses['lan-address']
|
||||||
: null,
|
: '',
|
||||||
'tor-address': uiAddresses['tor-address']
|
'tor-address': uiAddresses['tor-address']
|
||||||
? 'http://' + uiAddresses['tor-address']
|
? 'http://' + uiAddresses['tor-address']
|
||||||
: null,
|
: '',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,10 +64,10 @@ export class AppInterfacesPage {
|
|||||||
addresses: {
|
addresses: {
|
||||||
'lan-address': addresses['lan-address']
|
'lan-address': addresses['lan-address']
|
||||||
? 'https://' + addresses['lan-address']
|
? 'https://' + addresses['lan-address']
|
||||||
: null,
|
: '',
|
||||||
'tor-address': addresses['tor-address']
|
'tor-address': addresses['tor-address']
|
||||||
? 'http://' + addresses['tor-address']
|
? 'http://' + addresses['tor-address']
|
||||||
: null,
|
: '',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ export class AppListPkgComponent {
|
|||||||
constructor(private readonly launcherService: UiLauncherService) {}
|
constructor(private readonly launcherService: UiLauncherService) {}
|
||||||
|
|
||||||
get status(): PackageMainStatus {
|
get status(): PackageMainStatus {
|
||||||
return this.pkg.entry.installed?.status.main.status
|
return (
|
||||||
|
this.pkg.entry.installed?.status.main.status || PackageMainStatus.Stopped
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
get manifest(): Manifest {
|
get manifest(): Manifest {
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
<!-- header -->
|
<!-- header -->
|
||||||
<ion-item-divider>
|
<ion-item-divider>
|
||||||
{{ reordering ? "Reorder" : "Installed Services" }}
|
{{ reordering ? 'Reorder' : 'Installed Services' }}
|
||||||
<ion-button *ngIf="pkgs.length > 1" slot="end" fill="clear" (click)="toggle()">
|
<ion-button
|
||||||
|
*ngIf="pkgs.length > 1"
|
||||||
|
slot="end"
|
||||||
|
fill="clear"
|
||||||
|
(click)="toggle()"
|
||||||
|
>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
slot="start"
|
slot="start"
|
||||||
[name]="reordering ? 'checkmark' : 'swap-vertical'"
|
[name]="reordering ? 'checkmark' : 'swap-vertical'"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
{{ reordering ? "Done" : "Reorder" }}
|
{{ reordering ? 'Done' : 'Reorder' }}
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
|
|
||||||
@@ -14,11 +19,15 @@
|
|||||||
<ion-list *ngIf="reordering; else grid">
|
<ion-list *ngIf="reordering; else grid">
|
||||||
<ion-reorder-group disabled="false" (ionItemReorder)="reorder($any($event))">
|
<ion-reorder-group disabled="false" (ionItemReorder)="reorder($any($event))">
|
||||||
<ion-reorder *ngFor="let item of pkgs">
|
<ion-reorder *ngFor="let item of pkgs">
|
||||||
<ion-item color="light" *ngIf="item | packageInfo | async as pkg" class="item">
|
<ion-item
|
||||||
|
color="light"
|
||||||
|
*ngIf="item | packageInfo | async as pkg"
|
||||||
|
class="item"
|
||||||
|
>
|
||||||
<app-list-icon
|
<app-list-icon
|
||||||
slot="start"
|
slot="start"
|
||||||
[pkg]="pkg"
|
[pkg]="pkg"
|
||||||
[connectionFailure]="connectionFailure$ | async"
|
[connectionFailure]="!!(connectionFailure$ | async)"
|
||||||
></app-list-icon>
|
></app-list-icon>
|
||||||
<ion-thumbnail slot="start">
|
<ion-thumbnail slot="start">
|
||||||
<img alt="" [src]="pkg.entry['static-files'].icon" />
|
<img alt="" [src]="pkg.entry['static-files'].icon" />
|
||||||
@@ -27,7 +36,7 @@
|
|||||||
<h2>{{ pkg.entry.manifest.title }}</h2>
|
<h2>{{ pkg.entry.manifest.title }}</h2>
|
||||||
<p>{{ pkg.entry.manifest.version | displayEmver }}</p>
|
<p>{{ pkg.entry.manifest.version | displayEmver }}</p>
|
||||||
<status
|
<status
|
||||||
[disconnected]="connectionFailure$ | async"
|
[disconnected]="!!(connectionFailure$ | async)"
|
||||||
[rendering]="pkg.primaryRendering"
|
[rendering]="pkg.primaryRendering"
|
||||||
[installProgress]="pkg.installProgress?.totalProgress"
|
[installProgress]="pkg.installProgress?.totalProgress"
|
||||||
weight="bold"
|
weight="bold"
|
||||||
@@ -47,8 +56,9 @@
|
|||||||
<ion-row>
|
<ion-row>
|
||||||
<ion-col *ngFor="let pkg of pkgs" sizeXs="12" sizeXl="6">
|
<ion-col *ngFor="let pkg of pkgs" sizeXs="12" sizeXl="6">
|
||||||
<app-list-pkg
|
<app-list-pkg
|
||||||
[pkg]="pkg | packageInfo | async"
|
*ngIf="pkg | packageInfo | async as info"
|
||||||
[connectionFailure]="connectionFailure$ | async"
|
[pkg]="info"
|
||||||
|
[connectionFailure]="!!(connectionFailure$ | async)"
|
||||||
></app-list-pkg>
|
></app-list-pkg>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
</ion-row>
|
</ion-row>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
|
import { getPkgId } from '@start9labs/shared'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -8,22 +9,22 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|||||||
styleUrls: ['./app-logs.page.scss'],
|
styleUrls: ['./app-logs.page.scss'],
|
||||||
})
|
})
|
||||||
export class AppLogsPage {
|
export class AppLogsPage {
|
||||||
pkgId: string
|
readonly pkgId = getPkgId(this.route)
|
||||||
loading = true
|
loading = true
|
||||||
needInfinite = true
|
needInfinite = true
|
||||||
before: string
|
before: string
|
||||||
|
|
||||||
constructor (
|
constructor(
|
||||||
private readonly route: ActivatedRoute,
|
private readonly route: ActivatedRoute,
|
||||||
private readonly embassyApi: ApiService,
|
private readonly embassyApi: ApiService,
|
||||||
) { }
|
) {}
|
||||||
|
|
||||||
ngOnInit () {
|
fetchFetchLogs() {
|
||||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
return async (params: {
|
||||||
}
|
before_flag?: boolean
|
||||||
|
limit?: number
|
||||||
fetchFetchLogs () {
|
cursor?: string
|
||||||
return async (params: { before_flag?: boolean, limit?: number, cursor?: string }) => {
|
}) => {
|
||||||
return this.embassyApi.getPackageLogs({
|
return this.embassyApi.getPackageLogs({
|
||||||
id: this.pkgId,
|
id: this.pkgId,
|
||||||
before_flag: params.before_flag,
|
before_flag: params.before_flag,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Subscription } from 'rxjs'
|
|||||||
import { Metric } from 'src/app/services/api/api.types'
|
import { Metric } from 'src/app/services/api/api.types'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { MainStatus } from 'src/app/services/patch-db/data-model'
|
import { MainStatus } from 'src/app/services/patch-db/data-model'
|
||||||
import { pauseFor, ErrorToastService } from '@start9labs/shared'
|
import { pauseFor, ErrorToastService, getPkgId } from '@start9labs/shared'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-metrics',
|
selector: 'app-metrics',
|
||||||
@@ -14,7 +14,7 @@ import { pauseFor, ErrorToastService } from '@start9labs/shared'
|
|||||||
})
|
})
|
||||||
export class AppMetricsPage {
|
export class AppMetricsPage {
|
||||||
loading = true
|
loading = true
|
||||||
pkgId: string
|
readonly pkgId = getPkgId(this.route)
|
||||||
mainStatus: MainStatus
|
mainStatus: MainStatus
|
||||||
going = false
|
going = false
|
||||||
metrics: Metric
|
metrics: Metric
|
||||||
@@ -29,7 +29,6 @@ export class AppMetricsPage {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
|
||||||
this.startDaemon()
|
this.startDaemon()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { PackageProperties } from 'src/app/util/properties.util'
|
|||||||
import { QRComponent } from 'src/app/components/qr/qr.component'
|
import { QRComponent } from 'src/app/components/qr/qr.component'
|
||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||||
import { PackageMainStatus } from 'src/app/services/patch-db/data-model'
|
import { PackageMainStatus } from 'src/app/services/patch-db/data-model'
|
||||||
import { ErrorToastService } from '@start9labs/shared'
|
import { ErrorToastService, getPkgId } from '@start9labs/shared'
|
||||||
import { getValueByPointer } from 'fast-json-patch'
|
import { getValueByPointer } from 'fast-json-patch'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -25,7 +25,7 @@ import { getValueByPointer } from 'fast-json-patch'
|
|||||||
})
|
})
|
||||||
export class AppPropertiesPage {
|
export class AppPropertiesPage {
|
||||||
loading = true
|
loading = true
|
||||||
pkgId: string
|
readonly pkgId = getPkgId(this.route)
|
||||||
pointer: string
|
pointer: string
|
||||||
properties: PackageProperties
|
properties: PackageProperties
|
||||||
node: PackageProperties
|
node: PackageProperties
|
||||||
@@ -55,8 +55,6 @@ export class AppPropertiesPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.pkgId = this.route.snapshot.paramMap.get('pkgId')
|
|
||||||
|
|
||||||
await this.getProperties()
|
await this.getProperties()
|
||||||
|
|
||||||
this.subs = [
|
this.subs = [
|
||||||
@@ -100,7 +98,7 @@ export class AppPropertiesPage {
|
|||||||
|
|
||||||
const alert = await this.alertCtrl.create({
|
const alert = await this.alertCtrl.create({
|
||||||
header: property.key,
|
header: property.key,
|
||||||
message: property.value.description,
|
message: property.value.description || undefined,
|
||||||
})
|
})
|
||||||
await alert.present()
|
await alert.present()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<!-- ** status ** -->
|
<!-- ** status ** -->
|
||||||
<app-show-status
|
<app-show-status
|
||||||
[pkg]="pkg"
|
[pkg]="pkg"
|
||||||
[connectionFailure]="connectionFailure$ | async"
|
[connectionFailure]="!!(connectionFailure$ | async)"
|
||||||
[dependencies]="dependencies"
|
[dependencies]="dependencies"
|
||||||
[status]="status"
|
[status]="status"
|
||||||
></app-show-status>
|
></app-show-status>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<app-show-health-checks
|
<app-show-health-checks
|
||||||
*ngIf="isRunning(status)"
|
*ngIf="isRunning(status)"
|
||||||
[pkg]="pkg"
|
[pkg]="pkg"
|
||||||
[connectionFailure]="connectionFailure$ | async"
|
[connectionFailure]="!!(connectionFailure$ | async)"
|
||||||
></app-show-health-checks>
|
></app-show-health-checks>
|
||||||
<!-- ** dependencies ** -->
|
<!-- ** dependencies ** -->
|
||||||
<app-show-dependencies
|
<app-show-dependencies
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<app-show-menu [buttons]="pkg | toButtons"></app-show-menu>
|
<app-show-menu [buttons]="pkg | toButtons"></app-show-menu>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
|
|
||||||
<!-- ** installing, updating, restoring ** -->
|
<!-- ** installing, updating, restoring ** -->
|
||||||
<ion-content *ngIf="showProgress(pkg)">
|
<ion-content *ngIf="showProgress(pkg)">
|
||||||
<app-show-progress
|
<app-show-progress
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from 'src/app/services/connection.service'
|
} from 'src/app/services/connection.service'
|
||||||
import { map, startWith } from 'rxjs/operators'
|
import { map, startWith } from 'rxjs/operators'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
|
import { getPkgId } from '@start9labs/shared'
|
||||||
|
|
||||||
const STATES = [
|
const STATES = [
|
||||||
PackageState.Installing,
|
PackageState.Installing,
|
||||||
@@ -26,7 +27,7 @@ const STATES = [
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AppShowPage {
|
export class AppShowPage {
|
||||||
private readonly pkgId = this.route.snapshot.paramMap.get('pkgId')
|
private readonly pkgId = getPkgId(this.route)
|
||||||
|
|
||||||
readonly pkg$ = this.patch.watch$('package-data', this.pkgId).pipe(
|
readonly pkg$ = this.patch.watch$('package-data', this.pkgId).pipe(
|
||||||
map(pkg => {
|
map(pkg => {
|
||||||
|
|||||||
@@ -31,6 +31,6 @@ export class AppShowProgressComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getColor(action: keyof InstallProgress): string {
|
getColor(action: keyof InstallProgress): string {
|
||||||
return this.pkg['install-progress'][action] ? 'success' : 'secondary'
|
return this.pkg['install-progress']?.[action] ? 'success' : 'secondary'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
<ion-button
|
<ion-button
|
||||||
*ngIf="!pkgStatus.configured"
|
*ngIf="!pkgStatus?.configured"
|
||||||
class="action-button"
|
class="action-button"
|
||||||
slot="start"
|
slot="start"
|
||||||
color="warning"
|
color="warning"
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
<ion-button
|
<ion-button
|
||||||
*ngIf="interfaces | hasUi"
|
*ngIf="pkgStatus && (interfaces | hasUi)"
|
||||||
class="action-button"
|
class="action-button"
|
||||||
slot="start"
|
slot="start"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ export class AppShowStatusComponent {
|
|||||||
return this.pkg.manifest.interfaces
|
return this.pkg.manifest.interfaces
|
||||||
}
|
}
|
||||||
|
|
||||||
get pkgStatus(): Status {
|
get pkgStatus(): Status | null {
|
||||||
return this.pkg.installed.status
|
return this.pkg.installed?.status || null
|
||||||
}
|
}
|
||||||
|
|
||||||
get isInstalled(): boolean {
|
get isInstalled(): boolean {
|
||||||
@@ -75,7 +75,8 @@ export class AppShowStatusComponent {
|
|||||||
|
|
||||||
get isStopped(): boolean {
|
get isStopped(): boolean {
|
||||||
return (
|
return (
|
||||||
this.status.primary === PrimaryStatus.Stopped && this.pkgStatus.configured
|
this.status.primary === PrimaryStatus.Stopped &&
|
||||||
|
!!this.pkgStatus?.configured
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +110,7 @@ export class AppShowStatusComponent {
|
|||||||
async stop(): Promise<void> {
|
async stop(): Promise<void> {
|
||||||
const { id, title, version } = this.pkg.manifest
|
const { id, title, version } = this.pkg.manifest
|
||||||
const hasDependents = !!Object.keys(
|
const hasDependents = !!Object.keys(
|
||||||
this.pkg.installed['current-dependents'],
|
this.pkg.installed?.['current-dependents'] || {},
|
||||||
).filter(depId => depId !== id).length
|
).filter(depId => depId !== id).length
|
||||||
|
|
||||||
if (!hasDependents) {
|
if (!hasDependents) {
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export class ToButtonsPipe implements PipeTransform {
|
|||||||
private async donate({ manifest }: PackageDataEntry): Promise<void> {
|
private async donate({ manifest }: PackageDataEntry): Promise<void> {
|
||||||
const url = manifest['donation-url']
|
const url = manifest['donation-url']
|
||||||
if (url) {
|
if (url) {
|
||||||
this.document.defaultView.open(url, '_blank', 'noreferrer')
|
this.document.defaultView?.open(url, '_blank', 'noreferrer')
|
||||||
} else {
|
} else {
|
||||||
const alert = await this.alertCtrl.create({
|
const alert = await this.alertCtrl.create({
|
||||||
header: 'Not Accepting Donations',
|
header: 'Not Accepting Donations',
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export class ToDependenciesPipe implements PipeTransform {
|
|||||||
private setDepValues(
|
private setDepValues(
|
||||||
pkg: PackageDataEntry,
|
pkg: PackageDataEntry,
|
||||||
id: string,
|
id: string,
|
||||||
errors: { [id: string]: DependencyError },
|
errors: { [id: string]: DependencyError | null },
|
||||||
): DependencyInfo {
|
): DependencyInfo {
|
||||||
let errorText = ''
|
let errorText = ''
|
||||||
let actionText = 'View'
|
let actionText = 'View'
|
||||||
@@ -105,13 +105,13 @@ export class ToDependenciesPipe implements PipeTransform {
|
|||||||
errorText = `${errorText}. ${pkg.manifest.title} will not work as expected.`
|
errorText = `${errorText}. ${pkg.manifest.title} will not work as expected.`
|
||||||
}
|
}
|
||||||
|
|
||||||
const depInfo = pkg.installed['dependency-info'][id]
|
const depInfo = pkg.installed?.['dependency-info'][id]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
version: pkg.manifest.dependencies[id].version,
|
version: pkg.manifest.dependencies[id].version,
|
||||||
title: depInfo.manifest?.title || id,
|
title: depInfo?.manifest?.title || id,
|
||||||
icon: depInfo.icon,
|
icon: depInfo?.icon || '',
|
||||||
errorText,
|
errorText,
|
||||||
actionText,
|
actionText,
|
||||||
action,
|
action,
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { ModalController } from '@ionic/angular'
|
import { ModalController } from '@ionic/angular'
|
||||||
|
import { debounce, exists, ErrorToastService } from '@start9labs/shared'
|
||||||
import * as yaml from 'js-yaml'
|
import * as yaml from 'js-yaml'
|
||||||
import { take } from 'rxjs/operators'
|
import { filter, take } from 'rxjs/operators'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||||
|
import { getProjectId } from 'src/app/util/get-project-id'
|
||||||
import { GenericFormPage } from '../../../modals/generic-form/generic-form.page'
|
import { GenericFormPage } from '../../../modals/generic-form/generic-form.page'
|
||||||
import { debounce, ErrorToastService } from '@start9labs/shared'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'dev-config',
|
selector: 'dev-config',
|
||||||
@@ -14,7 +15,7 @@ import { debounce, ErrorToastService } from '@start9labs/shared'
|
|||||||
styleUrls: ['dev-config.page.scss'],
|
styleUrls: ['dev-config.page.scss'],
|
||||||
})
|
})
|
||||||
export class DevConfigPage {
|
export class DevConfigPage {
|
||||||
projectId: string
|
readonly projectId = getProjectId(this.route)
|
||||||
editorOptions = { theme: 'vs-dark', language: 'yaml' }
|
editorOptions = { theme: 'vs-dark', language: 'yaml' }
|
||||||
code: string = ''
|
code: string = ''
|
||||||
saving: boolean = false
|
saving: boolean = false
|
||||||
@@ -28,11 +29,9 @@ export class DevConfigPage {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.projectId = this.route.snapshot.paramMap.get('projectId')
|
|
||||||
|
|
||||||
this.patchDb
|
this.patchDb
|
||||||
.watch$('ui', 'dev', this.projectId, 'config')
|
.watch$('ui', 'dev', this.projectId, 'config')
|
||||||
.pipe(take(1))
|
.pipe(filter(exists), take(1))
|
||||||
.subscribe(config => {
|
.subscribe(config => {
|
||||||
this.code = config
|
this.code = config
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { ModalController } from '@ionic/angular'
|
import { ModalController } from '@ionic/angular'
|
||||||
import { take } from 'rxjs/operators'
|
import { filter, take } from 'rxjs/operators'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import {
|
import {
|
||||||
debounce,
|
debounce,
|
||||||
|
exists,
|
||||||
ErrorToastService,
|
ErrorToastService,
|
||||||
MarkdownComponent,
|
MarkdownComponent,
|
||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||||
|
import { getProjectId } from 'src/app/util/get-project-id'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'dev-instructions',
|
selector: 'dev-instructions',
|
||||||
@@ -16,7 +18,7 @@ import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
|||||||
styleUrls: ['dev-instructions.page.scss'],
|
styleUrls: ['dev-instructions.page.scss'],
|
||||||
})
|
})
|
||||||
export class DevInstructionsPage {
|
export class DevInstructionsPage {
|
||||||
projectId: string
|
readonly projectId = getProjectId(this.route)
|
||||||
editorOptions = { theme: 'vs-dark', language: 'markdown' }
|
editorOptions = { theme: 'vs-dark', language: 'markdown' }
|
||||||
code: string = ''
|
code: string = ''
|
||||||
saving: boolean = false
|
saving: boolean = false
|
||||||
@@ -30,11 +32,9 @@ export class DevInstructionsPage {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.projectId = this.route.snapshot.paramMap.get('projectId')
|
|
||||||
|
|
||||||
this.patchDb
|
this.patchDb
|
||||||
.watch$('ui', 'dev', this.projectId, 'instructions')
|
.watch$('ui', 'dev', this.projectId, 'instructions')
|
||||||
.pipe(take(1))
|
.pipe(filter(exists), take(1))
|
||||||
.subscribe(config => {
|
.subscribe(config => {
|
||||||
this.code = config
|
this.code = config
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ActivatedRoute } from '@angular/router'
|
|||||||
import * as yaml from 'js-yaml'
|
import * as yaml from 'js-yaml'
|
||||||
import { take } from 'rxjs/operators'
|
import { take } from 'rxjs/operators'
|
||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||||
|
import { getProjectId } from 'src/app/util/get-project-id'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'dev-manifest',
|
selector: 'dev-manifest',
|
||||||
@@ -10,7 +11,7 @@ import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
|||||||
styleUrls: ['dev-manifest.page.scss'],
|
styleUrls: ['dev-manifest.page.scss'],
|
||||||
})
|
})
|
||||||
export class DevManifestPage {
|
export class DevManifestPage {
|
||||||
projectId: string
|
readonly projectId = getProjectId(this.route)
|
||||||
editorOptions = { theme: 'vs-dark', language: 'yaml', readOnly: true }
|
editorOptions = { theme: 'vs-dark', language: 'yaml', readOnly: true }
|
||||||
manifest: string = ''
|
manifest: string = ''
|
||||||
|
|
||||||
@@ -20,8 +21,6 @@ export class DevManifestPage {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.projectId = this.route.snapshot.paramMap.get('projectId')
|
|
||||||
|
|
||||||
this.patchDb
|
this.patchDb
|
||||||
.watch$('ui', 'dev', this.projectId)
|
.watch$('ui', 'dev', this.projectId)
|
||||||
.pipe(take(1))
|
.pipe(take(1))
|
||||||
|
|||||||
@@ -231,9 +231,7 @@ const SAMPLE_CONFIG: ConfigSpec = {
|
|||||||
masked: false,
|
masked: false,
|
||||||
copyable: false,
|
copyable: false,
|
||||||
// optional
|
// optional
|
||||||
warning: null,
|
|
||||||
description: 'Example description for required string input.',
|
description: 'Example description for required string input.',
|
||||||
default: null,
|
|
||||||
placeholder: 'Enter string value',
|
placeholder: 'Enter string value',
|
||||||
pattern: '^[a-zA-Z0-9! _]+$',
|
pattern: '^[a-zA-Z0-9! _]+$',
|
||||||
'pattern-description': 'Must be alphanumeric (may contain underscore).',
|
'pattern-description': 'Must be alphanumeric (may contain underscore).',
|
||||||
@@ -248,14 +246,12 @@ const SAMPLE_CONFIG: ConfigSpec = {
|
|||||||
warning: 'Example warning to display when changing this number value.',
|
warning: 'Example warning to display when changing this number value.',
|
||||||
units: 'ms',
|
units: 'ms',
|
||||||
description: 'Example description for optional number input.',
|
description: 'Example description for optional number input.',
|
||||||
default: null,
|
|
||||||
placeholder: 'Enter number value',
|
placeholder: 'Enter number value',
|
||||||
},
|
},
|
||||||
'sample-boolean': {
|
'sample-boolean': {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
name: 'Example Boolean Toggle',
|
name: 'Example Boolean Toggle',
|
||||||
// optional
|
// optional
|
||||||
warning: null,
|
|
||||||
description: 'Example description for boolean toggle',
|
description: 'Example description for boolean toggle',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<ion-buttons slot="start">
|
<ion-buttons slot="start">
|
||||||
<ion-back-button defaultHref="/developer"></ion-back-button>
|
<ion-back-button defaultHref="/developer"></ion-back-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>{{ patchDb.data.ui.dev[projectId].name}}</ion-title>
|
<ion-title>{{ name }}</ion-title>
|
||||||
<ion-buttons slot="end">
|
<ion-buttons slot="end">
|
||||||
<ion-button routerLink="manifest">View Manifest</ion-button>
|
<ion-button routerLink="manifest">View Manifest</ion-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
|||||||
import { BasicInfo, getBasicInfoSpec } from './form-info'
|
import { BasicInfo, getBasicInfoSpec } from './form-info'
|
||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { ErrorToastService } from '@start9labs/shared'
|
import { ErrorToastService, DestroyService } from '@start9labs/shared'
|
||||||
import { takeUntil } from 'rxjs/operators'
|
import { takeUntil } from 'rxjs/operators'
|
||||||
import { DevProjectData } from 'src/app/services/patch-db/data-model'
|
import { DevProjectData } from 'src/app/services/patch-db/data-model'
|
||||||
import { DestroyService } from '../../../../../../shared/src/services/destroy.service'
|
import { getProjectId } from 'src/app/util/get-project-id'
|
||||||
import * as yaml from 'js-yaml'
|
import * as yaml from 'js-yaml'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -18,7 +18,7 @@ import * as yaml from 'js-yaml'
|
|||||||
providers: [DestroyService],
|
providers: [DestroyService],
|
||||||
})
|
})
|
||||||
export class DeveloperMenuPage {
|
export class DeveloperMenuPage {
|
||||||
projectId: string
|
readonly projectId = getProjectId(this.route)
|
||||||
projectData: DevProjectData
|
projectData: DevProjectData
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -28,12 +28,14 @@ export class DeveloperMenuPage {
|
|||||||
private readonly api: ApiService,
|
private readonly api: ApiService,
|
||||||
private readonly errToast: ErrorToastService,
|
private readonly errToast: ErrorToastService,
|
||||||
private readonly destroy$: DestroyService,
|
private readonly destroy$: DestroyService,
|
||||||
public readonly patchDb: PatchDbService,
|
private readonly patchDb: PatchDbService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
get name(): string {
|
||||||
this.projectId = this.route.snapshot.paramMap.get('projectId')
|
return this.patchDb.data.ui?.dev?.[this.projectId]?.name || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
this.patchDb
|
this.patchDb
|
||||||
.watch$('ui', 'dev', this.projectId)
|
.watch$('ui', 'dev', this.projectId)
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
@@ -51,14 +53,14 @@ export class DeveloperMenuPage {
|
|||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
text: 'Save',
|
text: 'Save',
|
||||||
handler: basicInfo => {
|
handler: (basicInfo: any) => {
|
||||||
basicInfo.description = {
|
basicInfo.description = {
|
||||||
short: basicInfo.short,
|
short: basicInfo.short,
|
||||||
long: basicInfo.long,
|
long: basicInfo.long,
|
||||||
}
|
}
|
||||||
delete basicInfo.short
|
delete basicInfo.short
|
||||||
delete basicInfo.long
|
delete basicInfo.long
|
||||||
this.saveBasicInfo(basicInfo as BasicInfo)
|
this.saveBasicInfo(basicInfo)
|
||||||
},
|
},
|
||||||
isSubmit: true,
|
isSubmit: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
<ion-content class="ion-padding">
|
<ion-content class="ion-padding">
|
||||||
<marketplace-list-content
|
<marketplace-list-content
|
||||||
*ngIf="loaded else loading"
|
*ngIf="loaded else loading"
|
||||||
[localPkgs]="localPkgs$ | async"
|
[localPkgs]="(localPkgs$ | async) || {}"
|
||||||
[pkgs]="pkgs$ | async"
|
[pkgs]="pkgs$ | async"
|
||||||
[categories]="categories$ | async"
|
[categories]="categories$ | async"
|
||||||
[name]="name$ | async"
|
[name]="(name$ | async) || ''"
|
||||||
></marketplace-list-content>
|
></marketplace-list-content>
|
||||||
|
|
||||||
<ng-template #loading>
|
<ng-template #loading>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export class MarketplaceShowControlsComponent {
|
|||||||
pkg: MarketplacePkg
|
pkg: MarketplacePkg
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
localPkg: PackageDataEntry
|
localPkg: PackageDataEntry | null = null
|
||||||
|
|
||||||
readonly PackageState = PackageState
|
readonly PackageState = PackageState
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ export class MarketplaceShowControlsComponent {
|
|||||||
title,
|
title,
|
||||||
version,
|
version,
|
||||||
serviceRequirements: dependencies,
|
serviceRequirements: dependencies,
|
||||||
installAlert: alerts.install,
|
installAlert: alerts.install || undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
const { cancelled } = await wizardModal(
|
const { cancelled } = await wizardModal(
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
<ng-container *ngIf="!(pkg | empty)">
|
<ng-container *ngIf="!(pkg | empty)">
|
||||||
<marketplace-package [pkg]="pkg">
|
<marketplace-package [pkg]="pkg">
|
||||||
<marketplace-status
|
<marketplace-status
|
||||||
|
*ngIf="localPkg$ | async as localPkg"
|
||||||
class="status"
|
class="status"
|
||||||
[version]="pkg.manifest.version"
|
[localPkg]="localPkg"
|
||||||
[localPkg]="localPkg$ | async"
|
|
||||||
></marketplace-status>
|
></marketplace-status>
|
||||||
<marketplace-show-controls
|
<marketplace-show-controls
|
||||||
position="controls"
|
position="controls"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import { ErrorToastService } from '@start9labs/shared'
|
import { ErrorToastService, getPkgId } from '@start9labs/shared'
|
||||||
import {
|
import {
|
||||||
MarketplacePkg,
|
MarketplacePkg,
|
||||||
AbstractMarketplaceService,
|
AbstractMarketplaceService,
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
import { BehaviorSubject, Observable, of } from 'rxjs'
|
import { BehaviorSubject, Observable, of } from 'rxjs'
|
||||||
import { catchError, filter, shareReplay, switchMap, tap } from 'rxjs/operators'
|
import { catchError, filter, shareReplay, switchMap } from 'rxjs/operators'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplace-show',
|
selector: 'marketplace-show',
|
||||||
@@ -17,7 +17,7 @@ import { catchError, filter, shareReplay, switchMap, tap } from 'rxjs/operators'
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class MarketplaceShowPage {
|
export class MarketplaceShowPage {
|
||||||
private readonly pkgId = this.route.snapshot.paramMap.get('pkgId')
|
private readonly pkgId = getPkgId(this.route)
|
||||||
|
|
||||||
readonly loadVersion$ = new BehaviorSubject<string>('*')
|
readonly loadVersion$ = new BehaviorSubject<string>('*')
|
||||||
|
|
||||||
@@ -28,12 +28,16 @@ export class MarketplaceShowPage {
|
|||||||
shareReplay({ bufferSize: 1, refCount: true }),
|
shareReplay({ bufferSize: 1, refCount: true }),
|
||||||
)
|
)
|
||||||
|
|
||||||
readonly pkg$: Observable<MarketplacePkg> = this.loadVersion$.pipe(
|
readonly pkg$: Observable<MarketplacePkg | null> = this.loadVersion$.pipe(
|
||||||
switchMap(version =>
|
switchMap(version =>
|
||||||
this.marketplaceService.getPackage(this.pkgId, version),
|
this.marketplaceService.getPackage(this.pkgId, version),
|
||||||
),
|
),
|
||||||
// TODO: Better fallback
|
// TODO: Better fallback
|
||||||
catchError(e => this.errToast.present(e) && of({} as MarketplacePkg)),
|
catchError(e => {
|
||||||
|
this.errToast.present(e)
|
||||||
|
|
||||||
|
return of({} as MarketplacePkg)
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { packageLoadingProgress } from 'src/app/util/package-loading-progress'
|
|||||||
name: 'installProgress',
|
name: 'installProgress',
|
||||||
})
|
})
|
||||||
export class InstallProgressPipe implements PipeTransform {
|
export class InstallProgressPipe implements PipeTransform {
|
||||||
transform(loadData: InstallProgress): string {
|
transform(loadData?: InstallProgress): string {
|
||||||
const { totalProgress } = packageLoadingProgress(loadData)
|
const totalProgress = packageLoadingProgress(loadData)?.totalProgress || 0
|
||||||
|
|
||||||
return totalProgress < 99 ? totalProgress + '%' : 'finalizing'
|
return totalProgress < 99 ? totalProgress + '%' : 'finalizing'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
|
import { getPkgId } from '@start9labs/shared'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './release-notes.page.html',
|
templateUrl: './release-notes.page.html',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class ReleaseNotesPage {
|
export class ReleaseNotesPage {
|
||||||
readonly href = `/marketplace/${this.route.snapshot.paramMap.get('pkgId')}`
|
readonly href = `/marketplace/${getPkgId(this.route)}`
|
||||||
|
|
||||||
constructor(private readonly route: ActivatedRoute) {}
|
constructor(private readonly route: ActivatedRoute) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
|||||||
export class NotificationsPage {
|
export class NotificationsPage {
|
||||||
loading = true
|
loading = true
|
||||||
notifications: ServerNotifications = []
|
notifications: ServerNotifications = []
|
||||||
beforeCursor: number
|
beforeCursor?: number
|
||||||
needInfinite = false
|
needInfinite = false
|
||||||
fromToast = false
|
fromToast = false
|
||||||
readonly perPage = 40
|
readonly perPage = 40
|
||||||
@@ -51,19 +51,23 @@ export class NotificationsPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getNotifications(): Promise<ServerNotifications> {
|
async getNotifications(): Promise<ServerNotifications> {
|
||||||
let notifications: ServerNotifications = []
|
|
||||||
try {
|
try {
|
||||||
notifications = await this.embassyApi.getNotifications({
|
const notifications = await this.embassyApi.getNotifications({
|
||||||
before: this.beforeCursor,
|
before: this.beforeCursor,
|
||||||
limit: this.perPage,
|
limit: this.perPage,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!notifications) return []
|
||||||
|
|
||||||
this.beforeCursor = notifications[notifications.length - 1]?.id
|
this.beforeCursor = notifications[notifications.length - 1]?.id
|
||||||
this.needInfinite = notifications.length >= this.perPage
|
this.needInfinite = notifications.length >= this.perPage
|
||||||
|
|
||||||
|
return notifications
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errToast.present(e)
|
this.errToast.present(e)
|
||||||
} finally {
|
|
||||||
return notifications
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(id: number, index: number): Promise<void> {
|
async delete(id: number, index: number): Promise<void> {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
>instructions</a
|
>instructions</a
|
||||||
>.
|
>.
|
||||||
</h2>
|
</h2>
|
||||||
<ng-container *ngIf="downloadIsDisabled && server$ | async as server">
|
<ng-container *ngIf="downloadIsDisabled && (server$ | async) as server">
|
||||||
<br />
|
<br />
|
||||||
<ion-text color="warning">
|
<ion-text color="warning">
|
||||||
For security reasons, you must setup LAN over a
|
For security reasons, you must setup LAN over a
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
|||||||
styleUrls: ['./lan.page.scss'],
|
styleUrls: ['./lan.page.scss'],
|
||||||
})
|
})
|
||||||
export class LANPage {
|
export class LANPage {
|
||||||
downloadIsDisabled: boolean
|
readonly downloadIsDisabled = !this.config.isTor()
|
||||||
|
|
||||||
readonly server$ = this.patch.watch$('server-info')
|
readonly server$ = this.patch.watch$('server-info')
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -17,11 +16,7 @@ export class LANPage {
|
|||||||
private readonly patch: PatchDbService,
|
private readonly patch: PatchDbService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.downloadIsDisabled = !this.config.isTor()
|
|
||||||
}
|
|
||||||
|
|
||||||
installCert(): void {
|
installCert(): void {
|
||||||
document.getElementById('install-cert').click()
|
document.getElementById('install-cert')?.click()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,19 @@ import { v4 } from 'uuid'
|
|||||||
import { UIMarketplaceData } from '../../../services/patch-db/data-model'
|
import { UIMarketplaceData } from '../../../services/patch-db/data-model'
|
||||||
import { ConfigService } from '../../../services/config.service'
|
import { ConfigService } from '../../../services/config.service'
|
||||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||||
import { finalize, first } from 'rxjs/operators'
|
import {
|
||||||
|
distinctUntilChanged,
|
||||||
|
finalize,
|
||||||
|
first,
|
||||||
|
map,
|
||||||
|
startWith,
|
||||||
|
} from 'rxjs/operators'
|
||||||
|
|
||||||
|
type Marketplaces = {
|
||||||
|
id: string | undefined
|
||||||
|
name: string
|
||||||
|
url: string
|
||||||
|
}[]
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplaces',
|
selector: 'marketplaces',
|
||||||
@@ -24,7 +36,7 @@ import { finalize, first } from 'rxjs/operators'
|
|||||||
})
|
})
|
||||||
export class MarketplacesPage {
|
export class MarketplacesPage {
|
||||||
selectedId: string | undefined
|
selectedId: string | undefined
|
||||||
marketplaces: { id: string | undefined; name: string; url: string }[] = []
|
marketplaces: Marketplaces = []
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly api: ApiService,
|
private readonly api: ApiService,
|
||||||
@@ -39,27 +51,33 @@ export class MarketplacesPage {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.patch.watch$('ui', 'marketplace').subscribe(mp => {
|
this.patch
|
||||||
const marketplaces = [
|
.watch$('ui')
|
||||||
{
|
.pipe(
|
||||||
id: undefined,
|
map(ui => ui.marketplace),
|
||||||
name: this.config.marketplace.name,
|
distinctUntilChanged(),
|
||||||
url: this.config.marketplace.url,
|
)
|
||||||
},
|
.subscribe(mp => {
|
||||||
]
|
let marketplaces: Marketplaces = [
|
||||||
if (mp) {
|
{
|
||||||
this.selectedId = mp['selected-id']
|
id: undefined,
|
||||||
const alts = Object.entries(mp['known-hosts']).map(([k, v]) => {
|
name: this.config.marketplace.name,
|
||||||
return {
|
url: this.config.marketplace.url,
|
||||||
id: k,
|
},
|
||||||
name: v.name,
|
]
|
||||||
url: v.url,
|
if (mp) {
|
||||||
}
|
this.selectedId = mp['selected-id'] || undefined
|
||||||
})
|
const alts = Object.entries(mp['known-hosts']).map(([k, v]) => {
|
||||||
marketplaces.push.apply(marketplaces, alts)
|
return {
|
||||||
}
|
id: k,
|
||||||
this.marketplaces = marketplaces
|
name: v.name,
|
||||||
})
|
url: v.url,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
marketplaces = marketplaces.concat(alts)
|
||||||
|
}
|
||||||
|
this.marketplaces = marketplaces
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async presentModalAdd() {
|
async presentModalAdd() {
|
||||||
@@ -91,9 +109,10 @@ export class MarketplacesPage {
|
|||||||
await modal.present()
|
await modal.present()
|
||||||
}
|
}
|
||||||
|
|
||||||
async presentAction(id: string) {
|
async presentAction(id: string = '') {
|
||||||
// no need to view actions if is selected marketplace
|
// no need to view actions if is selected marketplace
|
||||||
if (id === this.patch.getData().ui.marketplace?.['selected-id']) return
|
if (!id || id === this.patch.getData().ui.marketplace?.['selected-id'])
|
||||||
|
return
|
||||||
|
|
||||||
const buttons: ActionSheetButton[] = [
|
const buttons: ActionSheetButton[] = [
|
||||||
{
|
{
|
||||||
@@ -200,7 +219,10 @@ export class MarketplacesPage {
|
|||||||
? (JSON.parse(
|
? (JSON.parse(
|
||||||
JSON.stringify(this.patch.getData().ui.marketplace),
|
JSON.stringify(this.patch.getData().ui.marketplace),
|
||||||
) as UIMarketplaceData)
|
) as UIMarketplaceData)
|
||||||
: { 'selected-id': undefined, 'known-hosts': {} }
|
: {
|
||||||
|
'selected-id': undefined,
|
||||||
|
'known-hosts': {} as Record<string, unknown>,
|
||||||
|
}
|
||||||
|
|
||||||
// no-op on duplicates
|
// no-op on duplicates
|
||||||
const currentUrls = this.marketplaces.map(mp => mp.url)
|
const currentUrls = this.marketplaces.map(mp => mp.url)
|
||||||
@@ -242,7 +264,10 @@ export class MarketplacesPage {
|
|||||||
? (JSON.parse(
|
? (JSON.parse(
|
||||||
JSON.stringify(this.patch.getData().ui.marketplace),
|
JSON.stringify(this.patch.getData().ui.marketplace),
|
||||||
) as UIMarketplaceData)
|
) as UIMarketplaceData)
|
||||||
: { 'selected-id': undefined, 'known-hosts': {} }
|
: {
|
||||||
|
'selected-id': undefined,
|
||||||
|
'known-hosts': {} as Record<string, unknown>,
|
||||||
|
}
|
||||||
|
|
||||||
// no-op on duplicates
|
// no-op on duplicates
|
||||||
const currentUrls = this.marketplaces.map(mp => mp.url)
|
const currentUrls = this.marketplaces.map(mp => mp.url)
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ export class RestorePage {
|
|||||||
useMask: true,
|
useMask: true,
|
||||||
buttonText: 'Next',
|
buttonText: 'Next',
|
||||||
submitFn: async (password: string) => {
|
submitFn: async (password: string) => {
|
||||||
argon2.verify(target.entry['embassy-os']['password-hash'], password)
|
const passwordHash = target.entry['embassy-os']?.['password-hash'] || ''
|
||||||
|
argon2.verify(passwordHash, password)
|
||||||
await this.restoreFromBackup(target, password)
|
await this.restoreFromBackup(target, password)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export class ServerBackupPage {
|
|||||||
} else {
|
} else {
|
||||||
if (this.backingUp) {
|
if (this.backingUp) {
|
||||||
this.backingUp = false
|
this.backingUp = false
|
||||||
this.pkgs.forEach(pkg => pkg.sub.unsubscribe())
|
this.pkgs.forEach(pkg => pkg.sub?.unsubscribe())
|
||||||
this.navCtrl.navigateRoot('/embassy')
|
this.navCtrl.navigateRoot('/embassy')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ export class ServerBackupPage {
|
|||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.subs.forEach(sub => sub.unsubscribe())
|
this.subs.forEach(sub => sub.unsubscribe())
|
||||||
this.pkgs.forEach(pkg => pkg.sub.unsubscribe())
|
this.pkgs.forEach(pkg => pkg.sub?.unsubscribe())
|
||||||
}
|
}
|
||||||
|
|
||||||
async presentModalPassword(
|
async presentModalPassword(
|
||||||
@@ -98,7 +98,10 @@ export class ServerBackupPage {
|
|||||||
// existing backup
|
// existing backup
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
argon2.verify(target.entry['embassy-os']['password-hash'], password)
|
const passwordHash =
|
||||||
|
target.entry['embassy-os']?.['password-hash'] || ''
|
||||||
|
|
||||||
|
argon2.verify(passwordHash, password)
|
||||||
} catch {
|
} catch {
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() => this.presentModalOldPassword(target, password),
|
() => this.presentModalOldPassword(target, password),
|
||||||
@@ -133,7 +136,9 @@ export class ServerBackupPage {
|
|||||||
useMask: true,
|
useMask: true,
|
||||||
buttonText: 'Create Backup',
|
buttonText: 'Create Backup',
|
||||||
submitFn: async (oldPassword: string) => {
|
submitFn: async (oldPassword: string) => {
|
||||||
argon2.verify(target.entry['embassy-os']['password-hash'], oldPassword)
|
const passwordHash = target.entry['embassy-os']?.['password-hash'] || ''
|
||||||
|
|
||||||
|
argon2.verify(passwordHash, oldPassword)
|
||||||
await this.createBackup(target.id, password, oldPassword)
|
await this.createBackup(target.id, password, oldPassword)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -182,15 +187,11 @@ export class ServerBackupPage {
|
|||||||
pkg.installed?.status.main.status === PackageMainStatus.BackingUp,
|
pkg.installed?.status.main.status === PackageMainStatus.BackingUp,
|
||||||
)
|
)
|
||||||
|
|
||||||
this.pkgs = pkgArr.map((pkg, i) => {
|
this.pkgs = pkgArr.map((pkg, i) => ({
|
||||||
const pkgInfo = {
|
entry: pkg,
|
||||||
entry: pkg,
|
active: i === activeIndex,
|
||||||
active: i === activeIndex,
|
complete: i < activeIndex,
|
||||||
complete: i < activeIndex,
|
}))
|
||||||
sub: null,
|
|
||||||
}
|
|
||||||
return pkgInfo
|
|
||||||
})
|
|
||||||
|
|
||||||
// subscribe to pkg
|
// subscribe to pkg
|
||||||
this.pkgs.forEach(pkg => {
|
this.pkgs.forEach(pkg => {
|
||||||
@@ -220,5 +221,5 @@ interface PkgInfo {
|
|||||||
entry: PackageDataEntry
|
entry: PackageDataEntry
|
||||||
active: boolean
|
active: boolean
|
||||||
complete: boolean
|
complete: boolean
|
||||||
sub: Subscription
|
sub?: Subscription
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ export class ServerLogsPage {
|
|||||||
needInfinite = true
|
needInfinite = true
|
||||||
before: string
|
before: string
|
||||||
|
|
||||||
constructor (
|
constructor(
|
||||||
private readonly embassyApi: ApiService,
|
private readonly embassyApi: ApiService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
fetchFetchLogs () {
|
fetchFetchLogs() {
|
||||||
return async (params: { before_flag?: boolean, limit?: number, cursor?: string }) => {
|
return async (params: { before_flag?: boolean, limit?: number, cursor?: string }) => {
|
||||||
return this.embassyApi.getServerLogs({
|
return this.embassyApi.getServerLogs({
|
||||||
before_flag: params.before_flag,
|
before_flag: params.before_flag,
|
||||||
@@ -25,4 +25,22 @@ export class ServerLogsPage {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async copy(): Promise<void> {
|
||||||
|
const logs = document
|
||||||
|
.getElementById('template')
|
||||||
|
?.cloneNode(true) as HTMLElement
|
||||||
|
const formatted = '```' + logs.innerHTML + '```'
|
||||||
|
const success = await copyToClipboard(formatted)
|
||||||
|
const message = success
|
||||||
|
? 'Copied to clipboard!'
|
||||||
|
: 'Failed to copy to clipboard.'
|
||||||
|
|
||||||
|
const toast = await this.toastCtrl.create({
|
||||||
|
header: message,
|
||||||
|
position: 'bottom',
|
||||||
|
duration: 1000,
|
||||||
|
})
|
||||||
|
await toast.present()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export class ServerMetricsPage {
|
|||||||
})
|
})
|
||||||
const height = headersCount * 54 + rowsCount * 50 + 24 // extra 24 for room at the bottom
|
const height = headersCount * 54 + rowsCount * 50 + 24 // extra 24 for room at the bottom
|
||||||
const elem = document.getElementById('metricSection')
|
const elem = document.getElementById('metricSection')
|
||||||
elem.style.height = `${height}px`
|
if (elem) elem.style.height = `${height}px`
|
||||||
this.startDaemon()
|
this.startDaemon()
|
||||||
this.loading = false
|
this.loading = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title *ngIf="patch.loaded else loading">
|
<ion-title *ngIf="patch.loaded else loading">
|
||||||
{{ (ui$ | async).name || "Embassy-" + (server$ | async).id }}
|
{{ (ui$ | async)?.name || "Embassy-" + (server$ | async)?.id }}
|
||||||
</ion-title>
|
</ion-title>
|
||||||
<ng-template #loading>
|
<ng-template #loading>
|
||||||
<ion-title>Loading<span class="loading-dots"></span></ion-title>
|
<ion-title>Loading<span class="loading-dots"></span></ion-title>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export class WifiPage {
|
|||||||
await this.getWifi()
|
await this.getWifi()
|
||||||
}
|
}
|
||||||
|
|
||||||
async getWifi(timeout?: number): Promise<void> {
|
async getWifi(timeout: number = 0): Promise<void> {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
try {
|
try {
|
||||||
this.wifi = await this.api.getWifi({}, timeout)
|
this.wifi = await this.api.getWifi({}, timeout)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export class Range {
|
|||||||
|
|
||||||
checkIncludes(n: number) {
|
checkIncludes(n: number) {
|
||||||
if (
|
if (
|
||||||
this.hasMin() !== undefined &&
|
this.hasMin() &&
|
||||||
(this.min > n || (!this.minInclusive && this.min == n))
|
(this.min > n || (!this.minInclusive && this.min == n))
|
||||||
) {
|
) {
|
||||||
throw new Error(this.minMessage())
|
throw new Error(this.minMessage())
|
||||||
@@ -31,11 +31,11 @@ export class Range {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMin(): boolean {
|
hasMin(): this is Range & { min: number } {
|
||||||
return this.min !== undefined
|
return this.min !== undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMax(): boolean {
|
hasMax(): this is Range & { max: number } {
|
||||||
return this.max !== undefined
|
return this.max !== undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -177,8 +177,6 @@ export module Mock {
|
|||||||
nullable: true,
|
nullable: true,
|
||||||
masked: false,
|
masked: false,
|
||||||
copyable: false,
|
copyable: false,
|
||||||
pattern: null,
|
|
||||||
'pattern-description': null,
|
|
||||||
warning: 'You may loose all your money by providing your name.',
|
warning: 'You may loose all your money by providing your name.',
|
||||||
},
|
},
|
||||||
notifications: {
|
notifications: {
|
||||||
@@ -213,7 +211,6 @@ export module Mock {
|
|||||||
name: 'Top Speed',
|
name: 'Top Speed',
|
||||||
description: 'The fastest you can possibly run.',
|
description: 'The fastest you can possibly run.',
|
||||||
nullable: false,
|
nullable: false,
|
||||||
default: null,
|
|
||||||
range: '[-1000, 1000]',
|
range: '[-1000, 1000]',
|
||||||
integral: false,
|
integral: false,
|
||||||
units: 'm/s',
|
units: 'm/s',
|
||||||
@@ -248,7 +245,6 @@ export module Mock {
|
|||||||
name: {
|
name: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
description: null,
|
|
||||||
nullable: false,
|
nullable: false,
|
||||||
masked: false,
|
masked: false,
|
||||||
copyable: false,
|
copyable: false,
|
||||||
@@ -258,7 +254,6 @@ export module Mock {
|
|||||||
email: {
|
email: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
name: 'Email',
|
name: 'Email',
|
||||||
description: null,
|
|
||||||
nullable: false,
|
nullable: false,
|
||||||
masked: false,
|
masked: false,
|
||||||
copyable: true,
|
copyable: true,
|
||||||
@@ -1187,7 +1182,6 @@ export module Mock {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'User first name',
|
description: 'User first name',
|
||||||
nullable: true,
|
nullable: true,
|
||||||
default: null,
|
|
||||||
masked: false,
|
masked: false,
|
||||||
copyable: false,
|
copyable: false,
|
||||||
},
|
},
|
||||||
@@ -1210,7 +1204,6 @@ export module Mock {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
description: 'The age of the user',
|
description: 'The age of the user',
|
||||||
nullable: true,
|
nullable: true,
|
||||||
default: null,
|
|
||||||
integral: false,
|
integral: false,
|
||||||
warning: 'User must be at least 18.',
|
warning: 'User must be at least 18.',
|
||||||
range: '[18,*)',
|
range: '[18,*)',
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ export module RR {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type WithExpire<T> = { 'expire-id'?: string } & T
|
export type WithExpire<T> = { 'expire-id'?: string } & T
|
||||||
export type WithRevision<T> = { response: T; revision?: Revision }
|
export type WithRevision<T> = { response: T | null; revision?: Revision }
|
||||||
|
|
||||||
export interface MarketplaceEOS {
|
export interface MarketplaceEOS {
|
||||||
version: string
|
version: string
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export class MockApiService extends ApiService {
|
|||||||
|
|
||||||
async killSessions(params: RR.KillSessionsReq): Promise<RR.KillSessionsRes> {
|
async killSessions(params: RR.KillSessionsReq): Promise<RR.KillSessionsRes> {
|
||||||
await pauseFor(2000)
|
await pauseFor(2000)
|
||||||
return null
|
return { response: null }
|
||||||
}
|
}
|
||||||
|
|
||||||
// server
|
// server
|
||||||
@@ -749,13 +749,14 @@ export class MockApiService extends ApiService {
|
|||||||
{ progress: 'downloaded', completion: 'download-complete' },
|
{ progress: 'downloaded', completion: 'download-complete' },
|
||||||
{ progress: 'validated', completion: 'validation-complete' },
|
{ progress: 'validated', completion: 'validation-complete' },
|
||||||
{ progress: 'unpacked', completion: 'unpack-complete' },
|
{ progress: 'unpacked', completion: 'unpack-complete' },
|
||||||
]
|
] as const
|
||||||
|
|
||||||
for (let phase of phases) {
|
for (let phase of phases) {
|
||||||
let i = progress[phase.progress]
|
let i = progress[phase.progress]
|
||||||
while (i < progress.size) {
|
const size = progress?.size || 0
|
||||||
|
while (i < size) {
|
||||||
await pauseFor(250)
|
await pauseFor(250)
|
||||||
i = Math.min(i + 5, progress.size)
|
i = Math.min(i + 5, size)
|
||||||
progress[phase.progress] = i
|
progress[phase.progress] = i
|
||||||
|
|
||||||
if (i === progress.size) {
|
if (i === progress.size) {
|
||||||
@@ -858,7 +859,7 @@ export class MockApiService extends ApiService {
|
|||||||
|
|
||||||
private async withRevision<T>(
|
private async withRevision<T>(
|
||||||
patch: Operation<unknown>[],
|
patch: Operation<unknown>[],
|
||||||
response: T = null,
|
response: T | null = null,
|
||||||
): Promise<WithRevision<T>> {
|
): Promise<WithRevision<T>> {
|
||||||
if (!this.sequence) {
|
if (!this.sequence) {
|
||||||
const { sequence } = await this.bootstrapper.init()
|
const { sequence } = await this.bootstrapper.init()
|
||||||
|
|||||||
@@ -11,12 +11,9 @@ import {
|
|||||||
export const mockPatchData: DataModel = {
|
export const mockPatchData: DataModel = {
|
||||||
ui: {
|
ui: {
|
||||||
name: `Matt's Embassy`,
|
name: `Matt's Embassy`,
|
||||||
'auto-check-updates': undefined,
|
'auto-check-updates': false,
|
||||||
'pkg-order': [],
|
'pkg-order': [],
|
||||||
'ack-welcome': '1.0.0',
|
'ack-welcome': '1.0.0',
|
||||||
marketplace: undefined,
|
|
||||||
dev: undefined,
|
|
||||||
gaming: undefined,
|
|
||||||
},
|
},
|
||||||
'server-info': {
|
'server-info': {
|
||||||
id: 'abcdefgh',
|
id: 'abcdefgh',
|
||||||
@@ -212,8 +209,6 @@ export const mockPatchData: DataModel = {
|
|||||||
nullable: true,
|
nullable: true,
|
||||||
masked: false,
|
masked: false,
|
||||||
copyable: false,
|
copyable: false,
|
||||||
pattern: null,
|
|
||||||
'pattern-description': null,
|
|
||||||
warning: 'You may loose all your money by providing your name.',
|
warning: 'You may loose all your money by providing your name.',
|
||||||
},
|
},
|
||||||
notifications: {
|
notifications: {
|
||||||
@@ -248,7 +243,6 @@ export const mockPatchData: DataModel = {
|
|||||||
name: 'Top Speed',
|
name: 'Top Speed',
|
||||||
description: 'The fastest you can possibly run.',
|
description: 'The fastest you can possibly run.',
|
||||||
nullable: false,
|
nullable: false,
|
||||||
default: null,
|
|
||||||
range: '[-1000, 1000]',
|
range: '[-1000, 1000]',
|
||||||
integral: false,
|
integral: false,
|
||||||
units: 'm/s',
|
units: 'm/s',
|
||||||
@@ -283,7 +277,6 @@ export const mockPatchData: DataModel = {
|
|||||||
name: {
|
name: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
description: null,
|
|
||||||
nullable: false,
|
nullable: false,
|
||||||
masked: false,
|
masked: false,
|
||||||
copyable: false,
|
copyable: false,
|
||||||
@@ -293,7 +286,6 @@ export const mockPatchData: DataModel = {
|
|||||||
email: {
|
email: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
name: 'Email',
|
name: 'Email',
|
||||||
description: null,
|
|
||||||
nullable: false,
|
nullable: false,
|
||||||
masked: false,
|
masked: false,
|
||||||
copyable: true,
|
copyable: true,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export class ConfigService {
|
|||||||
api = api
|
api = api
|
||||||
marketplace = marketplace
|
marketplace = marketplace
|
||||||
skipStartupAlerts = useMocks && mocks.skipStartupAlerts
|
skipStartupAlerts = useMocks && mocks.skipStartupAlerts
|
||||||
isConsulate = window['platform'] === 'ios'
|
isConsulate = (window as any)['platform'] === 'ios'
|
||||||
supportsWebSockets = !!window.WebSocket || this.isConsulate
|
supportsWebSockets = !!window.WebSocket || this.isConsulate
|
||||||
|
|
||||||
isTor(): boolean {
|
isTor(): boolean {
|
||||||
@@ -76,14 +76,20 @@ export function hasLanUi(interfaces: Record<string, InterfaceDef>): boolean {
|
|||||||
return !!int?.['lan-config']
|
return !!int?.['lan-config']
|
||||||
}
|
}
|
||||||
|
|
||||||
export function torUiAddress(pkg: PackageDataEntry): string {
|
export function torUiAddress({
|
||||||
const key = getUiInterfaceKey(pkg.manifest.interfaces)
|
manifest,
|
||||||
return pkg.installed['interface-addresses'][key]['tor-address']
|
installed,
|
||||||
|
}: PackageDataEntry): string {
|
||||||
|
const key = getUiInterfaceKey(manifest.interfaces)
|
||||||
|
return installed ? installed['interface-addresses'][key]['tor-address'] : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
export function lanUiAddress(pkg: PackageDataEntry): string {
|
export function lanUiAddress({
|
||||||
const key = getUiInterfaceKey(pkg.manifest.interfaces)
|
manifest,
|
||||||
return pkg.installed['interface-addresses'][key]['lan-address']
|
installed,
|
||||||
|
}: PackageDataEntry): string {
|
||||||
|
const key = getUiInterfaceKey(manifest.interfaces)
|
||||||
|
return installed ? installed['interface-addresses'][key]['lan-address'] : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasUi(interfaces: Record<string, InterfaceDef>): boolean {
|
export function hasUi(interfaces: Record<string, InterfaceDef>): boolean {
|
||||||
@@ -103,11 +109,11 @@ export function removePort(str: string): string {
|
|||||||
export function getUiInterfaceKey(
|
export function getUiInterfaceKey(
|
||||||
interfaces: Record<string, InterfaceDef>,
|
interfaces: Record<string, InterfaceDef>,
|
||||||
): string {
|
): string {
|
||||||
return Object.keys(interfaces).find(key => interfaces[key].ui)
|
return Object.keys(interfaces).find(key => interfaces[key].ui) || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUiInterfaceValue(
|
export function getUiInterfaceValue(
|
||||||
interfaces: Record<string, InterfaceDef>,
|
interfaces: Record<string, InterfaceDef>,
|
||||||
): InterfaceDef {
|
): InterfaceDef | null {
|
||||||
return Object.values(interfaces).find(i => i.ui)
|
return Object.values(interfaces).find(i => i.ui) || null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export class FormService {
|
|||||||
validators: ValidatorFn[] = [],
|
validators: ValidatorFn[] = [],
|
||||||
current: { [key: string]: any } = {},
|
current: { [key: string]: any } = {},
|
||||||
): FormGroup {
|
): FormGroup {
|
||||||
let group = {}
|
let group: Record<string, FormGroup | FormArray | FormControl> = {}
|
||||||
Object.entries(config).map(([key, spec]) => {
|
Object.entries(config).map(([key, spec]) => {
|
||||||
if (spec.type === 'pointer') return
|
if (spec.type === 'pointer') return
|
||||||
group[key] = this.getFormEntry(spec, current ? current[key] : undefined)
|
group[key] = this.getFormEntry(spec, current ? current[key] : undefined)
|
||||||
@@ -137,6 +137,8 @@ export class FormService {
|
|||||||
case 'enum':
|
case 'enum':
|
||||||
value = currentValue === undefined ? spec.default : currentValue
|
value = currentValue === undefined ? spec.default : currentValue
|
||||||
return this.formBuilder.control(value)
|
return this.formBuilder.control(value)
|
||||||
|
default:
|
||||||
|
return this.formBuilder.control(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,25 +300,20 @@ export function listUnique(spec: ValueSpecList): ValidatorFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function listItemEquals(spec: ValueSpecList, val1: any, val2: any): boolean {
|
function listItemEquals(spec: ValueSpecList, val1: any, val2: any): boolean {
|
||||||
|
// TODO: fix types
|
||||||
switch (spec.subtype) {
|
switch (spec.subtype) {
|
||||||
case 'string':
|
case 'string':
|
||||||
case 'number':
|
case 'number':
|
||||||
case 'enum':
|
case 'enum':
|
||||||
return val1 == val2
|
return val1 == val2
|
||||||
case 'object':
|
case 'object':
|
||||||
return listObjEquals(
|
const obj: ListValueSpecObject = spec.spec as any
|
||||||
spec.spec['unique-by'],
|
|
||||||
spec.spec as ListValueSpecObject,
|
return listObjEquals(obj['unique-by'], obj, val1, val2)
|
||||||
val1,
|
|
||||||
val2,
|
|
||||||
)
|
|
||||||
case 'union':
|
case 'union':
|
||||||
return unionEquals(
|
const union: ListValueSpecUnion = spec.spec as any
|
||||||
spec.spec['unique-by'],
|
|
||||||
spec.spec as ListValueSpecUnion,
|
return unionEquals(union['unique-by'], union, val1, val2)
|
||||||
val1,
|
|
||||||
val2,
|
|
||||||
)
|
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -330,9 +327,21 @@ function itemEquals(spec: ValueSpec, val1: any, val2: any): boolean {
|
|||||||
case 'enum':
|
case 'enum':
|
||||||
return val1 == val2
|
return val1 == val2
|
||||||
case 'object':
|
case 'object':
|
||||||
return objEquals(spec['unique-by'], spec as ValueSpecObject, val1, val2)
|
// TODO: 'unique-by' does not exist on ValueSpecObject, fix types
|
||||||
|
return objEquals(
|
||||||
|
(spec as any)['unique-by'],
|
||||||
|
spec as ValueSpecObject,
|
||||||
|
val1,
|
||||||
|
val2,
|
||||||
|
)
|
||||||
case 'union':
|
case 'union':
|
||||||
return unionEquals(spec['unique-by'], spec as ValueSpecUnion, val1, val2)
|
// TODO: 'unique-by' does not exist on ValueSpecUnion, fix types
|
||||||
|
return unionEquals(
|
||||||
|
(spec as any)['unique-by'],
|
||||||
|
spec as ValueSpecUnion,
|
||||||
|
val1,
|
||||||
|
val2,
|
||||||
|
)
|
||||||
case 'list':
|
case 'list':
|
||||||
if (val1.length !== val2.length) {
|
if (val1.length !== val2.length) {
|
||||||
return false
|
return false
|
||||||
@@ -373,6 +382,7 @@ function listObjEquals(
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
function objEquals(
|
function objEquals(
|
||||||
@@ -384,7 +394,8 @@ function objEquals(
|
|||||||
if (uniqueBy === null) {
|
if (uniqueBy === null) {
|
||||||
return false
|
return false
|
||||||
} else if (typeof uniqueBy === 'string') {
|
} else if (typeof uniqueBy === 'string') {
|
||||||
return itemEquals(spec[uniqueBy], val1[uniqueBy], val2[uniqueBy])
|
// TODO: fix types
|
||||||
|
return itemEquals((spec as any)[uniqueBy], val1[uniqueBy], val2[uniqueBy])
|
||||||
} else if ('any' in uniqueBy) {
|
} else if ('any' in uniqueBy) {
|
||||||
for (let subSpec of uniqueBy.any) {
|
for (let subSpec of uniqueBy.any) {
|
||||||
if (objEquals(subSpec, spec, val1, val2)) {
|
if (objEquals(subSpec, spec, val1, val2)) {
|
||||||
@@ -400,6 +411,7 @@ function objEquals(
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
function unionEquals(
|
function unionEquals(
|
||||||
@@ -433,12 +445,13 @@ function unionEquals(
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
function uniqueByMessageWrapper(
|
function uniqueByMessageWrapper(
|
||||||
uniqueBy: UniqueBy,
|
uniqueBy: UniqueBy,
|
||||||
spec: ListValueSpecObject | ListValueSpecUnion,
|
spec: ListValueSpecObject | ListValueSpecUnion,
|
||||||
obj: object,
|
obj: Record<string, string>,
|
||||||
) {
|
) {
|
||||||
let configSpec: ConfigSpec
|
let configSpec: ConfigSpec
|
||||||
if (isUnion(spec)) {
|
if (isUnion(spec)) {
|
||||||
@@ -460,9 +473,9 @@ function uniqueByMessage(
|
|||||||
outermost = true,
|
outermost = true,
|
||||||
): string {
|
): string {
|
||||||
let joinFunc
|
let joinFunc
|
||||||
const subSpecs = []
|
const subSpecs: string[] = []
|
||||||
if (uniqueBy === null) {
|
if (uniqueBy === null) {
|
||||||
return null
|
return ''
|
||||||
} else if (typeof uniqueBy === 'string') {
|
} else if (typeof uniqueBy === 'string') {
|
||||||
return configSpec[uniqueBy] ? configSpec[uniqueBy].name : uniqueBy
|
return configSpec[uniqueBy] ? configSpec[uniqueBy].name : uniqueBy
|
||||||
} else if ('any' in uniqueBy) {
|
} else if ('any' in uniqueBy) {
|
||||||
@@ -476,7 +489,7 @@ function uniqueByMessage(
|
|||||||
subSpecs.push(uniqueByMessage(subSpec, configSpec, false))
|
subSpecs.push(uniqueByMessage(subSpec, configSpec, false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const ret = subSpecs.filter(ss => ss).join(joinFunc)
|
const ret = subSpecs.filter(Boolean).join(joinFunc)
|
||||||
return outermost || subSpecs.filter(ss => ss).length === 1
|
return outermost || subSpecs.filter(ss => ss).length === 1
|
||||||
? ret
|
? ret
|
||||||
: '(' + ret + ')'
|
: '(' + ret + ')'
|
||||||
@@ -486,7 +499,7 @@ function isObjectOrUnion(
|
|||||||
spec: ListValueSpecOf<any>,
|
spec: ListValueSpecOf<any>,
|
||||||
): spec is ListValueSpecObject | ListValueSpecUnion {
|
): spec is ListValueSpecObject | ListValueSpecUnion {
|
||||||
// only lists of objects and unions have unique-by
|
// only lists of objects and unions have unique-by
|
||||||
return spec['unique-by'] !== undefined
|
return 'unique-by' in spec
|
||||||
}
|
}
|
||||||
|
|
||||||
function isUnion(spec: any): spec is ListValueSpecUnion {
|
function isUnion(spec: any): spec is ListValueSpecUnion {
|
||||||
@@ -499,18 +512,20 @@ export function convertValuesRecursive(
|
|||||||
group: FormGroup,
|
group: FormGroup,
|
||||||
) {
|
) {
|
||||||
Object.entries(configSpec).forEach(([key, valueSpec]) => {
|
Object.entries(configSpec).forEach(([key, valueSpec]) => {
|
||||||
|
const control = group.get(key)
|
||||||
|
|
||||||
|
if (!control) return
|
||||||
|
|
||||||
if (valueSpec.type === 'number') {
|
if (valueSpec.type === 'number') {
|
||||||
const control = group.get(key)
|
|
||||||
control.setValue(control.value ? Number(control.value) : null)
|
control.setValue(control.value ? Number(control.value) : null)
|
||||||
} else if (valueSpec.type === 'string') {
|
} else if (valueSpec.type === 'string') {
|
||||||
const control = group.get(key)
|
|
||||||
if (!control.value) control.setValue(null)
|
if (!control.value) control.setValue(null)
|
||||||
} else if (valueSpec.type === 'object') {
|
} else if (valueSpec.type === 'object') {
|
||||||
convertValuesRecursive(valueSpec.spec, group.get(key) as FormGroup)
|
convertValuesRecursive(valueSpec.spec, group.get(key) as FormGroup)
|
||||||
} else if (valueSpec.type === 'union') {
|
} else if (valueSpec.type === 'union') {
|
||||||
const control = group.get(key) as FormGroup
|
const formGr = group.get(key) as FormGroup
|
||||||
const spec = valueSpec.variants[control.controls[valueSpec.tag.id].value]
|
const spec = valueSpec.variants[formGr.controls[valueSpec.tag.id].value]
|
||||||
convertValuesRecursive(spec, control)
|
convertValuesRecursive(spec, formGr)
|
||||||
} else if (valueSpec.type === 'list') {
|
} else if (valueSpec.type === 'list') {
|
||||||
const formArr = group.get(key) as FormArray
|
const formArr = group.get(key) as FormArray
|
||||||
const { controls } = formArr
|
const { controls } = formArr
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import {
|
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'
|
||||||
HttpClient,
|
|
||||||
HttpErrorResponse,
|
|
||||||
HttpHeaders,
|
|
||||||
HttpParams,
|
|
||||||
} from '@angular/common/http'
|
|
||||||
import { Observable, from, interval, race } from 'rxjs'
|
import { Observable, from, interval, race } from 'rxjs'
|
||||||
import { map, take } from 'rxjs/operators'
|
import { map, take } from 'rxjs/operators'
|
||||||
import { ConfigService } from './config.service'
|
import { ConfigService } from './config.service'
|
||||||
@@ -27,6 +22,7 @@ export class HttpService {
|
|||||||
this.fullUrl = `${window.location.protocol}//${window.location.hostname}:${port}`
|
this.fullUrl = `${window.location.protocol}//${window.location.hostname}:${port}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore TODO: fix typing
|
||||||
async rpcRequest<T>(rpcOpts: RPCOptions): Promise<T> {
|
async rpcRequest<T>(rpcOpts: RPCOptions): Promise<T> {
|
||||||
const { url, version } = this.config.api
|
const { url, version } = this.config.api
|
||||||
rpcOpts.params = rpcOpts.params || {}
|
rpcOpts.params = rpcOpts.params || {}
|
||||||
@@ -53,12 +49,15 @@ export class HttpService {
|
|||||||
|
|
||||||
const urlIsRelative = httpOpts.url.startsWith('/')
|
const urlIsRelative = httpOpts.url.startsWith('/')
|
||||||
const url = urlIsRelative ? this.fullUrl + httpOpts.url : httpOpts.url
|
const url = urlIsRelative ? this.fullUrl + httpOpts.url : httpOpts.url
|
||||||
|
const { params } = httpOpts
|
||||||
|
|
||||||
Object.keys(httpOpts.params || {}).forEach(key => {
|
if (hasParams(params)) {
|
||||||
if (httpOpts.params[key] === undefined) {
|
Object.keys(params).forEach(key => {
|
||||||
delete httpOpts.params[key]
|
if (params[key] === undefined) {
|
||||||
}
|
delete params[key]
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
responseType: httpOpts.responseType || 'json',
|
responseType: httpOpts.responseType || 'json',
|
||||||
@@ -181,6 +180,12 @@ export interface HttpOptions {
|
|||||||
timeout?: number
|
timeout?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasParams(
|
||||||
|
params?: HttpOptions['params'],
|
||||||
|
): params is Record<string, string | string[]> {
|
||||||
|
return !!params
|
||||||
|
}
|
||||||
|
|
||||||
function withTimeout<U>(req: Observable<U>, timeout: number): Observable<U> {
|
function withTimeout<U>(req: Observable<U>, timeout: number): Observable<U> {
|
||||||
return race(
|
return race(
|
||||||
from(req.toPromise()), // this guarantees it only emits on completion, intermediary emissions are suppressed.
|
from(req.toPromise()), // this guarantees it only emits on completion, intermediary emissions are suppressed.
|
||||||
|
|||||||
@@ -5,16 +5,20 @@ import {
|
|||||||
MarketplacePkg,
|
MarketplacePkg,
|
||||||
AbstractMarketplaceService,
|
AbstractMarketplaceService,
|
||||||
Marketplace,
|
Marketplace,
|
||||||
MarketplaceData,
|
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
import { defer, from, Observable, of } from 'rxjs'
|
import { defer, from, Observable, of } from 'rxjs'
|
||||||
import { RR } from 'src/app/services/api/api.types'
|
import { RR } from 'src/app/services/api/api.types'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
import { ServerInfo } from 'src/app/services/patch-db/data-model'
|
import {
|
||||||
|
ServerInfo,
|
||||||
|
UIMarketplaceData,
|
||||||
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||||
import {
|
import {
|
||||||
catchError,
|
catchError,
|
||||||
|
distinctUntilChanged,
|
||||||
|
filter,
|
||||||
map,
|
map,
|
||||||
shareReplay,
|
shareReplay,
|
||||||
startWith,
|
startWith,
|
||||||
@@ -27,35 +31,56 @@ import {
|
|||||||
export class MarketplaceService extends AbstractMarketplaceService {
|
export class MarketplaceService extends AbstractMarketplaceService {
|
||||||
private readonly notes = new Map<string, Record<string, string>>()
|
private readonly notes = new Map<string, Record<string, string>>()
|
||||||
|
|
||||||
private readonly init$: Observable<Marketplace> = this.patch
|
private readonly altMarketplaceData$: Observable<
|
||||||
.watch$('ui', 'marketplace')
|
UIMarketplaceData | undefined
|
||||||
.pipe(
|
> = this.patch.watch$('ui').pipe(
|
||||||
map(marketplace =>
|
map(ui => ui.marketplace),
|
||||||
marketplace?.['selected-id']
|
distinctUntilChanged(),
|
||||||
? marketplace['known-hosts'][marketplace['selected-id']]
|
shareReplay({ bufferSize: 1, refCount: true }),
|
||||||
: this.config.marketplace,
|
)
|
||||||
),
|
|
||||||
shareReplay(),
|
|
||||||
)
|
|
||||||
|
|
||||||
private readonly data$: Observable<MarketplaceData> = this.init$.pipe(
|
private readonly marketplace$ = this.altMarketplaceData$.pipe(
|
||||||
|
map(data => this.toMarketplace(data)),
|
||||||
|
)
|
||||||
|
|
||||||
|
private readonly serverInfo$: Observable<ServerInfo> = this.patch
|
||||||
|
.watch$('server-info')
|
||||||
|
.pipe(take(1), shareReplay({ bufferSize: 1, refCount: true }))
|
||||||
|
|
||||||
|
private readonly categories$: Observable<string[]> = this.marketplace$.pipe(
|
||||||
switchMap(({ url }) =>
|
switchMap(({ url }) =>
|
||||||
from(this.getMarketplaceData({ 'server-id': this.serverInfo.id }, url)),
|
this.serverInfo$.pipe(
|
||||||
),
|
switchMap(({ id }) =>
|
||||||
shareReplay(),
|
from(this.getMarketplaceData({ 'server-id': id }, url)),
|
||||||
)
|
),
|
||||||
|
|
||||||
private readonly pkg$: Observable<MarketplacePkg[]> = this.init$.pipe(
|
|
||||||
take(1),
|
|
||||||
switchMap(({ url, name }) =>
|
|
||||||
from(this.getMarketplacePkgs({ page: 1, 'per-page': 100 }, url)).pipe(
|
|
||||||
tap(() => this.onPackages(name)),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
shareReplay(),
|
map(({ categories }) => categories),
|
||||||
catchError(e => this.errToast.present(e) && of([])),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private readonly pkg$: Observable<MarketplacePkg[]> =
|
||||||
|
this.altMarketplaceData$.pipe(
|
||||||
|
switchMap(data =>
|
||||||
|
this.serverInfo$.pipe(
|
||||||
|
switchMap(info =>
|
||||||
|
from(
|
||||||
|
this.getMarketplacePkgs(
|
||||||
|
{ page: 1, 'per-page': 100 },
|
||||||
|
this.toMarketplace(data).url,
|
||||||
|
info['eos-version-compat'],
|
||||||
|
),
|
||||||
|
).pipe(tap(() => this.onPackages(data))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
catchError(e => {
|
||||||
|
this.errToast.present(e)
|
||||||
|
|
||||||
|
return of([])
|
||||||
|
}),
|
||||||
|
shareReplay({ bufferSize: 1, refCount: true }),
|
||||||
|
)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly api: ApiService,
|
private readonly api: ApiService,
|
||||||
private readonly patch: PatchDbService,
|
private readonly patch: PatchDbService,
|
||||||
@@ -68,22 +93,29 @@ export class MarketplaceService extends AbstractMarketplaceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getMarketplace(): Observable<Marketplace> {
|
getMarketplace(): Observable<Marketplace> {
|
||||||
return this.init$
|
return this.marketplace$
|
||||||
}
|
}
|
||||||
|
|
||||||
getCategories(): Observable<string[]> {
|
getCategories(): Observable<string[]> {
|
||||||
return this.data$.pipe(map(({ categories }) => categories))
|
return this.categories$
|
||||||
}
|
}
|
||||||
|
|
||||||
getPackages(): Observable<MarketplacePkg[]> {
|
getPackages(): Observable<MarketplacePkg[]> {
|
||||||
return this.pkg$
|
return this.pkg$
|
||||||
}
|
}
|
||||||
|
|
||||||
getPackage(id: string, version: string): Observable<MarketplacePkg> {
|
getPackage(id: string, version: string): Observable<MarketplacePkg | null> {
|
||||||
const params = { ids: [{ id, version }] }
|
const params = { ids: [{ id, version }] }
|
||||||
const fallback$ = this.init$.pipe(
|
const fallback$ = this.marketplace$.pipe(
|
||||||
take(1),
|
switchMap(({ url }) =>
|
||||||
switchMap(({ url }) => from(this.getMarketplacePkgs(params, url))),
|
this.serverInfo$.pipe(
|
||||||
|
switchMap(info =>
|
||||||
|
from(
|
||||||
|
this.getMarketplacePkgs(params, url, info['eos-version-compat']),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
map(pkgs => this.findPackage(pkgs, id, version)),
|
map(pkgs => this.findPackage(pkgs, id, version)),
|
||||||
startWith(null),
|
startWith(null),
|
||||||
)
|
)
|
||||||
@@ -91,23 +123,29 @@ export class MarketplaceService extends AbstractMarketplaceService {
|
|||||||
return this.getPackages().pipe(
|
return this.getPackages().pipe(
|
||||||
map(pkgs => this.findPackage(pkgs, id, version)),
|
map(pkgs => this.findPackage(pkgs, id, version)),
|
||||||
switchMap(pkg => (pkg ? of(pkg) : fallback$)),
|
switchMap(pkg => (pkg ? of(pkg) : fallback$)),
|
||||||
tap(pkg => {
|
filter((pkg): pkg is MarketplacePkg | null => {
|
||||||
if (pkg === undefined) {
|
if (pkg === undefined) {
|
||||||
throw new Error(`No results for ${id}${version ? ' ' + version : ''}`)
|
throw new Error(`No results for ${id}${version ? ' ' + version : ''}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
getReleaseNotes(id: string): Observable<Record<string, string>> {
|
getReleaseNotes(id: string): Observable<Record<string, string>> {
|
||||||
if (this.notes.has(id)) {
|
if (this.notes.has(id)) {
|
||||||
return of(this.notes.get(id))
|
return of(this.notes.get(id) || {})
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.init$.pipe(
|
return this.marketplace$.pipe(
|
||||||
switchMap(({ url }) => this.loadReleaseNotes(id, url)),
|
switchMap(({ url }) => this.loadReleaseNotes(id, url)),
|
||||||
tap(response => this.notes.set(id, response)),
|
tap(response => this.notes.set(id, response)),
|
||||||
catchError(e => this.errToast.present(e) && of({})),
|
catchError(e => {
|
||||||
|
this.errToast.present(e)
|
||||||
|
|
||||||
|
return of({})
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,22 +211,19 @@ export class MarketplaceService extends AbstractMarketplaceService {
|
|||||||
async getMarketplacePkgs(
|
async getMarketplacePkgs(
|
||||||
params: Omit<RR.GetMarketplacePackagesReq, 'eos-version-compat'>,
|
params: Omit<RR.GetMarketplacePackagesReq, 'eos-version-compat'>,
|
||||||
url: string,
|
url: string,
|
||||||
|
eosVersionCompat: string,
|
||||||
): Promise<RR.GetMarketplacePackagesRes> {
|
): Promise<RR.GetMarketplacePackagesRes> {
|
||||||
if (params.query) delete params.category
|
if (params.query) delete params.category
|
||||||
if (params.ids) params.ids = JSON.stringify(params.ids)
|
if (params.ids) params.ids = JSON.stringify(params.ids)
|
||||||
|
|
||||||
const qp: RR.GetMarketplacePackagesReq = {
|
const qp: RR.GetMarketplacePackagesReq = {
|
||||||
...params,
|
...params,
|
||||||
'eos-version-compat': this.serverInfo['eos-version-compat'],
|
'eos-version-compat': eosVersionCompat,
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.api.marketplaceProxy('/package/v0/index', qp, url)
|
return this.api.marketplaceProxy('/package/v0/index', qp, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
private get serverInfo(): ServerInfo {
|
|
||||||
return this.patch.getData()['server-info']
|
|
||||||
}
|
|
||||||
|
|
||||||
private loadReleaseNotes(
|
private loadReleaseNotes(
|
||||||
id: string,
|
id: string,
|
||||||
url: string,
|
url: string,
|
||||||
@@ -202,15 +237,15 @@ export class MarketplaceService extends AbstractMarketplaceService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private onPackages(name: string) {
|
private onPackages(data?: UIMarketplaceData) {
|
||||||
const { marketplace } = this.patch.getData().ui
|
const { name } = this.toMarketplace(data)
|
||||||
|
|
||||||
if (!marketplace?.['selected-id']) {
|
if (!data?.['selected-id']) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedId = marketplace['selected-id']
|
const selectedId = data['selected-id']
|
||||||
const knownHosts = marketplace['known-hosts']
|
const knownHosts = data['known-hosts']
|
||||||
|
|
||||||
if (knownHosts[selectedId].name !== name) {
|
if (knownHosts[selectedId].name !== name) {
|
||||||
this.api.setDbValue({
|
this.api.setDbValue({
|
||||||
@@ -220,6 +255,12 @@ export class MarketplaceService extends AbstractMarketplaceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private toMarketplace(marketplace?: UIMarketplaceData): Marketplace {
|
||||||
|
return marketplace?.['selected-id']
|
||||||
|
? marketplace['known-hosts'][marketplace['selected-id']]
|
||||||
|
: this.config.marketplace
|
||||||
|
}
|
||||||
|
|
||||||
private findPackage(
|
private findPackage(
|
||||||
pkgs: readonly MarketplacePkg[],
|
pkgs: readonly MarketplacePkg[],
|
||||||
id: string,
|
id: string,
|
||||||
|
|||||||
@@ -17,15 +17,13 @@ export interface UIData {
|
|||||||
'auto-check-updates': boolean
|
'auto-check-updates': boolean
|
||||||
'pkg-order': string[]
|
'pkg-order': string[]
|
||||||
'ack-welcome': string // EOS version
|
'ack-welcome': string // EOS version
|
||||||
marketplace: UIMarketplaceData
|
marketplace?: UIMarketplaceData
|
||||||
dev: DevData
|
dev?: DevData
|
||||||
gaming:
|
gaming?: {
|
||||||
| {
|
snake: {
|
||||||
snake: {
|
'high-score': number
|
||||||
'high-score': number
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
| undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UIMarketplaceData {
|
export interface UIMarketplaceData {
|
||||||
@@ -113,7 +111,7 @@ export interface CurrentDependencyInfo {
|
|||||||
'health-checks': string[] // array of health check IDs
|
'health-checks': string[] // array of health check IDs
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Manifest extends MarketplaceManifest<DependencyConfig> {
|
export interface Manifest extends MarketplaceManifest<DependencyConfig | null> {
|
||||||
main: ActionImpl
|
main: ActionImpl
|
||||||
'health-checks': Record<
|
'health-checks': Record<
|
||||||
string,
|
string,
|
||||||
@@ -124,7 +122,7 @@ export interface Manifest extends MarketplaceManifest<DependencyConfig> {
|
|||||||
'min-os-version': string
|
'min-os-version': string
|
||||||
interfaces: Record<string, InterfaceDef>
|
interfaces: Record<string, InterfaceDef>
|
||||||
backup: BackupActions
|
backup: BackupActions
|
||||||
migrations: Migrations
|
migrations: Migrations | null
|
||||||
actions: Record<string, Action>
|
actions: Record<string, Action>
|
||||||
permissions: any // @TODO 0.3.1
|
permissions: any // @TODO 0.3.1
|
||||||
}
|
}
|
||||||
@@ -155,8 +153,8 @@ export enum DockerIoFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ConfigActions {
|
export interface ConfigActions {
|
||||||
get: ActionImpl
|
get: ActionImpl | null
|
||||||
set: ActionImpl
|
set: ActionImpl | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Volume = VolumeData
|
export type Volume = VolumeData
|
||||||
@@ -229,7 +227,7 @@ export interface Action {
|
|||||||
warning: string | null
|
warning: string | null
|
||||||
implementation: ActionImpl
|
implementation: ActionImpl
|
||||||
'allowed-statuses': (PackageMainStatus.Stopped | PackageMainStatus.Running)[]
|
'allowed-statuses': (PackageMainStatus.Stopped | PackageMainStatus.Running)[]
|
||||||
'input-spec': ConfigSpec
|
'input-spec': ConfigSpec | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Status {
|
export interface Status {
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ export function realSourceFactory(
|
|||||||
{ defaultView }: Document,
|
{ defaultView }: Document,
|
||||||
): Source<DataModel>[] {
|
): Source<DataModel>[] {
|
||||||
const { patchDb } = config
|
const { patchDb } = config
|
||||||
const { host } = defaultView.location
|
const host = defaultView?.location.host
|
||||||
const protocol = defaultView.location.protocol === 'http:' ? 'ws' : 'wss'
|
const protocol = defaultView?.location.protocol === 'http:' ? 'ws' : 'wss'
|
||||||
|
|
||||||
return [
|
return [
|
||||||
new WebsocketSource<DataModel>(`${protocol}://${host}/ws/db`),
|
new WebsocketSource<DataModel>(`${protocol}://${host}/ws/db`),
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export function renderPkgStatus(pkg: PackageDataEntry): PackageStatus {
|
|||||||
let dependency: DependencyStatus | null = null
|
let dependency: DependencyStatus | null = null
|
||||||
let health: HealthStatus | null = null
|
let health: HealthStatus | null = null
|
||||||
|
|
||||||
if (pkg.state === PackageState.Installed) {
|
if (pkg.state === PackageState.Installed && pkg.installed) {
|
||||||
primary = getPrimaryStatus(pkg.installed.status)
|
primary = getPrimaryStatus(pkg.installed.status)
|
||||||
dependency = getDependencyStatus(pkg)
|
dependency = getDependencyStatus(pkg)
|
||||||
health = getHealthStatus(pkg.installed.status)
|
health = getHealthStatus(pkg.installed.status)
|
||||||
@@ -36,9 +36,10 @@ function getPrimaryStatus(status: Status): PrimaryStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDependencyStatus(pkg: PackageDataEntry): DependencyStatus {
|
function getDependencyStatus(pkg: PackageDataEntry): DependencyStatus | null {
|
||||||
const installed = pkg.installed
|
const installed = pkg.installed
|
||||||
if (isEmptyObject(installed['current-dependencies'])) return null
|
if (!installed || isEmptyObject(installed['current-dependencies']))
|
||||||
|
return null
|
||||||
|
|
||||||
const depErrors = installed.status['dependency-errors']
|
const depErrors = installed.status['dependency-errors']
|
||||||
const depIds = Object.keys(depErrors).filter(key => !!depErrors[key])
|
const depIds = Object.keys(depErrors).filter(key => !!depErrors[key])
|
||||||
@@ -46,9 +47,9 @@ function getDependencyStatus(pkg: PackageDataEntry): DependencyStatus {
|
|||||||
return depIds.length ? DependencyStatus.Warning : DependencyStatus.Satisfied
|
return depIds.length ? DependencyStatus.Warning : DependencyStatus.Satisfied
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHealthStatus(status: Status): HealthStatus {
|
function getHealthStatus(status: Status): HealthStatus | null {
|
||||||
if (status.main.status !== PackageMainStatus.Running || !status.main.health) {
|
if (status.main.status !== PackageMainStatus.Running || !status.main.health) {
|
||||||
return
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const values = Object.values(status.main.health)
|
const values = Object.values(status.main.health)
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ export class ServerConfigService {
|
|||||||
private readonly embassyApi: ApiService,
|
private readonly embassyApi: ApiService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async presentAlert(key: string, current?: any): Promise<HTMLIonAlertElement> {
|
async presentAlert(
|
||||||
|
key: string,
|
||||||
|
current?: any,
|
||||||
|
): Promise<HTMLIonAlertElement | null> {
|
||||||
const spec = serverConfig[key]
|
const spec = serverConfig[key]
|
||||||
|
|
||||||
let inputs: AlertInput[]
|
let inputs: AlertInput[]
|
||||||
@@ -66,7 +69,7 @@ export class ServerConfigService {
|
|||||||
]
|
]
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
return
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const alert = await this.alertCtrl.create({
|
const alert = await this.alertCtrl.create({
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export class UiLauncherService {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
launch(pkg: PackageDataEntry): void {
|
launch(pkg: PackageDataEntry): void {
|
||||||
this.document.defaultView.open(
|
this.document.defaultView?.open(
|
||||||
this.config.launchableURL(pkg),
|
this.config.launchableURL(pkg),
|
||||||
'_blank',
|
'_blank',
|
||||||
'noreferrer',
|
'noreferrer',
|
||||||
|
|||||||
11
frontend/projects/ui/src/app/util/get-project-id.ts
Normal file
11
frontend/projects/ui/src/app/util/get-project-id.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { ActivatedRoute } from '@angular/router'
|
||||||
|
|
||||||
|
export function getProjectId({ snapshot }: ActivatedRoute): string {
|
||||||
|
const projectId = snapshot.paramMap.get('projectId')
|
||||||
|
|
||||||
|
if (!projectId) {
|
||||||
|
throw new Error('projectId is missing from route params')
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectId
|
||||||
|
}
|
||||||
@@ -3,9 +3,9 @@ import { InstallProgress } from 'src/app/types/install-progress'
|
|||||||
import { ProgressData } from 'src/app/types/progress-data'
|
import { ProgressData } from 'src/app/types/progress-data'
|
||||||
|
|
||||||
export function packageLoadingProgress(
|
export function packageLoadingProgress(
|
||||||
loadData: InstallProgress,
|
loadData?: InstallProgress,
|
||||||
): ProgressData | null {
|
): ProgressData | null {
|
||||||
if (isEmptyObject(loadData)) {
|
if (!loadData || isEmptyObject(loadData)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ export function packageLoadingProgress(
|
|||||||
} = loadData
|
} = loadData
|
||||||
|
|
||||||
// only permit 100% when "complete" == true
|
// only permit 100% when "complete" == true
|
||||||
|
size = size || 0
|
||||||
downloaded = downloadComplete ? size : Math.max(downloaded - 1, 0)
|
downloaded = downloadComplete ? size : Math.max(downloaded - 1, 0)
|
||||||
validated = validationComplete ? size : Math.max(validated - 1, 0)
|
validated = validationComplete ? size : Math.max(validated - 1, 0)
|
||||||
unpacked = unpackComplete ? size : Math.max(unpacked - 1, 0)
|
unpacked = unpackComplete ? size : Math.max(unpacked - 1, 0)
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import {
|
|||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
|
|
||||||
export function parseDataModel(data: DataModel): ParsedData {
|
export function parseDataModel(data: DataModel): ParsedData {
|
||||||
const all = JSON.parse(JSON.stringify(data['package-data'])) as {
|
const all: Record<string, PackageDataEntry> = JSON.parse(
|
||||||
[id: string]: PackageDataEntry
|
JSON.stringify(data['package-data']),
|
||||||
}
|
)
|
||||||
|
|
||||||
const order = [...(data.ui['pkg-order'] || [])]
|
const order = [...(data.ui['pkg-order'] || [])]
|
||||||
const pkgs = []
|
const pkgs: PackageDataEntry[] = []
|
||||||
const recoveredPkgs = Object.entries(data['recovered-packages'])
|
const recoveredPkgs = Object.entries(data['recovered-packages'])
|
||||||
.filter(([id, _]) => !all[id])
|
.filter(([id, _]) => !all[id])
|
||||||
.map(([id, val]) => ({
|
.map(([id, val]) => ({
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const matchPropertiesV1 = shape(
|
|||||||
qr: boolean,
|
qr: boolean,
|
||||||
},
|
},
|
||||||
['description', 'copyable', 'qr'],
|
['description', 'copyable', 'qr'],
|
||||||
{ description: null as null, copyable: false, qr: false } as const,
|
{ copyable: false, qr: false } as const,
|
||||||
)
|
)
|
||||||
type PropertiesV1 = typeof matchPropertiesV1._TYPE
|
type PropertiesV1 = typeof matchPropertiesV1._TYPE
|
||||||
|
|
||||||
@@ -49,7 +49,6 @@ const matchPackagePropertyString = shape(
|
|||||||
},
|
},
|
||||||
['description', 'copyable', 'qr', 'masked'],
|
['description', 'copyable', 'qr', 'masked'],
|
||||||
{
|
{
|
||||||
description: null as null,
|
|
||||||
copyable: false,
|
copyable: false,
|
||||||
qr: false,
|
qr: false,
|
||||||
masked: false,
|
masked: false,
|
||||||
@@ -100,16 +99,16 @@ export function parsePropertiesPermissive(
|
|||||||
name,
|
name,
|
||||||
value: {
|
value: {
|
||||||
value: String(value),
|
value: String(value),
|
||||||
description: null,
|
|
||||||
copyable: false,
|
copyable: false,
|
||||||
qr: false,
|
qr: false,
|
||||||
masked: false,
|
masked: false,
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
.reduce((acc, { name, value }) => {
|
.reduce((acc, { name, value }) => {
|
||||||
acc[name] = value
|
// TODO: Fix type
|
||||||
|
acc[name] = value as any
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {} as PackageProperties)
|
||||||
}
|
}
|
||||||
switch (properties.version) {
|
switch (properties.version) {
|
||||||
case 1:
|
case 1:
|
||||||
|
|||||||
@@ -8,13 +8,14 @@
|
|||||||
"noImplicitOverride": true,
|
"noImplicitOverride": true,
|
||||||
"noPropertyAccessFromIndexSignature": true,
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
"alwaysStrict": true,
|
"alwaysStrict": true,
|
||||||
// "strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"strictBindCallApply": true,
|
"strictBindCallApply": true,
|
||||||
"strictFunctionTypes": true,
|
"strictFunctionTypes": true,
|
||||||
// "strictPropertyInitialization": true,
|
// "strictPropertyInitialization": true,
|
||||||
// "noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"noImplicitThis": true,
|
"noImplicitThis": true,
|
||||||
"useUnknownInCatchVariables": true,
|
"useUnknownInCatchVariables": true,
|
||||||
|
|
||||||
|
|||||||
2
patch-db
2
patch-db
Submodule patch-db updated: 8e7c893d48...a3aa821847
Reference in New Issue
Block a user