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::disk::repair;
use crate::init::SYSTEM_REBUILD_PATH;
use crate::logs::{fetch_logs, LogResponse, LogSource};
use crate::shutdown::Shutdown;
use crate::util::display_none;
@@ -13,7 +14,7 @@ use crate::Error;
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> {
Ok(())
}
@@ -51,6 +52,12 @@ pub fn restart(#[context] ctx: DiagnosticContext) -> Result<(), Error> {
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))]
pub fn disk() -> Result<(), Error> {
Ok(())

View File

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

View File

@@ -8,12 +8,12 @@ import { ApiService } from 'src/app/services/api/api.service'
styleUrls: ['home.page.scss'],
})
export class HomePage {
error: {
error?: {
code: number
problem: string
solution: string
details?: string
} = {} as any
}
solutions: string[] = []
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({
cssClass: 'loader',
})
@@ -134,36 +207,4 @@ export class HomePage {
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 forgetDrive(): Promise<void>
abstract repairDisk(): Promise<void>
abstract systemRebuild(): Promise<void>
abstract getLogs(params: ServerLogsReq): Promise<LogsRes>
}

View File

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

View File

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

View File

@@ -139,3 +139,21 @@ ion-modal {
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() {
const alert = await this.alertCtrl.create({
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: [
{
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 {
.alert-wrapper {
--max-height: 80% !important;