Merge pull request #1326 from GNS3/bugfix/imageProgressBar

Bugfix/image progress bar
This commit is contained in:
Jeremy Grossmann 2022-06-10 12:08:53 +08:00 committed by GitHub
commit 7d706a39f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1242 additions and 1234 deletions

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"angular.enable-strict-mode-prompt": false
}

View File

@ -26,7 +26,8 @@
"rxjs/add/operator/map", "rxjs/add/operator/map",
"rxjs-compat/add/operator/map", "rxjs-compat/add/operator/map",
"classnames", "classnames",
"stylenames" "stylenames",
"source-map-js"
], ],
"outputPath": "dist", "outputPath": "dist",
"index": "src/index.html", "index": "src/index.html",
@ -55,12 +56,10 @@
}, },
"configurations": { "configurations": {
"production": { "production": {
"budgets": [ "budgets": [{
{
"type": "anyComponentStyle", "type": "anyComponentStyle",
"maximumWarning": "6kb" "maximumWarning": "6kb"
} }],
],
"optimization": true, "optimization": true,
"outputHashing": "all", "outputHashing": "all",
"sourceMap": { "sourceMap": {
@ -72,20 +71,16 @@
"extractLicenses": true, "extractLicenses": true,
"vendorChunk": false, "vendorChunk": false,
"buildOptimizer": true, "buildOptimizer": true,
"fileReplacements": [ "fileReplacements": [{
{
"replace": "src/environments/environment.ts", "replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts" "with": "src/environments/environment.prod.ts"
} }]
]
}, },
"electronProd": { "electronProd": {
"budgets": [ "budgets": [{
{
"type": "anyComponentStyle", "type": "anyComponentStyle",
"maximumWarning": "6kb" "maximumWarning": "6kb"
} }],
],
"optimization": true, "optimization": true,
"outputHashing": "all", "outputHashing": "all",
"sourceMap": false, "sourceMap": false,
@ -93,34 +88,26 @@
"extractLicenses": true, "extractLicenses": true,
"vendorChunk": false, "vendorChunk": false,
"buildOptimizer": true, "buildOptimizer": true,
"fileReplacements": [ "fileReplacements": [{
{
"replace": "src/environments/environment.ts", "replace": "src/environments/environment.ts",
"with": "src/environments/environment.electron.prod.ts" "with": "src/environments/environment.electron.prod.ts"
} }]
]
}, },
"electronDev": { "electronDev": {
"budgets": [ "budgets": [{
{
"type": "anyComponentStyle", "type": "anyComponentStyle",
"maximumWarning": "6kb" "maximumWarning": "6kb"
} }],
], "fileReplacements": [{
"fileReplacements": [
{
"replace": "src/environments/environment.ts", "replace": "src/environments/environment.ts",
"with": "src/environments/environment.electron.ts" "with": "src/environments/environment.electron.ts"
} }]
]
}, },
"githubProd": { "githubProd": {
"budgets": [ "budgets": [{
{
"type": "anyComponentStyle", "type": "anyComponentStyle",
"maximumWarning": "6kb" "maximumWarning": "6kb"
} }],
],
"optimization": true, "optimization": true,
"outputHashing": "all", "outputHashing": "all",
"sourceMap": false, "sourceMap": false,
@ -128,12 +115,10 @@
"extractLicenses": true, "extractLicenses": true,
"vendorChunk": false, "vendorChunk": false,
"buildOptimizer": true, "buildOptimizer": true,
"fileReplacements": [ "fileReplacements": [{
{
"replace": "src/environments/environment.ts", "replace": "src/environments/environment.ts",
"with": "src/environments/environment.github.prod.ts" "with": "src/environments/environment.github.prod.ts"
} }]
]
} }
} }
}, },

View File

