install wizard project (#1893)

* install wizard project

* reboot endpoint

* Update frontend/projects/install-wizard/src/app/pages/home/home.page.ts

Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com>

* Update frontend/projects/install-wizard/src/app/pages/home/home.page.ts

Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com>

* Update frontend/projects/install-wizard/src/app/pages/home/home.page.ts

Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com>

* update build

* fix build

* backend portion

* increase image size

* loaded

* dont auto resize

* fix install wizard

* use localhost if still in setup mode

* fix compat

Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com>
Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
Matt Hill
2022-11-07 07:30:57 -07:00
committed by Aiden McClelland
parent bc23129759
commit a2f65de1ce
46 changed files with 2309 additions and 869 deletions

View File

@@ -0,0 +1,22 @@
import { NgModule } from '@angular/core'
import { PreloadAllModules, RouterModule, Routes } from '@angular/router'
const routes: Routes = [
{
path: '',
loadChildren: () =>
import('./pages/home/home.module').then(m => m.HomePageModule),
},
]
@NgModule({
imports: [
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled',
preloadingStrategy: PreloadAllModules,
useHash: true,
}),
],
exports: [RouterModule],
})
export class AppRoutingModule {}

View File

@@ -0,0 +1,3 @@
<ion-app>
<ion-router-outlet></ion-router-outlet>
</ion-app>

View File

@@ -0,0 +1,10 @@
import { Component } from '@angular/core'
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss'],
})
export class AppComponent {
constructor() {}
}

View File

@@ -0,0 +1,41 @@
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
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 { RELATIVE_URL, WorkspaceConfig } from '@start9labs/shared'
const {
useMocks,
ui: { api },
} = require('../../../../config.json') as WorkspaceConfig
@NgModule({
declarations: [AppComponent],
imports: [
HttpClientModule,
BrowserModule,
IonicModule.forRoot({
mode: 'md',
}),
AppRoutingModule,
],
providers: [
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
{
provide: ApiService,
useClass: useMocks ? MockApiService : LiveApiService,
},
{
provide: RELATIVE_URL,
useValue: `/${api.url}/${api.version}`,
},
],
bootstrap: [AppComponent],
})
export class AppModule {}

View File

@@ -0,0 +1,26 @@
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { FormsModule } from '@angular/forms'
import { HomePage } from './home.page'
import { SwiperModule } from 'swiper/angular'
const routes: Routes = [
{
path: '',
component: HomePage,
},
]
@NgModule({
imports: [
CommonModule,
FormsModule,
IonicModule,
RouterModule.forChild(routes),
SwiperModule,
],
declarations: [HomePage],
})
export class HomePageModule {}

View File

