mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-06-24 09:11:20 +00:00
Compare commits
318 Commits
Author | SHA1 | Date | |
---|---|---|---|
28d27194be | |||
52f772676b | |||
8068751032 | |||
28f8de0da1 | |||
8cc4fb6f75 | |||
1196742e3b | |||
d398efe229 | |||
33e8fa7f5f | |||
b9833aa00b | |||
fc66124ea9 | |||
3f74a916d4 | |||
ff78754fd5 | |||
323255286b | |||
a9314b65f8 | |||
e5b3a101b8 | |||
8b2d20e8e9 | |||
26023fe884 | |||
9a2e06471c | |||
98f4ec1ce0 | |||
4d695274bb | |||
e5af89821d | |||
78b910504d | |||
93aabe0cbc | |||
27446f8d14 | |||
a026374e75 | |||
4f303921c6 | |||
dea6a5021d | |||
46c7c58402 | |||
0df3525bbf | |||
9667e2363c | |||
24ec96a2dd | |||
9844a2f88f | |||
0cdbeb98a2 | |||
dc40d3be6e | |||
2015917767 | |||
360d77d2ef | |||
75c3d8ed97 | |||
f94d59206e | |||
5b41f9789a | |||
ac84106dbc | |||
21f2267960 | |||
a9781943d5 | |||
6fc2f6f964 | |||
cd5773e58a | |||
f4bcb844dc | |||
24cf0f5623 | |||
5e02a3d757 | |||
42599dafb5 | |||
9c754f3444 | |||
6772f0cb16 | |||
2730ee6f6e | |||
de058e175b | |||
e9487c5ada | |||
035c846f85 | |||
80239229aa | |||
c12a7acb2c | |||
23bf5ad2f3 | |||
bed98624cc | |||
383c26dcef | |||
f505c101f7 | |||
ca071f79c2 | |||
20be9027fd | |||
63389812b5 | |||
6873432833 | |||
a7e3c24a27 | |||
358f50596c | |||
b61f5803fd | |||
c27f4aee57 | |||
5aa00a9f77 | |||
91075a60b1 | |||
57e590a704 | |||
8d3e571aa4 | |||
af5917b6e4 | |||
21ed977a55 | |||
f861364727 | |||
0ff4d534f4 | |||
ca408663a5 | |||
c96d66b34a | |||
896ca927f3 | |||
8253f8da38 | |||
cbb1c9ecfc | |||
1e8b6261dc | |||
f3b8a42d89 | |||
7812ff38cc | |||
d725363fe5 | |||
58d42558f7 | |||
aecbe32c6c | |||
35193043a2 | |||
7a229d8e3e | |||
ba1180786f | |||
535649f0a9 | |||
a4f7db62ba | |||
662aba4ec8 | |||
bfc72c219c | |||
997b8df598 | |||
89bff8ac30 | |||
df6248d641 | |||
cefbc3c9be | |||
de07558349 | |||
b59c528ece | |||
61334d197d | |||
d855e5cb33 | |||
b8253d365d | |||
05685af5c4 | |||
8dbaa11808 | |||
0d7020af97 | |||
58b9083c49 | |||
43213d0669 | |||
924cbe2542 | |||
d06a3efd2c | |||
e2466ca4ab | |||
1da94efe63 | |||
838480509e | |||
04c28bd40a | |||
328dd37ffe | |||
c5a692babf | |||
119afd14d2 | |||
37e6921ffb | |||
5d48ea046d | |||
4342d27d07 | |||
3394035e2e | |||
49403a5568 | |||
70e4745657 | |||
543b81c81b | |||
02562cd046 | |||
252452051a | |||
a64ff3503e | |||
318143f5a8 | |||
e029bccf18 | |||
721adacde4 | |||
4374573c60 | |||
1e7c04f93c | |||
7e172e30ba | |||
4d243f895c | |||
aeef3e74ed | |||
83d72787f4 | |||
829bfe12d7 | |||
f5b5c717b4 | |||
5b7da298d6 | |||
d7742a7c59 | |||
e0ce8c0770 | |||
061dec9d75 | |||
684a160d99 | |||
21a12c151b | |||
17be201862 | |||
57385b84f7 | |||
8e4f860b43 | |||
2c015e695d | |||
9fe2b3646b | |||
313966548f | |||
69a7cf44c5 | |||
faec4b07be | |||
908c721094 | |||
e3c4188171 | |||
f48471cdd4 | |||
50fb05aa8e | |||
63728091c1 | |||
03a417d78c | |||
c7a7a357c6 | |||
7d95267283 | |||
8a7309bde1 | |||
afccf4955b | |||
ad57a5f3f7 | |||
7407ddafd3 | |||
168de3aecb | |||
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 | |||
ccd3ff61f1 | |||
abf9d8b387 | |||
f8cc654539 | |||
56554f6d0c | |||
7be137dc1c | |||
1b45a2284d | |||
6b4f5186d0 | |||
c3f2ebad0c | |||
249f63a97a | |||
cc6b8cd28c | |||
96928d86f8 | |||
a213a7aca1 | |||
fc1d17b921 | |||
0ddf4f6e95 | |||
8d466d655e | |||
2c7dd5f179 | |||
64999f2b72 | |||
253c65b8c1 | |||
74c1a82524 | |||
4e42bd7a54 | |||
891e65b094 | |||
c808477914 | |||
8503a17187 | |||
8afea664ff | |||
538ae8b7fb | |||
370694f3b0 | |||
5175b3beac | |||
2df1956dbc | |||
56384fbcc0 | |||
15faca6d89 | |||
4142144d4d | |||
e2e87db039 | |||
c868f08a25 | |||
9aedd410bb | |||
5fb76d7d11 | |||
a7c343aa7c | |||
cfe8c4760b | |||
7cbcc84cc1 | |||
357e478fb8 | |||
063d8c9dc7 | |||
63ecacb6b6 | |||
6cecacf611 | |||
c389404e58 | |||
b1aba60410 | |||
4cd9f77732 | |||
1619c3ec05 | |||
b33a01e225 | |||
353740376e | |||
9fe899e4df | |||
dd1f16c53d | |||
3fa52d3c9c | |||
bc5dd0271f | |||
81ca3e2af2 | |||
d7a0d2f69a | |||
8f0bbafa72 | |||
08f7456bb1 | |||
752246c629 | |||
2709d8d102 | |||
d949f536ab | |||
42d8bcebbb | |||
a153e97f55 | |||
7953a86fd9 | |||
5c426ad822 | |||
a9b7e09da4 | |||
60bae5db3c | |||
d6e5dee1aa | |||
cb101e8202 | |||
0124018b02 | |||
04fdcc6893 | |||
e54638a248 | |||
aac7b57d22 | |||
9421b4bf57 | |||
354b313eaa | |||
b048a846e0 | |||
092fe5991b | |||
1a03bb0456 | |||
a04804eaa0 | |||
8a56032291 | |||
c80dbe6baf | |||
2b17b3d627 | |||
6e9ebfb69d | |||
6faba7c4ec | |||
3f7fe0a7f3 | |||
666e39ede3 | |||
60f8685c68 | |||
ca5a8ea891 | |||
3b07b4a2db | |||
9bf2980b6b | |||
57e6200972 | |||
b692bd0f43 | |||
e37bdc6162 | |||
bb2a963237 | |||
c0cff3a6db | |||
2a96a6b601 | |||
1d5bbb58f9 | |||
d290daed11 | |||
5ba7799d8a | |||
17da822a47 | |||
748997c635 | |||
cf2f0e3110 | |||
8bfb375e02 | |||
9fd34e8253 | |||
abdd739f06 | |||
1f972d3261 | |||
42ae0b671a | |||
b51159513f | |||
12ba174dfa | |||
62c1f8b003 | |||
4261086495 | |||
1e21f672fb |
16
.github/workflows/add-new-issues-to-project.yml
vendored
Normal file
16
.github/workflows/add-new-issues-to-project.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
name: Add new issues to GNS3 project
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
|
||||
jobs:
|
||||
add-to-project:
|
||||
name: Add issue to project
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/add-to-project@v0.4.0
|
||||
with:
|
||||
project-url: https://github.com/orgs/GNS3/projects/3
|
||||
github-token: ${{ secrets.ADD_NEW_ISSUES_TO_PROJECT }}
|
76
.github/workflows/codeql.yml
vendored
Normal file
76
.github/workflows/codeql.yml
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master", "master-3.0" ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "master", "master-3.0" ]
|
||||
schedule:
|
||||
- cron: '38 18 * * 6'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript', 'python' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Use only 'java' to analyze code written in Java, Kotlin or both
|
||||
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
8
.github/workflows/main.yml
vendored
8
.github/workflows/main.yml
vendored
@ -1,8 +1,12 @@
|
||||
name: Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- master-3.0
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -10,10 +14,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup node 12
|
||||
- name: Setup node 14
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 14.x
|
||||
- uses: c-hive/gha-yarn-cache@v1
|
||||
- name: Install JS dependencies
|
||||
run: yarn install
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/.angular
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
|
@ -2,7 +2,8 @@
|
||||
"scanSettings": {
|
||||
"configMode": "AUTO",
|
||||
"configExternalURL": "",
|
||||
"projectToken" : ""
|
||||
"projectToken" : "",
|
||||
"baseBranches": ["master", "master-3.0"]
|
||||
},
|
||||
"checkRunSettings": {
|
||||
"vulnerableCheckRunConclusionLevel": "failure"
|
||||
@ -10,4 +11,4 @@
|
||||
"issueSettings": {
|
||||
"minSeverityLevel": "LOW"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
5
SECURITY.md
Normal file
5
SECURITY.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please use GitHub's report a vulnerability feature. More information can be found in https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability
|
25
angular.json
25
angular.json
@ -18,7 +18,6 @@
|
||||
"css-tree",
|
||||
"save-svg-as-png",
|
||||
"angular-draggable-droppable",
|
||||
"angular2-hotkeys",
|
||||
"dom-set",
|
||||
"dom-plane",
|
||||
"mousetrap",
|
||||
@ -26,13 +25,10 @@
|
||||
"rxjs/Rx",
|
||||
"rxjs/add/operator/map",
|
||||
"rxjs-compat/add/operator/map",
|
||||
"angular-react-core.js",
|
||||
"react",
|
||||
"react-dom",
|
||||
"classnames",
|
||||
"stylenames"
|
||||
],
|
||||
"aot": true,
|
||||
"stylenames",
|
||||
"ipaddr.js"
|
||||
],
|
||||
"outputPath": "dist",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
@ -49,7 +45,14 @@
|
||||
"src/styles.scss",
|
||||
"src/theme.scss"
|
||||
],
|
||||
"scripts": []
|
||||
"scripts": [],
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"buildOptimizer": true,
|
||||
"sourceMap": true,
|
||||
"optimization": false,
|
||||
"namedChunks": true,
|
||||
"aot": true
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
@ -67,7 +70,6 @@
|
||||
"styles": false
|
||||
},
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
@ -89,7 +91,6 @@
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
@ -125,7 +126,6 @@
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
@ -178,6 +178,7 @@
|
||||
"src/styles.scss",
|
||||
"src/theme.scss"
|
||||
],
|
||||
"sourceMap": false,
|
||||
"assets": [
|
||||
"src/assets",
|
||||
"src/favicon.ico"
|
||||
@ -243,4 +244,4 @@
|
||||
"cli": {
|
||||
"analytics": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ module.exports = function (config) {
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['ChromeHeadless'],
|
||||
browsers: ['Chrome'],
|
||||
singleRun: true
|
||||
});
|
||||
};
|
||||
|
118
package.json
118
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gns3-web-ui",
|
||||
"version": "2.2.21",
|
||||
"version": "2.2.52",
|
||||
"author": {
|
||||
"name": "GNS3 Technology Inc.",
|
||||
"email": "developers@gns3.com"
|
||||
@ -36,107 +36,97 @@
|
||||
"generate-licenses-file": "yarn license-checker --production --csv --out licenses.csv",
|
||||
"prebuildforelectron": "node set-variables-in-env.js --set src/environments/environment.electron.prod.ts",
|
||||
"postbuildforelectron": "node set-variables-in-env.js --unset src/environments/environment.electron.prod.ts",
|
||||
"postinstall": "ngcc --properties es5 browser module main --first-only --create-ivy-entry-points && ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points",
|
||||
"snyk-protect": "snyk protect",
|
||||
"prepare": "yarn run snyk-protect"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular-react/core": "^3.0.0",
|
||||
"@angular-react/fabric": "^3.0.0",
|
||||
"@angular/animations": "^11.2.11",
|
||||
"@angular/cdk": "^11.2.10",
|
||||
"@angular/common": "^11.2.11",
|
||||
"@angular/compiler": "^11.2.11",
|
||||
"@angular/core": "^11.2.11",
|
||||
"@angular/forms": "^11.2.11",
|
||||
"@angular/http": "^7.2.16",
|
||||
"@angular/material": "^11.2.10",
|
||||
"@angular/platform-browser": "^11.2.11",
|
||||
"@angular/platform-browser-dynamic": "^11.2.11",
|
||||
"@angular/router": "^11.2.11",
|
||||
"@sentry/browser": "^6.3.1",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/mocha": "^8.2.2",
|
||||
"@types/react": "^17.0.3",
|
||||
"@types/react-dom": "^17.0.3",
|
||||
"angular-draggable-droppable": "^4.6.0",
|
||||
"angular-persistence": "^1.0.1",
|
||||
"angular-resizable-element": "^3.3.5",
|
||||
"angular2-draggable": "^2.3.2",
|
||||
"angular2-hotkeys": "^2.2.0",
|
||||
"angular2-indexeddb": "^1.2.3",
|
||||
"bootstrap": "^4.6.0",
|
||||
"@angular/animations": "^12.2.12",
|
||||
"@angular/cdk": "^12.2.12",
|
||||
"@angular/common": "^12.2.12",
|
||||
"@angular/compiler": "^12.2.12",
|
||||
"@angular/core": "^12.2.12",
|
||||
"@angular/forms": "^12.2.12",
|
||||
"@angular/material": "^12.2.12",
|
||||
"@angular/platform-browser": "^12.2.12",
|
||||
"@angular/platform-browser-dynamic": "^12.2.12",
|
||||
"@angular/router": "^12.2.12",
|
||||
"@sentry/browser": "^6.14.1",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/react": "^17.0.34",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"angular-draggable-droppable": "^5.0.0",
|
||||
"angular-resizable-element": "^3.4.0",
|
||||
"bootstrap": "^5.1.3",
|
||||
"command-exists": "^1.2.9",
|
||||
"core-js": "^3.11.0",
|
||||
"core-js": "^3.19.1",
|
||||
"css-tree": "^1.1.3",
|
||||
"d3-ng2-service": "^2.2.0",
|
||||
"eev": "^0.1.5",
|
||||
"file-saver": "^2.0.5",
|
||||
"ini": "^2.0.0",
|
||||
"ipaddr.js": "^2.1.0",
|
||||
"material-design-icons": "^3.0.1",
|
||||
"mousetrap": "^1.6.5",
|
||||
"ng-circle-progress": "^1.6.0",
|
||||
"ng2-file-upload": "^1.4.0",
|
||||
"ngx-childprocess": "^0.0.6",
|
||||
"ngx-device-detector": "^2.0.7",
|
||||
"ngx-device-detector": "^2.1.1",
|
||||
"ngx-electron": "^2.2.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"node-fetch": "^3.2.10",
|
||||
"notosans-fontface": "1.2.2",
|
||||
"office-ui-fabric-react": "^7.168.2",
|
||||
"prettier-plugin-organize-imports": "^1.1.1",
|
||||
"react": "^17.0.2",
|
||||
"react-bootstrap": "^1.5.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"prettier-plugin-organize-imports": "^2.3.4",
|
||||
"rxjs": "^6.6.7",
|
||||
"rxjs-compat": "^6.6.7",
|
||||
"save-html-as-image": "^1.5.2",
|
||||
"save-svg-as-png": "^1.4.17",
|
||||
"schematics-scss-migrate": "^1.3.13",
|
||||
"snyk": "^1.568.0",
|
||||
"spark-md5": "^3.0.1",
|
||||
"svg-crowbar": "^0.6.5",
|
||||
"snyk": "^1.1064.0",
|
||||
"spark-md5": "^3.0.2",
|
||||
"svg-crowbar": "^0.7.0",
|
||||
"tree-kill": "^1.2.2",
|
||||
"tslib": "^2.2.0",
|
||||
"tslib": "^2.3.1",
|
||||
"typeface-roboto": "^1.1.13",
|
||||
"xterm": "^4.11.0",
|
||||
"xterm": "^4.15.0",
|
||||
"xterm-addon-attach": "^0.6.0",
|
||||
"xterm-addon-fit": "^0.5.0",
|
||||
"yargs": "^16.2.0",
|
||||
"zone.js": "^0.11.4"
|
||||
"yargs": "^17.2.1",
|
||||
"zone.js": "~0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^0.1102.10",
|
||||
"@angular/cli": "^11.2.10",
|
||||
"@angular/compiler-cli": "^11.2.11",
|
||||
"@angular/language-service": "^11.2.11",
|
||||
"@sentry/cli": "^1.64.1",
|
||||
"@sentry/electron": "^2.4.0",
|
||||
"@types/jasmine": "^3.6.10",
|
||||
"@types/jasminewd2": "^2.0.8",
|
||||
"@types/node": "14.14.37",
|
||||
"codelyzer": "^6.0.1",
|
||||
"electron": "^12.0.5",
|
||||
"electron-builder": "22.10.5",
|
||||
"@angular-devkit/build-angular": "^12.2.12",
|
||||
"@angular/cli": "^12.2.12",
|
||||
"@angular/compiler-cli": "^12.2.12",
|
||||
"@angular/language-service": "^12.2.12",
|
||||
"@sentry/cli": "^1.71.0",
|
||||
"@sentry/electron": "^2.5.4",
|
||||
"@types/jasmine": "^3.10.2",
|
||||
"@types/jasminewd2": "^2.0.10",
|
||||
"@types/node": "16.11.6",
|
||||
"codelyzer": "^6.0.2",
|
||||
"electron": "^13.6.6",
|
||||
"electron-builder": "^22.9.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"jasmine-core": "~3.7.1",
|
||||
"jasmine-spec-reporter": "~6.0.0",
|
||||
"jasmine-core": "~3.10.1",
|
||||
"jasmine-spec-reporter": "~7.0.0",
|
||||
"jquery": "^3.6.0",
|
||||
"karma": "^6.3.2",
|
||||
"karma": "^6.3.16",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-cli": "^2.0.0",
|
||||
"karma-coverage-istanbul-reporter": "~3.0.3",
|
||||
"karma-jasmine": "~4.0.1",
|
||||
"karma-jasmine-html-reporter": "^1.5.4",
|
||||
"karma-jasmine-html-reporter": "^1.7.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"popper.js": "^1.16.1",
|
||||
"prettier": "^2.2.1",
|
||||
"prettier": "^2.4.1",
|
||||
"protractor": "^7.0.0",
|
||||
"replace": "^1.2.1",
|
||||
"rxjs-tslint": "^0.1.8",
|
||||
"ts-mockito": "^2.6.1",
|
||||
"ts-node": "~9.1.1",
|
||||
"ts-node": "~10.4.0",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typescript": "4.0.2",
|
||||
"webpack": "5.31.0",
|
||||
"typescript": "4.2.3",
|
||||
"webpack": "5.76.0",
|
||||
"yarn-upgrade-all": "^0.5.4"
|
||||
},
|
||||
"greenkeeper": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
setuptools==54.2.0
|
||||
setuptools==71.1.0
|
||||
cx_Freeze==5.1.1
|
||||
requests==2.25.1
|
||||
requests==2.32.3
|
||||
packaging==20.9
|
||||
appdirs==1.4.4
|
||||
psutil==5.8.0
|
||||
|
@ -1,6 +1,10 @@
|
||||
GNS3 WebUI is web implementation of user interface for GNS3 software.
|
||||
|
||||
Current version: 2.2.19
|
||||
Current version: 2.2.32
|
||||
|
||||
Bug Fixes & enhancements
|
||||
- Fixed generated capture file is not valid
|
||||
- Fixed Docker additional directories
|
||||
|
||||
Current version: 2020.4.0-beta.1
|
||||
|
||||
|
@ -3,7 +3,6 @@ import { RouterModule, Routes } from '@angular/router';
|
||||
import { BundledServerFinderComponent } from './components/bundled-server-finder/bundled-server-finder.component';
|
||||
import { DirectLinkComponent } from './components/direct-link/direct-link.component';
|
||||
import { HelpComponent } from './components/help/help.component';
|
||||
import { ReportIssueComponent } from './components/help/report-issue/report-issue.component';
|
||||
import { InstalledSoftwareComponent } from './components/installed-software/installed-software.component';
|
||||
import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component';
|
||||
import { BuiltInPreferencesComponent } from './components/preferences/built-in/built-in-preferences.component';
|
||||
@ -69,7 +68,6 @@ const routes: Routes = [
|
||||
resolve: { server: ServerResolve },
|
||||
},
|
||||
{ path: 'help', component: HelpComponent },
|
||||
{ path: 'help/reportissue', component: ReportIssueComponent },
|
||||
{ path: 'settings', component: SettingsComponent },
|
||||
{ path: 'settings/console', component: ConsoleComponent },
|
||||
{ path: 'installed-software', component: InstalledSoftwareComponent },
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div [ngClass]="{ dark: darkThemeEnabled, light: !darkThemeEnabled }">
|
||||
<router-outlet></router-outlet>
|
||||
<app-adbutler></app-adbutler>
|
||||
<!-- <app-adbutler></app-adbutler> -->
|
||||
</div>
|
||||
|
@ -2,7 +2,6 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { PersistenceService } from 'angular-persistence';
|
||||
import { ElectronService, NgxElectronModule } from 'ngx-electron';
|
||||
import { AppComponent } from './app.component';
|
||||
import { ProgressService } from './common/progress/progress.service';
|
||||
@ -21,12 +20,12 @@ describe('AppComponent', () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AppComponent],
|
||||
imports: [RouterTestingModule, MatIconModule, NgxElectronModule],
|
||||
providers: [SettingsService, PersistenceService, ProgressService],
|
||||
providers: [SettingsService, ProgressService],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
}).compileComponents();
|
||||
|
||||
electronService = TestBed.get(ElectronService);
|
||||
settingsService = TestBed.get(SettingsService);
|
||||
electronService = TestBed.inject(ElectronService);
|
||||
settingsService = TestBed.inject(SettingsService);
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
@ -46,23 +45,18 @@ describe('AppComponent', () => {
|
||||
}));
|
||||
|
||||
it('should receive changed settings and forward to electron', async(() => {
|
||||
const spy = createSpyObj('Electron.IpcRenderer', ['send']);
|
||||
spyOnProperty(electronService, 'isElectronApp').and.returnValue(true);
|
||||
spyOnProperty(electronService, 'ipcRenderer').and.returnValue(spy);
|
||||
settingsService.set('crash_reports', true);
|
||||
settingsService.setReportsSettings(true);
|
||||
component.ngOnInit();
|
||||
settingsService.set('crash_reports', false);
|
||||
expect(spy.send).toHaveBeenCalled();
|
||||
expect(spy.send.calls.mostRecent().args[0]).toEqual('settings.changed');
|
||||
expect(spy.send.calls.mostRecent().args[1].crash_reports).toEqual(false);
|
||||
settingsService.setReportsSettings(false);
|
||||
}));
|
||||
|
||||
it('should receive changed settings and do not forward to electron', async(() => {
|
||||
const spy = createSpyObj('Electron.IpcRenderer', ['send']);
|
||||
spyOnProperty(electronService, 'isElectronApp').and.returnValue(false);
|
||||
settingsService.set('crash_reports', true);
|
||||
settingsService.setReportsSettings(true);
|
||||
component.ngOnInit();
|
||||
settingsService.set('crash_reports', false);
|
||||
settingsService.setReportsSettings(false);
|
||||
expect(spy.send).not.toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
|
@ -37,12 +37,6 @@ export class AppComponent implements OnInit {
|
||||
@HostBinding('class') componentCssClass;
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.electronService.isElectronApp) {
|
||||
this.settingsService.subscribe((settings) => {
|
||||
this.electronService.ipcRenderer.send('settings.changed', settings);
|
||||
});
|
||||
}
|
||||
|
||||
this.applyTheme(this.themeService.savedTheme + '-theme');
|
||||
this.themeService.themeChanged.subscribe((event: string) => {
|
||||
this.applyTheme(event);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { AngularReactBrowserModule } from '@angular-react/core';
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||
import { OverlayModule } from '@angular/cdk/overlay';
|
||||
import { CdkTableModule } from '@angular/cdk/table';
|
||||
@ -9,7 +8,6 @@ import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { BrowserModule, Title } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { DragAndDropModule } from 'angular-draggable-droppable';
|
||||
import { PersistenceModule } from 'angular-persistence';
|
||||
import { ResizableModule } from 'angular-resizable-element';
|
||||
import { D3Service } from 'd3-ng2-service';
|
||||
import { NgCircleProgressModule } from 'ng-circle-progress';
|
||||
@ -44,7 +42,6 @@ import { NodeLabelDraggedComponent } from './components/drawings-listeners/node-
|
||||
import { TextAddedComponent } from './components/drawings-listeners/text-added/text-added.component';
|
||||
import { TextEditedComponent } from './components/drawings-listeners/text-edited/text-edited.component';
|
||||
import { HelpComponent } from './components/help/help.component';
|
||||
import { ReportIssueComponent } from './components/help/report-issue/report-issue.component';
|
||||
import { InstallSoftwareComponent } from './components/installed-software/install-software/install-software.component';
|
||||
import { InstalledSoftwareComponent } from './components/installed-software/installed-software.component';
|
||||
import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component';
|
||||
@ -218,6 +215,7 @@ import { DefaultLayoutComponent } from './layouts/default-layout/default-layout.
|
||||
import { MATERIAL_IMPORTS } from './material.imports';
|
||||
import { ServerResolve } from './resolvers/server-resolve';
|
||||
import { ApplianceService } from './services/appliances.service';
|
||||
import { ProtocolHandlerService } from './services/protocol-handler.service';
|
||||
import { BuiltInTemplatesConfigurationService } from './services/built-in-templates-configuration.service';
|
||||
import { BuiltInTemplatesService } from './services/built-in-templates.service';
|
||||
import { ComputeService } from './services/compute.service';
|
||||
@ -228,7 +226,6 @@ import { ExternalSoftwareDefinitionService } from './services/external-software-
|
||||
import { Gns3vmService } from './services/gns3vm.service';
|
||||
import { GoogleAnalyticsService } from './services/google-analytics.service';
|
||||
import { HttpServer, ServerErrorHandler } from './services/http-server.service';
|
||||
import { IndexedDbService } from './services/indexed-db.service';
|
||||
import { InfoService } from './services/info.service';
|
||||
import { InstalledSoftwareService } from './services/installed-software.service';
|
||||
import { IosConfigurationService } from './services/ios-configuration.service';
|
||||
@ -457,11 +454,9 @@ import { RotationValidator } from './validators/rotation-validator';
|
||||
InformationDialogComponent,
|
||||
TemplateNameDialogComponent,
|
||||
ConfigureCustomAdaptersDialogComponent,
|
||||
EditNetworkConfigurationDialogComponent,
|
||||
ReportIssueComponent,
|
||||
EditNetworkConfigurationDialogComponent
|
||||
],
|
||||
imports: [
|
||||
AngularReactBrowserModule,
|
||||
BrowserModule,
|
||||
HttpClientModule,
|
||||
AppRoutingModule,
|
||||
@ -470,7 +465,6 @@ import { RotationValidator } from './validators/rotation-validator';
|
||||
BrowserAnimationsModule,
|
||||
CdkTableModule,
|
||||
CartographyModule,
|
||||
PersistenceModule,
|
||||
NgxElectronModule,
|
||||
FileUploadModule,
|
||||
MatSidenavModule,
|
||||
@ -494,7 +488,6 @@ import { RotationValidator } from './validators/rotation-validator';
|
||||
NodeService,
|
||||
LinkService,
|
||||
DrawingService,
|
||||
IndexedDbService,
|
||||
HttpServer,
|
||||
SnapshotService,
|
||||
ProgressDialogService,
|
||||
@ -546,6 +539,7 @@ import { RotationValidator } from './validators/rotation-validator';
|
||||
ComputeService,
|
||||
TracengService,
|
||||
PacketCaptureService,
|
||||
ProtocolHandlerService,
|
||||
NotificationService,
|
||||
Gns3vmService,
|
||||
ThemeService,
|
||||
|
@ -26,9 +26,7 @@ export class DrawingComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
try {
|
||||
this.drawing.element = this.svgToDrawingConverter.convert(this.drawing.svg);
|
||||
} catch (error) {
|
||||
console.log(`Cannot convert due to Error: '${error}'`);
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
OnDragging(evt) {
|
||||
|
@ -7,4 +7,6 @@
|
||||
[attr.stroke-dasharray]="stroke_dasharray"
|
||||
[attr.width]="rect.width"
|
||||
[attr.height]="rect.height"
|
||||
[attr.rx]="rect.rx"
|
||||
[attr.ry]="rect.ry"
|
||||
/>
|
||||
|
Before Width: | Height: | Size: 278 B After Width: | Height: | Size: 322 B |
@ -87,6 +87,7 @@ export class TextEditorComponent implements OnInit, OnDestroy {
|
||||
`scale(${this.mapScaleService.getScale()})`
|
||||
);
|
||||
this.temporaryTextElement.nativeElement.focus();
|
||||
document.documentElement.style.cursor = "default";
|
||||
|
||||
let textListener = () => {
|
||||
this.drawingsEventSource.textAdded.emit(
|
||||
|
@ -14,7 +14,7 @@ export class MapDrawingToSvgConverter implements Converter<MapDrawing, string> {
|
||||
let elem = ``;
|
||||
|
||||
if (mapDrawing.element instanceof RectElement) {
|
||||
elem = `<rect fill=\"${mapDrawing.element.fill}\" fill-opacity=\"${mapDrawing.element.fill_opacity}\" height=\"${mapDrawing.element.height}\" width=\"${mapDrawing.element.width}\" stroke=\"${mapDrawing.element.stroke}\" stroke-width=\"${mapDrawing.element.stroke_width}\" />`;
|
||||
elem = `<rect fill=\"${mapDrawing.element.fill}\" fill-opacity=\"${mapDrawing.element.fill_opacity}\" height=\"${mapDrawing.element.height}\" width=\"${mapDrawing.element.width}\" stroke=\"${mapDrawing.element.stroke}\" stroke-width=\"${mapDrawing.element.stroke_width}\" rx=\"${mapDrawing.element.rx}\" ry=\"${mapDrawing.element.ry}\" />`;
|
||||
} else if (mapDrawing.element instanceof EllipseElement) {
|
||||
elem = `<ellipse fill=\"${mapDrawing.element.fill}\" fill-opacity=\"${mapDrawing.element.fill_opacity}\" cx=\"${mapDrawing.element.cx}\" cy=\"${mapDrawing.element.cy}\" rx=\"${mapDrawing.element.rx}\" ry=\"${mapDrawing.element.ry}\" stroke=\"${mapDrawing.element.stroke}\" stroke-width=\"${mapDrawing.element.stroke_width}\" />`;
|
||||
} else if (mapDrawing.element instanceof LineElement) {
|
||||
|
@ -13,6 +13,8 @@ export class RectangleElementFactory implements DrawingElementFactory {
|
||||
rectElement.stroke_width = 2;
|
||||
rectElement.width = 200;
|
||||
rectElement.height = 100;
|
||||
rectElement.rx = 0;
|
||||
rectElement.ry = 0;
|
||||
return rectElement;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -17,6 +17,8 @@ describe('RectConverter', () => {
|
||||
|
||||
element.setAttribute('width', '100px');
|
||||
element.setAttribute('height', '200px');
|
||||
element.setAttribute('rx', '0');
|
||||
element.setAttribute('ry', '0');
|
||||
|
||||
const drawing = rectConverter.convert(element);
|
||||
expect(drawing.fill).toEqual('#ffffff');
|
||||
@ -25,6 +27,8 @@ describe('RectConverter', () => {
|
||||
expect(drawing.stroke_dasharray).toEqual('5,25,25');
|
||||
expect(drawing.width).toEqual(100);
|
||||
expect(drawing.height).toEqual(200);
|
||||
expect(drawing.rx).toEqual(0);
|
||||
expect(drawing.ry).toEqual(0);
|
||||
});
|
||||
|
||||
it('should parse with no attributes', () => {
|
||||
@ -37,5 +41,7 @@ describe('RectConverter', () => {
|
||||
expect(drawing.stroke_dasharray).toBeUndefined();
|
||||
expect(drawing.width).toBeUndefined();
|
||||
expect(drawing.height).toBeUndefined();
|
||||
expect(drawing.rx).toBeUndefined();
|
||||
expect(drawing.ry).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
@ -40,6 +40,16 @@ export class RectConverter implements SvgConverter {
|
||||
drawing.height = parseInt(height.value, 10);
|
||||
}
|
||||
|
||||
const rx = element.attributes.getNamedItem('rx');
|
||||
if (rx) {
|
||||
drawing.rx = parseInt(rx.value, 0);
|
||||
}
|
||||
|
||||
const ry = element.attributes.getNamedItem('ry');
|
||||
if (ry) {
|
||||
drawing.ry = parseInt(ry.value, 0);
|
||||
}
|
||||
|
||||
return drawing;
|
||||
}
|
||||
}
|
||||
|
@ -8,4 +8,6 @@ export class RectElement implements DrawingElement {
|
||||
stroke: string;
|
||||
stroke_width: number;
|
||||
stroke_dasharray: string;
|
||||
rx: number;
|
||||
ry: number;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ export class Properties {
|
||||
headless: boolean;
|
||||
linked_clone: boolean;
|
||||
on_close: string;
|
||||
aux: number;
|
||||
ram: number;
|
||||
nvram: number;
|
||||
usage: string;
|
||||
@ -53,7 +54,10 @@ export class Properties {
|
||||
qemu_path: string;
|
||||
environment: string;
|
||||
extra_hosts: string;
|
||||
extra_volumes: string[];
|
||||
replicate_network_connection_state: boolean;
|
||||
tpm: boolean;
|
||||
uefi: boolean;
|
||||
}
|
||||
|
||||
export class Node {
|
||||
|
@ -44,9 +44,7 @@ export class DrawingsWidget implements Widget {
|
||||
layer.drawings.forEach((d: MapDrawing) => {
|
||||
try {
|
||||
d.element = this.svgToDrawingConverter.convert(d.svg);
|
||||
} catch (error) {
|
||||
console.log(`Cannot convert due to Error: '${error}'`);
|
||||
}
|
||||
} catch (error) {}
|
||||
});
|
||||
return layer.drawings;
|
||||
},
|
||||
@ -81,9 +79,7 @@ export class DrawingsWidget implements Widget {
|
||||
.on('start', (datum: MapDrawing) => {
|
||||
document.body.style.cursor = 'ns-resize';
|
||||
topEdge = datum.y;
|
||||
console.log('started');
|
||||
y = event.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
|
||||
// startEvent = event;
|
||||
})
|
||||
.on('drag', (datum: MapDrawing) => {
|
||||
const evt = event;
|
||||
@ -91,55 +87,10 @@ export class DrawingsWidget implements Widget {
|
||||
y = event.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
|
||||
let height = datum.element.height - dy;
|
||||
if (height < 0) {
|
||||
// height = datum.y - startEvent.y;
|
||||
datum.y += height;
|
||||
height = topEdge - datum.y;
|
||||
// console.log(topEdge - datum.y);
|
||||
}
|
||||
console.log('Height', height);
|
||||
datum.element.height = height;
|
||||
|
||||
// datum.element.height -= dy;
|
||||
// if(datum.element.height < 0) {
|
||||
// datum.y -= datum.element.height;
|
||||
// datum.element.height = Math.abs(datum.element.height);
|
||||
// }
|
||||
|
||||
// if (!isReflectedVertical) {
|
||||
// if ((datum.element.height + evt.dy) < 0) {
|
||||
// isReflectedVertical = true;
|
||||
// y = topEdge;
|
||||
// console.log(y);
|
||||
// datum.element.height = Math.abs(datum.element.height + evt.dy);
|
||||
// console.log(datum.element.height);
|
||||
// } else {
|
||||
// datum.element.height += evt.dy;
|
||||
|
||||
// if (datum.element instanceof EllipseElement){
|
||||
// (datum.element as EllipseElement).cy = (datum.element as EllipseElement).cy + evt.dy/2 < 0 ? 1 : (datum.element as EllipseElement).cy += evt.dy/2;
|
||||
// (datum.element as EllipseElement).ry = (datum.element as EllipseElement).ry + evt.dy/2 < 0 ? 1 : (datum.element as EllipseElement).ry += evt.dy/2;
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// dy = y - (evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y);
|
||||
// y = evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
|
||||
|
||||
// if ((datum.element.height + dy) < 0){
|
||||
// isReflectedVertical = false;
|
||||
// y = topEdge;
|
||||
// console.log(y);
|
||||
// datum.element.height = Math.abs(datum.element.height + evt.dy);
|
||||
// console.log(datum.element.height);
|
||||
// } else {
|
||||
// datum.y = evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
|
||||
// datum.element.height += dy;
|
||||
// if (datum.element instanceof EllipseElement) {
|
||||
// (datum.element as EllipseElement).cy = (datum.element as EllipseElement).cy + dy/2 < 0 ? 1 : (datum.element as EllipseElement).cy += dy/2;
|
||||
// (datum.element as EllipseElement).ry = (datum.element as EllipseElement).ry + dy/2 < 0 ? 1 : (datum.element as EllipseElement).ry += dy/2;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
this.redrawDrawing(view, datum);
|
||||
})
|
||||
.on('end', (datum: MapDrawing) => {
|
||||
|
@ -46,9 +46,7 @@ export class DrawingsWidget implements Widget {
|
||||
layer.drawings.forEach((d: MapDrawing) => {
|
||||
try {
|
||||
d.element = this.svgToDrawingConverter.convert(d.svg);
|
||||
} catch (error) {
|
||||
console.log(`Cannot convert due to Error: '${error}'`);
|
||||
}
|
||||
} catch (error) {}
|
||||
});
|
||||
return layer.drawings;
|
||||
},
|
||||
|
@ -28,6 +28,8 @@ describe('RectDrawingWidget', () => {
|
||||
rect.stroke_dasharray = '5,25,25';
|
||||
rect.width = 100;
|
||||
rect.height = 200;
|
||||
rect.rx = 0;
|
||||
rect.ry = 0;
|
||||
drawing.element = rect;
|
||||
|
||||
const drawings = svg.canvas.selectAll<SVGGElement, MapDrawing>('g.drawing').data([drawing]);
|
||||
@ -46,5 +48,7 @@ describe('RectDrawingWidget', () => {
|
||||
expect(rect_element.getAttribute('stroke-dasharray')).toEqual('5,25,25');
|
||||
expect(rect_element.getAttribute('width')).toEqual('100');
|
||||
expect(rect_element.getAttribute('height')).toEqual('200');
|
||||
expect(rect_element.getAttribute('rx')).toEqual('0');
|
||||
expect(rect_element.getAttribute('ry')).toEqual('0');
|
||||
});
|
||||
});
|
||||
|
@ -33,7 +33,9 @@ export class RectDrawingWidget implements DrawingShapeWidget {
|
||||
.attr('stroke-width', (rect) => rect.stroke_width)
|
||||
.attr('stroke-dasharray', (rect) => this.qtDasharrayFixer.fix(rect.stroke_dasharray))
|
||||
.attr('width', (rect) => rect.width)
|
||||
.attr('height', (rect) => rect.height);
|
||||
.attr('height', (rect) => rect.height)
|
||||
.attr('rx', (rect) => rect.rx)
|
||||
.attr('ry', (rect) => rect.ry);
|
||||
|
||||
drawing.exit().remove();
|
||||
}
|
||||
|
@ -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');
|
||||
});
|
||||
|
||||
|
@ -90,16 +90,16 @@ export class InterfaceStatusWidget implements Widget {
|
||||
.merge(status_started_enter)
|
||||
.attr('class', 'status_started')
|
||||
.attr('width', (ls: LinkStatus) => {
|
||||
return ls.port.length * 8 + 10;
|
||||
return ls.port.length * 10 + 5;
|
||||
})
|
||||
.attr('height', 20)
|
||||
.attr('x', (ls: LinkStatus) => ls.x - 30)
|
||||
.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
|
||||
@ -122,16 +122,16 @@ export class InterfaceStatusWidget implements Widget {
|
||||
.merge(status_stopped_enter)
|
||||
.attr('class', 'status_stopped')
|
||||
.attr('width', (ls: LinkStatus) => {
|
||||
return ls.port.length * 8 + 10;
|
||||
return ls.port.length * 10 + 5;
|
||||
})
|
||||
.attr('height', 20)
|
||||
.attr('x', (ls: LinkStatus) => ls.x - 30)
|
||||
.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
|
||||
@ -154,7 +154,7 @@ export class InterfaceStatusWidget implements Widget {
|
||||
.merge(status_suspended_enter)
|
||||
.attr('class', 'status_suspended')
|
||||
.attr('width', (ls: LinkStatus) => {
|
||||
return ls.port.length * 8 + 10;
|
||||
return ls.port.length * 10 + 5;
|
||||
})
|
||||
.attr('height', 20)
|
||||
.attr('x', (ls: LinkStatus) => ls.x - 30)
|
||||
@ -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
|
||||
|
@ -87,6 +87,7 @@ export class LinkWidget implements Widget {
|
||||
.filter((l) => {
|
||||
return (
|
||||
!l.capturing &&
|
||||
!l.suspend &&
|
||||
(l.filters.bpf || l.filters.corrupt || l.filters.delay || l.filters.frequency_drop || l.filters.packet_loss)
|
||||
);
|
||||
})
|
||||
@ -113,9 +114,7 @@ export class LinkWidget implements Widget {
|
||||
link_body
|
||||
.filter((l) => {
|
||||
return (
|
||||
l.capturing &&
|
||||
l.suspend &&
|
||||
!(l.filters.bpf || l.filters.corrupt || l.filters.delay || l.filters.frequency_drop || l.filters.packet_loss)
|
||||
l.suspend
|
||||
);
|
||||
})
|
||||
.append<SVGGElement>('g')
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Injector } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { SettingsService } from '../../services/settings.service';
|
||||
import { MockedSettingsService } from '../../services/settings.service.spec';
|
||||
import { ToasterService } from '../../services/toaster.service';
|
||||
import { MockedToasterService } from '../../services/toaster.service.spec';
|
||||
import { SentryErrorHandler } from './sentry-error-handler';
|
||||
@ -17,19 +16,21 @@ class MockedToasterErrorHandler extends ToasterErrorHandler {
|
||||
describe('ToasterErrorHandler', () => {
|
||||
let handler: ToasterErrorHandler;
|
||||
let toasterService: MockedToasterService;
|
||||
let settingsService: SettingsService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{ provide: ToasterService, useClass: MockedToasterService },
|
||||
{ provide: SettingsService, useClass: MockedSettingsService },
|
||||
{ provide: SettingsService},
|
||||
SentryErrorHandler,
|
||||
ToasterErrorHandler,
|
||||
],
|
||||
});
|
||||
|
||||
handler = new MockedToasterErrorHandler(TestBed.get(Injector));
|
||||
handler = new MockedToasterErrorHandler(TestBed.inject(Injector));
|
||||
toasterService = TestBed.get(ToasterService);
|
||||
settingsService = TestBed.inject(SettingsService);
|
||||
});
|
||||
|
||||
it('should call toaster with error message', () => {
|
||||
|
@ -21,7 +21,15 @@ export class BundledServerFinderComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
this.progressService.activate();
|
||||
setTimeout(() => {
|
||||
let port = parseInt(this.document.location.port, 10) ? parseInt(this.document.location.port, 10) : 80;
|
||||
let port;
|
||||
|
||||
if (parseInt(this.document.location.port, 10)) {
|
||||
port = parseInt(this.document.location.port, 10);
|
||||
} else if (this.document.location.protocol == "https:") {
|
||||
port = 443;
|
||||
} else {
|
||||
port = 80;
|
||||
}
|
||||
|
||||
this.serverService.getLocalServer(this.document.location.hostname, port).then((server: Server) => {
|
||||
this.progressService.deactivate();
|
||||
|
@ -66,7 +66,6 @@ export class DirectLinkComponent implements OnInit {
|
||||
const servers = await this.serverService.findAll();
|
||||
const server = servers.filter((server) => server.host === this.serverIp && server.port === this.serverPort)[0];
|
||||
|
||||
console.log(servers);
|
||||
if (server) {
|
||||
this.router.navigate(['/server', server.id, 'project', this.projectId]);
|
||||
} else {
|
||||
|
@ -31,9 +31,6 @@
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
</div>
|
||||
<button mat-button class="full-width">
|
||||
<a href="https://docs.gns3.com/docs/"> Go to documentation </a>
|
||||
</button>
|
||||
<button mat-button routerLink="/help/reportissue" class="full-width">Report an issue</button>
|
||||
<button mat-button color="primary" class="full-width" (click)="goToDocumentation()">Go to documentation</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
.full-width {
|
||||
width: 50%;
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
@ -28,4 +28,8 @@ export class HelpComponent implements OnInit {
|
||||
this.releasenotes = data.replace(new RegExp('\n', 'g'), '<br />');
|
||||
});
|
||||
}
|
||||
|
||||
goToDocumentation() {
|
||||
window.location.href = "https://docs.gns3.com/docs/";
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +0,0 @@
|
||||
import React, {Component} from "react";
|
||||
import Form from 'react-bootstrap/Form';
|
||||
|
||||
const formGroupStyle = {
|
||||
margin: '20px'
|
||||
};
|
||||
|
||||
class FilterComponent extends Component<any, any> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Form.Group style={formGroupStyle}>
|
||||
<Form.Control size="lg" type="text" placeholder="Search by keyword" value={this.props.filter} onChange={this.props.handleChange} />
|
||||
</Form.Group>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default FilterComponent;
|
@ -1,95 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
import Card from 'react-bootstrap/Card';
|
||||
import Spinner from 'react-bootstrap/Spinner';
|
||||
import IssueComponent from './issue';
|
||||
import FilterComponent from './filter';
|
||||
import e from '../../../event-bus';
|
||||
|
||||
const wrapperStyle = {
|
||||
justifyContent: 'center' as 'center',
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
flexDirection: 'row' as 'row',
|
||||
flexWrap: 'wrap' as 'wrap',
|
||||
margin: '20px'
|
||||
};
|
||||
|
||||
const cardStyle = {
|
||||
width: '300px',
|
||||
margin: '20px'
|
||||
};
|
||||
|
||||
const cardTitleStyle = {
|
||||
color: 'red'
|
||||
};
|
||||
const message = 'Thank you for reporting the issue!';
|
||||
const apiUrl = 'https://api.github.com/repos/GNS3/gns3-web-ui/issues';
|
||||
const newIssueLink = 'https://github.com/GNS3/gns3-web-ui/issues/new';
|
||||
|
||||
class IssueListComponent extends Component<any, any> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
issues: [],
|
||||
filteredIssues: [],
|
||||
isFetching: true
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
fetch(apiUrl)
|
||||
.then(response => response.json())
|
||||
.then(data => this.setState({
|
||||
issues: data,
|
||||
filteredIssues: data,
|
||||
isFetching: false
|
||||
}));
|
||||
}
|
||||
|
||||
handleChange = (event) => {
|
||||
let filter = event.target.value;
|
||||
let filteredIssues = this.state.issues;
|
||||
|
||||
filteredIssues = filteredIssues.filter((issue) => {
|
||||
return issue.title.toLowerCase().includes(filter.toLowerCase())
|
||||
});
|
||||
|
||||
this.setState({
|
||||
filteredIssues: filteredIssues
|
||||
});
|
||||
}
|
||||
|
||||
newIssueOpened = (event) => {
|
||||
e.emit('message', { text: message });
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<FilterComponent handleChange={this.handleChange} filter={this.state.filter} />
|
||||
</div>
|
||||
|
||||
{this.state.isFetching ? (
|
||||
<div style={wrapperStyle}>
|
||||
<Spinner animation="grow" />
|
||||
</div>
|
||||
) : (
|
||||
<div style={wrapperStyle}>
|
||||
{this.state.filteredIssues.map(issue =>
|
||||
<IssueComponent key={issue.html_url} data={issue}/>
|
||||
)}
|
||||
<Card style={cardStyle}>
|
||||
<Card.Body>
|
||||
<Card.Title style={cardTitleStyle}>Don't see your issue here?</Card.Title>
|
||||
<Card.Link onClick={this.newIssueOpened} href={newIssueLink} target = "_blank">Open new issue</Card.Link>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default IssueListComponent;
|
@ -1,42 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
import Card from 'react-bootstrap/Card';
|
||||
|
||||
const cardStyle = {
|
||||
width: '300px',
|
||||
margin: '20px'
|
||||
};
|
||||
|
||||
const cardTitleStyle = {
|
||||
color: 'black'
|
||||
};
|
||||
|
||||
const cardTextStyle = {
|
||||
color: 'black',
|
||||
overflow: 'auto',
|
||||
height: '100px'
|
||||
};
|
||||
|
||||
class IssueComponent extends Component<any, any> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
data: this.props.data
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const issue = this.state.data;
|
||||
return (
|
||||
<Card key={issue.html_url} style={cardStyle}>
|
||||
<Card.Body>
|
||||
<Card.Title style={cardTitleStyle}>{issue.title}</Card.Title>
|
||||
<Card.Subtitle className="mb-2 text-muted">Status: {issue.state}</Card.Subtitle>
|
||||
<Card.Text style={cardTextStyle}>{issue.body}</Card.Text>
|
||||
<Card.Link href={issue.html_url} target = "_blank">{issue.html_url}</Card.Link>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default IssueComponent;
|
@ -1 +0,0 @@
|
||||
<span #issueListComponentContainer></span>
|
@ -1,52 +0,0 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnInit,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
ViewChild,
|
||||
ViewEncapsulation,
|
||||
AfterContentInit
|
||||
} from '@angular/core';
|
||||
import IssueListComponent from '../report-issue/issue-list';
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import e from '../../../event-bus';
|
||||
import { ToasterService } from '../../../services/toaster.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-report-issue',
|
||||
templateUrl: './report-issue.component.html',
|
||||
styleUrls: ['./report-issue.component.scss']
|
||||
})
|
||||
export class ReportIssueComponent implements OnDestroy, AfterViewInit, AfterContentInit {
|
||||
@ViewChild('issueListComponentContainer') containerRef: ElementRef;
|
||||
|
||||
constructor(private toasterService: ToasterService) {}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.render();
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
e.on('message', message => {
|
||||
this.toasterService.success(message.text);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
ReactDOM.unmountComponentAtNode(this.containerRef.nativeElement);
|
||||
}
|
||||
|
||||
private render() {
|
||||
ReactDOM.render(<div>
|
||||
<IssueListComponent />
|
||||
</div>, this.containerRef.nativeElement);
|
||||
}
|
||||
}
|
@ -13,8 +13,8 @@
|
||||
<th mat-header-cell *matHeaderCellDef>Adapter type</th>
|
||||
<td mat-cell *matCellDef="let element; let i = index">
|
||||
<mat-select placeholder="Type" [(ngModel)]="element.adapter_type">
|
||||
<mat-option *ngFor="let type of networkTypes" [value]="type">
|
||||
{{ type }}
|
||||
<mat-option *ngFor="let type of networkTypes" [value]="type.value">
|
||||
{{ type.name }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</td>
|
||||
|
@ -18,9 +18,7 @@ export class CustomAdaptersComponent {
|
||||
public adapters: CustomAdapter[];
|
||||
public numberOfAdapters: number;
|
||||
|
||||
constructor() {
|
||||
console.log(this.networkTypes);
|
||||
}
|
||||
constructor() {}
|
||||
|
||||
cancelConfigureCustomAdapters() {
|
||||
this.closeConfiguratorEmitter.emit(false);
|
||||
@ -28,8 +26,6 @@ export class CustomAdaptersComponent {
|
||||
|
||||
configureCustomAdapters() {
|
||||
this.adapters = [];
|
||||
console.log(this.customAdapters);
|
||||
|
||||
this.customAdapters.adapters.forEach((n) => {
|
||||
this.adapters.push({
|
||||
adapter_number: n.adapter_number,
|
||||
|
@ -59,6 +59,15 @@
|
||||
placeholder="Start command"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
[(ngModel)]="dockerTemplate.mac_address"
|
||||
placeholder="Base MAC"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<input
|
||||
formControlName="adapter"
|
||||
|
@ -61,7 +61,7 @@
|
||||
<div *ngIf="newImageSelected">
|
||||
<input
|
||||
type="file"
|
||||
accept=".bin"
|
||||
accept=".bin, .image, .iol"
|
||||
#file
|
||||
class="nonvisible"
|
||||
(change)="uploadImageFile($event)"
|
||||
|
@ -52,7 +52,7 @@
|
||||
</mat-form-field>
|
||||
<input
|
||||
type="file"
|
||||
accept=".bin"
|
||||
accept=".bin, .image, .iol"
|
||||
#file
|
||||
class="nonvisible"
|
||||
(change)="uploadImageFile($event)"
|
||||
@ -98,14 +98,14 @@
|
||||
<mat-checkbox [(ngModel)]="iouTemplate.l1_keepalives">
|
||||
Enable layer 1 keepalive messages (non-functional) </mat-checkbox
|
||||
><br />
|
||||
<mat-checkbox [(ngModel)]="defaultSettings"> Use default IOU values for memories </mat-checkbox>
|
||||
<mat-form-field class="form-field" *ngIf="!defaultSettings">
|
||||
<mat-checkbox [(ngModel)]="iouTemplate.use_default_iou_values"> Use default IOU values for memories </mat-checkbox>
|
||||
<mat-form-field class="form-field" *ngIf="!iouTemplate.use_default_iou_values">
|
||||
<input matInput type="number" [(ngModel)]="iouTemplate.ram" placeholder="RAM size" />
|
||||
<span matSuffix>MB</span>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field" *ngIf="!defaultSettings">
|
||||
<mat-form-field class="form-field" *ngIf="!iouTemplate.use_default_iou_values">
|
||||
<input matInput type="number" [(ngModel)]="iouTemplate.nvram" placeholder="NVRAM size" />
|
||||
<span matSuffix>MB</span>
|
||||
<span matSuffix>KB</span>
|
||||
</mat-form-field>
|
||||
</mat-expansion-panel>
|
||||
<mat-expansion-panel>
|
||||
|
@ -110,6 +110,9 @@
|
||||
placeholder="Please enter name"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<div *ngIf="uploadedFile">
|
||||
<mat-progress-bar mode="determinate" [value]="uploadProgress" aria-valuemin="0" aria-valuemax="100"></mat-progress-bar>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
|
@ -32,6 +32,8 @@ export class AddQemuVmTemplateComponent implements OnInit {
|
||||
chosenImage: string = '';
|
||||
qemuTemplate: QemuTemplate;
|
||||
uploader: FileUploader;
|
||||
uploadedFile: boolean = false;
|
||||
uploadProgress: number = 0;
|
||||
|
||||
nameForm: FormGroup;
|
||||
memoryForm: FormGroup;
|
||||
@ -86,6 +88,9 @@ export class AddQemuVmTemplateComponent implements OnInit {
|
||||
});
|
||||
this.toasterService.success('Image uploaded');
|
||||
};
|
||||
this.uploader.onProgressItem = (progress: any) => {
|
||||
this.uploadProgress = progress['progress'];
|
||||
};
|
||||
|
||||
const server_id = this.route.snapshot.paramMap.get('server_id');
|
||||
this.serverService.get(parseInt(server_id, 10)).then((server: Server) => {
|
||||
@ -127,6 +132,7 @@ export class AddQemuVmTemplateComponent implements OnInit {
|
||||
}
|
||||
|
||||
uploadImageFile(event) {
|
||||
this.uploadedFile = true;
|
||||
let name = event.target.files[0].name;
|
||||
this.diskForm.controls['fileName'].setValue(name);
|
||||
|
||||
|
@ -186,13 +186,15 @@
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-select placeholder="Type" [(ngModel)]="qemuTemplate.adapter_type">
|
||||
<mat-option *ngFor="let type of networkTypes" [value]="type[0]"> {{ type[1] }} ({{ type[0] }}) </mat-option>
|
||||
<mat-option *ngFor="let type of networkTypes" [value]="type.value">{{type.name}} ({{type.value}}) </mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<button mat-button class="configButton" (click)="setCustomAdaptersConfiguratorState(true)">
|
||||
Configure custom adapters</button
|
||||
><br />
|
||||
<mat-checkbox [(ngModel)]="qemuTemplate.legacy_networking"> Use the legacy networking mode </mat-checkbox>
|
||||
>
|
||||
<br /><mat-checkbox [(ngModel)]="qemuTemplate.legacy_networking"> Use the legacy networking mode </mat-checkbox>
|
||||
<br /><mat-checkbox [(ngModel)]="qemuTemplate.replicate_network_connection_state"> Replicate network connection state </mat-checkbox>
|
||||
|
||||
</mat-expansion-panel>
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
@ -271,6 +273,8 @@
|
||||
<input matInput type="text" [(ngModel)]="qemuTemplate.options" placeholder="Options" />
|
||||
</mat-form-field>
|
||||
<mat-checkbox [(ngModel)]="qemuTemplate.linked_clone"> Use as a linked base VM </mat-checkbox>
|
||||
<br /><mat-checkbox [(ngModel)]="qemuTemplate.tpm"> Enable the Trusted Platform Module (TPM)</mat-checkbox>
|
||||
<br /><mat-checkbox [(ngModel)]="qemuTemplate.uefi"> Enable the UEFI boot mode </mat-checkbox>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</mat-expansion-panel>
|
||||
|
@ -1,21 +1,47 @@
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { ToasterService } from '../../../services/toaster.service';
|
||||
import { MapSettingsService } from '../../../services/mapsettings.service';
|
||||
import { NodeConsoleService } from '../../../services/nodeConsole.service';
|
||||
import { ThemeService } from '../../../services/theme.service';
|
||||
import { ConsoleWrapperComponent } from './console-wrapper.component';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
|
||||
describe('ConsoleWrapperComponent', () => {
|
||||
let fixture: ComponentFixture<ConsoleWrapperComponent>;
|
||||
let component: ConsoleWrapperComponent;
|
||||
|
||||
let nodeConsoleService: NodeConsoleService;
|
||||
let themeService: ThemeService;
|
||||
let mapSettingsService: MapSettingsService;
|
||||
let toasterService: ToasterService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [RouterTestingModule, MatSnackBarModule],
|
||||
providers: [NodeConsoleService, ThemeService, MapSettingsService, ToasterService]
|
||||
}).compileComponents();
|
||||
|
||||
toasterService = TestBed.inject(ToasterService);
|
||||
nodeConsoleService = TestBed.inject(NodeConsoleService);
|
||||
themeService = TestBed.inject(ThemeService);
|
||||
mapSettingsService = TestBed.inject(MapSettingsService);
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ConsoleWrapperComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should get actual theme', () => {
|
||||
const consoleService = new NodeConsoleService();
|
||||
|
||||
const themeService = autoSpy(ThemeService);
|
||||
themeService.getActualTheme.and.returnValue('light');
|
||||
|
||||
const mapSettingsService = autoSpy(MapSettingsService);
|
||||
const component = new ConsoleWrapperComponent(consoleService, themeService, mapSettingsService);
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
expect(component.isLightThemeEnabled).toBe(true);
|
||||
expect(component.isLightThemeEnabled).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { Router } from '@angular/router';
|
||||
import { ElectronService } from 'ngx-electron';
|
||||
@ -20,7 +21,9 @@ import { ContextConsoleMenuComponent } from './context-console-menu.component';
|
||||
describe('ContextConsoleMenuComponent', () => {
|
||||
let component: ContextConsoleMenuComponent;
|
||||
let fixture: ComponentFixture<ContextConsoleMenuComponent>;
|
||||
let toasterService: MockedToasterService = new MockedToasterService();
|
||||
let nodeConsoleService: NodeConsoleService;
|
||||
let mapSettingsService: MapSettingsService;
|
||||
let toasterService: ToasterService;
|
||||
let router = {
|
||||
url: '',
|
||||
navigate: jasmine.createSpy('navigate'),
|
||||
@ -28,7 +31,6 @@ describe('ContextConsoleMenuComponent', () => {
|
||||
let node = {
|
||||
status: 'started',
|
||||
};
|
||||
let mapSettingsService = new MapSettingsService();
|
||||
|
||||
beforeEach(async(() => {
|
||||
const electronMock = {
|
||||
@ -36,20 +38,25 @@ describe('ContextConsoleMenuComponent', () => {
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [MatMenuModule, BrowserModule],
|
||||
imports: [MatMenuModule, BrowserModule, MatSnackBarModule],
|
||||
providers: [
|
||||
{ provide: ChangeDetectorRef },
|
||||
{ provide: ProjectService, useClass: MockedProjectService },
|
||||
{ provide: ElectronService, useValue: electronMock },
|
||||
{ provide: MapSettingsService, useValue: mapSettingsService },
|
||||
{ provide: NodeConsoleService },
|
||||
{ provide: ConsoleService },
|
||||
{ provide: ToasterService, useValue: toasterService },
|
||||
{ provide: Router, useValue: router },
|
||||
NodeConsoleService,
|
||||
ToasterService,
|
||||
MapSettingsService
|
||||
],
|
||||
declarations: [ContextConsoleMenuComponent, ConsoleDeviceActionComponent, ConsoleDeviceActionBrowserComponent],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
}).compileComponents();
|
||||
|
||||
toasterService = TestBed.inject(ToasterService);
|
||||
mapSettingsService = TestBed.inject(MapSettingsService);
|
||||
nodeConsoleService = TestBed.inject(NodeConsoleService);
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -2,3 +2,11 @@
|
||||
<mat-icon>web_asset</mat-icon>
|
||||
<span>Console</span>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
*ngIf="node.node_type === 'docker' || node.node_type === 'dynamips' || node.node_type === 'qemu'"
|
||||
(click)="openConsole(auxiliary=true)"
|
||||
>
|
||||
<mat-icon>web_asset</mat-icon>
|
||||
<span>Auxiliary console</span>
|
||||
</button>
|
||||
|
@ -1,8 +1,12 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||
import { Node } from '../../../../../cartography/models/node';
|
||||
import { Server } from '../../../../../models/server';
|
||||
import { NodeService } from '../../../../../services/node.service';
|
||||
import { ToasterService } from '../../../../../services/toaster.service';
|
||||
import { ProtocolHandlerService } from '../../../../../services/protocol-handler.service';
|
||||
|
||||
import * as ipaddr from 'ipaddr.js';
|
||||
|
||||
@Component({
|
||||
selector: 'app-console-device-action-browser',
|
||||
@ -12,16 +16,16 @@ export class ConsoleDeviceActionBrowserComponent {
|
||||
@Input() server: Server;
|
||||
@Input() node: Node;
|
||||
|
||||
constructor(private toasterService: ToasterService, private nodeService: NodeService) {}
|
||||
constructor(private toasterService: ToasterService, private nodeService: NodeService, private protocolHandlerService: ProtocolHandlerService) {}
|
||||
|
||||
openConsole() {
|
||||
openConsole(auxiliary: boolean = false) {
|
||||
this.nodeService.getNode(this.server, this.node).subscribe((node: Node) => {
|
||||
this.node = node;
|
||||
this.startConsole();
|
||||
this.startConsole(auxiliary);
|
||||
});
|
||||
}
|
||||
|
||||
startConsole() {
|
||||
startConsole(auxiliary: boolean) {
|
||||
if (this.node.status !== 'started') {
|
||||
this.toasterService.error('This node must be started before a console can be opened');
|
||||
} else {
|
||||
@ -33,20 +37,41 @@ 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 {
|
||||
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}`
|
||||
);
|
||||
} catch (e) {
|
||||
this.toasterService.error(e);
|
||||
try {
|
||||
var uri;
|
||||
var host = this.node.console_host;
|
||||
if (ipaddr.IPv6.isValid(host)) {
|
||||
host = `[${host}]`;
|
||||
}
|
||||
} else {
|
||||
this.toasterService.error('Supported console types: telnet, vnc, spice.');
|
||||
if (this.node.console_type === 'telnet') {
|
||||
|
||||
var console_port;
|
||||
if (auxiliary === true) {
|
||||
console_port = this.node.properties.aux;
|
||||
if (console_port === undefined) {
|
||||
this.toasterService.error('Auxiliary console port is not set.');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
console_port = this.node.console;
|
||||
}
|
||||
uri = `gns3+telnet://${host}:${console_port}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`;
|
||||
} else if (this.node.console_type === 'vnc') {
|
||||
uri = `gns3+vnc://${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')) {
|
||||
uri = `gns3+spice://${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('http')) {
|
||||
uri = `${this.node.console_type}://${host}:${this.node.console}`
|
||||
return window.open(uri); // open an http console directly in a new window/tab
|
||||
} else {
|
||||
this.toasterService.error('Supported console types are: telnet, vnc, spice and spice+agent.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.protocolHandlerService.open(uri);
|
||||
|
||||
} catch (e) {
|
||||
this.toasterService.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import { NodeService } from '../../../../../services/node.service';
|
||||
import { ServerService } from '../../../../../services/server.service';
|
||||
import { MockedServerService } from '../../../../../services/server.service.spec';
|
||||
import { SettingsService } from '../../../../../services/settings.service';
|
||||
import { MockedSettingsService } from '../../../../../services/settings.service.spec';
|
||||
import { ToasterService } from '../../../../../services/toaster.service';
|
||||
import { MockedToasterService } from '../../../../../services/toaster.service.spec';
|
||||
import { MockedNodeService } from '../../../project-map.component.spec';
|
||||
@ -18,7 +17,7 @@ describe('ConsoleDeviceActionComponent', () => {
|
||||
let fixture: ComponentFixture<ConsoleDeviceActionComponent>;
|
||||
let electronService;
|
||||
let server: Server;
|
||||
let mockedSettingsService: MockedSettingsService;
|
||||
let settingsService: SettingsService;
|
||||
let mockedServerService: MockedServerService;
|
||||
let mockedToaster: MockedToasterService;
|
||||
let mockedNodeService: MockedNodeService = new MockedNodeService();
|
||||
@ -35,7 +34,6 @@ describe('ConsoleDeviceActionComponent', () => {
|
||||
},
|
||||
};
|
||||
|
||||
mockedSettingsService = new MockedSettingsService();
|
||||
mockedServerService = new MockedServerService();
|
||||
mockedToaster = new MockedToasterService();
|
||||
|
||||
@ -47,13 +45,15 @@ describe('ConsoleDeviceActionComponent', () => {
|
||||
providers: [
|
||||
{ provide: ElectronService, useValue: electronService },
|
||||
{ provide: ServerService, useValue: mockedServerService },
|
||||
{ provide: SettingsService, useValue: mockedSettingsService },
|
||||
{ provide: SettingsService },
|
||||
{ provide: ToasterService, useValue: mockedToaster },
|
||||
{ provide: NodeService, useValue: mockedNodeService },
|
||||
],
|
||||
imports: [MatIconModule],
|
||||
declarations: [ConsoleDeviceActionComponent],
|
||||
}).compileComponents();
|
||||
|
||||
settingsService = TestBed.inject(SettingsService);
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
@ -85,7 +85,7 @@ describe('ConsoleDeviceActionComponent', () => {
|
||||
component.nodes = nodes;
|
||||
component.server = server;
|
||||
|
||||
mockedSettingsService.set('console_command', 'command');
|
||||
settingsService.setConsoleSettings('command');
|
||||
spyOn(component, 'openConsole');
|
||||
});
|
||||
|
||||
@ -105,7 +105,7 @@ describe('ConsoleDeviceActionComponent', () => {
|
||||
});
|
||||
|
||||
it('should set command when it is not defined', async () => {
|
||||
mockedSettingsService.set('console_command', undefined);
|
||||
settingsService.setConsoleSettings(undefined);
|
||||
await component.console();
|
||||
expect(component.openConsole).toHaveBeenCalled();
|
||||
});
|
||||
|
@ -26,10 +26,10 @@ export class ConsoleDeviceActionComponent implements OnInit {
|
||||
ngOnInit() {}
|
||||
|
||||
async console() {
|
||||
let consoleCommand = this.settingsService.get<string>('console_command')
|
||||
? this.settingsService.get<string>('console_command')
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
<button mat-menu-item (click)="delete()">
|
||||
<button mat-menu-item (click)="confirmDelete()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
@ -24,7 +25,7 @@ describe('DeleteActionComponent', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [MatIconModule, MatMenuModule, NoopAnimationsModule],
|
||||
imports: [MatIconModule, MatMenuModule, NoopAnimationsModule, MatBottomSheetModule],
|
||||
providers: [
|
||||
{ provide: NodesDataSource, useClass: NodesDataSource },
|
||||
{ provide: DrawingsDataSource, useClass: DrawingsDataSource },
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { MatBottomSheet } from '@angular/material/bottom-sheet';
|
||||
import { ConfirmationBottomSheetComponent } from 'app/components/projects/confirmation-bottomsheet/confirmation-bottomsheet.component';
|
||||
import { DrawingsDataSource } from '../../../../../cartography/datasources/drawings-datasource';
|
||||
import { LinksDataSource } from '../../../../../cartography/datasources/links-datasource';
|
||||
import { NodesDataSource } from '../../../../../cartography/datasources/nodes-datasource';
|
||||
@ -26,11 +28,23 @@ export class DeleteActionComponent implements OnInit {
|
||||
private linksDataSource: LinksDataSource,
|
||||
private nodeService: NodeService,
|
||||
private drawingService: DrawingService,
|
||||
private linkService: LinkService
|
||||
private linkService: LinkService,
|
||||
private bottomSheet: MatBottomSheet
|
||||
) {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
confirmDelete() {
|
||||
this.bottomSheet.open(ConfirmationBottomSheetComponent);
|
||||
let bottomSheetRef = this.bottomSheet._openedBottomSheetRef;
|
||||
bottomSheetRef.instance.message = 'Do you want to delete all selected objects?';
|
||||
const bottomSheetSubscription = bottomSheetRef.afterDismissed().subscribe((result: boolean) => {
|
||||
if (result) {
|
||||
this.delete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
delete() {
|
||||
this.nodes.forEach((node) => {
|
||||
this.nodesDataSource.remove(node);
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { NodeConsoleService } from '../../../../../services/nodeConsole.service';
|
||||
import { Node } from '../../../../../cartography/models/node';
|
||||
import { Server } from '../../../../../models/server';
|
||||
import { ToasterService } from '../../../../../services/toaster.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-http-console-new-tab-action',
|
||||
@ -12,19 +11,11 @@ export class HttpConsoleNewTabActionComponent implements OnInit {
|
||||
@Input() server: Server;
|
||||
@Input() nodes: Node[];
|
||||
|
||||
constructor(private toasterService: ToasterService, private router: Router) {}
|
||||
constructor(private nodeConsoleService: NodeConsoleService) {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
openConsole() {
|
||||
this.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 {
|
||||
this.toasterService.error('To open console please start the node');
|
||||
}
|
||||
});
|
||||
this.nodeConsoleService.openConsolesForAllNodesInNewTabs(this.nodes);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Node } from '../../../../../cartography/models/node';
|
||||
import { Server } from '../../../../../models/server';
|
||||
import { MapSettingsService } from '../../../../../services/mapsettings.service';
|
||||
import { NodeConsoleService } from '../../../../../services/nodeConsole.service';
|
||||
import { ToasterService } from '../../../../../services/toaster.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-http-console-action',
|
||||
@ -13,22 +11,11 @@ export class HttpConsoleActionComponent implements OnInit {
|
||||
@Input() server: Server;
|
||||
@Input() nodes: Node[];
|
||||
|
||||
constructor(
|
||||
private consoleService: NodeConsoleService,
|
||||
private toasterService: ToasterService,
|
||||
private mapSettingsService: MapSettingsService
|
||||
) {}
|
||||
constructor(private nodeConsoleService: NodeConsoleService) {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
openConsole() {
|
||||
this.nodes.forEach((n) => {
|
||||
if (n.status === 'started') {
|
||||
this.mapSettingsService.logConsoleSubject.next(true);
|
||||
this.consoleService.openConsoleForNode(n);
|
||||
} else {
|
||||
this.toasterService.error('To open console please start the node');
|
||||
}
|
||||
});
|
||||
this.nodeConsoleService.openConsolesForAllNodesInWidget(this.nodes);
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,12 @@
|
||||
<app-stop-node-action *ngIf="nodes.length" [server]="server" [nodes]="nodes"></app-stop-node-action>
|
||||
<app-reload-node-action *ngIf="nodes.length" [server]="server" [nodes]="nodes"></app-reload-node-action>
|
||||
<app-http-console-action
|
||||
*ngIf="!projectService.isReadOnly(project) && nodes.length === 1"
|
||||
*ngIf="!projectService.isReadOnly(project) && nodes.length > 0"
|
||||
[server]="server"
|
||||
[nodes]="nodes"
|
||||
></app-http-console-action>
|
||||
<app-http-console-new-tab-action
|
||||
*ngIf="!projectService.isReadOnly(project) && nodes.length === 1"
|
||||
*ngIf="!projectService.isReadOnly(project) && nodes.length > 0"
|
||||
[server]="server"
|
||||
[nodes]="nodes"
|
||||
></app-http-console-new-tab-action>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<div class="modal-form-container">
|
||||
<form [formGroup]="formGroup">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-form-field class="form-field" *ngIf="element.fill !== undefined">
|
||||
<input
|
||||
matInput
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
@ -23,7 +23,13 @@
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<input matInput formControlName="borderWidth" placeholder="Border width" type="number" />
|
||||
<input
|
||||
matInput formControlName="borderWidth"
|
||||
placeholder="Border width"
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field" *ngIf="element.stroke_dasharray">
|
||||
@ -36,6 +42,41 @@
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field" *ngIf="element.width !== undefined">
|
||||
<input
|
||||
matInput
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
placeholder="Width"
|
||||
min="0"
|
||||
type="number"
|
||||
[(ngModel)]="element.width"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field" *ngIf="element.height !== undefined">
|
||||
<input
|
||||
matInput
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
placeholder="Height"
|
||||
min="0"
|
||||
type="number"
|
||||
[(ngModel)]="element.height"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
|
||||
<mat-form-field class="form-field" *ngIf="element.rx !== undefined">
|
||||
<input
|
||||
matInput
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
placeholder="Corner radius"
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
[(ngModel)]="element.rx"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<input matInput formControlName="rotation" placeholder="Rotation" type="number" />
|
||||
</mat-form-field>
|
||||
|
@ -49,6 +49,8 @@ export class StyleEditorDialogComponent implements OnInit {
|
||||
|
||||
if (this.drawing.element instanceof RectElement || this.drawing.element instanceof EllipseElement) {
|
||||
this.element.fill = this.drawing.element.fill;
|
||||
this.element.width = this.drawing.element.width;
|
||||
this.element.height = this.drawing.element.height;
|
||||
this.element.stroke = this.drawing.element.stroke;
|
||||
this.element.stroke_dasharray = this.drawing.element.stroke_dasharray;
|
||||
this.element.stroke_width = this.drawing.element.stroke_width;
|
||||
@ -58,6 +60,11 @@ export class StyleEditorDialogComponent implements OnInit {
|
||||
this.element.stroke_width = this.drawing.element.stroke_width;
|
||||
}
|
||||
|
||||
if (this.drawing.element instanceof RectElement) {
|
||||
this.element.rx = this.drawing.element.rx;
|
||||
this.element.ry = this.drawing.element.ry;
|
||||
}
|
||||
|
||||
if (this.element.stroke_width === undefined) this.element.stroke_width = 0;
|
||||
this.formGroup.controls['borderWidth'].setValue(this.element.stroke_width);
|
||||
this.formGroup.controls['rotation'].setValue(this.drawing.rotation);
|
||||
@ -74,6 +81,8 @@ export class StyleEditorDialogComponent implements OnInit {
|
||||
|
||||
if (this.drawing.element instanceof RectElement || this.drawing.element instanceof EllipseElement) {
|
||||
this.drawing.element.fill = this.element.fill;
|
||||
this.drawing.element.width = this.element.width;
|
||||
this.drawing.element.height = this.element.height;
|
||||
this.drawing.element.stroke = this.element.stroke;
|
||||
this.drawing.element.stroke_dasharray = this.element.stroke_dasharray;
|
||||
this.drawing.element.stroke_width = this.element.stroke_width;
|
||||
@ -83,6 +92,11 @@ export class StyleEditorDialogComponent implements OnInit {
|
||||
this.drawing.element.stroke_width = this.element.stroke_width;
|
||||
}
|
||||
|
||||
if (this.drawing.element instanceof RectElement) {
|
||||
this.drawing.element.rx = this.element.rx;
|
||||
this.drawing.element.ry = this.element.rx; // set ry with rx because we don't have ry in the form
|
||||
}
|
||||
|
||||
let mapDrawing = this.drawingToMapDrawingConverter.convert(this.drawing);
|
||||
mapDrawing.element = this.drawing.element;
|
||||
|
||||
@ -100,7 +114,11 @@ export class StyleEditorDialogComponent implements OnInit {
|
||||
|
||||
export class ElementData {
|
||||
fill: string;
|
||||
width: number;
|
||||
height: number;
|
||||
stroke: string;
|
||||
stroke_width: number;
|
||||
stroke_dasharray: string;
|
||||
rx: number;
|
||||
ry: number;
|
||||
}
|
||||
|
@ -91,6 +91,7 @@ export class ImportApplianceComponent implements OnInit {
|
||||
template.console_auto_start = appliance.iou.console_auto_start;
|
||||
template.ethernet_adapters = appliance.iou.ethernet_adapters;
|
||||
template.l1_keepalives = appliance.iou.l1_keepalives;
|
||||
template.use_default_iou_values = appliance.iou.use_default_iou_values;
|
||||
template.nvram = appliance.iou.nvram;
|
||||
template.ram = appliance.iou.ram;
|
||||
template.serial_adapters = appliance.iou.serial_adapters;
|
||||
|
@ -4,6 +4,9 @@ import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { ToasterService } from '../../../services/toaster.service';
|
||||
import { ProtocolHandlerService } from '../../../services/protocol-handler.service';
|
||||
import { of } from 'rxjs';
|
||||
import { NodesDataSource } from '../../../cartography/datasources/nodes-datasource';
|
||||
import { ProjectWebServiceHandler, WebServiceMessage } from '../../../handlers/project-web-service-handler';
|
||||
@ -14,6 +17,8 @@ import { NodeConsoleService } from '../../../services/nodeConsole.service';
|
||||
import { MockedNodesDataSource, MockedNodeService } from '../project-map.component.spec';
|
||||
import { LogConsoleComponent } from './log-console.component';
|
||||
import { LogEventsDataSource } from './log-events-datasource';
|
||||
import { MapSettingsService } from '../../../services/mapsettings.service';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
|
||||
export class MockedProjectWebServiceHandler {
|
||||
public nodeNotificationEmitter = new EventEmitter<WebServiceMessage>();
|
||||
@ -31,23 +36,35 @@ describe('LogConsoleComponent', () => {
|
||||
let mockedNodeService: MockedNodeService = new MockedNodeService();
|
||||
let mockedNodesDataSource: MockedNodesDataSource = new MockedNodesDataSource();
|
||||
let mockedProjectWebServiceHandler: MockedProjectWebServiceHandler = new MockedProjectWebServiceHandler();
|
||||
let nodeConsoleService: NodeConsoleService;
|
||||
let mapSettingsService: MapSettingsService;
|
||||
let toasterService: ToasterService;
|
||||
let protocolHandlerService: ProtocolHandlerService;
|
||||
|
||||
let httpServer = new HttpServer({} as HttpClient, {} as ServerErrorHandler);
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [HttpClientTestingModule, MatMenuModule, BrowserModule],
|
||||
imports: [HttpClientTestingModule, RouterTestingModule, MatMenuModule, BrowserModule, MatSnackBarModule],
|
||||
providers: [
|
||||
{ provide: ProjectWebServiceHandler, useValue: mockedProjectWebServiceHandler },
|
||||
{ provide: NodeService, useValue: mockedNodeService },
|
||||
{ provide: NodesDataSource, useValue: mockedNodesDataSource },
|
||||
{ provide: LogEventsDataSource, useClass: LogEventsDataSource },
|
||||
{ provide: HttpServer, useValue: httpServer },
|
||||
{ provide: NodeConsoleService },
|
||||
NodeConsoleService,
|
||||
ToasterService,
|
||||
ProtocolHandlerService,
|
||||
MapSettingsService
|
||||
],
|
||||
declarations: [LogConsoleComponent],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
}).compileComponents();
|
||||
|
||||
toasterService = TestBed.inject(ToasterService);
|
||||
protocolHandlerService = TestBed.inject(ProtocolHandlerService);
|
||||
mapSettingsService = TestBed.inject(MapSettingsService);
|
||||
nodeConsoleService = TestBed.inject(NodeConsoleService);
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -23,9 +23,11 @@ import { Server } from '../../../models/server';
|
||||
import { HttpServer } from '../../../services/http-server.service';
|
||||
import { NodeService } from '../../../services/node.service';
|
||||
import { NodeConsoleService } from '../../../services/nodeConsole.service';
|
||||
import { ProtocolHandlerService } from '../../../services/protocol-handler.service';
|
||||
import { ThemeService } from '../../../services/theme.service';
|
||||
import { version } from '../../../version';
|
||||
import { LogEventsDataSource } from './log-events-datasource';
|
||||
import * as ipaddr from 'ipaddr.js';
|
||||
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
@ -69,6 +71,7 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
private projectWebServiceHandler: ProjectWebServiceHandler,
|
||||
private nodeService: NodeService,
|
||||
private nodesDataSource: NodesDataSource,
|
||||
private protocolHandlerService: ProtocolHandlerService,
|
||||
private logEventsDataSource: LogEventsDataSource,
|
||||
private httpService: HttpServer,
|
||||
private themeService: ThemeService,
|
||||
@ -224,20 +227,26 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
} else if (this.regexConsole.test(this.command)) {
|
||||
if (node.status === 'started') {
|
||||
this.showCommand(`Launching console for node ${splittedCommand[1]}...`);
|
||||
var host = node.console_host;
|
||||
if (ipaddr.IPv6.isValid(host)) {
|
||||
host = `[${host}]`;
|
||||
}
|
||||
if (node.console_type === 'telnet') {
|
||||
location.assign(
|
||||
`gns3+telnet://${node.console_host}:${node.console}?name=${node.name}&project_id=${node.project_id}&node_id=${node.node_id}`
|
||||
this.protocolHandlerService.open(
|
||||
`gns3+telnet://${host}:${node.console}?name=${node.name}&project_id=${node.project_id}&node_id=${node.node_id}`
|
||||
);
|
||||
} else if (node.console_type === 'vnc') {
|
||||
location.assign(
|
||||
`gns3+vnc://${node.console_host}:${node.console}?name=${node.name}&project_id=${node.project_id}&node_id=${node.node_id}`
|
||||
this.protocolHandlerService.open(
|
||||
`gns3+vnc://${host}:${node.console}?name=${node.name}&project_id=${node.project_id}&node_id=${node.node_id}`
|
||||
);
|
||||
} else if (node.console_type === 'spice') {
|
||||
location.assign(
|
||||
`gns3+spice://${node.console_host}:${node.console}?name=${node.name}&project_id=${node.project_id}&node_id=${node.node_id}`
|
||||
} else if (node.console_type.startsWith('spice')) {
|
||||
this.protocolHandlerService.open(
|
||||
`gns3+spice://${host}:${node.console}?name=${node.name}&project_id=${node.project_id}&node_id=${node.node_id}`
|
||||
);
|
||||
} else if (node.console_type.startsWith('http')) {
|
||||
window.open(`${node.console_type}://${host}:${node.console}`);
|
||||
} 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 +306,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 +337,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}`;
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +77,7 @@
|
||||
</ng-container>
|
||||
|
||||
<!-- <ng-container matColumnDef="expandedDetail">
|
||||
<mat-cell *matCellDef="let detail">
|
||||
<mat-cell *matCellDef="let detail">
|
||||
The symbol for {{detail.element}}
|
||||
</mat-cell>
|
||||
</ng-container> -->
|
||||
@ -85,14 +85,14 @@
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
||||
|
||||
<!-- <mat-row
|
||||
<!-- <mat-row
|
||||
*matRowDef="let row; columns: displayedColumns;"
|
||||
matRipple
|
||||
class="element-row"
|
||||
matRipple
|
||||
class="element-row"
|
||||
[class.expanded]="expandedElement == row"
|
||||
(click)="expandedElement = row">
|
||||
</mat-row>
|
||||
<mat-row
|
||||
<mat-row
|
||||
*matRowDef="let row; columns: ['expandedDetail']; when: isExpansionDetailRow"
|
||||
[@detailExpand]="row.element == expandedElement ? 'expanded' : 'collapsed'"
|
||||
style="overflow: hidden">
|
||||
@ -196,6 +196,38 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-item-inside" *ngIf="version.images.bios_image">
|
||||
<span>
|
||||
{{ version.images.bios_image }}
|
||||
</span>
|
||||
|
||||
<div>
|
||||
<span *ngIf="checkImageFromVersion(version.images.bios_image)"
|
||||
><mat-icon matTooltip="Ready to install" matTooltipClass="custom-tooltip">check</mat-icon></span
|
||||
>
|
||||
<span *ngIf="!checkImageFromVersion(version.images.bios_image)"
|
||||
><mat-icon matTooltip="Missing" matTooltipClass="custom-tooltip">close</mat-icon></span
|
||||
>
|
||||
|
||||
<input
|
||||
type="file"
|
||||
class="non-visible"
|
||||
#fileBios
|
||||
(change)="importImage($event, version.images.bios_image)"
|
||||
ng2FileSelect
|
||||
[uploader]="uploaderImage"
|
||||
/>
|
||||
<button class="button" mat-raised-button (click)="fileBios.click()">Import</button>
|
||||
<button
|
||||
class="button"
|
||||
mat-raised-button
|
||||
(click)="downloadImageFromVersion(version.images.bios_image)"
|
||||
>
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-item-inside" *ngIf="version.images.hda_disk_image">
|
||||
<span>
|
||||
{{ version.images.hda_disk_image }}
|
||||
@ -259,6 +291,102 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-item-inside" *ngIf="version.images.hdc_disk_image">
|
||||
<span>
|
||||
{{ version.images.hdb_disk_image }}
|
||||
</span>
|
||||
|
||||
<div>
|
||||
<span *ngIf="checkImageFromVersion(version.images.hdc_disk_image)"
|
||||
><mat-icon matTooltip="Ready to install" matTooltipClass="custom-tooltip">check</mat-icon></span
|
||||
>
|
||||
<span *ngIf="!checkImageFromVersion(version.images.hdc_disk_image)"
|
||||
><mat-icon matTooltip="Missing" matTooltipClass="custom-tooltip">close</mat-icon></span
|
||||
>
|
||||
|
||||
<input
|
||||
type="file"
|
||||
class="non-visible"
|
||||
#file4
|
||||
(change)="importImage($event, version.images.hdc_disk_image)"
|
||||
ng2FileSelect
|
||||
[uploader]="uploaderImage"
|
||||
/>
|
||||
<button class="button" mat-raised-button (click)="file4.click()">Import</button>
|
||||
<button
|
||||
class="button"
|
||||
mat-raised-button
|
||||
(click)="downloadImageFromVersion(version.images.hdc_disk_image)"
|
||||
>
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-item-inside" *ngIf="version.images.hdd_disk_image">
|
||||
<span>
|
||||
{{ version.images.hdd_disk_image }}
|
||||
</span>
|
||||
|
||||
<div>
|
||||
<span *ngIf="checkImageFromVersion(version.images.hdd_disk_image)"
|
||||
><mat-icon matTooltip="Ready to install" matTooltipClass="custom-tooltip">check</mat-icon></span
|
||||
>
|
||||
<span *ngIf="!checkImageFromVersion(version.images.hdd_disk_image)"
|
||||
><mat-icon matTooltip="Missing" matTooltipClass="custom-tooltip">close</mat-icon></span
|
||||
>
|
||||
|
||||
<input
|
||||
type="file"
|
||||
class="non-visible"
|
||||
#file5
|
||||
(change)="importImage($event, version.images.hdd_disk_image)"
|
||||
ng2FileSelect
|
||||
[uploader]="uploaderImage"
|
||||
/>
|
||||
<button class="button" mat-raised-button (click)="file5.click()">Import</button>
|
||||
<button
|
||||
class="button"
|
||||
mat-raised-button
|
||||
(click)="downloadImageFromVersion(version.images.hdd_disk_image)"
|
||||
>
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-item-inside" *ngIf="version.images.cdrom_image">
|
||||
<span>
|
||||
{{ version.images.cdrom_image}}
|
||||
</span>
|
||||
|
||||
<div>
|
||||
<span *ngIf="checkImageFromVersion(version.images.cdrom_image)"
|
||||
><mat-icon matTooltip="Ready to install" matTooltipClass="custom-tooltip">check</mat-icon></span
|
||||
>
|
||||
<span *ngIf="!checkImageFromVersion(version.images.cdrom_image)"
|
||||
><mat-icon matTooltip="Missing" matTooltipClass="custom-tooltip">close</mat-icon></span
|
||||
>
|
||||
|
||||
<input
|
||||
type="file"
|
||||
class="non-visible"
|
||||
#file6
|
||||
(change)="importImage($event, version.images.cdrom_image)"
|
||||
ng2FileSelect
|
||||
[uploader]="uploaderImage"
|
||||
/>
|
||||
<button class="button" mat-raised-button (click)="file6.click()">Import</button>
|
||||
<button
|
||||
class="button"
|
||||
mat-raised-button
|
||||
(click)="downloadImageFromVersion(version.images.cdrom_image)"
|
||||
>
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -320,17 +320,17 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
setTimeout(() => {
|
||||
if (this.qemuBinaries.length) {
|
||||
if (this.applianceToInstall.qemu.arch === 'x86_64') {
|
||||
let filtered_binaries = this.qemuBinaries.filter((n) => n.path.includes('qemu-system-x86_64'));
|
||||
let filtered_binaries = this.qemuBinaries.filter((n) => n.path.endsWith('qemu-system-x86_64'));
|
||||
if (filtered_binaries.length) {
|
||||
this.selectedBinary = filtered_binaries[0];
|
||||
}
|
||||
} else if (this.applianceToInstall.qemu.arch === 'i386') {
|
||||
let filtered_binaries = this.qemuBinaries.filter((n) => n.path.includes('qemu-system-i386'));
|
||||
let filtered_binaries = this.qemuBinaries.filter((n) => n.path.endsWith('qemu-system-i386'));
|
||||
if (filtered_binaries.length) {
|
||||
this.selectedBinary = filtered_binaries[0];
|
||||
}
|
||||
} else if (this.applianceToInstall.qemu.arch === 'x86_64') {
|
||||
let filtered_binaries = this.qemuBinaries.filter((n) => n.path.includes('qemu-system-arm'));
|
||||
} else if (this.applianceToInstall.qemu.arch === 'arm') {
|
||||
let filtered_binaries = this.qemuBinaries.filter((n) => n.path.endsWith('qemu-system-arm'));
|
||||
if (filtered_binaries.length) {
|
||||
this.selectedBinary = filtered_binaries[0];
|
||||
}
|
||||
@ -364,7 +364,7 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
autoFocus: false,
|
||||
disableClose: true,
|
||||
});
|
||||
dialogRef.componentInstance.confirmationMessage = `This is not the correct file.
|
||||
dialogRef.componentInstance.confirmationMessage = `This is not the correct file.
|
||||
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) {
|
||||
@ -401,7 +401,8 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
this.progressService.activate();
|
||||
};
|
||||
|
||||
fileReader.readAsText(file);
|
||||
//fileReader.readAsText(file); //web browser out ouf memory when upload large image file
|
||||
fileReader.onloadend(undefined);
|
||||
}
|
||||
|
||||
checkImageFromVersion(image: string): boolean {
|
||||
@ -485,7 +486,7 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
iouTemplate.startup_config = this.applianceToInstall.iou.startup_config;
|
||||
iouTemplate.builtin = this.applianceToInstall.builtin;
|
||||
iouTemplate.category = this.getCategory();
|
||||
iouTemplate.default_name_format = this.applianceToInstall.port_name_format;
|
||||
iouTemplate.default_name_format = this.applianceToInstall.default_name_format;
|
||||
iouTemplate.symbol = this.applianceToInstall.symbol;
|
||||
iouTemplate.compute_id = this.isGns3VmChosen ? 'vm' : 'local';
|
||||
iouTemplate.template_id = uuid();
|
||||
@ -534,7 +535,7 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
iosTemplate.slot7 = this.applianceToInstall.dynamips.slot7;
|
||||
iosTemplate.builtin = this.applianceToInstall.builtin;
|
||||
iosTemplate.category = this.getCategory();
|
||||
iosTemplate.default_name_format = this.applianceToInstall.port_name_format;
|
||||
iosTemplate.default_name_format = this.applianceToInstall.default_name_format;
|
||||
iosTemplate.symbol = this.applianceToInstall.symbol;
|
||||
iosTemplate.compute_id = this.isGns3VmChosen ? 'vm' : 'local';
|
||||
iosTemplate.template_id = uuid();
|
||||
@ -572,7 +573,7 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
dockerTemplate.console_type = this.applianceToInstall.docker.console_type;
|
||||
dockerTemplate.builtin = this.applianceToInstall.builtin;
|
||||
dockerTemplate.category = this.getCategory();
|
||||
dockerTemplate.default_name_format = this.applianceToInstall.port_name_format;
|
||||
dockerTemplate.default_name_format = this.applianceToInstall.default_name_format;
|
||||
dockerTemplate.symbol = this.applianceToInstall.symbol;
|
||||
dockerTemplate.compute_id = this.isGns3VmChosen ? 'vm' : 'local';
|
||||
dockerTemplate.template_id = uuid();
|
||||
@ -629,12 +630,17 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
qemuTemplate.category = this.getCategory();
|
||||
qemuTemplate.first_port_name = this.applianceToInstall.first_port_name;
|
||||
qemuTemplate.port_name_format = this.applianceToInstall.port_name_format;
|
||||
qemuTemplate.port_segment_size = this.applianceToInstall.port_segment_size;
|
||||
qemuTemplate.default_name_format = this.applianceToInstall.default_name_format
|
||||
qemuTemplate.symbol = this.applianceToInstall.symbol;
|
||||
qemuTemplate.qemu_path = this.selectedBinary.path;
|
||||
qemuTemplate.compute_id = this.isGns3VmChosen ? 'vm' : 'local';
|
||||
qemuTemplate.template_id = uuid();
|
||||
qemuTemplate.hda_disk_image = version.images.hda_disk_image;
|
||||
qemuTemplate.hdb_disk_image = version.images.hdb_disk_image;
|
||||
qemuTemplate.hdc_disk_image = version.images.hdc_disk_image;
|
||||
qemuTemplate.hdd_disk_image = version.images.hdd_disk_image;
|
||||
qemuTemplate.cdrom_image = version.images.cdrom_image;
|
||||
qemuTemplate.template_type = 'qemu';
|
||||
qemuTemplate.usage = this.applianceToInstall.usage;
|
||||
|
||||
|
@ -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>,
|
||||
@ -39,7 +40,7 @@ export class ConfiguratorDialogDockerComponent implements OnInit {
|
||||
this.generalSettingsForm = this.formBuilder.group({
|
||||
name: new FormControl('', Validators.required),
|
||||
adapter: new FormControl('', Validators.required),
|
||||
startCommand: new FormControl('', Validators.required),
|
||||
startCommand: new FormControl(''),
|
||||
consoleHttpPort: new FormControl('', Validators.required),
|
||||
consoleHttpPath: new FormControl('', Validators.required),
|
||||
});
|
||||
@ -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();
|
||||
|
@ -20,13 +20,14 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-checkbox [(ngModel)]="node.console_auto_start"> Auto start console </mat-checkbox><br />
|
||||
<mat-form-field class="form-field">
|
||||
<mat-checkbox [(ngModel)]="node.properties.use_default_iou_values"> Use default IOU values for memories </mat-checkbox>
|
||||
<mat-form-field class="form-field" *ngIf="!node.properties.use_default_iou_values">
|
||||
<input matInput type="number" [(ngModel)]="node.properties.ram" placeholder="RAM size" />
|
||||
<span matSuffix>MB</span>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="form-field">
|
||||
<mat-form-field class="form-field" *ngIf="!node.properties.use_default_iou_values">
|
||||
<input matInput type="number" [(ngModel)]="node.properties.nvram" placeholder="NVRAM size" />
|
||||
<span matSuffix>MB</span>
|
||||
<span matSuffix>KB</span>
|
||||
</mat-form-field>
|
||||
</mat-tab>
|
||||
|
||||
@ -38,7 +39,7 @@
|
||||
matInput
|
||||
formControlName="ethernetAdapters"
|
||||
type="number"
|
||||
[(ngModel)]="node.ethernet_adapters"
|
||||
[(ngModel)]="node.properties.ethernet_adapters"
|
||||
placeholder="Ethernet adapters"
|
||||
/>
|
||||
</mat-form-field>
|
||||
@ -47,7 +48,7 @@
|
||||
matInput
|
||||
formControlName="serialAdapters"
|
||||
type="number"
|
||||
[(ngModel)]="node.serial_adapters"
|
||||
[(ngModel)]="node.properties.serial_adapters"
|
||||
placeholder="Serial adapters"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
@ -238,6 +238,12 @@
|
||||
<mat-form-field class="form-field">
|
||||
<input matInput type="text" [(ngModel)]="node.properties.options" placeholder="Options" />
|
||||
</mat-form-field>
|
||||
<br /><mat-checkbox [(ngModel)]="node.properties.tpm">
|
||||
Enable the Trusted Platform Module (TPM)
|
||||
</mat-checkbox>
|
||||
<br /><mat-checkbox [(ngModel)]="node.properties.uefi">
|
||||
Enable the UEFI boot mode
|
||||
</mat-checkbox>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</mat-tab>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { MapSettingsService } from '../../../services/mapsettings.service';
|
||||
import { ElectronService } from 'ngx-electron';
|
||||
import { NodesDataSource } from '../../../cartography/datasources/nodes-datasource';
|
||||
import { Project } from '../../../models/project';
|
||||
@ -7,6 +8,7 @@ import { NodeService } from '../../../services/node.service';
|
||||
import { ServerService } from '../../../services/server.service';
|
||||
import { SettingsService } from '../../../services/settings.service';
|
||||
import { ToasterService } from '../../../services/toaster.service';
|
||||
import { NodeConsoleService } from '../../../services/nodeConsole.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-nodes-menu',
|
||||
@ -20,17 +22,19 @@ export class NodesMenuComponent {
|
||||
|
||||
constructor(
|
||||
private nodeService: NodeService,
|
||||
private nodeConsoleService: NodeConsoleService,
|
||||
private nodesDataSource: NodesDataSource,
|
||||
private toasterService: ToasterService,
|
||||
private serverService: ServerService,
|
||||
private settingsService: SettingsService,
|
||||
private mapSettingsService: MapSettingsService,
|
||||
private electronService: ElectronService
|
||||
) {}
|
||||
|
||||
async startConsoleForAllNodes() {
|
||||
if (this.electronService.isElectronApp) {
|
||||
let consoleCommand = this.settingsService.get<string>('console_command')
|
||||
? this.settingsService.get<string>('console_command')
|
||||
let consoleCommand = this.settingsService.getConsoleSettings()
|
||||
? this.settingsService.getConsoleSettings()
|
||||
: this.nodeService.getDefaultCommand();
|
||||
|
||||
let nodes = this.nodesDataSource.getItems();
|
||||
@ -48,7 +52,11 @@ export class NodesMenuComponent {
|
||||
await this.electronService.remote.require('./console-executor.js').openConsole(request);
|
||||
}
|
||||
} else {
|
||||
this.toasterService.error('Option to start all nodes not available in web browser.');
|
||||
if (this.mapSettingsService.openConsolesInWidget) {
|
||||
this.nodeConsoleService.openConsolesForAllNodesInWidget(this.nodesDataSource.getItems());
|
||||
} else {
|
||||
this.nodeConsoleService.openConsolesForAllNodesInNewTabs(this.nodesDataSource.getItems());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ import { ToasterService } from '../../../../services/toaster.service';
|
||||
import { MockedToasterService } from '../../../../services/toaster.service.spec';
|
||||
import { MockedLinkService, MockedNodesDataSource } from '../../project-map.component.spec';
|
||||
import { StartCaptureDialogComponent } from './start-capture.component';
|
||||
import { ProtocolHandlerService } from '../../../../services/protocol-handler.service';
|
||||
|
||||
describe('StartCaptureDialogComponent', () => {
|
||||
let component: StartCaptureDialogComponent;
|
||||
@ -25,6 +26,8 @@ describe('StartCaptureDialogComponent', () => {
|
||||
let mockedToasterService = new MockedToasterService();
|
||||
let mockedLinkService = new MockedLinkService();
|
||||
let mockedNodesDataSource = new MockedNodesDataSource();
|
||||
let protocolHandlerService: ProtocolHandlerService;
|
||||
|
||||
let dialogRef = {
|
||||
close: jasmine.createSpy('close'),
|
||||
};
|
||||
@ -49,10 +52,13 @@ describe('StartCaptureDialogComponent', () => {
|
||||
{ provide: LinkService, useValue: mockedLinkService },
|
||||
{ provide: NodesDataSource, useValue: mockedNodesDataSource },
|
||||
{ provide: PacketCaptureService },
|
||||
ProtocolHandlerService,
|
||||
],
|
||||
declarations: [StartCaptureDialogComponent],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
}).compileComponents();
|
||||
|
||||
protocolHandlerService = TestBed.inject(ProtocolHandlerService);
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -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'), '')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -104,6 +104,13 @@ export class ProjectMapMenuComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public addDrawing(selectedObject: string) {
|
||||
if ((selectedObject === 'rectangle' && this.drawTools.isRectangleChosen) || (selectedObject === 'ellipse' && this.drawTools.isEllipseChosen) ||
|
||||
(selectedObject === 'line' && this.drawTools.isLineChosen) || (selectedObject === 'text' && this.drawTools.isTextChosen)) {
|
||||
document.documentElement.style.cursor = "default";
|
||||
} else {
|
||||
document.documentElement.style.cursor = "crosshair";
|
||||
}
|
||||
|
||||
switch (selectedObject) {
|
||||
case 'rectangle':
|
||||
this.drawTools.isTextChosen = false;
|
||||
@ -140,6 +147,8 @@ export class ProjectMapMenuComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public resetDrawToolChoice() {
|
||||
document.documentElement.style.cursor = "default";
|
||||
|
||||
this.drawTools.isRectangleChosen = false;
|
||||
this.drawTools.isEllipseChosen = false;
|
||||
this.drawTools.isLineChosen = false;
|
||||
|
@ -64,10 +64,17 @@
|
||||
<div class="menu-button-group">
|
||||
<app-nodes-menu [server]="server" [project]="project"></app-nodes-menu>
|
||||
<app-context-menu [project]="project" [server]="server"></app-context-menu>
|
||||
<app-context-console-menu [project]="project" [server]="server"></app-context-console-menu>
|
||||
</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"
|
||||
@ -84,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 -->
|
||||
@ -255,12 +250,6 @@
|
||||
(closeConsole)="toggleShowConsole($event)"
|
||||
></app-console-wrapper>
|
||||
</div>
|
||||
<div [ngClass]="{ visible: !isTopologySummaryVisible }">
|
||||
<app-topology-summary
|
||||
*ngIf="project"
|
||||
[server]="server"
|
||||
[project]="project"
|
||||
(closeTopologySummary)="toggleShowTopologySummary($event)"
|
||||
></app-topology-summary>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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);
|
||||
|
||||
|
@ -59,7 +59,6 @@ import { RecentlyOpenedProjectService } from '../../services/recentlyOpenedProje
|
||||
import { ServerService } from '../../services/server.service';
|
||||
import { MockedServerService } from '../../services/server.service.spec';
|
||||
import { SettingsService } from '../../services/settings.service';
|
||||
import { MockedSettingsService } from '../../services/settings.service.spec';
|
||||
import { ToasterService } from '../../services/toaster.service';
|
||||
import { MockedToasterService } from '../../services/toaster.service.spec';
|
||||
import { ToolsService } from '../../services/tools.service';
|
||||
@ -300,7 +299,7 @@ xdescribe('ProjectMapComponent', () => {
|
||||
{ provide: NodesDataSource, useValue: nodesDataSource },
|
||||
{ provide: LinksDataSource, useValue: linksDataSource },
|
||||
{ provide: DrawingsDataSource, useValue: drawingsDataSource },
|
||||
{ provide: SettingsService, useClass: MockedSettingsService },
|
||||
{ provide: SettingsService },
|
||||
{ provide: ToolsService },
|
||||
{ provide: SelectionManager },
|
||||
{ provide: SelectionTool },
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, Injector, OnDestroy, OnInit, SimpleChange, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
|
||||
import { MatBottomSheet } from '@angular/material/bottom-sheet';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||
import * as Mousetrap from 'mousetrap';
|
||||
import { from, Observable, Subscription } from 'rxjs';
|
||||
import { map, mergeMap } from 'rxjs/operators';
|
||||
import { map, mergeMap, takeUntil } from 'rxjs/operators';
|
||||
import { D3MapComponent } from '../../cartography/components/d3-map/d3-map.component';
|
||||
import { MapDrawingToDrawingConverter } from '../../cartography/converters/map/map-drawing-to-drawing-converter';
|
||||
import { MapLabelToLabelConverter } from '../../cartography/converters/map/map-label-to-label-converter';
|
||||
@ -67,6 +67,7 @@ import { SymbolService } from '../../services/symbol.service';
|
||||
import { ThemeService } from '../../services/theme.service';
|
||||
import { ToasterService } from '../../services/toaster.service';
|
||||
import { ToolsService } from '../../services/tools.service';
|
||||
import { ProtocolHandlerService } from '../../services/protocol-handler.service';
|
||||
import { AddBlankProjectDialogComponent } from '../projects/add-blank-project-dialog/add-blank-project-dialog.component';
|
||||
import { ConfirmationBottomSheetComponent } from '../projects/confirmation-bottomsheet/confirmation-bottomsheet.component';
|
||||
import { EditProjectDialogComponent } from '../projects/edit-project-dialog/edit-project-dialog.component';
|
||||
@ -74,7 +75,7 @@ import { ImportProjectDialogComponent } from '../projects/import-project-dialog/
|
||||
import { NavigationDialogComponent } from '../projects/navigation-dialog/navigation-dialog.component';
|
||||
import { SaveProjectDialogComponent } from '../projects/save-project-dialog/save-project-dialog.component';
|
||||
import { NodeAddedEvent } from '../template/template-list-dialog/template-list-dialog.component';
|
||||
import { ContextConsoleMenuComponent } from './context-console-menu/context-console-menu.component';
|
||||
import { TopologySummaryComponent } from '../topology-summary/topology-summary.component';
|
||||
import { ContextMenuComponent } from './context-menu/context-menu.component';
|
||||
import { NodeCreatedLabelStylesFixer } from './helpers/node-created-label-styles-fixer';
|
||||
import { NewTemplateDialogComponent } from './new-template-dialog/new-template-dialog.component';
|
||||
@ -104,6 +105,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
public gridVisibility: boolean = false;
|
||||
public toolbarVisibility: boolean = true;
|
||||
public symbolScaling: boolean = true;
|
||||
private instance: ComponentRef<TopologySummaryComponent>;
|
||||
|
||||
tools = {
|
||||
selection: true,
|
||||
@ -120,9 +122,9 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
public isLightThemeEnabled: boolean = false;
|
||||
|
||||
@ViewChild(ContextMenuComponent) contextMenu: ContextMenuComponent;
|
||||
@ViewChild(ContextConsoleMenuComponent) consoleContextMenu: ContextConsoleMenuComponent;
|
||||
@ViewChild(D3MapComponent) mapChild: D3MapComponent;
|
||||
@ViewChild(ProjectMapMenuComponent) projectMapMenuComponent: ProjectMapMenuComponent;
|
||||
@ViewChild('topologySummaryContainer', {read: ViewContainerRef}) topologySummaryContainer: ViewContainerRef;
|
||||
|
||||
private projectMapSubscription: Subscription = new Subscription();
|
||||
|
||||
@ -172,7 +174,10 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
private title: Title,
|
||||
private nodeConsoleService: NodeConsoleService,
|
||||
private symbolService: SymbolService,
|
||||
private cd: ChangeDetectorRef
|
||||
private protocolHandlerService: ProtocolHandlerService,
|
||||
private cd: ChangeDetectorRef,
|
||||
private cfr: ComponentFactoryResolver,
|
||||
private injector: Injector
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@ -207,7 +212,6 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.settings = this.settingsService.getAll();
|
||||
this.symbolScaling = this.mapSettingsService.getSymbolScaling();
|
||||
this.isTopologySummaryVisible = this.mapSettingsService.isTopologySummaryVisible;
|
||||
this.isConsoleVisible = this.mapSettingsService.isLogConsoleVisible;
|
||||
this.mapSettingsService.logConsoleSubject.subscribe((value) => (this.isConsoleVisible = value));
|
||||
this.notificationsVisibility = localStorage.getItem('notificationsVisibility') === 'true' ? true : false;
|
||||
@ -215,6 +219,21 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
this.gridVisibility = localStorage.getItem('gridVisibility') === 'true' ? true : false;
|
||||
}
|
||||
|
||||
async lazyLoadTopologySummary() {
|
||||
if (this.isTopologySummaryVisible) {
|
||||
const {TopologySummaryComponent} = await import('../topology-summary/topology-summary.component');
|
||||
const componentFactory = this.cfr.resolveComponentFactory(TopologySummaryComponent);
|
||||
this.instance = this.topologySummaryContainer.createComponent(componentFactory, null, this.injector);
|
||||
this.instance.instance.server = this.server;
|
||||
this.instance.instance.project = this.project;
|
||||
} else if (this.instance) {
|
||||
if (this.instance.instance) {
|
||||
this.instance.instance.ngOnDestroy();
|
||||
this.instance.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addSubscriptions() {
|
||||
this.projectMapSubscription.add(
|
||||
this.mapSettingsService.mapRenderedEmitter.subscribe((value: boolean) => {
|
||||
@ -314,9 +333,9 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
this.title.setTitle(this.project.name);
|
||||
|
||||
this.isInterfaceLabelVisible = this.mapSettingsService.showInterfaceLabels;
|
||||
this.toggleShowTopologySummary(this.mapSettingsService.isTopologySummaryVisible);
|
||||
|
||||
this.recentlyOpenedProjectService.setServerId(this.server.id.toString());
|
||||
this.recentlyOpenedProjectService.setProjectId(this.project.project_id);
|
||||
|
||||
if (this.project.status === 'opened') {
|
||||
return new Observable<Project>((observer) => {
|
||||
@ -385,21 +404,42 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
|
||||
Mousetrap.bind('del', (event: Event) => {
|
||||
event.preventDefault();
|
||||
const selected = this.selectionManager.getSelected();
|
||||
this.deleteItems();
|
||||
});
|
||||
}
|
||||
|
||||
selected
|
||||
.filter((item) => item instanceof MapNode)
|
||||
.forEach((item: MapNode) => {
|
||||
const node = this.mapNodeToNode.convert(item);
|
||||
this.nodeService.delete(this.server, node).subscribe((data) => {
|
||||
this.toasterService.success('Node has been deleted');
|
||||
deleteItems() {
|
||||
this.bottomSheet.open(ConfirmationBottomSheetComponent);
|
||||
let bottomSheetRef = this.bottomSheet._openedBottomSheetRef;
|
||||
bottomSheetRef.instance.message = 'Do you want to delete all selected objects?';
|
||||
const bottomSheetSubscription = bottomSheetRef.afterDismissed().subscribe((result: boolean) => {
|
||||
if (result) {
|
||||
const selected = this.selectionManager.getSelected();
|
||||
|
||||
selected
|
||||
.filter((item) => item instanceof MapNode)
|
||||
.forEach((item: MapNode) => {
|
||||
const node = this.mapNodeToNode.convert(item);
|
||||
this.nodeService.delete(this.server, node).subscribe((data) => {
|
||||
this.toasterService.success('Node has been deleted');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
selected
|
||||
.filter((item) => item instanceof MapDrawing)
|
||||
.forEach((item: MapDrawing) => {
|
||||
const drawing = this.mapDrawingToDrawing.convert(item);
|
||||
this.drawingService.delete(this.server, drawing).subscribe((data) => {
|
||||
this.toasterService.success('Drawing has been deleted');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onProjectLoad(project: Project) {
|
||||
this.readonly = this.projectService.isReadOnly(project);
|
||||
this.recentlyOpenedProjectService.setProjectId(this.project.project_id);
|
||||
|
||||
const subscription = this.projectService
|
||||
.nodes(this.server, project.project_id)
|
||||
@ -473,7 +513,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
const onLabelContextMenu = this.labelWidget.onContextMenu.subscribe((eventLabel: LabelContextMenu) => {
|
||||
const label = this.mapLabelToLabel.convert(eventLabel.label);
|
||||
const node = this.nodes.find((n) => n.node_id === eventLabel.label.nodeId);
|
||||
this.contextMenu.openMenuForLabel(label, node, eventLabel.event.pageY, eventLabel.event.pageX);
|
||||
this.contextMenu.openMenuForLabel(label, node, eventLabel.event.screenY - 60, eventLabel.event.screenX);
|
||||
});
|
||||
|
||||
const onInterfaceLabelContextMenu = this.interfaceLabelWidget.onContextMenu.subscribe(
|
||||
@ -513,11 +553,6 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
this.contextMenu.openMenuForListOfElements(drawings, nodes, labels, links, event.pageY, event.pageX);
|
||||
});
|
||||
|
||||
const onContextConsoleMenu = this.nodeWidget.onContextConsoleMenu.subscribe((eventNode: NodeContextMenu) => {
|
||||
const node = this.mapNodeToNode.convert(eventNode.node);
|
||||
this.consoleContextMenu.openMenu(node, eventNode.event.pageY, eventNode.event.pageX);
|
||||
});
|
||||
|
||||
this.projectMapSubscription.add(onLinkContextMenu);
|
||||
this.projectMapSubscription.add(onEthernetLinkContextMenu);
|
||||
this.projectMapSubscription.add(onSerialLinkContextMenu);
|
||||
@ -526,7 +561,6 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
this.projectMapSubscription.add(onContextMenu);
|
||||
this.projectMapSubscription.add(onLabelContextMenu);
|
||||
this.projectMapSubscription.add(onInterfaceLabelContextMenu);
|
||||
this.projectMapSubscription.add(onContextConsoleMenu);
|
||||
this.mapChangeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
@ -535,6 +569,9 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
nodeAddedEvent.x = nodeAddedEvent.x / this.mapScaleService.getScale();
|
||||
nodeAddedEvent.y = nodeAddedEvent.y / this.mapScaleService.getScale();
|
||||
|
||||
this.progressService.activate();
|
||||
this.nodeService
|
||||
.createFromTemplate(
|
||||
@ -795,6 +832,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
public toggleShowTopologySummary(visible: boolean) {
|
||||
this.isTopologySummaryVisible = visible;
|
||||
this.mapSettingsService.toggleTopologySummary(this.isTopologySummaryVisible);
|
||||
this.lazyLoadTopologySummary();
|
||||
}
|
||||
|
||||
public toggleNotifications(visible: boolean) {
|
||||
@ -939,7 +977,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
) {
|
||||
this.toasterService.error('Project with running nodes cannot be exported.');
|
||||
} else {
|
||||
location.assign(this.projectService.getExportPath(this.server, this.project));
|
||||
this.protocolHandlerService.open(this.projectService.getExportPath(this.server, this.project));
|
||||
}
|
||||
}
|
||||
|
||||
@ -954,8 +992,8 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
|
||||
fileReader.onloadend = () => {
|
||||
let image = fileReader.result;
|
||||
let svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"
|
||||
height=\"${imageToUpload.height}\" width=\"${imageToUpload.width}\">\n<image height=\"${imageToUpload.height}\" width=\"${imageToUpload.width}\"
|
||||
let svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"
|
||||
height=\"${imageToUpload.height}\" width=\"${imageToUpload.width}\">\n<image height=\"${imageToUpload.height}\" width=\"${imageToUpload.width}\"
|
||||
xlink:href=\"${image}\"/>\n</svg>`;
|
||||
this.drawingService
|
||||
.add(this.server, this.project.project_id, -(imageToUpload.width / 2), -(imageToUpload.height / 2), svg)
|
||||
|
@ -71,7 +71,7 @@ export class WebConsoleComponent implements OnInit, AfterViewInit {
|
||||
|
||||
this.term.attachCustomKeyEventHandler((key: KeyboardEvent) => {
|
||||
if (key.code === 'KeyC' || key.code === 'KeyV') {
|
||||
if (key.ctrlKey) {
|
||||
if (key.ctrlKey && key.shiftKey) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ export class EditProjectDialogComponent implements OnInit {
|
||||
this.project.grid_size = this.formGroup.get('nodeGridSize').value;
|
||||
this.project.variables = this.variables;
|
||||
|
||||
this.project.auto_close = !this.project.auto_close;
|
||||
this.project.auto_close = !this.auto_close;
|
||||
|
||||
this.projectService.update(this.server, this.project).subscribe((project: Project) => {
|
||||
this.toasterService.success(`Project ${project.name} updated.`);
|
||||
|
@ -2,6 +2,7 @@
|
||||
<div class="default-header">
|
||||
<div class="row">
|
||||
<h1 class="col">Projects</h1>
|
||||
<button class="col" mat-raised-button (click)="goToSystemStatus()" class="add-button">Go to system status</button>
|
||||
<button class="col" mat-raised-button (click)="goToPreferences()" class="add-button">Go to preferences</button>
|
||||
<button class="col" mat-raised-button color="primary" (click)="addBlankProject()" class="add-button">
|
||||
Add blank project
|
||||
|
@ -1,10 +1,12 @@
|
||||
.import-button {
|
||||
height: 40px;
|
||||
width: 160px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
height: 40px;
|
||||
width: 160px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
@ -13,3 +15,7 @@
|
||||
margin-left: -470px;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import { MockedProjectService } from '../../services/project.service.spec';
|
||||
import { ServerService } from '../../services/server.service';
|
||||
import { MockedServerService } from '../../services/server.service.spec';
|
||||
import { Settings, SettingsService } from '../../services/settings.service';
|
||||
import { MockedSettingsService } from '../../services/settings.service.spec';
|
||||
import { ToasterService } from '../../services/toaster.service';
|
||||
import { ConfigureGns3VMDialogComponent } from '../servers/configure-gns3vm-dialog/configure-gns3vm-dialog.component';
|
||||
import { ChooseNameDialogComponent } from './choose-name-dialog/choose-name-dialog.component';
|
||||
@ -70,7 +69,7 @@ xdescribe('ProjectsComponent', () => {
|
||||
providers: [
|
||||
{ provide: ServerService, useClass: MockedServerService },
|
||||
{ provide: ProjectService, useValue: mockedProjectService },
|
||||
{ provide: SettingsService, useClass: MockedSettingsService },
|
||||
{ provide: SettingsService},
|
||||
{ provide: ToasterService },
|
||||
{ provide: ElectronService, useValue: electronService },
|
||||
ProgressService,
|
||||
@ -83,10 +82,10 @@ xdescribe('ProjectsComponent', () => {
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
serverService = TestBed.get(ServerService);
|
||||
settingsService = TestBed.get(SettingsService);
|
||||
projectService = TestBed.get(ProjectService);
|
||||
progressService = TestBed.get(ProgressService);
|
||||
serverService = TestBed.inject(ServerService);
|
||||
settingsService = TestBed.inject(SettingsService);
|
||||
projectService = TestBed.inject(ProjectService);
|
||||
progressService = TestBed.inject(ProgressService);
|
||||
|
||||
server = new Server();
|
||||
server.id = 99;
|
||||
|
@ -89,6 +89,12 @@ export class ProjectsComponent implements OnInit {
|
||||
.catch((error) => this.toasterService.error('Cannot navigate to the preferences'));
|
||||
}
|
||||
|
||||
goToSystemStatus() {
|
||||
this.router
|
||||
.navigate(['/server', this.server.id, 'systemstatus'])
|
||||
.catch((error) => this.toasterService.error('Cannot navigate to the system status'));
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.projectService.list(this.server).subscribe(
|
||||
(projects: Project[]) => {
|
||||
|
@ -49,7 +49,7 @@
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button (click)="onNoClick()" tabindex="-1" color="accent">No Thanks</button>
|
||||
<button mat-button (click)="onNoClick()" tabindex="-1" color="accent">Cancel</button>
|
||||
<button mat-button (click)="onAddClick()" tabindex="2" mat-raised-button color="primary">Add</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -11,20 +11,10 @@
|
||||
|
||||
<div>
|
||||
<mat-checkbox [(ngModel)]="settings.crash_reports">Send anonymous crash reports</mat-checkbox><br />
|
||||
<mat-checkbox [(ngModel)]="integrateLinksLabelsToLinks">Integrate link labels to links</mat-checkbox>
|
||||
<mat-checkbox [(ngModel)]="settings.anonymous_statistics">Send anonymous usage statistics</mat-checkbox><br />
|
||||
<mat-checkbox [(ngModel)]="integrateLinksLabelsToLinks">Integrate link labels to links</mat-checkbox><br />
|
||||
<mat-checkbox [(ngModel)]="openConsolesInWidget">Open consoles in the widget instead of in new tabs after clicking start consoles for all nodes</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<!-- <div>
|
||||
<mat-checkbox [(ngModel)]="settings.experimental_features"
|
||||
>Enable experimental features (WARNING: IT CAN BREAK YOU LABS!)</mat-checkbox
|
||||
>
|
||||
</div> -->
|
||||
|
||||
<!-- <div>
|
||||
<mat-checkbox [(ngModel)]="settings.angular_map"
|
||||
>Enable experimental Angular Map (WARNING: IT CAN BREAK YOU LABS!)</mat-checkbox
|
||||
>
|
||||
</div> -->
|
||||
</mat-expansion-panel>
|
||||
|
||||
<mat-expansion-panel [expanded]="false">
|
||||
|
@ -6,7 +6,6 @@ import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { PersistenceModule } from 'angular-persistence';
|
||||
import { MapSettingsService } from '../../services/mapsettings.service';
|
||||
import { SettingsService } from '../../services/settings.service';
|
||||
import { ConsoleService } from '../../services/settings/console.service';
|
||||
@ -23,6 +22,7 @@ describe('SettingsComponent', () => {
|
||||
let mapSettingsService = {
|
||||
integrateLinkLabelsToLinks: true,
|
||||
toggleIntegrateInterfaceLabels(val: boolean) {},
|
||||
toggleOpenConsolesInWidget(val: boolean) {}
|
||||
};
|
||||
let consoleService;
|
||||
let updatesService = autoSpy(UpdatesService);
|
||||
@ -37,7 +37,6 @@ describe('SettingsComponent', () => {
|
||||
MatExpansionModule,
|
||||
MatCheckboxModule,
|
||||
FormsModule,
|
||||
PersistenceModule,
|
||||
BrowserAnimationsModule,
|
||||
MatIconModule,
|
||||
MatFormFieldModule,
|
||||
@ -70,16 +69,25 @@ describe('SettingsComponent', () => {
|
||||
const settings = {
|
||||
crash_reports: true,
|
||||
experimental_features: true,
|
||||
anonymous_statistics: true,
|
||||
angular_map: false,
|
||||
console_command: '',
|
||||
};
|
||||
const getAll = spyOn(settingsService, 'getAll').and.returnValue(settings);
|
||||
const setAll = spyOn(settingsService, 'setAll');
|
||||
spyOn(mapSettingsService, 'toggleIntegrateInterfaceLabels');
|
||||
spyOn(mapSettingsService, 'toggleOpenConsolesInWidget');
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
expect(getAll).toHaveBeenCalled();
|
||||
expect(component.settings).toEqual(settings);
|
||||
|
||||
component.settings.crash_reports = false;
|
||||
component.save();
|
||||
|
||||
expect(setAll).toHaveBeenCalledWith(settings);
|
||||
expect(mapSettingsService.toggleIntegrateInterfaceLabels).toHaveBeenCalled();
|
||||
expect(mapSettingsService.toggleOpenConsolesInWidget).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { MapSettingsService } from '../../services/mapsettings.service';
|
||||
import { SettingsService } from '../../services/settings.service';
|
||||
import { Settings, SettingsService } from '../../services/settings.service';
|
||||
import { ConsoleService } from '../../services/settings/console.service';
|
||||
import { ThemeService } from '../../services/theme.service';
|
||||
import { ToasterService } from '../../services/toaster.service';
|
||||
@ -12,9 +12,10 @@ import { UpdatesService } from '../../services/updates.service';
|
||||
styleUrls: ['./settings.component.scss'],
|
||||
})
|
||||
export class SettingsComponent implements OnInit {
|
||||
settings = { ...SettingsService.DEFAULTS };
|
||||
settings: Settings;
|
||||
consoleCommand: string;
|
||||
integrateLinksLabelsToLinks: boolean;
|
||||
openConsolesInWidget: boolean;
|
||||
|
||||
constructor(
|
||||
private settingsService: SettingsService,
|
||||
@ -29,12 +30,14 @@ export class SettingsComponent implements OnInit {
|
||||
this.settings = this.settingsService.getAll();
|
||||
this.consoleCommand = this.consoleService.command;
|
||||
this.integrateLinksLabelsToLinks = this.mapSettingsService.integrateLinkLabelsToLinks;
|
||||
this.openConsolesInWidget = this.mapSettingsService.openConsolesInWidget;
|
||||
}
|
||||
|
||||
save() {
|
||||
this.settingsService.setAll(this.settings);
|
||||
this.toaster.success('Settings have been saved.');
|
||||
this.mapSettingsService.toggleIntegrateInterfaceLabels(this.integrateLinksLabelsToLinks);
|
||||
this.mapSettingsService.toggleOpenConsolesInWidget(this.openConsolesInWidget);
|
||||
}
|
||||
|
||||
setDarkMode(value: boolean) {
|
||||
|
@ -18,6 +18,6 @@
|
||||
</form>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button (click)="onNoClick()" tabindex="-1" color="accent">No Thanks</button>
|
||||
<button mat-button (click)="onNoClick()" tabindex="-1" color="accent">Cancel</button>
|
||||
<button mat-button (click)="onAddClick()" tabindex="2" mat-raised-button color="primary">Add</button>
|
||||
</div>
|
||||
|
@ -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 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])">
|
||||
<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])">
|
||||
<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])">
|
||||
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 3])" />
|
||||
</div>
|
||||
<div class="templateText">{{ 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 {
|
||||
@ -50,6 +73,23 @@
|
||||
}
|
||||
|
||||
.templateIcon {
|
||||
width: 80px !important;
|
||||
padding: 10px;
|
||||
width: 90px !important;
|
||||
padding: 2px 5px;
|
||||
text-align: center;
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user