fixing things up

This commit is contained in:
Matt Hill
2021-09-14 13:38:45 -06:00
committed by Matt Hill
parent bcaa6095bc
commit ae458f1220
31 changed files with 1654 additions and 1749 deletions

View File

@@ -30,3 +30,5 @@ npm-debug.log*
/platforms /platforms
/plugins /plugins
/www /www
config.json

25
setup-wizard/README.md Normal file
View File

@@ -0,0 +1,25 @@
# Embassy Setup Wizard
## Instructions for running locally
**Make sure you have git, node, and npm installed**
Install Ionic
`npm i -g @ionic/cli`
Clone this repository
`git clone https://github.com/Start9Labs/embassy-os.git`
`cd embassy-os/setup-wizard`
Install dependencies
`npm i`
Copy `config-sample.json` to new file `config.json`
Start the server
`ionic serve`

View File

@@ -0,0 +1,3 @@
{
"useMocks": true
}

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,7 @@
"@angular/platform-browser": "^12.2.1", "@angular/platform-browser": "^12.2.1",
"@angular/platform-browser-dynamic": "^12.2.1", "@angular/platform-browser-dynamic": "^12.2.1",
"@angular/router": "^12.2.1", "@angular/router": "^12.2.1",
"@ionic/angular": "^5.6.13", "@ionic/angular": "^5.7.0",
"rxjs": "^6.6.7", "rxjs": "^6.6.7",
"tslib": "^2.3.1", "tslib": "^2.3.1",
"zone.js": "^0.11.4" "zone.js": "^0.11.4"
@@ -34,7 +34,7 @@
"@angular/compiler-cli": "^12.2.1", "@angular/compiler-cli": "^12.2.1",
"@angular/language-service": "^12.2.1", "@angular/language-service": "^12.2.1",
"@ionic/angular-toolkit": "^4.0.0", "@ionic/angular-toolkit": "^4.0.0",
"@types/node": "^16.6.0", "@types/node": "^16.9.1",
"@typescript-eslint/eslint-plugin": "^4.29.1", "@typescript-eslint/eslint-plugin": "^4.29.1",
"@typescript-eslint/parser": "^4.29.1", "@typescript-eslint/parser": "^4.29.1",
"eslint": "^7.32.0", "eslint": "^7.32.0",

View File