@@ -0,0 +1,77 @@
<ion-content>
<ion-grid>
<ion-row>
<ion-col class="ion-text-center">
<div style="padding-bottom: 32px">
<img src="assets/img/logo.png" style="max-width: 240px" />
</div>
<ion-card color="dark">
<ion-card-header>
<ion-button
*ngIf="swiper?.activeIndex === 1"
class="back-button"
fill="clear"
color="light"
(click)="previous()"
>
<ion-icon slot="icon-only" name="arrow-back"></ion-icon>
</ion-button>
<ion-card-title>
{{ !swiper || swiper.activeIndex === 0 ? 'Select Disk' : 'Install
Type' }}
</ion-card-title>
<ion-card-subtitle *ngIf="error">
<ion-text color="danger">{{ error }}</ion-text>
</ion-card-subtitle>
</ion-card-header>
<ion-card-content class="ion-margin-bottom">
<swiper (swiper)="setSwiperInstance($event)">
<ng-template swiperSlide>
<ion-item
*ngFor="let disk of disks"
button
detail="true"
(click)="next(disk)"
>
<ion-icon
color="dark"
slot="start"
name="save-outline"
></ion-icon>
<ion-label>{{ disk.logicalname }}</ion-label>
</ion-item>
</ng-template>
<ng-template swiperSlide>
<ion-item button (click)="tryInstall(false)">
<ion-icon
color="dark"
slot="start"
name="medkit-outline"
></ion-icon>
<ion-label>
<h2><ion-text color="success">Light Install</ion-text></h2>
<p>Reinstall embassyOS but keep your existing data</p>
</ion-label>
</ion-item>
<ion-item button lines="none" (click)="tryInstall(true)">
<ion-icon
color="dark"
slot="start"
name="download-outline"
></ion-icon>
<ion-label>
<h2><ion-text color="warning">Full Install</ion-text></h2>
<p>
Install embassyOS and delete all existing data on disk
</p>
</ion-label>
</ion-item>
</ng-template>
</swiper>
</ion-card-content>
</ion-card>
</ion-col>
</ion-row>
</ion-grid>
</ion-content>

View File

@@ -0,0 +1,28 @@
/** Ionic CSS Variables overrides **/
:root {
--ion-font-family: 'Benton Sans';
}
ion-content {
--background: var(--ion-color-medium);
}
ion-grid {
padding-top: 32px;
height: 100%;
max-width: 640px;
}
.back-button {
position: absolute;
left: 16px;
top: 24px;
z-index: 1000000;
}
ion-card-title {
margin: 16px 0;
font-family: 'Montserrat';
font-size: x-large;
--color: var(--ion-color-light);
}

View File

@@ -0,0 +1,141 @@
import { Component } from '@angular/core'
import { AlertController, IonicSlides, LoadingController } from '@ionic/angular'
import { ApiService, Disk } from 'src/app/services/api/api.service'
import SwiperCore, { Swiper } from 'swiper'
SwiperCore.use([IonicSlides])
@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage {
swiper?: Swiper
disks: Disk[] = []
selectedDisk?: Disk
error = ''
constructor(
private readonly loadingCtrl: LoadingController,
private readonly api: ApiService,
private readonly alertCtrl: AlertController,
) {}
async ngOnInit() {
this.disks = await this.api.getDisks()
}
async ionViewDidEnter() {
if (this.swiper) {
this.swiper.allowTouchMove = false
}
}
setSwiperInstance(swiper: any) {
this.swiper = swiper
}
next(disk: Disk) {
this.selectedDisk = disk
this.swiper?.slideNext(500)
}
previous() {
this.swiper?.slidePrev(500)
}
async tryInstall(overwrite: boolean) {
if (!this.selectedDisk) return
const { logicalname, 'embassy-data': embassyData } = this.selectedDisk
if (embassyData && !overwrite) {
return this.install(logicalname, overwrite)
}
await this.presentAlertDanger(logicalname, embassyData)
}
private async install(logicalname: string, overwrite: boolean) {
const loader = await this.loadingCtrl.create({
message: 'Installing embassyOS...',
})
await loader.present()
try {
await this.api.install({ logicalname, overwrite })
this.presentAlertReboot()
} catch (e: any) {
this.error = e.message
} finally {
loader.dismiss()
}
}
private async presentAlertDanger(logicalname: string, embassyData: boolean) {
const message = embassyData
? 'This action COMPLETELY erases your existing Embassy data'
: `This action COMPLETELY erases the disk ${logicalname} and installs embassyOS`
const alert = await this.alertCtrl.create({
header: 'Warning',
message,
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Continue',
handler: () => {
this.install(logicalname, true)
},
},
],
cssClass: 'alert-warning-message',
})
await alert.present()
}
private async presentAlertReboot() {
const alert = await this.alertCtrl.create({
header: 'Install Success',
message: 'Reboot your device to begin using your new Emabssy',
buttons: [
{
text: 'Reboot',
handler: () => {
this.reboot()
},
},
],
cssClass: 'alert-warning-message',
})
await alert.present()
}
private async reboot() {
const loader = await this.loadingCtrl.create()
await loader.present()
try {
await this.api.reboot()
this.presentAlertComplete()
} catch (e: any) {
this.error = e.message
} finally {
loader.dismiss()
}
}
private async presentAlertComplete() {
const alert = await this.alertCtrl.create({
header: 'Rebooting',
message: 'Please wait for embassyOS to restart, then refresh this page',
buttons: ['OK'],
cssClass: 'alert-warning-message',
})
await alert.present()
}
}

View File

@@ -0,0 +1,16 @@
export abstract class ApiService {
abstract getDisks(): Promise<GetDisksRes> // install.status
abstract install(params: InstallReq): Promise<void> // install.execute
abstract reboot(): Promise<void> // install.reboot
}
export type GetDisksRes = Disk[]
export type Disk = {
logicalname: string
'embassy-data': boolean
}
export type InstallReq = {
logicalname: string
overwrite: boolean
}

View File

@@ -0,0 +1,46 @@
import { Injectable } from '@angular/core'
import {
HttpService,
isRpcError,
RpcError,
RPCOptions,
} from '@start9labs/shared'
import { ApiService, GetDisksRes, InstallReq } from './api.service'
@Injectable()
export class LiveApiService implements ApiService {
constructor(private readonly http: HttpService) {}
async getDisks(): Promise<GetDisksRes> {
return this.rpcRequest({
method: 'install.status',
params: {},
})
}
async install(params: InstallReq): Promise<void> {
return this.rpcRequest<void>({
method: 'install.execute',
params,
})
}
async reboot(): Promise<void> {
return this.rpcRequest<void>({
method: 'install.reboot',
params: {},
})
}
private async rpcRequest<T>(opts: RPCOptions): Promise<T> {
const res = await this.http.rpcRequest<T>(opts)
const rpcRes = res.body
if (isRpcError(rpcRes)) {
throw new RpcError(rpcRes.error)
}
return rpcRes.result
}
}

View File

@@ -0,0 +1,28 @@
import { Injectable } from '@angular/core'
import { pauseFor } from '@start9labs/shared'
import { ApiService, GetDisksRes, InstallReq } from './api.service'
@Injectable()
export class MockApiService implements ApiService {
async getDisks(): Promise<GetDisksRes> {
await pauseFor(500)
return [
{
logicalname: 'abcdefgh',
'embassy-data': false,
},
{
logicalname: '12345678',
'embassy-data': true,
},
]
}
async install(params: InstallReq): Promise<void> {
await pauseFor(1000)
}
async reboot(): Promise<void> {
await pauseFor(1000)
}
}

View File

@@ -0,0 +1,3 @@
export const environment = {
production: true,
}

View File

@@ -0,0 +1,16 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false,
}
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>embassyOS Install Wizard</title>
<base href="/" />
<meta name="color-scheme" content="light dark" />
<meta
name="viewport"
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" />
<link rel="icon" type="image/png" href="assets/icon/favicon.ico" />
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core'
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
import { AppModule } from './app/app.module'
import { environment } from './environments/environment'
if (environment.production) {
enableProdMode()
}
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch(err => console.error(err))

View File

@@ -0,0 +1,64 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
import './zone-flags'
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone' // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/

View File

@@ -0,0 +1,41 @@
@font-face {
font-family: 'Montserrat';
font-style: normal;
font-weight: normal;
src: url('/assets/fonts/Montserrat/Montserrat-Regular.ttf');
}
/** Ionic CSS Variables overrides **/
:root {
--ion-font-family: 'Montserrat';
--ion-color-primary: #0075e1;
--ion-color-medium: #989aa2;
--ion-color-medium-rgb: 152,154,162;
--ion-color-medium-contrast: #000000;
--ion-color-medium-contrast-rgb: 0,0,0;
--ion-color-medium-shade: #86888f;
--ion-color-medium-tint: #a2a4ab;
--ion-color-light: #222428;
--ion-color-light-rgb: 34,36,40;
--ion-color-light-contrast: #ffffff;
--ion-color-light-contrast-rgb: 255,255,255;
--ion-color-light-shade: #1e2023;
--ion-color-light-tint: #383a3e;
--ion-item-background: #2b2b2b;
--ion-toolbar-background: #2b2b2b;
--ion-card-background: #2b2b2b;
--ion-background-color: #282828;
--ion-background-color-rgb: 30,30,30;
--ion-text-color: var(--ion-color-dark);
--ion-text-color-rgb: var(--ion-color-dark-rgb);
}
.loader {
--spinner-color: var(--ion-color-warning) !important;
z-index: 40000 !important;
}

View File

@@ -0,0 +1,6 @@
/**
* Prevents Angular change detection from
* running with certain Web Component callbacks
*/
// eslint-disable-next-line no-underscore-dangle
(window as any).__Zone_disable_customElements = true

View File

@@ -0,0 +1,9 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": "./"
},
"files": ["src/main.ts", "src/polyfills.ts"],
"include": ["src/**/*.d.ts"]
}