Compare commits

..

63 Commits

Author SHA1 Message Date
a8054fa129 release v0.0.1-beta.0 2018-01-11 11:39:28 +01:00
e180bedcdf Update README how to use bump 2018-01-11 11:38:44 +01:00
598a52e41b release v0.0.2 2018-01-11 11:27:22 +01:00
33ba66538b release v0.0.1 2018-01-11 11:26:58 +01:00
fdffe15f5b AV: test different token 2018-01-11 10:48:21 +01:00
5f4b5ac7c5 AV: turn off crlf 2018-01-11 10:37:50 +01:00
06642c7d45 AV: update GH_TOKEN 2018-01-11 10:22:44 +01:00
b10d4f2dd5 Updated README how to release 2018-01-11 10:15:36 +01:00
0687c536af AV: GH_TOKEN 2018-01-11 10:03:47 +01:00
4abab7a96c CCI: build on tag and branches 2018-01-11 09:58:36 +01:00
1a3c538e68 CCI: build on tag 2018-01-11 09:56:22 +01:00
e6f5574b72 Test releases 2018-01-10 16:53:19 +01:00
35c0944d04 Release test 2018-01-10 16:39:44 +01:00
3056b0fbaf Travis: init 2018-01-10 16:08:17 +01:00
5b7bbe4f64 Compression level to normal 2018-01-10 15:53:33 +01:00
a037d97fbf Electronjs packages: limit files, ref #31 2018-01-10 15:36:30 +01:00
57f3bfb5b9 CCI: Fix Nightly Path - simple mistake 2018-01-10 14:53:53 +01:00
1d6466731a CCI: Fix Nightly Path 2018-01-10 14:49:40 +01:00
33b943138f CCI: deploy on SF - SCP 2018-01-10 14:39:18 +01:00
e706769c41 CCI: SF, AV: path 2018-01-10 14:29:26 +01:00
c510033de5 AV: push 2018-01-10 14:20:01 +01:00
8f095cbf51 AV: change var 2018-01-10 14:06:15 +01:00
73071d462a AV: fix var 2018-01-10 13:49:16 +01:00
498afb2632 AV: var as out dir 2018-01-10 13:35:33 +01:00
542be33c22 CCI: Yarn and AV: path 2018-01-10 13:27:25 +01:00
471c5f5774 CCI: Yarn and AV: path 2018-01-10 13:22:21 +01:00
e2ef2e30bc CCI: Yarn and AV: path 2018-01-10 13:18:17 +01:00
1d83d0a1f2 CCI: custom yarn - bash reload 2018-01-10 11:59:16 +01:00
0677618bb5 CCI: custom yarn - bash reload 2018-01-10 11:57:16 +01:00
206bdc7dc6 CCI: custom yarn - bash reload 2018-01-10 11:54:16 +01:00
c0919334bd CCI: custom yarn 2018-01-10 11:52:37 +01:00
0014fc7a62 AV: artifact with directory 2018-01-10 11:47:30 +01:00
a4057f1a04 CCI: brew yarn 2018-01-10 11:42:25 +01:00
2afc54051d AV: push path 2018-01-10 11:36:58 +01:00
6e6a2a4d6f CCI: net. conc 1 2018-01-10 11:33:27 +01:00
6064892be5 AV: Push Artifacts 2018-01-10 11:29:32 +01:00
f64479d755 CCI: Yarn clear cache 2018-01-10 11:27:32 +01:00
f5b3ff1787 CCI: Fix Yarn 2018-01-10 11:17:17 +01:00
b69bef7654 CCI: clean install 2018-01-10 10:57:04 +01:00
c96dcb1dbe AV: artifacts path; CCI: check yarn network conc. 1 2018-01-10 10:47:46 +01:00
f18f9deb34 AV: artifacts path 2018-01-10 10:44:15 +01:00
12ad880fa1 CCI: upgrade yarn 2018-01-10 10:33:33 +01:00
a96b83b481 CCI: brew is already installed 2018-01-10 10:30:24 +01:00
51af56f867 CCI: use XCode 9.1.0 image 2018-01-10 10:28:51 +01:00
90b478a969 Init CCI and fix artifacts paths on AV 2018-01-10 10:22:19 +01:00
8a9d4d5fe1 AF: publish only exe 2018-01-10 09:45:33 +01:00
a61989612c AV: Upload to Nightly Builds 2018-01-09 16:46:42 +01:00
a2c23d225d PS vars 2018-01-09 16:33:27 +01:00
3fe275cf6c Deploy on SF: only in PS 2018-01-09 16:18:19 +01:00
2efd541875 Deploy on SF: fix path 2018-01-09 16:06:30 +01:00
1c09c7b71c Deploy on SF: fix path 2018-01-09 16:00:08 +01:00
cd6b12dd60 Deploy on SF: fix path 2018-01-09 15:50:07 +01:00
be1378fcb2 Deploy on SF 2018-01-09 15:45:48 +01:00
9425d0edef EB: don't publish on github 2018-01-09 14:19:47 +01:00
09bacaefc6 Update app description 2018-01-09 14:19:04 +01:00
55cfdbca96 Build using asar 2018-01-09 13:51:29 +01:00
36253113d7 Remove option from electron-builder.yml 2018-01-09 13:43:26 +01:00
aebfa517bf Move config for electron-builder outside package.json 2018-01-09 13:39:37 +01:00
b513202339 AV: define artifacts path 2018-01-09 11:39:10 +01:00
7f059c7807 Copy all files during build 2018-01-09 11:11:17 +01:00
665e01f110 Integrate with AppVeyor 2018-01-09 11:01:20 +01:00
029ab352d2 AppId and proper categories for macOS and linux 2018-01-09 10:38:37 +01:00
1cc54e8520 Icons set 2018-01-04 12:50:20 +01:00
1126 changed files with 23190 additions and 62453 deletions

66
.angular-cli.json Normal file
View 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": {
}
}
}

View File

@ -3,37 +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 -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

View File

@ -1,91 +1,31 @@
# iOS CircleCI 2.1 configuration file
version: 2.1
# iOS CircleCI 2.0 configuration file
version: 2
jobs:
build:
macos:
xcode: "10.1.0"
xcode: "9.1.0"
steps:
- checkout
- run:
name: Install nodejs
command: |
brew update
brew upgrade yarn
brew upgrade node
- run:
name: Set timezone and check current datetime
command: |
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
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 switch 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
pip3 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 || true
yarn || true
- run:
name: Dist project
command: |
yarn electron-builder --mac --x64 --publish always
yarn distmac
- run:
name: Gather artifacts
@ -97,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:

View File

@ -1,4 +1,4 @@
# Editor configuration, see https://editorconfig.org
# Editor configuration, see http://editorconfig.org
root = true
[*]

15
.gitignore vendored
View File

