mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
switch all fe to camelCase
This commit is contained in:
@@ -49,7 +49,7 @@ export class LogsPage {
|
||||
|
||||
private async getLogs() {
|
||||
try {
|
||||
const { 'start-cursor': startCursor, entries } = await this.api.getLogs({
|
||||
const { startCursor, entries } = await this.api.getLogs({
|
||||
cursor: this.startCursor,
|
||||
before: !!this.startCursor,
|
||||
limit: this.limit,
|
||||
|
||||
@@ -45,8 +45,8 @@ export class MockApiService implements ApiService {
|
||||
}
|
||||
return {
|
||||
entries,
|
||||
'start-cursor': 'startCursor',
|
||||
'end-cursor': 'endCursor',
|
||||
startCursor: 'start-cursor',
|
||||
endCursor: 'end-cursor',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,12 +17,12 @@ export class MockApiService implements ApiService {
|
||||
label: null,
|
||||
capacity: 73264762332,
|
||||
used: null,
|
||||
'embassy-os': {
|
||||
startOs: {
|
||||
version: '0.2.17',
|
||||
full: true,
|
||||
'password-hash':
|
||||
passwordHash:
|
||||
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
||||
'wrapped-key': null,
|
||||
wrappedKey: null,
|
||||
},
|
||||
guid: null,
|
||||
},
|
||||
@@ -40,12 +40,12 @@ export class MockApiService implements ApiService {
|
||||
label: null,
|
||||
capacity: 73264762332,
|
||||
used: null,
|
||||
'embassy-os': {
|
||||
startOs: {
|
||||
version: '0.3.3',
|
||||
full: true,
|
||||
'password-hash':
|
||||
passwordHash:
|
||||
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
||||
'wrapped-key': null,
|
||||
wrappedKey: null,
|
||||
},
|
||||
guid: null,
|
||||
},
|
||||
@@ -63,12 +63,12 @@ export class MockApiService implements ApiService {
|
||||
label: null,
|
||||
capacity: 73264762332,
|
||||
used: null,
|
||||
'embassy-os': {
|
||||
startOs: {
|
||||
version: '0.3.2',
|
||||
full: true,
|
||||
'password-hash':
|
||||
passwordHash:
|
||||
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
||||
'wrapped-key': null,
|
||||
wrappedKey: null,
|
||||
},
|
||||
guid: 'guid-guid-guid-guid',
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</ion-item-divider>
|
||||
<ion-item lines="none" color="transparent">
|
||||
<ion-label>
|
||||
<div [innerHTML]="pkg.manifest['release-notes'] | markdown"></div>
|
||||
<div [innerHTML]="pkg.manifest.releaseNotes | markdown"></div>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-button routerLink="notes" fill="clear" strong>
|
||||
@@ -18,10 +18,7 @@
|
||||
<h2>{{ pkg.manifest.description.long }}</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<div
|
||||
*ngIf="pkg.manifest['marketing-site'] as url"
|
||||
style="padding: 4px 0 10px 14px"
|
||||
>
|
||||
<div *ngIf="pkg.manifest.marketingSite as url" style="padding: 4px 0 10px 14px">
|
||||
<ion-button [href]="url" target="_blank" rel="noreferrer" color="tertiary">
|
||||
View website
|
||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<ion-col responsiveCol sizeXs="12" sizeMd="6">
|
||||
<ion-item-group>
|
||||
<ion-item
|
||||
*ngIf="manifest['git-hash'] as gitHash; else noHash"
|
||||
*ngIf="manifest.gitHash as gitHash; else noHash"
|
||||
button
|
||||
detail="false"
|
||||
(click)="copy(gitHash)"
|
||||
@@ -64,39 +64,39 @@
|
||||
<ion-col responsiveCol sizeXs="12" sizeMd="6">
|
||||
<ion-item-group>
|
||||
<ion-item
|
||||
[href]="manifest['upstream-repo']"
|
||||
[href]="manifest.upstreamRepo"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
detail="false"
|
||||
>
|
||||
<ion-label>
|
||||
<h2>Source Repository</h2>
|
||||
<p>{{ manifest['upstream-repo'] }}</p>
|
||||
<p>{{ manifest.upstreamRepo }}</p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-item
|
||||
[href]="manifest['wrapper-repo']"
|
||||
[href]="manifest.wrapperRepo"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
detail="false"
|
||||
>
|
||||
<ion-label>
|
||||
<h2>Wrapper Repository</h2>
|
||||
<p>{{ manifest['wrapper-repo'] }}</p>
|
||||
<p>{{ manifest.wrapperRepo }}</p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-item
|
||||
[href]="manifest['support-site']"
|
||||
[disabled]="!manifest['support-site']"
|
||||
[href]="manifest.supportSite"
|
||||
[disabled]="!manifest.supportSite"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
detail="false"
|
||||
>
|
||||
<ion-label>
|
||||
<h2>Support Site</h2>
|
||||
<p>{{ manifest['support-site'] || 'Not provided' }}</p>
|
||||
<p>{{ manifest.supportSite || 'Not provided' }}</p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||
</ion-item>
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
</ion-thumbnail>
|
||||
<ion-label>
|
||||
<h2>
|
||||
{{ pkg['dependency-metadata'][dep.key].title }}
|
||||
{{ pkg.dependencyMetadata[dep.key].title }}
|
||||
<span *ngIf="dep.value.optional; else required">(optional)</span>
|
||||
<ng-template #required>
|
||||
<span *ngSwitchCase="'opt-in'">(optional)</span>
|
||||
<span>(Required)</span>
|
||||
</ng-template>
|
||||
</h2>
|
||||
<p>{{ dep.value.description }}</p>
|
||||
|
||||
@@ -11,6 +11,6 @@ export class DependenciesComponent {
|
||||
pkg!: MarketplacePkg
|
||||
|
||||
getImg(key: string): string {
|
||||
return this.pkg['dependency-metadata'][key].icon
|
||||
return this.pkg.dependencyMetadata[key].icon
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
<div class="text">
|
||||
<h1 ticker class="title">{{ pkg.manifest.title }}</h1>
|
||||
<p class="version">{{ pkg.manifest.version | displayEmver }}</p>
|
||||
<p class="published">
|
||||
Released: {{ pkg['published-at'] | date: 'medium' }}
|
||||
</p>
|
||||
<p class="published">Released: {{ pkg.publishedAt | date: 'medium' }}</p>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -71,8 +71,7 @@ export class FilterPackagesPipe implements PipeTransform {
|
||||
.filter(p => category === 'all' || p.categories.includes(category))
|
||||
.sort((a, b) => {
|
||||
return (
|
||||
new Date(b['published-at']).valueOf() -
|
||||
new Date(a['published-at']).valueOf()
|
||||
new Date(b.publishedAt).valueOf() - new Date(a.publishedAt).valueOf()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -28,10 +28,10 @@ export interface MarketplacePkg {
|
||||
manifest: Manifest
|
||||
categories: string[]
|
||||
versions: string[]
|
||||
'dependency-metadata': {
|
||||
dependencyMetadata: {
|
||||
[id: string]: DependencyMetadata
|
||||
}
|
||||
'published-at': string
|
||||
publishedAt: string
|
||||
}
|
||||
|
||||
export interface DependencyMetadata {
|
||||
@@ -45,19 +45,19 @@ export interface Manifest {
|
||||
id: string
|
||||
title: string
|
||||
version: string
|
||||
'git-hash'?: string
|
||||
gitHash?: string
|
||||
description: {
|
||||
short: string
|
||||
long: string
|
||||
}
|
||||
replaces?: string[]
|
||||
'release-notes': string
|
||||
releaseNotes: string
|
||||
license: string // name of license
|
||||
'wrapper-repo': Url
|
||||
'upstream-repo': Url
|
||||
'support-site': Url
|
||||
'marketing-site': Url
|
||||
'donation-url': Url | null
|
||||
wrapperRepo: Url
|
||||
upstreamRepo: Url
|
||||
supportSite: Url
|
||||
marketingSite: Url
|
||||
donationUrl: Url | null
|
||||
alerts: {
|
||||
install: string | null
|
||||
uninstall: string | null
|
||||
@@ -66,8 +66,8 @@ export interface Manifest {
|
||||
stop: string | null
|
||||
}
|
||||
dependencies: Record<string, Dependency>
|
||||
'os-version': string
|
||||
'has-config': boolean
|
||||
osVersion: string
|
||||
hasConfig: boolean
|
||||
}
|
||||
|
||||
export interface Dependency {
|
||||
|
||||
@@ -61,7 +61,7 @@ export class CifsModal {
|
||||
const target: CifsBackupTarget = {
|
||||
...this.cifs,
|
||||
mountable: true,
|
||||
'embassy-os': diskInfo,
|
||||
startOs: diskInfo,
|
||||
}
|
||||
|
||||
const modal = await this.modalController.create({
|
||||
|
||||
@@ -31,11 +31,11 @@ export class PasswordPage {
|
||||
}
|
||||
|
||||
async verifyPw() {
|
||||
if (!this.target || !this.target['embassy-os'])
|
||||
if (!this.target || !this.target.startOs)
|
||||
this.pwError = 'No recovery target' // unreachable
|
||||
|
||||
try {
|
||||
const passwordHash = this.target!['embassy-os']?.['password-hash'] || ''
|
||||
const passwordHash = this.target!.startOs?.passwordHash || ''
|
||||
|
||||
argon2.verify(passwordHash, this.password)
|
||||
this.modalController.dismiss({ password: this.password }, 'success')
|
||||
|
||||
@@ -38,10 +38,7 @@ export class LoadingPage {
|
||||
|
||||
if (!progress) return
|
||||
|
||||
const {
|
||||
'total-bytes': totalBytes,
|
||||
'bytes-transferred': bytesTransferred,
|
||||
} = progress
|
||||
const { totalBytes, bytesTransferred } = progress
|
||||
|
||||
this.progress$.next({
|
||||
totalBytes,
|
||||
|
||||
@@ -35,7 +35,7 @@ export class RecoverPage {
|
||||
}
|
||||
|
||||
driveClickable(mapped: MappedDisk) {
|
||||
return mapped.drive['embassy-os']?.full
|
||||
return mapped.drive.startOs?.full
|
||||
}
|
||||
|
||||
async getDrives() {
|
||||
@@ -53,10 +53,10 @@ export class RecoverPage {
|
||||
label: p.label,
|
||||
capacity: p.capacity,
|
||||
used: p.used,
|
||||
'embassy-os': p['embassy-os'],
|
||||
startOs: p.startOs,
|
||||
}
|
||||
this.mappedDrives.push({
|
||||
hasValidBackup: !!p['embassy-os']?.full,
|
||||
hasValidBackup: !!p.startOs?.full,
|
||||
drive,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -76,9 +76,9 @@ export class SuccessPage {
|
||||
try {
|
||||
const ret = await this.api.complete()
|
||||
if (!this.isKiosk) {
|
||||
this.torAddress = ret['tor-address'].replace(/^https:/, 'http:')
|
||||
this.lanAddress = ret['lan-address'].replace(/^https:/, 'http:')
|
||||
this.cert = ret['root-ca']
|
||||
this.torAddress = ret.torAddress.replace(/^https:/, 'http:')
|
||||
this.lanAddress = ret.lanAddress.replace(/^https:/, 'http:')
|
||||
this.cert = ret.rootCa
|
||||
|
||||
await this.api.exit()
|
||||
}
|
||||
|
||||
@@ -28,27 +28,27 @@ type Encrypted = {
|
||||
}
|
||||
|
||||
export type StatusRes = {
|
||||
'bytes-transferred': number
|
||||
'total-bytes': number | null
|
||||
bytesTransferred: number
|
||||
totalBytes: number | null
|
||||
complete: boolean
|
||||
} | null
|
||||
|
||||
export type AttachReq = {
|
||||
guid: string
|
||||
'embassy-password': Encrypted
|
||||
startOsPassword: Encrypted
|
||||
}
|
||||
|
||||
export type ExecuteReq = {
|
||||
'embassy-logicalname': string
|
||||
'embassy-password': Encrypted
|
||||
'recovery-source': RecoverySource | null
|
||||
'recovery-password': Encrypted | null
|
||||
startOsLogicalname: string
|
||||
startOsPassword: Encrypted
|
||||
recoverySource: RecoverySource | null
|
||||
recoveryPassword: Encrypted | null
|
||||
}
|
||||
|
||||
export type CompleteRes = {
|
||||
'tor-address': string
|
||||
'lan-address': string
|
||||
'root-ca': string
|
||||
torAddress: string
|
||||
lanAddress: string
|
||||
rootCa: string
|
||||
}
|
||||
|
||||
export type DiskBackupTarget = {
|
||||
@@ -58,7 +58,7 @@ export type DiskBackupTarget = {
|
||||
label: string | null
|
||||
capacity: number
|
||||
used: number | null
|
||||
'embassy-os': StartOSDiskInfo | null
|
||||
startOs: StartOSDiskInfo | null
|
||||
}
|
||||
|
||||
export type CifsBackupTarget = {
|
||||
@@ -66,7 +66,7 @@ export type CifsBackupTarget = {
|
||||
path: string
|
||||
username: string
|
||||
mountable: boolean
|
||||
'embassy-os': StartOSDiskInfo | null
|
||||
startOs: StartOSDiskInfo | null
|
||||
}
|
||||
|
||||
export type DiskRecoverySource = {
|
||||
|
||||
@@ -73,11 +73,10 @@ export class LiveApiService extends ApiService {
|
||||
}
|
||||
|
||||
async execute(setupInfo: ExecuteReq) {
|
||||
if (setupInfo['recovery-source']?.type === 'backup') {
|
||||
if (isCifsSource(setupInfo['recovery-source'].target)) {
|
||||
setupInfo['recovery-source'].target.path = setupInfo[
|
||||
'recovery-source'
|
||||
].target.path.replace('/\\/g', '/')
|
||||
if (setupInfo.recoverySource?.type === 'backup') {
|
||||
if (isCifsSource(setupInfo.recoverySource.target)) {
|
||||
setupInfo.recoverySource.target.path =
|
||||
setupInfo.recoverySource.target.path.replace('/\\/g', '/')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +94,7 @@ export class LiveApiService extends ApiService {
|
||||
|
||||
return {
|
||||
...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
|
||||
|
||||
return {
|
||||
'bytes-transferred': restoreOrMigrate ? progress : 0,
|
||||
'total-bytes': restoreOrMigrate ? total : null,
|
||||
bytesTransferred: restoreOrMigrate ? progress : 0,
|
||||
totalBytes: restoreOrMigrate ? total : null,
|
||||
complete: progress === total,
|
||||
}
|
||||
}
|
||||
@@ -65,12 +65,12 @@ export class MockApiService extends ApiService {
|
||||
label: null,
|
||||
capacity: 1979120929996,
|
||||
used: null,
|
||||
'embassy-os': {
|
||||
startOs: {
|
||||
version: '0.2.17',
|
||||
full: true,
|
||||
'password-hash':
|
||||
passwordHash:
|
||||
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
||||
'wrapped-key': null,
|
||||
wrappedKey: null,
|
||||
},
|
||||
guid: null,
|
||||
},
|
||||
@@ -88,12 +88,12 @@ export class MockApiService extends ApiService {
|
||||
label: null,
|
||||
capacity: 73264762332,
|
||||
used: null,
|
||||
'embassy-os': {
|
||||
startOs: {
|
||||
version: '0.3.3',
|
||||
full: true,
|
||||
'password-hash':
|
||||
passwordHash:
|
||||
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
||||
'wrapped-key': null,
|
||||
wrappedKey: null,
|
||||
},
|
||||
guid: null,
|
||||
},
|
||||
@@ -111,12 +111,12 @@ export class MockApiService extends ApiService {
|
||||
label: null,
|
||||
capacity: 73264762332,
|
||||
used: null,
|
||||
'embassy-os': {
|
||||
startOs: {
|
||||
version: '0.3.2',
|
||||
full: true,
|
||||
'password-hash':
|
||||
passwordHash:
|
||||
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
||||
'wrapped-key': null,
|
||||
wrappedKey: null,
|
||||
},
|
||||
guid: 'guid-guid-guid-guid',
|
||||
},
|
||||
@@ -132,9 +132,9 @@ export class MockApiService extends ApiService {
|
||||
return {
|
||||
version: '0.3.0',
|
||||
full: true,
|
||||
'password-hash':
|
||||
passwordHash:
|
||||
'$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> {
|
||||
await pauseFor(1000)
|
||||
return {
|
||||
'tor-address': 'https://asdafsadasdasasdasdfasdfasdf.onion',
|
||||
'lan-address': 'https://adjective-noun.local',
|
||||
'root-ca': encodeBase64(rootCA),
|
||||
torAddress: 'https://asdafsadasdasasdasdfasdfasdf.onion',
|
||||
lanAddress: 'https://adjective-noun.local',
|
||||
rootCa: encodeBase64(rootCA),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export class StateService {
|
||||
async importDrive(guid: string, password: string): Promise<void> {
|
||||
await this.api.attach({
|
||||
guid,
|
||||
'embassy-password': await this.api.encrypt(password),
|
||||
startOsPassword: await this.api.encrypt(password),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@ export class StateService {
|
||||
password: string,
|
||||
): Promise<void> {
|
||||
await this.api.execute({
|
||||
'embassy-logicalname': storageLogicalname,
|
||||
'embassy-password': await this.api.encrypt(password),
|
||||
'recovery-source': this.recoverySource || null,
|
||||
'recovery-password': this.recoveryPassword
|
||||
startOsLogicalname: storageLogicalname,
|
||||
startOsPassword: await this.api.encrypt(password),
|
||||
recoverySource: this.recoverySource || null,
|
||||
recoveryPassword: this.recoveryPassword
|
||||
? await this.api.encrypt(this.recoveryPassword)
|
||||
: null,
|
||||
})
|
||||
|
||||
@@ -6,8 +6,8 @@ export type ServerLogsReq = {
|
||||
|
||||
export type LogsRes = {
|
||||
entries: Log[]
|
||||
'start-cursor'?: string
|
||||
'end-cursor'?: string
|
||||
startCursor?: string
|
||||
endCursor?: string
|
||||
}
|
||||
|
||||
export interface Log {
|
||||
@@ -31,13 +31,13 @@ export interface PartitionInfo {
|
||||
label: string | null
|
||||
capacity: number
|
||||
used: number | null
|
||||
'embassy-os': StartOSDiskInfo | null
|
||||
startOs: StartOSDiskInfo | null
|
||||
guid: string | null
|
||||
}
|
||||
|
||||
export type StartOSDiskInfo = {
|
||||
version: string
|
||||
full: boolean
|
||||
'password-hash': string | null
|
||||
'wrapped-key': string | null
|
||||
passwordHash: string | null
|
||||
wrappedKey: string | null
|
||||
}
|
||||
|
||||
@@ -63,15 +63,6 @@ const routes: Routes = [
|
||||
m => m.AppsRoutingModule,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'developer',
|
||||
canActivate: [AuthGuard],
|
||||
canActivateChild: [AuthGuard],
|
||||
loadChildren: () =>
|
||||
import('./pages/developer-routes/developer-routing.module').then(
|
||||
m => m.DeveloperRoutingModule,
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -29,13 +29,12 @@ export class AppComponent implements OnDestroy {
|
||||
this.authService.isVerified$,
|
||||
this.connection.connected$,
|
||||
this.patch
|
||||
.watch$('server-info', 'status-info')
|
||||
.pipe(startWith({ restarting: false, 'shutting-down': false })),
|
||||
.watch$('serverInfo', 'statusInfo')
|
||||
.pipe(startWith({ restarting: false, shuttingDown: false })),
|
||||
]).pipe(
|
||||
map(
|
||||
([verified, connected, status]) =>
|
||||
verified &&
|
||||
(!connected || status.restarting || status['shutting-down']),
|
||||
verified && (!connected || status.restarting || status.shuttingDown),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { DataModel } from '../../services/patch-db/data-model'
|
||||
})
|
||||
export class FooterComponent {
|
||||
readonly progress$ = this.patch
|
||||
.watch$('server-info', 'status-info', 'update-progress')
|
||||
.watch$('serverInfo', 'statusInfo', 'updateProgress')
|
||||
.pipe(map(a => a && { ...a }))
|
||||
|
||||
readonly animation = {
|
||||
|
||||
@@ -11,9 +11,7 @@ import {
|
||||
filter,
|
||||
first,
|
||||
map,
|
||||
merge,
|
||||
Observable,
|
||||
of,
|
||||
pairwise,
|
||||
startWith,
|
||||
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 { Emver, THEME } from '@start9labs/shared'
|
||||
import { ConnectionService } from 'src/app/services/connection.service'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import {
|
||||
getManifest,
|
||||
isInstalled,
|
||||
isInstalling,
|
||||
isRestoring,
|
||||
isUpdating,
|
||||
} from 'src/app/util/get-package-data'
|
||||
import { getManifest } from 'src/app/util/get-package-data'
|
||||
|
||||
@Component({
|
||||
selector: 'app-menu',
|
||||
@@ -69,19 +60,19 @@ export class MenuComponent {
|
||||
]
|
||||
|
||||
readonly notificationCount$ = this.patch.watch$(
|
||||
'server-info',
|
||||
'unread-notification-count',
|
||||
'serverInfo',
|
||||
'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$
|
||||
|
||||
private readonly local$ = this.connectionService.connected$.pipe(
|
||||
filter(Boolean),
|
||||
switchMap(() => this.patch.watch$('package-data').pipe(first())),
|
||||
switchMap(() => this.patch.watch$('packageData').pipe(first())),
|
||||
switchMap(outer =>
|
||||
this.patch.watch$('package-data').pipe(
|
||||
this.patch.watch$('packageData').pipe(
|
||||
pairwise(),
|
||||
filter(([prev, curr]) =>
|
||||
Object.values(prev).some(
|
||||
@@ -90,9 +81,9 @@ export class MenuComponent {
|
||||
PackageState.Installing,
|
||||
PackageState.Updating,
|
||||
PackageState.Restoring,
|
||||
].includes(p['state-info'].state) &&
|
||||
].includes(p.stateInfo.state) &&
|
||||
[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 emver: Emver,
|
||||
private readonly connectionService: ConnectionService,
|
||||
private readonly config: ConfigService,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export class BackupService {
|
||||
}
|
||||
|
||||
hasValidBackup(target: BackupTarget): boolean {
|
||||
const backup = target['embassy-os']
|
||||
const backup = target.startOs
|
||||
return !!backup && this.emver.compare(backup.version, '0.3.0') !== -1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ const { enableWidgets } =
|
||||
})
|
||||
export class BadgeMenuComponent {
|
||||
readonly unreadCount$ = this.patch.watch$(
|
||||
'server-info',
|
||||
'unread-notification-count',
|
||||
'serverInfo',
|
||||
'unreadNotificationCount',
|
||||
)
|
||||
readonly sidebarOpen$ = this.splitPane.sidebarOpen$
|
||||
readonly widgetDrawer$ = this.clientStorageService.widgetDrawer$
|
||||
|
||||
@@ -22,8 +22,8 @@ export class ConnectionBarComponent {
|
||||
this.connectionService.networkConnected$,
|
||||
this.websocket$.pipe(startWith(false)),
|
||||
this.patch
|
||||
.watch$('server-info', 'status-info')
|
||||
.pipe(startWith({ restarting: false, 'shutting-down': false })),
|
||||
.watch$('serverInfo', 'statusInfo')
|
||||
.pipe(startWith({ restarting: false, shuttingDown: false })),
|
||||
]).pipe(
|
||||
map(([network, websocket, status]) => {
|
||||
if (!network)
|
||||
@@ -40,7 +40,7 @@ export class ConnectionBarComponent {
|
||||
icon: 'cloud-offline-outline',
|
||||
dots: true,
|
||||
}
|
||||
if (status['shutting-down'])
|
||||
if (status.shuttingDown)
|
||||
return {
|
||||
message: 'Shutting Down',
|
||||
color: 'dark',
|
||||
|
||||
@@ -78,7 +78,7 @@ export class LogsComponent {
|
||||
async ngOnInit() {
|
||||
from(this.followLogs({ limit: this.limit }))
|
||||
.pipe(
|
||||
switchMap(({ 'start-cursor': startCursor, guid }) => {
|
||||
switchMap(({ startCursor, guid }) => {
|
||||
this.startCursor = startCursor
|
||||
return this.connect$(guid)
|
||||
}),
|
||||
@@ -206,7 +206,7 @@ export class LogsComponent {
|
||||
}
|
||||
|
||||
private processRes(res: LogsRes) {
|
||||
const { entries, 'start-cursor': startCursor } = res
|
||||
const { entries, startCursor } = res
|
||||
|
||||
if (!entries.length) return
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class NotificationsToastService extends Observable<boolean> {
|
||||
private readonly stream$ = this.patch
|
||||
.watch$('server-info', 'unread-notification-count')
|
||||
.watch$('serverInfo', 'unreadNotificationCount')
|
||||
.pipe(
|
||||
pairwise(),
|
||||
map(([prev, cur]) => cur > prev),
|
||||
|
||||
@@ -8,7 +8,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
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)),
|
||||
endWith(false),
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class UpdateToastService extends Observable<boolean> {
|
||||
private readonly stream$ = this.patch
|
||||
.watch$('server-info', 'status-info', 'updated')
|
||||
.watch$('serverInfo', 'statusInfo', 'updated')
|
||||
.pipe(distinctUntilChanged(), filter(Boolean), endWith(false))
|
||||
|
||||
constructor(private readonly patch: PatchDB<DataModel>) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</ion-toolbar>
|
||||
</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 -->
|
||||
<text-spinner
|
||||
*ngIf="loading; else notLoading"
|
||||
@@ -82,7 +82,7 @@
|
||||
</ion-item>
|
||||
|
||||
<!-- no options -->
|
||||
<ion-item *ngIf="!hasOptions">
|
||||
<ion-item *ngIf="!hasConfig">
|
||||
<ion-label>
|
||||
<p>
|
||||
No config options for {{ manifest.title }} {{ manifest.version }}.
|
||||
@@ -111,11 +111,7 @@
|
||||
<ion-footer>
|
||||
<ion-toolbar>
|
||||
<ng-container *ngIf="!loading && !loadingError">
|
||||
<ion-buttons
|
||||
*ngIf="configForm && hasOptions"
|
||||
slot="start"
|
||||
class="ion-padding-start"
|
||||
>
|
||||
<ion-buttons *ngIf="hasConfig" slot="start" class="ion-padding-start">
|
||||
<ion-button fill="clear" (click)="resetDefaults()">
|
||||
<ion-icon slot="start" name="refresh"></ion-icon>
|
||||
Reset Defaults
|
||||
@@ -123,7 +119,7 @@
|
||||
</ion-buttons>
|
||||
<ion-buttons slot="end" class="ion-padding-end">
|
||||
<ion-button
|
||||
*ngIf="configForm"
|
||||
*ngIf="hasConfig"
|
||||
fill="solid"
|
||||
color="primary"
|
||||
[disabled]="saving"
|
||||
@@ -134,7 +130,7 @@
|
||||
Save
|
||||
</ion-button>
|
||||
<ion-button
|
||||
*ngIf="!configForm"
|
||||
*ngIf="!hasConfig"
|
||||
fill="solid"
|
||||
color="dark"
|
||||
(click)="dismiss()"
|
||||
|
||||
@@ -59,8 +59,6 @@ export class AppConfigPage {
|
||||
saving = false
|
||||
loadingError: string | IonicSafeString = ''
|
||||
|
||||
hasOptions = false
|
||||
|
||||
constructor(
|
||||
private readonly embassyApi: ApiService,
|
||||
private readonly errToast: ErrorToastService,
|
||||
@@ -71,6 +69,10 @@ export class AppConfigPage {
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
get hasConfig() {
|
||||
return this.pkg.stateInfo.manifest.hasConfig
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
try {
|
||||
const pkg = await getPackage(this.patch, this.pkgId)
|
||||
@@ -78,7 +80,7 @@ export class AppConfigPage {
|
||||
|
||||
this.pkg = pkg
|
||||
|
||||
if (!this.pkg['state-info'].manifest['has-config']) return
|
||||
if (!this.hasConfig) return
|
||||
|
||||
let newConfig: object | undefined
|
||||
let patch: Operation[] | undefined
|
||||
@@ -86,12 +88,12 @@ export class AppConfigPage {
|
||||
if (this.dependentInfo) {
|
||||
this.loadingText = `Setting properties to accommodate ${this.dependentInfo.title}`
|
||||
const {
|
||||
'old-config': oc,
|
||||
'new-config': nc,
|
||||
oldConfig: oc,
|
||||
newConfig: nc,
|
||||
spec: s,
|
||||
} = await this.embassyApi.dryConfigureDependency({
|
||||
'dependency-id': this.pkgId,
|
||||
'dependent-id': this.dependentInfo.id,
|
||||
dependencyId: this.pkgId,
|
||||
dependentId: this.dependentInfo.id,
|
||||
})
|
||||
this.original = oc
|
||||
newConfig = nc
|
||||
@@ -111,10 +113,6 @@ export class AppConfigPage {
|
||||
newConfig || this.original,
|
||||
)
|
||||
|
||||
this.hasOptions = !!Object.values(this.configSpec).find(
|
||||
valSpec => valSpec.type !== 'pointer',
|
||||
)
|
||||
|
||||
if (patch) {
|
||||
this.diff = this.getDiff(patch)
|
||||
this.markDirty(patch)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<ng-container
|
||||
*ngIf="packageData$ | toOptions : backupInfo['package-backups'] | async as options"
|
||||
*ngIf="packageData$ | toOptions : backupInfo.packageBackups | async as options"
|
||||
>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
|
||||
@@ -23,7 +23,7 @@ export class AppRecoverSelectPage {
|
||||
@Input() password!: 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
|
||||
error: string | IonicSafeString = ''
|
||||
@@ -53,8 +53,8 @@ export class AppRecoverSelectPage {
|
||||
try {
|
||||
await this.embassyApi.restorePackages({
|
||||
ids,
|
||||
'target-id': this.id,
|
||||
'old-password': this.oldPassword || null,
|
||||
targetId: this.id,
|
||||
oldPassword: this.oldPassword || null,
|
||||
password: this.password,
|
||||
})
|
||||
this.modalCtrl.dismiss(undefined, 'success')
|
||||
|
||||
@@ -34,7 +34,7 @@ export class ToOptionsPipe implements PipeTransform {
|
||||
id,
|
||||
installed: !!packageData[id],
|
||||
checked: false,
|
||||
'newer-eos': this.compare(packageBackups[id]['os-version']),
|
||||
'newer-eos': this.compare(packageBackups[id].osVersion),
|
||||
}))
|
||||
.sort((a, b) =>
|
||||
b.title.toLowerCase() > a.title.toLowerCase() ? -1 : 1,
|
||||
|
||||
@@ -29,7 +29,7 @@ export class BackupSelectPage {
|
||||
|
||||
async ngOnInit() {
|
||||
this.pkgs = await firstValueFrom(
|
||||
this.patch.watch$('package-data').pipe(
|
||||
this.patch.watch$('packageData').pipe(
|
||||
map(pkgs => {
|
||||
return Object.values(pkgs)
|
||||
.map(pkg => {
|
||||
@@ -38,7 +38,7 @@ export class BackupSelectPage {
|
||||
id,
|
||||
title,
|
||||
icon: pkg.icon,
|
||||
disabled: pkg['state-info'].state !== PackageState.Installed,
|
||||
disabled: pkg.stateInfo.state !== PackageState.Installed,
|
||||
checked: false,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -200,7 +200,7 @@ export class MarketplaceSettingsPage {
|
||||
): Promise<void> {
|
||||
// Error on duplicates
|
||||
const hosts = await firstValueFrom(
|
||||
this.patch.watch$('ui', 'marketplace', 'known-hosts'),
|
||||
this.patch.watch$('ui', 'marketplace', 'knownHosts'),
|
||||
)
|
||||
const currentUrls = Object.keys(hosts).map(toUrl)
|
||||
if (currentUrls.includes(url)) throw new Error('marketplace already added')
|
||||
@@ -217,7 +217,7 @@ export class MarketplaceSettingsPage {
|
||||
loader.message = 'Saving...'
|
||||
|
||||
await this.api.setDbValue<{ name: string }>(
|
||||
['marketplace', 'known-hosts', url],
|
||||
['marketplace', 'knownHosts', url],
|
||||
{ name },
|
||||
)
|
||||
}
|
||||
@@ -229,7 +229,7 @@ export class MarketplaceSettingsPage {
|
||||
await loader.present()
|
||||
|
||||
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)
|
||||
@@ -244,7 +244,7 @@ export class MarketplaceSettingsPage {
|
||||
|
||||
try {
|
||||
await this.api.setDbValue<{ [url: string]: UIStore }>(
|
||||
['marketplace', 'known-hosts'],
|
||||
['marketplace', 'knownHosts'],
|
||||
filtered,
|
||||
)
|
||||
} catch (e: any) {
|
||||
|
||||
@@ -22,7 +22,7 @@ export class OSUpdatePage {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
const releaseNotes = this.eosService.eos?.['release-notes']!
|
||||
const releaseNotes = this.eosService.eos?.releaseNotes!
|
||||
|
||||
this.versions = Object.keys(releaseNotes)
|
||||
.sort()
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<ion-content class="ion-padding-top with-widgets">
|
||||
<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 ** -->
|
||||
<ion-item-divider>Standard Actions</ion-item-divider>
|
||||
<app-actions-item
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
<!-- ** specific actions ** -->
|
||||
<ion-item-divider *ngIf="!(pkg.actions | empty)">
|
||||
Actions for {{ pkg['state-info'].manifest.title }}
|
||||
Actions for {{ pkg.stateInfo.manifest.title }}
|
||||
</ion-item-divider>
|
||||
<app-actions-item
|
||||
*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 {
|
||||
readonly pkgId = getPkgId(this.route)
|
||||
readonly pkg$ = this.patch.watch$('package-data', this.pkgId)
|
||||
readonly pkg$ = this.patch.watch$('packageData', this.pkgId)
|
||||
|
||||
constructor(
|
||||
private readonly route: ActivatedRoute,
|
||||
@@ -184,7 +184,7 @@ export class AppActionsPage {
|
||||
try {
|
||||
const res = await this.embassyApi.executePackageAction({
|
||||
id: this.pkgId,
|
||||
'action-id': actionId,
|
||||
actionId,
|
||||
input,
|
||||
})
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ export class AppInterfacesPage {
|
||||
readonly pkgId = getPkgId(this.route)
|
||||
|
||||
readonly serviceInterfaces$ = this.patch
|
||||
.watch$('package-data', this.pkgId, 'service-interfaces')
|
||||
.watch$('packageData', this.pkgId, 'serviceInterfaces')
|
||||
.pipe(
|
||||
map(interfaces => {
|
||||
const sorted = Object.values(interfaces)
|
||||
|
||||
@@ -14,20 +14,20 @@
|
||||
<p>{{ manifest.version | displayEmver }}</p>
|
||||
<status
|
||||
[rendering]="pkg.primaryRendering"
|
||||
[installingInfo]="$any(pkg.entry['state-info'])['installing-info']"
|
||||
[installingInfo]="$any(pkg.entry.stateInfo).installingInfo"
|
||||
weight="bold"
|
||||
size="small"
|
||||
[sigtermTimeout]="sigtermTimeout"
|
||||
></status>
|
||||
</ion-label>
|
||||
<ion-button
|
||||
*ngIf="pkg.entry['service-interfaces'] | hasUi"
|
||||
*ngIf="pkg.entry.serviceInterfaces | hasUi"
|
||||
slot="end"
|
||||
fill="clear"
|
||||
color="primary"
|
||||
(click)="launchUi($event, pkg.entry['service-interfaces'])"
|
||||
(click)="launchUi($event, pkg.entry.serviceInterfaces)"
|
||||
[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>
|
||||
|
||||
@@ -32,7 +32,7 @@ export class AppListPkgComponent {
|
||||
: null
|
||||
}
|
||||
|
||||
launchUi(e: Event, interfaces: PackageDataEntry['service-interfaces']): void {
|
||||
launchUi(e: Event, interfaces: PackageDataEntry['serviceInterfaces']): void {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
this.launcherService.launch(interfaces)
|
||||
|
||||
@@ -11,7 +11,7 @@ import { getManifest } from 'src/app/util/get-package-data'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AppListPage {
|
||||
readonly pkgs$ = this.patch.watch$('package-data').pipe(
|
||||
readonly pkgs$ = this.patch.watch$('packageData').pipe(
|
||||
map(pkgs => Object.values(pkgs)),
|
||||
startWith([]),
|
||||
pairwise(),
|
||||
|
||||
@@ -17,7 +17,7 @@ export class PackageInfoPipe implements PipeTransform {
|
||||
|
||||
transform(pkgId: string): Observable<PkgInfo> {
|
||||
return combineLatest([
|
||||
this.patch.watch$('package-data', pkgId).pipe(filter(Boolean)),
|
||||
this.patch.watch$('packageData', pkgId).pipe(filter(Boolean)),
|
||||
this.depErrorService.getPkgDepErrors$(pkgId),
|
||||
]).pipe(map(([pkg, depErrors]) => getPackageInfo(pkg, depErrors)))
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export class AppPropertiesPage {
|
||||
unmasked: { [key: string]: boolean } = {}
|
||||
|
||||
stopped$ = this.patch
|
||||
.watch$('package-data', this.pkgId, 'status', 'main', 'status')
|
||||
.watch$('packageData', this.pkgId, 'status', 'main', 'status')
|
||||
.pipe(map(status => status === PackageMainStatus.Stopped))
|
||||
|
||||
@ViewChild(IonBackButtonDelegate, { static: false })
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<!-- ** installing, updating, restoring ** -->
|
||||
<ng-container *ngIf="showProgress(pkg); else installed">
|
||||
<app-show-progress
|
||||
*ngIf="pkg['state-info']['installing-info'] as installingInfo"
|
||||
*ngIf="pkg.stateInfo.installingInfo as installingInfo"
|
||||
[phases]="installingInfo.progress.phases"
|
||||
></app-show-progress>
|
||||
</ng-container>
|
||||
@@ -35,7 +35,7 @@
|
||||
<app-show-menu [buttons]="pkg | toButtons"></app-show-menu>
|
||||
<!-- ** additional ** -->
|
||||
<app-show-additional
|
||||
[manifest]="pkg['state-info'].manifest"
|
||||
[manifest]="pkg.stateInfo.manifest"
|
||||
></app-show-additional>
|
||||
</ng-container>
|
||||
</ion-item-group>
|
||||
|
||||
@@ -47,7 +47,7 @@ export class AppShowPage {
|
||||
private readonly pkgId = getPkgId(this.route)
|
||||
|
||||
readonly pkgPlus$ = combineLatest([
|
||||
this.patch.watch$('package-data', this.pkgId),
|
||||
this.patch.watch$('packageData', this.pkgId),
|
||||
this.depErrorService.getPkgDepErrors$(this.pkgId),
|
||||
]).pipe(
|
||||
tap(([pkg, _]) => {
|
||||
@@ -85,7 +85,7 @@ export class AppShowPage {
|
||||
): DependencyInfo[] {
|
||||
const manifest = getManifest(pkg)
|
||||
|
||||
return Object.keys(pkg['current-dependencies'])
|
||||
return Object.keys(pkg.currentDependencies)
|
||||
.filter(id => !!manifest.dependencies[id])
|
||||
.map(id => this.getDepValues(pkg, manifest, id, depErrors))
|
||||
}
|
||||
@@ -103,11 +103,11 @@ export class AppShowPage {
|
||||
depErrors,
|
||||
)
|
||||
|
||||
const depInfo = pkg['dependency-info'][depId]
|
||||
const depInfo = pkg.dependencyInfo[depId]
|
||||
|
||||
return {
|
||||
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,
|
||||
icon: depInfo?.icon || '',
|
||||
errorText: errorText
|
||||
@@ -184,7 +184,7 @@ export class AppShowPage {
|
||||
const dependentInfo: DependentInfo = {
|
||||
id: pkgManifest.id,
|
||||
title: pkgManifest.title,
|
||||
version: pkg['current-dependencies'][depId].versionRange,
|
||||
version: pkg.currentDependencies[depId].versionRange,
|
||||
}
|
||||
const navigationExtras: NavigationExtras = {
|
||||
state: { dependentInfo },
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item
|
||||
*ngIf="manifest['git-hash'] as gitHash; else noHash"
|
||||
*ngIf="manifest.gitHash as gitHash; else noHash"
|
||||
button
|
||||
detail="false"
|
||||
(click)="copy(gitHash)"
|
||||
@@ -37,15 +37,15 @@
|
||||
<ion-icon slot="end" name="chevron-forward"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-item
|
||||
[href]="manifest['marketing-site']"
|
||||
[disabled]="!manifest['marketing-site']"
|
||||
[href]="manifest.marketingSite"
|
||||
[disabled]="!manifest.marketingSite"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
detail="false"
|
||||
>
|
||||
<ion-label>
|
||||
<h2>Marketing Site</h2>
|
||||
<p>{{ manifest['marketing-site'] || 'Not provided' }}</p>
|
||||
<p>{{ manifest.marketingSite || 'Not provided' }}</p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||
</ion-item>
|
||||
@@ -54,52 +54,52 @@
|
||||
<ion-col responsiveCol sizeXs="12" sizeMd="6">
|
||||
<ion-item-group>
|
||||
<ion-item
|
||||
[href]="manifest['upstream-repo']"
|
||||
[href]="manifest.upstreamRepo"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
detail="false"
|
||||
>
|
||||
<ion-label>
|
||||
<h2>Source Repository</h2>
|
||||
<p>{{ manifest['upstream-repo'] }}</p>
|
||||
<p>{{ manifest.upstreamRepo }}</p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-item
|
||||
[href]="manifest['wrapper-repo']"
|
||||
[href]="manifest.wrapperRepo"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
detail="false"
|
||||
>
|
||||
<ion-label>
|
||||
<h2>Wrapper Repository</h2>
|
||||
<p>{{ manifest['wrapper-repo'] }}</p>
|
||||
<p>{{ manifest.wrapperRepo }}</p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-item
|
||||
[href]="manifest['support-site']"
|
||||
[disabled]="!manifest['support-site']"
|
||||
[href]="manifest.supportSite"
|
||||
[disabled]="!manifest.supportSite"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
detail="false"
|
||||
>
|
||||
<ion-label>
|
||||
<h2>Support Site</h2>
|
||||
<p>{{ manifest['support-site'] || 'Not provided' }}</p>
|
||||
<p>{{ manifest.supportSite || 'Not provided' }}</p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||
</ion-item>
|
||||
<ion-item
|
||||
[href]="manifest['donation-url']"
|
||||
[disabled]="!manifest['donation-url']"
|
||||
[href]="manifest.donationUrl"
|
||||
[disabled]="!manifest.donationUrl"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
detail="false"
|
||||
>
|
||||
<ion-label>
|
||||
<h2>Donation Link</h2>
|
||||
<p>{{ manifest['donation-url'] || 'Not provided' }}</p>
|
||||
<p>{{ manifest.donationUrl || 'Not provided' }}</p>
|
||||
</ion-label>
|
||||
<ion-icon slot="end" name="open-outline"></ion-icon>
|
||||
</ion-item>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<status
|
||||
size="x-large"
|
||||
weight="600"
|
||||
[installingInfo]="$any(pkg['state-info'])['installing-info']"
|
||||
[installingInfo]="$any(pkg.stateInfo).installingInfo"
|
||||
[rendering]="PR[status.primary]"
|
||||
[sigtermTimeout]="sigtermTimeout"
|
||||
></status>
|
||||
@@ -60,7 +60,7 @@
|
||||
class="action-button"
|
||||
color="primary"
|
||||
[disabled]="
|
||||
!(pkg['state-info'].state | isLaunchable: pkgStatus.main.status)
|
||||
!(pkg.stateInfo.state | isLaunchable: pkgStatus.main.status)
|
||||
"
|
||||
(click)="launchUi(interfaces)"
|
||||
>
|
||||
|
||||
@@ -55,8 +55,8 @@ export class AppShowStatusComponent {
|
||||
private readonly patch: PatchDB<DataModel>,
|
||||
) {}
|
||||
|
||||
get interfaces(): PackageDataEntry['service-interfaces'] {
|
||||
return this.pkg['service-interfaces']
|
||||
get interfaces(): PackageDataEntry['serviceInterfaces'] {
|
||||
return this.pkg.serviceInterfaces
|
||||
}
|
||||
|
||||
get pkgStatus(): Status {
|
||||
@@ -89,7 +89,7 @@ export class AppShowStatusComponent {
|
||||
: null
|
||||
}
|
||||
|
||||
launchUi(interfaces: PackageDataEntry['service-interfaces']): void {
|
||||
launchUi(interfaces: PackageDataEntry['serviceInterfaces']): void {
|
||||
this.launcherService.launch(interfaces)
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export class ToButtonsPipe implements PipeTransform {
|
||||
) {}
|
||||
|
||||
transform(pkg: PackageDataEntry<InstalledState>): Button[] {
|
||||
const manifest = pkg['state-info'].manifest
|
||||
const manifest = pkg.stateInfo.manifest
|
||||
|
||||
return [
|
||||
// instructions
|
||||
@@ -46,7 +46,7 @@ export class ToButtonsPipe implements PipeTransform {
|
||||
description: `Understand how to use ${manifest.title}`,
|
||||
icon: 'list-outline',
|
||||
highlighted$: this.patch
|
||||
.watch$('ui', 'ack-instructions', manifest.id)
|
||||
.watch$('ui', 'ackInstructions', manifest.id)
|
||||
.pipe(map(seen => !seen)),
|
||||
},
|
||||
// config
|
||||
@@ -122,7 +122,7 @@ export class ToButtonsPipe implements PipeTransform {
|
||||
private viewInMarketplaceButton(
|
||||
pkg: PackageDataEntry<InstalledState>,
|
||||
): Button {
|
||||
const url = pkg['marketplace-url']
|
||||
const url = pkg.marketplaceUrl
|
||||
const queryParams = url ? { url } : {}
|
||||
|
||||
let button: Button = {
|
||||
@@ -130,7 +130,7 @@ export class ToButtonsPipe implements PipeTransform {
|
||||
icon: 'storefront-outline',
|
||||
action: () =>
|
||||
this.navCtrl.navigateForward(
|
||||
[`marketplace/${pkg['state-info'].manifest.id}`],
|
||||
[`marketplace/${pkg.stateInfo.manifest.id}`],
|
||||
{
|
||||
queryParams,
|
||||
},
|
||||
|
||||
@@ -19,16 +19,14 @@ export class ToHealthChecksPipe implements PipeTransform {
|
||||
transform(
|
||||
manifest: Manifest,
|
||||
): Observable<Record<string, HealthCheckResult | null> | null> {
|
||||
return this.patch
|
||||
.watch$('package-data', manifest.id, 'status', 'main')
|
||||
.pipe(
|
||||
map(main => {
|
||||
return main.status === PackageMainStatus.Running &&
|
||||
!isEmptyObject(main.health)
|
||||
? main.health
|
||||
: null
|
||||
}),
|
||||
startWith(null),
|
||||
)
|
||||
return this.patch.watch$('packageData', manifest.id, 'status', 'main').pipe(
|
||||
map(main => {
|
||||
return main.status === PackageMainStatus.Running &&
|
||||
!isEmptyObject(main.health)
|
||||
? main.health
|
||||
: 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(
|
||||
map(({ url, name }) => {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<ng-container *ngIf="localPkg; else install">
|
||||
<ng-container
|
||||
*ngIf="
|
||||
localPkg['state-info'].state === 'installed' &&
|
||||
localPkg.stateInfo.state === 'installed' &&
|
||||
(localPkg | toManifest) as manifest
|
||||
"
|
||||
>
|
||||
|
||||
@@ -66,7 +66,7 @@ export class MarketplaceShowControlsComponent {
|
||||
if (!this.localPkg) {
|
||||
this.alertInstall(url)
|
||||
} else {
|
||||
const originalUrl = this.localPkg['marketplace-url']
|
||||
const originalUrl = this.localPkg.marketplaceUrl
|
||||
|
||||
if (!sameUrl(url, originalUrl)) {
|
||||
const proceed = await this.presentAlertDifferentMarketplace(
|
||||
@@ -98,12 +98,11 @@ export class MarketplaceShowControlsComponent {
|
||||
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
|
||||
if (originalUrl) {
|
||||
originalName =
|
||||
marketplaces['known-hosts'][originalUrl]?.name || originalUrl
|
||||
originalName = marketplaces.knownHosts[originalUrl]?.name || originalUrl
|
||||
}
|
||||
|
||||
return new Promise(async resolve => {
|
||||
|
||||
@@ -20,7 +20,7 @@ export class MarketplaceShowPage {
|
||||
readonly loadVersion$ = new BehaviorSubject<string>('*')
|
||||
|
||||
readonly localPkg$ = this.patch
|
||||
.watch$('package-data', this.pkgId)
|
||||
.watch$('packageData', this.pkgId)
|
||||
.pipe(filter(Boolean), shareReplay({ bufferSize: 1, refCount: true }))
|
||||
|
||||
readonly pkg$ = this.loadVersion$.pipe(
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
>
|
||||
<ion-text
|
||||
*ngIf="
|
||||
localPkg['state-info']['installing-info']!.progress.overall
|
||||
localPkg.stateInfo.installingInfo.progress.overall
|
||||
| installingProgressString as progress
|
||||
"
|
||||
color="primary"
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
<ion-label>
|
||||
<h2>
|
||||
<b>
|
||||
<span *ngIf="not['package-id'] as pkgId">
|
||||
<span *ngIf="not.packageId as pkgId">
|
||||
{{ packageData[pkgId] ? (packageData[pkgId] |
|
||||
toManifest).title : pkgId }} -
|
||||
</span>
|
||||
@@ -104,7 +104,7 @@
|
||||
View Full Message
|
||||
</a>
|
||||
</p>
|
||||
<p>{{ not['created-at'] | date: 'medium' }}</p>
|
||||
<p>{{ not.createdAt | date: 'medium' }}</p>
|
||||
</ion-label>
|
||||
<ion-button
|
||||
*ngIf="not.code === 1"
|
||||
@@ -116,11 +116,11 @@
|
||||
View Report
|
||||
</ion-button>
|
||||
<ion-button
|
||||
*ngIf="not['package-id'] && packageData[not['package-id']]"
|
||||
*ngIf="not.packageId && packageData[not.packageId]"
|
||||
slot="end"
|
||||
fill="clear"
|
||||
color="dark"
|
||||
[routerLink]="['/services', not['package-id']]"
|
||||
[routerLink]="['/services', not.packageId]"
|
||||
>
|
||||
View Service
|
||||
</ion-button>
|
||||
|
||||
@@ -29,7 +29,7 @@ export class NotificationsPage {
|
||||
needInfinite = false
|
||||
fromToast = !!this.route.snapshot.queryParamMap.get('toast')
|
||||
readonly perPage = 40
|
||||
readonly packageData$ = this.patch.watch$('package-data').pipe(first())
|
||||
readonly packageData$ = this.patch.watch$('packageData').pipe(first())
|
||||
|
||||
constructor(
|
||||
private readonly embassyApi: ApiService,
|
||||
@@ -116,7 +116,7 @@ export class NotificationsPage {
|
||||
component: BackupReportPage,
|
||||
componentProps: {
|
||||
report: notification.data,
|
||||
timestamp: notification['created-at'],
|
||||
timestamp: notification.createdAt,
|
||||
},
|
||||
})
|
||||
await modal.present()
|
||||
|
||||
@@ -43,7 +43,7 @@ export class RestorePage {
|
||||
useMask: true,
|
||||
buttonText: 'Next',
|
||||
submitFn: async (password: string) => {
|
||||
const passwordHash = target.entry['embassy-os']?.['password-hash'] || ''
|
||||
const passwordHash = target.entry.startOs?.passwordHash || ''
|
||||
argon2.verify(passwordHash, password)
|
||||
await this.restoreFromBackup(target, password)
|
||||
},
|
||||
@@ -71,7 +71,7 @@ export class RestorePage {
|
||||
|
||||
try {
|
||||
const backupInfo = await this.embassyApi.getBackupInfo({
|
||||
'target-id': target.id,
|
||||
targetId: target.id,
|
||||
password,
|
||||
})
|
||||
this.presentModalSelect(target.id, backupInfo, password, oldPassword)
|
||||
|
||||
@@ -18,11 +18,11 @@ import { Observable } from 'rxjs'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
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$(
|
||||
'server-info',
|
||||
'status-info',
|
||||
'backup-progress',
|
||||
'serverInfo',
|
||||
'statusInfo',
|
||||
'backupProgress',
|
||||
)
|
||||
|
||||
constructor(private readonly patch: PatchDB<DataModel>) {}
|
||||
@@ -33,7 +33,7 @@ export class BackingUpComponent {
|
||||
})
|
||||
export class PkgMainStatusPipe implements PipeTransform {
|
||||
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>) {}
|
||||
|
||||
@@ -84,9 +84,7 @@ export class ServerBackupPage {
|
||||
buttonText: 'Create Backup',
|
||||
submitFn: async (password: string) => {
|
||||
// confirm password matches current master password
|
||||
const { 'password-hash': passwordHash } = await getServerInfo(
|
||||
this.patch,
|
||||
)
|
||||
const { passwordHash } = await getServerInfo(this.patch)
|
||||
argon2.verify(passwordHash, password)
|
||||
|
||||
// first time backup
|
||||
@@ -95,8 +93,7 @@ export class ServerBackupPage {
|
||||
// existing backup
|
||||
} else {
|
||||
try {
|
||||
const passwordHash =
|
||||
target.entry['embassy-os']?.['password-hash'] || ''
|
||||
const passwordHash = target.entry.startOs?.passwordHash || ''
|
||||
|
||||
argon2.verify(passwordHash, password)
|
||||
} catch {
|
||||
@@ -133,7 +130,7 @@ export class ServerBackupPage {
|
||||
useMask: true,
|
||||
buttonText: 'Create Backup',
|
||||
submitFn: async (oldPassword: string) => {
|
||||
const passwordHash = target.entry['embassy-os']?.['password-hash'] || ''
|
||||
const passwordHash = target.entry.startOs?.passwordHash || ''
|
||||
|
||||
argon2.verify(passwordHash, oldPassword)
|
||||
await this.createBackup(target, password, oldPassword)
|
||||
@@ -161,9 +158,9 @@ export class ServerBackupPage {
|
||||
|
||||
try {
|
||||
await this.embassyApi.createBackup({
|
||||
'target-id': target.id,
|
||||
'package-ids': this.serviceIds,
|
||||
'old-password': oldPassword || null,
|
||||
targetId: target.id,
|
||||
packageIds: this.serviceIds,
|
||||
oldPassword: oldPassword || null,
|
||||
password,
|
||||
})
|
||||
} finally {
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<ion-item-divider>Memory</ion-item-divider>
|
||||
<ion-item>
|
||||
<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-label>Total</ion-label>
|
||||
@@ -94,15 +94,15 @@
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<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-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-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-group>
|
||||
|
||||
@@ -110,18 +110,18 @@
|
||||
<ion-item-divider>CPU</ion-item-divider>
|
||||
<ion-item>
|
||||
<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-label>User Space</ion-label>
|
||||
<ion-note slot="end">
|
||||
<ion-text>{{ cpu['user-space'].value }} %</ion-text>
|
||||
<ion-text>{{ cpu.userSpace.value }} %</ion-text>
|
||||
</ion-note>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Kernel Space</ion-label>
|
||||
<ion-note slot="end">
|
||||
<ion-text>{{ cpu['kernel-space'].value }} %</ion-text>
|
||||
<ion-text>{{ cpu.kernelSpace.value }} %</ion-text>
|
||||
</ion-note>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
@@ -138,7 +138,7 @@
|
||||
<ion-item-divider>Disk</ion-item-divider>
|
||||
<ion-item>
|
||||
<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-label>Capacity</ion-label>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<!-- loaded -->
|
||||
<ion-item-group *ngIf="server$ | async as server; else loading">
|
||||
<ion-item
|
||||
*ngIf="!server['ntp-synced']"
|
||||
*ngIf="!server.ntpSynced"
|
||||
color="warning"
|
||||
class="ion-margin-bottom"
|
||||
>
|
||||
@@ -62,15 +62,15 @@
|
||||
|
||||
<!-- "Create Backup" button only -->
|
||||
<p *ngIf="button.title === 'Create Backup'">
|
||||
<ng-container *ngIf="server['status-info'] as statusInfo">
|
||||
<ng-container *ngIf="server.statusInfo as statusInfo">
|
||||
<ion-text
|
||||
[color]="server['last-backup'] | backupColor"
|
||||
*ngIf="!statusInfo['backup-progress'] && !statusInfo['update-progress']"
|
||||
[color]="server.lastBackup | backupColor"
|
||||
*ngIf="!statusInfo.backupProgress && !statusInfo.updateProgress"
|
||||
>
|
||||
Last Backup: {{ server['last-backup'] ? (server['last-backup'] |
|
||||
date: 'medium') : 'never' }}
|
||||
Last Backup: {{ server.lastBackup ? (server.lastBackup | date:
|
||||
'medium') : 'never' }}
|
||||
</ion-text>
|
||||
<span *ngIf="!!statusInfo['backup-progress']" class="inline">
|
||||
<span *ngIf="!!statusInfo.backupProgress" class="inline">
|
||||
<ion-spinner
|
||||
color="success"
|
||||
style="height: 12px; width: 12px; margin-right: 6px"
|
||||
@@ -82,7 +82,7 @@
|
||||
<!-- "Software Update" button only -->
|
||||
<p *ngIf="button.title === 'Software Update'">
|
||||
<ion-text
|
||||
*ngIf="server['status-info'].updated; else notUpdated"
|
||||
*ngIf="server.statusInfo.updated; else notUpdated"
|
||||
class="inline"
|
||||
color="warning"
|
||||
>
|
||||
|
||||
@@ -37,7 +37,7 @@ export class ServerShowPage {
|
||||
manageClicks = 0
|
||||
powerClicks = 0
|
||||
|
||||
readonly server$ = this.patch.watch$('server-info')
|
||||
readonly server$ = this.patch.watch$('serverInfo')
|
||||
readonly showUpdate$ = this.eosService.showUpdate$
|
||||
readonly showDiskRepair$ = this.ClientStorageService.showDiskRepair$
|
||||
|
||||
@@ -141,7 +141,7 @@ export class ServerShowPage {
|
||||
}
|
||||
|
||||
// confirm current password is correct
|
||||
const { 'password-hash': passwordHash } = await getServerInfo(this.patch)
|
||||
const { passwordHash } = await getServerInfo(this.patch)
|
||||
try {
|
||||
argon2.verify(passwordHash, value.currPass)
|
||||
} catch (e) {
|
||||
@@ -160,8 +160,8 @@ export class ServerShowPage {
|
||||
|
||||
try {
|
||||
await this.embassyApi.resetPassword({
|
||||
'old-password': value.currPass,
|
||||
'new-password': value.newPass,
|
||||
oldPassword: value.currPass,
|
||||
newPassword: value.newPass,
|
||||
})
|
||||
const toast = await this.toastCtrl.create({
|
||||
header: 'Password changed!',
|
||||
@@ -221,7 +221,7 @@ export class ServerShowPage {
|
||||
|
||||
try {
|
||||
await this.embassyApi.resetTor({
|
||||
'wipe-state': wipeState,
|
||||
wipeState: wipeState,
|
||||
reason: 'User triggered',
|
||||
})
|
||||
const toast = await this.toastCtrl.create({
|
||||
|
||||
@@ -30,17 +30,17 @@
|
||||
<ion-item>
|
||||
<ion-label class="break-all">
|
||||
<h2>Tor</h2>
|
||||
<p>{{ server['tor-address'] }}</p>
|
||||
<p>{{ server.torAddress }}</p>
|
||||
</ion-label>
|
||||
<div slot="end">
|
||||
<ion-button fill="clear" (click)="showQR(server['tor-address'])">
|
||||
<ion-button fill="clear" (click)="showQR(server.torAddress)">
|
||||
<ion-icon
|
||||
slot="icon-only"
|
||||
name="qr-code-outline"
|
||||
size="small"
|
||||
></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button fill="clear" (click)="copy(server['tor-address'])">
|
||||
<ion-button fill="clear" (click)="copy(server.torAddress)">
|
||||
<ion-icon
|
||||
slot="icon-only"
|
||||
name="copy-outline"
|
||||
@@ -52,13 +52,13 @@
|
||||
<ion-item>
|
||||
<ion-label class="break-all">
|
||||
<h2>LAN</h2>
|
||||
<p>{{ server['lan-address'] }}</p>
|
||||
<p>{{ server.lanAddress }}</p>
|
||||
</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-button>
|
||||
</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-label>
|
||||
<h2>{{ iface.key }} (IPv4)</h2>
|
||||
@@ -92,13 +92,9 @@
|
||||
<ion-item>
|
||||
<ion-label>
|
||||
<h2>CA fingerprint</h2>
|
||||
<p>{{ server['ca-fingerprint'] }}</p>
|
||||
<p>{{ server.caFingerprint }}</p>
|
||||
</ion-label>
|
||||
<ion-button
|
||||
slot="end"
|
||||
fill="clear"
|
||||
(click)="copy(server['ca-fingerprint'])"
|
||||
>
|
||||
<ion-button slot="end" fill="clear" (click)="copy(server.caFingerprint)">
|
||||
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
|
||||
@@ -13,7 +13,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ServerSpecsPage {
|
||||
readonly server$ = this.patch.watch$('server-info')
|
||||
readonly server$ = this.patch.watch$('serverInfo')
|
||||
|
||||
constructor(
|
||||
private readonly toastCtrl: ToastController,
|
||||
|
||||
@@ -52,10 +52,8 @@
|
||||
></ion-icon>
|
||||
<ion-label>
|
||||
<h1>{{ getPlatformName(currentSession.metadata.platforms) }}</h1>
|
||||
<h2>
|
||||
Last Active: {{ currentSession['last-active'] | date : 'medium' }}
|
||||
</h2>
|
||||
<p>{{ currentSession['user-agent'] }}</p>
|
||||
<h2>Last Active: {{ currentSession.lastActive| date : 'medium' }}</h2>
|
||||
<p>{{ currentSession.userAgent }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
@@ -80,8 +78,8 @@
|
||||
></ion-icon>
|
||||
<ion-label>
|
||||
<h1>{{ getPlatformName(session.metadata.platforms) }}</h1>
|
||||
<h2>Last Active: {{ session['last-active'] | date : 'medium' }}</h2>
|
||||
<p>{{ session['user-agent'] }}</p>
|
||||
<h2>Last Active: {{ session.lastActive | date : 'medium' }}</h2>
|
||||
<p>{{ session.userAgent }}</p>
|
||||
</ion-label>
|
||||
<ion-button
|
||||
slot="end"
|
||||
|
||||
@@ -35,8 +35,7 @@ export class SessionsPage {
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return (
|
||||
new Date(b['last-active']).valueOf() -
|
||||
new Date(a['last-active']).valueOf()
|
||||
new Date(b.lastActive).valueOf() - new Date(a.lastActive).valueOf()
|
||||
)
|
||||
})
|
||||
} catch (e: any) {
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
<ion-icon slot="start" name="key-outline" size="large"></ion-icon>
|
||||
<ion-label>
|
||||
<h1>{{ ssh.hostname }}</h1>
|
||||
<h2>{{ ssh['created-at'] | date: 'medium' }}</h2>
|
||||
<h2>{{ ssh.createdAt| date: 'medium' }}</h2>
|
||||
<p>{{ ssh.alg }} {{ ssh.fingerprint }}</p>
|
||||
</ion-label>
|
||||
<ion-button
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
</ion-item>
|
||||
|
||||
<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
|
||||
button
|
||||
detail="false"
|
||||
|
||||
@@ -39,8 +39,7 @@
|
||||
<h1 style="line-height: 1.3">{{ pkg.manifest.title }}</h1>
|
||||
<h2 class="inline">
|
||||
<span>
|
||||
{{ local['state-info'].manifest.version | displayEmver
|
||||
}}
|
||||
{{ local.stateInfo.manifest.version | displayEmver }}
|
||||
</span>
|
||||
|
||||
<ion-icon name="arrow-forward"></ion-icon>
|
||||
@@ -57,8 +56,8 @@
|
||||
</ion-label>
|
||||
<div slot="end" style="margin-left: 4px">
|
||||
<round-progress
|
||||
*ngIf="local['state-info'].state === 'updating' else notUpdating"
|
||||
[current]="(local['state-info']['installing-info'].progress.overall | installingProgress) || 0"
|
||||
*ngIf="local.stateInfo.state === 'updating' else notUpdating"
|
||||
[current]="(local.stateInfo.installingInfo.progress.overall | installingProgress) || 0"
|
||||
[max]="100"
|
||||
[radius]="13"
|
||||
[stroke]="3"
|
||||
@@ -86,9 +85,7 @@
|
||||
<div class="ion-padding" slot="content">
|
||||
<div class="notes">
|
||||
<h5>What's new</h5>
|
||||
<p
|
||||
[innerHTML]="pkg.manifest['release-notes'] | markdown"
|
||||
></p>
|
||||
<p [innerHTML]="pkg.manifest.releaseNotes| markdown"></p>
|
||||
</div>
|
||||
<ion-button
|
||||
fill="clear"
|
||||
|
||||
@@ -42,7 +42,7 @@ export class UpdatesPage {
|
||||
readonly data$: Observable<UpdatesData> = combineLatest({
|
||||
hosts: this.marketplaceService.getKnownHosts$(true),
|
||||
marketplace: this.marketplaceService.getMarketplace$(),
|
||||
localPkgs: this.patch.watch$('package-data').pipe(
|
||||
localPkgs: this.patch.watch$('packageData').pipe(
|
||||
map(pkgs =>
|
||||
Object.values(pkgs).reduce((acc, curr) => {
|
||||
if (isInstalled(curr) || isUpdating(curr)) return { ...acc, curr }
|
||||
@@ -171,7 +171,7 @@ export class FilterUpdatesPipe implements PipeTransform {
|
||||
localPkg &&
|
||||
this.emver.compare(
|
||||
manifest.version,
|
||||
localPkg['state-info'].manifest.version,
|
||||
localPkg.stateInfo.manifest.version,
|
||||
) === 1
|
||||
)
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ export class HealthComponent {
|
||||
] as const
|
||||
|
||||
readonly data$ = combineLatest([
|
||||
inject(PatchDB<DataModel>).watch$('package-data'),
|
||||
inject(PatchDB<DataModel>).watch$('packageData'),
|
||||
inject(DepErrorService).depErrors$,
|
||||
]).pipe(
|
||||
map(([data, depErrors]) => {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Manifest } from '@start9labs/marketplace'
|
||||
name: 'hasUi',
|
||||
})
|
||||
export class UiPipe implements PipeTransform {
|
||||
transform(interfaces: PackageDataEntry['service-interfaces']): boolean {
|
||||
transform(interfaces: PackageDataEntry['serviceInterfaces']): boolean {
|
||||
return interfaces ? hasUi(interfaces) : false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@ export type ValueSpecOf<T extends ValueType> = T extends 'string'
|
||||
? ValueSpecList
|
||||
: T extends 'object'
|
||||
? ValueSpecObject
|
||||
: T extends 'pointer'
|
||||
? ValueSpecPointer
|
||||
: T extends 'union'
|
||||
? ValueSpecUnion
|
||||
: never
|
||||
@@ -60,16 +58,6 @@ export interface ValueSpecUnion {
|
||||
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 {
|
||||
type: 'object'
|
||||
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