Merge branch 'master-3.0' into Add-navigation-to-user-management-page

This commit is contained in:
VUAGNAT Benoit DSCS/DSEC 2021-06-02 18:28:08 +02:00
commit 717336fbd1
84 changed files with 4151 additions and 6031 deletions

View File

@ -18,7 +18,6 @@
"css-tree",
"save-svg-as-png",
"angular-draggable-droppable",
"angular2-hotkeys",
"dom-set",
"dom-plane",
"mousetrap",
@ -26,13 +25,9 @@
"rxjs/Rx",
"rxjs/add/operator/map",
"rxjs-compat/add/operator/map",
"angular-react-core.js",
"react",
"react-dom",
"classnames",
"stylenames"
],
"aot": true,
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
@ -49,7 +44,13 @@
"src/styles.scss",
"src/theme.scss"
],
"scripts": []
"scripts": [],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true
},
"configurations": {
"production": {
@ -67,7 +68,6 @@
"styles": false
},
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
@ -89,7 +89,6 @@
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
@ -125,7 +124,6 @@
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,

View File

@ -41,39 +41,32 @@
},
"private": true,
"dependencies": {
"@angular-react/core": "^3.0.0",
"@angular-react/fabric": "^3.0.0",
"@angular/animations": "^11.2.13",
"@angular/cdk": "^11.2.12",
"@angular/common": "^11.2.13",
"@angular/compiler": "^11.2.13",
"@angular/core": "^11.2.13",
"@angular/forms": "^11.2.13",
"@angular/http": "^7.2.16",
"@angular/material": "^11.2.12",
"@angular/platform-browser": "^11.2.13",
"@angular/platform-browser-dynamic": "^11.2.13",
"@angular/router": "^11.2.13",
"@angular/animations": "^12.0.2",
"@angular/cdk": "^12.0.2",
"@angular/common": "^12.0.2",
"@angular/compiler": "^12.0.2",
"@angular/core": "^12.0.2",
"@angular/forms": "^12.0.2",
"@angular/material": "^12.0.2",
"@angular/platform-browser": "^12.0.2",
"@angular/platform-browser-dynamic": "^12.0.2",
"@angular/router": "^12.0.2",
"@sentry/browser": "^6.3.6",
"@types/jest": "^26.0.23",
"@types/mocha": "^8.2.2",
"@types/react": "^17.0.5",
"@types/react-dom": "^17.0.3",
"angular-draggable-droppable": "^4.6.0",
"angular-persistence": "^1.0.1",
"angular-resizable-element": "^3.3.5",
"angular2-draggable": "^2.3.2",
"angular2-hotkeys": "^2.2.0",
"angular2-indexeddb": "^1.2.3",
"bootstrap": "^5.0.0",
"command-exists": "^1.2.9",
"core-js": "^3.12.1",
"d3-ng2-service": "^2.2.0",
"eev": "^0.1.5",
"file-saver": "^2.0.5",
"ini": "^2.0.0",
"marked": "^2.0.3",
"material-design-icons": "^3.0.1",
"mousetrap": "^1.6.5",
"ng-circle-progress": "^1.6.0",
"ng2-file-upload": "^1.4.0",
"ngx-childprocess": "^0.0.6",
@ -81,16 +74,10 @@
"ngx-electron": "^2.2.0",
"node-fetch": "^2.6.1",
"notosans-fontface": "1.2.2",
"office-ui-fabric-react": "^7.170.0",
"prettier-plugin-organize-imports": "^2.0.0",
"react": "^17.0.2",
"react-bootstrap": "^1.5.2",
"react-dom": "^17.0.2",
"rxjs": "^6.6.7",
"rxjs-compat": "^6.6.7",
"save-html-as-image": "^1.5.2",
"rxjs": "^6.5.3",
"rxjs-compat": "^6.5.3",
"save-svg-as-png": "^1.4.17",
"schematics-scss-migrate": "^1.3.13",
"snyk": "^1.589.0",
"spark-md5": "^3.0.1",
"svg-crowbar": "^0.6.5",
@ -101,21 +88,21 @@
"xterm-addon-attach": "^0.6.0",
"xterm-addon-fit": "^0.5.0",
"yargs": "^17.0.1",
"zone.js": "^0.11.4"
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^0.1102.12",
"@angular/cli": "^11.2.12",
"@angular/compiler-cli": "^11.2.13",
"@angular/language-service": "^11.2.13",
"@angular-devkit/build-angular": "^12.0.2",
"@angular/cli": "^12.0.2",
"@angular/compiler-cli": "^12.0.2",
"@angular/language-service": "^12.0.2",
"@sentry/cli": "^1.64.2",
"@sentry/electron": "^2.4.1",
"@types/jasmine": "^3.7.1",
"@types/jasminewd2": "^2.0.9",
"@types/node": "15.0.2",
"@types/node": "15.6.1",
"codelyzer": "^6.0.2",
"electron": "^12.0.7",
"electron-builder": "22.11.1",
"electron": "^13.0.1",
"electron-builder": "22.10.5",
"file-loader": "^6.2.0",
"jasmine-core": "~3.7.1",
"jasmine-spec-reporter": "~7.0.0",
@ -133,11 +120,11 @@
"replace": "^1.2.1",
"rxjs-tslint": "^0.1.8",
"ts-mockito": "^2.6.1",
"ts-node": "~9.1.1",
"ts-node": "~10.0.0",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
"typescript": "4.0.2",
"webpack": "5.36.2",
"typescript": "4.2.4",
"webpack": "5.38.0",
"yarn-upgrade-all": "^0.5.4"
},
"greenkeeper": {
@ -146,4 +133,4 @@
]
},
"snyk": true
}
}

View File

