Add system rebuild and disk repair to Diagnostic UI (#2093)

* add system rebuild and disk repair to diagnostic

* add `diagnostic.rebuild`

Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
Matt Hill
2023-01-10 15:02:16 -07:00
committed by GitHub
parent f914110626
commit 8047008fa5
9 changed files with 173 additions and 105 deletions

View File

@@ -6,6 +6,7 @@ use rpc_toolkit::yajrc::RpcError;
use crate::context::DiagnosticContext; use crate::context::DiagnosticContext;
use crate::disk::repair; use crate::disk::repair;
use crate::init::SYSTEM_REBUILD_PATH;
use crate::logs::{fetch_logs, LogResponse, LogSource}; use crate::logs::{fetch_logs, LogResponse, LogSource};
use crate::shutdown::Shutdown; use crate::shutdown::Shutdown;
use crate::util::display_none; use crate::util::display_none;
@@ -13,7 +14,7 @@ use crate::Error;
pub const SYSTEMD_UNIT: &'static str = "embassy-init"; pub const SYSTEMD_UNIT: &'static str = "embassy-init";
#[command(subcommands(error, logs, exit, restart, forget_disk, disk))] #[command(subcommands(error, logs, exit, restart, forget_disk, disk, rebuild))]
pub fn diagnostic() -> Result<(), Error> { pub fn diagnostic() -> Result<(), Error> {
Ok(()) Ok(())
} }
@@ -51,6 +52,12 @@ pub fn restart(#[context] ctx: DiagnosticContext) -> Result<(), Error> {
Ok(()) Ok(())
} }
#[command(display(display_none))]
pub async fn rebuild(#[context] ctx: DiagnosticContext) -> Result<(), Error> {
tokio::fs::write(SYSTEM_REBUILD_PATH, b"").await?;
restart(ctx)
}
#[command(subcommands(forget_disk, repair))] #[command(subcommands(forget_disk, repair))]
pub fn disk() -> Result<(), Error> { pub fn disk() -> Result<(), Error> {
Ok(()) Ok(())

View File

@@ -7,54 +7,62 @@
> >
embassyOS - Diagnostic Mode embassyOS - Diagnostic Mode
</h1> </h1>
<h2
style=" <ng-container *ngIf="error">
padding-bottom: 16px; <h2
font-size: calc(1vw + 14px); style="
font-weight: bold; padding-bottom: 16px;
" font-size: calc(1vw + 14px);
> font-weight: bold;
embassyOS launch error: "
</h2> >
<div class="code-block"> embassyOS launch error:
<code> </h2>
<ion-text color="warning">{{ error.problem }}</ion-text> <div class="code-block">
<span *ngIf="error.details"> <code>
<br /> <ion-text color="warning">{{ error.problem }}</ion-text>
<br /> <span *ngIf="error.details">
<ion-text color="warning">{{ error.details }}</ion-text> <br />
</span> <br />
</code> <ion-text color="warning">{{ error.details }}</ion-text>
</div> </span>
<ion-button routerLink="logs"> View Logs </ion-button> </code>
<h2 </div>
style=" <ion-button routerLink="logs"> View Logs </ion-button>
padding: 32px 0 16px 0; <h2
font-size: calc(1vw + 12px); style="
font-weight: bold; padding: 32px 0 16px 0;
" font-size: calc(1vw + 12px);
> font-weight: bold;
Possible solution: "
</h2> >
<div class="code-block"> Possible solutions:
<code><ion-text color="success">{{ error.solution }}</ion-text></code> </h2>
</div> <div class="code-block">
<ion-button (click)="restart()"> Restart Embassy </ion-button> <code><ion-text color="success">{{ error.solution }}</ion-text></code>
<div </div>
*ngIf="error.code === 15 || error.code === 25" <ion-button (click)="restart()"> Restart Embassy </ion-button>
class="ion-padding-top" <ion-button
> class="ion-padding-start"
<ion-button *ngIf="error.code === 15" (click)="forgetDrive()"> *ngIf="error.code === 15 || error.code === 25"
(click)="forgetDrive()"
>
{{ error.code === 15 ? 'Setup Current Drive' : 'Enter Recovery Mode' {{ error.code === 15 ? 'Setup Current Drive' : 'Enter Recovery Mode'
}} }}
</ion-button> </ion-button>
</div>
<div <div class="ion-padding-top">
*ngIf="error.code === 2 || error.code === 48" <ion-button (click)="presentAlertSystemRebuild()" color="warning"
class="ion-padding-top" >System Rebuild</ion-button
> >
<ion-button (click)="repairDrive()"> {{ 'Repair Drive' }} </ion-button> </div>
</div>
<div class="ion-padding-top">
<ion-button (click)="presentAlertRepairDisk()" color="danger"
>Repair Drive</ion-button
>
</div>
</ng-container>
</ng-container> </ng-container>
<ng-template #refresh> <ng-template #refresh>

View File

@@ -8,12 +8,12 @@ import { ApiService } from 'src/app/services/api/api.service'
styleUrls: ['home.page.scss'], styleUrls: ['home.page.scss'],
}) })
export class HomePage { export class HomePage {
error: { error?: {
code: number code: number
problem: string problem: string
solution: string solution: string
details?: string details?: string
} = {} as any }
solutions: string[] = [] solutions: string[] = []
restarted = false restarted = false
@@ -118,7 +118,80 @@ export class HomePage {
} }
} }
async repairDrive(): Promise<void> { async presentAlertSystemRebuild() {
const alert = await this.alertCtrl.create({
header: 'Warning',
message:
'<p>This action will tear down all service containers and rebuild them from scratch. No data will be deleted.</p><p>A system rebuild can be useful if your system gets into a bad state, and it should only be performed if you are experiencing general performance or reliability issues.</p><p>It may take up to an hour to complete. During this time, you will lose all connectivity to your Embassy.</p>',
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Rebuild',
handler: () => {
try {
this.systemRebuild()
} catch (e) {
console.error(e)
}
},
},
],
cssClass: 'alert-warning-message',
})
await alert.present()
}
async presentAlertRepairDisk() {
const alert = await this.alertCtrl.create({
header: 'Warning',
message:
'<p>This action should only be executed if directed by a Start9 support specialist.</p><p>If anything happens to the device during the reboot, such as losing power or unplugging the drive, the filesystem <i>will</i> be in an unrecoverable state. Please proceed with caution.</p>',
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Repair',
handler: () => {
try {
this.repairDisk()
} catch (e) {
console.error(e)
}
},
},
],
cssClass: 'alert-error-message',
})
await alert.present()
}
refreshPage(): void {
window.location.reload()
}
private async systemRebuild(): Promise<void> {
const loader = await this.loadingCtrl.create({
cssClass: 'loader',
})
await loader.present()
try {
await this.api.systemRebuild()
await this.api.restart()
this.restarted = true
} catch (e) {
console.error(e)
} finally {
loader.dismiss()
}
}
private async repairDisk(): Promise<void> {
const loader = await this.loadingCtrl.create({ const loader = await this.loadingCtrl.create({
cssClass: 'loader', cssClass: 'loader',
}) })
@@ -134,36 +207,4 @@ export class HomePage {
loader.dismiss() loader.dismiss()
} }
} }
async presentAlertRepairDisk() {
const alert = await this.alertCtrl.create({
header: 'Warning',
message:
'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, such as losing 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: 'alert-warning-message',
})
await alert.present()
}
refreshPage(): void {
window.location.reload()
}
} }

