switch all fe to camelCase

This commit is contained in:
Matt Hill
2024-03-22 12:05:41 -06:00
parent a35baca580
commit 7e9d453a2c
120 changed files with 593 additions and 1739 deletions

View File

@@ -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,

View File

@@ -45,8 +45,8 @@ export class MockApiService implements ApiService {
}
return {
entries,
'start-cursor': 'startCursor',
'end-cursor': 'endCursor',
startCursor: 'start-cursor',
endCursor: 'end-cursor',
}
}
}

View File

@@ -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',
},

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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
}
}

View File

@@ -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>

View File

@@ -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()
)
})
}

View File

@@ -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 {

View File

@@ -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({

View File

@@ -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')

View File

@@ -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,

View File

@@ -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,
})
})

View File

@@ -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()
}

View File

@@ -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 = {

View File

@@ -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),
}
}

View File

@@ -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),
}
}

View File

@@ -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,
})

View File

@@ -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
}

View File

@@ -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({

View File

@@ -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),
),
)

View File

@@ -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 = {

View File

@@ -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,
) {}
}

View File

@@ -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
}
}

View File

@@ -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$

View File

@@ -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',

View File

@@ -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

View File

@@ -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),

View File

@@ -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),
)

View File

@@ -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>) {

View File

@@ -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()"

View File

@@ -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)

View File

@@ -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>

View File

@@ -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')

View File

@@ -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,

View File

@@ -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,
}
})

View File

@@ -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) {

View File

@@ -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()

View File

@@ -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"

View File

@@ -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,
})

View File

@@ -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)

View File

@@ -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>

View File

@@ -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)

View File

@@ -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(),

View File

@@ -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)))
}

View File

@@ -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 })

View File

@@ -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>

View File

@@ -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 },

View File

@@ -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>

View File

@@ -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)"
>

View File

@@ -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)
}

View File

@@ -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,
},

View File

@@ -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),
)
}
}

View File

@@ -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 {}

View File

@@ -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>

View File

@@ -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
}
}
}

View File

@@ -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 {}

View File

@@ -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>

View File

@@ -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
}
}
}

View File

@@ -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 {}

View File

@@ -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>

View File

@@ -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'])
})
}
}

View File

@@ -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 {}

View File

@@ -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>

View File

@@ -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',
},
}

View File

@@ -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 {}

View File

@@ -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>

View File

@@ -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()
}
}
}

View File

@@ -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'],
},
}
}

View File

@@ -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 {}

View File

@@ -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 }) => {

View File

@@ -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
"
>

View File

@@ -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 => {

View File

@@ -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(

View File

@@ -28,7 +28,7 @@
>
<ion-text
*ngIf="
localPkg['state-info']['installing-info']!.progress.overall
localPkg.stateInfo.installingInfo.progress.overall
| installingProgressString as progress
"
color="primary"

View File

@@ -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>

View File

@@ -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()

View File

@@ -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)

View File

@@ -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>) {}

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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"
>

View File

@@ -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({

View File

@@ -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>

View File

@@ -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,

View File

@@ -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"

View File

@@ -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) {

View File

@@ -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

View File

@@ -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"

View File

@@ -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>
&nbsp;
<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"

View File

@@ -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
)
})

View File

@@ -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]) => {

View File

@@ -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
}
}

View File

@@ -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