mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-02-22 18:22:35 +00:00
Merge branch 'master' into Rewrite-MovingTool-to-custom-solution
This commit is contained in:
commit
ee54d620ab
@ -20,7 +20,7 @@ install:
|
|||||||
build_script:
|
build_script:
|
||||||
- yarn buildforelectron
|
- yarn buildforelectron
|
||||||
- "%PYTHON%\\python.exe -m pip install -r scripts\\requirements.txt"
|
- "%PYTHON%\\python.exe -m pip install -r scripts\\requirements.txt"
|
||||||
- "%PYTHON%\\python.exe scripts\\build.py download"
|
- "%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 build_exe -b dist/exe.gns3server -s"
|
||||||
- "%PYTHON%\\python.exe scripts\\build.py validate -b dist"
|
- "%PYTHON%\\python.exe scripts\\build.py validate -b dist"
|
||||||
- "%PYTHON%\\python.exe scripts\\build.py download_dependencies -b dist"
|
- "%PYTHON%\\python.exe scripts\\build.py download_dependencies -b dist"
|
||||||
|
@ -47,7 +47,7 @@ jobs:
|
|||||||
command: |
|
command: |
|
||||||
python3 -V
|
python3 -V
|
||||||
pip3 install -r scripts/requirements.txt
|
pip3 install -r scripts/requirements.txt
|
||||||
python3 scripts/build.py download
|
python3 scripts/build.py download -a
|
||||||
python3 scripts/build.py build_exe -b dist/exe.gns3server -s
|
python3 scripts/build.py build_exe -b dist/exe.gns3server -s
|
||||||
python3 scripts/build.py validate -b dist
|
python3 scripts/build.py validate -b dist
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
|
|
||||||
node_js:
|
node_js:
|
||||||
- node
|
- '10' # use node for latest
|
||||||
|
|
||||||
# Issue with Travis: https://github.com/travis-ci/travis-ci/issues/8836#issuecomment-356362524
|
# Issue with Travis: https://github.com/travis-ci/travis-ci/issues/8836#issuecomment-356362524
|
||||||
sudo: required
|
sudo: required
|
||||||
@ -65,7 +65,7 @@ after_script:
|
|||||||
- yarn buildforelectron
|
- yarn buildforelectron
|
||||||
- |
|
- |
|
||||||
python3 -m pip install -r scripts/requirements.txt
|
python3 -m pip install -r scripts/requirements.txt
|
||||||
python3 scripts/build.py download
|
python3 scripts/build.py download -a
|
||||||
python3 scripts/build.py build_exe -b dist/exe.gns3server -s
|
python3 scripts/build.py build_exe -b dist/exe.gns3server -s
|
||||||
python3 scripts/build.py validate -b dist
|
python3 scripts/build.py validate -b dist
|
||||||
- yarn electron-builder --linux --x64 --publish always
|
- yarn electron-builder --linux --x64 --publish always
|
||||||
@ -73,7 +73,7 @@ after_script:
|
|||||||
# build sourcemaps and upload to Sentry
|
# build sourcemaps and upload to Sentry
|
||||||
# fix node issue with memory
|
# fix node issue with memory
|
||||||
- |
|
- |
|
||||||
if [ -n "$TRAVIS_TAG" ];
|
if [ -n "$TRAVIS_TAG" ]; then
|
||||||
export NODE_OPTIONS=--max_old_space_size=4096
|
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);")
|
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 ng build --configuration=production --base-href /static/web-ui/
|
||||||
|
65
console-executor.js
Normal file
65
console-executor.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
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;
|
||||||
|
}
|
@ -16,6 +16,7 @@ files:
|
|||||||
- sentry.js
|
- sentry.js
|
||||||
- installed-software.js
|
- installed-software.js
|
||||||
- local-server.js
|
- local-server.js
|
||||||
|
- console-executor.js
|
||||||
- package.json
|
- package.json
|
||||||
|
|
||||||
extraFiles:
|
extraFiles:
|
||||||
|
78
package.json
78
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gns3-web-ui",
|
"name": "gns3-web-ui",
|
||||||
"version": "2019.1.0-alpha.4dev",
|
"version": "2019.2.0-alpha.4dev",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "GNS3 Technology Inc.",
|
"name": "GNS3 Technology Inc.",
|
||||||
"email": "developers@gns3.com"
|
"email": "developers@gns3.com"
|
||||||
@ -38,23 +38,23 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^7.2.7",
|
"@angular/animations": "^7.2.14",
|
||||||
"@angular/cdk": "^7.3.3",
|
"@angular/cdk": "^7.3.7",
|
||||||
"@angular/common": "^7.2.7",
|
"@angular/common": "^7.2.14",
|
||||||
"@angular/compiler": "^7.2.7",
|
"@angular/compiler": "^7.2.14",
|
||||||
"@angular/core": "^7.2.7",
|
"@angular/core": "^7.2.14",
|
||||||
"@angular/forms": "^7.2.7",
|
"@angular/forms": "^7.2.14",
|
||||||
"@angular/http": "^7.2.7",
|
"@angular/http": "^7.2.14",
|
||||||
"@angular/material": "^7.3.3",
|
"@angular/material": "^7.3.7",
|
||||||
"@angular/platform-browser": "^7.2.7",
|
"@angular/platform-browser": "^7.2.14",
|
||||||
"@angular/platform-browser-dynamic": "^7.2.7",
|
"@angular/platform-browser-dynamic": "^7.2.14",
|
||||||
"@angular/router": "^7.2.7",
|
"@angular/router": "^7.2.14",
|
||||||
"angular-persistence": "^1.0.1",
|
"angular-persistence": "^1.0.1",
|
||||||
"angular2-hotkeys": "^2.1.4",
|
"angular2-hotkeys": "^2.1.4",
|
||||||
"angular2-indexeddb": "^1.2.3",
|
"angular2-indexeddb": "^1.2.3",
|
||||||
"bootstrap": "4.3.1",
|
"bootstrap": "4.3.1",
|
||||||
"command-exists": "^1.2.8",
|
"command-exists": "^1.2.8",
|
||||||
"core-js": "^2.6.5",
|
"core-js": "^3.0.1",
|
||||||
"css-tree": "^1.0.0-alpha.29",
|
"css-tree": "^1.0.0-alpha.29",
|
||||||
"d3-ng2-service": "^2.1.0",
|
"d3-ng2-service": "^2.1.0",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
@ -62,47 +62,47 @@
|
|||||||
"material-design-icons": "^3.0.1",
|
"material-design-icons": "^3.0.1",
|
||||||
"ng2-file-upload": "^1.3.0",
|
"ng2-file-upload": "^1.3.0",
|
||||||
"ngx-electron": "^2.1.1",
|
"ngx-electron": "^2.1.1",
|
||||||
"node-fetch": "^2.3.0",
|
"node-fetch": "^2.4.1",
|
||||||
"notosans-fontface": "^1.1.0",
|
"notosans-fontface": "^1.1.0",
|
||||||
"raven-js": "^3.27.0",
|
"raven-js": "^3.27.0",
|
||||||
"rxjs": "^6.4.0",
|
"rxjs": "^6.5.1",
|
||||||
"rxjs-compat": "^6.4.0",
|
"rxjs-compat": "^6.5.1",
|
||||||
"tree-kill": "^1.2.1",
|
"tree-kill": "^1.2.1",
|
||||||
"typeface-roboto": "^0.0.54",
|
"typeface-roboto": "^0.0.54",
|
||||||
"yargs": "^13.2.1",
|
"yargs": "^13.2.2",
|
||||||
"zone.js": "^0.8.29"
|
"zone.js": "^0.9.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "~0.13.3",
|
"@angular-devkit/build-angular": "~0.13.8",
|
||||||
"@angular/cli": "^7.3.3",
|
"@angular/cli": "^7.3.8",
|
||||||
"@angular/compiler-cli": "^7.2.7",
|
"@angular/compiler-cli": "^7.2.14",
|
||||||
"@angular/language-service": "^7.2.7",
|
"@angular/language-service": "^7.2.14",
|
||||||
"@sentry/cli": "^1.40.0",
|
"@sentry/cli": "^1.41.2",
|
||||||
"@sentry/electron": "^0.16.0",
|
"@sentry/electron": "^0.17.1",
|
||||||
"@types/jasmine": "~3.3.9",
|
"@types/jasmine": "~3.3.12",
|
||||||
"@types/jasminewd2": "~2.0.6",
|
"@types/jasminewd2": "~2.0.6",
|
||||||
"@types/node": "~11.9.5",
|
"@types/node": "~12.0.0",
|
||||||
"codelyzer": "~4.5.0",
|
"codelyzer": "~5.0.1",
|
||||||
"electron": "4.0.6",
|
"electron": "5.0.2",
|
||||||
"electron-builder": "20.38.2",
|
"electron-builder": "20.39.0",
|
||||||
"jasmine-core": "~3.3.0",
|
"jasmine-core": "~3.4.0",
|
||||||
"jasmine-spec-reporter": "~4.2.1",
|
"jasmine-spec-reporter": "~4.2.1",
|
||||||
"jquery": "^3.3.1",
|
"jquery": "^3.4.0",
|
||||||
"karma": "~4.0.0",
|
"karma": "~4.1.0",
|
||||||
"karma-chrome-launcher": "~2.2.0",
|
"karma-chrome-launcher": "~2.2.0",
|
||||||
"karma-cli": "~2.0.0",
|
"karma-cli": "~2.0.0",
|
||||||
"karma-coverage-istanbul-reporter": "^2.0.5",
|
"karma-coverage-istanbul-reporter": "^2.0.5",
|
||||||
"karma-jasmine": "~2.0.1",
|
"karma-jasmine": "~2.0.1",
|
||||||
"karma-jasmine-html-reporter": "^1.4.0",
|
"karma-jasmine-html-reporter": "^1.4.2",
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
"node-sass": "^4.11.0",
|
"node-sass": "^4.12.0",
|
||||||
"popper.js": "^1.14.7",
|
"popper.js": "^1.15.0",
|
||||||
"prettier": "^1.16.4",
|
"prettier": "^1.17.0",
|
||||||
"protractor": "~5.4.2",
|
"protractor": "~5.4.2",
|
||||||
"replace": "^1.0.1",
|
"replace": "^1.1.0",
|
||||||
"ts-mockito": "^2.3.1",
|
"ts-mockito": "^2.3.1",
|
||||||
"ts-node": "~8.0.2",
|
"ts-node": "~8.1.0",
|
||||||
"tslint": "~5.13.0",
|
"tslint": "~5.16.0",
|
||||||
"tslint-config-prettier": "^1.18.0",
|
"tslint-config-prettier": "^1.18.0",
|
||||||
"typescript": "<3.3.0"
|
"typescript": "<3.3.0"
|
||||||
},
|
},
|
||||||
|
102
scripts/build.py
102
scripts/build.py
@ -18,6 +18,7 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
import json
|
||||||
import shutil
|
import shutil
|
||||||
import psutil
|
import psutil
|
||||||
import zipfile
|
import zipfile
|
||||||
@ -31,6 +32,7 @@ from multiprocessing.queues import Empty
|
|||||||
|
|
||||||
from cx_Freeze import setup, Executable
|
from cx_Freeze import setup, Executable
|
||||||
|
|
||||||
|
DEFAULT_GNS3_SERVER_DEV_BRANCH = '2.2'
|
||||||
|
|
||||||
FILE_DIR = os.path.dirname(os.path.realpath(__file__))
|
FILE_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||||
WORKING_DIR = os.path.join(FILE_DIR, 'tmp')
|
WORKING_DIR = os.path.join(FILE_DIR, 'tmp')
|
||||||
@ -40,8 +42,9 @@ SOURCE_DESTINATION = os.path.join(WORKING_DIR, 'source')
|
|||||||
BINARIES_EXTENSION = platform.system() == "Windows" and ".exe" or ""
|
BINARIES_EXTENSION = platform.system() == "Windows" and ".exe" or ""
|
||||||
DEPENDENCIES = {
|
DEPENDENCIES = {
|
||||||
'ubridge': {
|
'ubridge': {
|
||||||
|
'type': 'github',
|
||||||
'releases': 'https://api.github.com/repos/GNS3/ubridge/releases',
|
'releases': 'https://api.github.com/repos/GNS3/ubridge/releases',
|
||||||
'version': 'LATEST',
|
'version': '0.9.16', # possible to use LATEST as value
|
||||||
'files': {
|
'files': {
|
||||||
'windows': [
|
'windows': [
|
||||||
'cygwin1.dll',
|
'cygwin1.dll',
|
||||||
@ -50,8 +53,9 @@ DEPENDENCIES = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'vpcs': {
|
'vpcs': {
|
||||||
|
'type': 'github',
|
||||||
'releases': 'https://api.github.com/repos/GNS3/vpcs/releases',
|
'releases': 'https://api.github.com/repos/GNS3/vpcs/releases',
|
||||||
'version': '0.6.1',
|
'version': '0.6.2',
|
||||||
'files': {
|
'files': {
|
||||||
'windows': [
|
'windows': [
|
||||||
'cygwin1.dll',
|
'cygwin1.dll',
|
||||||
@ -60,6 +64,7 @@ DEPENDENCIES = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'dynamips': {
|
'dynamips': {
|
||||||
|
'type': 'github',
|
||||||
'releases': 'https://api.github.com/repos/GNS3/dynamips/releases',
|
'releases': 'https://api.github.com/repos/GNS3/dynamips/releases',
|
||||||
'version': '0.2.17',
|
'version': '0.2.17',
|
||||||
'files': {
|
'files': {
|
||||||
@ -69,6 +74,16 @@ DEPENDENCIES = {
|
|||||||
'nvram_export.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',
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,10 +128,7 @@ def prepare():
|
|||||||
os.makedirs(WORKING_DIR, exist_ok=True)
|
os.makedirs(WORKING_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
def download_dependencies_command(arguments):
|
def download_from_github(name, definition, output_directory):
|
||||||
output_directory = os.path.join(os.getcwd(), arguments.b)
|
|
||||||
|
|
||||||
for name, definition in DEPENDENCIES.items():
|
|
||||||
response = requests.get(definition['releases'])
|
response = requests.get(definition['releases'])
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
releases = response.json()
|
releases = response.json()
|
||||||
@ -140,12 +152,85 @@ def download_dependencies_command(arguments):
|
|||||||
print('Downloaded {} to {}'.format(filename, 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):
|
def download_command(arguments):
|
||||||
|
if arguments.a:
|
||||||
|
auto_version(arguments)
|
||||||
|
|
||||||
shutil.rmtree(SOURCE_DESTINATION, ignore_errors=True)
|
shutil.rmtree(SOURCE_DESTINATION, ignore_errors=True)
|
||||||
os.makedirs(SOURCE_DESTINATION)
|
os.makedirs(SOURCE_DESTINATION)
|
||||||
|
|
||||||
download("https://github.com/GNS3/gns3-server/archive/2.2.zip", SOURCE_ZIP)
|
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)
|
files = unzip(SOURCE_ZIP, SOURCE_DESTINATION)
|
||||||
source_directory = os.path.join(SOURCE_DESTINATION, files[0].filename)
|
source_directory = os.path.join(SOURCE_DESTINATION, files[0].filename)
|
||||||
@ -297,6 +382,9 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
parser_download = subparsers.add_parser(
|
parser_download = subparsers.add_parser(
|
||||||
'download', help='Downloads source code of gns3server')
|
'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 = subparsers.add_parser('build_exe', help='Build gns3server')
|
||||||
parser_build.add_argument('-b', help='Output directory')
|
parser_build.add_argument('-b', help='Output directory')
|
||||||
|
@ -51,6 +51,7 @@ import { CopyIosTemplateComponent } from './components/preferences/dynamips/copy
|
|||||||
import { CopyDockerTemplateComponent } from './components/preferences/docker/copy-docker-template/copy-docker-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 { 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 { ListOfSnapshotsComponent } from './components/snapshots/list-of-snapshots/list-of-snapshots.component';
|
||||||
|
import { ConsoleComponent } from './components/settings/console/console.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@ -62,6 +63,7 @@ const routes: Routes = [
|
|||||||
{ path: 'bundled', component: BundledServerFinderComponent },
|
{ path: 'bundled', component: BundledServerFinderComponent },
|
||||||
{ path: 'server/:server_id/projects', component: ProjectsComponent },
|
{ path: 'server/:server_id/projects', component: ProjectsComponent },
|
||||||
{ path: 'settings', component: SettingsComponent },
|
{ path: 'settings', component: SettingsComponent },
|
||||||
|
{ path: 'settings/console', component: ConsoleComponent },
|
||||||
{ path: 'installed-software', component: InstalledSoftwareComponent },
|
{ path: 'installed-software', component: InstalledSoftwareComponent },
|
||||||
{ path: 'server/:server_id/project/:project_id/snapshots', component: ListOfSnapshotsComponent },
|
{ path: 'server/:server_id/project/:project_id/snapshots', component: ListOfSnapshotsComponent },
|
||||||
{ path: 'server/:server_id/preferences', component: PreferencesComponent },
|
{ path: 'server/:server_id/preferences', component: PreferencesComponent },
|
||||||
|
@ -168,6 +168,9 @@ import { ListOfSnapshotsComponent } from './components/snapshots/list-of-snapsho
|
|||||||
import { DateFilter } from './filters/dateFilter.pipe';
|
import { DateFilter } from './filters/dateFilter.pipe';
|
||||||
import { NameFilter } from './filters/nameFilter.pipe';
|
import { NameFilter } from './filters/nameFilter.pipe';
|
||||||
import { CustomAdaptersComponent } from './components/preferences/common/custom-adapters/custom-adapters.component';
|
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 { 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 { 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 { PacketFiltersDialogComponent } from './components/project-map/packet-capturing/packet-filters/packet-filters.component';
|
||||||
@ -178,6 +181,10 @@ import { SuspendLinkActionComponent } from './components/project-map/context-men
|
|||||||
import { ResumeLinkActionComponent } from './components/project-map/context-menu/actions/resume-link-action/resume-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 { StopCaptureActionComponent } from './components/project-map/context-menu/actions/stop-capture/stop-capture-action.component';
|
||||||
import { MapScaleService } from './services/mapScale.service';
|
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';
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
|
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
|
||||||
@ -292,6 +299,10 @@ if (environment.production) {
|
|||||||
NameFilter,
|
NameFilter,
|
||||||
ListOfSnapshotsComponent,
|
ListOfSnapshotsComponent,
|
||||||
CustomAdaptersComponent,
|
CustomAdaptersComponent,
|
||||||
|
NodesMenuComponent,
|
||||||
|
AdbutlerComponent,
|
||||||
|
ConsoleDeviceActionComponent,
|
||||||
|
ConsoleComponent,
|
||||||
NodesMenuComponent
|
NodesMenuComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
@ -362,7 +373,10 @@ if (environment.production) {
|
|||||||
IouConfigurationService,
|
IouConfigurationService,
|
||||||
RecentlyOpenedProjectService,
|
RecentlyOpenedProjectService,
|
||||||
ServerManagementService,
|
ServerManagementService,
|
||||||
MapScaleService
|
MapScaleService,
|
||||||
|
ConsoleService,
|
||||||
|
DefaultConsoleService,
|
||||||
|
NodeCreatedLabelStylesFixer
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
AddServerDialogComponent,
|
AddServerDialogComponent,
|
||||||
|
@ -5,7 +5,7 @@ import { MatIconModule, MatProgressSpinnerModule } from '@angular/material';
|
|||||||
import { ProgressService } from './progress.service';
|
import { ProgressService } from './progress.service';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
|
||||||
class MockedRouter {
|
class MockedRouter {
|
||||||
events: BehaviorSubject<boolean>;
|
events: BehaviorSubject<boolean>;
|
||||||
@ -13,18 +13,20 @@ class MockedRouter {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.events = new BehaviorSubject(true);
|
this.events = new BehaviorSubject(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
navigateByUrl() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ProgressComponent', () => {
|
describe('ProgressComponent', () => {
|
||||||
let component: ProgressComponent;
|
let component: ProgressComponent;
|
||||||
let fixture: ComponentFixture<ProgressComponent>;
|
let fixture: ComponentFixture<ProgressComponent>;
|
||||||
let progressService: ProgressService;
|
let progressService: ProgressService;
|
||||||
let router: MockedRouter;
|
let router: MockedRouter = new MockedRouter();
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [RouterTestingModule, MatProgressSpinnerModule, MatIconModule],
|
imports: [RouterTestingModule, MatProgressSpinnerModule, MatIconModule],
|
||||||
providers: [ProgressService, { provide: Router, useClass: MockedRouter }],
|
providers: [ProgressService, { provide: Router, useValue: router }],
|
||||||
declarations: [ProgressComponent]
|
declarations: [ProgressComponent]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
@ -71,4 +73,12 @@ describe('ProgressComponent', () => {
|
|||||||
|
|
||||||
expect(progressService.clear).toHaveBeenCalled();
|
expect(progressService.clear).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should reload page after clicking refresh", () => {
|
||||||
|
spyOn(router, 'navigateByUrl');
|
||||||
|
|
||||||
|
component.refresh();
|
||||||
|
|
||||||
|
expect(router.navigateByUrl).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -36,8 +36,7 @@ export class ProgressComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
// unfortunately we need to use global var
|
this.router.navigateByUrl(this.router.url);
|
||||||
location.reload();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
|
15
src/app/components/adbutler/adbutler.component.html
Normal file
15
src/app/components/adbutler/adbutler.component.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
<table class="butler" style="width: 600px; border: 0px solid #C0C0C0; background-color: #263238" cellSpacing=0 cellPadding=3>
|
||||||
|
<tr><td height=80 style="background-color: #263238" #code>
|
||||||
|
<div id="{{ this.divId }}"></div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Begin Ad Code -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- End Ad Code -->
|
||||||
|
</td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
|
7
src/app/components/adbutler/adbutler.component.scss
Normal file
7
src/app/components/adbutler/adbutler.component.scss
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.butler a {
|
||||||
|
color: #0097a7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.butler {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
25
src/app/components/adbutler/adbutler.component.spec.ts
Normal file
25
src/app/components/adbutler/adbutler.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AdbutlerComponent } from './adbutler.component';
|
||||||
|
|
||||||
|
describe('AdbutlerComponent', () => {
|
||||||
|
let component: AdbutlerComponent;
|
||||||
|
let fixture: ComponentFixture<AdbutlerComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ AdbutlerComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AdbutlerComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
68
src/app/components/adbutler/adbutler.component.ts
Normal file
68
src/app/components/adbutler/adbutler.component.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { Component, OnInit, ElementRef, ViewChild, AfterViewInit, ViewEncapsulation, OnDestroy } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-adbutler',
|
||||||
|
templateUrl: './adbutler.component.html',
|
||||||
|
styleUrls: ['./adbutler.component.scss'],
|
||||||
|
encapsulation: ViewEncapsulation.None
|
||||||
|
})
|
||||||
|
export class AdbutlerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
id: number;
|
||||||
|
setId: number;
|
||||||
|
rnd: number;
|
||||||
|
abkw: string;
|
||||||
|
sparkCounter: number;
|
||||||
|
divId: string;
|
||||||
|
|
||||||
|
@ViewChild('code') code: ElementRef;
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
var loadedTextAds355353 = (window as any).loadedTextAds355353;
|
||||||
|
if(loadedTextAds355353 == null) {
|
||||||
|
(window as any).loadedTextAds355353 = new Array();
|
||||||
|
}
|
||||||
|
|
||||||
|
(window as any).id355353 = 165803;
|
||||||
|
(window as any).setID355353 = 355353;
|
||||||
|
(window as any).rnd = (window as any).rnd || Math.floor(Math.random()*10e6);
|
||||||
|
(window as any).abkw = (window as any).abkw ||'';
|
||||||
|
|
||||||
|
var sparkCounter355353 = (window as any).sparkCounter355353;
|
||||||
|
if(sparkCounter355353 == null) {
|
||||||
|
sparkCounter355353 = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sparkCounter355353 = sparkCounter355353 + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
(window as any).sparkCounter355353 = sparkCounter355353;
|
||||||
|
(window as any).loadedTextAds355353[sparkCounter355353] = false;
|
||||||
|
|
||||||
|
this.id = (window as any).id355353;
|
||||||
|
this.divId = "abta355353" + sparkCounter355353;
|
||||||
|
this.setId = (window as any).setID355353;
|
||||||
|
this.abkw = (window as any).abkw;
|
||||||
|
this.rnd = (window as any).rnd;
|
||||||
|
this.sparkCounter = (window as any).sparkCounter355353;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
const scriptUrl = "https://servedbyadbutler.com/adserve/;ID=" + this.id + ";setID=" + this.setId + ";type=textad;kw=" + this.abkw + ";pid=" + this.rnd + ";layoutID=" + this.sparkCounter;
|
||||||
|
|
||||||
|
const scriptElement = document.createElement('script');
|
||||||
|
scriptElement.src = scriptUrl;
|
||||||
|
scriptElement.type = 'text/javascript';
|
||||||
|
scriptElement.async = true;
|
||||||
|
scriptElement.charset = 'utf-8';
|
||||||
|
this.code.nativeElement.appendChild(scriptElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
// start from 0 when switching pages
|
||||||
|
(window as any).sparkCounter355353 = 0;
|
||||||
|
delete (window as any).loadedTextAds355353[this.sparkCounter];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -58,7 +58,7 @@ describe('DrawingAddedComponent', () => {
|
|||||||
component.project = { project_id: 'sampleId' } as Project;
|
component.project = { project_id: 'sampleId' } as Project;
|
||||||
component.selectedDrawing = 'rectangle';
|
component.selectedDrawing = 'rectangle';
|
||||||
const pointToAddSelectedDataEvent = new AddedDataEvent(0, 0);
|
const pointToAddSelectedDataEvent = new AddedDataEvent(0, 0);
|
||||||
spyOn(mockedDrawingService, 'add').and.returnValue(Observable.of({}));
|
spyOn(mockedDrawingService, 'add').and.returnValue(Observable.of());
|
||||||
|
|
||||||
mockedDrawingsEventSource.pointToAddSelected.emit(pointToAddSelectedDataEvent);
|
mockedDrawingsEventSource.pointToAddSelected.emit(pointToAddSelectedDataEvent);
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ describe('DrawingDraggedComponent', () => {
|
|||||||
element: mapDrawingElement
|
element: mapDrawingElement
|
||||||
};
|
};
|
||||||
const drawingDraggedDataEvent = new DraggedDataEvent<MapDrawing>(mapDrawing, 0, 0);
|
const drawingDraggedDataEvent = new DraggedDataEvent<MapDrawing>(mapDrawing, 0, 0);
|
||||||
spyOn(mockedDrawingService, 'updatePosition').and.returnValue(Observable.of({}));
|
spyOn(mockedDrawingService, 'updatePosition').and.returnValue(Observable.of());
|
||||||
|
|
||||||
mockedDrawingsEventSource.dragged.emit(drawingDraggedDataEvent);
|
mockedDrawingsEventSource.dragged.emit(drawingDraggedDataEvent);
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ describe('DrawingResizedComponent', () => {
|
|||||||
element: mapDrawingElement
|
element: mapDrawingElement
|
||||||
};
|
};
|
||||||
const drawingResizedDataEvent = new ResizedDataEvent<MapDrawing>(mapDrawing, 0, 0, 100, 100);
|
const drawingResizedDataEvent = new ResizedDataEvent<MapDrawing>(mapDrawing, 0, 0, 100, 100);
|
||||||
spyOn(mockedDrawingService, 'updateSizeAndPosition').and.returnValue(Observable.of({}));
|
spyOn(mockedDrawingService, 'updateSizeAndPosition').and.returnValue(Observable.of());
|
||||||
|
|
||||||
mockedDrawingsEventSource.resized.emit(drawingResizedDataEvent);
|
mockedDrawingsEventSource.resized.emit(drawingResizedDataEvent);
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ describe('NodeDraggedComponent', () => {
|
|||||||
z: 0
|
z: 0
|
||||||
};
|
};
|
||||||
const draggedDataEvent = new DraggedDataEvent<MapNode>(mapNode, 0, 0);
|
const draggedDataEvent = new DraggedDataEvent<MapNode>(mapNode, 0, 0);
|
||||||
spyOn(mockedNodeService, 'updatePosition').and.returnValue(Observable.of({}));
|
spyOn(mockedNodeService, 'updatePosition').and.returnValue(Observable.of());
|
||||||
|
|
||||||
mockedNodesEventSource.dragged.emit(draggedDataEvent);
|
mockedNodesEventSource.dragged.emit(draggedDataEvent);
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ describe('NodeLabelDraggedComponent', () => {
|
|||||||
nodeId: 'node id'
|
nodeId: 'node id'
|
||||||
};
|
};
|
||||||
const nodeDraggedDataEvent = new DraggedDataEvent<MapLabel>(mapLabel, 0, 0);
|
const nodeDraggedDataEvent = new DraggedDataEvent<MapLabel>(mapLabel, 0, 0);
|
||||||
spyOn(mockedNodeService, 'updateLabel').and.returnValue(Observable.of({}));
|
spyOn(mockedNodeService, 'updateLabel').and.returnValue(Observable.of());
|
||||||
|
|
||||||
mockedNodesEventSource.labelDragged.emit(nodeDraggedDataEvent);
|
mockedNodesEventSource.labelDragged.emit(nodeDraggedDataEvent);
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ describe('TextAddedComponent', () => {
|
|||||||
it('should call drawing service when text added', () => {
|
it('should call drawing service when text added', () => {
|
||||||
component.project = { project_id: 'sampleId' } as Project;
|
component.project = { project_id: 'sampleId' } as Project;
|
||||||
const textAddedDataEvent = new TextAddedDataEvent('savedText', 0, 0);
|
const textAddedDataEvent = new TextAddedDataEvent('savedText', 0, 0);
|
||||||
spyOn(mockedDrawingService, 'add').and.returnValue(Observable.of({}));
|
spyOn(mockedDrawingService, 'add').and.returnValue(Observable.of());
|
||||||
|
|
||||||
mockedDrawingsEventSource.textAdded.emit(textAddedDataEvent);
|
mockedDrawingsEventSource.textAdded.emit(textAddedDataEvent);
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ describe('TextEditedComponent', () => {
|
|||||||
text_decoration: 'sample decoration'
|
text_decoration: 'sample decoration'
|
||||||
};
|
};
|
||||||
const textEditedDataEvent = new TextEditedDataEvent('id', 'edited text', textElement);
|
const textEditedDataEvent = new TextEditedDataEvent('id', 'edited text', textElement);
|
||||||
spyOn(mockedDrawingService, 'updateText').and.returnValue(Observable.of({}));
|
spyOn(mockedDrawingService, 'updateText').and.returnValue(Observable.of());
|
||||||
|
|
||||||
mockedDrawingsEventSource.textEdited.emit(textEditedDataEvent);
|
mockedDrawingsEventSource.textEdited.emit(textEditedDataEvent);
|
||||||
|
|
||||||
|
@ -8,13 +8,21 @@
|
|||||||
|
|
||||||
<ng-container matColumnDef="name">
|
<ng-container matColumnDef="name">
|
||||||
<mat-header-cell *matHeaderCellDef> Name </mat-header-cell>
|
<mat-header-cell *matHeaderCellDef> Name </mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row;">{{ row.name }}</mat-cell>
|
<mat-cell *matCellDef="let row;">
|
||||||
|
<ng-container *ngIf="row.type !== 'adbutler'">
|
||||||
|
{{ row.name }}
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="row.type === 'adbutler'">
|
||||||
|
<app-adbutler></app-adbutler>
|
||||||
|
</ng-container>
|
||||||
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="actions">
|
<ng-container matColumnDef="actions">
|
||||||
<mat-header-cell *matHeaderCellDef></mat-header-cell>
|
<mat-header-cell *matHeaderCellDef></mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row;" style="text-align: right">
|
<mat-cell *matCellDef="let row;" style="text-align: right">
|
||||||
<app-install-software [software]="row" (installedChanged)="onInstalled($event)"></app-install-software>
|
<app-install-software [software]="row" (installedChanged)="onInstalled($event)" *ngIf="row.type !== 'adbutler'"></app-install-software>
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
@ -47,7 +47,11 @@ export class InstalledSoftwareDataSource extends DataSource<any> {
|
|||||||
disconnect() {}
|
disconnect() {}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
this.installed.next(this.installedSoftwareService.list());
|
let installedSoftware = this.installedSoftwareService.list();
|
||||||
|
installedSoftware.push({
|
||||||
|
type: 'adbutler'
|
||||||
|
});
|
||||||
|
this.installed.next(installedSoftware);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
<button mat-menu-item (click)="console()">
|
||||||
|
<mat-icon>web_asset</mat-icon>
|
||||||
|
<span>Console</span>
|
||||||
|
</button>
|
@ -0,0 +1,126 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { MatIconModule } from '@angular/material';
|
||||||
|
import { ElectronService } from 'ngx-electron';
|
||||||
|
|
||||||
|
import { ConsoleDeviceActionComponent } from './console-device-action.component';
|
||||||
|
import { ServerService } from '../../../../../services/server.service';
|
||||||
|
import { MockedServerService } from '../../../../../services/server.service.spec';
|
||||||
|
import { ToasterService } from '../../../../../services/toaster.service';
|
||||||
|
import { MockedToasterService } from '../../../../../services/toaster.service.spec';
|
||||||
|
import { SettingsService } from '../../../../../services/settings.service';
|
||||||
|
import { MockedSettingsService } from '../../../../../services/settings.service.spec';
|
||||||
|
import { Node } from '../../../../../cartography/models/node';
|
||||||
|
import { Server } from '../../../../../models/server';
|
||||||
|
|
||||||
|
|
||||||
|
describe('ConsoleDeviceActionComponent', () => {
|
||||||
|
let component: ConsoleDeviceActionComponent;
|
||||||
|
let fixture: ComponentFixture<ConsoleDeviceActionComponent>;
|
||||||
|
let electronService;
|
||||||
|
let server: Server;
|
||||||
|
let mockedSettingsService: MockedSettingsService;
|
||||||
|
let mockedServerService: MockedServerService;
|
||||||
|
let mockedToaster: MockedToasterService
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
electronService = {
|
||||||
|
isElectronApp: true,
|
||||||
|
remote: {
|
||||||
|
require: (file) => {
|
||||||
|
return {
|
||||||
|
openConsole() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mockedSettingsService = new MockedSettingsService();
|
||||||
|
mockedServerService = new MockedServerService();
|
||||||
|
mockedToaster = new MockedToasterService();
|
||||||
|
|
||||||
|
server = { host: 'localhost', 'port': 222} as Server;
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
{ provide: ElectronService, useValue: electronService },
|
||||||
|
{ provide: ServerService, useValue: mockedServerService },
|
||||||
|
{ provide: SettingsService, useValue: mockedSettingsService },
|
||||||
|
{ provide: ToasterService, useValue: mockedToaster }
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
MatIconModule
|
||||||
|
],
|
||||||
|
declarations: [ ConsoleDeviceActionComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ConsoleDeviceActionComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('console to nodes', () => {
|
||||||
|
let nodes: Node[];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
nodes = [{
|
||||||
|
status: 'started',
|
||||||
|
console_type: 'telnet',
|
||||||
|
console_host: 'host',
|
||||||
|
console: 999,
|
||||||
|
name: 'Node 1',
|
||||||
|
project_id: '1111',
|
||||||
|
node_id: '2222',
|
||||||
|
} as Node];
|
||||||
|
|
||||||
|
component.nodes = nodes;
|
||||||
|
component.server = server;
|
||||||
|
|
||||||
|
mockedSettingsService.set('console_command', 'command');
|
||||||
|
spyOn(component, 'openConsole');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should console to device', async () => {
|
||||||
|
await component.console();
|
||||||
|
|
||||||
|
expect(component.openConsole).toHaveBeenCalledWith({
|
||||||
|
command: 'command',
|
||||||
|
type: 'telnet',
|
||||||
|
host: 'host',
|
||||||
|
port: 999,
|
||||||
|
name: 'Node 1',
|
||||||
|
project_id: '1111',
|
||||||
|
node_id: '2222',
|
||||||
|
server_url: 'localhost:222'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show message when command is not defined', async () => {
|
||||||
|
mockedSettingsService.set('console_command', undefined);
|
||||||
|
await component.console();
|
||||||
|
expect(component.openConsole).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show message when there is no started nodes', async () => {
|
||||||
|
nodes[0]['status'] = 'stopped';
|
||||||
|
await component.console();
|
||||||
|
expect(component.openConsole).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only start running nodes', async () => {
|
||||||
|
nodes.push({
|
||||||
|
status: 'stopped'
|
||||||
|
} as Node);
|
||||||
|
await component.console();
|
||||||
|
expect(component.openConsole).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,64 @@
|
|||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
import { Node } from '../../../../../cartography/models/node';
|
||||||
|
import { Server } from '../../../../../models/server';
|
||||||
|
import { ElectronService } from 'ngx-electron';
|
||||||
|
import { ServerService } from '../../../../../services/server.service';
|
||||||
|
import { SettingsService } from '../../../../../services/settings.service';
|
||||||
|
import { ToasterService } from '../../../../../services/toaster.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-console-device-action',
|
||||||
|
templateUrl: './console-device-action.component.html'
|
||||||
|
})
|
||||||
|
export class ConsoleDeviceActionComponent implements OnInit {
|
||||||
|
@Input() server: Server;
|
||||||
|
@Input() nodes: Node[];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private electronService: ElectronService,
|
||||||
|
private serverService: ServerService,
|
||||||
|
private settingsService: SettingsService,
|
||||||
|
private toasterService: ToasterService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
async console() {
|
||||||
|
const consoleCommand = this.settingsService.get<string>('console_command');
|
||||||
|
|
||||||
|
if(consoleCommand === undefined) {
|
||||||
|
this.toasterService.error('Console command is not defined. Please change it in the Settings.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startedNodes = this.nodes.filter(node => node.status === 'started');
|
||||||
|
|
||||||
|
if(startedNodes.length === 0) {
|
||||||
|
this.toasterService.error('Device needs to be started in order to console to it.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var node of this.nodes) {
|
||||||
|
if(node.status !== 'started') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const consoleRequest = {
|
||||||
|
command: consoleCommand,
|
||||||
|
type: node.console_type,
|
||||||
|
host: node.console_host,
|
||||||
|
port: node.console,
|
||||||
|
name: node.name,
|
||||||
|
project_id: node.project_id,
|
||||||
|
node_id: node.node_id,
|
||||||
|
server_url: this.serverService.getServerUrl(this.server)
|
||||||
|
};
|
||||||
|
await this.openConsole(consoleRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async openConsole(request) {
|
||||||
|
return await this.electronService.remote.require('./console-executor.js').openConsole(request);
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,11 @@
|
|||||||
<mat-menu #contextMenu="matMenu" class="context-menu-items">
|
<mat-menu #contextMenu="matMenu" class="context-menu-items">
|
||||||
<app-start-node-action *ngIf="nodes.length" [server]="server" [nodes]="nodes"></app-start-node-action>
|
<app-start-node-action *ngIf="nodes.length" [server]="server" [nodes]="nodes"></app-start-node-action>
|
||||||
<app-stop-node-action *ngIf="nodes.length" [server]="server" [nodes]="nodes"></app-stop-node-action>
|
<app-stop-node-action *ngIf="nodes.length" [server]="server" [nodes]="nodes"></app-stop-node-action>
|
||||||
|
<app-console-device-action
|
||||||
|
*ngIf="!projectService.isReadOnly(project) && nodes.length && isElectronApp"
|
||||||
|
[server]="server"
|
||||||
|
[nodes]="nodes"
|
||||||
|
></app-console-device-action>
|
||||||
<app-edit-style-action *ngIf="drawings.length===1 && !hasTextCapabilities"
|
<app-edit-style-action *ngIf="drawings.length===1 && !hasTextCapabilities"
|
||||||
[server]="server"
|
[server]="server"
|
||||||
[project]="project"
|
[project]="project"
|
||||||
|
@ -9,17 +9,23 @@ import { Drawing } from '../../../cartography/models/drawing';
|
|||||||
import { RectElement } from '../../../cartography/models/drawings/rect-element';
|
import { RectElement } from '../../../cartography/models/drawings/rect-element';
|
||||||
import { TextElement } from '../../../cartography/models/drawings/text-element';
|
import { TextElement } from '../../../cartography/models/drawings/text-element';
|
||||||
import { Server } from '../../../models/server';
|
import { Server } from '../../../models/server';
|
||||||
|
import { ElectronService } from 'ngx-electron';
|
||||||
|
|
||||||
describe('ContextMenuComponent', () => {
|
describe('ContextMenuComponent', () => {
|
||||||
let component: ContextMenuComponent;
|
let component: ContextMenuComponent;
|
||||||
let fixture: ComponentFixture<ContextMenuComponent>;
|
let fixture: ComponentFixture<ContextMenuComponent>;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
|
const electronMock = {
|
||||||
|
isElectronApp: true
|
||||||
|
};
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [MatMenuModule, BrowserModule],
|
imports: [MatMenuModule, BrowserModule],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ChangeDetectorRef },
|
{ provide: ChangeDetectorRef },
|
||||||
{ provide: ProjectService, useClass: MockedProjectService }
|
{ provide: ProjectService, useClass: MockedProjectService },
|
||||||
|
{ provide: ElectronService, useValue: electronMock}
|
||||||
],
|
],
|
||||||
declarations: [ContextMenuComponent],
|
declarations: [ContextMenuComponent],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
@ -37,6 +43,10 @@ describe('ContextMenuComponent', () => {
|
|||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should define property if running in electron ', () => {
|
||||||
|
expect(component.isElectronApp).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
it('should reset capabilities while opening menu for node', () => {
|
it('should reset capabilities while opening menu for node', () => {
|
||||||
component.contextMenu = { openMenu() {} } as MatMenuTrigger;
|
component.contextMenu = { openMenu() {} } as MatMenuTrigger;
|
||||||
var spy = spyOn<any>(component, 'resetCapabilities');
|
var spy = spyOn<any>(component, 'resetCapabilities');
|
||||||
|
@ -9,6 +9,7 @@ import { Drawing } from '../../../cartography/models/drawing';
|
|||||||
import { TextElement } from '../../../cartography/models/drawings/text-element';
|
import { TextElement } from '../../../cartography/models/drawings/text-element';
|
||||||
import { Label } from '../../../cartography/models/label';
|
import { Label } from '../../../cartography/models/label';
|
||||||
import { Link } from '../../../models/link';
|
import { Link } from '../../../models/link';
|
||||||
|
import { ElectronService } from 'ngx-electron';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -30,17 +31,21 @@ export class ContextMenuComponent implements OnInit {
|
|||||||
labels: Label[] = [];
|
labels: Label[] = [];
|
||||||
links: Link[] = [];
|
links: Link[] = [];
|
||||||
|
|
||||||
hasTextCapabilities: boolean = false;
|
hasTextCapabilities = false;
|
||||||
|
isElectronApp = false;
|
||||||
isBundledServer: boolean = false;
|
isBundledServer: boolean = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private sanitizer: DomSanitizer,
|
private sanitizer: DomSanitizer,
|
||||||
private changeDetector: ChangeDetectorRef,
|
private changeDetector: ChangeDetectorRef,
|
||||||
|
private electronService: ElectronService,
|
||||||
public projectService: ProjectService
|
public projectService: ProjectService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.setPosition(0, 0);
|
this.setPosition(0, 0);
|
||||||
|
|
||||||
|
this.isElectronApp = this.electronService.isElectronApp;
|
||||||
this.isBundledServer = this.server.location === 'bundled';
|
this.isBundledServer = this.server.location === 'bundled';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
import { NodeCreatedLabelStylesFixer } from "./node-created-label-styles-fixer";
|
||||||
|
import { Node } from '../../../cartography/models/node';
|
||||||
|
import { Label } from '../../../cartography/models/label';
|
||||||
|
|
||||||
|
describe('NodeCreatedLabelStylesFixer', () => {
|
||||||
|
let fixer: NodeCreatedLabelStylesFixer;
|
||||||
|
let calculator;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
calculator = {
|
||||||
|
calculate: (text, styles) => {
|
||||||
|
return {
|
||||||
|
width: 50,
|
||||||
|
height: 10
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fixer = new NodeCreatedLabelStylesFixer(
|
||||||
|
calculator
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fix label styles and position', () => {
|
||||||
|
const node = new Node();
|
||||||
|
node.width = 100;
|
||||||
|
node.height = 100;
|
||||||
|
node.label = new Label();
|
||||||
|
node.label.text = "my new label";
|
||||||
|
|
||||||
|
const fixed = fixer.fix(node);
|
||||||
|
|
||||||
|
expect(fixed.label.style).toEqual('font-family: TypeWriter;font-size: 10.0;font-weight: bold;fill: #000000;fill-opacity: 1.0;');
|
||||||
|
expect(fixed.label.x).toEqual(25);
|
||||||
|
expect(fixed.label.y).toEqual(-18);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,24 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Node } from '../../../cartography/models/node';
|
||||||
|
import { FontBBoxCalculator } from '../../../cartography/helpers/font-bbox-calculator';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class NodeCreatedLabelStylesFixer {
|
||||||
|
MARGIN_BETWEEN_NODE_AND_LABEL = 8;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private fontBBCalculator: FontBBoxCalculator
|
||||||
|
) {}
|
||||||
|
|
||||||
|
fix(node: Node): Node {
|
||||||
|
node.label.style = 'font-family: TypeWriter;font-size: 10.0;font-weight: bold;fill: #000000;fill-opacity: 1.0;';
|
||||||
|
const bb = this.fontBBCalculator.calculate(node.label.text, node.label.style);
|
||||||
|
|
||||||
|
// center label
|
||||||
|
node.label.x = node.width / 2 - bb.width / 2;
|
||||||
|
|
||||||
|
// move above the node
|
||||||
|
node.label.y = -bb.height - this.MARGIN_BETWEEN_NODE_AND_LABEL;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
@ -44,6 +44,7 @@ import { MovingEventSource } from '../../cartography/events/moving-event-source'
|
|||||||
import { CapturingSettings } from '../../models/capturingSettings';
|
import { CapturingSettings } from '../../models/capturingSettings';
|
||||||
import { LinkWidget } from '../../cartography/widgets/link';
|
import { LinkWidget } from '../../cartography/widgets/link';
|
||||||
import { MapScaleService } from '../../services/mapScale.service';
|
import { MapScaleService } from '../../services/mapScale.service';
|
||||||
|
import { NodeCreatedLabelStylesFixer } from './helpers/node-created-label-styles-fixer';
|
||||||
|
|
||||||
export class MockedProgressService {
|
export class MockedProgressService {
|
||||||
public activate() {}
|
public activate() {}
|
||||||
@ -184,8 +185,13 @@ describe('ProjectMapComponent', () => {
|
|||||||
let drawingsDataSource = new MockedDrawingsDataSource();
|
let drawingsDataSource = new MockedDrawingsDataSource();
|
||||||
let nodesDataSource = new MockedNodesDataSource();
|
let nodesDataSource = new MockedNodesDataSource();
|
||||||
let linksDataSource = new MockedLinksDataSource();
|
let linksDataSource = new MockedLinksDataSource();
|
||||||
|
let nodeCreatedLabelStylesFixer;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
|
nodeCreatedLabelStylesFixer = {
|
||||||
|
fix: (node) => node
|
||||||
|
};
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule],
|
imports: [MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule],
|
||||||
providers: [
|
providers: [
|
||||||
@ -217,7 +223,8 @@ describe('ProjectMapComponent', () => {
|
|||||||
provide: RecentlyOpenedProjectService,
|
provide: RecentlyOpenedProjectService,
|
||||||
useClass: RecentlyOpenedProjectService
|
useClass: RecentlyOpenedProjectService
|
||||||
},
|
},
|
||||||
{ provide: MapScaleService }
|
{ provide: MapScaleService },
|
||||||
|
{ provide: NodeCreatedLabelStylesFixer, useValue: nodeCreatedLabelStylesFixer}
|
||||||
],
|
],
|
||||||
declarations: [ProjectMapComponent, D3MapComponent, ...ANGULAR_MAP_DECLARATIONS],
|
declarations: [ProjectMapComponent, D3MapComponent, ...ANGULAR_MAP_DECLARATIONS],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
@ -45,6 +45,7 @@ import { MapLinkToLinkConverter } from '../../cartography/converters/map/map-lin
|
|||||||
import { MovingEventSource } from '../../cartography/events/moving-event-source';
|
import { MovingEventSource } from '../../cartography/events/moving-event-source';
|
||||||
import { LinkWidget } from '../../cartography/widgets/link';
|
import { LinkWidget } from '../../cartography/widgets/link';
|
||||||
import { MapScaleService } from '../../services/mapScale.service';
|
import { MapScaleService } from '../../services/mapScale.service';
|
||||||
|
import { NodeCreatedLabelStylesFixer } from './helpers/node-created-label-styles-fixer';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -112,7 +113,8 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
private selectionTool: SelectionTool,
|
private selectionTool: SelectionTool,
|
||||||
private recentlyOpenedProjectService: RecentlyOpenedProjectService,
|
private recentlyOpenedProjectService: RecentlyOpenedProjectService,
|
||||||
private movingEventSource: MovingEventSource,
|
private movingEventSource: MovingEventSource,
|
||||||
private mapScaleService: MapScaleService
|
private mapScaleService: MapScaleService,
|
||||||
|
private nodeCreatedLabelStylesFixer: NodeCreatedLabelStylesFixer
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -290,6 +292,12 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.nodeService.createFromTemplate(this.server, this.project, template, 0, 0, 'local').subscribe(() => {
|
this.nodeService.createFromTemplate(this.server, this.project, template, 0, 0, 'local').subscribe(() => {
|
||||||
this.projectService.nodes(this.server, this.project.project_id).subscribe((nodes: Node[]) => {
|
this.projectService.nodes(this.server, this.project.project_id).subscribe((nodes: Node[]) => {
|
||||||
|
|
||||||
|
nodes.filter((node) => node.label.style === null).forEach((node) => {
|
||||||
|
const fixedNode = this.nodeCreatedLabelStylesFixer.fix(node);
|
||||||
|
this.nodeService.updateLabel(this.server, node, fixedNode.label).subscribe();
|
||||||
|
});
|
||||||
|
|
||||||
this.nodesDataSource.set(nodes);
|
this.nodesDataSource.set(nodes);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
28
src/app/components/settings/console/console.component.html
Normal file
28
src/app/components/settings/console/console.component.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<div class="content">
|
||||||
|
<div class="default-header">
|
||||||
|
<div class="row">
|
||||||
|
<h1 class="col">Console settings</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="default-content">
|
||||||
|
<mat-card>
|
||||||
|
<form [formGroup]="consoleForm">
|
||||||
|
<mat-form-field class="form-field full-width-field">
|
||||||
|
<textarea matInput formControlName="command" placeholder="Command"></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
<div class="help">The following variables are replaced by GNS3:<br />
|
||||||
|
%h: console IP or hostname<br />
|
||||||
|
%p: console port<br />
|
||||||
|
%s: path of the serial connection<br />
|
||||||
|
%d: title of the console<br />
|
||||||
|
%i: Project UUID<br />
|
||||||
|
%c: server URL (http://user:password@server:port)
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</mat-card>
|
||||||
|
<div class="buttons-bar">
|
||||||
|
<button class="cancel-button" (click)="goBack()" mat-button>Cancel</button>
|
||||||
|
<button mat-raised-button color="primary" (click)="save()">Save</button><br/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,3 @@
|
|||||||
|
.help {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ConsoleComponent } from './console.component';
|
||||||
|
import { MatFormFieldModule, MatCardModule, MatInputModule } from '@angular/material';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { ConsoleService } from '../../../services/settings/console.service';
|
||||||
|
import { ToasterService } from '../../../services/toaster.service';
|
||||||
|
import { MockedToasterService } from '../../../services/toaster.service.spec';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
|
||||||
|
describe('ConsoleComponent', () => {
|
||||||
|
let component: ConsoleComponent;
|
||||||
|
let fixture: ComponentFixture<ConsoleComponent>;
|
||||||
|
let consoleService;
|
||||||
|
let router;
|
||||||
|
let toaster: MockedToasterService;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
consoleService = {
|
||||||
|
command: 'command'
|
||||||
|
};
|
||||||
|
|
||||||
|
router = {
|
||||||
|
navigate: jasmine.createSpy('navigate')
|
||||||
|
};
|
||||||
|
|
||||||
|
toaster = new MockedToasterService();
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
{ provide: ConsoleService, useValue: consoleService },
|
||||||
|
{ provide: ToasterService, useValue: toaster },
|
||||||
|
{ provide: Router, useValue: router}
|
||||||
|
],
|
||||||
|
imports: [ FormsModule, ReactiveFormsModule, MatFormFieldModule, MatCardModule, MatInputModule, NoopAnimationsModule ],
|
||||||
|
declarations: [ ConsoleComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ConsoleComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set default command', () => {
|
||||||
|
component.ngOnInit();
|
||||||
|
expect(component.consoleForm.value.command).toEqual('command');
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should go back', () => {
|
||||||
|
component.goBack();
|
||||||
|
expect(router.navigate).toHaveBeenCalledWith(['/settings']);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should update console command', () => {
|
||||||
|
component.consoleForm.get('command').setValue('newCommand');
|
||||||
|
spyOn(component, 'goBack');
|
||||||
|
component.save();
|
||||||
|
expect(toaster.success.length).toEqual(1);
|
||||||
|
expect(component.goBack).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
40
src/app/components/settings/console/console.component.ts
Normal file
40
src/app/components/settings/console/console.component.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormGroup, FormControl, Validators } from '@angular/forms';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { ConsoleService } from '../../../services/settings/console.service';
|
||||||
|
import { ToasterService } from '../../../services/toaster.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-console',
|
||||||
|
templateUrl: './console.component.html',
|
||||||
|
styleUrls: ['./console.component.scss']
|
||||||
|
})
|
||||||
|
export class ConsoleComponent implements OnInit {
|
||||||
|
|
||||||
|
consoleForm = new FormGroup({
|
||||||
|
'command': new FormControl(''),
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private router: Router,
|
||||||
|
private consoleService: ConsoleService,
|
||||||
|
private toasterService: ToasterService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
const commandControl = this.consoleForm.get('command');
|
||||||
|
commandControl.setValue(this.consoleService.command);
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack() {
|
||||||
|
this.router.navigate(['/settings']);
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
const formValue = this.consoleForm.value;
|
||||||
|
this.consoleService.command = formValue.command;
|
||||||
|
this.toasterService.success("Console command has been updated.");
|
||||||
|
this.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -27,6 +27,21 @@
|
|||||||
>
|
>
|
||||||
</div> -->
|
</div> -->
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
|
|
||||||
|
<mat-expansion-panel [expanded]="false">
|
||||||
|
<mat-expansion-panel-header>
|
||||||
|
<mat-panel-title> Console settings </mat-panel-title>
|
||||||
|
<mat-panel-description> Customize console settings </mat-panel-description>
|
||||||
|
</mat-expansion-panel-header>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<mat-form-field class="full-width-field">
|
||||||
|
<input matInput placeholder="console" [value]="consoleCommand" readonly="true">
|
||||||
|
<a mat-icon-button matSuffix routerLink="/settings/console"><mat-icon>mode_edit</mat-icon></a>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</mat-expansion-panel>
|
||||||
</mat-accordion>
|
</mat-accordion>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { MatCheckboxModule, MatExpansionModule } from '@angular/material';
|
import { MatCheckboxModule, MatExpansionModule, MatIconModule, MatFormFieldModule, MatInputModule } from '@angular/material';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
@ -9,16 +9,26 @@ import { SettingsComponent } from './settings.component';
|
|||||||
import { SettingsService } from '../../services/settings.service';
|
import { SettingsService } from '../../services/settings.service';
|
||||||
import { ToasterService } from '../../services/toaster.service';
|
import { ToasterService } from '../../services/toaster.service';
|
||||||
import { MockedToasterService } from '../../services/toaster.service.spec';
|
import { MockedToasterService } from '../../services/toaster.service.spec';
|
||||||
|
import { ConsoleService } from '../../services/settings/console.service';
|
||||||
|
|
||||||
describe('SettingsComponent', () => {
|
describe('SettingsComponent', () => {
|
||||||
let component: SettingsComponent;
|
let component: SettingsComponent;
|
||||||
let fixture: ComponentFixture<SettingsComponent>;
|
let fixture: ComponentFixture<SettingsComponent>;
|
||||||
let settingsService: SettingsService;
|
let settingsService: SettingsService;
|
||||||
|
let consoleService;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
|
consoleService = {
|
||||||
|
command: 'command'
|
||||||
|
};
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [MatExpansionModule, MatCheckboxModule, FormsModule, PersistenceModule, BrowserAnimationsModule],
|
imports: [MatExpansionModule, MatCheckboxModule, FormsModule, PersistenceModule, BrowserAnimationsModule, MatIconModule, MatFormFieldModule, MatInputModule],
|
||||||
providers: [SettingsService, { provide: ToasterService, useClass: MockedToasterService }],
|
providers: [
|
||||||
|
SettingsService,
|
||||||
|
{ provide: ToasterService, useClass: MockedToasterService },
|
||||||
|
{ provide: ConsoleService, useValue: consoleService}
|
||||||
|
],
|
||||||
declarations: [SettingsComponent]
|
declarations: [SettingsComponent]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
|
||||||
@ -39,7 +49,8 @@ describe('SettingsComponent', () => {
|
|||||||
const settings = {
|
const settings = {
|
||||||
crash_reports: true,
|
crash_reports: true,
|
||||||
experimental_features: true,
|
experimental_features: true,
|
||||||
angular_map: false
|
angular_map: false,
|
||||||
|
console_command: ''
|
||||||
};
|
};
|
||||||
const getAll = spyOn(settingsService, 'getAll').and.returnValue(settings);
|
const getAll = spyOn(settingsService, 'getAll').and.returnValue(settings);
|
||||||
const setAll = spyOn(settingsService, 'setAll');
|
const setAll = spyOn(settingsService, 'setAll');
|
||||||
@ -50,4 +61,5 @@ describe('SettingsComponent', () => {
|
|||||||
component.save();
|
component.save();
|
||||||
expect(setAll).toHaveBeenCalledWith(settings);
|
expect(setAll).toHaveBeenCalledWith(settings);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { SettingsService } from '../../services/settings.service';
|
import { SettingsService } from '../../services/settings.service';
|
||||||
import { ToasterService } from '../../services/toaster.service';
|
import { ToasterService } from '../../services/toaster.service';
|
||||||
|
import { ConsoleService } from '../../services/settings/console.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-settings',
|
selector: 'app-settings',
|
||||||
@ -9,11 +10,16 @@ import { ToasterService } from '../../services/toaster.service';
|
|||||||
})
|
})
|
||||||
export class SettingsComponent implements OnInit {
|
export class SettingsComponent implements OnInit {
|
||||||
settings = { ...SettingsService.DEFAULTS };
|
settings = { ...SettingsService.DEFAULTS };
|
||||||
|
consoleCommand: string;
|
||||||
|
|
||||||
constructor(private settingsService: SettingsService, private toaster: ToasterService) {}
|
constructor(
|
||||||
|
private settingsService: SettingsService,
|
||||||
|
private toaster: ToasterService,
|
||||||
|
private consoleService: ConsoleService) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.settings = this.settingsService.getAll();
|
this.settings = this.settingsService.getAll();
|
||||||
|
this.consoleCommand = this.consoleService.command;
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
|
@ -35,6 +35,10 @@ export class MockedServerService {
|
|||||||
resolve(this.servers);
|
resolve(this.servers);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getServerUrl(server: Server) {
|
||||||
|
return `${server.host}:${server.port}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ServerService', () => {
|
describe('ServerService', () => {
|
||||||
|
@ -55,6 +55,10 @@ export class ServerService {
|
|||||||
return this.onReady(() => this.indexedDbService.get().delete(this.tablename, server.id));
|
return this.onReady(() => this.indexedDbService.get().delete(this.tablename, server.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getServerUrl(server: Server) {
|
||||||
|
return `http://${server.host}:${server.port}/`;
|
||||||
|
}
|
||||||
|
|
||||||
public getLocalServer(host: string, port: number) {
|
public getLocalServer(host: string, port: number) {
|
||||||
const promise = new Promise((resolve, reject) => {
|
const promise = new Promise((resolve, reject) => {
|
||||||
this.findAll().then((servers: Server[]) => {
|
this.findAll().then((servers: Server[]) => {
|
||||||
|
@ -4,10 +4,20 @@ import { PersistenceService, StorageType } from 'angular-persistence';
|
|||||||
import { Settings, SettingsService } from './settings.service';
|
import { Settings, SettingsService } from './settings.service';
|
||||||
|
|
||||||
export class MockedSettingsService {
|
export class MockedSettingsService {
|
||||||
|
settings = {};
|
||||||
|
|
||||||
isExperimentalEnabled() {
|
isExperimentalEnabled() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
getAll() {}
|
getAll() {}
|
||||||
|
|
||||||
|
get(key: string) {
|
||||||
|
return this.settings[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key: string, value: any) {
|
||||||
|
this.settings[key] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('SettingsService', () => {
|
describe('SettingsService', () => {
|
||||||
@ -50,7 +60,8 @@ describe('SettingsService', () => {
|
|||||||
expect(service.getAll()).toEqual({
|
expect(service.getAll()).toEqual({
|
||||||
crash_reports: true,
|
crash_reports: true,
|
||||||
experimental_features: false,
|
experimental_features: false,
|
||||||
angular_map: false
|
angular_map: false,
|
||||||
|
console_command: undefined
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -63,7 +74,8 @@ describe('SettingsService', () => {
|
|||||||
expect(service.getAll()).toEqual({
|
expect(service.getAll()).toEqual({
|
||||||
crash_reports: false,
|
crash_reports: false,
|
||||||
experimental_features: false,
|
experimental_features: false,
|
||||||
angular_map: false
|
angular_map: false,
|
||||||
|
console_command: undefined
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ export interface Settings {
|
|||||||
crash_reports: boolean;
|
crash_reports: boolean;
|
||||||
experimental_features: boolean;
|
experimental_features: boolean;
|
||||||
angular_map: boolean;
|
angular_map: boolean;
|
||||||
|
console_command: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -13,7 +14,8 @@ export class SettingsService {
|
|||||||
static DEFAULTS: Settings = {
|
static DEFAULTS: Settings = {
|
||||||
crash_reports: true,
|
crash_reports: true,
|
||||||
experimental_features: false,
|
experimental_features: false,
|
||||||
angular_map: false
|
angular_map: false,
|
||||||
|
console_command: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
private settingsSubject: BehaviorSubject<Settings>;
|
private settingsSubject: BehaviorSubject<Settings>;
|
||||||
|
29
src/app/services/settings/console.service.spec.ts
Normal file
29
src/app/services/settings/console.service.spec.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { ConsoleService } from './console.service';
|
||||||
|
import { MockedSettingsService } from '../settings.service.spec';
|
||||||
|
|
||||||
|
describe('ConsoleService', () => {
|
||||||
|
let service: ConsoleService;
|
||||||
|
let settings: MockedSettingsService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
let defaultConsoleService = {
|
||||||
|
get: () => 'default'
|
||||||
|
};
|
||||||
|
settings = new MockedSettingsService();
|
||||||
|
service = new ConsoleService(defaultConsoleService as any, settings as any);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get command from settings if defined', () => {
|
||||||
|
settings.set('console_command', 'from_settings');
|
||||||
|
expect(service.command).toEqual('from_settings');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get command from default console if settings are not defined', () => {
|
||||||
|
settings.set('console_command', undefined);
|
||||||
|
expect(service.command).toEqual('default');
|
||||||
|
});
|
||||||
|
});
|
24
src/app/services/settings/console.service.ts
Normal file
24
src/app/services/settings/console.service.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { DefaultConsoleService } from './default-console.service';
|
||||||
|
import { SettingsService } from '../settings.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ConsoleService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private defaultConsoleService: DefaultConsoleService,
|
||||||
|
private settingsService: SettingsService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
get command(): string {
|
||||||
|
const command = this.settingsService.get<string>('console_command');
|
||||||
|
if(command === undefined) {
|
||||||
|
return this.defaultConsoleService.get();
|
||||||
|
}
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
set command(command: string) {
|
||||||
|
this.settingsService.set<string>('console_command', command);
|
||||||
|
}
|
||||||
|
}
|
45
src/app/services/settings/default-console.service.spec.ts
Normal file
45
src/app/services/settings/default-console.service.spec.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
|
||||||
|
import { DefaultConsoleService } from './default-console.service';
|
||||||
|
|
||||||
|
describe('DefaultConsoleService', () => {
|
||||||
|
let electronService;
|
||||||
|
let service: DefaultConsoleService;
|
||||||
|
beforeEach(() => {
|
||||||
|
electronService = {
|
||||||
|
isElectronApp: false,
|
||||||
|
isWindows: false,
|
||||||
|
isLinux: false
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
service = new DefaultConsoleService(electronService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined when not running in electron', () => {
|
||||||
|
electronService.isElectronApp = false;
|
||||||
|
expect(service.get()).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return console for windows', () => {
|
||||||
|
electronService.isElectronApp = true;
|
||||||
|
electronService.isWindows = true;
|
||||||
|
expect(service.get()).toEqual('putty.exe -telnet %h %p -loghost "%d"');
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return console for linux', () => {
|
||||||
|
electronService.isElectronApp = true;
|
||||||
|
electronService.isLinux = true;
|
||||||
|
expect(service.get()).toEqual('xfce4-terminal --tab -T "%d" -e "telnet %h %p"');
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return undefined for other platforms', () => {
|
||||||
|
electronService.isElectronApp = true;
|
||||||
|
expect(service.get()).toBeUndefined();
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
26
src/app/services/settings/default-console.service.ts
Normal file
26
src/app/services/settings/default-console.service.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ElectronService } from 'ngx-electron';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DefaultConsoleService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private electronService: ElectronService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
get() {
|
||||||
|
if(!this.electronService.isElectronApp) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.electronService.isLinux) {
|
||||||
|
return 'xfce4-terminal --tab -T "%d" -e "telnet %h %p"'
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.electronService.isWindows) {
|
||||||
|
return 'putty.exe -telnet %h %p -loghost "%d"'
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,10 @@ a.table-link {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.full-width-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
app-root {
|
app-root {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,11 @@
|
|||||||
"typeRoots": [
|
"typeRoots": [
|
||||||
"node_modules/@types"
|
"node_modules/@types"
|
||||||
],
|
],
|
||||||
|
// below is fix for angular-cli to support core-js >= 3.0
|
||||||
|
"paths": {
|
||||||
|
"core-js/es7/reflect": ["../node_modules/core-js/proposals/reflect-metadata"],
|
||||||
|
"core-js/es6/*": ["../node_modules/core-js/es/*"]
|
||||||
|
},
|
||||||
"lib": [
|
"lib": [
|
||||||
"es2018",
|
"es2018",
|
||||||
"dom"
|
"dom"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user