mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
no init
setup complete fix and minor copy changes return SetupResult on setup.complete
This commit is contained in:
committed by
Aiden McClelland
parent
df16943502
commit
61ee46f289
@@ -8,7 +8,7 @@ use patch_db::json_ptr::JsonPointer;
|
||||
use patch_db::PatchDb;
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
use rpc_toolkit::Context;
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::sqlite::SqliteConnectOptions;
|
||||
use sqlx::SqlitePool;
|
||||
use tokio::fs::File;
|
||||
@@ -25,6 +25,14 @@ use crate::util::io::from_toml_async_reader;
|
||||
use crate::util::AsyncFileExt;
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct SetupResult {
|
||||
pub tor_address: String,
|
||||
pub lan_address: String,
|
||||
pub root_ca: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct SetupContextConfig {
|
||||
@@ -62,7 +70,7 @@ pub struct SetupContextSeed {
|
||||
pub selected_v2_drive: RwLock<Option<PathBuf>>,
|
||||
pub cached_product_key: RwLock<Option<Arc<String>>>,
|
||||
pub recovery_status: RwLock<Option<Result<RecoveryStatus, RpcError>>>,
|
||||
pub disk_guid: RwLock<Option<Arc<String>>>,
|
||||
pub setup_result: RwLock<Option<(Arc<String>, SetupResult)>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -81,7 +89,7 @@ impl SetupContext {
|
||||
selected_v2_drive: RwLock::new(None),
|
||||
cached_product_key: RwLock::new(None),
|
||||
recovery_status: RwLock::new(None),
|
||||
disk_guid: RwLock::new(None),
|
||||
setup_result: RwLock::new(None),
|
||||
})))
|
||||
}
|
||||
#[instrument(skip(self))]
|
||||
|
||||
@@ -25,6 +25,7 @@ use tracing::instrument;
|
||||
use crate::backup::restore::recover_full_embassy;
|
||||
use crate::backup::target::BackupTargetFS;
|
||||
use crate::context::rpc::RpcContextConfig;
|
||||
use crate::context::setup::SetupResult;
|
||||
use crate::context::SetupContext;
|
||||
use crate::db::model::RecoveredPackageInfo;
|
||||
use crate::disk::main::DEFAULT_PASSWORD;
|
||||
@@ -112,18 +113,19 @@ pub async fn attach(
|
||||
&*ctx.product_key().await?,
|
||||
)
|
||||
.await?;
|
||||
*ctx.disk_guid.write().await = Some(guid.clone());
|
||||
let secrets = ctx.secret_store().await?;
|
||||
let tor_key = crate::net::tor::os_key(&mut secrets.acquire().await?).await?;
|
||||
let (_, root_ca) = SslManager::init(secrets).await?.export_root_ca().await?;
|
||||
Ok(SetupResult {
|
||||
let setup_result = SetupResult {
|
||||
tor_address: format!("http://{}", tor_key.public().get_onion_address()),
|
||||
lan_address: format!(
|
||||
"https://embassy-{}.local",
|
||||
crate::hostname::derive_id(&*ctx.product_key().await?)
|
||||
),
|
||||
root_ca: String::from_utf8(root_ca.to_pem()?)?,
|
||||
})
|
||||
};
|
||||
*ctx.setup_result.write().await = Some((guid, setup_result.clone()));
|
||||
Ok(setup_result)
|
||||
}
|
||||
|
||||
#[command(subcommands(v2, recovery_status))]
|
||||
@@ -191,14 +193,6 @@ pub async fn verify_cifs(
|
||||
embassy_os.ok_or_else(|| Error::new(eyre!("No Backup Found"), crate::ErrorKind::NotFound))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct SetupResult {
|
||||
tor_address: String,
|
||||
lan_address: String,
|
||||
root_ca: String,
|
||||
}
|
||||
|
||||
#[command(rpc_only)]
|
||||
pub async fn execute(
|
||||
#[context] ctx: SetupContext,
|
||||
@@ -240,9 +234,9 @@ pub async fn execute(
|
||||
|
||||
#[instrument(skip(ctx))]
|
||||
#[command(rpc_only)]
|
||||
pub async fn complete(#[context] ctx: SetupContext) -> Result<(), Error> {
|
||||
let guid = if let Some(guid) = &*ctx.disk_guid.read().await {
|
||||
guid.clone()
|
||||
pub async fn complete(#[context] ctx: SetupContext) -> Result<SetupResult, Error> {
|
||||
let (guid, setup_result) = if let Some((guid, setup_result)) = &*ctx.setup_result.read().await {
|
||||
(guid.clone(), setup_result.clone())
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
eyre!("setup.execute has not completed successfully"),
|
||||
@@ -281,7 +275,7 @@ pub async fn complete(#[context] ctx: SetupContext) -> Result<(), Error> {
|
||||
guid_file.write_all(guid.as_bytes()).await?;
|
||||
guid_file.sync_all().await?;
|
||||
ctx.shutdown.send(()).expect("failed to shutdown");
|
||||
Ok(())
|
||||
Ok(setup_result)
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx, embassy_password, recovery_password))]
|
||||
@@ -323,10 +317,21 @@ pub async fn execute_inner(
|
||||
&ctx.product_key().await?,
|
||||
)
|
||||
.await?;
|
||||
let res = (tor_addr, root_ca.clone());
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = recover_fut
|
||||
.and_then(|_| async {
|
||||
*ctx.disk_guid.write().await = Some(guid);
|
||||
*ctx.setup_result.write().await = Some((
|
||||
guid,
|
||||
SetupResult {
|
||||
tor_address: format!("http://{}", tor_addr),
|
||||
lan_address: format!(
|
||||
"https://embassy-{}.local",
|
||||
crate::hostname::derive_id(&ctx.product_key().await?)
|
||||
),
|
||||
root_ca: String::from_utf8(root_ca.to_pem()?)?,
|
||||
},
|
||||
));
|
||||
if let Some(Ok(recovery_status)) = &mut *ctx.recovery_status.write().await {
|
||||
recovery_status.complete = true;
|
||||
}
|
||||
@@ -342,16 +347,26 @@ pub async fn execute_inner(
|
||||
tracing::info!("Recovery Complete!");
|
||||
}
|
||||
});
|
||||
(tor_addr, root_ca)
|
||||
res
|
||||
} else {
|
||||
let res = fresh_setup(&ctx, &embassy_password).await?;
|
||||
let (tor_addr, root_ca) = fresh_setup(&ctx, &embassy_password).await?;
|
||||
init(
|
||||
&RpcContextConfig::load(ctx.config_path.as_ref()).await?,
|
||||
&ctx.product_key().await?,
|
||||
)
|
||||
.await?;
|
||||
*ctx.disk_guid.write().await = Some(guid);
|
||||
res
|
||||
*ctx.setup_result.write().await = Some((
|
||||
guid,
|
||||
SetupResult {
|
||||
tor_address: format!("http://{}", tor_addr),
|
||||
lan_address: format!(
|
||||
"https://embassy-{}.local",
|
||||
crate::hostname::derive_id(&ctx.product_key().await?)
|
||||
),
|
||||
root_ca: String::from_utf8(root_ca.to_pem()?)?,
|
||||
},
|
||||
));
|
||||
(tor_addr, root_ca)
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
|
||||
@@ -4,33 +4,41 @@ import { NavGuard, RecoveryNavGuard } from './guards/nav-guard'
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: '/product-key', pathMatch: 'full' },
|
||||
{
|
||||
path: 'init',
|
||||
loadChildren: () => import('./pages/init/init.module').then( m => m.InitPageModule),
|
||||
canActivate: [NavGuard],
|
||||
},
|
||||
{
|
||||
path: 'product-key',
|
||||
loadChildren: () => import('./pages/product-key/product-key.module').then( m => m.ProductKeyPageModule),
|
||||
loadChildren: () =>
|
||||
import('./pages/product-key/product-key.module').then(
|
||||
m => m.ProductKeyPageModule,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'home',
|
||||
loadChildren: () => import('./pages/home/home.module').then( m => m.HomePageModule),
|
||||
loadChildren: () =>
|
||||
import('./pages/home/home.module').then(m => m.HomePageModule),
|
||||
canActivate: [NavGuard],
|
||||
},
|
||||
{
|
||||
path: 'recover',
|
||||
loadChildren: () => import('./pages/recover/recover.module').then( m => m.RecoverPageModule),
|
||||
loadChildren: () =>
|
||||
import('./pages/recover/recover.module').then(m => m.RecoverPageModule),
|
||||
canActivate: [RecoveryNavGuard],
|
||||
},
|
||||
{
|
||||
path: 'embassy',
|
||||
loadChildren: () => import('./pages/embassy/embassy.module').then( m => m.EmbassyPageModule),
|
||||
loadChildren: () =>
|
||||
import('./pages/embassy/embassy.module').then(m => m.EmbassyPageModule),
|
||||
canActivate: [NavGuard],
|
||||
},
|
||||
{
|
||||
path: 'loading',
|
||||
loadChildren: () => import('./pages/loading/loading.module').then( m => m.LoadingPageModule),
|
||||
loadChildren: () =>
|
||||
import('./pages/loading/loading.module').then(m => m.LoadingPageModule),
|
||||
canActivate: [NavGuard],
|
||||
},
|
||||
{
|
||||
path: 'success',
|
||||
loadChildren: () =>
|
||||
import('./pages/success/success.module').then(m => m.SuccessPageModule),
|
||||
canActivate: [NavGuard],
|
||||
},
|
||||
]
|
||||
@@ -46,4 +54,4 @@ const routes: Routes = [
|
||||
],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AppRoutingModule { }
|
||||
export class AppRoutingModule {}
|
||||
|
||||
@@ -15,7 +15,6 @@ import { AppComponent } from './app.component'
|
||||
import { AppRoutingModule } from './app-routing.module'
|
||||
import { GlobalErrorHandler } from './services/global-error-handler.service'
|
||||
import { SuccessPageModule } from './pages/success/success.module'
|
||||
import { InitPageModule } from './pages/init/init.module'
|
||||
import { HomePageModule } from './pages/home/home.module'
|
||||
import { LoadingPageModule } from './pages/loading/loading.module'
|
||||
import { ProdKeyModalModule } from './modals/prod-key-modal/prod-key-modal.module'
|
||||
@@ -42,7 +41,6 @@ const { useMocks } = require('../../../../config.json') as WorkspaceConfig
|
||||
ProdKeyModalModule,
|
||||
ProductKeyPageModule,
|
||||
RecoverPageModule,
|
||||
InitPageModule,
|
||||
],
|
||||
providers: [
|
||||
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
|
||||
|
||||
@@ -129,7 +129,7 @@ export class EmbassyPage {
|
||||
|
||||
private async setupEmbassy(drive: DiskInfo, password: string): Promise<void> {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
message: 'Transferring encrypted data. This could take a while...',
|
||||
message: 'Initializing data drive. This could take a while...',
|
||||
})
|
||||
|
||||
await loader.present()
|
||||
@@ -139,7 +139,7 @@ export class EmbassyPage {
|
||||
if (!!this.stateService.recoverySource) {
|
||||
await this.navCtrl.navigateForward(`/loading`)
|
||||
} else {
|
||||
await this.navCtrl.navigateForward(`/init`)
|
||||
await this.navCtrl.navigateForward(`/success`)
|
||||
}
|
||||
} catch (e) {
|
||||
this.errorToastService.present(
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
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'
|
||||
import { SuccessPageModule } from '../success/success.module'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
InitPageRoutingModule,
|
||||
SuccessPageModule,
|
||||
],
|
||||
declarations: [InitPage],
|
||||
exports: [InitPage],
|
||||
})
|
||||
export class InitPageModule { }
|
||||
@@ -1,48 +0,0 @@
|
||||
<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>
|
||||
|
||||
<success
|
||||
[hidden]="!stateService.embassyLoaded"
|
||||
(onDownload)="download()"
|
||||
></success>
|
||||
|
||||
<ion-card [hidden]="stateService.embassyLoaded" color="dark">
|
||||
<ion-card-header>
|
||||
<ion-card-title style="font-size: 40px"
|
||||
>Initializing Embassy</ion-card-title
|
||||
>
|
||||
<ion-card-subtitle>Progress: {{ progress }}%</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 / 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>
|
||||
<p class="ion-text-start" style="color: var(--ion-color-danger)">
|
||||
<b>DO NOT:</b> Close or refresh the browser window during
|
||||
intialization process.
|
||||
</p>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ion-content>
|
||||
@@ -1,61 +0,0 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { interval, Subscription } from 'rxjs'
|
||||
import { finalize, take, tap } from 'rxjs/operators'
|
||||
import { ApiService } from 'src/app/services/api/api.service'
|
||||
import { StateService } from 'src/app/services/state.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-init',
|
||||
templateUrl: 'init.page.html',
|
||||
styleUrls: ['init.page.scss'],
|
||||
})
|
||||
export class InitPage {
|
||||
progress = 0
|
||||
sub: Subscription
|
||||
|
||||
constructor (
|
||||
private readonly apiService: ApiService,
|
||||
public readonly stateService: StateService,
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
// call setup.complete to tear down embassy.local and spin up embassy-[id].local
|
||||
this.apiService.setupComplete()
|
||||
|
||||
this.sub = interval(130)
|
||||
.pipe(
|
||||
take(101),
|
||||
tap(num => {
|
||||
this.progress = num
|
||||
}),
|
||||
finalize(() => {
|
||||
setTimeout(() => {
|
||||
this.stateService.embassyLoaded = true
|
||||
this.download()
|
||||
}, 500)
|
||||
}),
|
||||
).subscribe()
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
if (this.sub) this.sub.unsubscribe()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,19 +8,20 @@ import { StateService } from 'src/app/services/state.service'
|
||||
styleUrls: ['loading.page.scss'],
|
||||
})
|
||||
export class LoadingPage {
|
||||
constructor (
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
private navCtrl: NavController,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
ngOnInit () {
|
||||
ngOnInit() {
|
||||
this.stateService.pollDataTransferProgress()
|
||||
const progSub = this.stateService.dataCompletionSubject.subscribe(async complete => {
|
||||
if (complete) {
|
||||
progSub.unsubscribe()
|
||||
await this.navCtrl.navigateForward(`/init`)
|
||||
}
|
||||
})
|
||||
const progSub = this.stateService.dataCompletionSubject.subscribe(
|
||||
async complete => {
|
||||
if (complete) {
|
||||
progSub.unsubscribe()
|
||||
await this.navCtrl.navigateForward(`/success`)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ export class RecoverPage {
|
||||
await loader.present()
|
||||
try {
|
||||
await this.stateService.importDrive(guid)
|
||||
await this.navCtrl.navigateForward(`/init`)
|
||||
await this.navCtrl.navigateForward(`/success`)
|
||||
} catch (e) {
|
||||
this.errorToastService.present(e.message)
|
||||
} finally {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { InitPage } from './init.page'
|
||||
import { SuccessPage } from './success.page'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: InitPage,
|
||||
component: SuccessPage,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -13,4 +13,4 @@ const routes: Routes = [
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class InitPageRoutingModule { }
|
||||
export class SuccessPageRoutingModule {}
|
||||
@@ -4,6 +4,7 @@ import { IonicModule } from '@ionic/angular'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { SuccessPage } from './success.page'
|
||||
import { PasswordPageModule } from '../../modals/password/password.module'
|
||||
import { SuccessPageRoutingModule } from './success-routing.module'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -11,8 +12,9 @@ import { PasswordPageModule } from '../../modals/password/password.module'
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
PasswordPageModule,
|
||||
SuccessPageRoutingModule,
|
||||
],
|
||||
declarations: [SuccessPage],
|
||||
exports: [SuccessPage],
|
||||
})
|
||||
export class SuccessPageModule { }
|
||||
export class SuccessPageModule {}
|
||||
|
||||
@@ -1,167 +1,243 @@
|
||||
<ion-content>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<ion-card color="dark">
|
||||
<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-card-content>
|
||||
<br />
|
||||
<ng-template
|
||||
[ngIf]="stateService.recoverySource && stateService.recoverySource.type === 'disk'"
|
||||
>
|
||||
<h2>You can now safely unplug your backup drive.</h2>
|
||||
</ng-template>
|
||||
<!-- Tor Instructions -->
|
||||
<div (click)="toggleTor()" class="toggle-label">
|
||||
<h2>Tor Instructions:</h2>
|
||||
<ion-icon
|
||||
name="chevron-down-outline"
|
||||
[ngStyle]="{
|
||||
'transform': torOpen ? 'rotate(-90deg)' : 'rotate(0deg)',
|
||||
'transition': 'transform 0.4s ease-out'
|
||||
}"
|
||||
></ion-icon>
|
||||
</div>
|
||||
|
||||
<ion-card color="dark">
|
||||
<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-card-content>
|
||||
<br />
|
||||
<ng-template [ngIf]="stateService.recoverySource && stateService.recoverySource.type === 'disk'">
|
||||
<h2>You can now safely unplug your backup drive.</h2>
|
||||
</ng-template>
|
||||
<!-- Tor Instructions -->
|
||||
<div (click)="toggleTor()" class="toggle-label">
|
||||
<h2>Tor Instructions:</h2>
|
||||
<ion-icon
|
||||
name="chevron-down-outline"
|
||||
[ngStyle]="{
|
||||
'transform': torOpen ? 'rotate(-90deg)' : 'rotate(0deg)',
|
||||
'transition': 'transform 0.4s ease-out'
|
||||
}"
|
||||
></ion-icon>
|
||||
</div>
|
||||
<div
|
||||
[ngStyle]="{
|
||||
'overflow' : 'hidden',
|
||||
'max-height': torOpen ? '500px' : '0px',
|
||||
'transition': 'max-height 0.4s ease-out'
|
||||
}"
|
||||
>
|
||||
<div class="ion-padding ion-text-start">
|
||||
<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://start9.com/latest/user-manual/connecting/connecting-tor"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
><b>here</b></a
|
||||
>.
|
||||
</p>
|
||||
<br />
|
||||
<p>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>
|
||||
</div>
|
||||
<div style="padding-bottom: 24px; border-bottom: solid 1px"></div>
|
||||
<br />
|
||||
</div>
|
||||
|
||||
<div
|
||||
[ngStyle]="{
|
||||
'overflow' : 'hidden',
|
||||
'max-height': torOpen ? '500px' : '0px',
|
||||
'transition': 'max-height 0.4s ease-out'
|
||||
}"
|
||||
>
|
||||
<div class="ion-padding ion-text-start">
|
||||
<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://start9.com/latest/user-manual/connecting" target="_blank" rel="noreferrer"><b>here</b></a>.
|
||||
</p>
|
||||
<br />
|
||||
<p>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>
|
||||
</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>
|
||||
|
||||
<!-- 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 ion-text-start">
|
||||
<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://start9.com/latest/user-manual/connecting/connecting-lan"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
><b>here</b></a
|
||||
>.
|
||||
</p>
|
||||
|
||||
<div
|
||||
[ngStyle]="{
|
||||
'overflow' : 'hidden',
|
||||
'max-height': lanOpen ? '500px' : '0px',
|
||||
'transition': 'max-height 0.4s ease-out'
|
||||
}"
|
||||
>
|
||||
<div class="ion-padding ion-text-start">
|
||||
<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://start9.com/latest/user-manual/connecting/connecting-lan" target="_blank" rel="noreferrer"><b>here</b></a>.
|
||||
</p>
|
||||
<p>
|
||||
<b
|
||||
>Please note, once setup is complete, the embassy.local
|
||||
address will no longer connect to your Embassy.</b
|
||||
>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>Please note, once setup is complete, the embassy.local address will no longer connect to your Embassy.</b>
|
||||
</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>
|
||||
|
||||
<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>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>
|
||||
|
||||
<p>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>
|
||||
<!-- cert elem -->
|
||||
<a hidden id="install-cert" download="embassy.crt"></a>
|
||||
|
||||
<!-- download elem -->
|
||||
<div hidden id="downloadable">
|
||||
<div style="padding: 0 24px; font-family: Courier">
|
||||
<h1>Embassy Info</h1>
|
||||
|
||||
<!-- cert elem -->
|
||||
<a hidden id="install-cert" download="embassy.crt"></a>
|
||||
<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://start9.com/latest/user-manual/connecting/connecting-tor"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
><b>here</b></a
|
||||
>.
|
||||
</p>
|
||||
<p><b>Tor Address: </b><code id="tor-addr"></code></p>
|
||||
</section>
|
||||
|
||||
<!-- 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; 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://start9.com/latest/user-manual/connecting/connecting-lan"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
><b>here</b></a
|
||||
>.
|
||||
</p>
|
||||
|
||||
<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://start9.com/latest/user-manual/connecting" target="_blank" rel="noreferrer"><b>here</b></a>.
|
||||
</p>
|
||||
<p><b>Tor Address: </b><code id="tor-addr"></code></p>
|
||||
</section>
|
||||
<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>
|
||||
|
||||
<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://start9.com/latest/user-manual/connecting/connecting-lan" 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>
|
||||
<p><b>LAN Address: </b><code id="lan-addr"></code></p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ion-content>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Component, EventEmitter, Output } from '@angular/core'
|
||||
import { ToastController } from '@ionic/angular'
|
||||
import { ErrorToastService } from 'src/app/services/error-toast.service'
|
||||
import { StateService } from 'src/app/services/state.service'
|
||||
|
||||
@Component({
|
||||
@@ -12,16 +13,29 @@ export class SuccessPage {
|
||||
torOpen = true
|
||||
lanOpen = false
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
private readonly toastCtrl: ToastController,
|
||||
private readonly errCtrl: ErrorToastService,
|
||||
public readonly stateService: StateService,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
ngAfterViewInit () {
|
||||
document.getElementById('install-cert').setAttribute('href', 'data:application/x-x509-ca-cert;base64,' + encodeURIComponent(this.stateService.cert))
|
||||
async ngAfterViewInit() {
|
||||
try {
|
||||
await this.stateService.completeEmbassy()
|
||||
document
|
||||
.getElementById('install-cert')
|
||||
.setAttribute(
|
||||
'href',
|
||||
'data:application/x-x509-ca-cert;base64,' +
|
||||
encodeURIComponent(this.stateService.cert),
|
||||
)
|
||||
this.download()
|
||||
} catch (e) {
|
||||
await this.errCtrl.present(e)
|
||||
}
|
||||
}
|
||||
|
||||
async copy (address: string): Promise<void> {
|
||||
async copy(address: string): Promise<void> {
|
||||
const success = await this.copyToClipboard(address)
|
||||
const message = success ? 'copied to clipboard!' : 'failed to copy'
|
||||
|
||||
@@ -33,23 +47,45 @@ export class SuccessPage {
|
||||
await toast.present()
|
||||
}
|
||||
|
||||
toggleTor () {
|
||||
toggleTor() {
|
||||
this.torOpen = !this.torOpen
|
||||
}
|
||||
|
||||
toggleLan () {
|
||||
toggleLan() {
|
||||
this.lanOpen = !this.lanOpen
|
||||
}
|
||||
|
||||
installCert () {
|
||||
installCert() {
|
||||
document.getElementById('install-cert').click()
|
||||
}
|
||||
|
||||
download () {
|
||||
this.onDownload.emit()
|
||||
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> {
|
||||
private async copyToClipboard(str: string): Promise<boolean> {
|
||||
const el = document.createElement('textarea')
|
||||
el.value = str
|
||||
el.setAttribute('readonly', '')
|
||||
@@ -62,4 +98,3 @@ export class SuccessPage {
|
||||
return copy
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
export abstract class ApiService {
|
||||
// unencrypted
|
||||
abstract getStatus (): Promise<GetStatusRes> // setup.status
|
||||
abstract getDrives (): Promise<DiskListResponse> // setup.disk.list
|
||||
abstract set02XDrive (logicalname: string): Promise<void> // setup.recovery.v2.set
|
||||
abstract getRecoveryStatus (): Promise<RecoveryStatusRes> // setup.recovery.status
|
||||
abstract getStatus(): Promise<GetStatusRes> // setup.status
|
||||
abstract getDrives(): Promise<DiskListResponse> // setup.disk.list
|
||||
abstract set02XDrive(logicalname: string): Promise<void> // setup.recovery.v2.set
|
||||
abstract getRecoveryStatus(): Promise<RecoveryStatusRes> // setup.recovery.status
|
||||
|
||||
// encrypted
|
||||
abstract verifyCifs (cifs: CifsRecoverySource): Promise<EmbassyOSRecoveryInfo> // setup.cifs.verify
|
||||
abstract verifyProductKey (): Promise<void> // echo - throws error if invalid
|
||||
abstract importDrive (guid: string): Promise<SetupEmbassyRes> // setup.execute
|
||||
abstract setupEmbassy (setupInfo: SetupEmbassyReq): Promise<SetupEmbassyRes> // setup.execute
|
||||
abstract setupComplete (): Promise<void> // setup.complete
|
||||
abstract verifyCifs(cifs: CifsRecoverySource): Promise<EmbassyOSRecoveryInfo> // setup.cifs.verify
|
||||
abstract verifyProductKey(): Promise<void> // echo - throws error if invalid
|
||||
abstract importDrive(guid: string): Promise<SetupEmbassyRes> // setup.execute
|
||||
abstract setupEmbassy(setupInfo: SetupEmbassyReq): Promise<SetupEmbassyRes> // setup.execute
|
||||
abstract setupComplete(): Promise<SetupEmbassyRes> // setup.complete
|
||||
}
|
||||
|
||||
export interface GetStatusRes {
|
||||
@@ -75,12 +75,12 @@ export interface CifsRecoverySource {
|
||||
}
|
||||
|
||||
export interface DiskInfo {
|
||||
logicalname: string,
|
||||
vendor: string | null,
|
||||
model: string | null,
|
||||
partitions: PartitionInfo[],
|
||||
capacity: number,
|
||||
guid: string | null, // cant back up if guid exists
|
||||
logicalname: string
|
||||
vendor: string | null
|
||||
model: string | null
|
||||
partitions: PartitionInfo[]
|
||||
capacity: number
|
||||
guid: string | null // cant back up if guid exists
|
||||
}
|
||||
|
||||
export interface RecoveryStatusRes {
|
||||
@@ -90,9 +90,9 @@ export interface RecoveryStatusRes {
|
||||
}
|
||||
|
||||
export interface PartitionInfo {
|
||||
logicalname: string,
|
||||
label: string | null,
|
||||
capacity: number,
|
||||
used: number | null,
|
||||
'embassy-os': EmbassyOSRecoveryInfo | null,
|
||||
logicalname: string
|
||||
label: string | null
|
||||
capacity: number
|
||||
used: number | null
|
||||
'embassy-os': EmbassyOSRecoveryInfo | null
|
||||
}
|
||||
|
||||
@@ -1,49 +1,71 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ApiService, CifsRecoverySource, DiskInfo, DiskListResponse, DiskRecoverySource, EmbassyOSRecoveryInfo, GetStatusRes, RecoveryStatusRes, SetupEmbassyReq, SetupEmbassyRes } from './api.service'
|
||||
import {
|
||||
ApiService,
|
||||
CifsRecoverySource,
|
||||
DiskInfo,
|
||||
DiskListResponse,
|
||||
DiskRecoverySource,
|
||||
EmbassyOSRecoveryInfo,
|
||||
GetStatusRes,
|
||||
RecoveryStatusRes,
|
||||
SetupEmbassyReq,
|
||||
SetupEmbassyRes,
|
||||
} from './api.service'
|
||||
import { HttpService } from './http.service'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class LiveApiService extends ApiService {
|
||||
|
||||
constructor (
|
||||
private readonly http: HttpService,
|
||||
) { super() }
|
||||
constructor(private readonly http: HttpService) {
|
||||
super()
|
||||
}
|
||||
|
||||
// ** UNENCRYPTED **
|
||||
|
||||
async getStatus () {
|
||||
return this.http.rpcRequest<GetStatusRes>({
|
||||
method: 'setup.status',
|
||||
params: { },
|
||||
}, false)
|
||||
async getStatus() {
|
||||
return this.http.rpcRequest<GetStatusRes>(
|
||||
{
|
||||
method: 'setup.status',
|
||||
params: {},
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
async getDrives () {
|
||||
return this.http.rpcRequest<DiskListResponse>({
|
||||
method: 'setup.disk.list',
|
||||
params: { },
|
||||
}, false)
|
||||
async getDrives() {
|
||||
return this.http.rpcRequest<DiskListResponse>(
|
||||
{
|
||||
method: 'setup.disk.list',
|
||||
params: {},
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
async set02XDrive (logicalname) {
|
||||
return this.http.rpcRequest<void>({
|
||||
method: 'setup.recovery.v2.set',
|
||||
params: { logicalname },
|
||||
}, false)
|
||||
async set02XDrive(logicalname) {
|
||||
return this.http.rpcRequest<void>(
|
||||
{
|
||||
method: 'setup.recovery.v2.set',
|
||||
params: { logicalname },
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
async getRecoveryStatus () {
|
||||
return this.http.rpcRequest<RecoveryStatusRes>({
|
||||
method: 'setup.recovery.status',
|
||||
params: { },
|
||||
}, false)
|
||||
async getRecoveryStatus() {
|
||||
return this.http.rpcRequest<RecoveryStatusRes>(
|
||||
{
|
||||
method: 'setup.recovery.status',
|
||||
params: {},
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// ** ENCRYPTED **
|
||||
|
||||
async verifyCifs (source: CifsRecoverySource) {
|
||||
async verifyCifs(source: CifsRecoverySource) {
|
||||
source.path = source.path.replace('/\\/g', '/')
|
||||
return this.http.rpcRequest<EmbassyOSRecoveryInfo>({
|
||||
method: 'setup.cifs.verify',
|
||||
@@ -51,14 +73,14 @@ export class LiveApiService extends ApiService {
|
||||
})
|
||||
}
|
||||
|
||||
async verifyProductKey () {
|
||||
async verifyProductKey() {
|
||||
return this.http.rpcRequest<void>({
|
||||
method: 'echo',
|
||||
params: { 'message': 'hello' },
|
||||
params: { message: 'hello' },
|
||||
})
|
||||
}
|
||||
|
||||
async importDrive (guid: string) {
|
||||
async importDrive(guid: string) {
|
||||
const res = await this.http.rpcRequest<SetupEmbassyRes>({
|
||||
method: 'setup.attach',
|
||||
params: { guid },
|
||||
@@ -70,9 +92,11 @@ export class LiveApiService extends ApiService {
|
||||
}
|
||||
}
|
||||
|
||||
async setupEmbassy (setupInfo: SetupEmbassyReq) {
|
||||
async setupEmbassy(setupInfo: SetupEmbassyReq) {
|
||||
if (isCifsSource(setupInfo['recovery-source'])) {
|
||||
setupInfo['recovery-source'].path = setupInfo['recovery-source'].path.replace('/\\/g', '/')
|
||||
setupInfo['recovery-source'].path = setupInfo[
|
||||
'recovery-source'
|
||||
].path.replace('/\\/g', '/')
|
||||
}
|
||||
|
||||
const res = await this.http.rpcRequest<SetupEmbassyRes>({
|
||||
@@ -86,14 +110,16 @@ export class LiveApiService extends ApiService {
|
||||
}
|
||||
}
|
||||
|
||||
async setupComplete () {
|
||||
await this.http.rpcRequest<SetupEmbassyRes>({
|
||||
async setupComplete() {
|
||||
return this.http.rpcRequest<SetupEmbassyRes>({
|
||||
method: 'setup.complete',
|
||||
params: { },
|
||||
params: {},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function isCifsSource (source: CifsRecoverySource | DiskRecoverySource | undefined): source is CifsRecoverySource {
|
||||
function isCifsSource(
|
||||
source: CifsRecoverySource | DiskRecoverySource | undefined,
|
||||
): source is CifsRecoverySource {
|
||||
return !!(source as CifsRecoverySource)?.hostname
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ export class MockApiService extends ApiService {
|
||||
|
||||
async setupComplete() {
|
||||
await pauseFor(1000)
|
||||
return setupRes
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,4 +91,11 @@ export class StateService {
|
||||
this.lanAddress = ret['lan-address']
|
||||
this.cert = ret['root-ca']
|
||||
}
|
||||
|
||||
async completeEmbassy(): Promise<void> {
|
||||
const ret = await this.apiService.setupComplete()
|
||||
this.torAddress = ret['tor-address']
|
||||
this.lanAddress = ret['lan-address']
|
||||
this.cert = ret['root-ca']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,6 @@ export class MarkdownPage {
|
||||
if (!this.content) {
|
||||
this.content = await this.embassyApi.getStatic(this.contentUrl)
|
||||
}
|
||||
} catch (e) {
|
||||
this.loadingError = getErrorMessage(e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
await pauseFor(50)
|
||||
const links = document.links
|
||||
@@ -39,6 +36,9 @@ export class MarkdownPage {
|
||||
links[i].className += ' externalLink'
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
this.loadingError = getErrorMessage(e)
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ export class AppShowStatusComponent {
|
||||
const { id, title, version } = this.pkg.manifest
|
||||
const hasDependents = !!Object.keys(
|
||||
this.pkg.installed['current-dependents'],
|
||||
).filter(depId => depId !== this.pkg.manifest.id).length
|
||||
).filter(depId => depId !== id).length
|
||||
|
||||
if (!hasDependents) {
|
||||
const loader = await this.loadingCtrl.create({
|
||||
|
||||
Reference in New Issue
Block a user