mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-06-24 17:15:22 +00:00
Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
13f80cdaad | |||
28f32de0b2 | |||
a6a4fb401d | |||
f62366440c | |||
135ecbdc33 | |||
9ebbbb197b | |||
f90c074191 | |||
2d49ca30fa | |||
83f7d36e2d | |||
174053f297 | |||
0e4e124c14 | |||
c928ab0342 | |||
9efd99dccb | |||
7e43dc77cb | |||
d7752a4d7b | |||
40df0fe1ee | |||
d5bd84234d | |||
a9a7ecf3e7 | |||
5dc5a953e6 | |||
32c78450a2 | |||
82feb9aa92 | |||
a08a7e1476 | |||
b5e4972bdb | |||
dc5c0d3d94 | |||
04936cfc8d | |||
8728056b8d | |||
138d1f8552 | |||
dc31d51844 | |||
def33a353d | |||
ed3db2ea4d | |||
7ad6de2256 | |||
6dcc5cdc2e | |||
089e66a02b | |||
da848d42af | |||
6b08fb8d9a | |||
8874e7efbc | |||
2b834768c6 | |||
eabdda0e74 | |||
9e3f667767 | |||
8898141bc1 | |||
c8753ed45c | |||
d496d8dc64 | |||
a8f9b6948d |
@ -25,7 +25,7 @@ module.exports = function (config) {
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['ChromeHeadless'],
|
||||
browsers: ['Chrome'],
|
||||
singleRun: true
|
||||
});
|
||||
};
|
||||
|
12
package.json
12
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gns3-web-ui",
|
||||
"version": "2.2.29",
|
||||
"version": "2.2.35",
|
||||
"author": {
|
||||
"name": "GNS3 Technology Inc.",
|
||||
"email": "developers@gns3.com"
|
||||
@ -73,13 +73,13 @@
|
||||
"ngx-childprocess": "^0.0.6",
|
||||
"ngx-device-detector": "^2.1.1",
|
||||
"ngx-electron": "^2.2.0",
|
||||
"node-fetch": "^3.0.0",
|
||||
"node-fetch": "^3.2.10",
|
||||
"notosans-fontface": "1.2.2",
|
||||
"prettier-plugin-organize-imports": "^2.3.4",
|
||||
"rxjs": "^6.6.7",
|
||||
"rxjs-compat": "^6.6.7",
|
||||
"save-svg-as-png": "^1.4.17",
|
||||
"snyk": "^1.780.0",
|
||||
"snyk": "^1.996.0",
|
||||
"spark-md5": "^3.0.2",
|
||||
"svg-crowbar": "^0.7.0",
|
||||
"tree-kill": "^1.2.2",
|
||||
@ -102,13 +102,13 @@
|
||||
"@types/jasminewd2": "^2.0.10",
|
||||
"@types/node": "16.11.6",
|
||||
"codelyzer": "^6.0.2",
|
||||
"electron": "^13.2.2",
|
||||
"electron": "^13.6.6",
|
||||
"electron-builder": "^22.9.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"jasmine-core": "~3.10.1",
|
||||
"jasmine-spec-reporter": "~7.0.0",
|
||||
"jquery": "^3.6.0",
|
||||
"karma": "^6.3.8",
|
||||
"karma": "^6.3.16",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-cli": "^2.0.0",
|
||||
"karma-coverage-istanbul-reporter": "~3.0.3",
|
||||
@ -134,4 +134,4 @@
|
||||
]
|
||||
},
|
||||
"snyk": true
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
GNS3 WebUI is web implementation of user interface for GNS3 software.
|
||||
|
||||
Current version: 2.2.24
|
||||
Current version: 2.2.32
|
||||
|
||||
Bug Fixes & enhancements
|
||||
- security fixes
|
||||
- Fixed generated capture file is not valid
|
||||
- Fixed Docker additional directories
|
||||
|
||||
Current version: 2020.4.0-beta.1
|
||||
|
||||
|
@ -16,8 +16,8 @@ describe('FontFixer', () => {
|
||||
};
|
||||
|
||||
expect(fixer.fix(font)).toEqual({
|
||||
font_family: 'Noto Sans',
|
||||
font_size: 11,
|
||||
font_family: 'Arial',
|
||||
font_size: 12,
|
||||
font_weight: 'bold',
|
||||
});
|
||||
});
|
||||
@ -39,12 +39,12 @@ describe('FontFixer', () => {
|
||||
it('should fix TypeWriter font and 10px size in styles', () => {
|
||||
const styles = 'font-family: TypeWriter; font-size: 10px; font-weight: bold';
|
||||
|
||||
expect(fixer.fixStyles(styles)).toEqual('font-family:Noto Sans;font-size:11px;font-weight:bold');
|
||||
expect(fixer.fixStyles(styles)).toEqual('font-family:Arial;font-size:12px;font-weight:bold');
|
||||
});
|
||||
|
||||
it('should fix TypeWriter font and 10px size in styles with quotes', () => {
|
||||
const styles = 'font-family: "TypeWriter"; font-size: 10px; font-weight: bold';
|
||||
|
||||
expect(fixer.fixStyles(styles)).toEqual('font-family:Noto Sans;font-size:11px;font-weight:bold');
|
||||
expect(fixer.fixStyles(styles)).toEqual('font-family:Arial;font-size:12px;font-weight:bold');
|
||||
});
|
||||
});
|
||||
|
@ -9,8 +9,8 @@ import { Font } from '../models/font';
|
||||
export class FontFixer {
|
||||
static DEFAULT_FONT = 'TypeWriter';
|
||||
static DEFAULT_SIZE = 10;
|
||||
static REPLACE_BY_FONT = 'Noto Sans';
|
||||
static REPLACE_BY_SIZE = 11;
|
||||
static REPLACE_BY_FONT = 'Arial';
|
||||
static REPLACE_BY_SIZE = 12;
|
||||
|
||||
public fix(font: Font): Font {
|
||||
if (font.font_family === FontFixer.DEFAULT_FONT && font.font_size === FontFixer.DEFAULT_SIZE) {
|
||||
|
@ -53,6 +53,7 @@ export class Properties {
|
||||
qemu_path: string;
|
||||
environment: string;
|
||||
extra_hosts: string;
|
||||
extra_volumes: string[];
|
||||
replicate_network_connection_state: boolean;
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ describe('TextDrawingWidget', () => {
|
||||
const text_element = drew.nodes()[0];
|
||||
expect(text_element.innerHTML).toEqual('<tspan xml:space="preserve" x="0" dy="0em">THIS IS TEXT</tspan>');
|
||||
expect(text_element.getAttribute('fill')).toEqual('#000000');
|
||||
expect(text_element.getAttribute('style')).toEqual('font-family: "Noto Sans"; font-size: 11pt; font-weight: bold');
|
||||
expect(text_element.getAttribute('style')).toEqual('font-family: "Arial"; font-size: 12pt; font-weight: bold');
|
||||
expect(text_element.getAttribute('text-decoration')).toEqual('line-through');
|
||||
});
|
||||
|
||||
|
@ -97,9 +97,9 @@ export class InterfaceStatusWidget implements Widget {
|
||||
.attr('y', (ls: LinkStatus) => ls.y - 10)
|
||||
.attr('rx', 8)
|
||||
.attr('ry', 8)
|
||||
.style('fill', 'white')
|
||||
.style('fill', '#c7ffdf')
|
||||
.attr('stroke', '#2ecc71')
|
||||
.attr('stroke-width', 3);
|
||||
.attr('stroke-width', 2);
|
||||
status_started.exit().remove();
|
||||
const status_started_label = link_group
|
||||
.selectAll<SVGTextElement, LinkStatus>('text.status_started_label')
|
||||
@ -111,7 +111,7 @@ export class InterfaceStatusWidget implements Widget {
|
||||
.text((ls: LinkStatus) => ls.port)
|
||||
.attr('x', (ls: LinkStatus) => ls.x - 25)
|
||||
.attr('y', (ls: LinkStatus) => ls.y + 5)
|
||||
.attr('fill', `black`);
|
||||
.attr('fill', `#0e9647`);
|
||||
status_started_label.exit().remove();
|
||||
|
||||
const status_stopped = link_group
|
||||
@ -129,9 +129,9 @@ export class InterfaceStatusWidget implements Widget {
|
||||
.attr('y', (ls: LinkStatus) => ls.y - 10)
|
||||
.attr('rx', 8)
|
||||
.attr('ry', 8)
|
||||
.style('fill', 'white')
|
||||
.style('fill', '#ffe3e3')
|
||||
.attr('stroke', 'red')
|
||||
.attr('stroke-width', 3);
|
||||
.attr('stroke-width', 2);
|
||||
status_stopped.exit().remove();
|
||||
const status_stopped_label = link_group
|
||||
.selectAll<SVGTextElement, LinkStatus>('text.status_stopped_label')
|
||||
@ -143,7 +143,7 @@ export class InterfaceStatusWidget implements Widget {
|
||||
.text((ls: LinkStatus) => ls.port)
|
||||
.attr('x', (ls: LinkStatus) => ls.x - 25)
|
||||
.attr('y', (ls: LinkStatus) => ls.y + 5)
|
||||
.attr('fill', `black`);
|
||||
.attr('fill', `red`);
|
||||
status_stopped_label.exit().remove();
|
||||
|
||||
const status_suspended = link_group
|
||||
@ -162,8 +162,8 @@ export class InterfaceStatusWidget implements Widget {
|
||||
.attr('rx', 8)
|
||||
.attr('ry', 8)
|
||||
.style('fill', 'white')
|
||||
.attr('stroke', '#FFFF00')
|
||||
.attr('stroke-width', 3);
|
||||
.attr('stroke', '#fffbc3')
|
||||
.attr('stroke-width', 2);
|
||||
status_suspended.exit().remove();
|
||||
const status_suspended_label = link_group
|
||||
.selectAll<SVGTextElement, LinkStatus>('text.status_suspended_label')
|
||||
@ -175,7 +175,7 @@ export class InterfaceStatusWidget implements Widget {
|
||||
.text((ls: LinkStatus) => ls.port)
|
||||
.attr('x', (ls: LinkStatus) => ls.x - 25)
|
||||
.attr('y', (ls: LinkStatus) => ls.y + 5)
|
||||
.attr('fill', `black`);
|
||||
.attr('fill', `#6b5633`);
|
||||
status_suspended_label.exit().remove();
|
||||
} else {
|
||||
const status_started = link_group
|
||||
|
@ -33,20 +33,24 @@ export class ConsoleDeviceActionBrowserComponent {
|
||||
this.node.console_host = this.server.host;
|
||||
}
|
||||
|
||||
if (
|
||||
this.node.console_type === 'telnet' ||
|
||||
this.node.console_type === 'vnc' ||
|
||||
this.node.console_type === 'spice'
|
||||
) {
|
||||
try {
|
||||
try {
|
||||
if (this.node.console_type === 'telnet') {
|
||||
location.assign(
|
||||
`gns3+${this.node.console_type}://${this.node.console_host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`
|
||||
`gns3+telnet://${this.node.console_host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`
|
||||
);
|
||||
} catch (e) {
|
||||
this.toasterService.error(e);
|
||||
} else if (this.node.console_type === 'vnc') {
|
||||
location.assign(
|
||||
`gns3+vnc://${this.node.console_host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`
|
||||
);
|
||||
} else if (this.node.console_type.startsWith('spice')) {
|
||||
location.assign(
|
||||
`gns3+spice://${this.node.console_host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`
|
||||
);
|
||||
} else {
|
||||
this.toasterService.error('Supported console types are: telnet, vnc, spice and spice+agent.');
|
||||
}
|
||||
} else {
|
||||
this.toasterService.error('Supported console types: telnet, vnc, spice.');
|
||||
} catch (e) {
|
||||
this.toasterService.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ export class ConsoleDeviceActionComponent implements OnInit {
|
||||
let consoleCommand = this.settingsService.getConsoleSettings()
|
||||
? this.settingsService.getConsoleSettings()
|
||||
: this.nodeService.getDefaultCommand();
|
||||
const startedNodes = this.nodes.filter((node) => node.status === 'started');
|
||||
const startedNodes = this.nodes.filter((node) => node.status === 'started' && node.console_type !== 'none');
|
||||
|
||||
if (startedNodes.length === 0) {
|
||||
this.toasterService.error('Device needs to be started in order to console to it.');
|
||||
@ -37,7 +37,7 @@ export class ConsoleDeviceActionComponent implements OnInit {
|
||||
}
|
||||
|
||||
for (var node of this.nodes) {
|
||||
if (node.status !== 'started') {
|
||||
if (node.status !== 'started' && node.console_type !== 'none') {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -232,12 +232,12 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
location.assign(
|
||||
`gns3+vnc://${node.console_host}:${node.console}?name=${node.name}&project_id=${node.project_id}&node_id=${node.node_id}`
|
||||
);
|
||||
} else if (node.console_type === 'spice') {
|
||||
} else if (node.console_type.startsWith('spice')) {
|
||||
location.assign(
|
||||
`gns3+spice://${node.console_host}:${node.console}?name=${node.name}&project_id=${node.project_id}&node_id=${node.node_id}`
|
||||
);
|
||||
} else {
|
||||
this.showCommand('Supported console types: telnet, vnc, spice.');
|
||||
this.showCommand('Supported console types are: telnet, vnc, spice and spice+agent');
|
||||
}
|
||||
} else {
|
||||
this.showCommand(`This node must be started before a console can be opened.`);
|
||||
@ -297,28 +297,28 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
printNode(node: Node): string {
|
||||
return (
|
||||
`command_line: ${node.command_line},
|
||||
compute_id: ${node.compute_id},
|
||||
console: ${node.console},
|
||||
console_host: ${node.console_host},
|
||||
console_type: ${node.console_type},
|
||||
first_port_name: ${node.first_port_name},
|
||||
height: ${node.height},
|
||||
label: ${node.label.text},
|
||||
name: ${node.name},
|
||||
node_directory: ${node.node_directory},
|
||||
node_id: ${node.node_id},
|
||||
node_type: ${node.node_type},
|
||||
port_name_format: ${node.port_name_format},
|
||||
`command_line: ${node.command_line},
|
||||
compute_id: ${node.compute_id},
|
||||
console: ${node.console},
|
||||
console_host: ${node.console_host},
|
||||
console_type: ${node.console_type},
|
||||
first_port_name: ${node.first_port_name},
|
||||
height: ${node.height},
|
||||
label: ${node.label.text},
|
||||
name: ${node.name},
|
||||
node_directory: ${node.node_directory},
|
||||
node_id: ${node.node_id},
|
||||
node_type: ${node.node_type},
|
||||
port_name_format: ${node.port_name_format},
|
||||
port_segment_size: ${node.port_segment_size}, ` +
|
||||
this.printPorts(node.ports) +
|
||||
`project_id: ${node.project_id},
|
||||
status: ${node.status},
|
||||
symbol: ${node.symbol},
|
||||
symbol_url: ${node.symbol_url},
|
||||
width: ${node.width},
|
||||
x: ${node.x},
|
||||
y: ${node.y},
|
||||
`project_id: ${node.project_id},
|
||||
status: ${node.status},
|
||||
symbol: ${node.symbol},
|
||||
symbol_url: ${node.symbol_url},
|
||||
width: ${node.width},
|
||||
x: ${node.x},
|
||||
y: ${node.y},
|
||||
z: ${node.z}`
|
||||
);
|
||||
}
|
||||
@ -328,31 +328,31 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
ports.forEach((port) => {
|
||||
response =
|
||||
response +
|
||||
`adapter_number: ${port.adapter_number},
|
||||
link_type: ${port.link_type},
|
||||
name: ${port.name},
|
||||
port_number: ${port.port_number},
|
||||
`adapter_number: ${port.adapter_number},
|
||||
link_type: ${port.link_type},
|
||||
name: ${port.name},
|
||||
port_number: ${port.port_number},
|
||||
short_name: ${port.short_name}, `;
|
||||
});
|
||||
return response;
|
||||
}
|
||||
|
||||
printLink(link: Link): string {
|
||||
return `capture_file_name: ${link.capture_file_name},
|
||||
capture_file_path: ${link.capture_file_path},
|
||||
capturing: ${link.capturing},
|
||||
link_id: ${link.link_id},
|
||||
link_type: ${link.link_type},
|
||||
project_id: ${link.project_id},
|
||||
return `capture_file_name: ${link.capture_file_name},
|
||||
capture_file_path: ${link.capture_file_path},
|
||||
capturing: ${link.capturing},
|
||||
link_id: ${link.link_id},
|
||||
link_type: ${link.link_type},
|
||||
project_id: ${link.project_id},
|
||||
suspend: ${link.suspend}, `;
|
||||
}
|
||||
|
||||
printDrawing(drawing: Drawing): string {
|
||||
return `drawing_id: ${drawing.drawing_id},
|
||||
project_id: ${drawing.project_id},
|
||||
rotation: ${drawing.rotation},
|
||||
x: ${drawing.x},
|
||||
y: ${drawing.y},
|
||||
return `drawing_id: ${drawing.drawing_id},
|
||||
project_id: ${drawing.project_id},
|
||||
rotation: ${drawing.rotation},
|
||||
x: ${drawing.x},
|
||||
y: ${drawing.y},
|
||||
z: ${drawing.z}`;
|
||||
}
|
||||
}
|
||||
|
@ -101,7 +101,7 @@
|
||||
|
||||
<h6>Additional directories</h6>
|
||||
<mat-form-field class="form-field">
|
||||
<textarea matInput type="text" [(ngModel)]="node.properties.extra_volumes"></textarea>
|
||||
<textarea matInput type="text" [(ngModel)]="additionalDirectories"></textarea>
|
||||
</mat-form-field>
|
||||
</mat-tab>
|
||||
|
||||
|
@ -20,13 +20,14 @@ export class ConfiguratorDialogDockerComponent implements OnInit {
|
||||
name: string;
|
||||
generalSettingsForm: FormGroup;
|
||||
consoleTypes: string[] = [];
|
||||
consoleResolutions: string[] = ['640x480', '800x600', '1024x768', '1280x800', '1280x1024', '1366x768', '1920x1080'];
|
||||
consoleResolutions: string[] = ['2560x1440', '1920x1080', '1680x1050', '1440x900', '1366x768', '1280x1024', '1280x800', '1024x768', '800x600', '640x480'];
|
||||
private conf = {
|
||||
autoFocus: false,
|
||||
width: '800px',
|
||||
disableClose: true,
|
||||
};
|
||||
dialogRef;
|
||||
additionalDirectories: string = "";
|
||||
|
||||
constructor(
|
||||
public dialogReference: MatDialogRef<ConfiguratorDialogDockerComponent>,
|
||||
@ -50,6 +51,12 @@ export class ConfiguratorDialogDockerComponent implements OnInit {
|
||||
this.node = node;
|
||||
this.name = node.name;
|
||||
this.getConfiguration();
|
||||
if (this.node.properties.extra_volumes && this.node.properties.extra_volumes.length>0) {
|
||||
for (let index = 0; index < this.node.properties.extra_volumes.length - 1; index++) {
|
||||
this.additionalDirectories = this.additionalDirectories + this.node.properties.extra_volumes[index] + "\n";
|
||||
}
|
||||
this.additionalDirectories = this.additionalDirectories + this.node.properties.extra_volumes[this.node.properties.extra_volumes.length - 1];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -72,7 +79,16 @@ export class ConfiguratorDialogDockerComponent implements OnInit {
|
||||
}
|
||||
|
||||
onSaveClick() {
|
||||
var extraVolumes = this.additionalDirectories.split("\n").filter(elem => elem != "");
|
||||
for (const item of extraVolumes) {
|
||||
console.log(item);
|
||||
if (!item.startsWith("/")) {
|
||||
this.toasterService.error(`Wrong format for additional directories.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this.generalSettingsForm.valid) {
|
||||
this.node.properties.extra_volumes = extraVolumes;
|
||||
this.nodeService.updateNode(this.server, this.node).subscribe(() => {
|
||||
this.toasterService.success(`Node ${this.node.name} updated.`);
|
||||
this.onCancelClick();
|
||||
|
@ -56,7 +56,7 @@ export class StartCaptureDialogComponent implements OnInit {
|
||||
const sourcePort = sourceNode.ports[this.link.nodes[0].port_number];
|
||||
const targetPort = targetNode.ports[this.link.nodes[1].port_number];
|
||||
this.inputForm.controls['fileName'].setValue(
|
||||
`${sourceNode.name}_${sourcePort.name}_to_${targetNode.name}_${targetPort.name}`
|
||||
`${sourceNode.name}_${sourcePort.name}_to_${targetNode.name}_${targetPort.name}`.replace(new RegExp('[^0-9A-Za-z_-]', 'g'), '')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -67,6 +67,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="map-settings-button"
|
||||
matTooltip="Project Map Settings"
|
||||
matTooltipClass="custom-tooltip"
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="viewMenu">
|
||||
<mat-icon>view_module</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
matTooltip="Toggle topology/servers summary"
|
||||
matTooltipClass="custom-tooltip"
|
||||
@ -83,39 +91,27 @@
|
||||
|
||||
<!-- GNS3 menu -->
|
||||
<mat-menu #mainMenu="matMenu" [overlapTrigger]="false">
|
||||
<button mat-menu-item [matMenuTriggerFor]="projectMenu">
|
||||
<mat-icon>insert_drive_file</mat-icon>
|
||||
<span>Project settings</span>
|
||||
</button>
|
||||
<button mat-menu-item [routerLink]="['/server', server.id, 'projects']">
|
||||
<mat-icon>work</mat-icon>
|
||||
<span>Go to projects</span>
|
||||
<span>Projects</span>
|
||||
</button>
|
||||
<button mat-menu-item [routerLink]="['/servers']">
|
||||
<mat-icon>developer_board</mat-icon>
|
||||
<span>Go to servers</span>
|
||||
</button>
|
||||
<button mat-menu-item routerLink="/server/{{ server.id }}/preferences">
|
||||
<mat-icon>settings_applications</mat-icon>
|
||||
<span>Go to preferences</span>
|
||||
<span>Servers</span>
|
||||
</button>
|
||||
<button mat-menu-item routerLink="/server/{{ server.id }}/systemstatus">
|
||||
<mat-icon>info</mat-icon>
|
||||
<span>Go to system status</span>
|
||||
<mat-icon>data_usage</mat-icon>
|
||||
<span>System Status</span>
|
||||
</button>
|
||||
<button mat-menu-item routerLink="/settings">
|
||||
<mat-icon>settings</mat-icon>
|
||||
<span>Go to settings</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="addNewTemplate()">
|
||||
<mat-icon>control_point</mat-icon>
|
||||
<span>New template</span>
|
||||
<span>Settings</span>
|
||||
</button>
|
||||
<app-import-appliance [server]="server" [project]="project"></app-import-appliance>
|
||||
<button mat-menu-item [matMenuTriggerFor]="projectMenu">
|
||||
<mat-icon>settings</mat-icon>
|
||||
<span>Project settings</span>
|
||||
</button>
|
||||
<button mat-menu-item [matMenuTriggerFor]="viewMenu">
|
||||
<mat-icon>view_module</mat-icon>
|
||||
<span>Map settings</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
|
||||
<!-- Project Settings sub-menu -->
|
||||
@ -256,4 +252,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #topologySummaryContainer></ng-template>
|
||||
<ng-template #topologySummaryContainer></ng-template>
|
@ -73,6 +73,10 @@ g.node:hover {
|
||||
font-size: 28px !important;
|
||||
}
|
||||
|
||||
.map-settings-button mat-icon {
|
||||
font-size: 22px !important;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background: rgba(0, 151, 167, 0.1);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div class="title-container">
|
||||
<h1 mat-dialog-title>Add a node</h1>
|
||||
<h1 mat-dialog-title>Insert New Node</h1>
|
||||
<button
|
||||
mat-button
|
||||
class="top-button"
|
||||
|
@ -10,63 +10,95 @@
|
||||
</button>
|
||||
|
||||
<mat-menu #mainMenu="matMenu">
|
||||
<button mat-menu-item (click)="openDialog()">
|
||||
<mat-icon>add_to_queue</mat-icon>
|
||||
<span>Open dialog to configure</span>
|
||||
</button>
|
||||
<div class="templateMenu">
|
||||
<div class="templateMenuHeader">
|
||||
<button mat-menu-item (click)="openDialog()">
|
||||
<mat-icon>add_to_queue</mat-icon>
|
||||
<span>
|
||||
Insert New Node...
|
||||
</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="addNewTemplate()">
|
||||
<mat-icon>library_add</mat-icon>
|
||||
<span>
|
||||
New Template...
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<mat-form-field (click)="$event.stopPropagation()" class="form-field" floatPlaceholder="never">
|
||||
<input
|
||||
matInput
|
||||
placeholder="Search by name"
|
||||
(keyup)="filterTemplates($event)"
|
||||
[(ngModel)]="searchText"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<mat-form-field (click)="$event.stopPropagation()" class="form-field">
|
||||
<mat-select
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
placeholder="Filter templates by type"
|
||||
(selectionChange)="filterTemplates($event)"
|
||||
[(ngModel)]="selectedType"
|
||||
>
|
||||
<mat-option *ngFor="let type of templateTypes" [value]="type">
|
||||
{{ type }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<div class="templateFilterBar">
|
||||
<mat-form-field (click)="$event.stopPropagation()" class="form-field" floatPlaceholder="never">
|
||||
<input
|
||||
matInput
|
||||
placeholder="Filter by name"
|
||||
search="search"
|
||||
(keyup)="filterTemplates($event)"
|
||||
[(ngModel)]="searchText"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
/>
|
||||
<mat-icon class="searchIcon" matSuffix>search</mat-icon>
|
||||
</mat-form-field>
|
||||
<mat-form-field (click)="$event.stopPropagation()" class="form-field">
|
||||
<mat-select
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
placeholder="Filter by type"
|
||||
(selectionChange)="filterTemplates($event)"
|
||||
[(ngModel)]="selectedType"
|
||||
>
|
||||
<mat-option *ngFor="let type of templateTypes" [value]="type">
|
||||
{{ type }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="menu">
|
||||
<div class="templateList">
|
||||
<mat-list-item *ngFor="let template of filteredTemplates; let i = index">
|
||||
<span *ngIf="i % 4 === 0" class="templateRow">
|
||||
<span class="templateIcon">
|
||||
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, filteredTemplates[i])">
|
||||
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i])" />
|
||||
</div>
|
||||
<div [ngClass]="{ templateText: !isLightThemeEnabled, lightTemplateText: isLightThemeEnabled }">{{ filteredTemplates[i].name }}</div>
|
||||
</span>
|
||||
<span *ngIf="filteredTemplates[i + 1]" class="templateIcon">
|
||||
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, filteredTemplates[i + 1])">
|
||||
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 1])" />
|
||||
</div>
|
||||
<div [ngClass]="{ templateText: !isLightThemeEnabled, lightTemplateText: isLightThemeEnabled }">{{ filteredTemplates[i + 1].name }}</div>
|
||||
</span>
|
||||
<span *ngIf="filteredTemplates[i + 2]" class="templateIcon">
|
||||
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, filteredTemplates[i + 2])">
|
||||
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 2])" />
|
||||
</div>
|
||||
<div [ngClass]="{ templateText: !isLightThemeEnabled, lightTemplateText: isLightThemeEnabled }">{{ filteredTemplates[i + 2].name }}</div>
|
||||
</span>
|
||||
<span *ngIf="filteredTemplates[i + 3]" class="templateIcon">
|
||||
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, filteredTemplates[i + 3])">
|
||||
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 3])" />
|
||||
</div>
|
||||
<div [ngClass]="{ templateText: !isLightThemeEnabled, lightTemplateText: isLightThemeEnabled }">{{ filteredTemplates[i + 3].name }}</div>
|
||||
</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
</div>
|
||||
</div>
|
||||
</mat-menu>
|
||||
<div class="menu">
|
||||
<div class="templateList">
|
||||
<mat-list-item *ngFor="let template of filteredTemplates; let i = index">
|
||||
<span *ngIf="i % 4 === 0" class="templateRow">
|
||||
<span class="templateIcon">
|
||||
<div
|
||||
mwlDraggable
|
||||
(dragStart)="dragStart($event)"
|
||||
(dragEnd)="dragEnd($event, filteredTemplates[i])"
|
||||
class="iconContainer">
|
||||
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i])" />
|
||||
</div>
|
||||
<div class="templateText">{{ filteredTemplates[i].name }}</div>
|
||||
</span>
|
||||
<span *ngIf="filteredTemplates[i + 1]" class="templateIcon">
|
||||
<div
|
||||
mwlDraggable
|
||||
(dragStart)="dragStart($event)"
|
||||
(dragEnd)="dragEnd($event, filteredTemplates[i + 1])"
|
||||
class="iconContainer">
|
||||
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 1])" />
|
||||
</div>
|
||||
<div class="templateText">{{ filteredTemplates[i + 1].name }}</div>
|
||||
</span>
|
||||
<span *ngIf="filteredTemplates[i + 2]" class="templateIcon">
|
||||
<div
|
||||
mwlDraggable
|
||||
(dragStart)="dragStart($event)"
|
||||
(dragEnd)="dragEnd($event, filteredTemplates[i + 2])"
|
||||
class="iconContainer">
|
||||
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 2])" />
|
||||
</div>
|
||||
<div class="templateText">{{ filteredTemplates[i + 2].name }}</div>
|
||||
</span>
|
||||
<span *ngIf="filteredTemplates[i + 3]" class="templateIcon">
|
||||
<div
|
||||
mwlDraggable
|
||||
(dragStart)="dragStart($event)"
|
||||
(dragEnd)="dragEnd($event, filteredTemplates[i + 3])"
|
||||
class="iconContainer">
|
||||
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 3])" />
|
||||
</div>
|
||||
<div class="templateText">{{ filteredTemplates[i + 3].name }}</div>
|
||||
</span>
|
||||
</span>
|
||||
</mat-list-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-menu>
|
@ -1,6 +1,7 @@
|
||||
::ng-deep .mat-menu-panel {
|
||||
max-width: 400px;
|
||||
max-height: 500px;
|
||||
max-height: 640px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.menu {
|
||||
@ -8,6 +9,26 @@
|
||||
overflow-y: scroll;
|
||||
scrollbar-color: darkgrey #263238;
|
||||
scrollbar-width: thin;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.templateMenuHeader {
|
||||
border-bottom: 1px solid rgba(255,255,255,0.05);
|
||||
}
|
||||
|
||||
.templateFilterBar {
|
||||
padding: 10px 2%;
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.templateFilterBar > .form-field {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.templateFilterBar .searchIcon {
|
||||
position: relative;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
@ -24,20 +45,22 @@
|
||||
}
|
||||
|
||||
.form-field {
|
||||
width: 90%;
|
||||
margin-left: 5%;
|
||||
margin-right: 5%;
|
||||
width: 44%;
|
||||
margin-left: 3%;
|
||||
margin-right: 3%;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 65px;
|
||||
height: 65px;
|
||||
display: inline-block;
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
filter: invert(0);
|
||||
--webkit-filter: invert(0) !important;
|
||||
}
|
||||
|
||||
.templateList {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.templateRow {
|
||||
@ -49,12 +72,24 @@
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.lightTemplateText {
|
||||
word-wrap: break-word;
|
||||
color: black;
|
||||
.templateIcon {
|
||||
width: 90px !important;
|
||||
padding: 2px 5px;
|
||||
text-align: center;
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
.templateIcon {
|
||||
width: 80px !important;
|
||||
padding: 10px;
|
||||
.templateIcon > .iconContainer {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 50%;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.templateIcon > .iconContainer:hover {
|
||||
background-color: rgba(237, 246, 231, 0.08);
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { OverlayContainer } from '@angular/cdk/overlay';
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ThemeService } from '../../services/theme.service';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Project } from '../../models/project';
|
||||
import { Server } from '../../models/server';
|
||||
@ -10,6 +8,7 @@ import { MapScaleService } from '../../services/mapScale.service';
|
||||
import { SymbolService } from '../../services/symbol.service';
|
||||
import { TemplateService } from '../../services/template.service';
|
||||
import { NodeAddedEvent, TemplateListDialogComponent } from './template-list-dialog/template-list-dialog.component';
|
||||
import { NewTemplateDialogComponent } from '../project-map/new-template-dialog/new-template-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-template',
|
||||
@ -20,7 +19,7 @@ export class TemplateComponent implements OnInit, OnDestroy {
|
||||
@Input() server: Server;
|
||||
@Input() project: Project;
|
||||
@Output() onNodeCreation = new EventEmitter<any>();
|
||||
overlay;
|
||||
|
||||
templates: Template[] = [];
|
||||
filteredTemplates: Template[] = [];
|
||||
searchText: string = '';
|
||||
@ -47,19 +46,13 @@ export class TemplateComponent implements OnInit, OnDestroy {
|
||||
startY: number;
|
||||
|
||||
private subscription: Subscription;
|
||||
private themeSubscription: Subscription;
|
||||
private isLightThemeEnabled: boolean = false;
|
||||
|
||||
constructor(
|
||||
private dialog: MatDialog,
|
||||
private templateService: TemplateService,
|
||||
private scaleService: MapScaleService,
|
||||
private symbolService: SymbolService,
|
||||
private themeService: ThemeService,
|
||||
private overlayContainer: OverlayContainer,
|
||||
) {
|
||||
this.overlay = overlayContainer.getContainerElement();
|
||||
}
|
||||
private symbolService: SymbolService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.subscription = this.templateService.newTemplateCreated.subscribe((template: Template) => {
|
||||
@ -72,23 +65,6 @@ export class TemplateComponent implements OnInit, OnDestroy {
|
||||
this.templates = listOfTemplates;
|
||||
});
|
||||
this.symbolService.list(this.server);
|
||||
if (this.themeService.getActualTheme() === 'light') this.isLightThemeEnabled = true;
|
||||
this.themeSubscription = this.themeService.themeChanged.subscribe((value: string) => {
|
||||
if (value === 'light-theme') this.isLightThemeEnabled = true;
|
||||
this.toggleTheme();
|
||||
});
|
||||
}
|
||||
|
||||
toggleTheme(): void {
|
||||
if (this.overlay.classList.contains("dark-theme")) {
|
||||
this.overlay.classList.remove("dark-theme");
|
||||
this.overlay.classList.add("light-theme");
|
||||
} else if (this.overlay.classList.contains("light-theme")) {
|
||||
this.overlay.classList.remove("light-theme");
|
||||
this.overlay.classList.add("dark-theme");
|
||||
} else {
|
||||
this.overlay.classList.add("light-theme");
|
||||
}
|
||||
}
|
||||
|
||||
sortTemplates() {
|
||||
@ -152,6 +128,19 @@ export class TemplateComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
addNewTemplate() {
|
||||
const dialogRef = this.dialog.open(NewTemplateDialogComponent, {
|
||||
width: '1000px',
|
||||
maxHeight: '700px',
|
||||
autoFocus: false,
|
||||
disableClose: true,
|
||||
});
|
||||
|
||||
let instance = dialogRef.componentInstance;
|
||||
instance.server = this.server;
|
||||
instance.project = this.project;
|
||||
}
|
||||
|
||||
getImageSourceForTemplate(template: Template) {
|
||||
return `${this.server.protocol}//${this.server.host}:${this.server.port}/v2/symbols/${template.symbol}/raw`;
|
||||
}
|
||||
@ -159,4 +148,4 @@ export class TemplateComponent implements OnInit, OnDestroy {
|
||||
ngOnDestroy() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ export class DockerConfigurationService {
|
||||
}
|
||||
|
||||
getConsoleResolutions() {
|
||||
let consoleResolutions = ['1920x1080', '1366x768', '1280x1024', '1280x800', '1024x768', '800x600', '640x480'];
|
||||
let consoleResolutions = ['2560x1440', '1920x1080', '1680x1050', '1440x900', '1366x768', '1280x1024', '1280x800', '1024x768', '800x600', '640x480'];
|
||||
|
||||
return consoleResolutions;
|
||||
}
|
||||
|
@ -78,13 +78,15 @@ export class NodeConsoleService {
|
||||
let nodesToStart = 'Please start the following nodes if you want to open consoles for them: ';
|
||||
let nodesToStartCounter = 0;
|
||||
nodes.forEach((n) => {
|
||||
if (n.status === 'started') {
|
||||
this.mapSettingsService.logConsoleSubject.next(true);
|
||||
// this timeout is required due to xterm.js implementation
|
||||
setTimeout(() => { this.openConsoleForNode(n); }, 500);
|
||||
} else {
|
||||
nodesToStartCounter++;
|
||||
nodesToStart += n.name + ' '
|
||||
if (n.console_type !== "none") {
|
||||
if (n.status === 'started') {
|
||||
this.mapSettingsService.logConsoleSubject.next(true);
|
||||
// this timeout is required due to xterm.js implementation
|
||||
setTimeout(() => { this.openConsoleForNode(n); }, 500);
|
||||
} else {
|
||||
nodesToStartCounter++;
|
||||
nodesToStart += n.name + ' '
|
||||
}
|
||||
}
|
||||
});
|
||||
if (nodesToStartCounter > 0) {
|
||||
@ -93,16 +95,19 @@ export class NodeConsoleService {
|
||||
}
|
||||
|
||||
openConsolesForAllNodesInNewTabs(nodes: Node[]) {
|
||||
let nodesToStart = 'Please start the following nodes if you want to open consoles for them: ';
|
||||
let nodesToStart = 'Please start the following nodes if you want to open consoles in tabs for them: ';
|
||||
let nodesToStartCounter = 0;
|
||||
nodes.forEach((n) => {
|
||||
if (n.status === 'started') {
|
||||
let url = this.router.url.split('/');
|
||||
let urlString = `/static/web-ui/${url[1]}/${url[2]}/${url[3]}/${url[4]}/nodes/${n.node_id}`;
|
||||
window.open(urlString);
|
||||
} else {
|
||||
nodesToStartCounter++;
|
||||
nodesToStart += n.name + ' '
|
||||
// opening a console in tab is only supported for telnet type
|
||||
if (n.console_type === "telnet") {
|
||||
if (n.status === 'started') {
|
||||
let url = this.router.url.split('/');
|
||||
let urlString = `/static/web-ui/${url[1]}/${url[2]}/${url[3]}/${url[4]}/nodes/${n.node_id}`;
|
||||
window.open(urlString);
|
||||
} else {
|
||||
nodesToStartCounter++;
|
||||
nodesToStart += n.name + ' '
|
||||
}
|
||||
}
|
||||
});
|
||||
if (nodesToStartCounter > 0) {
|
||||
|
Reference in New Issue
Block a user