mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
rework Embassy tab, fix error type in patch-db, fix response type for embassy updates
This commit is contained in:
committed by
Aiden McClelland
parent
2f865a1953
commit
933481cd7d
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
) { }
|
||||
|
||||
|
||||
@@ -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 { }
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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 { }
|
||||
@@ -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({
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ export interface DependencyErrorIncorrectVersion {
|
||||
|
||||
export interface DependencyErrorConfigUnsatisfied {
|
||||
type: DependencyErrorType.ConfigUnsatisfied
|
||||
errors: string[]
|
||||
error: string
|
||||
}
|
||||
|
||||
export interface DependencyErrorHealthChecksFailed {
|
||||
|
||||
Reference in New Issue
Block a user