Add prune images and install images to image manager
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Build / Node 14 (push) Waiting to run
Build / Node 16 (push) Waiting to run
Build / Node 18 (push) Waiting to run

This commit is contained in:
grossmj 2025-02-15 23:57:30 +10:00
parent cd483e047b
commit 097efdcbcd
No known key found for this signature in database
GPG Key ID: 1E7DD6DBB53FF3D7
12 changed files with 122 additions and 31 deletions

View File

@ -31,7 +31,8 @@ import { ProgressComponent } from './common/progress/progress.component';
import { ProgressService } from './common/progress/progress.service'; import { ProgressService } from './common/progress/progress.service';
import { AdbutlerComponent } from './components/adbutler/adbutler.component'; import { AdbutlerComponent } from './components/adbutler/adbutler.component';
import { BundledControllerFinderComponent } from './components/bundled-controller-finder/bundled-controller-finder.component'; import { BundledControllerFinderComponent } from './components/bundled-controller-finder/bundled-controller-finder.component';
import { InformationDialogComponent } from './components/dialogs/information-dialog.component'; import { InformationDialogComponent } from './components/dialogs/information-dialog/information-dialog.component';
import { QuestionDialogComponent } from "./components/dialogs/question-dialog/question-dialog.component";
import { DirectLinkComponent } from './components/direct-link/direct-link.component'; import { DirectLinkComponent } from './components/direct-link/direct-link.component';
import { DrawingAddedComponent } from './components/drawings-listeners/drawing-added/drawing-added.component'; import { DrawingAddedComponent } from './components/drawings-listeners/drawing-added/drawing-added.component';
import { DrawingDraggedComponent } from './components/drawings-listeners/drawing-dragged/drawing-dragged.component'; import { DrawingDraggedComponent } from './components/drawings-listeners/drawing-dragged/drawing-dragged.component';
@ -511,6 +512,7 @@ import { DeleteResourceConfirmationDialogComponent } from './components/resource
ReadmeEditorComponent, ReadmeEditorComponent,
MarkedDirective, MarkedDirective,
InformationDialogComponent, InformationDialogComponent,
QuestionDialogComponent,
TemplateNameDialogComponent, TemplateNameDialogComponent,
ConfigureCustomAdaptersDialogComponent, ConfigureCustomAdaptersDialogComponent,
EditNetworkConfigurationDialogComponent, EditNetworkConfigurationDialogComponent,

View File

@ -0,0 +1,8 @@
<h1 mat-dialog-title>{{ data.title }}</h1>
<div mat-dialog-content>
<p>{{ data.question }}</p>
</div>
<div mat-dialog-actions>
<button mat-button (click)="onNoClick()">No</button>
<button mat-button (click)="onYesClick()">Yes</button>
</div>

View File

