Compare commits

..

17 Commits

Author SHA1 Message Date
67b597d8ec Release v3.0.3 2025-01-22 18:53:25 +10:00
7fd37610ee Merge remote-tracking branch 'origin/2.2' into master-3.0
# Conflicts:
#	package.json
#	src/app/components/project-map/new-template-dialog/new-template-dialog.component.ts
2025-01-22 18:44:55 +10:00
c8ccf4d475 Development on 2.2.54.dev1
Some checks failed
Build / build (push) Has been cancelled
2025-01-21 12:13:40 +10:00
1a49bec3d4 Release v2.2.53 2025-01-21 11:37:59 +10:00
64f9631946 Fix use of FileUploader 2025-01-21 11:37:22 +10:00
b02fe4c751 Merge remote-tracking branch 'origin/2.2' into master-3.0
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Build / Node ${{ matrix.node }} (14) (push) Has been cancelled
Build / Node ${{ matrix.node }} (16) (push) Has been cancelled
Build / Node ${{ matrix.node }} (18) (push) Has been cancelled
# Conflicts:
#	src/app/components/project-map/new-template-dialog/new-template-dialog.component.html
#	src/app/services/mapsettings.service.ts
2025-01-20 16:51:31 +10:00
84169a2c1e Add missing checkImageFromVersion() for IOS and IOU
Some checks are pending
Build / build (push) Waiting to run
2025-01-20 16:46:26 +10:00
2a9ced5cbd Allow ':' in project names. Fixes https://github.com/GNS3/gns3-gui/issues/3686 2025-01-20 13:39:04 +10:00
7c7e20d95f Release v3.0.2
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Build / Node ${{ matrix.node }} (14) (push) Has been cancelled
Build / Node ${{ matrix.node }} (16) (push) Has been cancelled
Build / Node ${{ matrix.node }} (18) (push) Has been cancelled
2025-01-03 21:36:27 +07:00
e959a947cc Use correct image upload endpoint
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Build / Node ${{ matrix.node }} (14) (push) Has been cancelled
Build / Node ${{ matrix.node }} (16) (push) Has been cancelled
Build / Node ${{ matrix.node }} (18) (push) Has been cancelled
2024-12-31 18:57:36 +07:00
7b633c29dd Allow ':' in project names. Fixes https://github.com/GNS3/gns3-gui/issues/3686
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Build / Node ${{ matrix.node }} (14) (push) Has been cancelled
Build / Node ${{ matrix.node }} (16) (push) Has been cancelled
Build / Node ${{ matrix.node }} (18) (push) Has been cancelled
2024-12-30 16:26:51 +07:00
c24517d1f0 Release v3.0.1
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Build / Node ${{ matrix.node }} (14) (push) Has been cancelled
Build / Node ${{ matrix.node }} (16) (push) Has been cancelled
Build / Node ${{ matrix.node }} (18) (push) Has been cancelled
2024-12-27 20:58:03 +07:00
9043c5b97c Use template image name when uploading to controller 2024-12-27 20:57:23 +07:00
2227d11932 Handle normal user cannot access user management page. Fixes https://github.com/GNS3/gns3-server/issues/2460
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Build / Node ${{ matrix.node }} (14) (push) Waiting to run
Build / Node ${{ matrix.node }} (16) (push) Waiting to run
Build / Node ${{ matrix.node }} (18) (push) Waiting to run
2024-12-26 17:21:37 +07:00
2e581c4495 Merge pull request #1533 from GNS3/bugfix/1436
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Build / Node ${{ matrix.node }} (14) (push) Has been cancelled
Build / Node ${{ matrix.node }} (16) (push) Has been cancelled
Build / Node ${{ matrix.node }} (18) (push) Has been cancelled
Fix error 404 when editing a project
2024-12-23 17:27:07 +07:00
ee5b88e19a Backport: fix interface labels are not persistant
Some checks failed
Build / build (push) Has been cancelled
2024-12-23 16:05:05 +07:00
cddce63e2b Fix error 404 when editing a project 2024-12-23 15:44:43 +07:00
12 changed files with 60 additions and 74 deletions