@ -3,7 +3,6 @@ import { RouterModule, Routes } from '@angular/router';
import { BundledServerFinderComponent } from './components/bundled-server-finder/bundled-server-finder.component';
import { DirectLinkComponent } from './components/direct-link/direct-link.component';
import { HelpComponent } from './components/help/help.component';
import { ReportIssueComponent } from './components/help/report-issue/report-issue.component';
import { InstalledSoftwareComponent } from './components/installed-software/installed-software.component';
import { LoginComponent } from './components/login/login.component';
import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component';
@ -57,6 +56,7 @@ import { LoginGuard } from './guards/login-guard';
import { DefaultLayoutComponent } from './layouts/default-layout/default-layout.component';
import { ServerResolve } from './resolvers/server-resolve';
import { UserManagementComponent } from './components/user-management/user-management.component';
import { LoggedUserComponent } from './components/users/logged-user/logged-user.component';
const routes: Routes = [
{
@ -67,6 +67,7 @@ const routes: Routes = [
{ path: 'servers', component: ServersComponent },
{ path: 'bundled', component: BundledServerFinderComponent },
{ path: 'server/:server_id/login', component: LoginComponent },
{ path: 'server/:server_id/loggeduser', component: LoggedUserComponent },
{
path: 'server/:server_id/projects',
component: ProjectsComponent,
@ -74,7 +75,6 @@ const routes: Routes = [
resolve: { server: ServerResolve },
},
{ path: 'help', component: HelpComponent },
{ path: 'help/reportissue', component: ReportIssueComponent },
{ path: 'settings', component: SettingsComponent },
{ path: 'settings/console', component: ConsoleComponent },
{ path: 'installed-software', component: InstalledSoftwareComponent },

View File

@ -2,7 +2,6 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MatIconModule } from '@angular/material/icon';
import { RouterTestingModule } from '@angular/router/testing';
import { PersistenceService } from 'angular-persistence';
import { ElectronService, NgxElectronModule } from 'ngx-electron';
import { AppComponent } from './app.component';
import { ProgressService } from './common/progress/progress.service';
@ -21,12 +20,12 @@ describe('AppComponent', () => {
TestBed.configureTestingModule({
declarations: [AppComponent],
imports: [RouterTestingModule, MatIconModule, NgxElectronModule],
providers: [SettingsService, PersistenceService, ProgressService],
providers: [SettingsService, ProgressService],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
electronService = TestBed.get(ElectronService);
settingsService = TestBed.get(SettingsService);
electronService = TestBed.inject(ElectronService);
settingsService = TestBed.inject(SettingsService);
}));
beforeEach(() => {
@ -46,23 +45,18 @@ describe('AppComponent', () => {
}));
it('should receive changed settings and forward to electron', async(() => {
const spy = createSpyObj('Electron.IpcRenderer', ['send']);
spyOnProperty(electronService, 'isElectronApp').and.returnValue(true);
spyOnProperty(electronService, 'ipcRenderer').and.returnValue(spy);
settingsService.set('crash_reports', true);
settingsService.setReportsSettings(true);
component.ngOnInit();
settingsService.set('crash_reports', false);
expect(spy.send).toHaveBeenCalled();
expect(spy.send.calls.mostRecent().args[0]).toEqual('settings.changed');
expect(spy.send.calls.mostRecent().args[1].crash_reports).toEqual(false);
settingsService.setReportsSettings(false);
}));
it('should receive changed settings and do not forward to electron', async(() => {
const spy = createSpyObj('Electron.IpcRenderer', ['send']);
spyOnProperty(electronService, 'isElectronApp').and.returnValue(false);
settingsService.set('crash_reports', true);
settingsService.setReportsSettings(true);
component.ngOnInit();
settingsService.set('crash_reports', false);
settingsService.setReportsSettings(false);
expect(spy.send).not.toHaveBeenCalled();
}));
});

View File

@ -37,12 +37,6 @@ export class AppComponent implements OnInit {
@HostBinding('class') componentCssClass;
ngOnInit(): void {
if (this.electronService.isElectronApp) {
this.settingsService.subscribe((settings) => {
this.electronService.ipcRenderer.send('settings.changed', settings);
});
}
this.applyTheme(this.themeService.savedTheme + '-theme');
this.themeService.themeChanged.subscribe((event: string) => {
this.applyTheme(event);

View File

@ -1,4 +1,3 @@
import { AngularReactBrowserModule } from '@angular-react/core';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { OverlayModule } from '@angular/cdk/overlay';
import { CdkTableModule } from '@angular/cdk/table';
@ -9,7 +8,6 @@ import { MatSidenavModule } from '@angular/material/sidenav';
import { BrowserModule, Title } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { DragAndDropModule } from 'angular-draggable-droppable';
import { PersistenceModule } from 'angular-persistence';
import { ResizableModule } from 'angular-resizable-element';
import { D3Service } from 'd3-ng2-service';
import { NgCircleProgressModule } from 'ng-circle-progress';
@ -44,7 +42,6 @@ import { NodeLabelDraggedComponent } from './components/drawings-listeners/node-
import { TextAddedComponent } from './components/drawings-listeners/text-added/text-added.component';
import { TextEditedComponent } from './components/drawings-listeners/text-edited/text-edited.component';
import { HelpComponent } from './components/help/help.component';
import { ReportIssueComponent } from './components/help/report-issue/report-issue.component';
import { InstallSoftwareComponent } from './components/installed-software/install-software/install-software.component';
import { InstalledSoftwareComponent } from './components/installed-software/installed-software.component';
import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component';
@ -204,6 +201,7 @@ import { TemplateComponent } from './components/template/template.component';
import { TopologySummaryComponent } from './components/topology-summary/topology-summary.component';
import { WebConsoleFullWindowComponent } from './components/web-console-full-window/web-console-full-window.component';
import { DataSourceFilter } from './filters/dataSourceFilter';
import { AuthImageFilter } from './filters/authImageFilter';
import { DateFilter } from './filters/dateFilter.pipe';
import { NameFilter } from './filters/nameFilter.pipe';
import { ProjectsFilter } from './filters/projectsFilter.pipe';
@ -226,7 +224,6 @@ import { ExternalSoftwareDefinitionService } from './services/external-software-
import { Gns3vmService } from './services/gns3vm.service';
import { GoogleAnalyticsService } from './services/google-analytics.service';
import { HttpServer, ServerErrorHandler } from './services/http-server.service';
import { IndexedDbService } from './services/indexed-db.service';
import { InfoService } from './services/info.service';
import { InstalledSoftwareService } from './services/installed-software.service';
import { IosConfigurationService } from './services/ios-configuration.service';
@ -274,10 +271,13 @@ import { LoginComponent } from './components/login/login.component';
import { LoginService } from './services/login.service';
import { HttpRequestsInterceptor } from './interceptors/http.interceptor';
import { UserManagementComponent } from './components/user-management/user-management.component'
import { UserService } from './services/user.service';
import { LoggedUserComponent } from './components/users/logged-user/logged-user.component';
@NgModule({
declarations: [
AppComponent,
LoggedUserComponent,
ProjectMapComponent,
LoginComponent,
ServersComponent,
@ -382,6 +382,7 @@ import { UserManagementComponent } from './components/user-management/user-manag
DataSourceFilter,
TemplateFilter,
ProjectsFilter,
AuthImageFilter,
ListOfSnapshotsComponent,
CustomAdaptersComponent,
NodesMenuComponent,
@ -458,11 +459,10 @@ import { UserManagementComponent } from './components/user-management/user-manag
TemplateNameDialogComponent,
ConfigureCustomAdaptersDialogComponent,
EditNetworkConfigurationDialogComponent,
ReportIssueComponent,
UserManagementComponent
UserManagementComponent,
ProjectReadmeComponent
],
imports: [
AngularReactBrowserModule,
BrowserModule,
HttpClientModule,
AppRoutingModule,
@ -471,7 +471,6 @@ import { UserManagementComponent } from './components/user-management/user-manag
BrowserAnimationsModule,
CdkTableModule,
CartographyModule,
PersistenceModule,
NgxElectronModule,
FileUploadModule,
MatSidenavModule,
@ -496,7 +495,6 @@ import { UserManagementComponent } from './components/user-management/user-manag
NodeService,
LinkService,
DrawingService,
IndexedDbService,
HttpServer,
SnapshotService,
ProgressDialogService,
@ -558,7 +556,8 @@ import { UserManagementComponent } from './components/user-management/user-manag
Title,
ApplianceService,
UpdatesService,
LoginService
LoginService,
UserService
],
entryComponents: [
AddServerDialogComponent,

View File

@ -26,9 +26,7 @@ export class DrawingComponent implements OnInit {
ngOnInit() {
try {
this.drawing.element = this.svgToDrawingConverter.convert(this.drawing.svg);
} catch (error) {
console.log(`Cannot convert due to Error: '${error}'`);
}
} catch (error) {}
}
OnDragging(evt) {

View File

@ -44,9 +44,7 @@ export class DrawingsWidget implements Widget {
layer.drawings.forEach((d: MapDrawing) => {
try {
d.element = this.svgToDrawingConverter.convert(d.svg);
} catch (error) {
console.log(`Cannot convert due to Error: '${error}'`);
}
} catch (error) {}
});
return layer.drawings;
},
@ -81,9 +79,7 @@ export class DrawingsWidget implements Widget {
.on('start', (datum: MapDrawing) => {
document.body.style.cursor = 'ns-resize';
topEdge = datum.y;
console.log('started');
y = event.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
// startEvent = event;
})
.on('drag', (datum: MapDrawing) => {
const evt = event;
@ -91,55 +87,10 @@ export class DrawingsWidget implements Widget {
y = event.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
let height = datum.element.height - dy;
if (height < 0) {
// height = datum.y - startEvent.y;
datum.y += height;
height = topEdge - datum.y;
// console.log(topEdge - datum.y);
}
console.log('Height', height);
datum.element.height = height;
// datum.element.height -= dy;
// if(datum.element.height < 0) {
// datum.y -= datum.element.height;
// datum.element.height = Math.abs(datum.element.height);
// }
// if (!isReflectedVertical) {
// if ((datum.element.height + evt.dy) < 0) {
// isReflectedVertical = true;
// y = topEdge;
// console.log(y);
// datum.element.height = Math.abs(datum.element.height + evt.dy);
// console.log(datum.element.height);
// } else {
// datum.element.height += evt.dy;
// if (datum.element instanceof EllipseElement){
// (datum.element as EllipseElement).cy = (datum.element as EllipseElement).cy + evt.dy/2 < 0 ? 1 : (datum.element as EllipseElement).cy += evt.dy/2;
// (datum.element as EllipseElement).ry = (datum.element as EllipseElement).ry + evt.dy/2 < 0 ? 1 : (datum.element as EllipseElement).ry += evt.dy/2;
// }
// }
// } else {
// dy = y - (evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y);
// y = evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
// if ((datum.element.height + dy) < 0){
// isReflectedVertical = false;
// y = topEdge;
// console.log(y);
// datum.element.height = Math.abs(datum.element.height + evt.dy);
// console.log(datum.element.height);
// } else {
// datum.y = evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
// datum.element.height += dy;
// if (datum.element instanceof EllipseElement) {
// (datum.element as EllipseElement).cy = (datum.element as EllipseElement).cy + dy/2 < 0 ? 1 : (datum.element as EllipseElement).cy += dy/2;
// (datum.element as EllipseElement).ry = (datum.element as EllipseElement).ry + dy/2 < 0 ? 1 : (datum.element as EllipseElement).ry += dy/2;
// }
// }
// }
this.redrawDrawing(view, datum);
})
.on('end', (datum: MapDrawing) => {

View File

@ -46,9 +46,7 @@ export class DrawingsWidget implements Widget {
layer.drawings.forEach((d: MapDrawing) => {
try {
d.element = this.svgToDrawingConverter.convert(d.svg);
} catch (error) {
console.log(`Cannot convert due to Error: '${error}'`);
}
} catch (error) {}
});
return layer.drawings;
},

View File

@ -1,4 +1,5 @@
import { EventEmitter, Injectable } from '@angular/core';
import { SymbolService } from '../../services/symbol.service';
import { event, select } from 'd3-selection';
import { MapSettingsService } from '../../services/mapsettings.service';
import { ClickedDataEvent } from '../events/event-source';
@ -22,7 +23,8 @@ export class NodeWidget implements Widget {
private selectionManager: SelectionManager,
private labelWidget: LabelWidget,
private nodesEventSource: NodesEventSource,
private mapSettingsService: MapSettingsService
private mapSettingsService: MapSettingsService,
private symbolService: SymbolService
) {}
public draw(view: SVGSelection) {
@ -88,7 +90,13 @@ export class NodeWidget implements Widget {
event.preventDefault();
self.onContextConsoleMenu.emit(new NodeContextMenu(event, n));
})
.attr('xnode:href', (n: MapNode) => n.symbolUrl)
.attr('xnode:href', (n: MapNode) => {
let symbol = this.symbolService.get(n.symbol.split('/')[2]);
if (symbol) {
return 'data:image/svg+xml;base64,' + btoa(symbol.raw);
}
return '';
})
.attr('width', (n: MapNode) => {
if (!n.width) return 60;
return n.width;

View File

@ -1,7 +1,6 @@
import { Injector } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { SettingsService } from '../../services/settings.service';
import { MockedSettingsService } from '../../services/settings.service.spec';
import { ToasterService } from '../../services/toaster.service';
import { MockedToasterService } from '../../services/toaster.service.spec';
import { SentryErrorHandler } from './sentry-error-handler';
@ -17,19 +16,21 @@ class MockedToasterErrorHandler extends ToasterErrorHandler {
describe('ToasterErrorHandler', () => {
let handler: ToasterErrorHandler;
let toasterService: MockedToasterService;
let settingsService: SettingsService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{ provide: ToasterService, useClass: MockedToasterService },
{ provide: SettingsService, useClass: MockedSettingsService },
{ provide: SettingsService},
SentryErrorHandler,
ToasterErrorHandler,
],
});
handler = new MockedToasterErrorHandler(TestBed.get(Injector));
handler = new MockedToasterErrorHandler(TestBed.inject(Injector));
toasterService = TestBed.get(ToasterService);
settingsService = TestBed.inject(SettingsService);
});
it('should call toaster with error message', () => {

View File

@ -21,7 +21,15 @@ export class BundledServerFinderComponent implements OnInit {
ngOnInit() {
this.progressService.activate();
setTimeout(() => {
let port = parseInt(this.document.location.port, 10) ? parseInt(this.document.location.port, 10) : 80;
let port;
if (parseInt(this.document.location.port, 10)) {
port = parseInt(this.document.location.port, 10);
} else if (this.document.location.protocol == "https:") {
port = 443;
} else {
port = 80;
}
this.serverService.getLocalServer(this.document.location.hostname, port).then((server: Server) => {
this.progressService.deactivate();

View File

@ -59,7 +59,6 @@ export class DirectLinkComponent implements OnInit {
const servers = await this.serverService.findAll();
const server = servers.filter((server) => server.host === this.serverIp && server.port === this.serverPort)[0];
console.log(servers);
if (server) {
this.router.navigate(['/server', server.id, 'project', this.projectId]);
} else {

View File

@ -31,9 +31,6 @@
</mat-expansion-panel>
</mat-accordion>
</div>
<button mat-button class="full-width">
<a href="https://docs.gns3.com/docs/"> Go to documentation </a>
</button>
<button mat-button routerLink="/help/reportissue" class="full-width">Report an issue</button>
<button mat-button color="primary" class="full-width" (click)="goToDocumentation()">Go to documentation</button>
</div>
</div>

View File

@ -1,4 +1,4 @@
.full-width {
width: 50%;
width: 100%;
margin-top: 20px;
}

View File

@ -28,4 +28,8 @@ export class HelpComponent implements OnInit {
this.releasenotes = data.replace(new RegExp('\n', 'g'), '<br />');
});
}
goToDocumentation() {
window.location.href = "https://docs.gns3.com/docs/";
}
}

View File

@ -1,24 +0,0 @@
import React, {Component} from "react";
import Form from 'react-bootstrap/Form';
const formGroupStyle = {
margin: '20px'
};
class FilterComponent extends Component<any, any> {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Form.Group style={formGroupStyle}>
<Form.Control size="lg" type="text" placeholder="Search by keyword" value={this.props.filter} onChange={this.props.handleChange} />
</Form.Group>
</div>
)
}
}
export default FilterComponent;

View File

@ -1,95 +0,0 @@
import React, { Component } from "react";
import Card from 'react-bootstrap/Card';
import Spinner from 'react-bootstrap/Spinner';
import IssueComponent from './issue';
import FilterComponent from './filter';
import e from '../../../event-bus';
const wrapperStyle = {
justifyContent: 'center' as 'center',
display: 'flex',
flex: 1,
flexDirection: 'row' as 'row',
flexWrap: 'wrap' as 'wrap',
margin: '20px'
};
const cardStyle = {
width: '300px',
margin: '20px'
};
const cardTitleStyle = {
color: 'red'
};
const message = 'Thank you for reporting the issue!';
const apiUrl = 'https://api.github.com/repos/GNS3/gns3-web-ui/issues';
const newIssueLink = 'https://github.com/GNS3/gns3-web-ui/issues/new';
class IssueListComponent extends Component<any, any> {
constructor(props) {
super(props);
this.state = {
issues: [],
filteredIssues: [],
isFetching: true
};
}
componentDidMount() {
fetch(apiUrl)
.then(response => response.json())
.then(data => this.setState({
issues: data,
filteredIssues: data,
isFetching: false
}));
}
handleChange = (event) => {
let filter = event.target.value;
let filteredIssues = this.state.issues;
filteredIssues = filteredIssues.filter((issue) => {
return issue.title.toLowerCase().includes(filter.toLowerCase())
});
this.setState({
filteredIssues: filteredIssues
});
}
newIssueOpened = (event) => {
e.emit('message', { text: message });
}
render() {
return (
<div>
<div>
<FilterComponent handleChange={this.handleChange} filter={this.state.filter} />
</div>
{this.state.isFetching ? (
<div style={wrapperStyle}>
<Spinner animation="grow" />
</div>
) : (
<div style={wrapperStyle}>
{this.state.filteredIssues.map(issue =>
<IssueComponent key={issue.html_url} data={issue}/>
)}
<Card style={cardStyle}>
<Card.Body>
<Card.Title style={cardTitleStyle}>Don't see your issue here?</Card.Title>
<Card.Link onClick={this.newIssueOpened} href={newIssueLink} target = "_blank">Open new issue</Card.Link>
</Card.Body>
</Card>
</div>
)}
</div>
);
}
};
export default IssueListComponent;

View File

@ -1,42 +0,0 @@
import React, { Component } from "react";
import Card from 'react-bootstrap/Card';
const cardStyle = {
width: '300px',
margin: '20px'
};
const cardTitleStyle = {
color: 'black'
};
const cardTextStyle = {
color: 'black',
overflow: 'auto',
height: '100px'
};
class IssueComponent extends Component<any, any> {
constructor(props) {
super(props);
this.state = {
data: this.props.data
}
}
render() {
const issue = this.state.data;
return (
<Card key={issue.html_url} style={cardStyle}>
<Card.Body>
<Card.Title style={cardTitleStyle}>{issue.title}</Card.Title>
<Card.Subtitle className="mb-2 text-muted">Status: {issue.state}</Card.Subtitle>
<Card.Text style={cardTextStyle}>{issue.body}</Card.Text>
<Card.Link href={issue.html_url} target = "_blank">{issue.html_url}</Card.Link>
</Card.Body>
</Card>
);
}
};
export default IssueComponent;

View File

@ -1 +0,0 @@
<span #issueListComponentContainer></span>

View File

@ -1,52 +0,0 @@
import { HttpClient } from '@angular/common/http';
import {
AfterViewInit,
Component,
ElementRef,
EventEmitter,
Input,
OnInit,
OnChanges,
OnDestroy,
Output,
SimpleChanges,
ViewChild,
ViewEncapsulation,
AfterContentInit
} from '@angular/core';
import IssueListComponent from '../report-issue/issue-list';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import e from '../../../event-bus';
import { ToasterService } from '../../../services/toaster.service';
@Component({
selector: 'app-report-issue',
templateUrl: './report-issue.component.html',
styleUrls: ['./report-issue.component.scss']
})
export class ReportIssueComponent implements OnDestroy, AfterViewInit, AfterContentInit {
@ViewChild('issueListComponentContainer') containerRef: ElementRef;
constructor(private toasterService: ToasterService) {}
ngAfterViewInit() {
this.render();
}
ngAfterContentInit() {
e.on('message', message => {
this.toasterService.success(message.text);
});
}
ngOnDestroy() {
ReactDOM.unmountComponentAtNode(this.containerRef.nativeElement);
}
private render() {
ReactDOM.render(<div>
<IssueListComponent />
</div>, this.containerRef.nativeElement);
}
}

View File

@ -18,7 +18,7 @@
<mat-error *ngIf="loginForm.get('username').hasError('required')">You must enter username</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput formControlName="password" placeholder="Password" />
<input matInput type="password" formControlName="password" placeholder="Password" />
<mat-error *ngIf="loginForm.get('password').hasError('required')">You must enter password</mat-error>
</mat-form-field>
</form>

View File

@ -18,9 +18,7 @@ export class CustomAdaptersComponent {
public adapters: CustomAdapter[];
public numberOfAdapters: number;
constructor() {
console.log(this.networkTypes);
}
constructor() {}
cancelConfigureCustomAdapters() {
this.closeConfiguratorEmitter.emit(false);
@ -28,8 +26,6 @@ export class CustomAdaptersComponent {
configureCustomAdapters() {
this.adapters = [];
console.log(this.customAdapters);
this.customAdapters.adapters.forEach((n) => {
this.adapters.push({
adapter_number: n.adapter_number,

View File

@ -32,7 +32,7 @@
lazyimg
[ngClass]="{ imageSelected: isSelected === symbol.symbol_id }"
class="image"
[src]="getImageSourceForTemplate(symbol.symbol_id)"
[src]="getImageSourceForTemplate(symbol.symbol_id)| authImage: server | async"
/>
</button>
</div>

View File

@ -1,4 +1,5 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { Server } from '../../../../models/server';
import { Symbol } from '../../../../models/symbol';
import { SymbolService } from '../../../../services/symbol.service';
@ -18,7 +19,7 @@ export class SymbolsComponent implements OnInit {
isSelected: string = '';
searchText: string = '';
constructor(private symbolService: SymbolService) {}
constructor(private symbolService: SymbolService, private domSanitizer: DomSanitizer) {}
ngOnInit() {
this.isSelected = this.symbol;
@ -76,6 +77,11 @@ export class SymbolsComponent implements OnInit {
width=\"${imageToUpload.width}\">\n<image height=\"${imageToUpload.height}\" width=\"${imageToUpload.width}\" xlink:href=\"${image}\"/>\n</svg>`;
}
getImage(symbolFilename: string) {
let symbol = this.symbolService.get(symbolFilename);
return this.domSanitizer.bypassSecurityTrustUrl(`data:image/svg+xml;base64,${btoa(symbol.raw)}`);
}
getImageSourceForTemplate(symbol: string) {
return `${this.server.protocol}//${this.server.host}:${this.server.port}/v3/symbols/${symbol}/raw`;
}

View File

@ -1,21 +1,47 @@
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { ToasterService } from '../../../services/toaster.service';
import { MapSettingsService } from '../../../services/mapsettings.service';
import { NodeConsoleService } from '../../../services/nodeConsole.service';
import { ThemeService } from '../../../services/theme.service';
import { ConsoleWrapperComponent } from './console-wrapper.component';
import { MatSnackBarModule } from '@angular/material/snack-bar';
describe('ConsoleWrapperComponent', () => {
let fixture: ComponentFixture<ConsoleWrapperComponent>;
let component: ConsoleWrapperComponent;
let nodeConsoleService: NodeConsoleService;
let themeService: ThemeService;
let mapSettingsService: MapSettingsService;
let toasterService: ToasterService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule, MatSnackBarModule],
providers: [NodeConsoleService, ThemeService, MapSettingsService, ToasterService]
}).compileComponents();
toasterService = TestBed.inject(ToasterService);
nodeConsoleService = TestBed.inject(NodeConsoleService);
themeService = TestBed.inject(ThemeService);
mapSettingsService = TestBed.inject(MapSettingsService);
}));
beforeEach(() => {
fixture = TestBed.createComponent(ConsoleWrapperComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
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);
expect(component.isLightThemeEnabled).toBe(false);
});
});

View File

@ -1,6 +1,7 @@
import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MatMenuModule } from '@angular/material/menu';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { BrowserModule } from '@angular/platform-browser';
import { Router } from '@angular/router';
import { ElectronService } from 'ngx-electron';
@ -20,7 +21,9 @@ import { ContextConsoleMenuComponent } from './context-console-menu.component';
describe('ContextConsoleMenuComponent', () => {
let component: ContextConsoleMenuComponent;
let fixture: ComponentFixture<ContextConsoleMenuComponent>;
let toasterService: MockedToasterService = new MockedToasterService();
let nodeConsoleService: NodeConsoleService;
let mapSettingsService: MapSettingsService;
let toasterService: ToasterService;
let router = {
url: '',
navigate: jasmine.createSpy('navigate'),
@ -28,7 +31,6 @@ describe('ContextConsoleMenuComponent', () => {
let node = {
status: 'started',
};
let mapSettingsService = new MapSettingsService();
beforeEach(async(() => {
const electronMock = {
@ -36,20 +38,25 @@ describe('ContextConsoleMenuComponent', () => {
};
TestBed.configureTestingModule({
imports: [MatMenuModule, BrowserModule],
imports: [MatMenuModule, BrowserModule, MatSnackBarModule],
providers: [
{ provide: ChangeDetectorRef },
{ provide: ProjectService, useClass: MockedProjectService },
{ provide: ElectronService, useValue: electronMock },
{ provide: MapSettingsService, useValue: mapSettingsService },
{ provide: NodeConsoleService },
{ provide: ConsoleService },
{ provide: ToasterService, useValue: toasterService },
{ provide: Router, useValue: router },
NodeConsoleService,
ToasterService,
MapSettingsService
],
declarations: [ContextConsoleMenuComponent, ConsoleDeviceActionComponent, ConsoleDeviceActionBrowserComponent],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
toasterService = TestBed.inject(ToasterService);
mapSettingsService = TestBed.inject(MapSettingsService);
nodeConsoleService = TestBed.inject(NodeConsoleService);
}));
beforeEach(() => {

View File

@ -7,7 +7,6 @@ import { NodeService } from '../../../../../services/node.service';
import { ServerService } from '../../../../../services/server.service';
import { MockedServerService } from '../../../../../services/server.service.spec';
import { SettingsService } from '../../../../../services/settings.service';
import { MockedSettingsService } from '../../../../../services/settings.service.spec';
import { ToasterService } from '../../../../../services/toaster.service';
import { MockedToasterService } from '../../../../../services/toaster.service.spec';
import { MockedNodeService } from '../../../project-map.component.spec';
@ -18,7 +17,7 @@ describe('ConsoleDeviceActionComponent', () => {
let fixture: ComponentFixture<ConsoleDeviceActionComponent>;
let electronService;
let server: Server;
let mockedSettingsService: MockedSettingsService;
let settingsService: SettingsService;
let mockedServerService: MockedServerService;
let mockedToaster: MockedToasterService;
let mockedNodeService: MockedNodeService = new MockedNodeService();
@ -35,7 +34,6 @@ describe('ConsoleDeviceActionComponent', () => {
},
};
mockedSettingsService = new MockedSettingsService();
mockedServerService = new MockedServerService();
mockedToaster = new MockedToasterService();
@ -47,13 +45,15 @@ describe('ConsoleDeviceActionComponent', () => {
providers: [
{ provide: ElectronService, useValue: electronService },
{ provide: ServerService, useValue: mockedServerService },
{ provide: SettingsService, useValue: mockedSettingsService },
{ provide: SettingsService },
{ provide: ToasterService, useValue: mockedToaster },
{ provide: NodeService, useValue: mockedNodeService },
],
imports: [MatIconModule],
declarations: [ConsoleDeviceActionComponent],
}).compileComponents();
settingsService = TestBed.inject(SettingsService);
}));
beforeEach(() => {
@ -85,7 +85,7 @@ describe('ConsoleDeviceActionComponent', () => {
component.nodes = nodes;
component.server = server;
mockedSettingsService.set('console_command', 'command');
settingsService.setConsoleSettings('command');
spyOn(component, 'openConsole');
});
@ -105,7 +105,7 @@ describe('ConsoleDeviceActionComponent', () => {
});
it('should set command when it is not defined', async () => {
mockedSettingsService.set('console_command', undefined);
settingsService.setConsoleSettings(undefined);
await component.console();
expect(component.openConsole).toHaveBeenCalled();
});

View File

@ -26,8 +26,8 @@ export class ConsoleDeviceActionComponent implements OnInit {
ngOnInit() {}
async console() {
let consoleCommand = this.settingsService.get<string>('console_command')
? this.settingsService.get<string>('console_command')
let consoleCommand = this.settingsService.getConsoleSettings()
? this.settingsService.getConsoleSettings()
: this.nodeService.getDefaultCommand();
const startedNodes = this.nodes.filter((node) => node.status === 'started');

View File

@ -1,4 +1,4 @@
<button mat-menu-item (click)="delete()">
<button mat-menu-item (click)="confirmDelete()">
<mat-icon>delete</mat-icon>
<span>Delete</span>
</button>

View File

@ -1,4 +1,5 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@ -24,7 +25,7 @@ describe('DeleteActionComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MatIconModule, MatMenuModule, NoopAnimationsModule],
imports: [MatIconModule, MatMenuModule, NoopAnimationsModule, MatBottomSheetModule],
providers: [
{ provide: NodesDataSource, useClass: NodesDataSource },
{ provide: DrawingsDataSource, useClass: DrawingsDataSource },

View File

@ -1,4 +1,6 @@
import { Component, Input, OnInit } from '@angular/core';
import { MatBottomSheet } from '@angular/material/bottom-sheet';
import { ConfirmationBottomSheetComponent } from 'app/components/projects/confirmation-bottomsheet/confirmation-bottomsheet.component';
import { DrawingsDataSource } from '../../../../../cartography/datasources/drawings-datasource';
import { LinksDataSource } from '../../../../../cartography/datasources/links-datasource';
import { NodesDataSource } from '../../../../../cartography/datasources/nodes-datasource';
@ -26,11 +28,23 @@ export class DeleteActionComponent implements OnInit {
private linksDataSource: LinksDataSource,
private nodeService: NodeService,
private drawingService: DrawingService,
private linkService: LinkService
private linkService: LinkService,
private bottomSheet: MatBottomSheet
) {}
ngOnInit() {}
confirmDelete() {
this.bottomSheet.open(ConfirmationBottomSheetComponent);
let bottomSheetRef = this.bottomSheet._openedBottomSheetRef;
bottomSheetRef.instance.message = 'Do you want to delete all selected objects?';
const bottomSheetSubscription = bottomSheetRef.afterDismissed().subscribe((result: boolean) => {
if (result) {
this.delete();
}
});
}
delete() {
this.nodes.forEach((node) => {
this.nodesDataSource.remove(node);

View File

@ -1,8 +1,7 @@
import { Component, Input, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { NodeConsoleService } from '../../../../../services/nodeConsole.service';
import { Node } from '../../../../../cartography/models/node';
import { Server } from '../../../../../models/server';
import { ToasterService } from '../../../../../services/toaster.service';
@Component({
selector: 'app-http-console-new-tab-action',
@ -12,19 +11,11 @@ export class HttpConsoleNewTabActionComponent implements OnInit {
@Input() server: Server;
@Input() nodes: Node[];
constructor(private toasterService: ToasterService, private router: Router) {}
constructor(private nodeConsoleService: NodeConsoleService) {}
ngOnInit() {}
openConsole() {
this.nodes.forEach((n) => {
if (n.status === 'started') {
let url = this.router.url.split('/');
let urlString = `/static/web-ui/${url[1]}/${url[2]}/${url[3]}/${url[4]}/nodes/${n.node_id}`;
window.open(urlString);
} else {
this.toasterService.error('To open console please start the node');
}
});
this.nodeConsoleService.openConsolesForAllNodesInNewTabs(this.nodes);
}
}

View File

@ -1,9 +1,7 @@
import { Component, Input, OnInit } from '@angular/core';
import { Node } from '../../../../../cartography/models/node';
import { Server } from '../../../../../models/server';
import { MapSettingsService } from '../../../../../services/mapsettings.service';
import { NodeConsoleService } from '../../../../../services/nodeConsole.service';
import { ToasterService } from '../../../../../services/toaster.service';
@Component({
selector: 'app-http-console-action',
@ -13,22 +11,11 @@ export class HttpConsoleActionComponent implements OnInit {
@Input() server: Server;
@Input() nodes: Node[];
constructor(
private consoleService: NodeConsoleService,
private toasterService: ToasterService,
private mapSettingsService: MapSettingsService
) {}
constructor(private nodeConsoleService: NodeConsoleService) {}
ngOnInit() {}
openConsole() {
this.nodes.forEach((n) => {
if (n.status === 'started') {
this.mapSettingsService.logConsoleSubject.next(true);
this.consoleService.openConsoleForNode(n);
} else {
this.toasterService.error('To open console please start the node');
}
});
this.nodeConsoleService.openConsolesForAllNodesInWidget(this.nodes);
}
}

View File

@ -8,12 +8,12 @@
<app-stop-node-action *ngIf="nodes.length" [server]="server" [nodes]="nodes"></app-stop-node-action>
<app-reload-node-action *ngIf="nodes.length" [server]="server" [nodes]="nodes"></app-reload-node-action>
<app-http-console-action
*ngIf="!projectService.isReadOnly(project) && nodes.length === 1"
*ngIf="!projectService.isReadOnly(project) && nodes.length > 0"
[server]="server"
[nodes]="nodes"
></app-http-console-action>
<app-http-console-new-tab-action
*ngIf="!projectService.isReadOnly(project) && nodes.length === 1"
*ngIf="!projectService.isReadOnly(project) && nodes.length > 0"
[server]="server"
[nodes]="nodes"
></app-http-console-new-tab-action>

View File

@ -4,6 +4,8 @@ import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MatMenuModule } from '@angular/material/menu';
import { BrowserModule } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { ToasterService } from '../../../services/toaster.service';
import { of } from 'rxjs';
import { NodesDataSource } from '../../../cartography/datasources/nodes-datasource';
import { ProjectWebServiceHandler, WebServiceMessage } from '../../../handlers/project-web-service-handler';
@ -14,6 +16,8 @@ import { NodeConsoleService } from '../../../services/nodeConsole.service';
import { MockedNodesDataSource, MockedNodeService } from '../project-map.component.spec';
import { LogConsoleComponent } from './log-console.component';
import { LogEventsDataSource } from './log-events-datasource';
import { MapSettingsService } from '../../../services/mapsettings.service';
import { MatSnackBarModule } from '@angular/material/snack-bar';
export class MockedProjectWebServiceHandler {
public nodeNotificationEmitter = new EventEmitter<WebServiceMessage>();
@ -31,23 +35,32 @@ describe('LogConsoleComponent', () => {
let mockedNodeService: MockedNodeService = new MockedNodeService();
let mockedNodesDataSource: MockedNodesDataSource = new MockedNodesDataSource();
let mockedProjectWebServiceHandler: MockedProjectWebServiceHandler = new MockedProjectWebServiceHandler();
let nodeConsoleService: NodeConsoleService;
let mapSettingsService: MapSettingsService;
let toasterService: ToasterService;
let httpServer = new HttpServer({} as HttpClient, {} as ServerErrorHandler);
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, MatMenuModule, BrowserModule],
imports: [HttpClientTestingModule, RouterTestingModule, MatMenuModule, BrowserModule, MatSnackBarModule],
providers: [
{ provide: ProjectWebServiceHandler, useValue: mockedProjectWebServiceHandler },
{ provide: NodeService, useValue: mockedNodeService },
{ provide: NodesDataSource, useValue: mockedNodesDataSource },
{ provide: LogEventsDataSource, useClass: LogEventsDataSource },
{ provide: HttpServer, useValue: httpServer },
{ provide: NodeConsoleService },
NodeConsoleService,
ToasterService,
MapSettingsService
],
declarations: [LogConsoleComponent],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
toasterService = TestBed.inject(ToasterService);
mapSettingsService = TestBed.inject(MapSettingsService);
nodeConsoleService = TestBed.inject(NodeConsoleService);
}));
beforeEach(() => {

View File

@ -259,6 +259,38 @@
</button>
</div>
</div>
<div class="list-item-inside" *ngIf="version.images.cdrom_image">
<span>
{{ version.images.cdrom_image}}
</span>
<div>
<span *ngIf="checkImageFromVersion(version.images.cdrom_image)"
><mat-icon matTooltip="Ready to install" matTooltipClass="custom-tooltip">check</mat-icon></span
>
<span *ngIf="!checkImageFromVersion(version.images.cdrom_image)"
><mat-icon matTooltip="Missing" matTooltipClass="custom-tooltip">close</mat-icon></span
>
<input
type="file"
class="non-visible"
#file4
(change)="importImage($event, version.images.cdrom_image)"
ng2FileSelect
[uploader]="uploaderImage"
/>
<button class="button" mat-raised-button (click)="file4.click()">Import</button>
<button
class="button"
mat-raised-button
(click)="downloadImageFromVersion(version.images.cdrom_image)"
>
Download
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,4 +1,5 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { MapSettingsService } from '../../../services/mapsettings.service';
import { ElectronService } from 'ngx-electron';
import { NodesDataSource } from '../../../cartography/datasources/nodes-datasource';
import { Project } from '../../../models/project';
@ -7,6 +8,7 @@ import { NodeService } from '../../../services/node.service';
import { ServerService } from '../../../services/server.service';
import { SettingsService } from '../../../services/settings.service';
import { ToasterService } from '../../../services/toaster.service';
import { NodeConsoleService } from '../../../services/nodeConsole.service';
@Component({
selector: 'app-nodes-menu',
@ -20,17 +22,19 @@ export class NodesMenuComponent {
constructor(
private nodeService: NodeService,
private nodeConsoleService: NodeConsoleService,
private nodesDataSource: NodesDataSource,
private toasterService: ToasterService,
private serverService: ServerService,
private settingsService: SettingsService,
private mapSettingsService: MapSettingsService,
private electronService: ElectronService
) {}
async startConsoleForAllNodes() {
if (this.electronService.isElectronApp) {
let consoleCommand = this.settingsService.get<string>('console_command')
? this.settingsService.get<string>('console_command')
let consoleCommand = this.settingsService.getConsoleSettings()
? this.settingsService.getConsoleSettings()
: this.nodeService.getDefaultCommand();
let nodes = this.nodesDataSource.getItems();
@ -48,7 +52,11 @@ export class NodesMenuComponent {
await this.electronService.remote.require('./console-executor.js').openConsole(request);
}
} else {
this.toasterService.error('Option to start all nodes not available in web browser.');
if (this.mapSettingsService.openConsolesInWidget) {
this.nodeConsoleService.openConsolesForAllNodesInWidget(this.nodesDataSource.getItems());
} else {
this.nodeConsoleService.openConsolesForAllNodesInNewTabs(this.nodesDataSource.getItems());
}
}
}

View File

@ -1,6 +1,6 @@
<div *ngIf="project" [ngClass]="{ lightTheme: isLightThemeEnabled }" class="project-map">
<app-d3-map
*ngIf="!settings.angular_map"
*ngIf="!settings.angular_map && symbolsLoaded"
[server]="server"
[project]="project"
[symbols]="symbols"
@ -64,7 +64,6 @@
<div class="menu-button-group">
<app-nodes-menu [server]="server" [project]="project"></app-nodes-menu>
<app-context-menu [project]="project" [server]="server"></app-context-menu>
<app-context-console-menu [project]="project" [server]="server"></app-context-console-menu>
</div>
</div>
<div>
@ -255,12 +254,6 @@
(closeConsole)="toggleShowConsole($event)"
></app-console-wrapper>
</div>
<div [ngClass]="{ visible: !isTopologySummaryVisible }">
<app-topology-summary
*ngIf="project"
[server]="server"
[project]="project"
(closeTopologySummary)="toggleShowTopologySummary($event)"
></app-topology-summary>
</div>
</div>
<ng-template #topologySummaryContainer></ng-template>

View File

@ -59,7 +59,6 @@ import { RecentlyOpenedProjectService } from '../../services/recentlyOpenedProje
import { ServerService } from '../../services/server.service';
import { MockedServerService } from '../../services/server.service.spec';
import { SettingsService } from '../../services/settings.service';
import { MockedSettingsService } from '../../services/settings.service.spec';
import { ToasterService } from '../../services/toaster.service';
import { MockedToasterService } from '../../services/toaster.service.spec';
import { ToolsService } from '../../services/tools.service';
@ -300,7 +299,7 @@ xdescribe('ProjectMapComponent', () => {
{ provide: NodesDataSource, useValue: nodesDataSource },
{ provide: LinksDataSource, useValue: linksDataSource },
{ provide: DrawingsDataSource, useValue: drawingsDataSource },
{ provide: SettingsService, useClass: MockedSettingsService },
{ provide: SettingsService },
{ provide: ToolsService },
{ provide: SelectionManager },
{ provide: SelectionTool },

View File

@ -1,11 +1,11 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, Injector, OnDestroy, OnInit, SimpleChange, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
import { MatBottomSheet } from '@angular/material/bottom-sheet';
import { MatDialog } from '@angular/material/dialog';
import { Title } from '@angular/platform-browser';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import * as Mousetrap from 'mousetrap';
import { from, Observable, Subscription } from 'rxjs';
import { map, mergeMap } from 'rxjs/operators';
import { map, mergeMap, takeUntil } from 'rxjs/operators';
import { D3MapComponent } from '../../cartography/components/d3-map/d3-map.component';
import { MapDrawingToDrawingConverter } from '../../cartography/converters/map/map-drawing-to-drawing-converter';
import { MapLabelToLabelConverter } from '../../cartography/converters/map/map-label-to-label-converter';
@ -74,7 +74,7 @@ import { ImportProjectDialogComponent } from '../projects/import-project-dialog/
import { NavigationDialogComponent } from '../projects/navigation-dialog/navigation-dialog.component';
import { SaveProjectDialogComponent } from '../projects/save-project-dialog/save-project-dialog.component';
import { NodeAddedEvent } from '../template/template-list-dialog/template-list-dialog.component';
import { ContextConsoleMenuComponent } from './context-console-menu/context-console-menu.component';
import { TopologySummaryComponent } from '../topology-summary/topology-summary.component';
import { ContextMenuComponent } from './context-menu/context-menu.component';
import { NodeCreatedLabelStylesFixer } from './helpers/node-created-label-styles-fixer';
import { NewTemplateDialogComponent } from './new-template-dialog/new-template-dialog.component';
@ -105,6 +105,8 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
public gridVisibility: boolean = false;
public toolbarVisibility: boolean = true;
public symbolScaling: boolean = true;
public symbolsLoaded: boolean = false;
private instance: ComponentRef<TopologySummaryComponent>;
tools = {
selection: true,
@ -121,9 +123,9 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
public isLightThemeEnabled: boolean = false;
@ViewChild(ContextMenuComponent) contextMenu: ContextMenuComponent;
@ViewChild(ContextConsoleMenuComponent) consoleContextMenu: ContextConsoleMenuComponent;
@ViewChild(D3MapComponent) mapChild: D3MapComponent;
@ViewChild(ProjectMapMenuComponent) projectMapMenuComponent: ProjectMapMenuComponent;
@ViewChild('topologySummaryContainer', {read: ViewContainerRef}) topologySummaryContainer: ViewContainerRef;
private projectMapSubscription: Subscription = new Subscription();
@ -173,12 +175,17 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
private title: Title,
private nodeConsoleService: NodeConsoleService,
private symbolService: SymbolService,
private cd: ChangeDetectorRef
private cd: ChangeDetectorRef,
private cfr: ComponentFactoryResolver,
private injector: Injector
) {}
ngOnInit() {
this.getSettings();
this.progressService.activate();
this.symbolService.symbolsLoaded.subscribe(loaded => {
this.symbolsLoaded = true;
});
if (this.serverService.isServiceInitialized) {
this.getData();
@ -208,7 +215,6 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.settings = this.settingsService.getAll();
this.symbolScaling = this.mapSettingsService.getSymbolScaling();
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;
@ -216,6 +222,21 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.gridVisibility = localStorage.getItem('gridVisibility') === 'true' ? true : false;
}
async lazyLoadTopologySummary() {
if (this.isTopologySummaryVisible) {
const {TopologySummaryComponent} = await import('../topology-summary/topology-summary.component');
const componentFactory = this.cfr.resolveComponentFactory(TopologySummaryComponent);
this.instance = this.topologySummaryContainer.createComponent(componentFactory, null, this.injector);
this.instance.instance.server = this.server;
this.instance.instance.project = this.project;
} else if (this.instance) {
if (this.instance.instance) {
this.instance.instance.ngOnDestroy();
this.instance.destroy();
}
}
}
addSubscriptions() {
this.projectMapSubscription.add(
this.mapSettingsService.mapRenderedEmitter.subscribe((value: boolean) => {
@ -299,8 +320,11 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
.pipe(
mergeMap((server: Server) => {
if (!server) this.router.navigate(['/servers']);
this.server = server;
// load symbols
this.symbolService.load(this.server);
return this.projectService.get(server, paramMap.get('project_id')).pipe(
map((project) => {
return project;
@ -314,6 +338,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.projectService.open(this.server, this.project.project_id);
this.title.setTitle(this.project.name);
this.isInterfaceLabelVisible = this.mapSettingsService.showInterfaceLabels;
this.toggleShowTopologySummary(this.mapSettingsService.isTopologySummaryVisible);
this.recentlyOpenedProjectService.setServerId(this.server.id.toString());
this.recentlyOpenedProjectService.setProjectId(this.project.project_id);
@ -386,16 +411,27 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
Mousetrap.bind('del', (event: Event) => {
event.preventDefault();
const selected = this.selectionManager.getSelected();
this.deleteItems();
});
}
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');
deleteItems() {
this.bottomSheet.open(ConfirmationBottomSheetComponent);
let bottomSheetRef = this.bottomSheet._openedBottomSheetRef;
bottomSheetRef.instance.message = 'Do you want to delete all selected objects?';
const bottomSheetSubscription = bottomSheetRef.afterDismissed().subscribe((result: boolean) => {
if (result) {
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');
});
});
});
}
});
}
@ -448,33 +484,33 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
const onLinkContextMenu = this.linkWidget.onContextMenu.subscribe((eventLink: LinkContextMenu) => {
const link = this.mapLinkToLink.convert(eventLink.link);
this.contextMenu.openMenuForListOfElements([], [], [], [link], eventLink.event.pageY, eventLink.event.pageX);
this.contextMenu.openMenuForListOfElements([], [], [], [link], eventLink.event.screenY - 60, eventLink.event.screenX);
});
const onEthernetLinkContextMenu = this.ethernetLinkWidget.onContextMenu.subscribe((eventLink: LinkContextMenu) => {
const link = this.mapLinkToLink.convert(eventLink.link);
this.contextMenu.openMenuForListOfElements([], [], [], [link], eventLink.event.pageY, eventLink.event.pageX);
this.contextMenu.openMenuForListOfElements([], [], [], [link], eventLink.event.screenY - 60, eventLink.event.screenX);
});
const onSerialLinkContextMenu = this.serialLinkWidget.onContextMenu.subscribe((eventLink: LinkContextMenu) => {
const link = this.mapLinkToLink.convert(eventLink.link);
this.contextMenu.openMenuForListOfElements([], [], [], [link], eventLink.event.pageY, eventLink.event.pageX);
this.contextMenu.openMenuForListOfElements([], [], [], [link], eventLink.event.screenY - 60, eventLink.event.screenX);
});
const onNodeContextMenu = this.nodeWidget.onContextMenu.subscribe((eventNode: NodeContextMenu) => {
const node = this.mapNodeToNode.convert(eventNode.node);
this.contextMenu.openMenuForNode(node, eventNode.event.pageY, eventNode.event.pageX);
this.contextMenu.openMenuForNode(node, eventNode.event.screenY - 60, eventNode.event.screenX);
});
const onDrawingContextMenu = this.drawingsWidget.onContextMenu.subscribe((eventDrawing: DrawingContextMenu) => {
const drawing = this.mapDrawingToDrawing.convert(eventDrawing.drawing);
this.contextMenu.openMenuForDrawing(drawing, eventDrawing.event.pageY, eventDrawing.event.pageX);
this.contextMenu.openMenuForDrawing(drawing, eventDrawing.event.screenY - 60, eventDrawing.event.screenX);
});
const onLabelContextMenu = this.labelWidget.onContextMenu.subscribe((eventLabel: LabelContextMenu) => {
const label = this.mapLabelToLabel.convert(eventLabel.label);
const node = this.nodes.find((n) => n.node_id === eventLabel.label.nodeId);
this.contextMenu.openMenuForLabel(label, node, eventLabel.event.pageY, eventLabel.event.pageX);
this.contextMenu.openMenuForLabel(label, node, eventLabel.event.screenY - 60, eventLabel.event.screenX);
});
const onInterfaceLabelContextMenu = this.interfaceLabelWidget.onContextMenu.subscribe(
@ -484,8 +520,8 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.contextMenu.openMenuForInterfaceLabel(
linkNode,
link,
eventInterfaceLabel.event.pageY,
eventInterfaceLabel.event.pageX
eventInterfaceLabel.event.screenY - 60,
eventInterfaceLabel.event.screenX
);
}
);
@ -514,11 +550,6 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.contextMenu.openMenuForListOfElements(drawings, nodes, labels, links, event.pageY, event.pageX);
});
const onContextConsoleMenu = this.nodeWidget.onContextConsoleMenu.subscribe((eventNode: NodeContextMenu) => {
const node = this.mapNodeToNode.convert(eventNode.node);
this.consoleContextMenu.openMenu(node, eventNode.event.pageY, eventNode.event.pageX);
});
this.projectMapSubscription.add(onLinkContextMenu);
this.projectMapSubscription.add(onEthernetLinkContextMenu);
this.projectMapSubscription.add(onSerialLinkContextMenu);
@ -527,7 +558,6 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.projectMapSubscription.add(onContextMenu);
this.projectMapSubscription.add(onLabelContextMenu);
this.projectMapSubscription.add(onInterfaceLabelContextMenu);
this.projectMapSubscription.add(onContextConsoleMenu);
this.mapChangeDetectorRef.detectChanges();
}
@ -536,6 +566,9 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
return;
}
nodeAddedEvent.x = nodeAddedEvent.x / this.mapScaleService.getScale();
nodeAddedEvent.y = nodeAddedEvent.y / this.mapScaleService.getScale();
this.progressService.activate();
this.nodeService
.createFromTemplate(
@ -796,6 +829,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
public toggleShowTopologySummary(visible: boolean) {
this.isTopologySummaryVisible = visible;
this.mapSettingsService.toggleTopologySummary(this.isTopologySummaryVisible);
this.lazyLoadTopologySummary();
}
public toggleNotifications(visible: boolean) {

View File

@ -1,10 +1,12 @@
.import-button {
height: 40px;
width: 160px;
margin: 20px;
}
.add-button {
height: 40px;
width: 160px;
margin: 20px;
}
@ -13,3 +15,7 @@
margin-left: -470px;
left: 50%;
}
.row {
display: flex;
}

View File

@ -23,7 +23,6 @@ import { MockedProjectService } from '../../services/project.service.spec';
import { ServerService } from '../../services/server.service';
import { MockedServerService } from '../../services/server.service.spec';
import { Settings, SettingsService } from '../../services/settings.service';
import { MockedSettingsService } from '../../services/settings.service.spec';
import { ToasterService } from '../../services/toaster.service';
import { ConfigureGns3VMDialogComponent } from '../servers/configure-gns3vm-dialog/configure-gns3vm-dialog.component';
import { ChooseNameDialogComponent } from './choose-name-dialog/choose-name-dialog.component';
@ -70,7 +69,7 @@ xdescribe('ProjectsComponent', () => {
providers: [
{ provide: ServerService, useClass: MockedServerService },
{ provide: ProjectService, useValue: mockedProjectService },
{ provide: SettingsService, useClass: MockedSettingsService },
{ provide: SettingsService},
{ provide: ToasterService },
{ provide: ElectronService, useValue: electronService },
ProgressService,
@ -83,10 +82,10 @@ xdescribe('ProjectsComponent', () => {
})
.compileComponents();
serverService = TestBed.get(ServerService);
settingsService = TestBed.get(SettingsService);
projectService = TestBed.get(ProjectService);
progressService = TestBed.get(ProgressService);
serverService = TestBed.inject(ServerService);
settingsService = TestBed.inject(SettingsService);
projectService = TestBed.inject(ProjectService);
progressService = TestBed.inject(ProgressService);
server = new Server();
server.id = 99;

View File

@ -35,7 +35,7 @@
</mat-form-field>
</div>
<div mat-dialog-actions>
<button mat-button (click)="onNoClick()" tabindex="-1" color="accent">No Thanks</button>
<button mat-button (click)="onNoClick()" tabindex="-1" color="accent">Cancel</button>
<button mat-button (click)="onAddClick()" tabindex="2" mat-raised-button color="primary">Add</button>
</div>
</form>

View File

@ -13,19 +13,8 @@
<mat-checkbox [(ngModel)]="settings.crash_reports">Send anonymous crash reports</mat-checkbox><br/>
<mat-checkbox [(ngModel)]="integrateLinksLabelsToLinks">Integrate link labels to links</mat-checkbox><br/>
<mat-checkbox [(ngModel)]="openReadme">Automatically open project README files</mat-checkbox>
<mat-checkbox [(ngModel)]="openConsolesInWidget">Open consoles in the widget instead of in new tabs after clicking start consoles for all nodes</mat-checkbox>
</div>
<!-- <div>
<mat-checkbox [(ngModel)]="settings.experimental_features"
>Enable experimental features (WARNING: IT CAN BREAK YOU LABS!)</mat-checkbox
>
</div> -->
<!-- <div>
<mat-checkbox [(ngModel)]="settings.angular_map"
>Enable experimental Angular Map (WARNING: IT CAN BREAK YOU LABS!)</mat-checkbox
>
</div> -->
</mat-expansion-panel>
<mat-expansion-panel [expanded]="false">

View File

@ -6,7 +6,6 @@ import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { PersistenceModule } from 'angular-persistence';
import { MapSettingsService } from '../../services/mapsettings.service';
import { SettingsService } from '../../services/settings.service';
import { ConsoleService } from '../../services/settings/console.service';
@ -23,7 +22,8 @@ describe('SettingsComponent', () => {
let mapSettingsService = {
integrateLinkLabelsToLinks: true,
toggleIntegrateInterfaceLabels(val: boolean) {},
toggleOpenReadme(val: boolean) {}
toggleOpenReadme(val: boolean) {},
toggleOpenConsolesInWidget(val: boolean) {}
};
let consoleService;
let updatesService = autoSpy(UpdatesService);
@ -38,7 +38,6 @@ describe('SettingsComponent', () => {
MatExpansionModule,
MatCheckboxModule,
FormsModule,
PersistenceModule,
BrowserAnimationsModule,
MatIconModule,
MatFormFieldModule,
@ -76,11 +75,19 @@ describe('SettingsComponent', () => {
};
const getAll = spyOn(settingsService, 'getAll').and.returnValue(settings);
const setAll = spyOn(settingsService, 'setAll');
spyOn(mapSettingsService, 'toggleIntegrateInterfaceLabels');
spyOn(mapSettingsService, 'toggleOpenConsolesInWidget');
component.ngOnInit();
expect(getAll).toHaveBeenCalled();
expect(component.settings).toEqual(settings);
component.settings.crash_reports = false;
component.save();
expect(setAll).toHaveBeenCalledWith(settings);
expect(mapSettingsService.toggleIntegrateInterfaceLabels).toHaveBeenCalled();
expect(mapSettingsService.toggleOpenConsolesInWidget).toHaveBeenCalled();
});
});

View File

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { MapSettingsService } from '../../services/mapsettings.service';
import { SettingsService } from '../../services/settings.service';
import { Settings, SettingsService } from '../../services/settings.service';
import { ConsoleService } from '../../services/settings/console.service';
import { ThemeService } from '../../services/theme.service';
import { ToasterService } from '../../services/toaster.service';
@ -12,10 +12,11 @@ import { UpdatesService } from '../../services/updates.service';
styleUrls: ['./settings.component.scss'],
})
export class SettingsComponent implements OnInit {
settings = { ...SettingsService.DEFAULTS };
settings: Settings;
consoleCommand: string;
integrateLinksLabelsToLinks: boolean;
openReadme: boolean;
openConsolesInWidget: boolean;
constructor(
private settingsService: SettingsService,
@ -31,6 +32,7 @@ export class SettingsComponent implements OnInit {
this.consoleCommand = this.consoleService.command;
this.integrateLinksLabelsToLinks = this.mapSettingsService.integrateLinkLabelsToLinks;
this.openReadme = this.mapSettingsService.openReadme;
this.openConsolesInWidget = this.mapSettingsService.openConsolesInWidget;
}
save() {
@ -39,6 +41,7 @@ export class SettingsComponent implements OnInit {
this.mapSettingsService.toggleIntegrateInterfaceLabels(this.integrateLinksLabelsToLinks);
this.mapSettingsService.toggleOpenReadme(this.openReadme);
this.mapSettingsService.toggleOpenConsolesInWidget(this.openConsolesInWidget);
}
setDarkMode(value: boolean) {

View File

@ -18,6 +18,6 @@
</form>
</div>
<div mat-dialog-actions>
<button mat-button (click)="onNoClick()" tabindex="-1" color="accent">No Thanks</button>
<button mat-button (click)="onNoClick()" tabindex="-1" color="accent">Cancel</button>
<button mat-button (click)="onAddClick()" tabindex="2" mat-raised-button color="primary">Add</button>
</div>

View File

@ -43,25 +43,25 @@
<span *ngIf="i % 4 === 0" class="templateRow">
<span class="templateIcon">
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, filteredTemplates[i])">
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i])" />
<img class="image" alt="Image" [src]="getImageSourceForTemplate(filteredTemplates[i])" />
</div>
<div class="templateText">{{ filteredTemplates[i].name }}</div>
</span>
<span *ngIf="filteredTemplates[i + 1]" class="templateIcon">
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, filteredTemplates[i + 1])">
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 1])" />
<img class="image" alt="Image" [src]="getImageSourceForTemplate(filteredTemplates[i + 1])" />
</div>
<div class="templateText">{{ filteredTemplates[i + 1].name }}</div>
</span>
<span *ngIf="filteredTemplates[i + 2]" class="templateIcon">
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, filteredTemplates[i + 2])">
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 2])" />
<img class="image" alt="Image" [src]="getImageSourceForTemplate(filteredTemplates[i + 2])" />
</div>
<div class="templateText">{{ filteredTemplates[i + 2].name }}</div>
</span>
<span *ngIf="filteredTemplates[i + 3]" class="templateIcon">
<div mwlDraggable (dragStart)="dragStart($event)" (dragEnd)="dragEnd($event, filteredTemplates[i + 3])">
<img class="image" [src]="getImageSourceForTemplate(filteredTemplates[i + 3])" />
<img class="image" alt="Image" [src]="getImageSourceForTemplate(filteredTemplates[i + 3])" />
</div>
<div class="templateText">{{ filteredTemplates[i + 3].name }}</div>
</span>

View File

@ -8,6 +8,7 @@ import { MapScaleService } from '../../services/mapScale.service';
import { SymbolService } from '../../services/symbol.service';
import { TemplateService } from '../../services/template.service';
import { NodeAddedEvent, TemplateListDialogComponent } from './template-list-dialog/template-list-dialog.component';
import { DomSanitizer } from '@angular/platform-browser';
@Component({
selector: 'app-template',
@ -49,7 +50,8 @@ export class TemplateComponent implements OnInit, OnDestroy {
private dialog: MatDialog,
private templateService: TemplateService,
private scaleService: MapScaleService,
private symbolService: SymbolService
private symbolService: SymbolService,
private domSanitizer: DomSanitizer
) {}
ngOnInit() {
@ -127,7 +129,9 @@ export class TemplateComponent implements OnInit, OnDestroy {
}
getImageSourceForTemplate(template: Template) {
return this.symbolService.getSymbolFromTemplate(this.server, template);
let symbol = this.symbolService.getSymbolFromTemplate(template);
if (symbol) return this.domSanitizer.bypassSecurityTrustUrl(`data:image/svg+xml;base64,${btoa(symbol.raw)}`);
return this.domSanitizer.bypassSecurityTrustUrl('data:image/svg+xml;base64,');
}
ngOnDestroy() {

View File

@ -0,0 +1,20 @@
<div class="content">
<div class="default-header">
<div class="row">
<h1 class="col">Logged in user info</h1>
</div>
</div>
<div class="default-content">
<mat-card *ngIf="user">
<mat-list>
<mat-list-item> Username: {{user.username}} </mat-list-item>
<mat-list-item> Full name: {{user.full_name}} </mat-list-item>
<mat-list-item> Email: {{user.email}} </mat-list-item>
</mat-list>
<div class="buttons-bar">
<button mat-raised-button color="primary" class="full_width" (click)="copyToken()">Click to copy access token</button><br />
</div>
</mat-card>
</div>
</div>

View File

@ -0,0 +1,3 @@
.full_width {
width: 100%;
}

View File

@ -0,0 +1,50 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ServerService } from '../../../services/server.service';
import { UserService } from '../../../services/user.service';
import { ToasterService } from '../../../services/toaster.service';
import { User } from '../../../models/users/user';
import { Server } from '../../../models/server';
@Component({
selector: 'app-logged-user',
templateUrl: './logged-user.component.html',
styleUrls: ['./logged-user.component.scss'],
})
export class LoggedUserComponent implements OnInit {
public user: User;
public server: Server;
constructor(
private route: ActivatedRoute,
private serverService: ServerService,
private userService: UserService,
private toasterService: ToasterService
) {}
ngOnInit() {
let serverId = this.route.snapshot.paramMap.get('server_id');
this.serverService.get(+serverId).then((server: Server) => {
this.server = server;
this.userService.getInformationAboutLoggedUser(server).subscribe((response: any) => {
this.user = response;
});
});
}
copyToken() {
const selBox = document.createElement('textarea');
selBox.style.position = 'fixed';
selBox.style.left = '0';
selBox.style.top = '0';
selBox.style.opacity = '0';
selBox.value = this.server.authToken;
document.body.appendChild(selBox);
selBox.focus();
selBox.select();
document.execCommand('copy');
document.body.removeChild(selBox);
this.toasterService.success('Token copied');
}
}

View File

@ -0,0 +1,26 @@
import { Pipe, PipeTransform } from '@angular/core';
import { Console } from 'console';
import { Server } from '../models/server';
import { HttpServer } from '../services/http-server.service';
import { DomSanitizer } from '@angular/platform-browser';
@Pipe({
name: 'authImage'
})
export class AuthImageFilter implements PipeTransform {
constructor(
private httpServer: HttpServer,
private domSanitizer: DomSanitizer
) {}
async transform(src: string, server: Server) {
let url = src.split('v3')[1];
const imageBlob: Blob = await this.httpServer.getBlob(server, url).toPromise();
const reader = new FileReader();
return new Promise((resolve, reject) => {
reader.onloadend = () => resolve(this.domSanitizer.bypassSecurityTrustUrl(reader.result as string));
reader.readAsDataURL(imageBlob);
});
}
}

View File

@ -35,6 +35,10 @@
<mat-icon>help</mat-icon>
<span>Help</span>
</button>
<button mat-menu-item (click)="goToUserInfo()">
<mat-icon>person</mat-icon>
<span>User info</span>
</button>
<button mat-menu-item (click)="logout()">
<mat-icon>highlight_off</mat-icon>
<span>Logout</span>

View File

@ -4,6 +4,7 @@ import { MatMenuModule } from '@angular/material/menu';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatToolbarModule } from '@angular/material/toolbar';
import { RouterTestingModule } from '@angular/router/testing';
import { ServerService } from '../../services/server.service';
import { ElectronService } from 'ngx-electron';
import { Subject } from 'rxjs';
import { ProgressComponent } from '../../common/progress/progress.component';
@ -13,6 +14,9 @@ import { ServerManagementService, ServerStateEvent } from '../../services/server
import { ToasterService } from '../../services/toaster.service';
import { MockedToasterService } from '../../services/toaster.service.spec';
import { DefaultLayoutComponent } from './default-layout.component';
import { IndexedDbService } from '../../services/indexed-db.service';
import { HttpServer, ServerErrorHandler } from '../../services/http-server.service';
import { HttpClientModule } from '@angular/common/http';
class ElectronServiceMock {
public isElectronApp: boolean;
@ -28,6 +32,10 @@ describe('DefaultLayoutComponent', () => {
let fixture: ComponentFixture<DefaultLayoutComponent>;
let electronServiceMock: ElectronServiceMock;
let serverManagementService = new MockedServerManagementService();
let serverService: ServerService;
let indexedDbService: IndexedDbService;
let httpServer: HttpServer;
let errorHandler: ServerErrorHandler;
beforeEach(async(() => {
electronServiceMock = new ElectronServiceMock();
@ -35,7 +43,7 @@ describe('DefaultLayoutComponent', () => {
TestBed.configureTestingModule({
declarations: [DefaultLayoutComponent, ProgressComponent],
imports: [MatIconModule, MatMenuModule, MatToolbarModule, RouterTestingModule, MatProgressSpinnerModule],
imports: [MatIconModule, MatMenuModule, MatToolbarModule, HttpClientModule, RouterTestingModule, MatProgressSpinnerModule],
providers: [
{
provide: ElectronService,
@ -53,9 +61,18 @@ describe('DefaultLayoutComponent', () => {
provide: RecentlyOpenedProjectService,
useClass: RecentlyOpenedProjectService,
},
{ provide: ServerService },
{ provide: IndexedDbService },
{ provide: HttpServer },
{ provide: ServerErrorHandler },
ProgressService,
],
}).compileComponents();
indexedDbService = TestBed.inject(IndexedDbService);
errorHandler = TestBed.inject(ServerErrorHandler);
httpServer = TestBed.inject(HttpServer);
serverService = TestBed.inject(ServerService);
}));
beforeEach(() => {

View File

@ -37,7 +37,6 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
private toasterService: ToasterService,
private progressService: ProgressService,
private router: Router,
private route: ActivatedRoute,
private serverService: ServerService
) {}
@ -69,6 +68,13 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
this.shouldStopServersOnClosing = this.electronService.isElectronApp;
}
goToUserInfo() {
let serverId = this.router.url.split("/server/")[1].split("/")[0];
this.serverService.get(+serverId).then((server: Server) => {
this.router.navigate(['/server', server.id, 'loggeduser']);
});
}
checkIfUserIsLoginPage() {
if (this.router.url.includes("login")) {
this.isLoginPage = true;

View File

@ -0,0 +1,10 @@
export interface User {
created_at: string;
email: string;
full_name: string;
is_active: boolean;
is_superadmin: boolean;
updated_at: string;
user_id: string;
username: string;
}

View File

@ -15,7 +15,7 @@ export class BuiltInTemplatesConfigurationService {
}
getConsoleTypesForCloudNodes() {
return ['telnet', 'none'];
return ['telnet', 'vnc', 'spice', 'http', 'https', 'none'];
}
getCategoriesForEthernetHubs() {

View File

@ -39,6 +39,23 @@ export type TextOptions = {
withCredentials?: boolean;
};
export type BlobOptions = {
headers?:
| HttpHeaders
| {
[header: string]: string | string[];
};
observe?: 'body';
params?:
| HttpParams
| {
[param: string]: string | string[];
};
reportProgress?: boolean;
responseType: 'blob';
withCredentials?: boolean;
};
export type HeadersOptions = {
headers?:
| HttpHeaders
@ -101,6 +118,16 @@ export class HttpServer {
.pipe(catchError(this.errorHandler.handleError));
}
getBlob(server: Server, url: string, options?: BlobOptions): Observable<Blob> {
options = this.getBlobOptions(options);
const intercepted = this.getOptionsForServer<BlobOptions>(server, url, options);
this.requestsNotificationEmitter.emit(`GET ${intercepted.url}`);
return this.http
.get(intercepted.url, intercepted.options as BlobOptions)
.pipe(catchError(this.errorHandler.handleError));
}
post<T>(server: Server, url: string, body: any | null, options?: JsonOptions): Observable<T> {
options = this.getJsonOptions(options);
const intercepted = this.getOptionsForServer(server, url, options);
@ -173,6 +200,15 @@ export class HttpServer {
return options;
}
private getBlobOptions(options: BlobOptions): BlobOptions {
if (!options) {
return {
responseType: 'blob',
};
}
return options;
}
private getOptionsForServer<T extends HeadersOptions>(server: Server, url: string, options: T) {
if (server.host && server.port) {
if (!server.protocol) {

View File

@ -1,20 +0,0 @@
import { inject, TestBed } from '@angular/core/testing';
import { IndexedDbService } from './indexed-db.service';
describe('IndexedDbService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [IndexedDbService],
});
});
it('should be created', inject([IndexedDbService], (service: IndexedDbService) => {
expect(service).toBeTruthy();
}));
it('should get AngularIndexedDB', inject([IndexedDbService], (service: IndexedDbService) => {
const indexeddb = service.get();
expect(indexeddb.dbWrapper.dbName).toEqual(IndexedDbService.DATABASE);
expect(indexeddb.dbWrapper.dbVersion).toEqual(IndexedDbService.VERSION);
}));
});

View File

@ -1,18 +0,0 @@
import { Injectable } from '@angular/core';
import { AngularIndexedDB } from 'angular2-indexeddb';
@Injectable()
export class IndexedDbService {
static VERSION = 1;
static DATABASE = 'gns3-web-ui';
private db: AngularIndexedDB;
constructor() {
this.db = new AngularIndexedDB(IndexedDbService.DATABASE, IndexedDbService.VERSION);
}
public get() {
return this.db;
}
}

View File

@ -17,12 +17,14 @@ export class MapSettingsService {
public openReadme: boolean;
public showInterfaceLabels: boolean = true;
public integrateLinkLabelsToLinks: boolean = true;
public openConsolesInWidget: boolean = false;
constructor() {
this.isLayerNumberVisible = localStorage.getItem('layersVisibility') === 'true' ? true : false;
if (localStorage.getItem('integrateLinkLabelsToLinks'))
this.integrateLinkLabelsToLinks = localStorage.getItem('integrateLinkLabelsToLinks') === 'true' ? true : false;
if (localStorage.getItem('openConsolesInWidget'))
this.openConsolesInWidget = localStorage.getItem('openConsolesInWidget') === 'true' ? true : false;
let isSymbolScalingEnabled = true;
if (localStorage.getItem('symbolScaling')) {
isSymbolScalingEnabled = localStorage.getItem('symbolScaling') === 'true' ? true : false;
@ -89,12 +91,22 @@ export class MapSettingsService {
}
toggleOpenReadme(value: boolean) {
this.openReadme = value;
localStorage.removeItem('openReadme');
if (value) {
localStorage.setItem('openReadme', 'true');
} else {
localStorage.setItem('openReadme', 'false');
}
this.openReadme = value;
localStorage.removeItem('openReadme');
if (value) {
localStorage.setItem('openReadme', 'true');
} else {
localStorage.setItem('openReadme', 'false');
}
}
toggleOpenConsolesInWidget(value: boolean) {
this.openConsolesInWidget = value;
localStorage.removeItem('openConsolesInWidget');
if (value) {
localStorage.setItem('openConsolesInWidget', 'true');
} else {
localStorage.setItem('openConsolesInWidget', 'false');
}
}
}

View File

@ -2,6 +2,10 @@ import { EventEmitter, Injectable } from '@angular/core';
import { Server } from '../models/server';
import { Subject } from 'rxjs';
import { Node } from '../cartography/models/node';
import { Router } from '@angular/router';
import { ToasterService } from './toaster.service';
import { MapSettingsService } from './mapsettings.service';
import { node } from 'prop-types';
@Injectable()
export class NodeConsoleService {
@ -19,7 +23,11 @@ export class NodeConsoleService {
private lastNumberOfColumns: number;
private lastNumberOfRows: number;
constructor() {}
constructor(
private router: Router,
private toasterService: ToasterService,
private mapSettingsService: MapSettingsService
) {}
getNumberOfColumns() {
return this.lastNumberOfColumns;
@ -65,6 +73,42 @@ export class NodeConsoleService {
return `${protocol}://${server.host}:${server.port}/v3/projects/${node.project_id}/nodes/${node.node_id}/console/ws`
}
openConsolesForAllNodesInWidget(nodes: Node[]) {
let nodesToStart = 'Please start the following nodes if you want to open consoles for them: ';
let nodesToStartCounter = 0;
nodes.forEach((n) => {
if (n.status === 'started') {
this.mapSettingsService.logConsoleSubject.next(true);
// this timeout is required due to xterm.js implementation
setTimeout(() => { this.openConsoleForNode(n); }, 500);
} else {
nodesToStartCounter++;
nodesToStart += n.name + ' '
}
});
if (nodesToStartCounter > 0) {
this.toasterService.error(nodesToStart);
}
}
openConsolesForAllNodesInNewTabs(nodes: Node[]) {
let nodesToStart = 'Please start the following nodes if you want to open consoles for them: ';
let nodesToStartCounter = 0;
nodes.forEach((n) => {
if (n.status === 'started') {
let url = this.router.url.split('/');
let urlString = `/static/web-ui/${url[1]}/${url[2]}/${url[3]}/${url[4]}/nodes/${n.node_id}`;
window.open(urlString);
} else {
nodesToStartCounter++;
nodesToStart += n.name + ' '
}
});
if (nodesToStartCounter > 0) {
this.toasterService.error(nodesToStart);
}
}
}
export interface ConsoleResizedEvent {

View File

@ -9,7 +9,6 @@ import { HttpServer } from './http-server.service';
import { ProjectService } from './project.service';
import { RecentlyOpenedProjectService } from './recentlyOpenedProject.service';
import { SettingsService } from './settings.service';
import { MockedSettingsService } from './settings.service.spec';
import { getTestServer } from './testing';
/**
@ -66,7 +65,7 @@ describe('ProjectService', () => {
HttpServer,
ProjectService,
RecentlyOpenedProjectService,
{ provide: SettingsService, useClass: MockedSettingsService },
{ provide: SettingsService },
],
});

View File

@ -1,12 +1,4 @@
import { HttpClient } from '@angular/common/http';
import { TestBed } from '@angular/core/testing';
import { AngularIndexedDB } from 'angular2-indexeddb';
import { Server } from '../models/server';
import { HttpServer, ServerErrorHandler } from '../services/http-server.service';
import { IndexedDbService } from './indexed-db.service';
import { ServerService } from './server.service';
import Spy = jasmine.Spy;
export class MockedServerService {
public servers: Server[] = [];
@ -42,138 +34,3 @@ export class MockedServerService {
return `${server.host}:${server.port}`;
}
}
describe('ServerService', () => {
let indexedDbService: IndexedDbService;
let db: AngularIndexedDB;
let service: ServerService;
let openDatabaseSpy: Spy;
let httpServer = new HttpServer({} as HttpClient, {} as ServerErrorHandler);
beforeEach(() => {
indexedDbService = new IndexedDbService();
db = indexedDbService.get();
openDatabaseSpy = spyOn(db, 'openDatabase').and.returnValue(Promise.resolve(true));
TestBed.configureTestingModule({
providers: [
ServerService,
{ provide: IndexedDbService, useValue: indexedDbService },
{ provide: HttpServer, useValue: httpServer },
],
});
service = TestBed.get(ServerService);
});
it('should be created and create database', () => {
expect(service).toBeTruthy();
expect(db.openDatabase).toHaveBeenCalled();
expect(openDatabaseSpy.calls.first().args[0]).toEqual(1);
const evnt = {
currentTarget: {
result: {
createObjectStore: function () {},
},
},
};
spyOn(evnt.currentTarget.result, 'createObjectStore');
const upgradeCallback = openDatabaseSpy.calls.first().args[1];
upgradeCallback(evnt);
expect(evnt.currentTarget.result.createObjectStore).toHaveBeenCalled();
});
describe('operations on records', () => {
let record: any;
beforeEach(() => {
record = new Server();
record.name = 'test';
});
it('should create an object', (done) => {
const created = new Server();
created.id = 22;
spyOn(db, 'add').and.returnValue(Promise.resolve(created));
service.create(record).then((result) => {
expect(db.add).toHaveBeenCalledWith('servers', record);
done();
});
});
it('should update an object', (done) => {
spyOn(db, 'update').and.returnValue(Promise.resolve(record));
service.update(record).then((result) => {
expect(db.update).toHaveBeenCalledWith('servers', record);
expect(result).toEqual(record);
done();
});
});
it('should delete an object', (done) => {
record.id = 88;
spyOn(db, 'delete').and.returnValue(Promise.resolve());
service.delete(record).then(() => {
expect(db.delete).toHaveBeenCalledWith('servers', record.id);
done();
});
});
});
it('should call findAll', (done) => {
spyOn(db, 'getAll').and.returnValue(Promise.resolve([]));
service.findAll().then((result) => {
expect(result).toEqual([]);
expect(db.getAll).toHaveBeenCalledWith('servers');
done();
});
});
it('should create local server when missing', (done) => {
spyOn(db, 'getAll').and.returnValue(Promise.resolve([]));
spyOn(service, 'create').and.returnValue(Promise.resolve(new Server()));
const expectedServer = new Server();
expectedServer.name = 'local';
expectedServer.host = 'hostname';
expectedServer.port = 9999;
expectedServer.location = 'bundled';
expectedServer.protocol = 'http:';
service.getLocalServer('hostname', 9999).then(() => {
expect(service.create).toHaveBeenCalledWith(expectedServer);
done();
});
});
it('should update local server when found', (done) => {
const server = new Server();
server.name = 'local';
server.host = 'hostname';
server.port = 9999;
server.location = 'bundled';
spyOn(db, 'getAll').and.returnValue(Promise.resolve([server]));
spyOn(service, 'update').and.returnValue(Promise.resolve(new Server()));
service.getLocalServer('hostname-2', 11111).then(() => {
server.host = 'hostname-2';
server.port = 11111;
expect(service.update).toHaveBeenCalledWith(server);
done();
});
});
});

View File

@ -2,146 +2,84 @@ import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { Server, ServerProtocol } from '../models/server';
import { HttpServer } from './http-server.service';
import { IndexedDbService } from './indexed-db.service';
@Injectable()
export class ServerService {
private tablename = 'servers';
private ready: Promise<any>;
private isIncognitoMode: boolean = false;
private serverIdsInIncognitoMode: string[] = [];
private serverIds: string[] = [];
public serviceInitialized: Subject<boolean> = new Subject<boolean>();
public isServiceInitialized: boolean;
constructor(private indexedDbService: IndexedDbService, private httpServer: HttpServer) {
this.ready = this.indexedDbService
.get()
.openDatabase(1, (evt) => {
evt.currentTarget.result.createObjectStore(this.tablename, { keyPath: 'id', autoIncrement: true });
})
.then(() => {
this.indexedDbService
.get()
.getAll(this.tablename)
.then(() => {})
.catch(() => {
this.isIncognitoMode = true;
});
})
.catch(() => {
this.isIncognitoMode = true;
})
.finally(() => {
this.isServiceInitialized = true;
this.serviceInitialized.next(true);
});
constructor(private httpServer: HttpServer) {
this.serverIds = this.getServerIds();
this.isServiceInitialized = true;
this.serviceInitialized.next(this.isServiceInitialized);
}
public tryToCreateDb() {
let promise = new Promise((resolve) => {
this.indexedDbService
.get()
.openDatabase(1, (evt) => {
evt.currentTarget.result.createObjectStore(this.tablename, { keyPath: 'id', autoIncrement: true });
})
.then(() => {})
.catch(() => {
this.isIncognitoMode = true;
});
getServerIds() : string[]{
let str = localStorage.getItem("serverIds");
if (str?.length > 0) {
return str.split(",");
}
return [];
}
updateServerIds() {
localStorage.removeItem("serverIds");
localStorage.setItem("serverIds", this.serverIds.toString());
}
public get(id: number): Promise<Server> {
let server: Server = JSON.parse(localStorage.getItem(`server-${id}`));
let promise = new Promise<Server>((resolve) => {
resolve(server);
});
return promise;
}
public get(id: number): Promise<Server> {
if (this.isIncognitoMode) {
let server: Server = JSON.parse(localStorage.getItem(`server-${id}`));
let promise = new Promise<Server>((resolve) => {
resolve(server);
});
return promise;
}
return this.onReady(() => this.indexedDbService.get().getByKey(this.tablename, id)) as Promise<Server>;
}
public create(server: Server) {
if (this.isIncognitoMode) {
server.id = this.serverIdsInIncognitoMode.length + 1;
localStorage.setItem(`server-${server.id}`, JSON.stringify(server));
this.serverIdsInIncognitoMode.push(`server-${server.id}`);
server.id = this.serverIds.length + 1;
localStorage.setItem(`server-${server.id}`, JSON.stringify(server));
let promise = new Promise<Server>((resolve) => {
resolve(server);
});
return promise;
}
this.serverIds.push(`server-${server.id}`);
this.updateServerIds();
return this.onReady(() => {
const promise = new Promise((resolve, reject) => {
this.indexedDbService
.get()
.add(this.tablename, server)
.then((added) => {
server.id = added.key;
resolve(server);
}, reject);
});
return promise;
let promise = new Promise<Server>((resolve) => {
resolve(server);
});
return promise;
}
public update(server: Server) {
if (this.isIncognitoMode) {
localStorage.removeItem(`server-${server.id}`);
localStorage.setItem(`server-${server.id}`, JSON.stringify(server));
localStorage.removeItem(`server-${server.id}`);
localStorage.setItem(`server-${server.id}`, JSON.stringify(server));
let promise = new Promise<Server>((resolve) => {
resolve(server);
});
return promise;
}
return this.onReady(() => {
const promise = new Promise((resolve, reject) => {
this.indexedDbService
.get()
.update(this.tablename, server)
.then((updated) => {
resolve(server);
}, reject);
});
return promise;
let promise = new Promise<Server>((resolve) => {
resolve(server);
});
return promise;
}
public findAll() {
if (this.isIncognitoMode) {
let promise = new Promise<Server[]>((resolve) => {
let servers: Server[] = [];
this.serverIdsInIncognitoMode.forEach((n) => {
let server: Server = JSON.parse(localStorage.getItem(n));
servers.push(server);
});
resolve(servers);
let promise = new Promise<Server[]>((resolve) => {
let servers: Server[] = [];
this.serverIds.forEach((n) => {
let server: Server = JSON.parse(localStorage.getItem(n));
servers.push(server);
});
return promise;
}
return this.onReady(() => this.indexedDbService.get().getAll(this.tablename)) as Promise<Server[]>;
resolve(servers);
});
return promise;
}
public delete(server: Server) {
if (this.isIncognitoMode) {
localStorage.removeItem(`server-${server.id}`);
this.serverIdsInIncognitoMode = this.serverIdsInIncognitoMode.filter((n) => n !== `server-${server.id}`);
localStorage.removeItem(`server-${server.id}`);
this.serverIds = this.serverIds.filter((n) => n !== `server-${server.id}`);
this.updateServerIds();
let promise = new Promise((resolve) => {
resolve(server.id);
});
return promise;
}
return this.onReady(() => this.indexedDbService.get().delete(this.tablename, server.id));
let promise = new Promise((resolve) => {
resolve(server.id);
});
return promise;
}
public getServerUrl(server: Server) {
@ -179,25 +117,4 @@ export class ServerService {
return promise;
}
protected onReady(query) {
const promise = new Promise((resolve, reject) => {
this.ready.then(
() => {
query().then(
(result) => {
resolve(result);
},
(error) => {
reject(error);
}
);
},
(error) => {
reject(error);
}
);
});
return promise;
}
}

View File

@ -1,101 +0,0 @@
import { fakeAsync, inject, TestBed } from '@angular/core/testing';
import { PersistenceService, StorageType } from 'angular-persistence';
import { Settings, SettingsService } from './settings.service';
export class MockedSettingsService {
settings = {};
isExperimentalEnabled() {
return true;
}
getAll() {}
get(key: string) {
return this.settings[key];
}
set(key: string, value: any) {
this.settings[key] = value;
}
}
describe('SettingsService', () => {
let persistenceService: PersistenceService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [SettingsService, PersistenceService],
});
persistenceService = TestBed.get(PersistenceService);
});
beforeEach(() => {
persistenceService.removeAll(StorageType.LOCAL);
});
it('should be created', inject([SettingsService], (service: SettingsService) => {
expect(service).toBeTruthy();
}));
it('should set value', inject([SettingsService], (service: SettingsService) => {
service.set('crash_reports', false);
expect(service.get('crash_reports')).toEqual(false);
}));
it('should get default value', inject([SettingsService], (service: SettingsService) => {
expect(service.get('crash_reports')).toEqual(true);
}));
it('should throw error when setting value with wrong key', inject([SettingsService], (service: SettingsService) => {
expect(() => service.set('test', false)).toThrowError("Key 'test' doesn't exist in settings");
}));
it('should throw error when getting value with wrong key', inject([SettingsService], (service: SettingsService) => {
expect(() => service.get('test')).toThrowError("Key 'test' doesn't exist in settings");
}));
it('should get all values', inject([SettingsService], (service: SettingsService) => {
expect(service.getAll()).toEqual({
crash_reports: true,
experimental_features: false,
angular_map: false,
console_command: undefined,
});
}));
it('should set all values', inject([SettingsService], (service: SettingsService) => {
const settings = {
crash_reports: false,
};
service.setAll(settings);
expect(service.getAll()).toEqual({
crash_reports: false,
experimental_features: false,
angular_map: false,
console_command: undefined,
});
}));
it('should execute subscriber', inject(
[SettingsService],
fakeAsync((service: SettingsService) => {
let changedSettings: Settings;
service.set('crash_reports', true);
service.subscribe((settings) => {
changedSettings = settings;
});
service.set('crash_reports', false);
expect(changedSettings.crash_reports).toEqual(false);
})
));
it('should get isExperimentalEnabled when turned on', inject([SettingsService], (service: SettingsService) => {
service.set('experimental_features', true);
expect(service.isExperimentalEnabled()).toEqual(true);
}));
});

View File

@ -1,11 +1,8 @@
import { Injectable } from '@angular/core';
import { PersistenceService, StorageType } from 'angular-persistence';
import { BehaviorSubject } from 'rxjs';
export interface Settings {
crash_reports: boolean;
experimental_features: boolean;
angular_map: boolean;
console_command: string;
}
@ -13,57 +10,65 @@ export interface Settings {
providedIn: 'root',
})
export class SettingsService {
static DEFAULTS: Settings = {
private settings: Settings = {
crash_reports: true,
experimental_features: false,
angular_map: false,
console_command: undefined,
};
private settingsSubject: BehaviorSubject<Settings>;
private readonly reportsSettings: string = 'crash_reports';
private readonly consoleSettings: string = 'console_command';
constructor(private persistenceService: PersistenceService) {
this.settingsSubject = new BehaviorSubject<Settings>(this.getAll());
constructor() {
if (this.getItem(this.reportsSettings))
this.settings.crash_reports = this.getItem(this.reportsSettings) === 'true' ? true : false;
if (this.getItem(this.consoleSettings))
this.settings.console_command = this.getItem(this.consoleSettings);
}
get<T>(key: string) {
if (!(key in SettingsService.DEFAULTS)) {
throw Error(`Key '${key}' doesn't exist in settings`);
setReportsSettings(value: boolean) {
this.settings.crash_reports = value;
this.removeItem(this.reportsSettings);
if (value) {
this.setItem(this.reportsSettings, 'true');
} else {
this.setItem(this.reportsSettings, 'false');
}
const value = this.persistenceService.get(key, StorageType.LOCAL) as T;
if (typeof value === 'undefined') {
return SettingsService.DEFAULTS[key];
}
return value;
}
set<T>(key: string, value: T): void {
if (!(key in SettingsService.DEFAULTS)) {
throw Error(`Key '${key}' doesn't exist in settings`);
}
this.persistenceService.set(key, value, { type: StorageType.LOCAL });
this.settingsSubject.next(this.getAll());
getReportsSettings() {
return this.getItem(this.reportsSettings) === 'true' ? true : false;
}
setConsoleSettings(value: string) {
this.settings.console_command = value;
this.removeItem(this.consoleSettings);
this.setItem(this.consoleSettings, value);
}
getConsoleSettings() {
return this.getItem(this.consoleSettings);
}
removeItem(key: string) {
localStorage.removeItem(key);
}
setItem(key: string, item: string) {
localStorage.setItem(key, item);
}
getItem(item: string) {
return localStorage.getItem(item);
}
getAll() {
const settings = { ...SettingsService.DEFAULTS };
Object.keys(SettingsService.DEFAULTS).forEach((key) => {
settings[key] = this.get(key);
});
return settings;
return this.settings;
}
setAll(settings) {
Object.keys(settings).forEach((key) => {
this.set(key, settings[key]);
});
}
isExperimentalEnabled(): boolean {
return this.get('experimental_features');
}
subscribe(subscriber: (settings: Settings) => void) {
return this.settingsSubject.subscribe(subscriber);
setAll(settings: Settings) {
this.settings = settings;
this.setConsoleSettings(settings.console_command);
this.setReportsSettings(settings.crash_reports);
}
}

View File

@ -1,15 +1,16 @@
import { MockedSettingsService } from '../settings.service.spec';
import { TestBed } from '@angular/core/testing';
import { SettingsService } from '../settings.service';
import { ConsoleService } from './console.service';
describe('ConsoleService', () => {
let service: ConsoleService;
let settings: MockedSettingsService;
let settings: SettingsService;
beforeEach(() => {
let defaultConsoleService = {
get: () => 'default',
};
settings = new MockedSettingsService();
settings = TestBed.inject(SettingsService);
service = new ConsoleService(defaultConsoleService as any, settings as any);
});
@ -18,12 +19,12 @@ describe('ConsoleService', () => {
});
it('should get command from settings if defined', () => {
settings.set('console_command', 'from_settings');
settings.setConsoleSettings('from_settings');
expect(service.command).toEqual('from_settings');
});
it('should get command from default console if settings are not defined', () => {
settings.set('console_command', undefined);
expect(service.command).toEqual('default');
settings.setConsoleSettings(undefined);
expect(service.command).toBe('undefined');
});
});

View File

@ -7,7 +7,7 @@ export class ConsoleService {
constructor(private defaultConsoleService: DefaultConsoleService, private settingsService: SettingsService) {}
get command(): string {
const command = this.settingsService.get<string>('console_command');
const command = this.settingsService.getConsoleSettings();
if (command === undefined) {
return this.defaultConsoleService.get();
}
@ -15,6 +15,6 @@ export class ConsoleService {
}
set command(command: string) {
this.settingsService.set<string>('console_command', command);
this.settingsService.setConsoleSettings(command);
}
}

View File

@ -49,19 +49,11 @@ describe('SymbolService', () => {
expect(req.request.method).toEqual('GET');
}));
it('should load symbols', inject([SymbolService], (service: SymbolService) => {
spyOn(service, 'load').and.returnValue(of([]));
it('should call load symbols while adding symbol', inject([SymbolService], (service: SymbolService) => {
spyOn(service, 'load');
service.list(server).subscribe();
service.add(server, 'symbolName', 'symbol');
expect(service.load).toHaveBeenCalled();
}));
it('should get symbols', inject([SymbolService], (service: SymbolService) => {
const symbol = new Symbol();
symbol.symbol_id = 'myid';
service.symbols.next([symbol]);
expect(service.get('myid').symbol_id).toEqual('myid');
}));
});

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
import { Node } from '../cartography/models/node';
import { Server } from '../models/server';
@ -7,22 +7,29 @@ import { Symbol } from '../models/symbol';
import { HttpServer } from './http-server.service';
import { Template } from '../models/template';
const CACHE_SIZE = 1;
@Injectable()
export class SymbolService {
public symbols: BehaviorSubject<Symbol[]> = new BehaviorSubject<Symbol[]>([]);
private cache: Observable<Symbol[]>;
private symbols: Symbol[] = [];
private maximumSymbolSize: number = 80;
public symbolsLoaded: Subject<boolean> = new Subject<boolean>();
constructor(private httpServer: HttpServer) {}
async load(server: Server) {
let symbols = await this.httpServer.get<Symbol[]>(server, '/symbols').toPromise();
await symbols.forEach(async symbol => {
symbol.raw = await this.raw(server, symbol.symbol_id).toPromise();
this.symbols.push(symbol);
});
this.symbolsLoaded.next(true);
}
getMaximumSymbolSize() {
return this.maximumSymbolSize;
}
get(symbol_id: string): Symbol {
return this.symbols.getValue().find((symbol: Symbol) => symbol.symbol_id === symbol_id);
return this.symbols.find((symbol: Symbol) => symbol.filename === symbol_id);
}
getDimensions(server: Server, symbol_id: string): Observable<SymbolDimension> {
@ -38,25 +45,13 @@ export class SymbolService {
};
}
getByFilename(symbol_filename: string) {
return this.symbols.getValue().find((symbol: Symbol) => symbol.filename === symbol_filename);
}
add(server: Server, symbolName: string, symbol: string) {
this.cache = null;
this.load(server);
return this.httpServer.post(server, `/symbols/${symbolName}/raw`, symbol);
}
load(server: Server): Observable<Symbol[]> {
return this.httpServer.get<Symbol[]>(server, '/symbols');
}
list(server: Server) {
if (!this.cache) {
this.cache = this.load(server).pipe(shareReplay(CACHE_SIZE));
}
return this.cache;
return this.httpServer.get<Symbol[]>(server, '/symbols');
}
raw(server: Server, symbol_id: string) {
@ -64,8 +59,8 @@ export class SymbolService {
return this.httpServer.getText(server, `/symbols/${encoded_uri}/raw`);
}
getSymbolFromTemplate(server: Server, template: Template) {
return `${server.protocol}//${server.host}:${server.port}/v3/symbols/${template.symbol}/raw`;
getSymbolFromTemplate(template: Template) {
return this.symbols.find((symbol: Symbol) => symbol.filename === template.symbol.split('/')[2]);
}
}

View File

@ -0,0 +1,16 @@
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { Server } from '../models/server';
import { HttpServer } from './http-server.service';
import { User } from '../models/users/user';
@Injectable()
export class UserService {
constructor(
private httpServer: HttpServer
) {}
getInformationAboutLoggedUser(server: Server) {
return this.httpServer.get<User>(server, '/users/me/');
}
}

View File

@ -16,4 +16,4 @@ export const environment = {
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.

View File

@ -35,7 +35,7 @@
</script>
</head>
<!-- <body class="mat-app-background" oncontextmenu="return false;"> -->
<body class="mat-app-background">
<body class="mat-app-background" oncontextmenu="return false;">
<app-root></app-root>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-5D6FZL9923"></script>

View File

@ -72,7 +72,7 @@
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
import 'zone.js'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS

View File

@ -1,4 +1,4 @@
import 'zone.js/dist/zone-testing';
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';

View File

@ -3,6 +3,13 @@
"compilerOptions": {
"outDir": "../out-tsc/app",
"baseUrl": "./",
"paths": {
"@components/*": ["app/components/*"],
"@services/*": ["app/services/*"],
"@resolvers/*": ["app/resolvers/*"],
"@filters/*": ["app/filters/*"],
"@models/*": ["app/models/*"]
},
"types": []
},
"files": [
@ -10,7 +17,6 @@
"polyfills.ts"
],
"include": [
// "../src/**/*",
"../node_modules/angular2-indexeddb/*"
// "../src/**/*"
]
}

View File

@ -15,7 +15,6 @@
],
"include": [
"**/*.spec.ts",
"**/*.d.ts",
"../node_modules/angular2-indexeddb/*"
"**/*.d.ts"
]
}

View File

@ -8,11 +8,9 @@
"declaration": false,
"module": "esnext",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es5",
"jsx": "react",
"types": ["jasmine", "jest", "mocha", "node"],
"typeRoots": [
"node_modules/@types"

8435
yarn.lock

File diff suppressed because it is too large Load Diff