mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-01 21:13:09 +00:00
336 lines
8.7 KiB
TypeScript
336 lines
8.7 KiB
TypeScript
import { Component, EventEmitter, Input, Output } from '@angular/core'
|
|
import { BackupService } from './backup.service'
|
|
import {
|
|
CifsBackupTarget,
|
|
DiskBackupTarget,
|
|
RR,
|
|
} from 'src/app/services/api/api.types'
|
|
import {
|
|
ActionSheetController,
|
|
AlertController,
|
|
LoadingController,
|
|
ModalController,
|
|
} from '@ionic/angular'
|
|
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
|
import { InputSpec } from 'start-sdk/lib/config/configTypes'
|
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|
import { ErrorToastService } from '@start9labs/shared'
|
|
import { MappedBackupTarget } from 'src/app/types/mapped-backup-target'
|
|
|
|
type BackupType = 'create' | 'restore'
|
|
|
|
@Component({
|
|
selector: 'backup-drives',
|
|
templateUrl: './backup-drives.component.html',
|
|
styleUrls: ['./backup-drives.component.scss'],
|
|
})
|
|
export class BackupDrivesComponent {
|
|
@Input() type!: BackupType
|
|
@Output() onSelect: EventEmitter<
|
|
MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>
|
|
> = new EventEmitter()
|
|
loadingText = ''
|
|
|
|
constructor(
|
|
private readonly loadingCtrl: LoadingController,
|
|
private readonly actionCtrl: ActionSheetController,
|
|
private readonly alertCtrl: AlertController,
|
|
private readonly modalCtrl: ModalController,
|
|
private readonly embassyApi: ApiService,
|
|
private readonly errToast: ErrorToastService,
|
|
private readonly backupService: BackupService,
|
|
) {}
|
|
|
|
get loading() {
|
|
return this.backupService.loading
|
|
}
|
|
|
|
get loadingError() {
|
|
return this.backupService.loadingError
|
|
}
|
|
|
|
get drives() {
|
|
return this.backupService.drives
|
|
}
|
|
|
|
get cifs() {
|
|
return this.backupService.cifs
|
|
}
|
|
|
|
ngOnInit() {
|
|
this.loadingText =
|
|
this.type === 'create'
|
|
? 'Fetching Backup Targets'
|
|
: 'Fetching Backup Sources'
|
|
this.backupService.getBackupTargets()
|
|
}
|
|
|
|
select(
|
|
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
|
|
): void {
|
|
if (target.entry.type === 'cifs' && !target.entry.mountable) {
|
|
const message =
|
|
'Unable to connect to Network Folder. Ensure (1) target computer is connected to the same LAN as your Start9 Server, (2) target folder is being shared, and (3) hostname, path, and credentials are accurate.'
|
|
this.presentAlertError(message)
|
|
return
|
|
}
|
|
|
|
if (this.type === 'restore' && !target.hasValidBackup) {
|
|
const message = `${
|
|
target.entry.type === 'cifs' ? 'Network Folder' : 'Drive partition'
|
|
} does not contain a valid Start9 Server backup.`
|
|
this.presentAlertError(message)
|
|
return
|
|
}
|
|
|
|
this.onSelect.emit(target)
|
|
}
|
|
|
|
async presentModalAddCifs(): Promise<void> {
|
|
const modal = await this.modalCtrl.create({
|
|
component: GenericFormPage,
|
|
componentProps: {
|
|
title: 'New Network Folder',
|
|
spec: CifsSpec,
|
|
buttons: [
|
|
{
|
|
text: 'Connect',
|
|
handler: (value: RR.AddBackupTargetReq) => {
|
|
return this.addCifs(value)
|
|
},
|
|
isSubmit: true,
|
|
},
|
|
],
|
|
},
|
|
})
|
|
await modal.present()
|
|
}
|
|
|
|
async presentActionCifs(
|
|
event: Event,
|
|
target: MappedBackupTarget<CifsBackupTarget>,
|
|
index: number,
|
|
): Promise<void> {
|
|
event.stopPropagation()
|
|
|
|
const entry = target.entry as CifsBackupTarget
|
|
|
|
const action = await this.actionCtrl.create({
|
|
header: entry.hostname,
|
|
subHeader: 'Shared Folder',
|
|
mode: 'ios',
|
|
buttons: [
|
|
{
|
|
text: 'Forget',
|
|
icon: 'trash',
|
|
role: 'destructive',
|
|
handler: () => {
|
|
this.deleteCifs(target.id, index)
|
|
},
|
|
},
|
|
{
|
|
text: 'Edit',
|
|
icon: 'pencil',
|
|
handler: () => {
|
|
this.presentModalEditCifs(target.id, entry, index)
|
|
},
|
|
},
|
|
],
|
|
})
|
|
|
|
await action.present()
|
|
}
|
|
|
|
private async presentAlertError(message: string): Promise<void> {
|
|
const alert = await this.alertCtrl.create({
|
|
header: 'Error',
|
|
message,
|
|
buttons: ['OK'],
|
|
})
|
|
await alert.present()
|
|
}
|
|
|
|
private async addCifs(value: RR.AddBackupTargetReq): Promise<boolean> {
|
|
const loader = await this.loadingCtrl.create({
|
|
message: 'Testing connectivity to shared folder...',
|
|
})
|
|
await loader.present()
|
|
|
|
try {
|
|
const res = await this.embassyApi.addBackupTarget(value)
|
|
const [id, entry] = Object.entries(res)[0]
|
|
this.backupService.cifs.unshift({
|
|
id,
|
|
hasValidBackup: this.backupService.hasValidBackup(entry),
|
|
entry,
|
|
})
|
|
return true
|
|
} catch (e: any) {
|
|
this.errToast.present(e)
|
|
return false
|
|
} finally {
|
|
loader.dismiss()
|
|
}
|
|
}
|
|
|
|
private async presentModalEditCifs(
|
|
id: string,
|
|
entry: CifsBackupTarget,
|
|
index: number,
|
|
): Promise<void> {
|
|
const { hostname, path, username } = entry
|
|
|
|
const modal = await this.modalCtrl.create({
|
|
component: GenericFormPage,
|
|
componentProps: {
|
|
title: 'Update Shared Folder',
|
|
spec: CifsSpec,
|
|
buttons: [
|
|
{
|
|
text: 'Save',
|
|
handler: (value: RR.AddBackupTargetReq) => {
|
|
return this.editCifs({ id, ...value }, index)
|
|
},
|
|
isSubmit: true,
|
|
},
|
|
],
|
|
initialValue: {
|
|
hostname,
|
|
path,
|
|
username,
|
|
},
|
|
},
|
|
})
|
|
await modal.present()
|
|
}
|
|
|
|
private async editCifs(
|
|
value: RR.UpdateBackupTargetReq,
|
|
index: number,
|
|
): Promise<void> {
|
|
const loader = await this.loadingCtrl.create({
|
|
message: 'Testing connectivity to shared folder...',
|
|
})
|
|
await loader.present()
|
|
|
|
try {
|
|
const res = await this.embassyApi.updateBackupTarget(value)
|
|
this.backupService.cifs[index].entry = Object.values(res)[0]
|
|
} catch (e: any) {
|
|
this.errToast.present(e)
|
|
} finally {
|
|
loader.dismiss()
|
|
}
|
|
}
|
|
|
|
private async deleteCifs(id: string, index: number): Promise<void> {
|
|
const loader = await this.loadingCtrl.create({
|
|
message: 'Removing...',
|
|
})
|
|
await loader.present()
|
|
|
|
try {
|
|
await this.embassyApi.removeBackupTarget({ id })
|
|
this.backupService.cifs.splice(index, 1)
|
|
} catch (e: any) {
|
|
this.errToast.present(e)
|
|
} finally {
|
|
loader.dismiss()
|
|
}
|
|
}
|
|
|
|
refresh() {
|
|
this.backupService.getBackupTargets()
|
|
}
|
|
}
|
|
|
|
@Component({
|
|
selector: 'backup-drives-header',
|
|
templateUrl: './backup-drives-header.component.html',
|
|
styleUrls: ['./backup-drives.component.scss'],
|
|
})
|
|
export class BackupDrivesHeaderComponent {
|
|
@Input() type!: BackupType
|
|
@Output() onClose: EventEmitter<void> = new EventEmitter()
|
|
|
|
constructor(private readonly backupService: BackupService) {}
|
|
|
|
get loading() {
|
|
return this.backupService.loading
|
|
}
|
|
|
|
refresh() {
|
|
this.backupService.getBackupTargets()
|
|
}
|
|
}
|
|
|
|
@Component({
|
|
selector: 'backup-drives-status',
|
|
templateUrl: './backup-drives-status.component.html',
|
|
styleUrls: ['./backup-drives.component.scss'],
|
|
})
|
|
export class BackupDrivesStatusComponent {
|
|
@Input() type!: BackupType
|
|
@Input() hasValidBackup!: boolean
|
|
}
|
|
|
|
const CifsSpec: InputSpec = {
|
|
hostname: {
|
|
type: 'text',
|
|
name: 'Hostname',
|
|
description:
|
|
'The hostname of your target device on the Local Area Network.',
|
|
inputmode: 'text',
|
|
placeholder: `e.g. 'My Computer' OR 'my-computer.local'`,
|
|
minLength: null,
|
|
maxLength: null,
|
|
patterns: [],
|
|
required: true,
|
|
masked: false,
|
|
default: null,
|
|
warning: null,
|
|
},
|
|
path: {
|
|
type: 'text',
|
|
name: 'Path',
|
|
description: `On Windows, this is the fully qualified path to the shared folder, (e.g. /Desktop/my-folder).\n\n On Linux and Mac, this is the literal name of the shared folder (e.g. my-shared-folder).`,
|
|
inputmode: 'text',
|
|
placeholder: 'e.g. my-shared-folder or /Desktop/my-folder',
|
|
patterns: [],
|
|
minLength: null,
|
|
maxLength: null,
|
|
required: true,
|
|
masked: false,
|
|
default: null,
|
|
warning: null,
|
|
},
|
|
username: {
|
|
type: 'text',
|
|
name: 'Username',
|
|
description: `On Linux, this is the samba username you created when sharing the folder.\n\n On Mac and Windows, this is the username of the user who is sharing the folder.`,
|
|
inputmode: 'text',
|
|
minLength: null,
|
|
maxLength: null,
|
|
placeholder: null,
|
|
patterns: [],
|
|
required: true,
|
|
masked: false,
|
|
default: null,
|
|
warning: null,
|
|
},
|
|
password: {
|
|
type: 'text',
|
|
name: 'Password',
|
|
description: `On Linux, this is the samba password you created when sharing the folder.\n\n On Mac and Windows, this is the password of the user who is sharing the folder.`,
|
|
inputmode: 'text',
|
|
placeholder: null,
|
|
minLength: null,
|
|
maxLength: null,
|
|
patterns: [],
|
|
required: false,
|
|
masked: true,
|
|
default: null,
|
|
warning: null,
|
|
},
|
|
}
|