Skip to content

Commit

Permalink
Cobbler-Frontend: Progress View/Edit screens (list component)
Browse files Browse the repository at this point in the history
  • Loading branch information
SchoolGuy committed Jul 28, 2024
1 parent 47117d0 commit 3dc739b
Show file tree
Hide file tree
Showing 20 changed files with 521 additions and 103 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<h2 mat-dialog-title>Add new Option:</h2>
<mat-dialog-content>
<mat-form-field>
<mat-label>New Option</mat-label>
<input matInput [(ngModel)]="dialogCloseSignal" />
</mat-form-field>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button (click)="onNoClick()">No Thanks</button>
<button mat-button [mat-dialog-close]="dialogCloseSignal()" cdkFocusInitial>Ok</button>
</mat-dialog-actions>
Empty file.
Original file line number Diff line number Diff line change
@@ -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 { DialogTextInputComponent } from './dialog-text-input.component';

describe('DialogTextInputComponent', () => {
let component: DialogTextInputComponent;
let fixture: ComponentFixture<DialogTextInputComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
DialogTextInputComponent,
MatDialogModule,
NoopAnimationsModule,
],
providers: [
{
provide: MatDialogRef,
useValue: {}
},
{
provide: MAT_DIALOG_DATA,
useValue: {text: ""}
}
]
})
.compileComponents();

fixture = TestBed.createComponent(DialogTextInputComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {Component, inject, model} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {MatButton} from '@angular/material/button';
import {
MAT_DIALOG_DATA,
MatDialogActions,
MatDialogClose,
MatDialogContent, MatDialogRef,
MatDialogTitle
} from '@angular/material/dialog';
import {MatFormField, MatLabel} from '@angular/material/form-field';
import {MatInput} from '@angular/material/input';

export interface DialogTextInputData {
text: string;
}


@Component({
selector: 'cobbler-dialog-text-input',
standalone: true,
imports: [
MatDialogContent,
MatDialogTitle,
MatFormField,
MatDialogActions,
MatButton,
MatDialogClose,
MatInput,
MatLabel,
FormsModule,
],
templateUrl: './dialog-text-input.component.html',
styleUrl: './dialog-text-input.component.scss'
})
export class DialogTextInputComponent {
readonly dialogRef = inject(MatDialogRef<DialogTextInputComponent>);
readonly data = inject<DialogTextInputData>(MAT_DIALOG_DATA);
readonly dialogCloseSignal = model(this.data.text)

onNoClick(): void {
this.dialogRef.close();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<mat-card appearance="outlined">
<mat-card-header>
<mat-card-title>{{label}}</mat-card-title>
</mat-card-header>
@if (multiSelectOptions.length === 0) {
<p style="text-align: center">Empty list of options</p>
} @else {
<!-- TODO: See https://github.com/cobbler/cobbler-web/issues/270
<mat-form-field style="margin: 0 1em">
<mat-label>Search for option</mat-label>
<input matInput type="text">
</mat-form-field>
-->
<ng-container [formGroup]="matSelectOptionsFormGroup">
<mat-checkbox *ngFor="let option of multiSelectOptions" (change)="changeValues($event)" [formControlName]=option [value]="option">{{option}}</mat-checkbox>
</ng-container>
}
<button mat-button [disabled]="isDisabled" (click)="addOption()">Add option</button>
</mat-card>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { MultiSelectComponent } from './multi-select.component';

describe('MultiSelectComponent', () => {
let component: MultiSelectComponent;
let fixture: ComponentFixture<MultiSelectComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MultiSelectComponent]
})
.compileComponents();

fixture = TestBed.createComponent(MultiSelectComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import {AsyncPipe, NgForOf} from '@angular/common';
import {ChangeDetectionStrategy, Component, forwardRef, inject, Input, OnInit, signal} from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
FormControl, FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR,
ReactiveFormsModule,
ValidationErrors,
Validator
} from '@angular/forms';
import {MatButton, MatFabButton, MatIconButton} from '@angular/material/button';
import {MatCard, MatCardHeader, MatCardTitle} from '@angular/material/card';
import {MatCheckbox, MatCheckboxChange} from '@angular/material/checkbox';
import {MatDialog} from '@angular/material/dialog';
import {MatFormFieldModule, MatLabel} from '@angular/material/form-field';
import {MatIcon} from '@angular/material/icon';
import {MatInput} from '@angular/material/input';
import {MatListItem} from '@angular/material/list';
import {MatOption, MatSelectModule} from '@angular/material/select';
import {DialogTextInputComponent} from '../dialog-text-input/dialog-text-input.component';



@Component({
selector: 'cobbler-multi-select',
standalone: true,
imports: [
MatFormFieldModule,
MatSelectModule,
MatOption,
MatLabel,
ReactiveFormsModule,
AsyncPipe,
MatListItem,
NgForOf,
MatCheckbox,
MatButton,
MatIconButton,
MatIcon,
MatCard,
MatCardHeader,
MatCardTitle,
MatFabButton,
MatInput,
],
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: MultiSelectComponent,
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: MultiSelectComponent
}
],
templateUrl: './multi-select.component.html',
styleUrl: './multi-select.component.scss'
})
export class MultiSelectComponent implements OnInit, ControlValueAccessor, Validator {
@Input() multiSelectOptions: Array<string> = [];
@Input() label = "";
matSelectOptionsFormGroup: FormGroup<{}> = new FormGroup({})
onChange = (options: string[]) => {};
onTouched = (options: string[]) => {};
readonly dialog = inject(MatDialog);
readonly optionSignal = signal('');
isDisabled = true;

ngOnInit(): void {
this.buildFormGroup(this.multiSelectOptions)
}

buildFormGroup(options: string[], checked = false): void {
options.forEach(value => {
this.matSelectOptionsFormGroup.addControl(value, new FormControl({value: checked, disabled: this.isDisabled}))
})
}

updateFormGroup(options: string[], checked = false): void {
options.forEach(value => {
this.matSelectOptionsFormGroup.get(value).setValue(checked)
})
}

registerOnChange(fn: any): void {
this.onChange = fn;
}

registerOnTouched(fn: any): void {
this.onTouched = fn;
}

setDisabledState(isDisabled: boolean): void {
this.isDisabled = isDisabled;
if (this.isDisabled) {
this.matSelectOptionsFormGroup.disable();
} else {
this.matSelectOptionsFormGroup.enable();
}
}

writeValue(obj: string[]): void {
this.buildFormGroup(obj)
this.updateFormGroup(obj, true)
}

registerOnValidatorChange(fn: () => void): void {
}

validate(control: AbstractControl): ValidationErrors | null {
return undefined;
}

changeValues(e: MatCheckboxChange) {
let options: string[] = []
Object.keys(this.matSelectOptionsFormGroup.controls).forEach(key => {
const control = this.matSelectOptionsFormGroup.get(key)
if (control instanceof FormControl) {
if (control.value) {
options.push(key)
}
}
})

this.onTouched(options)
this.onChange(options)
}

addOption(): void {
const dialogRef = this.dialog.open(DialogTextInputComponent, {
data: {data: this.optionSignal()},
});

dialogRef.afterClosed().subscribe(result => {
if (result !== undefined) {
this.optionSignal.set(result);
this.multiSelectOptions.push(result)
this.buildFormGroup(this.multiSelectOptions)
}
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,8 @@ <h1 class="title title-cell-text">Name: {{ name }}</h1>
<!-- TODO: autoinstall_meta -->
<!-- TODO: boot_files -->
<ng-container class="form-field-full-width">
<mat-form-field>
<mat-label>Boot Loaders</mat-label>
<mat-select formControlName="boot_loaders" multiple>
@for (boot_loader of ["ipxe", "grub", "pxe"]; track boot_loader) {
<mat-option [value]="boot_loader">{{boot_loader}}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-checkbox formControlName="bootloader_inherited">Inherited</mat-checkbox>
<cobbler-multi-select label="Boot Loaders" [multiSelectOptions]='["ipxe", "grub", "pxe"]' formControlName="boot_loaders"></cobbler-multi-select>
<mat-checkbox formControlName="bootloader_inherited">Inherited</mat-checkbox>
</ng-container>
<mat-form-field class="form-field-full-width">
<mat-label>Breed</mat-label>
Expand Down Expand Up @@ -110,12 +103,18 @@ <h1 class="title title-cell-text">Name: {{ name }}</h1>
</mat-form-field>
<!-- TODO: kernel_options -->
<!-- TODO: kernel_options_post -->
<!-- TODO: mgmt_classes -->
<ng-container class="form-field-full-width">
<cobbler-multi-select label="Management Classes" formControlName="mgmt_classes"></cobbler-multi-select>
<mat-checkbox formControlName="mgmt_classes_inherited">Inherited</mat-checkbox>
</ng-container>
<mat-form-field class="form-field-full-width">
<mat-label>Operating System Version</mat-label>
<input matInput type="text" formControlName="os_version"/>
</mat-form-field>
<!-- TODO: owners -->
<ng-container class="form-field-full-width">
<cobbler-multi-select label="Owners" [multiSelectOptions]="distroOwners" formControlName="owners"></cobbler-multi-select>
<mat-checkbox formControlName="owners_inherited">Inherited</mat-checkbox>
</ng-container>
<mat-form-field class="form-field-full-width">
<mat-label>RedHat Management Key</mat-label>
<input matInput type="text" formControlName="redhat_management_key"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ 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 {MultiSelectComponent} from '../../../common/multi-select/multi-select.component';
import {UserService} from '../../../services/user.service';

@Component({
Expand All @@ -28,6 +29,7 @@ import {UserService} from '../../../services/user.service';
MatCheckbox,
MatSelect,
MatOption,
MultiSelectComponent,
],
templateUrl: './distro-edit.component.html',
styleUrl: './distro-edit.component.scss'
Expand Down Expand Up @@ -57,6 +59,10 @@ export class DistroEditComponent implements OnInit{
redhat_management_key: new FormControl({value: "", disabled: true}),
boot_loaders: new FormControl({value: [], disabled: true}),
bootloader_inherited: new FormControl({value: false, disabled: true}),
owners: new FormControl({value: [], disabled: true}),
owners_inherited: new FormControl({value: false, disabled: true}),
mgmt_classes: new FormControl({value: [], disabled: true}),
mgmt_classes_inherited: new FormControl({value: false, disabled: true}),
});
isEditMode: boolean = false;

Expand Down Expand Up @@ -101,6 +107,12 @@ export class DistroEditComponent implements OnInit{
this.distroFormGroup.controls.bootloader_inherited.setValue(false)
this.distroFormGroup.controls.boot_loaders.setValue(this.distro.boot_loaders)
}
if (typeof this.distro.owners === "string") {
this.distroFormGroup.controls.owners_inherited.setValue(true)
} else {
this.distroFormGroup.controls.owners_inherited.setValue(false)
this.distroFormGroup.controls.owners.setValue(this.distro.owners)
}
}, error => {
// HTML encode the error message since it originates from XML
this._snackBar.open(this.toHTML(error.message), 'Close');
Expand Down Expand Up @@ -139,6 +151,16 @@ export class DistroEditComponent implements OnInit{
// TODO
}

get distroOwners(): string[] {
if (this.distro && this.distro.owners) {
const ownersResult = this.distro.owners
if (typeof ownersResult !== 'string') {
return ownersResult;
}
}
return []
}

toHTML(input: string): any {
// FIXME: Deduplicate method
return new DOMParser().parseFromString(input, 'text/html').documentElement.textContent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ <h1 class="title title-cell-text">Name: {{ name }}</h1>
<input matInput type="text" formControlName="class_name"/>
</mat-form-field>
<!-- TODO: Files Array -->
<!-- TODO: Owners Array -->
<ng-container class="form-field-full-width">
<cobbler-multi-select label="Owners" [multiSelectOptions]="mgmtClassOwners" formControlName="owners"></cobbler-multi-select>
<mat-checkbox formControlName="owners_inherited">Inherited</mat-checkbox>
</ng-container>
<!-- TODO: Packages Array -->
<!-- TODO: Params Map -->
@if (isEditMode) {
Expand Down
Loading

0 comments on commit 3dc739b

Please sign in to comment.