@ -59,6 +59,7 @@
"@types/react-dom": "^17.0.11", "@types/react-dom": "^17.0.11",
"angular-draggable-droppable": "^6.1.0", "angular-draggable-droppable": "^6.1.0",
"angular-resizable-element": "^3.4.0", "angular-resizable-element": "^3.4.0",
"autoprefixer": "10.4.5",
"bootstrap": "^5.1.3", "bootstrap": "^5.1.3",
"command-exists": "^1.2.9", "command-exists": "^1.2.9",
"core-js": "^3.22.3", "core-js": "^3.22.3",
@ -103,8 +104,8 @@
"@types/jasmine": "^4.0.3", "@types/jasmine": "^4.0.3",
"@types/jasminewd2": "^2.0.10", "@types/jasminewd2": "^2.0.10",
"@types/node": "^17.0.31", "@types/node": "^17.0.31",
"codelyzer": "^6.0.2", "codelyzer": "^0.0.28",
"electron": "^13.2.2", "electron": "13.6.6",
"electron-builder": "^23.0.3", "electron-builder": "^23.0.3",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"jasmine-core": "^4.1.0", "jasmine-core": "^4.1.0",
@ -126,7 +127,7 @@
"ts-node": "^10.7.0", "ts-node": "^10.7.0",
"tslint": "^6.1.3", "tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0", "tslint-config-prettier": "^1.18.0",
"typescript": "^4.6.4", "typescript": "4.6.4",
"webpack": "^5.72.0", "webpack": "^5.72.0",
"yarn-upgrade-all": "^0.7.1" "yarn-upgrade-all": "^0.7.1"
}, },

View File

@ -23,7 +23,7 @@ export class UploadingProcessbarComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.subscription = this._US.currentCount.subscribe((count:number) => { this.subscription = this._US.currentCount.subscribe((count:number) => {
this.uploadProgress = count; this.uploadProgress = count;
if (this.uploadProgress === 100) { if (this.uploadProgress === 100 || this.uploadProgress == null ) {
this.dismiss() this.dismiss()
} }
}) })

View File