@ -6,11 +6,6 @@
/out-tsc
/ng-dist
/build
/scripts/tmp
/scripts/env
/scripts/build
/scripts/dist
/env
# dependencies
/node_modules
@ -40,7 +35,6 @@ npm-debug.log
testem.log
/typings
/yarn-error.log
package-lock.json
# e2e
/e2e/*.js
@ -49,12 +43,3 @@ package-lock.json
# System Files
.DS_Store
Thumbs.db
# Licenses file
licenses.csv
# Temps
.temp-var-file.ts
# external software
/external

View File

@ -1,9 +0,0 @@
{
"printWidth": 120,
"singleQuote": true,
"useTabs": false,
"tabWidth": 2,
"semi": true,
"bracketSpacing": true,
"jsxBracketSameLine": false,
}

View File

@ -1,4 +0,0 @@
[defaults]
url = https://sentry.io/
org = gns3
project = gns3-web-ui

8
.snyk
View File

@ -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'

View File

@ -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
View File

@ -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}"
}
]
}

View File

@ -1,13 +0,0 @@
{
"scanSettings": {
"configMode": "AUTO",
"configExternalURL": "",
"projectToken" : ""
},
"checkRunSettings": {
"vulnerableCheckRunConclusionLevel": "failure"
},
"issueSettings": {
"minSeverityLevel": "LOW"
}
}

View File

@ -37,3 +37,4 @@ Gruntfile.js
# misc
*.gz
*.md

View File

@ -1,26 +0,0 @@
# Dockerfile for GNS3 Web-ui development
FROM node:carbon
# 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

127
README.md
View File

@ -1,96 +1,63 @@
# gns3-web-ui
[![Known Vulnerabilities](https://snyk.io/test/github/GNS3/gns3-web-ui/badge.svg)](https://snyk.io/test/github/GNS3/gns3-web-ui)
[![Travis CI](https://api.travis-ci.org/GNS3/gns3-web-ui.svg?branch=master)](https://travis-ci.org)
[![AppVeyor](https://ci.appveyor.com/api/projects/status/github/GNS3/gns3-web-ui?branch=master&svg=true)](https://www.appveyor.com/)
[![CircleCI](https://circleci.com/gh/GNS3/gns3-web-ui/tree/master.png)](https://circleci.com/gh/GNS3/gns3-web-ui/tree/master.png)
[![codecov](https://codecov.io/gh/GNS3/gns3-web-ui/branch/master/graph/badge.svg)](https://codecov.io/gh/GNS3/gns3-web-ui)
[![Dependency](https://img.shields.io/librariesio/github/GNS3/gns3-web-ui)](https://libraries.io/github/GNS3/gns3-web-ui)
[![Packages versions](https://repology.org/badge/latest-versions/gns3.svg)](https://repology.org/metapackage/gns3/versions)
[![Packages](https://repology.org/badge/tiny-repos/gns3.svg)](https://repology.org/metapackage/gns3/versions)
Test WebUI implementation for GNS3.
This is not production ready version. It has been made to evaluate possibility of creation Web User Interface for GNS3 application.
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.2.6.
## Demo
## Installation for development
Please use GNS3 WebUI bundled in `gns3server` and `gns3`.
Please install `npm` if not present in your system.
## Development
### Branches policy
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
```
#### Run GNS3 server
## Development server
Visit [gns3-server](https://github.com/GNS3/gns3-server) for guide how to run GNS3 server.
### Run GNS3 server
#### Run WebUI
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:
```
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
@ -98,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

View File

@ -1,252 +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",
"angular2-hotkeys",
"dom-set",
"dom-plane",
"mousetrap",
"@mattlewis92/dom-autoscroller",
"rxjs/Rx",
"rxjs/add/operator/map",
"rxjs-compat/add/operator/map"
],
"aot": true,
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "src/tsconfig.app.json",
"polyfills": "src/polyfills.ts",
"extractCss": true,
"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.css",
{
"inject": false,
"input": "src/theme.scss",
"bundleName": "theme-default-dark"
},
{
"inject": false,
"input": "src/theme-light.scss",
"bundleName": "theme-default"
}
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "anyComponentStyle",
"maximumWarning": "6kb"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": {
"hidden": true,
"scripts": true,
"styles": false
},
"extractCss": true,
"namedChunks": false,
"aot": true,
"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,
"extractCss": true,
"namedChunks": false,
"aot": true,
"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,
"extractCss": true,
"namedChunks": false,
"aot": true,
"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.css",
"src/theme.scss"
],
"assets": [
"src/assets",
"src/favicon.ico"
],
"codeCoverageExclude": [
"src/app/cartography/components/experimental-map/**/*"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**",
"**/*.spec.ts"
]
}
}
}
},
"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
}
}

View File

@ -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;
}

View File

@ -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!');
});
});

View File

@ -5,10 +5,6 @@ export class Gns3WebUiPage {
return browser.get('/');
}
getTitleText() {
return browser.driver.getTitle();
}
getParagraphText() {
return element(by.css('app-root h1')).getText();
}

View File

@ -1,22 +0,0 @@
import { browser, by, element } from 'protractor';
import { ServersPage } from './server.po';
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();
}
}

View File

@ -1,50 +0,0 @@
import { TestHelper } from "./common.po"
import { browser, by } from "protractor";
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;
}
}

View File

@ -1,20 +0,0 @@
import { TestHelper } from "./common.po"
import { browser, by } from "protractor";
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();
}
}

View File

@ -1,41 +0,0 @@
import { browser, by, element } 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();
}
}

View File

@ -1,41 +0,0 @@
import { ServersPage } from './helpers/server.po';
import { TestHelper } from './helpers/common.po';
import { element } from 'protractor';
import { ProjectsPage } from './helpers/project.po';
import { ProjectMapPage } from './helpers/project-map.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);
});
});

View File

@ -1,34 +0,0 @@
import { ServersPage } from './helpers/server.po';
import { TestHelper } from './helpers/common.po';
import { element } from 'protractor';
import { ProjectsPage } from './helpers/project.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/');
});
});

View File

@ -1,44 +0,0 @@
import { ServersPage } from './helpers/server.po';
import { TestHelper } from './helpers/common.po';
import { element } from 'protractor';
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');
});
});

View File

@ -1,5 +1,5 @@
{
"extends": "../tsconfig.base.json",
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"baseUrl": "./",

View File

@ -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"

View File

@ -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();
});

View File

@ -4,22 +4,24 @@
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,

View File

@ -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
View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -1,136 +1,69 @@
{
"name": "gns3-web-ui",
"version": "2020.4.0-beta.1",
"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",
"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",
"snyk-protect": "snyk protect",
"prepare": "yarn run snyk-protect"
"release": "build"
},
"private": true,
"dependencies": {
"@angular/animations": "^10.1.2",
"@angular/cdk": "^10.2.2",
"@angular/common": "^10.1.2",
"@angular/compiler": "^10.1.2",
"@angular/core": "^10.1.2",
"@angular/forms": "^10.1.2",
"@angular/http": "^7.2.16",
"@angular/material": "^10.2.2",
"@angular/platform-browser": "^10.1.2",
"@angular/platform-browser-dynamic": "^10.1.2",
"@angular/router": "^10.1.2",
"@sentry/browser": "^5.24.2",
"@types/jest": "^26.0.14",
"@types/mocha": "^8.0.3",
"angular-draggable-droppable": "^4.5.4",
"angular-persistence": "^1.0.1",
"angular-resizable-element": "^3.3.3",
"angular2-draggable": "^2.3.2",
"angular2-hotkeys": "^2.2.0",
"angular2-indexeddb": "^1.2.3",
"bootstrap": "4.5.2",
"command-exists": "^1.2.9",
"core-js": "^3.6.5",
"css-tree": "^1.0.0-alpha.36",
"d3-ng2-service": "^2.1.0",
"file-saver": "^2.0.2",
"ini": "^1.3.5",
"material-design-icons": "^3.0.1",
"ng-circle-progress": "^1.5.1",
"ng2-file-upload": "^1.3.0",
"ngx-childprocess": "^0.0.6",
"ngx-device-detector": "^2.0.0",
"ngx-electron": "^2.1.1",
"node-fetch": "^2.6.1",
"notosans-fontface": "1.2.2",
"rxjs": "^6.6.3",
"rxjs-compat": "^6.6.3",
"save-html-as-image": "^1.3.4",
"save-svg-as-png": "^1.4.14",
"snyk": "^1.399.1",
"svg-crowbar": "^0.6.1",
"tree-kill": "^1.2.1",
"tslib": "^2.0.1",
"typeface-roboto": "^0.0.75",
"xterm": "^4.9.0",
"xterm-addon-attach": "^0.6.0",
"xterm-addon-fit": "^0.4.0",
"yargs": "^16.0.3",
"zone.js": "~0.11.1"
"@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": "~0.1001.2",
"@angular/cli": "^10.1.2",
"@angular/compiler-cli": "^10.1.2",
"@angular/language-service": "^10.1.2",
"@sentry/cli": "^1.57.0",
"@sentry/electron": "^2.0.1",
"@types/jasmine": "^3.5.14",
"@types/jasminewd2": "^2.0.8",
"@types/node": "14.11.2",
"codelyzer": "^6.0.0",
"electron": "^10.1.2",
"electron-builder": "22.8.1",
"file-loader": "^6.1.0",
"jasmine-core": "^3.6.0",
"jasmine-spec-reporter": "^6.0.0",
"jquery": "^3.5.1",
"karma": "^5.2.2",
"karma-chrome-launcher": "^3.1.0",
"karma-cli": "^2.0.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "^4.0.1",
"karma-jasmine-html-reporter": "^1.5.4",
"license-checker": "^25.0.1",
"node-sass": "^4.14.1",
"popper.js": "^1.16.1",
"prettier": "^2.1.2",
"protractor": "^7.0.0",
"replace": "^1.2.0",
"rxjs-tslint": "^0.1.8",
"ts-mockito": "^2.6.1",
"ts-node": "~9.0.0",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
"typescript": "^4.0.3",
"webpack": "^4.44.2"
},
"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"
}
}

View File

@ -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)
}
})

View File

@ -1,411 +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'
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)

View File

@ -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

View File

@ -1,7 +0,0 @@
setuptools==40.8.0
cx_Freeze==5.1.1
requests==2.21.0
packaging==19.0
appdirs==1.4.3
psutil==5.6.7
jsonschema==2.6.0 # lock down jsonschema, 3.0 makes problems

View File

@ -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
});

View File

@ -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);
}

View File

@ -1,174 +0,0 @@
GNS3 WebUI is web implementation of user interface for GNS3 software.
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

View File

@ -1,176 +1,36 @@
import { environment } from "../environments/environment";
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProjectMapComponent } from './components/project-map/project-map.component';
import { ServersComponent } from './components/servers/servers.component';
import { ProjectsComponent } from './components/projects/projects.component';
import { DefaultLayoutComponent } from './layouts/default-layout/default-layout.component';
import { SettingsComponent } from './components/settings/settings.component';
import { BundledServerFinderComponent } from './components/bundled-server-finder/bundled-server-finder.component';
import { PreferencesComponent } from './components/preferences/preferences.component';
import { QemuPreferencesComponent } from './components/preferences/qemu/qemu-preferences/qemu-preferences.component';
import { QemuVmTemplatesComponent } from './components/preferences/qemu/qemu-vm-templates/qemu-vm-templates.component';
import { QemuVmTemplateDetailsComponent } from './components/preferences/qemu/qemu-vm-template-details/qemu-vm-template-details.component';
import { AddQemuVmTemplateComponent } from './components/preferences/qemu/add-qemu-vm-template/add-qemu-vm-template.component';
import { GeneralPreferencesComponent } from './components/preferences/general/general-preferences.component';
import { VpcsPreferencesComponent } from './components/preferences/vpcs/vpcs-preferences/vpcs-preferences.component';
import { VpcsTemplatesComponent } from './components/preferences/vpcs/vpcs-templates/vpcs-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 { VirtualBoxPreferencesComponent } from './components/preferences/virtual-box/virtual-box-preferences/virtual-box-preferences.component';
import { VirtualBoxTemplatesComponent } from './components/preferences/virtual-box/virtual-box-templates/virtual-box-templates.component';
import { VirtualBoxTemplateDetailsComponent } from './components/preferences/virtual-box/virtual-box-template-details/virtual-box-template-details.component';
import { AddVirtualBoxTemplateComponent } from './components/preferences/virtual-box/add-virtual-box-template/add-virtual-box-template.component';
import { BuiltInPreferencesComponent } from './components/preferences/built-in/built-in-preferences.component';
import { EthernetHubsTemplatesComponent } from './components/preferences/built-in/ethernet-hubs/ethernet-hubs-templates/ethernet-hubs-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 { CloudNodesTemplatesComponent } from './components/preferences/built-in/cloud-nodes/cloud-nodes-templates/cloud-nodes-templates.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 { EthernetSwitchesTemplatesComponent } from './components/preferences/built-in/ethernet-switches/ethernet-switches-templates/ethernet-switches-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 { DynamipsPreferencesComponent } from './components/preferences/dynamips/dynamips-preferences/dynamips-preferences.component';
import { IosTemplatesComponent } from './components/preferences/dynamips/ios-templates/ios-templates.component';
import { InstalledSoftwareComponent } from './components/installed-software/installed-software.component';
import { IosTemplateDetailsComponent } from './components/preferences/dynamips/ios-template-details/ios-template-details.component';
import { AddIosTemplateComponent } from './components/preferences/dynamips/add-ios-template/add-ios-template.component';
import { VmwarePreferencesComponent } from './components/preferences/vmware/vmware-preferences/vmware-preferences.component';
import { VmwareTemplatesComponent } from './components/preferences/vmware/vmware-templates/vmware-templates.component';
import { VmwareTemplateDetailsComponent } from './components/preferences/vmware/vmware-template-details/vmware-template-details.component';
import { AddVmwareTemplateComponent } from './components/preferences/vmware/add-vmware-template/add-vmware-template.component';
import { DockerTemplatesComponent } from './components/preferences/docker/docker-templates/docker-templates.component';
import { AddDockerTemplateComponent } from './components/preferences/docker/add-docker-template/add-docker-template.component';
import { DockerTemplateDetailsComponent } from './components/preferences/docker/docker-template-details/docker-template-details.component';
import { IouTemplatesComponent } from './components/preferences/ios-on-unix/iou-templates/iou-templates.component';
import { AddIouTemplateComponent } from './components/preferences/ios-on-unix/add-iou-template/add-iou-template.component';
import { IouTemplateDetailsComponent } from './components/preferences/ios-on-unix/iou-template-details/iou-template-details.component';
import { CopyQemuVmTemplateComponent } from './components/preferences/qemu/copy-qemu-vm-template/copy-qemu-vm-template.component';
import { CopyIosTemplateComponent } from './components/preferences/dynamips/copy-ios-template/copy-ios-template.component';
import { CopyDockerTemplateComponent } from './components/preferences/docker/copy-docker-template/copy-docker-template.component';
import { CopyIouTemplateComponent } from './components/preferences/ios-on-unix/copy-iou-template/copy-iou-template.component';
import { ListOfSnapshotsComponent } from './components/snapshots/list-of-snapshots/list-of-snapshots.component';
import { ConsoleComponent } from './components/settings/console/console.component';
import { HelpComponent } from './components/help/help.component';
import { TracengPreferencesComponent } from './components/preferences/traceng/traceng-preferences/traceng-preferences.component';
import { TracengTemplatesComponent } from './components/preferences/traceng/traceng-templates/traceng-templates.component';
import { AddTracengTemplateComponent } from './components/preferences/traceng/add-traceng/add-traceng-template.component';
import { TracengTemplateDetailsComponent } from './components/preferences/traceng/traceng-template-details/traceng-template-details.component';
import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component';
import { Gns3vmComponent } from './components/preferences/gns3vm/gns3vm.component';
import { DirectLinkComponent } from './components/direct-link/direct-link.component';
import { SystemStatusComponent } from './components/system-status/system-status.component';
import { ServerResolve } from './resolvers/server-resolve';
import { WebConsoleFullWindowComponent } from './components/web-console-full-window/web-console-full-window.component';
import { ConsoleGuard } from './guards/console-guard';
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/projects', component: ProjectsComponent }
]
},
{
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/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 {}

View File

@ -1,3 +0,0 @@
mat-menu-panel {
min-height: 0px;
}

View File

@ -1,2 +1,2 @@
<router-outlet></router-outlet>
<app-notification-box></app-notification-box>
<ng2-toasty></ng2-toasty>

View File

@ -1,68 +1,30 @@
import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
import { MatIconModule } from '@angular/material/icon';
import { SettingsService } from './services/settings.service';
import { PersistenceService } from 'angular-persistence';
import { ElectronService, NgxElectronModule } from 'ngx-electron';
import createSpyObj = jasmine.createSpyObj;
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ProgressService } from './common/progress/progress.service';
// import 'jasmine';
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, PersistenceService, ProgressService],
schemas: [NO_ERRORS_SCHEMA]
declarations: [
AppComponent
],
imports: [
RouterTestingModule
]
}).compileComponents();
electronService = TestBed.get(ElectronService);
settingsService = TestBed.get(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(() => {
const spy = createSpyObj('Electron.IpcRenderer', ['send']);
spyOnProperty(electronService, 'isElectronApp').and.returnValue(true);
spyOnProperty(electronService, 'ipcRenderer').and.returnValue(spy);
settingsService.set('crash_reports', true);
component.ngOnInit();
settingsService.set('crash_reports', false);
expect(spy.send).toHaveBeenCalled();
expect(spy.send.calls.mostRecent().args[0]).toEqual('settings.changed');
expect(spy.send.calls.mostRecent().args[1].crash_reports).toEqual(false);
}));
it('should receive changed settings and do not forward to electron', async(() => {
const spy = createSpyObj('Electron.IpcRenderer', ['send']);
spyOnProperty(electronService, 'isElectronApp').and.returnValue(false);
settingsService.set('crash_reports', true);
component.ngOnInit();
settingsService.set('crash_reports', 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');
// }));
});

View File

@ -1,58 +1,23 @@
import { Component, OnInit } from '@angular/core';
import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser';
import { ElectronService } from 'ngx-electron';
import { SettingsService } from './services/settings.service';
import { ThemeService } from './services/theme.service';
import { Router, NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from '@angular/router';
import { ProgressService } from './common/progress/progress.service';
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.css']
styleUrls: [
'./app.component.css'
]
})
export class AppComponent implements OnInit {
constructor(
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'));
constructor(http: Http, iconReg: MatIconRegistry, sanitizer: DomSanitizer, toastyConfig: ToastyConfig) {
toastyConfig.theme = 'material';
router.events.subscribe((value) => {
this.checkEvent(value);
});
iconReg.addSvgIcon('gns3', sanitizer.bypassSecurityTrustResourceUrl('./assets/gns3_icon.svg'));
}
ngOnInit(): void {
if (this.electronService.isElectronApp) {
this.settingsService.subscribe(settings => {
this.electronService.ipcRenderer.send('settings.changed', settings);
});
}
let theme = localStorage.getItem('theme');
if (theme === 'light') {
this.themeService.setDarkMode(false);
} else {
this.themeService.setDarkMode(true);
}
}
checkEvent(routerEvent) : void {
if (routerEvent instanceof NavigationStart) {
this.progressService.activate();
}
else if (routerEvent instanceof NavigationEnd ||
routerEvent instanceof NavigationCancel ||
routerEvent instanceof NavigationError) {
this.progressService.deactivate();
}
}
}

View File

@ -1,287 +1,57 @@
import { BrowserModule, Title } from '@angular/platform-browser';
import { NgModule, ErrorHandler } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
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 { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {
MatButtonModule,
MatCardModule,
MatMenuModule,
MatToolbarModule,
MatIconModule,
MatFormFieldModule,
MatInputModule,
MatTableModule,
MatDialogModule,
MatProgressBarModule,
MatProgressSpinnerModule
} from '@angular/material';
import { D3Service } from 'd3-ng2-service';
import { HotkeyModule } from 'angular2-hotkeys';
import { PersistenceModule } from 'angular-persistence';
import { NgxElectronModule } from 'ngx-electron';
import { FileUploadModule } from 'ng2-file-upload';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { ToastyModule } from 'ng2-toasty';
import { AppRoutingModule } from './app-routing.module';
import { VersionService } from './services/version.service';
import { ProjectService } from './services/project.service';
import { SymbolService } from './services/symbol.service';
import { ServerService } from './services/server.service';
import { IndexedDbService } from './services/indexed-db.service';
import { HttpServer, ServerErrorHandler } from './services/http-server.service';
import { SnapshotService } from './services/snapshot.service';
import { ProgressDialogService } from './common/progress-dialog/progress-dialog.service';
import { NodeService } from './services/node.service';
import { TemplateService } from './services/template.service';
import { LinkService } from './services/link.service';
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 './components/projects/projects.component';
import { AddBlankProjectDialogComponent } from './components/projects/add-blank-project-dialog/add-blank-project-dialog.component';
import { ImportProjectDialogComponent } from './components/projects/import-project-dialog/import-project-dialog.component';
import { ConfirmationDialogComponent } from './components/projects/confirmation-dialog/confirmation-dialog.component';
import { DefaultLayoutComponent } from './layouts/default-layout/default-layout.component';
import { ProgressDialogComponent } from './common/progress-dialog/progress-dialog.component';
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 { ProjectMapComponent } from './components/project-map/project-map.component';
import { ServersComponent } from './components/servers/servers.component';
import { AddServerDialogComponent } from './components/servers/add-server-dialog/add-server-dialog.component';
import { ContextMenuComponent } from './components/project-map/context-menu/context-menu.component';
import { ContextConsoleMenuComponent } from './components/project-map/context-console-menu/context-console-menu.component';
import { StartNodeActionComponent } from './components/project-map/context-menu/actions/start-node-action/start-node-action.component';
import { StopNodeActionComponent } from './components/project-map/context-menu/actions/stop-node-action/stop-node-action.component';
import { TemplateComponent } from './components/template/template.component';
import { TemplateListDialogComponent } from './components/template/template-list-dialog/template-list-dialog.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 { ToasterService } from './services/toaster.service';
import { ProjectWebServiceHandler } from './handlers/project-web-service-handler';
import { LinksDataSource } from './cartography/datasources/links-datasource';
import { NodesDataSource } from './cartography/datasources/nodes-datasource';
import { SymbolsDataSource } from './cartography/datasources/symbols-datasource';
import { SelectionManager } from './cartography/managers/selection-manager';
import { InRectangleHelper } from './cartography/helpers/in-rectangle-helper';
import { DrawingsDataSource } from './cartography/datasources/drawings-datasource';
import { EditStyleActionComponent } from './components/project-map/context-menu/actions/edit-style-action/edit-style-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 { ProjectMapShortcutsComponent } from './components/project-map/project-map-shortcuts/project-map-shortcuts.component';
import { SettingsComponent } from './components/settings/settings.component';
import { SettingsService } from './services/settings.service';
import { BundledServerFinderComponent } from './components/bundled-server-finder/bundled-server-finder.component';
import { ProgressComponent } from './common/progress/progress.component';
import { ProgressService } from './common/progress/progress.service';
import { version } from './version';
import { ToasterErrorHandler } from './common/error-handlers/toaster-error-handler';
import { environment } from '../environments/environment';
import { ServerDiscoveryComponent } from './components/servers/server-discovery/server-discovery.component';
import { ServerDatabase } from './services/server.database';
import { CreateSnapshotDialogComponent } from './components/snapshots/create-snapshot-dialog/create-snapshot-dialog.component';
import { SnapshotMenuItemComponent } from './components/snapshots/snapshot-menu-item/snapshot-menu-item.component';
import { MATERIAL_IMPORTS } from './material.imports';
import { DrawingService } from './services/drawing.service';
import { ProjectNameValidator } from './components/projects/models/projectNameValidator';
import { MatSidenavModule } from '@angular/material/sidenav';
import { NodeSelectInterfaceComponent } from './components/project-map/node-select-interface/node-select-interface.component';
import { DrawLinkToolComponent } from './components/project-map/draw-link-tool/draw-link-tool.component';
import { InstalledSoftwareComponent } from './components/installed-software/installed-software.component';
import { DrawingResizedComponent } from './components/drawings-listeners/drawing-resized/drawing-resized.component';
import { TextEditedComponent } from './components/drawings-listeners/text-edited/text-edited.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 { DrawingDraggedComponent } from './components/drawings-listeners/drawing-dragged/drawing-dragged.component';
import { LinkCreatedComponent } from './components/drawings-listeners/link-created/link-created.component';
import { InterfaceLabelDraggedComponent } from './components/drawings-listeners/interface-label-dragged/interface-label-dragged.component';
import { ToolsService } from './services/tools.service';
import { TextAddedComponent } from './components/drawings-listeners/text-added/text-added.component';
import { DrawingAddedComponent } from './components/drawings-listeners/drawing-added/drawing-added.component';
import { InstallSoftwareComponent } from './components/installed-software/install-software/install-software.component';
import { StyleEditorDialogComponent } from './components/project-map/drawings-editors/style-editor/style-editor.component';
import { EditTextActionComponent } from './components/project-map/context-menu/actions/edit-text-action/edit-text-action.component';
import { TextEditorDialogComponent } from './components/project-map/drawings-editors/text-editor/text-editor.component';
import { PreferencesComponent } from './components/preferences/preferences.component';
import { QemuPreferencesComponent } from './components/preferences/qemu/qemu-preferences/qemu-preferences.component';
import { ServerSettingsService } from './services/server-settings.service';
import { QemuVmTemplatesComponent } from './components/preferences/qemu/qemu-vm-templates/qemu-vm-templates.component';
import { AddQemuVmTemplateComponent } from './components/preferences/qemu/add-qemu-vm-template/add-qemu-vm-template.component';
import { QemuVmTemplateDetailsComponent } from './components/preferences/qemu/qemu-vm-template-details/qemu-vm-template-details.component';
import { QemuService } from './services/qemu.service';
import { GeneralPreferencesComponent } from './components/preferences/general/general-preferences.component';
import { VpcsPreferencesComponent } from './components/preferences/vpcs/vpcs-preferences/vpcs-preferences.component';
import { VpcsTemplatesComponent } from './components/preferences/vpcs/vpcs-templates/vpcs-templates.component';
import { VpcsService } from './services/vpcs.service';
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 { TemplateMocksService } from './services/template-mocks.service';
import { VirtualBoxPreferencesComponent } from './components/preferences/virtual-box/virtual-box-preferences/virtual-box-preferences.component';
import { VirtualBoxTemplatesComponent } from './components/preferences/virtual-box/virtual-box-templates/virtual-box-templates.component';
import { VirtualBoxService } from './services/virtual-box.service';
import { VirtualBoxTemplateDetailsComponent } from './components/preferences/virtual-box/virtual-box-template-details/virtual-box-template-details.component';
import { AddVirtualBoxTemplateComponent } from './components/preferences/virtual-box/add-virtual-box-template/add-virtual-box-template.component';
import { BuiltInPreferencesComponent } from './components/preferences/built-in/built-in-preferences.component';
import { EthernetHubsTemplatesComponent } from './components/preferences/built-in/ethernet-hubs/ethernet-hubs-templates/ethernet-hubs-templates.component';
import { BuiltInTemplatesService } from './services/built-in-templates.service';
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 { CloudNodesTemplatesComponent } from './components/preferences/built-in/cloud-nodes/cloud-nodes-templates/cloud-nodes-templates.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 { EthernetSwitchesTemplatesComponent } from './components/preferences/built-in/ethernet-switches/ethernet-switches-templates/ethernet-switches-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 { DynamipsPreferencesComponent } from './components/preferences/dynamips/dynamips-preferences/dynamips-preferences.component';
import { IosTemplatesComponent } from './components/preferences/dynamips/ios-templates/ios-templates.component';
import { IosService } from './services/ios.service';
import { SymbolsComponent } from './components/preferences/common/symbols/symbols.component';
import { InstalledSoftwareService } from './services/installed-software.service';
import { ExternalSoftwareDefinitionService } from './services/external-software-definition.service';
import { PlatformService } from './services/platform.service';
import { IosTemplateDetailsComponent } from './components/preferences/dynamips/ios-template-details/ios-template-details.component';
import { AddIosTemplateComponent } from './components/preferences/dynamips/add-ios-template/add-ios-template.component';
import { IosConfigurationService } from './services/ios-configuration.service';
import { QemuConfigurationService } from './services/qemu-configuration.service';
import { VirtualBoxConfigurationService } from './services/virtual-box-configuration.service';
import { VpcsConfigurationService } from './services/vpcs-configuration.service';
import { BuiltInTemplatesConfigurationService } from './services/built-in-templates-configuration.service';
import { VmwarePreferencesComponent } from './components/preferences/vmware/vmware-preferences/vmware-preferences.component';
import { VmwareTemplatesComponent } from './components/preferences/vmware/vmware-templates/vmware-templates.component';
import { VmwareService } from './services/vmware.service';
import { VmwareConfigurationService } from './services/vmware-configuration.service';
import { VmwareTemplateDetailsComponent } from './components/preferences/vmware/vmware-template-details/vmware-template-details.component';
import { AddVmwareTemplateComponent } from './components/preferences/vmware/add-vmware-template/add-vmware-template.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 { DockerService } from './services/docker.service';
import { DockerTemplatesComponent } from './components/preferences/docker/docker-templates/docker-templates.component';
import { DockerConfigurationService } from './services/docker-configuration.service';
import { AddDockerTemplateComponent } from './components/preferences/docker/add-docker-template/add-docker-template.component';
import { DockerTemplateDetailsComponent } from './components/preferences/docker/docker-template-details/docker-template-details.component';
import { IouTemplatesComponent } from './components/preferences/ios-on-unix/iou-templates/iou-templates.component';
import { IouService } from './services/iou.service';
import { AddIouTemplateComponent } from './components/preferences/ios-on-unix/add-iou-template/add-iou-template.component';
import { IouConfigurationService } from './services/iou-configuration.service';
import { IouTemplateDetailsComponent } from './components/preferences/ios-on-unix/iou-template-details/iou-template-details.component';
import { CopyQemuVmTemplateComponent } from './components/preferences/qemu/copy-qemu-vm-template/copy-qemu-vm-template.component';
import { CopyIosTemplateComponent } from './components/preferences/dynamips/copy-ios-template/copy-ios-template.component';
import { CopyIouTemplateComponent } from './components/preferences/ios-on-unix/copy-iou-template/copy-iou-template.component';
import { CopyDockerTemplateComponent } from './components/preferences/docker/copy-docker-template/copy-docker-template.component';
import { EmptyTemplatesListComponent } from './components/preferences/common/empty-templates-list/empty-templates-list.component';
import { SymbolsMenuComponent } from './components/preferences/common/symbols-menu/symbols-menu.component';
import { SearchFilter } from './filters/searchFilter.pipe';
import { RecentlyOpenedProjectService } from './services/recentlyOpenedProject.service';
import { ServerManagementService } from './services/server-management.service';
import { DeleteActionComponent } from './components/project-map/context-menu/actions/delete-action/delete-action.component';
import { ListOfSnapshotsComponent } from './components/snapshots/list-of-snapshots/list-of-snapshots.component';
import { DateFilter } from './filters/dateFilter.pipe';
import { NameFilter } from './filters/nameFilter.pipe';
import { CustomAdaptersComponent } from './components/preferences/common/custom-adapters/custom-adapters.component';
import { ConsoleDeviceActionComponent } from './components/project-map/context-menu/actions/console-device-action/console-device-action.component';
import { ConsoleComponent } from './components/settings/console/console.component';
import { NodesMenuComponent } from './components/project-map/nodes-menu/nodes-menu.component';
import { PacketFiltersActionComponent } from './components/project-map/context-menu/actions/packet-filters-action/packet-filters-action.component';
import { PacketFiltersDialogComponent } from './components/project-map/packet-capturing/packet-filters/packet-filters.component';
import { HelpDialogComponent } from './components/project-map/help-dialog/help-dialog.component';
import { StartCaptureActionComponent } from './components/project-map/context-menu/actions/start-capture/start-capture-action.component';
import { StartCaptureDialogComponent } from './components/project-map/packet-capturing/start-capture/start-capture.component';
import { SuspendLinkActionComponent } from './components/project-map/context-menu/actions/suspend-link/suspend-link-action.component';
import { ResumeLinkActionComponent } from './components/project-map/context-menu/actions/resume-link-action/resume-link-action.component';
import { StopCaptureActionComponent } from './components/project-map/context-menu/actions/stop-capture/stop-capture-action.component';
import { MapScaleService } from './services/mapScale.service';
import { AdbutlerComponent } from './components/adbutler/adbutler.component';
import { ConsoleService } from './services/settings/console.service';
import { DefaultConsoleService } from './services/settings/default-console.service';
import { NodeCreatedLabelStylesFixer } from './components/project-map/helpers/node-created-label-styles-fixer';
import { NotificationBoxComponent } from './components/notification-box/notification-box.component';
import { NonNegativeValidator } from './validators/non-negative-validator';
import { RotationValidator } from './validators/rotation-validator';
import { DuplicateActionComponent } from './components/project-map/context-menu/actions/duplicate-action/duplicate-action.component';
import { MapSettingsService } from './services/mapsettings.service';
import { ProjectMapMenuComponent } from './components/project-map/project-map-menu/project-map-menu.component';
import { HelpComponent } from './components/help/help.component';
import { ConfigEditorDialogComponent } from './components/project-map/node-editors/config-editor/config-editor.component';
import { EditConfigActionComponent } from './components/project-map/context-menu/actions/edit-config/edit-config-action.component';
import { LogConsoleComponent } from './components/project-map/log-console/log-console.component';
import { LogEventsDataSource } from './components/project-map/log-console/log-events-datasource';
import { SaveProjectDialogComponent } from './components/projects/save-project-dialog/save-project-dialog.component';
import { TopologySummaryComponent } from './components/topology-summary/topology-summary.component';
import { ShowNodeActionComponent } from './components/project-map/context-menu/actions/show-node-action/show-node-action.component';
import { InfoDialogComponent } from './components/project-map/info-dialog/info-dialog.component';
import { InfoService } from './services/info.service';
import { BringToFrontActionComponent } from './components/project-map/context-menu/actions/bring-to-front-action/bring-to-front-action.component';
import { ExportConfigActionComponent } from './components/project-map/context-menu/actions/export-config/export-config-action.component';
import { ImportConfigActionComponent } from './components/project-map/context-menu/actions/import-config/import-config-action.component';
import { ConsoleDeviceActionBrowserComponent } from './components/project-map/context-menu/actions/console-device-action-browser/console-device-action-browser.component';
import { ChangeSymbolDialogComponent } from './components/project-map/change-symbol-dialog/change-symbol-dialog.component';
import { ChangeSymbolActionComponent } from './components/project-map/context-menu/actions/change-symbol/change-symbol-action.component';
import { EditProjectDialogComponent } from './components/projects/edit-project-dialog/edit-project-dialog.component';
import { ProjectsFilter } from './filters/projectsFilter.pipe';
import { ComputeService } from './services/compute.service';
import { ReloadNodeActionComponent } from './components/project-map/context-menu/actions/reload-node-action/reload-node-action.component';
import { SuspendNodeActionComponent } from './components/project-map/context-menu/actions/suspend-node-action/suspend-node-action.component';
import { ConfigActionComponent } from './components/project-map/context-menu/actions/config-action/config-action.component';
import { ConfiguratorDialogVpcsComponent } from './components/project-map/node-editors/configurator/vpcs/configurator-vpcs.component';
import { ConfiguratorDialogEthernetHubComponent } from './components/project-map/node-editors/configurator/ethernet_hub/configurator-ethernet-hub.component';
import { ConfiguratorDialogEthernetSwitchComponent } from './components/project-map/node-editors/configurator/ethernet-switch/configurator-ethernet-switch.component';
import { PortsComponent } from './components/preferences/common/ports/ports.component';
import { ConfiguratorDialogSwitchComponent } from './components/project-map/node-editors/configurator/switch/configurator-switch.component';
import { ConfiguratorDialogVirtualBoxComponent } from './components/project-map/node-editors/configurator/virtualbox/configurator-virtualbox.component';
import { CustomAdaptersTableComponent } from './components/preferences/common/custom-adapters-table/custom-adapters-table.component';
import { ConfiguratorDialogQemuComponent } from './components/project-map/node-editors/configurator/qemu/configurator-qemu.component';
import { ConfiguratorDialogCloudComponent } from './components/project-map/node-editors/configurator/cloud/configurator-cloud.component';
import { UdpTunnelsComponent } from './components/preferences/common/udp-tunnels/udp-tunnels.component';
import { ConfiguratorDialogAtmSwitchComponent } from './components/project-map/node-editors/configurator/atm_switch/configurator-atm-switch.component';
import { ConfiguratorDialogVmwareComponent } from './components/project-map/node-editors/configurator/vmware/configurator-vmware.component';
import { ConfiguratorDialogIouComponent } from './components/project-map/node-editors/configurator/iou/configurator-iou.component';
import { ConfiguratorDialogIosComponent } from './components/project-map/node-editors/configurator/ios/configurator-ios.component';
import { ConfiguratorDialogDockerComponent } from './components/project-map/node-editors/configurator/docker/configurator-docker.component';
import { EditNetworkConfigurationDialogComponent } from './components/project-map/node-editors/configurator/docker/edit-network-configuration/edit-network-configuration.component';
import { ConfigureCustomAdaptersDialogComponent } from './components/project-map/node-editors/configurator/docker/configure-custom-adapters/configure-custom-adapters.component';
import { ConfiguratorDialogNatComponent } from './components/project-map/node-editors/configurator/nat/configurator-nat.component';
import { ConfiguratorDialogTracengComponent } from './components/project-map/node-editors/configurator/traceng/configurator-traceng.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 { TracengTemplatesComponent } from './components/preferences/traceng/traceng-templates/traceng-templates.component';
import { TracengService } from './services/traceng.service';
import { TracengTemplateDetailsComponent } from './components/preferences/traceng/traceng-template-details/traceng-template-details.component';
import { QemuImageCreatorComponent } from './components/project-map/node-editors/configurator/qemu/qemu-image-creator/qemu-image-creator.component';
import { ChooseNameDialogComponent } from './components/projects/choose-name-dialog/choose-name-dialog.component';
import { PacketCaptureService } from './services/packet-capture.service';
import { StartCaptureOnStartedLinkActionComponent } from './components/project-map/context-menu/actions/start-capture-on-started-link/start-capture-on-started-link.component';
import { LockActionComponent } from './components/project-map/context-menu/actions/lock-action/lock-action.component';
import { NavigationDialogComponent } from './components/projects/navigation-dialog/navigation-dialog.component';
import { ScreenshotDialogComponent } from './components/project-map/screenshot-dialog/screenshot-dialog.component';
import { ResizableModule } from 'angular-resizable-element';
import { DragAndDropModule } from 'angular-draggable-droppable';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { PageNotFoundComponent } from './components/page-not-found/page-not-found.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 { ConfirmationBottomSheetComponent } from './components/projects/confirmation-bottomsheet/confirmation-bottomsheet.component';
import { TemplateFilter } from './filters/templateFilter.pipe';
import { NotificationService } from './services/notification.service';
import { ConfigDialogComponent } from './components/project-map/context-menu/dialogs/config-dialog/config-dialog.component';
import { Gns3vmComponent } from './components/preferences/gns3vm/gns3vm.component';
import { Gns3vmService } from './services/gns3vm.service';
import { ThemeService } from './services/theme.service';
import { ConfigureGns3VMDialogComponent } from './components/servers/configure-gns3vm-dialog/configure-gns3vm-dialog.component';
import { ImportApplianceComponent } from './components/project-map/import-appliance/import-appliance.component';
import { GoogleAnalyticsService } from './services/google-analytics.service';
import { DirectLinkComponent } from './components/direct-link/direct-link.component';
import { SystemStatusComponent } from './components/system-status/system-status.component';
import { StatusInfoComponent } from './components/system-status/status-info/status-info.component';
import { StatusChartComponent } from './components/system-status/status-chart/status-chart.component';
import { NgCircleProgressModule } from 'ng-circle-progress';
import { OpenFileExplorerActionComponent } from './components/project-map/context-menu/actions/open-file-explorer/open-file-explorer-action.component';
import { NgxChildProcessModule } from 'ngx-childprocess';
import { ServerResolve } from './resolvers/server-resolve';
import { HttpConsoleActionComponent } from './components/project-map/context-menu/actions/http-console/http-console-action.component';
import { WebConsoleComponent } from './components/project-map/web-console/web-console.component';
import { ConsoleWrapperComponent } from './components/project-map/console-wrapper/console-wrapper.component';
import { NodeConsoleService } from './services/nodeConsole.service';
import { HttpConsoleNewTabActionComponent } from './components/project-map/context-menu/actions/http-console-new-tab/http-console-new-tab-action.component';
import { WebConsoleFullWindowComponent } from './components/web-console-full-window/web-console-full-window.component';
import { ConsoleGuard } from './guards/console-guard';
import { NewTemplateDialogComponent } from './components/project-map/new-template-dialog/new-template-dialog.component';
import { ApplianceService } from './services/appliances.service';
import { DataSourceFilter } from './filters/dataSourceFilter';
import { ChangeHostnameActionComponent } from './components/project-map/context-menu/actions/change-hostname/change-hostname-action.component';
import { ChangeHostnameDialogComponent } from './components/project-map/change-hostname-dialog/change-hostname-dialog.component';
import { ApplianceInfoDialogComponent } from './components/project-map/new-template-dialog/appliance-info-dialog/appliance-info-dialog.component';
import { InformationDialogComponent } from './components/dialogs/information-dialog.component';
import { TemplateNameDialogComponent } from './components/project-map/new-template-dialog/template-name-dialog/template-name-dialog.component';
@NgModule({
@ -291,332 +61,59 @@ import { TemplateNameDialogComponent } from './components/project-map/new-templa
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,
ProjectMapShortcutsComponent,
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,
NotificationBoxComponent,
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,
HotkeyModule.forRoot(),
PersistenceModule,
NgxElectronModule,
FileUploadModule,
MatSidenavModule,
ResizableModule,
DragAndDropModule,
DragDropModule,
NgxChildProcessModule,
MATERIAL_IMPORTS,
NgCircleProgressModule.forRoot()
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,
NotificationService,
Gns3vmService,
ThemeService,
GoogleAnalyticsService,
NodeConsoleService,
ServerResolve,
ConsoleGuard,
Title,
ApplianceService
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 { }

View File

@ -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>

View File

@ -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;
}

View File

@ -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();
});
});

View File

@ -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() {}
}

View File

@ -0,0 +1,3 @@
<button mat-icon-button (click)="listAppliancesModal()">
<mat-icon>add_to_queue</mat-icon>
</button>

View File

@ -1,25 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AdbutlerComponent } from './adbutler.component';
import { ApplianceComponent } from './appliance.component';
xdescribe('AdbutlerComponent', () => {
let component: AdbutlerComponent;
let fixture: ComponentFixture<AdbutlerComponent>;
describe('ApplianceComponent', () => {
let component: ApplianceComponent;
let fixture: ComponentFixture<ApplianceComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AdbutlerComponent ]
declarations: [ ApplianceComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AdbutlerComponent);
fixture = TestBed.createComponent(ApplianceComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
xit('should create', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
});

View 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);
}
});
}
}

View File

@ -1,27 +0,0 @@
import { NodeComponent } from './components/experimental-map/node/node.component';
import { LinkComponent } from './components/experimental-map/link/link.component';
import { StatusComponent } from './components/experimental-map/status/status.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 { DraggableComponent } from './components/experimental-map/draggable/draggable.component';
import { SelectionComponent } from './components/experimental-map/selection/selection.component';
export const ANGULAR_MAP_DECLARATIONS = [
NodeComponent,
LinkComponent,
StatusComponent,
DrawingComponent,
EllipseComponent,
ImageComponent,
LineComponent,
RectComponent,
TextComponent,
DraggableComponent,
SelectionComponent,
InterfaceLabelComponent
];

View File

@ -1,129 +1,12 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { CssFixer } from './helpers/css-fixer';
import { FontFixer } from './helpers/font-fixer';
import { DefaultDrawingsFactory } from './helpers/default-drawings-factory';
import { MultiLinkCalculatorHelper } from './helpers/multi-link-calculator-helper';
import { SvgToDrawingConverter } from './helpers/svg-to-drawing-converter';
import { QtDasharrayFixer } from './helpers/qt-dasharray-fixer';
import { LayersManager } from './managers/layers-manager';
import { MapChangeDetectorRef } from './services/map-change-detector-ref';
import { Context } from './models/context';
import { ANGULAR_MAP_DECLARATIONS } from './angular-map.imports';
import { D3_MAP_IMPORTS } from './d3-map.imports';
import { CanvasSizeDetector } from './helpers/canvas-size-detector';
import { DrawingsEventSource } from './events/drawings-event-source';
import { NodesEventSource } from './events/nodes-event-source';
import { MapDrawingToSvgConverter } from './converters/map/map-drawing-to-svg-converter';
import { DrawingToMapDrawingConverter } from './converters/map/drawing-to-map-drawing-converter';
import { LabelToMapLabelConverter } from './converters/map/label-to-map-label-converter';
import { LinkToMapLinkConverter } from './converters/map/link-to-map-link-converter';
import { MapDrawingToDrawingConverter } from './converters/map/map-drawing-to-drawing-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 { LinkNodeToMapLinkNodeConverter } from './converters/map/link-node-to-map-link-node-converter';
import { GraphDataManager } from './managers/graph-data-manager';
import {
MapNodesDataSource,
MapLinksDataSource,
MapDrawingsDataSource,
MapSymbolsDataSource
} from './datasources/map-datasource';
import { LinksEventSource } from './events/links-event-source';
import { D3MapComponent } from './components/d3-map/d3-map.component';
import { ExperimentalMapComponent } from './components/experimental-map/experimental-map.component';
import { SelectionEventSource } from './events/selection-event-source';
import { SelectionControlComponent } from './components/selection-control/selection-control.component';
import { SelectionSelectComponent } from './components/selection-select/selection-select.component';
import { DraggableSelectionComponent } from './components/draggable-selection/draggable-selection.component';
import { MapSettingsManager } from './managers/map-settings-manager';
import { DrawingResizingComponent } from './components/drawing-resizing/drawing-resizing.component';
import { FontBBoxCalculator } from './helpers/font-bbox-calculator';
import { StylesToFontConverter } from './converters/styles-to-font-converter';
import { TextElementFactory } from './helpers/drawings-factory/text-element-factory';
import { EllipseElementFactory } from './helpers/drawings-factory/ellipse-element-factory';
import { RectangleElementFactory } from './helpers/drawings-factory/rectangle-element-factory';
import { LineElementFactory } from './helpers/drawings-factory/line-element-factory';
import { TextEditorComponent } from './components/text-editor/text-editor.component';
import { DrawingAddingComponent } from './components/drawing-adding/drawing-adding.component';
import { MovingEventSource } from './events/moving-event-source';
import { MovingCanvasDirective } from './directives/moving-canvas.directive';
import { ZoomingCanvasDirective } from './directives/zooming-canvas.directive';
import { EthernetLinkWidget } from './widgets/links/ethernet-link';
import { SerialLinkWidget } from './widgets/links/serial-link';
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 { }

View File

@ -1,24 +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.4 KiB

View File

@ -1,3 +0,0 @@
svg {
display: block;
}

View File

@ -1,235 +0,0 @@
import {
Component,
ElementRef,
HostListener,
Input,
OnChanges,
OnDestroy,
OnInit,
SimpleChange,
EventEmitter,
Output,
ViewChild
} from '@angular/core';
import { Selection, select } from 'd3-selection';
import { GraphLayout } from '../../widgets/graph-layout';
import { Context } from '../../models/context';
import { Size } from '../../models/size';
import { Subscription } from 'rxjs';
import { InterfaceLabelWidget } from '../../widgets/interface-label';
import { SelectionTool } from '../../tools/selection-tool';
import { MovingTool } from '../../tools/moving-tool';
import { MapChangeDetectorRef } from '../../services/map-change-detector-ref';
import { CanvasSizeDetector } from '../../helpers/canvas-size-detector';
import { Node } from '../../models/node';
import { Link } from '../../../models/link';
import { Drawing } from '../../models/drawing';
import { Symbol } from '../../../models/symbol';
import { GraphDataManager } from '../../managers/graph-data-manager';
import { MapSettingsManager } from '../../managers/map-settings-manager';
import { Server } from '../../../models/server';
import { ToolsService } from '../../../services/tools.service';
import { TextEditorComponent } from '../text-editor/text-editor.component';
import { MapScaleService } from '../../../services/mapScale.service';
import { Project } from '../../../models/project';
import { MapSettingsService } from '../../../services/mapsettings.service';
@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 {
this.svg.attr('height', this.height);
}
}
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();
}
}

View File

@ -1,548 +0,0 @@
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { DraggableSelectionComponent } from './draggable-selection.component';
import { NodesWidget } from '../../widgets/nodes';
import { DrawingsWidget } from '../../widgets/drawings';
import { LinksWidget } from '../../widgets/links';
import { LabelWidget } from '../../widgets/label';
import { InterfaceLabelWidget } from '../../widgets/interface-label';
import { SelectionManager } from '../../managers/selection-manager';
import { SelectionManagerMock } from '../../managers/selection-manager.spec';
import { NodesEventSource } from '../../events/nodes-event-source';
import { DrawingsEventSource } from '../../events/drawings-event-source';
import { GraphDataManager } from '../../managers/graph-data-manager';
import { MockedGraphDataManager } from '../../managers/graph-data-manager.spec';
import { LinksEventSource } from '../../events/links-event-source';
import { DraggableStart, DraggableDrag, DraggableEnd } from '../../events/draggable';
import { MapNode } from '../../models/map/map-node';
import { EventEmitter } from '@angular/core';
import { MapDrawing } from '../../models/map/map-drawing';
import { MapLabel } from '../../models/map/map-label';
import { MapLinkNode } from '../../models/map/map-link-node';
import { select } from 'd3-selection';
import { MapLink } from '../../models/map/map-link';
import { MapSettingsService } from '../../../services/mapsettings.service';
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();
}));
});
});

View File

@ -1,222 +0,0 @@
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
import { Subscription, merge } from 'rxjs';
import { NodesWidget } from '../../widgets/nodes';
import { DrawingsWidget } from '../../widgets/drawings';
import { LinksWidget } from '../../widgets/links';
import { SelectionManager } from '../../managers/selection-manager';
import { NodesEventSource } from '../../events/nodes-event-source';
import { DrawingsEventSource } from '../../events/drawings-event-source';
import { GraphDataManager } from '../../managers/graph-data-manager';
import { DraggableStart, DraggableDrag, DraggableEnd } from '../../events/draggable';
import { MapNode } from '../../models/map/map-node';
import { MapDrawing } from '../../models/map/map-drawing';
import { DraggedDataEvent } from '../../events/event-source';
import { select } from 'd3-selection';
import { MapLabel } from '../../models/map/map-label';
import { LabelWidget } from '../../widgets/label';
import { InterfaceLabelWidget } from '../../widgets/interface-label';
import { MapLinkNode } from '../../models/map/map-link-node';
import { LinksEventSource } from '../../events/links-event-source';
import { MapSettingsService } from '../../../services/mapsettings.service';
@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();
}
}

View File

@ -1,48 +0,0 @@
import { DrawingAddingComponent } from './drawing-adding.component';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { DrawingsEventSource } from '../../events/drawings-event-source';
import { Context } from '../../models/context';
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();
});
});

View File

@ -1,47 +0,0 @@
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { Context } from '../../models/context';
import { DrawingsEventSource } from '../../events/drawings-event-source';
import { AddedDataEvent } from '../../events/event-source';
import { Subscription } from 'rxjs';
@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();
}
}

View File

@ -1,68 +0,0 @@
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { DrawingResizingComponent } from './drawing-resizing.component';
import { DrawingsWidget } from '../../widgets/drawings';
import { DrawingsEventSource } from '../../events/drawings-event-source';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { EventEmitter } from '@angular/core';
import { ResizingEnd } from '../../events/resizing';
import { MapDrawing } from '../../models/map/map-drawing';
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();
});
});

View File

@ -1,32 +0,0 @@
import { Component, OnInit, ElementRef, OnDestroy, Input, Output, EventEmitter } from '@angular/core';
import { Subscription } from 'rxjs';
import { DrawingsEventSource } from '../../events/drawings-event-source';
import { DrawingsWidget } from '../../widgets/drawings';
import { MapDrawing } from '../../models/map/map-drawing';
import { ResizedDataEvent } from '../../events/event-source';
import { ResizingEnd } from '../../events/resizing';
@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();
}
}

