Merge pull request #1291 from GNS3/enhancement/1284

Enhancement/1284
This commit is contained in:
Jeremy Grossmann 2022-04-13 17:45:34 +07:00 committed by GitHub
commit 67c564caa7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 923 additions and 1 deletions

View File

@ -56,6 +56,7 @@ import { DefaultLayoutComponent } from './layouts/default-layout/default-layout.
import { ServerResolve } from './resolvers/server-resolve';
import { UserManagementComponent } from './components/user-management/user-management.component';
import { LoggedUserComponent } from './components/users/logged-user/logged-user.component';
import { ImageManagerComponent } from '@components/image-manager/image-manager.component';
const routes: Routes = [
{
@ -67,6 +68,7 @@ const routes: Routes = [
{ path: 'bundled', component: BundledServerFinderComponent },
{ path: 'server/:server_id/login', component: LoginComponent },
{ path: 'server/:server_id/loggeduser', component: LoggedUserComponent },
{path : 'server/:server_id/image-manager', component: ImageManagerComponent},
{
path: 'server/:server_id/projects',
component: ProjectsComponent,

View File

@ -272,6 +272,9 @@ import { HttpRequestsInterceptor } from './interceptors/http.interceptor';
import { UserManagementComponent } from './components/user-management/user-management.component'
import { UserService } from './services/user.service';
import { LoggedUserComponent } from './components/users/logged-user/logged-user.component';
import { ImageManagerComponent } from './components/image-manager/image-manager.component';
import { AddImageDialogComponent } from './components/image-manager/add-image-dialog/add-image-dialog.component';
import { DeleteAllImageFilesDialogComponent } from './components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component';
@NgModule({
declarations: [
@ -459,7 +462,10 @@ import { LoggedUserComponent } from './components/users/logged-user/logged-user.
ConfigureCustomAdaptersDialogComponent,
EditNetworkConfigurationDialogComponent,
UserManagementComponent,
ProjectReadmeComponent
ProjectReadmeComponent,
ImageManagerComponent,
AddImageDialogComponent,
DeleteAllImageFilesDialogComponent,
],
imports: [
BrowserModule,

View File

@ -0,0 +1,70 @@
<div *ngIf="!isInstallAppliance">
<div class="row">
<div class="col-md-10">
<h5>Would you like to automatically install appliances for this image?</h5>
</div>
<div class="col-md-2 txt-align">
<button mat-button (click)="dialogRef.close()">
<mat-icon>close</mat-icon>
</button>
</div>
</div>
<div mat-dialog-content>
<mat-radio-group name="install_appliances" class="choose-instal-appliance" [value]="install_appliance" (change)="selectInstallApplianceOption($event)">
<mat-radio-button value="true" class="instal-appliances-button" [checked]="install_appliance">
Yes
</mat-radio-button>
<mat-radio-button value="false" class="instal-appliances-button" [checked]="!install_appliance">
No
</mat-radio-button>
</mat-radio-group>
</div>
<div mat-dialog-actions align="end">
<button mat-raised-button color="primary" (click)="isInstallAppliance =!isInstallAppliance">Next</button>
</div>
</div>
<div *ngIf="!isExistImage && isInstallAppliance">
<div class="row" style="display: flex;">
<div class="col-md-6">
<h5>Please Select image</h5>
</div>
<div class="col-md-4 txt-align">
<input type="file" accept=".qcow2, .bin,.image,.qcow2,.vmdk,.img,.tmp" multiple #file class="non-visible"
(change)="uploadImageFile($event)" />
<button mat-raised-button color="primary" (click)="file.click()" class="file-button">Browse</button>
</div>
<div class="col-md-2 txt-align">
<button mat-button (click)="dialogRef.close()">
<mat-icon>close</mat-icon>
</button>
</div>
</div>
<div class="row" *ngFor="let img of selectFile; let i = index">
<mat-title> {{i+1}}. {{img?.name}} </mat-title>
</div>
<div class="row">
<div class="col-md-12">
<div *ngIf="uploadedFile">
<mat-progress-bar mode="indeterminate" [value]="uploadProgress" aria-valuemin="0" aria-valuemax="100">
</mat-progress-bar>
</div>
</div>
</div>
</div>
<div *ngIf="isExistImage">
<div mat-dialog-content>
<div *ngIf="uploadFileMessage.length > 0">
<p class="uploaded-text">Uploaded image details</p>
<p *ngFor="let uploadFile of uploadFileMessage; let i = index" [ngClass]="{'uploaded-error-text': uploadFile?.error?.message}">{{i+1}}. {{uploadFile?.filename ?? uploadFile?.error?.message}}</p>
</div>
</div>
<div mat-dialog-actions align="end">
<button mat-raised-button color="primary" (click)="dialogRef.close(false)">Close</button>
</div>
</div>

View File

@ -0,0 +1,83 @@
.progress-bar {
padding: 0;
}
.progress {
width: 50px;
background-color: #263238;
height: 28px;
margin-left: 13px;
}
.mat-input-element {
font-size: medium;
font-weight: 200;
}
#fileInput {
position: absolute;
cursor: pointer;
z-index: 10;
opacity: 0;
height: 100%;
left: 0px;
top: 0px;
}
.mat-toolbar-single-row {
height: auto;
background: transparent;
padding: 0;
}
.mat-toolbar-single-row button {
width: 100px;
}
.mat-form-field {
width: 100%;
}
.message {
background-color: #ddd;
padding: 15px;
color: #333;
border: #aaa solid 1px;
border-radius: 4px;
margin: 15px 0;
}
.preview {
max-width: 200px;
vertical-align: middle;
}
.list-card {
margin-top: 20px;
}
.list-item {
margin-bottom: 20px;
}
.non-visible {
display: none;
}
mat-progress-bar{
margin-top: 10px;
}
.txt-align{
text-align: end;
}
.uploaded-text{
color: #0ca8c7;
font-size: 17px;
}
.uploaded-error-text{
color: #d52435;
font-weight: 600;
}
.choose-instal-appliance {
display: flex;
flex-direction: row;
margin: 15px 0px 11px 0px;
align-items: flex-start;
}
.instal-appliances-button {
margin: 11px;
}

View File

@ -0,0 +1,64 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialog, MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatToolbarModule } from '@angular/material/toolbar';
import { ImageManagerService } from 'app/services/image-manager.service';
import { ServerService } from '../../../services/server.service';
import { MockedServerService } from '../../../services/server.service.spec';
import { of } from 'rxjs';
import { Server } from '../../../models/server';
import { AddImageDialogComponent } from './add-image-dialog.component';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ToasterService } from 'app/services/toaster.service';
import { MockedToasterService } from 'app/services/toaster.service.spec';
export class MockedImageManagerService {
public getImages(server: Server) {
return of();
}
}
describe('AddImageDialogComponent', () => {
let component: AddImageDialogComponent;
let fixture: ComponentFixture<AddImageDialogComponent>;
let mockedServerService = new MockedServerService();
let mockedImageManagerService = new MockedImageManagerService()
let mockedToasterService = new MockedToasterService()
beforeEach(async () => {
await TestBed.configureTestingModule({
imports:[
MatIconModule,
MatToolbarModule,
MatMenuModule,
MatCheckboxModule,
MatDialogModule
],
providers: [
{ provide: ServerService, useValue: mockedServerService },
{ provide: ImageManagerService, useValue: mockedImageManagerService },
{ provide: MAT_DIALOG_DATA, useValue: {} },
{ provide: MatDialogRef, useValue: {} },
{ provide: ToasterService, useValue: mockedToasterService },
],
declarations: [ AddImageDialogComponent ],
schemas: [NO_ERRORS_SCHEMA],
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AddImageDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,67 @@
import { Component, Inject, OnInit } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Server } from '../../../models/server';
import { ImageManagerService } from '../../../services/image-manager.service';
import { Observable, of } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { ImageData } from '../../../models/images';
@Component({
selector: 'app-add-image-dialog',
templateUrl: './add-image-dialog.component.html',
styleUrls: ['./add-image-dialog.component.scss'],
animations: [
trigger('detailExpand', [
state('collapsed', style({ height: '0px', minHeight: '0', visibility: 'hidden' })),
state('expanded', style({ height: '*', visibility: 'visible' })),
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
]),
],
})
export class AddImageDialogComponent implements OnInit {
server: Server;
uploadedFile: boolean = false;
isExistImage: boolean = false;
isInstallAppliance: boolean = false
install_appliance: boolean = false
selectFile: any = [];
uploadFileMessage: ImageData = []
constructor(
@Inject(MAT_DIALOG_DATA) public data: any,
public dialogRef: MatDialogRef<AddImageDialogComponent>,
private imageService: ImageManagerService,
) { }
public ngOnInit(): void {
this.server = this.data
}
selectInstallApplianceOption(ev) {
this.install_appliance = ev.value
}
async uploadImageFile(event) {
for (let imgFile of event.target.files) {
this.selectFile.push(imgFile)
}
await this.upload()
}
// files uploading
upload() {
const calls = [];
this.uploadedFile = true;
this.selectFile.forEach(imgElement => {
calls.push(this.imageService.uploadedImage(this.server, this.install_appliance, imgElement.name, imgElement).pipe(catchError(error => of(error))))
});
Observable.forkJoin(calls).subscribe(responses => {
this.uploadFileMessage = responses
this.uploadedFile = false;
this.isExistImage = true;
});
}
}

View File

@ -0,0 +1,34 @@
<div *ngIf="!isDelete && !isUsedFiles">
<h1 mat-dialog-title>Do you want delete all files ?.</h1>
<div mat-dialog-content>
<p>Your selected files</p>
<p *ngFor="let file of deleteData?.deleteFilesPaths; let i = index">{{i+1}}. {{file?.filename}}</p>
</div>
<div mat-dialog-actions align="end">
<button mat-button (click)="deleteAll()">Delete</button>
<button mat-button mat-dialog-close cdkFocusInitial>Cancel</button>
</div>
</div>
<div *ngIf="isDelete && !isUsedFiles">
<h1 align="center" mat-dialog-title>Please wait.</h1>
<div mat-dialog-content align="center">
<mat-spinner color="accent"></mat-spinner>
</div>
</div>
<div *ngIf="isDelete && isUsedFiles">
<div mat-dialog-content>
<div *ngIf="deleteFliesDetails.length > 0">
<h5>Images can't be deleted because image used in one or more template.</h5>
<p *ngFor="let message of deleteFliesDetails; let i = index" [ngClass]="{'deleted-error-text': message?.error?.message}"><span *ngIf="message !=null">{{i+1}}. {{message?.error?.message}}</span></p>
</div>
<div *ngIf="fileNotDeleted.length > 0">
<h5 class="delete-text">{{fileNotDeleted.length}} Images deleted successfully.</h5>
</div>
</div>
<div mat-dialog-actions align="end">
<button mat-raised-button color="primary" (click)="dialogRef.close(false)">Close</button>
</div>
</div>

View File

@ -0,0 +1,11 @@
.delete-text{
color: #0ca8c7;
font-size: 17px;
}
.deleted-error-text{
color: #d52435;
font-weight: 600;
}

View File

@ -0,0 +1,65 @@
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatToolbarModule } from '@angular/material/toolbar';
import { ToasterService } from 'app/services/toaster.service';
import { MockedToasterService } from 'app/services/toaster.service.spec';
import { Server } from 'http';
import { of } from 'rxjs';
import { ImageManagerService } from '../../../services/image-manager.service';
import { ServerService } from '../../../services/server.service';
import { MockedServerService } from '../../../services/server.service.spec';
import { ImageManagerComponent } from '../image-manager.component';
import { DeleteAllImageFilesDialogComponent } from './deleteallfiles-dialog.component';
export class MockedImageManagerService {
public deleteALLFile(server: Server, image_path) {
return of();
}
}
describe('DeleteAllImageFilesDialogComponent', () => {
let component: DeleteAllImageFilesDialogComponent;
let fixture: ComponentFixture<DeleteAllImageFilesDialogComponent>;
let mockedServerService = new MockedServerService();
let mockedImageManagerService = new MockedImageManagerService()
let mockedToasterService = new MockedToasterService()
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
MatIconModule,
MatToolbarModule,
MatMenuModule,
MatCheckboxModule,
MatDialogModule,
],
providers: [
{ provide: ServerService, useValue: mockedServerService },
{ provide: ImageManagerService, useValue: mockedImageManagerService },
{ provide: MAT_DIALOG_DATA, useValue: {} },
{ provide: MatDialogRef, useValue: {} },
{ provide: ToasterService, useValue: mockedToasterService },
],
declarations: [DeleteAllImageFilesDialogComponent,
],
schemas: [NO_ERRORS_SCHEMA],
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DeleteAllImageFilesDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,50 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { ImageManagerService } from '../../../services/image-manager.service';
import { ToasterService } from '../../../services/toaster.service';
import { Observable, of } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { ImageData } from '../../../models/images';
@Component({
selector: 'app-deleteallfiles-dialog',
templateUrl: './deleteallfiles-dialog.component.html',
styleUrls: ['./deleteallfiles-dialog.component.scss']
})
export class DeleteAllImageFilesDialogComponent implements OnInit {
isDelete: boolean = false;
isUsedFiles: boolean = false;
deleteFliesDetails: ImageData = []
fileNotDeleted: ImageData = []
constructor(
@Inject(MAT_DIALOG_DATA) public deleteData: any,
public dialogRef: MatDialogRef<DeleteAllImageFilesDialogComponent>,
private imageService: ImageManagerService,
private toasterService: ToasterService
) { }
ngOnInit(): void {
}
async deleteAll() {
this.isDelete = true
await this.deleteFile()
}
deleteFile() {
const calls = [];
this.deleteData.deleteFilesPaths.forEach(pathElement => {
calls.push(this.imageService.deleteFile(this.deleteData.server, pathElement.filename).pipe(catchError(error => of(error))))
});
Observable.forkJoin(calls).subscribe(responses => {
this.deleteFliesDetails = responses.filter(x => x !== null)
this.fileNotDeleted = responses.filter(x => x === null)
this.isUsedFiles = true;
this.isDelete = true
});
}
}

View File

@ -0,0 +1,36 @@
import { DataSource, SelectionModel } from '@angular/cdk/collections';
import { MatSort } from '@angular/material/sort';
import { BehaviorSubject, Observable, Subscription, merge } from 'rxjs';
import { map } from 'rxjs/operators';
import { Image } from '../../models/images';
export class imageDatabase {
dataChange: BehaviorSubject<Image[]> = new BehaviorSubject<Image[]>([]);
get data(): Image[] {
return this.dataChange.value;
}
public addImages(fileData: Image[]) {
this.dataChange.next(fileData);
}
}
export class imageDataSource extends DataSource<Image> {
constructor(private serverDatabase: imageDatabase) {
super();
}
connect(): Observable<Image[]> {
return merge(this.serverDatabase.dataChange).pipe(
map(() => {
return this.serverDatabase.data;
})
);
}
disconnect() { }
}

View File

@ -0,0 +1,70 @@
<div class="content">
<div class="default-header">
<div class="row">
<div class="col-md-9">
<h1>Image Manager</h1>
</div>
<div class="col-md-3 btn-box">
<button class="img-btn" mat-button
(click)="addImageDialog()">
<mat-icon>add</mat-icon> Add Image
</button>
</div>
</div>
</div>
<div class="default-content">
<app-server-discovery></app-server-discovery>
<div class="mat-elevation-z8">
<mat-table #table [dataSource]="dataSource">
<ng-container matColumnDef="select">
<mat-header-cell *matHeaderCellDef>
<mat-checkbox (change)="$event ? selectAllImages() : null" [checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</mat-header-cell>
<mat-cell *matCellDef="let row">
<mat-checkbox (click)="$event.stopPropagation()" (change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)">
</mat-checkbox>
</mat-cell>
</ng-container>
<ng-container matColumnDef="filename">
<mat-header-cell *matHeaderCellDef> File Name </mat-header-cell>
<mat-cell *matCellDef="let row">
<a class="table-link">{{ row.filename }}</a>
</mat-cell>
</ng-container>
<ng-container matColumnDef="image_type">
<mat-header-cell *matHeaderCellDef> Image Type </mat-header-cell>
<mat-cell *matCellDef="let row"> {{ row.image_type }} </mat-cell>
</ng-container>
<ng-container matColumnDef="image_size">
<mat-header-cell *matHeaderCellDef> Image Size </mat-header-cell>
<mat-cell *matCellDef="let row"> {{ (row.image_size/1000000).toFixed()}} MB </mat-cell>
</ng-container>
<ng-container matColumnDef="delete" >
<mat-header-cell *matHeaderCellDef>
<button mat-button *ngIf="(selection.hasValue() && isAllSelected()) || selection.selected.length > 1" (click)="deleteAllFiles()" aria-label="Example icon button with a delete icon">
<mat-icon>delete</mat-icon>
</button>
</mat-header-cell>
<mat-cell *matCellDef="let row" >
<button mat-button *ngIf="selection.isSelected(row)" (click)="deleteFile(row.path)" aria-label="Example icon button with a delete icon">
<mat-icon>delete</mat-icon>
</button>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
</mat-table>
</div>
</div>
</div>

View File

@ -0,0 +1,17 @@
.non-visible {
display: none;
}
.img-btn{
margin: auto;
}
.btn-box{
display: flex;
margin-top: 10px;
}
mat-header-cell, mat-cell {
justify-content: center;
}

View File

@ -0,0 +1,96 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatToolbarModule } from '@angular/material/toolbar';
import { ImageManagerService } from 'app/services/image-manager.service';
import { ServerService } from 'app/services/server.service';
import { MockedServerService } from 'app/services/server.service.spec';
import { of } from 'rxjs';
import { Server } from '../../models/server';
import { ImageManagerComponent } from './image-manager.component';
import { Image } from '../../models/images';
import { ProgressService } from 'app/common/progress/progress.service';
import { MockedProgressService } from '../project-map/project-map.component.spec';
import { MockedActivatedRoute } from '../preferences/preferences.component.spec';
import { ActivatedRoute } from '@angular/router';
import { MockedVersionService } from '../../services/version.service.spec';
import { VersionService } from 'app/services/version.service';
import { ToasterService } from 'app/services/toaster.service';
import { MockedToasterService } from 'app/services/toaster.service.spec';
export class MockedImageManagerService {
public getImages(server: Server) {
return of();
}
public deleteFile(server: Server, image_path) {
return of();
}
}
describe('ImageManagerComponent', () => {
let component: ImageManagerComponent;
let fixture: ComponentFixture<ImageManagerComponent>;
let mockedServerService = new MockedServerService();
let mockedImageManagerService = new MockedImageManagerService()
let mockedProgressService = new MockedProgressService()
let mockedVersionService = new MockedVersionService()
let mockedToasterService = new MockedToasterService()
let activatedRoute = new MockedActivatedRoute().get();
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
MatIconModule,
MatToolbarModule,
MatMenuModule,
MatCheckboxModule,
MatDialogModule
],
providers: [
{
provide: ActivatedRoute,
useValue: activatedRoute,
},
{ provide: ServerService, useValue: mockedServerService },
{ provide: ImageManagerService, useValue: mockedImageManagerService },
{ provide: ProgressService, useValue: mockedProgressService },
{ provide: VersionService, useValue: mockedVersionService },
{ provide: ToasterService, useValue: mockedToasterService },
],
declarations: [ImageManagerComponent],
schemas: [NO_ERRORS_SCHEMA],
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ImageManagerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should call save images', () => {
spyOn(mockedImageManagerService, 'getImages').and.returnValue(of({} as Image));
component.getImages()
expect(mockedImageManagerService.getImages).toHaveBeenCalled();
});
it('should delete image', () => {
spyOn(mockedImageManagerService, 'deleteFile').and.returnValue(of({} as Image));
component.deleteFile('image_path')
expect(mockedImageManagerService.deleteFile).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,149 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ServerService } from '../../services/server.service';
import { VersionService } from '../../services/version.service';
import { ProgressService } from 'app/common/progress/progress.service';
import { Image } from '../../models/images';
import { Server } from '../../models/server';
import { ImageManagerService } from "../../services/image-manager.service";
import { DataSource, SelectionModel } from '@angular/cdk/collections';
import { AddImageDialogComponent } from './add-image-dialog/add-image-dialog.component';
import { MatDialog } from '@angular/material/dialog';
import { ToasterService } from '../../services/toaster.service';
import { DeleteAllImageFilesDialogComponent } from './deleteallfiles-dialog/deleteallfiles-dialog.component';
import { imageDataSource, imageDatabase } from "./image-database-file";
@Component({
selector: 'app-image-manager',
templateUrl: './image-manager.component.html',
styleUrls: ['./image-manager.component.scss']
})
export class ImageManagerComponent implements OnInit {
server: Server;
public version: string;
dataSource: imageDataSource;
imageDatabase = new imageDatabase();
isAllDelete: boolean = false
selection = new SelectionModel(true, []);
displayedColumns = ['select', 'filename', 'image_type', 'image_size','delete'];
constructor(
private imageService: ImageManagerService,
private progressService: ProgressService,
private route: ActivatedRoute,
private serverService: ServerService,
private versionService: VersionService,
private dialog: MatDialog,
private toasterService: ToasterService,
) { }
ngOnInit(): void {
let server_id = parseInt(this.route.snapshot.paramMap.get('server_id'));
this.serverService.get(server_id).then((server: Server) => {
this.server = server;
if (server.authToken) {
this.getImages()
}
// this.versionService.get(this.server).subscribe((version: Version) => {
// this.version = version.version;
// });
});
this.dataSource = new imageDataSource(this.imageDatabase);
}
getImages() {
this.imageService.getImages(this.server).subscribe(
(images: Image[]) => {
this.imageDatabase.addImages(images)
},
(error) => {
this.toasterService.error(error.error.message)
}
);
}
deleteFile(path) {
this.imageService.deleteFile(this.server, path).subscribe(
(res) => {
this.getImages()
this.unChecked()
this.toasterService.success('File deleted');
},
(error) => {
this.getImages()
this.unChecked()
this.toasterService.error(error.error.message)
}
);
}
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = this.imageDatabase.data.length;
return numSelected === numRows;
}
selectAllImages() {
this.isAllSelected() ? this.unChecked() : this.allChecked()
}
unChecked() {
this.selection.clear()
this.isAllDelete = false
}
allChecked() {
this.imageDatabase.data.forEach(row => this.selection.select(row))
this.isAllDelete = true;
}
public addImageDialog() {
const dialogRef = this.dialog.open(AddImageDialogComponent, {
width: '600px',
maxHeight: '550px',
autoFocus: false,
disableClose: true,
data: this.server
});
dialogRef.afterClosed().subscribe((isAddes: boolean) => {
if (isAddes) {
this.getImages()
this.unChecked()
} else {
this.getImages()
this.unChecked()
return false;
}
});
}
deleteAllFiles() {
const dialogRef = this.dialog.open(DeleteAllImageFilesDialogComponent, {
width: '500px',
maxHeight: '650px',
autoFocus: false,
disableClose: true,
data: {
server: this.server,
deleteFilesPaths: this.selection.selected
}
});
dialogRef.afterClosed().subscribe((isAllfilesdeleted: boolean) => {
if (isAllfilesdeleted) {
this.unChecked()
this.getImages()
this.toasterService.success('All files deleted');
} else {
this.unChecked()
this.getImages()
return false;
}
});
}
}

View File

@ -103,6 +103,10 @@
<mat-icon>settings</mat-icon>
<span>Go to settings</span>
</button>
<button mat-menu-item [routerLink]="['/server', server.id, 'image-manager']">
<mat-icon>collections</mat-icon>
<span>Image manager</span>
</button>
<button mat-menu-item (click)="addNewTemplate()">
<mat-icon>control_point</mat-icon>
<span>New template</span>

14
src/app/models/images.ts Normal file
View File

@ -0,0 +1,14 @@
export class Image {
filename: string;
path: string;
image_type: string;
image_size: number;
checksum: string;
checksum_algorithm: string;
created_at: string;
updated_at: string;
}
export class ImageData {
}

View File

@ -0,0 +1,60 @@
import { HttpClient } from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { inject, TestBed } from '@angular/core/testing';
import { AppTestingModule } from 'app/testing/app-testing/app-testing.module';
import { Server } from '../models/server';
import { HttpServer } from './http-server.service';
import { getTestServer } from './testing';
import { ImageManagerService } from './image-manager.service';
import { Image } from "../models/images";
describe('ImageManagerService', () => {
let httpClient: HttpClient;
let httpTestingController: HttpTestingController;
let httpServer: HttpServer;
let server: Server;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, AppTestingModule],
providers: [HttpServer, ImageManagerService],
});
httpClient = TestBed.get(HttpClient);
httpTestingController = TestBed.get(HttpTestingController);
httpServer = TestBed.get(HttpServer);
server = getTestServer();
// service = TestBed.inject(ImageManagerService);
});
afterEach(() => {
httpTestingController.verify();
});
it('should be get Images', inject([ImageManagerService], (service: ImageManagerService) => {
service.getImages(server).subscribe();
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v3/images');
expect(req.request.method).toEqual('GET');
expect(service).toBeTruthy();
}));
it('should add image', inject([ImageManagerService], (service: ImageManagerService) => {
let install_appliance = true
const image: Image = {
filename: '',
path: '',
image_type: '',
image_size: 0,
checksum: '',
checksum_algorithm: '',
created_at: '',
updated_at: '',
};
service.uploadedImage(server, install_appliance, image.filename, image).subscribe();
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v3/images/upload/?install_appliances=true');
expect(req.request.method).toEqual('POST');
expect(req.request.body).toEqual(image);
}));
});

View File

@ -0,0 +1,24 @@
import { Injectable } from '@angular/core';
import { Server } from '../models/server';
import { HttpServer } from './http-server.service';
import { Observable } from 'rxjs';
import { Image } from "../models/images";
@Injectable({
providedIn: 'root'
})
export class ImageManagerService {
constructor(private httpServer: HttpServer) { }
getImages(server: Server) {
return this.httpServer.get<Image[]>(server, '/images') as Observable<Image[]>;
}
uploadedImage(server:Server, install_appliance, image_path, flie){
return this.httpServer.post<Image[]>(server, `/images/upload/${image_path}?install_appliances=${install_appliance}`,flie) as Observable<Image[]>;
}
deleteFile(server:Server, image_path){
return this.httpServer.delete<Image[]>(server, `/images/${image_path}`) as Observable<Image[]>;
}
}