@@ -1,17 +1,15 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser'
import { RouteReuseStrategy } from '@angular/router'; import { RouteReuseStrategy } from '@angular/router'
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http'
import { ApiService } from './services/api/api.service' import { ApiService } from './services/api/api.service'
import { MockApiService } from './services/api/mock-api.service' import { MockApiService } from './services/api/mock-api.service'
import { LiveApiService } from './services/api/live-api.service' import { LiveApiService } from './services/api/live-api.service'
import { HttpService } from './services/api/http.service' import { HttpService } from './services/api/http.service'
import { IonicModule, IonicRouteStrategy, iosTransitionAnimation } from '@ionic/angular'
import { IonicModule, IonicRouteStrategy, iosTransitionAnimation } from '@ionic/angular'; import { AppComponent } from './app.component'
import * as config from './config/config' import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'; const useMocks = require('../../config.json').useMocks as boolean
import { AppRoutingModule } from './app-routing.module';
@NgModule({ @NgModule({
declarations: [AppComponent], declarations: [AppComponent],
@@ -30,7 +28,7 @@ import { AppRoutingModule } from './app-routing.module';
{ {
provide: ApiService , provide: ApiService ,
useFactory: (http: HttpService) => { useFactory: (http: HttpService) => {
if(config.config.useMocks) { if (useMocks) {
return new MockApiService() return new MockApiService()
} else { } else {
return new LiveApiService(http) return new LiveApiService(http)

View File

@@ -1,7 +0,0 @@
export const config: Config = {
"useMocks": true
}
interface Config {
useMocks: boolean
}

View File

@@ -4,9 +4,8 @@ import { IonicModule } from '@ionic/angular';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { EmbassyPage } from './embassy.page'; import { EmbassyPage } from './embassy.page';
import { PasswordPageModule } from '../password/password.module'; import { PasswordPageModule } from '../password/password.module';
import { EmbassyPageRoutingModule } from './embassy-routing.module'; import { EmbassyPageRoutingModule } from './embassy-routing.module';
import { PipesModule } from 'src/app/pipes/pipe.module'
@NgModule({ @NgModule({
imports: [ imports: [
@@ -15,6 +14,7 @@ import { EmbassyPageRoutingModule } from './embassy-routing.module';
IonicModule, IonicModule,
EmbassyPageRoutingModule, EmbassyPageRoutingModule,
PasswordPageModule, PasswordPageModule,
PipesModule,
], ],
declarations: [EmbassyPage] declarations: [EmbassyPage]
}) })

View File

@@ -9,19 +9,15 @@
<ion-card color="dark"> <ion-card color="dark">
<ion-card-header class="ion-text-center" style="padding-bottom: 8px;"> <ion-card-header class="ion-text-center" style="padding-bottom: 8px;">
<ion-card-title>{{ loading ? 'Loading Embassy Drives' : 'Select Embassy Drive'}}</ion-card-title> <ion-card-title>{{ loading ? 'Loading Drives' : 'Select Storage Drive'}}</ion-card-title>
<ion-card-subtitle>Select the drive where all your Embassy data will be stored.</ion-card-subtitle> <ion-card-subtitle>Select the drive where all your Embassy data will be stored.</ion-card-subtitle>
</ion-card-header> </ion-card-header>
<ion-card-content class="ion-margin"> <ion-card-content class="ion-margin">
<ng-container *ngIf="!loading && !embassyDrives.length"> <ng-container *ngIf="!loading && !storageDrives.length">
<h2 color="light">No Embassy drives found</h2> <h2 color="light">No drives found</h2>
<p color="light">Please connect an Embassy drive to your embassy and refresh the page.</p> <p color="light">Please connect an storage drive to your Embassy and refresh the page.</p>
<ion-button <ion-button style="margin-top: 25px;" (click)="window.location.reload()" color="light">
(click)="window.location.reload()"
style="text-align:center"
class="claim-button"
>
Refresh Refresh
</ion-button> </ion-button>
</ng-container> </ng-container>
@@ -39,13 +35,16 @@
</ion-label> </ion-label>
</ion-item> </ion-item>
</ng-container> </ng-container>
<ng-container *ngIf="embassyDrives.length"> <ng-container *ngIf="storageDrives.length">
<ion-item (click)="chooseDrive(drive)" class="ion-margin-bottom" button color="light" lines="none" *ngFor="let drive of embassyDrives"> <ion-item (click)="chooseDrive(drive)" class="ion-margin-bottom" button color="light" lines="none" *ngFor="let drive of storageDrives">
<ion-icon slot="start" name="save-outline"></ion-icon> <ion-icon slot="start" name="save-outline"></ion-icon>
<ion-label class="ion-text-wrap"> <ion-label class="ion-text-wrap">
<h1>{{ drive.logicalname }}</h1> <h1>{{ drive.logicalname }} - {{ drive.capacity | convertBytes }}</h1>
<h2 style="min-height: 19px;">{{ getLabel(drive) }}</h2> <h2 *ngIf="drive.vendor || drive.model">
<p> Using {{ getUsage(drive) }} of {{drive.capacity.toFixed(2)}} GiB</p> {{ drive.vendor }}
<span *ngIf="drive.vendor && drive.model"> - </span>
{{ drive.model }}
</h2>
</ion-label> </ion-label>
</ion-item> </ion-item>
</ng-container> </ng-container>

View File

@@ -7,38 +7,4 @@ ion-card-title {
font-family: 'Montserrat'; font-family: 'Montserrat';
font-size: x-large; font-size: x-large;
--color: var(--ion-color-light); --color: var(--ion-color-light);
}
// ion-item {
// --border-radius: 4px;
// --border-style: solid;
// --border-width: 1px;
// --border-color: var(--ion-color-light);
// }
.input-label {
text-align: left;
padding-bottom: 2px;
font-size: small;
color: var(--ion-color-light);
font-weight: bold;
}
.claim-button {
margin-inline-start: 0;
margin-inline-end: 0;
margin-top: 24px;
height: 48px;
--background: linear-gradient(45deg, var(--ion-color-light) 16%, var(--ion-color-medium) 150%);
}
.card-footer {
text-align: left;
--background: rgb(222, 222, 222);
border-top: solid;
border-width: 1px;
border-color: var(--ion-color-medium);
ion-item {
--border-color: var(--ion-color-medium);
}
} }

View File

@@ -1,5 +1,5 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { iosTransitionAnimation, LoadingController, ModalController, NavController } from '@ionic/angular' import { AlertController, iosTransitionAnimation, LoadingController, ModalController, NavController } from '@ionic/angular'
import { ApiService, DiskInfo } from 'src/app/services/api/api.service' import { ApiService, DiskInfo } from 'src/app/services/api/api.service'
import { StateService } from 'src/app/services/state.service' import { StateService } from 'src/app/services/state.service'
import { PasswordPage } from '../password/password.page' import { PasswordPage } from '../password/password.page'
@@ -10,7 +10,7 @@ import { PasswordPage } from '../password/password.page'
styleUrls: ['embassy.page.scss'], styleUrls: ['embassy.page.scss'],
}) })
export class EmbassyPage { export class EmbassyPage {
embassyDrives = [] storageDrives = []
selectedDrive: DiskInfo = null selectedDrive: DiskInfo = null
loading = true loading = true
window = window window = window
@@ -18,23 +18,48 @@ export class EmbassyPage {
constructor( constructor(
private readonly apiService: ApiService, private readonly apiService: ApiService,
private readonly navCtrl: NavController, private readonly navCtrl: NavController,
private modalController: ModalController, private readonly modalController: ModalController,
private stateService: StateService, private readonly alertCtrl: AlertController,
private loadingCtrl: LoadingController private readonly stateService: StateService,
) {} private readonly loadingCtrl: LoadingController,
) { }
async ngOnInit() { async ngOnInit () {
const drives = (await this.apiService.getDrives()).filter(d => !d['embassy_os']) this.storageDrives = await this.apiService.getDrives()
this.embassyDrives = (await this.apiService.getDrives()).filter(d => !d['embassy_os'])
this.loading = false this.loading = false
} }
async chooseDrive(drive: DiskInfo) { async chooseDrive (drive: DiskInfo) {
if (!!drive.partitions.find(p => p.used)) {
const alert = await this.alertCtrl.create({
header: 'Warning',
subHeader: 'Drive contains data!',
message: 'All data stored on this drive will be permanently deleted.',
buttons: [
{
role: 'cancel',
text: 'Cancel',
},
{
text: 'Continue',
handler: () => {
this.presentModalPassword(drive)
}
}
]
})
await alert.present()
} else {
this.presentModalPassword(drive)
}
}
private async presentModalPassword (drive: DiskInfo): Promise<void> {
const modal = await this.modalController.create({ const modal = await this.modalController.create({
component: PasswordPage, component: PasswordPage,
componentProps: { componentProps: {
embassyDrive: drive storageDrive: drive
} },
}) })
modal.onDidDismiss().then(async ret => { modal.onDidDismiss().then(async ret => {
if (!ret.data || !ret.data.password) return if (!ret.data || !ret.data.password) return
@@ -45,13 +70,13 @@ export class EmbassyPage {
await loader.present() await loader.present()
this.stateService.embassyDrive = drive this.stateService.storageDrive = drive
this.stateService.embassyPassword = ret.data.password this.stateService.embassyPassword = ret.data.password
try { try {
this.stateService.torAddress = (await this.stateService.setupEmbassy()).torAddress this.stateService.torAddress = (await this.stateService.setupEmbassy()).torAddress
} catch (e) { } catch (e) {
console.log(e.message) console.error(e.message)
} finally { } finally {
loader.dismiss() loader.dismiss()
if(!!this.stateService.recoveryDrive) { if(!!this.stateService.recoveryDrive) {
@@ -61,21 +86,6 @@ export class EmbassyPage {
} }
} }
}) })
await modal.present(); await modal.present()
}
getLabel(drive: DiskInfo) {
const labels = drive.partitions.map(p => p.label).filter(l => !!l)
return labels.length ? labels.join(' / ') : 'unnamed'
}
getUsage(drive: DiskInfo) {
let usage = 0
drive.partitions.forEach(par => {
if(par.used) {
usage += par.used
}
})
return usage.toFixed(2)
} }
} }

View File

@@ -13,31 +13,4 @@ ion-item {
--border-style: solid; --border-style: solid;
--border-width: 1px; --border-width: 1px;
--border-color: var(--ion-color-light); --border-color: var(--ion-color-light);
}
.input-label {
text-align: left;
padding-bottom: 2px;
font-size: small;
color: var(--ion-color-light);
font-weight: bold;
}
.claim-button {
margin-inline-start: 0;
margin-inline-end: 0;
margin-top: 24px;
height: 48px;
--background: linear-gradient(45deg, var(--ion-color-light) 16%, var(--ion-color-medium) 150%);
}
.card-footer {
text-align: left;
--background: rgb(222, 222, 222);
border-top: solid;
border-width: 1px;
border-color: var(--ion-color-medium);
ion-item {
--border-color: var(--ion-color-medium);
}
} }

View File

@@ -13,31 +13,4 @@ ion-item {
--border-style: solid; --border-style: solid;
--border-width: 1px; --border-width: 1px;
--border-color: var(--ion-color-light); --border-color: var(--ion-color-light);
}
.input-label {
text-align: left;
padding-bottom: 2px;
font-size: small;
color: var(--ion-color-light);
font-weight: bold;
}
.claim-button {
margin-inline-start: 0;
margin-inline-end: 0;
margin-top: 24px;
height: 48px;
--background: linear-gradient(45deg, var(--ion-color-light) 16%, var(--ion-color-medium) 150%);
}
.card-footer {
text-align: left;
--background: rgb(222, 222, 222);
border-top: solid;
border-width: 1px;
border-color: var(--ion-color-medium);
ion-item {
--border-color: var(--ion-color-medium);
}
} }

View File

@@ -1,25 +1,29 @@
<ion-header> <ion-header>
<ion-toolbar color="light"> <ion-toolbar color="light">
<ion-title> <ion-title>
<span *ngIf="!!recoveryDrive">Unlock Drive</span> {{ !!storageDrive ? 'Set Password' : 'Unlock Drive' }}
<span *ngIf="!recoveryDrive">Set Password</span>
</ion-title> </ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content color="light"> <ion-content color="light">
<form (ngSubmit)="!!recoveryDrive ? verifyPw() : submitPw()"> <form (ngSubmit)="!!storageDrive ? submitPw() : verifyPw()">
<div style="padding: 8px 24px;"> <div style="padding: 8px 24px;">
<p *ngIf="!!embassyDrive">Choose a password for your embassy. You will need it every time you log in. If you lose it you will be permanently locked out of your embassy.</p> <div style="padding-bottom: 16px;">
<p *ngIf="embassyDrive && getUsage(embassyDrive) > 0" style="padding-bottom: 15px;color: var(--ion-color-warning);"><b>Warning:</b> After submit, any data currently stored on <b>{{ getLabel(embassyDrive) }}</b> will be wiped.</p> <ng-container *ngIf="!!storageDrive">
<h3>Choose a password for your Embassy. Make it good. Write it down. If you lose this password, you may be permanently locked out of your Embassy.</h3>
<p *ngIf="hasData" style="color: var(--ion-color-warning);"><b>Warning:</b> data on this drive will be permanently deleted.</p>
</ng-container>
<p *ngIf="!storageDrive">Enter the password that was used to encrypt this drive.</p>
</div>
<h4 class="input-label"> <h4 class="password-input" *ngIf="!!storageDrive">
Password: Password:
</h4> </h4>
<ion-item <ion-item
color="dark" color="dark"
[class]="pwError ? 'error-border' : password && !recoveryDrive ? 'success-border' : ''" [class]="pwError ? 'error-border' : password && !!storageDrive ? 'success-border' : ''"
> >
<ion-input <ion-input
[(ngModel)]="password" [(ngModel)]="password"
@@ -34,9 +38,9 @@
<ion-icon slot="icon-only" [name]="unmasked1 ? 'eye-off-outline' : 'eye-outline'" size="small"></ion-icon> <ion-icon slot="icon-only" [name]="unmasked1 ? 'eye-off-outline' : 'eye-outline'" size="small"></ion-icon>
</ion-button> </ion-button>
</ion-item> </ion-item>
<p style="color: var(--ion-color-danger);">{{pwError}}</p> <p style="color: var(--ion-color-danger);">{{ pwError }}</p>
<ng-container *ngIf="!recoveryDrive"> <ng-container *ngIf="!!storageDrive">
<h4 class="input-label"> <h4 class="password-input">
Verify Password: Verify Password:
</h4> </h4>
@@ -63,14 +67,14 @@
<ion-footer> <ion-footer>
<ion-toolbar color="light"> <ion-toolbar color="light">
<ion-buttons slot="end" class="ion-padding-end"> <!-- <ion-buttons slot="end" class="ion-padding-end"> -->
<ion-button color="dark" class="claim-button" (click)="cancel()"> <ion-button class="ion-padding-end" slot="end" color="dark" fill="clear" (click)="cancel()">
Cancel Cancel
</ion-button> </ion-button>
<ion-button color="dark" class="claim-button" (click)="!!recoveryDrive ? verifyPw() : submitPw()"> <ion-button class="ion-padding-end" slot="end" color="dark" fill="clear" strong="true" (click)="!!storageDrive ? submitPw() : verifyPw()">
{{ !!recoveryDrive ? 'Unlock' : 'Submit' }} {{ !!storageDrive ? 'Finish' : 'Unlock' }}
</ion-button> </ion-button>
</ion-buttons> <!-- </ion-buttons> -->
</ion-toolbar> </ion-toolbar>
</ion-footer> </ion-footer>

View File

@@ -1,13 +1,5 @@
.claim-button { .password-input {
margin-inline-start: 0;
margin-inline-end: 0;
margin: 6px;
height: 48px;
}
.input-label {
color: var(--ion-color-dark); color: var(--ion-color-dark);
// padding-top: 10px;
margin-bottom: 6px; margin-bottom: 6px;
font-size: medium; font-size: medium;
font-weight: 500; font-weight: 500;

View File

@@ -1,7 +1,6 @@
import { Component, Input } from '@angular/core' import { Component, Input } from '@angular/core'
import { LoadingController, ModalController } from '@ionic/angular' import { LoadingController, ModalController } from '@ionic/angular'
import { ApiService, DiskInfo } from 'src/app/services/api/api.service' import { ApiService, DiskInfo } from 'src/app/services/api/api.service'
import { StateService } from 'src/app/services/state.service'
@Component({ @Component({
selector: 'app-password', selector: 'app-password',
@@ -10,7 +9,7 @@ import { StateService } from 'src/app/services/state.service'
}) })
export class PasswordPage { export class PasswordPage {
@Input() recoveryDrive: DiskInfo @Input() recoveryDrive: DiskInfo
@Input() embassyDrive: DiskInfo @Input() storageDrive: DiskInfo
pwError = '' pwError = ''
password = '' password = ''
@@ -20,18 +19,22 @@ export class PasswordPage {
passwordVer = '' passwordVer = ''
unmasked2 = false unmasked2 = false
hasData: boolean
constructor( constructor(
private modalController: ModalController, private modalController: ModalController,
private apiService: ApiService, private apiService: ApiService,
private loadingCtrl: LoadingController, private loadingCtrl: LoadingController,
private stateService: StateService
) {} ) {}
ngOnInit() { } ngOnInit() {
if (this.storageDrive && this.storageDrive.partitions.find(p => p.used)) {
this.hasData = true
}
}
async verifyPw () { async verifyPw () {
if (!this.recoveryDrive) this.pwError = 'No recovery drive' // unreachable
if(!this.recoveryDrive) this.pwError = 'No recovery drive' // unreachable
const loader = await this.loadingCtrl.create({ const loader = await this.loadingCtrl.create({
message: 'Verifying Password' message: 'Verifying Password'
}) })
@@ -79,23 +82,7 @@ export class PasswordPage {
this.verError = this.password !== this.passwordVer ? "*passwords do not match" : '' this.verError = this.password !== this.passwordVer ? "*passwords do not match" : ''
} }
cancel () { cancel () {
this.modalController.dismiss() this.modalController.dismiss()
} }
getLabel(drive: DiskInfo) {
const labels = drive.partitions.map(p => p.label).filter(l => !!l)
return labels.length ? labels.join(' / ') : 'unnamed'
}
getUsage(drive: DiskInfo) {
let usage = 0
drive.partitions.forEach(par => {
if(par.used) {
usage += par.used
}
})
return usage
}
} }

View File

@@ -24,7 +24,7 @@
<p *ngIf="error" style="padding-top: 4px"><ion-text color="danger">*{{ error }}</ion-text></p> <p *ngIf="error" style="padding-top: 4px"><ion-text color="danger">*{{ error }}</ion-text></p>
</div> </div>
</ion-item-group> </ion-item-group>
<ion-button style="margin-top: 25px;" type="submit" color="light"> <ion-button type="submit" color="light" class="claim-button">
Submit Submit
</ion-button> </ion-button>
</form> </form>

View File

@@ -13,31 +13,4 @@ ion-item {
--border-style: solid; --border-style: solid;
--border-width: 1px; --border-width: 1px;
--border-color: var(--ion-color-light); --border-color: var(--ion-color-light);
}
.input-label {
text-align: left;
padding-bottom: 2px;
font-size: small;
color: var(--ion-color-light);
font-weight: bold;
}
.claim-button {
margin-inline-start: 0;
margin-inline-end: 0;
margin-top: 24px;
height: 48px;
--background: linear-gradient(45deg, var(--ion-color-light) 16%, var(--ion-color-medium) 150%);
}
.card-footer {
text-align: left;
--background: rgb(222, 222, 222);
border-top: solid;
border-width: 1px;
border-color: var(--ion-color-medium);
ion-item {
--border-color: var(--ion-color-medium);
}
} }

View File

@@ -4,8 +4,8 @@ import { IonicModule } from '@ionic/angular';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { RecoverPage } from './recover.page'; import { RecoverPage } from './recover.page';
import { PasswordPageModule } from '../password/password.module'; import { PasswordPageModule } from '../password/password.module';
import { RecoverPageRoutingModule } from './recover-routing.module'; import { RecoverPageRoutingModule } from './recover-routing.module';
import { PipesModule } from 'src/app/pipes/pipe.module';
@NgModule({ @NgModule({
@@ -15,6 +15,7 @@ import { RecoverPageRoutingModule } from './recover-routing.module';
IonicModule, IonicModule,
RecoverPageRoutingModule, RecoverPageRoutingModule,
PasswordPageModule, PasswordPageModule,
PipesModule,
], ],
declarations: [RecoverPage] declarations: [RecoverPage]
}) })

View File

@@ -10,13 +10,14 @@
<ion-card color="dark"> <ion-card color="dark">
<ion-card-header class="ion-text-center" style="padding-bottom: 8px;"> <ion-card-header class="ion-text-center" style="padding-bottom: 8px;">
<ion-card-title>{{ loading ? 'Loading Recovery Drives' : 'Select Recovery Drive'}}</ion-card-title> <ion-card-title>{{ loading ? 'Loading Recovery Drives' : 'Select Recovery Drive'}}</ion-card-title>
<ion-card-subtitle>Select the drive containing the Embassy you want to recover.</ion-card-subtitle>
</ion-card-header> </ion-card-header>
<ion-card-content class="ion-margin"> <ion-card-content class="ion-margin">
<ng-container *ngIf="!loading && !recoveryDrives.length"> <ng-container *ngIf="!loading && !recoveryDrives.length">
<h2 color="light">No recovery drives found</h2> <h2 color="light">No recovery drives found</h2>
<p color="light">Please connect a recovery drive to your embassy and refresh the page.</p> <p color="light">Please connect a recovery drive to your Embassy and refresh the page.</p>
<ion-button <ion-button
(click)="window.location.reload()" (click)="window.location.reload()"
style="text-align:center" style="text-align:center"
@@ -43,22 +44,27 @@
<ion-item (click)="chooseDrive(drive)" class="ion-margin-bottom" button color="light" lines="none" *ngFor="let drive of recoveryDrives" [ngClass]="drive.logicalname === selectedDrive?.logicalname ? 'selected' : null"> <ion-item (click)="chooseDrive(drive)" class="ion-margin-bottom" button color="light" lines="none" *ngFor="let drive of recoveryDrives" [ngClass]="drive.logicalname === selectedDrive?.logicalname ? 'selected' : null">
<ion-icon slot="start" name="save-outline"></ion-icon> <ion-icon slot="start" name="save-outline"></ion-icon>
<ion-label class="ion-text-wrap"> <ion-label class="ion-text-wrap">
<h1>{{ drive.logicalname }}</h1> <h1>{{ drive.logicalname }} - {{ drive.capacity | convertBytes }}</h1>
<h2>{{ drive.model }}</h2> <h2 *ngIf="drive.vendor || drive.model">
<p> Embassy version: {{drive['embassy_os'].version}}</p> {{ drive.vendor }}
<span *ngIf="drive.vendor && drive.model"> - </span>
{{ drive.model }}
</h2>
<h2> Embassy version: {{drive['embassy_os'].version}}</h2>
</ion-label> </ion-label>
<ion-icon *ngIf="drive['embassy_os'].version.startsWith('0.2') || passwords[drive.logicalname]" color="success" slot="end" name="lock-open-outline"></ion-icon> <ion-icon *ngIf="drive['embassy_os'].version.startsWith('0.2') || passwords[drive.logicalname]" color="success" slot="end" name="lock-open-outline"></ion-icon>
<ion-icon *ngIf="!drive['embassy_os'].version.startsWith('0.2') && !passwords[drive.logicalname]" color="danger" slot="end" name="lock-closed-outline"></ion-icon> <ion-icon *ngIf="!drive['embassy_os'].version.startsWith('0.2') && !passwords[drive.logicalname]" color="danger" slot="end" name="lock-closed-outline"></ion-icon>
</ion-item> </ion-item>
</ng-container> </ng-container>
<ion-button <ion-button
(click)="selectRecoveryDrive()" (click)="selectRecoveryDrive()"
[disabled]="!selectedDrive || (!passwords[selectedDrive.logicalname] && !selectedDrive['embassy_os'].version.startsWith('0.2'))" color="light"
class="claim-button" [disabled]="!selectedDrive || (!passwords[selectedDrive.logicalname] && !selectedDrive['embassy_os'].version.startsWith('0.2'))"
> class="claim-button"
Next >
</ion-button> Next
</ion-button>
</ion-item-group> </ion-item-group>
</ion-card-content> </ion-card-content>
</ion-card> </ion-card>

View File

@@ -16,31 +16,4 @@ ion-item {
--border-style: solid; --border-style: solid;
--border-width: 1px; --border-width: 1px;
--border-color: var(--ion-color-light); --border-color: var(--ion-color-light);
}
.input-label {
text-align: left;
padding-bottom: 2px;
font-size: small;
color: var(--ion-color-light);
font-weight: bold;
}
.claim-button {
margin-inline-start: 0;
margin-inline-end: 0;
margin-top: 24px;
height: 48px;
--background: linear-gradient(45deg, var(--ion-color-light) 16%, var(--ion-color-medium) 150%);
}
.card-footer {
text-align: left;
--background: rgb(222, 222, 222);
border-top: solid;
border-width: 1px;
border-color: var(--ion-color-medium);
ion-item {
--border-color: var(--ion-color-medium);
}
} }

View File

@@ -19,8 +19,8 @@ export class RecoverPage {
constructor( constructor(
private readonly apiService: ApiService, private readonly apiService: ApiService,
private readonly navCtrl: NavController, private readonly navCtrl: NavController,
private modalController: ModalController, private readonly modalController: ModalController,
private stateService: StateService private readonly stateService: StateService
) {} ) {}
async ngOnInit() { async ngOnInit() {
@@ -43,7 +43,8 @@ export class RecoverPage {
component: PasswordPage, component: PasswordPage,
componentProps: { componentProps: {
recoveryDrive: this.selectedDrive recoveryDrive: this.selectedDrive
} },
cssClass: 'alertlike-modal',
}) })
modal.onDidDismiss().then(async ret => { modal.onDidDismiss().then(async ret => {
if (!ret.data) { if (!ret.data) {

View File

@@ -13,7 +13,7 @@
</ion-card-header> </ion-card-header>
<div class="divider"></div> <div class="divider"></div>
<ion-card-content class="ion-margin"> <ion-card-content class="ion-margin">
<p class="input-label">Tor Address:</p> <p class="addr-label">Tor Address:</p>
<ion-item style="--border-radius: 8px 8px 0 0 !important;" color="light"> <ion-item style="--border-radius: 8px 8px 0 0 !important;" color="light">
<ion-label> <ion-label>
<p>{{ stateService.torAddress }}</p> <p>{{ stateService.torAddress }}</p>

View File

@@ -15,35 +15,16 @@ ion-item {
--border-color: var(--ion-color-light); --border-color: var(--ion-color-light);
} }
.input-label {
text-align: left;
padding-bottom: 2px;
font-size: medium;
color: var(--ion-color-light);
font-weight: bold;
}
.claim-button {
margin-inline-start: 0;
margin-inline-end: 0;
margin-top: 24px;
height: 48px;
--background: linear-gradient(45deg, var(--ion-color-light) 16%, var(--ion-color-medium) 150%);
}
.card-footer {
text-align: left;
--background: rgb(222, 222, 222);
border-top: solid;
border-width: 1px;
border-color: var(--ion-color-medium);
ion-item {
--border-color: var(--ion-color-medium);
}
}
.divider { .divider {
margin: 0; margin: 0;
background: linear-gradient(90deg,var(--ion-color-dark) 0,var(--ion-color-medium) 50%,var(--ion-color-dark) 100%); background: linear-gradient(90deg,var(--ion-color-dark) 0,var(--ion-color-medium) 50%,var(--ion-color-dark) 100%);
height: 1px; height: 1px;
}
.addr-label {
text-align: left;
padding-bottom: 2px;
font-size: small;
color: var(--ion-color-light);
font-weight: bold;
} }

View File

@@ -0,0 +1,18 @@
import { Pipe, PipeTransform } from '@angular/core'
// converts bytes to gigabytes
@Pipe({
name: 'convertBytes',
})
export class ConvertBytesPipe implements PipeTransform {
transform (bytes: number): string {
if (bytes === 0) return '0 Bytes'
const k = 1024
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
}
}

View File

@@ -0,0 +1,10 @@
import { NgModule } from '@angular/core';
import { ConvertBytesPipe } from './convert-bytes.pipe';
@NgModule({
declarations: [ConvertBytesPipe],
imports: [],
exports: [ConvertBytesPipe],
})
export class PipesModule {}

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { pauseFor } from '../state.service' import { pauseFor } from '../state.service'
import { ApiService, DiskInfo } from './api.service' import { ApiService } from './api.service'
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@@ -30,69 +30,75 @@ export class MockApiService extends ApiService {
async getDrives() { async getDrives() {
return [ return [
{ {
vendor: 'vendor', vendor: 'Vendor',
model: 'model', model: 'Model',
logicalname: 'Name1', logicalname: '/dev/sda',
partitions: [{ partitions: [
logicalname: 'Name1', {
logicalname: 'sda1',
label: 'label 1', label: 'label 1',
capacity: 100000, capacity: 100000,
used: 200.1255312 used: 200.1255312
}, { },
logicalname: 'Name1', {
label: 'label 2', logicalname: 'sda2',
capacity: 50000, label: 'label 2',
used: 200.1255312 capacity: 50000,
}], used: 200.1255312
}
],
capacity: 150000, capacity: 150000,
'embassy_os': null 'embassy_os': null
}, },
{ {
vendor: 'vendor', vendor: 'Vendor',
model: 'model', model: 'Model',
logicalname: 'Name2', logicalname: 'dev/sdb',
partitions: [{ partitions: [
logicalname: 'Name2', // {
label: null, // logicalname: 'sdb1',
capacity: 1600.01234, // label: null,
used: 0.00, // capacity: 1600.01234,
}], // used: 0.00,
// }
],
capacity: 1600.01234, capacity: 1600.01234,
'embassy_os': null 'embassy_os': null
}, },
{ {
vendor: 'vendor', vendor: 'Vendor',
model: 'model', model: 'Model',
logicalname: 'Name3', logicalname: 'dev/sdc',
partitions: [{ partitions: [
logicalname: 'Name3', {
logicalname: 'sdc1',
label: 'label 1', label: 'label 1',
capacity: null, capacity: null,
used: null used: null
}], }
],
capacity: 100000, capacity: 100000,
'embassy_os': { 'embassy_os': {
version: '0.3.3', version: '0.3.3',
} }
}, },
{ {
vendor: 'vendor', vendor: 'Vendor',
model: 'model', model: 'Model',
logicalname: 'Name4', logicalname: '/dev/sdd',
partitions: [{ partitions: [
logicalname: 'Name4', {
logicalname: 'sdd1',
label: null, label: null,
capacity: 10000, capacity: 10000,
used: null used: null
}], }
],
capacity: 10000, capacity: 10000,
'embassy_os': { 'embassy_os': {
version: '0.2.7', version: '0.2.7',
} }
} }
] ]
} }

View File

@@ -8,7 +8,7 @@ import { ApiService, DiskInfo } from './api/api.service'
export class StateService { export class StateService {
polling = false polling = false
embassyDrive: DiskInfo; storageDrive: DiskInfo;
embassyPassword: string embassyPassword: string
recoveryDrive: DiskInfo; recoveryDrive: DiskInfo;
recoveryPassword: string recoveryPassword: string
@@ -45,7 +45,7 @@ export class StateService {
async setupEmbassy () : Promise<{ torAddress: string }> { async setupEmbassy () : Promise<{ torAddress: string }> {
const ret = await this.apiService.setupEmbassy({ const ret = await this.apiService.setupEmbassy({
'embassy-logicalname': this.embassyDrive.logicalname, 'embassy-logicalname': this.storageDrive.logicalname,
'embassy-password': this.embassyPassword, 'embassy-password': this.embassyPassword,
'recovery-logicalname': this.recoveryDrive?.logicalname, 'recovery-logicalname': this.recoveryDrive?.logicalname,
'recovery-password': this.recoveryPassword 'recovery-password': this.recoveryPassword

View File

@@ -25,65 +25,39 @@
@import "~@ionic/angular/css/text-transformation.css"; @import "~@ionic/angular/css/text-transformation.css";
@import "~@ionic/angular/css/flex-utils.css"; @import "~@ionic/angular/css/flex-utils.css";
.input-label {
text-align: left;
padding-bottom: 2px;
font-size: small;
color: var(--ion-color-light);
font-weight: bold;
}
.alert-error .alert-title {
color: var(--ion-color-danger) !important;
}
.loader {
--spinner-color: var(--ion-color-light) !important;
}
ion-avatar { ion-avatar {
width: 27px; width: 27px;
height: 27px; height: 27px;
} }
ion-action-sheet {
--backdrop-opacity: 0.75 !important;
}
ion-alert {
--backdrop-opacity: 0.75 !important;
}
ion-loading {
--backdrop-opacity: 0.75 !important;
}
ion-item { ion-item {
--highlight-color-valid: transparent; --highlight-color-valid: transparent;
--highlight-color-invalid: transparent; --highlight-color-invalid: transparent;
--highlight-color-focused: var(--ion-color-light); --highlight-color-focused: var(--ion-color-light);
} }
.divider { .claim-button {
margin: 50px 0; margin-inline-start: 0;
height: 2px; margin-inline-end: 0;
background: linear-gradient(90deg,rgba(255,255,255,0) 0,rgba(255, 255, 255, 0.31) 50%,rgba(255,255,255,0) 100%); margin-top: 24px;
min-width: 140px;
} }
.footer { .alertlike-modal {
margin-bottom: 24px; .modal-wrapper {
ion-icon { max-height: 380px !important;
font-size: 28px; top: 25% !important;
margin-right: 10px; width: 90% !important;
left: 5% !important;
--box-shadow: none !important;
} }
} }
.success-alert .alert-wrapper { @media (min-width:1000px) {
box-shadow: 4px 4px 30px var(--ion-color-light); .alertlike-modal {
border: 1px solid rgba(255,255,255,.3); .modal-wrapper {
background: var(--ion-color-light); width: 40% !important;
} left: 30% !important;
}
.success-alert .alert-title { }
color: var(--ion-color-success);
} }

View File

@@ -3,9 +3,6 @@
/** Ionic CSS Variables **/ /** Ionic CSS Variables **/
:root { :root {
--ion-text-color: var(--ion-color-dark);
--ion-text-color-rgb: var(--ion-color-dark-rgb);
--ion-font-family: 'Benton Sans'; --ion-font-family: 'Benton Sans';
/** primary **/ /** primary **/
--ion-color-primary: #428cff; --ion-color-primary: #428cff;

View File

@@ -4,7 +4,6 @@
"compilerOptions": { "compilerOptions": {
"baseUrl": "./", "baseUrl": "./",
"outDir": "./out-tsc/app", "outDir": "./out-tsc/app",
"types": [],
"sourceMap": true, "sourceMap": true,
"declaration": false, "declaration": false,
"downlevelIteration": true, "downlevelIteration": true,
@@ -16,11 +15,18 @@
"lib": ["es2018", "dom"] "lib": ["es2018", "dom"]
}, },
"angularCompilerOptions": { "angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true, "strictInjectionParameters": true,
"enableI18nLegacyMessageIdFormat": false,
"strictInputAccessModifiers": true, "strictInputAccessModifiers": true,
"strictTemplates": true "strictTemplates": true
}, },
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2018",
"dom"
],
"files": [ "files": [
"src/main.ts", "src/main.ts",
"src/polyfills.ts" "src/polyfills.ts"