View File

@@ -5,6 +5,7 @@ export abstract class ApiService {
abstract restart(): Promise<void> abstract restart(): Promise<void>
abstract forgetDrive(): Promise<void> abstract forgetDrive(): Promise<void>
abstract repairDisk(): Promise<void> abstract repairDisk(): Promise<void>
abstract systemRebuild(): Promise<void>
abstract getLogs(params: ServerLogsReq): Promise<LogsRes> abstract getLogs(params: ServerLogsReq): Promise<LogsRes>
} }

View File

@@ -12,35 +12,42 @@ import { LogsRes, ServerLogsReq } from '@start9labs/shared'
export class LiveApiService implements ApiService { export class LiveApiService implements ApiService {
constructor(private readonly http: HttpService) {} constructor(private readonly http: HttpService) {}
getError(): Promise<GetErrorRes> { async getError(): Promise<GetErrorRes> {
return this.rpcRequest<GetErrorRes>({ return this.rpcRequest<GetErrorRes>({
method: 'diagnostic.error', method: 'diagnostic.error',
params: {}, params: {},
}) })
} }
restart(): Promise<void> { async restart(): Promise<void> {
return this.rpcRequest<void>({ return this.rpcRequest<void>({
method: 'diagnostic.restart', method: 'diagnostic.restart',
params: {}, params: {},
}) })
} }
forgetDrive(): Promise<void> { async forgetDrive(): Promise<void> {
return this.rpcRequest<void>({ return this.rpcRequest<void>({
method: 'diagnostic.disk.forget', method: 'diagnostic.disk.forget',
params: {}, params: {},
}) })
} }
repairDisk(): Promise<void> { async repairDisk(): Promise<void> {
return this.rpcRequest<void>({ return this.rpcRequest<void>({
method: 'diagnostic.disk.repair', method: 'diagnostic.disk.repair',
params: {}, params: {},
}) })
} }
getLogs(params: ServerLogsReq): Promise<LogsRes> { async systemRebuild(): Promise<void> {
return this.rpcRequest<void>({
method: 'diagnostic.rebuild',
params: {},
})
}
async getLogs(params: ServerLogsReq): Promise<LogsRes> {
return this.rpcRequest<LogsRes>({ return this.rpcRequest<LogsRes>({
method: 'diagnostic.logs', method: 'diagnostic.logs',
params, params,

View File

@@ -26,6 +26,10 @@ export class MockApiService implements ApiService {
await pauseFor(1000) await pauseFor(1000)
} }
async systemRebuild(): Promise<void> {
await pauseFor(1000)
}
async getLogs(params: ServerLogsReq): Promise<LogsRes> { async getLogs(params: ServerLogsReq): Promise<LogsRes> {
await pauseFor(1000) await pauseFor(1000)
let entries: Log[] let entries: Log[]

View File

@@ -139,3 +139,21 @@ ion-modal {
content: '...'; content: '...';
} }
} }
.alert-error-message {
.alert-title {
color: var(--ion-color-danger);
}
}
.alert-warning-message {
.alert-title {
color: var(--ion-color-warning);
}
}
.alert-success-message {
.alert-title {
color: var(--ion-color-success);
}
}

View File

@@ -184,7 +184,7 @@ export class ServerShowPage {
async presentAlertRepairDisk() { async presentAlertRepairDisk() {
const alert = await this.alertCtrl.create({ const alert = await this.alertCtrl.create({
header: 'Warning', header: 'Warning',
message: `<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, such as losing power, a power surge, unplugging the drive, or unplugging the Embassy, the filesystem <i>will</i> be in an unrecoverable state. Please proceed with caution.</p>`, message: `<p>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, such as losing power or unplugging the drive, the filesystem <i>will</i> be in an unrecoverable state. Please proceed with caution.</p>`,
buttons: [ buttons: [
{ {
text: 'Cancel', text: 'Cancel',

View File

@@ -221,24 +221,6 @@ ion-button {
} }
} }
.alert-error-message {
.alert-title {
color: var(--ion-color-danger);
}
}
.alert-warning-message {
.alert-title {
color: var(--ion-color-warning);
}
}
.alert-success-message {
.alert-title {
color: var(--ion-color-success);
}
}
.notification-detail-alert { .notification-detail-alert {
.alert-wrapper { .alert-wrapper {
--max-height: 80% !important; --max-height: 80% !important;