mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
Feature/diagnostic repair disk (#1358)
* add disk repair actions to diagnostic ui and server menu * only display repair disk button when activated * fix typo * add repairDrive fn with restart to diagnostic ui * fix copy * add alert before repairing disk in diagnostic ui * fix repair disk message spacing and hidden display * fix version comparisons and enable dismissable refresh modal * eager load medkit and fix storefront to outline icon
This commit is contained in:
@@ -49,6 +49,12 @@
|
||||
}}
|
||||
</ion-button>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="error.code === 2 || error.code === 48"
|
||||
class="ion-padding-top"
|
||||
>
|
||||
<ion-button (click)="repairDrive()"> {{ 'Repair Drive' }} </ion-button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #refresh>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { LoadingController } from '@ionic/angular'
|
||||
import {
|
||||
AlertController,
|
||||
IonicSafeString,
|
||||
LoadingController,
|
||||
} from '@ionic/angular'
|
||||
import { ApiService } from 'src/app/services/api/api.service'
|
||||
|
||||
@Component({
|
||||
@@ -20,6 +24,7 @@ export class HomePage {
|
||||
constructor(
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly api: ApiService,
|
||||
private readonly alertCtrl: AlertController,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -48,16 +53,33 @@ export class HomePage {
|
||||
this.error = {
|
||||
code: 25,
|
||||
problem:
|
||||
'Storage drive corrupted. This could be the result of data corruption or a physical damage.',
|
||||
'Storage drive corrupted. This could be the result of data corruption or physical damage.',
|
||||
solution:
|
||||
'It may or may not be possible to re-use this drive by reformatting and recovering from backup. To enter recovery mode, click ENTER RECOVERY MODE below, then follow instructions. No data will be erased during this step.',
|
||||
details: error.data?.details,
|
||||
}
|
||||
// filesystem I/O error - disk needs repair
|
||||
} else if (error.code === 2) {
|
||||
this.error = {
|
||||
code: 2,
|
||||
problem: 'Filesystem I/O error.',
|
||||
solution: '',
|
||||
details: error.data?.details,
|
||||
}
|
||||
// disk management error - disk needs repair
|
||||
} else if (error.code === 48) {
|
||||
this.error = {
|
||||
code: 48,
|
||||
problem: 'Disk management error.',
|
||||
solution:
|
||||
'Repairing the disk could help resolve this issue. This will occur on a restart between the bep and chime. Please DO NOT unplug the drive or Embassy during this time or the situation will become worse.',
|
||||
details: error.data?.details,
|
||||
}
|
||||
} else {
|
||||
this.error = {
|
||||
code: error.code,
|
||||
problem: error.message,
|
||||
solution: 'Please conact support.',
|
||||
solution: 'Please contact support.',
|
||||
details: error.data?.details,
|
||||
}
|
||||
}
|
||||
@@ -101,6 +123,53 @@ export class HomePage {
|
||||
}
|
||||
}
|
||||
|
||||
async repairDrive(): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
cssClass: 'loader',
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.api.repairDisk()
|
||||
await this.api.restart()
|
||||
this.restarted = true
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
async presentAlertRepairDisk() {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'RepairDisk',
|
||||
message: new IonicSafeString(
|
||||
`<ion-text color="warning">Warning:</ion-text> This action will attempt to preform a disk repair operation and system reboot. No data will be deleted. This action should only be executed if directed by a Start9 support specialist. We recommend backing up your device before preforming this action. If anything happens to the device during the reboot (between the bep and chime), such as loosing power, a power surge, unplugging the drive, or unplugging the Embassy, the filesystem *will* be in an unrecoverable state. Please proceed with caution.`,
|
||||
),
|
||||
buttons: [
|
||||
{
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
},
|
||||
{
|
||||
text: 'Repair',
|
||||
handler: () => {
|
||||
try {
|
||||
this.api.repairDisk().then(_ => {
|
||||
this.restart()
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
],
|
||||
})
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
refreshPage(): void {
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
@@ -1,22 +1,31 @@
|
||||
export abstract class ApiService {
|
||||
abstract getError (): Promise<GetErrorRes>
|
||||
abstract restart (): Promise<void>
|
||||
abstract forgetDrive (): Promise<void>
|
||||
abstract getLogs (params: GetLogsReq): Promise<GetLogsRes>
|
||||
abstract getError(): Promise<GetErrorRes>
|
||||
abstract restart(): Promise<void>
|
||||
abstract forgetDrive(): Promise<void>
|
||||
abstract repairDisk(): Promise<void>
|
||||
abstract getLogs(params: GetLogsReq): Promise<GetLogsRes>
|
||||
}
|
||||
|
||||
export interface GetErrorRes {
|
||||
code: number,
|
||||
message: string,
|
||||
code: number
|
||||
message: string
|
||||
data: { details: string }
|
||||
}
|
||||
|
||||
export type GetLogsReq = { cursor?: string, before_flag?: boolean, limit?: number }
|
||||
export type GetLogsReq = {
|
||||
cursor?: string
|
||||
before_flag?: boolean
|
||||
limit?: number
|
||||
}
|
||||
export type GetLogsRes = LogsRes
|
||||
|
||||
export type LogsRes = { entries: Log[], 'start-cursor'?: string, 'end-cursor'?: string }
|
||||
export type LogsRes = {
|
||||
entries: Log[]
|
||||
'start-cursor'?: string
|
||||
'end-cursor'?: string
|
||||
}
|
||||
|
||||
export interface Log {
|
||||
timestamp: string
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,14 @@ export class LiveApiService extends ApiService {
|
||||
|
||||
forgetDrive(): Promise<void> {
|
||||
return this.http.rpcRequest<void>({
|
||||
method: 'diagnostic.forget-disk',
|
||||
method: 'diagnostic.disk.forget',
|
||||
params: {},
|
||||
})
|
||||
}
|
||||
|
||||
repairDisk(): Promise<void> {
|
||||
return this.http.rpcRequest<void>({
|
||||
method: 'diagnostic.disk.repair',
|
||||
params: {},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -33,6 +33,11 @@ export class MockApiService extends ApiService {
|
||||
return null
|
||||
}
|
||||
|
||||
async repairDisk(): Promise<void> {
|
||||
await pauseFor(1000)
|
||||
return null
|
||||
}
|
||||
|
||||
async getLogs(params: GetLogsReq): Promise<GetLogsRes> {
|
||||
await pauseFor(1000)
|
||||
let entries: Log[]
|
||||
|
||||
@@ -162,6 +162,7 @@
|
||||
<ion-icon name="logo-bitcoin"></ion-icon>
|
||||
<ion-icon name="mail-outline"></ion-icon>
|
||||
<ion-icon name="map-outline"></ion-icon>
|
||||
<ion-icon name="medkit-outline"></ion-icon>
|
||||
<ion-icon name="newspaper-outline"></ion-icon>
|
||||
<ion-icon name="notifications-outline"></ion-icon>
|
||||
<ion-icon name="options-outline"></ion-icon>
|
||||
|
||||
@@ -396,7 +396,7 @@ export class AppComponent {
|
||||
|
||||
private async presentAlertRefreshNeeded() {
|
||||
const alert = await this.alertCtrl.create({
|
||||
backdropDismiss: false,
|
||||
backdropDismiss: true,
|
||||
header: 'Refresh Needed',
|
||||
message:
|
||||
'Your user interface is cached and out of date. Hard refresh the page to get the latest UI.',
|
||||
|
||||
@@ -56,8 +56,6 @@ export class BackupService {
|
||||
}
|
||||
|
||||
hasValidBackup(target: BackupTarget): boolean {
|
||||
return [0, 1].includes(
|
||||
this.emver.compare(target['embassy-os']?.version, '0.3.0'),
|
||||
)
|
||||
return this.emver.compare(target['embassy-os']?.version, '0.3.0') !== -1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,73 +24,83 @@
|
||||
<ng-template #data>
|
||||
<ion-item-group>
|
||||
<div *ngFor="let cat of settings | keyvalue : asIsOrder">
|
||||
<ion-item-divider
|
||||
><ion-text color="dark">{{ cat.key }}</ion-text></ion-item-divider
|
||||
>
|
||||
<ion-item
|
||||
button
|
||||
*ngFor="let button of cat.value"
|
||||
[detail]="button.detail"
|
||||
[disabled]="button.disabled | async"
|
||||
(click)="button.action()"
|
||||
>
|
||||
<ion-icon slot="start" [name]="button.icon"></ion-icon>
|
||||
<ion-label>
|
||||
<h2>{{ button.title }}</h2>
|
||||
<p *ngIf="button.description">{{ button.description }}</p>
|
||||
<ion-item-divider>
|
||||
<ion-text color="dark" *ngIf="cat.key !== 'Power'"
|
||||
>{{ cat.key }}</ion-text
|
||||
>
|
||||
<ion-text
|
||||
color="dark"
|
||||
*ngIf="cat.key === 'Power'"
|
||||
(click)="addClick()"
|
||||
>{{ cat.key }}</ion-text
|
||||
>
|
||||
</ion-item-divider>
|
||||
<ng-container *ngFor="let button of cat.value">
|
||||
<ion-item
|
||||
button
|
||||
[style.display]="(button.title === 'Repair Disk' && !(localStorageService.showDiskRepair$ | async)) ? 'none' : 'block'"
|
||||
[detail]="button.detail"
|
||||
[disabled]="button.disabled | async"
|
||||
(click)="button.action()"
|
||||
>
|
||||
<ion-icon slot="start" [name]="button.icon"></ion-icon>
|
||||
<ion-label>
|
||||
<h2>{{ button.title }}</h2>
|
||||
<p *ngIf="button.description">{{ button.description }}</p>
|
||||
|
||||
<!-- "Create Backup" button only -->
|
||||
<p *ngIf="button.title === 'Create Backup'">
|
||||
<ng-container
|
||||
*ngIf="patch.data['server-info']['status-info'] as statusInfo"
|
||||
>
|
||||
<ion-text
|
||||
color="warning"
|
||||
*ngIf="!statusInfo['backing-up'] && !statusInfo['update-progress']"
|
||||
>
|
||||
Last Backup: {{ patch.data['server-info']['last-backup'] ?
|
||||
(patch.data['server-info']['last-backup'] | date: 'short') :
|
||||
'never' }}
|
||||
</ion-text>
|
||||
<span *ngIf="!!statusInfo['backing-up']" class="inline">
|
||||
<ion-spinner
|
||||
color="success"
|
||||
style="height: 12px; width: 12px; margin-right: 6px"
|
||||
></ion-spinner>
|
||||
<ion-text color="success"> Backing up </ion-text>
|
||||
</span>
|
||||
</ng-container>
|
||||
</p>
|
||||
<!-- "Software Update" button only -->
|
||||
<p *ngIf="button.title === 'Software Update'">
|
||||
<ng-container *ngIf="button.disabled | async; else enabled">
|
||||
<ion-text
|
||||
*ngIf="patch.data['server-info']['status-info'].updated"
|
||||
class="inline"
|
||||
color="warning"
|
||||
>
|
||||
Update Complete, Restart to apply changes
|
||||
</ion-text>
|
||||
</ng-container>
|
||||
<ng-template #enabled>
|
||||
<!-- "Create Backup" button only -->
|
||||
<p *ngIf="button.title === 'Create Backup'">
|
||||
<ng-container
|
||||
*ngIf="eosService.updateAvailable$ | async; else check"
|
||||
*ngIf="patch.data['server-info']['status-info'] as statusInfo"
|
||||
>
|
||||
<ion-text class="inline" color="success">
|
||||
<ion-icon name="rocket-outline"></ion-icon>
|
||||
Update Available
|
||||
<ion-text
|
||||
color="warning"
|
||||
*ngIf="!statusInfo['backing-up'] && !statusInfo['update-progress']"
|
||||
>
|
||||
Last Backup: {{ patch.data['server-info']['last-backup'] ?
|
||||
(patch.data['server-info']['last-backup'] | date: 'short') :
|
||||
'never' }}
|
||||
</ion-text>
|
||||
<span *ngIf="!!statusInfo['backing-up']" class="inline">
|
||||
<ion-spinner
|
||||
color="success"
|
||||
style="height: 12px; width: 12px; margin-right: 6px"
|
||||
></ion-spinner>
|
||||
<ion-text color="success"> Backing up </ion-text>
|
||||
</span>
|
||||
</ng-container>
|
||||
</p>
|
||||
<!-- "Software Update" button only -->
|
||||
<p *ngIf="button.title === 'Software Update'">
|
||||
<ng-container *ngIf="button.disabled | async; else enabled">
|
||||
<ion-text
|
||||
*ngIf="patch.data['server-info']['status-info'].updated"
|
||||
class="inline"
|
||||
color="warning"
|
||||
>
|
||||
Update Complete, Restart to apply changes
|
||||
</ion-text>
|
||||
</ng-container>
|
||||
<ng-template #check>
|
||||
<ion-text class="inline" color="dark">
|
||||
<ion-icon name="refresh"></ion-icon>
|
||||
Check for updates
|
||||
</ion-text>
|
||||
<ng-template #enabled>
|
||||
<ng-container
|
||||
*ngIf="eosService.updateAvailable$ | async; else check"
|
||||
>
|
||||
<ion-text class="inline" color="success">
|
||||
<ion-icon name="rocket-outline"></ion-icon>
|
||||
Update Available
|
||||
</ion-text>
|
||||
</ng-container>
|
||||
<ng-template #check>
|
||||
<ion-text class="inline" color="dark">
|
||||
<ion-icon name="refresh"></ion-icon>
|
||||
Check for updates
|
||||
</ion-text>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ion-item-group>
|
||||
</ng-template>
|
||||
|
||||
@@ -16,6 +16,7 @@ import { wizardModal } from 'src/app/components/install-wizard/install-wizard.co
|
||||
import { exists, isEmptyObject, ErrorToastService } from '@start9labs/shared'
|
||||
import { EOSService } from 'src/app/services/eos.service'
|
||||
import { ServerStatus } from 'src/app/services/patch-db/data-model'
|
||||
import { LocalStorageService } from 'src/app/services/local-storage.service'
|
||||
|
||||
@Component({
|
||||
selector: 'server-show',
|
||||
@@ -25,6 +26,7 @@ import { ServerStatus } from 'src/app/services/patch-db/data-model'
|
||||
export class ServerShowPage {
|
||||
ServerStatus = ServerStatus
|
||||
hasRecoveredPackage: boolean
|
||||
clicks = 0
|
||||
|
||||
constructor(
|
||||
private readonly alertCtrl: AlertController,
|
||||
@@ -37,6 +39,7 @@ export class ServerShowPage {
|
||||
private readonly route: ActivatedRoute,
|
||||
public readonly eosService: EOSService,
|
||||
public readonly patch: PatchDbService,
|
||||
public readonly localStorageService: LocalStorageService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -143,6 +146,35 @@ export class ServerShowPage {
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
async presentAlertRepairDisk() {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Repair Disk',
|
||||
message: new IonicSafeString(
|
||||
`<ion-text color="warning">Warning:</ion-text> <p>This action will attempt to preform a disk repair operation and system reboot. No data will be deleted. This action should only be executed if directed by a Start9 support specialist. We recommend backing up your device before preforming this action.</p><p>If anything happens to the device during the reboot (between the bep and chime), such as loosing power, a power surge, unplugging the drive, or unplugging the Embassy, the filesystem *will* be in an unrecoverable state. Please proceed with caution.</p>`,
|
||||
),
|
||||
buttons: [
|
||||
{
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
},
|
||||
{
|
||||
text: 'Repair',
|
||||
handler: () => {
|
||||
try {
|
||||
this.embassyApi.repairDisk({}).then(_ => {
|
||||
this.restart()
|
||||
})
|
||||
} catch (e) {
|
||||
this.errToast.present(e)
|
||||
}
|
||||
},
|
||||
cssClass: 'enter-click',
|
||||
},
|
||||
],
|
||||
})
|
||||
await alert.present()
|
||||
}
|
||||
|
||||
private async restart() {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
@@ -305,7 +337,7 @@ export class ServerShowPage {
|
||||
{
|
||||
title: 'Marketplace Settings',
|
||||
description: 'Add or remove marketplaces',
|
||||
icon: 'storefront',
|
||||
icon: 'storefront-outline',
|
||||
action: () =>
|
||||
this.navCtrl.navigateForward(['marketplaces'], {
|
||||
relativeTo: this.route,
|
||||
@@ -418,12 +450,31 @@ export class ServerShowPage {
|
||||
detail: false,
|
||||
disabled: of(false),
|
||||
},
|
||||
{
|
||||
title: 'Repair Disk',
|
||||
description: '',
|
||||
icon: 'medkit-outline',
|
||||
action: () => this.presentAlertRepairDisk(),
|
||||
detail: false,
|
||||
disabled: of(false),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
asIsOrder() {
|
||||
return 0
|
||||
}
|
||||
|
||||
async addClick() {
|
||||
this.clicks++
|
||||
if (this.clicks >= 5) {
|
||||
this.clicks = 0
|
||||
const newVal = await this.localStorageService.toggleShowDiskRepair()
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.clicks = Math.max(this.clicks - 1, 0)
|
||||
}, 10000)
|
||||
}
|
||||
}
|
||||
|
||||
interface ServerSettings {
|
||||
|
||||
@@ -93,6 +93,8 @@ export abstract class ApiService
|
||||
params: RR.SystemRebuildReq,
|
||||
): Promise<RR.SystemRebuildRes>
|
||||
|
||||
abstract repairDisk(params: RR.SystemRebuildReq): Promise<RR.SystemRebuildRes>
|
||||
|
||||
// marketplace URLs
|
||||
|
||||
abstract marketplaceProxy<T>(
|
||||
|
||||
@@ -99,6 +99,10 @@ export class LiveApiService extends ApiService {
|
||||
return this.http.rpcRequest({ method: 'server.rebuild', params })
|
||||
}
|
||||
|
||||
async repairDisk(params: RR.RestartServerReq): Promise<RR.RestartServerRes> {
|
||||
return this.http.rpcRequest({ method: 'disk.repair', params })
|
||||
}
|
||||
|
||||
// marketplace URLs
|
||||
|
||||
async marketplaceProxy<T>(path: string, params: {}, url: string): Promise<T> {
|
||||
|
||||
@@ -193,6 +193,11 @@ export class MockApiService extends ApiService {
|
||||
return null
|
||||
}
|
||||
|
||||
async repairDisk(params: RR.RestartServerReq): Promise<RR.RestartServerRes> {
|
||||
await pauseFor(2000)
|
||||
return null
|
||||
}
|
||||
|
||||
// marketplace URLs
|
||||
|
||||
async marketplaceProxy(path: string, params: {}, url: string): Promise<any> {
|
||||
|
||||
@@ -27,7 +27,7 @@ export const mockPatchData: DataModel = {
|
||||
'unread-notification-count': 4,
|
||||
'password-hash':
|
||||
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
|
||||
'eos-version-compat': '>=0.3.0',
|
||||
'eos-version-compat': '>=0.3.0 <=0.3.0.1',
|
||||
'status-info': null,
|
||||
},
|
||||
'recovered-packages': {
|
||||
|
||||
@@ -2,17 +2,21 @@ import { Injectable } from '@angular/core'
|
||||
import { Storage } from '@ionic/storage-angular'
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
const SHOW_DEV_TOOLS = 'SHOW_DEV_TOOLS'
|
||||
const SHOW_DISK_REPAIR = 'SHOW_DISK_REPAIR'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class LocalStorageService {
|
||||
showDevTools$: BehaviorSubject<boolean> = new BehaviorSubject(false)
|
||||
showDiskRepair$: BehaviorSubject<boolean> = new BehaviorSubject(false)
|
||||
constructor(private readonly storage: Storage) {}
|
||||
|
||||
async init() {
|
||||
const val = await this.storage.get(SHOW_DEV_TOOLS)
|
||||
this.showDevTools$.next(!!val)
|
||||
const devTools = await this.storage.get(SHOW_DEV_TOOLS)
|
||||
this.showDevTools$.next(!!devTools)
|
||||
const diskRepair = await this.storage.get(SHOW_DISK_REPAIR)
|
||||
this.showDiskRepair$.next(!!diskRepair)
|
||||
}
|
||||
|
||||
async toggleShowDevTools(): Promise<boolean> {
|
||||
@@ -21,4 +25,11 @@ export class LocalStorageService {
|
||||
this.showDevTools$.next(newVal)
|
||||
return newVal
|
||||
}
|
||||
|
||||
async toggleShowDiskRepair(): Promise<boolean> {
|
||||
const newVal = !(await this.storage.get(SHOW_DISK_REPAIR))
|
||||
await this.storage.set(SHOW_DISK_REPAIR, newVal)
|
||||
this.showDiskRepair$.next(newVal)
|
||||
return newVal
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user