mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-05-02 16:52:50 +00:00
Merge pull request #327 from GNS3/enhanced-configuration
Enhanced configuration of local server; Binary dependencies
This commit is contained in:
commit
2b0bb19f88
@ -23,6 +23,7 @@ build_script:
|
|||||||
- "%PYTHON%\\python.exe scripts\\build.py download"
|
- "%PYTHON%\\python.exe scripts\\build.py download"
|
||||||
- "%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"
|
||||||
- yarn electron-builder --win --x64
|
- yarn electron-builder --win --x64
|
||||||
|
|
||||||
test: off
|
test: off
|
||||||
|
@ -19,6 +19,9 @@ files:
|
|||||||
|
|
||||||
extraFiles:
|
extraFiles:
|
||||||
- dist/exe.gns3server/**
|
- dist/exe.gns3server/**
|
||||||
|
- dist/ubridge/**
|
||||||
|
- dist/vpcs/**
|
||||||
|
- dist/dynamips/**
|
||||||
|
|
||||||
mac:
|
mac:
|
||||||
category: public.app-category.developer-tools
|
category: public.app-category.developer-tools
|
||||||
|
136
local-server.js
136
local-server.js
@ -2,6 +2,7 @@ const { spawn } = require('child_process');
|
|||||||
const kill = require('tree-kill');
|
const kill = require('tree-kill');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const ini = require('ini');
|
||||||
const { ipcMain } = require('electron')
|
const { ipcMain } = require('electron')
|
||||||
const { app } = require('electron')
|
const { app } = require('electron')
|
||||||
|
|
||||||
@ -10,19 +11,13 @@ const isWin = /^win/.test(process.platform);
|
|||||||
let runningServers = {};
|
let runningServers = {};
|
||||||
|
|
||||||
exports.getLocalServerPath = async () => {
|
exports.getLocalServerPath = async () => {
|
||||||
const lookupDirectories = [
|
let binary = isWin ? 'gns3server.exe': 'gns3server';
|
||||||
__dirname,
|
return findBinary('exe.', binary);
|
||||||
path.dirname(app.getPath('exe'))
|
}
|
||||||
];
|
|
||||||
|
|
||||||
for(var directory of lookupDirectories) {
|
exports.getUbridgePath = async () => {
|
||||||
const serverPath = await findLocalServerPath(directory);
|
let binary = isWin ? 'ubridge.exe': 'ubridge';
|
||||||
if(serverPath !== undefined) {
|
return findBinary('ubridge', binary);
|
||||||
return serverPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.startLocalServer = async (server) => {
|
exports.startLocalServer = async (server) => {
|
||||||
@ -43,7 +38,21 @@ exports.stopAllLocalServers = async () => {
|
|||||||
return await stopAll();
|
return await stopAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function findLocalServerPath(baseDirectory) {
|
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');
|
const distDirectory = path.join(baseDirectory, 'dist');
|
||||||
|
|
||||||
if (!fs.existsSync(distDirectory)) {
|
if (!fs.existsSync(distDirectory)) {
|
||||||
@ -52,27 +61,23 @@ async function findLocalServerPath(baseDirectory) {
|
|||||||
|
|
||||||
const files = fs.readdirSync(distDirectory);
|
const files = fs.readdirSync(distDirectory);
|
||||||
|
|
||||||
let serverPath = null;
|
let binaryPath = null;
|
||||||
|
|
||||||
files.forEach((directory) => {
|
files.forEach((directory) => {
|
||||||
if(directory.startsWith('exe.')) {
|
if(directory.startsWith(binaryDirectory)) {
|
||||||
if (isWin) {
|
binaryPath = path.join(baseDirectory, 'dist', directory, filename);
|
||||||
serverPath = path.join(baseDirectory, 'dist', directory, 'gns3server.exe');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
serverPath = path.join(baseDirectory, 'dist', directory, 'gns3server');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if(serverPath !== null && fs.existsSync(serverPath)) {
|
if(binaryPath !== null && fs.existsSync(binaryPath)) {
|
||||||
return serverPath;
|
return binaryPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getServerArguments(server, overrides) {
|
|
||||||
|
function getServerArguments(server, overrides, configPath) {
|
||||||
let serverArguments = [];
|
let serverArguments = [];
|
||||||
if(server.host) {
|
if(server.host) {
|
||||||
serverArguments.push('--host');
|
serverArguments.push('--host');
|
||||||
@ -82,6 +87,14 @@ function getServerArguments(server, overrides) {
|
|||||||
serverArguments.push('--port');
|
serverArguments.push('--port');
|
||||||
serverArguments.push(server.port);
|
serverArguments.push(server.port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
serverArguments.push('--local');
|
||||||
|
|
||||||
|
if(configPath) {
|
||||||
|
serverArguments.push('--config');
|
||||||
|
serverArguments.push(configPath);
|
||||||
|
}
|
||||||
|
|
||||||
return serverArguments;
|
return serverArguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +166,56 @@ async function stop(serverName) {
|
|||||||
return 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) {
|
async function run(server, options) {
|
||||||
if(!options) {
|
if(!options) {
|
||||||
options = {};
|
options = {};
|
||||||
@ -161,9 +224,17 @@ async function run(server, options) {
|
|||||||
const logStdout = options.logStdout || false;
|
const logStdout = options.logStdout || false;
|
||||||
const logSterr = options.logSterr || 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}'`);
|
console.log(`Running '${server.path}'`);
|
||||||
|
|
||||||
let serverProcess = spawn(server.path, getServerArguments(server));
|
let serverProcess = spawn(server.path, getServerArguments(server, {}, configPath));
|
||||||
|
|
||||||
notifyStatus({
|
notifyStatus({
|
||||||
serverName: server.name,
|
serverName: server.name,
|
||||||
@ -225,14 +296,15 @@ async function main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcMain.on('local-server-run', async function (event, server) {
|
if(ipcMain) {
|
||||||
const responseChannel = getChannelForServer();
|
ipcMain.on('local-server-run', async function (event, server) {
|
||||||
await run(server);
|
const responseChannel = getChannelForServer();
|
||||||
event.sender.send(responseChannel, {
|
await run(server);
|
||||||
success: true
|
event.sender.send(responseChannel, {
|
||||||
|
success: true
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
process.on('SIGINT', function() {
|
process.on('SIGINT', function() {
|
||||||
|
@ -58,6 +58,7 @@
|
|||||||
"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",
|
||||||
|
"ini": "^1.3.5",
|
||||||
"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",
|
||||||
@ -66,9 +67,9 @@
|
|||||||
"raven-js": "^3.27.0",
|
"raven-js": "^3.27.0",
|
||||||
"rxjs": "^6.4.0",
|
"rxjs": "^6.4.0",
|
||||||
"rxjs-compat": "^6.4.0",
|
"rxjs-compat": "^6.4.0",
|
||||||
|
"tree-kill": "^1.2.1",
|
||||||
"typeface-roboto": "^0.0.54",
|
"typeface-roboto": "^0.0.54",
|
||||||
"yargs": "^13.2.1",
|
"yargs": "^13.2.1",
|
||||||
"tree-kill": "^1.2.1",
|
|
||||||
"zone.js": "^0.8.29"
|
"zone.js": "^0.8.29"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -38,6 +38,39 @@ WORKING_DIR = os.path.join(FILE_DIR, 'tmp')
|
|||||||
SOURCE_ZIP = os.path.join(WORKING_DIR, 'gns3-server.source.zip')
|
SOURCE_ZIP = os.path.join(WORKING_DIR, 'gns3-server.source.zip')
|
||||||
SOURCE_DESTINATION = os.path.join(WORKING_DIR, 'source')
|
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 = {
|
||||||
|
'ubridge': {
|
||||||
|
'releases': 'https://api.github.com/repos/GNS3/ubridge/releases',
|
||||||
|
'version': 'LATEST',
|
||||||
|
'files': {
|
||||||
|
'windows': [
|
||||||
|
'cygwin1.dll',
|
||||||
|
'ubridge.exe'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'vpcs': {
|
||||||
|
'releases': 'https://api.github.com/repos/GNS3/vpcs/releases',
|
||||||
|
'version': '0.6.1',
|
||||||
|
'files': {
|
||||||
|
'windows': [
|
||||||
|
'cygwin1.dll',
|
||||||
|
'vpcs.exe'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'dynamips': {
|
||||||
|
'releases': 'https://api.github.com/repos/GNS3/dynamips/releases',
|
||||||
|
'version': '0.2.17',
|
||||||
|
'files': {
|
||||||
|
'windows': [
|
||||||
|
'cygwin1.dll',
|
||||||
|
'dynamips.exe',
|
||||||
|
'nvram_export.exe'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def download(url, output):
|
def download(url, output):
|
||||||
@ -80,6 +113,34 @@ def prepare():
|
|||||||
os.makedirs(WORKING_DIR, exist_ok=True)
|
os.makedirs(WORKING_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def download_dependencies_command(arguments):
|
||||||
|
output_directory = os.path.join(os.getcwd(), arguments.b)
|
||||||
|
|
||||||
|
for name, definition in DEPENDENCIES.items():
|
||||||
|
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_command(arguments):
|
def download_command(arguments):
|
||||||
shutil.rmtree(SOURCE_DESTINATION, ignore_errors=True)
|
shutil.rmtree(SOURCE_DESTINATION, ignore_errors=True)
|
||||||
os.makedirs(SOURCE_DESTINATION)
|
os.makedirs(SOURCE_DESTINATION)
|
||||||
@ -243,6 +304,9 @@ if __name__ == '__main__':
|
|||||||
parser_validate = subparsers.add_parser('validate', help='Validate build')
|
parser_validate = subparsers.add_parser('validate', help='Validate build')
|
||||||
parser_validate.add_argument('-b', help='Output directory')
|
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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.command == 'build_exe':
|
if args.command == 'build_exe':
|
||||||
@ -251,6 +315,9 @@ if __name__ == '__main__':
|
|||||||
elif args.command == 'download':
|
elif args.command == 'download':
|
||||||
prepare()
|
prepare()
|
||||||
download_command(args)
|
download_command(args)
|
||||||
|
elif args.command == 'download_dependencies':
|
||||||
|
prepare()
|
||||||
|
download_dependencies_command(args)
|
||||||
elif args.command == 'validate':
|
elif args.command == 'validate':
|
||||||
prepare()
|
prepare()
|
||||||
validate_command(args)
|
validate_command(args)
|
||||||
|
@ -16,6 +16,10 @@
|
|||||||
<input matInput tabindex="1" formControlName="path" placeholder="Local server path" />
|
<input matInput tabindex="1" formControlName="path" placeholder="Local server path" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field *ngIf="serverForm.get('location').value === 'local'">
|
||||||
|
<input matInput tabindex="1" formControlName="ubridge_path" placeholder="Ubridge path" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input matInput tabindex="1" formControlName="host" placeholder="Host" />
|
<input matInput tabindex="1" formControlName="host" placeholder="Host" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
@ -18,6 +18,7 @@ export class AddServerDialogComponent implements OnInit {
|
|||||||
'name': new FormControl('', [ Validators.required ]),
|
'name': new FormControl('', [ Validators.required ]),
|
||||||
'location': new FormControl(''),
|
'location': new FormControl(''),
|
||||||
'path': new FormControl(''),
|
'path': new FormControl(''),
|
||||||
|
'ubridge_path': new FormControl(''),
|
||||||
'host': new FormControl('', [ Validators.required ]),
|
'host': new FormControl('', [ Validators.required ]),
|
||||||
'port': new FormControl('', [ Validators.required, Validators.min(1) ]),
|
'port': new FormControl('', [ Validators.required, Validators.min(1) ]),
|
||||||
'authorization': new FormControl('none'),
|
'authorization': new FormControl('none'),
|
||||||
@ -72,24 +73,39 @@ export class AddServerDialogComponent implements OnInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getDefaultUbridgePath() {
|
||||||
|
if(this.electronService.isElectronApp) {
|
||||||
|
return await this.electronService.remote.require('./local-server.js').getUbridgePath();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.locations = await this.getLocations();
|
this.locations = await this.getLocations();
|
||||||
|
|
||||||
const defaultLocalServerPath = await this.getDefaultLocalServerPath();
|
const defaultLocalServerPath = await this.getDefaultLocalServerPath();
|
||||||
|
const defaultUbridgePath = await this.getDefaultUbridgePath();
|
||||||
|
|
||||||
this.serverForm.get('location').valueChanges.subscribe((location: string) => {
|
this.serverForm.get('location').valueChanges.subscribe((location: string) => {
|
||||||
const pathControl = this.serverForm.get('path');
|
const pathControl = this.serverForm.get('path');
|
||||||
|
const ubridgePathControl = this.serverForm.get('ubridge_path');
|
||||||
|
|
||||||
if(location === 'local') {
|
if(location === 'local') {
|
||||||
pathControl.setValue(defaultLocalServerPath);
|
pathControl.setValue(defaultLocalServerPath);
|
||||||
pathControl.setValidators([Validators.required]);
|
pathControl.setValidators([Validators.required]);
|
||||||
|
|
||||||
|
ubridgePathControl.setValue(defaultUbridgePath);
|
||||||
|
ubridgePathControl.setValidators([Validators.required]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
pathControl.setValue('');
|
pathControl.setValue('');
|
||||||
pathControl.clearValidators();
|
pathControl.clearValidators();
|
||||||
|
|
||||||
|
ubridgePathControl.setValue('');
|
||||||
|
ubridgePathControl.clearValidators();
|
||||||
}
|
}
|
||||||
|
|
||||||
[pathControl].forEach((control) => {
|
[pathControl, ubridgePathControl].forEach((control) => {
|
||||||
control.updateValueAndValidity({
|
control.updateValueAndValidity({
|
||||||
onlySelf: true
|
onlySelf: true
|
||||||
});
|
});
|
||||||
|
@ -38,9 +38,11 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
|
|||||||
// attach to notification stream when any of running local servers experienced issues
|
// attach to notification stream when any of running local servers experienced issues
|
||||||
this.serverStatusSubscription = this.serverManagement.serverStatusChanged.subscribe((serverStatus) => {
|
this.serverStatusSubscription = this.serverManagement.serverStatusChanged.subscribe((serverStatus) => {
|
||||||
if(serverStatus.status === 'errored') {
|
if(serverStatus.status === 'errored') {
|
||||||
|
console.error(serverStatus.message);
|
||||||
this.toasterService.error(serverStatus.message);
|
this.toasterService.error(serverStatus.message);
|
||||||
}
|
}
|
||||||
if(serverStatus.status === 'stderr') {
|
if(serverStatus.status === 'stderr') {
|
||||||
|
console.error(serverStatus.message);
|
||||||
this.toasterService.error(serverStatus.message);
|
this.toasterService.error(serverStatus.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -9,6 +9,7 @@ export class Server {
|
|||||||
host: string;
|
host: string;
|
||||||
port: number;
|
port: number;
|
||||||
path: string;
|
path: string;
|
||||||
|
ubridge_path: string;
|
||||||
authorization: ServerAuthorization;
|
authorization: ServerAuthorization;
|
||||||
login: string;
|
login: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user