mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 22:39:46 +00:00
switch all fe to camelCase
This commit is contained in:
@@ -49,7 +49,7 @@ export class LogsPage {
|
|||||||
|
|
||||||
private async getLogs() {
|
private async getLogs() {
|
||||||
try {
|
try {
|
||||||
const { 'start-cursor': startCursor, entries } = await this.api.getLogs({
|
const { startCursor, entries } = await this.api.getLogs({
|
||||||
cursor: this.startCursor,
|
cursor: this.startCursor,
|
||||||
before: !!this.startCursor,
|
before: !!this.startCursor,
|
||||||
limit: this.limit,
|
limit: this.limit,
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ export class MockApiService implements ApiService {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
entries,
|
entries,
|
||||||
'start-cursor': 'startCursor',
|
startCursor: 'start-cursor',
|
||||||
'end-cursor': 'endCursor',
|
endCursor: 'end-cursor',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ export class MockApiService implements ApiService {
|
|||||||
label: null,
|
label: null,
|
||||||
capacity: 73264762332,
|
capacity: 73264762332,
|
||||||
used: null,
|
used: null,
|
||||||
'embassy-os': {
|
startOs: {
|
||||||
version: '0.2.17',
|
version: '0.2.17',
|
||||||
full: true,
|
full: true,
|
||||||
'password-hash':
|
passwordHash:
|
||||||
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
||||||
'wrapped-key': null,
|
wrappedKey: null,
|
||||||
},
|
},
|
||||||
guid: null,
|
guid: null,
|
||||||
},
|
},
|
||||||
@@ -40,12 +40,12 @@ export class MockApiService implements ApiService {
|
|||||||
label: null,
|
label: null,
|
||||||
capacity: 73264762332,
|
capacity: 73264762332,
|
||||||
used: null,
|
used: null,
|
||||||
'embassy-os': {
|
startOs: {
|
||||||
version: '0.3.3',
|
version: '0.3.3',
|
||||||
full: true,
|
full: true,
|
||||||
'password-hash':
|
passwordHash:
|
||||||
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
||||||
'wrapped-key': null,
|
wrappedKey: null,
|
||||||
},
|
},
|
||||||
guid: null,
|
guid: null,
|
||||||
},
|
},
|
||||||
@@ -63,12 +63,12 @@ export class MockApiService implements ApiService {
|
|||||||
label: null,
|
label: null,
|
||||||
capacity: 73264762332,
|
capacity: 73264762332,
|
||||||
used: null,
|
used: null,
|
||||||
'embassy-os': {
|
startOs: {
|
||||||
version: '0.3.2',
|
version: '0.3.2',
|
||||||
full: true,
|
full: true,
|
||||||
'password-hash':
|
passwordHash:
|
||||||
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
||||||
'wrapped-key': null,
|
wrappedKey: null,
|
||||||
},
|
},
|
||||||
guid: 'guid-guid-guid-guid',
|
guid: 'guid-guid-guid-guid',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<ion-item lines="none" color="transparent">
|
<ion-item lines="none" color="transparent">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<div [innerHTML]="pkg.manifest['release-notes'] | markdown"></div>
|
<div [innerHTML]="pkg.manifest.releaseNotes | markdown"></div>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-button routerLink="notes" fill="clear" strong>
|
<ion-button routerLink="notes" fill="clear" strong>
|
||||||
@@ -18,10 +18,7 @@
|
|||||||
<h2>{{ pkg.manifest.description.long }}</h2>
|
<h2>{{ pkg.manifest.description.long }}</h2>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<div
|
<div *ngIf="pkg.manifest.marketingSite as url" style="padding: 4px 0 10px 14px">
|
||||||
*ngIf="pkg.manifest['marketing-site'] as url"
|
|
||||||
style="padding: 4px 0 10px 14px"
|
|
||||||
>
|
|
||||||
<ion-button [href]="url" target="_blank" rel="noreferrer" color="tertiary">
|
<ion-button [href]="url" target="_blank" rel="noreferrer" color="tertiary">
|
||||||
View website
|
View website
|
||||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<ion-col responsiveCol sizeXs="12" sizeMd="6">
|
<ion-col responsiveCol sizeXs="12" sizeMd="6">
|
||||||
<ion-item-group>
|
<ion-item-group>
|
||||||
<ion-item
|
<ion-item
|
||||||
*ngIf="manifest['git-hash'] as gitHash; else noHash"
|
*ngIf="manifest.gitHash as gitHash; else noHash"
|
||||||
button
|
button
|
||||||
detail="false"
|
detail="false"
|
||||||
(click)="copy(gitHash)"
|
(click)="copy(gitHash)"
|
||||||
@@ -64,39 +64,39 @@
|
|||||||
<ion-col responsiveCol sizeXs="12" sizeMd="6">
|
<ion-col responsiveCol sizeXs="12" sizeMd="6">
|
||||||
<ion-item-group>
|
<ion-item-group>
|
||||||
<ion-item
|
<ion-item
|
||||||
[href]="manifest['upstream-repo']"
|
[href]="manifest.upstreamRepo"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
detail="false"
|
detail="false"
|
||||||
>
|
>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>Source Repository</h2>
|
<h2>Source Repository</h2>
|
||||||
<p>{{ manifest['upstream-repo'] }}</p>
|
<p>{{ manifest.upstreamRepo }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item
|
<ion-item
|
||||||
[href]="manifest['wrapper-repo']"
|
[href]="manifest.wrapperRepo"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
detail="false"
|
detail="false"
|
||||||
>
|
>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>Wrapper Repository</h2>
|
<h2>Wrapper Repository</h2>
|
||||||
<p>{{ manifest['wrapper-repo'] }}</p>
|
<p>{{ manifest.wrapperRepo }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item
|
<ion-item
|
||||||
[href]="manifest['support-site']"
|
[href]="manifest.supportSite"
|
||||||
[disabled]="!manifest['support-site']"
|
[disabled]="!manifest.supportSite"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
detail="false"
|
detail="false"
|
||||||
>
|
>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>Support Site</h2>
|
<h2>Support Site</h2>
|
||||||
<p>{{ manifest['support-site'] || 'Not provided' }}</p>
|
<p>{{ manifest.supportSite || 'Not provided' }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|||||||
@@ -17,10 +17,10 @@
|
|||||||
</ion-thumbnail>
|
</ion-thumbnail>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>
|
<h2>
|
||||||
{{ pkg['dependency-metadata'][dep.key].title }}
|
{{ pkg.dependencyMetadata[dep.key].title }}
|
||||||
<span *ngIf="dep.value.optional; else required">(optional)</span>
|
<span *ngIf="dep.value.optional; else required">(optional)</span>
|
||||||
<ng-template #required>
|
<ng-template #required>
|
||||||
<span *ngSwitchCase="'opt-in'">(optional)</span>
|
<span>(Required)</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</h2>
|
</h2>
|
||||||
<p>{{ dep.value.description }}</p>
|
<p>{{ dep.value.description }}</p>
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ export class DependenciesComponent {
|
|||||||
pkg!: MarketplacePkg
|
pkg!: MarketplacePkg
|
||||||
|
|
||||||
getImg(key: string): string {
|
getImg(key: string): string {
|
||||||
return this.pkg['dependency-metadata'][key].icon
|
return this.pkg.dependencyMetadata[key].icon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,7 @@
|
|||||||
<div class="text">
|
<div class="text">
|
||||||
<h1 ticker class="title">{{ pkg.manifest.title }}</h1>
|
<h1 ticker class="title">{{ pkg.manifest.title }}</h1>
|
||||||
<p class="version">{{ pkg.manifest.version | displayEmver }}</p>
|
<p class="version">{{ pkg.manifest.version | displayEmver }}</p>
|
||||||
<p class="published">
|
<p class="published">Released: {{ pkg.publishedAt | date: 'medium' }}</p>
|
||||||
Released: {{ pkg['published-at'] | date: 'medium' }}
|
|
||||||
</p>
|
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -71,8 +71,7 @@ export class FilterPackagesPipe implements PipeTransform {
|
|||||||
.filter(p => category === 'all' || p.categories.includes(category))
|
.filter(p => category === 'all' || p.categories.includes(category))
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
return (
|
return (
|
||||||
new Date(b['published-at']).valueOf() -
|
new Date(b.publishedAt).valueOf() - new Date(a.publishedAt).valueOf()
|
||||||
new Date(a['published-at']).valueOf()
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ export interface MarketplacePkg {
|
|||||||
manifest: Manifest
|
manifest: Manifest
|
||||||
categories: string[]
|
categories: string[]
|
||||||
versions: string[]
|
versions: string[]
|
||||||
'dependency-metadata': {
|
dependencyMetadata: {
|
||||||
[id: string]: DependencyMetadata
|
[id: string]: DependencyMetadata
|
||||||
}
|
}
|
||||||
'published-at': string
|
publishedAt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DependencyMetadata {
|
export interface DependencyMetadata {
|
||||||
@@ -45,19 +45,19 @@ export interface Manifest {
|
|||||||
id: string
|
id: string
|
||||||
title: string
|
title: string
|
||||||
version: string
|
version: string
|
||||||
'git-hash'?: string
|
gitHash?: string
|
||||||
description: {
|
description: {
|
||||||
short: string
|
short: string
|
||||||
long: string
|
long: string
|
||||||
}
|
}
|
||||||
replaces?: string[]
|
replaces?: string[]
|
||||||
'release-notes': string
|
releaseNotes: string
|
||||||
license: string // name of license
|
license: string // name of license
|
||||||
'wrapper-repo': Url
|
wrapperRepo: Url
|
||||||
'upstream-repo': Url
|
upstreamRepo: Url
|
||||||
'support-site': Url
|
supportSite: Url
|
||||||
'marketing-site': Url
|
marketingSite: Url
|
||||||
'donation-url': Url | null
|
donationUrl: Url | null
|
||||||
alerts: {
|
alerts: {
|
||||||
install: string | null
|
install: string | null
|
||||||
uninstall: string | null
|
uninstall: string | null
|
||||||
@@ -66,8 +66,8 @@ export interface Manifest {
|
|||||||
stop: string | null
|
stop: string | null
|
||||||
}
|
}
|
||||||
dependencies: Record<string, Dependency>
|
dependencies: Record<string, Dependency>
|
||||||
'os-version': string
|
osVersion: string
|
||||||
'has-config': boolean
|
hasConfig: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Dependency {
|
export interface Dependency {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export class CifsModal {
|
|||||||
const target: CifsBackupTarget = {
|
const target: CifsBackupTarget = {
|
||||||
...this.cifs,
|
...this.cifs,
|
||||||
mountable: true,
|
mountable: true,
|
||||||
'embassy-os': diskInfo,
|
startOs: diskInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
const modal = await this.modalController.create({
|
const modal = await this.modalController.create({
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ export class PasswordPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async verifyPw() {
|
async verifyPw() {
|
||||||
if (!this.target || !this.target['embassy-os'])
|
if (!this.target || !this.target.startOs)
|
||||||
this.pwError = 'No recovery target' // unreachable
|
this.pwError = 'No recovery target' // unreachable
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const passwordHash = this.target!['embassy-os']?.['password-hash'] || ''
|
const passwordHash = this.target!.startOs?.passwordHash || ''
|
||||||
|
|
||||||
argon2.verify(passwordHash, this.password)
|
argon2.verify(passwordHash, this.password)
|
||||||
this.modalController.dismiss({ password: this.password }, 'success')
|
this.modalController.dismiss({ password: this.password }, 'success')
|
||||||
|
|||||||
@@ -38,10 +38,7 @@ export class LoadingPage {
|
|||||||
|
|
||||||
if (!progress) return
|
if (!progress) return
|
||||||
|
|
||||||
const {
|
const { totalBytes, bytesTransferred } = progress
|
||||||
'total-bytes': totalBytes,
|
|
||||||
'bytes-transferred': bytesTransferred,
|
|
||||||
} = progress
|
|
||||||
|
|
||||||
this.progress$.next({
|
this.progress$.next({
|
||||||
totalBytes,
|
totalBytes,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export class RecoverPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
driveClickable(mapped: MappedDisk) {
|
driveClickable(mapped: MappedDisk) {
|
||||||
return mapped.drive['embassy-os']?.full
|
return mapped.drive.startOs?.full
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDrives() {
|
async getDrives() {
|
||||||
@@ -53,10 +53,10 @@ export class RecoverPage {
|
|||||||
label: p.label,
|
label: p.label,
|
||||||
capacity: p.capacity,
|
capacity: p.capacity,
|
||||||
used: p.used,
|
used: p.used,
|
||||||
'embassy-os': p['embassy-os'],
|
startOs: p.startOs,
|
||||||
}
|
}
|
||||||
this.mappedDrives.push({
|
this.mappedDrives.push({
|
||||||
hasValidBackup: !!p['embassy-os']?.full,
|
hasValidBackup: !!p.startOs?.full,
|
||||||
drive,
|
drive,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -76,9 +76,9 @@ export class SuccessPage {
|
|||||||
try {
|
try {
|
||||||
const ret = await this.api.complete()
|
const ret = await this.api.complete()
|
||||||
if (!this.isKiosk) {
|
if (!this.isKiosk) {
|
||||||
this.torAddress = ret['tor-address'].replace(/^https:/, 'http:')
|
this.torAddress = ret.torAddress.replace(/^https:/, 'http:')
|
||||||
this.lanAddress = ret['lan-address'].replace(/^https:/, 'http:')
|
this.lanAddress = ret.lanAddress.replace(/^https:/, 'http:')
|
||||||
this.cert = ret['root-ca']
|
this.cert = ret.rootCa
|
||||||
|
|
||||||
await this.api.exit()
|
await this.api.exit()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,27 +28,27 @@ type Encrypted = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type StatusRes = {
|
export type StatusRes = {
|
||||||
'bytes-transferred': number
|
bytesTransferred: number
|
||||||
'total-bytes': number | null
|
totalBytes: number | null
|
||||||
complete: boolean
|
complete: boolean
|
||||||
} | null
|
} | null
|
||||||
|
|
||||||
export type AttachReq = {
|
export type AttachReq = {
|
||||||
guid: string
|
guid: string
|
||||||
'embassy-password': Encrypted
|
startOsPassword: Encrypted
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ExecuteReq = {
|
export type ExecuteReq = {
|
||||||
'embassy-logicalname': string
|
startOsLogicalname: string
|
||||||
'embassy-password': Encrypted
|
startOsPassword: Encrypted
|
||||||
'recovery-source': RecoverySource | null
|
recoverySource: RecoverySource | null
|
||||||
'recovery-password': Encrypted | null
|
recoveryPassword: Encrypted | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompleteRes = {
|
export type CompleteRes = {
|
||||||
'tor-address': string
|
torAddress: string
|
||||||
'lan-address': string
|
lanAddress: string
|
||||||
'root-ca': string
|
rootCa: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DiskBackupTarget = {
|
export type DiskBackupTarget = {
|
||||||
@@ -58,7 +58,7 @@ export type DiskBackupTarget = {
|
|||||||
label: string | null
|
label: string | null
|
||||||
capacity: number
|
capacity: number
|
||||||
used: number | null
|
used: number | null
|
||||||
'embassy-os': StartOSDiskInfo | null
|
startOs: StartOSDiskInfo | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CifsBackupTarget = {
|
export type CifsBackupTarget = {
|
||||||
@@ -66,7 +66,7 @@ export type CifsBackupTarget = {
|
|||||||
path: string
|
path: string
|
||||||
username: string
|
username: string
|
||||||
mountable: boolean
|
mountable: boolean
|
||||||
'embassy-os': StartOSDiskInfo | null
|
startOs: StartOSDiskInfo | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DiskRecoverySource = {
|
export type DiskRecoverySource = {
|
||||||
|
|||||||
@@ -73,11 +73,10 @@ export class LiveApiService extends ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async execute(setupInfo: ExecuteReq) {
|
async execute(setupInfo: ExecuteReq) {
|
||||||
if (setupInfo['recovery-source']?.type === 'backup') {
|
if (setupInfo.recoverySource?.type === 'backup') {
|
||||||
if (isCifsSource(setupInfo['recovery-source'].target)) {
|
if (isCifsSource(setupInfo.recoverySource.target)) {
|
||||||
setupInfo['recovery-source'].target.path = setupInfo[
|
setupInfo.recoverySource.target.path =
|
||||||
'recovery-source'
|
setupInfo.recoverySource.target.path.replace('/\\/g', '/')
|
||||||
].target.path.replace('/\\/g', '/')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +94,7 @@ export class LiveApiService extends ApiService {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...res,
|
...res,
|
||||||
'root-ca': encodeBase64(res['root-ca']),
|
rootCa: encodeBase64(res.rootCa),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ export class MockApiService extends ApiService {
|
|||||||
const progress = tries > 4 ? (tries - 4) * 268435456 : 0
|
const progress = tries > 4 ? (tries - 4) * 268435456 : 0
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'bytes-transferred': restoreOrMigrate ? progress : 0,
|
bytesTransferred: restoreOrMigrate ? progress : 0,
|
||||||
'total-bytes': restoreOrMigrate ? total : null,
|
totalBytes: restoreOrMigrate ? total : null,
|
||||||
complete: progress === total,
|
complete: progress === total,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,12 +65,12 @@ export class MockApiService extends ApiService {
|
|||||||
label: null,
|
label: null,
|
||||||
capacity: 1979120929996,
|
capacity: 1979120929996,
|
||||||
used: null,
|
used: null,
|
||||||
'embassy-os': {
|
startOs: {
|
||||||
version: '0.2.17',
|
version: '0.2.17',
|
||||||
full: true,
|
full: true,
|
||||||
'password-hash':
|
passwordHash:
|
||||||
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
||||||
'wrapped-key': null,
|
wrappedKey: null,
|
||||||
},
|
},
|
||||||
guid: null,
|
guid: null,
|
||||||
},
|
},
|
||||||
@@ -88,12 +88,12 @@ export class MockApiService extends ApiService {
|
|||||||
label: null,
|
label: null,
|
||||||
capacity: 73264762332,
|
capacity: 73264762332,
|
||||||
used: null,
|
used: null,
|
||||||
'embassy-os': {
|
startOs: {
|
||||||
version: '0.3.3',
|
version: '0.3.3',
|
||||||
full: true,
|
full: true,
|
||||||
'password-hash':
|
passwordHash:
|
||||||
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
||||||
'wrapped-key': null,
|
wrappedKey: null,
|
||||||
},
|
},
|
||||||
guid: null,
|
guid: null,
|
||||||
},
|
},
|
||||||
@@ -111,12 +111,12 @@ export class MockApiService extends ApiService {
|
|||||||
label: null,
|
label: null,
|
||||||
capacity: 73264762332,
|
capacity: 73264762332,
|
||||||
used: null,
|
used: null,
|
||||||
'embassy-os': {
|
startOs: {
|
||||||
version: '0.3.2',
|
version: '0.3.2',
|
||||||
full: true,
|
full: true,
|
||||||
'password-hash':
|
passwordHash:
|
||||||
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
||||||
'wrapped-key': null,
|
wrappedKey: null,
|
||||||
},
|
},
|
||||||
guid: 'guid-guid-guid-guid',
|
guid: 'guid-guid-guid-guid',
|
||||||
},
|
},
|
||||||
@@ -132,9 +132,9 @@ export class MockApiService extends ApiService {
|
|||||||
return {
|
return {
|
||||||
version: '0.3.0',
|
version: '0.3.0',
|
||||||
full: true,
|
full: true,
|
||||||
'password-hash':
|
passwordHash:
|
||||||
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
||||||
'wrapped-key': '',
|
wrappedKey: '',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,9 +149,9 @@ export class MockApiService extends ApiService {
|
|||||||
async complete(): Promise<CompleteRes> {
|
async complete(): Promise<CompleteRes> {
|
||||||
await pauseFor(1000)
|
await pauseFor(1000)
|
||||||
return {
|
return {
|
||||||
'tor-address': 'https://asdafsadasdasasdasdfasdfasdf.onion',
|
torAddress: 'https://asdafsadasdasasdasdfasdfasdf.onion',
|
||||||
'lan-address': 'https://adjective-noun.local',
|
lanAddress: 'https://adjective-noun.local',
|
||||||
'root-ca': encodeBase64(rootCA),
|
rootCa: encodeBase64(rootCA),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class StateService {
|
|||||||
async importDrive(guid: string, password: string): Promise<void> {
|
async importDrive(guid: string, password: string): Promise<void> {
|
||||||
await this.api.attach({
|
await this.api.attach({
|
||||||
guid,
|
guid,
|
||||||
'embassy-password': await this.api.encrypt(password),
|
startOsPassword: await this.api.encrypt(password),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,10 +24,10 @@ export class StateService {
|
|||||||
password: string,
|
password: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.api.execute({
|
await this.api.execute({
|
||||||
'embassy-logicalname': storageLogicalname,
|
startOsLogicalname: storageLogicalname,
|
||||||
'embassy-password': await this.api.encrypt(password),
|
startOsPassword: await this.api.encrypt(password),
|
||||||
'recovery-source': this.recoverySource || null,
|
recoverySource: this.recoverySource || null,
|
||||||
'recovery-password': this.recoveryPassword
|
recoveryPassword: this.recoveryPassword
|
||||||
? await this.api.encrypt(this.recoveryPassword)
|
? await this.api.encrypt(this.recoveryPassword)
|
||||||
: null,
|
: null,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ export type ServerLogsReq = {
|
|||||||
|
|
||||||
export type LogsRes = {
|
export type LogsRes = {
|
||||||
entries: Log[]
|
entries: Log[]
|
||||||
'start-cursor'?: string
|
startCursor?: string
|
||||||
'end-cursor'?: string
|
endCursor?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Log {
|
export interface Log {
|
||||||
@@ -31,13 +31,13 @@ export interface PartitionInfo {
|
|||||||
label: string | null
|
label: string | null
|
||||||
capacity: number
|
capacity: number
|
||||||
used: number | null
|
used: number | null
|
||||||
'embassy-os': StartOSDiskInfo | null
|
startOs: StartOSDiskInfo | null
|
||||||
guid: string | null
|
guid: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StartOSDiskInfo = {
|
export type StartOSDiskInfo = {
|
||||||
version: string
|
version: string
|
||||||
full: boolean
|
full: boolean
|
||||||
'password-hash': string | null
|
passwordHash: string | null
|
||||||
'wrapped-key': string | null
|
wrappedKey: string | null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,15 +63,6 @@ const routes: Routes = [
|
|||||||
m => m.AppsRoutingModule,
|
m => m.AppsRoutingModule,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'developer',
|
|
||||||
canActivate: [AuthGuard],
|
|
||||||
canActivateChild: [AuthGuard],
|
|
||||||
loadChildren: () =>
|
|
||||||
import('./pages/developer-routes/developer-routing.module').then(
|
|
||||||
m => m.DeveloperRoutingModule,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
@@ -29,13 +29,12 @@ export class AppComponent implements OnDestroy {
|
|||||||
this.authService.isVerified$,
|
this.authService.isVerified$,
|
||||||
this.connection.connected$,
|
this.connection.connected$,
|
||||||
this.patch
|
this.patch
|
||||||
.watch$('server-info', 'status-info')
|
.watch$('serverInfo', 'statusInfo')
|
||||||
.pipe(startWith({ restarting: false, 'shutting-down': false })),
|
.pipe(startWith({ restarting: false, shuttingDown: false })),
|
||||||
]).pipe(
|
]).pipe(
|
||||||
map(
|
map(
|
||||||
([verified, connected, status]) =>
|
([verified, connected, status]) =>
|
||||||
verified &&
|
verified && (!connected || status.restarting || status.shuttingDown),
|
||||||
(!connected || status.restarting || status['shutting-down']),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { DataModel } from '../../services/patch-db/data-model'
|
|||||||
})
|
})
|
||||||
export class FooterComponent {
|
export class FooterComponent {
|
||||||
readonly progress$ = this.patch
|
readonly progress$ = this.patch
|
||||||
.watch$('server-info', 'status-info', 'update-progress')
|
.watch$('serverInfo', 'statusInfo', 'updateProgress')
|
||||||
.pipe(map(a => a && { ...a }))
|
.pipe(map(a => a && { ...a }))
|
||||||
|
|
||||||
readonly animation = {
|
readonly animation = {
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ import {
|
|||||||
filter,
|
filter,
|
||||||
first,
|
first,
|
||||||
map,
|
map,
|
||||||
merge,
|
|
||||||
Observable,
|
Observable,
|
||||||
of,
|
|
||||||
pairwise,
|
pairwise,
|
||||||
startWith,
|
startWith,
|
||||||
switchMap,
|
switchMap,
|
||||||
@@ -24,14 +22,7 @@ import { DataModel, PackageState } from 'src/app/services/patch-db/data-model'
|
|||||||
import { SplitPaneTracker } from 'src/app/services/split-pane.service'
|
import { SplitPaneTracker } from 'src/app/services/split-pane.service'
|
||||||
import { Emver, THEME } from '@start9labs/shared'
|
import { Emver, THEME } from '@start9labs/shared'
|
||||||
import { ConnectionService } from 'src/app/services/connection.service'
|
import { ConnectionService } from 'src/app/services/connection.service'
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { getManifest } from 'src/app/util/get-package-data'
|
||||||
import {
|
|
||||||
getManifest,
|
|
||||||
isInstalled,
|
|
||||||
isInstalling,
|
|
||||||
isRestoring,
|
|
||||||
isUpdating,
|
|
||||||
} from 'src/app/util/get-package-data'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-menu',
|
selector: 'app-menu',
|
||||||
@@ -69,19 +60,19 @@ export class MenuComponent {
|
|||||||
]
|
]
|
||||||
|
|
||||||
readonly notificationCount$ = this.patch.watch$(
|
readonly notificationCount$ = this.patch.watch$(
|
||||||
'server-info',
|
'serverInfo',
|
||||||
'unread-notification-count',
|
'unreadNotificationCount',
|
||||||
)
|
)
|
||||||
|
|
||||||
readonly snekScore$ = this.patch.watch$('ui', 'gaming', 'snake', 'high-score')
|
readonly snekScore$ = this.patch.watch$('ui', 'gaming', 'snake', 'highScore')
|
||||||
|
|
||||||
readonly showEOSUpdate$ = this.eosService.showUpdate$
|
readonly showEOSUpdate$ = this.eosService.showUpdate$
|
||||||
|
|
||||||
private readonly local$ = this.connectionService.connected$.pipe(
|
private readonly local$ = this.connectionService.connected$.pipe(
|
||||||
filter(Boolean),
|
filter(Boolean),
|
||||||
switchMap(() => this.patch.watch$('package-data').pipe(first())),
|
switchMap(() => this.patch.watch$('packageData').pipe(first())),
|
||||||
switchMap(outer =>
|
switchMap(outer =>
|
||||||
this.patch.watch$('package-data').pipe(
|
this.patch.watch$('packageData').pipe(
|
||||||
pairwise(),
|
pairwise(),
|
||||||
filter(([prev, curr]) =>
|
filter(([prev, curr]) =>
|
||||||
Object.values(prev).some(
|
Object.values(prev).some(
|
||||||
@@ -90,9 +81,9 @@ export class MenuComponent {
|
|||||||
PackageState.Installing,
|
PackageState.Installing,
|
||||||
PackageState.Updating,
|
PackageState.Updating,
|
||||||
PackageState.Restoring,
|
PackageState.Restoring,
|
||||||
].includes(p['state-info'].state) &&
|
].includes(p.stateInfo.state) &&
|
||||||
[PackageState.Installed, PackageState.Removing].includes(
|
[PackageState.Installed, PackageState.Removing].includes(
|
||||||
curr[getManifest(p).id]['state-info'].state,
|
curr[getManifest(p).id].stateInfo.state,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -136,6 +127,5 @@ export class MenuComponent {
|
|||||||
private readonly splitPane: SplitPaneTracker,
|
private readonly splitPane: SplitPaneTracker,
|
||||||
private readonly emver: Emver,
|
private readonly emver: Emver,
|
||||||
private readonly connectionService: ConnectionService,
|
private readonly connectionService: ConnectionService,
|
||||||
private readonly config: ConfigService,
|
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export class BackupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasValidBackup(target: BackupTarget): boolean {
|
hasValidBackup(target: BackupTarget): boolean {
|
||||||
const backup = target['embassy-os']
|
const backup = target.startOs
|
||||||
return !!backup && this.emver.compare(backup.version, '0.3.0') !== -1
|
return !!backup && this.emver.compare(backup.version, '0.3.0') !== -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ const { enableWidgets } =
|
|||||||
})
|
})
|
||||||
export class BadgeMenuComponent {
|
export class BadgeMenuComponent {
|
||||||
readonly unreadCount$ = this.patch.watch$(
|
readonly unreadCount$ = this.patch.watch$(
|
||||||
'server-info',
|
'serverInfo',
|
||||||
'unread-notification-count',
|
'unreadNotificationCount',
|
||||||
)
|
)
|
||||||
readonly sidebarOpen$ = this.splitPane.sidebarOpen$
|
readonly sidebarOpen$ = this.splitPane.sidebarOpen$
|
||||||
readonly widgetDrawer$ = this.clientStorageService.widgetDrawer$
|
readonly widgetDrawer$ = this.clientStorageService.widgetDrawer$
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ export class ConnectionBarComponent {
|
|||||||
this.connectionService.networkConnected$,
|
this.connectionService.networkConnected$,
|
||||||
this.websocket$.pipe(startWith(false)),
|
this.websocket$.pipe(startWith(false)),
|
||||||
this.patch
|
this.patch
|
||||||
.watch$('server-info', 'status-info')
|
.watch$('serverInfo', 'statusInfo')
|
||||||
.pipe(startWith({ restarting: false, 'shutting-down': false })),
|
.pipe(startWith({ restarting: false, shuttingDown: false })),
|
||||||
]).pipe(
|
]).pipe(
|
||||||
map(([network, websocket, status]) => {
|
map(([network, websocket, status]) => {
|
||||||
if (!network)
|
if (!network)
|
||||||
@@ -40,7 +40,7 @@ export class ConnectionBarComponent {
|
|||||||
icon: 'cloud-offline-outline',
|
icon: 'cloud-offline-outline',
|
||||||
dots: true,
|
dots: true,
|
||||||
}
|
}
|
||||||
if (status['shutting-down'])
|
if (status.shuttingDown)
|
||||||
return {
|
return {
|
||||||
message: 'Shutting Down',
|
message: 'Shutting Down',
|
||||||
color: 'dark',
|
color: 'dark',
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export class LogsComponent {
|
|||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
from(this.followLogs({ limit: this.limit }))
|
from(this.followLogs({ limit: this.limit }))
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(({ 'start-cursor': startCursor, guid }) => {
|
switchMap(({ startCursor, guid }) => {
|
||||||
this.startCursor = startCursor
|
this.startCursor = startCursor
|
||||||
return this.connect$(guid)
|
return this.connect$(guid)
|
||||||
}),
|
}),
|
||||||
@@ -206,7 +206,7 @@ export class LogsComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private processRes(res: LogsRes) {
|
private processRes(res: LogsRes) {
|
||||||
const { entries, 'start-cursor': startCursor } = res
|
const { entries, startCursor } = res
|
||||||
|
|
||||||
if (!entries.length) return
|
if (!entries.length) return
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
|||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class NotificationsToastService extends Observable<boolean> {
|
export class NotificationsToastService extends Observable<boolean> {
|
||||||
private readonly stream$ = this.patch
|
private readonly stream$ = this.patch
|
||||||
.watch$('server-info', 'unread-notification-count')
|
.watch$('serverInfo', 'unreadNotificationCount')
|
||||||
.pipe(
|
.pipe(
|
||||||
pairwise(),
|
pairwise(),
|
||||||
map(([prev, cur]) => cur > prev),
|
map(([prev, cur]) => cur > prev),
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
|||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class RefreshAlertService extends Observable<boolean> {
|
export class RefreshAlertService extends Observable<boolean> {
|
||||||
private readonly stream$ = this.patch.watch$('server-info', 'version').pipe(
|
private readonly stream$ = this.patch.watch$('serverInfo', 'version').pipe(
|
||||||
map(version => !!this.emver.compare(this.config.version, version)),
|
map(version => !!this.emver.compare(this.config.version, version)),
|
||||||
endWith(false),
|
endWith(false),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
|||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class UpdateToastService extends Observable<boolean> {
|
export class UpdateToastService extends Observable<boolean> {
|
||||||
private readonly stream$ = this.patch
|
private readonly stream$ = this.patch
|
||||||
.watch$('server-info', 'status-info', 'updated')
|
.watch$('serverInfo', 'statusInfo', 'updated')
|
||||||
.pipe(distinctUntilChanged(), filter(Boolean), endWith(false))
|
.pipe(distinctUntilChanged(), filter(Boolean), endWith(false))
|
||||||
|
|
||||||
constructor(private readonly patch: PatchDB<DataModel>) {
|
constructor(private readonly patch: PatchDB<DataModel>) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content class="ion-padding" *ngIf="pkg['state-info'].manifest as manifest">
|
<ion-content class="ion-padding" *ngIf="pkg.stateInfo.manifest as manifest">
|
||||||
<!-- loading -->
|
<!-- loading -->
|
||||||
<text-spinner
|
<text-spinner
|
||||||
*ngIf="loading; else notLoading"
|
*ngIf="loading; else notLoading"
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<!-- no options -->
|
<!-- no options -->
|
||||||
<ion-item *ngIf="!hasOptions">
|
<ion-item *ngIf="!hasConfig">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p>
|
<p>
|
||||||
No config options for {{ manifest.title }} {{ manifest.version }}.
|
No config options for {{ manifest.title }} {{ manifest.version }}.
|
||||||
@@ -111,11 +111,7 @@
|
|||||||
<ion-footer>
|
<ion-footer>
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ng-container *ngIf="!loading && !loadingError">
|
<ng-container *ngIf="!loading && !loadingError">
|
||||||
<ion-buttons
|
<ion-buttons *ngIf="hasConfig" slot="start" class="ion-padding-start">
|
||||||
*ngIf="configForm && hasOptions"
|
|
||||||
slot="start"
|
|
||||||
class="ion-padding-start"
|
|
||||||
>
|
|
||||||
<ion-button fill="clear" (click)="resetDefaults()">
|
<ion-button fill="clear" (click)="resetDefaults()">
|
||||||
<ion-icon slot="start" name="refresh"></ion-icon>
|
<ion-icon slot="start" name="refresh"></ion-icon>
|
||||||
Reset Defaults
|
Reset Defaults
|
||||||
@@ -123,7 +119,7 @@
|
|||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-buttons slot="end" class="ion-padding-end">
|
<ion-buttons slot="end" class="ion-padding-end">
|
||||||
<ion-button
|
<ion-button
|
||||||
*ngIf="configForm"
|
*ngIf="hasConfig"
|
||||||
fill="solid"
|
fill="solid"
|
||||||
color="primary"
|
color="primary"
|
||||||
[disabled]="saving"
|
[disabled]="saving"
|
||||||
@@ -134,7 +130,7 @@
|
|||||||
Save
|
Save
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button
|
<ion-button
|
||||||
*ngIf="!configForm"
|
*ngIf="!hasConfig"
|
||||||
fill="solid"
|
fill="solid"
|
||||||
color="dark"
|
color="dark"
|
||||||
(click)="dismiss()"
|
(click)="dismiss()"
|
||||||
|
|||||||
@@ -59,8 +59,6 @@ export class AppConfigPage {
|
|||||||
saving = false
|
saving = false
|
||||||
loadingError: string | IonicSafeString = ''
|
loadingError: string | IonicSafeString = ''
|
||||||
|
|
||||||
hasOptions = false
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly embassyApi: ApiService,
|
private readonly embassyApi: ApiService,
|
||||||
private readonly errToast: ErrorToastService,
|
private readonly errToast: ErrorToastService,
|
||||||
@@ -71,6 +69,10 @@ export class AppConfigPage {
|
|||||||
private readonly patch: PatchDB<DataModel>,
|
private readonly patch: PatchDB<DataModel>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
get hasConfig() {
|
||||||
|
return this.pkg.stateInfo.manifest.hasConfig
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
try {
|
try {
|
||||||
const pkg = await getPackage(this.patch, this.pkgId)
|
const pkg = await getPackage(this.patch, this.pkgId)
|
||||||
@@ -78,7 +80,7 @@ export class AppConfigPage {
|
|||||||
|
|
||||||
this.pkg = pkg
|
this.pkg = pkg
|
||||||
|
|
||||||
if (!this.pkg['state-info'].manifest['has-config']) return
|
if (!this.hasConfig) return
|
||||||
|
|
||||||
let newConfig: object | undefined
|
let newConfig: object | undefined
|
||||||
let patch: Operation[] | undefined
|
let patch: Operation[] | undefined
|
||||||
@@ -86,12 +88,12 @@ export class AppConfigPage {
|
|||||||
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 {
|
||||||
'old-config': oc,
|
oldConfig: oc,
|
||||||
'new-config': nc,
|
newConfig: nc,
|
||||||
spec: s,
|
spec: s,
|
||||||
} = await this.embassyApi.dryConfigureDependency({
|
} = await this.embassyApi.dryConfigureDependency({
|
||||||
'dependency-id': this.pkgId,
|
dependencyId: this.pkgId,
|
||||||
'dependent-id': this.dependentInfo.id,
|
dependentId: this.dependentInfo.id,
|
||||||
})
|
})
|
||||||
this.original = oc
|
this.original = oc
|
||||||
newConfig = nc
|
newConfig = nc
|
||||||
@@ -111,10 +113,6 @@ export class AppConfigPage {
|
|||||||
newConfig || this.original,
|
newConfig || this.original,
|
||||||
)
|
)
|
||||||
|
|
||||||
this.hasOptions = !!Object.values(this.configSpec).find(
|
|
||||||
valSpec => valSpec.type !== 'pointer',
|
|
||||||
)
|
|
||||||
|
|
||||||
if (patch) {
|
if (patch) {
|
||||||
this.diff = this.getDiff(patch)
|
this.diff = this.getDiff(patch)
|
||||||
this.markDirty(patch)
|
this.markDirty(patch)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<ng-container
|
<ng-container
|
||||||
*ngIf="packageData$ | toOptions : backupInfo['package-backups'] | async as options"
|
*ngIf="packageData$ | toOptions : backupInfo.packageBackups | async as options"
|
||||||
>
|
>
|
||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export class AppRecoverSelectPage {
|
|||||||
@Input() password!: string
|
@Input() password!: string
|
||||||
@Input() oldPassword?: string
|
@Input() oldPassword?: string
|
||||||
|
|
||||||
readonly packageData$ = this.patch.watch$('package-data').pipe(take(1))
|
readonly packageData$ = this.patch.watch$('packageData').pipe(take(1))
|
||||||
|
|
||||||
hasSelection = false
|
hasSelection = false
|
||||||
error: string | IonicSafeString = ''
|
error: string | IonicSafeString = ''
|
||||||
@@ -53,8 +53,8 @@ export class AppRecoverSelectPage {
|
|||||||
try {
|
try {
|
||||||
await this.embassyApi.restorePackages({
|
await this.embassyApi.restorePackages({
|
||||||
ids,
|
ids,
|
||||||
'target-id': this.id,
|
targetId: this.id,
|
||||||
'old-password': this.oldPassword || null,
|
oldPassword: this.oldPassword || null,
|
||||||
password: this.password,
|
password: this.password,
|
||||||
})
|
})
|
||||||
this.modalCtrl.dismiss(undefined, 'success')
|
this.modalCtrl.dismiss(undefined, 'success')
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export class ToOptionsPipe implements PipeTransform {
|
|||||||
id,
|
id,
|
||||||
installed: !!packageData[id],
|
installed: !!packageData[id],
|
||||||
checked: false,
|
checked: false,
|
||||||
'newer-eos': this.compare(packageBackups[id]['os-version']),
|
'newer-eos': this.compare(packageBackups[id].osVersion),
|
||||||
}))
|
}))
|
||||||
.sort((a, b) =>
|
.sort((a, b) =>
|
||||||
b.title.toLowerCase() > a.title.toLowerCase() ? -1 : 1,
|
b.title.toLowerCase() > a.title.toLowerCase() ? -1 : 1,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export class BackupSelectPage {
|
|||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.pkgs = await firstValueFrom(
|
this.pkgs = await firstValueFrom(
|
||||||
this.patch.watch$('package-data').pipe(
|
this.patch.watch$('packageData').pipe(
|
||||||
map(pkgs => {
|
map(pkgs => {
|
||||||
return Object.values(pkgs)
|
return Object.values(pkgs)
|
||||||
.map(pkg => {
|
.map(pkg => {
|
||||||
@@ -38,7 +38,7 @@ export class BackupSelectPage {
|
|||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
icon: pkg.icon,
|
icon: pkg.icon,
|
||||||
disabled: pkg['state-info'].state !== PackageState.Installed,
|
disabled: pkg.stateInfo.state !== PackageState.Installed,
|
||||||
checked: false,
|
checked: false,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ export class MarketplaceSettingsPage {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Error on duplicates
|
// Error on duplicates
|
||||||
const hosts = await firstValueFrom(
|
const hosts = await firstValueFrom(
|
||||||
this.patch.watch$('ui', 'marketplace', 'known-hosts'),
|
this.patch.watch$('ui', 'marketplace', 'knownHosts'),
|
||||||
)
|
)
|
||||||
const currentUrls = Object.keys(hosts).map(toUrl)
|
const currentUrls = Object.keys(hosts).map(toUrl)
|
||||||
if (currentUrls.includes(url)) throw new Error('marketplace already added')
|
if (currentUrls.includes(url)) throw new Error('marketplace already added')
|
||||||
@@ -217,7 +217,7 @@ export class MarketplaceSettingsPage {
|
|||||||
loader.message = 'Saving...'
|
loader.message = 'Saving...'
|
||||||
|
|
||||||
await this.api.setDbValue<{ name: string }>(
|
await this.api.setDbValue<{ name: string }>(
|
||||||
['marketplace', 'known-hosts', url],
|
['marketplace', 'knownHosts', url],
|
||||||
{ name },
|
{ name },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -229,7 +229,7 @@ export class MarketplaceSettingsPage {
|
|||||||
await loader.present()
|
await loader.present()
|
||||||
|
|
||||||
const hosts = await firstValueFrom(
|
const hosts = await firstValueFrom(
|
||||||
this.patch.watch$('ui', 'marketplace', 'known-hosts'),
|
this.patch.watch$('ui', 'marketplace', 'knownHosts'),
|
||||||
)
|
)
|
||||||
|
|
||||||
const filtered: { [url: string]: UIStore } = Object.keys(hosts)
|
const filtered: { [url: string]: UIStore } = Object.keys(hosts)
|
||||||
@@ -244,7 +244,7 @@ export class MarketplaceSettingsPage {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.api.setDbValue<{ [url: string]: UIStore }>(
|
await this.api.setDbValue<{ [url: string]: UIStore }>(
|
||||||
['marketplace', 'known-hosts'],
|
['marketplace', 'knownHosts'],
|
||||||
filtered,
|
filtered,
|
||||||
)
|
)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export class OSUpdatePage {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
const releaseNotes = this.eosService.eos?.['release-notes']!
|
const releaseNotes = this.eosService.eos?.releaseNotes!
|
||||||
|
|
||||||
this.versions = Object.keys(releaseNotes)
|
this.versions = Object.keys(releaseNotes)
|
||||||
.sort()
|
.sort()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<ion-content class="ion-padding-top with-widgets">
|
<ion-content class="ion-padding-top with-widgets">
|
||||||
<ng-container *ngIf="pkg$ | async as pkg">
|
<ng-container *ngIf="pkg$ | async as pkg">
|
||||||
<ion-item-group *ngIf="pkg['state-info'].state === 'installed'">
|
<ion-item-group *ngIf="pkg.stateInfo.state === 'installed'">
|
||||||
<!-- ** standard actions ** -->
|
<!-- ** standard actions ** -->
|
||||||
<ion-item-divider>Standard Actions</ion-item-divider>
|
<ion-item-divider>Standard Actions</ion-item-divider>
|
||||||
<app-actions-item
|
<app-actions-item
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
<!-- ** specific actions ** -->
|
<!-- ** specific actions ** -->
|
||||||
<ion-item-divider *ngIf="!(pkg.actions | empty)">
|
<ion-item-divider *ngIf="!(pkg.actions | empty)">
|
||||||
Actions for {{ pkg['state-info'].manifest.title }}
|
Actions for {{ pkg.stateInfo.manifest.title }}
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<app-actions-item
|
<app-actions-item
|
||||||
*ngFor="let action of pkg.actions | keyvalue: asIsOrder"
|
*ngFor="let action of pkg.actions | keyvalue: asIsOrder"
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import { ActionMetadata } from '@start9labs/start-sdk/cjs/sdk/lib/types'
|
|||||||
})
|
})
|
||||||
export class AppActionsPage {
|
export class AppActionsPage {
|
||||||
readonly pkgId = getPkgId(this.route)
|
readonly pkgId = getPkgId(this.route)
|
||||||
readonly pkg$ = this.patch.watch$('package-data', this.pkgId)
|
readonly pkg$ = this.patch.watch$('packageData', this.pkgId)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly route: ActivatedRoute,
|
private readonly route: ActivatedRoute,
|
||||||
@@ -184,7 +184,7 @@ export class AppActionsPage {
|
|||||||
try {
|
try {
|
||||||
const res = await this.embassyApi.executePackageAction({
|
const res = await this.embassyApi.executePackageAction({
|
||||||
id: this.pkgId,
|
id: this.pkgId,
|
||||||
'action-id': actionId,
|
actionId,
|
||||||
input,
|
input,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export class AppInterfacesPage {
|
|||||||
readonly pkgId = getPkgId(this.route)
|
readonly pkgId = getPkgId(this.route)
|
||||||
|
|
||||||
readonly serviceInterfaces$ = this.patch
|
readonly serviceInterfaces$ = this.patch
|
||||||
.watch$('package-data', this.pkgId, 'service-interfaces')
|
.watch$('packageData', this.pkgId, 'serviceInterfaces')
|
||||||
.pipe(
|
.pipe(
|
||||||
map(interfaces => {
|
map(interfaces => {
|
||||||
const sorted = Object.values(interfaces)
|
const sorted = Object.values(interfaces)
|
||||||
|
|||||||
@@ -14,20 +14,20 @@
|
|||||||
<p>{{ manifest.version | displayEmver }}</p>
|
<p>{{ manifest.version | displayEmver }}</p>
|
||||||
<status
|
<status
|
||||||
[rendering]="pkg.primaryRendering"
|
[rendering]="pkg.primaryRendering"
|
||||||
[installingInfo]="$any(pkg.entry['state-info'])['installing-info']"
|
[installingInfo]="$any(pkg.entry.stateInfo).installingInfo"
|
||||||
weight="bold"
|
weight="bold"
|
||||||
size="small"
|
size="small"
|
||||||
[sigtermTimeout]="sigtermTimeout"
|
[sigtermTimeout]="sigtermTimeout"
|
||||||
></status>
|
></status>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-button
|
<ion-button
|
||||||
*ngIf="pkg.entry['service-interfaces'] | hasUi"
|
*ngIf="pkg.entry.serviceInterfaces | hasUi"
|
||||||
slot="end"
|
slot="end"
|
||||||
fill="clear"
|
fill="clear"
|
||||||
color="primary"
|
color="primary"
|
||||||
(click)="launchUi($event, pkg.entry['service-interfaces'])"
|
(click)="launchUi($event, pkg.entry.serviceInterfaces)"
|
||||||
[disabled]="
|
[disabled]="
|
||||||
!(pkg.entry['state-info'].state | isLaunchable: pkgMainStatus.status)
|
!(pkg.entry.stateInfo.state | isLaunchable: pkgMainStatus.status)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<ion-icon slot="icon-only" name="open-outline"></ion-icon>
|
<ion-icon slot="icon-only" name="open-outline"></ion-icon>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export class AppListPkgComponent {
|
|||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
launchUi(e: Event, interfaces: PackageDataEntry['service-interfaces']): void {
|
launchUi(e: Event, interfaces: PackageDataEntry['serviceInterfaces']): void {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.launcherService.launch(interfaces)
|
this.launcherService.launch(interfaces)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { getManifest } from 'src/app/util/get-package-data'
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AppListPage {
|
export class AppListPage {
|
||||||
readonly pkgs$ = this.patch.watch$('package-data').pipe(
|
readonly pkgs$ = this.patch.watch$('packageData').pipe(
|
||||||
map(pkgs => Object.values(pkgs)),
|
map(pkgs => Object.values(pkgs)),
|
||||||
startWith([]),
|
startWith([]),
|
||||||
pairwise(),
|
pairwise(),
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export class PackageInfoPipe implements PipeTransform {
|
|||||||
|
|
||||||
transform(pkgId: string): Observable<PkgInfo> {
|
transform(pkgId: string): Observable<PkgInfo> {
|
||||||
return combineLatest([
|
return combineLatest([
|
||||||
this.patch.watch$('package-data', pkgId).pipe(filter(Boolean)),
|
this.patch.watch$('packageData', pkgId).pipe(filter(Boolean)),
|
||||||
this.depErrorService.getPkgDepErrors$(pkgId),
|
this.depErrorService.getPkgDepErrors$(pkgId),
|
||||||
]).pipe(map(([pkg, depErrors]) => getPackageInfo(pkg, depErrors)))
|
]).pipe(map(([pkg, depErrors]) => getPackageInfo(pkg, depErrors)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export class AppPropertiesPage {
|
|||||||
unmasked: { [key: string]: boolean } = {}
|
unmasked: { [key: string]: boolean } = {}
|
||||||
|
|
||||||
stopped$ = this.patch
|
stopped$ = this.patch
|
||||||
.watch$('package-data', this.pkgId, 'status', 'main', 'status')
|
.watch$('packageData', this.pkgId, 'status', 'main', 'status')
|
||||||
.pipe(map(status => status === PackageMainStatus.Stopped))
|
.pipe(map(status => status === PackageMainStatus.Stopped))
|
||||||
|
|
||||||
@ViewChild(IonBackButtonDelegate, { static: false })
|
@ViewChild(IonBackButtonDelegate, { static: false })
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<!-- ** installing, updating, restoring ** -->
|
<!-- ** installing, updating, restoring ** -->
|
||||||
<ng-container *ngIf="showProgress(pkg); else installed">
|
<ng-container *ngIf="showProgress(pkg); else installed">
|
||||||
<app-show-progress
|
<app-show-progress
|
||||||
*ngIf="pkg['state-info']['installing-info'] as installingInfo"
|
*ngIf="pkg.stateInfo.installingInfo as installingInfo"
|
||||||
[phases]="installingInfo.progress.phases"
|
[phases]="installingInfo.progress.phases"
|
||||||
></app-show-progress>
|
></app-show-progress>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<app-show-menu [buttons]="pkg | toButtons"></app-show-menu>
|
<app-show-menu [buttons]="pkg | toButtons"></app-show-menu>
|
||||||
<!-- ** additional ** -->
|
<!-- ** additional ** -->
|
||||||
<app-show-additional
|
<app-show-additional
|
||||||
[manifest]="pkg['state-info'].manifest"
|
[manifest]="pkg.stateInfo.manifest"
|
||||||
></app-show-additional>
|
></app-show-additional>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export class AppShowPage {
|
|||||||
private readonly pkgId = getPkgId(this.route)
|
private readonly pkgId = getPkgId(this.route)
|
||||||
|
|
||||||
readonly pkgPlus$ = combineLatest([
|
readonly pkgPlus$ = combineLatest([
|
||||||
this.patch.watch$('package-data', this.pkgId),
|
this.patch.watch$('packageData', this.pkgId),
|
||||||
this.depErrorService.getPkgDepErrors$(this.pkgId),
|
this.depErrorService.getPkgDepErrors$(this.pkgId),
|
||||||
]).pipe(
|
]).pipe(
|
||||||
tap(([pkg, _]) => {
|
tap(([pkg, _]) => {
|
||||||
@@ -85,7 +85,7 @@ export class AppShowPage {
|
|||||||
): DependencyInfo[] {
|
): DependencyInfo[] {
|
||||||
const manifest = getManifest(pkg)
|
const manifest = getManifest(pkg)
|
||||||
|
|
||||||
return Object.keys(pkg['current-dependencies'])
|
return Object.keys(pkg.currentDependencies)
|
||||||
.filter(id => !!manifest.dependencies[id])
|
.filter(id => !!manifest.dependencies[id])
|
||||||
.map(id => this.getDepValues(pkg, manifest, id, depErrors))
|
.map(id => this.getDepValues(pkg, manifest, id, depErrors))
|
||||||
}
|
}
|
||||||
@@ -103,11 +103,11 @@ export class AppShowPage {
|
|||||||
depErrors,
|
depErrors,
|
||||||
)
|
)
|
||||||
|
|
||||||
const depInfo = pkg['dependency-info'][depId]
|
const depInfo = pkg.dependencyInfo[depId]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: depId,
|
id: depId,
|
||||||
version: pkg['current-dependencies'][depId].versionRange, // @TODO do we want this version range?
|
version: pkg.currentDependencies[depId].versionRange, // @TODO do we want this version range?
|
||||||
title: depInfo?.title || depId,
|
title: depInfo?.title || depId,
|
||||||
icon: depInfo?.icon || '',
|
icon: depInfo?.icon || '',
|
||||||
errorText: errorText
|
errorText: errorText
|
||||||
@@ -184,7 +184,7 @@ export class AppShowPage {
|
|||||||
const dependentInfo: DependentInfo = {
|
const dependentInfo: DependentInfo = {
|
||||||
id: pkgManifest.id,
|
id: pkgManifest.id,
|
||||||
title: pkgManifest.title,
|
title: pkgManifest.title,
|
||||||
version: pkg['current-dependencies'][depId].versionRange,
|
version: pkg.currentDependencies[depId].versionRange,
|
||||||
}
|
}
|
||||||
const navigationExtras: NavigationExtras = {
|
const navigationExtras: NavigationExtras = {
|
||||||
state: { dependentInfo },
|
state: { dependentInfo },
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item
|
<ion-item
|
||||||
*ngIf="manifest['git-hash'] as gitHash; else noHash"
|
*ngIf="manifest.gitHash as gitHash; else noHash"
|
||||||
button
|
button
|
||||||
detail="false"
|
detail="false"
|
||||||
(click)="copy(gitHash)"
|
(click)="copy(gitHash)"
|
||||||
@@ -37,15 +37,15 @@
|
|||||||
<ion-icon slot="end" name="chevron-forward"></ion-icon>
|
<ion-icon slot="end" name="chevron-forward"></ion-icon>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item
|
<ion-item
|
||||||
[href]="manifest['marketing-site']"
|
[href]="manifest.marketingSite"
|
||||||
[disabled]="!manifest['marketing-site']"
|
[disabled]="!manifest.marketingSite"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
detail="false"
|
detail="false"
|
||||||
>
|
>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>Marketing Site</h2>
|
<h2>Marketing Site</h2>
|
||||||
<p>{{ manifest['marketing-site'] || 'Not provided' }}</p>
|
<p>{{ manifest.marketingSite || 'Not provided' }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
@@ -54,52 +54,52 @@
|
|||||||
<ion-col responsiveCol sizeXs="12" sizeMd="6">
|
<ion-col responsiveCol sizeXs="12" sizeMd="6">
|
||||||
<ion-item-group>
|
<ion-item-group>
|
||||||
<ion-item
|
<ion-item
|
||||||
[href]="manifest['upstream-repo']"
|
[href]="manifest.upstreamRepo"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
detail="false"
|
detail="false"
|
||||||
>
|
>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>Source Repository</h2>
|
<h2>Source Repository</h2>
|
||||||
<p>{{ manifest['upstream-repo'] }}</p>
|
<p>{{ manifest.upstreamRepo }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item
|
<ion-item
|
||||||
[href]="manifest['wrapper-repo']"
|
[href]="manifest.wrapperRepo"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
detail="false"
|
detail="false"
|
||||||
>
|
>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>Wrapper Repository</h2>
|
<h2>Wrapper Repository</h2>
|
||||||
<p>{{ manifest['wrapper-repo'] }}</p>
|
<p>{{ manifest.wrapperRepo }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item
|
<ion-item
|
||||||
[href]="manifest['support-site']"
|
[href]="manifest.supportSite"
|
||||||
[disabled]="!manifest['support-site']"
|
[disabled]="!manifest.supportSite"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
detail="false"
|
detail="false"
|
||||||
>
|
>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>Support Site</h2>
|
<h2>Support Site</h2>
|
||||||
<p>{{ manifest['support-site'] || 'Not provided' }}</p>
|
<p>{{ manifest.supportSite || 'Not provided' }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item
|
<ion-item
|
||||||
[href]="manifest['donation-url']"
|
[href]="manifest.donationUrl"
|
||||||
[disabled]="!manifest['donation-url']"
|
[disabled]="!manifest.donationUrl"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
detail="false"
|
detail="false"
|
||||||
>
|
>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>Donation Link</h2>
|
<h2>Donation Link</h2>
|
||||||
<p>{{ manifest['donation-url'] || 'Not provided' }}</p>
|
<p>{{ manifest.donationUrl || 'Not provided' }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<status
|
<status
|
||||||
size="x-large"
|
size="x-large"
|
||||||
weight="600"
|
weight="600"
|
||||||
[installingInfo]="$any(pkg['state-info'])['installing-info']"
|
[installingInfo]="$any(pkg.stateInfo).installingInfo"
|
||||||
[rendering]="PR[status.primary]"
|
[rendering]="PR[status.primary]"
|
||||||
[sigtermTimeout]="sigtermTimeout"
|
[sigtermTimeout]="sigtermTimeout"
|
||||||
></status>
|
></status>
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
class="action-button"
|
class="action-button"
|
||||||
color="primary"
|
color="primary"
|
||||||
[disabled]="
|
[disabled]="
|
||||||
!(pkg['state-info'].state | isLaunchable: pkgStatus.main.status)
|
!(pkg.stateInfo.state | isLaunchable: pkgStatus.main.status)
|
||||||
"
|
"
|
||||||
(click)="launchUi(interfaces)"
|
(click)="launchUi(interfaces)"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ export class AppShowStatusComponent {
|
|||||||
private readonly patch: PatchDB<DataModel>,
|
private readonly patch: PatchDB<DataModel>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
get interfaces(): PackageDataEntry['service-interfaces'] {
|
get interfaces(): PackageDataEntry['serviceInterfaces'] {
|
||||||
return this.pkg['service-interfaces']
|
return this.pkg.serviceInterfaces
|
||||||
}
|
}
|
||||||
|
|
||||||
get pkgStatus(): Status {
|
get pkgStatus(): Status {
|
||||||
@@ -89,7 +89,7 @@ export class AppShowStatusComponent {
|
|||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
launchUi(interfaces: PackageDataEntry['service-interfaces']): void {
|
launchUi(interfaces: PackageDataEntry['serviceInterfaces']): void {
|
||||||
this.launcherService.launch(interfaces)
|
this.launcherService.launch(interfaces)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export class ToButtonsPipe implements PipeTransform {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
transform(pkg: PackageDataEntry<InstalledState>): Button[] {
|
transform(pkg: PackageDataEntry<InstalledState>): Button[] {
|
||||||
const manifest = pkg['state-info'].manifest
|
const manifest = pkg.stateInfo.manifest
|
||||||
|
|
||||||
return [
|
return [
|
||||||
// instructions
|
// instructions
|
||||||
@@ -46,7 +46,7 @@ export class ToButtonsPipe implements PipeTransform {
|
|||||||
description: `Understand how to use ${manifest.title}`,
|
description: `Understand how to use ${manifest.title}`,
|
||||||
icon: 'list-outline',
|
icon: 'list-outline',
|
||||||
highlighted$: this.patch
|
highlighted$: this.patch
|
||||||
.watch$('ui', 'ack-instructions', manifest.id)
|
.watch$('ui', 'ackInstructions', manifest.id)
|
||||||
.pipe(map(seen => !seen)),
|
.pipe(map(seen => !seen)),
|
||||||
},
|
},
|
||||||
// config
|
// config
|
||||||
@@ -122,7 +122,7 @@ export class ToButtonsPipe implements PipeTransform {
|
|||||||
private viewInMarketplaceButton(
|
private viewInMarketplaceButton(
|
||||||
pkg: PackageDataEntry<InstalledState>,
|
pkg: PackageDataEntry<InstalledState>,
|
||||||
): Button {
|
): Button {
|
||||||
const url = pkg['marketplace-url']
|
const url = pkg.marketplaceUrl
|
||||||
const queryParams = url ? { url } : {}
|
const queryParams = url ? { url } : {}
|
||||||
|
|
||||||
let button: Button = {
|
let button: Button = {
|
||||||
@@ -130,7 +130,7 @@ export class ToButtonsPipe implements PipeTransform {
|
|||||||
icon: 'storefront-outline',
|
icon: 'storefront-outline',
|
||||||
action: () =>
|
action: () =>
|
||||||
this.navCtrl.navigateForward(
|
this.navCtrl.navigateForward(
|
||||||
[`marketplace/${pkg['state-info'].manifest.id}`],
|
[`marketplace/${pkg.stateInfo.manifest.id}`],
|
||||||
{
|
{
|
||||||
queryParams,
|
queryParams,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,16 +19,14 @@ export class ToHealthChecksPipe implements PipeTransform {
|
|||||||
transform(
|
transform(
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
): Observable<Record<string, HealthCheckResult | null> | null> {
|
): Observable<Record<string, HealthCheckResult | null> | null> {
|
||||||
return this.patch
|
return this.patch.watch$('packageData', manifest.id, 'status', 'main').pipe(
|
||||||
.watch$('package-data', manifest.id, 'status', 'main')
|
map(main => {
|
||||||
.pipe(
|
return main.status === PackageMainStatus.Running &&
|
||||||
map(main => {
|
!isEmptyObject(main.health)
|
||||||
return main.status === PackageMainStatus.Running &&
|
? main.health
|
||||||
!isEmptyObject(main.health)
|
: null
|
||||||
? main.health
|
}),
|
||||||
: null
|
startWith(null),
|
||||||
}),
|
)
|
||||||
startWith(null),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { IonicModule } from '@ionic/angular'
|
|
||||||
import { RouterModule, Routes } from '@angular/router'
|
|
||||||
import { DevConfigPage } from './dev-config.page'
|
|
||||||
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
|
||||||
import { BackupReportPageModule } from 'src/app/modals/backup-report/backup-report.module'
|
|
||||||
import { FormsModule } from '@angular/forms'
|
|
||||||
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: DevConfigPage,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
IonicModule,
|
|
||||||
RouterModule.forChild(routes),
|
|
||||||
BadgeMenuComponentModule,
|
|
||||||
BackupReportPageModule,
|
|
||||||
FormsModule,
|
|
||||||
MonacoEditorModule,
|
|
||||||
],
|
|
||||||
declarations: [DevConfigPage],
|
|
||||||
})
|
|
||||||
export class DevConfigPageModule {}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-buttons slot="start">
|
|
||||||
<ion-back-button
|
|
||||||
[defaultHref]="'/developer/projects/' + projectId"
|
|
||||||
></ion-back-button>
|
|
||||||
</ion-buttons>
|
|
||||||
<ion-title>
|
|
||||||
Config
|
|
||||||
<ion-spinner
|
|
||||||
*ngIf="saving"
|
|
||||||
name="crescent"
|
|
||||||
style="transform: scale(0.55); position: absolute"
|
|
||||||
></ion-spinner>
|
|
||||||
</ion-title>
|
|
||||||
<ion-buttons slot="end">
|
|
||||||
<ion-button (click)="preview()">Preview</ion-button>
|
|
||||||
</ion-buttons>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-content>
|
|
||||||
<ngx-monaco-editor
|
|
||||||
(keyup)="save()"
|
|
||||||
[options]="editorOptions"
|
|
||||||
[(ngModel)]="code"
|
|
||||||
></ngx-monaco-editor>
|
|
||||||
</ion-content>
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
import { Component } from '@angular/core'
|
|
||||||
import { ActivatedRoute } from '@angular/router'
|
|
||||||
import { ModalController } from '@ionic/angular'
|
|
||||||
import { debounce, ErrorToastService } from '@start9labs/shared'
|
|
||||||
import * as yaml from 'js-yaml'
|
|
||||||
import { filter, take } from 'rxjs/operators'
|
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|
||||||
import { PatchDB } from 'patch-db-client'
|
|
||||||
import { getProjectId } from 'src/app/util/get-project-id'
|
|
||||||
import { GenericFormPage } from '../../../modals/generic-form/generic-form.page'
|
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'dev-config',
|
|
||||||
templateUrl: 'dev-config.page.html',
|
|
||||||
styleUrls: ['dev-config.page.scss'],
|
|
||||||
})
|
|
||||||
export class DevConfigPage {
|
|
||||||
readonly projectId = getProjectId(this.route)
|
|
||||||
editorOptions = { theme: 'vs-dark', language: 'yaml' }
|
|
||||||
code: string = ''
|
|
||||||
saving: boolean = false
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly route: ActivatedRoute,
|
|
||||||
private readonly errToast: ErrorToastService,
|
|
||||||
private readonly modalCtrl: ModalController,
|
|
||||||
private readonly patch: PatchDB<DataModel>,
|
|
||||||
private readonly api: ApiService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.patch
|
|
||||||
.watch$('ui', 'dev', this.projectId, 'config')
|
|
||||||
.pipe(filter(Boolean), take(1))
|
|
||||||
.subscribe(config => {
|
|
||||||
this.code = config
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async preview() {
|
|
||||||
let doc: any
|
|
||||||
try {
|
|
||||||
doc = yaml.load(this.code)
|
|
||||||
} catch (e: any) {
|
|
||||||
this.errToast.present(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
const modal = await this.modalCtrl.create({
|
|
||||||
component: GenericFormPage,
|
|
||||||
componentProps: {
|
|
||||||
title: 'Config Sample',
|
|
||||||
spec: JSON.parse(JSON.stringify(doc, null, 2)),
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: 'OK',
|
|
||||||
handler: () => {
|
|
||||||
return
|
|
||||||
},
|
|
||||||
isSubmit: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
await modal.present()
|
|
||||||
}
|
|
||||||
|
|
||||||
@debounce(1000)
|
|
||||||
async save() {
|
|
||||||
this.saving = true
|
|
||||||
try {
|
|
||||||
await this.api.setDbValue<string>(
|
|
||||||
['dev', this.projectId, 'config'],
|
|
||||||
this.code,
|
|
||||||
)
|
|
||||||
} catch (e: any) {
|
|
||||||
this.errToast.present(e)
|
|
||||||
} finally {
|
|
||||||
this.saving = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { IonicModule } from '@ionic/angular'
|
|
||||||
import { RouterModule, Routes } from '@angular/router'
|
|
||||||
import { DevInstructionsPage } from './dev-instructions.page'
|
|
||||||
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
|
||||||
import { BackupReportPageModule } from 'src/app/modals/backup-report/backup-report.module'
|
|
||||||
import { FormsModule } from '@angular/forms'
|
|
||||||
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: DevInstructionsPage,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
IonicModule,
|
|
||||||
RouterModule.forChild(routes),
|
|
||||||
BadgeMenuComponentModule,
|
|
||||||
BackupReportPageModule,
|
|
||||||
FormsModule,
|
|
||||||
MonacoEditorModule,
|
|
||||||
],
|
|
||||||
declarations: [DevInstructionsPage],
|
|
||||||
})
|
|
||||||
export class DevInstructionsPageModule {}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-buttons slot="start">
|
|
||||||
<ion-back-button
|
|
||||||
[defaultHref]="'/developer/projects/' + projectId"
|
|
||||||
></ion-back-button>
|
|
||||||
</ion-buttons>
|
|
||||||
<ion-title>
|
|
||||||
Instructions
|
|
||||||
<ion-spinner
|
|
||||||
*ngIf="saving"
|
|
||||||
name="crescent"
|
|
||||||
style="transform: scale(0.55); position: absolute"
|
|
||||||
></ion-spinner>
|
|
||||||
</ion-title>
|
|
||||||
<ion-buttons slot="end">
|
|
||||||
<ion-button (click)="preview()">Preview</ion-button>
|
|
||||||
</ion-buttons>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-content>
|
|
||||||
<ngx-monaco-editor
|
|
||||||
(keyup)="save()"
|
|
||||||
[options]="editorOptions"
|
|
||||||
[(ngModel)]="code"
|
|
||||||
></ngx-monaco-editor>
|
|
||||||
</ion-content>
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import { Component } from '@angular/core'
|
|
||||||
import { ActivatedRoute } from '@angular/router'
|
|
||||||
import { ModalController } from '@ionic/angular'
|
|
||||||
import { filter, take } from 'rxjs/operators'
|
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|
||||||
import {
|
|
||||||
debounce,
|
|
||||||
ErrorToastService,
|
|
||||||
MarkdownComponent,
|
|
||||||
} from '@start9labs/shared'
|
|
||||||
import { PatchDB } from 'patch-db-client'
|
|
||||||
import { getProjectId } from 'src/app/util/get-project-id'
|
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'dev-instructions',
|
|
||||||
templateUrl: 'dev-instructions.page.html',
|
|
||||||
styleUrls: ['dev-instructions.page.scss'],
|
|
||||||
})
|
|
||||||
export class DevInstructionsPage {
|
|
||||||
readonly projectId = getProjectId(this.route)
|
|
||||||
editorOptions = { theme: 'vs-dark', language: 'markdown' }
|
|
||||||
code = ''
|
|
||||||
saving = false
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly route: ActivatedRoute,
|
|
||||||
private readonly errToast: ErrorToastService,
|
|
||||||
private readonly modalCtrl: ModalController,
|
|
||||||
private readonly patch: PatchDB<DataModel>,
|
|
||||||
private readonly api: ApiService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.patch
|
|
||||||
.watch$('ui', 'dev', this.projectId, 'instructions')
|
|
||||||
.pipe(filter(Boolean), take(1))
|
|
||||||
.subscribe(config => {
|
|
||||||
this.code = config
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async preview() {
|
|
||||||
const modal = await this.modalCtrl.create({
|
|
||||||
componentProps: {
|
|
||||||
title: 'Instructions Sample',
|
|
||||||
content: this.code,
|
|
||||||
},
|
|
||||||
component: MarkdownComponent,
|
|
||||||
})
|
|
||||||
|
|
||||||
await modal.present()
|
|
||||||
}
|
|
||||||
|
|
||||||
@debounce(1000)
|
|
||||||
async save() {
|
|
||||||
this.saving = true
|
|
||||||
try {
|
|
||||||
await this.api.setDbValue<string>(
|
|
||||||
['dev', this.projectId, 'instructions'],
|
|
||||||
this.code,
|
|
||||||
)
|
|
||||||
} catch (e: any) {
|
|
||||||
this.errToast.present(e)
|
|
||||||
} finally {
|
|
||||||
this.saving = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { IonicModule } from '@ionic/angular'
|
|
||||||
import { RouterModule, Routes } from '@angular/router'
|
|
||||||
import { DevManifestPage } from './dev-manifest.page'
|
|
||||||
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
|
||||||
import { BackupReportPageModule } from 'src/app/modals/backup-report/backup-report.module'
|
|
||||||
import { FormsModule } from '@angular/forms'
|
|
||||||
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: DevManifestPage,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
IonicModule,
|
|
||||||
RouterModule.forChild(routes),
|
|
||||||
BadgeMenuComponentModule,
|
|
||||||
BackupReportPageModule,
|
|
||||||
FormsModule,
|
|
||||||
MonacoEditorModule,
|
|
||||||
],
|
|
||||||
declarations: [DevManifestPage],
|
|
||||||
})
|
|
||||||
export class DevManifestPageModule {}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-buttons slot="start">
|
|
||||||
<ion-back-button
|
|
||||||
[defaultHref]="'/developer/projects/' + projectId"
|
|
||||||
></ion-back-button>
|
|
||||||
</ion-buttons>
|
|
||||||
<ion-title>Manifest</ion-title>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-content>
|
|
||||||
<ngx-monaco-editor
|
|
||||||
[options]="editorOptions"
|
|
||||||
[(ngModel)]="manifest"
|
|
||||||
></ngx-monaco-editor>
|
|
||||||
</ion-content>
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { Component } from '@angular/core'
|
|
||||||
import { ActivatedRoute } from '@angular/router'
|
|
||||||
import * as yaml from 'js-yaml'
|
|
||||||
import { take } from 'rxjs/operators'
|
|
||||||
import { PatchDB } from 'patch-db-client'
|
|
||||||
import { getProjectId } from 'src/app/util/get-project-id'
|
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'dev-manifest',
|
|
||||||
templateUrl: 'dev-manifest.page.html',
|
|
||||||
styleUrls: ['dev-manifest.page.scss'],
|
|
||||||
})
|
|
||||||
export class DevManifestPage {
|
|
||||||
readonly projectId = getProjectId(this.route)
|
|
||||||
editorOptions = { theme: 'vs-dark', language: 'yaml', readOnly: true }
|
|
||||||
manifest: string = ''
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly route: ActivatedRoute,
|
|
||||||
private readonly patch: PatchDB<DataModel>,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.patch
|
|
||||||
.watch$('ui', 'dev', this.projectId)
|
|
||||||
.pipe(take(1))
|
|
||||||
.subscribe(devData => {
|
|
||||||
this.manifest = yaml.dump(devData['basic-info'])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { IonicModule } from '@ionic/angular'
|
|
||||||
import { RouterModule, Routes } from '@angular/router'
|
|
||||||
import { DeveloperListPage } from './developer-list.page'
|
|
||||||
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
|
||||||
import { BackupReportPageModule } from 'src/app/modals/backup-report/backup-report.module'
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: DeveloperListPage,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
IonicModule,
|
|
||||||
RouterModule.forChild(routes),
|
|
||||||
BadgeMenuComponentModule,
|
|
||||||
BackupReportPageModule,
|
|
||||||
],
|
|
||||||
declarations: [DeveloperListPage],
|
|
||||||
})
|
|
||||||
export class DeveloperListPageModule {}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-buttons slot="start">
|
|
||||||
<ion-back-button></ion-back-button>
|
|
||||||
</ion-buttons>
|
|
||||||
<ion-title>Developer Tools</ion-title>
|
|
||||||
<ion-buttons slot="end">
|
|
||||||
<badge-menu-button></badge-menu-button>
|
|
||||||
</ion-buttons>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-content>
|
|
||||||
<ion-item-divider>Projects</ion-item-divider>
|
|
||||||
|
|
||||||
<ion-item button detail="false" (click)="openCreateProjectModal()">
|
|
||||||
<ion-icon slot="start" name="add" color="dark"></ion-icon>
|
|
||||||
<ion-label>
|
|
||||||
<ion-text color="dark">Create project</ion-text>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<ion-item
|
|
||||||
button
|
|
||||||
*ngFor="let entry of devData | keyvalue"
|
|
||||||
[routerLink]="[entry.key]"
|
|
||||||
>
|
|
||||||
<p>{{ entry.value.name }}</p>
|
|
||||||
<ion-button
|
|
||||||
slot="end"
|
|
||||||
fill="clear"
|
|
||||||
(click)="presentAction(entry.key, $event)"
|
|
||||||
>
|
|
||||||
<ion-icon name="ellipsis-horizontal"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
</ion-item>
|
|
||||||
</ion-content>
|
|
||||||
@@ -1,266 +0,0 @@
|
|||||||
import { Component } from '@angular/core'
|
|
||||||
import {
|
|
||||||
ActionSheetButton,
|
|
||||||
ActionSheetController,
|
|
||||||
AlertController,
|
|
||||||
LoadingController,
|
|
||||||
ModalController,
|
|
||||||
} from '@ionic/angular'
|
|
||||||
import {
|
|
||||||
GenericInputComponent,
|
|
||||||
GenericInputOptions,
|
|
||||||
} from 'src/app/modals/generic-input/generic-input.component'
|
|
||||||
import { PatchDB } from 'patch-db-client'
|
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|
||||||
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
|
||||||
import * as yaml from 'js-yaml'
|
|
||||||
import { v4 } from 'uuid'
|
|
||||||
import { DataModel, DevData } from 'src/app/services/patch-db/data-model'
|
|
||||||
import { ErrorToastService } from '@start9labs/shared'
|
|
||||||
import { TuiDestroyService } from '@taiga-ui/cdk'
|
|
||||||
import { takeUntil } from 'rxjs/operators'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'developer-list',
|
|
||||||
templateUrl: 'developer-list.page.html',
|
|
||||||
styleUrls: ['developer-list.page.scss'],
|
|
||||||
providers: [TuiDestroyService],
|
|
||||||
})
|
|
||||||
export class DeveloperListPage {
|
|
||||||
devData: DevData = {}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly modalCtrl: ModalController,
|
|
||||||
private readonly api: ApiService,
|
|
||||||
private readonly loadingCtrl: LoadingController,
|
|
||||||
private readonly errToast: ErrorToastService,
|
|
||||||
private readonly alertCtrl: AlertController,
|
|
||||||
private readonly destroy$: TuiDestroyService,
|
|
||||||
private readonly patch: PatchDB<DataModel>,
|
|
||||||
private readonly actionCtrl: ActionSheetController,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.patch
|
|
||||||
.watch$('ui', 'dev')
|
|
||||||
.pipe(takeUntil(this.destroy$))
|
|
||||||
.subscribe(dd => {
|
|
||||||
this.devData = dd
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async openCreateProjectModal() {
|
|
||||||
const projNumber = Object.keys(this.devData).length + 1
|
|
||||||
const options: GenericInputOptions = {
|
|
||||||
title: 'Add new project',
|
|
||||||
message: 'Create a new dev project.',
|
|
||||||
label: 'New project',
|
|
||||||
useMask: false,
|
|
||||||
placeholder: `Project ${projNumber}`,
|
|
||||||
nullable: true,
|
|
||||||
initialValue: `Project ${projNumber}`,
|
|
||||||
buttonText: 'Save',
|
|
||||||
submitFn: (value: string) => this.createProject(value),
|
|
||||||
}
|
|
||||||
|
|
||||||
const modal = await this.modalCtrl.create({
|
|
||||||
componentProps: { options },
|
|
||||||
cssClass: 'alertlike-modal',
|
|
||||||
presentingElement: await this.modalCtrl.getTop(),
|
|
||||||
component: GenericInputComponent,
|
|
||||||
})
|
|
||||||
|
|
||||||
await modal.present()
|
|
||||||
}
|
|
||||||
|
|
||||||
async presentAction(id: string, event: Event) {
|
|
||||||
event.stopPropagation()
|
|
||||||
event.preventDefault()
|
|
||||||
const buttons: ActionSheetButton[] = [
|
|
||||||
{
|
|
||||||
text: 'Edit Name',
|
|
||||||
icon: 'pencil',
|
|
||||||
handler: () => {
|
|
||||||
this.openEditNameModal(id)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Delete',
|
|
||||||
icon: 'trash',
|
|
||||||
role: 'destructive',
|
|
||||||
handler: () => {
|
|
||||||
this.presentAlertDelete(id)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const action = await this.actionCtrl.create({
|
|
||||||
header: this.devData[id].name,
|
|
||||||
subHeader: 'Manage project',
|
|
||||||
mode: 'ios',
|
|
||||||
buttons,
|
|
||||||
})
|
|
||||||
|
|
||||||
await action.present()
|
|
||||||
}
|
|
||||||
|
|
||||||
async openEditNameModal(id: string) {
|
|
||||||
const curName = this.devData[id].name
|
|
||||||
const options: GenericInputOptions = {
|
|
||||||
title: 'Edit Name',
|
|
||||||
message: 'Edit the name of your project.',
|
|
||||||
label: 'Name',
|
|
||||||
useMask: false,
|
|
||||||
placeholder: curName,
|
|
||||||
nullable: true,
|
|
||||||
initialValue: curName,
|
|
||||||
buttonText: 'Save',
|
|
||||||
submitFn: (value: string) => this.editName(id, value),
|
|
||||||
}
|
|
||||||
|
|
||||||
const modal = await this.modalCtrl.create({
|
|
||||||
componentProps: { options },
|
|
||||||
cssClass: 'alertlike-modal',
|
|
||||||
presentingElement: await this.modalCtrl.getTop(),
|
|
||||||
component: GenericInputComponent,
|
|
||||||
})
|
|
||||||
|
|
||||||
await modal.present()
|
|
||||||
}
|
|
||||||
|
|
||||||
async createProject(name: string) {
|
|
||||||
// fail silently if duplicate project name
|
|
||||||
if (
|
|
||||||
Object.values(this.devData)
|
|
||||||
.map(v => v.name)
|
|
||||||
.includes(name)
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
const loader = await this.loadingCtrl.create({
|
|
||||||
message: 'Creating Project...',
|
|
||||||
})
|
|
||||||
await loader.present()
|
|
||||||
|
|
||||||
try {
|
|
||||||
const id = v4()
|
|
||||||
const config = yaml
|
|
||||||
.dump(SAMPLE_CONFIG)
|
|
||||||
.replace(/warning:/g, '# Optional\n warning:')
|
|
||||||
|
|
||||||
const def = { name, config, instructions: SAMPLE_INSTUCTIONS }
|
|
||||||
await this.api.setDbValue<{
|
|
||||||
name: string
|
|
||||||
config: string
|
|
||||||
instructions: string
|
|
||||||
}>(['dev', id], def)
|
|
||||||
} catch (e: any) {
|
|
||||||
this.errToast.present(e)
|
|
||||||
} finally {
|
|
||||||
loader.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async presentAlertDelete(id: string) {
|
|
||||||
const alert = await this.alertCtrl.create({
|
|
||||||
header: 'Caution',
|
|
||||||
message: `Are you sure you want to delete this project?`,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: 'Cancel',
|
|
||||||
role: 'cancel',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Delete',
|
|
||||||
handler: () => {
|
|
||||||
this.delete(id)
|
|
||||||
},
|
|
||||||
cssClass: 'enter-click',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
await alert.present()
|
|
||||||
}
|
|
||||||
|
|
||||||
async editName(id: string, newName: string) {
|
|
||||||
const loader = await this.loadingCtrl.create({
|
|
||||||
message: 'Saving...',
|
|
||||||
})
|
|
||||||
await loader.present()
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.api.setDbValue<string>(['dev', id, 'name'], newName)
|
|
||||||
} catch (e: any) {
|
|
||||||
this.errToast.present(e)
|
|
||||||
} finally {
|
|
||||||
loader.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(id: string) {
|
|
||||||
const loader = await this.loadingCtrl.create({
|
|
||||||
message: 'Removing Project...',
|
|
||||||
})
|
|
||||||
await loader.present()
|
|
||||||
|
|
||||||
try {
|
|
||||||
const devDataToSave: DevData = JSON.parse(JSON.stringify(this.devData))
|
|
||||||
delete devDataToSave[id]
|
|
||||||
await this.api.setDbValue<DevData>(['dev'], devDataToSave)
|
|
||||||
} catch (e: any) {
|
|
||||||
this.errToast.present(e)
|
|
||||||
} finally {
|
|
||||||
loader.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const SAMPLE_INSTUCTIONS = `# Create Instructions using Markdown! :)`
|
|
||||||
|
|
||||||
const SAMPLE_CONFIG: ConfigSpec = {
|
|
||||||
'sample-string': {
|
|
||||||
type: 'string',
|
|
||||||
name: 'Example String Input',
|
|
||||||
nullable: false,
|
|
||||||
masked: false,
|
|
||||||
copyable: false,
|
|
||||||
// optional
|
|
||||||
description: 'Example description for required string input.',
|
|
||||||
placeholder: 'Enter string value',
|
|
||||||
pattern: '^[a-zA-Z0-9! _]+$',
|
|
||||||
'pattern-description': 'Must be alphanumeric (may contain underscore).',
|
|
||||||
},
|
|
||||||
'sample-number': {
|
|
||||||
type: 'number',
|
|
||||||
name: 'Example Number Input',
|
|
||||||
nullable: false,
|
|
||||||
range: '[5,1000000]',
|
|
||||||
integral: true,
|
|
||||||
// optional
|
|
||||||
warning: 'Example warning to display when changing this number value.',
|
|
||||||
units: 'ms',
|
|
||||||
description: 'Example description for optional number input.',
|
|
||||||
placeholder: 'Enter number value',
|
|
||||||
},
|
|
||||||
'sample-boolean': {
|
|
||||||
type: 'boolean',
|
|
||||||
name: 'Example Boolean Toggle',
|
|
||||||
// optional
|
|
||||||
description: 'Example description for boolean toggle',
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
'sample-enum': {
|
|
||||||
type: 'enum',
|
|
||||||
name: 'Example Enum Select',
|
|
||||||
values: ['red', 'blue', 'green'],
|
|
||||||
'value-names': {
|
|
||||||
red: 'Red',
|
|
||||||
blue: 'Blue',
|
|
||||||
green: 'Green',
|
|
||||||
},
|
|
||||||
// optional
|
|
||||||
warning: 'Example warning to display when changing this enum value.',
|
|
||||||
description: 'Example description for enum select',
|
|
||||||
default: 'red',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { IonicModule } from '@ionic/angular'
|
|
||||||
import { RouterModule, Routes } from '@angular/router'
|
|
||||||
import { DeveloperMenuPage } from './developer-menu.page'
|
|
||||||
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
|
||||||
import { BackupReportPageModule } from 'src/app/modals/backup-report/backup-report.module'
|
|
||||||
import { GenericFormPageModule } from 'src/app/modals/generic-form/generic-form.module'
|
|
||||||
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'
|
|
||||||
import { FormsModule } from '@angular/forms'
|
|
||||||
import { SharedPipesModule } from '@start9labs/shared'
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: DeveloperMenuPage,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
IonicModule,
|
|
||||||
RouterModule.forChild(routes),
|
|
||||||
BadgeMenuComponentModule,
|
|
||||||
BackupReportPageModule,
|
|
||||||
GenericFormPageModule,
|
|
||||||
FormsModule,
|
|
||||||
MonacoEditorModule,
|
|
||||||
SharedPipesModule,
|
|
||||||
],
|
|
||||||
declarations: [DeveloperMenuPage],
|
|
||||||
})
|
|
||||||
export class DeveloperMenuPageModule {}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-buttons slot="start">
|
|
||||||
<ion-back-button defaultHref="/developer"></ion-back-button>
|
|
||||||
</ion-buttons>
|
|
||||||
<ion-title>{{ (projectData$ | async)?.name || '' }}</ion-title>
|
|
||||||
<ion-buttons slot="end">
|
|
||||||
<ion-button routerLink="manifest">View Manifest</ion-button>
|
|
||||||
</ion-buttons>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-content>
|
|
||||||
<ion-item
|
|
||||||
*ngIf="projectData$ | async as projectData"
|
|
||||||
button
|
|
||||||
(click)="openBasicInfoModal(projectData)"
|
|
||||||
>
|
|
||||||
<ion-icon slot="start" name="information-circle-outline"></ion-icon>
|
|
||||||
<ion-label>
|
|
||||||
<h2>Basic Info</h2>
|
|
||||||
<p>Complete basic info for your package</p>
|
|
||||||
</ion-label>
|
|
||||||
<ion-icon
|
|
||||||
slot="end"
|
|
||||||
color="success"
|
|
||||||
name="checkmark"
|
|
||||||
*ngIf="!(projectData['basic-info'] | empty)"
|
|
||||||
></ion-icon>
|
|
||||||
<ion-icon
|
|
||||||
slot="end"
|
|
||||||
color="warning"
|
|
||||||
name="remove-outline"
|
|
||||||
*ngIf="projectData['basic-info'] | empty"
|
|
||||||
></ion-icon>
|
|
||||||
</ion-item>
|
|
||||||
<ion-item button detail routerLink="instructions" routerDirection="forward">
|
|
||||||
<ion-icon slot="start" name="list-outline"></ion-icon>
|
|
||||||
<ion-label>
|
|
||||||
<h2>Instructions Generator</h2>
|
|
||||||
<p>Create instructions and see how they will appear to the end user</p>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
<ion-item button detail routerLink="config">
|
|
||||||
<ion-icon slot="start" name="construct-outline"></ion-icon>
|
|
||||||
<ion-label>
|
|
||||||
<h2>Config Generator</h2>
|
|
||||||
<p>Edit the config with YAML and see it in real time</p>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
</ion-content>
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
|
||||||
import { ActivatedRoute } from '@angular/router'
|
|
||||||
import { LoadingController, ModalController } from '@ionic/angular'
|
|
||||||
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
|
||||||
import { BasicInfo, getBasicInfoSpec } from './form-info'
|
|
||||||
import { PatchDB } from 'patch-db-client'
|
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|
||||||
import { ErrorToastService } from '@start9labs/shared'
|
|
||||||
import { getProjectId } from 'src/app/util/get-project-id'
|
|
||||||
import { DataModel, DevProjectData } from 'src/app/services/patch-db/data-model'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'developer-menu',
|
|
||||||
templateUrl: 'developer-menu.page.html',
|
|
||||||
styleUrls: ['developer-menu.page.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class DeveloperMenuPage {
|
|
||||||
readonly projectId = getProjectId(this.route)
|
|
||||||
readonly projectData$ = this.patch.watch$('ui', 'dev', this.projectId)
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly route: ActivatedRoute,
|
|
||||||
private readonly modalCtrl: ModalController,
|
|
||||||
private readonly loadingCtrl: LoadingController,
|
|
||||||
private readonly api: ApiService,
|
|
||||||
private readonly errToast: ErrorToastService,
|
|
||||||
private readonly patch: PatchDB<DataModel>,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async openBasicInfoModal(data: DevProjectData) {
|
|
||||||
const modal = await this.modalCtrl.create({
|
|
||||||
component: GenericFormPage,
|
|
||||||
componentProps: {
|
|
||||||
title: 'Basic Info',
|
|
||||||
spec: getBasicInfoSpec(data),
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: 'Save',
|
|
||||||
handler: (basicInfo: BasicInfo) => {
|
|
||||||
this.saveBasicInfo(basicInfo)
|
|
||||||
},
|
|
||||||
isSubmit: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
await modal.present()
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveBasicInfo(basicInfo: BasicInfo) {
|
|
||||||
const loader = await this.loadingCtrl.create({
|
|
||||||
message: 'Saving...',
|
|
||||||
})
|
|
||||||
await loader.present()
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.api.setDbValue<BasicInfo>(
|
|
||||||
['dev', this.projectId, 'basic-info'],
|
|
||||||
basicInfo,
|
|
||||||
)
|
|
||||||
} catch (e: any) {
|
|
||||||
this.errToast.present(e)
|
|
||||||
} finally {
|
|
||||||
loader.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
import { ConfigSpec } from 'src/app/pkg-config/config-types'
|
|
||||||
import { DevProjectData } from 'src/app/services/patch-db/data-model'
|
|
||||||
|
|
||||||
export type BasicInfo = {
|
|
||||||
id: string
|
|
||||||
title: string
|
|
||||||
'service-version-number': string
|
|
||||||
'release-notes': string
|
|
||||||
license: string
|
|
||||||
'wrapper-repo': string
|
|
||||||
'upstream-repo'?: string
|
|
||||||
'support-site'?: string
|
|
||||||
'marketing-site'?: string
|
|
||||||
description: {
|
|
||||||
short: string
|
|
||||||
long: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getBasicInfoSpec(devData: DevProjectData): ConfigSpec {
|
|
||||||
const basicInfo = devData['basic-info']
|
|
||||||
return {
|
|
||||||
id: {
|
|
||||||
type: 'string',
|
|
||||||
name: 'ID',
|
|
||||||
description: 'The package identifier used by the OS',
|
|
||||||
placeholder: 'e.g. bitcoind',
|
|
||||||
nullable: false,
|
|
||||||
masked: false,
|
|
||||||
copyable: false,
|
|
||||||
pattern: '^([a-z][a-z0-9]*)(-[a-z0-9]+)*$',
|
|
||||||
'pattern-description': 'Must be kebab case',
|
|
||||||
default: basicInfo?.id,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: 'string',
|
|
||||||
name: 'Service Name',
|
|
||||||
description: 'A human readable service title',
|
|
||||||
placeholder: 'e.g. Bitcoin Core',
|
|
||||||
nullable: false,
|
|
||||||
masked: false,
|
|
||||||
copyable: false,
|
|
||||||
default: basicInfo ? basicInfo.title : devData.name,
|
|
||||||
},
|
|
||||||
'service-version-number': {
|
|
||||||
type: 'string',
|
|
||||||
name: 'Service Version',
|
|
||||||
description:
|
|
||||||
'Service version - accepts up to four digits, where the last confirms to revisions necessary for StartOS - see documentation: https://github.com/Start9Labs/emver-rs. This value will change with each release of the service',
|
|
||||||
placeholder: 'e.g. 0.1.2.3',
|
|
||||||
nullable: false,
|
|
||||||
masked: false,
|
|
||||||
copyable: false,
|
|
||||||
pattern: '^([0-9]+).([0-9]+).([0-9]+).([0-9]+)$',
|
|
||||||
'pattern-description': 'Must be valid Emver version',
|
|
||||||
default: basicInfo?.['service-version-number'],
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: 'object',
|
|
||||||
name: 'Marketplace Descriptions',
|
|
||||||
spec: {
|
|
||||||
short: {
|
|
||||||
type: 'string',
|
|
||||||
name: 'Short Description',
|
|
||||||
description:
|
|
||||||
'This is the first description visible to the user in the marketplace',
|
|
||||||
nullable: false,
|
|
||||||
masked: false,
|
|
||||||
copyable: false,
|
|
||||||
textarea: true,
|
|
||||||
default: basicInfo?.description?.short,
|
|
||||||
pattern: '^.{1,320}$',
|
|
||||||
'pattern-description': 'Must be shorter than 320 characters',
|
|
||||||
},
|
|
||||||
long: {
|
|
||||||
type: 'string',
|
|
||||||
name: 'Long Description',
|
|
||||||
description: `This description will display with additional details in the service's individual marketplace page`,
|
|
||||||
nullable: false,
|
|
||||||
masked: false,
|
|
||||||
copyable: false,
|
|
||||||
textarea: true,
|
|
||||||
default: basicInfo?.description?.long,
|
|
||||||
pattern: '^.{1,5000}$',
|
|
||||||
'pattern-description': 'Must be shorter than 5000 characters',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'release-notes': {
|
|
||||||
type: 'string',
|
|
||||||
name: 'Release Notes',
|
|
||||||
description:
|
|
||||||
'Markdown supported release notes for this version of this service.',
|
|
||||||
placeholder: 'e.g. Markdown _release notes_ for **Bitcoin Core**',
|
|
||||||
nullable: false,
|
|
||||||
masked: false,
|
|
||||||
copyable: false,
|
|
||||||
textarea: true,
|
|
||||||
default: basicInfo?.['release-notes'],
|
|
||||||
},
|
|
||||||
license: {
|
|
||||||
type: 'enum',
|
|
||||||
name: 'License',
|
|
||||||
values: [
|
|
||||||
'gnu-agpl-v3',
|
|
||||||
'gnu-gpl-v3',
|
|
||||||
'gnu-lgpl-v3',
|
|
||||||
'mozilla-public-license-2.0',
|
|
||||||
'apache-license-2.0',
|
|
||||||
'mit',
|
|
||||||
'boost-software-license-1.0',
|
|
||||||
'the-unlicense',
|
|
||||||
'custom',
|
|
||||||
],
|
|
||||||
'value-names': {
|
|
||||||
'gnu-agpl-v3': 'GNU AGPLv3',
|
|
||||||
'gnu-gpl-v3': 'GNU GPLv3',
|
|
||||||
'gnu-lgpl-v3': 'GNU LGPLv3',
|
|
||||||
'mozilla-public-license-2.0': 'Mozilla Public License 2.0',
|
|
||||||
'apache-license-2.0': 'Apache License 2.0',
|
|
||||||
mit: 'mit',
|
|
||||||
'boost-software-license-1.0': 'Boost Software License 1.0',
|
|
||||||
'the-unlicense': 'The Unlicense',
|
|
||||||
custom: 'Custom',
|
|
||||||
},
|
|
||||||
description: 'Example description for enum select',
|
|
||||||
default: 'mit',
|
|
||||||
},
|
|
||||||
'wrapper-repo': {
|
|
||||||
type: 'string',
|
|
||||||
name: 'Wrapper Repo',
|
|
||||||
description:
|
|
||||||
'The Start9 wrapper repository URL for the package. This repo contains the manifest file (this), any scripts necessary for configuration, backups, actions, or health checks',
|
|
||||||
placeholder: 'e.g. www.github.com/example',
|
|
||||||
nullable: false,
|
|
||||||
masked: false,
|
|
||||||
copyable: false,
|
|
||||||
default: basicInfo?.['wrapper-repo'],
|
|
||||||
},
|
|
||||||
'upstream-repo': {
|
|
||||||
type: 'string',
|
|
||||||
name: 'Upstream Repo',
|
|
||||||
description: 'The original project repository URL',
|
|
||||||
placeholder: 'e.g. www.github.com/example',
|
|
||||||
nullable: true,
|
|
||||||
masked: false,
|
|
||||||
copyable: false,
|
|
||||||
default: basicInfo?.['upstream-repo'],
|
|
||||||
},
|
|
||||||
'support-site': {
|
|
||||||
type: 'string',
|
|
||||||
name: 'Support Site',
|
|
||||||
description: 'URL to the support site / channel for the project',
|
|
||||||
placeholder: 'e.g. start9.com/support',
|
|
||||||
nullable: true,
|
|
||||||
masked: false,
|
|
||||||
copyable: false,
|
|
||||||
default: basicInfo?.['support-site'],
|
|
||||||
},
|
|
||||||
'marketing-site': {
|
|
||||||
type: 'string',
|
|
||||||
name: 'Marketing Site',
|
|
||||||
description: 'URL to the marketing site / channel for the project',
|
|
||||||
placeholder: 'e.g. start9.com',
|
|
||||||
nullable: true,
|
|
||||||
masked: false,
|
|
||||||
copyable: false,
|
|
||||||
default: basicInfo?.['marketing-site'],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { Routes, RouterModule } from '@angular/router'
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
redirectTo: 'projects',
|
|
||||||
pathMatch: 'full',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'projects',
|
|
||||||
loadChildren: () =>
|
|
||||||
import('./developer-list/developer-list.module').then(
|
|
||||||
m => m.DeveloperListPageModule,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'projects/:projectId',
|
|
||||||
loadChildren: () =>
|
|
||||||
import('./developer-menu/developer-menu.module').then(
|
|
||||||
m => m.DeveloperMenuPageModule,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'projects/:projectId/config',
|
|
||||||
loadChildren: () =>
|
|
||||||
import('./dev-config/dev-config.module').then(m => m.DevConfigPageModule),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'projects/:projectId/instructions',
|
|
||||||
loadChildren: () =>
|
|
||||||
import('./dev-instructions/dev-instructions.module').then(
|
|
||||||
m => m.DevInstructionsPageModule,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'projects/:projectId/manifest',
|
|
||||||
loadChildren: () =>
|
|
||||||
import('./dev-manifest/dev-manifest.module').then(
|
|
||||||
m => m.DevManifestPageModule,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [RouterModule.forChild(routes)],
|
|
||||||
exports: [RouterModule],
|
|
||||||
})
|
|
||||||
export class DeveloperRoutingModule {}
|
|
||||||
@@ -29,7 +29,7 @@ export class MarketplaceListPage {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
readonly localPkgs$ = this.patch.watch$('package-data')
|
readonly localPkgs$ = this.patch.watch$('packageData')
|
||||||
|
|
||||||
readonly details$ = this.marketplaceService.getSelectedHost$().pipe(
|
readonly details$ = this.marketplaceService.getSelectedHost$().pipe(
|
||||||
map(({ url, name }) => {
|
map(({ url, name }) => {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<ng-container *ngIf="localPkg; else install">
|
<ng-container *ngIf="localPkg; else install">
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngIf="
|
*ngIf="
|
||||||
localPkg['state-info'].state === 'installed' &&
|
localPkg.stateInfo.state === 'installed' &&
|
||||||
(localPkg | toManifest) as manifest
|
(localPkg | toManifest) as manifest
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export class MarketplaceShowControlsComponent {
|
|||||||
if (!this.localPkg) {
|
if (!this.localPkg) {
|
||||||
this.alertInstall(url)
|
this.alertInstall(url)
|
||||||
} else {
|
} else {
|
||||||
const originalUrl = this.localPkg['marketplace-url']
|
const originalUrl = this.localPkg.marketplaceUrl
|
||||||
|
|
||||||
if (!sameUrl(url, originalUrl)) {
|
if (!sameUrl(url, originalUrl)) {
|
||||||
const proceed = await this.presentAlertDifferentMarketplace(
|
const proceed = await this.presentAlertDifferentMarketplace(
|
||||||
@@ -98,12 +98,11 @@ export class MarketplaceShowControlsComponent {
|
|||||||
this.patch.watch$('ui', 'marketplace'),
|
this.patch.watch$('ui', 'marketplace'),
|
||||||
)
|
)
|
||||||
|
|
||||||
const name: string = marketplaces['known-hosts'][url]?.name || url
|
const name: string = marketplaces.knownHosts[url]?.name || url
|
||||||
|
|
||||||
let originalName: string | undefined
|
let originalName: string | undefined
|
||||||
if (originalUrl) {
|
if (originalUrl) {
|
||||||
originalName =
|
originalName = marketplaces.knownHosts[originalUrl]?.name || originalUrl
|
||||||
marketplaces['known-hosts'][originalUrl]?.name || originalUrl
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise(async resolve => {
|
return new Promise(async resolve => {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export class MarketplaceShowPage {
|
|||||||
readonly loadVersion$ = new BehaviorSubject<string>('*')
|
readonly loadVersion$ = new BehaviorSubject<string>('*')
|
||||||
|
|
||||||
readonly localPkg$ = this.patch
|
readonly localPkg$ = this.patch
|
||||||
.watch$('package-data', this.pkgId)
|
.watch$('packageData', this.pkgId)
|
||||||
.pipe(filter(Boolean), shareReplay({ bufferSize: 1, refCount: true }))
|
.pipe(filter(Boolean), shareReplay({ bufferSize: 1, refCount: true }))
|
||||||
|
|
||||||
readonly pkg$ = this.loadVersion$.pipe(
|
readonly pkg$ = this.loadVersion$.pipe(
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
>
|
>
|
||||||
<ion-text
|
<ion-text
|
||||||
*ngIf="
|
*ngIf="
|
||||||
localPkg['state-info']['installing-info']!.progress.overall
|
localPkg.stateInfo.installingInfo.progress.overall
|
||||||
| installingProgressString as progress
|
| installingProgressString as progress
|
||||||
"
|
"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>
|
<h2>
|
||||||
<b>
|
<b>
|
||||||
<span *ngIf="not['package-id'] as pkgId">
|
<span *ngIf="not.packageId as pkgId">
|
||||||
{{ packageData[pkgId] ? (packageData[pkgId] |
|
{{ packageData[pkgId] ? (packageData[pkgId] |
|
||||||
toManifest).title : pkgId }} -
|
toManifest).title : pkgId }} -
|
||||||
</span>
|
</span>
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
View Full Message
|
View Full Message
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p>{{ not['created-at'] | date: 'medium' }}</p>
|
<p>{{ not.createdAt | date: 'medium' }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-button
|
<ion-button
|
||||||
*ngIf="not.code === 1"
|
*ngIf="not.code === 1"
|
||||||
@@ -116,11 +116,11 @@
|
|||||||
View Report
|
View Report
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button
|
<ion-button
|
||||||
*ngIf="not['package-id'] && packageData[not['package-id']]"
|
*ngIf="not.packageId && packageData[not.packageId]"
|
||||||
slot="end"
|
slot="end"
|
||||||
fill="clear"
|
fill="clear"
|
||||||
color="dark"
|
color="dark"
|
||||||
[routerLink]="['/services', not['package-id']]"
|
[routerLink]="['/services', not.packageId]"
|
||||||
>
|
>
|
||||||
View Service
|
View Service
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export class NotificationsPage {
|
|||||||
needInfinite = false
|
needInfinite = false
|
||||||
fromToast = !!this.route.snapshot.queryParamMap.get('toast')
|
fromToast = !!this.route.snapshot.queryParamMap.get('toast')
|
||||||
readonly perPage = 40
|
readonly perPage = 40
|
||||||
readonly packageData$ = this.patch.watch$('package-data').pipe(first())
|
readonly packageData$ = this.patch.watch$('packageData').pipe(first())
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly embassyApi: ApiService,
|
private readonly embassyApi: ApiService,
|
||||||
@@ -116,7 +116,7 @@ export class NotificationsPage {
|
|||||||
component: BackupReportPage,
|
component: BackupReportPage,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
report: notification.data,
|
report: notification.data,
|
||||||
timestamp: notification['created-at'],
|
timestamp: notification.createdAt,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await modal.present()
|
await modal.present()
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export class RestorePage {
|
|||||||
useMask: true,
|
useMask: true,
|
||||||
buttonText: 'Next',
|
buttonText: 'Next',
|
||||||
submitFn: async (password: string) => {
|
submitFn: async (password: string) => {
|
||||||
const passwordHash = target.entry['embassy-os']?.['password-hash'] || ''
|
const passwordHash = target.entry.startOs?.passwordHash || ''
|
||||||
argon2.verify(passwordHash, password)
|
argon2.verify(passwordHash, password)
|
||||||
await this.restoreFromBackup(target, password)
|
await this.restoreFromBackup(target, password)
|
||||||
},
|
},
|
||||||
@@ -71,7 +71,7 @@ export class RestorePage {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const backupInfo = await this.embassyApi.getBackupInfo({
|
const backupInfo = await this.embassyApi.getBackupInfo({
|
||||||
'target-id': target.id,
|
targetId: target.id,
|
||||||
password,
|
password,
|
||||||
})
|
})
|
||||||
this.presentModalSelect(target.id, backupInfo, password, oldPassword)
|
this.presentModalSelect(target.id, backupInfo, password, oldPassword)
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ import { Observable } from 'rxjs'
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class BackingUpComponent {
|
export class BackingUpComponent {
|
||||||
readonly pkgs$ = this.patch.watch$('package-data').pipe(take(1))
|
readonly pkgs$ = this.patch.watch$('packageData').pipe(take(1))
|
||||||
readonly backupProgress$ = this.patch.watch$(
|
readonly backupProgress$ = this.patch.watch$(
|
||||||
'server-info',
|
'serverInfo',
|
||||||
'status-info',
|
'statusInfo',
|
||||||
'backup-progress',
|
'backupProgress',
|
||||||
)
|
)
|
||||||
|
|
||||||
constructor(private readonly patch: PatchDB<DataModel>) {}
|
constructor(private readonly patch: PatchDB<DataModel>) {}
|
||||||
@@ -33,7 +33,7 @@ export class BackingUpComponent {
|
|||||||
})
|
})
|
||||||
export class PkgMainStatusPipe implements PipeTransform {
|
export class PkgMainStatusPipe implements PipeTransform {
|
||||||
transform(pkgId: string): Observable<PackageMainStatus> {
|
transform(pkgId: string): Observable<PackageMainStatus> {
|
||||||
return this.patch.watch$('package-data', pkgId, 'status', 'main', 'status')
|
return this.patch.watch$('packageData', pkgId, 'status', 'main', 'status')
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private readonly patch: PatchDB<DataModel>) {}
|
constructor(private readonly patch: PatchDB<DataModel>) {}
|
||||||
|
|||||||
@@ -84,9 +84,7 @@ export class ServerBackupPage {
|
|||||||
buttonText: 'Create Backup',
|
buttonText: 'Create Backup',
|
||||||
submitFn: async (password: string) => {
|
submitFn: async (password: string) => {
|
||||||
// confirm password matches current master password
|
// confirm password matches current master password
|
||||||
const { 'password-hash': passwordHash } = await getServerInfo(
|
const { passwordHash } = await getServerInfo(this.patch)
|
||||||
this.patch,
|
|
||||||
)
|
|
||||||
argon2.verify(passwordHash, password)
|
argon2.verify(passwordHash, password)
|
||||||
|
|
||||||
// first time backup
|
// first time backup
|
||||||
@@ -95,8 +93,7 @@ export class ServerBackupPage {
|
|||||||
// existing backup
|
// existing backup
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const passwordHash =
|
const passwordHash = target.entry.startOs?.passwordHash || ''
|
||||||
target.entry['embassy-os']?.['password-hash'] || ''
|
|
||||||
|
|
||||||
argon2.verify(passwordHash, password)
|
argon2.verify(passwordHash, password)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -133,7 +130,7 @@ export class ServerBackupPage {
|
|||||||
useMask: true,
|
useMask: true,
|
||||||
buttonText: 'Create Backup',
|
buttonText: 'Create Backup',
|
||||||
submitFn: async (oldPassword: string) => {
|
submitFn: async (oldPassword: string) => {
|
||||||
const passwordHash = target.entry['embassy-os']?.['password-hash'] || ''
|
const passwordHash = target.entry.startOs?.passwordHash || ''
|
||||||
|
|
||||||
argon2.verify(passwordHash, oldPassword)
|
argon2.verify(passwordHash, oldPassword)
|
||||||
await this.createBackup(target, password, oldPassword)
|
await this.createBackup(target, password, oldPassword)
|
||||||
@@ -161,9 +158,9 @@ export class ServerBackupPage {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.embassyApi.createBackup({
|
await this.embassyApi.createBackup({
|
||||||
'target-id': target.id,
|
targetId: target.id,
|
||||||
'package-ids': this.serviceIds,
|
packageIds: this.serviceIds,
|
||||||
'old-password': oldPassword || null,
|
oldPassword: oldPassword || null,
|
||||||
password,
|
password,
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
<ion-item-divider>Memory</ion-item-divider>
|
<ion-item-divider>Memory</ion-item-divider>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Percentage Used</ion-label>
|
<ion-label>Percentage Used</ion-label>
|
||||||
<ion-note slot="end">{{ memory['percentage-used'].value }} %</ion-note>
|
<ion-note slot="end">{{ memory.percentageUsed }} %</ion-note>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Total</ion-label>
|
<ion-label>Total</ion-label>
|
||||||
@@ -94,15 +94,15 @@
|
|||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>zram Used</ion-label>
|
<ion-label>zram Used</ion-label>
|
||||||
<ion-note slot="end">{{ memory['zram-used'].value }} MiB</ion-note>
|
<ion-note slot="end">{{ memory.zramUsed.value }} MiB</ion-note>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>zram Total</ion-label>
|
<ion-label>zram Total</ion-label>
|
||||||
<ion-note slot="end">{{ memory['zram-total'].value }} MiB</ion-note>
|
<ion-note slot="end">{{ memory.zramTotal }} MiB</ion-note>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>zram Available</ion-label>
|
<ion-label>zram Available</ion-label>
|
||||||
<ion-note slot="end">{{ memory['zram-available'].value }} MiB</ion-note>
|
<ion-note slot="end">{{ memory.available.value }} MiB</ion-note>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-item-group>
|
</ion-item-group>
|
||||||
|
|
||||||
@@ -110,18 +110,18 @@
|
|||||||
<ion-item-divider>CPU</ion-item-divider>
|
<ion-item-divider>CPU</ion-item-divider>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Percentage Used</ion-label>
|
<ion-label>Percentage Used</ion-label>
|
||||||
<ion-note slot="end">{{ cpu['percentage-used'].value }} %</ion-note>
|
<ion-note slot="end">{{ cpu.percentageUsed.value }} %</ion-note>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>User Space</ion-label>
|
<ion-label>User Space</ion-label>
|
||||||
<ion-note slot="end">
|
<ion-note slot="end">
|
||||||
<ion-text>{{ cpu['user-space'].value }} %</ion-text>
|
<ion-text>{{ cpu.userSpace.value }} %</ion-text>
|
||||||
</ion-note>
|
</ion-note>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Kernel Space</ion-label>
|
<ion-label>Kernel Space</ion-label>
|
||||||
<ion-note slot="end">
|
<ion-note slot="end">
|
||||||
<ion-text>{{ cpu['kernel-space'].value }} %</ion-text>
|
<ion-text>{{ cpu.kernelSpace.value }} %</ion-text>
|
||||||
</ion-note>
|
</ion-note>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
<ion-item-divider>Disk</ion-item-divider>
|
<ion-item-divider>Disk</ion-item-divider>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Percentage Used</ion-label>
|
<ion-label>Percentage Used</ion-label>
|
||||||
<ion-note slot="end">{{ disk['percentage-used'].value }} %</ion-note>
|
<ion-note slot="end">{{ disk.percentageUsed.value }} %</ion-note>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Capacity</ion-label>
|
<ion-label>Capacity</ion-label>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<!-- loaded -->
|
<!-- loaded -->
|
||||||
<ion-item-group *ngIf="server$ | async as server; else loading">
|
<ion-item-group *ngIf="server$ | async as server; else loading">
|
||||||
<ion-item
|
<ion-item
|
||||||
*ngIf="!server['ntp-synced']"
|
*ngIf="!server.ntpSynced"
|
||||||
color="warning"
|
color="warning"
|
||||||
class="ion-margin-bottom"
|
class="ion-margin-bottom"
|
||||||
>
|
>
|
||||||
@@ -62,15 +62,15 @@
|
|||||||
|
|
||||||
<!-- "Create Backup" button only -->
|
<!-- "Create Backup" button only -->
|
||||||
<p *ngIf="button.title === 'Create Backup'">
|
<p *ngIf="button.title === 'Create Backup'">
|
||||||
<ng-container *ngIf="server['status-info'] as statusInfo">
|
<ng-container *ngIf="server.statusInfo as statusInfo">
|
||||||
<ion-text
|
<ion-text
|
||||||
[color]="server['last-backup'] | backupColor"
|
[color]="server.lastBackup | backupColor"
|
||||||
*ngIf="!statusInfo['backup-progress'] && !statusInfo['update-progress']"
|
*ngIf="!statusInfo.backupProgress && !statusInfo.updateProgress"
|
||||||
>
|
>
|
||||||
Last Backup: {{ server['last-backup'] ? (server['last-backup'] |
|
Last Backup: {{ server.lastBackup ? (server.lastBackup | date:
|
||||||
date: 'medium') : 'never' }}
|
'medium') : 'never' }}
|
||||||
</ion-text>
|
</ion-text>
|
||||||
<span *ngIf="!!statusInfo['backup-progress']" class="inline">
|
<span *ngIf="!!statusInfo.backupProgress" class="inline">
|
||||||
<ion-spinner
|
<ion-spinner
|
||||||
color="success"
|
color="success"
|
||||||
style="height: 12px; width: 12px; margin-right: 6px"
|
style="height: 12px; width: 12px; margin-right: 6px"
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
<!-- "Software Update" button only -->
|
<!-- "Software Update" button only -->
|
||||||
<p *ngIf="button.title === 'Software Update'">
|
<p *ngIf="button.title === 'Software Update'">
|
||||||
<ion-text
|
<ion-text
|
||||||
*ngIf="server['status-info'].updated; else notUpdated"
|
*ngIf="server.statusInfo.updated; else notUpdated"
|
||||||
class="inline"
|
class="inline"
|
||||||
color="warning"
|
color="warning"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export class ServerShowPage {
|
|||||||
manageClicks = 0
|
manageClicks = 0
|
||||||
powerClicks = 0
|
powerClicks = 0
|
||||||
|
|
||||||
readonly server$ = this.patch.watch$('server-info')
|
readonly server$ = this.patch.watch$('serverInfo')
|
||||||
readonly showUpdate$ = this.eosService.showUpdate$
|
readonly showUpdate$ = this.eosService.showUpdate$
|
||||||
readonly showDiskRepair$ = this.ClientStorageService.showDiskRepair$
|
readonly showDiskRepair$ = this.ClientStorageService.showDiskRepair$
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ export class ServerShowPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// confirm current password is correct
|
// confirm current password is correct
|
||||||
const { 'password-hash': passwordHash } = await getServerInfo(this.patch)
|
const { passwordHash } = await getServerInfo(this.patch)
|
||||||
try {
|
try {
|
||||||
argon2.verify(passwordHash, value.currPass)
|
argon2.verify(passwordHash, value.currPass)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -160,8 +160,8 @@ export class ServerShowPage {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.embassyApi.resetPassword({
|
await this.embassyApi.resetPassword({
|
||||||
'old-password': value.currPass,
|
oldPassword: value.currPass,
|
||||||
'new-password': value.newPass,
|
newPassword: value.newPass,
|
||||||
})
|
})
|
||||||
const toast = await this.toastCtrl.create({
|
const toast = await this.toastCtrl.create({
|
||||||
header: 'Password changed!',
|
header: 'Password changed!',
|
||||||
@@ -221,7 +221,7 @@ export class ServerShowPage {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.embassyApi.resetTor({
|
await this.embassyApi.resetTor({
|
||||||
'wipe-state': wipeState,
|
wipeState: wipeState,
|
||||||
reason: 'User triggered',
|
reason: 'User triggered',
|
||||||
})
|
})
|
||||||
const toast = await this.toastCtrl.create({
|
const toast = await this.toastCtrl.create({
|
||||||
|
|||||||
@@ -30,17 +30,17 @@
|
|||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label class="break-all">
|
<ion-label class="break-all">
|
||||||
<h2>Tor</h2>
|
<h2>Tor</h2>
|
||||||
<p>{{ server['tor-address'] }}</p>
|
<p>{{ server.torAddress }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<div slot="end">
|
<div slot="end">
|
||||||
<ion-button fill="clear" (click)="showQR(server['tor-address'])">
|
<ion-button fill="clear" (click)="showQR(server.torAddress)">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
slot="icon-only"
|
slot="icon-only"
|
||||||
name="qr-code-outline"
|
name="qr-code-outline"
|
||||||
size="small"
|
size="small"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button fill="clear" (click)="copy(server['tor-address'])">
|
<ion-button fill="clear" (click)="copy(server.torAddress)">
|
||||||
<ion-icon
|
<ion-icon
|
||||||
slot="icon-only"
|
slot="icon-only"
|
||||||
name="copy-outline"
|
name="copy-outline"
|
||||||
@@ -52,13 +52,13 @@
|
|||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label class="break-all">
|
<ion-label class="break-all">
|
||||||
<h2>LAN</h2>
|
<h2>LAN</h2>
|
||||||
<p>{{ server['lan-address'] }}</p>
|
<p>{{ server.lanAddress }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-button slot="end" fill="clear" (click)="copy(server['lan-address'])">
|
<ion-button slot="end" fill="clear" (click)="copy(server.lanAddress)">
|
||||||
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ng-container *ngFor="let iface of server['ip-info'] | keyvalue">
|
<ng-container *ngFor="let iface of server.ipInfo| keyvalue">
|
||||||
<ion-item *tuiLet="iface.value.ipv4 as ipv4">
|
<ion-item *tuiLet="iface.value.ipv4 as ipv4">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ iface.key }} (IPv4)</h2>
|
<h2>{{ iface.key }} (IPv4)</h2>
|
||||||
@@ -92,13 +92,9 @@
|
|||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>CA fingerprint</h2>
|
<h2>CA fingerprint</h2>
|
||||||
<p>{{ server['ca-fingerprint'] }}</p>
|
<p>{{ server.caFingerprint }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-button
|
<ion-button slot="end" fill="clear" (click)="copy(server.caFingerprint)">
|
||||||
slot="end"
|
|
||||||
fill="clear"
|
|
||||||
(click)="copy(server['ca-fingerprint'])"
|
|
||||||
>
|
|
||||||
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class ServerSpecsPage {
|
export class ServerSpecsPage {
|
||||||
readonly server$ = this.patch.watch$('server-info')
|
readonly server$ = this.patch.watch$('serverInfo')
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly toastCtrl: ToastController,
|
private readonly toastCtrl: ToastController,
|
||||||
|
|||||||
@@ -52,10 +52,8 @@
|
|||||||
></ion-icon>
|
></ion-icon>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h1>{{ getPlatformName(currentSession.metadata.platforms) }}</h1>
|
<h1>{{ getPlatformName(currentSession.metadata.platforms) }}</h1>
|
||||||
<h2>
|
<h2>Last Active: {{ currentSession.lastActive| date : 'medium' }}</h2>
|
||||||
Last Active: {{ currentSession['last-active'] | date : 'medium' }}
|
<p>{{ currentSession.userAgent }}</p>
|
||||||
</h2>
|
|
||||||
<p>{{ currentSession['user-agent'] }}</p>
|
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
@@ -80,8 +78,8 @@
|
|||||||
></ion-icon>
|
></ion-icon>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h1>{{ getPlatformName(session.metadata.platforms) }}</h1>
|
<h1>{{ getPlatformName(session.metadata.platforms) }}</h1>
|
||||||
<h2>Last Active: {{ session['last-active'] | date : 'medium' }}</h2>
|
<h2>Last Active: {{ session.lastActive | date : 'medium' }}</h2>
|
||||||
<p>{{ session['user-agent'] }}</p>
|
<p>{{ session.userAgent }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-button
|
<ion-button
|
||||||
slot="end"
|
slot="end"
|
||||||
|
|||||||
@@ -35,8 +35,7 @@ export class SessionsPage {
|
|||||||
})
|
})
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
return (
|
return (
|
||||||
new Date(b['last-active']).valueOf() -
|
new Date(b.lastActive).valueOf() - new Date(a.lastActive).valueOf()
|
||||||
new Date(a['last-active']).valueOf()
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
<ion-icon slot="start" name="key-outline" size="large"></ion-icon>
|
<ion-icon slot="start" name="key-outline" size="large"></ion-icon>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h1>{{ ssh.hostname }}</h1>
|
<h1>{{ ssh.hostname }}</h1>
|
||||||
<h2>{{ ssh['created-at'] | date: 'medium' }}</h2>
|
<h2>{{ ssh.createdAt| date: 'medium' }}</h2>
|
||||||
<p>{{ ssh.alg }} {{ ssh.fingerprint }}</p>
|
<p>{{ ssh.alg }} {{ ssh.fingerprint }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-button
|
<ion-button
|
||||||
|
|||||||
@@ -129,7 +129,7 @@
|
|||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item-divider>Available Networks</ion-item-divider>
|
<ion-item-divider>Available Networks</ion-item-divider>
|
||||||
<ng-container *ngFor="let avWifi of wifi['available-wifi']">
|
<ng-container *ngFor="let avWifi of wifi.availableWifi">
|
||||||
<ion-item
|
<ion-item
|
||||||
button
|
button
|
||||||
detail="false"
|
detail="false"
|
||||||
|
|||||||
@@ -39,8 +39,7 @@
|
|||||||
<h1 style="line-height: 1.3">{{ pkg.manifest.title }}</h1>
|
<h1 style="line-height: 1.3">{{ pkg.manifest.title }}</h1>
|
||||||
<h2 class="inline">
|
<h2 class="inline">
|
||||||
<span>
|
<span>
|
||||||
{{ local['state-info'].manifest.version | displayEmver
|
{{ local.stateInfo.manifest.version | displayEmver }}
|
||||||
}}
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<ion-icon name="arrow-forward"></ion-icon>
|
<ion-icon name="arrow-forward"></ion-icon>
|
||||||
@@ -57,8 +56,8 @@
|
|||||||
</ion-label>
|
</ion-label>
|
||||||
<div slot="end" style="margin-left: 4px">
|
<div slot="end" style="margin-left: 4px">
|
||||||
<round-progress
|
<round-progress
|
||||||
*ngIf="local['state-info'].state === 'updating' else notUpdating"
|
*ngIf="local.stateInfo.state === 'updating' else notUpdating"
|
||||||
[current]="(local['state-info']['installing-info'].progress.overall | installingProgress) || 0"
|
[current]="(local.stateInfo.installingInfo.progress.overall | installingProgress) || 0"
|
||||||
[max]="100"
|
[max]="100"
|
||||||
[radius]="13"
|
[radius]="13"
|
||||||
[stroke]="3"
|
[stroke]="3"
|
||||||
@@ -86,9 +85,7 @@
|
|||||||
<div class="ion-padding" slot="content">
|
<div class="ion-padding" slot="content">
|
||||||
<div class="notes">
|
<div class="notes">
|
||||||
<h5>What's new</h5>
|
<h5>What's new</h5>
|
||||||
<p
|
<p [innerHTML]="pkg.manifest.releaseNotes| markdown"></p>
|
||||||
[innerHTML]="pkg.manifest['release-notes'] | markdown"
|
|
||||||
></p>
|
|
||||||
</div>
|
</div>
|
||||||
<ion-button
|
<ion-button
|
||||||
fill="clear"
|
fill="clear"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export class UpdatesPage {
|
|||||||
readonly data$: Observable<UpdatesData> = combineLatest({
|
readonly data$: Observable<UpdatesData> = combineLatest({
|
||||||
hosts: this.marketplaceService.getKnownHosts$(true),
|
hosts: this.marketplaceService.getKnownHosts$(true),
|
||||||
marketplace: this.marketplaceService.getMarketplace$(),
|
marketplace: this.marketplaceService.getMarketplace$(),
|
||||||
localPkgs: this.patch.watch$('package-data').pipe(
|
localPkgs: this.patch.watch$('packageData').pipe(
|
||||||
map(pkgs =>
|
map(pkgs =>
|
||||||
Object.values(pkgs).reduce((acc, curr) => {
|
Object.values(pkgs).reduce((acc, curr) => {
|
||||||
if (isInstalled(curr) || isUpdating(curr)) return { ...acc, curr }
|
if (isInstalled(curr) || isUpdating(curr)) return { ...acc, curr }
|
||||||
@@ -171,7 +171,7 @@ export class FilterUpdatesPipe implements PipeTransform {
|
|||||||
localPkg &&
|
localPkg &&
|
||||||
this.emver.compare(
|
this.emver.compare(
|
||||||
manifest.version,
|
manifest.version,
|
||||||
localPkg['state-info'].manifest.version,
|
localPkg.stateInfo.manifest.version,
|
||||||
) === 1
|
) === 1
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export class HealthComponent {
|
|||||||
] as const
|
] as const
|
||||||
|
|
||||||
readonly data$ = combineLatest([
|
readonly data$ = combineLatest([
|
||||||
inject(PatchDB<DataModel>).watch$('package-data'),
|
inject(PatchDB<DataModel>).watch$('packageData'),
|
||||||
inject(DepErrorService).depErrors$,
|
inject(DepErrorService).depErrors$,
|
||||||
]).pipe(
|
]).pipe(
|
||||||
map(([data, depErrors]) => {
|
map(([data, depErrors]) => {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Manifest } from '@start9labs/marketplace'
|
|||||||
name: 'hasUi',
|
name: 'hasUi',
|
||||||
})
|
})
|
||||||
export class UiPipe implements PipeTransform {
|
export class UiPipe implements PipeTransform {
|
||||||
transform(interfaces: PackageDataEntry['service-interfaces']): boolean {
|
transform(interfaces: PackageDataEntry['serviceInterfaces']): boolean {
|
||||||
return interfaces ? hasUi(interfaces) : false
|
return interfaces ? hasUi(interfaces) : false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ export type ValueSpecOf<T extends ValueType> = T extends 'string'
|
|||||||
? ValueSpecList
|
? ValueSpecList
|
||||||
: T extends 'object'
|
: T extends 'object'
|
||||||
? ValueSpecObject
|
? ValueSpecObject
|
||||||
: T extends 'pointer'
|
|
||||||
? ValueSpecPointer
|
|
||||||
: T extends 'union'
|
: T extends 'union'
|
||||||
? ValueSpecUnion
|
? ValueSpecUnion
|
||||||
: never
|
: never
|
||||||
@@ -60,16 +58,6 @@ export interface ValueSpecUnion {
|
|||||||
default: string
|
default: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ValueSpecPointer extends WithStandalone {
|
|
||||||
type: 'pointer'
|
|
||||||
subtype: 'package' | 'system'
|
|
||||||
'package-id': string
|
|
||||||
target: 'lan-address' | 'tor-address' | 'config' | 'tor-key'
|
|
||||||
interface: string // will only exist if target = tor-key || tor-address || lan-address
|
|
||||||
selector?: string // will only exist if target = config
|
|
||||||
multi?: boolean // will only exist if target = config
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ValueSpecObject extends WithStandalone {
|
export interface ValueSpecObject extends WithStandalone {
|
||||||
type: 'object'
|
type: 'object'
|
||||||
spec: ConfigSpec
|
spec: ConfigSpec
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user