@ -11,7 +11,12 @@
</div> </div>
<div mat-dialog-content> <div mat-dialog-content>
<mat-radio-group name="install_appliances" class="choose-instal-appliance" [value]="install_appliance" (change)="selectInstallApplianceOption($event)"> <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"> <mat-radio-button value="true" class="instal-appliances-button" [checked]="install_appliance">
Yes Yes
</mat-radio-button> </mat-radio-button>
@ -25,46 +30,76 @@
</div> </div>
</div> </div>
<div *ngIf="isInstallAppliance">
<div class="row" style="display: flex">
<div *ngIf="!isExistImage && isInstallAppliance">
<div class="row" style="display: flex;">
<div class="col-md-6"> <div class="col-md-6">
<h5>Please Select image</h5> <h5 *ngIf="uploaderImage.queue.length <= 0">Please Select image</h5>
<p *ngIf="uploaderImage.queue.length > 0"class="uploaded-text">Uploaded image details</p>
</div> </div>
<div class="col-md-4 txt-align"> <div class="col-md-4 txt-align" *ngIf="uploaderImage.queue.length <= 0">
<input type="file" accept=".qcow2, .bin,.image,.qcow2,.vmdk,.img,.tmp" multiple #file class="non-visible" <input
(change)="uploadImageFile($event)" /> type="file"
accept=".qcow2, .bin,.image,.qcow2,.vmdk,.img,.tmp"
multiple
#file
class="non-visible"
ng2FileSelect
[uploader]="uploaderImage"
(onFileSelected)="uploadImageFile($event)"
/>
<button mat-raised-button color="primary" (click)="file.click()" class="file-button">Browse</button> <button mat-raised-button color="primary" (click)="file.click()" class="file-button">Browse</button>
</div> </div>
<div class="col-md-2 txt-align"> <div class="{{uploaderImage.queue.length > 0 ? 'col-md-6 txt-align' : 'col-md-2 txt-align'}} ">
<button mat-button (click)="dialogRef.close()"> <button mat-button (click)="dialogRef.close()">
<mat-icon>close</mat-icon> <mat-icon>close</mat-icon>
</button> </button>
</div> </div>
</div> </div>
<div class="row" *ngFor="let img of selectFile; let i = index">
<mat-title> {{i+1}}. {{img?.name}} </mat-title>
<div class="row" *ngFor="let img of uploaderImage.queue; let i = index">
<div class="{{img.isError || img.isUploaded ? 'col-md-10' : 'col-md-6'}}">
<mat-title [ngClass]="{ 'uploaded-error-text': img.isError }">
{{ i + 1 }}. {{ img.isError ? 'Image ' : '' }} {{ img?.some?.name }}
{{ img.isError ? ' already exists' : '' }}
</mat-title>
</div> </div>
<div class="row"> <div class="{{img.isError || img.isUploaded ? 'col-md-1' : 'col-md-4'}}" *ngIf="!img.isUploaded">
<div class="col-md-12"> <mat-progress-bar
<div *ngIf="uploadedFile"> mode="determinate"
<mat-progress-bar mode="determinate" [value]="uploadProgress" aria-valuemin="0" aria-valuemax="100"> *ngIf="img?.some?.name === uploadProgress.some?.name"
[value]="uploadProgress.progress"
aria-valuemin="0"
aria-valuemax="100"
></mat-progress-bar>
<mat-progress-bar
mode="determinate"
*ngIf="img?.some?.name !== uploadProgress.some?.name && !img.isUploaded"
[value]="0"
aria-valuemin="0"
aria-valuemax="100"
>
</mat-progress-bar> </mat-progress-bar>
</div> </div>
<div class="{{img.isError || img.isUploaded ? 'col-md-1' : 'col-md-2'}}"
*ngIf="img?.some?.name === uploadProgress.some?.name && img.isReady && !img.isUploaded"
>
{{ uploadProgress.progress + ' %' }}
</div>
<div
class="col-md-2"
*ngIf="img?.some?.name !== uploadProgress.some?.name && img.isReady && !uploadProgress.isUploaded"
>
{{ '0 %' }}
</div> </div>
</div> </div>
</div> <div mat-dialog-actions align="end" *ngIf="uploaderImage.queue.length > 0 && !uploadProgress.isUploaded">
<div *ngIf="isExistImage"> <button
<div mat-dialog-content> mat-raised-button
<div *ngIf="uploadFileMessage.length > 0"> color="primary"
<p class="uploaded-text">Uploaded image details</p> (click)="uploaderImage.queue.length > 0 ? cancelUploading() : dialogRef.close(false)"
<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> Cancel
</div> </button>
<div mat-dialog-actions align="end">
<button mat-raised-button color="primary" (click)="dialogRef.close(false)">Close</button>
</div> </div>
</div> </div>

View File