@ -0,0 +1,21 @@
import { Component, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
@Component({
selector: 'app-question-dialog',
templateUrl: './question-dialog.component.html',
})
export class QuestionDialogComponent {
constructor(
public dialogRef: MatDialogRef<QuestionDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: { title: string, question: string}
) {}
onNoClick(): void {
this.dialogRef.close(false);
}
onYesClick(): void {
this.dialogRef.close(true);
}
}

View File

@ -1,19 +1,19 @@
<div class="content"> <div class="content">
<div class="default-header"> <div class="default-header">
<div class="row"> <div class="row">
<div class="col-md-9"> <h1 class="col">Image Manager</h1>
<h1>Image Manager</h1> <button mat-raised-button color="primary" (click)="installAllImages()" class="install-all-button">
</div> <mat-icon>done_all</mat-icon> Install all images
<div class="col-md-3 btn-box"> </button>
<button class="img-btn" mat-button <button mat-raised-button color="primary" (click)="pruneImages()" class="prune-button">
(click)="addImageDialog()"> <mat-icon>delete</mat-icon> Prune Images
<mat-icon>add</mat-icon> Add Image </button>
</button> <button mat-raised-button color="primary" (click)="addImageDialog()" class="add-button">
</div> <mat-icon>add</mat-icon> Add Image
</button>
</div> </div>
</div> </div>
<div class="default-content"> <div class="default-content">
<app-controller-discovery></app-controller-discovery> <app-controller-discovery></app-controller-discovery>
@ -49,9 +49,9 @@
<mat-header-cell *matHeaderCellDef> Image Size </mat-header-cell> <mat-header-cell *matHeaderCellDef> Image Size </mat-header-cell>
<mat-cell *matCellDef="let row"> {{ (row.image_size/1000000).toFixed()}} MB </mat-cell> <mat-cell *matCellDef="let row"> {{ (row.image_size/1000000).toFixed()}} MB </mat-cell>
</ng-container> </ng-container>
<ng-container matColumnDef="delete" > <ng-container matColumnDef="delete" >
<mat-header-cell *matHeaderCellDef> <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"> <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> <mat-icon>delete</mat-icon>
</button> </button>
@ -67,4 +67,4 @@
</mat-table> </mat-table>
</div> </div>
</div> </div>
</div> </div>

View File

@ -2,12 +2,22 @@
display: none; display: none;
} }
.img-btn{ .install-all-button {
margin: auto; height: 40px;
} width: 160px;
.btn-box{ margin: 20px;
display: flex; }
margin-top: 10px;
.prune-button {
height: 40px;
width: 160px;
margin: 20px;
}
.add-button {
height: 40px;
width: 160px;
margin: 20px;
} }
mat-header-cell, mat-cell { mat-header-cell, mat-cell {
@ -21,4 +31,3 @@ mat-cell, mat-header-cell, mat-footer-cell {
min-height: inherit; min-height: inherit;
} }

View File

@ -6,12 +6,13 @@ import { ProgressService } from 'app/common/progress/progress.service';
import { Image } from '../../models/images'; import { Image } from '../../models/images';
import { Controller } from '../../models/controller'; import { Controller } from '../../models/controller';
import { ImageManagerService } from "../../services/image-manager.service"; import { ImageManagerService } from "../../services/image-manager.service";
import { DataSource, SelectionModel } from '@angular/cdk/collections'; import { SelectionModel } from '@angular/cdk/collections';
import { AddImageDialogComponent } from './add-image-dialog/add-image-dialog.component'; import { AddImageDialogComponent } from './add-image-dialog/add-image-dialog.component';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { ToasterService } from '../../services/toaster.service'; import { ToasterService } from '../../services/toaster.service';
import { DeleteAllImageFilesDialogComponent } from './deleteallfiles-dialog/deleteallfiles-dialog.component'; import { DeleteAllImageFilesDialogComponent } from './deleteallfiles-dialog/deleteallfiles-dialog.component';
import { imageDataSource, imageDatabase } from "./image-database-file"; import { imageDataSource, imageDatabase } from "./image-database-file";
import { QuestionDialogComponent } from "@components/dialogs/question-dialog/question-dialog.component";
@Component({ @Component({
selector: 'app-image-manager', selector: 'app-image-manager',
@ -36,7 +37,6 @@ export class ImageManagerComponent implements OnInit {
private versionService: VersionService, private versionService: VersionService,
private dialog: MatDialog, private dialog: MatDialog,
private toasterService: ToasterService, private toasterService: ToasterService,
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
@ -60,7 +60,6 @@ export class ImageManagerComponent implements OnInit {
}, },
(error) => { (error) => {
this.toasterService.error(error.error.message) this.toasterService.error(error.error.message)
} }
); );
} }
@ -100,6 +99,49 @@ export class ImageManagerComponent implements OnInit {
this.isAllDelete = true; this.isAllDelete = true;
} }
installAllImages() {
const dialogRef = this.dialog.open(QuestionDialogComponent, {
width: '450px',
data: { title: 'Install all images', question: 'This will attempt to automatically create templates based on image checksums. Continue?'}
});
dialogRef.afterClosed().subscribe((result: boolean) => {
if (result) {
this.imageService.installImages(this.controller).subscribe(() => {
this.toasterService.success('Images installed');
},
(error) => {
this.toasterService.error(error.error.message)
}
);
}
});
}
pruneImages() {
const dialogRef = this.dialog.open(QuestionDialogComponent, {
width: '450px',
data: { title: 'Prune images', question: 'Delete all images not used by a template? This cannot be reverted.'}
});
dialogRef.afterClosed().subscribe((result: boolean) => {
if (result) {
this.imageService.pruneImages(this.controller).subscribe(
() => {
this.getImages()
this.unChecked()
this.toasterService.success('Images pruned');
},
(error) => {
this.getImages()
this.unChecked()
this.toasterService.error(error.error.message)
}
);
}
});
}
public addImageDialog() { public addImageDialog() {
const dialogRef = this.dialog.open(AddImageDialogComponent, { const dialogRef = this.dialog.open(AddImageDialogComponent, {
width: '600px', width: '600px',

View File

@ -11,7 +11,7 @@ import { FileItem, FileUploader, ParsedResponseHeaders } from 'ng2-file-upload';
import * as SparkMD5 from 'spark-md5'; import * as SparkMD5 from 'spark-md5';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { ProgressService } from '../../../common/progress/progress.service'; import { ProgressService } from '../../../common/progress/progress.service';
import { InformationDialogComponent } from '../../../components/dialogs/information-dialog.component'; import { InformationDialogComponent } from '../../../components/dialogs/information-dialog/information-dialog.component';
import { Appliance, Image, Version } from '../../../models/appliance'; import { Appliance, Image, Version } from '../../../models/appliance';
import { Project } from '../../../models/project'; import { Project } from '../../../models/project';
import { QemuBinary } from '../../../models/qemu/qemu-binary'; import { QemuBinary } from '../../../models/qemu/qemu-binary';

View File

@ -12,18 +12,27 @@ export class ImageManagerService {
constructor(private httpController: HttpController) { } constructor(private httpController: HttpController) { }
getImages(controller:Controller ) { getImages(controller: Controller) {
return this.httpController.get<Image[]>(controller, '/images') as Observable<Image[]>; return this.httpController.get<Image[]>(controller, '/images') as Observable<Image[]>;
} }
getImagePath(controller :Controller, install_appliance, image_path){ getImagePath(controller: Controller, install_appliance, image_path){
return `${controller.protocol}//${controller.host}:${controller.port}/${environment.current_version}/images/upload/${image_path}?install_appliances=${install_appliance}`; return `${controller.protocol}//${controller.host}:${controller.port}/${environment.current_version}/images/upload/${image_path}?install_appliances=${install_appliance}`;
} }
uploadedImage(controller :Controller, install_appliance, image_path, flie){ uploadedImage(controller: Controller, install_appliance, image_path, file){
return this.httpController.post<Image[]>(controller, `/images/upload/${image_path}?install_appliances=${install_appliance}`,flie) as Observable<Image[]>; return this.httpController.post<Image[]>(controller, `/images/upload/${image_path}?install_appliances=${install_appliance}`, file) as Observable<Image[]>;
} }
deleteFile(controller :Controller, image_path){
deleteFile(controller: Controller, image_path){
return this.httpController.delete<Image[]>(controller, `/images/${image_path}`) as Observable<Image[]>; return this.httpController.delete<Image[]>(controller, `/images/${image_path}`) as Observable<Image[]>;
} }
pruneImages(controller: Controller){
return this.httpController.delete(controller, '/images/prune');
}
installImages(controller: Controller) {
return this.httpController.post(controller, '/images/install', {});
}
} }