Skip to content
Snippets Groups Projects
Commit 98495ce4 authored by Lukasz Lopatowski's avatar Lukasz Lopatowski
Browse files

Merge branch 'cluster-wizard' into 'develop'

add new adding wizard

See merge request !204
parents 0be23923 8d78c93e
No related branches found
No related tags found
2 merge requests!258Develop,!204add new adding wizard
Showing
with 571 additions and 111 deletions
......@@ -63,7 +63,7 @@ export const jwtOptionsFactory = (appConfig: AppConfigService) => ({
AppComponent,
LeftMenuComponent,
ToastContainerComponent,
AdminLeftMenuComponent
AdminLeftMenuComponent,
],
imports: [
BrowserModule,
......
......@@ -5,6 +5,7 @@ import {RoleGuard} from '../../../auth/role.guard';
import {ComponentMode} from '../../../shared/common/componentmode';
import { ClusterManagerComponent } from '../../../shared/admin/clusters/manager/manager.component';
import { ClusterManagerDetailsComponent } from '../../../shared/admin/clusters/managerdetails/managerdetails.component';
import { AddClusterComponent } from '../../../shared/admin/clusters/add-cluster/add-cluster.component';
export const ClustersRoutes: Route[] = [
{ path: 'clusters', component: ClusterDetailsComponent, canActivate: [AuthGuard, RoleGuard],
......@@ -13,6 +14,9 @@ export const ClustersRoutes: Route[] = [
data: {mode: ComponentMode.VIEW, roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_OPERATOR']}},
{ path: 'manage/clusters', component: ClusterManagerComponent, canActivate: [AuthGuard, RoleGuard],
data: {mode: ComponentMode.VIEW, roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_OPERATOR']}},
{ path: 'manage/clusters/add', component: AddClusterComponent, canActivate: [AuthGuard, RoleGuard],
data: {mode: ComponentMode.VIEW, roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_OPERATOR']}},
{ path: 'manage/clusters/:id', component: ClusterManagerDetailsComponent, canActivate: [AuthGuard, RoleGuard],
data: {mode: ComponentMode.EDIT, roles: ['ROLE_SYSTEM_ADMIN', 'ROLE_OPERATOR']}},
];
......@@ -40,4 +40,12 @@ export class ClusterManagerService {
return this.http.delete<void>(this.url + '/' + id);
}
public readClusterFile(file: File, view: ClusterManager): Observable<ClusterManager> {
const formParams = new FormData();
formParams.append('file', file);
formParams.append('data', new Blob([JSON.stringify(view)], { type: 'application/json' }));
return this.http.post<ClusterManager>(this.url + '/read', formParams);
}
}
\ No newline at end of file
This diff is collapsed.
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { AddClusterComponent } from './add-cluster.component';
import { ClusterManagerService } from '../../../../service/cluster-manager.service';
import { Router } from '@angular/router';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { FormsModule } from '@angular/forms';
import { of, throwError } from 'rxjs';
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { DatePipe } from '@angular/common';
describe('AddClusterComponent', () => {
let component: AddClusterComponent;
let fixture: ComponentFixture<AddClusterComponent>;
let clusterService: jasmine.SpyObj<ClusterManagerService>;
let mockRouter: jasmine.SpyObj<Router>;
beforeEach(waitForAsync(() => {
const clusterServiceSpy = jasmine.createSpyObj('ClusterManagerService', ['sendCluster']);
const routerSpy = jasmine.createSpyObj('Router', ['navigate']);
TestBed.configureTestingModule({
declarations: [AddClusterComponent, ],
imports: [
FormsModule,
HttpClientTestingModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateFakeLoader
}
}),
],
providers: [
{ provide: ClusterManagerService, useValue: clusterServiceSpy },
{ provide: Router, useValue: routerSpy },
DatePipe
],
schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
clusterService = TestBed.inject(ClusterManagerService) as jasmine.SpyObj<ClusterManagerService>;
mockRouter = TestBed.inject(Router) as jasmine.SpyObj<Router>;
}));
beforeEach(() => {
fixture = TestBed.createComponent(AddClusterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create the component', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { MenuItem } from 'primeng/api';
import { Cluster, ClusterExtNetwork, IngressCertificateConfigOption, IngressControllerConfigOption, IngressResourceConfigOption, NamespaceConfigOption } from '../../../../model/cluster';
import { ClusterManager } from '../../../../model/cluster-manager';
import { ClusterManagerService } from '../../../../service/cluster-manager.service';
import { DomainService } from '../../../../service';
import { DatePipe } from '@angular/common';
import { Router } from '@angular/router';
@Component({
selector: 'app-add-cluster',
templateUrl: './add-cluster.component.html',
styleUrl: './add-cluster.component.css'
})
export class AddClusterComponent implements OnInit {
public steps: MenuItem[];
public activeStepIndex = 0;
public translateUpdate: any;
public kubernetesFile: string;
public domains = [];
public assignedDomain: boolean = false;
controllerConfigOption: Map<string, IngressControllerConfigOption> = new Map<string, IngressControllerConfigOption>();
resourceConfigOption: Map<string, IngressResourceConfigOption> = new Map<string, IngressResourceConfigOption>();
namespaceConfigOption: Map<string, NamespaceConfigOption> = new Map<string, NamespaceConfigOption>();
certificateConfigOption: Map<string, IngressCertificateConfigOption> = new Map<string, IngressCertificateConfigOption>();
public error = "";
public cluster: ClusterManager = new ClusterManager();
constructor(public translate: TranslateService,
private cluserService: ClusterManagerService,
private domainService: DomainService,
private datePipe: DatePipe,
private router: Router
) {
this.domainService.getAllBase().subscribe(result => {
this.domains = result.filter(d => d.id !== this.domainService.getGlobalDomainId());
});
}
ngOnInit(): void {
this.initializeMaps();
this.translateUpdate = setInterval(() => {
if (this.translate.instant('CLUSTERS.WIZARD.ADD') !== null) {
this.steps = [
{ label: this.translate.instant('CLUSTERS.WIZARD.STEP_1') },
{ label: this.translate.instant('CLUSTERS.WIZARD.STEP_2') },
{ label: this.translate.instant('CLUSTERS.WIZARD.STEP_3') },
];
this.stopTranslationUpdate();
}
}, 200);
}
public nextStep(): void {
this.activeStepIndex += 1;
}
private stopTranslationUpdate() {
clearInterval(this.translateUpdate);
this.translateUpdate = null;
}
public uploadKubernetesFile(): void {
this.cluserService.readClusterFile(new File([this.kubernetesFile], 'kubernetes.yaml'), this.cluster).subscribe(result => {
console.log(result);
this.cluster = result;
this.nextStep();
}, error => {
console.error('Error reading Kubernetes file:', error);
});
}
public onDomainSelection(event: any) {
console.log(event);
this.cluster.domainNames = [event]
}
public onDomainChange(event: any) {
console.log(event);
this.cluster.domainNames = [this.domains[0].name];
}
private initializeMaps() {
this.resourceConfigOption.set('Do nothing', IngressResourceConfigOption.NOT_USED);
this.resourceConfigOption.set('Deploy new resource from the definition in the application chart', IngressResourceConfigOption.DEPLOY_FROM_CHART);
this.controllerConfigOption.set('Use existing', IngressControllerConfigOption.USE_EXISTING);
this.controllerConfigOption.set('Deploy new controller from chart repository', IngressControllerConfigOption.DEPLOY_NEW_FROM_REPO);
this.controllerConfigOption.set('Deploy new controller from local chart archive', IngressControllerConfigOption.DEPLOY_NEW_FROM_ARCHIVE);
this.namespaceConfigOption.set('Use default namespace', NamespaceConfigOption.USE_DEFAULT_NAMESPACE);
this.namespaceConfigOption.set('Use domain namespace', NamespaceConfigOption.USE_DOMAIN_NAMESPACE);
this.namespaceConfigOption.set('Create namespace', NamespaceConfigOption.CREATE_NAMESPACE);
this.certificateConfigOption.set('Use my own wildcard certificate', IngressCertificateConfigOption.USE_WILDCARD);
this.certificateConfigOption.set('Generate LetsEncrypt certificates automatically', IngressCertificateConfigOption.USE_LETSENCRYPT);
}
public getKeys(map) {
return Array.from(map.keys());
}
public submit(): void {
console.log(this.cluster);
this.deteleDates();
this.cluserService.sendCluster(new File([this.kubernetesFile], 'kubernetes.yaml'), this.cluster).subscribe(result => {
console.log(result);
this.cluster = result;
this.router.navigate(['/admin/manage/clusters']);
});
}
private deteleDates() {
this.cluster.creationDate = null;
this.cluster.modificationDate = null;
this.cluster.currentStateSince = null;
}
public removeNetwork(id) {
this.cluster.externalNetworks.splice(
this.cluster.externalNetworks.findIndex(
function (i) {
return i.id = id;
}), 1);
}
public addNetwork() {
const newobj: ClusterExtNetwork = new ClusterExtNetwork();
this.cluster.externalNetworks.push(newobj);
}
public formatDate(date: Date) {
return this.datePipe.transform(date, 'dd-MM-yyyy HH:mm');
}
}
......@@ -2,7 +2,7 @@
<div style="display: flex; align-items: center; margin-top:20px">
<div style=" display:flex; align-items: center;">
<div style="margin-right:20px;">
<button class="btn btn-primary" (click)="modal.show()">New Cluster</button>
<button class="btn btn-primary" [routerLink]="['add']" >New Cluster</button>
</div>
<span class="p-input-icon-right">
<i class="pi pi-search" style="font-size: 13px; top: 16px; margin-right: 5px;"></i>
......@@ -62,58 +62,3 @@
</div>
<nmaas-modal >
<div class="nmaas-modal-header">{{'CLUSTERS.CONNECT_CLUSTER' | translate}}</div>
<div class="nmaas-modal-body">
<div class="flex flex-column">
<div class="mt-4">
<label for="name">{{'CLUSTERS.NAME' | translate}}</label>
<input id="name" type="text" class="form-control" [(ngModel)]="addedCluster.name" [ngModelOptions]="{standalone: true}">
</div>
<div class="mt-4">
<label for="desc">{{'CLUSTERS.DESCRIPTION' | translate}}</label>
<input id="desc" type="text" class="form-control" [(ngModel)]="addedCluster.description" [ngModelOptions]="{standalone: true}">
</div>
<div class="mt-4">
<label for="contactEmail">{{'CLUSTERS.CONTACT_MAIL' | translate}}</label>
<input id="contactEmail" type="text" class="form-control" [(ngModel)]="addedCluster.contactEmail" [ngModelOptions]="{standalone: true}">
</div>
<div class="mt-4">
<div>
<label for="assignDomain">Assign to domain? </label>
<input type="checkbox" [(ngModel)]="assignedDomain" (change)="onDomainChange($event)" id="assignDomain" />
</div>
<div class="mt-4" *ngIf="assignedDomain">
<label for="name">{{'CLUSTERS.DOMAIN' | translate}}</label>
<select id="domain" #domainSelect class="form-control" (change)="onDomainSelection(domainSelect.value)" plaaceholder="testtesttest" >
<option *ngFor="let domain of domains" [value]="domain.name"
>{{domain.name}}
</option>
</select>
</div>
</div>
<div class="card flex justify-content-center mt-4" >
<p-fileUpload name="json" (onUpload)="saveFile($event)" customUpload="true" [draggable]="true" pTooltip="Upload file is required before save"
(uploadHandler)="saveFile($event)" [multiple]="false" accept=".yaml" maxFileSize="1000000">
</p-fileUpload>
</div>
</div>
</div>
<div class="nmaas-modal-footer">
<button type="button" class="btn btn-secondary"
(click)="this.modal.hide()">{{'UNDEPLOY_MODAL.CANCEL_BUTTON' | translate}}</button>
<button type="button" class="btn btn-primary" [disabled]="updatedFile === null || addedCluster?.name === null || addedCluster?.description === null"(click)="closeModalAndSaveCluster()"
pTooltip="Upload file is required before save" showDelay="2000" >{{'CLUSTERS.SAVE' | translate}}</button>
</div>
</nmaas-modal>
......@@ -129,18 +129,5 @@ describe('ClusterManagerComponent', () => {
expect(component.updatedFile.name).toBe('test.yaml');
});
it('should call closeModalAndSaveCluster and reset state after saving', () => {
const mockFile = new File(['test content'], 'test.yaml', { type: 'application/x-yaml' });
// const mockCluster = { id: 3, name: 'Cluster C', codename: 'CodeC' };
component.updatedFile = mockFile;
component.addedCluster = mockClusters[0];
clusterService.sendCluster.and.returnValue(of(mockClusters[0]));
component.closeModalAndSaveCluster();
expect(clusterService.sendCluster).toHaveBeenCalledWith(mockFile, mockClusters[0]);
// expect(component.updatedFile).toBeNull();
// expect(component.addedCluster).toEqual(new ClusterManager());
});
});
\ No newline at end of file
......@@ -13,24 +13,20 @@ export class ClusterManagerComponent {
public clusters: ClusterManager[] = [];
public addedCluster: ClusterManager = new ClusterManager();
public updatedFile: File = null;
public maxItemsOnPage = 15;
public assignedDomain: boolean = false;
public searchValue = '';
filteredClusters: ClusterManager[] = [];
public domains = [];
@ViewChild(ModalComponent, { static: true })
public modal: ModalComponent;
constructor(private clusterService: ClusterManagerService,
private domainService: DomainService) {
) {
this.getAllClusters();
this.domainService.getAllBase().subscribe(result => {
this.domains = result.filter(d => d.id !== this.domainService.getGlobalDomainId());
});
}
public saveFile(event: any) {
......@@ -46,35 +42,6 @@ export class ClusterManagerComponent {
})
}
public closeModalAndSaveCluster() {
if(this.addedCluster.domainNames !== undefined && this.addedCluster.domainNames !== null) {
this.addedCluster.domainNames = [null];
}
console.log(this.addedCluster);
this.clusterService.sendCluster(this.updatedFile, this.addedCluster).subscribe(result => {
console.log(result);
this.getAllClusters();
this.modal.hide();
this.updatedFile = null;
this.addedCluster = new ClusterManager();
}, error => {
console.error(error);
})
}
public onDomainSelection(event: any) {
console.log(event);
this.addedCluster.domainNames = [event]
}
public openModal() {
if (this.domains.length > 0) {
this.addedCluster.domainNames = [this.domains[0].name];
}
this.modal.show();
}
public deleteCluster(cluster: ClusterManager) {
this.clusterService.deleteCluster(cluster.id).subscribe(() => {
......@@ -86,10 +53,6 @@ public deleteCluster(cluster: ClusterManager) {
);
}
public onDomainChange(event: any) {
console.log(event);
this.addedCluster.domainNames = [this.domains[0].name];
}
filterClusters() {
const value = this.searchValue?.toLowerCase() || '';
......
......@@ -74,7 +74,9 @@ import { RolesExcludedDirective } from '../directive/roles-exluded.directive';
import { FileUploadModule } from 'primeng/fileupload';
import { RecaptchaVisibilityService } from '../service/recaptcha-visibility.service';
import {CalendarModule} from 'primeng/calendar';
import { AddClusterComponent } from './admin/clusters/add-cluster/add-cluster.component';
import { StepsModule } from 'primeng/steps';
import {InputTextareaModule} from 'primeng/inputtextarea';
......@@ -104,7 +106,9 @@ import {CalendarModule} from 'primeng/calendar';
ChartModule,
FileUploadModule,
TableModule,
CalendarModule
CalendarModule,
StepsModule,
InputTextareaModule
],
declarations: [
RateComponent,
......@@ -153,6 +157,8 @@ import {CalendarModule} from 'primeng/calendar';
DomainNamespaceAnnotationsComponent,
AccessTokensComponent,
AdminDashboardComponent,
AddClusterComponent
],
providers: [
PasswordValidator,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment