mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-01-02 19:16:44 +00:00
Merge branch 'master' into master-2.3
This commit is contained in:
commit
7bc95954ec
@ -1,6 +1,5 @@
|
||||
# gns3-web-ui
|
||||
|
||||
[![Known Vulnerabilities](https://snyk.io/test/github/GNS3/gns3-web-ui/badge.svg)](https://snyk.io/test/github/GNS3/gns3-web-ui)
|
||||
[![Travis CI](https://api.travis-ci.org/GNS3/gns3-web-ui.svg?branch=master)](https://travis-ci.org)
|
||||
[![AppVeyor](https://ci.appveyor.com/api/projects/status/github/GNS3/gns3-web-ui?branch=master&svg=true)](https://www.appveyor.com/)
|
||||
[![CircleCI](https://circleci.com/gh/GNS3/gns3-web-ui/tree/master.png)](https://circleci.com/gh/GNS3/gns3-web-ui/tree/master.png)
|
||||
@ -10,10 +9,6 @@
|
||||
[![Packages versions](https://repology.org/badge/latest-versions/gns3.svg)](https://repology.org/metapackage/gns3/versions)
|
||||
[![Packages](https://repology.org/badge/tiny-repos/gns3.svg)](https://repology.org/metapackage/gns3/versions)
|
||||
|
||||
Test WebUI implementation for GNS3.
|
||||
|
||||
This is not production ready version. It has been made to evaluate possibility of creation Web User Interface for GNS3 application.
|
||||
|
||||
|
||||
## Demo
|
||||
|
||||
|
17
angular.json
17
angular.json
@ -33,7 +33,6 @@
|
||||
"main": "src/main.ts",
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"extractCss": true,
|
||||
"assets": [
|
||||
"src/assets",
|
||||
"src/favicon.ico",
|
||||
@ -42,14 +41,14 @@
|
||||
"styles": [
|
||||
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||
"node_modules/notosans-fontface/css/notosans-fontface.min.css",
|
||||
"src/styles.css",
|
||||
"src/styles.scss",
|
||||
{
|
||||
"inject": false,
|
||||
"inject": true,
|
||||
"input": "src/theme.scss",
|
||||
"bundleName": "theme-default-dark"
|
||||
},
|
||||
{
|
||||
"inject": false,
|
||||
"inject": true,
|
||||
"input": "src/theme-light.scss",
|
||||
"bundleName": "theme-default"
|
||||
}
|
||||
@ -71,7 +70,6 @@
|
||||
"scripts": true,
|
||||
"styles": false
|
||||
},
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
@ -94,7 +92,6 @@
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
@ -131,7 +128,6 @@
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
@ -183,7 +179,7 @@
|
||||
"styles": [
|
||||
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||
"node_modules/notosans-fontface/css/notosans-fontface.min.css",
|
||||
"src/styles.css",
|
||||
"src/styles.scss",
|
||||
"src/theme.scss"
|
||||
],
|
||||
"assets": [
|
||||
@ -208,6 +204,11 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gns3-web-ui-e2e": {
|
||||
|
1
debug.log
Normal file
1
debug.log
Normal file
@ -0,0 +1 @@
|
||||
[1109/003452.026:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
|
99
package.json
99
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gns3-web-ui",
|
||||
"version": "2.2.16dev1",
|
||||
"version": "2.2.18dev",
|
||||
"author": {
|
||||
"name": "GNS3 Technology Inc.",
|
||||
"email": "developers@gns3.com"
|
||||
@ -40,39 +40,38 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^10.1.5",
|
||||
"@angular/cdk": "^10.2.4",
|
||||
"@angular/common": "^10.1.5",
|
||||
"@angular/compiler": "^10.1.5",
|
||||
"@angular/core": "^10.1.5",
|
||||
"@angular/forms": "^10.1.5",
|
||||
"@angular/animations": "^11.0.8",
|
||||
"@angular/cdk": "^11.0.3",
|
||||
"@angular/common": "^11.0.8",
|
||||
"@angular/compiler": "^11.0.8",
|
||||
"@angular/core": "^11.0.8",
|
||||
"@angular/forms": "^11.0.8",
|
||||
"@angular/http": "^7.2.16",
|
||||
"@angular/material": "^10.2.4",
|
||||
"@angular/platform-browser": "^10.1.5",
|
||||
"@angular/platform-browser-dynamic": "^10.1.5",
|
||||
"@angular/router": "^10.1.5",
|
||||
"@sentry/browser": "^5.26.0",
|
||||
"@types/jest": "^26.0.14",
|
||||
"@types/mocha": "^8.0.3",
|
||||
"angular-draggable-droppable": "^4.5.4",
|
||||
"@angular/material": "^11.0.3",
|
||||
"@angular/platform-browser": "^11.0.8",
|
||||
"@angular/platform-browser-dynamic": "^11.0.8",
|
||||
"@angular/router": "^11.0.8",
|
||||
"@sentry/browser": "^5.29.2",
|
||||
"@types/jest": "^26.0.20",
|
||||
"@types/mocha": "^8.2.0",
|
||||
"angular-draggable-droppable": "^4.6.0",
|
||||
"angular-persistence": "^1.0.1",
|
||||
"angular-resizable-element": "^3.3.3",
|
||||
"angular-resizable-element": "^3.3.4",
|
||||
"angular2-draggable": "^2.3.2",
|
||||
"angular2-hotkeys": "^2.2.0",
|
||||
"angular2-indexeddb": "^1.2.3",
|
||||
"bootstrap": "4.5.2",
|
||||
"bootstrap": "^4.5.3",
|
||||
"command-exists": "^1.2.9",
|
||||
"core-js": "^3.6.5",
|
||||
"css-tree": "^1.0.0-alpha.36",
|
||||
"core-js": "^3.8.2",
|
||||
"d3-ng2-service": "^2.1.0",
|
||||
"file-saver": "^2.0.2",
|
||||
"ini": "^1.3.5",
|
||||
"marked": "^1.1.1",
|
||||
"file-saver": "^2.0.5",
|
||||
"ini": "^1.3.8",
|
||||
"material-design-icons": "^3.0.1",
|
||||
"ng-circle-progress": "^1.6.0",
|
||||
"ng2-file-upload": "^1.3.0",
|
||||
"ngx-childprocess": "^0.0.6",
|
||||
"ngx-device-detector": "^2.0.0",
|
||||
"ngx-device-detector": "^2.0.5",
|
||||
"ngx-electron": "^2.1.1",
|
||||
"node-fetch": "^2.6.1",
|
||||
"notosans-fontface": "1.2.2",
|
||||
@ -80,44 +79,45 @@
|
||||
"rxjs-compat": "^6.6.3",
|
||||
"save-html-as-image": "^1.3.4",
|
||||
"save-svg-as-png": "^1.4.14",
|
||||
"snyk": "^1.413.3",
|
||||
"svg-crowbar": "^0.6.1",
|
||||
"schematics-scss-migrate": "^1.2.10",
|
||||
"snyk": "^1.437.3",
|
||||
"spark-md5": "^3.0.1",
|
||||
"svg-crowbar": "^0.6.5",
|
||||
"tree-kill": "^1.2.1",
|
||||
"tslib": "^2.0.3",
|
||||
"typeface-roboto": "^0.0.75",
|
||||
"tslib": "^2.1.0",
|
||||
"typeface-roboto": "^1.1.13",
|
||||
"xterm": "^4.9.0",
|
||||
"xterm-addon-attach": "^0.6.0",
|
||||
"xterm-addon-fit": "^0.4.0",
|
||||
"yargs": "^16.0.3",
|
||||
"zone.js": "~0.11.1"
|
||||
"yargs": "^16.2.0",
|
||||
"zone.js": "^0.11.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^0.1001.6",
|
||||
"@angular/cli": "^10.1.6",
|
||||
"@angular/compiler-cli": "^10.1.5",
|
||||
"@angular/language-service": "^10.1.5",
|
||||
"@sentry/cli": "^1.58.0",
|
||||
"@sentry/electron": "^2.0.1",
|
||||
"@types/jasmine": "^3.5.14",
|
||||
"@angular-devkit/build-angular": "^0.1100.6",
|
||||
"@angular/cli": "^11.0.6",
|
||||
"@angular/compiler-cli": "^11.0.8",
|
||||
"@angular/language-service": "^11.0.8",
|
||||
"@sentry/cli": "^1.61.0",
|
||||
"@sentry/electron": "^2.1.0",
|
||||
"@types/jasmine": "~3.6.0",
|
||||
"@types/jasminewd2": "^2.0.8",
|
||||
"@types/node": "14.11.2",
|
||||
"codelyzer": "^6.0.1",
|
||||
"electron": "^10.1.3",
|
||||
"electron-builder": "22.8.1",
|
||||
"file-loader": "^6.1.1",
|
||||
"jasmine-core": "^3.6.0",
|
||||
"jasmine-spec-reporter": "^6.0.0",
|
||||
"@types/node": "14.14.10",
|
||||
"codelyzer": "^6.0.0",
|
||||
"electron": "^11.2.0",
|
||||
"electron-builder": "22.9.1",
|
||||
"file-loader": "^6.2.0",
|
||||
"jasmine-core": "~3.6.0",
|
||||
"jasmine-spec-reporter": "~5.0.0",
|
||||
"jquery": "^3.5.1",
|
||||
"karma": "^5.2.3",
|
||||
"karma-chrome-launcher": "^3.1.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-cli": "^2.0.0",
|
||||
"karma-coverage-istanbul-reporter": "~3.0.2",
|
||||
"karma-jasmine": "^4.0.1",
|
||||
"karma-jasmine-html-reporter": "^1.5.4",
|
||||
"karma-jasmine": "~4.0.0",
|
||||
"karma-jasmine-html-reporter": "^1.5.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"node-sass": "^4.14.1",
|
||||
"popper.js": "^1.16.1",
|
||||
"prettier": "^2.1.2",
|
||||
"prettier": "^2.2.1",
|
||||
"protractor": "^7.0.0",
|
||||
"replace": "^1.2.0",
|
||||
"rxjs-tslint": "^0.1.8",
|
||||
@ -125,8 +125,9 @@
|
||||
"ts-node": "~9.0.0",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typescript": "^4.0.3",
|
||||
"webpack": "^4.44.2"
|
||||
"typescript": "4.0.2",
|
||||
"webpack": "4.44.2",
|
||||
"yarn-upgrade-all": "^0.5.4"
|
||||
},
|
||||
"greenkeeper": {
|
||||
"ignore": [
|
||||
|
@ -10,7 +10,7 @@ import { ProgressService } from './common/progress/progress.service';
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
constructor(
|
||||
|
@ -7,7 +7,6 @@ import { HttpClientModule } from '@angular/common/http';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { D3Service } from 'd3-ng2-service';
|
||||
import { HotkeyModule } from 'angular2-hotkeys';
|
||||
import { PersistenceModule } from 'angular-persistence';
|
||||
import { NgxElectronModule } from 'ngx-electron';
|
||||
import { FileUploadModule } from 'ng2-file-upload';
|
||||
@ -54,7 +53,6 @@ import { DrawingsDataSource } from './cartography/datasources/drawings-datasourc
|
||||
import { EditStyleActionComponent } from './components/project-map/context-menu/actions/edit-style-action/edit-style-action.component';
|
||||
import { MoveLayerDownActionComponent } from './components/project-map/context-menu/actions/move-layer-down-action/move-layer-down-action.component';
|
||||
import { MoveLayerUpActionComponent } from './components/project-map/context-menu/actions/move-layer-up-action/move-layer-up-action.component';
|
||||
import { ProjectMapShortcutsComponent } from './components/project-map/project-map-shortcuts/project-map-shortcuts.component';
|
||||
import { SettingsComponent } from './components/settings/settings.component';
|
||||
import { SettingsService } from './services/settings.service';
|
||||
|
||||
@ -321,7 +319,6 @@ import { ProjectReadmeComponent } from './components/project-map/project-readme/
|
||||
ResumeLinkActionComponent,
|
||||
SuspendLinkActionComponent,
|
||||
ResetLinkActionComponent,
|
||||
ProjectMapShortcutsComponent,
|
||||
SettingsComponent,
|
||||
PreferencesComponent,
|
||||
BundledServerFinderComponent,
|
||||
@ -489,7 +486,6 @@ import { ProjectReadmeComponent } from './components/project-map/project-readme/
|
||||
BrowserAnimationsModule,
|
||||
CdkTableModule,
|
||||
CartographyModule,
|
||||
HotkeyModule.forRoot(),
|
||||
PersistenceModule,
|
||||
NgxElectronModule,
|
||||
FileUploadModule,
|
||||
|
@ -103,8 +103,12 @@ export class NodeWidget implements Widget {
|
||||
if (n.height > 64) return 64;
|
||||
return n.height;
|
||||
})
|
||||
.attr('x', (n: MapNode) => 0)
|
||||
.attr('y', (n: MapNode) => 0)
|
||||
.attr('x', (n: MapNode) => {
|
||||
return 0
|
||||
})
|
||||
.attr('y', (n: MapNode) => {
|
||||
return 0
|
||||
})
|
||||
.on('mouseover', function(this, n: MapNode) {
|
||||
select(this).attr('class', 'over');
|
||||
})
|
||||
@ -113,6 +117,7 @@ export class NodeWidget implements Widget {
|
||||
});
|
||||
|
||||
node_body_merge.attr('transform', (n: MapNode) => {
|
||||
if (!n.width) return `translate(${n.x - 30},${n.y - 30})`
|
||||
return `translate(${n.x},${n.y})`;
|
||||
});
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
@Component({
|
||||
selector: 'app-information-dialog',
|
||||
templateUrl: 'information-dialog.component.html',
|
||||
styleUrls: ['information-dialog.component.css']
|
||||
styleUrls: ['information-dialog.component.scss']
|
||||
})
|
||||
export class InformationDialogComponent implements OnInit {
|
||||
public confirmationMessage: string;
|
||||
|
@ -13,7 +13,7 @@ import { AddedDataEvent } from '../../../cartography/events/event-source';
|
||||
@Component({
|
||||
selector: 'app-drawing-added',
|
||||
templateUrl: './drawing-added.component.html',
|
||||
styleUrls: ['./drawing-added.component.css']
|
||||
styleUrls: ['./drawing-added.component.scss']
|
||||
})
|
||||
export class DrawingAddedComponent implements OnInit, OnDestroy {
|
||||
@Input() server: Server;
|
||||
|
@ -12,7 +12,7 @@ import { Project } from '../../../models/project';
|
||||
@Component({
|
||||
selector: 'app-drawing-dragged',
|
||||
templateUrl: './drawing-dragged.component.html',
|
||||
styleUrls: ['./drawing-dragged.component.css']
|
||||
styleUrls: ['./drawing-dragged.component.scss']
|
||||
})
|
||||
export class DrawingDraggedComponent implements OnInit, OnDestroy {
|
||||
@Input() server: Server;
|
||||
|
@ -12,7 +12,7 @@ import { Subscription } from 'rxjs';
|
||||
@Component({
|
||||
selector: 'app-drawing-resized',
|
||||
templateUrl: './drawing-resized.component.html',
|
||||
styleUrls: ['./drawing-resized.component.css']
|
||||
styleUrls: ['./drawing-resized.component.scss']
|
||||
})
|
||||
export class DrawingResizedComponent implements OnInit, OnDestroy {
|
||||
@Input() server: Server;
|
||||
|
@ -11,7 +11,7 @@ import { LinksEventSource } from '../../../cartography/events/links-event-source
|
||||
@Component({
|
||||
selector: 'app-interface-label-dragged',
|
||||
templateUrl: './interface-label-dragged.component.html',
|
||||
styleUrls: ['./interface-label-dragged.component.css']
|
||||
styleUrls: ['./interface-label-dragged.component.scss']
|
||||
})
|
||||
export class InterfaceLabelDraggedComponent {
|
||||
@Input() server: Server;
|
||||
|
@ -14,7 +14,7 @@ import { LinksEventSource } from '../../../cartography/events/links-event-source
|
||||
@Component({
|
||||
selector: 'app-link-created',
|
||||
templateUrl: './link-created.component.html',
|
||||
styleUrls: ['./link-created.component.css']
|
||||
styleUrls: ['./link-created.component.scss']
|
||||
})
|
||||
export class LinkCreatedComponent implements OnInit, OnDestroy {
|
||||
@Input() server: Server;
|
||||
|
@ -12,7 +12,7 @@ import { Project } from '../../../models/project';
|
||||
@Component({
|
||||
selector: 'app-node-dragged',
|
||||
templateUrl: './node-dragged.component.html',
|
||||
styleUrls: ['./node-dragged.component.css']
|
||||
styleUrls: ['./node-dragged.component.scss']
|
||||
})
|
||||
export class NodeDraggedComponent implements OnInit, OnDestroy {
|
||||
@Input() server: Server;
|
||||
|
@ -12,7 +12,7 @@ import { MapLabel } from '../../../cartography/models/map/map-label';
|
||||
@Component({
|
||||
selector: 'app-node-label-dragged',
|
||||
templateUrl: './node-label-dragged.component.html',
|
||||
styleUrls: ['./node-label-dragged.component.css']
|
||||
styleUrls: ['./node-label-dragged.component.scss']
|
||||
})
|
||||
export class NodeLabelDraggedComponent implements OnInit, OnDestroy {
|
||||
@Input() server: Server;
|
||||
|
@ -15,7 +15,7 @@ import { Context } from '../../../cartography/models/context';
|
||||
@Component({
|
||||
selector: 'app-text-added',
|
||||
templateUrl: './text-added.component.html',
|
||||
styleUrls: ['./text-added.component.css']
|
||||
styleUrls: ['./text-added.component.scss']
|
||||
})
|
||||
export class TextAddedComponent implements OnInit, OnDestroy {
|
||||
@Input() server: Server;
|
||||
|
@ -13,7 +13,7 @@ import { MapDrawingToSvgConverter } from '../../../cartography/converters/map/ma
|
||||
@Component({
|
||||
selector: 'app-text-edited',
|
||||
templateUrl: './text-edited.component.html',
|
||||
styleUrls: ['./text-edited.component.css']
|
||||
styleUrls: ['./text-edited.component.scss']
|
||||
})
|
||||
export class TextEditedComponent implements OnInit, OnDestroy {
|
||||
@Input() server: Server;
|
||||
|
@ -26,7 +26,7 @@
|
||||
<div class="wrapper">
|
||||
<div class="buttonWrapper" *ngFor="let symbol of filteredSymbols | filenamefilter: searchText">
|
||||
<button [ngClass]="{ buttonSelected: isSelected === symbol.symbol_id }" class="button" (click)="setSelected(symbol.symbol_id)">
|
||||
<img [ngClass]="{ imageSelected: isSelected === symbol.symbol_id }" class="image" [src]="getImageSourceForTemplate(symbol.symbol_id)"/>
|
||||
<img lazyimg [ngClass]="{ imageSelected: isSelected === symbol.symbol_id }" class="image" [src]="getImageSourceForTemplate(symbol.symbol_id)"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -78,6 +78,6 @@ export class SymbolsComponent implements OnInit {
|
||||
}
|
||||
|
||||
getImageSourceForTemplate(symbol: string) {
|
||||
return `http://${this.server.host}:${this.server.port}/v2/symbols/${symbol}/raw`;
|
||||
return `${this.server.protocol}//${this.server.host}:${this.server.port}/v2/symbols/${symbol}/raw`;
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,9 @@
|
||||
@media screen and (max-width: 700px) {
|
||||
.consoleWrapper {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.consoleWrapper {
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
position: fixed;
|
||||
|
@ -0,0 +1,33 @@
|
||||
import { ConsoleWrapperComponent } from "./console-wrapper.component";
|
||||
import { NodeConsoleService } from '../../../services/nodeConsole.service';
|
||||
import { ThemeService } from '../../../services/theme.service';
|
||||
import { MapSettingsService } from '../../../services/mapsettings.service';
|
||||
|
||||
describe('ConsoleWrapperComponent', () => {
|
||||
it('should get actual theme', () => {
|
||||
const consoleService = new NodeConsoleService();
|
||||
|
||||
const themeService = autoSpy(ThemeService);
|
||||
themeService.getActualTheme.and.returnValue('light');
|
||||
|
||||
const mapSettingsService = autoSpy(MapSettingsService);
|
||||
const component = new ConsoleWrapperComponent(consoleService, themeService, mapSettingsService);
|
||||
|
||||
component.ngOnInit();
|
||||
|
||||
expect(component.isLightThemeEnabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
export type SpyOf<T> = T & { [k in keyof T]: jasmine.Spy };
|
||||
|
||||
export function autoSpy<T>(obj: new (...args: any[]) => T): SpyOf<T> {
|
||||
const res: SpyOf<T> = {} as any;
|
||||
|
||||
const keys = Object.getOwnPropertyNames(obj.prototype);
|
||||
keys.forEach((key) => {
|
||||
res[key] = jasmine.createSpy(key);
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
@ -17,7 +17,7 @@ import { Node } from '../../../cartography/models/node';
|
||||
import { ConsoleDeviceActionComponent } from '../context-menu/actions/console-device-action/console-device-action.component';
|
||||
import { ConsoleDeviceActionBrowserComponent } from '../context-menu/actions/console-device-action-browser/console-device-action-browser.component';
|
||||
|
||||
fdescribe('ContextConsoleMenuComponent', () => {
|
||||
describe('ContextConsoleMenuComponent', () => {
|
||||
let component: ContextConsoleMenuComponent;
|
||||
let fixture: ComponentFixture<ContextConsoleMenuComponent>;
|
||||
let toasterService: MockedToasterService = new MockedToasterService();
|
||||
|
@ -154,6 +154,6 @@ export class ImportApplianceComponent implements OnInit {
|
||||
}
|
||||
|
||||
private getUploadPath(server: Server, emulator: string, filename: string) {
|
||||
return `http://${server.host}:${server.port}/v2/${emulator}/images/${filename}`;
|
||||
return `${server.protocol}//${server.host}:${server.port}/v2/${emulator}/images/${filename}`;
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ describe('LogConsoleComponent', () => {
|
||||
|
||||
component.handleCommand();
|
||||
|
||||
expect(component.showMessage).toHaveBeenCalledWith({type: 'command', message: 'Current version: 2020.3.0-beta.4'});
|
||||
expect(component.showMessage).toHaveBeenCalledWith({type: 'command', message: 'Current version: 2.2.18dev'});
|
||||
});
|
||||
|
||||
it('should call show message when unknown command entered', () => {
|
||||
|
@ -167,7 +167,7 @@
|
||||
type="file"
|
||||
class="non-visible"
|
||||
#file2
|
||||
(change)="importImage($event)"
|
||||
(change)="importImage($event, version.images.hda_disk_image)"
|
||||
ng2FileSelect
|
||||
[uploader]="uploaderImage"/>
|
||||
<button class="button" mat-raised-button (click)="file2.click()">Import</button>
|
||||
@ -187,11 +187,11 @@
|
||||
<input
|
||||
type="file"
|
||||
class="non-visible"
|
||||
#file2
|
||||
(change)="importImage($event)"
|
||||
#file3
|
||||
(change)="importImage($event, version.images.hdb_disk_image)"
|
||||
ng2FileSelect
|
||||
[uploader]="uploaderImage"/>
|
||||
<button class="button" mat-raised-button (click)="file2.click()">Import</button>
|
||||
<button class="button" mat-raised-button (click)="file3.click()">Import</button>
|
||||
<button class="button" mat-raised-button (click)="downloadImageFromVersion(version.images.hdb_disk_image)">Download</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -235,7 +235,7 @@
|
||||
type="file"
|
||||
class="non-visible"
|
||||
#file2
|
||||
(change)="importImage($event)"
|
||||
(change)="importImage($event, image.filename)"
|
||||
ng2FileSelect
|
||||
[uploader]="uploaderImage"/>
|
||||
<button class="button" mat-raised-button (click)="file2.click()">Import</button>
|
||||
@ -271,7 +271,7 @@
|
||||
type="file"
|
||||
class="non-visible"
|
||||
#file2
|
||||
(change)="importImage($event)"
|
||||
(change)="importImage($, image.filename)"
|
||||
ng2FileSelect
|
||||
[uploader]="uploaderImage"/>
|
||||
<button class="button" mat-raised-button (click)="file2.click()">Import</button>
|
||||
|
@ -30,6 +30,7 @@ import { ComputeService } from '../../../services/compute.service';
|
||||
import { InformationDialogComponent } from '../../../components/dialogs/information-dialog.component';
|
||||
import { ProgressService } from '../../../common/progress/progress.service';
|
||||
import { TemplateNameDialogComponent } from './template-name-dialog/template-name-dialog.component';
|
||||
import * as SparkMD5 from 'spark-md5';
|
||||
|
||||
@Component({
|
||||
selector: 'app-new-template-dialog',
|
||||
@ -166,12 +167,14 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
this.uploaderImage.onErrorItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => {
|
||||
this.toasterService.error('An error has occured');
|
||||
this.progressService.deactivate();
|
||||
this.uploaderImage.clearQueue();
|
||||
};
|
||||
|
||||
this.uploaderImage.onSuccessItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => {
|
||||
this.toasterService.success('Image imported succesfully');
|
||||
this.refreshImages();
|
||||
this.progressService.deactivate();
|
||||
this.uploaderImage.clearQueue();
|
||||
};
|
||||
}
|
||||
|
||||
@ -332,7 +335,35 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
dialogRef.componentInstance.appliance = object;
|
||||
}
|
||||
|
||||
importImage(event) {
|
||||
importImage(event, imageName) {
|
||||
this.progressService.activate();
|
||||
this.computeChecksumMd5(event.target.files[0], false).then((output) => {
|
||||
let imageToInstall = this.applianceToInstall.images.filter(n => n.filename === imageName)[0];
|
||||
|
||||
if (imageToInstall.md5sum !== output) {
|
||||
this.progressService.deactivate();
|
||||
const dialogRef = this.dialog.open(InformationDialogComponent, {
|
||||
width: '400px',
|
||||
height: '200px',
|
||||
autoFocus: false,
|
||||
disableClose: true
|
||||
});
|
||||
dialogRef.componentInstance.confirmationMessage = `This is not the correct file.
|
||||
The MD5 sum is ${output} and should be ${imageToInstall.md5sum}. Do you want to accept it at your own risks?`;
|
||||
dialogRef.afterClosed().subscribe((answer: boolean) => {
|
||||
if (answer) {
|
||||
this.importImageFile(event)
|
||||
} else {
|
||||
this.uploaderImage.clearQueue();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.importImageFile(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
importImageFile(event) {
|
||||
let name = event.target.files[0].name.split('-')[0];
|
||||
let fileName = event.target.files[0].name;
|
||||
let file = event.target.files[0];
|
||||
@ -373,10 +404,10 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
checkImages(version: Version): boolean {
|
||||
if (version.images.hdb_disk_image) {
|
||||
if (this.checkImageFromVersion(version.images.hda_disk_image) && this.checkImageFromVersion(version.images.hdb_disk_image)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.checkImageFromVersion(version.images.hda_disk_image)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -611,6 +642,36 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private computeChecksumMd5(file: File, encode = false): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunkSize = 2097152;
|
||||
const spark = new SparkMD5.ArrayBuffer();
|
||||
const fileReader = new FileReader();
|
||||
let cursor = 0;
|
||||
|
||||
fileReader.onerror = function(): void {
|
||||
reject('MD5 computation failed - error reading the file');
|
||||
};
|
||||
|
||||
function processChunk(chunkStart: number): void {
|
||||
const chunkEnd = Math.min(file.size, chunkStart + chunkSize);
|
||||
fileReader.readAsArrayBuffer(file.slice(chunkStart, chunkEnd));
|
||||
}
|
||||
|
||||
fileReader.onload = function(e: any): void {
|
||||
spark.append(e.target.result);
|
||||
cursor += chunkSize;
|
||||
if (cursor < file.size) {
|
||||
processChunk(cursor);
|
||||
} else {
|
||||
resolve(spark.end(encode));
|
||||
}
|
||||
};
|
||||
|
||||
processChunk(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function compareNames(a: string, b: string, isAsc: boolean) {
|
||||
|
@ -1,114 +0,0 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { inject } from '@angular/core/testing';
|
||||
|
||||
import { mock, instance, capture, when, anything } from 'ts-mockito';
|
||||
import { HotkeyModule, HotkeysService, Hotkey } from 'angular2-hotkeys';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { ProjectMapShortcutsComponent } from './project-map-shortcuts.component';
|
||||
import { ToasterService } from '../../../services/toaster.service';
|
||||
import { NodeService } from '../../../services/node.service';
|
||||
import { HttpServer } from '../../../services/http-server.service';
|
||||
import { SelectionManager } from '../../../cartography/managers/selection-manager';
|
||||
import { Server } from '../../../models/server';
|
||||
import { Project } from '../../../models/project';
|
||||
import { ProjectService } from '../../../services/project.service';
|
||||
import { MockedProjectService } from '../../../services/project.service.spec';
|
||||
import { SettingsService } from '../../../services/settings.service';
|
||||
import { MockedToasterService } from '../../../services/toaster.service.spec';
|
||||
import { mapTo } from 'rxjs/internal/operators';
|
||||
import { MapNodeToNodeConverter } from '../../../cartography/converters/map/map-node-to-node-converter';
|
||||
import { MapNode } from '../../../cartography/models/map/map-node';
|
||||
import { MapLabelToLabelConverter } from '../../../cartography/converters/map/map-label-to-label-converter';
|
||||
import { MapPortToPortConverter } from '../../../cartography/converters/map/map-port-to-port-converter';
|
||||
import { Node } from '../../../cartography/models/node';
|
||||
import { FontBBoxCalculator } from '../../../cartography/helpers/font-bbox-calculator';
|
||||
import { CssFixer } from '../../../cartography/helpers/css-fixer';
|
||||
import { FontFixer } from '../../../cartography/helpers/font-fixer';
|
||||
|
||||
describe('ProjectMapShortcutsComponent', () => {
|
||||
let component: ProjectMapShortcutsComponent;
|
||||
let fixture: ComponentFixture<ProjectMapShortcutsComponent>;
|
||||
let hotkeyServiceMock: HotkeysService;
|
||||
let hotkeyServiceInstanceMock: HotkeysService;
|
||||
let nodeServiceMock: NodeService;
|
||||
let node: MapNode;
|
||||
|
||||
beforeEach(async(() => {
|
||||
node = new MapNode();
|
||||
const selectionManagerMock = mock(SelectionManager);
|
||||
when(selectionManagerMock.getSelected()).thenReturn([node]);
|
||||
|
||||
nodeServiceMock = mock(NodeService);
|
||||
hotkeyServiceMock = mock(HotkeysService);
|
||||
hotkeyServiceInstanceMock = instance(hotkeyServiceMock);
|
||||
TestBed.configureTestingModule({
|
||||
imports: [HotkeyModule.forRoot(), HttpClientTestingModule],
|
||||
providers: [
|
||||
HttpServer,
|
||||
{ provide: NodeService, useFactory: () => instance(nodeServiceMock) },
|
||||
{ provide: HotkeysService, useFactory: () => hotkeyServiceInstanceMock },
|
||||
{ provide: ToasterService, useClass: MockedToasterService },
|
||||
{ provide: ProjectService, useClass: MockedProjectService },
|
||||
{ provide: SelectionManager, useValue: instance(selectionManagerMock) },
|
||||
SettingsService,
|
||||
MapNodeToNodeConverter,
|
||||
MapLabelToLabelConverter,
|
||||
MapPortToPortConverter,
|
||||
MapLabelToLabelConverter,
|
||||
FontBBoxCalculator,
|
||||
CssFixer,
|
||||
FontFixer
|
||||
],
|
||||
declarations: [ProjectMapShortcutsComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProjectMapShortcutsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should bind delete key', () => {
|
||||
component.ngOnInit();
|
||||
const [hotkey] = capture(hotkeyServiceMock.add).last();
|
||||
expect((hotkey as Hotkey).combo).toEqual(['del']);
|
||||
expect((hotkey as Hotkey).callback).toBeDefined();
|
||||
});
|
||||
|
||||
it('should remove binding', () => {
|
||||
component.ngOnDestroy();
|
||||
const [hotkey] = capture(hotkeyServiceMock.remove).last();
|
||||
expect((hotkey as Hotkey).combo).toEqual(['del']);
|
||||
});
|
||||
|
||||
describe('onDeleteHandler', () => {
|
||||
beforeEach(() => {
|
||||
const server = new Server();
|
||||
const project = new Project();
|
||||
|
||||
when(nodeServiceMock.delete(server, anything())).thenReturn(of(new Node()).pipe(mapTo(null)));
|
||||
|
||||
component.project = project;
|
||||
component.server = server;
|
||||
});
|
||||
|
||||
it('should handle delete', inject([ToasterService], (toaster: MockedToasterService) => {
|
||||
component.project.readonly = false;
|
||||
component.onDeleteHandler(null);
|
||||
expect(toaster.successes).toEqual(['Node has been deleted']);
|
||||
}));
|
||||
|
||||
it('should not delete when project in readonly mode', inject([ToasterService], (toaster: MockedToasterService) => {
|
||||
component.project.readonly = true;
|
||||
component.onDeleteHandler(null);
|
||||
expect(toaster.successes).toEqual([]);
|
||||
}));
|
||||
});
|
||||
});
|
@ -1,59 +0,0 @@
|
||||
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
|
||||
import { HotkeysService, Hotkey } from 'angular2-hotkeys';
|
||||
|
||||
import { SelectionManager } from '../../../cartography/managers/selection-manager';
|
||||
import { NodeService } from '../../../services/node.service';
|
||||
import { Server } from '../../../models/server';
|
||||
import { ToasterService } from '../../../services/toaster.service';
|
||||
import { Project } from '../../../models/project';
|
||||
import { ProjectService } from '../../../services/project.service';
|
||||
import { MapNode } from '../../../cartography/models/map/map-node';
|
||||
import { MapNodeToNodeConverter } from '../../../cartography/converters/map/map-node-to-node-converter';
|
||||
|
||||
@Component({
|
||||
selector: 'app-project-map-shortcuts',
|
||||
template: ''
|
||||
})
|
||||
export class ProjectMapShortcutsComponent implements OnInit, OnDestroy {
|
||||
@Input() project: Project;
|
||||
@Input() server: Server;
|
||||
|
||||
private deleteHotkey: Hotkey;
|
||||
|
||||
constructor(
|
||||
private hotkeysService: HotkeysService,
|
||||
private toaster: ToasterService,
|
||||
private nodesService: NodeService,
|
||||
private projectService: ProjectService,
|
||||
private mapNodeToNode: MapNodeToNodeConverter,
|
||||
private selectionManager: SelectionManager
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
const self = this;
|
||||
this.deleteHotkey = new Hotkey('del', (event: KeyboardEvent) => {
|
||||
return self.onDeleteHandler(event);
|
||||
});
|
||||
this.hotkeysService.add(this.deleteHotkey);
|
||||
}
|
||||
|
||||
onDeleteHandler(event: KeyboardEvent): boolean {
|
||||
if (!this.projectService.isReadOnly(this.project)) {
|
||||
const selected = this.selectionManager.getSelected();
|
||||
|
||||
selected
|
||||
.filter(item => item instanceof MapNode)
|
||||
.forEach((item: MapNode) => {
|
||||
const node = this.mapNodeToNode.convert(item);
|
||||
this.nodesService.delete(this.server, node).subscribe(data => {
|
||||
this.toaster.success('Node has been deleted');
|
||||
});
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.hotkeysService.remove(this.deleteHotkey);
|
||||
}
|
||||
}
|
@ -193,7 +193,6 @@
|
||||
</div>
|
||||
|
||||
<app-progress></app-progress>
|
||||
<app-project-map-shortcuts *ngIf="project" [project]="project" [server]="server"></app-project-map-shortcuts>
|
||||
<app-draw-link-tool [links]="links" *ngIf="tools.draw_link"></app-draw-link-tool>
|
||||
|
||||
<app-drawing-dragged [server]="server" [project]="project"></app-drawing-dragged>
|
||||
|
@ -73,6 +73,7 @@ import { Title } from '@angular/platform-browser';
|
||||
import { NewTemplateDialogComponent } from './new-template-dialog/new-template-dialog.component';
|
||||
import { NodeConsoleService } from '../../services/nodeConsole.service';
|
||||
import { ProjectReadmeComponent } from './project-readme/project-readme.component';
|
||||
import * as Mousetrap from 'mousetrap';
|
||||
|
||||
|
||||
@Component({
|
||||
@ -168,12 +169,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.themeService.getActualTheme() === 'light' ? this.isLightThemeEnabled = true : this.isLightThemeEnabled = false;
|
||||
this.settings = this.settingsService.getAll();
|
||||
this.isTopologySummaryVisible = this.mapSettingsService.isTopologySummaryVisible;
|
||||
this.isConsoleVisible = this.mapSettingsService.isLogConsoleVisible;
|
||||
this.mapSettingsService.logConsoleSubject.subscribe(value => this.isConsoleVisible = value);
|
||||
|
||||
this.getSettings();
|
||||
this.progressService.activate();
|
||||
|
||||
if (this.serverService.isServiceInitialized) {
|
||||
@ -186,6 +182,22 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
this.addSubscriptions();
|
||||
this.addKeyboardListeners();
|
||||
}
|
||||
|
||||
getSettings() {
|
||||
this.themeService.getActualTheme() === 'light' ? this.isLightThemeEnabled = true : this.isLightThemeEnabled = false;
|
||||
this.settings = this.settingsService.getAll();
|
||||
this.isTopologySummaryVisible = this.mapSettingsService.isTopologySummaryVisible;
|
||||
this.isConsoleVisible = this.mapSettingsService.isLogConsoleVisible;
|
||||
this.mapSettingsService.logConsoleSubject.subscribe(value => this.isConsoleVisible = value);
|
||||
this.notificationsVisibility = localStorage.getItem('notificationsVisibility') === 'true' ? true : false;
|
||||
this.layersVisibility = localStorage.getItem('layersVisibility') === 'true' ? true : false;
|
||||
this.gridVisibility = localStorage.getItem('gridVisibility') === 'true' ? true : false;
|
||||
}
|
||||
|
||||
addSubscriptions() {
|
||||
this.projectMapSubscription.add(
|
||||
this.mapSettingsService.mapRenderedEmitter.subscribe((value: boolean) => {
|
||||
if (this.scrollEnabled) this.centerCanvas();
|
||||
@ -203,7 +215,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
this.nodesDataSource.changes.subscribe((nodes: Node[]) => {
|
||||
if (!this.server) return;
|
||||
nodes.forEach((node: Node) => {
|
||||
node.symbol_url = `http://${this.server.host}:${this.server.port}/v2/symbols/${node.symbol}/raw`;
|
||||
node.symbol_url = `${this.server.protocol}//${this.server.host}:${this.server.port}/v2/symbols/${node.symbol}/raw`;
|
||||
});
|
||||
|
||||
this.nodes = nodes;
|
||||
@ -231,11 +243,6 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
message: message
|
||||
});
|
||||
}));
|
||||
|
||||
this.notificationsVisibility = localStorage.getItem('notificationsVisibility') === 'true' ? true : false;
|
||||
this.layersVisibility = localStorage.getItem('layersVisibility') === 'true' ? true : false;
|
||||
this.gridVisibility = localStorage.getItem('gridVisibility') === 'true' ? true : false;
|
||||
this.addKeyboardListeners();
|
||||
}
|
||||
|
||||
getData() {
|
||||
@ -330,6 +337,20 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
event.preventDefault();
|
||||
this.router.navigate(['/server', this.server.id, 'preferences']);
|
||||
});
|
||||
|
||||
Mousetrap.bind('del', (event: Event) => {
|
||||
event.preventDefault();
|
||||
const selected = this.selectionManager.getSelected();
|
||||
|
||||
selected
|
||||
.filter(item => item instanceof MapNode)
|
||||
.forEach((item: MapNode) => {
|
||||
const node = this.mapNodeToNode.convert(item);
|
||||
this.nodeService.delete(this.server, node).subscribe(data => {
|
||||
this.toasterService.success('Node has been deleted');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onProjectLoad(project: Project) {
|
||||
|
@ -15,7 +15,7 @@ import { projectNameAsyncValidator } from '../../../validators/project-name-asyn
|
||||
@Component({
|
||||
selector: 'app-add-blank-project-dialog',
|
||||
templateUrl: './add-blank-project-dialog.component.html',
|
||||
styleUrls: ['./add-blank-project-dialog.component.css'],
|
||||
styleUrls: ['./add-blank-project-dialog.component.scss'],
|
||||
providers: [ProjectNameValidator]
|
||||
})
|
||||
export class AddBlankProjectDialogComponent implements OnInit {
|
||||
|
@ -5,7 +5,7 @@ import { Project } from '../../../models/project';
|
||||
@Component({
|
||||
selector: 'app-import-project-dialog',
|
||||
templateUrl: 'confirmation-dialog.component.html',
|
||||
styleUrls: ['confirmation-dialog.component.css']
|
||||
styleUrls: ['confirmation-dialog.component.scss']
|
||||
})
|
||||
export class ConfirmationDialogComponent implements OnInit {
|
||||
private existingProject: Project;
|
||||
|
@ -13,7 +13,7 @@ import { ProjectNameValidator } from '../models/projectNameValidator';
|
||||
@Component({
|
||||
selector: 'app-import-project-dialog',
|
||||
templateUrl: 'import-project-dialog.component.html',
|
||||
styleUrls: ['import-project-dialog.component.css'],
|
||||
styleUrls: ['import-project-dialog.component.scss'],
|
||||
providers: [ProjectNameValidator]
|
||||
})
|
||||
export class ImportProjectDialogComponent implements OnInit {
|
||||
|
@ -28,7 +28,7 @@ import { ElectronService } from 'ngx-electron';
|
||||
@Component({
|
||||
selector: 'app-projects',
|
||||
templateUrl: './projects.component.html',
|
||||
styleUrls: ['./projects.component.css']
|
||||
styleUrls: ['./projects.component.scss']
|
||||
})
|
||||
export class ProjectsComponent implements OnInit {
|
||||
server: Server;
|
||||
|
@ -14,7 +14,7 @@ import { NodesDataSource } from '../../../cartography/datasources/nodes-datasour
|
||||
@Component({
|
||||
selector: 'app-save-project-dialog',
|
||||
templateUrl: './save-project-dialog.component.html',
|
||||
styleUrls: ['./save-project-dialog.component.css'],
|
||||
styleUrls: ['./save-project-dialog.component.scss'],
|
||||
providers: [ProjectNameValidator]
|
||||
})
|
||||
export class SaveProjectDialogComponent implements OnInit {
|
||||
|
@ -28,6 +28,12 @@
|
||||
<input matInput tabindex="1" formControlName="port" placeholder="Port" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-select placeholder="Protocol" formControlName="protocol" >
|
||||
<mat-option *ngFor="let protocol of protocols" [value]="protocol.key"> {{ protocol.name }} </mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field *ngIf="serverForm.get('location').value === 'remote'">
|
||||
<mat-select placeholder="Authorization" formControlName="authorization" >
|
||||
<mat-option *ngFor="let auth of authorizations" [value]="auth.key"> {{ auth.name }} </mat-option>
|
||||
|
@ -13,6 +13,7 @@ import { ToasterService } from '../../../services/toaster.service';
|
||||
})
|
||||
export class AddServerDialogComponent implements OnInit {
|
||||
authorizations = [{ key: 'none', name: 'No authorization' }, { key: 'basic', name: 'Basic authorization' }];
|
||||
protocols = [{ key: 'http:', name: 'HTTP' }, { key: 'https:', name: 'HTTPS' }];
|
||||
locations = [];
|
||||
|
||||
serverForm = new FormGroup({
|
||||
@ -22,6 +23,7 @@ export class AddServerDialogComponent implements OnInit {
|
||||
'ubridge_path': new FormControl(''),
|
||||
'host': new FormControl('', [ Validators.required ]),
|
||||
'port': new FormControl('', [ Validators.required, Validators.min(1) ]),
|
||||
'protocol': new FormControl('http:'),
|
||||
'authorization': new FormControl('none'),
|
||||
'login': new FormControl(''),
|
||||
'password': new FormControl('')
|
||||
|
@ -3,13 +3,14 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { map } from 'rxjs//operators';
|
||||
|
||||
import { Server } from '../../../models/server';
|
||||
import { Server, ServerProtocol } from '../../../models/server';
|
||||
import { VersionService } from '../../../services/version.service';
|
||||
import { Version } from '../../../models/version';
|
||||
import { forkJoin } from 'rxjs';
|
||||
import { ServerService } from '../../../services/server.service';
|
||||
import { ServerDatabase } from '../../../services/server.database';
|
||||
import { from } from 'rxjs';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-server-discovery',
|
||||
@ -29,24 +30,52 @@ export class ServerDiscoveryComponent implements OnInit {
|
||||
constructor(
|
||||
private versionService: VersionService,
|
||||
private serverService: ServerService,
|
||||
private serverDatabase: ServerDatabase
|
||||
private serverDatabase: ServerDatabase,
|
||||
private route : ActivatedRoute
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.serverService.isServiceInitialized) this.discoverFirstAvailableServer();
|
||||
|
||||
if (this.serverService.isServiceInitialized) this.discoverFirstServer();
|
||||
this.serverService.serviceInitialized.subscribe(async (value: boolean) => {
|
||||
if (value) {
|
||||
this.discoverFirstAvailableServer();
|
||||
this.discoverFirstServer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async discoverFirstServer() {
|
||||
let discovered = await this.discoverServers();
|
||||
let local = await this.serverService.findAll();
|
||||
|
||||
local.forEach(added => {
|
||||
discovered = discovered.filter(server => {
|
||||
return !(server.host == added.host && server.port == added.port);
|
||||
});
|
||||
});
|
||||
|
||||
if (discovered.length > 0) {
|
||||
this.discoveredServer = discovered.shift();
|
||||
};
|
||||
}
|
||||
|
||||
async discoverServers() {
|
||||
let discoveredServers: Server[] = [];
|
||||
this.defaultServers.forEach(async (testServer) => {
|
||||
const server = new Server();
|
||||
server.host = testServer.host;
|
||||
server.port = testServer.port;
|
||||
let version = await this.versionService.get(server).toPromise().catch(error => null);
|
||||
if (version) discoveredServers.push(server);
|
||||
});
|
||||
return discoveredServers;
|
||||
}
|
||||
|
||||
discoverFirstAvailableServer() {
|
||||
forkJoin(
|
||||
[from(this.serverService.findAll()).pipe(map((s: Server[]) => s)),
|
||||
this.discovery()]
|
||||
).subscribe(([local, discovered]) => {
|
||||
).subscribe(
|
||||
([local, discovered]) => {
|
||||
local.forEach(added => {
|
||||
discovered = discovered.filter(server => {
|
||||
return !(server.host == added.host && server.port == added.port);
|
||||
@ -55,7 +84,8 @@ export class ServerDiscoveryComponent implements OnInit {
|
||||
if (discovered.length > 0) {
|
||||
this.discoveredServer = discovered.shift();
|
||||
}
|
||||
});
|
||||
},
|
||||
error => {});
|
||||
}
|
||||
|
||||
discovery(): Observable<Server[]> {
|
||||
@ -94,6 +124,7 @@ export class ServerDiscoveryComponent implements OnInit {
|
||||
}
|
||||
|
||||
server.location = 'remote';
|
||||
server.protocol = location.protocol as ServerProtocol;
|
||||
|
||||
this.serverService.create(server).then((created: Server) => {
|
||||
this.serverDatabase.addServer(created);
|
||||
|
@ -4,8 +4,8 @@ import { MatBottomSheet } from '@angular/material/bottom-sheet';
|
||||
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { Observable, merge, Subscription } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { Router } from '@angular/router';
|
||||
import { Server } from '../../models/server';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Server, ServerProtocol } from '../../models/server';
|
||||
import { ServerService } from '../../services/server.service';
|
||||
import { ServerDatabase } from '../../services/server.database';
|
||||
import { AddServerDialogComponent } from './add-server-dialog/add-server-dialog.component';
|
||||
@ -18,7 +18,7 @@ import { ConfirmationBottomSheetComponent } from '../projects/confirmation-botto
|
||||
@Component({
|
||||
selector: 'app-server-list',
|
||||
templateUrl: './servers.component.html',
|
||||
styleUrls: ['./servers.component.css']
|
||||
styleUrls: ['./servers.component.scss']
|
||||
})
|
||||
export class ServersComponent implements OnInit, OnDestroy {
|
||||
dataSource: ServerDataSource;
|
||||
@ -35,6 +35,7 @@ export class ServersComponent implements OnInit, OnDestroy {
|
||||
private electronService: ElectronService,
|
||||
private childProcessService: ChildProcessService,
|
||||
private bottomSheet: MatBottomSheet,
|
||||
private route : ActivatedRoute
|
||||
) {}
|
||||
|
||||
getServers() {
|
||||
@ -52,6 +53,7 @@ export class ServersComponent implements OnInit, OnDestroy {
|
||||
this.serverService.checkServerVersion(server).subscribe(
|
||||
(serverInfo) => {
|
||||
if ((serverInfo.version.split('.')[1]>=2) && (serverInfo.version.split('.')[0]>=2)) {
|
||||
if (!server.protocol) server.protocol = location.protocol as ServerProtocol;
|
||||
if (!this.serverDatabase.find(server.name)) this.serverDatabase.addServer(server);
|
||||
}
|
||||
},
|
||||
|
@ -15,6 +15,8 @@ import { ToasterService } from '../../services/toaster.service';
|
||||
import { MockedToasterService } from '../../services/toaster.service.spec';
|
||||
import { ConsoleService } from '../../services/settings/console.service';
|
||||
import { MapSettingsService } from '../../services/mapsettings.service';
|
||||
import { UpdatesService } from '../../services/updates.service';
|
||||
import { autoSpy } from '../project-map/console-wrapper/console-wrapper.component.spec';
|
||||
|
||||
describe('SettingsComponent', () => {
|
||||
let component: SettingsComponent;
|
||||
@ -25,6 +27,7 @@ describe('SettingsComponent', () => {
|
||||
toggleIntegrateInterfaceLabels(val: boolean) {}
|
||||
};
|
||||
let consoleService;
|
||||
let updatesService = autoSpy(UpdatesService);
|
||||
|
||||
beforeEach(async(() => {
|
||||
consoleService = {
|
||||
@ -37,7 +40,8 @@ describe('SettingsComponent', () => {
|
||||
SettingsService,
|
||||
{ provide: ToasterService, useClass: MockedToasterService },
|
||||
{ provide: ConsoleService, useValue: consoleService },
|
||||
{ provide: MapSettingsService, useValue: mapSettingsService }
|
||||
{ provide: MapSettingsService, useValue: mapSettingsService },
|
||||
{ provide: UpdatesService, useValue: updatesService }
|
||||
],
|
||||
declarations: [SettingsComponent]
|
||||
}).compileComponents();
|
||||
|
@ -116,7 +116,7 @@ export class TemplateComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
getImageSourceForTemplate(template: Template) {
|
||||
return `http://${this.server.host}:${this.server.port}/v2/symbols/${template.symbol}/raw`;
|
||||
return `${this.server.protocol}//${this.server.host}:${this.server.port}/v2/symbols/${template.symbol}/raw`;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
@ -72,6 +72,12 @@
|
||||
<div class="summaryContentServers">
|
||||
<div class="nodeRow" *ngFor="let compute of computes">
|
||||
<div>
|
||||
<svg *ngIf="compute.connected" width="10" height="10">
|
||||
<rect class="status_started" x="0" y="0" width="10" height="10" fill="green"></rect>
|
||||
</svg>
|
||||
<svg *ngIf="!compute.connected" width="10" height="10">
|
||||
<rect class="status_stopped" x="0" y="0" width="10" height="10" fill="red"></rect>
|
||||
</svg>
|
||||
{{compute.name}}
|
||||
</div>
|
||||
<div>
|
||||
|
@ -1,3 +1,9 @@
|
||||
@media screen and (max-width: 600px) {
|
||||
.summaryWrapper {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.summaryWrapper {
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||
position: fixed;
|
||||
|
12
src/app/directives/LazyImg.directive.ts
Normal file
12
src/app/directives/LazyImg.directive.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { Directive, ElementRef } from '@angular/core';
|
||||
|
||||
@Directive({ selector: '[lazyimg]' })
|
||||
export class LazyImgDirective {
|
||||
constructor({ nativeElement }: ElementRef<HTMLImageElement>) {
|
||||
const supports = 'loading' in HTMLImageElement.prototype;
|
||||
|
||||
if (supports) {
|
||||
nativeElement.setAttribute('loading', 'lazy');
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ import { Router } from '@angular/router';
|
||||
selector: 'app-default-layout',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
templateUrl: './default-layout.component.html',
|
||||
styleUrls: ['./default-layout.component.css']
|
||||
styleUrls: ['./default-layout.component.scss']
|
||||
})
|
||||
export class DefaultLayoutComponent implements OnInit, OnDestroy {
|
||||
public isInstalledSoftwareAvailable = false;
|
||||
|
@ -1,6 +1,7 @@
|
||||
export type ServerAuthorization = 'basic' | 'none';
|
||||
export type ServerLocation = 'local' | 'remote' | 'bundled';
|
||||
export type ServerStatus = 'stopped' | 'starting' | 'running';
|
||||
export type ServerProtocol = 'http:' | 'https:'
|
||||
|
||||
export class Server {
|
||||
id: number;
|
||||
@ -14,4 +15,5 @@ export class Server {
|
||||
login: string;
|
||||
password: string;
|
||||
status: ServerStatus;
|
||||
protocol: ServerProtocol;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ export class ApplianceService {
|
||||
}
|
||||
|
||||
getUploadPath(server: Server, emulator: string, filename: string) {
|
||||
return `http://${server.host}:${server.port}/v2/compute/${emulator}/images/${filename}`;
|
||||
return `${server.protocol}//${server.host}:${server.port}/v2/compute/${emulator}/images/${filename}`;
|
||||
}
|
||||
|
||||
updateAppliances(server: Server): Observable<Appliance[]> {
|
||||
|
@ -6,8 +6,8 @@ export class BuiltInTemplatesConfigurationService {
|
||||
let categories = [["Default", "guest"],
|
||||
["Routers", "router"],
|
||||
["Switches", "switch"],
|
||||
["End devices", "end_device"],
|
||||
["Security devices", "security_device"]];
|
||||
["End devices", "guest"],
|
||||
["Security devices", "firewall"]];
|
||||
|
||||
return categories;
|
||||
}
|
||||
@ -20,8 +20,8 @@ export class BuiltInTemplatesConfigurationService {
|
||||
let categories = [["Default", "guest"],
|
||||
["Routers", "router"],
|
||||
["Switches", "switch"],
|
||||
["End devices", "end_device"],
|
||||
["Security devices", "security_device"]];
|
||||
["End devices", "guest"],
|
||||
["Security devices", "firewall"]];
|
||||
|
||||
return categories;
|
||||
}
|
||||
@ -30,8 +30,8 @@ export class BuiltInTemplatesConfigurationService {
|
||||
let categories = [["Default", "guest"],
|
||||
["Routers", "router"],
|
||||
["Switches", "switch"],
|
||||
["End devices", "end_device"],
|
||||
["Security devices", "security_device"]];
|
||||
["End devices", "guest"],
|
||||
["Security devices", "firewall"]];
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ export class ComputeService {
|
||||
}
|
||||
|
||||
getUploadPath(server: Server, emulator: string, filename: string) {
|
||||
return `http://${server.host}:${server.port}/v2/${emulator}/images/${filename}`;
|
||||
return `${server.protocol}//${server.host}:${server.port}/v2/${emulator}/images/${filename}`;
|
||||
}
|
||||
|
||||
getStatistics(server: Server): Observable<ComputeStatistics[]> {
|
||||
|
@ -8,10 +8,10 @@ export class DockerConfigurationService {
|
||||
|
||||
getCategories() {
|
||||
let categories = [["Default", "guest"],
|
||||
["Routers", "routers"],
|
||||
["Switches", "switches"],
|
||||
["End devices", "end_devices"],
|
||||
["Security devices", "security_devices"]];
|
||||
["Routers", "router"],
|
||||
["Switches", "switch"],
|
||||
["End devices", "guest"],
|
||||
["Security devices", "firewall"]];
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { HttpHeaders, HttpClient, HttpParams, HttpErrorResponse } from '@angular
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
import { Server } from '../models/server';
|
||||
import { Server, ServerProtocol } from '../models/server';
|
||||
|
||||
/* tslint:disable:interface-over-type-literal */
|
||||
export type JsonOptions = {
|
||||
@ -177,13 +177,10 @@ export class HttpServer {
|
||||
|
||||
private getOptionsForServer<T extends HeadersOptions>(server: Server, url: string, options: T) {
|
||||
if (server.host && server.port) {
|
||||
if (server.authorization === 'basic') {
|
||||
url = `https://${server.host}:${server.port}/v2${url}`;
|
||||
console.log(url);
|
||||
} else {
|
||||
url = `http://${server.host}:${server.port}/v2${url}`;
|
||||
console.log(url);
|
||||
if (!server.protocol) {
|
||||
server.protocol = location.protocol as ServerProtocol;
|
||||
}
|
||||
url = `${server.protocol}//${server.host}:${server.port}/v2${url}`;
|
||||
} else {
|
||||
url = `/v2${url}`;
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ export class IosService {
|
||||
}
|
||||
|
||||
getImagePath(server: Server, filename: string): string {
|
||||
return `http://${server.host}:${server.port}/v2/compute/dynamips/images/${filename}`;
|
||||
return `${server.protocol}//${server.host}:${server.port}/v2/compute/dynamips/images/${filename}`;
|
||||
}
|
||||
|
||||
getTemplates(server: Server): Observable<IosTemplate[]> {
|
||||
|
@ -10,8 +10,8 @@ export class IouConfigurationService {
|
||||
let categories = [["Default", "guest"],
|
||||
["Routers", "router"],
|
||||
["Switches", "switch"],
|
||||
["End devices", "end_device"],
|
||||
["Security devices", "security_device"]];
|
||||
["End devices", "guest"],
|
||||
["Security devices", "firewall"]];
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ export class IouService {
|
||||
}
|
||||
|
||||
getImagePath(server: Server, filename: string): string {
|
||||
return `http://${server.host}:${server.port}/v2/compute/iou/images/${filename}`;
|
||||
return `${server.protocol}//${server.host}:${server.port}/v2/compute/iou/images/${filename}`;
|
||||
}
|
||||
|
||||
addTemplate(server: Server, iouTemplate: any): Observable<any> {
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { Injectable, EventEmitter } from "@angular/core";
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MapSettingsService {
|
||||
public isScrollDisabled = new Subject<boolean>();
|
||||
public isMapLocked = new Subject<boolean>();
|
||||
|
@ -77,11 +77,11 @@ export class ProjectService {
|
||||
}
|
||||
|
||||
getUploadPath(server: Server, uuid: string, project_name: string) {
|
||||
return `http://${server.host}:${server.port}/v2/projects/${uuid}/import?name=${project_name}`;
|
||||
return `${server.protocol}//${server.host}:${server.port}/v2/projects/${uuid}/import?name=${project_name}`;
|
||||
}
|
||||
|
||||
getExportPath(server: Server, project: Project) {
|
||||
return `http://${server.host}:${server.port}/v2/projects/${project.project_id}/export`;
|
||||
return `${server.protocol}//${server.host}:${server.port}/v2/projects/${project.project_id}/export`;
|
||||
}
|
||||
|
||||
export(server: Server, project_id: string): Observable<any> {
|
||||
|
@ -82,10 +82,10 @@ export class QemuConfigurationService {
|
||||
|
||||
getCategories() {
|
||||
let categories = [["Default", "guest"],
|
||||
["Routers", "routers"],
|
||||
["Switches", "switches"],
|
||||
["End devices", "end_devices"],
|
||||
["Security devices", "security_devices"]];
|
||||
["Routers", "router"],
|
||||
["Switches", "switch"],
|
||||
["End devices", "guest"],
|
||||
["Security devices", "firewall"]];
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ export class QemuService {
|
||||
}
|
||||
|
||||
getImagePath(server: Server, filename: string): string {
|
||||
return `http://${server.host}:${server.port}/v2/compute/qemu/images/${filename}`;
|
||||
return `${server.protocol}//${server.host}:${server.port}/v2/compute/qemu/images/${filename}`;
|
||||
}
|
||||
|
||||
getBinaries(server: Server): Observable<QemuBinary[]> {
|
||||
|
@ -150,6 +150,7 @@ describe('ServerService', () => {
|
||||
expectedServer.host = 'hostname';
|
||||
expectedServer.port = 9999;
|
||||
expectedServer.location = 'bundled';
|
||||
expectedServer.protocol = 'http:';
|
||||
|
||||
service.getLocalServer('hostname', 9999).then(() => {
|
||||
expect(service.create).toHaveBeenCalledWith(expectedServer);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Injectable, EventEmitter } from '@angular/core';
|
||||
import { IndexedDbService } from './indexed-db.service';
|
||||
import { Server } from '../models/server';
|
||||
import { Server, ServerProtocol } from '../models/server';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { HttpServer } from './http-server.service';
|
||||
|
||||
@ -138,7 +138,7 @@ export class ServerService {
|
||||
}
|
||||
|
||||
public getServerUrl(server: Server) {
|
||||
return `http://${server.host}:${server.port}/`;
|
||||
return `${server.protocol}//${server.host}:${server.port}/`;
|
||||
}
|
||||
|
||||
public checkServerVersion(server: Server): Observable<any> {
|
||||
@ -152,6 +152,7 @@ export class ServerService {
|
||||
if (local) {
|
||||
local.host = host;
|
||||
local.port = port;
|
||||
local.protocol = location.protocol as ServerProtocol;
|
||||
this.update(local).then(updated => {
|
||||
resolve(updated);
|
||||
}, reject);
|
||||
@ -161,6 +162,7 @@ export class ServerService {
|
||||
server.host = host;
|
||||
server.port = port;
|
||||
server.location = 'bundled';
|
||||
server.protocol = location.protocol as ServerProtocol;
|
||||
this.create(server).then(created => {
|
||||
resolve(created);
|
||||
}, reject);
|
||||
|
@ -9,7 +9,9 @@ export interface Settings {
|
||||
console_command: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SettingsService {
|
||||
static DEFAULTS: Settings = {
|
||||
crash_reports: true,
|
||||
|
@ -5,5 +5,6 @@ export function getTestServer(): Server {
|
||||
server.host = '127.0.0.1';
|
||||
server.port = 3080;
|
||||
server.authorization = 'none';
|
||||
server.protocol = 'http:';
|
||||
return server;
|
||||
}
|
||||
|
@ -16,10 +16,10 @@ export class VirtualBoxConfigurationService{
|
||||
|
||||
getCategories() {
|
||||
let categories = [["Default", "guest"],
|
||||
["Routers", "routers"],
|
||||
["Switches", "switches"],
|
||||
["End devices", "end_devices"],
|
||||
["Security devices", "security_devices"]];
|
||||
["Routers", "router"],
|
||||
["Switches", "switch"],
|
||||
["End devices", "guest"],
|
||||
["Security devices", "firewall"]];
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
@ -16,10 +16,10 @@ export class VmwareConfigurationService{
|
||||
|
||||
getCategories() {
|
||||
let categories = [["Default", "guest"],
|
||||
["Routers", "routers"],
|
||||
["Switches", "switches"],
|
||||
["End devices", "end_devices"],
|
||||
["Security devices", "security_devices"]];
|
||||
["Routers", "router"],
|
||||
["Switches", "switch"],
|
||||
["End devices", "guest"],
|
||||
["Security devices", "firewall"]];
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
@ -8,10 +8,10 @@ export class VpcsConfigurationService {
|
||||
|
||||
getCategories(){
|
||||
let categories = [["Default", "guest"],
|
||||
["Routers", "routers"],
|
||||
["Switches", "switches"],
|
||||
["End devices", "end_devices"],
|
||||
["Security devices", "security_devices"]];
|
||||
["Routers", "router"],
|
||||
["Switches", "switch"],
|
||||
["End devices", "guest"],
|
||||
["Security devices", "firewall"]];
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user