View File

@ -1,24 +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();
// });
});

View File

@ -1,89 +0,0 @@
import { Component, OnInit, ElementRef, AfterViewInit, OnDestroy, Input, Output, EventEmitter } 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();
}
}

View File

@ -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

View File

@ -1,24 +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();
// });
});

View File

@ -1,71 +0,0 @@
import { Component, OnInit, Input, ChangeDetectorRef } from '@angular/core';
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 { SvgToDrawingConverter } from '../../../helpers/svg-to-drawing-converter';
import { DraggedDataEvent } from '../../../events/event-source';
import { MapDrawing } from '../../../models/map/map-drawing';
import { DrawingsEventSource } from '../../../events/drawings-event-source';
@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) {
console.log(`Cannot convert due to Error: '${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})`;
}
}

View File

@ -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

View File

@ -1,24 +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();
// });
});

View File

@ -1,37 +0,0 @@
import { Component, OnInit, Input } from '@angular/core';
import { EllipseElement } from '../../../../../models/drawings/ellipse-element';
import { QtDasharrayFixer } from '../../../../../helpers/qt-dasharray-fixer';
@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;
}
}

View File

@ -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

View File

@ -1,24 +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();
// });
});

View File

@ -1,15 +0,0 @@
import { Component, OnInit, Input } 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() {}
}

View File

@ -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

View File

@ -1,24 +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();
// });
});

View File

@ -1,30 +0,0 @@
import { Component, OnInit, Input } 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;
}
}

View File

@ -1,10 +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"
/>

Before

Width:  |  Height:  |  Size: 278 B

View File

@ -1,24 +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();
// });
});

View File

@ -1,37 +0,0 @@
import { Component, OnInit, Input } from '@angular/core';
import { RectElement } from '../../../../../models/drawings/rect-element';
import { QtDasharrayFixer } from '../../../../../helpers/qt-dasharray-fixer';
@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;
}
}

View File

@ -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

View File

@ -1,24 +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();
// });
});

View File

@ -1,64 +0,0 @@
import { Component, OnInit, Input, ViewChild, ElementRef, DoCheck } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { TextElement } from '../../../../../models/drawings/text-element';
import { FontFixer } from '../../../../../helpers/font-fixer';
@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/);
}
}

View File

@ -1,25 +0,0 @@
<svg #svg class="map" preserveAspectRatio="none" [attr.width]="width" [attr.height]="height">
<g [attr.transform]="transform">
<g *ngFor="let layer of layers">
<g class="links">
<g
*ngFor="let link of layer.links"
[app-link]="link"
[show-interface-labels]="settings.show_interface_labels"
></g>
<!-- [node-changed]="nodeChanged" -->
</g>
<g class="nodes">
<g *ngFor="let node of layer.nodes" [app-node]="node" [symbols]="symbols"></g>
<!-- [node-changed]="nodeChanged"
(valueChange)="onNodeChanged($event)" -->
</g>
<g class="drawings"><g *ngFor="let drawing of layer.drawings" [app-drawing]="drawing"></g></g>
</g>
</g>
<g [app-selection]="svg"></g>
<!-- (selected)="onSelection($event)" -->
<filter id="grayscale"><feColorMatrix id="feGrayscale" type="saturate" values="0" /></filter>
</svg>

Before

Width:  |  Height:  |  Size: 930 B

View File

@ -1,3 +0,0 @@
svg {
display: block;
}

View File

@ -1,26 +0,0 @@
// import { async, ComponentFixture, TestBed } from '@angular/core/testing';
// import { MapComponent } from './map.component';
// describe('MapComponent', () => {
// let component: MapComponent;
// let fixture: ComponentFixture<MapComponent>;
// beforeEach(async(() => {
// TestBed.configureTestingModule({
// declarations: [ MapComponent ]
// })
// .compileComponents();
// }));
// // beforeEach(() => {
// // fixture = TestBed.createComponent(MapComponent);
// // component = fixture.componentInstance;
// // fixture.detectChanges();
// // });
// //
// // it('should create', () => {
// // expect(component).toBeTruthy();
// // });
// });
// //

View File

@ -1,132 +0,0 @@
import {
Component,
ElementRef,
HostListener,
Input,
OnChanges,
OnDestroy,
OnInit,
SimpleChange,
ChangeDetectionStrategy,
ChangeDetectorRef,
ViewChild
} from '@angular/core';
import { GraphLayout } from '../../widgets/graph-layout';
import { Context } from '../../models/context';
import { Size } from '../../models/size';
import { Subscription } from 'rxjs';
import { MapChangeDetectorRef } from '../../services/map-change-detector-ref';
import { CanvasSizeDetector } from '../../helpers/canvas-size-detector';
import { Node } from '../../models/node';
import { Link } from '../../../models/link';
import { Drawing } from '../../models/drawing';
import { Symbol } from '../../../models/symbol';
import { GraphDataManager } from '../../managers/graph-data-manager';
import { LayersManager } from '../../managers/layers-manager';
@Component({
selector: 'app-experimental-map',
templateUrl: './experimental-map.component.html',
styleUrls: ['./experimental-map.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ExperimentalMapComponent implements OnInit, OnChanges, OnDestroy {
@Input() nodes: Node[] = [];
@Input() links: Link[] = [];
@Input() drawings: Drawing[] = [];
@Input() symbols: Symbol[] = [];
// @Input() changed: EventEmitter<any>;
// @Input('node-updated') nodeUpdated: EventEmitter<any>;
@Input() width = 1500;
@Input() height = 600;
@ViewChild('svg') svg: ElementRef;
private changesDetected: Subscription;
protected settings = {
show_interface_labels: true
};
constructor(
private graphDataManager: GraphDataManager,
private context: Context,
private mapChangeDetectorRef: MapChangeDetectorRef,
private canvasSizeDetector: CanvasSizeDetector,
private changeDetectorRef: ChangeDetectorRef,
private layersManger: LayersManager,
public graphLayout: GraphLayout
) {}
@Input('show-interface-labels')
set showInterfaceLabels(value) {
this.settings.show_interface_labels = value;
this.mapChangeDetectorRef.detectChanges();
}
@Input('moving-tool')
set movingTool(value) {
this.mapChangeDetectorRef.detectChanges();
}
@Input('selection-tool')
set selectionTool(value) {
this.mapChangeDetectorRef.detectChanges();
}
@Input('draw-link-tool') drawLinkTool: boolean;
@Input('readonly') set readonly(value) {}
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {}
ngOnInit() {
// this.changeDetectorRef.detach();
this.changesDetected = this.mapChangeDetectorRef.changesDetected.subscribe(() => {
this.graphDataManager.setNodes(this.nodes);
this.graphDataManager.setLinks(this.links);
this.graphDataManager.setDrawings(this.drawings);
this.graphDataManager.setSymbols(this.symbols);
this.changeDetectorRef.detectChanges();
});
// this.changedSubscription = this.changed.subscribe(() => {
// this.changeDetectorRef.detectChanges();
// });
// this.nodeUpdated.subscribe((node: Node) => {
// this.nodeChanged.emit(node);
// });
}
ngOnDestroy() {
this.changesDetected.unsubscribe();
// this.changedSubscription.unsubscribe();
}
public getSize(): Size {
return this.canvasSizeDetector.getOptimalSize(this.width, this.height);
}
public get layers() {
return this.layersManger.getLayersList();
}
public get transform() {
const ctx = new Context();
ctx.size = this.getSize();
const xTrans = ctx.getZeroZeroTransformationPoint().x + ctx.transformation.x;
const yTrans = ctx.getZeroZeroTransformationPoint().y + ctx.transformation.y;
const kTrans = ctx.transformation.k;
return `translate(${xTrans}, ${yTrans}) scale(${kTrans})`;
}
@HostListener('window:resize', ['$event'])
onResize(event) {}
}

View File

@ -1,16 +0,0 @@
<svg:g class="text_container" [attr.transform]="transform" width="100" height="100">
<svg:rect
stroke-dasharray="3,3"
stroke-width="0.5"
fill="none"
stroke="black"
[attr.x]="rectX"
[attr.y]="rectY"
[attr.width]="rectWidth"
[attr.height]="rectHeight"
/>
<svg:text #textSvg class="interface_label" [attr.style]="sanitizedStyle" [attr.x]="borderSize" [attr.y]="-borderSize">
{{ text }}
</svg:text>
</svg:g>

Before

Width:  |  Height:  |  Size: 449 B

Some files were not shown because too many files have changed in this diff Show More