mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
rework success page, html download, and more
This commit is contained in:
committed by
Aiden McClelland
parent
5eca577152
commit
c65f019ffc
@@ -2,6 +2,10 @@ import { NgModule } from '@angular/core'
|
||||
import { PreloadAllModules, RouterModule, Routes } from '@angular/router'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'init',
|
||||
loadChildren: () => import('./pages/init/init.module').then( m => m.InitPageModule),
|
||||
},
|
||||
{
|
||||
path: 'product-key',
|
||||
loadChildren: () => import('./pages/product-key/product-key.module').then( m => m.ProductKeyPageModule),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<ion-content color="light">
|
||||
<ion-content>
|
||||
<ion-grid style="padding-top: 32px; height: 100%; max-width: 540px;">
|
||||
<ion-row style="height: 100%;">
|
||||
<ion-col class="ion-text-center">
|
||||
@@ -15,8 +15,8 @@
|
||||
|
||||
<ion-card-content class="ion-margin">
|
||||
<ng-container *ngIf="!loading && !storageDrives.length">
|
||||
<h2 color="light">No drives found</h2>
|
||||
<p color="light">Please connect an storage drive to your Embassy and refresh the page.</p>
|
||||
<h2>No drives found</h2>
|
||||
<p>Please connect an storage drive to your Embassy and refresh the page.</p>
|
||||
<ion-button style="margin-top: 25px;" (click)="refresh()" color="light">
|
||||
Refresh
|
||||
</ion-button>
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<ion-item-group>
|
||||
<ng-container *ngIf="loading">
|
||||
<ion-item button color="light" lines="none">
|
||||
<ion-item button lines="none">
|
||||
<ion-avatar slot="start">
|
||||
<ion-skeleton-text animated></ion-skeleton-text>
|
||||
</ion-avatar>
|
||||
@@ -35,15 +35,11 @@
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="storageDrives.length">
|
||||
<ion-item (click)="chooseDrive(drive)" class="ion-margin-bottom" button color="light" lines="none" *ngFor="let drive of storageDrives">
|
||||
<ion-item (click)="chooseDrive(drive)" class="ion-margin-bottom" button lines="none" *ngFor="let drive of storageDrives">
|
||||
<ion-icon slot="start" name="save-outline"></ion-icon>
|
||||
<ion-label class="ion-text-wrap">
|
||||
<h1>{{ drive.logicalname }} - {{ drive.capacity | convertBytes }}</h1>
|
||||
<h2 *ngIf="drive.vendor || drive.model">
|
||||
{{ drive.vendor }}
|
||||
<span *ngIf="drive.vendor && drive.model"> - </span>
|
||||
{{ drive.model }}
|
||||
</h2>
|
||||
<h1>{{ drive.vendor || 'Unknown Vendor' }} - {{ drive.model || 'Unknown Model' }}</h1>
|
||||
<h2>{{ drive.logicalname }} - {{ drive.capacity | convertBytes }}</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
.selected {
|
||||
border: 4px solid gray;
|
||||
}
|
||||
|
||||
ion-card-title {
|
||||
margin: 24px 0;
|
||||
font-family: 'Montserrat';
|
||||
font-size: x-large;
|
||||
--color: var(--ion-color-light);
|
||||
}
|
||||
@@ -82,7 +82,7 @@ export class EmbassyPage {
|
||||
if (!ret.data || !ret.data.password) return
|
||||
|
||||
const loader = await this.loadingCtrl.create({
|
||||
message: 'Setting up your Embassy!',
|
||||
message: 'Transferring encrypted data',
|
||||
})
|
||||
|
||||
await loader.present()
|
||||
@@ -99,9 +99,9 @@ export class EmbassyPage {
|
||||
} finally {
|
||||
loader.dismiss()
|
||||
if (!!this.stateService.recoveryPartition) {
|
||||
await this.navCtrl.navigateForward(`/loading`, { animationDirection: 'forward' })
|
||||
await this.navCtrl.navigateForward(`/loading`)
|
||||
} else {
|
||||
await this.navCtrl.navigateForward(`/success`, { animationDirection: 'forward' })
|
||||
await this.navCtrl.navigateForward(`/init`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<ion-content color="light">
|
||||
<ion-content>
|
||||
<ion-grid style="padding-top: 32px; height: 100%; max-width: 540px;">
|
||||
<ion-row style="height: 100%;">
|
||||
<ion-col class="ion-text-center">
|
||||
@@ -12,7 +12,6 @@
|
||||
<!-- fresh -->
|
||||
<ion-card
|
||||
routerLink="/embassy"
|
||||
button="true"
|
||||
color="light"
|
||||
style="text-align: center; background-color: #00919b !important; height: 160px; margin-bottom: 20px; box-shadow: 4px 4px 16px var(--ion-color-light);"
|
||||
>
|
||||
@@ -24,8 +23,7 @@
|
||||
<!-- recover -->
|
||||
</ion-card>
|
||||
<ion-card
|
||||
routerLink="/recover"
|
||||
button="true"
|
||||
routerLink="/recover"
|
||||
color="light"
|
||||
style="text-align: center; background-color: #bf5900 !important; height: 160px; box-shadow: 4px 4px 16px var(--ion-color-light);"
|
||||
>
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
.selected {
|
||||
border: 4px solid gray;
|
||||
}
|
||||
ion-card-title {
|
||||
margin: 24px 0;
|
||||
font-family: 'Montserrat';
|
||||
font-size: x-large;
|
||||
--color: var(--ion-color-light);
|
||||
}
|
||||
|
||||
ion-item {
|
||||
--border-radius: 4px;
|
||||
--border-style: solid;
|
||||
--border-width: 1px;
|
||||
--border-color: var(--ion-color-light);
|
||||
}
|
||||
16
setup-wizard/src/app/pages/init/init-routing.module.ts
Normal file
16
setup-wizard/src/app/pages/init/init-routing.module.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { InitPage } from './init.page'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: InitPage,
|
||||
},
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class InitPageRoutingModule { }
|
||||
15
setup-wizard/src/app/pages/init/init.module.ts
Normal file
15
setup-wizard/src/app/pages/init/init.module.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { InitPage } from './init.page'
|
||||
import { InitPageRoutingModule } from './init-routing.module'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
InitPageRoutingModule,
|
||||
],
|
||||
declarations: [InitPage],
|
||||
})
|
||||
export class InitPageModule { }
|
||||
24
setup-wizard/src/app/pages/init/init.page.html
Normal file
24
setup-wizard/src/app/pages/init/init.page.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<ion-content>
|
||||
<ion-grid style="padding-top: 32px; height: 100%; max-width: 540px;">
|
||||
<ion-row style="height: 100%;">
|
||||
<ion-col class="ion-text-center">
|
||||
|
||||
<div style="padding-bottom: 32px;">
|
||||
<img src="assets/png/logo.png" style="max-width: 240px;" />
|
||||
</div>
|
||||
|
||||
<ion-card color="dark">
|
||||
<ion-card-header>
|
||||
<ion-card-title style="font-size: 40px;">Initializing Embassy</ion-card-title>
|
||||
<ion-card-subtitle>Progress: {{ progress | async }}%</ion-card-subtitle>
|
||||
</ion-card-header>
|
||||
|
||||
<ion-card-content class="ion-margin">
|
||||
<ion-progress-bar color="primary" style="max-width: 700px; margin: auto; padding-bottom: 20px; margin-bottom: 40px;" [value]="(progress | async) / 100"></ion-progress-bar>
|
||||
<p class="ion-text-start">After completion, you will be prompted to download a file from your Embassy. Save the file somewhere safe, it is the easiest way to recover your Embassy's addresses and SSL certificate in case you lose them.</p>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ion-content>
|
||||
0
setup-wizard/src/app/pages/init/init.page.scss
Normal file
0
setup-wizard/src/app/pages/init/init.page.scss
Normal file
31
setup-wizard/src/app/pages/init/init.page.ts
Normal file
31
setup-wizard/src/app/pages/init/init.page.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { NavController } from '@ionic/angular'
|
||||
import { interval, Observable } from 'rxjs'
|
||||
import { finalize, skip, take } from 'rxjs/operators'
|
||||
import { pauseFor } from 'src/app/util/misc.util'
|
||||
|
||||
@Component({
|
||||
selector: 'app-init',
|
||||
templateUrl: 'init.page.html',
|
||||
styleUrls: ['init.page.scss'],
|
||||
})
|
||||
export class InitPage {
|
||||
progress: Observable<number>
|
||||
|
||||
constructor (
|
||||
private navCtrl: NavController,
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
this.progress = interval(500)
|
||||
.pipe(
|
||||
skip(1),
|
||||
take(100),
|
||||
finalize(async () => {
|
||||
await pauseFor(1000)
|
||||
this.navCtrl.navigateForward('/success')
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { LoadingPage } from './loading.page'
|
||||
import { PasswordPageModule } from '../password/password.module'
|
||||
import { LoadingPageRoutingModule } from './loading-routing.module'
|
||||
|
||||
@NgModule({
|
||||
@@ -12,7 +11,6 @@ import { LoadingPageRoutingModule } from './loading-routing.module'
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
LoadingPageRoutingModule,
|
||||
PasswordPageModule,
|
||||
],
|
||||
declarations: [LoadingPage],
|
||||
})
|
||||
|
||||
@@ -10,12 +10,11 @@
|
||||
<ion-card color="dark">
|
||||
<ion-card-header>
|
||||
<ion-card-title style="font-size: 40px;">Recovering From Backup</ion-card-title>
|
||||
<ion-card-subtitle>Progress: {{(stateService.dataProgress * 100).toFixed(0) }}%</ion-card-subtitle>
|
||||
<ion-card-subtitle>Progress: {{ (stateService.dataProgress * 100).toFixed(0) }}%</ion-card-subtitle>
|
||||
</ion-card-header>
|
||||
|
||||
<ion-card-content class="ion-margin">
|
||||
<ion-progress-bar color="primary" style="max-width: 700px; margin: auto; padding-bottom: 20px; margin-bottom: 40px;" value="{{stateService.dataProgress}}"></ion-progress-bar>
|
||||
|
||||
<ion-progress-bar color="primary" style="max-width: 700px; margin: auto; padding-bottom: 20px; margin-bottom: 40px;" [value]="stateService.dataProgress"></ion-progress-bar>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</ion-col>
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
.selected {
|
||||
border: 4px solid gray;
|
||||
}
|
||||
ion-card-title {
|
||||
margin: 24px 0;
|
||||
font-family: 'Montserrat';
|
||||
font-size: x-large;
|
||||
--color: var(--ion-color-light);
|
||||
}
|
||||
|
||||
ion-item {
|
||||
--border-radius: 4px;
|
||||
--border-style: solid;
|
||||
--border-width: 1px;
|
||||
--border-color: var(--ion-color-light);
|
||||
}
|
||||
@@ -1,26 +1,25 @@
|
||||
<ion-header>
|
||||
<ion-toolbar color="light">
|
||||
<ion-toolbar>
|
||||
<ion-title>
|
||||
{{ !!storageDrive ? 'Set Password' : 'Unlock Drive' }}
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content color="light">
|
||||
<ion-content color="medium">
|
||||
<form (ngSubmit)="!!storageDrive ? submitPw() : verifyPw()">
|
||||
<div style="padding: 8px 24px;">
|
||||
<div style="padding-bottom: 16px;">
|
||||
<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>
|
||||
<p>Choose a password for your Embassy. <i>Make it good. Write it down.</i></p>
|
||||
<p style="color: var(--ion-color-warning);">Losing your password can result in total loss of data.</p>
|
||||
</ng-container>
|
||||
<p *ngIf="!storageDrive">Enter the password that was used to encrypt this drive.</p>
|
||||
</div>
|
||||
|
||||
<h4 class="password-input" *ngIf="!!storageDrive">
|
||||
<p class="input-label">
|
||||
Password:
|
||||
</h4>
|
||||
|
||||
</p>
|
||||
<ion-item
|
||||
color="dark"
|
||||
[class]="pwError ? 'error-border' : password && !!storageDrive ? 'success-border' : ''"
|
||||
@@ -37,12 +36,13 @@
|
||||
<ion-icon slot="icon-only" [name]="unmasked1 ? 'eye-off-outline' : 'eye-outline'" size="small"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
<p style="color: var(--ion-color-danger);">{{ pwError }}</p>
|
||||
<div style="height: 16px;">
|
||||
<p style="color: var(--ion-color-danger); font-size: x-small;">{{ pwError }}</p>
|
||||
</div>
|
||||
<ng-container *ngIf="!!storageDrive">
|
||||
<h4 class="password-input">
|
||||
Verify Password:
|
||||
</h4>
|
||||
|
||||
<p class="input-label">
|
||||
Confirm Password:
|
||||
</p>
|
||||
<ion-item color="dark" [class]="verError ? 'error-border' : passwordVer ? 'success-border' : ''">
|
||||
<ion-input
|
||||
[(ngModel)]="passwordVer"
|
||||
@@ -56,7 +56,9 @@
|
||||
<ion-icon slot="icon-only" [name]="unmasked2 ? 'eye-off-outline' : 'eye-outline'" size="small"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
<p style="color: var(--ion-color-danger);">{{ verError }}</p>
|
||||
<div style="height: 16px;">
|
||||
<p style="color: var(--ion-color-danger); font-size: x-small;">{{ verError }}</p>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
<input type="submit" style="display: none" />
|
||||
@@ -64,15 +66,13 @@
|
||||
</ion-content>
|
||||
|
||||
<ion-footer>
|
||||
<ion-toolbar color="light">
|
||||
<!-- <ion-buttons slot="end" class="ion-padding-end"> -->
|
||||
<ion-button class="ion-padding-end" slot="end" color="dark" fill="clear" (click)="cancel()">
|
||||
Cancel
|
||||
</ion-button>
|
||||
<ion-button class="ion-padding-end" slot="end" color="dark" fill="clear" strong="true" (click)="!!storageDrive ? submitPw() : verifyPw()">
|
||||
{{ !!storageDrive ? 'Finish' : 'Unlock' }}
|
||||
</ion-button>
|
||||
<!-- </ion-buttons> -->
|
||||
<ion-toolbar>
|
||||
<ion-button class="ion-padding-end" slot="end" color="dark" fill="clear" (click)="cancel()">
|
||||
Cancel
|
||||
</ion-button>
|
||||
<ion-button class="ion-padding-end" slot="end" color="dark" fill="clear" strong="true" (click)="!!storageDrive ? submitPw() : verifyPw()">
|
||||
{{ !!storageDrive ? 'Finish' : 'Unlock' }}
|
||||
</ion-button>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
.password-input {
|
||||
color: var(--ion-color-dark);
|
||||
margin-bottom: 6px;
|
||||
font-size: medium;
|
||||
font-weight: 500;
|
||||
* {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.error-border {
|
||||
border: 2px solid var(--ion-color-danger);
|
||||
}
|
||||
|
||||
.success-border {
|
||||
border: 2px solid var(--ion-color-success);
|
||||
}
|
||||
|
||||
ion-input {
|
||||
font-weight: 500;
|
||||
--placeholder-font-weight: 400;
|
||||
width: 100%;
|
||||
background: var(--ion-color-dark);
|
||||
border-radius: 3px;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, Input } from '@angular/core'
|
||||
import { LoadingController, ModalController } from '@ionic/angular'
|
||||
import { ApiService, DiskInfo, PartitionInfo } from 'src/app/services/api/api.service'
|
||||
import { ModalController } from '@ionic/angular'
|
||||
import { DiskInfo, PartitionInfo } from 'src/app/services/api/api.service'
|
||||
import * as argon2 from '@start9labs/argon2'
|
||||
|
||||
@Component({
|
||||
@@ -20,25 +20,15 @@ export class PasswordPage {
|
||||
passwordVer = ''
|
||||
unmasked2 = false
|
||||
|
||||
hasData: boolean
|
||||
|
||||
constructor (
|
||||
private modalController: ModalController,
|
||||
private apiService: ApiService,
|
||||
private loadingCtrl: LoadingController,
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
if (this.storageDrive && this.storageDrive.partitions.find(p => p.used)) {
|
||||
this.hasData = true
|
||||
}
|
||||
}
|
||||
|
||||
async verifyPw () {
|
||||
if (!this.recoveryPartition || !this.recoveryPartition['embassy-os']) this.pwError = 'No recovery drive' // unreachable
|
||||
|
||||
try {
|
||||
argon2.verify( this.recoveryPartition['embassy-os']['password-hash'], this.password)
|
||||
argon2.verify(this.recoveryPartition['embassy-os']['password-hash'], this.password)
|
||||
this.modalController.dismiss({ password: this.password })
|
||||
} catch (e) {
|
||||
this.pwError = 'Incorrect password provided'
|
||||
@@ -63,14 +53,14 @@ export class PasswordPage {
|
||||
}
|
||||
|
||||
if (this.password.length < 12) {
|
||||
this.pwError = '*password must be 12 characters or greater'
|
||||
this.pwError = 'Must be 12 characters or greater'
|
||||
} else {
|
||||
this.pwError = ''
|
||||
}
|
||||
}
|
||||
|
||||
checkVer () {
|
||||
this.verError = this.password !== this.passwordVer ? '*passwords do not match' : ''
|
||||
this.verError = this.password !== this.passwordVer ? 'Passwords do not match' : ''
|
||||
}
|
||||
|
||||
cancel () {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<ion-header>
|
||||
<ion-toolbar color="light">
|
||||
<ion-toolbar>
|
||||
<ion-title>
|
||||
Verify Recovery Product Key
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content color="light">
|
||||
<ion-content>
|
||||
<form (ngSubmit)="verifyProductKey()">
|
||||
<div style="padding: 8px 24px;">
|
||||
<div style="padding-bottom: 16px;">
|
||||
@@ -35,7 +35,7 @@
|
||||
</ion-content>
|
||||
|
||||
<ion-footer>
|
||||
<ion-toolbar color="light">
|
||||
<ion-toolbar>
|
||||
<ion-button class="ion-padding-end" slot="end" color="dark" fill="clear" (click)="cancel()">
|
||||
Cancel
|
||||
</ion-button>
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
.password-input {
|
||||
color: var(--ion-color-dark);
|
||||
margin-bottom: 6px;
|
||||
font-size: medium;
|
||||
font-weight: 500;
|
||||
* {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.error-border {
|
||||
border: 2px solid var(--ion-color-danger);
|
||||
}
|
||||
|
||||
.success-border {
|
||||
border: 2px solid var(--ion-color-success);
|
||||
}
|
||||
|
||||
ion-input {
|
||||
font-weight: 500;
|
||||
--placeholder-font-weight: 400;
|
||||
width: 100%;
|
||||
background: var(--ion-color-dark);
|
||||
border-radius: 3px;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<ion-content color="light">
|
||||
<ion-content>
|
||||
<ion-grid style="padding-top: 32px; height: 100%; max-width: 540px;">
|
||||
<ion-row style="height: 100%;">
|
||||
<ion-col class="ion-text-center">
|
||||
@@ -12,16 +12,16 @@
|
||||
<ion-card-title>Enter Product Key</ion-card-title>
|
||||
</ion-card-header>
|
||||
|
||||
<ion-card-content style="padding-bottom: 0px;" class="ion-margin">
|
||||
<ion-card-content class="ion-margin">
|
||||
<form (submit)="submit()" style="margin-bottom: 12px;">
|
||||
<ion-item-group>
|
||||
<ion-item>
|
||||
<ion-icon color="light" slot="start" name="key-outline" style="margin-right: 16px;"></ion-icon>
|
||||
<ion-input style="color: var(--ion-color-light)" (click)="error = ''" color="medium" maxlength="12" name="productKey" [(ngModel)]="productKey"></ion-input>
|
||||
<ion-item-group class="ion-padding-bottom">
|
||||
<p class="input-label">Product Key</p>
|
||||
<ion-item color="dark">
|
||||
<ion-icon slot="start" name="key-outline" style="margin-right: 16px;"></ion-icon>
|
||||
<ion-input color="medium" maxlength="12" name="productKey" [(ngModel)]="productKey" (ionChange)="error = ''"></ion-input>
|
||||
</ion-item>
|
||||
<div class="ion-text-left">
|
||||
|
||||
<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>
|
||||
</ion-item-group>
|
||||
<ion-button type="submit" color="light" class="claim-button">
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
.selected {
|
||||
border: 4px solid gray;
|
||||
}
|
||||
ion-card-title {
|
||||
margin: 24px 0;
|
||||
font-family: 'Montserrat';
|
||||
font-size: x-large;
|
||||
--color: var(--ion-color-light);
|
||||
}
|
||||
|
||||
ion-item {
|
||||
--border-radius: 4px;
|
||||
--border-style: solid;
|
||||
--border-width: 1px;
|
||||
--border-color: var(--ion-color-light);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<ion-content color="light">
|
||||
<ion-content>
|
||||
<ion-grid style="padding-top: 32px; height: 100%; max-width: 540px;">
|
||||
<ion-row style="height: 100%;">
|
||||
<ion-col class="ion-text-center">
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
<ion-card-content class="ion-margin">
|
||||
<ng-container *ngIf="!loading && !recoveryPartitions.length">
|
||||
<h2 color="light">No recovery drives found</h2>
|
||||
<p color="light">Please connect a recovery drive to your Embassy and refresh the page.</p>
|
||||
<h2>No recovery drives found</h2>
|
||||
<p>Please connect a recovery drive to your Embassy and refresh the page.</p>
|
||||
<ion-button
|
||||
(click)="refresh()"
|
||||
style="text-align:center"
|
||||
|
||||
@@ -1,19 +1,4 @@
|
||||
.selected {
|
||||
border-radius: 8px;
|
||||
border: 4px solid var(--ion-color-secondary);
|
||||
box-shadow: 4px 4px 16px var(--ion-color-light);
|
||||
}
|
||||
|
||||
ion-card-title {
|
||||
margin: 24px 0;
|
||||
font-family: 'Montserrat';
|
||||
font-size: x-large;
|
||||
--color: var(--ion-color-light);
|
||||
}
|
||||
|
||||
ion-item {
|
||||
--border-radius: 4px;
|
||||
--border-style: solid;
|
||||
--border-width: 1px;
|
||||
--border-color: var(--ion-color-light);
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export class RecoverPage {
|
||||
|
||||
async getPartitions () {
|
||||
try {
|
||||
let drives = (await this.apiService.getDrives())
|
||||
let drives = await this.apiService.getDrives()
|
||||
|
||||
this.recoveryPartitions = drives.map(d => d.partitions.map(p => ({ partition: p, vendor: d.vendor, model: d.model})).filter(p => p.partition['embassy-os']?.full)).flat()
|
||||
// if theres no product key, only show 0.2s
|
||||
|
||||
@@ -1,91 +1,171 @@
|
||||
<ion-content color="light">
|
||||
<ion-content>
|
||||
<ion-grid style="padding-top: 32px; height: 100%; max-width: 540px;">
|
||||
<ion-row style="height: 100%;">
|
||||
<ion-col class="ion-text-center">
|
||||
<ion-col>
|
||||
|
||||
<div style="padding-bottom: 32px;">
|
||||
<div style="padding-bottom: 32px; text-align: center;">
|
||||
<img src="assets/png/logo.png" style="max-width: 240px;" />
|
||||
</div>
|
||||
|
||||
<ion-card color="dark">
|
||||
<ion-card-header class="ion-text-center" style="padding-bottom: 8px;">
|
||||
<ion-card-title style="margin-bottom: 10px;">Setup Complete!</ion-card-title>
|
||||
<ion-card-header class="ion-text-center" color="success">
|
||||
<ion-icon style="font-size: 80px;" name="checkmark-circle-outline"></ion-icon>
|
||||
<ion-card-title>Setup Complete!</ion-card-title>
|
||||
</ion-card-header>
|
||||
<ion-icon style="font-size: 124px; color: var(--ion-color-success)" name="checkmark-circle-outline"></ion-icon>
|
||||
<ion-card-content style="padding-top: 0px;" class="ion-margin">
|
||||
<p style="padding-bottom: 10px;">Congratulations! In a few minutes, you will be able to access your Embassy via TOR or LAN.</p>
|
||||
<p class="addr-label">Tor Address:</p>
|
||||
<ion-item style="--border-radius: 8px !important;" color="medium">
|
||||
<ion-label>
|
||||
<p>{{ stateService.torAddress }}</p>
|
||||
</ion-label>
|
||||
<ion-buttons>
|
||||
<ion-button fill="clear" (click)="copy(stateService.torAddress)">
|
||||
<ion-icon size="small" slot="icon-only" name="copy-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-item>
|
||||
<p style="margin-top: 8px;">We suggest you copy and save your tor adress in a safe place. This page will no longer be available once you access your Embassy.</p>
|
||||
|
||||
<div (click)="toggleLan()">
|
||||
<ion-card-content>
|
||||
<br />
|
||||
<!-- Tor Instructions -->
|
||||
<div (click)="toggleTor()" class="toggle-label">
|
||||
<h2>Tor Instructions:</h2>
|
||||
<ion-icon
|
||||
style="float: right; font-size: 24px; margin-right: 28px;"
|
||||
name="caret-down-outline"
|
||||
name="chevron-down-outline"
|
||||
[ngStyle]="{
|
||||
'transform': lanInstructionsOpen ? 'rotate(-90deg)' : 'rotate(0deg)',
|
||||
'transform': torOpen ? 'rotate(-90deg)' : 'rotate(0deg)',
|
||||
'transition': 'transform 0.4s ease-out'
|
||||
}"
|
||||
></ion-icon>
|
||||
<p class="addr-label" style="margin: 20px 0 10px 0;">LAN Instructions (Optional):</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
[ngStyle]="{
|
||||
'overflow' : 'hidden',
|
||||
'max-height': lanInstructionsOpen ? '455px' : '0px',
|
||||
'transition': 'max-height 0.4s ease-out'
|
||||
}"
|
||||
[ngStyle]="{
|
||||
'overflow' : 'hidden',
|
||||
'max-height': torOpen ? '500px' : '0px',
|
||||
'transition': 'max-height 0.4s ease-out'
|
||||
}"
|
||||
>
|
||||
<ion-item-group>
|
||||
<!-- about -->
|
||||
<ion-item color="medium" class="ion-padding-bottom ion-padding-top">
|
||||
<h2>
|
||||
Connecting to your Embassy over LAN provides a lightning fast experience and is a reliable fallback in case Tor is having problems. To connect to your Embassy's .local address, you must:
|
||||
<ol>
|
||||
<li>Be connected to the same Local Area Network (LAN) as your Embassy.</li>
|
||||
<li>Download and trust your Embassy's SSL Certificate Authority (below).</li>
|
||||
</ol>
|
||||
View the full <a href="https://docs.start9.com/user-manual/general/lan-setup" target="_blank" rel="noreferrer">instructions</a>.
|
||||
</h2>
|
||||
</ion-item>
|
||||
|
||||
<ion-item color="medium" button (click)="installCert()">
|
||||
<ion-icon slot="start" name="download-outline" size="large"></ion-icon>
|
||||
<ion-label>
|
||||
<h1>Download Root CA</h1>
|
||||
<p>Download and trust your Embassy's Root Cert</p>
|
||||
<div class="ion-padding">
|
||||
<p>
|
||||
To use your Embassy over Tor, visit its unique Tor address from any Tor-enabled browser.
|
||||
For a list of recommended browsers, click <a href="https://docs.start9.com/user-manual/connecting.html" target="_blank" rel="noreferrer"><b>here</b></a>.
|
||||
</p>
|
||||
<br />
|
||||
<p class="input-label">Tor Address</p>
|
||||
<ion-item lines="none" color="dark">
|
||||
<ion-label class="ion-text-wrap">
|
||||
<code><ion-text color="light">{{ stateService.torAddress }}</ion-text></code>
|
||||
</ion-label>
|
||||
<ion-button color="light" fill="clear" (click)="copy(stateService.torAddress)">
|
||||
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
|
||||
<p style="padding-top: 10px;" class="addr-label">LAN Address:</p>
|
||||
|
||||
<ion-item style="--border-radius: 8px !important;" color="medium">
|
||||
<ion-label>
|
||||
<p>{{ stateService.lanAddress }}</p>
|
||||
</ion-label>
|
||||
<ion-buttons>
|
||||
<ion-button fill="clear" (click)="copy(stateService.lanAddress)">
|
||||
<ion-icon size="small" slot="icon-only" name="copy-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
</div>
|
||||
<div style="padding-bottom: 24px; border-bottom: solid 1px;"></div>
|
||||
<br />
|
||||
</div>
|
||||
|
||||
<!-- LAN Instructions -->
|
||||
<div (click)="toggleLan()" class="toggle-label">
|
||||
<h2>LAN Instructions (Slightly Advanced):</h2>
|
||||
<ion-icon
|
||||
name="chevron-down-outline"
|
||||
[ngStyle]="{
|
||||
'transform': lanOpen ? 'rotate(-90deg)' : 'rotate(0deg)',
|
||||
'transition': 'transform 0.4s ease-out'
|
||||
}"
|
||||
></ion-icon>
|
||||
</div>
|
||||
|
||||
<div
|
||||
[ngStyle]="{
|
||||
'overflow' : 'hidden',
|
||||
'max-height': lanOpen ? '500px' : '0px',
|
||||
'transition': 'max-height 0.4s ease-out'
|
||||
}"
|
||||
>
|
||||
<div class="ion-padding">
|
||||
<p>To use your Embassy locally, you must:</p>
|
||||
<ol>
|
||||
<li>Currently be connected to the same Local Area Network (LAN) as your Embassy.</li>
|
||||
<li>Download your Embassy's Root Certificate Authority.</li>
|
||||
<li>Trust your Embassy's Root CA on <i>both</i> your computer/phone and in your browser settings.</li>
|
||||
</ol>
|
||||
<p>
|
||||
For step-by-step instructions, click
|
||||
<a href="https://docs.start9.com/user-manual/general/lan-setup.html" target="_blank" rel="noreferrer"><b>here</b></a>.
|
||||
</p>
|
||||
|
||||
<ion-button style="margin-top: 24px; margin-bottom: 24px;" color="light" (click)="installCert()">
|
||||
Download Root CA
|
||||
<ion-icon slot="end" name="download-outline"></ion-icon>
|
||||
</ion-button>
|
||||
|
||||
<p class="input-label">LAN Address</p>
|
||||
<ion-item lines="none" color="dark">
|
||||
<ion-label class="ion-text-wrap">
|
||||
<code><ion-text color="light">{{ stateService.lanAddress }}</ion-text></code>
|
||||
</ion-label>
|
||||
<ion-button color="light" fill="clear" (click)="copy(stateService.lanAddress)">
|
||||
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
</div>
|
||||
<div style="padding-bottom: 24px; border-bottom: solid 1px;"></div>
|
||||
<br />
|
||||
</div>
|
||||
<div class="ion-text-center ion-padding-top">
|
||||
<ion-button color="light" fill="clear" color="primary" strong (click)="download()">
|
||||
Download this page
|
||||
<ion-icon slot="end" name="download-outline"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
<br />
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
|
||||
<a id="install-cert" href="/public/local.crt" download="Embassy Local CA.crt"></a>
|
||||
|
||||
</ion-content>
|
||||
|
||||
<!-- cert elem -->
|
||||
<a hidden id="install-cert" href="/public/local.crt" download="embassy.crt"></a>
|
||||
|
||||
<!-- download elem -->
|
||||
<div hidden id="downloadable">
|
||||
<div style="padding: 0 24px; font-family: Courier;">
|
||||
<h1>Embassy Info</h1>
|
||||
|
||||
<section style="padding: 16px; border: solid 1px;">
|
||||
<h2>Tor Info</h2>
|
||||
<p>
|
||||
To use your Embassy over Tor, visit its unique Tor address from any Tor-enabled browser.
|
||||
</p>
|
||||
<p>
|
||||
For a list of recommended browsers, click <a href="https://docs.start9.com/user-manual/connecting.html" target="_blank" rel="noreferrer"><b>here</b></a>.
|
||||
</p>
|
||||
<p><b>Tor Address: </b><code id="tor-addr"></code></p>
|
||||
</section>
|
||||
|
||||
<section style="padding: 16px; border: solid 1px; border-top: none;">
|
||||
<h2>LAN Info</h2>
|
||||
<p>To use your Embassy locally, you must:</p>
|
||||
<ol>
|
||||
<li>Currently be connected to the same Local Area Network (LAN) as your Embassy.</li>
|
||||
<li>Download your Embassy's Root Certificate Authority.</li>
|
||||
<li>Trust your Embassy's Root CA on <i>both</i> your computer/phone and in your browser settings.</li>
|
||||
</ol>
|
||||
<p>
|
||||
For step-by-step instructions, click
|
||||
<a href="https://docs.start9.com/user-manual/general/lan-setup.html" target="_blank" rel="noreferrer"><b>here</b></a>.
|
||||
</p>
|
||||
|
||||
<div style="margin: 42px 0;">
|
||||
<a
|
||||
id="cert"
|
||||
download="embassy.crt"
|
||||
style="
|
||||
background: #25272b;
|
||||
padding: 10px;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
"
|
||||
>
|
||||
Download Root CA
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p><b>LAN Address: </b><code id="lan-addr"></code></p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
.selected {
|
||||
border: 4px solid gray;
|
||||
}
|
||||
ion-card-title {
|
||||
margin: 24px 0;
|
||||
font-family: 'Montserrat';
|
||||
font-size: x-large;
|
||||
--color: var(--ion-color-light);
|
||||
}
|
||||
|
||||
ion-item {
|
||||
--border-radius: 4px;
|
||||
--border-style: solid;
|
||||
--border-width: 1px;
|
||||
--border-color: var(--ion-color-light);
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 0;
|
||||
background: linear-gradient(90deg,var(--ion-color-dark) 0,var(--ion-color-medium) 50%,var(--ion-color-dark) 100%);
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.addr-label {
|
||||
text-align: left;
|
||||
padding-bottom: 2px;
|
||||
font-size: small;
|
||||
p {
|
||||
color: var(--ion-color-light);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.toggle-label {
|
||||
padding: 24px 0 8px 0;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
* {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ion-icon {
|
||||
text-align: right;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { AlertController, ToastController } from '@ionic/angular'
|
||||
import { ToastController } from '@ionic/angular'
|
||||
import { StateService } from 'src/app/services/state.service'
|
||||
|
||||
@Component({
|
||||
@@ -8,19 +8,21 @@ import { StateService } from 'src/app/services/state.service'
|
||||
styleUrls: ['success.page.scss'],
|
||||
})
|
||||
export class SuccessPage {
|
||||
torOpen = true
|
||||
lanOpen = false
|
||||
|
||||
constructor (
|
||||
private readonly toastCtrl: ToastController,
|
||||
private readonly alertCtrl: AlertController,
|
||||
public readonly stateService: StateService,
|
||||
) { }
|
||||
|
||||
window = window
|
||||
lanInstructionsOpen = false
|
||||
ngAfterViewInit () {
|
||||
this.download()
|
||||
}
|
||||
|
||||
async copy (address: string): Promise<void> {
|
||||
let message = ''
|
||||
await this.copyToClipboard(address)
|
||||
.then(success => message = success ? 'copied to clipboard!' : 'failed to copy')
|
||||
const success = await this.copyToClipboard(address)
|
||||
const message = success ? 'copied to clipboard!' : 'failed to copy'
|
||||
|
||||
const toast = await this.toastCtrl.create({
|
||||
header: message,
|
||||
@@ -30,51 +32,46 @@ export class SuccessPage {
|
||||
await toast.present()
|
||||
}
|
||||
|
||||
async goToEmbassy () {
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
async copyToClipboard (str: string): Promise<boolean> {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Please Save',
|
||||
message: 'Make sure you save the address to your Embassy in a safe place.',
|
||||
buttons: [
|
||||
{
|
||||
text: 'OK',
|
||||
role: 'cancel',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
await alert.present()
|
||||
if (window.isSecureContext) {
|
||||
return navigator.clipboard.writeText(str)
|
||||
.then(() => {
|
||||
return true
|
||||
})
|
||||
.catch(err => {
|
||||
return false
|
||||
})
|
||||
} else {
|
||||
const el = document.createElement('textarea')
|
||||
el.value = str
|
||||
el.setAttribute('readonly', '')
|
||||
el.style.position = 'absolute'
|
||||
el.style.left = '-9999px'
|
||||
document.body.appendChild(el)
|
||||
el.select()
|
||||
const copy = document.execCommand('copy')
|
||||
document.body.removeChild(el)
|
||||
return copy
|
||||
}
|
||||
toggleTor () {
|
||||
this.torOpen = !this.torOpen
|
||||
}
|
||||
|
||||
toggleLan () {
|
||||
this.lanInstructionsOpen = !this.lanInstructionsOpen
|
||||
this.lanOpen = !this.lanOpen
|
||||
}
|
||||
|
||||
installCert () {
|
||||
document.getElementById('install-cert').click()
|
||||
}
|
||||
|
||||
download () {
|
||||
document.getElementById('tor-addr').innerHTML = this.stateService.torAddress
|
||||
document.getElementById('lan-addr').innerHTML = this.stateService.lanAddress
|
||||
document.getElementById('cert').setAttribute('href', 'data:application/x-x509-ca-cert;base64,' + encodeURIComponent(this.stateService.cert))
|
||||
let html = document.getElementById('downloadable').innerHTML
|
||||
const filename = 'embassy-info.html'
|
||||
|
||||
const elem = document.createElement('a')
|
||||
elem.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(html))
|
||||
elem.setAttribute('download', filename)
|
||||
elem.style.display = 'none'
|
||||
|
||||
document.body.appendChild(elem)
|
||||
elem.click()
|
||||
document.body.removeChild(elem)
|
||||
}
|
||||
|
||||
private async copyToClipboard (str: string): Promise<boolean> {
|
||||
const el = document.createElement('textarea')
|
||||
el.value = str
|
||||
el.setAttribute('readonly', '')
|
||||
el.style.position = 'absolute'
|
||||
el.style.left = '-9999px'
|
||||
document.body.appendChild(el)
|
||||
el.select()
|
||||
const copy = document.execCommand('copy')
|
||||
document.body.removeChild(el)
|
||||
return copy
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface SetupEmbassyReq {
|
||||
export interface SetupEmbassyRes {
|
||||
'tor-address': string
|
||||
'lan-address': string
|
||||
'root-ca': string
|
||||
}
|
||||
|
||||
export interface DiskInfo {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { pauseFor } from '../state.service'
|
||||
import { pauseFor } from 'src/app/util/misc.util'
|
||||
import { ApiService, SetupEmbassyReq } from './api.service'
|
||||
|
||||
let tries = 0
|
||||
@@ -18,7 +18,7 @@ export class MockApiService extends ApiService {
|
||||
async getStatus () {
|
||||
await pauseFor(1000)
|
||||
return {
|
||||
'product-key': false,
|
||||
'product-key': true,
|
||||
migrating: false,
|
||||
}
|
||||
}
|
||||
@@ -148,6 +148,7 @@ export class MockApiService extends ApiService {
|
||||
return {
|
||||
'tor-address': 'asdfasdfasdf.onion',
|
||||
'lan-address': 'embassy-dfasdf.local',
|
||||
'root-ca': rootCA,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,3 +168,5 @@ export class MockApiService extends ApiService {
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const rootCA = 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURwekNDQW8rZ0F3SUJBZ0lSQUlJdU9hcmxRRVRsVVFFT1pKR1pZZEl3RFFZSktvWklodmNOQVFFTEJRQXcKYlRFTE1Ba0dBMVVFQmhNQ1ZWTXhGVEFUQmdOVkJBb01ERVY0WVcxd2JHVWdRMjl5Y0RFT01Bd0dBMVVFQ3d3RgpVMkZzWlhNeEN6QUpCZ05WQkFnTUFsZEJNUmd3RmdZRFZRUUREQTkzZDNjdVpYaGhiWEJzWlM1amIyMHhFREFPCkJnTlZCQWNNQjFObFlYUjBiR1V3SGhjTk1qRXdNekE0TVRVME5qSTNXaGNOTWpJd016QTRNVFkwTmpJM1dqQnQKTVFzd0NRWURWUVFHRXdKVlV6RVZNQk1HQTFVRUNnd01SWGhoYlhCc1pTQkRiM0p3TVE0d0RBWURWUVFMREFWVApZV3hsY3pFTE1Ba0dBMVVFQ0F3Q1YwRXhHREFXQmdOVkJBTU1EM2QzZHk1bGVHRnRjR3hsTG1OdmJURVFNQTRHCkExVUVCd3dIVTJWaGRIUnNaVENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFNUDcKdDVBS0ZaUTdhYnFrZXlVanNCVklXUmE5dENoOG9nZTl1L0x2Q2J4VTczOEc0anNzVCtPdWQzV01haklqdU5vdwpjcGMrMFEvZTQyVUxPLzZnVE5yVHM2T0NPbzlsVjZHMERwcmYvZTkxRFdvS2dQYXRlbS9wVWpOeXJhaWZIWmZ1CmI1bUxIQ2ZhaGpXWFVRdGMvc2ptRFFhWlJLM0thcjZsamxVQkUvTGU5TkV5T0FJa1NMUHpEdFc4TFhtNGl3Y1UKQlpyYjgyOHJLZDFBdzlvSTErM2JmekI2eFhtelp4YzVSTFh2ZU9DRWhLR0QzMmpLWi9STkZTQzhBWkF3SmUreApiVHN5cy9sVU9ZRlR1VDhCbjBUR3hSOHg3WTRINzUrRjlCYXZZM3YrV2tMajRNK29sTjlkTVI3RXQ5Rk10NHU0CllSb2t2NXpwOHpJYjVpVG5lMWtDQXdFQUFhTkNNRUF3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFkQmdOVkhRNEUKRmdRVWFXMytyMzI4dVRMb2tvZzJUa2xtb0JLK3l0NHdEZ1lEVlIwUEFRSC9CQVFEQWdHR01BMEdDU3FHU0liMwpEUUVCQ3dVQUE0SUJBUUFYamQvN1VaOFJERStQTFdTRE5HUWRMZW1PQlRjYXdGK3RLK1B6QTRFdmxtbjlWdU5jCmcreDNvWnZWWlNEUUJBTlV6MGI5b1BlbzU0YUUzOGRXMXpRbTJxZlRhYjg4MjJhcWVXTUx5SjFkTXNBZ3FZWDIKdDkrdTZ3M056UkN3OFB2ejE4VjY5K2RGRTVBZVhtTlAwWjUvZ2R6OEgvTlNwY3RqbHpvcGJTY1JaS0NTbFBpZApSZjNaT1BtOVFQOTJZcFd5WURrZkFVMDR4ZERvMXZSME1ZaktQa2w0TGpScVNVL3RjQ0puUE1iSml3cStiV3BYCjJXSm9FQlhCL3AxNUtuNkp4akkwemUyU25TSTQ4Slo4aXQ0ZnZ4cmhPbzBWb0xOSXVDdU5YSk93VTE3UmRsMVcKWUppZGFxN2plNmsxOEFkZ1BBMEtoOHkxWHRmVUgzZlRhVnc0Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0='
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ErrorHandler, Injectable } from '@angular/core'
|
||||
export class GlobalErrorHandler implements ErrorHandler {
|
||||
|
||||
handleError (error: any): void {
|
||||
const chunkFailedMessage = /Loading chunk [\d]+ failed/
|
||||
const chunkFailedMessage = /Loading chunk [\d]+ failed/
|
||||
|
||||
if (chunkFailedMessage.test(error.message)) {
|
||||
window.location.reload()
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
import { ApiService, DiskInfo, PartitionInfo } from './api/api.service'
|
||||
import { ErrorToastService } from './error-toast.service'
|
||||
import { pauseFor } from '../util/misc.util'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -22,13 +23,14 @@ export class StateService {
|
||||
|
||||
torAddress: string
|
||||
lanAddress: string
|
||||
cert: string
|
||||
|
||||
constructor (
|
||||
private readonly apiService: ApiService,
|
||||
private readonly errorToastService: ErrorToastService,
|
||||
) { }
|
||||
|
||||
async pollDataTransferProgress (callback?: () => void) {
|
||||
async pollDataTransferProgress () {
|
||||
this.polling = true
|
||||
await pauseFor(1000)
|
||||
|
||||
@@ -54,7 +56,7 @@ export class StateService {
|
||||
this.dataProgSubject.next(this.dataProgress)
|
||||
}
|
||||
}
|
||||
this.pollDataTransferProgress(callback)
|
||||
this.pollDataTransferProgress()
|
||||
}
|
||||
|
||||
async setupEmbassy () : Promise<void> {
|
||||
@@ -64,12 +66,8 @@ export class StateService {
|
||||
'recovery-partition': this.recoveryPartition,
|
||||
'recovery-password': this.recoveryPassword,
|
||||
})
|
||||
this.torAddress = ret['tor-address']
|
||||
this.lanAddress = ret['lan-address']
|
||||
this.torAddress = 'http://' + ret['tor-address']
|
||||
this.lanAddress = 'https://' + ret['lan-address']
|
||||
this.cert = ret['root-ca']
|
||||
}
|
||||
}
|
||||
|
||||
export const pauseFor = (ms: number) => {
|
||||
const promise = new Promise(resolve => setTimeout(resolve, ms))
|
||||
return promise
|
||||
}
|
||||
|
||||
3
setup-wizard/src/app/util/misc.util.ts
Normal file
3
setup-wizard/src/app/util/misc.util.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const pauseFor = (ms: number) => {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
BIN
setup-wizard/src/assets/fonts/Montserrat/Montserrat-Bold.ttf
Normal file
BIN
setup-wizard/src/assets/fonts/Montserrat/Montserrat-Bold.ttf
Normal file
Binary file not shown.
BIN
setup-wizard/src/assets/fonts/Montserrat/Montserrat-Light.ttf
Normal file
BIN
setup-wizard/src/assets/fonts/Montserrat/Montserrat-Light.ttf
Normal file
Binary file not shown.
@@ -1 +0,0 @@
|
||||
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -25,32 +25,41 @@
|
||||
@import "~@ionic/angular/css/text-transformation.css";
|
||||
@import "~@ionic/angular/css/flex-utils.css";
|
||||
|
||||
ion-toolbar {
|
||||
--ion-background-color: var(--ion-color-light);
|
||||
}
|
||||
|
||||
ion-avatar {
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
}
|
||||
|
||||
ion-alert {
|
||||
.alert-button {
|
||||
color: var(--ion-color-dark) !important;
|
||||
}
|
||||
}
|
||||
|
||||
ion-button {
|
||||
--color: var(--ion-color-dark) !important;
|
||||
}
|
||||
|
||||
ion-item {
|
||||
--highlight-color-valid: transparent;
|
||||
--highlight-color-invalid: transparent;
|
||||
--highlight-color-focused: var(--ion-color-light);
|
||||
|
||||
--border-radius: 4px;
|
||||
--border-style: solid;
|
||||
--border-width: 1px;
|
||||
--border-color: var(--ion-color-light);
|
||||
}
|
||||
|
||||
.claim-button {
|
||||
margin-inline-start: 0;
|
||||
margin-inline-end: 0;
|
||||
margin-top: 24px;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.alertlike-modal {
|
||||
.modal-wrapper {
|
||||
max-height: 380px !important;
|
||||
top: 25% !important;
|
||||
width: 90% !important;
|
||||
left: 5% !important;
|
||||
--box-shadow: none !important;
|
||||
}
|
||||
ion-card-title {
|
||||
margin: 16px 0;
|
||||
font-family: 'Montserrat';
|
||||
font-size: x-large;
|
||||
--color: var(--ion-color-light);
|
||||
}
|
||||
|
||||
ion-toast {
|
||||
@@ -61,6 +70,20 @@ ion-toast {
|
||||
--color: white;
|
||||
}
|
||||
|
||||
.inline {
|
||||
* {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.claim-button {
|
||||
margin-inline-start: 0;
|
||||
margin-inline-end: 0;
|
||||
margin-top: 24px;
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.error-toast {
|
||||
--border-color: var(--ion-color-danger);
|
||||
width: 40%;
|
||||
@@ -71,11 +94,64 @@ ion-toast {
|
||||
top: 64px;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
text-align: left;
|
||||
padding-bottom: 2px;
|
||||
font-size: small;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.error-border {
|
||||
border: 2px solid var(--ion-color-danger);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.success-border {
|
||||
border: 2px solid var(--ion-color-success);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.modal-wrapper.sc-ion-modal-md {
|
||||
border-radius: 6px;
|
||||
border: 2px solid rgba(255,255,255,.03);
|
||||
box-shadow: 0 0 70px 70px black;
|
||||
}
|
||||
|
||||
.modal-wrapper {
|
||||
position: absolute;
|
||||
height: 90% !important;
|
||||
top: 5%;
|
||||
width: 90% !important;
|
||||
left: 5%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (min-width:1000px) {
|
||||
.modal-wrapper {
|
||||
position: absolute;
|
||||
height: 80% !important;
|
||||
top: 10%;
|
||||
width: 50% !important;
|
||||
left: 25%;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.alertlike-modal {
|
||||
.modal-wrapper {
|
||||
max-height: 380px !important;
|
||||
top: 25% !important;
|
||||
width: 90% !important;
|
||||
left: 5% !important;
|
||||
--box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width:1000px) {
|
||||
.alertlike-modal {
|
||||
.modal-wrapper {
|
||||
width: 40% !important;
|
||||
left: 30% !important;
|
||||
width: 60% !important;
|
||||
left: 20% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
<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" />
|
||||
@@ -15,10 +14,6 @@
|
||||
var global = window;
|
||||
</script>
|
||||
<link rel="icon" type="image/x-icon" href="assets/icon/favicon.ico"/>
|
||||
|
||||
<!-- add to homescreen for ios -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -4,17 +4,19 @@
|
||||
/** Ionic CSS Variables **/
|
||||
:root {
|
||||
--ion-font-family: 'Benton Sans';
|
||||
--ion-background-color: var(--ion-color-medium);
|
||||
--ion-background-color-rgb: var(--ion-color-medium-rgb);
|
||||
--ion-text-color: var(--ion-color-dark);
|
||||
--ion-text-color-rgb: var(--ion-color-dark-rgb);
|
||||
/** primary **/
|
||||
--ion-color-primary: #428cff;
|
||||
--ion-backdrop-opacity: .75;
|
||||
|
||||
--ion-color-primary: #0075e1;
|
||||
--ion-color-primary-rgb: 66,140,255;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
--ion-color-primary-contrast-rgb: 255,255,255;
|
||||
--ion-color-primary-shade: #3a7be0;
|
||||
--ion-color-primary-tint: #5598ff;
|
||||
|
||||
/** secondary **/
|
||||
--ion-color-secondary: #50c8ff;
|
||||
--ion-color-secondary-rgb: 80,200,255;
|
||||
--ion-color-secondary-contrast: #ffffff;
|
||||
@@ -22,7 +24,6 @@
|
||||
--ion-color-secondary-shade: #46b0e0;
|
||||
--ion-color-secondary-tint: #62ceff;
|
||||
|
||||
/** tertiary **/
|
||||
--ion-color-tertiary: #6a64ff;
|
||||
--ion-color-tertiary-rgb: 106,100,255;
|
||||
--ion-color-tertiary-contrast: #ffffff;
|
||||
@@ -30,7 +31,6 @@
|
||||
--ion-color-tertiary-shade: #5d58e0;
|
||||
--ion-color-tertiary-tint: #7974ff;
|
||||
|
||||
/** success **/
|
||||
--ion-color-success: #2fdf75;
|
||||
--ion-color-success-rgb: 47,223,117;
|
||||
--ion-color-success-contrast: #000000;
|
||||
@@ -38,7 +38,6 @@
|
||||
--ion-color-success-shade: #29c467;
|
||||
--ion-color-success-tint: #44e283;
|
||||
|
||||
/** warning **/
|
||||
--ion-color-warning: #ffd534;
|
||||
--ion-color-warning-rgb: 255,213,52;
|
||||
--ion-color-warning-contrast: #000000;
|
||||
@@ -46,7 +45,6 @@
|
||||
--ion-color-warning-shade: #e0bb2e;
|
||||
--ion-color-warning-tint: #ffd948;
|
||||
|
||||
/** danger **/
|
||||
--ion-color-danger: #ff4961;
|
||||
--ion-color-danger-rgb: 255,73,97;
|
||||
--ion-color-danger-contrast: #ffffff;
|
||||
@@ -54,7 +52,20 @@
|
||||
--ion-color-danger-shade: #e04055;
|
||||
--ion-color-danger-tint: #ff5b71;
|
||||
|
||||
/** dark **/
|
||||
--ion-color-light: #181818;
|
||||
--ion-color-light-rgb: 24,24,24;
|
||||
--ion-color-light-contrast: #ffffff;
|
||||
--ion-color-light-contrast-rgb: 0,0,0;
|
||||
--ion-color-light-shade: #000000;
|
||||
--ion-color-light-tint: #000000;
|
||||
|
||||
--ion-color-medium: #222428;
|
||||
--ion-color-medium-rgb: 34,36,40;
|
||||
--ion-color-medium-contrast: #ffffff;
|
||||
--ion-color-medium-contrast-rgb: 255,255,255;
|
||||
--ion-color-medium-shade: #1e2023;
|
||||
--ion-color-medium-tint: #383a3e;
|
||||
|
||||
--ion-color-dark: #e0e0e0;
|
||||
--ion-color-dark-rgb: 224,224,224;
|
||||
--ion-color-dark-contrast: #000000;
|
||||
@@ -62,21 +73,25 @@
|
||||
--ion-color-dark-shade: #bfbfbf;
|
||||
--ion-color-dark-tint: #d8d8d8;
|
||||
|
||||
/** medium **/
|
||||
--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;
|
||||
|
||||
/** light **/
|
||||
--ion-color-light: #25272b;
|
||||
--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-color-step-50: #1e1e1e;
|
||||
--ion-color-step-100: #2a2a2a;
|
||||
--ion-color-step-150: #363636;
|
||||
--ion-color-step-200: #414141;
|
||||
--ion-color-step-250: #4d4d4d;
|
||||
--ion-color-step-300: #595959;
|
||||
--ion-color-step-350: #656565;
|
||||
--ion-color-step-400: #717171;
|
||||
--ion-color-step-450: #7d7d7d;
|
||||
--ion-color-step-500: #898989;
|
||||
--ion-color-step-550: #949494;
|
||||
--ion-color-step-600: #a0a0a0;
|
||||
--ion-color-step-650: #acacac;
|
||||
--ion-color-step-700: #b8b8b8;
|
||||
--ion-color-step-750: #c4c4c4;
|
||||
--ion-color-step-800: #d0d0d0;
|
||||
--ion-color-step-850: #dbdbdb;
|
||||
--ion-color-step-900: #e7e7e7;
|
||||
--ion-color-step-950: #f3f3f3;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@@ -86,6 +101,20 @@
|
||||
src: url('../assets/fonts/Montserrat/Montserrat-Regular.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
src: url('../assets/fonts/Montserrat/Montserrat-Bold.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
font-style: normal;
|
||||
font-weight: thin;
|
||||
src: url('../assets/fonts/Montserrat/Montserrat-Light.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Benton Sans';
|
||||
font-style: normal;
|
||||
|
||||
Reference in New Issue
Block a user