diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 3691417b..59d1ae19 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -8,6 +8,7 @@ import { ServersComponent } from "./servers/servers.component"; import { ProjectsComponent } from "./projects/projects.component"; import { DefaultLayoutComponent } from "./default-layout/default-layout.component"; import { SettingsComponent } from "./settings/settings.component"; +import { LocalServerComponent } from "./local-server/local-server.component"; const routes: Routes = [ @@ -15,6 +16,7 @@ const routes: Routes = [ children: [ { path: '', redirectTo: 'servers', pathMatch: 'full'}, { path: 'servers', component: ServersComponent }, + { path: 'local', component: LocalServerComponent }, { path: 'server/:server_id/projects', component: ProjectsComponent }, { path: 'settings', component: SettingsComponent }, ] diff --git a/src/app/app.module.ts b/src/app/app.module.ts index dc43ce8c..7c4ba936 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -75,6 +75,7 @@ import { SettingsComponent } from './settings/settings.component'; import { SettingsService } from "./shared/services/settings.service"; import { RavenErrorHandler } from "./raven-error-handler"; +import { LocalServerComponent } from './local-server/local-server.component'; Raven .config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726') @@ -102,6 +103,7 @@ Raven MoveLayerUpActionComponent, ProjectMapShortcutsComponent, SettingsComponent, + LocalServerComponent, ], imports: [ NgbModule.forRoot(), diff --git a/src/app/local-server/local-server.component.html b/src/app/local-server/local-server.component.html new file mode 100644 index 00000000..e69de29b diff --git a/src/app/local-server/local-server.component.scss b/src/app/local-server/local-server.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/local-server/local-server.component.spec.ts b/src/app/local-server/local-server.component.spec.ts new file mode 100644 index 00000000..04c04a2b --- /dev/null +++ b/src/app/local-server/local-server.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LocalServerComponent } from './local-server.component'; + +describe('LocalServerComponent', () => { + let component: LocalServerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ LocalServerComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LocalServerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/local-server/local-server.component.ts b/src/app/local-server/local-server.component.ts new file mode 100644 index 00000000..fd1a8964 --- /dev/null +++ b/src/app/local-server/local-server.component.ts @@ -0,0 +1,27 @@ +import { Component, OnInit } from '@angular/core'; +import { Location } from "@angular/common"; +import { Router } from "@angular/router"; + +import { ServerService } from "../shared/services/server.service"; +import { Server } from "../shared/models/server"; + + +@Component({ + selector: 'app-local-server', + templateUrl: './local-server.component.html', + styleUrls: ['./local-server.component.scss'] +}) +export class LocalServerComponent implements OnInit { + + constructor(private location: Location, + private router: Router, + private serverService: ServerService) { } + + ngOnInit() { + this.serverService.getLocalServer(location.hostname, parseInt(location.port, 10)) + .then((server: Server) => { + this.router.navigate(['/server', server.id, 'projects']); + }); + } + +} diff --git a/src/app/project-map/project-map-shortcuts/project-map-shortcuts.component.spec.ts b/src/app/project-map/project-map-shortcuts/project-map-shortcuts.component.spec.ts index b3d0e43c..2b611a23 100644 --- a/src/app/project-map/project-map-shortcuts/project-map-shortcuts.component.spec.ts +++ b/src/app/project-map/project-map-shortcuts/project-map-shortcuts.component.spec.ts @@ -17,6 +17,9 @@ import { SelectionManager } from "../../cartography/shared/managers/selection-ma import { Server } from "../../shared/models/server"; import { Node } from "../../cartography/shared/models/node"; import { Project } from "../../shared/models/project"; +import { ProjectService } from "../../shared/services/project.service"; +import { MockedProjectService } from "../../shared/services/project.service.spec"; +import { SettingsService } from "../../shared/services/settings.service"; describe('ProjectMapShortcutsComponent', () => { @@ -37,9 +40,11 @@ describe('ProjectMapShortcutsComponent', () => { ], providers: [ HttpServer, - { provide: NodeService, useFactory: () => instance(nodeServiceMock)}, - { provide: HotkeysService, useFactory: () => hotkeyServiceInstanceMock}, - { provide: ToasterService, useClass: MockedToasterService}, + { provide: NodeService, useFactory: () => instance(nodeServiceMock) }, + { provide: HotkeysService, useFactory: () => hotkeyServiceInstanceMock }, + { provide: ToasterService, useClass: MockedToasterService }, + { provide: ProjectService, useClass: MockedProjectService }, + { provide: SettingsService, useClass: SettingsService } ], declarations: [ ProjectMapShortcutsComponent ] }) diff --git a/src/app/project-map/project-map-shortcuts/project-map-shortcuts.component.ts b/src/app/project-map/project-map-shortcuts/project-map-shortcuts.component.ts index 6b64476e..493de4c2 100644 --- a/src/app/project-map/project-map-shortcuts/project-map-shortcuts.component.ts +++ b/src/app/project-map/project-map-shortcuts/project-map-shortcuts.component.ts @@ -6,6 +6,7 @@ import { NodeService } from '../../shared/services/node.service'; import { Server } from '../../shared/models/server'; import { ToasterService } from '../../shared/services/toaster.service'; import { Project } from "../../shared/models/project"; +import { ProjectService } from "../../shared/services/project.service"; @Component({ @@ -23,6 +24,7 @@ export class ProjectMapShortcutsComponent implements OnInit, OnDestroy { private hotkeysService: HotkeysService, private toaster: ToasterService, private nodesService: NodeService, + private projectService: ProjectService ) { } ngOnInit() { @@ -30,8 +32,8 @@ export class ProjectMapShortcutsComponent implements OnInit, OnDestroy { this.hotkeysService.add(this.deleteHotkey); } - onDeleteHandler(event: KeyboardEvent):boolean { - if (!this.project.readonly) { + onDeleteHandler(event: KeyboardEvent): boolean { + if (!this.projectService.isReadOnly(this.project)) { const selectedNodes = this.selectionManager.getSelectedNodes(); if (selectedNodes) { selectedNodes.forEach((node) => { diff --git a/src/app/project-map/project-map.component.html b/src/app/project-map/project-map.component.html index 067bff29..63962683 100644 --- a/src/app/project-map/project-map.component.html +++ b/src/app/project-map/project-map.component.html @@ -33,7 +33,7 @@ - + @@ -45,13 +45,13 @@ - + - + diff --git a/src/app/project-map/project-map.component.ts b/src/app/project-map/project-map.component.ts index c759754d..ead5949d 100644 --- a/src/app/project-map/project-map.component.ts +++ b/src/app/project-map/project-map.component.ts @@ -40,7 +40,7 @@ import { SelectionManager } from "../cartography/shared/managers/selection-manag import { InRectangleHelper } from "../cartography/map/helpers/in-rectangle-helper"; import { DrawingsDataSource } from "../cartography/shared/datasources/drawings-datasource"; import { Subscription } from "rxjs/Subscription"; - +import { SettingsService } from "../shared/services/settings.service"; @Component({ @@ -61,6 +61,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy { private ws: Subject; private drawLineMode = false; private movingMode = false; + private readonly = false; protected selectionManager: SelectionManager; @@ -85,6 +86,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy { private progressDialogService: ProgressDialogService, private toaster: ToasterService, private projectWebServiceHandler: ProjectWebServiceHandler, + private settingsService: SettingsService, protected nodesDataSource: NodesDataSource, protected linksDataSource: LinksDataSource, protected drawingsDataSource: DrawingsDataSource, @@ -104,7 +106,6 @@ export class ProjectMapComponent implements OnInit, OnDestroy { .flatMap((server: Server) => { this.server = server; return this.projectService.get(server, paramMap.get('project_id')).map((project) => { - project.readonly = true; return project; }); }) @@ -162,6 +163,8 @@ export class ProjectMapComponent implements OnInit, OnDestroy { } onProjectLoad(project: Project) { + this.readonly = this.projectService.isReadOnly(project); + const subscription = this.symbolService .load(this.server) .flatMap(() => { @@ -195,10 +198,11 @@ export class ProjectMapComponent implements OnInit, OnDestroy { } setUpMapCallbacks(project: Project) { - if (this.project.readonly) { + if (this.readonly) { this.mapChild.graphLayout.getSelectionTool().deactivate(); } - this.mapChild.graphLayout.getNodesWidget().setDraggingEnabled(!this.project.readonly); + + this.mapChild.graphLayout.getNodesWidget().setDraggingEnabled(!this.readonly); this.mapChild.graphLayout.getNodesWidget().setOnContextMenuCallback((event: any, node: Node) => { this.nodeContextMenu.open(node, event.clientY, event.clientX); @@ -280,18 +284,19 @@ export class ProjectMapComponent implements OnInit, OnDestroy { public toggleMovingMode() { this.movingMode = !this.movingMode; if (this.movingMode) { - if (!this.project.readonly) { + if (!this.readonly) { this.mapChild.graphLayout.getSelectionTool().deactivate(); } this.mapChild.graphLayout.getMovingTool().activate(); } else { this.mapChild.graphLayout.getMovingTool().deactivate(); - if (!this.project.readonly) { + if (!this.readonly) { this.mapChild.graphLayout.getSelectionTool().activate(); } } } + public onChooseInterface(event) { const node: Node = event.node; const port: Port = event.port; diff --git a/src/app/projects/projects.component.ts b/src/app/projects/projects.component.ts index f66f200c..5c9a3711 100644 --- a/src/app/projects/projects.component.ts +++ b/src/app/projects/projects.component.ts @@ -46,7 +46,7 @@ export class ProjectsComponent implements OnInit { this.route.paramMap .switchMap((params: ParamMap) => { const server_id = params.get('server_id'); - return this.serverService.getLocalOrRemote(server_id); + return this.serverService.get(parseInt(server_id, 10)); }) .subscribe((server: Server) => { this.server = server; diff --git a/src/app/settings/settings.component.spec.ts b/src/app/settings/settings.component.spec.ts index c5a151e6..feaf3b22 100644 --- a/src/app/settings/settings.component.spec.ts +++ b/src/app/settings/settings.component.spec.ts @@ -6,6 +6,7 @@ import { FormsModule } from "@angular/forms"; import { SettingsService } from "../shared/services/settings.service"; import { PersistenceModule } from "angular-persistence"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { MockedToasterService, ToasterService } from "../shared/services/toaster.service"; describe('SettingsComponent', () => { let component: SettingsComponent; @@ -17,7 +18,10 @@ describe('SettingsComponent', () => { imports: [ MatExpansionModule, MatCheckboxModule, FormsModule, PersistenceModule, BrowserAnimationsModule ], - providers: [ SettingsService ], + providers: [ + SettingsService, + { provide: ToasterService, useClass: MockedToasterService } + ], declarations: [ SettingsComponent ] }) .compileComponents(); @@ -37,7 +41,8 @@ describe('SettingsComponent', () => { it('should get and save new settings', () => { const settings = { - 'crash_reports': true + 'crash_reports': true, + 'experimental_features': true }; const getAll = spyOn(settingsService, 'getAll').and.returnValue(settings); const setAll = spyOn(settingsService, 'setAll'); diff --git a/src/app/shared/models/server.ts b/src/app/shared/models/server.ts index e3a381a0..fcac8277 100644 --- a/src/app/shared/models/server.ts +++ b/src/app/shared/models/server.ts @@ -8,4 +8,5 @@ export class Server { authorization: ServerAuthorization; login: string; password: string; + is_local: boolean; } diff --git a/src/app/shared/node-context-menu/node-context-menu.component.html b/src/app/shared/node-context-menu/node-context-menu.component.html index bd0a086b..cd50b5f6 100644 --- a/src/app/shared/node-context-menu/node-context-menu.component.html +++ b/src/app/shared/node-context-menu/node-context-menu.component.html @@ -3,7 +3,7 @@ - - + + diff --git a/src/app/shared/node-context-menu/node-context-menu.component.ts b/src/app/shared/node-context-menu/node-context-menu.component.ts index 1fda55c5..4f4ad8ea 100644 --- a/src/app/shared/node-context-menu/node-context-menu.component.ts +++ b/src/app/shared/node-context-menu/node-context-menu.component.ts @@ -4,6 +4,7 @@ import { DomSanitizer } from "@angular/platform-browser"; import { Node } from "../../cartography/shared/models/node"; import { Server } from "../models/server"; import { Project } from "../models/project"; +import { ProjectService } from "../services/project.service"; @Component({ @@ -23,7 +24,8 @@ export class NodeContextMenuComponent implements OnInit { constructor( private sanitizer: DomSanitizer, - private changeDetector: ChangeDetectorRef) {} + private changeDetector: ChangeDetectorRef, + protected projectService: ProjectService) {} ngOnInit() { this.setPosition(0, 0); diff --git a/src/app/shared/services/http-server.service.spec.ts b/src/app/shared/services/http-server.service.spec.ts index 4ce363fc..0034dd70 100644 --- a/src/app/shared/services/http-server.service.spec.ts +++ b/src/app/shared/services/http-server.service.spec.ts @@ -176,4 +176,17 @@ describe('HttpServer', () => { expect(req.request.headers.get('Authorization')).toEqual('Basic bG9naW46cGFzc3dvcmQ='); expect(req.request.headers.get('CustomHeader')).toEqual('value'); }); + + it('should make local call when ip and port is not defined', () => { + server.ip = null; + server.port = null; + + service.get(server, '/test', { + headers: { + 'CustomHeader': 'value' + } + }).subscribe(); + + const req = httpTestingController.expectOne('/v2/test'); + }); }); diff --git a/src/app/shared/services/http-server.service.ts b/src/app/shared/services/http-server.service.ts index d9aee35d..56a22919 100644 --- a/src/app/shared/services/http-server.service.ts +++ b/src/app/shared/services/http-server.service.ts @@ -112,7 +112,11 @@ export class HttpServer { } private getOptionsForServer(server: Server, url: string, options: T) { - url = `http://${server.ip}:${server.port}/v2${url}`; + if (server.ip && server.port) { + url = `http://${server.ip}:${server.port}/v2${url}`; + } else { + url = `/v2${url}`; + } if (!options.headers) { options.headers = {}; diff --git a/src/app/shared/services/project.service.spec.ts b/src/app/shared/services/project.service.spec.ts index 60f47ffd..f9ba5236 100644 --- a/src/app/shared/services/project.service.spec.ts +++ b/src/app/shared/services/project.service.spec.ts @@ -4,12 +4,21 @@ import { HttpClient } from '@angular/common/http'; import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; import { HttpServer } from './http-server.service'; import { Server } from '../models/server'; -import { Node } from '../../cartography/shared/models/node'; -import { Port } from '../models/port'; import { getTestServer } from './testing'; -import { Appliance } from '../models/appliance'; -import { Project } from '../models/project'; import { ProjectService } from './project.service'; +import { SettingsService } from "./settings.service"; +import { MockedSettingsService } from "./settings.service.spec"; + + +/** + * Mocks ProjectsService so it's not based on settings + */ +export class MockedProjectService { + isReadOnly(project) { + return project.readonly; + } +} + describe('ProjectService', () => { let httpClient: HttpClient; @@ -25,7 +34,8 @@ describe('ProjectService', () => { ], providers: [ HttpServer, - ProjectService + ProjectService, + { provide: SettingsService, useClass: MockedSettingsService } ] }); @@ -105,4 +115,5 @@ describe('ProjectService', () => { const path = service.notificationsPath(server, "myproject"); expect(path).toEqual('ws://127.0.0.1:3080/v2/projects/myproject/notifications/ws') })); + }); diff --git a/src/app/shared/services/project.service.ts b/src/app/shared/services/project.service.ts index c8f83440..c3ef1f0c 100644 --- a/src/app/shared/services/project.service.ts +++ b/src/app/shared/services/project.service.ts @@ -8,11 +8,13 @@ import { Link } from "../../cartography/shared/models/link"; import { Server } from "../models/server"; import { HttpServer } from "./http-server.service"; import {Drawing} from "../../cartography/shared/models/drawing"; +import { SettingsService } from "./settings.service"; @Injectable() export class ProjectService { - constructor(private httpServer: HttpServer) { } + constructor(private httpServer: HttpServer, + private settingsService: SettingsService) { } get(server: Server, project_id: string) { return this.httpServer @@ -52,4 +54,11 @@ export class ProjectService { notificationsPath(server: Server, project_id: string): string { return `ws://${server.ip}:${server.port}/v2/projects/${project_id}/notifications/ws`; } + + isReadOnly(project: Project) { + if (project.readonly) { + return project.readonly; + } + return !this.settingsService.isExperimentalEnabled(); + } } diff --git a/src/app/shared/services/server.service.ts b/src/app/shared/services/server.service.ts index 0f4c792b..f9ad00ae 100644 --- a/src/app/shared/services/server.service.ts +++ b/src/app/shared/services/server.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import {IndexedDbService} from "./indexed-db.service"; import {Server} from "../models/server"; +import { Observable } from "rxjs/Observable"; @Injectable() @@ -21,13 +22,6 @@ export class ServerService { this.indexedDbService.get().getByKey(this.tablename, id)); } - public getLocalOrRemote(id: string) { - if (id === 'local') { - - } - return this.get(parseInt(id, 10)); - } - public create(server: Server) { return this.onReady(() => { const promise = new Promise((resolve, reject) => { @@ -40,6 +34,17 @@ export class ServerService { }); } + public update(server: Server) { + return this.onReady(() => { + const promise = new Promise((resolve, reject) => { + this.indexedDbService.get().update(this.tablename, server).then((updated) => { + resolve(server); + }, reject); + }); + return promise; + }); + } + public findAll() { return this.onReady(() => this.indexedDbService.get().getAll(this.tablename)); @@ -50,6 +55,32 @@ export class ServerService { this.indexedDbService.get().delete(this.tablename, server.id)); } + public getLocalServer(ip: string, port: number) { + const promise = new Promise((resolve, reject) => { + this.findAll().then((servers: Server[]) => { + const local = servers.find((server) => server.is_local); + if (local) { + local.ip = ip; + local.port = port; + this.update(local).then((updated) => { + resolve(updated); + }, reject); + } else { + const server = new Server(); + server.name = 'local'; + server.ip = ip; + server.port = port; + server.is_local = true; + this.create(server).then((created) => { + resolve(created); + }, reject); + } + }, reject); + }); + + return promise; + } + private onReady(query) { const promise = new Promise((resolve, reject) => { this.ready.then(() => { diff --git a/src/app/shared/services/settings.service.spec.ts b/src/app/shared/services/settings.service.spec.ts index 6a32066c..7f5650cf 100644 --- a/src/app/shared/services/settings.service.spec.ts +++ b/src/app/shared/services/settings.service.spec.ts @@ -5,6 +5,13 @@ import { Settings, SettingsService } from './settings.service'; import createSpyObj = jasmine.createSpyObj; +export class MockedSettingsService { + isExperimentalEnabled() { + return true; + } +} + + describe('SettingsService', () => { let persistenceService: PersistenceService; @@ -45,7 +52,8 @@ describe('SettingsService', () => { it('should get all values', inject([SettingsService], (service: SettingsService) => { expect(service.getAll()).toEqual({ - 'crash_reports': true + 'crash_reports': true, + 'experimental_features': false }); })); @@ -53,10 +61,11 @@ describe('SettingsService', () => { const settings = { 'crash_reports': false }; - service.setAll(settings) + service.setAll(settings); expect(service.getAll()).toEqual({ - 'crash_reports': false + 'crash_reports': false, + 'experimental_features': false }); })); diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 3612073b..cbe10f98 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,3 +1,5 @@ export const environment = { - production: true + production: true, + electron: false, + githubio: false };