@ -14,6 +14,7 @@ import { AddImageDialogComponent } from './add-image-dialog.component';
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ToasterService } from 'app/services/toaster.service'; import { ToasterService } from 'app/services/toaster.service';
import { MockedToasterService } from 'app/services/toaster.service.spec'; import { MockedToasterService } from 'app/services/toaster.service.spec';
import { MatSnackBarModule } from '@angular/material/snack-bar';
export class MockedImageManagerService { export class MockedImageManagerService {
public getImages(server: Server) { public getImages(server: Server) {
@ -37,7 +38,8 @@ describe('AddImageDialogComponent', () => {
MatToolbarModule, MatToolbarModule,
MatMenuModule, MatMenuModule,
MatCheckboxModule, MatCheckboxModule,
MatDialogModule MatDialogModule,
MatSnackBarModule
], ],
providers: [ providers: [
{ provide: ServerService, useValue: mockedServerService }, { provide: ServerService, useValue: mockedServerService },

View File

@ -1,11 +1,11 @@
import { Component, DoCheck, Inject, OnInit } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations'; import { animate, state, style, transition, trigger } from '@angular/animations';
import { Component, Inject, OnInit } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { UploadServiceService } from 'app/common/uploading-processbar/upload-service.service';
import { FileItem, FileUploader, ParsedResponseHeaders } from 'ng2-file-upload';
import { Server } from '../../../models/server'; import { Server } from '../../../models/server';
import { ImageManagerService } from '../../../services/image-manager.service'; import { ImageManagerService } from '../../../services/image-manager.service';
import { Observable, of } from 'rxjs'; import { ToasterService } from '../../../services/toaster.service';
import { catchError } from 'rxjs/operators';
import { ImageData } from '../../../models/images';
@Component({ @Component({
selector: 'app-add-image-dialog', selector: 'app-add-image-dialog',
@ -19,58 +19,93 @@ import { ImageData } from '../../../models/images';
]), ]),
], ],
}) })
export class AddImageDialogComponent implements OnInit {
export class AddImageDialogComponent implements OnInit,DoCheck {
server: Server; server: Server;
uploadedFile: boolean = false; isInstallAppliance: boolean = false;
isExistImage: boolean = false; install_appliance: boolean = false;
isInstallAppliance: boolean = false
install_appliance: boolean = false
selectFile: any = []; selectFile: any = [];
uploadFileMessage: ImageData = [] uploaderImage: FileUploader;
uploadProgress:number = 0 uploadProgress: number = 0;
constructor( constructor(
@Inject(MAT_DIALOG_DATA) public data: any, @Inject(MAT_DIALOG_DATA) public data: any,
public dialogRef: MatDialogRef<AddImageDialogComponent>, public dialogRef: MatDialogRef<AddImageDialogComponent>,
private imageService: ImageManagerService, private imageService: ImageManagerService,
private toasterService: ToasterService,
private uploadServiceService: UploadServiceService
) {} ) {}
public ngOnInit(): void { public ngOnInit() {
this.server = this.data this.server = this.data;
this.uploaderImage = new FileUploader({});
this.uploaderImage.onAfterAddingFile = (file) => {
file.withCredentials = false;
};
this.uploaderImage.onErrorItem = (
item: FileItem,
response: string,
status: number,
headers: ParsedResponseHeaders
) => {
let responseData = {
name: item.file.name,
message: JSON.parse(response),
};
this.toasterService.error(responseData?.message.message);
};
this.uploaderImage.onSuccessItem = (
item: FileItem,
response: string,
status: number,
headers: ParsedResponseHeaders
) => {
let responseData = {
filename: item.file.name,
message: JSON.parse(response),
};
this.toasterService.success('Image ' + responseData?.message.filename + ' imported succesfully' );
};
this.uploaderImage.onProgressItem = (progress: any) => {
this.uploadProgress = progress;
};
}
cancelUploading() {
this.uploaderImage.clearQueue();
this.dialogRef.close();
this.uploadServiceService.processBarCount(null);
this.toasterService.warning('Image file Uploading canceled');
} }
selectInstallApplianceOption(ev) { selectInstallApplianceOption(ev) {
this.install_appliance = ev.value this.install_appliance = ev.value;
} }
async uploadImageFile(event) { async uploadImageFile(event) {
for (let imgFile of event.target.files) { for (let imgFile of event) {
this.selectFile.push(imgFile) this.selectFile.push(imgFile);
} }
await this.upload() await this.importImageFile();
} }
// files uploading // files uploading
upload() { importImageFile() {
const calls = []; this.selectFile.forEach((event, i) => {
this.uploadedFile = true; let fileName = event.name;
this.selectFile.forEach(imgElement => { let file = event;
calls.push(this.imageService.uploadedImage(this.server, this.install_appliance, imgElement.name, imgElement).pipe(catchError(error => of(error)))) let fileReader: FileReader = new FileReader();
}); fileReader.onloadend = () => {
this.uploadProgress = calls.length const url = this.imageService.getImagePath(this.server, this.install_appliance, fileName);
Observable.forkJoin(calls).subscribe(responses => { const itemToUpload = this.uploaderImage.queue[i];
this.uploadFileMessage = responses itemToUpload.url = url;
this.uploadedFile = false; if ((itemToUpload as any).options) (itemToUpload as any).options.disableMultipart = true;
this.isExistImage = true; (itemToUpload as any).options.headers = [{ name: 'Authorization', value: 'Bearer ' + this.server.authToken }];
this.uploaderImage.uploadItem(itemToUpload);
};
fileReader.readAsText(file);
}); });
} }
ngDoCheck(){
setTimeout(() => {
if(this.uploadProgress < 95){
this.uploadProgress = this.uploadProgress + 1
}
}, 100000);
}
} }

