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
very cool
MattDHill committed May 9, 2023
commit 70e80ccdf3a896dac03be4b74858822a0311daa7
27 changes: 25 additions & 2 deletions frontend/package-lock.json

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

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
@@ -63,6 +63,7 @@
"cbor": "npm:@jprochazk/cbor@^0.4.9",
"cbor-web": "^8.1.0",
"core-js": "^3.21.1",
"cronstrue": "^2.21.0",
"dompurify": "^2.3.6",
"fast-json-patch": "^3.1.1",
"fuse.js": "^6.4.6",
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button defaultHref="backups"></ion-back-button>
<ion-back-button defaultHref="system/backups"></ion-back-button>
</ion-buttons>
<ion-title>{{
type === 'create' ? 'Create Backup' : 'Restore From Backup'
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<ion-header>
<ion-toolbar>
<ion-title>Create New Job</ion-title>
<ion-buttons slot="end">
<ion-button (click)="dismiss()">
<ion-icon slot="icon-only" name="close"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>

<ion-content class="ion-padding">
<ion-item-group>
<h4 class="input-label">Backup Target</h4>
<ion-item button (click)="presentModalTarget()">
<ion-label>
<h2>{{ job.target?.type || 'Select target' }}</h2>
</ion-label>
</ion-item>

<h4 class="input-label">Packages</h4>
<ion-item button (click)="presentModalPackages()">
<ion-label>
<h2>{{ job['package-ids'].length + ' packages selected' }}</h2>
</ion-label>
</ion-item>

<h4 class="input-label">Schedule</h4>
<ion-item>
<ion-input placeholder="* * * * *" [(ngModel)]="job.cron"></ion-input>
</ion-item>

<p *ngIf="job.cron | toHumanCron as human" class="ion-padding-start">
<ion-text [color]="human.color"> {{ human.message }} </ion-text>
</p>
</ion-item-group>
</ion-content>

<ion-footer>
<ion-toolbar>
<ion-buttons slot="end" class="ion-padding-end">
<ion-button
*ngIf="job.target && job.cron"
fill="solid"
color="primary"
[disabled]="saving"
(click)="save()"
class="enter-click btn-128"
[class.no-click]="saving"
>
Save
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-footer>
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Component } from '@angular/core'
import { BackupTarget } from 'src/app/services/api/api.types'
import { ModalController } from '@ionic/angular'
import { BackupSelectPage } from 'src/app/modals/backup-select/backup-select.page'
import { BackupDrivesComponent } from '../backup-drives/backup-drives.component'
import { ApiService } from 'src/app/services/api/embassy-api.service'

@Component({
selector: 'backup-job-modal',
templateUrl: './backup-job-modal.page.html',
styleUrls: ['./backup-jobs.page.scss'],
})
export class BackupJobModal {
readonly docsUrl =
'https://docs.start9.com/latest/user-manual/backups/backup-jobs'
saving = false

job = new BackupJob()

constructor(
private readonly modalCtrl: ModalController,
private readonly api: ApiService,
) {}

async dismiss() {
this.modalCtrl.dismiss()
}

async presentModalTarget() {
const modal = await this.modalCtrl.create({
presentingElement: await this.modalCtrl.getTop(),
component: BackupDrivesComponent,
})

modal.onWillDismiss().then(res => {
if (res.data) {
this.job.target = res.data
}
})

await modal.present()
}

async presentModalPackages() {
const modal = await this.modalCtrl.create({
presentingElement: await this.modalCtrl.getTop(),
component: BackupSelectPage,
})

modal.onWillDismiss().then(res => {
if (res.data) {
this.job['package-ids'] = res.data
}
})

await modal.present()
}

async save() {
this.modalCtrl.dismiss(this.job)
}
}

export class BackupJob {
target: BackupTarget | null = null
cron = '0 2 * * *' // '* * * * * *' https://cloud.google.com/scheduler/docs/configuring/cron-job-schedules
'package-ids' = []
}
Original file line number Diff line number Diff line change
@@ -3,6 +3,9 @@ import { RouterModule, Routes } from '@angular/router'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { BackupJobsPage } from './backup-jobs.page'
import { BackupJobModal } from './backup-job-modal.page'
import { ToHumanCronPipe } from './pipes'
import { FormsModule } from '@angular/forms'

const routes: Routes = [
{
@@ -12,7 +15,12 @@ const routes: Routes = [
]

@NgModule({
imports: [CommonModule, IonicModule, RouterModule.forChild(routes)],
declarations: [BackupJobsPage],
imports: [
CommonModule,
IonicModule,
RouterModule.forChild(routes),
FormsModule,
],
declarations: [BackupJobsPage, ToHumanCronPipe, BackupJobModal],
})
export class BackupJobsPageModule {}
Original file line number Diff line number Diff line change
@@ -1,10 +1,43 @@
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button defaultHref="backups"></ion-back-button>
<ion-back-button defaultHref="system/backups"></ion-back-button>
</ion-buttons>
<ion-title>Backup Jobs</ion-title>
</ion-toolbar>
</ion-header>

<ion-content class="ion-padding"> </ion-content>
<ion-content class="ion-padding">
<ion-item-group>
<!-- always -->
<ion-item>
<ion-label>
<h2>
Scheduling automatic backups on a recurring basis is an excellent way
to ensure your Embassy data is safely backed up. Your Embassy will
issue a notification whenever one of your scheduled backups succeeds
or fails.
<a [href]="docsUrl" target="_blank" rel="noreferrer"
>View instructions</a
>
</h2>
</ion-label>
</ion-item>

<ion-item-divider>Scheduled Jobs</ion-item-divider>

<ion-item button detail="false" (click)="presentModalCreate()">
<ion-icon slot="start" name="add" color="dark"></ion-icon>
<ion-label>
<b>Create New Job</b>
</ion-label>
</ion-item>

<ion-item *ngFor="let job of jobs">
<ion-label>
<h2>{{ job.cron | toHumanCron }}</h2>
<p>{{ job.target }}</p>
</ion-label>
</ion-item>
</ion-item-group>
</ion-content>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ion-input {
font-family: 'Courier New';
font-weight: bold;
--placeholder-font-weight: 400;
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
import { Component } from '@angular/core'
import { ModalController } from '@ionic/angular'
import { BackupJob, BackupJobModal } from './backup-job-modal.page'

@Component({
selector: 'backup-jobs',
templateUrl: './backup-jobs.page.html',
styleUrls: ['./backup-jobs.page.scss'],
})
export class BackupJobsPage {
constructor() {}
readonly docsUrl =
'https://docs.start9.com/latest/user-manual/backups/backup-jobs'
jobs: BackupJob[] = []

constructor(private readonly modalCtrl: ModalController) {}

async presentModalCreate() {
const modal = await this.modalCtrl.create({
presentingElement: await this.modalCtrl.getTop(),
component: BackupJobModal,
})

modal.onWillDismiss().then(res => {
if (res.data) {
this.jobs.unshift(res.data.job)
}
})

await modal.present()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Pipe, PipeTransform } from '@angular/core'
import cronstrue from 'cronstrue'

@Pipe({
name: 'toHumanCron',
})
export class ToHumanCronPipe implements PipeTransform {
transform(cron: string): { message: string; color: string } {
try {
return {
message: cronstrue.toString(cron, {
verbose: true,
throwExceptionOnParseError: true,
}),
color: 'success',
}
} catch (e) {
return {
message: e as string,
color: 'danger',
}
}
}
}
Original file line number Diff line number Diff line change
@@ -26,8 +26,8 @@ <h2>Restore From Backup</h2>
<ion-item button routerLink="jobs">
<ion-icon slot="start" name="time-outline"></ion-icon>
<ion-label>
<h2>Automated Backups</h2>
<p>Schedule jobs to perform automatic backups</p>
<h2>Schedule Backup Jobs</h2>
<p>Perform custom backups automatically at a scheduled time</p>
</ion-label>
</ion-item>
</ion-item-group>