Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/automated backups #2142

Merged
merged 25 commits into from
May 9, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix checkboxes and add API for deleting backup runs
MattDHill committed May 9, 2023
commit 086d6904b8624517eae9834012e5d3be59895d0c
18 changes: 2 additions & 16 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -8,73 +8,85 @@
</ion-header>

<ion-content class="ion-padding-top">
<ion-item-group>
<div class="grid-fixed">
<ion-grid class="ion-padding">
<ion-row class="grid-headings">
<ion-col size="2.5">Started At</ion-col>
<ion-col size="2">Duration</ion-col>
<ion-col size="1.5">Result</ion-col>
<ion-col size="2.5">Job</ion-col>
<ion-col size="2.5">Target</ion-col>
<ion-col size="1"></ion-col>
</ion-row>
<ion-item-divider>
Past Events
<ion-button
class="ion-padding-start"
strong
color="primary"
[fill]="empty ? 'outline' : 'solid'"
(click)="deleteSelected()"
[disabled]="empty"
>
Delete Selected
</ion-button>
</ion-item-divider>
<div class="grid-fixed">
<ion-grid class="ion-padding">
<ion-row class="grid-headings">
<ion-col size="3" class="inline">
<div class="checkbox" (click)="toggleAll(runs)">
<ion-icon
[name]="empty ? 'square-outline' : count === runs.length ? 'checkbox-outline' : 'remove-circle-outline'"
></ion-icon>
</div>
Started At
</ion-col>
<ion-col size="2">Duration</ion-col>
<ion-col size="1.5">Result</ion-col>
<ion-col size="2.5">Job</ion-col>
<ion-col size="3">Target</ion-col>
</ion-row>

<ng-container *ngIf="runs$ | async as runs; else loading">
<ion-row
*ngFor="let run of runs; let i = index"
class="ion-align-items-center grid-row-border"
>
<ion-col size="2.5"
>{{ run['started-at'] | date : 'medium' }}</ion-col
>
<ion-col size="2"
>{{ run['started-at']| duration : run['completed-at'] }}
Minutes</ion-col
>
<ion-col size="1.5">
<ion-icon
*ngIf="run.report | hasError; else noError"
name="close"
color="danger"
></ion-icon>
<ng-template #noError>
<ion-icon name="checkmark" color="success"></ion-icon>
</ng-template>
<a (click)="presentModalReport(run)"> Report </a>
</ion-col>
<ion-col size="2.5">{{ run.job.name || 'No job' }}</ion-col>
<ion-col size="2.5" class="inline">
<!-- loading -->
<ng-container *ngIf="loading$ | async; else loaded">
<ion-row
*ngFor="let row of ['', '', '']"
class="ion-align-items-center grid-row-border"
>
<ion-col>
<ion-skeleton-text animated></ion-skeleton-text>
</ion-col>
</ion-row>
</ng-container>
<!-- loaded -->
<ng-template #loaded>
<ion-row
*ngFor="let run of runs"
class="ion-align-items-center grid-row-border"
>
<ion-col size="3" class="inline">
<div class="checkbox" (click)="toggleChecked(run.id)">
<ion-icon
[name]="(run.job.target | getDisplayInfo).icon"
size="small"
[name]="selected[run.id] ? 'checkbox-outline' : 'square-outline'"
></ion-icon>
&nbsp; {{ run.job.target.name }}
</ion-col>
<ion-col size="1">
<ion-buttons style="float: right">
<ion-button
size="small"
(click)="presentAlertDelete(run.id, i)"
>
<ion-icon name="trash"></ion-icon>
</ion-button>
</ion-buttons>
</ion-col>
</ion-row>
</ng-container>
<!-- loading -->
<ng-template #loading>
<ion-row
*ngFor="let row of ['', '', '']"
class="ion-align-items-center grid-row-border"
>
<ion-col>
<ion-skeleton-text animated></ion-skeleton-text>
</ion-col>
</ion-row>
</ng-template>
</ion-grid>
</div>
</ion-item-group>
</div>
{{ run['started-at'] | date : 'medium' }}
</ion-col>
<ion-col size="2">
{{ run['started-at']| duration : run['completed-at'] }} Minutes
</ion-col>
<ion-col size="1.5">
<ion-icon
*ngIf="run.report | hasError; else noError"
name="close"
color="danger"
></ion-icon>
<ng-template #noError>
<ion-icon name="checkmark" color="success"></ion-icon>
</ng-template>
<a (click)="presentModalReport(run)">Report</a>
</ion-col>
<ion-col size="2.5">{{ run.job.name || 'No job' }}</ion-col>
<ion-col size="3" class="inline">
<ion-icon
[name]="(run.job.target | getDisplayInfo).icon"
size="small"
></ion-icon>
&nbsp; {{ run.job.target.name }}
</ion-col>
</ion-row>
</ng-template>
</ion-grid>
</div>
</ion-content>
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { Component } from '@angular/core'
import { Pipe, PipeTransform } from '@angular/core'
import { BackupReport, BackupRun } from 'src/app/services/api/api.types'
import {
AlertController,
LoadingController,
ModalController,
} from '@ionic/angular'
import { LoadingController, ModalController } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ErrorToastService } from '@start9labs/shared'
import { catchError, defer } from 'rxjs'
import { BehaviorSubject } from 'rxjs'
import { BackupReportPage } from 'src/app/modals/backup-report/backup-report.page'

@Component({
@@ -17,40 +13,33 @@ import { BackupReportPage } from 'src/app/modals/backup-report/backup-report.pag
styleUrls: ['./backup-history.page.scss'],
})
export class BackupHistoryPage {
readonly runs$ = defer(() => this.api.getBackupRuns({})).pipe(
catchError(e => {
this.errToast.present(e)
return []
}),
)
selected: Record<string, boolean> = {}
runs: BackupRun[] = []
loading$ = new BehaviorSubject(true)

constructor(
private readonly modalCtrl: ModalController,
private readonly alertCtrl: AlertController,
private readonly loadingCtrl: LoadingController,
private readonly errToast: ErrorToastService,
private readonly api: ApiService,
) {}

async presentAlertDelete(id: string, index: number) {
const alert = await this.alertCtrl.create({
header: 'Confirm',
message: 'Delete backup record? This actions cannot be undone.',
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Delete',
handler: () => {
this.delete(id, index)
},
cssClass: 'enter-click',
},
],
})
await alert.present()
async ngOnInit() {
try {
this.runs = await this.api.getBackupRuns({})
} catch (e: any) {
this.errToast.present(e)
} finally {
this.loading$.next(false)
}
}

get empty() {
return this.count === 0
}

get count() {
return Object.keys(this.selected).length
}

async presentModalReport(run: BackupRun) {
@@ -64,14 +53,34 @@ export class BackupHistoryPage {
await modal.present()
}

async delete(id: string, index: number): Promise<void> {
async toggleChecked(id: string) {
if (this.selected[id]) {
delete this.selected[id]
} else {
this.selected[id] = true
}
}

async toggleAll(runs: BackupRun[]) {
if (this.empty) {
runs.forEach(r => (this.selected[r.id] = true))
} else {
this.selected = {}
}
}

async deleteSelected(): Promise<void> {
const ids = Object.keys(this.selected)

const loader = await this.loadingCtrl.create({
message: 'Removing...',
message: 'Deleting...',
})
await loader.present()

try {
await this.api.removeBackupTarget({ id })
await this.api.deleteBackupRuns({ ids })
this.selected = {}
this.runs = this.runs.filter(r => !ids.includes(r.id))
} catch (e: any) {
this.errToast.present(e)
} finally {
Loading