View File

@ -123,7 +123,7 @@ export class ImageManagerComponent implements OnInit {
deleteAllFiles() { deleteAllFiles() {
const dialogRef = this.dialog.open(DeleteAllImageFilesDialogComponent, { const dialogRef = this.dialog.open(DeleteAllImageFilesDialogComponent, {
width: '500px', width: '550px',
maxHeight: '650px', maxHeight: '650px',
autoFocus: false, autoFocus: false,
disableClose: true, disableClose: true,

View File

@ -274,7 +274,7 @@ export class AddIosTemplateComponent implements OnInit, OnDestroy {
cancelUploading() { cancelUploading() {
this.uploader.clearQueue(); this.uploader.clearQueue();
this.uploadServiceService.processBarCount(100) this.uploadServiceService.processBarCount(null)
this.toasterService.warning('Image upload cancelled'); this.toasterService.warning('Image upload cancelled');
// this.uploadServiceService.cancelFileUploading(false) // this.uploadServiceService.cancelFileUploading(false)
// window.location.reload() // window.location.reload()

View File

@ -162,7 +162,7 @@ export class AddQemuVmTemplateComponent implements OnInit {
cancelUploading() { cancelUploading() {
this.uploader.clearQueue(); this.uploader.clearQueue();
this.uploadServiceService.processBarCount(100) this.uploadServiceService.processBarCount(null)
this.toasterService.warning('Image Uploading canceled'); this.toasterService.warning('Image Uploading canceled');
this.uploadServiceService.cancelFileUploading(false) this.uploadServiceService.cancelFileUploading(false)

View File

@ -370,7 +370,7 @@
type="file" type="file"
class="non-visible" class="non-visible"
#file2 #file2
(change)="importImage($, image.filename)" (change)="importImage($event, image.filename)"
ng2FileSelect ng2FileSelect
[uploader]="uploaderImage" [uploader]="uploaderImage"
/> />

View File

@ -185,7 +185,7 @@ export class NewTemplateDialogComponent implements OnInit {
status: number, status: number,
headers: ParsedResponseHeaders headers: ParsedResponseHeaders
) => { ) => {
this.toasterService.error('An error has occured'); this.toasterService.error('An error has occured because image already exists');
this.progressService.deactivate(); this.progressService.deactivate();
this.uploaderImage.clearQueue(); this.uploaderImage.clearQueue();
}; };
@ -428,7 +428,7 @@ export class NewTemplateDialogComponent implements OnInit {
cancelUploading() { cancelUploading() {
this.uploaderImage.clearQueue(); this.uploaderImage.clearQueue();
this.uploadServiceService.processBarCount(100) this.uploadServiceService.processBarCount(null)
this.toasterService.warning('Image upload cancelled'); this.toasterService.warning('Image upload cancelled');
this.uploadServiceService.cancelFileUploading(false) this.uploadServiceService.cancelFileUploading(false)

1740
yarn.lock

File diff suppressed because it is too large Load Diff