mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
mvp
This commit is contained in:
committed by
Aiden McClelland
parent
29b4c27812
commit
2d301e84ea
2
diagnosticUI/.gitignore
vendored
2
diagnosticUI/.gitignore
vendored
@@ -29,3 +29,5 @@ npm-debug.log*
|
||||
/platforms
|
||||
/plugins
|
||||
/www
|
||||
|
||||
config.json
|
||||
|
||||
3
diagnosticUI/config-sample.json
Normal file
3
diagnosticUI/config-sample.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"useMocks": true
|
||||
}
|
||||
@@ -4,13 +4,17 @@ import { PreloadAllModules, RouterModule, Routes } from '@angular/router'
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => import('./home/home.module').then( m => m.HomePageModule)
|
||||
loadChildren: () => import('./pages/home/home.module').then( m => m.HomePageModule)
|
||||
},
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
|
||||
RouterModule.forRoot(routes, {
|
||||
scrollPositionRestoration: 'enabled',
|
||||
preloadingStrategy: PreloadAllModules,
|
||||
useHash: true,
|
||||
})
|
||||
],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
|
||||
@@ -4,12 +4,37 @@ import { RouteReuseStrategy } from '@angular/router'
|
||||
import { IonicModule, IonicRouteStrategy } from '@ionic/angular'
|
||||
import { AppComponent } from './app.component'
|
||||
import { AppRoutingModule } from './app-routing.module'
|
||||
import { HttpClientModule } from '@angular/common/http'
|
||||
import { ApiService } from './services/api/api.service'
|
||||
import { MockApiService } from './services/api/mock-api.service'
|
||||
import { LiveApiService } from './services/api/live-api.service'
|
||||
import { HttpService } from './services/http.service'
|
||||
|
||||
const useMocks = require('../../config.json').useMocks
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
entryComponents: [],
|
||||
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule],
|
||||
providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }],
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
BrowserModule,
|
||||
IonicModule.forRoot(),
|
||||
AppRoutingModule,
|
||||
],
|
||||
providers: [
|
||||
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
|
||||
{
|
||||
provide: ApiService,
|
||||
useFactory: (http: HttpService) => {
|
||||
if (useMocks) {
|
||||
return new MockApiService()
|
||||
} else {
|
||||
return new LiveApiService(http)
|
||||
}
|
||||
},
|
||||
deps: [HttpService]
|
||||
},
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<ion-content>
|
||||
<div style="padding: 48px">
|
||||
<h1 class="ion-text-center" style="padding-bottom: 36px;">EmbassyOS - Diagnostic Mode</h1>
|
||||
<h2 style="padding-bottom: 24px;">EmbassyOS failed to launch due to the following error:</h2>
|
||||
<code><ion-text color="warning">jwenfjnwc wekjdnwm ckjwenm kjhe xkjew nj wehjd </ion-text></code>
|
||||
<h2>Possible Solutions</h2>
|
||||
<ul>
|
||||
<li></li>
|
||||
</ul>
|
||||
</div>
|
||||
</ion-content>
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Component } from '@angular/core'
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
templateUrl: 'home.page.html',
|
||||
styleUrls: ['home.page.scss'],
|
||||
})
|
||||
export class HomePage {
|
||||
|
||||
constructor() {}
|
||||
|
||||
}
|
||||
31
diagnosticUI/src/app/pages/home/home.page.html
Normal file
31
diagnosticUI/src/app/pages/home/home.page.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<ion-content>
|
||||
<div style="padding: 48px">
|
||||
<ng-container *ngIf="!restarted && !forgotten; else refresh">
|
||||
<h1 class="ion-text-center" style="padding-bottom: 36px; font-size: calc(2vw + 12px);">EmbassyOS - Diagnostic Mode</h1>
|
||||
<h2 style="padding-bottom: 16px; font-size: calc(1vw + 12px);">EmbassyOS launch error:</h2>
|
||||
<div class="code-block">
|
||||
<code><ion-text color="warning">{{ error.problem }}</ion-text></code>
|
||||
</div>
|
||||
<h2 style="padding-bottom: 16px; font-size: calc(1vw + 12px);">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()">
|
||||
{{ error.code === 15 ? 'Use Current Drive' : 'Enter Recovery Mode' }}
|
||||
</ion-button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #refresh>
|
||||
<h1 class="ion-text-center" style="padding-bottom: 36px; font-size: calc(2vw + 12px);">Embassy is restarting</h1>
|
||||
<h2 style="padding-bottom: 16px; font-size: calc(1vw + 12px);">Wait for Embassy restart, then refresh this page or click REFRESH below.</h2>
|
||||
<ion-button (click)="refreshPage()">
|
||||
Refresh
|
||||
</ion-button>
|
||||
</ng-template>
|
||||
</div>
|
||||
</ion-content>
|
||||
5
diagnosticUI/src/app/pages/home/home.page.scss
Normal file
5
diagnosticUI/src/app/pages/home/home.page.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
.code-block {
|
||||
background-color: rgb(69, 69, 69);
|
||||
padding: 12px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
92
diagnosticUI/src/app/pages/home/home.page.ts
Normal file
92
diagnosticUI/src/app/pages/home/home.page.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { LoadingController } from '@ionic/angular'
|
||||
import { ApiService } from 'src/app/services/api/api.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
templateUrl: 'home.page.html',
|
||||
styleUrls: ['home.page.scss'],
|
||||
})
|
||||
export class HomePage {
|
||||
error: {
|
||||
code: number
|
||||
problem: string
|
||||
solution: string
|
||||
} = { } as any
|
||||
solutions: string[] = []
|
||||
restarted = false
|
||||
forgotten = false
|
||||
|
||||
constructor (
|
||||
private readonly loadingCtrl: LoadingController,
|
||||
private readonly api: ApiService,
|
||||
) { }
|
||||
|
||||
async ngOnInit () {
|
||||
try {
|
||||
const error = await this.api.getError()
|
||||
// incorrect drive
|
||||
if (error.code === 15) {
|
||||
this.error = {
|
||||
code: 15,
|
||||
problem: 'Unknown storage drive detected',
|
||||
solution: 'To use a different storage drive, replace the current one and click RESTART EMBASSY below. To use the current storage drive, click USE CURRENT DRIVE below, then follow instructions. No data will be erased during this process.'
|
||||
}
|
||||
// no drive
|
||||
} else if (error.code === 20) {
|
||||
this.error = {
|
||||
code: 20,
|
||||
problem: 'Storage drive not found',
|
||||
solution: 'Insert your EmbassyOS storage drive and click RESTART EMBASSY below.'
|
||||
}
|
||||
// drive corrupted
|
||||
} else if (error.code === 25) {
|
||||
this.error = {
|
||||
code: 25,
|
||||
problem: 'Storage drive corrupted. This could be the result of data corruption or a 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.'
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
async restart (): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
cssClass: 'loader',
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.api.restart()
|
||||
this.restarted = true
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
async forgetDrive (): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
spinner: 'lines',
|
||||
cssClass: 'loader',
|
||||
})
|
||||
await loader.present()
|
||||
|
||||
try {
|
||||
await this.api.forgetDrive()
|
||||
this.forgotten = true
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
refreshPage (): void {
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
22
diagnosticUI/src/app/services/api/api.service.ts
Normal file
22
diagnosticUI/src/app/services/api/api.service.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export abstract class ApiService {
|
||||
abstract getError (): Promise<GetErrorRes>
|
||||
abstract restart (): Promise<void>
|
||||
abstract forgetDrive (): Promise<void>
|
||||
abstract getLogs (params: GetLogsReq): Promise<GetLogsRes>
|
||||
}
|
||||
|
||||
export interface GetErrorRes {
|
||||
code: number,
|
||||
message: string,
|
||||
data: { details: string }
|
||||
}
|
||||
|
||||
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 interface Log {
|
||||
timestamp: string
|
||||
message: string
|
||||
}
|
||||
39
diagnosticUI/src/app/services/api/live-api.service.ts
Normal file
39
diagnosticUI/src/app/services/api/live-api.service.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Injectable } from "@angular/core"
|
||||
import { HttpService } from "../http.service"
|
||||
import { ApiService, GetErrorRes, GetLogsReq, GetLogsRes } from "./api.service"
|
||||
|
||||
@Injectable()
|
||||
export class LiveApiService extends ApiService {
|
||||
|
||||
constructor (
|
||||
private readonly http: HttpService,
|
||||
) { super() }
|
||||
|
||||
getError (): Promise<GetErrorRes> {
|
||||
return this.http.rpcRequest<GetErrorRes>({
|
||||
method: 'diagnostic.error',
|
||||
params: { },
|
||||
})
|
||||
}
|
||||
|
||||
restart (): Promise<void> {
|
||||
return this.http.rpcRequest<void>({
|
||||
method: 'diagnostic.restart',
|
||||
params: { },
|
||||
})
|
||||
}
|
||||
|
||||
forgetDrive (): Promise<void> {
|
||||
return this.http.rpcRequest<void>({
|
||||
method: 'diagnostic.forget-disk',
|
||||
params: { },
|
||||
})
|
||||
}
|
||||
|
||||
getLogs (params: GetLogsReq): Promise<GetLogsRes> {
|
||||
return this.http.rpcRequest<GetLogsRes>({
|
||||
method: 'diagnostic.logs',
|
||||
params,
|
||||
})
|
||||
}
|
||||
}
|
||||
59
diagnosticUI/src/app/services/api/mock-api.service.ts
Normal file
59
diagnosticUI/src/app/services/api/mock-api.service.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Injectable } from "@angular/core"
|
||||
import { pauseFor } from "../../util/misc.util"
|
||||
import { ApiService, GetErrorRes, GetLogsReq, GetLogsRes, Log } from "./api.service"
|
||||
|
||||
@Injectable()
|
||||
export class MockApiService extends ApiService {
|
||||
|
||||
constructor () { super() }
|
||||
|
||||
async getError (): Promise<GetErrorRes> {
|
||||
await pauseFor(1000)
|
||||
return {
|
||||
code: 15,
|
||||
message: 'Unknown Embassy',
|
||||
data: { details: 'Some details about the error here' }
|
||||
}
|
||||
}
|
||||
|
||||
async restart (): Promise<void> {
|
||||
await pauseFor(1000)
|
||||
return null
|
||||
}
|
||||
|
||||
async forgetDrive (): Promise<void> {
|
||||
await pauseFor(1000)
|
||||
return null
|
||||
}
|
||||
|
||||
async getLogs (params: GetLogsReq): Promise<GetLogsRes> {
|
||||
await pauseFor(1000)
|
||||
let entries: Log[]
|
||||
if (Math.random() < .2) {
|
||||
entries = packageLogs
|
||||
} else {
|
||||
const arrLength = params.limit ? Math.ceil(params.limit / packageLogs.length) : 10
|
||||
entries = new Array(arrLength).fill(packageLogs).reduce((acc, val) => acc.concat(val), [])
|
||||
}
|
||||
return {
|
||||
entries,
|
||||
'start-cursor': 'startCursor',
|
||||
'end-cursor': 'endCursor',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const packageLogs = [
|
||||
{
|
||||
timestamp: '2019-12-26T14:20:30.872Z',
|
||||
message: '****** START *****',
|
||||
},
|
||||
{
|
||||
timestamp: '2019-12-26T14:21:30.872Z',
|
||||
message: 'ServerLogs ServerLogs ServerLogs ServerLogs ServerLogs',
|
||||
},
|
||||
{
|
||||
timestamp: '2019-12-26T14:22:30.872Z',
|
||||
message: '****** FINISH *****',
|
||||
},
|
||||
]
|
||||
100
diagnosticUI/src/app/services/http.service.ts
Normal file
100
diagnosticUI/src/app/services/http.service.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class HttpService {
|
||||
|
||||
constructor (
|
||||
private readonly http: HttpClient,
|
||||
) { }
|
||||
|
||||
async rpcRequest<T> (options: RPCOptions): Promise<T> {
|
||||
const res = await this.httpRequest<RPCResponse<T>>(options)
|
||||
|
||||
if (isRpcError(res)) throw new RpcError(res.error)
|
||||
|
||||
if (isRpcSuccess(res)) return res.result
|
||||
}
|
||||
|
||||
async httpRequest<T> (body: RPCOptions): Promise<T> {
|
||||
const url = `${window.location.protocol}//${window.location.hostname}:${window.location.port}/rpc/v1`
|
||||
return this.http.post(url, body)
|
||||
.toPromise()
|
||||
.then((res: any) => res.body)
|
||||
.catch(e => { throw new HttpError(e) })
|
||||
}
|
||||
}
|
||||
|
||||
function RpcError (e: RPCError['error']): void {
|
||||
const { code, message, data } = e
|
||||
|
||||
this.code = code
|
||||
this.message = message
|
||||
|
||||
if (typeof data === 'string') {
|
||||
this.details = e.data
|
||||
this.revision = null
|
||||
} else {
|
||||
this.details = data.details
|
||||
}
|
||||
}
|
||||
|
||||
function HttpError (e: HttpErrorResponse): void {
|
||||
const { status, statusText } = e
|
||||
|
||||
this.code = status
|
||||
this.message = statusText
|
||||
this.details = null
|
||||
this.revision = null
|
||||
}
|
||||
|
||||
function isRpcError<Error, Result> (arg: { error: Error } | { result: Result}): arg is { error: Error } {
|
||||
return !!(arg as any).error
|
||||
}
|
||||
|
||||
function isRpcSuccess<Error, Result> (arg: { error: Error } | { result: Result}): arg is { result: Result } {
|
||||
return !!(arg as any).result
|
||||
}
|
||||
|
||||
export interface RPCOptions {
|
||||
method: string
|
||||
params: { [param: string]: Params }
|
||||
}
|
||||
|
||||
export interface RequestError {
|
||||
code: number
|
||||
message: string
|
||||
details: string
|
||||
}
|
||||
|
||||
export type Params = string | number | boolean | object | string[] | number[]
|
||||
|
||||
interface RPCBase {
|
||||
jsonrpc: '2.0'
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface RPCRequest<T> extends RPCBase {
|
||||
method: string
|
||||
params?: T
|
||||
}
|
||||
|
||||
export interface RPCSuccess<T> extends RPCBase {
|
||||
result: T
|
||||
}
|
||||
|
||||
export interface RPCError extends RPCBase {
|
||||
error: {
|
||||
code: number,
|
||||
message: string
|
||||
data?: {
|
||||
details: string
|
||||
} | string
|
||||
}
|
||||
}
|
||||
|
||||
export type RPCResponse<T> = RPCSuccess<T> | RPCError
|
||||
|
||||
type HttpError = HttpErrorResponse & { error: { code: string, message: string } }
|
||||
3
diagnosticUI/src/app/util/misc.util.ts
Normal file
3
diagnosticUI/src/app/util/misc.util.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function pauseFor (ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
@@ -24,3 +24,8 @@
|
||||
@import "~@ionic/angular/css/text-alignment.css";
|
||||
@import "~@ionic/angular/css/text-transformation.css";
|
||||
@import "~@ionic/angular/css/flex-utils.css";
|
||||
|
||||
.loader {
|
||||
--spinner-color: var(--ion-color-warning) !important;
|
||||
z-index: 40000 !important;
|
||||
}
|
||||
Reference in New Issue
Block a user