mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
better migration progress bar (#1993)
* better migration progress bar * show different messages based on setup type and fix modal height * type safety Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
This commit is contained in:
@@ -358,11 +358,11 @@ async fn perform_backup<Db: DbHandle>(
|
||||
.await;
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
let lock_package = crate::db::DatabaseModel::new()
|
||||
crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx(&package_id)
|
||||
.lock(&mut tx, LockType::Write)
|
||||
.await;
|
||||
.await?;
|
||||
|
||||
installed_model.lock(&mut tx, LockType::Write).await?;
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ impl ProgressInfo {
|
||||
}
|
||||
|
||||
SetupStatus {
|
||||
total_bytes,
|
||||
total_bytes: Some(total_bytes),
|
||||
bytes_transferred,
|
||||
complete: false,
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ pub async fn attach(
|
||||
}
|
||||
*status = Some(Ok(SetupStatus {
|
||||
bytes_transferred: 0,
|
||||
total_bytes: 0,
|
||||
total_bytes: None,
|
||||
complete: false,
|
||||
}));
|
||||
drop(status);
|
||||
@@ -165,7 +165,7 @@ pub async fn attach(
|
||||
}));
|
||||
*ctx.setup_status.write().await = Some(Ok(SetupStatus {
|
||||
bytes_transferred: 0,
|
||||
total_bytes: 0,
|
||||
total_bytes: None,
|
||||
complete: true,
|
||||
}));
|
||||
Ok(())
|
||||
@@ -182,7 +182,7 @@ pub async fn attach(
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct SetupStatus {
|
||||
pub bytes_transferred: u64,
|
||||
pub total_bytes: u64,
|
||||
pub total_bytes: Option<u64>,
|
||||
pub complete: bool,
|
||||
}
|
||||
|
||||
@@ -277,7 +277,7 @@ pub async fn execute(
|
||||
}
|
||||
*status = Some(Ok(SetupStatus {
|
||||
bytes_transferred: 0,
|
||||
total_bytes: 0,
|
||||
total_bytes: None,
|
||||
complete: false,
|
||||
}));
|
||||
drop(status);
|
||||
@@ -306,7 +306,7 @@ pub async fn execute(
|
||||
));
|
||||
*ctx.setup_status.write().await = Some(Ok(SetupStatus {
|
||||
bytes_transferred: 0,
|
||||
total_bytes: 0,
|
||||
total_bytes: None,
|
||||
complete: true,
|
||||
}));
|
||||
}
|
||||
@@ -440,10 +440,9 @@ async fn migrate(
|
||||
old_guid: &str,
|
||||
embassy_password: String,
|
||||
) -> Result<(Arc<String>, Hostname, OnionAddressV3, X509), Error> {
|
||||
|
||||
*ctx.setup_status.write().await = Some(Ok(SetupStatus {
|
||||
bytes_transferred: 0,
|
||||
total_bytes: 110,
|
||||
total_bytes: None,
|
||||
complete: false,
|
||||
}));
|
||||
|
||||
@@ -465,15 +464,6 @@ async fn migrate(
|
||||
exclude: Vec::new(),
|
||||
},
|
||||
)?;
|
||||
while let Some(progress) = main_transfer.progress.next().await {
|
||||
*ctx.setup_status.write().await = Some(Ok(SetupStatus {
|
||||
bytes_transferred: (progress * 10.0) as u64,
|
||||
total_bytes: 110,
|
||||
complete: false,
|
||||
}));
|
||||
}
|
||||
main_transfer.wait().await?;
|
||||
|
||||
let mut package_data_transfer = Rsync::new(
|
||||
"/media/embassy/migrate/package-data/",
|
||||
"/embassy-data/package-data/",
|
||||
@@ -484,14 +474,45 @@ async fn migrate(
|
||||
exclude: vec!["tmp".to_owned()],
|
||||
},
|
||||
)?;
|
||||
while let Some(progress) = package_data_transfer.progress.next().await {
|
||||
*ctx.setup_status.write().await = Some(Ok(SetupStatus {
|
||||
bytes_transferred: 10 + (progress * 100.0) as u64,
|
||||
total_bytes: 110,
|
||||
complete: false,
|
||||
}));
|
||||
|
||||
let mut main_prog = 0.0;
|
||||
let mut main_complete = false;
|
||||
let mut pkg_prog = 0.0;
|
||||
let mut pkg_complete = false;
|
||||
loop {
|
||||
tokio::select! {
|
||||
p = main_transfer.progress.next() => {
|
||||
if let Some(p) = p {
|
||||
main_prog = p;
|
||||
} else {
|
||||
main_prog = 1.0;
|
||||
main_complete = true;
|
||||
}
|
||||
}
|
||||
p = package_data_transfer.progress.next() => {
|
||||
if let Some(p) = p {
|
||||
pkg_prog = p;
|
||||
} else {
|
||||
pkg_prog = 1.0;
|
||||
pkg_complete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if main_prog > 0.0 && pkg_prog > 0.0 {
|
||||
*ctx.setup_status.write().await = Some(Ok(SetupStatus {
|
||||
bytes_transferred: ((main_prog * 50.0) + (pkg_prog * 950.0)) as u64,
|
||||
total_bytes: Some(1000),
|
||||
complete: false,
|
||||
}));
|
||||
}
|
||||
if main_complete && pkg_complete {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
main_transfer.wait().await?;
|
||||
package_data_transfer.wait().await?;
|
||||
|
||||
let (hostname, tor_addr, root_ca) = setup_init(&ctx, Some(embassy_password)).await?;
|
||||
|
||||
crate::disk::main::export(&old_guid, "/media/embassy/migrate").await?;
|
||||
|
||||
@@ -28,6 +28,7 @@ export class AttachPage {
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.stateService.setupType = 'attach'
|
||||
await this.getDrives()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<ion-content>
|
||||
<ion-grid>
|
||||
<ion-grid *ngIf="!loading">
|
||||
<ion-row class="ion-align-items-center">
|
||||
<ion-col class="ion-text-center">
|
||||
<div style="padding-bottom: 32px">
|
||||
@@ -9,7 +9,7 @@
|
||||
style="max-width: 220px"
|
||||
/>
|
||||
</div>
|
||||
<ion-card *ngIf="!loading" color="dark">
|
||||
<ion-card color="dark">
|
||||
<ion-card-header>
|
||||
<ion-button
|
||||
*ngIf="swiper?.activeIndex === 1"
|
||||
|
||||
@@ -3,6 +3,7 @@ import { IonicSlides } from '@ionic/angular'
|
||||
import { ApiService } from 'src/app/services/api/api.service'
|
||||
import SwiperCore, { Swiper } from 'swiper'
|
||||
import { ErrorToastService } from '@start9labs/shared'
|
||||
import { StateService } from 'src/app/services/state.service'
|
||||
|
||||
SwiperCore.use([IonicSlides])
|
||||
|
||||
@@ -19,9 +20,11 @@ export class HomePage {
|
||||
constructor(
|
||||
private readonly api: ApiService,
|
||||
private readonly errToastService: ErrorToastService,
|
||||
private readonly stateService: StateService,
|
||||
) {}
|
||||
|
||||
async ionViewDidEnter() {
|
||||
this.stateService.setupType = 'fresh'
|
||||
if (this.swiper) {
|
||||
this.swiper.allowTouchMove = false
|
||||
}
|
||||
|
||||
@@ -2,16 +2,11 @@ import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { LoadingPage } from './loading.page'
|
||||
import { LoadingPage, ToMessagePipe } from './loading.page'
|
||||
import { LoadingPageRoutingModule } from './loading-routing.module'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
LoadingPageRoutingModule,
|
||||
],
|
||||
declarations: [LoadingPage],
|
||||
imports: [CommonModule, FormsModule, IonicModule, LoadingPageRoutingModule],
|
||||
declarations: [LoadingPage, ToMessagePipe],
|
||||
})
|
||||
export class LoadingPageModule { }
|
||||
export class LoadingPageModule {}
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
<ion-grid>
|
||||
<ion-row class="ion-align-items-center">
|
||||
<ion-col class="ion-text-center">
|
||||
<ion-card color="dark">
|
||||
<ion-card
|
||||
*ngIf="{ decimal: progress$ | async } as progress"
|
||||
color="dark"
|
||||
>
|
||||
<ion-card-header>
|
||||
<ion-card-title>Initializing Embassy</ion-card-title>
|
||||
<div class="center-wrapper">
|
||||
<ion-card-subtitle *ngIf="stateService.dataProgress">
|
||||
Progress: {{ (stateService.dataProgress * 100).toFixed(0)}}%
|
||||
<ion-card-subtitle *ngIf="progress.decimal as decimal">
|
||||
Progress: {{ (decimal * 100).toFixed(0)}}%
|
||||
</ion-card-subtitle>
|
||||
</div>
|
||||
</ion-card-header>
|
||||
@@ -21,10 +24,10 @@
|
||||
padding-bottom: 20px;
|
||||
margin-bottom: 40px;
|
||||
"
|
||||
[type]="stateService.dataProgress ? 'determinate' : 'indeterminate'"
|
||||
[value]="stateService.dataProgress"
|
||||
[type]="progress.decimal ? 'determinate' : 'indeterminate'"
|
||||
[value]="progress.decimal || 0"
|
||||
></ion-progress-bar>
|
||||
<p>Setting up your Embassy. This can take a while.</p>
|
||||
<p>{{ progress.decimal | toMessage }}</p>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</ion-col>
|
||||
|
||||
@@ -8,14 +8,16 @@ import { StateService } from 'src/app/services/state.service'
|
||||
styleUrls: ['loading.page.scss'],
|
||||
})
|
||||
export class LoadingPage {
|
||||
readonly progress$ = this.stateService.dataProgress$
|
||||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
private navCtrl: NavController,
|
||||
private readonly stateService: StateService,
|
||||
private readonly navCtrl: NavController,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.stateService.pollDataTransferProgress()
|
||||
const progSub = this.stateService.dataCompletionSubject.subscribe(
|
||||
const progSub = this.stateService.dataCompletionSubject$.subscribe(
|
||||
async complete => {
|
||||
if (complete) {
|
||||
progSub.unsubscribe()
|
||||
@@ -25,3 +27,30 @@ export class LoadingPage {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
|
||||
@Pipe({
|
||||
name: 'toMessage',
|
||||
})
|
||||
export class ToMessagePipe implements PipeTransform {
|
||||
constructor(private readonly stateService: StateService) {}
|
||||
|
||||
transform(progress: number | null): string {
|
||||
switch (this.stateService.setupType) {
|
||||
case 'fresh':
|
||||
case 'attach':
|
||||
return 'Setting up your Embassy'
|
||||
case 'restore':
|
||||
return 'Restoring data. This can take a while.'
|
||||
case 'transfer':
|
||||
if (!progress) {
|
||||
return 'Preparing data. Depending on how much data you have, this could take up to 1 hour'
|
||||
} else {
|
||||
return 'Transferring data'
|
||||
}
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ export class RecoverPage {
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.stateService.setupType = 'restore'
|
||||
await this.getDrives()
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ export class TransferPage {
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.stateService.setupType = 'transfer'
|
||||
await this.getDrives()
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ type Encrypted = {
|
||||
|
||||
export type StatusRes = {
|
||||
'bytes-transferred': number
|
||||
'total-bytes': number
|
||||
'total-bytes': number | null
|
||||
complete: boolean
|
||||
} | null
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export class MockApiService extends ApiService {
|
||||
|
||||
return {
|
||||
'bytes-transferred': restoreOrMigrate ? progress : 0,
|
||||
'total-bytes': restoreOrMigrate ? total : 0,
|
||||
'total-bytes': restoreOrMigrate ? total : null,
|
||||
complete: progress === total,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,18 @@ import { pauseFor, ErrorToastService } from '@start9labs/shared'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class StateService {
|
||||
setupType?: 'fresh' | 'restore' | 'attach' | 'transfer'
|
||||
|
||||
recoverySource?: RecoverySource
|
||||
recoveryPassword?: string
|
||||
|
||||
dataTransferProgress?: {
|
||||
bytesTransferred: number
|
||||
totalBytes: number
|
||||
totalBytes: number | null
|
||||
complete: boolean
|
||||
}
|
||||
dataProgress = 0
|
||||
dataCompletionSubject = new BehaviorSubject(false)
|
||||
dataProgress$ = new BehaviorSubject<number>(0)
|
||||
dataCompletionSubject$ = new BehaviorSubject(false)
|
||||
|
||||
constructor(
|
||||
private readonly api: ApiService,
|
||||
@@ -27,7 +29,7 @@ export class StateService {
|
||||
await pauseFor(500)
|
||||
|
||||
if (this.dataTransferProgress?.complete) {
|
||||
this.dataCompletionSubject.next(true)
|
||||
this.dataCompletionSubject$.next(true)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -41,9 +43,10 @@ export class StateService {
|
||||
complete: progress.complete,
|
||||
}
|
||||
if (this.dataTransferProgress.totalBytes) {
|
||||
this.dataProgress =
|
||||
this.dataProgress$.next(
|
||||
this.dataTransferProgress.bytesTransferred /
|
||||
this.dataTransferProgress.totalBytes
|
||||
this.dataTransferProgress.totalBytes,
|
||||
)
|
||||
}
|
||||
} catch (e: any) {
|
||||
this.errorToastService.present({
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
$wide-modal: 900px;
|
||||
$medium-modal: 600px;
|
||||
|
||||
body {
|
||||
-webkit-user-select: text;
|
||||
@@ -24,17 +23,16 @@ ion-alert {
|
||||
}
|
||||
}
|
||||
|
||||
ion-modal::part(content) {
|
||||
position: absolute;
|
||||
height: 90% !important;
|
||||
top: 5%;
|
||||
width: 90% !important;
|
||||
left: 5%;
|
||||
display: block;
|
||||
|
||||
border-radius: 6px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.03);
|
||||
box-shadow: 0 32px 64px rgba(0, 0, 0, 0.2);
|
||||
ion-modal {
|
||||
--max-height: 600px;
|
||||
&::part(content) {
|
||||
width: 90% !important;
|
||||
left: 5%;
|
||||
|
||||
border-radius: 6px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.03);
|
||||
box-shadow: 0 32px 64px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.alertlike-modal {
|
||||
@@ -47,33 +45,8 @@ ion-modal::part(content) {
|
||||
}
|
||||
}
|
||||
|
||||
.medium-modal {
|
||||
&::part(content) {
|
||||
position: absolute;
|
||||
height: 80% !important;
|
||||
top: 10%;
|
||||
width: 90% !important;
|
||||
left: 5% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
.medium-modal {
|
||||
&::part(content) {
|
||||
position: absolute;
|
||||
height: 80% !important;
|
||||
top: 10%;
|
||||
width: $medium-modal !important;
|
||||
left: calc((100vw - $medium-modal) / 2) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
ion-modal::part(content) {
|
||||
position: absolute;
|
||||
height: 80% !important;
|
||||
top: 10%;
|
||||
width: $wide-modal !important;
|
||||
left: calc((100vw - $wide-modal) / 2) !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user