From 91d0687c0566e70ff92fa84f1483883e37021c81 Mon Sep 17 00:00:00 2001 From: Enno Gotthold Date: Fri, 1 Nov 2024 18:01:09 +0100 Subject: [PATCH] Items: Implement copy button inside the details view --- .../dialog-item-copy.component.html | 21 +++++ .../dialog-item-copy.component.scss | 3 + .../dialog-item-copy.component.spec.ts | 39 ++++++++++ .../dialog-item-copy.component.ts | 48 ++++++++++++ .../distro/edit/distro-edit.component.html | 2 +- .../distro/edit/distro-edit.component.ts | 70 +++++++++++++---- .../items/file/edit/file-edit.component.html | 2 +- .../items/file/edit/file-edit.component.ts | 66 +++++++++++++--- .../image/edit/image-edit.component.html | 2 +- .../items/image/edit/image-edit.component.ts | 66 +++++++++++++--- .../edit/management-class-edit.component.html | 2 +- .../edit/management-class-edit.component.ts | 78 +++++++++++++++---- .../package/edit/package-edit.component.html | 2 +- .../package/edit/package-edit.component.ts | 70 +++++++++++++---- .../profile/edit/profile-edit.component.html | 2 +- .../profile/edit/profile-edit.component.ts | 70 +++++++++++++---- .../edit/repository-edit.component.html | 2 +- .../edit/repository-edit.component.ts | 66 +++++++++++++--- .../system/edit/system-edit.component.html | 2 +- .../system/edit/system-edit.component.ts | 68 ++++++++++++---- 20 files changed, 570 insertions(+), 111 deletions(-) create mode 100644 projects/cobbler-frontend/src/app/common/dialog-item-copy/dialog-item-copy.component.html create mode 100644 projects/cobbler-frontend/src/app/common/dialog-item-copy/dialog-item-copy.component.scss create mode 100644 projects/cobbler-frontend/src/app/common/dialog-item-copy/dialog-item-copy.component.spec.ts create mode 100644 projects/cobbler-frontend/src/app/common/dialog-item-copy/dialog-item-copy.component.ts diff --git a/projects/cobbler-frontend/src/app/common/dialog-item-copy/dialog-item-copy.component.html b/projects/cobbler-frontend/src/app/common/dialog-item-copy/dialog-item-copy.component.html new file mode 100644 index 00000000..06910ba6 --- /dev/null +++ b/projects/cobbler-frontend/src/app/common/dialog-item-copy/dialog-item-copy.component.html @@ -0,0 +1,21 @@ +

Copy {{ data.itemType }}

