rework Embassy tab, fix error type in patch-db, fix response type for embassy updates

This commit is contained in:
Matt Hill
2021-10-14 10:00:16 -06:00
committed by Aiden McClelland
parent 2f865a1953
commit 933481cd7d
24 changed files with 126 additions and 191 deletions

View File

@@ -42,7 +42,7 @@ export class AppShowPage {
} = { } as any
connectionFailure: boolean
loading = true
healthChecks: { [id: string]: HealthCheckResult }
healthChecks: { [id: string]: HealthCheckResult } = { }
installProgress: ProgressData
@ViewChild(IonContent) content: IonContent
@@ -80,18 +80,17 @@ export class AppShowPage {
this.installProgress = !isEmptyObject(pkg['install-progress']) ? this.packageLoadingService.transform(pkg['install-progress']) : undefined
this.statuses = renderPkgStatus(pkg)
// health
if (this.pkg.installed?.status.main.status === PackageMainStatus.Running) {
this.healthChecks = { ...this.pkg.installed.status.main.health }
} else {
this.healthChecks = { }
}
const installed = pkg.installed
// dependencies
if (!pkg.installed) {
this.dependencies = { }
} else {
const currentDeps = pkg.installed['current-dependencies']
if (!!installed) {
// health
if (installed.status.main.status === PackageMainStatus.Running) {
this.healthChecks = { ...installed.status.main.health }
} else {
this.healthChecks = { }
}
// dependencies
const currentDeps = installed['current-dependencies']
Object.keys(currentDeps).forEach(key => {
const manifestDep = pkg.manifest.dependencies[key]
if (!this.dependencies[key] && manifestDep) {
@@ -243,7 +242,7 @@ export class AppShowPage {
// config unsatisfied
} else if (error.type === DependencyErrorType.ConfigUnsatisfied) {
errorText = 'Config not satisfied'
actionText = 'Auto Config'
actionText = 'Auto config'
action = () => this.fixDep('configure', id)
} else if (error.type === DependencyErrorType.Transitive) {
errorText = 'Dependency has a dependency issue'
@@ -254,8 +253,6 @@ export class AppShowPage {
}
}
if (!this.dependencies[id]) this.dependencies[id] = { } as any
const depInfo = this.pkg.installed['dependency-info'][id]
this.dependencies[id].title = depInfo.manifest.title
@@ -287,16 +284,11 @@ export class AppShowPage {
}
private async configureDep (depId: string): Promise<void> {
const configErrors = (this.pkg.installed.status['dependency-errors'][depId] as DependencyErrorConfigUnsatisfied).errors
const description = `<ul>${configErrors.map(d => `<li>${d}</li>`).join('\n')}</ul>`
const dependentTitle = this.pkg.manifest.title
const configRecommendation: Recommendation = {
dependentId: this.pkgId,
dependentTitle,
dependentTitle: this.pkg.manifest.title,
dependentIcon: this.pkg['static-files'].icon,
description,
description: (this.pkg.installed.status['dependency-errors'][depId] as DependencyErrorConfigUnsatisfied).error,
}
const params = {
pkgId: depId,
@@ -352,7 +344,7 @@ export class AppShowPage {
{
action: () => this.navCtrl.navigateForward(['instructions'], { relativeTo: this.route }),
title: 'Instructions',
description: '',
description: `Understand how to use ${pkgTitle}`,
icon: 'list-outline',
color: 'danger',
},
@@ -400,7 +392,7 @@ export class AppShowPage {
{
action: () => this.navCtrl.navigateForward(['logs'], { relativeTo: this.route }),
title: 'Logs',
description: '',
description: 'Raw, unfiltered service logs',
icon: 'receipt-outline',
color: 'danger',
},

View File

@@ -9,11 +9,33 @@
<ion-content class="ion-padding-top">
<ion-item-group>
<ion-item-group *ngIf="patch.data['server-info'] as server">
<ion-item-divider>General</ion-item-divider>
<ion-item button (click)="presentModalName()">
<ion-label>{{ fields['name'].name }}</ion-label>
<ion-note slot="end">{{ patch.data.ui.name || defaultName }}</ion-note>
</ion-item>
<ion-item button (click)="serverConfig.presentAlert('share-stats', server['share-stats'])">
<ion-label>Auto Report Bugs</ion-label>
<ion-note slot="end">{{ server['share-stats'] ? 'Enabled' : 'Disabled' }}</ion-note>
</ion-item>
<!-- <ion-item button (click)="presentModalValueEdit('password')">
<ion-label>Change Password</ion-label>
<ion-note slot="end">********</ion-note>
</ion-item> -->
<ion-item-divider>Marketplace</ion-item-divider>
<ion-item button (click)="serverConfig.presentAlert('auto-check-updates', patch.data.ui['auto-check-updates'])">
<ion-label>Auto Check for Updates</ion-label>
<ion-note slot="end">{{ patch.data.ui['auto-check-updates'] ? 'Enabled' : 'Disabled' }}</ion-note>
</ion-item>
<!-- <ion-item button (click)="presentModalValueEdit('packageMarketplace', server['package-marketplace'])">
<ion-label>Package Marketplace</ion-label>
<ion-note slot="end">{{ server['package-marketplace'] }}</ion-note>
</ion-item> -->
</ion-item-group>
</ion-content>

View File

@@ -4,7 +4,7 @@ import { IonContent, ModalController } from '@ionic/angular'
import { GenericInputComponent } from 'src/app/modals/generic-input/generic-input.component'
import { ConfigSpec } from 'src/app/pkg-config/config-types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ErrorToastService } from 'src/app/services/error-toast.service'
import { ServerConfigService } from 'src/app/services/server-config.service'
@Component({
selector: 'preferences',
@@ -19,6 +19,7 @@ export class PreferencesPage {
constructor (
private readonly modalCtrl: ModalController,
private readonly api: ApiService,
public readonly serverConfig: ServerConfigService,
public readonly patch: PatchDbService,
) { }

View File

@@ -1,26 +0,0 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { SecurityOptionsPage } from './security-options.page'
import { Routes, RouterModule } from '@angular/router'
import { SharingModule } from 'src/app/modules/sharing.module'
const routes: Routes = [
{
path: '',
component: SecurityOptionsPage,
},
]
@NgModule({
imports: [
CommonModule,
IonicModule,
RouterModule.forChild(routes),
SharingModule,
],
declarations: [
SecurityOptionsPage,
],
})
export class SecurityOptionsPageModule { }

View File

@@ -1,42 +0,0 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<pwa-back-button></pwa-back-button>
</ion-buttons>
<ion-title>Privacy and Security</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding-top" *ngIf="patch.data['server-info'] as server">
<ion-item-group>
<ion-item-divider>General</ion-item-divider>
<ion-item button (click)="serverConfig.presentAlert('share-stats', server['share-stats'])">
<ion-label>Report Bugs</ion-label>
<ion-note slot="end">{{ server['share-stats'] ? 'Enabled' : 'Disabled' }}</ion-note>
</ion-item>
<ion-item-divider>Marketplace</ion-item-divider>
<ion-item button (click)="serverConfig.presentAlert('auto-check-updates', patch.data.ui['auto-check-updates'])">
<ion-label>Auto Check for Updates</ion-label>
<ion-note slot="end">{{ patch.data.ui['auto-check-updates'] ? 'Enabled' : 'Disabled' }}</ion-note>
</ion-item>
<!-- <ion-item button (click)="presentModalValueEdit('packageMarketplace', server['package-marketplace'])">
<ion-label>Package Marketplace</ion-label>
<ion-note slot="end">{{ server['package-marketplace'] }}</ion-note>
</ion-item> -->
<ion-item-divider>Security</ion-item-divider>
<!-- <ion-item button (click)="presentModalValueEdit('password')">
<ion-label>Change Password</ion-label>
<ion-note slot="end">********</ion-note>
</ion-item> -->
<ion-item detail="true" button [routerLink]="['ssh-keys']">
<ion-label>SSH Keys</ion-label>
</ion-item>
<ion-item detail="true" button [routerLink]="['sessions']">
<ion-label>Active Sessions</ion-label>
</ion-item>
</ion-item-group>
</ion-content>

View File

@@ -1,24 +0,0 @@
import { Component, ViewChild } from '@angular/core'
import { ServerConfigService } from 'src/app/services/server-config.service'
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
import { ConfigService } from 'src/app/services/config.service'
import { IonContent } from '@ionic/angular'
@Component({
selector: 'security-options',
templateUrl: './security-options.page.html',
styleUrls: ['./security-options.page.scss'],
})
export class SecurityOptionsPage {
@ViewChild(IonContent) content: IonContent
constructor (
public readonly serverConfig: ServerConfigService,
public readonly config: ConfigService,
public readonly patch: PatchDbService,
) { }
ngAfterViewInit () {
this.content.scrollToPoint(undefined, 1)
}
}

View File

@@ -1,23 +0,0 @@
import { NgModule } from '@angular/core'
import { Routes, RouterModule } from '@angular/router'
const routes: Routes = [
{
path: '',
loadChildren: () => import('./security-options/security-options.module').then(m => m.SecurityOptionsPageModule),
},
{
path: 'sessions',
loadChildren: () => import('./sessions/sessions.module').then(m => m.SessionsPageModule),
},
{
path: 'ssh-keys',
loadChildren: () => import('./ssh-keys/ssh-keys.module').then(m => m.SSHKeysPageModule),
},
]
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class SecurityRoutingModule { }

View File

@@ -11,33 +11,37 @@ const routes: Routes = [
loadChildren: () => import('./server-backup/server-backup.module').then(m => m.ServerBackupPageModule),
},
{
path: 'specs',
loadChildren: () => import('./server-specs/server-specs.module').then(m => m.ServerSpecsPageModule),
},
{
path: 'metrics',
loadChildren: () => import('./server-metrics/server-metrics.module').then(m => m.ServerMetricsPageModule),
path: 'lan',
loadChildren: () => import('./lan/lan.module').then(m => m.LANPageModule),
},
{
path: 'logs',
loadChildren: () => import('./server-logs/server-logs.module').then(m => m.ServerLogsPageModule),
},
{
path: 'wifi',
loadChildren: () => import('./wifi/wifi.module').then(m => m.WifiPageModule),
},
{
path: 'lan',
loadChildren: () => import('./lan/lan.module').then(m => m.LANPageModule),
},
{
path: 'security',
loadChildren: () => import('./security-routes/security-routing.module').then( m => m.SecurityRoutingModule),
path: 'metrics',
loadChildren: () => import('./server-metrics/server-metrics.module').then(m => m.ServerMetricsPageModule),
},
{
path: 'preferences',
loadChildren: () => import('./preferences/preferences.module').then( m => m.PreferencesPageModule),
},
{
path: 'sessions',
loadChildren: () => import('./sessions/sessions.module').then( m => m.SessionsPageModule),
},
{
path: 'specs',
loadChildren: () => import('./server-specs/server-specs.module').then(m => m.ServerSpecsPageModule),
},
{
path: 'ssh',
loadChildren: () => import('./ssh-keys/ssh-keys.module').then( m => m.SSHKeysPageModule),
},
{
path: 'wifi',
loadChildren: () => import('./wifi/wifi.module').then(m => m.WifiPageModule),
},
]
@NgModule({

View File

@@ -13,7 +13,10 @@
<ion-item-divider><ion-text color="dark">{{ cat.key }}</ion-text></ion-item-divider>
<ion-item [detail]="button.detail" button *ngFor="let button of cat.value" (click)="button.action()">
<ion-icon slot="start" [name]="button.icon"></ion-icon>
<ion-label>{{ button.title }}</ion-label>
<ion-label>
<h2>{{ button.title }}</h2>
<p *ngIf="button.description">{{ button.description }}</p>
</ion-label>
</ion-item>
</div>
</ion-item-group>

View File

@@ -108,66 +108,83 @@ export class ServerShowPage {
'Backups': [
{
title: 'Create Backup',
description: 'Back up your Embassy and all its services',
icon: 'save-outline',
action: () => this.navCtrl.navigateForward(['backup'], { relativeTo: this.route }),
detail: true,
},
],
'Settings': [
{
title: 'Preferences',
icon: 'options-outline',
action: () => this.navCtrl.navigateForward(['preferences'], { relativeTo: this.route }),
detail: true,
},
{
title: 'Privacy and Security',
icon: 'shield-checkmark-outline',
action: () => this.navCtrl.navigateForward(['security'], { relativeTo: this.route }),
detail: true,
},
{
title: 'LAN',
icon: 'home-outline',
action: () => this.navCtrl.navigateForward(['lan'], { relativeTo: this.route }),
detail: true,
},
{
title: 'WiFi',
icon: 'wifi',
action: () => this.navCtrl.navigateForward(['wifi'], { relativeTo: this.route }),
detail: true,
},
],
'Insights': [
{
title: 'About',
description: 'Basic information about your Embassy',
icon: 'information-circle-outline',
action: () => this.navCtrl.navigateForward(['specs'], { relativeTo: this.route }),
detail: true,
},
{
title: 'Monitor',
description: 'CPU, disk, memory, and other useful metrics',
icon: 'pulse',
action: () => this.navCtrl.navigateForward(['metrics'], { relativeTo: this.route }),
detail: true,
},
{
title: 'Logs',
description: 'Raw, unfiltered device logs',
icon: 'newspaper-outline',
action: () => this.navCtrl.navigateForward(['logs'], { relativeTo: this.route }),
detail: true,
},
],
'Settings': [
{
title: 'Preferences',
description: 'Device name, background tasks',
icon: 'options-outline',
action: () => this.navCtrl.navigateForward(['preferences'], { relativeTo: this.route }),
detail: true,
},
{
title: 'LAN',
description: 'Access your Embassy on the Local Area Network',
icon: 'home-outline',
action: () => this.navCtrl.navigateForward(['lan'], { relativeTo: this.route }),
detail: true,
},
{
title: 'SSH',
description: 'Access your Embassy from the command line',
icon: 'terminal-outline',
action: () => this.navCtrl.navigateForward(['ssh'], { relativeTo: this.route }),
detail: true,
},
{
title: 'WiFi',
description: 'Add or remove WiFi networks',
icon: 'wifi',
action: () => this.navCtrl.navigateForward(['wifi'], { relativeTo: this.route }),
detail: true,
},
{
title: 'Active Sessions',
description: 'View and manage device access',
icon: 'desktop-outline',
action: () => this.navCtrl.navigateForward(['sessions'], { relativeTo: this.route }),
detail: true,
},
],
'Power': [
{
title: 'Restart',
description: '',
icon: 'reload',
action: () => this.presentAlertRestart(),
detail: false,
},
{
title: 'Shutdown',
description: '',
icon: 'power',
action: () => this.presentAlertShutdown(),
detail: false,
@@ -184,6 +201,7 @@ export class ServerShowPage {
interface ServerSettings {
[key: string]: {
title: string
description: string
icon: string
action: Function
detail: boolean

View File

@@ -34,7 +34,7 @@ export module RR {
export type GetServerMetricsRes = Metrics
export type UpdateServerReq = WithExpire<{ }> // server.update
export type UpdateServerRes = WithRevision<null>
export type UpdateServerRes = WithRevision<'updating' | 'no-updates'>
export type RestartServerReq = { } // server.restart
export type RestartServerRes = null

View File

@@ -54,8 +54,15 @@ export abstract class ApiService implements Source<DataModel>, Http<DataModel> {
protected abstract updateServerRaw (params: RR.UpdateServerReq): Promise<RR.UpdateServerRes>
updateServer = (params: RR.UpdateServerReq) => this.syncResponse(
() => this.updateServerRaw(params),
() => this.updateServerWrapper(params),
)()
async updateServerWrapper (params: RR.UpdateServerReq) {
const res = await this.updateServerRaw(params)
if (res.response === 'no-updates') {
throw new Error('Could ont find a newer version of EmbassyOS')
}
return res
}
abstract restartServer (params: RR.UpdateServerReq): Promise<RR.RestartServerRes>

View File

@@ -118,9 +118,11 @@ export class MockApiService extends ApiService {
value: initialProgress,
},
]
const res = await this.http.rpcRequest<WithRevision<null>>({ method: 'db.patch', params: { patch } })
const res = await this.http.rpcRequest<RR.UpdateServerRes>({ method: 'db.patch', params: { patch } })
res.response = 'updating'
this.updateOSProgress(initialProgress.size)
return res
}

View File

@@ -3,10 +3,11 @@ import { ErrorHandler, Injectable } from '@angular/core'
@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
handleError (error: any): void {
const chunkFailedMessage = /Loading chunk [\d]+ failed/
handleError (e: any): void {
console.error(e)
const chunkFailedMessage = /Loading chunk [\d]+ failed/
if (chunkFailedMessage.test(error.message)) {
if (chunkFailedMessage.test(e.message)) {
window.location.reload()
}
}

View File

@@ -334,7 +334,7 @@ export interface DependencyErrorIncorrectVersion {
export interface DependencyErrorConfigUnsatisfied {
type: DependencyErrorType.ConfigUnsatisfied
errors: string[]
error: string
}
export interface DependencyErrorHealthChecksFailed {