View File

@ -1,6 +1,6 @@
{
"name": "gns3-web-ui",
"version": "3.1.0.dev1",
"version": "3.0.3",
"author": {
"name": "GNS3 Technology Inc.",
"email": "developers@gns3.com"

View File

@ -142,7 +142,7 @@ export class ImportApplianceComponent implements OnInit {
}
this.template = template;
const url = this.getUploadPath(this.controller, template.template_type, name);
const url = this.getUploadPath(this.controller, name);
this.uploader.queue.forEach((elem) => (elem.url = url));
const itemToUpload = this.uploader.queue[0];
this.uploader.uploadItem(itemToUpload);
@ -150,7 +150,7 @@ export class ImportApplianceComponent implements OnInit {
fileReader.readAsText(file);
}
private getUploadPath(controller:Controller , emulator: string, filename: string) {
return `${controller.protocol}//${controller.host}:${controller.port}/${environment.current_version}/${emulator}/images/${filename}`;
private getUploadPath(controller:Controller , filename: string) {
return `${controller.protocol}//${controller.host}:${controller.port}/${environment.current_version}/images/upload/${filename}`;
}
}

View File

@ -132,7 +132,7 @@
<mat-card [hidden]="!(!isLinuxPlatform || applianceToInstall.dynamips)">
<div *ngIf="applianceToInstall.qemu">
<div>
Install required files
Install the required files
<button
mat-icon-button
matTooltip="Refresh list of images"
@ -380,11 +380,11 @@
{{ image.filename }}
</span>
<div>
<span *ngIf="checkImageFromVersion(image.filename)"
><mat-icon matTooltip="Ready to install" matTooltipClass="custom-tooltip">check</mat-icon>
<span *ngIf="checkImageFromVersion(image.filename)">
<mat-icon matTooltip="Ready to install" matTooltipClass="custom-tooltip">check</mat-icon>
</span>
<span *ngIf="!checkImageFromVersion(image.filename)"
><mat-icon matTooltip="Missing" matTooltipClass="custom-tooltip">close</mat-icon>
<span *ngIf="!checkImageFromVersion(image.filename)">
<mat-icon matTooltip="Missing" matTooltipClass="custom-tooltip">close</mat-icon>
</span>
<input
type="file"
@ -407,7 +407,7 @@
<div *ngIf="applianceToInstall.iou">
<div>
Install required images
Install the required images
<button
mat-icon-button
matTooltip="Refresh list of images"
@ -419,16 +419,16 @@
<mat-list>
<mat-list-item *ngFor="let image of applianceToInstall.images">
<div class="list-item">
<div>
<span>
{{ image.filename }}
</div>
</span>
<span *ngIf="checkImageFromVersion(image.filename)">
<mat-icon matTooltip="Ready to install" matTooltipClass="custom-tooltip">check</mat-icon>
</span>
<span *ngIf="!checkImageFromVersion(image.filename)">
<mat-icon matTooltip="Missing" matTooltipClass="custom-tooltip">close</mat-icon>
</span>
<div>
<span *ngIf="checkImageFromVersion(image.filename)"
><mat-icon matTooltip="Ready to install" matTooltipClass="custom-tooltip">check</mat-icon>
</span>
<span *ngIf="!checkImageFromVersion(image.filename)"
><mat-icon matTooltip="Missing" matTooltipClass="custom-tooltip">close</mat-icon>
</span>
<input
type="file"
class="non-visible"

View File

@ -266,7 +266,7 @@ export class NewTemplateDialogComponent implements OnInit {
if (appliance.iou) emulator = 'iou';
if (appliance.qemu) emulator = 'qemu';
const url = this.applianceService.getUploadPath(this.controller, emulator, fileName);
const url = this.applianceService.getUploadPath(this.controller, fileName);
this.uploader.queue.forEach((elem) => (elem.url = url));
const itemToUpload = this.uploader.queue[0];
@ -341,8 +341,6 @@ export class NewTemplateDialogComponent implements OnInit {
dialogRef.componentInstance.appliance = object;
}
importImage(event, imageName) {
this.computeChecksumMd5(event.target.files[0], false).then((output) => {
let imageToInstall = this.applianceToInstall.images.filter((n) => n.filename === imageName)[0];
@ -359,22 +357,21 @@ export class NewTemplateDialogComponent implements OnInit {
The MD5 sum is ${output} and should be ${imageToInstall.md5sum}. Do you want to accept it at your own risks?`;
dialogRef.afterClosed().subscribe((answer: boolean) => {
if (answer) {
this.importImageFile(event);
this.importImageFile(event, imageName);
this.openSnackBar()
} else {
this.uploaderImage.clearQueue();
}
});
} else {
this.importImageFile(event);
this.importImageFile(event, imageName);
this.openSnackBar()
}
});
}
importImageFile(event) {
importImageFile(event, imageName) {
let name = event.target.files[0].name.split('-')[0];
let fileName = event.target.files[0].name;
let file = event.target.files[0];
let fileReader: FileReader = new FileReader();
let emulator;
@ -384,7 +381,7 @@ export class NewTemplateDialogComponent implements OnInit {
if (this.applianceToInstall.dynamips) emulator = 'dynamips';
if (this.applianceToInstall.iou) emulator = 'iou';
const url = this.applianceService.getUploadPath(this.controller, emulator, fileName);
const url = this.applianceService.getUploadPath(this.controller, imageName);
this.uploaderImage.queue.forEach((elem) => (elem.url = url));
const itemToUpload = this.uploaderImage.queue[0];

View File

@ -98,42 +98,25 @@ export class ProjectMapMenuComponent implements OnInit, OnDestroy {
private async saveImage(screenshotProperties: Screenshot) {
if (screenshotProperties.filetype === 'png') {
try {
// Get the SVG element and clone it to avoid modifying the original
const originalSvg = document.getElementsByTagName('svg')[0];
const svgClone = originalSvg.cloneNode(true) as SVGElement;
let splittedSvg = document.getElementsByTagName('svg')[0].outerHTML.split('image');
let i = 1;
// Process any embedded images
const images = svgClone.getElementsByTagName('image');
for (let i = 0; i < images.length; i++) {
const image = images[i];
const href = image.getAttribute('href') || image.getAttribute('xlink:href');
if (href) {
const urlParts = href.split('/');
const symbolId = urlParts[urlParts.length - 1];
try {
const rawSvg = await this.symbolService.raw(this.controller, symbolId).toPromise();
if (rawSvg) {
// Extract SVG content, fallback to raw content if parsing fails
const svgContent = rawSvg.includes('-->') ?
rawSvg.split('-->')[1].trim() :
rawSvg.trim();
image.outerHTML = svgContent;
}
} catch (err) {
console.warn(`Failed to process embedded image: ${symbolId}`, err);
}
}
}
while (i < splittedSvg.length) {
let splittedImage = splittedSvg[i].split('"');
let splittedUrl = splittedImage[1].split('/');
// Create a temporary container and save as PNG
const container = document.createElement('div');
container.appendChild(svgClone);
svg.saveSvgAsPng(svgClone, `${screenshotProperties.name}.png`);
} catch (err) {
console.error('Failed to save PNG:', err);
throw err;
let elem = await this.symbolService.raw(this.controller, splittedUrl[7]).toPromise();
let splittedElement = elem.split('-->');
splittedSvg[i] = splittedElement[1].substring(2);
i += 2;
}
let svgString = splittedSvg.join();
let placeholder = document.createElement('div');
placeholder.innerHTML = svgString;
let element = placeholder.firstChild;
svg.saveSvgAsPng(element, `${screenshotProperties.name}.png`);
} else {
var svg_el = select('svg').attr('version', 1.1).attr('xmlns', 'http://www.w3.org/2000/svg').node();
downloadSvg(select('svg').node(), `${screenshotProperties.name}`);

View File

@ -25,7 +25,7 @@ export class ScreenshotDialogComponent implements OnInit {
this.nameForm = this.formBuilder.group({
screenshotName: new UntypedFormControl(`screenshot-${Date.now()}`, [Validators.required]),
});
this.isPngAvailable = true;
this.isPngAvailable = this.electronService.isWindows || this.deviceService.getDeviceInfo().os === 'Windows';
}
ngOnInit() {}

View File

@ -21,8 +21,18 @@ export class ReadmeEditorComponent implements OnInit {
) {}
ngOnInit() {
this.projectService.getReadmeFile(this.controller, this.project.project_id).subscribe(file => {
if (file) this.markdown = file;
this.projectService.getReadmeFile(this.controller, this.project.project_id).subscribe({
next: (file) => {
if (file) {
this.markdown = file;
}
},
error: (err) => {
if (err.status === 404) {
// File doesn't exist yet, which is fine
this.markdown = '';
}
}
});
}
}

View File

@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
@Injectable()
export class ProjectNameValidator {
get(projectName) {
var pattern = new RegExp(/[~`!#$%\^&*+=\[\]\\';,/{}|\\":<>\?]/);
var pattern = new RegExp(/[~`!#$%\^&*+=\[\]\\';,/{}|\\"<>\?]/);
if (!pattern.test(projectName.value)) {
return null;

View File

@ -11,6 +11,7 @@
* Author: Sylvain MATHIEU, Elise LEBEAU
*/
import {Component, OnInit, QueryList, ViewChild, ViewChildren} from '@angular/core';
import {Location} from '@angular/common';
import {ActivatedRoute, Router} from "@angular/router";
import {Controller} from "@models/controller";
import {MatSort} from "@angular/material/sort";
@ -49,7 +50,8 @@ export class UserManagementComponent implements OnInit {
private progressService: ProgressService,
private controllerService: ControllerService,
public dialog: MatDialog,
private toasterService: ToasterService) { }
private toasterService: ToasterService,
private location: Location) { }
ngOnInit() {
const controllerId = this.route.parent.snapshot.paramMap.get('controller_id');
@ -88,6 +90,8 @@ export class UserManagementComponent implements OnInit {
},
(error) => {
this.progressService.setError(error);
this.toasterService.error(`Cannot open the user management page`);
this.location.back();
}
);
}

View File

@ -17,8 +17,8 @@ export class ApplianceService {
return this.httpController.get<Appliance>(controller, url) as Observable<Appliance>;
}
getUploadPath(controller:Controller , emulator: string, filename: string) {
return `${controller.protocol}//${controller.host}:${controller.port}/${environment.current_version}/images/upload/${filename}?allow_raw_image=true`;
getUploadPath(controller:Controller, filename: string) {
return `${controller.protocol}//${controller.host}:${controller.port}/${environment.current_version}/images/upload/${filename}`;
}
updateAppliances(controller:Controller ): Observable<Appliance[]> {

View File

@ -14,10 +14,6 @@ export class ComputeService {
return this.httpController.get<Compute[]>(controller, '/computes') as Observable<Compute[]>;
}
getUploadPath(controller:Controller , emulator: string, filename: string) {
return `${controller.protocol}//${controller.host}:${controller.port}/${environment.current_version}/${emulator}/images/${filename}`;
}
getStatistics(controller:Controller ): Observable<ComputeStatistics[]> {
return this.httpController.get(controller, `/statistics`);
}

View File

@ -20,10 +20,6 @@ export class ImageManagerService {
return `${controller.protocol}//${controller.host}:${controller.port}/${environment.current_version}/images/upload/${image_path}?install_appliances=${install_appliance}`;
}
getUploadPath(controller:Controller , emulator: string, filename: string) {
return `${controller.protocol}//${controller.host}:${controller.port}/${environment.current_version}/images/upload/${filename}`;
}
uploadedImage(controller :Controller, install_appliance, image_path, flie){
return this.httpController.post<Image[]>(controller, `/images/upload/${image_path}?install_appliances=${install_appliance}`,flie) as Observable<Image[]>;
}