Compare commits
63 Commits
v2.2.53
...
v0.0.1-bet
Author | SHA1 | Date | |
---|---|---|---|
a8054fa129 | |||
e180bedcdf | |||
598a52e41b | |||
33ba66538b | |||
fdffe15f5b | |||
5f4b5ac7c5 | |||
06642c7d45 | |||
b10d4f2dd5 | |||
0687c536af | |||
4abab7a96c | |||
1a3c538e68 | |||
e6f5574b72 | |||
35c0944d04 | |||
3056b0fbaf | |||
5b7bbe4f64 | |||
a037d97fbf | |||
57f3bfb5b9 | |||
1d6466731a | |||
33b943138f | |||
e706769c41 | |||
c510033de5 | |||
8f095cbf51 | |||
73071d462a | |||
498afb2632 | |||
542be33c22 | |||
471c5f5774 | |||
e2ef2e30bc | |||
1d83d0a1f2 | |||
0677618bb5 | |||
206bdc7dc6 | |||
c0919334bd | |||
0014fc7a62 | |||
a4057f1a04 | |||
2afc54051d | |||
6e6a2a4d6f | |||
6064892be5 | |||
f64479d755 | |||
f5b3ff1787 | |||
b69bef7654 | |||
c96dcb1dbe | |||
f18f9deb34 | |||
12ad880fa1 | |||
a96b83b481 | |||
51af56f867 | |||
90b478a969 | |||
8a9d4d5fe1 | |||
a61989612c | |||
a2c23d225d | |||
3fe275cf6c | |||
2efd541875 | |||
1c09c7b71c | |||
cd6b12dd60 | |||
be1378fcb2 | |||
9425d0edef | |||
09bacaefc6 | |||
55cfdbca96 | |||
36253113d7 | |||
aebfa517bf | |||
b513202339 | |||
7f059c7807 | |||
665e01f110 | |||
029ab352d2 | |||
1cc54e8520 |
66
.angular-cli.json
Normal file
@ -0,0 +1,66 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"project": {
|
||||
"name": "gns3-web-ui"
|
||||
},
|
||||
"apps": [
|
||||
{
|
||||
"root": "src",
|
||||
"outDir": "dist",
|
||||
"assets": [
|
||||
"assets",
|
||||
"favicon.ico"
|
||||
],
|
||||
"index": "index.html",
|
||||
"main": "main.ts",
|
||||
"polyfills": "polyfills.ts",
|
||||
"test": "test.ts",
|
||||
"tsconfig": "tsconfig.app.json",
|
||||
"testTsconfig": "tsconfig.spec.json",
|
||||
"prefix": "app",
|
||||
"styles": [
|
||||
"../node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||
"../node_modules/ng2-toasty/bundles/style-material.css",
|
||||
"styles.css",
|
||||
"theme.scss"
|
||||
],
|
||||
"scripts": [],
|
||||
"environmentSource": "environments/environment.ts",
|
||||
"environments": {
|
||||
"dev": "environments/environment.ts",
|
||||
"prod": "environments/environment.prod.ts",
|
||||
"electronProd": "environments/environment.electron.prod.ts",
|
||||
"electronDev": "environments/environment.electron.ts"
|
||||
}
|
||||
}
|
||||
],
|
||||
"e2e": {
|
||||
"protractor": {
|
||||
"config": "./protractor.conf.js"
|
||||
}
|
||||
},
|
||||
"lint": [
|
||||
{
|
||||
"project": "src/tsconfig.app.json",
|
||||
"exclude": "**/node_modules/**"
|
||||
},
|
||||
{
|
||||
"project": "src/tsconfig.spec.json",
|
||||
"exclude": "**/node_modules/**"
|
||||
},
|
||||
{
|
||||
"project": "e2e/tsconfig.e2e.json",
|
||||
"exclude": "**/node_modules/**"
|
||||
}
|
||||
],
|
||||
"test": {
|
||||
"karma": {
|
||||
"config": "./karma.conf.js"
|
||||
}
|
||||
},
|
||||
"defaults": {
|
||||
"styleExt": "scss",
|
||||
"component": {
|
||||
}
|
||||
}
|
||||
}
|
@ -3,38 +3,45 @@ version: 1.0.{build}
|
||||
# Do not build feature branch with open Pull Requests
|
||||
skip_branch_with_pr: true
|
||||
|
||||
cache:
|
||||
- node_modules -> .appveyor.yml,package.json,yarn.lock
|
||||
- '%LOCALAPPDATA%\Yarn -> .appveyor.yml,package.json,yarn.lock'
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
init:
|
||||
- git config --global core.autocrlf input
|
||||
#init:
|
||||
# - git config --global core.autocrlf input
|
||||
|
||||
install:
|
||||
- ps: Install-Product node 12 x64
|
||||
- yarn install
|
||||
- ps: Install-Product node 8 x64
|
||||
- yarn
|
||||
|
||||
build_script:
|
||||
- cmd: set NODE_OPTIONS=--max-old-space-size=8092
|
||||
- yarn buildforelectron
|
||||
- "%PYTHON%\\python.exe -m pip install -U pip"
|
||||
- "%PYTHON%\\python.exe -m pip install -r scripts\\requirements.txt"
|
||||
- "%PYTHON%\\python.exe scripts\\build.py download -a"
|
||||
- "%PYTHON%\\python.exe scripts\\build.py build_exe -b dist/exe.gns3server -s"
|
||||
- "%PYTHON%\\python.exe scripts\\build.py validate -b dist"
|
||||
- "%PYTHON%\\python.exe scripts\\build.py download_dependencies -b dist"
|
||||
- yarn electron-builder --win --x64 --publish always
|
||||
- yarn distwin
|
||||
- ps: $OutputDirectory = $((Get-Date).ToString('yyyy-MM-dd'))
|
||||
- ps: If ($env:APPVEYOR_REPO_TAG -eq $false) { New-Item -ItemType Directory -Path "$OutputDirectory" }
|
||||
- ps: If ($env:APPVEYOR_REPO_TAG -eq $false) { move build\*.exe "$OutputDirectory" }
|
||||
- ps: If ($env:APPVEYOR_REPO_TAG -eq $false) { cd $OutputDirectory; Get-ChildItem -Filter '*.exe' | Rename-Item -NewName {$_.Name -replace ".exe","-$env:APPVEYOR_REPO_BRANCH-$env:APPVEYOR_BUILD_NUMBER.exe"} }
|
||||
- ps: If ($env:APPVEYOR_REPO_TAG -eq $false) { $execs = Get-ChildItem -Filter '*.exe'; $artifact = $execs[0].basename; }
|
||||
- ps: If ($env:APPVEYOR_REPO_TAG -eq $false) { cd ..; Push-AppveyorArtifact "$((Get-Date).ToString('yyyy-MM-dd'))\*.exe" -FileName "$((Get-Date).ToString('yyyy-MM-dd'))\$artifact.exe" }
|
||||
|
||||
# - ps: If ($env:APPVEYOR_REPO_TAG -eq $true) { yarn release }
|
||||
|
||||
test: off
|
||||
|
||||
artifacts:
|
||||
- path: 'gns3-web-ui*.exe'
|
||||
name: gns3-web-ui
|
||||
#artifacts:
|
||||
# - path: '$(OutputDirectory)\*.exe'
|
||||
|
||||
deploy:
|
||||
- provider: FTP
|
||||
protocol: sftp
|
||||
host: frs.sourceforge.net
|
||||
username: gns3build
|
||||
password:
|
||||
secure: YRiLLoY27UOZ8QJHqqdESBQFfPfENKV0cLI/QFSsbWc=
|
||||
folder: "../../../../frs/project/gns-3/Nightly Builds"
|
||||
artifact: /.*\.exe/
|
||||
on:
|
||||
appveyor_repo_tag: false # deploy on branch only
|
||||
|
||||
environment:
|
||||
GH_TOKEN:
|
||||
secure: Zb0F4wfA/3zXZBQiEmEGpKIP17hD9gb/CNwxQE2N3J4Eq3z58mp0K0ey5g8Dupsb
|
||||
PYTHON: "C:\\Python36-x64"
|
||||
secure: EgwJ4mP2sPsfurW//aPDUXW+O7R+3N0pSfr+Y3SpiKK+70tGVbqy93pWCcdrPf45
|
||||
|
||||
|
@ -1,26 +1,12 @@
|
||||
# iOS CircleCI 2.1 configuration file
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
node: circleci/node@4.2.0
|
||||
|
||||
# iOS CircleCI 2.0 configuration file
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
macos:
|
||||
xcode: "10.1.0"
|
||||
xcode: "9.1.0"
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- node/install:
|
||||
install-yarn: true
|
||||
|
||||
- run:
|
||||
name: Update BREW and print configs
|
||||
command: |
|
||||
brew update
|
||||
brew analytics off
|
||||
brew --env
|
||||
brew --config
|
||||
|
||||
- run:
|
||||
name: Set timezone and check current datetime
|
||||
@ -28,76 +14,18 @@ jobs:
|
||||
sudo systemsetup -settimezone Europe/Warsaw
|
||||
echo "Today is $(date +"%Y-%m-%d %T")"
|
||||
|
||||
- run:
|
||||
name: Set ENV variables
|
||||
command: |
|
||||
echo 'export HOMEBREW_NO_AUTO_UPDATE=1' >> ~/.envs
|
||||
|
||||
- run:
|
||||
name: Install Python version 3.6.5 & readline 7.0.5
|
||||
command: |
|
||||
source ~/.envs
|
||||
curl -o /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/python.rb https://raw.githubusercontent.com/Homebrew/homebrew-core/f2a764ef944b1080be64bd88dca9a1d80130c558/Formula/python.rb
|
||||
curl -o /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/readline.rb https://raw.githubusercontent.com/Homebrew/homebrew-core/b1bd1c4a62e1336422de3614d1fc49ffbce589a8/Formula/readline.rb
|
||||
# remove check for old compilers which creates the error described in https://github.com/sashkab/homebrew-python/issues/36
|
||||
sed -i.bak -e '58,61d' /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/python.rb
|
||||
# remove 'do devel' block to avoid error: Calling 'devel' blocks in formulae is disabled!
|
||||
sed -i.bak -e '14,17d' /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/python.rb
|
||||
brew unlink python
|
||||
brew uninstall --ignore-dependencies readline
|
||||
brew install readline
|
||||
brew info readline
|
||||
brew pin readline
|
||||
# --ignore-dependencies is used to prevent this issue: https://github.com/tensorflow/tensorflow/issues/25093
|
||||
brew install --ignore-dependencies python
|
||||
#brew link python 3.6.5_1
|
||||
brew info python
|
||||
brew pin python
|
||||
|
||||
- run:
|
||||
name: Installed python and pip version
|
||||
command: |
|
||||
python3 -V
|
||||
pip3 -V
|
||||
|
||||
- restore_cache:
|
||||
name: Restore Yarn Package Cache
|
||||
keys:
|
||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||
|
||||
- run:
|
||||
name: Install project
|
||||
# increased timeout is for material-design-icons
|
||||
# there is an issue with yarn and cache during executed on CI; for now we just run it twice, second should
|
||||
# be successful. Check it later if updates fixed the issue
|
||||
command: |
|
||||
yarn install --network-timeout 1000000
|
||||
|
||||
- save_cache:
|
||||
name: Save Yarn Package Cache
|
||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
|
||||
- run:
|
||||
name: Building WebUI for distribution
|
||||
command: |
|
||||
yarn buildforelectron
|
||||
|
||||
- run:
|
||||
name: Building gns3server
|
||||
command: |
|
||||
python3 -V
|
||||
python3 -m pip install -U pip
|
||||
python3 -m pip install -r scripts/requirements.txt
|
||||
python3 scripts/build.py download -a
|
||||
# necessary because of https://github.com/GNS3/gns3-gui/issues/2849
|
||||
python3 -m pip install jsonschema==2.6.0
|
||||
python3 scripts/build.py build_exe -b dist/exe.gns3server -s
|
||||
python3 scripts/build.py validate -b dist
|
||||
yarn || true
|
||||
yarn || true
|
||||
|
||||
- run:
|
||||
name: Dist project
|
||||
command: |
|
||||
yarn electron-builder --mac --x64 --publish always
|
||||
yarn distmac
|
||||
|
||||
- run:
|
||||
name: Gather artifacts
|
||||
@ -109,6 +37,25 @@ jobs:
|
||||
path: artifacts
|
||||
destination: artifacts
|
||||
|
||||
- deploy:
|
||||
name: Deploying on SourceForge
|
||||
command: |
|
||||
echo "Deploying on SourceForge"
|
||||
ssh-keyscan -H frs.sourceforge.net >> ~/.ssh/known_hosts
|
||||
mkdir -p artifacts/release
|
||||
cd artifacts; for file in *.dmg; do mv "$file" "release/${file%.dmg}-${CIRCLE_BRANCH}-${CIRCLE_BUILD_NUM}.dmg"; done; cd ..
|
||||
echo "mkdir \"/home/frs/project/gns-3/Nightly Builds/$(date +"%Y-%m-%d")\"" | sftp gns3build@frs.sourceforge.net || true
|
||||
echo "Copying to SourceForge"
|
||||
scp artifacts/release/* gns3build@frs.sourceforge.net:"/home/frs/project/gns-3/Nightly\ Builds/$(date +"%Y-%m-%d")/"
|
||||
#
|
||||
# release:
|
||||
# macos:
|
||||
# xcode: "9.1.0"
|
||||
#
|
||||
# steps:
|
||||
# - yarn release
|
||||
#
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build_and_deploy:
|
||||
@ -117,3 +64,4 @@ workflows:
|
||||
filters:
|
||||
tags:
|
||||
only: /v.*/
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
# Editor configuration, see http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
|
16
.github/workflows/add-new-issues-to-project.yml
vendored
@ -1,16 +0,0 @@
|
||||
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
@ -1,76 +0,0 @@
|
||||
# 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}}"
|
25
.github/workflows/main.yml
vendored
@ -1,25 +0,0 @@
|
||||
name: Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- master-3.0
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup node 14
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14.x
|
||||
- uses: c-hive/gha-yarn-cache@v1
|
||||
- name: Install JS dependencies
|
||||
run: yarn install
|
||||
- name: Test
|
||||
run: yarn test
|
16
.gitignore
vendored
@ -1,17 +1,11 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/.angular
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
/ng-dist
|
||||
/build
|
||||
/scripts/tmp
|
||||
/scripts/env
|
||||
/scripts/build
|
||||
/scripts/dist
|
||||
/env
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
@ -41,7 +35,6 @@ npm-debug.log
|
||||
testem.log
|
||||
/typings
|
||||
/yarn-error.log
|
||||
package-lock.json
|
||||
|
||||
# e2e
|
||||
/e2e/*.js
|
||||
@ -50,12 +43,3 @@ package-lock.json
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Licenses file
|
||||
licenses.csv
|
||||
|
||||
# Temps
|
||||
.temp-var-file.ts
|
||||
|
||||
# external software
|
||||
/external
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"singleQuote": true,
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
[defaults]
|
||||
url = https://sentry.io/
|
||||
org = gns3
|
||||
project = gns3-web-ui
|
8
.snyk
@ -1,8 +0,0 @@
|
||||
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
|
||||
version: v1.16.0
|
||||
ignore: {}
|
||||
# patches apply the minimum changes required to fix a vulnerability
|
||||
patch:
|
||||
SNYK-JS-LODASH-567746:
|
||||
- ngx-childprocess > @types/electron > electron > @electron/get > global-tunnel-ng > lodash:
|
||||
patched: '2020-07-10T04:10:11.863Z'
|
64
.travis.yml
@ -1,54 +1,29 @@
|
||||
language: node_js
|
||||
|
||||
node_js:
|
||||
- '11' # use node for latest
|
||||
|
||||
# Issue with Travis: https://github.com/travis-ci/travis-ci/issues/8836#issuecomment-356362524
|
||||
sudo: required
|
||||
|
||||
cache:
|
||||
yarn: true
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
dist: xenial
|
||||
- node
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- google-chrome
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- google_chrome
|
||||
- g++-4.8
|
||||
|
||||
services:
|
||||
- xvfb
|
||||
- google-chrome-stable
|
||||
- google-chrome-beta
|
||||
|
||||
before_install:
|
||||
# greenkeeper-lockfile support
|
||||
- yarn global add greenkeeper-lockfile@1
|
||||
- |
|
||||
python3 -V
|
||||
wget -qO- https://bootstrap.pypa.io/get-pip.py | sudo python3
|
||||
python3 -m pip -V
|
||||
|
||||
- export CHROME_BIN=chromium-browser
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
|
||||
before_script:
|
||||
# greenkeeper-lockfile support
|
||||
- greenkeeper-lockfile-update
|
||||
# - npm install -g codecov
|
||||
- npm install -g karma
|
||||
|
||||
script: yarn coverage
|
||||
|
||||
after_success:
|
||||
# - codecov
|
||||
script: ng test --watch=false
|
||||
|
||||
after_script:
|
||||
# greenkeeper-lockfile support
|
||||
- greenkeeper-lockfile-upload
|
||||
# publish on gns3.github.io
|
||||
- yarn buildforgithub --base-href /${TRAVIS_BRANCH}/
|
||||
- ng build --base-href /${TRAVIS_BRANCH}/
|
||||
- export GIT_LAST_LOG="$(git log -1 --pretty=%B)"
|
||||
- git clone https://${GITHUB_CREDENTIALS}@github.com/GNS3/gns3.github.io.git github-pages
|
||||
- mkdir -p github-pages/${TRAVIS_BRANCH}
|
||||
@ -59,24 +34,7 @@ after_script:
|
||||
- git add -A
|
||||
- git commit -m "Deploy - $GIT_LAST_LOG"
|
||||
- git push origin master
|
||||
- cd $TRAVIS_BUILD_DIR
|
||||
|
||||
# publish
|
||||
- yarn buildforelectron
|
||||
- |
|
||||
python3 -m pip install -r scripts/requirements.txt
|
||||
python3 scripts/build.py download -a
|
||||
python3 scripts/build.py build_exe -b dist/exe.gns3server -s
|
||||
python3 scripts/build.py validate -b dist
|
||||
- yarn electron-builder --linux --x64 --publish always
|
||||
|
||||
# build sourcemaps and upload to Sentry
|
||||
# fix node issue with memory
|
||||
- |
|
||||
if [ -n "$TRAVIS_TAG" ]; then
|
||||
export NODE_OPTIONS=--max_old_space_size=4096
|
||||
export RELEASE_VERSION=$(node -e "const fs = require('fs'); let p = fs.readFileSync('package.json'); console.log(JSON.parse(p).version);")
|
||||
yarn ng build --configuration=production --base-href /static/web-ui/
|
||||
yarn sentry-cli releases new $RELEASE_VERSION
|
||||
yarn sentry-cli releases files $RELEASE_VERSION upload-sourcemaps dist/
|
||||
fi
|
||||
- yarn
|
||||
- yarn distlinux
|
||||
|
16
.vscode/launch.json
vendored
@ -1,16 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch localhost",
|
||||
"type": "firefox",
|
||||
"request": "launch",
|
||||
"reAttach": true,
|
||||
"url": "http://localhost:4200",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
14
.whitesource
@ -1,14 +0,0 @@
|
||||
{
|
||||
"scanSettings": {
|
||||
"configMode": "AUTO",
|
||||
"configExternalURL": "",
|
||||
"projectToken" : "",
|
||||
"baseBranches": ["master", "master-3.0"]
|
||||
},
|
||||
"checkRunSettings": {
|
||||
"vulnerableCheckRunConclusionLevel": "failure"
|
||||
},
|
||||
"issueSettings": {
|
||||
"minSeverityLevel": "LOW"
|
||||
}
|
||||
}
|
@ -37,3 +37,4 @@ Gruntfile.js
|
||||
|
||||
# misc
|
||||
*.gz
|
||||
*.md
|
||||
|
26
Dockerfile
@ -1,26 +0,0 @@
|
||||
# Dockerfile for GNS3 Web-ui development
|
||||
FROM node:stretch
|
||||
|
||||
# Create user
|
||||
RUN useradd --user-group --create-home --shell /bin/false gns3-web-ui
|
||||
|
||||
# Create app directory
|
||||
ENV HOME /home/gns3-web-ui
|
||||
WORKDIR $HOME
|
||||
|
||||
# Copy source
|
||||
COPY . .
|
||||
RUN chown -R gns3-web-ui:gns3-web-ui $HOME
|
||||
|
||||
# Switch to gns3-web-ui user
|
||||
USER gns3-web-ui
|
||||
|
||||
# Install dependencies
|
||||
RUN yarn global add @angular/cli
|
||||
RUN yarn install --pure-lockfile
|
||||
|
||||
ENV PATH /home/gns3-web-ui/.yarn/bin:$PATH
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
CMD ng serve --host 0.0.0.0 --port 8080
|
131
README.md
@ -1,94 +1,63 @@
|
||||
# gns3-web-ui
|
||||
|
||||
[](https://github.com/GNS3/gns3-web-ui/actions/workflows/main.yml)
|
||||
[](https://libraries.io/github/GNS3/gns3-web-ui)
|
||||
[](https://repology.org/metapackage/gns3/versions)
|
||||
[](https://repology.org/metapackage/gns3/versions)
|
||||
Test WebUI implementation for GNS3.
|
||||
|
||||
## Demo
|
||||
This is not production ready version. It has been made to evaluate possibility of creation Web User Interface for GNS3 application.
|
||||
|
||||
Please use GNS3 WebUI bundled in `gns3server` and `gns3`.
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.2.6.
|
||||
|
||||
## Development
|
||||
## Installation for development
|
||||
|
||||
### Branches policy
|
||||
Please install `npm` if not present in your system.
|
||||
|
||||
On branch `master` you can find the latest codebase including under development features. If you are looking for stable version with features promoted to be included in the current/next release please switch to `stable` branch.
|
||||
|
||||
### Installation
|
||||
|
||||
We're using [yarn](https://yarnpkg.com/lang/en/) for packages installation:
|
||||
Next step is `angular-cli` installation:
|
||||
|
||||
```
|
||||
yarn install
|
||||
npm install @angular/cli
|
||||
```
|
||||
|
||||
## JavaScript heap out of memory
|
||||
## Development server
|
||||
|
||||
Increase the memory allocated to Node if you get JavaScript heap out of memory errors.
|
||||
### Run GNS3 server
|
||||
|
||||
Please run locally GNS3 server.
|
||||
|
||||
### Using default CORS policy.
|
||||
|
||||
GNS3 server contains CORS policies to run Web UI on 8080 at localhost. In order to use it, please run development server with custom port:
|
||||
|
||||
```
|
||||
export NODE_OPTIONS=--max-old-space-size=8192
|
||||
```
|
||||
|
||||
#### Run GNS3 server
|
||||
|
||||
Visit [gns3-server](https://github.com/GNS3/gns3-server) for guide how to run GNS3 server.
|
||||
|
||||
#### Run WebUI
|
||||
|
||||
```
|
||||
yarn ng serve
|
||||
ng serve --port 8080
|
||||
```
|
||||
|
||||
Application is accessible on `http://127.0.0.1:4200/`. The app will automatically reload if you change any of the source files.
|
||||
|
||||
### Docker container
|
||||
|
||||
For development you can also run the GNS3 Web UI in a container
|
||||
|
||||
```
|
||||
bash scripts/docker_dev_webui.sh
|
||||
```
|
||||
|
||||
### How to upgrade package.json?
|
||||
|
||||
```
|
||||
yarn upgrade --latest
|
||||
```
|
||||
|
||||
### gns3server bundled in WebUI - electron based
|
||||
|
||||
In special cases it's possible to build `gns3server` for GNS3 WebUI. This version is included in `electronjs` dist application.
|
||||
|
||||
```
|
||||
python3 scripts/build.py build -b dist
|
||||
```
|
||||
|
||||
### Code scaffolding
|
||||
|
||||
Run `yarn ng generate component component-name` to generate a new component. You can also use `yarn ng generate directive|pipe|service|class|module`.
|
||||
|
||||
### Build
|
||||
|
||||
Run `yarn ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
|
||||
|
||||
### Running unit tests
|
||||
|
||||
Run `yarn ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
Application is accessible on `http://localhost:8080/`. The app will automatically reload if you change any of the source files.
|
||||
|
||||
|
||||
## Releasing
|
||||
## Code scaffolding
|
||||
|
||||
### Release naming convention
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|module`.
|
||||
|
||||
Releases are named by the year and quarter when release is happening, e.g. January 2020 release is named 2020.1.X.
|
||||
## Build
|
||||
|
||||
### Bumping releases
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
|
||||
Before running the tests make sure you are serving the app via `ng serve`.
|
||||
|
||||
|
||||
# Releasing
|
||||
|
||||
## Bumping releases
|
||||
|
||||
We're using [version-bump-prompt](https://www.npmjs.com/package/version-bump-prompt) for increasing version.
|
||||
|
||||
Install `version-bump-prompt` via:
|
||||
Intall `bump` via:
|
||||
|
||||
npm install -g version-bump-prompt
|
||||
|
||||
@ -96,31 +65,31 @@ If you would like to bump prepatch just type:
|
||||
|
||||
bump --prepatch --tag --push
|
||||
|
||||
### Distribute release
|
||||
## Final release
|
||||
|
||||
We have got configured CircleCI, TravisCI and AppVeyor for distributing application for particular platform. In order to release you need to tag&push your changes from master.
|
||||
We have got configured CircleCI, TravisCI and AppVeyor for distributing application for particular platform. In order to release you need to tag your code nad push it.
|
||||
|
||||
First of all please remove `dev` from version in `package.json` (for instance `2019.2.0-alpha.4dev` to `2019.2.0-alpha.4`). Commit & push changes with message `Release 2019.2.0-alpha.4` . Next step is to tag repository and push to origin:
|
||||
Using `bump`:
|
||||
|
||||
bump --patch --tag --push
|
||||
|
||||
git tag v2019.2.0-alpha.4
|
||||
git push origin v2019.2.0-alpha.4
|
||||
Or manually:
|
||||
|
||||
git tag v0.0.1
|
||||
git push origin v0.0.1
|
||||
|
||||
|
||||
When artifacts are made you can see draft release here: [gns3-web-ui releases](https://github.com/GNS3/gns3-web-ui/releases) which is waiting to be published.
|
||||
After release please change current version in `package.json` to `2019.2.0-alpha.5dev`'. Otherwise artifacts will be overwritten during the next commit. Don't forget to commit & push changes.
|
||||
After release please change current version in `package.json` to `X.X.X-beta.0`'. Otherwise artifacts will be overwritten during the next commit.
|
||||
|
||||
#### Updating gns3server
|
||||
You may use `bump` to achieve that:
|
||||
|
||||
bump --prepatch
|
||||
|
||||
Checkout the latest master of `gns3server`. Run command `./scripts/update-bundled-web-ui.sh --tag=v2019.2.0-alpha.5`. Commit & push changes with message `Release 2019.2.0-alpha.4`.
|
||||
## Staging release
|
||||
|
||||
### Staging release
|
||||
In case you would like to create a new staging release. Please create draft release on github, like `0.0.1-dev1`. After successful build you can find there artifacts.
|
||||
|
||||
In case you would like to create a new staging release. Please create draft release on github, like `0.0.1-dev1`. After successful build you can find artifacts there.
|
||||
|
||||
### Updating signing certificate for Windows
|
||||
|
||||
Please follow this guide: [code-signing](https://www.electron.build/code-signing), use `certmgr.msc` exporting tool to limit the size of certificate.
|
||||
|
||||
## Further help
|
||||
|
||||
|
@ -1,5 +0,0 @@
|
||||
# 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
|
247
angular.json
@ -1,247 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"gns3-web-ui": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"allowedCommonJsDependencies": [
|
||||
"rxjs",
|
||||
"rxjs-compat",
|
||||
"uuid",
|
||||
"css-tree",
|
||||
"save-svg-as-png",
|
||||
"angular-draggable-droppable",
|
||||
"dom-set",
|
||||
"dom-plane",
|
||||
"mousetrap",
|
||||
"@mattlewis92/dom-autoscroller",
|
||||
"rxjs/Rx",
|
||||
"rxjs/add/operator/map",
|
||||
"rxjs-compat/add/operator/map",
|
||||
"classnames",
|
||||
"stylenames",
|
||||
"ipaddr.js"
|
||||
],
|
||||
"outputPath": "dist",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"assets": [
|
||||
"src/assets",
|
||||
"src/favicon.ico",
|
||||
"src/ReleaseNotes.txt"
|
||||
],
|
||||
"styles": [
|
||||
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||
"node_modules/notosans-fontface/css/notosans-fontface.min.css",
|
||||
"src/styles.scss",
|
||||
"src/theme.scss"
|
||||
],
|
||||
"scripts": [],
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"buildOptimizer": true,
|
||||
"sourceMap": true,
|
||||
"optimization": false,
|
||||
"namedChunks": true,
|
||||
"aot": true
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": {
|
||||
"hidden": true,
|
||||
"scripts": true,
|
||||
"styles": false
|
||||
},
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
]
|
||||
},
|
||||
"electronProd": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.electron.prod.ts"
|
||||
}
|
||||
]
|
||||
},
|
||||
"electronDev": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb"
|
||||
}
|
||||
],
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.electron.ts"
|
||||
}
|
||||
]
|
||||
},
|
||||
"githubProd": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.github.prod.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "gns3-web-ui:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "gns3-web-ui:build:production"
|
||||
},
|
||||
"electronProd": {
|
||||
"browserTarget": "gns3-web-ui:build:electronProd"
|
||||
},
|
||||
"electronDev": {
|
||||
"browserTarget": "gns3-web-ui:build:electronDev"
|
||||
},
|
||||
"githubProd": {
|
||||
"browserTarget": "gns3-web-ui:build:githubProd"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "gns3-web-ui:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"karmaConfig": "./karma.conf.js",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.spec.json",
|
||||
"scripts": [],
|
||||
"styles": [
|
||||
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||
"node_modules/notosans-fontface/css/notosans-fontface.min.css",
|
||||
"src/styles.scss",
|
||||
"src/theme.scss"
|
||||
],
|
||||
"sourceMap": false,
|
||||
"assets": [
|
||||
"src/assets",
|
||||
"src/favicon.ico"
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"src/tsconfig.app.json",
|
||||
"src/tsconfig.spec.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**",
|
||||
"**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gns3-web-ui-e2e": {
|
||||
"root": "e2e",
|
||||
"sourceRoot": "e2e",
|
||||
"projectType": "application",
|
||||
"architect": {
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "./protractor.conf.js",
|
||||
"devServerTarget": "gns3-web-ui:serve"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"e2e/tsconfig.e2e.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "gns3-web-ui",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"prefix": "app",
|
||||
"style": "scss"
|
||||
},
|
||||
"@schematics/angular:directive": {
|
||||
"prefix": "app"
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"analytics": false
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
const { spawn } = require('child_process');
|
||||
const { app } = require('electron');
|
||||
const path = require('path');
|
||||
|
||||
async function setPATHEnv() {
|
||||
const puttyLookup = [
|
||||
path.join(__dirname, 'dist', 'putty'),
|
||||
path.join(path.dirname(app.getPath('exe')), 'dist', 'putty')
|
||||
];
|
||||
|
||||
// prevent adding duplicates
|
||||
let extra = [
|
||||
...puttyLookup,
|
||||
].filter((dir) => {
|
||||
return process.env.PATH.indexOf(dir) < 0;
|
||||
});
|
||||
extra.push(process.env.PATH);
|
||||
process.env.PATH = extra.join(";");
|
||||
}
|
||||
|
||||
exports.openConsole = async (consoleRequest) => {
|
||||
// const genericConsoleCommand = 'xfce4-terminal --tab -T "%d" -e "telnet %h %p"';
|
||||
const genericConsoleCommand = 'putty.exe -telnet %h %p -loghost "%d"';
|
||||
|
||||
const command = prepareCommand(genericConsoleCommand, consoleRequest);
|
||||
|
||||
console.log(`Setting up PATH`);
|
||||
await setPATHEnv();
|
||||
|
||||
console.log(`Starting console with command: '${command}'`);
|
||||
|
||||
let consoleProcess = spawn(command, [], {
|
||||
shell :true
|
||||
});
|
||||
|
||||
consoleProcess.stdout.on('data', (data) => {
|
||||
console.log(`Console stdout is producing: ${data.toString()}`);
|
||||
});
|
||||
|
||||
consoleProcess.stderr.on('data', (data) => {
|
||||
console.log(`Console stderr is producing: ${data.toString()}`);
|
||||
});
|
||||
|
||||
consoleProcess.on('close', (code) => {
|
||||
console.log(`child process exited with code ${code}`);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function prepareCommand(consoleCommand, consoleRequest) {
|
||||
const mapping = {
|
||||
h: consoleRequest.host,
|
||||
p: consoleRequest.port,
|
||||
d: consoleRequest.name,
|
||||
i: consoleRequest.project_id,
|
||||
n: consoleRequest.node_id,
|
||||
c: consoleRequest.server_url
|
||||
};
|
||||
|
||||
for(var key in mapping) {
|
||||
const regExp = new RegExp(`%${key}`, 'g');
|
||||
consoleCommand = consoleCommand.replace(regExp, mapping[key]);
|
||||
}
|
||||
return consoleCommand;
|
||||
}
|
@ -1 +0,0 @@
|
||||
[1109/003452.026:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
|
@ -1,20 +1,14 @@
|
||||
import { Gns3WebUiPage } from './app.po';
|
||||
|
||||
describe('GNS3 Web UI Application', () => {
|
||||
describe('gns3-web-ui App', () => {
|
||||
let page: Gns3WebUiPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new Gns3WebUiPage();
|
||||
});
|
||||
|
||||
it('should have correct page title', async () => {
|
||||
// arrange
|
||||
await page.navigateTo();
|
||||
|
||||
// act
|
||||
let text = await page.getTitleText();
|
||||
|
||||
// assert
|
||||
expect(text).toEqual('GNS3 Web UI');
|
||||
it('should display welcome message', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getParagraphText()).toEqual('Welcome to app!');
|
||||
});
|
||||
});
|
||||
|
@ -5,10 +5,6 @@ export class Gns3WebUiPage {
|
||||
return browser.get('/');
|
||||
}
|
||||
|
||||
getTitleText() {
|
||||
return browser.driver.getTitle();
|
||||
}
|
||||
|
||||
getParagraphText() {
|
||||
return element(by.css('app-root h1')).getText();
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
import { browser } from 'protractor';
|
||||
|
||||
export class TestHelper {
|
||||
sleep(value: number) {
|
||||
browser.sleep(value);
|
||||
}
|
||||
|
||||
waitForLoading() {
|
||||
browser.waitForAngular();
|
||||
}
|
||||
|
||||
async asyncForEach(array, callback) {
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
await callback(array[index], index, array);
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentUrl() {
|
||||
return browser.getCurrentUrl();
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
import { browser, by } from 'protractor';
|
||||
import { TestHelper } from './common.po';
|
||||
|
||||
export class ProjectMapPage {
|
||||
helper = new TestHelper();
|
||||
|
||||
async openAddProjectDialog() {
|
||||
let addButton = await browser.driver.findElement(by.css('button.addNode'));
|
||||
await addButton.click();
|
||||
}
|
||||
|
||||
async addNode() {
|
||||
let inputs = await browser.driver.findElements(by.css('input.mat-input-element'));
|
||||
await inputs[0].sendKeys('VPCS');
|
||||
this.helper.sleep(1000);
|
||||
|
||||
let selects = await browser.driver.findElements(by.css('mat-select.mat-select'));
|
||||
await selects[1].click();
|
||||
this.helper.sleep(1000);
|
||||
|
||||
let options = await browser.driver.findElements(by.css('mat-option.mat-option'));
|
||||
await options[1].click(); //first option should be chosen
|
||||
this.helper.sleep(1000);
|
||||
|
||||
// new select appears after refreshing data
|
||||
selects = await browser.driver.findElements(by.css('mat-select.mat-select'));
|
||||
if (selects[2]) {
|
||||
await selects[2].click();
|
||||
this.helper.sleep(1000);
|
||||
|
||||
options = await browser.driver.findElements(by.css('mat-option.mat-option'));
|
||||
await options[0].click();
|
||||
this.helper.sleep(1000);
|
||||
}
|
||||
|
||||
let addButton = await browser.driver.findElement(by.css('button.addButton'));
|
||||
await addButton.click();
|
||||
this.helper.sleep(1000);
|
||||
}
|
||||
|
||||
async verifyIfNodeWithLabelExists(labelToFind: string) {
|
||||
this.helper.sleep(5000);
|
||||
let nodeLabel = await browser.driver.findElement(by.css('#map > g > g.layer > g.nodes > g > g > g > g > text'));
|
||||
let selectedNode;
|
||||
let textFromNodeLabel = await nodeLabel.getText();
|
||||
if (textFromNodeLabel == labelToFind) selectedNode = nodeLabel;
|
||||
|
||||
return selectedNode ? true : false;
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
import { browser, by } from 'protractor';
|
||||
import { TestHelper } from './common.po';
|
||||
|
||||
export class ProjectsPage {
|
||||
helper = new TestHelper();
|
||||
|
||||
async openAddProjectDialog() {
|
||||
let addButton = await browser.driver.findElement(by.css('button.add-button'));
|
||||
await addButton.click();
|
||||
}
|
||||
|
||||
async createProject() {
|
||||
let today = new Date();
|
||||
let inputs = await browser.driver.findElements(by.css('input.mat-input-element'));
|
||||
await inputs[1].sendKeys('test project ' + today.getUTCMilliseconds());
|
||||
this.helper.sleep(2000);
|
||||
let dialogButton = await browser.driver.findElement(by.css('button.add-project-button'));
|
||||
await dialogButton.click();
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import { browser, by } from 'protractor';
|
||||
import { TestHelper } from './common.po';
|
||||
|
||||
export class ServersPage {
|
||||
helper = new TestHelper();
|
||||
|
||||
maximizeWindow() {
|
||||
browser.driver.manage().window().maximize();
|
||||
}
|
||||
|
||||
navigateToServersPage() {
|
||||
return browser.get('/servers');
|
||||
}
|
||||
|
||||
getAddServerNotificationText() {
|
||||
return browser.driver.findElement(by.className('mat-card-content')).getText();
|
||||
}
|
||||
|
||||
async clickAddServer() {
|
||||
let serversTable = await this.checkServersTable();
|
||||
if (serversTable.length === 0) {
|
||||
let buttons = await browser.driver.findElements(by.className('mat-button mat-button-base'));
|
||||
await buttons[3].click();
|
||||
}
|
||||
}
|
||||
|
||||
checkServersTable() {
|
||||
return browser.driver.findElements(by.css('mat-cell'));
|
||||
}
|
||||
|
||||
async navigateToServerProjects() {
|
||||
this.helper.sleep(2000);
|
||||
let hyperlinks = await browser.driver.findElements(by.css('a.table-link'));
|
||||
let serverLink;
|
||||
await this.helper.asyncForEach(hyperlinks, async (element) => {
|
||||
let text = await element.getText();
|
||||
if (text === '127.0.0.1') serverLink = element;
|
||||
});
|
||||
await serverLink.click();
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import { TestHelper } from './helpers/common.po';
|
||||
import { ProjectMapPage } from './helpers/project-map.po';
|
||||
import { ProjectsPage } from './helpers/project.po';
|
||||
import { ServersPage } from './helpers/server.po';
|
||||
|
||||
describe('Project map page', () => {
|
||||
let serversPage: ServersPage;
|
||||
let projectsPage: ProjectsPage;
|
||||
let projectMapPage: ProjectMapPage;
|
||||
let helper: TestHelper;
|
||||
|
||||
beforeEach(async () => {
|
||||
serversPage = new ServersPage();
|
||||
projectsPage = new ProjectsPage();
|
||||
projectMapPage = new ProjectMapPage();
|
||||
helper = new TestHelper();
|
||||
|
||||
serversPage.maximizeWindow();
|
||||
await serversPage.navigateToServersPage();
|
||||
await serversPage.clickAddServer();
|
||||
await serversPage.navigateToServerProjects();
|
||||
await projectsPage.openAddProjectDialog();
|
||||
helper.sleep(2000);
|
||||
await projectsPage.createProject();
|
||||
helper.sleep(2000);
|
||||
});
|
||||
|
||||
it('user should have possibility to add nodes to map', async () => {
|
||||
// arrange
|
||||
projectMapPage.openAddProjectDialog();
|
||||
helper.sleep(2000);
|
||||
|
||||
//act
|
||||
projectMapPage.addNode();
|
||||
helper.sleep(2000);
|
||||
|
||||
//assert
|
||||
expect(await projectMapPage.verifyIfNodeWithLabelExists('PC1')).toBe(true);
|
||||
});
|
||||
});
|
@ -1,33 +0,0 @@
|
||||
import { TestHelper } from './helpers/common.po';
|
||||
import { ProjectsPage } from './helpers/project.po';
|
||||
import { ServersPage } from './helpers/server.po';
|
||||
|
||||
describe('Projects page', () => {
|
||||
let serversPage: ServersPage;
|
||||
let projectsPage: ProjectsPage;
|
||||
let helper: TestHelper;
|
||||
|
||||
beforeEach(() => {
|
||||
serversPage = new ServersPage();
|
||||
projectsPage = new ProjectsPage();
|
||||
helper = new TestHelper();
|
||||
});
|
||||
|
||||
it('user should have possibility to create new project', async () => {
|
||||
// arrange
|
||||
serversPage.maximizeWindow();
|
||||
await serversPage.navigateToServersPage();
|
||||
await serversPage.clickAddServer();
|
||||
await serversPage.navigateToServerProjects();
|
||||
helper.sleep(2000);
|
||||
|
||||
//act
|
||||
await projectsPage.openAddProjectDialog();
|
||||
helper.sleep(2000);
|
||||
await projectsPage.createProject();
|
||||
helper.sleep(2000);
|
||||
|
||||
//assert
|
||||
expect(helper.getCurrentUrl()).toMatch('server/1/project/');
|
||||
});
|
||||
});
|
@ -1,43 +0,0 @@
|
||||
import { TestHelper } from './helpers/common.po';
|
||||
import { ServersPage } from './helpers/server.po';
|
||||
|
||||
describe('Servers page', () => {
|
||||
let page: ServersPage;
|
||||
let helper: TestHelper;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new ServersPage();
|
||||
helper = new TestHelper();
|
||||
});
|
||||
|
||||
xit('user should have possibility to add server', async () => {
|
||||
// arrange
|
||||
page.maximizeWindow();
|
||||
await page.navigateToServersPage();
|
||||
|
||||
// act
|
||||
let text = await page.getAddServerNotificationText();
|
||||
|
||||
// assert
|
||||
expect(text).toBe("We've discovered GNS3 server on 127.0.0.1:3080, would you like to add to the list?");
|
||||
});
|
||||
|
||||
it('user should see added server in the list', async () => {
|
||||
// arrange
|
||||
page.maximizeWindow();
|
||||
await page.navigateToServersPage();
|
||||
await page.clickAddServer();
|
||||
helper.sleep(1000);
|
||||
|
||||
// act
|
||||
let firstRowOfServersTable = await page.checkServersTable();
|
||||
let serverData = [];
|
||||
await helper.asyncForEach(firstRowOfServersTable, async (element) => {
|
||||
serverData.push(await element.getText());
|
||||
});
|
||||
|
||||
// assert
|
||||
expect(serverData).toContain('127.0.0.1');
|
||||
expect(serverData).toContain('3080');
|
||||
});
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/e2e",
|
||||
"baseUrl": "./",
|
||||
|
@ -1,10 +1,10 @@
|
||||
appId: com.gns3.web-ui
|
||||
copyright: "Copyright © 2018 GNS3"
|
||||
productName: "GNS3 Web UI"
|
||||
productName: "GNS3 Web UI Prototype"
|
||||
#forceCodeSigning: true
|
||||
artifactName: "${productName}-${os}-${arch}-${version}.${ext}"
|
||||
asar: true
|
||||
compression: normal
|
||||
generateUpdatesFilesForAllChannels: true
|
||||
|
||||
directories:
|
||||
output: build
|
||||
@ -12,19 +12,8 @@ directories:
|
||||
files:
|
||||
- dist
|
||||
- main.js
|
||||
- renderer.js
|
||||
- sentry.js
|
||||
- installed-software.js
|
||||
- local-server.js
|
||||
- console-executor.js
|
||||
- package.json
|
||||
|
||||
extraFiles:
|
||||
- dist/exe.gns3server/**
|
||||
- dist/ubridge/**
|
||||
- dist/vpcs/**
|
||||
- dist/dynamips/**
|
||||
|
||||
mac:
|
||||
category: public.app-category.developer-tools
|
||||
# publish: github
|
||||
@ -48,20 +37,15 @@ linux:
|
||||
icon: "dist/assets/icons/png"
|
||||
category: "Network"
|
||||
packageCategory: "Network"
|
||||
description: "GNS3 Web UI Prototype application. Please don't use it as long as it's not officially announced."
|
||||
description: "GNS3 Web Ui Prototype application. Please don't use it as long as it's not officially announced."
|
||||
target:
|
||||
- deb
|
||||
- AppImage
|
||||
maintainer: "Dominik Ziajka <dominik@gns3.net>"
|
||||
win:
|
||||
publish:
|
||||
provider: "github"
|
||||
owner: "GNS3"
|
||||
# publish: github
|
||||
icon: "dist/assets/icons/win/icon.ico"
|
||||
publisherName: GNS3 Technologies Corporation
|
||||
|
||||
nsis:
|
||||
perMachine: true
|
||||
oneClick: false
|
||||
allowToChangeInstallationDirectory: true
|
||||
license: "LICENSE"
|
||||
|
||||
|
@ -1,104 +0,0 @@
|
||||
var commandExistsSync = require('command-exists').sync;
|
||||
var app = require('electron').app;
|
||||
var fs = require('fs');
|
||||
var util = require('util');
|
||||
var fetch = require('node-fetch')
|
||||
var stream = require('stream');
|
||||
var path = require('path');
|
||||
const { spawn } = require('child_process');
|
||||
const { ipcMain } = require('electron');
|
||||
|
||||
var pipeline = util.promisify(stream.pipeline);
|
||||
|
||||
exports.getInstalledSoftware = (softwareList) => {
|
||||
const installed = {};
|
||||
for(var software of softwareList) {
|
||||
var name = software.name;
|
||||
var locations = software.locations;
|
||||
|
||||
installed[name] = [];
|
||||
|
||||
for(var location of locations) {
|
||||
// var exists = commandExistsSync(command);
|
||||
var exists = fs.existsSync(location);
|
||||
if(exists) {
|
||||
installed[name].push(location);
|
||||
}
|
||||
}
|
||||
}
|
||||
return installed;
|
||||
}
|
||||
|
||||
async function downloadFile(resource, softwarePath) {
|
||||
var response = await fetch(resource);
|
||||
if (response.status != 200) {
|
||||
throw new Error(`Cannot download file ${resource}, response status = ${response.status}`);
|
||||
}
|
||||
await pipeline(
|
||||
response.body,
|
||||
fs.createWriteStream(softwarePath)
|
||||
);
|
||||
}
|
||||
|
||||
async function getSoftwareInstallationPath(software) {
|
||||
if (software.installer) {
|
||||
return path.join(app.getPath('temp'), software.binary);
|
||||
}
|
||||
else {
|
||||
const externalPath = path.join(app.getAppPath(), 'external');
|
||||
const exists = fs.existsSync(externalPath);
|
||||
if (!exists) {
|
||||
fs.mkdirSync(externalPath);
|
||||
}
|
||||
return path.join(externalPath, software.binary);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ipcMain.on('installed-software-install', async function (event, software) {
|
||||
const softwarePath = await getSoftwareInstallationPath(software);
|
||||
|
||||
const responseChannel = `installed-software-installed-${software.name}`;
|
||||
|
||||
if (software.type == 'web') {
|
||||
const exists = fs.existsSync(softwarePath);
|
||||
if (exists) {
|
||||
console.log(`Skipping downloading file due to '${softwarePath}' path exists`);
|
||||
}
|
||||
else {
|
||||
console.log(`File '${softwarePath}' doesn't exist. Downloading file.`);
|
||||
try {
|
||||
await downloadFile(software.resource, softwarePath);
|
||||
} catch(error) {
|
||||
event.sender.send(responseChannel, {
|
||||
success: false,
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let child;
|
||||
|
||||
if (software.sudo) {
|
||||
child = spawn('powershell.exe', ['Start-Process', '-FilePath', `"${softwarePath}"`]);
|
||||
}
|
||||
else {
|
||||
child = spawn(softwarePath, software.installation_arguments);
|
||||
}
|
||||
|
||||
child.on('exit', () => {
|
||||
event.sender.send(responseChannel, {
|
||||
success: true
|
||||
});
|
||||
});
|
||||
|
||||
child.on('error', (err) => {
|
||||
event.sender.send(responseChannel, {
|
||||
success: false,
|
||||
message: err.message
|
||||
});
|
||||
});
|
||||
|
||||
child.stdin.end();
|
||||
});
|
@ -4,28 +4,30 @@
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
frameworks: ['jasmine', '@angular/cli'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
require('@angular/cli/plugins/karma')
|
||||
],
|
||||
client:{
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
|
||||
reports: [ 'html', 'lcovonly' ],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
|
||||
angularCli: {
|
||||
environment: 'dev'
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: true
|
||||
singleRun: false
|
||||
});
|
||||
};
|
||||
|
321
local-server.js
@ -1,321 +0,0 @@
|
||||
const { spawn } = require('child_process');
|
||||
const kill = require('tree-kill');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const ini = require('ini');
|
||||
const { ipcMain } = require('electron')
|
||||
const { app } = require('electron')
|
||||
|
||||
const isWin = /^win/.test(process.platform);
|
||||
|
||||
let runningServers = {};
|
||||
|
||||
exports.getLocalServerPath = async () => {
|
||||
let binary = isWin ? 'gns3server.exe': 'gns3server';
|
||||
return findBinary('exe.', binary);
|
||||
}
|
||||
|
||||
exports.getUbridgePath = async () => {
|
||||
let binary = isWin ? 'ubridge.exe': 'ubridge';
|
||||
return findBinary('ubridge', binary);
|
||||
}
|
||||
|
||||
exports.startLocalServer = async (server) => {
|
||||
return await run(server, {
|
||||
logStdout: true
|
||||
});
|
||||
}
|
||||
|
||||
exports.stopLocalServer = async (server) => {
|
||||
return await stop(server.name);
|
||||
}
|
||||
|
||||
exports.getRunningServers = () => {
|
||||
return Object.keys(runningServers);
|
||||
}
|
||||
|
||||
exports.stopAllLocalServers = async () => {
|
||||
return await stopAll();
|
||||
}
|
||||
|
||||
async function findBinary(binaryDirectory, filename) {
|
||||
const lookupDirectories = [
|
||||
__dirname,
|
||||
path.dirname(app.getPath('exe'))
|
||||
];
|
||||
|
||||
for(var directory of lookupDirectories) {
|
||||
const serverPath = await findBinaryInDirectory(directory, binaryDirectory, filename);
|
||||
if(serverPath !== undefined) {
|
||||
return serverPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function findBinaryInDirectory(baseDirectory, binaryDirectory, filename) {
|
||||
const distDirectory = path.join(baseDirectory, 'dist');
|
||||
|
||||
if (!fs.existsSync(distDirectory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(distDirectory);
|
||||
|
||||
let binaryPath = null;
|
||||
|
||||
files.forEach((directory) => {
|
||||
if(directory.startsWith(binaryDirectory)) {
|
||||
binaryPath = path.join(baseDirectory, 'dist', directory, filename);
|
||||
}
|
||||
});
|
||||
|
||||
if(binaryPath !== null && fs.existsSync(binaryPath)) {
|
||||
return binaryPath;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
function getServerArguments(server, overrides, configPath) {
|
||||
let serverArguments = [];
|
||||
if(server.host) {
|
||||
serverArguments.push('--host');
|
||||
serverArguments.push(server.host);
|
||||
}
|
||||
if(server.port) {
|
||||
serverArguments.push('--port');
|
||||
serverArguments.push(server.port);
|
||||
}
|
||||
|
||||
serverArguments.push('--local');
|
||||
|
||||
if(configPath) {
|
||||
serverArguments.push('--config');
|
||||
serverArguments.push(configPath);
|
||||
}
|
||||
|
||||
return serverArguments;
|
||||
}
|
||||
|
||||
function getChannelForServer(server) {
|
||||
return `local-server-run-${server.name}`;
|
||||
}
|
||||
|
||||
function notifyStatus(status) {
|
||||
ipcMain.emit('local-server-status-events', status);
|
||||
}
|
||||
|
||||
function filterOutput(line) {
|
||||
const index = line.search('CRITICAL');
|
||||
if(index > -1) {
|
||||
return {
|
||||
isCritical: true,
|
||||
errorMessage: line.substr(index)
|
||||
};
|
||||
}
|
||||
return {
|
||||
isCritical: false
|
||||
}
|
||||
}
|
||||
|
||||
async function stopAll() {
|
||||
for(var serverName in runningServers) {
|
||||
let result, error = await stop(serverName);
|
||||
}
|
||||
console.log(`Stopped all servers`);
|
||||
}
|
||||
|
||||
async function stop(serverName) {
|
||||
let pid = undefined;
|
||||
|
||||
const runningServer = runningServers[serverName];
|
||||
|
||||
if(runningServer !== undefined && runningServer.process) {
|
||||
pid = runningServer.process.pid;
|
||||
}
|
||||
|
||||
console.log(`Stopping '${serverName}' with PID='${pid}'`);
|
||||
|
||||
const stopped = new Promise((resolve, reject) => {
|
||||
if(pid === undefined) {
|
||||
resolve(`Server '${serverName} is already stopped`);
|
||||
delete runningServers[serverName];
|
||||
return;
|
||||
}
|
||||
|
||||
kill(pid, (error) => {
|
||||
if(error) {
|
||||
console.error(`Error occured during stopping '${serverName}' with PID='${pid}'`);
|
||||
reject(error);
|
||||
}
|
||||
else {
|
||||
delete runningServers[serverName];
|
||||
console.log(`Stopped '${serverName}' with PID='${pid}'`);
|
||||
resolve(`Stopped '${serverName}' with PID='${pid}'`);
|
||||
|
||||
notifyStatus({
|
||||
serverName: serverName,
|
||||
status: 'stopped',
|
||||
message: `Server '${serverName}' stopped'`
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return stopped;
|
||||
}
|
||||
|
||||
async function getIniFile(server) {
|
||||
return path.join(app.getPath('userData'), `gns3_server_${server.id}.ini`);
|
||||
}
|
||||
|
||||
async function configure(configPath, server) {
|
||||
if(!fs.existsSync(configPath)) {
|
||||
fs.closeSync(fs.openSync(configPath, 'w'));
|
||||
console.log(`Configuration file '${configPath}' has been created.`);
|
||||
}
|
||||
|
||||
var config = ini.parse(fs.readFileSync(configPath, 'utf-8'));
|
||||
|
||||
if(server.path) {
|
||||
config.path = server.path;
|
||||
}
|
||||
if(server.host) {
|
||||
config.host = server.host;
|
||||
}
|
||||
if(server.port) {
|
||||
config.port = server.port;
|
||||
}
|
||||
if(server.ubridge_path) {
|
||||
config.ubridge_path = server.ubridge_path;
|
||||
}
|
||||
|
||||
fs.writeFileSync(configPath, ini.stringify(config, { section: 'Server' }));
|
||||
}
|
||||
|
||||
async function setPATHEnv() {
|
||||
const vpcsLookup = [
|
||||
path.join(__dirname, 'dist', 'vpcs'),
|
||||
path.join(path.dirname(app.getPath('exe')), 'dist', 'vpcs')
|
||||
];
|
||||
|
||||
const dynamipsLookup = [
|
||||
path.join(__dirname, 'dist', 'dynamips'),
|
||||
path.join(path.dirname(app.getPath('exe')), 'dist', 'dynamips')
|
||||
];
|
||||
|
||||
// prevent adding duplicates
|
||||
let extra = [
|
||||
...vpcsLookup,
|
||||
...dynamipsLookup
|
||||
].filter((dir) => {
|
||||
return process.env.PATH.indexOf(dir) < 0;
|
||||
});
|
||||
extra.push(process.env.PATH);
|
||||
process.env.PATH = extra.join(";");
|
||||
}
|
||||
|
||||
async function run(server, options) {
|
||||
if(!options) {
|
||||
options = {};
|
||||
}
|
||||
|
||||
const logStdout = options.logStdout || false;
|
||||
const logSterr = options.logSterr || false;
|
||||
|
||||
console.log(`Configuring`);
|
||||
|
||||
const configPath = await getIniFile(server);
|
||||
await configure(configPath, server);
|
||||
|
||||
console.log(`Setting up PATH`);
|
||||
await setPATHEnv();
|
||||
|
||||
console.log(`Running '${server.path}'`);
|
||||
|
||||
let serverProcess = spawn(server.path, getServerArguments(server, {}, configPath));
|
||||
|
||||
notifyStatus({
|
||||
serverName: server.name,
|
||||
status: 'started',
|
||||
message: `Server '${server.name}' started'`
|
||||
});
|
||||
|
||||
runningServers[server.name] = {
|
||||
process: serverProcess
|
||||
};
|
||||
|
||||
serverProcess.stdout.on('data', function(data) {
|
||||
const line = data.toString();
|
||||
const { isCritical, errorMessage } = filterOutput(line);
|
||||
if(isCritical) {
|
||||
notifyStatus({
|
||||
serverName: server.name,
|
||||
status: 'stderr',
|
||||
message: `Server reported error: '${errorMessage}`
|
||||
});
|
||||
}
|
||||
|
||||
if(logStdout) {
|
||||
console.log(data.toString());
|
||||
}
|
||||
});
|
||||
|
||||
serverProcess.stderr.on('data', function(data) {
|
||||
if(logSterr) {
|
||||
console.log(data.toString());
|
||||
}
|
||||
});
|
||||
|
||||
serverProcess.on('exit', (code, signal) => {
|
||||
notifyStatus({
|
||||
serverName: server.name,
|
||||
status: 'errored',
|
||||
message: `Server '${server.name}' has exited with status='${code}'`
|
||||
});
|
||||
});
|
||||
|
||||
serverProcess.on('error', (err) => {
|
||||
notifyStatus({
|
||||
serverName: server.name,
|
||||
status: 'errored',
|
||||
message: `Server errored: '${err}`
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await run({
|
||||
name: 'my-local',
|
||||
path: 'c:\\Program Files\\GNS3\\gns3server.EXE',
|
||||
port: 3080
|
||||
}, {
|
||||
logStdout: true
|
||||
});
|
||||
}
|
||||
|
||||
if(ipcMain) {
|
||||
ipcMain.on('local-server-run', async function (event, server) {
|
||||
const responseChannel = getChannelForServer();
|
||||
await run(server);
|
||||
event.sender.send(responseChannel, {
|
||||
success: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
process.on('SIGINT', function() {
|
||||
console.log("Caught interrupt signal");
|
||||
stopAll();
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.log(`UnhandledRejection occured '${reason}'`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
main();
|
||||
}
|
71
main.js
@ -1,75 +1,36 @@
|
||||
const electron = require('electron');
|
||||
// Module to control application life.
|
||||
const app = electron.app;
|
||||
// Module to create native browser window.
|
||||
const BrowserWindow = electron.BrowserWindow;
|
||||
|
||||
const path = require('path');
|
||||
const url = require('url');
|
||||
const yargs = require('yargs');
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
let mainWindow;
|
||||
let isDev = false;
|
||||
|
||||
const argv = yargs
|
||||
.describe('m', 'Maximizes window on startup.')
|
||||
.boolean('m')
|
||||
.describe('e', 'Environment, `dev` for developer mode and when not specified then production mode.')
|
||||
.choices('e', ['dev', null])
|
||||
.describe('d', 'Enable developer tools.')
|
||||
.boolean('d')
|
||||
.argv;
|
||||
|
||||
if (argv.e == 'dev') {
|
||||
isDev = true;
|
||||
}
|
||||
|
||||
|
||||
function createWindow () {
|
||||
// Create the browser window.
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
preload: path.join(__dirname, 'sentry.js')
|
||||
}
|
||||
});
|
||||
mainWindow = new BrowserWindow({width: 800, height: 600});
|
||||
|
||||
// and load the index.html of the app.
|
||||
mainWindow.loadURL(url.format({
|
||||
pathname: path.join(__dirname, 'dist/index.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true
|
||||
}));
|
||||
|
||||
if(isDev) {
|
||||
mainWindow.loadURL('http://localhost:4200/');
|
||||
}
|
||||
else {
|
||||
mainWindow.loadURL(url.format({
|
||||
pathname: path.join(__dirname, 'dist/index.html'),
|
||||
protocol: 'file:',
|
||||
slashes: true
|
||||
}));
|
||||
mainWindow.maximize();
|
||||
}
|
||||
|
||||
if(argv.d) {
|
||||
// Open the DevTools.
|
||||
mainWindow.webContents.openDevTools();
|
||||
}
|
||||
|
||||
if(argv.m) {
|
||||
mainWindow.maximize();
|
||||
}
|
||||
// Open the DevTools.
|
||||
// mainWindow.webContents.openDevTools();
|
||||
|
||||
// Emitted when the window is closed.
|
||||
mainWindow.on('closed',async function () {
|
||||
mainWindow.on('closed', function () {
|
||||
// Dereference the window object, usually you would store windows
|
||||
// in an array if your app supports multi windows, this is the time
|
||||
// when you should delete the corresponding element.
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
|
||||
// forward event to renderer
|
||||
electron.ipcMain.on('local-server-status-events', (event) => {
|
||||
mainWindow.webContents.send('local-server-status-events', event);
|
||||
mainWindow = null
|
||||
});
|
||||
}
|
||||
|
||||
@ -78,9 +39,6 @@ function createWindow () {
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.on('ready', createWindow);
|
||||
|
||||
// app.on('ready', createServerProc);
|
||||
// app.on('will-quit', exitServerProc);
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on('window-all-closed', function () {
|
||||
// On OS X it is common for applications and their menu bar
|
||||
@ -94,10 +52,9 @@ app.on('activate', function () {
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (mainWindow === null) {
|
||||
createWindow();
|
||||
createWindow()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// In this file you can include the rest of your app's specific main process
|
||||
// code. You can also put them in separate files and require them here.
|
||||
|
14063
package-lock.json
generated
Normal file
169
package.json
@ -1,138 +1,69 @@
|
||||
{
|
||||
"name": "gns3-web-ui",
|
||||
"version": "2.2.53",
|
||||
"author": {
|
||||
"name": "GNS3 Technology Inc.",
|
||||
"email": "developers@gns3.com"
|
||||
},
|
||||
"description": "Graphical Network Simulator-3 is a network software emulator.",
|
||||
"version": "0.0.1-beta.0",
|
||||
"license": "GPLv3",
|
||||
"main": "main.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/GNS3/gns3-web-ui.git"
|
||||
},
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"startforelectron": "ng serve --configuration=electronDev",
|
||||
"build": "ng build",
|
||||
"buildforproduction": "ng build --source-map=false --build-optimizer --configuration=production --base-href /static/web-ui/",
|
||||
"buildforelectron": "ng build --configuration=electronProd",
|
||||
"buildforgithub": "ng build --configuration=githubProd",
|
||||
"buildforelectron": "ng build -e electronProd",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e",
|
||||
"electron": "electron .",
|
||||
"electrondev": "concurrently -k \"yarn startforelectron\" \"electron . -e dev\"",
|
||||
"electrondev": "concurrently -k \"ng serve -e electronDev\" \"electron .\"",
|
||||
"distlinux": "yarn buildforelectron && electron-builder --linux --x64",
|
||||
"distwin": "yarn buildforelectron && electron-builder --win --x64",
|
||||
"distmac": "yarn buildforelectron && electron-builder --mac --x64",
|
||||
"release": "build",
|
||||
"coverage": "ng test --watch=false --code-coverage",
|
||||
"prettier:base": "prettier",
|
||||
"prettier:check": "yarn prettier:base -- --list-different \"src/**/*.{ts,js,html,scss}\"",
|
||||
"prettier:write": "yarn prettier:base -- --write \"src/**/*.{ts,js,html,scss}\"",
|
||||
"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"
|
||||
"release": "build"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@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.19.1",
|
||||
"css-tree": "^1.1.3",
|
||||
"d3-ng2-service": "^2.2.0",
|
||||
"eev": "^0.1.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.1.1",
|
||||
"ngx-electron": "^2.2.0",
|
||||
"node-fetch": "^3.2.10",
|
||||
"notosans-fontface": "1.2.2",
|
||||
"prettier-plugin-organize-imports": "^2.3.4",
|
||||
"rxjs": "^6.6.7",
|
||||
"rxjs-compat": "^6.6.7",
|
||||
"save-svg-as-png": "^1.4.17",
|
||||
"snyk": "^1.1064.0",
|
||||
"spark-md5": "^3.0.2",
|
||||
"svg-crowbar": "^0.7.0",
|
||||
"tree-kill": "^1.2.2",
|
||||
"tslib": "^2.3.1",
|
||||
"typeface-roboto": "^1.1.13",
|
||||
"xterm": "^4.15.0",
|
||||
"xterm-addon-attach": "^0.6.0",
|
||||
"xterm-addon-fit": "^0.5.0",
|
||||
"yargs": "^17.2.1",
|
||||
"zone.js": "~0.11.4"
|
||||
"@angular/animations": "^5.1.2",
|
||||
"@angular/cdk": "^5.0.3",
|
||||
"@angular/common": "^5.1.2",
|
||||
"@angular/compiler": "^5.1.2",
|
||||
"@angular/core": "^5.1.2",
|
||||
"@angular/forms": "^5.1.2",
|
||||
"@angular/http": "^5.1.2",
|
||||
"@angular/material": "^5.0.3",
|
||||
"@angular/platform-browser": "^5.1.2",
|
||||
"@angular/platform-browser-dynamic": "^5.1.2",
|
||||
"@angular/router": "^5.1.2",
|
||||
"@ng-bootstrap/ng-bootstrap": "^1.0.0-beta.4",
|
||||
"angular2-indexeddb": "^1.0.11",
|
||||
"bootstrap": "4.0.0-beta.2",
|
||||
"core-js": "^2.4.1",
|
||||
"d3-ng2-service": "^1.16.0",
|
||||
"ng2-toasty": "^4.0.3",
|
||||
"npm-check-updates": "^2.13.0",
|
||||
"rxjs": "^5.4.1",
|
||||
"zone.js": "^0.8.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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.10.1",
|
||||
"jasmine-spec-reporter": "~7.0.0",
|
||||
"jquery": "^3.6.0",
|
||||
"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.7.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"popper.js": "^1.16.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": "~10.4.0",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typescript": "4.2.3",
|
||||
"webpack": "5.76.0",
|
||||
"yarn-upgrade-all": "^0.5.4"
|
||||
},
|
||||
"greenkeeper": {
|
||||
"ignore": [
|
||||
"typescript"
|
||||
]
|
||||
},
|
||||
"snyk": true
|
||||
}
|
||||
"@angular/cli": "^1.6.3",
|
||||
"@angular/compiler-cli": "^5.1.2",
|
||||
"@angular/language-service": "^5.1.2",
|
||||
"@types/jasmine": "~2.8.3",
|
||||
"@types/jasminewd2": "~2.0.2",
|
||||
"@types/node": "~8.5.2",
|
||||
"codelyzer": "~4.0.2",
|
||||
"electron": "1.7.10",
|
||||
"electron-builder": "^19.42.1",
|
||||
"jasmine-core": "~2.8.0",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
"karma": "~2.0.0",
|
||||
"karma-chrome-launcher": "~2.2.0",
|
||||
"karma-cli": "~1.0.1",
|
||||
"karma-coverage-istanbul-reporter": "^1.2.1",
|
||||
"karma-jasmine": "~1.1.0",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"node-sass": "^4.5.3",
|
||||
"protractor": "~5.2.0",
|
||||
"ts-node": "~4.1.0",
|
||||
"tslint": "~5.8.0",
|
||||
"typescript": ">=2.4.0 <2.6.0",
|
||||
"popper.js": "^1.12.3",
|
||||
"jquery": "1.9.1 - 3"
|
||||
}
|
||||
}
|
@ -1,11 +1,3 @@
|
||||
// This file is required by the index.html file and will
|
||||
// be executed in the renderer process for that window.
|
||||
// All of the Node.js APIs are available in this process.
|
||||
|
||||
let shell = require('electron').shell
|
||||
document.addEventListener('click', function (event) {
|
||||
if (event.target.tagName === 'A' && event.target.href.startsWith('http')) {
|
||||
event.preventDefault()
|
||||
shell.openExternal(event.target.href)
|
||||
}
|
||||
})
|
||||
|
413
scripts/build.py
@ -1,413 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2018 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import psutil
|
||||
import zipfile
|
||||
import requests
|
||||
import platform
|
||||
import argparse
|
||||
import subprocess
|
||||
|
||||
from multiprocessing import Process, Queue
|
||||
from multiprocessing.queues import Empty
|
||||
|
||||
from cx_Freeze import setup, Executable
|
||||
|
||||
DEFAULT_GNS3_SERVER_DEV_BRANCH = '2.2'
|
||||
|
||||
FILE_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
WORKING_DIR = os.path.join(FILE_DIR, 'tmp')
|
||||
|
||||
SOURCE_ZIP = os.path.join(WORKING_DIR, 'gns3-server.source.zip')
|
||||
SOURCE_DESTINATION = os.path.join(WORKING_DIR, 'source')
|
||||
BINARIES_EXTENSION = platform.system() == "Windows" and ".exe" or ""
|
||||
DEPENDENCIES = {
|
||||
'ubridge': {
|
||||
'type': 'github',
|
||||
'releases': 'https://api.github.com/repos/GNS3/ubridge/releases',
|
||||
'version': '0.9.16', # possible to use LATEST as value
|
||||
'files': {
|
||||
'windows': [
|
||||
'cygwin1.dll',
|
||||
'ubridge.exe'
|
||||
]
|
||||
}
|
||||
},
|
||||
'vpcs': {
|
||||
'type': 'github',
|
||||
'releases': 'https://api.github.com/repos/GNS3/vpcs/releases',
|
||||
'version': '0.6.2',
|
||||
'files': {
|
||||
'windows': [
|
||||
'cygwin1.dll',
|
||||
'vpcs.exe'
|
||||
]
|
||||
}
|
||||
},
|
||||
'dynamips': {
|
||||
'type': 'github',
|
||||
'releases': 'https://api.github.com/repos/GNS3/dynamips/releases',
|
||||
'version': '0.2.17',
|
||||
'files': {
|
||||
'windows': [
|
||||
'cygwin1.dll',
|
||||
'dynamips.exe',
|
||||
'nvram_export.exe'
|
||||
]
|
||||
}
|
||||
},
|
||||
'putty': {
|
||||
'type': 'http',
|
||||
'url': 'https://the.earth.li/~sgtatham/putty/{version}/w64/putty.exe',
|
||||
'version': '0.71',
|
||||
'files': {
|
||||
'windows': [
|
||||
'putty.exe',
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def download(url, output):
|
||||
print("Downloading {} to {}".format(url, output))
|
||||
|
||||
if os.path.exists(output):
|
||||
print("{} already exist skip downloading".format(output))
|
||||
return
|
||||
r = requests.get(url, stream=True)
|
||||
if r.status_code == 200:
|
||||
with open(output, 'wb') as f:
|
||||
for chunk in r.iter_content():
|
||||
f.write(chunk)
|
||||
else:
|
||||
print("Download error for {} status {}".format(url, r.status_code))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def unzip(filename, directory):
|
||||
zip = zipfile.ZipFile(filename, 'r')
|
||||
zip.extractall(directory)
|
||||
zip.close()
|
||||
return zip.filelist
|
||||
|
||||
|
||||
def getversion(version):
|
||||
match = re.search("^(\d+\.\d+(.\d+)?).*$", version)
|
||||
assert match
|
||||
return match.group(1)
|
||||
|
||||
|
||||
def getsource_directory():
|
||||
files = os.listdir(SOURCE_DESTINATION)
|
||||
if len(files) > 0:
|
||||
return os.path.join(SOURCE_DESTINATION, files[0])
|
||||
raise Exception("Cannot find sources for gns3server")
|
||||
|
||||
|
||||
def prepare():
|
||||
os.makedirs(WORKING_DIR, exist_ok=True)
|
||||
|
||||
|
||||
def download_from_github(name, definition, output_directory):
|
||||
response = requests.get(definition['releases'])
|
||||
response.raise_for_status()
|
||||
releases = response.json()
|
||||
|
||||
if definition['version'] == 'LATEST':
|
||||
release = releases[0]
|
||||
else:
|
||||
release = list(filter(lambda x: x['tag_name'] == "v{}".format(definition['version']), releases))[0]
|
||||
|
||||
dependency_dir = os.path.join(output_directory, name)
|
||||
os.makedirs(dependency_dir, exist_ok=True)
|
||||
|
||||
files = []
|
||||
if platform.system() == "Windows":
|
||||
files = definition['files']['windows']
|
||||
|
||||
for filename in files:
|
||||
dependency_file = os.path.join(dependency_dir, filename)
|
||||
dependency_url = list(filter(lambda x: x['name'] == filename, release['assets']))[0]['browser_download_url']
|
||||
download(dependency_url, dependency_file)
|
||||
print('Downloaded {} to {}'.format(filename, dependency_file))
|
||||
|
||||
|
||||
def download_from_http(name, definition, output_directory):
|
||||
url = definition['url'].format(version=definition['version'])
|
||||
|
||||
dependency_dir = os.path.join(output_directory, name)
|
||||
os.makedirs(dependency_dir, exist_ok=True)
|
||||
|
||||
files = []
|
||||
if platform.system() == "Windows":
|
||||
files = definition['files']['windows']
|
||||
|
||||
for filename in files:
|
||||
dependency_file = os.path.join(dependency_dir, filename)
|
||||
download(url, dependency_file)
|
||||
print('Downloaded {} to {}'.format(filename, dependency_file))
|
||||
|
||||
|
||||
def download_dependencies_command(arguments):
|
||||
output_directory = os.path.join(os.getcwd(), arguments.b)
|
||||
|
||||
for name, definition in DEPENDENCIES.items():
|
||||
if definition['type'] == 'github':
|
||||
download_from_github(name, definition, output_directory)
|
||||
if definition['type'] == 'http':
|
||||
download_from_http(name, definition, output_directory)
|
||||
|
||||
|
||||
def get_latest_version():
|
||||
response = requests.get('https://api.github.com/repos/GNS3/gns3-server/releases')
|
||||
response.raise_for_status()
|
||||
releases = response.json()
|
||||
latest = list(filter(lambda r: r['tag_name'].startswith('v'.format(DEFAULT_GNS3_SERVER_DEV_BRANCH)), releases))[0]
|
||||
return latest['tag_name'].replace('v', '')
|
||||
|
||||
|
||||
def is_tagged():
|
||||
if 'TRAVIS_TAG' in os.environ.keys():
|
||||
return True
|
||||
if 'CIRCLE_TAG' in os.environ.keys():
|
||||
return True
|
||||
if os.environ.get('APPVEYOR_REPO_TAG', False) in (1, "True", "true"):
|
||||
return True
|
||||
|
||||
|
||||
def is_web_ui_non_dev():
|
||||
package_file = os.path.join(FILE_DIR, '..', 'package.json')
|
||||
with open(package_file) as fp:
|
||||
package_content = fp.read()
|
||||
package = json.loads(package_content)
|
||||
version = package['version']
|
||||
return not version.endswith('dev')
|
||||
|
||||
|
||||
def auto_version(arguments):
|
||||
# if we are on tagged repo it means we should use released gns3server
|
||||
if is_tagged():
|
||||
arguments.l = True
|
||||
|
||||
# if we are building non-dev version it should be same as above
|
||||
if is_web_ui_non_dev():
|
||||
arguments.l = True
|
||||
|
||||
|
||||
def download_command(arguments):
|
||||
if arguments.a:
|
||||
auto_version(arguments)
|
||||
|
||||
shutil.rmtree(SOURCE_DESTINATION, ignore_errors=True)
|
||||
os.makedirs(SOURCE_DESTINATION)
|
||||
|
||||
if arguments.l:
|
||||
version = get_latest_version()
|
||||
download_url = "https://api.github.com/repos/GNS3/gns3-server/zipball/v{version}"
|
||||
else:
|
||||
version = DEFAULT_GNS3_SERVER_DEV_BRANCH
|
||||
download_url = "https://github.com/GNS3/gns3-server/archive/{version}.zip"
|
||||
|
||||
print("Using {version} with download_url: {download_url}".format(version=version, download_url=download_url))
|
||||
|
||||
download(download_url.format(version=version), SOURCE_ZIP)
|
||||
|
||||
files = unzip(SOURCE_ZIP, SOURCE_DESTINATION)
|
||||
source_directory = os.path.join(SOURCE_DESTINATION, files[0].filename)
|
||||
|
||||
if platform.system() == "Windows":
|
||||
requirements = 'win-requirements.txt'
|
||||
elif platform.system() == "Darwin":
|
||||
requirements = 'mac-requirements.txt'
|
||||
else:
|
||||
requirements = 'requirements.txt'
|
||||
|
||||
subprocess.check_call([sys.executable, '-m', 'pip', 'install', '-r', os.path.join(source_directory, requirements)])
|
||||
|
||||
|
||||
def build_command(arguments):
|
||||
source_directory = getsource_directory()
|
||||
|
||||
sys.path.append(source_directory)
|
||||
|
||||
if platform.system() == 'Darwin':
|
||||
# this fixes cx_freeze bug for OSX and python 3.6, see:
|
||||
# https://bitbucket.org/ronaldoussoren/pyobjc/issues/185/python-36-modulenotfounderror-no-module
|
||||
with open(os.path.join(source_directory, 'gns3server', 'main.py')) as f:
|
||||
main_content = f.read()
|
||||
|
||||
if 'cx_freeze_and_python_3_6_missing_library' not in main_content:
|
||||
main_content += "\ndef cx_freeze_and_python_3_6_missing_library():\n import _sysconfigdata_m_darwin_darwin\n\n"
|
||||
|
||||
with open(os.path.join(source_directory, 'gns3server', 'main.py'), 'w') as f:
|
||||
f.write(main_content)
|
||||
|
||||
from gns3server.version import __version__
|
||||
|
||||
# cx_Freeze on Windows requires version to be in format a.b.c.d
|
||||
server_version = getversion(__version__)
|
||||
|
||||
executables = [
|
||||
Executable(
|
||||
os.path.join(source_directory, "gns3server/main.py"),
|
||||
targetName="gns3server{}".format(BINARIES_EXTENSION)
|
||||
),
|
||||
Executable(
|
||||
os.path.join(source_directory, "gns3server/utils/vmnet.py"),
|
||||
targetName="gns3vmnet{}".format(BINARIES_EXTENSION)
|
||||
)
|
||||
]
|
||||
|
||||
excludes = [
|
||||
"distutils", # issue on macOS
|
||||
"tkinter", # issue on Windows
|
||||
]
|
||||
|
||||
packages = [
|
||||
"psutil",
|
||||
"asyncio",
|
||||
"packaging", # needed for linux
|
||||
"appdirs",
|
||||
"idna", # required by aiohttp >= 2.3, cannot be found by cx_Freeze
|
||||
]
|
||||
|
||||
include_files = [
|
||||
("gns3server/configs", "configs"),
|
||||
("gns3server/appliances", "appliances"),
|
||||
("gns3server/templates", "templates"),
|
||||
("gns3server/symbols", "symbols"),
|
||||
("gns3server/static/web-ui", "static/web-ui")
|
||||
]
|
||||
|
||||
include_files = [(os.path.join(source_directory, x), y) for x, y in include_files]
|
||||
|
||||
setup(
|
||||
name="GNS3",
|
||||
version=server_version,
|
||||
description="GNS3 Network simulator",
|
||||
executables=executables,
|
||||
options={
|
||||
"build_exe": {
|
||||
"includes": [],
|
||||
"excludes": excludes,
|
||||
"packages": packages,
|
||||
"include_files": include_files
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def execute(exe, queue, pid_q):
|
||||
binary_process = subprocess.Popen(
|
||||
[exe],
|
||||
bufsize=1, shell=False,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT
|
||||
)
|
||||
|
||||
pid_q.put(binary_process.pid)
|
||||
|
||||
while True:
|
||||
out = binary_process.stdout.read(1)
|
||||
if out == b'' and binary_process.poll() is not None:
|
||||
break
|
||||
queue.put(out)
|
||||
|
||||
|
||||
def validate_command(arguments):
|
||||
output_directory = os.path.join(os.getcwd(), arguments.b)
|
||||
files = os.listdir(output_directory)
|
||||
matching = [f for f in files if f.startswith('exe.')]
|
||||
if len(matching) == 0:
|
||||
raise Exception("Cannot find binaries of gns3server")
|
||||
binary = os.path.join(output_directory, matching[0], 'gns3server{}'.format(BINARIES_EXTENSION))
|
||||
|
||||
print("Validating: {}".format(binary))
|
||||
|
||||
pid_queue = Queue()
|
||||
output_queue = Queue()
|
||||
|
||||
process = Process(target=execute, args=(binary, output_queue, pid_queue))
|
||||
process.start()
|
||||
|
||||
pid = pid_queue.get()
|
||||
|
||||
print("Process is running on pid: " + str(pid))
|
||||
|
||||
output = ""
|
||||
while True:
|
||||
try:
|
||||
char = output_queue.get(timeout=3)
|
||||
output += char.decode()
|
||||
except Empty:
|
||||
break
|
||||
|
||||
process.terminate()
|
||||
|
||||
print("Output of process:")
|
||||
print(output)
|
||||
|
||||
parent = psutil.Process(pid)
|
||||
parent.kill()
|
||||
|
||||
result = "GNS3 Technologies Inc" not in output and 1 or 0
|
||||
sys.exit(result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Building gns3server for distribution')
|
||||
|
||||
subparsers = parser.add_subparsers(
|
||||
dest='command', help='Command which needs to be executed')
|
||||
|
||||
parser_download = subparsers.add_parser(
|
||||
'download', help='Downloads source code of gns3server')
|
||||
parser_download.add_argument('-l', action='store_true', help="Use the latest released version (incl. alpha), if not specified then dev version will be taken.")
|
||||
parser_download.add_argument('-a', action='store_true', help="Automatically choose version based on CI/CD pipeline")
|
||||
|
||||
|
||||
parser_build = subparsers.add_parser('build_exe', help='Build gns3server')
|
||||
parser_build.add_argument('-b', help='Output directory')
|
||||
parser_build.add_argument('-s', action='store_true', help='Silient building')
|
||||
|
||||
parser_validate = subparsers.add_parser('validate', help='Validate build')
|
||||
parser_validate.add_argument('-b', help='Output directory')
|
||||
|
||||
parser_validate = subparsers.add_parser('download_dependencies', help='Download dependencies')
|
||||
parser_validate.add_argument('-b', help='Output directory')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == 'build_exe':
|
||||
prepare()
|
||||
build_command(args)
|
||||
elif args.command == 'download':
|
||||
prepare()
|
||||
download_command(args)
|
||||
elif args.command == 'download_dependencies':
|
||||
prepare()
|
||||
download_dependencies_command(args)
|
||||
elif args.command == 'validate':
|
||||
prepare()
|
||||
validate_command(args)
|
||||
|
@ -1,22 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Copyright (C) 2018 GNS3 Technologies Inc.
|
||||
# Copyright (C) 2018 Nabil Bendafi <nabil@bendafi.fr>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# A docker web UI use for localy test
|
||||
|
||||
docker build -t gns3-web-ui .
|
||||
docker run -i -h gns3UIvm -p 8080:8080/tcp -t gns3-web-ui
|
@ -1,6 +0,0 @@
|
||||
setuptools==71.1.0
|
||||
cx_Freeze==5.1.1
|
||||
requests==2.32.3
|
||||
packaging==20.9
|
||||
appdirs==1.4.4
|
||||
psutil==5.8.0
|
27
sentry.js
@ -1,27 +0,0 @@
|
||||
const { init } = require('@sentry/electron');
|
||||
const fs = require('fs');
|
||||
|
||||
const { ipcMain } = require('electron')
|
||||
|
||||
let crashReportsEnabled = true;
|
||||
const DSN =
|
||||
'https://cb7b474b2e874afb8e400c47d1452ecc:7876224cbff543d992cb0ac4021962f8@sentry.io/1040940';
|
||||
|
||||
const isDev = () => {
|
||||
return fs.existsSync('.git');
|
||||
};
|
||||
|
||||
const shouldSendCallback = () => {
|
||||
return !isDev() && crashReportsEnabled;
|
||||
};
|
||||
|
||||
|
||||
ipcMain.on('settings.changed', function (event, settings) {
|
||||
crashReportsEnabled = settings.crash_reports;
|
||||
});
|
||||
|
||||
|
||||
init({
|
||||
dsn: DSN,
|
||||
shouldSendCallback: shouldSendCallback
|
||||
});
|
@ -1,25 +0,0 @@
|
||||
const yargs = require('yargs');
|
||||
const fs = require('fs');
|
||||
|
||||
const argv = yargs.argv;
|
||||
const tempFile = `.temp-var-file.ts`;
|
||||
|
||||
if(argv.set) {
|
||||
const envFile = argv.set;
|
||||
console.log(`Backuping up '${envFile}' into '${tempFile}'.`);
|
||||
fs.copyFileSync(envFile, tempFile);
|
||||
let content = fs.readFileSync(envFile, "utf8");
|
||||
|
||||
if(process.env.SOLARPUTTY_DOWNLOAD_URL) {
|
||||
const variables = `solarputty_download_url: '${process.env.SOLARPUTTY_DOWNLOAD_URL}',`
|
||||
content = content.replace('solarputty_download_url: "",', variables);
|
||||
}
|
||||
|
||||
fs.writeFileSync(envFile, content);
|
||||
}
|
||||
|
||||
if(argv.unset) {
|
||||
const envFile = argv.unset;
|
||||
console.log(`Restoring '${tempFile}' into '${envFile}'.`);
|
||||
fs.copyFileSync(tempFile, envFile);
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
GNS3 WebUI is web implementation of user interface for GNS3 software.
|
||||
|
||||
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
|
||||
|
||||
Bug Fixes & enhancements
|
||||
- symbol is not properly selected in change symbol dialog
|
||||
- issue when using the scroll wheel on the web console
|
||||
- missing settings for Docker nodes
|
||||
- error on servers page
|
||||
|
||||
What's new
|
||||
- double click nodes to open the console
|
||||
|
||||
Current version: 2020.3.0-beta.3
|
||||
|
||||
Bug Fixes & enhancements
|
||||
- direct download URL in template dialog
|
||||
- fix for issues with suspnded nodes
|
||||
- fix for bug with deleting templates
|
||||
- fix for importing images
|
||||
|
||||
What's new
|
||||
- Option to resize console
|
||||
- Improvements in creating templates
|
||||
|
||||
GNS3 Web UI 2020.3.0-beta.1
|
||||
|
||||
Bug Fixes & enhancements
|
||||
- refreshing list of templates after adding new template from project map
|
||||
- link to preferences from project page
|
||||
- disallow user to create Qemu template when binary is not selected
|
||||
- extending the time for notification to appear
|
||||
- open first settings menu at start
|
||||
- the menu for the map rearranged
|
||||
- restyling SystemStatus page
|
||||
- marking files which already exist in appliance wizard
|
||||
|
||||
What's new
|
||||
- Option to import appliances
|
||||
|
||||
GNS3 Web UI 2020.2.0-beta.5
|
||||
|
||||
Bug Fixes
|
||||
|
||||
- Bug in symbol selection
|
||||
- Same question is asked after going back to project
|
||||
- Cannot read property 'forEach' of undefined
|
||||
- Error when selecting existing Docker image
|
||||
- Invalid property when adding VMware VM template
|
||||
- Invalid type for adapters field when adding Docker template
|
||||
- Prevent user to move to another step when adding template
|
||||
- Web UI cannot set flag "Leave this project running in the background after closing"
|
||||
|
||||
What's new
|
||||
- Default values in templates
|
||||
- New option for Qemu VMs
|
||||
- Ability to quickly change Hostname from right click
|
||||
- Progress bar for node creation
|
||||
|
||||
GNS3 Web UI 2020.2.0-beta.4
|
||||
|
||||
Bug Fixes
|
||||
- New port setting for GNS3 VM preferences
|
||||
- Option to auto-hide menu toolbar on the left side
|
||||
- Server type in template preferences
|
||||
- Error when selecting existing Docker image
|
||||
- Default values in templates
|
||||
- TypeError: Cannot read property 'message' of undefined
|
||||
- TypeError: e.error is undefined
|
||||
- TypeError: Cannot read property 'placements' of null
|
||||
- Creating IOS templates -> fix for platforms and network adapters
|
||||
|
||||
GNS3 Web UI 2020.2.0-beta.2
|
||||
|
||||
What's New
|
||||
- Drag & drop to add new nodes on topology
|
||||
- Option to minimize/maximize and hide console widget
|
||||
- Ability to add IOS templates
|
||||
- Node names in HTTP console tabs
|
||||
- Default settings for templates
|
||||
- Support for adding IOS images
|
||||
- Node dialog updated
|
||||
- Messages with description in toasts
|
||||
- Adding interfaces to cloud nodes
|
||||
- Changes in notification box mechanism (once per day option)
|
||||
- Additional tooltips added
|
||||
- Copy/paste options in console (only Chrome full support)
|
||||
- More details for server failed connections
|
||||
|
||||
Bug Fixes
|
||||
- Fix for console icons
|
||||
- Fix for creating ethernet switches and hubs
|
||||
- Fix for opening console from context menu
|
||||
- Qemu configurator now works properly
|
||||
- Fixes in snap to grid option
|
||||
- Symbols preview now works correctly
|
||||
- Error messages in preferences should be displayed
|
||||
- Default values for New Ethernet devices in configurator
|
||||
- Fix for wrong adapter types in Qemu
|
||||
- Fix for fit in view option on Firefox
|
||||
- Fix for navigation errors
|
||||
|
||||
GNS3 Web UI 2020.2.0-beta.1
|
||||
|
||||
What's New
|
||||
- Support for suspended status added
|
||||
- Suport for 404 page
|
||||
- Actions for group of nodes added
|
||||
- Updating packages
|
||||
- Button to close project added
|
||||
- Opening ads in new window
|
||||
- New dialog for adding nodes
|
||||
- Option to import config
|
||||
- Support for light theme added
|
||||
|
||||
Bug Fixes
|
||||
- Fix for navigating to project that doesn't exist
|
||||
- Fix for AdButler errors
|
||||
- Fix for screenshot issue
|
||||
- Proper centering of icons
|
||||
- Fix for adding custom symbols
|
||||
- Fix for return command in console
|
||||
- Fix for deleting links
|
||||
- Fix for duplicating any node type
|
||||
- Fix for console errors on servers page
|
||||
- Fix for console errors on projects page
|
||||
|
||||
GNS3 Web UI 2019.2.0 v10
|
||||
|
||||
What's New
|
||||
- Qemu image configurator
|
||||
- Custom console for particular node
|
||||
- Option to connect console to all nodes
|
||||
- Option to start Winpcap
|
||||
- Filtering devices with packet filters on topology summary
|
||||
- Filtering devices with captures on topology summary
|
||||
- View options taken from map configuration
|
||||
- Servers summary widget
|
||||
- Ability to lock single item on the map
|
||||
- Editing & import & export config files
|
||||
- Context menu for inserted drawings
|
||||
- Ability to drag topology summary & servers summary & console widgets
|
||||
- Ability to resize topology summary & servers summary & console widgets
|
||||
- Option to show the grid
|
||||
- Option to snap to grid
|
||||
- Usage instructions available from context menu
|
||||
- Errors & warnings visible as notifications
|
||||
- Fit in view options
|
||||
- Support for global variables
|
||||
- Support for layers
|
||||
- Extending template preferences
|
||||
|
||||
Bug Fixes
|
||||
- Input validation in styles editor
|
||||
- Fix for saving map as image
|
||||
- Removing errors with uncorrect subscriptions
|
||||
|
||||
GNS3 Web UI 2019.2.0 v9
|
||||
|
||||
What's New
|
||||
- Editing interface labels on double click
|
||||
- Support for keyboard shortcuts
|
||||
- Menu extended with option to delete currently opened project, export & import project
|
||||
- Possibility to save current state of project
|
||||
- Ability to duplicate project from projects page
|
||||
- Node information dialog available from context menu
|
||||
- Topology summary widget on map view
|
||||
- Improvements in dialog styles
|
||||
|
||||
Bug Fixes
|
||||
- Removing issues with opening console
|
||||
- Context menu now is correctly placed
|
||||
- Text validation in dialogs
|
||||
- Removing errors with creating WebSockets
|
@ -1,210 +1,36 @@
|
||||
import { environment } from "../environments/environment";
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
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 { 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';
|
||||
import { CloudNodesAddTemplateComponent } from './components/preferences/built-in/cloud-nodes/cloud-nodes-add-template/cloud-nodes-add-template.component';
|
||||
import { CloudNodesTemplateDetailsComponent } from './components/preferences/built-in/cloud-nodes/cloud-nodes-template-details/cloud-nodes-template-details.component';
|
||||
import { CloudNodesTemplatesComponent } from './components/preferences/built-in/cloud-nodes/cloud-nodes-templates/cloud-nodes-templates.component';
|
||||
import { EthernetHubsAddTemplateComponent } from './components/preferences/built-in/ethernet-hubs/ethernet-hubs-add-template/ethernet-hubs-add-template.component';
|
||||
import { EthernetHubsTemplateDetailsComponent } from './components/preferences/built-in/ethernet-hubs/ethernet-hubs-template-details/ethernet-hubs-template-details.component';
|
||||
import { EthernetHubsTemplatesComponent } from './components/preferences/built-in/ethernet-hubs/ethernet-hubs-templates/ethernet-hubs-templates.component';
|
||||
import { EthernetSwitchesAddTemplateComponent } from './components/preferences/built-in/ethernet-switches/ethernet-switches-add-template/ethernet-switches-add-template.component';
|
||||
import { EthernetSwitchesTemplateDetailsComponent } from './components/preferences/built-in/ethernet-switches/ethernet-switches-template-details/ethernet-switches-template-details.component';
|
||||
import { EthernetSwitchesTemplatesComponent } from './components/preferences/built-in/ethernet-switches/ethernet-switches-templates/ethernet-switches-templates.component';
|
||||
import { AddDockerTemplateComponent } from './components/preferences/docker/add-docker-template/add-docker-template.component';
|
||||
import { CopyDockerTemplateComponent } from './components/preferences/docker/copy-docker-template/copy-docker-template.component';
|
||||
import { DockerTemplateDetailsComponent } from './components/preferences/docker/docker-template-details/docker-template-details.component';
|
||||
import { DockerTemplatesComponent } from './components/preferences/docker/docker-templates/docker-templates.component';
|
||||
import { AddIosTemplateComponent } from './components/preferences/dynamips/add-ios-template/add-ios-template.component';
|
||||
import { CopyIosTemplateComponent } from './components/preferences/dynamips/copy-ios-template/copy-ios-template.component';
|
||||
import { IosTemplateDetailsComponent } from './components/preferences/dynamips/ios-template-details/ios-template-details.component';
|
||||
import { IosTemplatesComponent } from './components/preferences/dynamips/ios-templates/ios-templates.component';
|
||||
import { Gns3vmComponent } from './components/preferences/gns3vm/gns3vm.component';
|
||||
import { AddIouTemplateComponent } from './components/preferences/ios-on-unix/add-iou-template/add-iou-template.component';
|
||||
import { CopyIouTemplateComponent } from './components/preferences/ios-on-unix/copy-iou-template/copy-iou-template.component';
|
||||
import { IouTemplateDetailsComponent } from './components/preferences/ios-on-unix/iou-template-details/iou-template-details.component';
|
||||
import { IouTemplatesComponent } from './components/preferences/ios-on-unix/iou-templates/iou-templates.component';
|
||||
import { PreferencesComponent } from './components/preferences/preferences.component';
|
||||
import { AddQemuVmTemplateComponent } from './components/preferences/qemu/add-qemu-vm-template/add-qemu-vm-template.component';
|
||||
import { CopyQemuVmTemplateComponent } from './components/preferences/qemu/copy-qemu-vm-template/copy-qemu-vm-template.component';
|
||||
import { QemuVmTemplateDetailsComponent } from './components/preferences/qemu/qemu-vm-template-details/qemu-vm-template-details.component';
|
||||
import { QemuVmTemplatesComponent } from './components/preferences/qemu/qemu-vm-templates/qemu-vm-templates.component';
|
||||
import { AddVirtualBoxTemplateComponent } from './components/preferences/virtual-box/add-virtual-box-template/add-virtual-box-template.component';
|
||||
import { VirtualBoxTemplateDetailsComponent } from './components/preferences/virtual-box/virtual-box-template-details/virtual-box-template-details.component';
|
||||
import { VirtualBoxTemplatesComponent } from './components/preferences/virtual-box/virtual-box-templates/virtual-box-templates.component';
|
||||
import { AddVmwareTemplateComponent } from './components/preferences/vmware/add-vmware-template/add-vmware-template.component';
|
||||
import { VmwareTemplateDetailsComponent } from './components/preferences/vmware/vmware-template-details/vmware-template-details.component';
|
||||
import { VmwareTemplatesComponent } from './components/preferences/vmware/vmware-templates/vmware-templates.component';
|
||||
import { AddVpcsTemplateComponent } from './components/preferences/vpcs/add-vpcs-template/add-vpcs-template.component';
|
||||
import { VpcsTemplateDetailsComponent } from './components/preferences/vpcs/vpcs-template-details/vpcs-template-details.component';
|
||||
import { VpcsTemplatesComponent } from './components/preferences/vpcs/vpcs-templates/vpcs-templates.component';
|
||||
import { ProjectMapComponent } from './components/project-map/project-map.component';
|
||||
import { ProjectsComponent } from './components/projects/projects.component';
|
||||
import { ServersComponent } from './components/servers/servers.component';
|
||||
import { ConsoleComponent } from './components/settings/console/console.component';
|
||||
import { SettingsComponent } from './components/settings/settings.component';
|
||||
import { ListOfSnapshotsComponent } from './components/snapshots/list-of-snapshots/list-of-snapshots.component';
|
||||
import { SystemStatusComponent } from './components/system-status/system-status.component';
|
||||
import { WebConsoleFullWindowComponent } from './components/web-console-full-window/web-console-full-window.component';
|
||||
import { ConsoleGuard } from './guards/console-guard';
|
||||
import { DefaultLayoutComponent } from './layouts/default-layout/default-layout.component';
|
||||
import { ServerResolve } from './resolvers/server-resolve';
|
||||
|
||||
import { ProjectMapComponent } from './project-map/project-map.component';
|
||||
import { ServersComponent } from "./servers/servers.component";
|
||||
import { ProjectsComponent } from "./projects/projects.component";
|
||||
import { DefaultLayoutComponent } from "./default-layout/default-layout.component";
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: DefaultLayoutComponent,
|
||||
{ path: '', component: DefaultLayoutComponent,
|
||||
children: [
|
||||
{ path: '', redirectTo: 'servers', pathMatch: 'full' },
|
||||
{ path: '', redirectTo: 'servers', pathMatch: 'full'},
|
||||
{ path: 'servers', component: ServersComponent },
|
||||
{ path: 'bundled', component: BundledServerFinderComponent },
|
||||
{
|
||||
path: 'server/:server_id/projects',
|
||||
component: ProjectsComponent,
|
||||
resolve: { server: ServerResolve },
|
||||
},
|
||||
{ path: 'help', component: HelpComponent },
|
||||
{ path: 'settings', component: SettingsComponent },
|
||||
{ path: 'settings/console', component: ConsoleComponent },
|
||||
{ path: 'installed-software', component: InstalledSoftwareComponent },
|
||||
{ path: 'server/:server_id/systemstatus', component: SystemStatusComponent },
|
||||
|
||||
{ path: 'server/:server_ip/:server_port/project/:project_id', component: DirectLinkComponent },
|
||||
{
|
||||
path: 'server/:server_id/project/:project_id/snapshots',
|
||||
component: ListOfSnapshotsComponent,
|
||||
resolve: { server: ServerResolve },
|
||||
},
|
||||
{ path: 'server/:server_id/preferences', component: PreferencesComponent },
|
||||
{ path: 'server/:server_id/preferences/gns3vm', component: Gns3vmComponent },
|
||||
// { path: 'server/:server_id/preferences/general', component: GeneralPreferencesComponent },
|
||||
{ path: 'server/:server_id/preferences/builtin', component: BuiltInPreferencesComponent },
|
||||
|
||||
{ path: 'server/:server_id/preferences/builtin/ethernet-hubs', component: EthernetHubsTemplatesComponent },
|
||||
{
|
||||
path: 'server/:server_id/preferences/builtin/ethernet-hubs/addtemplate',
|
||||
component: EthernetHubsAddTemplateComponent,
|
||||
},
|
||||
{
|
||||
path: 'server/:server_id/preferences/builtin/ethernet-hubs/:template_id',
|
||||
component: EthernetHubsTemplateDetailsComponent,
|
||||
},
|
||||
|
||||
{
|
||||
path: 'server/:server_id/preferences/builtin/ethernet-switches',
|
||||
component: EthernetSwitchesTemplatesComponent,
|
||||
},
|
||||
{
|
||||
path: 'server/:server_id/preferences/builtin/ethernet-switches/addtemplate',
|
||||
component: EthernetSwitchesAddTemplateComponent,
|
||||
},
|
||||
{
|
||||
path: 'server/:server_id/preferences/builtin/ethernet-switches/:template_id',
|
||||
component: EthernetSwitchesTemplateDetailsComponent,
|
||||
},
|
||||
|
||||
{ path: 'server/:server_id/preferences/builtin/cloud-nodes', component: CloudNodesTemplatesComponent },
|
||||
{
|
||||
path: 'server/:server_id/preferences/builtin/cloud-nodes/addtemplate',
|
||||
component: CloudNodesAddTemplateComponent,
|
||||
},
|
||||
{
|
||||
path: 'server/:server_id/preferences/builtin/cloud-nodes/:template_id',
|
||||
component: CloudNodesTemplateDetailsComponent,
|
||||
},
|
||||
|
||||
//{ path: 'server/:server_id/preferences/dynamips', component: DynamipsPreferencesComponent },
|
||||
{ path: 'server/:server_id/preferences/dynamips/templates', component: IosTemplatesComponent },
|
||||
{ path: 'server/:server_id/preferences/dynamips/templates/addtemplate', component: AddIosTemplateComponent },
|
||||
{ path: 'server/:server_id/preferences/dynamips/templates/:template_id', component: IosTemplateDetailsComponent },
|
||||
{
|
||||
path: 'server/:server_id/preferences/dynamips/templates/:template_id/copy',
|
||||
component: CopyIosTemplateComponent,
|
||||
},
|
||||
|
||||
// { path: 'server/:server_id/preferences/qemu', component: QemuPreferencesComponent },
|
||||
{ path: 'server/:server_id/preferences/qemu/templates', component: QemuVmTemplatesComponent },
|
||||
{
|
||||
path: 'server/:server_id/preferences/qemu/templates/:template_id/copy',
|
||||
component: CopyQemuVmTemplateComponent,
|
||||
},
|
||||
{ path: 'server/:server_id/preferences/qemu/templates/:template_id', component: QemuVmTemplateDetailsComponent },
|
||||
{ path: 'server/:server_id/preferences/qemu/addtemplate', component: AddQemuVmTemplateComponent },
|
||||
|
||||
// { path: 'server/:server_id/preferences/vpcs', component: VpcsPreferencesComponent },
|
||||
{ path: 'server/:server_id/preferences/vpcs/templates', component: VpcsTemplatesComponent },
|
||||
{ path: 'server/:server_id/preferences/vpcs/templates/:template_id', component: VpcsTemplateDetailsComponent },
|
||||
{ path: 'server/:server_id/preferences/vpcs/addtemplate', component: AddVpcsTemplateComponent },
|
||||
|
||||
// { path: 'server/:server_id/preferences/virtualbox', component: VirtualBoxPreferencesComponent },
|
||||
{ path: 'server/:server_id/preferences/virtualbox/templates', component: VirtualBoxTemplatesComponent },
|
||||
{
|
||||
path: 'server/:server_id/preferences/virtualbox/templates/:template_id',
|
||||
component: VirtualBoxTemplateDetailsComponent,
|
||||
},
|
||||
{ path: 'server/:server_id/preferences/virtualbox/addtemplate', component: AddVirtualBoxTemplateComponent },
|
||||
|
||||
// { path: 'server/:server_id/preferences/vmware', component: VmwarePreferencesComponent },
|
||||
{ path: 'server/:server_id/preferences/vmware/templates', component: VmwareTemplatesComponent },
|
||||
{
|
||||
path: 'server/:server_id/preferences/vmware/templates/:template_id',
|
||||
component: VmwareTemplateDetailsComponent,
|
||||
},
|
||||
{ path: 'server/:server_id/preferences/vmware/addtemplate', component: AddVmwareTemplateComponent },
|
||||
|
||||
// { path: 'server/:server_id/preferences/traceng', component: TracengPreferencesComponent },
|
||||
// { path: 'server/:server_id/preferences/traceng/templates', component: TracengTemplatesComponent },
|
||||
// { path: 'server/:server_id/preferences/traceng/templates/:template_id', component: TracengTemplateDetailsComponent },
|
||||
// { path: 'server/:server_id/preferences/traceng/addtemplate', component: AddTracengTemplateComponent },
|
||||
|
||||
{ path: 'server/:server_id/preferences/docker/templates', component: DockerTemplatesComponent },
|
||||
{
|
||||
path: 'server/:server_id/preferences/docker/templates/:template_id',
|
||||
component: DockerTemplateDetailsComponent,
|
||||
},
|
||||
{
|
||||
path: 'server/:server_id/preferences/docker/templates/:template_id/copy',
|
||||
component: CopyDockerTemplateComponent,
|
||||
},
|
||||
{ path: 'server/:server_id/preferences/docker/addtemplate', component: AddDockerTemplateComponent },
|
||||
|
||||
{ path: 'server/:server_id/preferences/iou/templates', component: IouTemplatesComponent },
|
||||
{ path: 'server/:server_id/preferences/iou/templates/:template_id', component: IouTemplateDetailsComponent },
|
||||
{ path: 'server/:server_id/preferences/iou/templates/:template_id/copy', component: CopyIouTemplateComponent },
|
||||
{ path: 'server/:server_id/preferences/iou/addtemplate', component: AddIouTemplateComponent },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'server/:server_id/project/:project_id',
|
||||
component: ProjectMapComponent,
|
||||
canDeactivate: [ConsoleGuard],
|
||||
},
|
||||
{
|
||||
path: 'server/:server_id/project/:project_id/nodes/:node_id',
|
||||
component: WebConsoleFullWindowComponent,
|
||||
},
|
||||
{
|
||||
path: 'static/web-ui/server/:server_id/project/:project_id/nodes/:node_id',
|
||||
component: WebConsoleFullWindowComponent,
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
component: PageNotFoundComponent,
|
||||
{ path: 'server/:server_id/projects', component: ProjectsComponent }
|
||||
]
|
||||
},
|
||||
{ path: 'server/:server_id/project/:project_id', component: ProjectMapComponent },
|
||||
];
|
||||
|
||||
let routerModule;
|
||||
if (environment.electron) {
|
||||
// angular in electron has problem with base-href and links separated by slashes, because of that
|
||||
// we use simply hashes
|
||||
routerModule = RouterModule.forRoot(routes, {useHash: true});
|
||||
} else {
|
||||
routerModule = RouterModule.forRoot(routes);
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forRoot(routes, {
|
||||
anchorScrolling: 'enabled',
|
||||
enableTracing: false,
|
||||
scrollPositionRestoration: 'enabled',
|
||||
}),
|
||||
],
|
||||
exports: [RouterModule],
|
||||
imports: [ routerModule ],
|
||||
exports: [ RouterModule ]
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
|
@ -1,4 +1,2 @@
|
||||
<div [ngClass]="{ dark: darkThemeEnabled, light: !darkThemeEnabled }">
|
||||
<router-outlet></router-outlet>
|
||||
<!-- <app-adbutler></app-adbutler> -->
|
||||
</div>
|
||||
<router-outlet></router-outlet>
|
||||
<ng2-toasty></ng2-toasty>
|
||||
|
@ -1,11 +0,0 @@
|
||||
mat-menu-panel {
|
||||
min-height: 0px;
|
||||
}
|
||||
|
||||
.dark {
|
||||
background: #263238 !important;
|
||||
}
|
||||
|
||||
.light {
|
||||
background: white !important;
|
||||
}
|
@ -1,62 +1,30 @@
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { ElectronService, NgxElectronModule } from 'ngx-electron';
|
||||
import { AppComponent } from './app.component';
|
||||
import { ProgressService } from './common/progress/progress.service';
|
||||
import { SettingsService } from './services/settings.service';
|
||||
|
||||
import createSpyObj = jasmine.createSpyObj;
|
||||
// import 'jasmine';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let component: AppComponent;
|
||||
let fixture: ComponentFixture<AppComponent>;
|
||||
let electronService: ElectronService;
|
||||
let settingsService: SettingsService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AppComponent],
|
||||
imports: [RouterTestingModule, MatIconModule, NgxElectronModule],
|
||||
providers: [SettingsService, ProgressService],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
imports: [
|
||||
RouterTestingModule
|
||||
]
|
||||
}).compileComponents();
|
||||
|
||||
electronService = TestBed.inject(ElectronService);
|
||||
settingsService = TestBed.inject(SettingsService);
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AppComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
// it('should create the app', async(() => {
|
||||
// const fixture = TestBed.createComponent(AppComponent);
|
||||
// const app = fixture.debugElement.componentInstance;
|
||||
// expect(app).toBeTruthy();
|
||||
// }));
|
||||
|
||||
it('should create the app', async(() => {
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should have footer', async(() => {
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('router-outlet').textContent).toEqual('');
|
||||
}));
|
||||
|
||||
it('should receive changed settings and forward to electron', async(() => {
|
||||
spyOnProperty(electronService, 'isElectronApp').and.returnValue(true);
|
||||
settingsService.setReportsSettings(true);
|
||||
component.ngOnInit();
|
||||
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.setReportsSettings(true);
|
||||
component.ngOnInit();
|
||||
settingsService.setReportsSettings(false);
|
||||
expect(spy.send).not.toHaveBeenCalled();
|
||||
}));
|
||||
// it('should have footer', async(() => {
|
||||
// const fixture = TestBed.createComponent(AppComponent);
|
||||
// fixture.detectChanges();
|
||||
// const compiled = fixture.debugElement.nativeElement;
|
||||
// expect(compiled.querySelector('.text-muted').textContent).toContain('GNS3 Web UI demo');
|
||||
// }));
|
||||
});
|
||||
|
@ -1,67 +1,23 @@
|
||||
import { OverlayContainer } from '@angular/cdk/overlay';
|
||||
import { Component, HostBinding, OnInit } from '@angular/core';
|
||||
import { MatIconRegistry } from '@angular/material/icon';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Router } from '@angular/router';
|
||||
import { ElectronService } from 'ngx-electron';
|
||||
import { ProgressService } from './common/progress/progress.service';
|
||||
import { SettingsService } from './services/settings.service';
|
||||
import { ThemeService } from './services/theme.service';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import {Http} from "@angular/http";
|
||||
import {MatIconRegistry} from "@angular/material";
|
||||
import {DomSanitizer} from "@angular/platform-browser";
|
||||
import {ToastyConfig} from "ng2-toasty";
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss'],
|
||||
styleUrls: [
|
||||
'./app.component.css'
|
||||
]
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
public darkThemeEnabled: boolean = false;
|
||||
constructor(http: Http, iconReg: MatIconRegistry, sanitizer: DomSanitizer, toastyConfig: ToastyConfig) {
|
||||
toastyConfig.theme = 'material';
|
||||
|
||||
constructor(
|
||||
private overlayContainer: OverlayContainer,
|
||||
iconReg: MatIconRegistry,
|
||||
sanitizer: DomSanitizer,
|
||||
private settingsService: SettingsService,
|
||||
private electronService: ElectronService,
|
||||
private themeService: ThemeService,
|
||||
private router: Router,
|
||||
private progressService: ProgressService
|
||||
) {
|
||||
iconReg.addSvgIcon('gns3', sanitizer.bypassSecurityTrustResourceUrl('./assets/gns3_icon.svg'));
|
||||
iconReg.addSvgIcon('gns3black', sanitizer.bypassSecurityTrustResourceUrl('./assets/gns3_icon_black.svg'));
|
||||
|
||||
router.events.subscribe((value) => {
|
||||
this.checkEvent(value);
|
||||
});
|
||||
iconReg.addSvgIcon('gns3', sanitizer.bypassSecurityTrustResourceUrl('./assets/gns3_icon.svg'));
|
||||
}
|
||||
|
||||
@HostBinding('class') componentCssClass;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.applyTheme(this.themeService.savedTheme + '-theme');
|
||||
this.themeService.themeChanged.subscribe((event: string) => {
|
||||
this.applyTheme(event);
|
||||
});
|
||||
}
|
||||
|
||||
applyTheme(theme: string) {
|
||||
if (theme === 'dark-theme') {
|
||||
this.darkThemeEnabled = true;
|
||||
} else {
|
||||
this.darkThemeEnabled = false;
|
||||
}
|
||||
this.overlayContainer.getContainerElement().classList.add(theme);
|
||||
this.componentCssClass = theme;
|
||||
}
|
||||
|
||||
checkEvent(routerEvent): void {
|
||||
if (routerEvent instanceof NavigationStart) {
|
||||
this.progressService.activate();
|
||||
} else if (
|
||||
routerEvent instanceof NavigationEnd ||
|
||||
routerEvent instanceof NavigationCancel ||
|
||||
routerEvent instanceof NavigationError
|
||||
) {
|
||||
this.progressService.deactivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,274 +1,58 @@
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||
import { OverlayModule } from '@angular/cdk/overlay';
|
||||
import { CdkTableModule } from '@angular/cdk/table';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { CdkTableModule } from "@angular/cdk/table";
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { ErrorHandler, NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
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 { ResizableModule } from 'angular-resizable-element';
|
||||
import {
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatMenuModule,
|
||||
MatToolbarModule,
|
||||
MatIconModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatTableModule,
|
||||
MatDialogModule,
|
||||
MatProgressBarModule,
|
||||
MatProgressSpinnerModule
|
||||
} from '@angular/material';
|
||||
|
||||
import { D3Service } from 'd3-ng2-service';
|
||||
import { NgCircleProgressModule } from 'ng-circle-progress';
|
||||
import { FileUploadModule } from 'ng2-file-upload';
|
||||
import { NgxChildProcessModule } from 'ngx-childprocess';
|
||||
import { NgxElectronModule } from 'ngx-electron';
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ToastyModule } from 'ng2-toasty';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
|
||||
import { VersionService } from './shared/services/version.service';
|
||||
import { ProjectService } from './shared/services/project.service';
|
||||
import { SymbolService } from "./shared/services/symbol.service";
|
||||
import { ServerService } from "./shared/services/server.service";
|
||||
import { IndexedDbService } from "./shared/services/indexed-db.service";
|
||||
import { HttpServer } from "./shared/services/http-server.service";
|
||||
import { SnapshotService } from "./shared/services/snapshot.service";
|
||||
import { ProgressDialogService } from "./shared/progress-dialog/progress-dialog.service";
|
||||
import { NodeService } from "./shared/services/node.service";
|
||||
import { ApplianceService } from "./shared/services/appliance.service";
|
||||
import { LinkService } from "./shared/services/link.service";
|
||||
|
||||
import { ProjectsComponent } from './projects/projects.component';
|
||||
import { DefaultLayoutComponent } from './default-layout/default-layout.component';
|
||||
import { ProgressDialogComponent } from './shared/progress-dialog/progress-dialog.component';
|
||||
import { AppComponent } from './app.component';
|
||||
//import { MapComponent } from './cartography/map/map.component';
|
||||
import { CreateSnapshotDialogComponent, ProjectMapComponent } from './project-map/project-map.component';
|
||||
import { ServersComponent, AddServerDialogComponent } from './servers/servers.component';
|
||||
import { NodeContextMenuComponent } from './shared/node-context-menu/node-context-menu.component';
|
||||
import { StartNodeActionComponent } from './shared/node-context-menu/actions/start-node-action/start-node-action.component';
|
||||
import { StopNodeActionComponent } from './shared/node-context-menu/actions/stop-node-action/stop-node-action.component';
|
||||
import { ApplianceComponent } from './appliance/appliance.component';
|
||||
import { ApplianceListDialogComponent } from './appliance/appliance-list-dialog/appliance-list-dialog.component';
|
||||
import { NodeSelectInterfaceComponent } from './shared/node-select-interface/node-select-interface.component';
|
||||
import { CartographyModule } from './cartography/cartography.module';
|
||||
import { DrawingsDataSource } from './cartography/datasources/drawings-datasource';
|
||||
import { LinksDataSource } from './cartography/datasources/links-datasource';
|
||||
import { NodesDataSource } from './cartography/datasources/nodes-datasource';
|
||||
import { SymbolsDataSource } from './cartography/datasources/symbols-datasource';
|
||||
import { InRectangleHelper } from './cartography/helpers/in-rectangle-helper';
|
||||
import { SelectionManager } from './cartography/managers/selection-manager';
|
||||
import { ToasterErrorHandler } from './common/error-handlers/toaster-error-handler';
|
||||
import { ProgressDialogComponent } from './common/progress-dialog/progress-dialog.component';
|
||||
import { ProgressDialogService } from './common/progress-dialog/progress-dialog.service';
|
||||
import { ProgressComponent } from './common/progress/progress.component';
|
||||
import { ProgressService } from './common/progress/progress.service';
|
||||
import { AdbutlerComponent } from './components/adbutler/adbutler.component';
|
||||
import { BundledServerFinderComponent } from './components/bundled-server-finder/bundled-server-finder.component';
|
||||
import { InformationDialogComponent } from './components/dialogs/information-dialog.component';
|
||||
import { DirectLinkComponent } from './components/direct-link/direct-link.component';
|
||||
import { DrawingAddedComponent } from './components/drawings-listeners/drawing-added/drawing-added.component';
|
||||
import { DrawingDraggedComponent } from './components/drawings-listeners/drawing-dragged/drawing-dragged.component';
|
||||
import { DrawingResizedComponent } from './components/drawings-listeners/drawing-resized/drawing-resized.component';
|
||||
import { InterfaceLabelDraggedComponent } from './components/drawings-listeners/interface-label-dragged/interface-label-dragged.component';
|
||||
import { LinkCreatedComponent } from './components/drawings-listeners/link-created/link-created.component';
|
||||
import { NodeDraggedComponent } from './components/drawings-listeners/node-dragged/node-dragged.component';
|
||||
import { NodeLabelDraggedComponent } from './components/drawings-listeners/node-label-dragged/node-label-dragged.component';
|
||||
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 { 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';
|
||||
import { BuiltInPreferencesComponent } from './components/preferences/built-in/built-in-preferences.component';
|
||||
import { CloudNodesAddTemplateComponent } from './components/preferences/built-in/cloud-nodes/cloud-nodes-add-template/cloud-nodes-add-template.component';
|
||||
import { CloudNodesTemplateDetailsComponent } from './components/preferences/built-in/cloud-nodes/cloud-nodes-template-details/cloud-nodes-template-details.component';
|
||||
import { CloudNodesTemplatesComponent } from './components/preferences/built-in/cloud-nodes/cloud-nodes-templates/cloud-nodes-templates.component';
|
||||
import { EthernetHubsAddTemplateComponent } from './components/preferences/built-in/ethernet-hubs/ethernet-hubs-add-template/ethernet-hubs-add-template.component';
|
||||
import { EthernetHubsTemplateDetailsComponent } from './components/preferences/built-in/ethernet-hubs/ethernet-hubs-template-details/ethernet-hubs-template-details.component';
|
||||
import { EthernetHubsTemplatesComponent } from './components/preferences/built-in/ethernet-hubs/ethernet-hubs-templates/ethernet-hubs-templates.component';
|
||||
import { EthernetSwitchesAddTemplateComponent } from './components/preferences/built-in/ethernet-switches/ethernet-switches-add-template/ethernet-switches-add-template.component';
|
||||
import { EthernetSwitchesTemplateDetailsComponent } from './components/preferences/built-in/ethernet-switches/ethernet-switches-template-details/ethernet-switches-template-details.component';
|
||||
import { EthernetSwitchesTemplatesComponent } from './components/preferences/built-in/ethernet-switches/ethernet-switches-templates/ethernet-switches-templates.component';
|
||||
import { CustomAdaptersTableComponent } from './components/preferences/common/custom-adapters-table/custom-adapters-table.component';
|
||||
import { CustomAdaptersComponent } from './components/preferences/common/custom-adapters/custom-adapters.component';
|
||||
import { DeleteConfirmationDialogComponent } from './components/preferences/common/delete-confirmation-dialog/delete-confirmation-dialog.component';
|
||||
import { DeleteTemplateComponent } from './components/preferences/common/delete-template-component/delete-template.component';
|
||||
import { EmptyTemplatesListComponent } from './components/preferences/common/empty-templates-list/empty-templates-list.component';
|
||||
import { PortsComponent } from './components/preferences/common/ports/ports.component';
|
||||
import { SymbolsMenuComponent } from './components/preferences/common/symbols-menu/symbols-menu.component';
|
||||
import { SymbolsComponent } from './components/preferences/common/symbols/symbols.component';
|
||||
import { UdpTunnelsComponent } from './components/preferences/common/udp-tunnels/udp-tunnels.component';
|
||||
import { AddDockerTemplateComponent } from './components/preferences/docker/add-docker-template/add-docker-template.component';
|
||||
import { CopyDockerTemplateComponent } from './components/preferences/docker/copy-docker-template/copy-docker-template.component';
|
||||
import { DockerTemplateDetailsComponent } from './components/preferences/docker/docker-template-details/docker-template-details.component';
|
||||
import { DockerTemplatesComponent } from './components/preferences/docker/docker-templates/docker-templates.component';
|
||||
import { AddIosTemplateComponent } from './components/preferences/dynamips/add-ios-template/add-ios-template.component';
|
||||
import { CopyIosTemplateComponent } from './components/preferences/dynamips/copy-ios-template/copy-ios-template.component';
|
||||
import { DynamipsPreferencesComponent } from './components/preferences/dynamips/dynamips-preferences/dynamips-preferences.component';
|
||||
import { IosTemplateDetailsComponent } from './components/preferences/dynamips/ios-template-details/ios-template-details.component';
|
||||
import { IosTemplatesComponent } from './components/preferences/dynamips/ios-templates/ios-templates.component';
|
||||
import { GeneralPreferencesComponent } from './components/preferences/general/general-preferences.component';
|
||||
import { Gns3vmComponent } from './components/preferences/gns3vm/gns3vm.component';
|
||||
import { AddIouTemplateComponent } from './components/preferences/ios-on-unix/add-iou-template/add-iou-template.component';
|
||||
import { CopyIouTemplateComponent } from './components/preferences/ios-on-unix/copy-iou-template/copy-iou-template.component';
|
||||
import { IouTemplateDetailsComponent } from './components/preferences/ios-on-unix/iou-template-details/iou-template-details.component';
|
||||
import { IouTemplatesComponent } from './components/preferences/ios-on-unix/iou-templates/iou-templates.component';
|
||||
import { PreferencesComponent } from './components/preferences/preferences.component';
|
||||
import { AddQemuVmTemplateComponent } from './components/preferences/qemu/add-qemu-vm-template/add-qemu-vm-template.component';
|
||||
import { CopyQemuVmTemplateComponent } from './components/preferences/qemu/copy-qemu-vm-template/copy-qemu-vm-template.component';
|
||||
import { QemuPreferencesComponent } from './components/preferences/qemu/qemu-preferences/qemu-preferences.component';
|
||||
import { QemuVmTemplateDetailsComponent } from './components/preferences/qemu/qemu-vm-template-details/qemu-vm-template-details.component';
|
||||
import { QemuVmTemplatesComponent } from './components/preferences/qemu/qemu-vm-templates/qemu-vm-templates.component';
|
||||
import { AddTracengTemplateComponent } from './components/preferences/traceng/add-traceng/add-traceng-template.component';
|
||||
import { TracengPreferencesComponent } from './components/preferences/traceng/traceng-preferences/traceng-preferences.component';
|
||||
import { TracengTemplateDetailsComponent } from './components/preferences/traceng/traceng-template-details/traceng-template-details.component';
|
||||
import { TracengTemplatesComponent } from './components/preferences/traceng/traceng-templates/traceng-templates.component';
|
||||
import { AddVirtualBoxTemplateComponent } from './components/preferences/virtual-box/add-virtual-box-template/add-virtual-box-template.component';
|
||||
import { VirtualBoxPreferencesComponent } from './components/preferences/virtual-box/virtual-box-preferences/virtual-box-preferences.component';
|
||||
import { VirtualBoxTemplateDetailsComponent } from './components/preferences/virtual-box/virtual-box-template-details/virtual-box-template-details.component';
|
||||
import { VirtualBoxTemplatesComponent } from './components/preferences/virtual-box/virtual-box-templates/virtual-box-templates.component';
|
||||
import { AddVmwareTemplateComponent } from './components/preferences/vmware/add-vmware-template/add-vmware-template.component';
|
||||
import { VmwarePreferencesComponent } from './components/preferences/vmware/vmware-preferences/vmware-preferences.component';
|
||||
import { VmwareTemplateDetailsComponent } from './components/preferences/vmware/vmware-template-details/vmware-template-details.component';
|
||||
import { VmwareTemplatesComponent } from './components/preferences/vmware/vmware-templates/vmware-templates.component';
|
||||
import { AddVpcsTemplateComponent } from './components/preferences/vpcs/add-vpcs-template/add-vpcs-template.component';
|
||||
import { VpcsPreferencesComponent } from './components/preferences/vpcs/vpcs-preferences/vpcs-preferences.component';
|
||||
import { VpcsTemplateDetailsComponent } from './components/preferences/vpcs/vpcs-template-details/vpcs-template-details.component';
|
||||
import { VpcsTemplatesComponent } from './components/preferences/vpcs/vpcs-templates/vpcs-templates.component';
|
||||
import { ChangeHostnameDialogComponent } from './components/project-map/change-hostname-dialog/change-hostname-dialog.component';
|
||||
import { ChangeSymbolDialogComponent } from './components/project-map/change-symbol-dialog/change-symbol-dialog.component';
|
||||
import { ConsoleWrapperComponent } from './components/project-map/console-wrapper/console-wrapper.component';
|
||||
import { ContextConsoleMenuComponent } from './components/project-map/context-console-menu/context-console-menu.component';
|
||||
import { AlignHorizontallyActionComponent } from './components/project-map/context-menu/actions/align-horizontally/align-horizontally.component';
|
||||
import { AlignVerticallyActionComponent } from './components/project-map/context-menu/actions/align_vertically/align-vertically.component';
|
||||
import { BringToFrontActionComponent } from './components/project-map/context-menu/actions/bring-to-front-action/bring-to-front-action.component';
|
||||
import { ChangeHostnameActionComponent } from './components/project-map/context-menu/actions/change-hostname/change-hostname-action.component';
|
||||
import { ChangeSymbolActionComponent } from './components/project-map/context-menu/actions/change-symbol/change-symbol-action.component';
|
||||
import { ConfigActionComponent } from './components/project-map/context-menu/actions/config-action/config-action.component';
|
||||
import { ConsoleDeviceActionBrowserComponent } from './components/project-map/context-menu/actions/console-device-action-browser/console-device-action-browser.component';
|
||||
import { ConsoleDeviceActionComponent } from './components/project-map/context-menu/actions/console-device-action/console-device-action.component';
|
||||
import { DeleteActionComponent } from './components/project-map/context-menu/actions/delete-action/delete-action.component';
|
||||
import { DuplicateActionComponent } from './components/project-map/context-menu/actions/duplicate-action/duplicate-action.component';
|
||||
import { EditConfigActionComponent } from './components/project-map/context-menu/actions/edit-config/edit-config-action.component';
|
||||
import { EditStyleActionComponent } from './components/project-map/context-menu/actions/edit-style-action/edit-style-action.component';
|
||||
import { EditTextActionComponent } from './components/project-map/context-menu/actions/edit-text-action/edit-text-action.component';
|
||||
import { ExportConfigActionComponent } from './components/project-map/context-menu/actions/export-config/export-config-action.component';
|
||||
import { HttpConsoleNewTabActionComponent } from './components/project-map/context-menu/actions/http-console-new-tab/http-console-new-tab-action.component';
|
||||
import { HttpConsoleActionComponent } from './components/project-map/context-menu/actions/http-console/http-console-action.component';
|
||||
import { ImportConfigActionComponent } from './components/project-map/context-menu/actions/import-config/import-config-action.component';
|
||||
import { LockActionComponent } from './components/project-map/context-menu/actions/lock-action/lock-action.component';
|
||||
import { MoveLayerDownActionComponent } from './components/project-map/context-menu/actions/move-layer-down-action/move-layer-down-action.component';
|
||||
import { MoveLayerUpActionComponent } from './components/project-map/context-menu/actions/move-layer-up-action/move-layer-up-action.component';
|
||||
import { OpenFileExplorerActionComponent } from './components/project-map/context-menu/actions/open-file-explorer/open-file-explorer-action.component';
|
||||
import { PacketFiltersActionComponent } from './components/project-map/context-menu/actions/packet-filters-action/packet-filters-action.component';
|
||||
import { ReloadNodeActionComponent } from './components/project-map/context-menu/actions/reload-node-action/reload-node-action.component';
|
||||
import { ResumeLinkActionComponent } from './components/project-map/context-menu/actions/resume-link-action/resume-link-action.component';
|
||||
import { ShowNodeActionComponent } from './components/project-map/context-menu/actions/show-node-action/show-node-action.component';
|
||||
import { StartCaptureOnStartedLinkActionComponent } from './components/project-map/context-menu/actions/start-capture-on-started-link/start-capture-on-started-link.component';
|
||||
import { StartCaptureActionComponent } from './components/project-map/context-menu/actions/start-capture/start-capture-action.component';
|
||||
import { StartNodeActionComponent } from './components/project-map/context-menu/actions/start-node-action/start-node-action.component';
|
||||
import { StopCaptureActionComponent } from './components/project-map/context-menu/actions/stop-capture/stop-capture-action.component';
|
||||
import { StopNodeActionComponent } from './components/project-map/context-menu/actions/stop-node-action/stop-node-action.component';
|
||||
import { SuspendLinkActionComponent } from './components/project-map/context-menu/actions/suspend-link/suspend-link-action.component';
|
||||
import { SuspendNodeActionComponent } from './components/project-map/context-menu/actions/suspend-node-action/suspend-node-action.component';
|
||||
import { ContextMenuComponent } from './components/project-map/context-menu/context-menu.component';
|
||||
import { ConfigDialogComponent } from './components/project-map/context-menu/dialogs/config-dialog/config-dialog.component';
|
||||
import { DrawLinkToolComponent } from './components/project-map/draw-link-tool/draw-link-tool.component';
|
||||
import { StyleEditorDialogComponent } from './components/project-map/drawings-editors/style-editor/style-editor.component';
|
||||
import { TextEditorDialogComponent } from './components/project-map/drawings-editors/text-editor/text-editor.component';
|
||||
import { HelpDialogComponent } from './components/project-map/help-dialog/help-dialog.component';
|
||||
import { NodeCreatedLabelStylesFixer } from './components/project-map/helpers/node-created-label-styles-fixer';
|
||||
import { ImportApplianceComponent } from './components/project-map/import-appliance/import-appliance.component';
|
||||
import { InfoDialogComponent } from './components/project-map/info-dialog/info-dialog.component';
|
||||
import { LogConsoleComponent } from './components/project-map/log-console/log-console.component';
|
||||
import { LogEventsDataSource } from './components/project-map/log-console/log-events-datasource';
|
||||
import { ApplianceInfoDialogComponent } from './components/project-map/new-template-dialog/appliance-info-dialog/appliance-info-dialog.component';
|
||||
import { NewTemplateDialogComponent } from './components/project-map/new-template-dialog/new-template-dialog.component';
|
||||
import { TemplateNameDialogComponent } from './components/project-map/new-template-dialog/template-name-dialog/template-name-dialog.component';
|
||||
import { ConfigEditorDialogComponent } from './components/project-map/node-editors/config-editor/config-editor.component';
|
||||
import { ConfiguratorDialogAtmSwitchComponent } from './components/project-map/node-editors/configurator/atm_switch/configurator-atm-switch.component';
|
||||
import { ConfiguratorDialogCloudComponent } from './components/project-map/node-editors/configurator/cloud/configurator-cloud.component';
|
||||
import { ConfiguratorDialogDockerComponent } from './components/project-map/node-editors/configurator/docker/configurator-docker.component';
|
||||
import { ConfigureCustomAdaptersDialogComponent } from './components/project-map/node-editors/configurator/docker/configure-custom-adapters/configure-custom-adapters.component';
|
||||
import { EditNetworkConfigurationDialogComponent } from './components/project-map/node-editors/configurator/docker/edit-network-configuration/edit-network-configuration.component';
|
||||
import { ConfiguratorDialogEthernetSwitchComponent } from './components/project-map/node-editors/configurator/ethernet-switch/configurator-ethernet-switch.component';
|
||||
import { ConfiguratorDialogEthernetHubComponent } from './components/project-map/node-editors/configurator/ethernet_hub/configurator-ethernet-hub.component';
|
||||
import { ConfiguratorDialogIosComponent } from './components/project-map/node-editors/configurator/ios/configurator-ios.component';
|
||||
import { ConfiguratorDialogIouComponent } from './components/project-map/node-editors/configurator/iou/configurator-iou.component';
|
||||
import { ConfiguratorDialogNatComponent } from './components/project-map/node-editors/configurator/nat/configurator-nat.component';
|
||||
import { ConfiguratorDialogQemuComponent } from './components/project-map/node-editors/configurator/qemu/configurator-qemu.component';
|
||||
import { QemuImageCreatorComponent } from './components/project-map/node-editors/configurator/qemu/qemu-image-creator/qemu-image-creator.component';
|
||||
import { ConfiguratorDialogSwitchComponent } from './components/project-map/node-editors/configurator/switch/configurator-switch.component';
|
||||
import { ConfiguratorDialogTracengComponent } from './components/project-map/node-editors/configurator/traceng/configurator-traceng.component';
|
||||
import { ConfiguratorDialogVirtualBoxComponent } from './components/project-map/node-editors/configurator/virtualbox/configurator-virtualbox.component';
|
||||
import { ConfiguratorDialogVmwareComponent } from './components/project-map/node-editors/configurator/vmware/configurator-vmware.component';
|
||||
import { ConfiguratorDialogVpcsComponent } from './components/project-map/node-editors/configurator/vpcs/configurator-vpcs.component';
|
||||
import { NodeSelectInterfaceComponent } from './components/project-map/node-select-interface/node-select-interface.component';
|
||||
import { NodesMenuComponent } from './components/project-map/nodes-menu/nodes-menu.component';
|
||||
import { PacketFiltersDialogComponent } from './components/project-map/packet-capturing/packet-filters/packet-filters.component';
|
||||
import { StartCaptureDialogComponent } from './components/project-map/packet-capturing/start-capture/start-capture.component';
|
||||
import { ProjectMapMenuComponent } from './components/project-map/project-map-menu/project-map-menu.component';
|
||||
import { ProjectMapComponent } from './components/project-map/project-map.component';
|
||||
import { ScreenshotDialogComponent } from './components/project-map/screenshot-dialog/screenshot-dialog.component';
|
||||
import { WebConsoleComponent } from './components/project-map/web-console/web-console.component';
|
||||
import { AddBlankProjectDialogComponent } from './components/projects/add-blank-project-dialog/add-blank-project-dialog.component';
|
||||
import { ChooseNameDialogComponent } from './components/projects/choose-name-dialog/choose-name-dialog.component';
|
||||
import { ConfirmationBottomSheetComponent } from './components/projects/confirmation-bottomsheet/confirmation-bottomsheet.component';
|
||||
import { ConfirmationDialogComponent } from './components/projects/confirmation-dialog/confirmation-dialog.component';
|
||||
import { EditProjectDialogComponent } from './components/projects/edit-project-dialog/edit-project-dialog.component';
|
||||
import { ImportProjectDialogComponent } from './components/projects/import-project-dialog/import-project-dialog.component';
|
||||
import { ProjectNameValidator } from './components/projects/models/projectNameValidator';
|
||||
import { NavigationDialogComponent } from './components/projects/navigation-dialog/navigation-dialog.component';
|
||||
import { ProjectsComponent } from './components/projects/projects.component';
|
||||
import { SaveProjectDialogComponent } from './components/projects/save-project-dialog/save-project-dialog.component';
|
||||
import { AddServerDialogComponent } from './components/servers/add-server-dialog/add-server-dialog.component';
|
||||
import { ConfigureGns3VMDialogComponent } from './components/servers/configure-gns3vm-dialog/configure-gns3vm-dialog.component';
|
||||
import { ServerDiscoveryComponent } from './components/servers/server-discovery/server-discovery.component';
|
||||
import { ServersComponent } from './components/servers/servers.component';
|
||||
import { ConsoleComponent } from './components/settings/console/console.component';
|
||||
import { SettingsComponent } from './components/settings/settings.component';
|
||||
import { CreateSnapshotDialogComponent } from './components/snapshots/create-snapshot-dialog/create-snapshot-dialog.component';
|
||||
import { ListOfSnapshotsComponent } from './components/snapshots/list-of-snapshots/list-of-snapshots.component';
|
||||
import { SnapshotMenuItemComponent } from './components/snapshots/snapshot-menu-item/snapshot-menu-item.component';
|
||||
import { StatusChartComponent } from './components/system-status/status-chart/status-chart.component';
|
||||
import { StatusInfoComponent } from './components/system-status/status-info/status-info.component';
|
||||
import { SystemStatusComponent } from './components/system-status/system-status.component';
|
||||
import { TemplateListDialogComponent } from './components/template/template-list-dialog/template-list-dialog.component';
|
||||
import { TemplateComponent } from './components/template/template.component';
|
||||
import { TopologySummaryComponent } from './components/topology-summary/topology-summary.component';
|
||||
import { WebConsoleFullWindowComponent } from './components/web-console-full-window/web-console-full-window.component';
|
||||
import { DataSourceFilter } from './filters/dataSourceFilter';
|
||||
import { DateFilter } from './filters/dateFilter.pipe';
|
||||
import { NameFilter } from './filters/nameFilter.pipe';
|
||||
import { ProjectsFilter } from './filters/projectsFilter.pipe';
|
||||
import { SearchFilter } from './filters/searchFilter.pipe';
|
||||
import { TemplateFilter } from './filters/templateFilter.pipe';
|
||||
import { ConsoleGuard } from './guards/console-guard';
|
||||
import { ProjectWebServiceHandler } from './handlers/project-web-service-handler';
|
||||
import { DefaultLayoutComponent } from './layouts/default-layout/default-layout.component';
|
||||
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';
|
||||
import { DockerConfigurationService } from './services/docker-configuration.service';
|
||||
import { DockerService } from './services/docker.service';
|
||||
import { DrawingService } from './services/drawing.service';
|
||||
import { ExternalSoftwareDefinitionService } from './services/external-software-definition.service';
|
||||
import { Gns3vmService } from './services/gns3vm.service';
|
||||
import { GoogleAnalyticsService } from './services/google-analytics.service';
|
||||
import { HttpServer, ServerErrorHandler } from './services/http-server.service';
|
||||
import { InfoService } from './services/info.service';
|
||||
import { InstalledSoftwareService } from './services/installed-software.service';
|
||||
import { IosConfigurationService } from './services/ios-configuration.service';
|
||||
import { IosService } from './services/ios.service';
|
||||
import { IouConfigurationService } from './services/iou-configuration.service';
|
||||
import { IouService } from './services/iou.service';
|
||||
import { LinkService } from './services/link.service';
|
||||
import { MapScaleService } from './services/mapScale.service';
|
||||
import { MapSettingsService } from './services/mapsettings.service';
|
||||
import { NodeService } from './services/node.service';
|
||||
import { NodeConsoleService } from './services/nodeConsole.service';
|
||||
import { NotificationService } from './services/notification.service';
|
||||
import { PacketCaptureService } from './services/packet-capture.service';
|
||||
import { PlatformService } from './services/platform.service';
|
||||
import { ProjectService } from './services/project.service';
|
||||
import { QemuConfigurationService } from './services/qemu-configuration.service';
|
||||
import { QemuService } from './services/qemu.service';
|
||||
import { RecentlyOpenedProjectService } from './services/recentlyOpenedProject.service';
|
||||
import { ServerManagementService } from './services/server-management.service';
|
||||
import { ServerSettingsService } from './services/server-settings.service';
|
||||
import { ServerDatabase } from './services/server.database';
|
||||
import { ServerService } from './services/server.service';
|
||||
import { SettingsService } from './services/settings.service';
|
||||
import { ConsoleService } from './services/settings/console.service';
|
||||
import { DefaultConsoleService } from './services/settings/default-console.service';
|
||||
import { SnapshotService } from './services/snapshot.service';
|
||||
import { SymbolService } from './services/symbol.service';
|
||||
import { TemplateMocksService } from './services/template-mocks.service';
|
||||
import { TemplateService } from './services/template.service';
|
||||
import { ThemeService } from './services/theme.service';
|
||||
import { ToasterService } from './services/toaster.service';
|
||||
import { ToolsService } from './services/tools.service';
|
||||
import { TracengService } from './services/traceng.service';
|
||||
import { UpdatesService } from './services/updates.service';
|
||||
import { VersionService } from './services/version.service';
|
||||
import { VirtualBoxConfigurationService } from './services/virtual-box-configuration.service';
|
||||
import { VirtualBoxService } from './services/virtual-box.service';
|
||||
import { VmwareConfigurationService } from './services/vmware-configuration.service';
|
||||
import { VmwareService } from './services/vmware.service';
|
||||
import { VpcsConfigurationService } from './services/vpcs-configuration.service';
|
||||
import { VpcsService } from './services/vpcs.service';
|
||||
import { NonNegativeValidator } from './validators/non-negative-validator';
|
||||
import { RotationValidator } from './validators/rotation-validator';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -277,330 +61,59 @@ import { RotationValidator } from './validators/rotation-validator';
|
||||
ServersComponent,
|
||||
AddServerDialogComponent,
|
||||
CreateSnapshotDialogComponent,
|
||||
SnapshotMenuItemComponent,
|
||||
ProjectsComponent,
|
||||
AddBlankProjectDialogComponent,
|
||||
ImportProjectDialogComponent,
|
||||
ConfirmationDialogComponent,
|
||||
DefaultLayoutComponent,
|
||||
ProgressDialogComponent,
|
||||
ContextMenuComponent,
|
||||
ContextConsoleMenuComponent,
|
||||
NodeContextMenuComponent,
|
||||
StartNodeActionComponent,
|
||||
StopNodeActionComponent,
|
||||
TemplateComponent,
|
||||
TemplateListDialogComponent,
|
||||
MoveLayerDownActionComponent,
|
||||
MoveLayerUpActionComponent,
|
||||
EditStyleActionComponent,
|
||||
EditTextActionComponent,
|
||||
DeleteActionComponent,
|
||||
DuplicateActionComponent,
|
||||
PacketFiltersActionComponent,
|
||||
StartCaptureActionComponent,
|
||||
StopCaptureActionComponent,
|
||||
ResumeLinkActionComponent,
|
||||
SuspendLinkActionComponent,
|
||||
SettingsComponent,
|
||||
PreferencesComponent,
|
||||
BundledServerFinderComponent,
|
||||
ProgressComponent,
|
||||
ServerDiscoveryComponent,
|
||||
ApplianceComponent,
|
||||
ApplianceListDialogComponent,
|
||||
NodeSelectInterfaceComponent,
|
||||
DrawLinkToolComponent,
|
||||
InstalledSoftwareComponent,
|
||||
DrawingAddedComponent,
|
||||
DrawingResizedComponent,
|
||||
TextAddedComponent,
|
||||
TextEditedComponent,
|
||||
NodeDraggedComponent,
|
||||
NodeLabelDraggedComponent,
|
||||
DrawingDraggedComponent,
|
||||
LinkCreatedComponent,
|
||||
InterfaceLabelDraggedComponent,
|
||||
InstallSoftwareComponent,
|
||||
StyleEditorDialogComponent,
|
||||
TextEditorDialogComponent,
|
||||
PacketFiltersDialogComponent,
|
||||
QemuPreferencesComponent,
|
||||
QemuVmTemplatesComponent,
|
||||
AddQemuVmTemplateComponent,
|
||||
QemuVmTemplateDetailsComponent,
|
||||
GeneralPreferencesComponent,
|
||||
VpcsPreferencesComponent,
|
||||
VpcsTemplatesComponent,
|
||||
AddVpcsTemplateComponent,
|
||||
VpcsTemplateDetailsComponent,
|
||||
VirtualBoxPreferencesComponent,
|
||||
VirtualBoxTemplatesComponent,
|
||||
VirtualBoxTemplateDetailsComponent,
|
||||
AddVirtualBoxTemplateComponent,
|
||||
BuiltInPreferencesComponent,
|
||||
EthernetHubsTemplatesComponent,
|
||||
EthernetHubsAddTemplateComponent,
|
||||
EthernetHubsTemplateDetailsComponent,
|
||||
CloudNodesTemplatesComponent,
|
||||
CloudNodesAddTemplateComponent,
|
||||
CloudNodesTemplateDetailsComponent,
|
||||
EthernetSwitchesTemplatesComponent,
|
||||
EthernetSwitchesAddTemplateComponent,
|
||||
EthernetSwitchesTemplateDetailsComponent,
|
||||
DynamipsPreferencesComponent,
|
||||
IosTemplatesComponent,
|
||||
IosTemplateDetailsComponent,
|
||||
AddIosTemplateComponent,
|
||||
SymbolsComponent,
|
||||
VmwarePreferencesComponent,
|
||||
VmwareTemplatesComponent,
|
||||
VmwareTemplateDetailsComponent,
|
||||
AddVmwareTemplateComponent,
|
||||
DeleteConfirmationDialogComponent,
|
||||
HelpDialogComponent,
|
||||
StartCaptureDialogComponent,
|
||||
DeleteTemplateComponent,
|
||||
DockerTemplatesComponent,
|
||||
AddDockerTemplateComponent,
|
||||
DockerTemplateDetailsComponent,
|
||||
IouTemplatesComponent,
|
||||
AddIouTemplateComponent,
|
||||
IouTemplateDetailsComponent,
|
||||
CopyQemuVmTemplateComponent,
|
||||
CopyIosTemplateComponent,
|
||||
CopyIouTemplateComponent,
|
||||
CopyDockerTemplateComponent,
|
||||
EmptyTemplatesListComponent,
|
||||
SymbolsMenuComponent,
|
||||
SearchFilter,
|
||||
DateFilter,
|
||||
NameFilter,
|
||||
DataSourceFilter,
|
||||
TemplateFilter,
|
||||
ProjectsFilter,
|
||||
ListOfSnapshotsComponent,
|
||||
CustomAdaptersComponent,
|
||||
NodesMenuComponent,
|
||||
AdbutlerComponent,
|
||||
ConsoleDeviceActionComponent,
|
||||
ShowNodeActionComponent,
|
||||
ConsoleComponent,
|
||||
NodesMenuComponent,
|
||||
ProjectMapMenuComponent,
|
||||
HelpComponent,
|
||||
ConfigEditorDialogComponent,
|
||||
EditConfigActionComponent,
|
||||
LogConsoleComponent,
|
||||
SaveProjectDialogComponent,
|
||||
TopologySummaryComponent,
|
||||
InfoDialogComponent,
|
||||
BringToFrontActionComponent,
|
||||
ExportConfigActionComponent,
|
||||
ImportConfigActionComponent,
|
||||
ConsoleDeviceActionBrowserComponent,
|
||||
ChangeSymbolDialogComponent,
|
||||
ChangeSymbolActionComponent,
|
||||
EditProjectDialogComponent,
|
||||
ReloadNodeActionComponent,
|
||||
SuspendNodeActionComponent,
|
||||
ConfigActionComponent,
|
||||
ConfiguratorDialogVpcsComponent,
|
||||
ConfiguratorDialogEthernetHubComponent,
|
||||
ConfiguratorDialogEthernetSwitchComponent,
|
||||
PortsComponent,
|
||||
ConfiguratorDialogSwitchComponent,
|
||||
ConfiguratorDialogVirtualBoxComponent,
|
||||
CustomAdaptersTableComponent,
|
||||
ConfiguratorDialogQemuComponent,
|
||||
ConfiguratorDialogCloudComponent,
|
||||
UdpTunnelsComponent,
|
||||
ConfiguratorDialogAtmSwitchComponent,
|
||||
ConfiguratorDialogVmwareComponent,
|
||||
ConfiguratorDialogIouComponent,
|
||||
ConfiguratorDialogIosComponent,
|
||||
ConfiguratorDialogDockerComponent,
|
||||
ConfiguratorDialogNatComponent,
|
||||
ConfiguratorDialogTracengComponent,
|
||||
AddTracengTemplateComponent,
|
||||
TracengPreferencesComponent,
|
||||
TracengTemplatesComponent,
|
||||
TracengTemplateDetailsComponent,
|
||||
QemuImageCreatorComponent,
|
||||
ChooseNameDialogComponent,
|
||||
StartCaptureOnStartedLinkActionComponent,
|
||||
LockActionComponent,
|
||||
NavigationDialogComponent,
|
||||
ScreenshotDialogComponent,
|
||||
PageNotFoundComponent,
|
||||
AlignHorizontallyActionComponent,
|
||||
AlignVerticallyActionComponent,
|
||||
ConfirmationBottomSheetComponent,
|
||||
ConfigDialogComponent,
|
||||
Gns3vmComponent,
|
||||
ConfigureGns3VMDialogComponent,
|
||||
ImportApplianceComponent,
|
||||
DirectLinkComponent,
|
||||
SystemStatusComponent,
|
||||
StatusInfoComponent,
|
||||
StatusChartComponent,
|
||||
OpenFileExplorerActionComponent,
|
||||
HttpConsoleActionComponent,
|
||||
WebConsoleComponent,
|
||||
ConsoleWrapperComponent,
|
||||
HttpConsoleNewTabActionComponent,
|
||||
WebConsoleFullWindowComponent,
|
||||
NewTemplateDialogComponent,
|
||||
ChangeHostnameActionComponent,
|
||||
ChangeHostnameDialogComponent,
|
||||
ApplianceInfoDialogComponent,
|
||||
InformationDialogComponent,
|
||||
TemplateNameDialogComponent,
|
||||
ConfigureCustomAdaptersDialogComponent,
|
||||
EditNetworkConfigurationDialogComponent
|
||||
],
|
||||
imports: [
|
||||
NgbModule.forRoot(),
|
||||
ToastyModule.forRoot(),
|
||||
BrowserModule,
|
||||
HttpModule,
|
||||
HttpClientModule,
|
||||
AppRoutingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
BrowserAnimationsModule,
|
||||
MatButtonModule,
|
||||
MatMenuModule,
|
||||
MatCardModule,
|
||||
MatToolbarModule,
|
||||
MatIconModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatTableModule,
|
||||
MatDialogModule,
|
||||
MatProgressBarModule,
|
||||
MatProgressSpinnerModule,
|
||||
CdkTableModule,
|
||||
CartographyModule,
|
||||
NgxElectronModule,
|
||||
FileUploadModule,
|
||||
MatSidenavModule,
|
||||
ResizableModule,
|
||||
DragAndDropModule,
|
||||
DragDropModule,
|
||||
NgxChildProcessModule,
|
||||
MATERIAL_IMPORTS,
|
||||
NgCircleProgressModule.forRoot(),
|
||||
OverlayModule,
|
||||
CartographyModule
|
||||
],
|
||||
providers: [
|
||||
SettingsService,
|
||||
{ provide: ErrorHandler, useClass: ToasterErrorHandler },
|
||||
D3Service,
|
||||
VersionService,
|
||||
ProjectService,
|
||||
SymbolService,
|
||||
ServerService,
|
||||
TemplateService,
|
||||
ApplianceService,
|
||||
NodeService,
|
||||
LinkService,
|
||||
DrawingService,
|
||||
IndexedDbService,
|
||||
HttpServer,
|
||||
SnapshotService,
|
||||
ProgressDialogService,
|
||||
ToasterService,
|
||||
ProgressService,
|
||||
ProjectWebServiceHandler,
|
||||
LinksDataSource,
|
||||
NodesDataSource,
|
||||
SymbolsDataSource,
|
||||
LogEventsDataSource,
|
||||
SelectionManager,
|
||||
InRectangleHelper,
|
||||
DrawingsDataSource,
|
||||
ServerErrorHandler,
|
||||
ServerDatabase,
|
||||
ProjectNameValidator,
|
||||
ToolsService,
|
||||
ServerSettingsService,
|
||||
QemuService,
|
||||
VpcsService,
|
||||
TemplateMocksService,
|
||||
VirtualBoxService,
|
||||
BuiltInTemplatesService,
|
||||
IosService,
|
||||
InstalledSoftwareService,
|
||||
ExternalSoftwareDefinitionService,
|
||||
PlatformService,
|
||||
IosConfigurationService,
|
||||
QemuConfigurationService,
|
||||
VirtualBoxConfigurationService,
|
||||
VpcsConfigurationService,
|
||||
BuiltInTemplatesConfigurationService,
|
||||
VmwareService,
|
||||
VmwareConfigurationService,
|
||||
DockerService,
|
||||
DockerConfigurationService,
|
||||
IouService,
|
||||
IouConfigurationService,
|
||||
RecentlyOpenedProjectService,
|
||||
ServerManagementService,
|
||||
MapScaleService,
|
||||
ConsoleService,
|
||||
DefaultConsoleService,
|
||||
NodeCreatedLabelStylesFixer,
|
||||
NonNegativeValidator,
|
||||
RotationValidator,
|
||||
MapSettingsService,
|
||||
InfoService,
|
||||
ComputeService,
|
||||
TracengService,
|
||||
PacketCaptureService,
|
||||
ProtocolHandlerService,
|
||||
NotificationService,
|
||||
Gns3vmService,
|
||||
ThemeService,
|
||||
GoogleAnalyticsService,
|
||||
NodeConsoleService,
|
||||
ServerResolve,
|
||||
ConsoleGuard,
|
||||
Title,
|
||||
ApplianceService,
|
||||
UpdatesService,
|
||||
ProgressDialogService
|
||||
],
|
||||
entryComponents: [
|
||||
AddServerDialogComponent,
|
||||
CreateSnapshotDialogComponent,
|
||||
ProgressDialogComponent,
|
||||
TemplateListDialogComponent,
|
||||
AddBlankProjectDialogComponent,
|
||||
ImportProjectDialogComponent,
|
||||
ConfirmationDialogComponent,
|
||||
StyleEditorDialogComponent,
|
||||
PacketFiltersDialogComponent,
|
||||
TextEditorDialogComponent,
|
||||
SymbolsComponent,
|
||||
DeleteConfirmationDialogComponent,
|
||||
HelpDialogComponent,
|
||||
StartCaptureDialogComponent,
|
||||
ConfigEditorDialogComponent,
|
||||
SaveProjectDialogComponent,
|
||||
InfoDialogComponent,
|
||||
ChangeSymbolDialogComponent,
|
||||
EditProjectDialogComponent,
|
||||
ConfigureGns3VMDialogComponent,
|
||||
ConfiguratorDialogVpcsComponent,
|
||||
ConfiguratorDialogEthernetHubComponent,
|
||||
ConfiguratorDialogEthernetSwitchComponent,
|
||||
ConfiguratorDialogSwitchComponent,
|
||||
ConfiguratorDialogVirtualBoxComponent,
|
||||
ConfiguratorDialogQemuComponent,
|
||||
ConfiguratorDialogCloudComponent,
|
||||
ConfiguratorDialogAtmSwitchComponent,
|
||||
ConfiguratorDialogVmwareComponent,
|
||||
ConfiguratorDialogIouComponent,
|
||||
ConfiguratorDialogIosComponent,
|
||||
ConfiguratorDialogDockerComponent,
|
||||
ConfiguratorDialogNatComponent,
|
||||
ConfiguratorDialogTracengComponent,
|
||||
QemuImageCreatorComponent,
|
||||
ChooseNameDialogComponent,
|
||||
NavigationDialogComponent,
|
||||
ScreenshotDialogComponent,
|
||||
ConfirmationBottomSheetComponent,
|
||||
ConfigDialogComponent,
|
||||
AdbutlerComponent,
|
||||
NewTemplateDialogComponent,
|
||||
ChangeHostnameDialogComponent,
|
||||
ApplianceInfoDialogComponent,
|
||||
ConfigureCustomAdaptersDialogComponent,
|
||||
EditNetworkConfigurationDialogComponent,
|
||||
ApplianceListDialogComponent
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule {
|
||||
constructor(protected _googleAnalyticsService: GoogleAnalyticsService) {}
|
||||
}
|
||||
export class AppModule { }
|
||||
|
@ -0,0 +1,21 @@
|
||||
<div mat-dialog-content>
|
||||
<div class="example-header">
|
||||
<mat-form-field floatPlaceholder="never">
|
||||
<input matInput #filter placeholder="Filter appliances">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<mat-table #table [dataSource]="dataSource">
|
||||
|
||||
<ng-container matColumnDef="name">
|
||||
<mat-header-cell *matHeaderCellDef> Name </mat-header-cell>
|
||||
<mat-cell *matCellDef="let row;"> <a (click)="addNode(row)" href='javascript:void(0);' class="table-link">{{row.name}}</a> </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
|
||||
</mat-table>
|
||||
</div>
|
||||
<div mat-dialog-actions align="end">
|
||||
<button mat-button (click)="onNoClick()" tabindex="-1" color="accent">Close</button>
|
||||
</div>
|
@ -0,0 +1,19 @@
|
||||
.example-header {
|
||||
min-height: 64px;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
padding: 8px 24px 0;
|
||||
font-size: 20px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
|
||||
.mat-table {
|
||||
overflow: auto;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
.mat-form-field {
|
||||
font-size: 16px;
|
||||
flex-grow: 1;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ApplianceListDialogComponent } from './appliance-list-dialog.component';
|
||||
|
||||
describe('ApplianceListDialogComponent', () => {
|
||||
let component: ApplianceListDialogComponent;
|
||||
let fixture: ComponentFixture<ApplianceListDialogComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ApplianceListDialogComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ApplianceListDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,104 @@
|
||||
import {Component, ElementRef, Inject, Input, OnInit, ViewChild} from '@angular/core';
|
||||
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material";
|
||||
import {DataSource} from "@angular/cdk/collections";
|
||||
import {Observable} from "rxjs/Observable";
|
||||
import {Appliance} from "../../shared/models/appliance";
|
||||
import {ApplianceService} from "../../shared/services/appliance.service";
|
||||
import {Server} from "../../shared/models/server";
|
||||
import {BehaviorSubject} from "rxjs/BehaviorSubject";
|
||||
|
||||
import 'rxjs/add/operator/startWith';
|
||||
import 'rxjs/add/observable/merge';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import 'rxjs/add/observable/fromEvent';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-appliance-list-dialog',
|
||||
templateUrl: './appliance-list-dialog.component.html',
|
||||
styleUrls: ['./appliance-list-dialog.component.scss']
|
||||
})
|
||||
export class ApplianceListDialogComponent implements OnInit {
|
||||
server: Server;
|
||||
applianceDatabase: ApplianceDatabase;
|
||||
dataSource: ApplianceDataSource;
|
||||
displayedColumns = ['name'];
|
||||
|
||||
@ViewChild('filter') filter: ElementRef;
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<ApplianceListDialogComponent>,
|
||||
private applianceService: ApplianceService,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any) {
|
||||
this.server = data['server'];
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.applianceDatabase = new ApplianceDatabase(this.server, this.applianceService);
|
||||
this.dataSource = new ApplianceDataSource(this.applianceDatabase);
|
||||
|
||||
Observable.fromEvent(this.filter.nativeElement, 'keyup')
|
||||
.debounceTime(150)
|
||||
.distinctUntilChanged()
|
||||
.subscribe(() => {
|
||||
if (!this.dataSource) { return; }
|
||||
this.dataSource.filter = this.filter.nativeElement.value;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
onNoClick(): void {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
addNode(appliance: Appliance): void {
|
||||
this.dialogRef.close(appliance);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class ApplianceDatabase {
|
||||
dataChange: BehaviorSubject<Appliance[]> = new BehaviorSubject<Appliance[]>([]);
|
||||
|
||||
get data(): Appliance[] {
|
||||
return this.dataChange.value;
|
||||
}
|
||||
|
||||
constructor(private server: Server, private applianceService: ApplianceService) {
|
||||
this.applianceService.list(this.server).subscribe((appliances: Appliance[]) => {
|
||||
this.dataChange.next(appliances);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export class ApplianceDataSource extends DataSource<Appliance> {
|
||||
filterChange = new BehaviorSubject('');
|
||||
|
||||
get filter(): string { return this.filterChange.value; }
|
||||
set filter(filter: string) { this.filterChange.next(filter); }
|
||||
|
||||
constructor(private applianceDatabase: ApplianceDatabase) {
|
||||
super();
|
||||
}
|
||||
|
||||
connect(): Observable<Appliance[]> {
|
||||
const displayDataChanges = [
|
||||
this.applianceDatabase.dataChange,
|
||||
this.filterChange,
|
||||
];
|
||||
|
||||
return Observable.merge(...displayDataChanges).map(() => {
|
||||
return this.applianceDatabase.data.slice().filter((item: Appliance) => {
|
||||
const searchStr = (item.name).toLowerCase();
|
||||
return searchStr.indexOf(this.filter.toLowerCase()) !== -1;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
disconnect() {}
|
||||
|
||||
}
|
3
src/app/appliance/appliance.component.html
Normal file
@ -0,0 +1,3 @@
|
||||
<button mat-icon-button (click)="listAppliancesModal()">
|
||||
<mat-icon>add_to_queue</mat-icon>
|
||||
</button>
|
25
src/app/appliance/appliance.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ApplianceComponent } from './appliance.component';
|
||||
|
||||
describe('ApplianceComponent', () => {
|
||||
let component: ApplianceComponent;
|
||||
let fixture: ComponentFixture<ApplianceComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ApplianceComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ApplianceComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
36
src/app/appliance/appliance.component.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
|
||||
import {MatDialog} from "@angular/material";
|
||||
import {ApplianceListDialogComponent} from "./appliance-list-dialog/appliance-list-dialog.component";
|
||||
|
||||
import {Server} from "../shared/models/server";
|
||||
|
||||
@Component({
|
||||
selector: 'app-appliance',
|
||||
templateUrl: './appliance.component.html',
|
||||
styleUrls: ['./appliance.component.scss']
|
||||
})
|
||||
export class ApplianceComponent implements OnInit {
|
||||
@Input() server: Server;
|
||||
@Output() onNodeCreation = new EventEmitter<any>();
|
||||
|
||||
constructor(private dialog: MatDialog) { }
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
listAppliancesModal() {
|
||||
const dialogRef = this.dialog.open(ApplianceListDialogComponent, {
|
||||
width: '600px',
|
||||
height: '560px',
|
||||
data: {
|
||||
'server': this.server
|
||||
}
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((appliance: AppendMode) => {
|
||||
if (appliance !== null) {
|
||||
this.onNodeCreation.emit(appliance);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +0,0 @@
|
||||
import { DraggableComponent } from './components/experimental-map/draggable/draggable.component';
|
||||
import { DrawingComponent } from './components/experimental-map/drawing/drawing.component';
|
||||
import { EllipseComponent } from './components/experimental-map/drawing/drawings/ellipse/ellipse.component';
|
||||
import { ImageComponent } from './components/experimental-map/drawing/drawings/image/image.component';
|
||||
import { LineComponent } from './components/experimental-map/drawing/drawings/line/line.component';
|
||||
import { RectComponent } from './components/experimental-map/drawing/drawings/rect/rect.component';
|
||||
import { TextComponent } from './components/experimental-map/drawing/drawings/text/text.component';
|
||||
import { InterfaceLabelComponent } from './components/experimental-map/interface-label/interface-label.component';
|
||||
import { LinkComponent } from './components/experimental-map/link/link.component';
|
||||
import { NodeComponent } from './components/experimental-map/node/node.component';
|
||||
import { SelectionComponent } from './components/experimental-map/selection/selection.component';
|
||||
import { StatusComponent } from './components/experimental-map/status/status.component';
|
||||
|
||||
export const ANGULAR_MAP_DECLARATIONS = [
|
||||
NodeComponent,
|
||||
LinkComponent,
|
||||
StatusComponent,
|
||||
DrawingComponent,
|
||||
EllipseComponent,
|
||||
ImageComponent,
|
||||
LineComponent,
|
||||
RectComponent,
|
||||
TextComponent,
|
||||
DraggableComponent,
|
||||
SelectionComponent,
|
||||
InterfaceLabelComponent,
|
||||
];
|
@ -1,128 +1,12 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { ANGULAR_MAP_DECLARATIONS } from './angular-map.imports';
|
||||
import { D3MapComponent } from './components/d3-map/d3-map.component';
|
||||
import { DraggableSelectionComponent } from './components/draggable-selection/draggable-selection.component';
|
||||
import { DrawingAddingComponent } from './components/drawing-adding/drawing-adding.component';
|
||||
import { DrawingResizingComponent } from './components/drawing-resizing/drawing-resizing.component';
|
||||
import { ExperimentalMapComponent } from './components/experimental-map/experimental-map.component';
|
||||
import { SelectionControlComponent } from './components/selection-control/selection-control.component';
|
||||
import { SelectionSelectComponent } from './components/selection-select/selection-select.component';
|
||||
import { TextEditorComponent } from './components/text-editor/text-editor.component';
|
||||
import { DrawingToMapDrawingConverter } from './converters/map/drawing-to-map-drawing-converter';
|
||||
import { LabelToMapLabelConverter } from './converters/map/label-to-map-label-converter';
|
||||
import { LinkNodeToMapLinkNodeConverter } from './converters/map/link-node-to-map-link-node-converter';
|
||||
import { LinkToMapLinkConverter } from './converters/map/link-to-map-link-converter';
|
||||
import { MapDrawingToDrawingConverter } from './converters/map/map-drawing-to-drawing-converter';
|
||||
import { MapDrawingToSvgConverter } from './converters/map/map-drawing-to-svg-converter';
|
||||
import { MapLabelToLabelConverter } from './converters/map/map-label-to-label-converter';
|
||||
import { MapLinkNodeToLinkNodeConverter } from './converters/map/map-link-node-to-link-node-converter';
|
||||
import { MapLinkToLinkConverter } from './converters/map/map-link-to-link-converter';
|
||||
import { MapNodeToNodeConverter } from './converters/map/map-node-to-node-converter';
|
||||
import { MapPortToPortConverter } from './converters/map/map-port-to-port-converter';
|
||||
import { MapSymbolToSymbolConverter } from './converters/map/map-symbol-to-symbol-converter';
|
||||
import { NodeToMapNodeConverter } from './converters/map/node-to-map-node-converter';
|
||||
import { PortToMapPortConverter } from './converters/map/port-to-map-port-converter';
|
||||
import { SymbolToMapSymbolConverter } from './converters/map/symbol-to-map-symbol-converter';
|
||||
import { StylesToFontConverter } from './converters/styles-to-font-converter';
|
||||
import { D3_MAP_IMPORTS } from './d3-map.imports';
|
||||
import {
|
||||
MapDrawingsDataSource,
|
||||
MapLinksDataSource,
|
||||
MapNodesDataSource,
|
||||
MapSymbolsDataSource,
|
||||
} from './datasources/map-datasource';
|
||||
import { MovingCanvasDirective } from './directives/moving-canvas.directive';
|
||||
import { ZoomingCanvasDirective } from './directives/zooming-canvas.directive';
|
||||
import { DrawingsEventSource } from './events/drawings-event-source';
|
||||
import { LinksEventSource } from './events/links-event-source';
|
||||
import { MovingEventSource } from './events/moving-event-source';
|
||||
import { NodesEventSource } from './events/nodes-event-source';
|
||||
import { SelectionEventSource } from './events/selection-event-source';
|
||||
import { CanvasSizeDetector } from './helpers/canvas-size-detector';
|
||||
import { CssFixer } from './helpers/css-fixer';
|
||||
import { DefaultDrawingsFactory } from './helpers/default-drawings-factory';
|
||||
import { EllipseElementFactory } from './helpers/drawings-factory/ellipse-element-factory';
|
||||
import { LineElementFactory } from './helpers/drawings-factory/line-element-factory';
|
||||
import { RectangleElementFactory } from './helpers/drawings-factory/rectangle-element-factory';
|
||||
import { TextElementFactory } from './helpers/drawings-factory/text-element-factory';
|
||||
import { FontBBoxCalculator } from './helpers/font-bbox-calculator';
|
||||
import { FontFixer } from './helpers/font-fixer';
|
||||
import { MultiLinkCalculatorHelper } from './helpers/multi-link-calculator-helper';
|
||||
import { QtDasharrayFixer } from './helpers/qt-dasharray-fixer';
|
||||
import { SvgToDrawingConverter } from './helpers/svg-to-drawing-converter';
|
||||
import { GraphDataManager } from './managers/graph-data-manager';
|
||||
import { LayersManager } from './managers/layers-manager';
|
||||
import { MapSettingsManager } from './managers/map-settings-manager';
|
||||
import { Context } from './models/context';
|
||||
import { MapChangeDetectorRef } from './services/map-change-detector-ref';
|
||||
import { EthernetLinkWidget } from './widgets/links/ethernet-link';
|
||||
import { SerialLinkWidget } from './widgets/links/serial-link';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MapComponent } from './map/map.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, MatMenuModule, MatIconModule],
|
||||
declarations: [
|
||||
D3MapComponent,
|
||||
ExperimentalMapComponent,
|
||||
DrawingAddingComponent,
|
||||
DrawingResizingComponent,
|
||||
TextEditorComponent,
|
||||
...ANGULAR_MAP_DECLARATIONS,
|
||||
SelectionControlComponent,
|
||||
SelectionSelectComponent,
|
||||
DraggableSelectionComponent,
|
||||
MovingCanvasDirective,
|
||||
ZoomingCanvasDirective,
|
||||
imports: [
|
||||
CommonModule
|
||||
],
|
||||
providers: [
|
||||
CssFixer,
|
||||
FontFixer,
|
||||
DefaultDrawingsFactory,
|
||||
TextElementFactory,
|
||||
EllipseElementFactory,
|
||||
RectangleElementFactory,
|
||||
LineElementFactory,
|
||||
MultiLinkCalculatorHelper,
|
||||
SvgToDrawingConverter,
|
||||
QtDasharrayFixer,
|
||||
LayersManager,
|
||||
MapChangeDetectorRef,
|
||||
CanvasSizeDetector,
|
||||
Context,
|
||||
DrawingsEventSource,
|
||||
NodesEventSource,
|
||||
LinksEventSource,
|
||||
MovingEventSource,
|
||||
MapDrawingToSvgConverter,
|
||||
DrawingToMapDrawingConverter,
|
||||
LabelToMapLabelConverter,
|
||||
LinkToMapLinkConverter,
|
||||
LinkNodeToMapLinkNodeConverter,
|
||||
MapDrawingToDrawingConverter,
|
||||
MapLabelToLabelConverter,
|
||||
MapLinkNodeToLinkNodeConverter,
|
||||
MapLinkToLinkConverter,
|
||||
MapNodeToNodeConverter,
|
||||
MapPortToPortConverter,
|
||||
MapSymbolToSymbolConverter,
|
||||
NodeToMapNodeConverter,
|
||||
PortToMapPortConverter,
|
||||
SymbolToMapSymbolConverter,
|
||||
GraphDataManager,
|
||||
MapNodesDataSource,
|
||||
MapLinksDataSource,
|
||||
MapDrawingsDataSource,
|
||||
MapSymbolsDataSource,
|
||||
SelectionEventSource,
|
||||
MapSettingsManager,
|
||||
FontBBoxCalculator,
|
||||
StylesToFontConverter,
|
||||
EthernetLinkWidget,
|
||||
SerialLinkWidget,
|
||||
...D3_MAP_IMPORTS,
|
||||
],
|
||||
exports: [D3MapComponent, ExperimentalMapComponent],
|
||||
declarations: [MapComponent],
|
||||
exports: [MapComponent]
|
||||
})
|
||||
export class CartographyModule {}
|
||||
export class CartographyModule { }
|
||||
|
@ -1,48 +0,0 @@
|
||||
<svg id="map" #svg class="map" preserveAspectRatio="none" movingCanvas zoomingCanvas>
|
||||
<filter id="grayscale"><feColorMatrix id="feGrayscale" type="saturate" values="0" /></filter>
|
||||
<defs>
|
||||
<pattern
|
||||
attr.x="{{ drawingGridX }}"
|
||||
attr.y="{{ drawingGridY }}"
|
||||
id="gridDrawing"
|
||||
attr.width="{{ project.drawing_grid_size }}"
|
||||
attr.height="{{ project.drawing_grid_size }}"
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<path
|
||||
attr.d="M {{ project.drawing_grid_size }} 0 L 0 0 0 {{ project.drawing_grid_size }}"
|
||||
fill="none"
|
||||
stroke="silver"
|
||||
attr.stroke-width="{{ gridVisibility }}"
|
||||
/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<defs>
|
||||
<pattern
|
||||
attr.x="{{ nodeGridX }}"
|
||||
attr.y="{{ nodeGridY }}"
|
||||
id="gridNode"
|
||||
attr.width="{{ project.grid_size }}"
|
||||
attr.height="{{ project.grid_size }}"
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<path
|
||||
attr.d="M {{ project.grid_size }} 0 L 0 0 0 {{ project.grid_size }}"
|
||||
fill="none"
|
||||
stroke="DarkSlateGray"
|
||||
attr.stroke-width="{{ gridVisibility }}"
|
||||
/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="url(#gridDrawing)" />
|
||||
<rect width="100%" height="100%" fill="url(#gridNode)" />
|
||||
</svg>
|
||||
|
||||
<app-drawing-adding [svg]="svg"></app-drawing-adding>
|
||||
<app-drawing-resizing></app-drawing-resizing>
|
||||
<app-selection-control></app-selection-control>
|
||||
<app-selection-select></app-selection-select>
|
||||
<app-text-editor #textEditor [server]="server" [svg]="svg"></app-text-editor>
|
||||
<app-draggable-selection [svg]="svg"></app-draggable-selection>
|
Before Width: | Height: | Size: 1.6 KiB |
@ -1,3 +0,0 @@
|
||||
svg {
|
||||
display: block;
|
||||
}
|
@ -1,245 +0,0 @@
|
||||
import {
|
||||
Component,
|
||||
ElementRef,
|
||||
HostListener,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
SimpleChange,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { select, Selection } from 'd3-selection';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Link } from '../../../models/link';
|
||||
import { Project } from '../../../models/project';
|
||||
import { Server } from '../../../models/server';
|
||||
import { Symbol } from '../../../models/symbol';
|
||||
import { MapScaleService } from '../../../services/mapScale.service';
|
||||
import { MapSettingsService } from '../../../services/mapsettings.service';
|
||||
import { ToolsService } from '../../../services/tools.service';
|
||||
import { CanvasSizeDetector } from '../../helpers/canvas-size-detector';
|
||||
import { GraphDataManager } from '../../managers/graph-data-manager';
|
||||
import { MapSettingsManager } from '../../managers/map-settings-manager';
|
||||
import { Context } from '../../models/context';
|
||||
import { Drawing } from '../../models/drawing';
|
||||
import { Node } from '../../models/node';
|
||||
import { Size } from '../../models/size';
|
||||
import { MapChangeDetectorRef } from '../../services/map-change-detector-ref';
|
||||
import { MovingTool } from '../../tools/moving-tool';
|
||||
import { SelectionTool } from '../../tools/selection-tool';
|
||||
import { GraphLayout } from '../../widgets/graph-layout';
|
||||
import { InterfaceLabelWidget } from '../../widgets/interface-label';
|
||||
import { TextEditorComponent } from '../text-editor/text-editor.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-d3-map',
|
||||
templateUrl: './d3-map.component.html',
|
||||
styleUrls: ['./d3-map.component.scss'],
|
||||
})
|
||||
export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@Input() nodes: Node[] = [];
|
||||
@Input() links: Link[] = [];
|
||||
@Input() drawings: Drawing[] = [];
|
||||
@Input() symbols: Symbol[] = [];
|
||||
@Input() project: Project;
|
||||
@Input() server: Server;
|
||||
|
||||
@Input() width = 1500;
|
||||
@Input() height = 600;
|
||||
|
||||
@ViewChild('svg') svgRef: ElementRef;
|
||||
@ViewChild('textEditor') textEditor: TextEditorComponent;
|
||||
|
||||
private parentNativeElement: any;
|
||||
private svg: Selection<SVGSVGElement, any, null, undefined>;
|
||||
private onChangesDetected: Subscription;
|
||||
private subscriptions: Subscription[] = [];
|
||||
private drawLinkTool: boolean;
|
||||
protected settings = {
|
||||
show_interface_labels: true,
|
||||
};
|
||||
public gridVisibility: number = 0;
|
||||
|
||||
public nodeGridX: number = 0;
|
||||
public nodeGridY: number = 0;
|
||||
public drawingGridX: number = 0;
|
||||
public drawingGridY: number = 0;
|
||||
|
||||
constructor(
|
||||
private graphDataManager: GraphDataManager,
|
||||
public context: Context,
|
||||
private mapChangeDetectorRef: MapChangeDetectorRef,
|
||||
private canvasSizeDetector: CanvasSizeDetector,
|
||||
private mapSettings: MapSettingsManager,
|
||||
protected element: ElementRef,
|
||||
protected interfaceLabelWidget: InterfaceLabelWidget,
|
||||
protected selectionToolWidget: SelectionTool,
|
||||
protected movingToolWidget: MovingTool,
|
||||
public graphLayout: GraphLayout,
|
||||
private toolsService: ToolsService,
|
||||
private mapScaleService: MapScaleService,
|
||||
private mapSettingsService: MapSettingsService
|
||||
) {
|
||||
this.parentNativeElement = element.nativeElement;
|
||||
}
|
||||
|
||||
@Input('show-interface-labels')
|
||||
set showInterfaceLabels(value) {
|
||||
if (value && !this.mapSettingsService.integrateLinkLabelsToLinks) {
|
||||
this.settings.show_interface_labels = true;
|
||||
this.interfaceLabelWidget.setEnabled(true);
|
||||
} else {
|
||||
this.settings.show_interface_labels = false;
|
||||
this.interfaceLabelWidget.setEnabled(false);
|
||||
}
|
||||
|
||||
this.mapChangeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
@Input('readonly') set readonly(value) {
|
||||
this.mapSettings.isReadOnly = value;
|
||||
}
|
||||
|
||||
resize(val: boolean) {
|
||||
if (val) {
|
||||
this.svg.attr('height', window.innerHeight + window.scrollY - 16);
|
||||
} else {
|
||||
let heightOfProjectWindow = window.innerHeight - 16;
|
||||
|
||||
if (this.height > heightOfProjectWindow) {
|
||||
this.svg.attr('height', this.height);
|
||||
} else {
|
||||
this.svg.attr('height', heightOfProjectWindow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
|
||||
if (
|
||||
(changes['width'] && !changes['width'].isFirstChange()) ||
|
||||
(changes['height'] && !changes['height'].isFirstChange()) ||
|
||||
(changes['drawings'] && !changes['drawings'].isFirstChange()) ||
|
||||
(changes['nodes'] && !changes['nodes'].isFirstChange()) ||
|
||||
(changes['links'] && !changes['links'].isFirstChange()) ||
|
||||
(changes['symbols'] && !changes['symbols'].isFirstChange())
|
||||
) {
|
||||
if (this.svg.empty && !this.svg.empty()) {
|
||||
if (changes['symbols']) {
|
||||
this.onSymbolsChange(changes['symbols']);
|
||||
}
|
||||
this.changeLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.parentNativeElement !== null) {
|
||||
this.createGraph(this.parentNativeElement);
|
||||
}
|
||||
this.context.size = this.getSize();
|
||||
|
||||
this.onChangesDetected = this.mapChangeDetectorRef.changesDetected.subscribe(() => {
|
||||
if (this.mapChangeDetectorRef.hasBeenDrawn) {
|
||||
this.redraw();
|
||||
}
|
||||
});
|
||||
|
||||
this.subscriptions.push(this.mapScaleService.scaleChangeEmitter.subscribe((value: number) => this.redraw()));
|
||||
|
||||
this.subscriptions.push(
|
||||
this.toolsService.isMovingToolActivated.subscribe((value: boolean) => {
|
||||
this.mapChangeDetectorRef.detectChanges();
|
||||
})
|
||||
);
|
||||
|
||||
this.subscriptions.push(
|
||||
this.toolsService.isSelectionToolActivated.subscribe((value: boolean) => {
|
||||
this.selectionToolWidget.setEnabled(value);
|
||||
this.mapChangeDetectorRef.detectChanges();
|
||||
})
|
||||
);
|
||||
|
||||
this.subscriptions.push(
|
||||
this.toolsService.isDrawLinkToolActivated.subscribe((value: boolean) => {
|
||||
this.drawLinkTool = value;
|
||||
})
|
||||
);
|
||||
|
||||
this.gridVisibility = localStorage.getItem('gridVisibility') === 'true' ? 1 : 0;
|
||||
this.mapSettingsService.isScrollDisabled.subscribe((val) => this.resize(val));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.graphLayout.disconnect(this.svg);
|
||||
this.onChangesDetected.unsubscribe();
|
||||
this.subscriptions.forEach((subscription: Subscription) => {
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
}
|
||||
|
||||
public applyMapSettingsChanges() {
|
||||
this.redraw();
|
||||
}
|
||||
|
||||
public createGraph(domElement: HTMLElement) {
|
||||
const rootElement = select(domElement);
|
||||
this.svg = rootElement.select<SVGSVGElement>('svg');
|
||||
this.graphLayout.connect(this.svg, this.context);
|
||||
this.graphLayout.draw(this.svg, this.context);
|
||||
this.mapChangeDetectorRef.hasBeenDrawn = true;
|
||||
}
|
||||
|
||||
public getSize(): Size {
|
||||
return this.canvasSizeDetector.getOptimalSize(this.width, this.height);
|
||||
}
|
||||
|
||||
private changeLayout() {
|
||||
if (this.parentNativeElement != null) {
|
||||
this.context.size = this.getSize();
|
||||
}
|
||||
|
||||
this.redraw();
|
||||
}
|
||||
|
||||
private onSymbolsChange(change: SimpleChange) {
|
||||
this.graphDataManager.setSymbols(this.symbols);
|
||||
}
|
||||
|
||||
private redraw() {
|
||||
this.updateGrid();
|
||||
|
||||
this.graphDataManager.setNodes(this.nodes);
|
||||
this.graphDataManager.setLinks(this.links);
|
||||
this.graphDataManager.setDrawings(this.drawings);
|
||||
this.graphLayout.draw(this.svg, this.context);
|
||||
this.textEditor.activateTextEditingForDrawings();
|
||||
this.textEditor.activateTextEditingForNodeLabels();
|
||||
this.mapSettingsService.mapRenderedEmitter.emit(true);
|
||||
}
|
||||
|
||||
updateGrid() {
|
||||
if (this.project.grid_size && this.project.grid_size > 0)
|
||||
this.nodeGridX =
|
||||
this.project.scene_width / 2 -
|
||||
Math.floor(this.project.scene_width / 2 / this.project.grid_size) * this.project.grid_size;
|
||||
if (this.project.grid_size && this.project.grid_size > 0)
|
||||
this.nodeGridY =
|
||||
this.project.scene_height / 2 -
|
||||
Math.floor(this.project.scene_height / 2 / this.project.grid_size) * this.project.grid_size;
|
||||
|
||||
if (this.project.drawing_grid_size && this.project.drawing_grid_size > 0)
|
||||
this.drawingGridX =
|
||||
this.project.scene_width / 2 -
|
||||
Math.floor(this.project.scene_width / 2 / this.project.drawing_grid_size) * this.project.drawing_grid_size;
|
||||
if (this.project.drawing_grid_size && this.project.drawing_grid_size > 0)
|
||||
this.drawingGridY =
|
||||
this.project.scene_height / 2 -
|
||||
Math.floor(this.project.scene_height / 2 / this.project.drawing_grid_size) * this.project.drawing_grid_size;
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(event) {
|
||||
this.changeLayout();
|
||||
}
|
||||
}
|
@ -1,547 +0,0 @@
|
||||
import { EventEmitter } from '@angular/core';
|
||||
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { select } from 'd3-selection';
|
||||
import { MapSettingsService } from '../../../services/mapsettings.service';
|
||||
import { DraggableDrag, DraggableEnd, DraggableStart } from '../../events/draggable';
|
||||
import { DrawingsEventSource } from '../../events/drawings-event-source';
|
||||
import { LinksEventSource } from '../../events/links-event-source';
|
||||
import { NodesEventSource } from '../../events/nodes-event-source';
|
||||
import { GraphDataManager } from '../../managers/graph-data-manager';
|
||||
import { MockedGraphDataManager } from '../../managers/graph-data-manager.spec';
|
||||
import { SelectionManager } from '../../managers/selection-manager';
|
||||
import { SelectionManagerMock } from '../../managers/selection-manager.spec';
|
||||
import { MapDrawing } from '../../models/map/map-drawing';
|
||||
import { MapLabel } from '../../models/map/map-label';
|
||||
import { MapLink } from '../../models/map/map-link';
|
||||
import { MapLinkNode } from '../../models/map/map-link-node';
|
||||
import { MapNode } from '../../models/map/map-node';
|
||||
import { DrawingsWidget } from '../../widgets/drawings';
|
||||
import { InterfaceLabelWidget } from '../../widgets/interface-label';
|
||||
import { LabelWidget } from '../../widgets/label';
|
||||
import { LinksWidget } from '../../widgets/links';
|
||||
import { NodesWidget } from '../../widgets/nodes';
|
||||
import { DraggableSelectionComponent } from './draggable-selection.component';
|
||||
|
||||
describe('DraggableSelectionComponent', () => {
|
||||
let component: DraggableSelectionComponent;
|
||||
let fixture: ComponentFixture<DraggableSelectionComponent>;
|
||||
let mockedGraphDataManager: MockedGraphDataManager;
|
||||
let nodesStartEventEmitter: EventEmitter<DraggableStart<MapNode>>;
|
||||
let nodesDragEventEmitter: EventEmitter<DraggableDrag<MapNode>>;
|
||||
let nodesEndEventEmitter: EventEmitter<DraggableEnd<MapNode>>;
|
||||
|
||||
let drawingsStartEventEmitter: EventEmitter<DraggableStart<MapDrawing>>;
|
||||
let drawingsDragEventEmitter: EventEmitter<DraggableDrag<MapDrawing>>;
|
||||
let drawingsEndEventEmitter: EventEmitter<DraggableEnd<MapDrawing>>;
|
||||
|
||||
let labelStartEventEmitter: EventEmitter<DraggableStart<MapLabel>>;
|
||||
let labelDragEventEmitter: EventEmitter<DraggableDrag<MapLabel>>;
|
||||
let labelEndEventEmitter: EventEmitter<DraggableEnd<MapLabel>>;
|
||||
|
||||
let interfaceLabelStartEventEmitter: EventEmitter<DraggableStart<MapLinkNode>>;
|
||||
let interfaceLabelDragEventEmitter: EventEmitter<DraggableDrag<MapLinkNode>>;
|
||||
let interfaceLabelEndEventEmitter: EventEmitter<DraggableEnd<MapLinkNode>>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
mockedGraphDataManager = new MockedGraphDataManager();
|
||||
|
||||
nodesStartEventEmitter = new EventEmitter<DraggableStart<MapNode>>();
|
||||
nodesDragEventEmitter = new EventEmitter<DraggableDrag<MapNode>>();
|
||||
nodesEndEventEmitter = new EventEmitter<DraggableEnd<MapNode>>();
|
||||
|
||||
drawingsStartEventEmitter = new EventEmitter<DraggableStart<MapDrawing>>();
|
||||
drawingsDragEventEmitter = new EventEmitter<DraggableDrag<MapDrawing>>();
|
||||
drawingsEndEventEmitter = new EventEmitter<DraggableEnd<MapDrawing>>();
|
||||
|
||||
labelStartEventEmitter = new EventEmitter<DraggableStart<MapLabel>>();
|
||||
labelDragEventEmitter = new EventEmitter<DraggableDrag<MapLabel>>();
|
||||
labelEndEventEmitter = new EventEmitter<DraggableEnd<MapLabel>>();
|
||||
|
||||
interfaceLabelStartEventEmitter = new EventEmitter<DraggableStart<MapLinkNode>>();
|
||||
interfaceLabelDragEventEmitter = new EventEmitter<DraggableDrag<MapLinkNode>>();
|
||||
interfaceLabelEndEventEmitter = new EventEmitter<DraggableEnd<MapLinkNode>>();
|
||||
|
||||
const nodesWidgetStub = {
|
||||
redrawNode: () => {},
|
||||
draggable: {
|
||||
start: nodesStartEventEmitter,
|
||||
drag: nodesDragEventEmitter,
|
||||
end: nodesEndEventEmitter,
|
||||
},
|
||||
};
|
||||
|
||||
const drawingsWidgetStub = {
|
||||
redrawDrawing: () => {},
|
||||
draggable: {
|
||||
start: drawingsStartEventEmitter,
|
||||
drag: drawingsDragEventEmitter,
|
||||
end: drawingsEndEventEmitter,
|
||||
},
|
||||
};
|
||||
const linksWidgetStub = {
|
||||
redrawLink: () => {},
|
||||
};
|
||||
|
||||
const labelWidgetStub = {
|
||||
redrawLabel: () => {},
|
||||
draggable: {
|
||||
start: labelStartEventEmitter,
|
||||
drag: labelDragEventEmitter,
|
||||
end: labelEndEventEmitter,
|
||||
},
|
||||
};
|
||||
|
||||
const interfaceLabelWidgetStub = {
|
||||
draggable: {
|
||||
start: interfaceLabelStartEventEmitter,
|
||||
drag: interfaceLabelDragEventEmitter,
|
||||
end: interfaceLabelEndEventEmitter,
|
||||
},
|
||||
};
|
||||
|
||||
const nodesEventSourceStub = {
|
||||
dragged: { emit: () => {} },
|
||||
labelDragged: { emit: () => {} },
|
||||
};
|
||||
const drawingsEventSourceStub = {
|
||||
dragged: { emit: () => {} },
|
||||
};
|
||||
const linksEventSourceStub = {
|
||||
interfaceDragged: { emit: () => {} },
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{ provide: NodesWidget, useValue: nodesWidgetStub },
|
||||
{ provide: DrawingsWidget, useValue: drawingsWidgetStub },
|
||||
{ provide: LinksWidget, useValue: linksWidgetStub },
|
||||
{ provide: LabelWidget, useValue: labelWidgetStub },
|
||||
{ provide: InterfaceLabelWidget, useValue: interfaceLabelWidgetStub },
|
||||
{ provide: SelectionManager, useValue: new SelectionManagerMock() },
|
||||
{ provide: NodesEventSource, useValue: nodesEventSourceStub },
|
||||
{ provide: DrawingsEventSource, useValue: drawingsEventSourceStub },
|
||||
{ provide: GraphDataManager, useValue: mockedGraphDataManager },
|
||||
{ provide: LinksEventSource, useValue: linksEventSourceStub },
|
||||
{ provide: MapSettingsService, useClass: MapSettingsService },
|
||||
],
|
||||
declarations: [DraggableSelectionComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DraggableSelectionComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('nodes dragging', () => {
|
||||
let nodesWidgetStub: NodesWidget;
|
||||
let linksWidgetStub: LinksWidget;
|
||||
let selectionManagerStub: SelectionManager;
|
||||
let node: MapNode;
|
||||
|
||||
beforeEach(() => {
|
||||
nodesWidgetStub = fixture.debugElement.injector.get(NodesWidget);
|
||||
linksWidgetStub = fixture.debugElement.injector.get(LinksWidget);
|
||||
selectionManagerStub = fixture.debugElement.injector.get(SelectionManager);
|
||||
node = new MapNode();
|
||||
node.id = 'nodeid';
|
||||
node.x = 1;
|
||||
node.y = 2;
|
||||
});
|
||||
|
||||
it('should select node when started dragging', fakeAsync(() => {
|
||||
nodesWidgetStub.draggable.start.emit(new DraggableStart<MapNode>(node));
|
||||
tick();
|
||||
expect(selectionManagerStub.getSelected().length).toEqual(1);
|
||||
}));
|
||||
|
||||
it('should ignore node when started dragging and node is in selection', fakeAsync(() => {
|
||||
selectionManagerStub.setSelected([node]);
|
||||
nodesWidgetStub.draggable.start.emit(new DraggableStart<MapNode>(node));
|
||||
tick();
|
||||
expect(selectionManagerStub.getSelected().length).toEqual(1);
|
||||
}));
|
||||
|
||||
it('should update node position when dragging', fakeAsync(() => {
|
||||
spyOn(nodesWidgetStub, 'redrawNode');
|
||||
selectionManagerStub.setSelected([node]);
|
||||
|
||||
const dragEvent = new DraggableDrag<MapNode>(node);
|
||||
dragEvent.dx = 10;
|
||||
dragEvent.dy = 20;
|
||||
|
||||
nodesWidgetStub.draggable.drag.emit(dragEvent);
|
||||
tick();
|
||||
expect(nodesWidgetStub.redrawNode).toHaveBeenCalledWith(select(fixture.componentInstance.svg), node);
|
||||
expect(node.x).toEqual(11);
|
||||
expect(node.y).toEqual(22);
|
||||
}));
|
||||
|
||||
it('should redraw related links target when dragging node', fakeAsync(() => {
|
||||
spyOn(nodesWidgetStub, 'redrawNode');
|
||||
spyOn(linksWidgetStub, 'redrawLink');
|
||||
const link = new MapLink();
|
||||
link.target = node;
|
||||
mockedGraphDataManager.setLinks([link]);
|
||||
selectionManagerStub.setSelected([node]);
|
||||
nodesWidgetStub.draggable.drag.emit(new DraggableDrag<MapNode>(node));
|
||||
|
||||
tick();
|
||||
expect(linksWidgetStub.redrawLink).toHaveBeenCalledWith(select(fixture.componentInstance.svg), link);
|
||||
}));
|
||||
|
||||
it('should redraw related links source when dragging node', fakeAsync(() => {
|
||||
spyOn(nodesWidgetStub, 'redrawNode');
|
||||
spyOn(linksWidgetStub, 'redrawLink');
|
||||
const link = new MapLink();
|
||||
link.source = node;
|
||||
mockedGraphDataManager.setLinks([link]);
|
||||
selectionManagerStub.setSelected([node]);
|
||||
nodesWidgetStub.draggable.drag.emit(new DraggableDrag<MapNode>(node));
|
||||
|
||||
tick();
|
||||
expect(linksWidgetStub.redrawLink).toHaveBeenCalledWith(select(fixture.componentInstance.svg), link);
|
||||
}));
|
||||
|
||||
it('should emit event when node stopped dragging', fakeAsync(() => {
|
||||
const nodesEventSourceStub = fixture.debugElement.injector.get(NodesEventSource);
|
||||
const spyDragged = spyOn(nodesEventSourceStub.dragged, 'emit');
|
||||
|
||||
selectionManagerStub.setSelected([node]);
|
||||
const dragEvent = new DraggableEnd<MapNode>(node);
|
||||
dragEvent.dx = 10;
|
||||
dragEvent.dy = 20;
|
||||
|
||||
nodesWidgetStub.draggable.end.emit(dragEvent);
|
||||
tick();
|
||||
expect(nodesEventSourceStub.dragged.emit).toHaveBeenCalled();
|
||||
expect(spyDragged.calls.mostRecent().args[0].datum).toEqual(node);
|
||||
expect(spyDragged.calls.mostRecent().args[0].dx).toEqual(10);
|
||||
expect(spyDragged.calls.mostRecent().args[0].dy).toEqual(20);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('drawings dragging', () => {
|
||||
let drawingsWidgetStub: DrawingsWidget;
|
||||
let selectionManagerStub: SelectionManager;
|
||||
let drawing: MapDrawing;
|
||||
|
||||
beforeEach(() => {
|
||||
drawingsWidgetStub = fixture.debugElement.injector.get(DrawingsWidget);
|
||||
selectionManagerStub = fixture.debugElement.injector.get(SelectionManager);
|
||||
drawing = new MapDrawing();
|
||||
drawing.id = 'drawingid';
|
||||
drawing.x = 1;
|
||||
drawing.y = 2;
|
||||
});
|
||||
|
||||
it('should select drawing when started dragging', fakeAsync(() => {
|
||||
drawingsWidgetStub.draggable.start.emit(new DraggableStart<MapDrawing>(drawing));
|
||||
tick();
|
||||
expect(selectionManagerStub.getSelected().length).toEqual(1);
|
||||
}));
|
||||
|
||||
it('should ignore drawing when started dragging and node is in selection', fakeAsync(() => {
|
||||
selectionManagerStub.setSelected([drawing]);
|
||||
drawingsWidgetStub.draggable.start.emit(new DraggableStart<MapDrawing>(drawing));
|
||||
tick();
|
||||
expect(selectionManagerStub.getSelected().length).toEqual(1);
|
||||
}));
|
||||
|
||||
it('should update drawing position when dragging', fakeAsync(() => {
|
||||
spyOn(drawingsWidgetStub, 'redrawDrawing');
|
||||
selectionManagerStub.setSelected([drawing]);
|
||||
|
||||
const dragEvent = new DraggableDrag<MapDrawing>(drawing);
|
||||
dragEvent.dx = 10;
|
||||
dragEvent.dy = 20;
|
||||
|
||||
drawingsWidgetStub.draggable.drag.emit(dragEvent);
|
||||
tick();
|
||||
expect(drawingsWidgetStub.redrawDrawing).toHaveBeenCalledWith(select(fixture.componentInstance.svg), drawing);
|
||||
expect(drawing.x).toEqual(11);
|
||||
expect(drawing.y).toEqual(22);
|
||||
}));
|
||||
|
||||
it('should emit event when drawing stopped dragging', fakeAsync(() => {
|
||||
const drawingsEventSourceStub = fixture.debugElement.injector.get(DrawingsEventSource);
|
||||
const spyDragged = spyOn(drawingsEventSourceStub.dragged, 'emit');
|
||||
|
||||
selectionManagerStub.setSelected([drawing]);
|
||||
const dragEvent = new DraggableEnd<MapDrawing>(drawing);
|
||||
dragEvent.dx = 10;
|
||||
dragEvent.dy = 20;
|
||||
|
||||
drawingsWidgetStub.draggable.end.emit(dragEvent);
|
||||
tick();
|
||||
expect(drawingsEventSourceStub.dragged.emit).toHaveBeenCalled();
|
||||
expect(spyDragged.calls.mostRecent().args[0].datum).toEqual(drawing);
|
||||
expect(spyDragged.calls.mostRecent().args[0].dx).toEqual(10);
|
||||
expect(spyDragged.calls.mostRecent().args[0].dy).toEqual(20);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('labels dragging', () => {
|
||||
let labelWidgetStub: LabelWidget;
|
||||
let selectionManagerStub: SelectionManager;
|
||||
let label: MapLabel;
|
||||
|
||||
beforeEach(() => {
|
||||
labelWidgetStub = fixture.debugElement.injector.get(LabelWidget);
|
||||
selectionManagerStub = fixture.debugElement.injector.get(SelectionManager);
|
||||
label = new MapLabel();
|
||||
label.id = 'labelid';
|
||||
label.x = 1;
|
||||
label.y = 2;
|
||||
});
|
||||
|
||||
it('should select label when started dragging', fakeAsync(() => {
|
||||
labelWidgetStub.draggable.start.emit(new DraggableStart<MapLabel>(label));
|
||||
tick();
|
||||
expect(selectionManagerStub.getSelected().length).toEqual(1);
|
||||
}));
|
||||
|
||||
it('should ignore label when started dragging and node is in selection', fakeAsync(() => {
|
||||
selectionManagerStub.setSelected([label]);
|
||||
labelWidgetStub.draggable.start.emit(new DraggableStart<MapLabel>(label));
|
||||
tick();
|
||||
expect(selectionManagerStub.getSelected().length).toEqual(1);
|
||||
}));
|
||||
|
||||
it('should update label position when dragging', fakeAsync(() => {
|
||||
spyOn(labelWidgetStub, 'redrawLabel');
|
||||
selectionManagerStub.setSelected([label]);
|
||||
const node = new MapNode();
|
||||
node.id = 'nodeid';
|
||||
node.label = label;
|
||||
label.nodeId = node.id;
|
||||
|
||||
mockedGraphDataManager.setNodes([node]);
|
||||
|
||||
const dragEvent = new DraggableDrag<MapLabel>(label);
|
||||
dragEvent.dx = 10;
|
||||
dragEvent.dy = 20;
|
||||
|
||||
labelWidgetStub.draggable.drag.emit(dragEvent);
|
||||
tick();
|
||||
expect(labelWidgetStub.redrawLabel).toHaveBeenCalledWith(select(fixture.componentInstance.svg), label);
|
||||
expect(label.x).toEqual(11);
|
||||
expect(label.y).toEqual(22);
|
||||
}));
|
||||
|
||||
it('should not update label position when dragging and parent is selected', fakeAsync(() => {
|
||||
spyOn(labelWidgetStub, 'redrawLabel');
|
||||
const node = new MapNode();
|
||||
node.id = 'nodeid';
|
||||
node.label = label;
|
||||
label.nodeId = node.id;
|
||||
|
||||
selectionManagerStub.setSelected([label, node]);
|
||||
mockedGraphDataManager.setNodes([node]);
|
||||
|
||||
const dragEvent = new DraggableDrag<MapLabel>(label);
|
||||
dragEvent.dx = 10;
|
||||
dragEvent.dy = 20;
|
||||
|
||||
labelWidgetStub.draggable.drag.emit(dragEvent);
|
||||
tick();
|
||||
expect(labelWidgetStub.redrawLabel).not.toHaveBeenCalled();
|
||||
expect(label.x).toEqual(1);
|
||||
expect(label.y).toEqual(2);
|
||||
}));
|
||||
|
||||
it('should emit event when label stopped dragging', fakeAsync(() => {
|
||||
const nodesEventSourceStub = fixture.debugElement.injector.get(NodesEventSource);
|
||||
const spyDragged = spyOn(nodesEventSourceStub.labelDragged, 'emit');
|
||||
|
||||
selectionManagerStub.setSelected([label]);
|
||||
const dragEvent = new DraggableEnd<MapLabel>(label);
|
||||
dragEvent.dx = 10;
|
||||
dragEvent.dy = 20;
|
||||
|
||||
labelWidgetStub.draggable.end.emit(dragEvent);
|
||||
tick();
|
||||
expect(nodesEventSourceStub.labelDragged.emit).toHaveBeenCalled();
|
||||
expect(spyDragged.calls.mostRecent().args[0].datum).toEqual(label);
|
||||
expect(spyDragged.calls.mostRecent().args[0].dx).toEqual(10);
|
||||
expect(spyDragged.calls.mostRecent().args[0].dy).toEqual(20);
|
||||
}));
|
||||
|
||||
it('should not emit event when label stopped dragging and parent node is selected', fakeAsync(() => {
|
||||
const nodesEventSourceStub = fixture.debugElement.injector.get(NodesEventSource);
|
||||
spyOn(nodesEventSourceStub.labelDragged, 'emit');
|
||||
const node = new MapNode();
|
||||
node.id = 'nodeid';
|
||||
label.nodeId = node.id;
|
||||
|
||||
selectionManagerStub.setSelected([label, node]);
|
||||
const dragEvent = new DraggableEnd<MapLabel>(label);
|
||||
dragEvent.dx = 10;
|
||||
dragEvent.dy = 20;
|
||||
|
||||
labelWidgetStub.draggable.end.emit(dragEvent);
|
||||
tick();
|
||||
expect(nodesEventSourceStub.labelDragged.emit).not.toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('interfaces labels dragging', () => {
|
||||
let linksWidgetStub: LinksWidget;
|
||||
let interfaceLabelWidgetStub: InterfaceLabelWidget;
|
||||
let selectionManagerStub: SelectionManager;
|
||||
let linkNode: MapLinkNode;
|
||||
|
||||
beforeEach(() => {
|
||||
interfaceLabelWidgetStub = fixture.debugElement.injector.get(InterfaceLabelWidget);
|
||||
linksWidgetStub = fixture.debugElement.injector.get(LinksWidget);
|
||||
selectionManagerStub = fixture.debugElement.injector.get(SelectionManager);
|
||||
linkNode = new MapLinkNode();
|
||||
linkNode.label = new MapLabel();
|
||||
linkNode.label.x = 1;
|
||||
linkNode.label.y = 2;
|
||||
linkNode.id = 'linknodeid';
|
||||
});
|
||||
|
||||
it('should select interface label when started dragging', fakeAsync(() => {
|
||||
interfaceLabelWidgetStub.draggable.start.emit(new DraggableStart<MapLinkNode>(linkNode));
|
||||
tick();
|
||||
expect(selectionManagerStub.getSelected().length).toEqual(1);
|
||||
}));
|
||||
|
||||
it('should ignore interface label when started dragging and node is in selection', fakeAsync(() => {
|
||||
selectionManagerStub.setSelected([linkNode]);
|
||||
interfaceLabelWidgetStub.draggable.start.emit(new DraggableStart<MapLinkNode>(linkNode));
|
||||
tick();
|
||||
expect(selectionManagerStub.getSelected().length).toEqual(1);
|
||||
}));
|
||||
|
||||
it('should update interface label position when dragging first node', fakeAsync(() => {
|
||||
spyOn(linksWidgetStub, 'redrawLink');
|
||||
selectionManagerStub.setSelected([linkNode]);
|
||||
const node = new MapNode();
|
||||
node.id = 'nodeid';
|
||||
linkNode.nodeId = node.id;
|
||||
|
||||
const secondLinkNode = new MapLinkNode();
|
||||
secondLinkNode.label = new MapLabel();
|
||||
secondLinkNode.label.x = 1;
|
||||
secondLinkNode.label.y = 2;
|
||||
secondLinkNode.id = 'secondlinknodeid';
|
||||
|
||||
const link = new MapLink();
|
||||
link.nodes = [linkNode, secondLinkNode];
|
||||
|
||||
mockedGraphDataManager.setLinks([link]);
|
||||
|
||||
const dragEvent = new DraggableDrag<MapLinkNode>(linkNode);
|
||||
dragEvent.dx = 10;
|
||||
dragEvent.dy = 20;
|
||||
|
||||
interfaceLabelWidgetStub.draggable.drag.emit(dragEvent);
|
||||
tick();
|
||||
expect(linksWidgetStub.redrawLink).toHaveBeenCalledWith(select(fixture.componentInstance.svg), link);
|
||||
expect(linkNode.label.x).toEqual(11);
|
||||
expect(linkNode.label.y).toEqual(22);
|
||||
}));
|
||||
|
||||
it('should update interface label position when dragging second node', fakeAsync(() => {
|
||||
spyOn(linksWidgetStub, 'redrawLink');
|
||||
selectionManagerStub.setSelected([linkNode]);
|
||||
const node = new MapNode();
|
||||
node.id = 'nodeid';
|
||||
linkNode.nodeId = node.id;
|
||||
|
||||
const secondLinkNode = new MapLinkNode();
|
||||
secondLinkNode.label = new MapLabel();
|
||||
secondLinkNode.label.x = 1;
|
||||
secondLinkNode.label.y = 2;
|
||||
secondLinkNode.id = 'secondlinknodeid';
|
||||
|
||||
const link = new MapLink();
|
||||
link.nodes = [secondLinkNode, linkNode];
|
||||
|
||||
mockedGraphDataManager.setLinks([link]);
|
||||
|
||||
const dragEvent = new DraggableDrag<MapLinkNode>(linkNode);
|
||||
dragEvent.dx = 10;
|
||||
dragEvent.dy = 20;
|
||||
|
||||
interfaceLabelWidgetStub.draggable.drag.emit(dragEvent);
|
||||
tick();
|
||||
expect(linksWidgetStub.redrawLink).toHaveBeenCalledWith(select(fixture.componentInstance.svg), link);
|
||||
expect(linkNode.label.x).toEqual(11);
|
||||
expect(linkNode.label.y).toEqual(22);
|
||||
}));
|
||||
|
||||
it('should not update interface label position when dragging and parent node is selected', fakeAsync(() => {
|
||||
spyOn(linksWidgetStub, 'redrawLink');
|
||||
const node = new MapNode();
|
||||
node.id = 'nodeid';
|
||||
linkNode.nodeId = node.id;
|
||||
|
||||
selectionManagerStub.setSelected([linkNode, node]);
|
||||
|
||||
const secondLinkNode = new MapLinkNode();
|
||||
secondLinkNode.label = new MapLabel();
|
||||
secondLinkNode.label.x = 1;
|
||||
secondLinkNode.label.y = 2;
|
||||
secondLinkNode.id = 'secondlinknodeid';
|
||||
|
||||
const link = new MapLink();
|
||||
link.nodes = [linkNode, secondLinkNode];
|
||||
|
||||
mockedGraphDataManager.setLinks([link]);
|
||||
|
||||
const dragEvent = new DraggableDrag<MapLinkNode>(linkNode);
|
||||
dragEvent.dx = 10;
|
||||
dragEvent.dy = 20;
|
||||
|
||||
interfaceLabelWidgetStub.draggable.drag.emit(dragEvent);
|
||||
tick();
|
||||
expect(linksWidgetStub.redrawLink).not.toHaveBeenCalled();
|
||||
expect(linkNode.label.x).toEqual(1);
|
||||
expect(linkNode.label.y).toEqual(2);
|
||||
}));
|
||||
|
||||
it('should emit event when interface label stopped dragging', fakeAsync(() => {
|
||||
const linksEventSourceStub = fixture.debugElement.injector.get(LinksEventSource);
|
||||
const spyDragged = spyOn(linksEventSourceStub.interfaceDragged, 'emit');
|
||||
|
||||
selectionManagerStub.setSelected([linkNode]);
|
||||
const dragEvent = new DraggableEnd<MapLinkNode>(linkNode);
|
||||
dragEvent.dx = 10;
|
||||
dragEvent.dy = 20;
|
||||
|
||||
interfaceLabelWidgetStub.draggable.end.emit(dragEvent);
|
||||
tick();
|
||||
expect(linksEventSourceStub.interfaceDragged.emit).toHaveBeenCalled();
|
||||
expect(spyDragged.calls.mostRecent().args[0].datum).toEqual(linkNode);
|
||||
expect(spyDragged.calls.mostRecent().args[0].dx).toEqual(10);
|
||||
expect(spyDragged.calls.mostRecent().args[0].dy).toEqual(20);
|
||||
}));
|
||||
|
||||
it('should not emit event when interface label stopped dragging and parent node is selected', fakeAsync(() => {
|
||||
const linksEventSourceStub = fixture.debugElement.injector.get(LinksEventSource);
|
||||
spyOn(linksEventSourceStub.interfaceDragged, 'emit');
|
||||
|
||||
const node = new MapNode();
|
||||
node.id = 'nodeid';
|
||||
linkNode.nodeId = node.id;
|
||||
|
||||
selectionManagerStub.setSelected([linkNode, node]);
|
||||
const dragEvent = new DraggableEnd<MapLinkNode>(linkNode);
|
||||
dragEvent.dx = 10;
|
||||
dragEvent.dy = 20;
|
||||
|
||||
interfaceLabelWidgetStub.draggable.end.emit(dragEvent);
|
||||
tick();
|
||||
expect(linksEventSourceStub.interfaceDragged.emit).not.toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
});
|
@ -1,230 +0,0 @@
|
||||
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import { select } from 'd3-selection';
|
||||
import { merge, Subscription } from 'rxjs';
|
||||
import { MapSettingsService } from '../../../services/mapsettings.service';
|
||||
import { DraggableDrag, DraggableEnd, DraggableStart } from '../../events/draggable';
|
||||
import { DrawingsEventSource } from '../../events/drawings-event-source';
|
||||
import { DraggedDataEvent } from '../../events/event-source';
|
||||
import { LinksEventSource } from '../../events/links-event-source';
|
||||
import { NodesEventSource } from '../../events/nodes-event-source';
|
||||
import { GraphDataManager } from '../../managers/graph-data-manager';
|
||||
import { SelectionManager } from '../../managers/selection-manager';
|
||||
import { MapDrawing } from '../../models/map/map-drawing';
|
||||
import { MapLabel } from '../../models/map/map-label';
|
||||
import { MapLinkNode } from '../../models/map/map-link-node';
|
||||
import { MapNode } from '../../models/map/map-node';
|
||||
import { DrawingsWidget } from '../../widgets/drawings';
|
||||
import { InterfaceLabelWidget } from '../../widgets/interface-label';
|
||||
import { LabelWidget } from '../../widgets/label';
|
||||
import { LinksWidget } from '../../widgets/links';
|
||||
import { NodesWidget } from '../../widgets/nodes';
|
||||
|
||||
@Component({
|
||||
selector: 'app-draggable-selection',
|
||||
templateUrl: './draggable-selection.component.html',
|
||||
styleUrls: ['./draggable-selection.component.scss'],
|
||||
})
|
||||
export class DraggableSelectionComponent implements OnInit, OnDestroy {
|
||||
private start: Subscription;
|
||||
private drag: Subscription;
|
||||
private end: Subscription;
|
||||
private mapSettingsSubscription: Subscription;
|
||||
private isMapLocked: boolean = false;
|
||||
|
||||
@Input('svg') svg: SVGSVGElement;
|
||||
|
||||
constructor(
|
||||
private nodesWidget: NodesWidget,
|
||||
private drawingsWidget: DrawingsWidget,
|
||||
private linksWidget: LinksWidget,
|
||||
private labelWidget: LabelWidget,
|
||||
private interfaceWidget: InterfaceLabelWidget,
|
||||
private selectionManager: SelectionManager,
|
||||
private nodesEventSource: NodesEventSource,
|
||||
private drawingsEventSource: DrawingsEventSource,
|
||||
private graphDataManager: GraphDataManager,
|
||||
private linksEventSource: LinksEventSource,
|
||||
private mapSettingsService: MapSettingsService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
const svg = select(this.svg);
|
||||
|
||||
this.mapSettingsSubscription = this.mapSettingsService.isMapLocked.subscribe((value) => {
|
||||
this.isMapLocked = value;
|
||||
});
|
||||
|
||||
this.start = merge(
|
||||
this.nodesWidget.draggable.start,
|
||||
this.drawingsWidget.draggable.start,
|
||||
this.labelWidget.draggable.start,
|
||||
this.interfaceWidget.draggable.start
|
||||
).subscribe((evt: DraggableStart<any>) => {
|
||||
const selected = this.selectionManager.getSelected();
|
||||
if (evt.datum instanceof MapNode) {
|
||||
if (selected.filter((item) => item instanceof MapNode && item.id === evt.datum.id).length === 0) {
|
||||
this.selectionManager.setSelected([evt.datum]);
|
||||
}
|
||||
}
|
||||
|
||||
if (evt.datum instanceof MapDrawing) {
|
||||
if (selected.filter((item) => item instanceof MapDrawing && item.id === evt.datum.id).length === 0) {
|
||||
this.selectionManager.setSelected([evt.datum]);
|
||||
}
|
||||
}
|
||||
|
||||
if (evt.datum instanceof MapLabel) {
|
||||
if (selected.filter((item) => item instanceof MapLabel && item.id === evt.datum.id).length === 0) {
|
||||
this.selectionManager.setSelected([evt.datum]);
|
||||
}
|
||||
}
|
||||
|
||||
if (evt.datum instanceof MapLinkNode) {
|
||||
if (selected.filter((item) => item instanceof MapLinkNode && item.id === evt.datum.id).length === 0) {
|
||||
this.selectionManager.setSelected([evt.datum]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.drag = merge(
|
||||
this.nodesWidget.draggable.drag,
|
||||
this.drawingsWidget.draggable.drag,
|
||||
this.labelWidget.draggable.drag,
|
||||
this.interfaceWidget.draggable.drag
|
||||
).subscribe((evt: DraggableDrag<any>) => {
|
||||
if (!this.isMapLocked) {
|
||||
const selected = this.selectionManager.getSelected();
|
||||
// update nodes
|
||||
let mapNodes = selected.filter((item) => item instanceof MapNode);
|
||||
const lockedNodes = mapNodes.filter((item: MapNode) => item.locked);
|
||||
const selectedNodes = mapNodes.filter((item: MapNode) => !item.locked);
|
||||
selectedNodes.forEach((node: MapNode) => {
|
||||
node.x += evt.dx;
|
||||
node.y += evt.dy;
|
||||
|
||||
this.nodesWidget.redrawNode(svg, node);
|
||||
|
||||
const links = this.graphDataManager
|
||||
.getLinks()
|
||||
.filter(
|
||||
(link) =>
|
||||
(link.target !== undefined && link.target.id === node.id) ||
|
||||
(link.source !== undefined && link.source.id === node.id)
|
||||
);
|
||||
|
||||
links.forEach((link) => {
|
||||
this.linksWidget.redrawLink(svg, link);
|
||||
});
|
||||
});
|
||||
|
||||
// update drawings
|
||||
let mapDrawings = selected.filter((item) => item instanceof MapDrawing);
|
||||
const selectedDrawings = mapDrawings.filter((item: MapDrawing) => !item.locked);
|
||||
selectedDrawings.forEach((drawing: MapDrawing) => {
|
||||
drawing.x += evt.dx;
|
||||
drawing.y += evt.dy;
|
||||
this.drawingsWidget.redrawDrawing(svg, drawing);
|
||||
});
|
||||
|
||||
// update labels
|
||||
let mapLabels = selected.filter((item) => item instanceof MapLabel);
|
||||
const selectedLabels = mapLabels.filter(
|
||||
(item: MapLabel) => lockedNodes.filter((node) => node.id === item.nodeId).length === 0
|
||||
);
|
||||
selectedLabels.forEach((label: MapLabel) => {
|
||||
const isParentNodeSelected = selectedNodes.filter((node) => node.id === label.nodeId).length > 0;
|
||||
if (isParentNodeSelected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = this.graphDataManager.getNodes().filter((node) => node.id === label.nodeId)[0];
|
||||
node.label.x += evt.dx;
|
||||
node.label.y += evt.dy;
|
||||
this.labelWidget.redrawLabel(svg, label);
|
||||
});
|
||||
|
||||
// update interface labels
|
||||
let mapLinkNodes = selected.filter((item) => item instanceof MapLinkNode);
|
||||
const selectedLinkNodes = mapLinkNodes.filter(
|
||||
(item: MapLinkNode) => lockedNodes.filter((node) => node.id === item.nodeId).length === 0
|
||||
);
|
||||
selectedLinkNodes.forEach((interfaceLabel: MapLinkNode) => {
|
||||
const isParentNodeSelected = selectedNodes.filter((node) => node.id === interfaceLabel.nodeId).length > 0;
|
||||
if (isParentNodeSelected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const link = this.graphDataManager
|
||||
.getLinks()
|
||||
.filter((link) => link.nodes[0].id === interfaceLabel.id || link.nodes[1].id === interfaceLabel.id)[0];
|
||||
if (link.nodes[0].id === interfaceLabel.id) {
|
||||
link.nodes[0].label.x += evt.dx;
|
||||
link.nodes[0].label.y += evt.dy;
|
||||
}
|
||||
if (link.nodes[1].id === interfaceLabel.id) {
|
||||
link.nodes[1].label.x += evt.dx;
|
||||
link.nodes[1].label.y += evt.dy;
|
||||
}
|
||||
|
||||
this.linksWidget.redrawLink(svg, link);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.end = merge(
|
||||
this.nodesWidget.draggable.end,
|
||||
this.drawingsWidget.draggable.end,
|
||||
this.labelWidget.draggable.end,
|
||||
this.interfaceWidget.draggable.end
|
||||
).subscribe((evt: DraggableEnd<any>) => {
|
||||
if (!this.isMapLocked) {
|
||||
const selected = this.selectionManager.getSelected();
|
||||
|
||||
let mapNodes = selected.filter((item) => item instanceof MapNode);
|
||||
const lockedNodes = mapNodes.filter((item: MapNode) => item.locked);
|
||||
const selectedNodes = mapNodes.filter((item: MapNode) => !item.locked);
|
||||
selectedNodes.forEach((item: MapNode) => {
|
||||
this.nodesEventSource.dragged.emit(new DraggedDataEvent<MapNode>(item, evt.dx, evt.dy));
|
||||
});
|
||||
|
||||
let mapDrawings = selected.filter((item) => item instanceof MapDrawing);
|
||||
const selectedDrawings = mapDrawings.filter((item: MapDrawing) => !item.locked);
|
||||
selectedDrawings.forEach((item: MapDrawing) => {
|
||||
this.drawingsEventSource.dragged.emit(new DraggedDataEvent<MapDrawing>(item, evt.dx, evt.dy));
|
||||
});
|
||||
|
||||
let mapLabels = selected.filter((item) => item instanceof MapLabel);
|
||||
const selectedLabels = mapLabels.filter(
|
||||
(item: MapLabel) => lockedNodes.filter((node) => node.id === item.nodeId).length === 0
|
||||
);
|
||||
selectedLabels.forEach((label: MapLabel) => {
|
||||
const isParentNodeSelected = selectedNodes.filter((node) => node.id === label.nodeId).length > 0;
|
||||
if (isParentNodeSelected) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.nodesEventSource.labelDragged.emit(new DraggedDataEvent<MapLabel>(label, evt.dx, evt.dy));
|
||||
});
|
||||
|
||||
let mapLinkNodes = selected.filter((item) => item instanceof MapLinkNode);
|
||||
const selectedLinkNodes = mapLinkNodes.filter(
|
||||
(item: MapLinkNode) => lockedNodes.filter((node) => node.id === item.nodeId).length === 0
|
||||
);
|
||||
selectedLinkNodes.forEach((label: MapLinkNode) => {
|
||||
const isParentNodeSelected = selectedNodes.filter((node) => node.id === label.nodeId).length > 0;
|
||||
if (isParentNodeSelected) {
|
||||
return;
|
||||
}
|
||||
this.linksEventSource.interfaceDragged.emit(new DraggedDataEvent<MapLinkNode>(label, evt.dx, evt.dy));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.start.unsubscribe();
|
||||
this.drag.unsubscribe();
|
||||
this.end.unsubscribe();
|
||||
this.mapSettingsSubscription.unsubscribe();
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { DrawingsEventSource } from '../../events/drawings-event-source';
|
||||
import { Context } from '../../models/context';
|
||||
import { DrawingAddingComponent } from './drawing-adding.component';
|
||||
|
||||
describe('DrawingAddingComponent', () => {
|
||||
let component: DrawingAddingComponent;
|
||||
let fixture: ComponentFixture<DrawingAddingComponent>;
|
||||
let drawingsEventSource = new DrawingsEventSource();
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [NoopAnimationsModule],
|
||||
providers: [
|
||||
{ provide: DrawingsEventSource, useValue: drawingsEventSource },
|
||||
{ provide: Context, useClass: Context },
|
||||
],
|
||||
declarations: [DrawingAddingComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DrawingAddingComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should deactivate listener when none of the available drawings is selected', () => {
|
||||
spyOn(component, 'deactivate');
|
||||
|
||||
drawingsEventSource.selected.emit('');
|
||||
|
||||
expect(component.deactivate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should activate listener when drawing is selected', () => {
|
||||
spyOn(component, 'activate');
|
||||
|
||||
drawingsEventSource.selected.emit('rectangle');
|
||||
|
||||
expect(component.activate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
@ -1,51 +0,0 @@
|
||||
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { DrawingsEventSource } from '../../events/drawings-event-source';
|
||||
import { AddedDataEvent } from '../../events/event-source';
|
||||
import { Context } from '../../models/context';
|
||||
|
||||
@Component({
|
||||
selector: 'app-drawing-adding',
|
||||
templateUrl: './drawing-adding.component.html',
|
||||
styleUrls: ['./drawing-adding.component.scss'],
|
||||
})
|
||||
export class DrawingAddingComponent implements OnInit, OnDestroy {
|
||||
@Input('svg') svg: SVGSVGElement;
|
||||
|
||||
private mapListener: Function;
|
||||
private drawingSelected: Subscription;
|
||||
|
||||
constructor(private drawingsEventSource: DrawingsEventSource, private context: Context) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.drawingSelected = this.drawingsEventSource.selected.subscribe((evt) => {
|
||||
evt === '' ? this.deactivate() : this.activate();
|
||||
});
|
||||
}
|
||||
|
||||
activate() {
|
||||
let listener = (event: MouseEvent) => {
|
||||
let x =
|
||||
(event.pageX - (this.context.getZeroZeroTransformationPoint().x + this.context.transformation.x)) /
|
||||
this.context.transformation.k;
|
||||
let y =
|
||||
(event.pageY - (this.context.getZeroZeroTransformationPoint().y + this.context.transformation.y)) /
|
||||
this.context.transformation.k;
|
||||
|
||||
this.drawingsEventSource.pointToAddSelected.emit(new AddedDataEvent(x, y));
|
||||
this.deactivate();
|
||||
};
|
||||
|
||||
this.deactivate();
|
||||
this.mapListener = listener;
|
||||
this.svg.addEventListener('click', this.mapListener as EventListenerOrEventListenerObject);
|
||||
}
|
||||
|
||||
deactivate() {
|
||||
this.svg.removeEventListener('click', this.mapListener as EventListenerOrEventListenerObject);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.drawingSelected.unsubscribe();
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
import { EventEmitter } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { DrawingsEventSource } from '../../events/drawings-event-source';
|
||||
import { ResizingEnd } from '../../events/resizing';
|
||||
import { MapDrawing } from '../../models/map/map-drawing';
|
||||
import { DrawingsWidget } from '../../widgets/drawings';
|
||||
import { DrawingResizingComponent } from './drawing-resizing.component';
|
||||
|
||||
export class DrawingWidgetMock {
|
||||
resizingFinished = new EventEmitter<ResizingEnd<MapDrawing>>();
|
||||
evt: any;
|
||||
constructor() {}
|
||||
|
||||
emitEvent() {
|
||||
const evt = new ResizingEnd<MapDrawing>();
|
||||
evt.x = 0;
|
||||
evt.y = 0;
|
||||
evt.width = 10;
|
||||
evt.height = 10;
|
||||
evt.datum = {} as MapDrawing;
|
||||
|
||||
this.resizingFinished.emit(evt);
|
||||
}
|
||||
}
|
||||
|
||||
describe('DrawingResizingComponent', () => {
|
||||
let component: DrawingResizingComponent;
|
||||
let fixture: ComponentFixture<DrawingResizingComponent>;
|
||||
let drawingsWidgetMock = new DrawingWidgetMock();
|
||||
let drawingsEventSource = new DrawingsEventSource();
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [NoopAnimationsModule],
|
||||
providers: [
|
||||
{ provide: DrawingsWidget, useValue: drawingsWidgetMock },
|
||||
{ provide: DrawingsEventSource, useValue: drawingsEventSource },
|
||||
],
|
||||
declarations: [DrawingResizingComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DrawingResizingComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should emit event after size changes', () => {
|
||||
spyOn(drawingsEventSource.resized, 'emit');
|
||||
const evt = new ResizingEnd<MapDrawing>();
|
||||
evt.x = 0;
|
||||
evt.y = 0;
|
||||
evt.width = 10;
|
||||
evt.height = 10;
|
||||
evt.datum = {} as MapDrawing;
|
||||
|
||||
drawingsWidgetMock.emitEvent();
|
||||
|
||||
expect(drawingsEventSource.resized.emit).toHaveBeenCalled();
|
||||
});
|
||||
});
|
@ -1,30 +0,0 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { DrawingsEventSource } from '../../events/drawings-event-source';
|
||||
import { ResizedDataEvent } from '../../events/event-source';
|
||||
import { ResizingEnd } from '../../events/resizing';
|
||||
import { MapDrawing } from '../../models/map/map-drawing';
|
||||
import { DrawingsWidget } from '../../widgets/drawings';
|
||||
|
||||
@Component({
|
||||
selector: 'app-drawing-resizing',
|
||||
template: ` <ng-content></ng-content> `,
|
||||
styleUrls: ['./drawing-resizing.component.scss'],
|
||||
})
|
||||
export class DrawingResizingComponent implements OnInit, OnDestroy {
|
||||
resizingFinished: Subscription;
|
||||
|
||||
constructor(private drawingsWidget: DrawingsWidget, private drawingsEventSource: DrawingsEventSource) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.resizingFinished = this.drawingsWidget.resizingFinished.subscribe((evt: ResizingEnd<MapDrawing>) => {
|
||||
this.drawingsEventSource.resized.emit(
|
||||
new ResizedDataEvent<MapDrawing>(evt.datum, evt.x, evt.y, evt.width, evt.height)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.resizingFinished.unsubscribe();
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { DraggableComponent } from './draggable.component';
|
||||
|
||||
describe('DraggableComponent', () => {
|
||||
let component: DraggableComponent;
|
||||
let fixture: ComponentFixture<DraggableComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [DraggableComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DraggableComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -1,87 +0,0 @@
|
||||
import { AfterViewInit, Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { Point } from '../../../models/point';
|
||||
|
||||
export class DraggableDraggedEvent {
|
||||
constructor(public x: number, public y: number, public dx: number, public dy: number) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: '[app-draggable]',
|
||||
template: ` <ng-content></ng-content> `,
|
||||
styleUrls: ['./draggable.component.scss'],
|
||||
})
|
||||
export class DraggableComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
@Input('app-draggable') item: Point;
|
||||
@Output() dragging = new EventEmitter<DraggableDraggedEvent>();
|
||||
@Output() dragged = new EventEmitter<DraggableDraggedEvent>();
|
||||
|
||||
draggable: Subscription;
|
||||
|
||||
private startX: number;
|
||||
private startY: number;
|
||||
|
||||
private posX: number;
|
||||
private posY: number;
|
||||
|
||||
constructor(private elementRef: ElementRef) {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
ngAfterViewInit() {
|
||||
const down = Observable.fromEvent(this.elementRef.nativeElement, 'mousedown').do((e: MouseEvent) =>
|
||||
e.preventDefault()
|
||||
);
|
||||
|
||||
down.subscribe((e: MouseEvent) => {
|
||||
this.posX = this.item.x;
|
||||
this.posY = this.item.y;
|
||||
|
||||
this.startX = e.clientX;
|
||||
this.startY = e.clientY;
|
||||
});
|
||||
|
||||
const up = Observable.fromEvent(document, 'mouseup').do((e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
const mouseMove = Observable.fromEvent(document, 'mousemove').do((e: MouseEvent) => e.stopPropagation());
|
||||
|
||||
const scrollWindow = Observable.fromEvent(document, 'scroll').startWith({});
|
||||
|
||||
const move = Observable.combineLatest(mouseMove, scrollWindow);
|
||||
|
||||
const drag = down.mergeMap((md: MouseEvent) => {
|
||||
return move
|
||||
.map(([mm, s]) => mm)
|
||||
.do((mm: MouseEvent) => {
|
||||
const x = this.startX - mm.clientX;
|
||||
const y = this.startY - mm.clientY;
|
||||
|
||||
this.item.x = Math.round(this.posX - x);
|
||||
this.item.y = Math.round(this.posY - y);
|
||||
this.dragging.emit(new DraggableDraggedEvent(this.item.x, this.item.y, -x, -y));
|
||||
})
|
||||
.skipUntil(
|
||||
up.take(1).do((e: MouseEvent) => {
|
||||
const x = this.startX - e.clientX;
|
||||
const y = this.startY - e.clientY;
|
||||
|
||||
this.item.x = Math.round(this.posX - x);
|
||||
this.item.y = Math.round(this.posY - y);
|
||||
|
||||
this.dragged.emit(new DraggableDraggedEvent(this.item.x, this.item.y, -x, -y));
|
||||
})
|
||||
)
|
||||
.take(1);
|
||||
});
|
||||
|
||||
this.draggable = drag.subscribe((e: MouseEvent) => {
|
||||
// this.cd.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.draggable.unsubscribe();
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
<svg:g
|
||||
class="drawing"
|
||||
[attr.transform]="transformation"
|
||||
[app-draggable]="drawing"
|
||||
(dragging)="OnDragging($event)"
|
||||
(dragged)="OnDragged($event)"
|
||||
>
|
||||
<svg:g *ngIf="is(drawing.element, 'ellipse')" [app-ellipse]="drawing.element" />
|
||||
|
||||
<svg:g *ngIf="is(drawing.element, 'image')" [app-image]="drawing.element" />
|
||||
|
||||
<svg:g *ngIf="is(drawing.element, 'line')" [app-line]="drawing.element" />
|
||||
|
||||
<svg:g *ngIf="is(drawing.element, 'rect')" [app-rect]="drawing.element" />
|
||||
|
||||
<svg:g *ngIf="is(drawing.element, 'text')" [app-text]="drawing.element" />
|
||||
</svg:g>
|
Before Width: | Height: | Size: 563 B |
@ -1,23 +0,0 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { DrawingComponent } from './drawing.component';
|
||||
|
||||
describe('DrawingComponent', () => {
|
||||
let component: DrawingComponent;
|
||||
let fixture: ComponentFixture<DrawingComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [DrawingComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DrawingComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -1,69 +0,0 @@
|
||||
import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
|
||||
import { DrawingsEventSource } from '../../../events/drawings-event-source';
|
||||
import { DraggedDataEvent } from '../../../events/event-source';
|
||||
import { SvgToDrawingConverter } from '../../../helpers/svg-to-drawing-converter';
|
||||
import { EllipseElement } from '../../../models/drawings/ellipse-element';
|
||||
import { ImageElement } from '../../../models/drawings/image-element';
|
||||
import { LineElement } from '../../../models/drawings/line-element';
|
||||
import { RectElement } from '../../../models/drawings/rect-element';
|
||||
import { TextElement } from '../../../models/drawings/text-element';
|
||||
import { MapDrawing } from '../../../models/map/map-drawing';
|
||||
|
||||
@Component({
|
||||
selector: '[app-drawing]',
|
||||
templateUrl: './drawing.component.html',
|
||||
styleUrls: ['./drawing.component.scss'],
|
||||
})
|
||||
export class DrawingComponent implements OnInit {
|
||||
@Input('app-drawing') drawing: MapDrawing;
|
||||
|
||||
constructor(
|
||||
private svgToDrawingConverter: SvgToDrawingConverter,
|
||||
private drawingsEventSource: DrawingsEventSource,
|
||||
private cd: ChangeDetectorRef
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
try {
|
||||
this.drawing.element = this.svgToDrawingConverter.convert(this.drawing.svg);
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
OnDragging(evt) {
|
||||
this.drawing.x = evt.x;
|
||||
this.drawing.y = evt.y;
|
||||
this.cd.detectChanges();
|
||||
}
|
||||
|
||||
OnDragged(evt) {
|
||||
this.cd.detectChanges();
|
||||
this.drawingsEventSource.dragged.emit(new DraggedDataEvent<MapDrawing>(this.drawing, evt.dx, evt.dy));
|
||||
}
|
||||
|
||||
is(element, type: string) {
|
||||
if (!element) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type === 'ellipse') {
|
||||
return element instanceof EllipseElement;
|
||||
}
|
||||
if (type === 'image') {
|
||||
return element instanceof ImageElement;
|
||||
}
|
||||
if (type === 'line') {
|
||||
return element instanceof LineElement;
|
||||
}
|
||||
if (type === 'rect') {
|
||||
return element instanceof RectElement;
|
||||
}
|
||||
if (type === 'text') {
|
||||
return element instanceof TextElement;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get transformation() {
|
||||
return `translate(${this.drawing.x},${this.drawing.y}) rotate(${this.drawing.rotation})`;
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<svg:ellipse
|
||||
class="ellipse_element noselect"
|
||||
[attr.fill]="ellipse.fill"
|
||||
[attr.fill-opacity]="fill_opacity"
|
||||
[attr.stroke]="ellipse.stroke"
|
||||
[attr.stroke-width]="stroke_width"
|
||||
[attr.stroke-dasharray]="stroke_dasharray"
|
||||
[attr.cx]="ellipse.cx"
|
||||
[attr.cy]="ellipse.cy"
|
||||
[attr.rx]="ellipse.rx"
|
||||
[attr.ry]="ellipse.ry"
|
||||
/>
|
Before Width: | Height: | Size: 332 B |
@ -1,23 +0,0 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { EllipseComponent } from './ellipse.component';
|
||||
|
||||
describe('EllipseComponent', () => {
|
||||
let component: EllipseComponent;
|
||||
let fixture: ComponentFixture<EllipseComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [EllipseComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EllipseComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -1,37 +0,0 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { QtDasharrayFixer } from '../../../../../helpers/qt-dasharray-fixer';
|
||||
import { EllipseElement } from '../../../../../models/drawings/ellipse-element';
|
||||
|
||||
@Component({
|
||||
selector: '[app-ellipse]',
|
||||
templateUrl: './ellipse.component.html',
|
||||
styleUrls: ['./ellipse.component.scss'],
|
||||
})
|
||||
export class EllipseComponent implements OnInit {
|
||||
@Input('app-ellipse') ellipse: EllipseElement;
|
||||
|
||||
constructor(private qtDasharrayFixer: QtDasharrayFixer) {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
get fill_opacity() {
|
||||
if (isFinite(this.ellipse.fill_opacity)) {
|
||||
return this.ellipse.fill_opacity;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get stroke_width() {
|
||||
if (isFinite(this.ellipse.stroke_width)) {
|
||||
return this.ellipse.stroke_width;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get stroke_dasharray() {
|
||||
if (this.ellipse.stroke_dasharray) {
|
||||
return this.qtDasharrayFixer.fix(this.ellipse.stroke_dasharray);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
<svg:image
|
||||
class="image_element noselect"
|
||||
[attr.xlink:href]="image.data"
|
||||
[attr.width]="image.width"
|
||||
[attr.height]="image.height"
|
||||
/>
|
Before Width: | Height: | Size: 140 B |
@ -1,23 +0,0 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ImageComponent } from './image.component';
|
||||
|
||||
describe('ImageComponent', () => {
|
||||
let component: ImageComponent;
|
||||
let fixture: ComponentFixture<ImageComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ImageComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ImageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -1,15 +0,0 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { ImageElement } from '../../../../../models/drawings/image-element';
|
||||
|
||||
@Component({
|
||||
selector: '[app-image]',
|
||||
templateUrl: './image.component.html',
|
||||
styleUrls: ['./image.component.scss'],
|
||||
})
|
||||
export class ImageComponent implements OnInit {
|
||||
@Input('app-image') image: ImageElement;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<svg:line
|
||||
class="line_element noselect"
|
||||
[attr.stroke]="line.stroke"
|
||||
[attr.stroke-width]="stroke_width"
|
||||
[attr.stroke-dasharray]="stroke_dasharray"
|
||||
[attr.x1]="line.x1"
|
||||
[attr.x2]="line.x2"
|
||||
[attr.y1]="line.y1"
|
||||
[attr.y2]="line.y2"
|
||||
/>
|
Before Width: | Height: | Size: 245 B |
@ -1,23 +0,0 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { LineComponent } from './line.component';
|
||||
|
||||
describe('LineComponent', () => {
|
||||
let component: LineComponent;
|
||||
let fixture: ComponentFixture<LineComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [LineComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LineComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -1,30 +0,0 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { QtDasharrayFixer } from '../../../../../helpers/qt-dasharray-fixer';
|
||||
import { LineElement } from '../../../../../models/drawings/line-element';
|
||||
|
||||
@Component({
|
||||
selector: '[app-line]',
|
||||
templateUrl: './line.component.html',
|
||||
styleUrls: ['./line.component.scss'],
|
||||
})
|
||||
export class LineComponent implements OnInit {
|
||||
@Input('app-line') line: LineElement;
|
||||
|
||||
constructor(private qtDasharrayFixer: QtDasharrayFixer) {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
get stroke_width() {
|
||||
if (isFinite(this.line.stroke_width)) {
|
||||
return this.line.stroke_width;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get stroke_dasharray() {
|
||||
if (this.line.stroke_dasharray) {
|
||||
return this.qtDasharrayFixer.fix(this.line.stroke_dasharray);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<svg:rect
|
||||
class="rect_element noselect"
|
||||
[attr.fill]="rect.fill"
|
||||
[attr.fill-opacity]="fill_opacity"
|
||||
[attr.stroke]="rect.stroke"
|
||||
[attr.stroke-width]="stroke_width"
|
||||
[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: 322 B |
@ -1,23 +0,0 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { RectComponent } from './rect.component';
|
||||
|
||||
describe('RectComponent', () => {
|
||||
let component: RectComponent;
|
||||
let fixture: ComponentFixture<RectComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [RectComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(RectComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -1,37 +0,0 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { QtDasharrayFixer } from '../../../../../helpers/qt-dasharray-fixer';
|
||||
import { RectElement } from '../../../../../models/drawings/rect-element';
|
||||
|
||||
@Component({
|
||||
selector: '[app-rect]',
|
||||
templateUrl: './rect.component.html',
|
||||
styleUrls: ['./rect.component.scss'],
|
||||
})
|
||||
export class RectComponent implements OnInit {
|
||||
@Input('app-rect') rect: RectElement;
|
||||
|
||||
constructor(private qtDasharrayFixer: QtDasharrayFixer) {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
get fill_opacity() {
|
||||
if (isFinite(this.rect.fill_opacity)) {
|
||||
return this.rect.fill_opacity;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get stroke_width() {
|
||||
if (isFinite(this.rect.stroke_width)) {
|
||||
return this.rect.stroke_width;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get stroke_dasharray() {
|
||||
if (this.rect.stroke_dasharray) {
|
||||
return this.qtDasharrayFixer.fix(this.rect.stroke_dasharray);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<svg:text
|
||||
#text
|
||||
class="text_element noselect"
|
||||
[attr.style]="style"
|
||||
[attr.text-decoration]="textDecoration"
|
||||
[attr.fill]="text.fill"
|
||||
[attr.transform]="transformation"
|
||||
>
|
||||
<svg:tspan *ngFor="let line of lines; index as i" xml:space="preserve" x="0" [attr.dy]="i == 0 ? '0em' : '1.4em'">
|
||||
{{ line }}
|
||||
</svg:tspan>
|
||||
</svg:text>
|
Before Width: | Height: | Size: 338 B |
@ -1,23 +0,0 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TextComponent } from './text.component';
|
||||
|
||||
describe('TextComponent', () => {
|
||||
let component: TextComponent;
|
||||
let fixture: ComponentFixture<TextComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TextComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TextComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -1,64 +0,0 @@
|
||||
import { Component, DoCheck, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { FontFixer } from '../../../../../helpers/font-fixer';
|
||||
import { TextElement } from '../../../../../models/drawings/text-element';
|
||||
|
||||
@Component({
|
||||
selector: '[app-text]',
|
||||
templateUrl: './text.component.html',
|
||||
styleUrls: ['./text.component.scss'],
|
||||
})
|
||||
export class TextComponent implements OnInit, DoCheck {
|
||||
static MARGIN = 4;
|
||||
|
||||
@Input('app-text') text: TextElement;
|
||||
|
||||
@ViewChild('text') textRef: ElementRef;
|
||||
|
||||
lines: string[] = [];
|
||||
|
||||
transformation = '';
|
||||
|
||||
constructor(private fontFixer: FontFixer, private sanitizer: DomSanitizer) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.lines = this.getLines(this.text.text);
|
||||
}
|
||||
|
||||
ngDoCheck() {
|
||||
this.transformation = this.calculateTransformation();
|
||||
}
|
||||
|
||||
get style() {
|
||||
const font = this.fontFixer.fix(this.text);
|
||||
|
||||
const styles: string[] = [];
|
||||
if (font.font_family) {
|
||||
styles.push(`font-family: "${this.text.font_family}"`);
|
||||
}
|
||||
if (font.font_size) {
|
||||
styles.push(`font-size: ${this.text.font_size}pt`);
|
||||
}
|
||||
if (font.font_weight) {
|
||||
styles.push(`font-weight: ${this.text.font_weight}`);
|
||||
}
|
||||
return this.sanitizer.bypassSecurityTrustStyle(styles.join('; '));
|
||||
}
|
||||
|
||||
get textDecoration() {
|
||||
return this.text.text_decoration;
|
||||
}
|
||||
|
||||
calculateTransformation() {
|
||||
const tspans = this.textRef.nativeElement.getElementsByTagName('tspan');
|
||||
if (tspans.length > 0) {
|
||||
const height = this.textRef.nativeElement.getBBox().height / tspans.length;
|
||||
return `translate(${TextComponent.MARGIN}, ${height - TextComponent.MARGIN})`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
getLines(text: string) {
|
||||
return text.split(/\r?\n/);
|
||||
}
|
||||
}
|