+ + + Old name + + + + + New name + + + + + + + diff --git a/projects/cobbler-frontend/src/app/common/dialog-item-copy/dialog-item-copy.component.scss b/projects/cobbler-frontend/src/app/common/dialog-item-copy/dialog-item-copy.component.scss new file mode 100644 index 00000000..cb97d439 --- /dev/null +++ b/projects/cobbler-frontend/src/app/common/dialog-item-copy/dialog-item-copy.component.scss @@ -0,0 +1,3 @@ +.form-field-full-width { + width: 100%; +} diff --git a/projects/cobbler-frontend/src/app/common/dialog-item-copy/dialog-item-copy.component.spec.ts b/projects/cobbler-frontend/src/app/common/dialog-item-copy/dialog-item-copy.component.spec.ts new file mode 100644 index 00000000..75faa85b --- /dev/null +++ b/projects/cobbler-frontend/src/app/common/dialog-item-copy/dialog-item-copy.component.spec.ts @@ -0,0 +1,39 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { + MAT_DIALOG_DATA, + MatDialogModule, + MatDialogRef, +} from '@angular/material/dialog'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { provideRouter } from '@angular/router'; + +import { DialogItemCopyComponent } from './dialog-item-copy.component'; + +describe('DialogItemRenameComponent', () => { + let component: DialogItemCopyComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DialogItemCopyComponent, MatDialogModule, NoopAnimationsModule], + providers: [ + { + provide: MatDialogRef, + useValue: {}, + }, + { + provide: MAT_DIALOG_DATA, + useValue: { itemType: 'Test', itemName: 'test', itemUid: '' }, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(DialogItemCopyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/cobbler-frontend/src/app/common/dialog-item-copy/dialog-item-copy.component.ts b/projects/cobbler-frontend/src/app/common/dialog-item-copy/dialog-item-copy.component.ts new file mode 100644 index 00000000..3f1a46b3 --- /dev/null +++ b/projects/cobbler-frontend/src/app/common/dialog-item-copy/dialog-item-copy.component.ts @@ -0,0 +1,48 @@ +import { + ChangeDetectionStrategy, + Component, + inject, + Inject, + model, +} from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatButton, MatButtonModule } from '@angular/material/button'; +import { + MAT_DIALOG_DATA, + MatDialogModule, + MatDialogRef, +} from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; + +export interface DialogItemCopyData { + itemType: string; + itemName: string; + itemUid: string; +} + +@Component({ + selector: 'cobbler-dialog-item-copy', + standalone: true, + imports: [ + MatDialogModule, + MatButtonModule, + ReactiveFormsModule, + MatFormFieldModule, + MatInputModule, + FormsModule, + ], + templateUrl: './dialog-item-copy.component.html', + styleUrl: './dialog-item-copy.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DialogItemCopyComponent { + readonly dialogRef = inject(MatDialogRef); + readonly dialogCloseSignal = model(''); + + constructor(@Inject(MAT_DIALOG_DATA) public data: DialogItemCopyData) {} + + onNoClick(): void { + this.dialogRef.close(); + } +} diff --git a/projects/cobbler-frontend/src/app/items/distro/edit/distro-edit.component.html b/projects/cobbler-frontend/src/app/items/distro/edit/distro-edit.component.html index 44e260dc..2c9f73f4 100644 --- a/projects/cobbler-frontend/src/app/items/distro/edit/distro-edit.component.html +++ b/projects/cobbler-frontend/src/app/items/distro/edit/distro-edit.component.html @@ -11,7 +11,7 @@

Name: {{ name }}

- diff --git a/projects/cobbler-frontend/src/app/items/distro/edit/distro-edit.component.ts b/projects/cobbler-frontend/src/app/items/distro/edit/distro-edit.component.ts index a9d0a3c2..5eb54686 100644 --- a/projects/cobbler-frontend/src/app/items/distro/edit/distro-edit.component.ts +++ b/projects/cobbler-frontend/src/app/items/distro/edit/distro-edit.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, OnInit } from '@angular/core'; +import { Component, Inject, inject, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder, FormControl, @@ -7,6 +7,7 @@ import { } from '@angular/forms'; import { MatButton, MatIconButton } from '@angular/material/button'; import { MatCheckbox } from '@angular/material/checkbox'; +import { MatDialog } from '@angular/material/dialog'; import { MatFormField, MatLabel } from '@angular/material/form-field'; import { MatIcon } from '@angular/material/icon'; import { MatInput } from '@angular/material/input'; @@ -15,6 +16,9 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { MatTooltip } from '@angular/material/tooltip'; import { ActivatedRoute, Router } from '@angular/router'; import { CobblerApiService, Distro } from 'cobbler-api'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { DialogItemCopyComponent } from '../../../common/dialog-item-copy/dialog-item-copy.component'; import { KeyValueEditorComponent } from '../../../common/key-value-editor/key-value-editor.component'; import { MultiSelectComponent } from '../../../common/multi-select/multi-select.component'; import { UserService } from '../../../services/user.service'; @@ -42,7 +46,11 @@ import Utils from '../../../utils'; templateUrl: './distro-edit.component.html', styleUrl: './distro-edit.component.scss', }) -export class DistroEditComponent implements OnInit { +export class DistroEditComponent implements OnInit, OnDestroy { + // Unsubscribe + private ngUnsubscribe = new Subject(); + + // Form name: string; distro: Distro; private readonly _formBuilder = inject(FormBuilder); @@ -101,6 +109,7 @@ export class DistroEditComponent implements OnInit { private cobblerApiService: CobblerApiService, private _snackBar: MatSnackBar, private router: Router, + @Inject(MatDialog) readonly dialog: MatDialog, ) { this.name = this.route.snapshot.paramMap.get('name'); } @@ -109,9 +118,15 @@ export class DistroEditComponent implements OnInit { this.refreshData(); } + ngOnDestroy(): void { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } + refreshData(): void { this.cobblerApiService .get_distro(this.name, false, false, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) .subscribe( (value) => { this.distro = value; @@ -238,6 +253,7 @@ export class DistroEditComponent implements OnInit { removeDistro(): void { this.cobblerApiService .remove_distro(this.name, this.userService.token, false) + .pipe(takeUntil(this.ngUnsubscribe)) .subscribe( (value) => { if (value) { @@ -261,18 +277,44 @@ export class DistroEditComponent implements OnInit { this._snackBar.open('Not implemented at the moment!', 'Close'); } - copyDistro(): void { - this.cobblerApiService - .copy_distro('', '', this.userService.token) - .subscribe( - (value) => { - // TODO - }, - (error) => { - // HTML encode the error message since it originates from XML - this._snackBar.open(Utils.toHTML(error.message), 'Close'); - }, - ); + copyDistro(uid: string, name: string): void { + const dialogRef = this.dialog.open(DialogItemCopyComponent, { + data: { + itemType: 'Distro', + itemName: name, + itemUid: uid, + }, + }); + + dialogRef.afterClosed().subscribe((newItemName) => { + if (newItemName === undefined) { + // Cancel means we don't need to rename the distro + return; + } + this.cobblerApiService + .get_distro_handle(name, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (distroHandle) => { + this.cobblerApiService + .copy_distro(distroHandle, newItemName, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (value) => { + this.router.navigate(['/items', 'distro', newItemName]); + }, + (error) => { + // HTML encode the error message since it originates from XML + this._snackBar.open(Utils.toHTML(error.message), 'Close'); + }, + ); + }, + (error) => { + // HTML encode the error message since it originates from XML + this._snackBar.open(Utils.toHTML(error.message), 'Close'); + }, + ); + }); } saveDistro(): void { diff --git a/projects/cobbler-frontend/src/app/items/file/edit/file-edit.component.html b/projects/cobbler-frontend/src/app/items/file/edit/file-edit.component.html index 6c9fe404..5bed4032 100644 --- a/projects/cobbler-frontend/src/app/items/file/edit/file-edit.component.html +++ b/projects/cobbler-frontend/src/app/items/file/edit/file-edit.component.html @@ -11,7 +11,7 @@

Name: {{ name }}

- diff --git a/projects/cobbler-frontend/src/app/items/file/edit/file-edit.component.ts b/projects/cobbler-frontend/src/app/items/file/edit/file-edit.component.ts index c9dc19be..c8a174a6 100644 --- a/projects/cobbler-frontend/src/app/items/file/edit/file-edit.component.ts +++ b/projects/cobbler-frontend/src/app/items/file/edit/file-edit.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, OnInit } from '@angular/core'; +import { Component, Inject, inject, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder, FormControl, @@ -8,6 +8,7 @@ import { import { MatOption } from '@angular/material/autocomplete'; import { MatButton, MatIconButton } from '@angular/material/button'; import { MatCheckbox } from '@angular/material/checkbox'; +import { MatDialog } from '@angular/material/dialog'; import { MatFormField, MatLabel } from '@angular/material/form-field'; import { MatIcon } from '@angular/material/icon'; import { MatInput } from '@angular/material/input'; @@ -16,6 +17,9 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { MatTooltip } from '@angular/material/tooltip'; import { ActivatedRoute, Router } from '@angular/router'; import { CobblerApiService, File } from 'cobbler-api'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { DialogItemCopyComponent } from '../../../common/dialog-item-copy/dialog-item-copy.component'; import { UserService } from '../../../services/user.service'; import Utils from '../../../utils'; @@ -39,7 +43,11 @@ import Utils from '../../../utils'; templateUrl: './file-edit.component.html', styleUrl: './file-edit.component.scss', }) -export class FileEditComponent implements OnInit { +export class FileEditComponent implements OnInit, OnDestroy { + // Unsubscribe + private ngUnsubscribe = new Subject(); + + // Form name: string; file: File; private readonly _formBuilder = inject(FormBuilder); @@ -68,6 +76,7 @@ export class FileEditComponent implements OnInit { private cobblerApiService: CobblerApiService, private _snackBar: MatSnackBar, private router: Router, + @Inject(MatDialog) readonly dialog: MatDialog, ) { this.name = this.route.snapshot.paramMap.get('name'); } @@ -76,9 +85,15 @@ export class FileEditComponent implements OnInit { this.refreshData(); } + ngOnDestroy(): void { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } + refreshData(): void { this.cobblerApiService .get_file(this.name, false, false, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) .subscribe( (value) => { this.file = value; @@ -112,6 +127,7 @@ export class FileEditComponent implements OnInit { removeFile(): void { this.cobblerApiService .remove_file(this.name, this.userService.token, false) + .pipe(takeUntil(this.ngUnsubscribe)) .subscribe( (value) => { if (value) { @@ -135,16 +151,44 @@ export class FileEditComponent implements OnInit { this._snackBar.open('Not implemented at the moment!', 'Close'); } - copyFile(): void { - this.cobblerApiService.copy_file('', '', this.userService.token).subscribe( - (value) => { - // TODO + copyFile(uid: string, name: string): void { + const dialogRef = this.dialog.open(DialogItemCopyComponent, { + data: { + itemType: 'File', + itemName: name, + itemUid: uid, }, - (error) => { - // HTML encode the error message since it originates from XML - this._snackBar.open(Utils.toHTML(error.message), 'Close'); - }, - ); + }); + + dialogRef.afterClosed().subscribe((newItemName) => { + if (newItemName === undefined) { + // Cancel means we don't need to rename the file + return; + } + this.cobblerApiService + .get_file_handle(name, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (fileHandle) => { + this.cobblerApiService + .copy_file(fileHandle, newItemName, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (value) => { + this.router.navigate(['/items', 'file', newItemName]); + }, + (error) => { + // HTML encode the error message since it originates from XML + this._snackBar.open(Utils.toHTML(error.message), 'Close'); + }, + ); + }, + (error) => { + // HTML encode the error message since it originates from XML + this._snackBar.open(Utils.toHTML(error.message), 'Close'); + }, + ); + }); } saveFile(): void { diff --git a/projects/cobbler-frontend/src/app/items/image/edit/image-edit.component.html b/projects/cobbler-frontend/src/app/items/image/edit/image-edit.component.html index 61c4cf9e..9790302e 100644 --- a/projects/cobbler-frontend/src/app/items/image/edit/image-edit.component.html +++ b/projects/cobbler-frontend/src/app/items/image/edit/image-edit.component.html @@ -11,7 +11,7 @@

Name: {{ name }}

- diff --git a/projects/cobbler-frontend/src/app/items/image/edit/image-edit.component.ts b/projects/cobbler-frontend/src/app/items/image/edit/image-edit.component.ts index a401c942..8d820799 100644 --- a/projects/cobbler-frontend/src/app/items/image/edit/image-edit.component.ts +++ b/projects/cobbler-frontend/src/app/items/image/edit/image-edit.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, OnInit } from '@angular/core'; +import { Component, Inject, inject, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder, FormControl, @@ -8,6 +8,7 @@ import { import { MatOption } from '@angular/material/autocomplete'; import { MatButton, MatIconButton } from '@angular/material/button'; import { MatCheckbox } from '@angular/material/checkbox'; +import { MatDialog } from '@angular/material/dialog'; import { MatFormField, MatLabel } from '@angular/material/form-field'; import { MatIcon } from '@angular/material/icon'; import { MatInput } from '@angular/material/input'; @@ -16,6 +17,9 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { MatTooltip } from '@angular/material/tooltip'; import { ActivatedRoute, Router } from '@angular/router'; import { CobblerApiService, Image } from 'cobbler-api'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { DialogItemCopyComponent } from '../../../common/dialog-item-copy/dialog-item-copy.component'; import { MultiSelectComponent } from '../../../common/multi-select/multi-select.component'; import { UserService } from '../../../services/user.service'; import Utils from '../../../utils'; @@ -41,7 +45,11 @@ import Utils from '../../../utils'; templateUrl: './image-edit.component.html', styleUrl: './image-edit.component.scss', }) -export class ImageEditComponent implements OnInit { +export class ImageEditComponent implements OnInit, OnDestroy { + // Unsubscribe + private ngUnsubscribe = new Subject(); + + // Form name: string; image: Image; private readonly _formBuilder = inject(FormBuilder); @@ -75,6 +83,7 @@ export class ImageEditComponent implements OnInit { private cobblerApiService: CobblerApiService, private _snackBar: MatSnackBar, private router: Router, + @Inject(MatDialog) readonly dialog: MatDialog, ) { this.name = this.route.snapshot.paramMap.get('name'); } @@ -83,9 +92,15 @@ export class ImageEditComponent implements OnInit { this.refreshData(); } + ngOnDestroy(): void { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } + refreshData(): void { this.cobblerApiService .get_image(this.name, false, false, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) .subscribe( (value) => { this.image = value; @@ -143,6 +158,7 @@ export class ImageEditComponent implements OnInit { removeImage(): void { this.cobblerApiService .remove_image(this.name, this.userService.token, false) + .pipe(takeUntil(this.ngUnsubscribe)) .subscribe( (value) => { if (value) { @@ -166,16 +182,44 @@ export class ImageEditComponent implements OnInit { this._snackBar.open('Not implemented at the moment!', 'Close'); } - copyImage(): void { - this.cobblerApiService.copy_image('', '', this.userService.token).subscribe( - (value) => { - // TODO + copyImage(uid: string, name: string): void { + const dialogRef = this.dialog.open(DialogItemCopyComponent, { + data: { + itemType: 'Image', + itemName: name, + itemUid: uid, }, - (error) => { - // HTML encode the error message since it originates from XML - this._snackBar.open(Utils.toHTML(error.message), 'Close'); - }, - ); + }); + + dialogRef.afterClosed().subscribe((newItemName) => { + if (newItemName === undefined) { + // Cancel means we don't need to rename the image + return; + } + this.cobblerApiService + .get_image_handle(name, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (imageHandle) => { + this.cobblerApiService + .copy_image(imageHandle, newItemName, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (value) => { + this.router.navigate(['/items', 'image', newItemName]); + }, + (error) => { + // HTML encode the error message since it originates from XML + this._snackBar.open(Utils.toHTML(error.message), 'Close'); + }, + ); + }, + (error) => { + // HTML encode the error message since it originates from XML + this._snackBar.open(Utils.toHTML(error.message), 'Close'); + }, + ); + }); } saveImage(): void { diff --git a/projects/cobbler-frontend/src/app/items/management-class/edit/management-class-edit.component.html b/projects/cobbler-frontend/src/app/items/management-class/edit/management-class-edit.component.html index f98cad19..d7a35f36 100644 --- a/projects/cobbler-frontend/src/app/items/management-class/edit/management-class-edit.component.html +++ b/projects/cobbler-frontend/src/app/items/management-class/edit/management-class-edit.component.html @@ -11,7 +11,7 @@

Name: {{ name }}

- diff --git a/projects/cobbler-frontend/src/app/items/management-class/edit/management-class-edit.component.ts b/projects/cobbler-frontend/src/app/items/management-class/edit/management-class-edit.component.ts index 71a08260..ed5e5bbb 100644 --- a/projects/cobbler-frontend/src/app/items/management-class/edit/management-class-edit.component.ts +++ b/projects/cobbler-frontend/src/app/items/management-class/edit/management-class-edit.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, OnInit } from '@angular/core'; +import { Component, Inject, inject, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder, FormControl, @@ -8,6 +8,7 @@ import { import { MatOption } from '@angular/material/autocomplete'; import { MatButton, MatIconButton } from '@angular/material/button'; import { MatCheckbox } from '@angular/material/checkbox'; +import { MatDialog } from '@angular/material/dialog'; import { MatFormField, MatLabel } from '@angular/material/form-field'; import { MatIcon } from '@angular/material/icon'; import { MatInput } from '@angular/material/input'; @@ -16,6 +17,9 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { MatTooltip } from '@angular/material/tooltip'; import { ActivatedRoute, Router } from '@angular/router'; import { CobblerApiService, Mgmgtclass } from 'cobbler-api'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { DialogItemCopyComponent } from '../../../common/dialog-item-copy/dialog-item-copy.component'; import { KeyValueEditorComponent } from '../../../common/key-value-editor/key-value-editor.component'; import { MultiSelectComponent } from '../../../common/multi-select/multi-select.component'; import { UserService } from '../../../services/user.service'; @@ -43,7 +47,11 @@ import Utils from '../../../utils'; templateUrl: './management-class-edit.component.html', styleUrl: './management-class-edit.component.scss', }) -export class ManagementClassEditComponent implements OnInit { +export class ManagementClassEditComponent implements OnInit, OnDestroy { + // Unsubscribe + private ngUnsubscribe = new Subject(); + + // Form name: string; managementClass: Mgmgtclass; private readonly _formBuilder = inject(FormBuilder); @@ -72,6 +80,7 @@ export class ManagementClassEditComponent implements OnInit { private cobblerApiService: CobblerApiService, private _snackBar: MatSnackBar, private router: Router, + @Inject(MatDialog) readonly dialog: MatDialog, ) { this.name = this.route.snapshot.paramMap.get('name'); } @@ -80,9 +89,15 @@ export class ManagementClassEditComponent implements OnInit { this.refreshData(); } + ngOnDestroy(): void { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } + refreshData(): void { this.cobblerApiService .get_mgmtclass(this.name, false, false, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) .subscribe( (value) => { this.managementClass = value; @@ -145,6 +160,7 @@ export class ManagementClassEditComponent implements OnInit { removeManagementClass(): void { this.cobblerApiService .remove_mgmtclass(this.name, this.userService.token, false) + .pipe(takeUntil(this.ngUnsubscribe)) .subscribe( (value) => { if (value) { @@ -168,18 +184,52 @@ export class ManagementClassEditComponent implements OnInit { this._snackBar.open('Not implemented at the moment!', 'Close'); } - copyProfile(): void { - this.cobblerApiService - .copy_mgmtclass('', '', this.userService.token) - .subscribe( - (value) => { - // TODO - }, - (error) => { - // HTML encode the error message since it originates from XML - this._snackBar.open(Utils.toHTML(error.message), 'Close'); - }, - ); + copyMgmtClass(uid: string, name: string): void { + const dialogRef = this.dialog.open(DialogItemCopyComponent, { + data: { + itemType: 'Mangement Class', + itemName: name, + itemUid: uid, + }, + }); + + dialogRef.afterClosed().subscribe((newItemName) => { + if (newItemName === undefined) { + // Cancel means we don't need to rename the management class + return; + } + this.cobblerApiService + .get_mgmtclass_handle(name, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (mgmtClassHandle) => { + this.cobblerApiService + .copy_mgmtclass( + mgmtClassHandle, + newItemName, + this.userService.token, + ) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (value) => { + this.router.navigate([ + '/items', + 'management-class', + newItemName, + ]); + }, + (error) => { + // HTML encode the error message since it originates from XML + this._snackBar.open(Utils.toHTML(error.message), 'Close'); + }, + ); + }, + (error) => { + // HTML encode the error message since it originates from XML + this._snackBar.open(Utils.toHTML(error.message), 'Close'); + }, + ); + }); } saveProfile(): void { diff --git a/projects/cobbler-frontend/src/app/items/package/edit/package-edit.component.html b/projects/cobbler-frontend/src/app/items/package/edit/package-edit.component.html index 48c2ffd1..cba15ac2 100644 --- a/projects/cobbler-frontend/src/app/items/package/edit/package-edit.component.html +++ b/projects/cobbler-frontend/src/app/items/package/edit/package-edit.component.html @@ -11,7 +11,7 @@

Name: {{ name }}

- diff --git a/projects/cobbler-frontend/src/app/items/package/edit/package-edit.component.ts b/projects/cobbler-frontend/src/app/items/package/edit/package-edit.component.ts index 03e3e7e2..a1e591cf 100644 --- a/projects/cobbler-frontend/src/app/items/package/edit/package-edit.component.ts +++ b/projects/cobbler-frontend/src/app/items/package/edit/package-edit.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, OnInit } from '@angular/core'; +import { Component, Inject, inject, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder, FormControl, @@ -8,6 +8,7 @@ import { import { MatOption } from '@angular/material/autocomplete'; import { MatButton, MatIconButton } from '@angular/material/button'; import { MatCheckbox } from '@angular/material/checkbox'; +import { MatDialog } from '@angular/material/dialog'; import { MatFormField, MatLabel } from '@angular/material/form-field'; import { MatIcon } from '@angular/material/icon'; import { MatInput } from '@angular/material/input'; @@ -16,6 +17,9 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { MatTooltip } from '@angular/material/tooltip'; import { ActivatedRoute, Router } from '@angular/router'; import { CobblerApiService, Package } from 'cobbler-api'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { DialogItemCopyComponent } from '../../../common/dialog-item-copy/dialog-item-copy.component'; import { MultiSelectComponent } from '../../../common/multi-select/multi-select.component'; import { UserService } from '../../../services/user.service'; import Utils from '../../../utils'; @@ -41,7 +45,11 @@ import Utils from '../../../utils'; templateUrl: './package-edit.component.html', styleUrl: './package-edit.component.scss', }) -export class PackageEditComponent implements OnInit { +export class PackageEditComponent implements OnInit, OnDestroy { + // Unsubscribe + private ngUnsubscribe = new Subject(); + + // Form name: string; package: Package; private readonly _formBuilder = inject(FormBuilder); @@ -73,6 +81,7 @@ export class PackageEditComponent implements OnInit { private cobblerApiService: CobblerApiService, private _snackBar: MatSnackBar, private router: Router, + @Inject(MatDialog) readonly dialog: MatDialog, ) { this.name = this.route.snapshot.paramMap.get('name'); } @@ -81,9 +90,15 @@ export class PackageEditComponent implements OnInit { this.refreshData(); } + ngOnDestroy(): void { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } + refreshData(): void { this.cobblerApiService .get_package(this.name, false, false, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) .subscribe( (value) => { this.package = value; @@ -129,6 +144,7 @@ export class PackageEditComponent implements OnInit { removePackage(): void { this.cobblerApiService .remove_package(this.name, this.userService.token, false) + .pipe(takeUntil(this.ngUnsubscribe)) .subscribe( (value) => { if (value) { @@ -152,18 +168,44 @@ export class PackageEditComponent implements OnInit { this._snackBar.open('Not implemented at the moment!', 'Close'); } - copyPackage(): void { - this.cobblerApiService - .copy_package('', '', this.userService.token) - .subscribe( - (value) => { - // TODO - }, - (error) => { - // HTML encode the error message since it originates from XML - this._snackBar.open(Utils.toHTML(error.message), 'Close'); - }, - ); + copyPackage(uid: string, name: string): void { + const dialogRef = this.dialog.open(DialogItemCopyComponent, { + data: { + itemType: 'Package', + itemName: name, + itemUid: uid, + }, + }); + + dialogRef.afterClosed().subscribe((newItemName) => { + if (newItemName === undefined) { + // Cancel means we don't need to rename the package + return; + } + this.cobblerApiService + .get_package_handle(name, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (packageHandle) => { + this.cobblerApiService + .copy_package(packageHandle, newItemName, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (value) => { + this.router.navigate(['/items', 'package', newItemName]); + }, + (error) => { + // HTML encode the error message since it originates from XML + this._snackBar.open(Utils.toHTML(error.message), 'Close'); + }, + ); + }, + (error) => { + // HTML encode the error message since it originates from XML + this._snackBar.open(Utils.toHTML(error.message), 'Close'); + }, + ); + }); } savePackage(): void { diff --git a/projects/cobbler-frontend/src/app/items/profile/edit/profile-edit.component.html b/projects/cobbler-frontend/src/app/items/profile/edit/profile-edit.component.html index f288a587..19bdf53b 100644 --- a/projects/cobbler-frontend/src/app/items/profile/edit/profile-edit.component.html +++ b/projects/cobbler-frontend/src/app/items/profile/edit/profile-edit.component.html @@ -11,7 +11,7 @@

Name: {{ name }}

- diff --git a/projects/cobbler-frontend/src/app/items/profile/edit/profile-edit.component.ts b/projects/cobbler-frontend/src/app/items/profile/edit/profile-edit.component.ts index 45e8051b..a98672c3 100644 --- a/projects/cobbler-frontend/src/app/items/profile/edit/profile-edit.component.ts +++ b/projects/cobbler-frontend/src/app/items/profile/edit/profile-edit.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, OnInit } from '@angular/core'; +import { Component, Inject, inject, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder, FormControl, @@ -8,6 +8,7 @@ import { import { MatOption } from '@angular/material/autocomplete'; import { MatButton, MatIconButton } from '@angular/material/button'; import { MatCheckbox } from '@angular/material/checkbox'; +import { MatDialog } from '@angular/material/dialog'; import { MatFormField, MatLabel } from '@angular/material/form-field'; import { MatIcon } from '@angular/material/icon'; import { MatInput } from '@angular/material/input'; @@ -16,6 +17,9 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { MatTooltip } from '@angular/material/tooltip'; import { ActivatedRoute, Router } from '@angular/router'; import { CobblerApiService, Profile } from 'cobbler-api'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { DialogItemCopyComponent } from '../../../common/dialog-item-copy/dialog-item-copy.component'; import { KeyValueEditorComponent } from '../../../common/key-value-editor/key-value-editor.component'; import { MultiSelectComponent } from '../../../common/multi-select/multi-select.component'; import { UserService } from '../../../services/user.service'; @@ -43,7 +47,11 @@ import Utils from '../../../utils'; templateUrl: './profile-edit.component.html', styleUrl: './profile-edit.component.scss', }) -export class ProfileEditComponent implements OnInit { +export class ProfileEditComponent implements OnInit, OnDestroy { + // Unsubscribe + private ngUnsubscribe = new Subject(); + + // Form name: string; profile: Profile; private readonly _formBuilder = inject(FormBuilder); @@ -110,6 +118,7 @@ export class ProfileEditComponent implements OnInit { private cobblerApiService: CobblerApiService, private _snackBar: MatSnackBar, private router: Router, + @Inject(MatDialog) readonly dialog: MatDialog, ) { this.name = this.route.snapshot.paramMap.get('name'); } @@ -118,9 +127,15 @@ export class ProfileEditComponent implements OnInit { this.refreshData(); } + ngOnDestroy(): void { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } + refreshData(): void { this.cobblerApiService .get_profile(this.name, false, false, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) .subscribe( (value) => { this.profile = value; @@ -284,6 +299,7 @@ export class ProfileEditComponent implements OnInit { removeProfile(): void { this.cobblerApiService .remove_profile(this.name, this.userService.token, false) + .pipe(takeUntil(this.ngUnsubscribe)) .subscribe( (value) => { if (value) { @@ -307,18 +323,44 @@ export class ProfileEditComponent implements OnInit { this._snackBar.open('Not implemented at the moment!', 'Close'); } - copyProfile(): void { - this.cobblerApiService - .copy_profile('', '', this.userService.token) - .subscribe( - (value) => { - // TODO - }, - (error) => { - // HTML encode the error message since it originates from XML - this._snackBar.open(Utils.toHTML(error.message), 'Close'); - }, - ); + copyProfile(uid: string, name: string): void { + const dialogRef = this.dialog.open(DialogItemCopyComponent, { + data: { + itemType: 'Profile', + itemName: name, + itemUid: uid, + }, + }); + + dialogRef.afterClosed().subscribe((newItemName) => { + if (newItemName === undefined) { + // Cancel means we don't need to rename the profile + return; + } + this.cobblerApiService + .get_profile_handle(name, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (profileHandle) => { + this.cobblerApiService + .copy_profile(profileHandle, newItemName, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (value) => { + this.router.navigate(['/items', 'profile', newItemName]); + }, + (error) => { + // HTML encode the error message since it originates from XML + this._snackBar.open(Utils.toHTML(error.message), 'Close'); + }, + ); + }, + (error) => { + // HTML encode the error message since it originates from XML + this._snackBar.open(Utils.toHTML(error.message), 'Close'); + }, + ); + }); } saveProfile(): void { diff --git a/projects/cobbler-frontend/src/app/items/repository/edit/repository-edit.component.html b/projects/cobbler-frontend/src/app/items/repository/edit/repository-edit.component.html index ced76ba0..2bd8a7cf 100644 --- a/projects/cobbler-frontend/src/app/items/repository/edit/repository-edit.component.html +++ b/projects/cobbler-frontend/src/app/items/repository/edit/repository-edit.component.html @@ -11,7 +11,7 @@

Name: {{ name }}

- diff --git a/projects/cobbler-frontend/src/app/items/repository/edit/repository-edit.component.ts b/projects/cobbler-frontend/src/app/items/repository/edit/repository-edit.component.ts index 90171ee3..0e4cacc0 100644 --- a/projects/cobbler-frontend/src/app/items/repository/edit/repository-edit.component.ts +++ b/projects/cobbler-frontend/src/app/items/repository/edit/repository-edit.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, OnInit } from '@angular/core'; +import { Component, Inject, inject, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder, FormControl, @@ -8,6 +8,7 @@ import { import { MatOption } from '@angular/material/autocomplete'; import { MatButton, MatIconButton } from '@angular/material/button'; import { MatCheckbox } from '@angular/material/checkbox'; +import { MatDialog } from '@angular/material/dialog'; import { MatFormField, MatLabel } from '@angular/material/form-field'; import { MatIcon } from '@angular/material/icon'; import { MatInput } from '@angular/material/input'; @@ -16,6 +17,9 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { MatTooltip } from '@angular/material/tooltip'; import { ActivatedRoute, Router } from '@angular/router'; import { CobblerApiService, Repo } from 'cobbler-api'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { DialogItemCopyComponent } from '../../../common/dialog-item-copy/dialog-item-copy.component'; import { KeyValueEditorComponent } from '../../../common/key-value-editor/key-value-editor.component'; import { MultiSelectComponent } from '../../../common/multi-select/multi-select.component'; import { UserService } from '../../../services/user.service'; @@ -43,7 +47,11 @@ import Utils from '../../../utils'; templateUrl: './repository-edit.component.html', styleUrl: './repository-edit.component.scss', }) -export class RepositoryEditComponent implements OnInit { +export class RepositoryEditComponent implements OnInit, OnDestroy { + // Unsubscribe + private ngUnsubscribe = new Subject(); + + // Form name: string; repository: Repo; private readonly _formBuilder = inject(FormBuilder); @@ -82,6 +90,7 @@ export class RepositoryEditComponent implements OnInit { private cobblerApiService: CobblerApiService, private _snackBar: MatSnackBar, private router: Router, + @Inject(MatDialog) readonly dialog: MatDialog, ) { this.name = this.route.snapshot.paramMap.get('name'); } @@ -90,9 +99,15 @@ export class RepositoryEditComponent implements OnInit { this.refreshData(); } + ngOnDestroy(): void { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } + refreshData(): void { this.cobblerApiService .get_repo(this.name, false, false, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) .subscribe( (value) => { this.repository = value; @@ -177,6 +192,7 @@ export class RepositoryEditComponent implements OnInit { removeRepository(): void { this.cobblerApiService .remove_repo(this.name, this.userService.token, false) + .pipe(takeUntil(this.ngUnsubscribe)) .subscribe( (value) => { if (value) { @@ -200,16 +216,44 @@ export class RepositoryEditComponent implements OnInit { this._snackBar.open('Not implemented at the moment!', 'Close'); } - copyRepository(): void { - this.cobblerApiService.copy_repo('', '', this.userService.token).subscribe( - (value) => { - // TODO + copyRepository(uid: string, name: string): void { + const dialogRef = this.dialog.open(DialogItemCopyComponent, { + data: { + itemType: 'Repository', + itemName: name, + itemUid: uid, }, - (error) => { - // HTML encode the error message since it originates from XML - this._snackBar.open(Utils.toHTML(error.message), 'Close'); - }, - ); + }); + + dialogRef.afterClosed().subscribe((newItemName) => { + if (newItemName === undefined) { + // Cancel means we don't need to rename the repository + return; + } + this.cobblerApiService + .get_repo_handle(name, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (repositoryHandle) => { + this.cobblerApiService + .copy_repo(repositoryHandle, newItemName, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (value) => { + this.router.navigate(['/items', 'repository', newItemName]); + }, + (error) => { + // HTML encode the error message since it originates from XML + this._snackBar.open(Utils.toHTML(error.message), 'Close'); + }, + ); + }, + (error) => { + // HTML encode the error message since it originates from XML + this._snackBar.open(Utils.toHTML(error.message), 'Close'); + }, + ); + }); } saveRepository(): void { diff --git a/projects/cobbler-frontend/src/app/items/system/edit/system-edit.component.html b/projects/cobbler-frontend/src/app/items/system/edit/system-edit.component.html index 769ce560..3ed83b7e 100644 --- a/projects/cobbler-frontend/src/app/items/system/edit/system-edit.component.html +++ b/projects/cobbler-frontend/src/app/items/system/edit/system-edit.component.html @@ -11,7 +11,7 @@

Name: {{ name }}

- diff --git a/projects/cobbler-frontend/src/app/items/system/edit/system-edit.component.ts b/projects/cobbler-frontend/src/app/items/system/edit/system-edit.component.ts index 78b2fb18..4db15dbe 100644 --- a/projects/cobbler-frontend/src/app/items/system/edit/system-edit.component.ts +++ b/projects/cobbler-frontend/src/app/items/system/edit/system-edit.component.ts @@ -1,4 +1,4 @@ -import { Component, inject, OnInit } from '@angular/core'; +import { Component, Inject, inject, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder, FormControl, @@ -8,6 +8,7 @@ import { import { MatOption } from '@angular/material/autocomplete'; import { MatButton, MatIconButton } from '@angular/material/button'; import { MatCheckbox } from '@angular/material/checkbox'; +import { MatDialog } from '@angular/material/dialog'; import { MatFormField, MatLabel } from '@angular/material/form-field'; import { MatIcon } from '@angular/material/icon'; import { MatInput } from '@angular/material/input'; @@ -16,6 +17,9 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { MatTooltip } from '@angular/material/tooltip'; import { ActivatedRoute, Router } from '@angular/router'; import { CobblerApiService, System } from 'cobbler-api'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { DialogItemCopyComponent } from '../../../common/dialog-item-copy/dialog-item-copy.component'; import { KeyValueEditorComponent } from '../../../common/key-value-editor/key-value-editor.component'; import { MultiSelectComponent } from '../../../common/multi-select/multi-select.component'; import { UserService } from '../../../services/user.service'; @@ -43,7 +47,11 @@ import Utils from '../../../utils'; templateUrl: './system-edit.component.html', styleUrl: './system-edit.component.scss', }) -export class SystemEditComponent implements OnInit { +export class SystemEditComponent implements OnInit, OnDestroy { + // Unsubscribe + private ngUnsubscribe = new Subject(); + + // Form name: string; system: System; private readonly _formBuilder = inject(FormBuilder); @@ -132,6 +140,7 @@ export class SystemEditComponent implements OnInit { private cobblerApiService: CobblerApiService, private _snackBar: MatSnackBar, private router: Router, + @Inject(MatDialog) readonly dialog: MatDialog, ) { this.name = this.route.snapshot.paramMap.get('name'); } @@ -140,6 +149,11 @@ export class SystemEditComponent implements OnInit { this.refreshData(); } + ngOnDestroy(): void { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } + refreshData(): void { this.cobblerApiService .get_system(this.name, false, false, this.userService.token) @@ -393,18 +407,44 @@ export class SystemEditComponent implements OnInit { this._snackBar.open('Not implemented at the moment!', 'Close'); } - copySystem(): void { - this.cobblerApiService - .copy_system('', '', this.userService.token) - .subscribe( - (value) => { - // TODO - }, - (error) => { - // HTML encode the error message since it originates from XML - this._snackBar.open(Utils.toHTML(error.message), 'Close'); - }, - ); + copySystem(uid: string, name: string): void { + const dialogRef = this.dialog.open(DialogItemCopyComponent, { + data: { + itemType: 'System', + itemName: name, + itemUid: uid, + }, + }); + + dialogRef.afterClosed().subscribe((newItemName) => { + if (newItemName === undefined) { + // Cancel means we don't need to rename the system + return; + } + this.cobblerApiService + .get_system_handle(name, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (systemHandle) => { + this.cobblerApiService + .copy_system(systemHandle, newItemName, this.userService.token) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe( + (value) => { + this.router.navigate(['/items', 'system', newItemName]); + }, + (error) => { + // HTML encode the error message since it originates from XML + this._snackBar.open(Utils.toHTML(error.message), 'Close'); + }, + ); + }, + (error) => { + // HTML encode the error message since it originates from XML + this._snackBar.open(Utils.toHTML(error.message), 'Close'); + }, + ); + }); } saveSystem(): void {