diff --git a/@overflow/commons/ui/component/primeng/popup-panel.component.ts b/@overflow/commons/ui/component/primeng/popup-panel.component.ts index 64daa3f..db3214d 100644 --- a/@overflow/commons/ui/component/primeng/popup-panel.component.ts +++ b/@overflow/commons/ui/component/primeng/popup-panel.component.ts @@ -98,7 +98,7 @@ export class PopupPanelComponent implements OnDestroy { } onOverlayAnimationDone(event: AnimationEvent) { - console.log('onOverlayAnimationDone'); + } appendOverlay() { diff --git a/@overflow/commons/ui/directive/auto-height.directive.ts b/@overflow/commons/ui/directive/auto-height.directive.ts index d4ed6d6..1a6655f 100644 --- a/@overflow/commons/ui/directive/auto-height.directive.ts +++ b/@overflow/commons/ui/directive/auto-height.directive.ts @@ -30,10 +30,6 @@ export class AutoHeightDirective implements OnInit, OnDestroy, AfterViewInit { ngOnDestroy(): void { } - onVisible(event) { - console.log('visibilitychange'); - } - /** * forceCalculate */ @@ -51,7 +47,6 @@ export class AutoHeightDirective implements OnInit, OnDestroy, AfterViewInit { const footerElementMargin = this.getfooterElementMargin(); this.elementRef.nativeElement.style.height = windowHeight - footerElementMargin - elementOffsetTop + 'px'; - console.log([windowHeight, elementOffsetTop, elementMarginBottom, footerElementMargin, this.elementRef.nativeElement.style.height]); } private getElementOffsetTop() { diff --git a/@overflow/model/discovery/index.ts b/@overflow/model/discovery/index.ts index 8166441..f9ab223 100644 --- a/@overflow/model/discovery/index.ts +++ b/@overflow/model/discovery/index.ts @@ -1,3 +1,5 @@ +export * from './discovery'; + export * from './DiscoverHost'; export * from './DiscoverPort'; export * from './DiscoverService'; diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index d5a9bdd..61de7b5 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,12 +1,21 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; +import { PagesComponent } from './pages/pages.component'; +import { HomePageComponent } from './pages/home/home-page.component'; const routes: Routes = [ - { path: '', loadChildren: './pages/pages.module#PagesModule' }, + { path: '', redirectTo: 'home', pathMatch: 'full' }, + { + path: '', + component: PagesComponent, + children: [ + { path: 'home', component: HomePageComponent }, + ] + }, ]; @NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule] + imports: [RouterModule.forRoot(routes, { useHash: true })], + exports: [RouterModule], }) export class AppRoutingModule { } diff --git a/src/app/app-store.module.ts b/src/app/app-store.module.ts index 52cde6f..8a710a2 100644 --- a/src/app/app-store.module.ts +++ b/src/app/app-store.module.ts @@ -8,14 +8,14 @@ import { EffectsModule } from '@ngrx/effects'; import { environment } from '../environments/environment'; -import { reducers, metaReducers, effects } from '../commons/store'; +import { REDUCERS, META_REDUCERS, EFFECTS } from './store'; @NgModule({ exports: [ StoreModule, ], imports: [ - StoreModule.forRoot(reducers, { metaReducers }), + StoreModule.forRoot(REDUCERS, { metaReducers: META_REDUCERS }), /** * @ngrx/router-store keeps router state up-to-date in the store. */ @@ -41,7 +41,7 @@ import { reducers, metaReducers, effects } from '../commons/store'; maxAge: 50, logOnly: environment.production, }), - EffectsModule.forRoot(effects), + EffectsModule.forRoot(EFFECTS), ], providers: [ ], diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 9a95840..b546585 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,18 +1,21 @@ -import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core'; +import { Component, OnInit, OnDestroy, ChangeDetectorRef, AfterContentInit, AfterViewInit } from '@angular/core'; + +import { Store, select } from '@ngrx/store'; import { of, Subscription } from 'rxjs'; import { map, catchError, take } from 'rxjs/operators'; -import { ElectronProxyService } from '../commons/service/electron-proxy.service'; -import { ProbeService } from '../commons/service/probe.service'; +import { ElectronProxyService } from './service/electron-proxy.service'; import { MenuEvent } from '../commons/type'; +import * as AppStore from './store/app'; +import * as UserStore from './store/environment/user'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) -export class AppComponent implements OnInit, OnDestroy { +export class AppComponent implements OnInit, AfterContentInit, AfterViewInit, OnDestroy { title = 'scanner-app'; showTitleBar: boolean; block: boolean; @@ -25,6 +28,7 @@ export class AppComponent implements OnInit, OnDestroy { public constructor( private changeDetector: ChangeDetectorRef, + private store: Store, private electronProxyService: ElectronProxyService, ) { // this.showTitleBar = !__LINUX__; @@ -33,6 +37,8 @@ export class AppComponent implements OnInit, OnDestroy { } ngOnInit(): void { + this.store.dispatch(new AppStore.AppInit()); + this.store.dispatch(new UserStore.SetMemberID({ memberID: 'scannerUser' })); // this.probeService.connect(); this.electronProxyService.sendReady(performance.now()); this.menuSubscription = this.electronProxyService.menuObservable() @@ -64,7 +70,19 @@ export class AppComponent implements OnInit, OnDestroy { } + ngAfterContentInit(): void { + this.store.dispatch(new AppStore.AppAfterContentInit()); + + } + + ngAfterViewInit(): void { + this.store.dispatch(new AppStore.AppAfterViewInit()); + } + + ngOnDestroy(): void { + this.store.dispatch(new AppStore.AppDestroy()); + this.menuSubscription.unsubscribe(); } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index c884970..f1c6e4f 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,30 +4,50 @@ import { NgModule } from '@angular/core'; import { CommonsUIModule } from '@overflow/commons/ui/commons-ui.module'; +import { + PerfectScrollbarModule, + PERFECT_SCROLLBAR_CONFIG, + PerfectScrollbarConfigInterface, +} from 'ngx-perfect-scrollbar'; + + import { AppRoutingModule } from './app-routing.module'; import { AppStoreModule } from './app-store.module'; import { AppComponent } from './app.component'; +import { COMPONENTS } from './component'; +import { PAGES } from './pages'; + +import { SERVICES } from './service'; + +const DEFAULT_PERFECT_SCROLLBAR_CONFIG: PerfectScrollbarConfigInterface = { + suppressScrollX: true +}; -import { CommonsModule } from '../commons/commons.module'; -import { SERVICES } from '../commons/service'; @NgModule({ - declarations: [ - AppComponent - ], imports: [ BrowserModule, BrowserAnimationsModule, + PerfectScrollbarModule, + CommonsUIModule, - CommonsModule, AppRoutingModule, AppStoreModule, ], + declarations: [ + AppComponent, + ...COMPONENTS, + ...PAGES, + ], providers: [ ...SERVICES, + { + provide: PERFECT_SCROLLBAR_CONFIG, + useValue: DEFAULT_PERFECT_SCROLLBAR_CONFIG + } ], bootstrap: [AppComponent] }) diff --git a/src/app/component/index.ts b/src/app/component/index.ts new file mode 100644 index 0000000..3069d82 --- /dev/null +++ b/src/app/component/index.ts @@ -0,0 +1,18 @@ +import { + COMPONENTS as LAYOUT_COMPONENTS +} from './layout'; + +import { + COMPONENTS as MENU_COMPONENTS +} from './menu'; + +import { + COMPONENTS as TARGET_COMPONENTS +} from './target'; + + +export const COMPONENTS = [ + ...LAYOUT_COMPONENTS, + ...MENU_COMPONENTS, + ...TARGET_COMPONENTS, +]; diff --git a/src/app/component/layout/index.ts b/src/app/component/layout/index.ts new file mode 100644 index 0000000..8cdbdd6 --- /dev/null +++ b/src/app/component/layout/index.ts @@ -0,0 +1,8 @@ +import { + COMPONENTS as TOOLBAR_COMPONENTS +} from './toolbar'; + + +export const COMPONENTS = [ + ...TOOLBAR_COMPONENTS, +]; diff --git a/src/app/component/layout/toolbar/index.ts b/src/app/component/layout/toolbar/index.ts new file mode 100644 index 0000000..953e13e --- /dev/null +++ b/src/app/component/layout/toolbar/index.ts @@ -0,0 +1,7 @@ +import { NicDropdownComponent } from './nic-dropdown.component'; +import { ScannerSettingDropdownComponent } from './scanner-setting-dropdown.component'; + +export const COMPONENTS = [ + NicDropdownComponent, + ScannerSettingDropdownComponent, +]; diff --git a/src/commons/component/toolbar/nic-dropdown.component.html b/src/app/component/layout/toolbar/nic-dropdown.component.html similarity index 93% rename from src/commons/component/toolbar/nic-dropdown.component.html rename to src/app/component/layout/toolbar/nic-dropdown.component.html index dbb0920..31bd6be 100644 --- a/src/commons/component/toolbar/nic-dropdown.component.html +++ b/src/app/component/layout/toolbar/nic-dropdown.component.html @@ -4,11 +4,11 @@ -
+
{{selected.network || ''}}
{{selected.friendlyName || ''}}
- + Loading...
+ +
+ + + + + + + + + + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/src/commons/component/detail/node-detail.component.scss b/src/app/component/target/detail/node.component.scss similarity index 100% rename from src/commons/component/detail/node-detail.component.scss rename to src/app/component/target/detail/node.component.scss diff --git a/src/app/component/target/detail/node.component.ts b/src/app/component/target/detail/node.component.ts new file mode 100644 index 0000000..646e5f0 --- /dev/null +++ b/src/app/component/target/detail/node.component.ts @@ -0,0 +1,26 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { PingResult } from '@overflow/model/ping'; +import { Host, Port, Service } from '@overflow/model/discovery'; + + +@Component({ + selector: 'app-target-detail-node', + templateUrl: './node.component.html', + styleUrls: ['./node.component.scss'], +}) +export class NodeComponent { + + @Input() selectedTarget: { group: string, target: Host | Port | Service } | null; + @Output() otherHostSelect = new EventEmitter(); + @Output() ping = new EventEmitter(); + + constructor( + ) { + + } + + otherHostSelected(host: Host) { + this.otherHostSelect.emit(host); + } + +} diff --git a/src/commons/component/detail/service-detail.component.html b/src/app/component/target/detail/service.component.html similarity index 100% rename from src/commons/component/detail/service-detail.component.html rename to src/app/component/target/detail/service.component.html diff --git a/src/commons/component/detail/service-detail.component.scss b/src/app/component/target/detail/service.component.scss similarity index 100% rename from src/commons/component/detail/service-detail.component.scss rename to src/app/component/target/detail/service.component.scss diff --git a/src/commons/component/detail/service-detail.component.ts b/src/app/component/target/detail/service.component.ts similarity index 75% rename from src/commons/component/detail/service-detail.component.ts rename to src/app/component/target/detail/service.component.ts index 24e7cbc..540be31 100644 --- a/src/commons/component/detail/service-detail.component.ts +++ b/src/app/component/target/detail/service.component.ts @@ -1,17 +1,21 @@ import { Component, Input, OnChanges, SimpleChanges, ViewChildren, QueryList } from '@angular/core'; -import { Service } from '@overflow/model/discovery'; -import { PingResult } from '@overflow/model/ping'; -import { ProbeService } from '../../service/probe.service'; -import { map, catchError, take } from 'rxjs/operators'; + import { of } from 'rxjs'; +import { map, catchError, take } from 'rxjs/operators'; + +import { Service } from '@overflow/model/discovery'; import { AutoHeightDirective } from '@overflow/commons/ui/directive/auto-height.directive'; +import { PingResult } from '@overflow/model/ping'; + +import { PingService } from '../../../service/ping.service'; +import { PingOption } from '@overflow/model/config/ping'; @Component({ - selector: 'app-service-detail', - templateUrl: './service-detail.component.html', - styleUrls: ['./service-detail.component.scss'], + selector: 'app-target-detail-service', + templateUrl: './service.component.html', + styleUrls: ['./service.component.scss'], }) -export class ServiceDetailComponent implements OnChanges { +export class ServiceComponent implements OnChanges { @Input() service: Service; @@ -28,7 +32,7 @@ export class ServiceDetailComponent implements OnChanges { constructor( - private probeService: ProbeService + private pingService: PingService ) { this.pingWaiting = false; this.count = 5; @@ -44,14 +48,13 @@ export class ServiceDetailComponent implements OnChanges { doPing() { this.pingWaiting = true; - const option = { - Count: this.count, - Interval: this.interval, - Deadline: this.deadline, + const option: PingOption = { + count: this.count, + interval: this.interval, + deadline: this.deadline, }; - this.probeService - .call('PingService.PingService', this.service, option) + this.pingService.pingService(this.service, option) .pipe( map((pingResult: PingResult) => { console.log(pingResult); diff --git a/src/commons/component/detail/zone-detail.component.html b/src/app/component/target/detail/zone.component.html similarity index 100% rename from src/commons/component/detail/zone-detail.component.html rename to src/app/component/target/detail/zone.component.html diff --git a/src/commons/component/detail/zone-detail.component.scss b/src/app/component/target/detail/zone.component.scss similarity index 100% rename from src/commons/component/detail/zone-detail.component.scss rename to src/app/component/target/detail/zone.component.scss diff --git a/src/commons/component/detail/zone-detail.component.ts b/src/app/component/target/detail/zone.component.ts similarity index 84% rename from src/commons/component/detail/zone-detail.component.ts rename to src/app/component/target/detail/zone.component.ts index 5c3e610..bbb5db1 100644 --- a/src/commons/component/detail/zone-detail.component.ts +++ b/src/app/component/target/detail/zone.component.ts @@ -5,11 +5,11 @@ import { AutoHeightDirective } from '@overflow/commons/ui/directive/auto-height. const IPCIDR = require('ip-cidr'); @Component({ - selector: 'app-zone-detail', - templateUrl: './zone-detail.component.html', - styleUrls: ['./zone-detail.component.scss'], + selector: 'app-target-detail-zone', + templateUrl: './zone.component.html', + styleUrls: ['./zone.component.scss'], }) -export class ZoneDetailComponent implements OnInit { +export class ZoneComponent implements OnInit { @Input() zone: Zone; @Output() otherHostSelect = new EventEmitter(); diff --git a/src/app/component/target/display/display.component.html b/src/app/component/target/display/display.component.html new file mode 100644 index 0000000..342554b --- /dev/null +++ b/src/app/component/target/display/display.component.html @@ -0,0 +1,18 @@ +
+ + + + + + + +
+ + +
+ +
+
+ +
\ No newline at end of file diff --git a/src/app/component/target/display/display.component.scss b/src/app/component/target/display/display.component.scss new file mode 100644 index 0000000..11671d7 --- /dev/null +++ b/src/app/component/target/display/display.component.scss @@ -0,0 +1,21 @@ +/deep/ .target-display { + width: 100%; + height: 100vh; + margin: -0.6em -0.9em -0.7em -0.9em; //-0.5em -0.75em; + padding: 0; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + min-width: 400px; //text-align: center; +} + +.detail-sidebar { + top: 0; + left: 0; + height: 100%; +} + +/deep/ .ui-panel .ui-panel-content { + padding: .6em .75em; +} \ No newline at end of file diff --git a/src/app/component/target/display/display.component.ts b/src/app/component/target/display/display.component.ts new file mode 100644 index 0000000..d1113b3 --- /dev/null +++ b/src/app/component/target/display/display.component.ts @@ -0,0 +1,74 @@ +import { + Component, + OnInit, + OnDestroy, + AfterContentInit, + ChangeDetectorRef +} from '@angular/core'; + +import { Store, select } from '@ngrx/store'; + +import { Observable, Subscription } from 'rxjs'; +import { map, catchError, take, tap } from 'rxjs/operators'; + +import { Port, Service, Host } from '@overflow/model/discovery'; + +import { TargetDisplayType } from '../../../core/type'; +import * as AppStore from '../../../store'; +import * as TargetStore from '../../../store/target/target'; + + +@Component({ + selector: 'app-target-display', + templateUrl: './display.component.html', + styleUrls: ['./display.component.scss'] +}) +export class DisplayComponent implements OnInit, AfterContentInit, OnDestroy { + private targetDisplayType$: Observable; + + displaySidebar = false; + selectedTargetSubscription: Subscription | null; + selectedTarget: { group: string, target: Host | Port | Service } | null; + + constructor( + private store: Store, + private changeDetector: ChangeDetectorRef, + ) { + } + + ngOnInit(): void { + const __this = this; + + this.targetDisplayType$ = this.store.pipe(select(AppStore.TargetSelector.TargetSelector.selectTargetDisplayType)); + + this.selectedTargetSubscription = this.store.pipe( + select(AppStore.TargetSelector.TargetSelector.selectSelectedTarget) + ).pipe( + map((_selectedTarget) => { + if (null === _selectedTarget) { + __this.displaySidebar = false; + __this.selectedTarget = null; + } else { + __this.displaySidebar = true; + __this.selectedTarget = _selectedTarget; + } + __this.changeDetector.detectChanges(); + }), + ).subscribe(); + + } + + ngAfterContentInit(): void { + } + + ngOnDestroy(): void { + if (null !== this.selectedTargetSubscription) { + this.selectedTargetSubscription.unsubscribe(); + } + } + + onHideDetail() { + this.store.dispatch(new TargetStore.ChangeSelectedTarget(null)); + } + +} diff --git a/src/app/component/target/display/grid.component.html b/src/app/component/target/display/grid.component.html new file mode 100644 index 0000000..849c823 --- /dev/null +++ b/src/app/component/target/display/grid.component.html @@ -0,0 +1,3 @@ +
+ Grid +
\ No newline at end of file diff --git a/src/commons/component/discovery/discovery-map.component.scss b/src/app/component/target/display/grid.component.scss similarity index 100% rename from src/commons/component/discovery/discovery-map.component.scss rename to src/app/component/target/display/grid.component.scss diff --git a/src/app/component/target/display/grid.component.ts b/src/app/component/target/display/grid.component.ts new file mode 100644 index 0000000..4135610 --- /dev/null +++ b/src/app/component/target/display/grid.component.ts @@ -0,0 +1,31 @@ +import { + Component, + OnInit, + OnDestroy, + AfterContentInit +} from '@angular/core'; + +import { Store } from '@ngrx/store'; + +@Component({ + selector: 'app-target-display-grid', + templateUrl: './grid.component.html', + styleUrls: ['./grid.component.scss'] +}) +export class GridComponent implements OnInit, AfterContentInit, OnDestroy { + constructor( + private store: Store, + ) { + } + + ngOnInit(): void { + + } + + ngAfterContentInit(): void { + } + + ngOnDestroy(): void { + } + +} diff --git a/src/app/component/target/display/index.ts b/src/app/component/target/display/index.ts new file mode 100644 index 0000000..46a5c24 --- /dev/null +++ b/src/app/component/target/display/index.ts @@ -0,0 +1,11 @@ +import { DisplayComponent } from './display.component'; +import { GridComponent } from './grid.component'; +import { MapComponent } from './map.component'; +import { TreeComponent } from './tree.component'; + +export const COMPONENTS = [ + DisplayComponent, + GridComponent, + MapComponent, + TreeComponent, +]; diff --git a/src/commons/component/discovery/discovery-map.component.html b/src/app/component/target/display/map.component.html similarity index 89% rename from src/commons/component/discovery/discovery-map.component.html rename to src/app/component/target/display/map.component.html index 1d0c03b..d97d8e0 100644 --- a/src/commons/component/discovery/discovery-map.component.html +++ b/src/app/component/target/display/map.component.html @@ -1,5 +1,5 @@ -
- +
+ - +
-
Total Hosts:
{{discoveryResult.totalHosts}} +
Total Hosts:
{{displaySummary.totalHosts}}
-
Total Services:
{{discoveryResult.totalServices}} +
Total Ports:
{{displaySummary.totalPorts}} +
+
+
Total Services:
{{displaySummary.totalServices}}
-
Elapsed:
{{discoveryResult.elapsedTime}} +
Elapsed:
{{displaySummary.elapsedTime}}
\ No newline at end of file diff --git a/src/app/component/target/display/map.component.scss b/src/app/component/target/display/map.component.scss new file mode 100644 index 0000000..9c9ae95 --- /dev/null +++ b/src/app/component/target/display/map.component.scss @@ -0,0 +1,83 @@ +.link { + stroke: #999; + stroke-opacity: 0.6; +} + +.textClass { + stroke: #323232; + font-family: "Lucida Grande", "Droid Sans", Arial, Helvetica, sans-serif; + font-weight: normal; + stroke-width: .5; + font-size: 14px; +} + +.linkTextClass { + stroke: #b6b4b4; + font-family: "Lucida Grande", "Droid Sans", Arial, Helvetica, sans-serif; + font-weight: normal; + stroke-width: .3; + font-size: 9px; +} + +.focused { + opacity: 1 !important; +} + +.semi-focused { + opacity: 0.8 !important; +} + +.semi-unfocused { + opacity: 0.8; +} + +.unfocused { + opacity: 0.3; +} + +.semi-unselected { + opacity: 0.8; +} + +.unselected { + opacity: 0.3; +} + +.ui-map-info { + position: absolute; + top: 10px; + left: 10px; + .ui-map-info-row { + //display: inline; + font-size: 0.8em; + line-height: 2em; + width: 120px; + font-weight: bold; + user-select: text; + div { + display: inline-block; + font-weight: normal; + padding-left: 5px; + width: 75px; + user-select: text; + } + } + .ui-border-bottom { + border-bottom: 1px solid #d6d6d6; + } +} + +/deep/ .ui-card-body { + padding: 0.5em; + border: 1px solid #d6d6d6; + border-radius: 3px; +} + +// /deep/ .ui-blockui { +// opacity: 0.1; +// } +/deep/ .ui-widget-overlay { + background-color: #666666; + opacity: .20; + filter: Alpha(Opacity=50); +} \ No newline at end of file diff --git a/src/app/component/target/display/map.component.ts b/src/app/component/target/display/map.component.ts new file mode 100644 index 0000000..087da7f --- /dev/null +++ b/src/app/component/target/display/map.component.ts @@ -0,0 +1,820 @@ +import { + Component, + Input, + OnInit, + EventEmitter, + Output, + ViewChild, + OnDestroy, + ElementRef, + AfterContentInit, + ChangeDetectorRef, + HostListener +} from '@angular/core'; + +import { Observable, of, Subscription, combineLatest } from 'rxjs'; +import { catchError, map, tap, exhaustMap, take, filter } from 'rxjs/operators'; +import { Store, select } from '@ngrx/store'; + +import * as d3 from 'd3'; + +import { Zone, Host, Service, Port } from '@overflow/model/discovery'; +import { Link } from '../../../core/model/link'; +import { Node } from '../../../core/model/node'; + +import * as AppStore from '../../../store'; +import * as DiscoveryConfigStore from '../../../store/discovery/config'; +import * as TargetStore from '../../../store/target/target'; +import * as UILayoutStore from '../../../store/ui/layout'; +import { DiscoverySession } from '../../../core/discovery/discovery-session'; +import { DiscoveryMessageType } from 'src/app/core/type'; + +export class DisplaySummary { + totalHosts: number; + totalPorts: number; + totalServices: number; + elapsedTime: string; + + startDate: Date; + + hTimer: any; + + constructor() { + this.totalHosts = 0; + this.totalPorts = 0; + this.totalServices = 0; + this.elapsedTime = '00:00:00'; + } + + start(startDate: Date) { + this.startDate = startDate; + const __this = this; + const _startDate = new Date(); + this.hTimer = setInterval(function () { + // Get todays date and time + const now = new Date().getTime(); + __this.setElapsedTime(_startDate, new Date()); + }, 1000); + } + + setElapsedTime(startDate: Date, now: Date) { + const distance = now.getTime() - startDate.getTime(); + + // Time calculations for days, hours, minutes and seconds + const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((distance % (1000 * 60)) / 1000); + + this.elapsedTime = String(hours).padStart(2, '0') + ':' + String(minutes).padStart(2, '0') + ':' + String(seconds).padStart(2, '0'); + } + + stop(stopDate: Date) { + clearInterval(this.hTimer); + this.setElapsedTime(this.startDate, stopDate); + } + + increaseHost() { + this.totalHosts++; + } + + increasePort() { + this.totalPorts++; + } + + increaseService() { + this.totalServices++; + } +} + +@Component({ + selector: 'app-target-display-map', + templateUrl: './map.component.html', + styleUrls: ['./map.component.scss'] +}) +export class MapComponent implements OnInit, AfterContentInit, OnDestroy { + + zone: Zone | null = null; + hosts: Map; + ports: Map>; + services: Map>>; + + zoneNode: Node | null = null; + nodes: Node[]; + links: Link[]; + + selectedNode: Node | null = null; + + @ViewChild('displayTarget') set displayTarget(content: ElementRef) { + this.displayTargetRef = content; + } + + private displayTargetRef: ElementRef; + public simulation: d3.Simulation | undefined; + private zoomBehavior: d3.ZoomBehavior; + private displayTargetWidth: number; + private displayTargetHeight: number; + private readonly maxScale: number; + private readonly minScale: number; + + displaySummary: DisplaySummary | null = null; + + discoverySessionSubscription: Subscription | null = null; + refreshTargetDisplaySubscription: Subscription | null = null; + targetSubscription: Subscription | null = null; + targetObservable: Observable; + selectedTargetSubscription: Subscription | null = null; + + discoverySubscription: Subscription | null = null; + + constructor( + private store: Store, + private changeDetector: ChangeDetectorRef, + ) { + this.maxScale = 1; + this.minScale = 0.7; + + const __this = this; + this.targetObservable = this.store.pipe( + take(1), + select((state) => { + const zone = AppStore.DiscoverySelector.ConfigSelector.selectZone(state); + const hosts = AppStore.TargetSelector.TargetSelector.selectHosts(state); + const ports = AppStore.TargetSelector.TargetSelector.selectPorts(state); + const services = AppStore.TargetSelector.TargetSelector.selectServices(state); + + __this.initMapDisplay(zone, hosts, ports, services); + }), + ); + } + + @HostListener('window:resize', ['$event']) + onResize(event) { + if (undefined !== this.displayTargetRef) { + this.displayTargetWidth = this.displayTargetRef.nativeElement.clientWidth; + this.displayTargetHeight = this.displayTargetRef.nativeElement.clientHeight; + + this.zoneNode.fx = this.displayTargetWidth / 2; + this.zoneNode.fy = this.displayTargetHeight / 2; + + this.simulationRestart(false); + } + } + + + ngOnInit(): void { + const __this = this; + this.discoverySessionSubscription = this.store.pipe( + select(AppStore.DiscoverySelector.RequestSelector.selectDiscoverySession), + map((discoverySession: DiscoverySession) => { + if (null === discoverySession) { + if (null !== __this.discoverySubscription) { + __this.discoverySubscription.unsubscribe(); + __this.discoverySubscription = null; + } + return; + } + __this.discoverySubscription = discoverySession.discoveryObservable().pipe( + map((discoveryMessage: { messageType: DiscoveryMessageType, params: any[] }) => { + switch (discoveryMessage.messageType) { + case DiscoveryMessageType.Queueing: + break; + case DiscoveryMessageType.QueueingFailed: + break; + case DiscoveryMessageType.QueueingTimeout: + break; + case DiscoveryMessageType.DiscoveryStart: { + __this.store.dispatch(new UILayoutStore.ChangeBlockPagesContent({ blockPagesContent: true })); + __this.initMapDisplay(__this.zone, null, null, null); + __this.displaySummary.start(discoveryMessage.params[0]); + } + break; + case DiscoveryMessageType.DiscoveryStop: { + __this.simulationRestart(true); + __this.zoomToFit(0.95, 500); + + __this.displaySummary.stop(discoveryMessage.params[0]); + __this.store.dispatch(new UILayoutStore.ChangeBlockPagesContent({ blockPagesContent: false })); + } + break; + case DiscoveryMessageType.DiscoveryMode: + break; + case DiscoveryMessageType.DiscoveryError: + break; + case DiscoveryMessageType.DiscoveredHost: + + __this.addHost(discoveryMessage.params[0], true); + break; + case DiscoveryMessageType.DiscoveredPort: + __this.addPort(discoveryMessage.params[0], false); + break; + case DiscoveryMessageType.DiscoveredService: + __this.addService(discoveryMessage.params[0], true); + break; + default: + break; + } + }), + ).subscribe(); + }), + catchError(error => { + console.log(error); + return of(); + }), + ).subscribe(); + + this.refreshTargetDisplaySubscription = this.store.pipe( + select(AppStore.TargetSelector.TargetSelector.selectRefreshTargetDisplay), + filter(v => !!v), + map((refreshTargetDisplay: boolean) => { + if (!refreshTargetDisplay) { + return; + } + __this.targetObservable.subscribe(); + }), + catchError(error => { + console.log(error); + return of(); + }), + ).subscribe(); + + this.selectedTargetSubscription = this.store.pipe( + select(AppStore.TargetSelector.TargetSelector.selectSelectedTarget) + ).pipe( + map((selectedTarget) => { + if (null === selectedTarget) { + __this.onHideDetail(); + return; + } else { + + } + }), + ).subscribe(); + + } + + ngAfterContentInit(): void { + this.displayTargetWidth = this.displayTargetRef.nativeElement.clientWidth; + this.displayTargetHeight = this.displayTargetRef.nativeElement.clientHeight; + + this.targetObservable.subscribe(); + } + + ngOnDestroy(): void { + if (null !== this.targetSubscription) { + this.targetSubscription.unsubscribe(); + } + if (null !== this.refreshTargetDisplaySubscription) { + this.refreshTargetDisplaySubscription.unsubscribe(); + } + if (null !== this.discoverySessionSubscription) { + this.discoverySessionSubscription.unsubscribe(); + } + if (null !== this.selectedTargetSubscription) { + this.selectedTargetSubscription.unsubscribe(); + } + } + + initMapDisplay(zone: Zone, hosts: Host[], ports: Port[], services: Service[]) { + if (null === zone) { + return; + } + this.nodes = []; + this.links = []; + this.hosts = new Map(); + this.ports = new Map(); + this.services = new Map(); + this.displaySummary = new DisplaySummary(); + + this.simulationInit(); + + this.setZone(zone); + + if (null !== hosts) { + hosts.forEach((host) => { + this.addHost(host); + }); + } + + if (null !== ports) { + ports.forEach((port) => { + this.addPort(port); + }); + } + + if (null !== services) { + services.forEach((service) => { + this.addService(service); + }); + } + + this.simulationRestart(true); + } + + simulationInit() { + if (undefined !== this.simulation) { + return; + } + + const svg = d3.select(this.displayTargetRef.nativeElement); + + this.zoomBehavior = d3.zoom() + .scaleExtent([0.2, 4]) + .on('zoom', () => { + const transform = d3.event.transform; + svg.select('g').attr('transform', 'translate(' + transform.x + ',' + transform.y + ') scale(' + transform.k + ')'); + }); + svg.call(this.zoomBehavior); + + const __this = this; + this.simulation = d3 + .forceSimulation() + .nodes(this.nodes) + .force('charge', d3.forceManyBody().strength(-200)) + .force('link', d3.forceLink(this.links).distance(150)) + .force('center', d3.forceCenter(this.displayTargetWidth / 2, this.displayTargetHeight / 2)) + .force('collision', d3.forceCollide().radius(function (node: Node) { + return node.r * 1.2; + })) + .on('tick', () => { + __this.changeDetector.markForCheck(); + }) + ; + } + + simulationRestart(attachEvent: boolean = false) { + // Update and restart the simulation. + this.simulation + .nodes(this.nodes) + .force('link', d3.forceLink(this.links).distance(150)) + .force('center', d3.forceCenter(this.displayTargetWidth / 2, this.displayTargetHeight / 2)) + .alpha(1) + .restart() + ; + this.changeDetector.detectChanges(); + + if (attachEvent) { + const __this = this; + + const nodeElements = d3.select(this.displayTargetRef.nativeElement).selectAll('.node-container'); + const linkElements = d3.select(this.displayTargetRef.nativeElement).selectAll('.link-container'); + + function getNodeFromElement(element: Element): Node | null { + const container = d3.select(element); + const nodeId = container.attr('nodeId'); + return __this.getNode(nodeId); + } + + // drag + nodeElements.call( + d3.drag() + .on('start', function () { + const node = getNodeFromElement(this); + if (null === node || 'zone' === node.group) { + return; + } + + d3.event.sourceEvent.stopPropagation(); + + if (!d3.event.active) { + __this.simulation.alphaTarget(0.3).restart(); + } + + d3.event.on('drag', dragged).on('end', ended); + + function dragged(d, i) { + node.fx = d3.event.x; + node.fy = d3.event.y; + } + + function ended() { + if (!d3.event.active) { + __this.simulation.alphaTarget(0); + } + + node.fx = null; + node.fy = null; + } + }) + ); + + enum ConnectedNode { + TARGET = 1, + CONNECTED, + NOT_CONNECTED, + } + + function isConnectedNode(node: Node, element: Element): ConnectedNode { + const _node = getNodeFromElement(element); + if (node === _node) { + return ConnectedNode.TARGET; + } + for (let i = 0; i < node.links.length; i++) { + const link = node.links[i]; + if (node === _node || link.source === _node || link.target === _node) { + return ConnectedNode.CONNECTED; + } + } + return ConnectedNode.NOT_CONNECTED; + } + + function isConnectedLink(node: Node, element: Element) { + const _linkElement = d3.select(element); + const sourceId = _linkElement.attr('sourceId'); + const targetId = _linkElement.attr('targetId'); + + if (sourceId === node.id || targetId === node.id) { + return true; + } + + return false; + } + + nodeElements.on('click', function () { + d3.event.stopPropagation(); + + const nodeElement = this as Element; + const node = getNodeFromElement(nodeElement); + if (null === node || 'zone' === node.group) { + __this.onTargetClick(node); + return; + } + + if (null === __this.selectedNode) { + nodeElements.each(function () { + const _thisElement = this as Element; + d3.select(_thisElement).classed('semi-unfocused', false); + d3.select(_thisElement).classed('unfocused', false); + }); + + linkElements.each(function () { + const _thisElement = this as Element; + d3.select(_thisElement).classed('semi-unfocused', false); + d3.select(_thisElement).classed('unfocused', false); + }); + } else { + nodeElements.each(function () { + const _thisElement = this as Element; + d3.select(_thisElement).classed('semi-unselected', false); + d3.select(_thisElement).classed('unselected', false); + }); + + linkElements.each(function () { + const _thisElement = this as Element; + d3.select(_thisElement).classed('semi-unselected', false); + d3.select(_thisElement).classed('unselected', false); + }); + } + + __this.selectedNode = node; + + nodeElements.each(function () { + const _thisElement = this as Element; + switch (isConnectedNode(node, _thisElement)) { + case ConnectedNode.CONNECTED: + d3.select(_thisElement).classed('semi-unselected', true); + break; + case ConnectedNode.NOT_CONNECTED: + d3.select(_thisElement).classed('unselected', true); + break; + default: + break; + } + }); + + linkElements.each(function () { + const _thisElement = this as Element; + if (isConnectedLink(node, _thisElement)) { + d3.select(_thisElement).classed('semi-unselected', true); + } else { + d3.select(_thisElement).classed('unselected', true); + } + }); + + + __this.onTargetClick(node); + }); + + // Highlight + const displayTarget = d3.select(this.displayTargetRef.nativeElement); + displayTarget.on('click', function () { + __this.selectedNode = null; + __this.store.dispatch(new TargetStore.ChangeSelectedTarget(null)); + + nodeElements.each(function () { + const _thisElement = this as Element; + d3.select(_thisElement).classed('semi-unselected', false); + d3.select(_thisElement).classed('unselected', false); + }); + + linkElements.each(function () { + const _thisElement = this as Element; + d3.select(_thisElement).classed('semi-unselected', false); + d3.select(_thisElement).classed('unselected', false); + }); + }); + + nodeElements + .on('mouseover', function () { + const node = getNodeFromElement(this as Element); + if ('zone' === node.group) { + return; + } + + if (null === __this.selectedNode) { + nodeElements.each(function () { + const _thisElement = this as Element; + switch (isConnectedNode(node, _thisElement)) { + case ConnectedNode.CONNECTED: + d3.select(_thisElement).classed('semi-unfocused', true); + break; + case ConnectedNode.NOT_CONNECTED: + d3.select(_thisElement).classed('unfocused', true); + break; + default: + break; + } + }); + + linkElements.each(function () { + const _thisElement = this as Element; + if (isConnectedLink(node, _thisElement)) { + d3.select(_thisElement).classed('semi-unfocused', true); + } else { + d3.select(_thisElement).classed('unfocused', true); + } + }); + } else { + nodeElements.each(function () { + const _thisElement = this as Element; + switch (isConnectedNode(node, _thisElement)) { + case ConnectedNode.TARGET: + d3.select(_thisElement).classed('focused', true); + break; + case ConnectedNode.CONNECTED: + d3.select(_thisElement).classed('semi-focused', true); + break; + default: + break; + } + }); + + linkElements.each(function () { + const _thisElement = this as Element; + if (isConnectedLink(node, _thisElement)) { + d3.select(_thisElement).classed('semi-focused', true); + } + }); + } + + }) + .on('mouseout', function () { + const node = getNodeFromElement(this as Element); + + if (null === __this.selectedNode) { + nodeElements.each(function () { + const _thisElement = this as Element; + d3.select(_thisElement).classed('semi-unfocused', false); + d3.select(_thisElement).classed('unfocused', false); + }); + + linkElements.each(function () { + const _thisElement = this as Element; + d3.select(_thisElement).classed('semi-unfocused', false); + d3.select(_thisElement).classed('unfocused', false); + }); + + } else { + nodeElements.each(function () { + const _thisElement = this as Element; + d3.select(_thisElement).classed('semi-focused', false); + d3.select(_thisElement).classed('focused', false); + }); + + linkElements.each(function () { + const _thisElement = this as Element; + d3.select(_thisElement).classed('semi-focused', false); + d3.select(_thisElement).classed('focused', false); + }); + + } + }) + ; + + } + } + + zoomToFit(paddingPercent, transitionDuration) { + const root = d3.select(this.displayTargetRef.nativeElement); + const bounds = root.node().getBBox(); + + const fullWidth = this.displayTargetWidth; + const fullHeight = this.displayTargetHeight; + + const width = bounds.width; + const height = bounds.height; + + const midX = bounds.x + width / 2; + const midY = bounds.y + height / 2; + + if (width === 0 || height === 0) { return; } // nothing to fit + + let scale = (paddingPercent || 0.75) / Math.max(width / fullWidth, height / fullHeight); + console.log(`scale: ${scale}`); + + if (this.maxScale < scale) { + scale = this.maxScale; + } + if (this.minScale > scale) { + scale = this.minScale; + } + + const translate = [fullWidth / 2 - scale * midX, fullHeight / 2 - scale * midY]; + + root + .transition() + .duration(transitionDuration || 0) // milliseconds + .call(this.zoomBehavior.transform, d3.zoomIdentity.translate(translate[0], translate[1]).scale(scale)); + } + + onTargetClick(node: Node) { + console.log(node); + + switch (node.group) { + case 'zone': + const zone: Zone = node.target; + zone.hostList = []; + + this.hosts.forEach(_host => { + if (_host.zone.network === zone.network) { + zone.hostList.push(_host); + } + }); + + break; + case 'host': + const host: Host = node.target; + host.portList = []; + if (this.ports.has(host.address)) { + this.ports.get(host.address).forEach(port => { + host.portList.push(port); + }); + } + break; + default: + break; + } + + this.store.dispatch(new TargetStore.ChangeSelectedTarget({ + group: node.group, + target: node.target, + })); + + this.selectedNode = node; + } + + onHideDetail() { + const __this = this; + + const nodeElements = d3.select(this.displayTargetRef.nativeElement).selectAll('.node-container'); + const linkElements = d3.select(this.displayTargetRef.nativeElement).selectAll('.link-container'); + + __this.selectedNode = null; + + nodeElements.each(function () { + const _thisElement = this as Element; + d3.select(_thisElement).classed('semi-unselected', false); + d3.select(_thisElement).classed('unselected', false); + }); + + linkElements.each(function () { + const _thisElement = this as Element; + d3.select(_thisElement).classed('semi-unselected', false); + d3.select(_thisElement).classed('unselected', false); + }); + } + + + private getNode(id: string): Node | null { + let _n: Node = null; + this.nodes.some((node): boolean => { + if (node.id === id) { + _n = node; + return true; + } + return false; + }); + return _n; + } + + + refreshTargetDisplay(zone: Zone, hosts: Host[], services: Service[]) { + + } + + setZone(zone: Zone, requireRefresh: boolean = false) { + if (null === zone) { + return; + } + this.zone = zone; + this.zoneNode = new Node(zone.network); + this.zoneNode.group = 'zone'; + this.zoneNode.target = zone; + this.zoneNode.fx = this.displayTargetWidth / 2; + this.zoneNode.fy = this.displayTargetHeight / 2; + this.zoneNode.r = 60; + + this.nodes.push( + this.zoneNode, + ); + + if (requireRefresh) { + this.simulationRestart(); + } + } + + addHost(host: Host, requireRefresh: boolean = false) { + if (null === host) { + return; + } + this.hosts.set(host.address, host); + + const hostId = `${host.address}`; + + let hostNode = this.getNode(hostId); + if (null !== hostNode) { + hostNode.target = host; + } else { + hostNode = new Node(hostId); + hostNode.target = host; + hostNode.group = 'host'; + hostNode.r = 40; + hostNode.x = this.zoneNode.x; + hostNode.y = this.zoneNode.y; + + this.nodes.push(hostNode); + this.links.push(new Link(this.zoneNode, hostNode)); + + this.displaySummary.increaseHost(); + + if (requireRefresh) { + this.simulationRestart(); + } + } + + } + + addPort(port: Port, requireRefresh: boolean = false) { + if (null === port) { + return; + } + + let _ports: Map; + if (!this.ports.has(port.host.address)) { + _ports = new Map(); + this.ports.set(port.host.address, _ports); + } else { + _ports = this.ports.get(port.host.address); + } + + if (!_ports.has(port.portNumber)) { + this.displaySummary.increasePort(); + } + + _ports.set(port.portNumber, port); + + if (requireRefresh) { + this.simulationRestart(); + } + } + + addService(service: Service, requireRefresh: boolean = false) { + if (null === service) { + return; + } + + const hostId = `${service.port.host.address}`; + const serviceId = `${service.port.host.address}-${service.port.portNumber}-${service.port.metaPortType.key}`; + + const hostNode = this.getNode(hostId); + let serviceNode = this.getNode(serviceId); + if (null !== serviceNode) { + serviceNode.target = service; + } else { + serviceNode = new Node(serviceId); + serviceNode.target = service; + serviceNode.group = 'service'; + serviceNode.r = 30; + serviceNode.x = hostNode.x; + serviceNode.y = hostNode.y; + + this.nodes.push(serviceNode); + this.links.push(new Link(hostNode, serviceNode)); + + this.displaySummary.increaseService(); + + if (requireRefresh) { + this.simulationRestart(); + } + } + } +} diff --git a/src/app/component/target/display/tree.component.html b/src/app/component/target/display/tree.component.html new file mode 100644 index 0000000..4064f01 --- /dev/null +++ b/src/app/component/target/display/tree.component.html @@ -0,0 +1,3 @@ +
+ Tree +
\ No newline at end of file diff --git a/src/app/component/target/display/tree.component.scss b/src/app/component/target/display/tree.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/component/target/display/tree.component.ts b/src/app/component/target/display/tree.component.ts new file mode 100644 index 0000000..34a3541 --- /dev/null +++ b/src/app/component/target/display/tree.component.ts @@ -0,0 +1,31 @@ +import { + Component, + OnInit, + OnDestroy, + AfterContentInit +} from '@angular/core'; + +import { Store } from '@ngrx/store'; + +@Component({ + selector: 'app-target-display-tree', + templateUrl: './tree.component.html', + styleUrls: ['./tree.component.scss'] +}) +export class TreeComponent implements OnInit, AfterContentInit, OnDestroy { + constructor( + private store: Store, + ) { + } + + ngOnInit(): void { + + } + + ngAfterContentInit(): void { + } + + ngOnDestroy(): void { + } + +} diff --git a/src/app/component/target/index.ts b/src/app/component/target/index.ts new file mode 100644 index 0000000..b393505 --- /dev/null +++ b/src/app/component/target/index.ts @@ -0,0 +1,13 @@ +import { + COMPONENTS as DETAIL_COMPONENTS +} from './detail'; + +import { + COMPONENTS as DISPLAY_COMPONENTS +} from './display'; + + +export const COMPONENTS = [ + ...DETAIL_COMPONENTS, + ...DISPLAY_COMPONENTS, +]; diff --git a/src/app/core/discovery/discovery-session.ts b/src/app/core/discovery/discovery-session.ts new file mode 100644 index 0000000..11ddb71 --- /dev/null +++ b/src/app/core/discovery/discovery-session.ts @@ -0,0 +1,230 @@ +import { Store, select } from '@ngrx/store'; + +import { Host, Port, DiscoveryModeType, Service } from '@overflow/model/discovery'; +import { RPCSubscriber } from '@overflow/commons/ui/decorator/RPCSubscriber'; +import { RPCError } from '@overflow/rpc-js'; + +import { ProbeService } from '../../service/probe.service'; +import { DiscoverRequestInfo } from '../model'; + +import * as AppStore from '../../store'; +import * as DiscoveryRequestStore from '../../store/discovery/request'; +import * as UserStore from '../../store/environment/user'; +import * as TargetTargetStore from '../../store/target/target'; +import { take, map } from 'rxjs/operators'; +import { DiscoveryMessageType, TargetDisplayType, DiscoveryStatusType } from '../type'; +import { Subject, Observable } from 'rxjs'; + +export class DiscoverySession { + + private _discoverRequestInfo: DiscoverRequestInfo | null; + private _requestID: string | null; + private startDate: Date | null; + private discoverySubject: Subject<{ messageType: DiscoveryMessageType, params: any[] }> | null; + + private constructor( + private store: Store, + private probeService: ProbeService, + ) { + } + + public static requestDiscover(store: Store, probeService: ProbeService): void { + store.pipe( + take(1), + select((state) => { + const memberID = AppStore.EnvironmentSelector.UserSelector.selectMemberID(state); + const zone = AppStore.DiscoverySelector.ConfigSelector.selectZone(state); + const host = AppStore.DiscoverySelector.ConfigSelector.selectHost(state); + const port = AppStore.DiscoverySelector.ConfigSelector.selectPort(state); + const discoverHost = AppStore.DiscoverySelector.ConfigSelector.selectDiscoverHost(state); + const discoverPort = AppStore.DiscoverySelector.ConfigSelector.selectDiscoverPort(state); + const discoverService = AppStore.DiscoverySelector.ConfigSelector.selectDiscoverService(state); + const discoverySession = AppStore.DiscoverySelector.RequestSelector.selectDiscoverySession(state); + + if (null !== discoverySession) { + return null; + } + const discoverRequestInfo = { + requesterID: memberID, + zone, + host, + port, + discoverHost, + discoverPort, + discoverService, + }; + const _discoverySession = new DiscoverySession(store, probeService); + _discoverySession.init(discoverRequestInfo); + + return _discoverySession; + }), + ).pipe( + map((discoverySession) => { + if (null === discoverySession) { + return; + } + + store.dispatch(new UserStore.ChangeShowWelcomPage({ showWelcomPage: false })); + store.dispatch(new TargetTargetStore.ChangeTargetDisplayType({ targetDisplayType: TargetDisplayType.MAP })); + + store.dispatch(new DiscoveryRequestStore.RequestDiscover({ discoverySession })); + // store.dispatch(new UserStore.ChangeShowWelcomPage({ showWelcomPage: false })); + // store.dispatch(new TargetTargetStore.ChangeTargetDisplayType({ targetDisplayType: TargetDisplayType.MAP })); + }), + ).subscribe(); + } + + public static requestDiscoverStop(store: Store): void { + store.pipe( + take(1), + select((state) => { + const discoverySession = AppStore.DiscoverySelector.RequestSelector.selectDiscoverySession(state); + + if (null === discoverySession) { + return null; + } + + return discoverySession; + }), + ).pipe( + map((discoverySession) => { + if (null === discoverySession) { + return; + } + store.dispatch(new DiscoveryRequestStore.RequestDiscoverStop({ discoverySession })); + }), + ).subscribe(); + } + + init(discoverRequestInfo: DiscoverRequestInfo) { + this._discoverRequestInfo = discoverRequestInfo; + this.discoverySubject = new Subject(); + this.probeService.subscribeNotification(this); + } + + destroy(requireDispatch: boolean = true) { + this.probeService.unsubscribeNotification(this); + + if (requireDispatch) { + this.store.dispatch(new DiscoveryRequestStore.DiscoveryComplete()); + } + + this._requestID = null; + this.startDate = null; + + this._discoverRequestInfo = null; + this.discoverySubject = null; + } + + public get requestID(): string { + return this._requestID; + } + + public get discoverRequestInfo(): DiscoverRequestInfo { + return this._discoverRequestInfo; + } + + public discoveryObservable(): Observable<{ messageType: DiscoveryMessageType, params: any[] }> { + return this.discoverySubject.asObservable(); + } + + + /** + * DiscoveryQueueing + */ + @RPCSubscriber({ method: 'DiscoveryService.Queueing' }) + public DiscoveryQueueing(requestID: string, queueingDate: Date) { + this._requestID = requestID; + + this.store.dispatch(new DiscoveryRequestStore.ChangeDiscoveryStatus({ discoveryStatus: DiscoveryStatusType.Queueing })); + this.discoverySubject.next({ messageType: DiscoveryMessageType.Queueing, params: [requestID, queueingDate] }); + } + + /** + * DiscoveryQueueingFailed + */ + @RPCSubscriber({ method: 'DiscoveryService.QueueingFailed' }) + public DiscoveryQueueingFailed(queueingFailedDate: Date) { + this.store.dispatch(new DiscoveryRequestStore.ChangeDiscoveryStatus({ discoveryStatus: DiscoveryStatusType.Stopped })); + this.discoverySubject.next({ messageType: DiscoveryMessageType.QueueingFailed, params: [queueingFailedDate] }); + + this.destroy(); + } + + /** + * DiscoveryQueueingTimeout + */ + @RPCSubscriber({ method: 'DiscoveryService.QueueingTimeout' }) + public DiscoveryQueueingTimeout(queueingTimeoutDate: Date) { + this._requestID = null; + + this.store.dispatch(new DiscoveryRequestStore.ChangeDiscoveryStatus({ discoveryStatus: DiscoveryStatusType.Stopped })); + this.discoverySubject.next({ messageType: DiscoveryMessageType.QueueingTimeout, params: [queueingTimeoutDate] }); + + this.destroy(); + } + + /** + * DiscoveryStart + */ + @RPCSubscriber({ method: 'DiscoveryService.DiscoveryStart' }) + public DiscoveryStart(startDate: Date) { + this.startDate = startDate; + + this.store.dispatch(new DiscoveryRequestStore.ChangeDiscoveryStatus({ discoveryStatus: DiscoveryStatusType.Started })); + this.discoverySubject.next({ messageType: DiscoveryMessageType.DiscoveryStart, params: [startDate] }); + } + + /** + * DiscoveryStop + */ + @RPCSubscriber({ method: 'DiscoveryService.DiscoveryStop' }) + public DiscoveryStop(stopDate: Date) { + this.store.dispatch(new DiscoveryRequestStore.ChangeDiscoveryStatus({ discoveryStatus: DiscoveryStatusType.Stopped })); + this.discoverySubject.next({ messageType: DiscoveryMessageType.DiscoveryStop, params: [stopDate] }); + + this.destroy(); + } + + /** + * DiscoveryMode + */ + @RPCSubscriber({ method: 'DiscoveryService.DiscoveryMode' }) + public DiscoveryMode(discoveryMode: DiscoveryModeType) { + this.store.dispatch(new DiscoveryRequestStore.ChangeDiscoveryMode({ discoveryMode: discoveryMode })); + this.discoverySubject.next({ messageType: DiscoveryMessageType.DiscoveryMode, params: [discoveryMode] }); + } + + /** + * DiscoveryError + */ + @RPCSubscriber({ method: 'DiscoveryService.DiscoveryError' }) + public DiscoveryError(err: RPCError) { + this.discoverySubject.next({ messageType: DiscoveryMessageType.DiscoveryError, params: [err] }); + } + + /** + * DiscoveredHost + */ + @RPCSubscriber({ method: 'DiscoveryService.DiscoveredHost' }) + public DiscoveredHost(host: Host) { + this.discoverySubject.next({ messageType: DiscoveryMessageType.DiscoveredHost, params: [host] }); + } + + /** + * DiscoveredPort + */ + @RPCSubscriber({ method: 'DiscoveryService.DiscoveredPort' }) + public DiscoveredPort(port: Port) { + this.discoverySubject.next({ messageType: DiscoveryMessageType.DiscoveredPort, params: [port] }); + } + + /** + * DiscoveredService + */ + @RPCSubscriber({ method: 'DiscoveryService.DiscoveredService' }) + public DiscoveredService(service: Service) { + this.discoverySubject.next({ messageType: DiscoveryMessageType.DiscoveredService, params: [service] }); + } + +} diff --git a/src/app/core/model/discover-request-info.ts b/src/app/core/model/discover-request-info.ts new file mode 100644 index 0000000..1519326 --- /dev/null +++ b/src/app/core/model/discover-request-info.ts @@ -0,0 +1,11 @@ +import { Zone, Host, Port, DiscoverHost, DiscoverPort, DiscoverService } from '@overflow/model/discovery'; + +export interface DiscoverRequestInfo { + requesterID: string; + zone: Zone | null; + host: Host | null; + port: Port | null; + discoverHost: DiscoverHost | null; + discoverPort: DiscoverPort | null; + discoverService: DiscoverService | null; +} diff --git a/src/app/core/model/index.ts b/src/app/core/model/index.ts new file mode 100644 index 0000000..6c3d186 --- /dev/null +++ b/src/app/core/model/index.ts @@ -0,0 +1,3 @@ +export * from './link'; +export * from './node'; +export * from './discover-request-info'; diff --git a/src/commons/model/link.ts b/src/app/core/model/link.ts similarity index 100% rename from src/commons/model/link.ts rename to src/app/core/model/link.ts diff --git a/src/commons/model/node.ts b/src/app/core/model/node.ts similarity index 100% rename from src/commons/model/node.ts rename to src/app/core/model/node.ts diff --git a/src/app/core/type/discovery-message.ts b/src/app/core/type/discovery-message.ts new file mode 100644 index 0000000..d79891e --- /dev/null +++ b/src/app/core/type/discovery-message.ts @@ -0,0 +1,13 @@ + +export enum DiscoveryMessageType { + Queueing = 'Queueing', + QueueingFailed = 'QueueingFailed', + QueueingTimeout = 'QueueingTimeout', + DiscoveryStart = 'DiscoveryStart', + DiscoveryStop = 'DiscoveryStop', + DiscoveryMode = 'DiscoveryMode', + DiscoveryError = 'DiscoveryError', + DiscoveredHost = 'DiscoveredHost', + DiscoveredPort = 'DiscoveredPort', + DiscoveredService = 'DiscoveredService', +} diff --git a/src/app/core/type/discovery-status.ts b/src/app/core/type/discovery-status.ts new file mode 100644 index 0000000..4fd761e --- /dev/null +++ b/src/app/core/type/discovery-status.ts @@ -0,0 +1,7 @@ +export enum DiscoveryStatusType { + Requested = 'Requested', + Queueing = 'Queueing', + Started = 'Started', + Stopping = 'Stopping', + Stopped = 'Stopped' +} diff --git a/src/app/core/type/index.ts b/src/app/core/type/index.ts new file mode 100644 index 0000000..0012843 --- /dev/null +++ b/src/app/core/type/index.ts @@ -0,0 +1,3 @@ +export * from './discovery-message'; +export * from './discovery-status'; +export * from './target-display'; diff --git a/src/app/core/type/target-display.ts b/src/app/core/type/target-display.ts new file mode 100644 index 0000000..b5673d3 --- /dev/null +++ b/src/app/core/type/target-display.ts @@ -0,0 +1,7 @@ + +export enum TargetDisplayType { + NONE = 'NONE', + MAP = 'MAP', + GRID = 'GRID', + TREE = 'TREE', +} diff --git a/src/app/core/util/discovery.util.ts b/src/app/core/util/discovery.util.ts new file mode 100644 index 0000000..514c1f5 --- /dev/null +++ b/src/app/core/util/discovery.util.ts @@ -0,0 +1,48 @@ +import { combineLatest, Observable } from 'rxjs'; +import { Store } from '@ngrx/store'; + +import { DiscoverHost, DiscoverPort, DiscoverService, Zone, Host, Port } from '@overflow/model/discovery'; + +import * as AppStore from '../../store'; +import { DiscoverRequestInfo } from '../model'; +import { DiscoverySession } from '../discovery/discovery-session'; + +export class DiscoveryUtil { + /** + * name + */ + public static getRequestInfo(store: Store): Observable { + return combineLatest( + store.select(AppStore.EnvironmentSelector.UserSelector.selectMemberID), + store.select(AppStore.DiscoverySelector.ConfigSelector.selectZone), + store.select(AppStore.DiscoverySelector.ConfigSelector.selectHost), + store.select(AppStore.DiscoverySelector.ConfigSelector.selectPort), + store.select(AppStore.DiscoverySelector.ConfigSelector.selectDiscoverHost), + store.select(AppStore.DiscoverySelector.ConfigSelector.selectDiscoverPort), + store.select(AppStore.DiscoverySelector.ConfigSelector.selectDiscoverService), + ( + memberID: string, + zone: Zone, host: Host, port: Port, + discoverHost: DiscoverHost, discoverPort: DiscoverPort, discoverService: DiscoverService) => { + return { + requesterID: memberID, + zone, + host, + port, + discoverHost, + discoverPort, + discoverService, + }; + } + ); + } + + public static getDiscoverySession(store: Store): Observable { + return combineLatest( + store.select(AppStore.DiscoverySelector.RequestSelector.selectDiscoverySession), + (discoverySession: DiscoverySession) => { + return discoverySession; + } + ); + } +} diff --git a/src/app/pages/home/home-page-routing.module.ts b/src/app/pages/home/home-page-routing.module.ts deleted file mode 100644 index 126218e..0000000 --- a/src/app/pages/home/home-page-routing.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; -import { HomePageComponent } from './home-page.component'; - -const routes: Routes = [ - { - path: '', - component: HomePageComponent, - } -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class HomePageRoutingModule { } diff --git a/src/app/pages/home/home-page.component.html b/src/app/pages/home/home-page.component.html index 9057f50..e1d1d3e 100644 --- a/src/app/pages/home/home-page.component.html +++ b/src/app/pages/home/home-page.component.html @@ -1,6 +1,6 @@ - -
-
+ +
+
@@ -10,127 +10,19 @@

Network Scanner


- 를 이용해 주셔서 감사합니다. + 이용해 주셔서 감사합니다.
좌측의 버튼을 클릭 하시면 기본 설정으로 스캐닝이 시작 됩니다. 설정의 변경을 원하시면 상단의 설정 영역을 클릭하여 변경이 가능합니다.
-
- - - - - - {{link.target.target.port.portNumber}} - - - - - - - - - {{node.target.network}} - - - - - - - - - - - - - {{node.target.address}} - - - - - - - - - {{node.target.name}} - - - - - - -
-
Total Hosts:
{{discoveryResult.totalHosts}} -
-
-
Total Services:
{{discoveryResult.totalServices}} -
-
-
Elapsed:
{{discoveryResult.elapsedTime}} -
-
- -
- - -
- -
-
+
+
- - -
Discovery is being stopped...
-
- - - \ No newline at end of file + \ No newline at end of file diff --git a/src/app/pages/home/home-page.component.scss b/src/app/pages/home/home-page.component.scss index 6e65a14..2334de7 100644 --- a/src/app/pages/home/home-page.component.scss +++ b/src/app/pages/home/home-page.component.scss @@ -48,105 +48,15 @@ padding: .6em .75em; } -.discovery-container { - height: 100vh; - margin: -0.6em -0.9em -0.7em -0.9em; //-0.5em -0.75em; - padding: 0; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: center; - min-width: 400px; //text-align: center; -} - -.link { - stroke: #999; - stroke-opacity: 0.6; -} - -.textClass { - stroke: #323232; - font-family: "Lucida Grande", "Droid Sans", Arial, Helvetica, sans-serif; - font-weight: normal; - stroke-width: .5; - font-size: 14px; -} - -.linkTextClass { - stroke: #b6b4b4; - font-family: "Lucida Grande", "Droid Sans", Arial, Helvetica, sans-serif; - font-weight: normal; - stroke-width: .3; - font-size: 9px; -} - -.focused { - opacity: 1 !important; -} - -.semi-focused { - opacity: 0.8 !important; -} - -.semi-unfocused { - opacity: 0.8; -} - -.unfocused { - opacity: 0.3; -} - -.semi-unselected { - opacity: 0.8; -} - -.unselected { - opacity: 0.3; -} - -.ui-map-info { - position: absolute; - top: 10px; - left: 10px; - .ui-map-info-row { - //display: inline; - font-size: 0.8em; - line-height: 2em; - width: 120px; - font-weight: bold; - user-select: text; - div { - display: inline-block; - font-weight: normal; - padding-left: 5px; - width: 75px; - user-select: text; - } - } - .ui-border-bottom { - border-bottom: 1px solid #d6d6d6; - } -} - -/deep/ .ui-card-body { - padding: 0.5em; - border: 1px solid #d6d6d6; - border-radius: 3px; -} - -.detail-sidebar { - top: 0; - left: 0; - height: 100%; -} - -// /deep/ .ui-blockui { -// opacity: 0.1; -// } -/deep/ .ui-widget-overlay { - background-color: #666666; - opacity: .20; - filter: Alpha(Opacity=50); +.display-container { + position: relative; // height: 100vh; + // margin: -0.6em -0.9em -0.7em -0.9em; //-0.5em -0.75em; + // padding: 0; + // box-sizing: border-box; + // display: flex; + // align-items: center; + // justify-content: center; + // min-width: 400px; //text-align: center; } .ui-dialog-text-center { diff --git a/src/app/pages/home/home-page.component.ts b/src/app/pages/home/home-page.component.ts index 9e49d1c..22af793 100644 --- a/src/app/pages/home/home-page.component.ts +++ b/src/app/pages/home/home-page.component.ts @@ -1,75 +1,19 @@ import { Component, OnInit, OnDestroy, ViewChild, ElementRef, ChangeDetectorRef, HostListener } from '@angular/core'; -import { Subject, Subscription, of } from 'rxjs'; -import { map, catchError, take } from 'rxjs/operators'; +import { Subject, Subscription, of, Observable } from 'rxjs'; +import { map, catchError, take, tap } from 'rxjs/operators'; -import * as d3 from 'd3'; +import { Store, select } from '@ngrx/store'; import { ConfirmationService } from 'primeng/primeng'; -import { Zone, Host, Port, Service, DiscoverHost } from '@overflow/model/discovery'; -import { RPCSubscriber } from '@overflow/commons/ui/decorator/RPCSubscriber'; -import { RPCError } from '@overflow/rpc-js'; -import { toMetaIPType, MetaIPTypeEnum, toMetaCryptoType, MetaCryptoTypeEnum, toMetaPortType, MetaPortTypeEnum } from '@overflow/model/meta'; -import { DiscoveryModeType } from '@overflow/model/discovery/discovery'; +import { ProbeService } from '../../service/probe.service'; -import { ProbeService, requesterID } from '../../../commons/service/probe.service'; -import { DiscoveryConfigService, DiscoveryStatusType } from '../../../commons/service/discovery-config.service'; -import { Node } from '../../../commons/model/node'; -import { Link } from '../../../commons/model/link'; +import * as AppStore from '../../store'; +import { DiscoverySession } from '../../core/discovery/discovery-session'; -export class DiscoveryResult { - totalHosts: number; - totalServices: number; - elapsedTime: string; - - hTimer: any; - - constructor() { - this.totalHosts = 0; - this.totalServices = 0; - this.elapsedTime = '00:00:00'; - } - - start() { - const __this = this; - const startTime = new Date().getTime(); - this.hTimer = setInterval(function () { - // Get todays date and time - const now = new Date().getTime(); - - // Find the distance between now and the count down date - const distance = now - startTime; - - // Time calculations for days, hours, minutes and seconds - const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); - const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); - const seconds = Math.floor((distance % (1000 * 60)) / 1000); - - __this.setElapsedTime(hours, minutes, seconds); - }, 1000); - } - - setElapsedTime(hour: number, minute: number, second: number) { - this.elapsedTime = String(hour).padStart(2, '0') + ':' + String(minute).padStart(2, '0') + ':' + String(second).padStart(2, '0'); - } - - stop(hours: number, minutes: number, seconds: number) { - clearInterval(this.hTimer); - this.setElapsedTime(hours, minutes, seconds); - } - - increaseHost() { - this.totalHosts++; - } - - increaseService() { - this.totalServices++; - } -} - @Component({ selector: 'app-pages-home', templateUrl: './home-page.component.html', @@ -80,101 +24,32 @@ export class DiscoveryResult { }) export class HomePageComponent implements OnInit, OnDestroy { - private discoveryTargetRef: ElementRef; - - zone: Zone; - discoverHost: DiscoverHost; - - blockedPanel = false; - displaySidebar = false; - stopping = false; - blockedDocument = false; - showIntro = true; - selectedNode = null; - - private zoneNode: Node; - private nodes: Node[]; - private links: Link[]; - public simulation: d3.Simulation | undefined; - private zoomBehavior: d3.ZoomBehavior; - private discoveryContainerWidth: number; - private discoveryContainerHeight: number; - private readonly maxScale: number; - private readonly minScale: number; - - private discoveryStartDate: Date; - discoveryRequestID: string | null; - hosts: Map; - ports: Map>; - discoveryResult: DiscoveryResult; private preventBrowserCloseSubject: Subject; private preventBrowserCloseSubscription: Subscription; - @HostListener('window:resize', ['$event']) - onResize(event) { - if (undefined !== this.discoveryTargetRef) { - this.discoveryContainerWidth = this.discoveryTargetRef.nativeElement.clientWidth; - this.discoveryContainerHeight = this.discoveryTargetRef.nativeElement.clientHeight; - - this.zoneNode.fx = this.discoveryContainerWidth / 2; - this.zoneNode.fy = this.discoveryContainerHeight / 2; - - this.simulationRestart(false); - } - } + showWelcomPage$: Observable; @HostListener('window:beforeunload', ['$event']) onBeforeUnload($event: Event) { - if (null !== this.discoveryRequestID) { - this.preventBrowserCloseSubject.next(); - $event.returnValue = true; - } - } - - @ViewChild('discoveryTarget') set discoveryTarget(content: ElementRef) { - this.discoveryTargetRef = content; + // if (null !== this.discoveryRequestID) { + // this.preventBrowserCloseSubject.next(); + // $event.returnValue = true; + // } } constructor( - private changeDetector: ChangeDetectorRef, private confirmationService: ConfirmationService, private probeService: ProbeService, - private discoveryConfigService: DiscoveryConfigService, + private store: Store, ) { - this.nodes = []; - this.links = []; - this.maxScale = 1; - this.minScale = 0.7; - - this.hosts = new Map(); - this.ports = new Map(); - - this.discoveryRequestID = null; this.preventBrowserCloseSubject = new Subject(); - this.discoveryResult = null; } ngOnInit() { - this.probeService.subscribeNotification(this); - // this.startDiscovery(""); - this.discoveryConfigService.zone.subscribe(res => { - this.zone = res as Zone; - }); - this.discoveryConfigService.discoverHost.subscribe(res => { - this.discoverHost = res as DiscoverHost; - }); - this.discoveryConfigService.discoveryStatus.subscribe(res => { - if (res === DiscoveryStatusType.Started) { - this.startDiscovery(); - } else if (res === DiscoveryStatusType.Stopping) { - if (null !== this.discoveryRequestID) { - this.stopping = true; - this.probeService.send('DiscoveryService.DiscoverStop', requesterID, this.discoveryRequestID); - } - } - }); + this.showWelcomPage$ = this.store.pipe(select(AppStore.EnvironmentSelector.UserSelector.selectShowWelcomPage)); + this.preventBrowserCloseSubscription = this.preventBrowserCloseSubject .pipe( map(() => { @@ -197,7 +72,6 @@ export class HomePageComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { - this.probeService.unsubscribeNotification(this); this.preventBrowserCloseSubscription.unsubscribe(); } @@ -208,831 +82,8 @@ export class HomePageComponent implements OnInit, OnDestroy { }, 3000); } - startDiscovery() { - // this.zone = { - // network: '192.168.1.0/24', - // iface: 'enp3s0', - // metaIPType: toMetaIPType(MetaIPTypeEnum.V4), - // address: '192.168.1.101', - // mac: '44:8a:5b:f1:f1:f3', - // }; - - // this.discoverHost = { - // metaIPType: toMetaIPType(MetaIPTypeEnum.V4), - // firstScanRange: '192.168.1.1', - // lastScanRange: '192.168.1.254', - // discoveryConfig: { - // }, - // discoverPort: { - // firstScanRange: 1, - // lastScanRange: 65535, - // includeTCP: true, - // includeUDP: true, - // discoverService: { - // } - // } - // }; - - console.log('########################################################'); - console.log(this.zone); - console.log(this.discoverHost); - console.log('########################################################'); - - this.discoveryResult = new DiscoveryResult(); - this.showIntro = false; - this.changeDetector.detectChanges(); - - this.discoveryContainerWidth = this.discoveryTargetRef.nativeElement.clientWidth; - this.discoveryContainerHeight = this.discoveryTargetRef.nativeElement.clientHeight; - - this.simulationInit(); - - this.nodes = []; - this.links = []; - - this.zoneNode = new Node(this.zone.network); - this.zoneNode.group = 'zone'; - this.zoneNode.target = this.zone; - this.zoneNode.fx = this.discoveryContainerWidth / 2; - this.zoneNode.fy = this.discoveryContainerHeight / 2; - this.zoneNode.r = 60; - - this.nodes.push( - this.zoneNode, - ); - - this.links.push( - ); - - // this.setTestData(); - // this.simulationRestart(true); - - this.simulationRestart(); - this.probeService.send('DiscoveryService.DiscoverHost', requesterID, this.zone, this.discoverHost); - } - - simulationInit() { - if (undefined !== this.simulation) { - return; - } - - const svg = d3.select(this.discoveryTargetRef.nativeElement); - - this.zoomBehavior = d3.zoom() - .scaleExtent([0.2, 4]) - .on('zoom', () => { - const transform = d3.event.transform; - svg.select('g').attr('transform', 'translate(' + transform.x + ',' + transform.y + ') scale(' + transform.k + ')'); - }); - svg.call(this.zoomBehavior); - - const __this = this; - this.simulation = d3 - .forceSimulation() - .nodes(this.nodes) - .force('charge', d3.forceManyBody().strength(-200)) - .force('link', d3.forceLink(this.links).distance(150)) - .force('center', d3.forceCenter(this.discoveryContainerWidth / 2, this.discoveryContainerHeight / 2)) - .force('collision', d3.forceCollide().radius(function (node: Node) { - return node.r * 1.2; - })) - .on('tick', () => { - __this.changeDetector.markForCheck(); - }) - ; - } - - simulationRestart(attachEvent: boolean = false) { - // Update and restart the simulation. - this.simulation - .nodes(this.nodes) - .force('link', d3.forceLink(this.links).distance(150)) - .force('center', d3.forceCenter(this.discoveryContainerWidth / 2, this.discoveryContainerHeight / 2)) - .alpha(1) - .restart() - ; - this.changeDetector.detectChanges(); - - if (attachEvent) { - const __this = this; - - const nodeElements = d3.select(this.discoveryTargetRef.nativeElement).selectAll('.node-container'); - const linkElements = d3.select(this.discoveryTargetRef.nativeElement).selectAll('.link-container'); - - function getNodeFromElement(element: Element): Node | null { - const container = d3.select(element); - const nodeId = container.attr('nodeId'); - return __this.getNode(nodeId); - } - - // drag - nodeElements.call( - d3.drag() - .on('start', function () { - const node = getNodeFromElement(this); - if (null === node || 'zone' === node.group) { - return; - } - - d3.event.sourceEvent.stopPropagation(); - - if (!d3.event.active) { - __this.simulation.alphaTarget(0.3).restart(); - } - - d3.event.on('drag', dragged).on('end', ended); - - function dragged(d, i) { - node.fx = d3.event.x; - node.fy = d3.event.y; - } - - function ended() { - if (!d3.event.active) { - __this.simulation.alphaTarget(0); - } - - node.fx = null; - node.fy = null; - } - }) - ); - - enum ConnectedNode { - TARGET = 1, - CONNECTED, - NOT_CONNECTED, - } - - function isConnectedNode(node: Node, element: Element): ConnectedNode { - const _node = getNodeFromElement(element); - if (node === _node) { - return ConnectedNode.TARGET; - } - for (let i = 0; i < node.links.length; i++) { - const link = node.links[i]; - if (node === _node || link.source === _node || link.target === _node) { - return ConnectedNode.CONNECTED; - } - } - return ConnectedNode.NOT_CONNECTED; - } - - function isConnectedLink(node: Node, element: Element) { - const _linkElement = d3.select(element); - const sourceId = _linkElement.attr('sourceId'); - const targetId = _linkElement.attr('targetId'); - - if (sourceId === node.id || targetId === node.id) { - return true; - } - - return false; - } - - nodeElements.on('click', function () { - d3.event.stopPropagation(); - - const nodeElement = this as Element; - const node = getNodeFromElement(nodeElement); - if (null === node || 'zone' === node.group) { - __this.onTargetClick(node); - return; - } - - if (null === __this.selectedNode) { - nodeElements.each(function () { - const _thisElement = this as Element; - d3.select(_thisElement).classed('semi-unfocused', false); - d3.select(_thisElement).classed('unfocused', false); - }); - - linkElements.each(function () { - const _thisElement = this as Element; - d3.select(_thisElement).classed('semi-unfocused', false); - d3.select(_thisElement).classed('unfocused', false); - }); - } else { - nodeElements.each(function () { - const _thisElement = this as Element; - d3.select(_thisElement).classed('semi-unselected', false); - d3.select(_thisElement).classed('unselected', false); - }); - - linkElements.each(function () { - const _thisElement = this as Element; - d3.select(_thisElement).classed('semi-unselected', false); - d3.select(_thisElement).classed('unselected', false); - }); - } - - __this.selectedNode = node; - - nodeElements.each(function () { - const _thisElement = this as Element; - switch (isConnectedNode(node, _thisElement)) { - case ConnectedNode.CONNECTED: - d3.select(_thisElement).classed('semi-unselected', true); - break; - case ConnectedNode.NOT_CONNECTED: - d3.select(_thisElement).classed('unselected', true); - break; - default: - break; - } - }); - - linkElements.each(function () { - const _thisElement = this as Element; - if (isConnectedLink(node, _thisElement)) { - d3.select(_thisElement).classed('semi-unselected', true); - } else { - d3.select(_thisElement).classed('unselected', true); - } - }); - - - __this.onTargetClick(node); - }); - - // Highlight - const discoveryContainer = d3.select(this.discoveryTargetRef.nativeElement); - discoveryContainer.on('click', function () { - __this.selectedNode = null; - __this.displaySidebar = false; - - nodeElements.each(function () { - const _thisElement = this as Element; - d3.select(_thisElement).classed('semi-unselected', false); - d3.select(_thisElement).classed('unselected', false); - }); - - linkElements.each(function () { - const _thisElement = this as Element; - d3.select(_thisElement).classed('semi-unselected', false); - d3.select(_thisElement).classed('unselected', false); - }); - }); - - nodeElements - .on('mouseover', function () { - const node = getNodeFromElement(this as Element); - if ('zone' === node.group) { - return; - } - - if (null === __this.selectedNode) { - nodeElements.each(function () { - const _thisElement = this as Element; - switch (isConnectedNode(node, _thisElement)) { - case ConnectedNode.CONNECTED: - d3.select(_thisElement).classed('semi-unfocused', true); - break; - case ConnectedNode.NOT_CONNECTED: - d3.select(_thisElement).classed('unfocused', true); - break; - default: - break; - } - }); - - linkElements.each(function () { - const _thisElement = this as Element; - if (isConnectedLink(node, _thisElement)) { - d3.select(_thisElement).classed('semi-unfocused', true); - } else { - d3.select(_thisElement).classed('unfocused', true); - } - }); - } else { - nodeElements.each(function () { - const _thisElement = this as Element; - switch (isConnectedNode(node, _thisElement)) { - case ConnectedNode.TARGET: - d3.select(_thisElement).classed('focused', true); - break; - case ConnectedNode.CONNECTED: - d3.select(_thisElement).classed('semi-focused', true); - break; - default: - break; - } - }); - - linkElements.each(function () { - const _thisElement = this as Element; - if (isConnectedLink(node, _thisElement)) { - d3.select(_thisElement).classed('semi-focused', true); - } - }); - } - - }) - .on('mouseout', function () { - const node = getNodeFromElement(this as Element); - - if (null === __this.selectedNode) { - nodeElements.each(function () { - const _thisElement = this as Element; - d3.select(_thisElement).classed('semi-unfocused', false); - d3.select(_thisElement).classed('unfocused', false); - }); - - linkElements.each(function () { - const _thisElement = this as Element; - d3.select(_thisElement).classed('semi-unfocused', false); - d3.select(_thisElement).classed('unfocused', false); - }); - - } else { - nodeElements.each(function () { - const _thisElement = this as Element; - d3.select(_thisElement).classed('semi-focused', false); - d3.select(_thisElement).classed('focused', false); - }); - - linkElements.each(function () { - const _thisElement = this as Element; - d3.select(_thisElement).classed('semi-focused', false); - d3.select(_thisElement).classed('focused', false); - }); - - } - }) - ; - - } - } - - zoomToFit(paddingPercent, transitionDuration) { - const root = d3.select(this.discoveryTargetRef.nativeElement); - const bounds = root.node().getBBox(); - - const fullWidth = this.discoveryContainerWidth; - const fullHeight = this.discoveryContainerHeight; - - const width = bounds.width; - const height = bounds.height; - - const midX = bounds.x + width / 2; - const midY = bounds.y + height / 2; - - if (width === 0 || height === 0) { return; } // nothing to fit - - let scale = (paddingPercent || 0.75) / Math.max(width / fullWidth, height / fullHeight); - console.log(`scale: ${scale}`); - - if (this.maxScale < scale) { - scale = this.maxScale; - } - if (this.minScale > scale) { - scale = this.minScale; - } - - const translate = [fullWidth / 2 - scale * midX, fullHeight / 2 - scale * midY]; - - root - .transition() - .duration(transitionDuration || 0) // milliseconds - .call(this.zoomBehavior.transform, d3.zoomIdentity.translate(translate[0], translate[1]).scale(scale)); - } - - onTargetClick(node: Node) { - console.log(node); - this.displaySidebar = true; - - switch (node.group) { - case 'zone': - const zone: Zone = node.target; - zone.hostList = []; - - this.hosts.forEach(_host => { - if (_host.zone.network === zone.network) { - zone.hostList.push(_host); - } - }); - break; - case 'host': - const host: Host = node.target; - host.portList = []; - if (this.ports.has(host.address)) { - this.ports.get(host.address).forEach(port => { - host.portList.push(port); - }); - } - break; - default: - break; - } - this.selectedNode = node; - } - - onHideDetail() { - const __this = this; - - const nodeElements = d3.select(this.discoveryTargetRef.nativeElement).selectAll('.node-container'); - const linkElements = d3.select(this.discoveryTargetRef.nativeElement).selectAll('.link-container'); - - __this.selectedNode = null; - - nodeElements.each(function () { - const _thisElement = this as Element; - d3.select(_thisElement).classed('semi-unselected', false); - d3.select(_thisElement).classed('unselected', false); - }); - - linkElements.each(function () { - const _thisElement = this as Element; - d3.select(_thisElement).classed('semi-unselected', false); - d3.select(_thisElement).classed('unselected', false); - }); - } - - /** - * DiscoverHost - */ - @RPCSubscriber({ method: 'DiscoveryService.Queueing' }) - public DiscoveryQueueing(requestID: string, queueingDate: Date) { - console.log('DiscoveryQueueing', requestID, queueingDate); - this.discoveryRequestID = requestID; - } - - /** - * DiscoverHost - */ - @RPCSubscriber({ method: 'DiscoveryService.QueueingFailed' }) - public DiscoveryQueueingFailed(queueingFailedDate: Date) { - console.log('DiscoveryQueueingFailed', queueingFailedDate); - } - - /** - * DiscoverHost - */ - @RPCSubscriber({ method: 'DiscoveryService.QueueingTimeout' }) - public DiscoveryQueueingTimeout(queueingTimeoutDate: Date) { - console.log('DiscoveryQueueingTimeout', queueingTimeoutDate); - this.discoveryRequestID = null; - } - - /** - * DiscoverHost - */ - @RPCSubscriber({ method: 'DiscoveryService.DiscoveryStart' }) - public DiscoveryStart(startDate: Date) { - console.log('DiscoveryStart', startDate); - this.discoveryStartDate = startDate; - - this.discoveryResult.start(); - } - - /** - * DiscoverHost - */ - @RPCSubscriber({ method: 'DiscoveryService.DiscoveryStop' }) - public DiscoveryStop(stopDate: Date) { - console.log('DiscoveryStop', stopDate); - - this.discoveryRequestID = null; - - const _millisecond = Math.abs(stopDate.getTime() - this.discoveryStartDate.getTime()); - - const _seconds = _millisecond / 1000; - const seconds = Math.floor(_seconds % 60); - const _minutes = _seconds / 60; - const minutes = Math.floor(_minutes % 60); - const _hours = _minutes / 60; - const hours = Math.floor(_hours % 24); - const days = Math.floor(_hours / 24); - - console.log(`${days}일 ${hours}시 ${minutes}분 ${seconds}초`); - - let hostCount = 0; - let serviceCount = 0; - - this.nodes.forEach((node) => { - switch (node.group) { - case 'host': - hostCount++; - break; - case 'service': - serviceCount++; - break; - - default: - break; - } - }); - - console.log(`Host: ${hostCount}개 Service: ${serviceCount}개`); - - this.discoveryConfigService.setDiscoveryStatus(DiscoveryStatusType.Stopped); - - this.simulationRestart(true); - this.zoomToFit(0.95, 500); - - this.discoveryResult.stop(hours, minutes, seconds); - // this.resultMsg = []; - // this.resultMsg.push(hostCount + ''); - // this.resultMsg.push(serviceCount + ''); - // this.resultMsg.push(hours + ':' + minutes + ':' + seconds); - this.stopping = false; - } - - /** - * DiscoveryMode - */ - @RPCSubscriber({ method: 'DiscoveryService.DiscoveryMode' }) - public DiscoveryMode(mode: DiscoveryModeType) { - this.discoveryConfigService.setDiscoveryMode(mode); - } - - /** - * DiscoverHost - */ - @RPCSubscriber({ method: 'DiscoveryService.DiscoveryError' }) - public DiscoveryError(err: RPCError) { - console.log('DiscoveryError', err); - } - - /** - * DiscoveredHost - */ - @RPCSubscriber({ method: 'DiscoveryService.DiscoveredHost' }) - public DiscoveredHost(host: Host) { - console.log('DiscoveredHost', host); - - // if (this.hosts.has(host.address)) { - - // } - - // let dup = false; - // this.hosts.forEach((item) => { - // if (item.address === host.address) { - // dup = true; - // return; - // } - // }); - // if (!dup) { - // this.hosts.push(host); - // } - this.hosts.set(host.address, host); - - const hostId = `${host.address}`; - - let hostNode = this.getNode(hostId); - if (null !== hostNode) { - hostNode.target = host; - } else { - hostNode = new Node(hostId); - hostNode.target = host; - hostNode.group = 'host'; - hostNode.r = 40; - hostNode.x = this.zoneNode.x; - hostNode.y = this.zoneNode.y; - - this.nodes.push(hostNode); - this.links.push(new Link(this.zoneNode, hostNode)); - - this.discoveryResult.increaseHost(); - } - - this.simulationRestart(); - } - - /** - * DiscoveredPort - */ - @RPCSubscriber({ method: 'DiscoveryService.DiscoveredPort' }) - public DiscoveredPort(port: Port) { - console.log('DiscoveredPort', port); - - let _ports: Map; - if (!this.ports.has(port.host.address)) { - _ports = new Map(); - this.ports.set(port.host.address, _ports); - } else { - _ports = this.ports.get(port.host.address); - } - - _ports.set(port.portNumber, port); - } - - /** - * DiscoveredService - */ - @RPCSubscriber({ method: 'DiscoveryService.DiscoveredService' }) - public DiscoveredService(service: Service) { - console.log('DiscoveredService', service); - const hostId = service.port.host.address; - const serviceId = `${service.port.host.address}-${service.port.portNumber}-${service.port.metaPortType.key}`; - - const hostNode = this.getNode(hostId); - let serviceNode = this.getNode(serviceId); - if (null !== serviceNode) { - serviceNode.target = service; - } else { - serviceNode = new Node(serviceId); - serviceNode.target = service; - serviceNode.group = 'service'; - serviceNode.r = 30; - serviceNode.x = hostNode.x; - serviceNode.y = hostNode.y; - - this.nodes.push(serviceNode); - this.links.push(new Link(hostNode, serviceNode)); - - this.discoveryResult.increaseService(); - } - - this.simulationRestart(); - } - - private getNode(id: string): Node | null { - let _n: Node = null; - this.nodes.some((node): boolean => { - if (node.id === id) { - _n = node; - return true; - } - return false; - }); - return _n; - } - - - otherHostSelected(host: Host) { - const foundNode = this.nodes.find(node => { - return node.group === 'host' && node.id === host.address; - }); - if (!foundNode) { - return; - } - this.selectedNode = foundNode; - } - - private setTestData() { - - const host1: Host = { - metaIPType: toMetaIPType(MetaIPTypeEnum.V4), - name: 'Host1', - address: '192.168.1.1', - mac: '44:8a:5b:f1:f1:f3', - osType: 'UNKNOWN', - hostType: 'HOST', - hostVendor: 'UNKNOWN', - hostModel: 'UNKNOWN', - zone: this.zone, - }; - - const hostNode = new Node('192.168.1.1'); - hostNode.group = 'host'; - hostNode.target = host1; - hostNode.r = 40; - - const host2: Host = { - metaIPType: toMetaIPType(MetaIPTypeEnum.V4), - name: 'Host2', - address: '192.168.1.2', - mac: '44:8a:5b:f1:f1:f3', - osType: 'UNKNOWN', - hostType: 'HOST', - hostVendor: 'UNKNOWN', - hostModel: 'UNKNOWN', - zone: this.zone, - }; - - const hostNode2 = new Node('192.168.1.2'); - hostNode2.group = 'host'; - hostNode2.target = host2; - hostNode2.r = 40; - - const host3: Host = { - metaIPType: toMetaIPType(MetaIPTypeEnum.V4), - name: 'Host2', - address: '192.168.1.3', - mac: '44:8a:5b:f1:f1:f3', - osType: 'UNKNOWN', - hostType: 'HOST', - hostVendor: 'UNKNOWN', - hostModel: 'UNKNOWN', - zone: this.zone, - }; - - const hostNode3 = new Node('192.168.1.3'); - hostNode3.group = 'host'; - hostNode3.target = host3; - hostNode3.r = 40; - - const serviceNode1 = new Node('192.168.1.1-10-HTTP'); - serviceNode1.target = { - metaCryptoType: toMetaCryptoType(MetaCryptoTypeEnum.NONE), - key: 'HTTP', - name: 'Apache', - serviceType: 'WEB', - serviceVendor: 'Apache', - serviceVersion: 'UNKNOWN', - port: { - host: host1, - portNumber: 10, - metaPortType: toMetaPortType(MetaPortTypeEnum.TCP), - }, - }; - serviceNode1.group = 'service'; - serviceNode1.r = 30; - - const serviceNode2 = new Node('192.168.1.1-20-HTTP'); - serviceNode2.target = { - metaCryptoType: toMetaCryptoType(MetaCryptoTypeEnum.NONE), - key: 'HTTP', - name: 'Apache', - serviceType: 'WEB', - serviceVendor: 'Apache', - serviceVersion: 'UNKNOWN', - port: { - host: host1, - portNumber: 20, - metaPortType: toMetaPortType(MetaPortTypeEnum.TCP), - }, - }; - serviceNode2.group = 'service'; - serviceNode2.r = 30; - - const serviceNode3 = new Node('192.168.1.1-30-HTTP'); - serviceNode3.target = { - metaCryptoType: toMetaCryptoType(MetaCryptoTypeEnum.NONE), - key: 'HTTP', - name: 'Apache', - serviceType: 'WEB', - serviceVendor: 'Apache', - serviceVersion: 'UNKNOWN', - port: { - host: host1, - portNumber: 30, - metaPortType: toMetaPortType(MetaPortTypeEnum.TCP), - }, - - }; - serviceNode3.group = 'service'; - serviceNode3.r = 30; - - - const serviceNode31 = new Node('192.168.1.3-10-HTTP'); - serviceNode31.target = { - metaCryptoType: toMetaCryptoType(MetaCryptoTypeEnum.NONE), - key: 'HTTP', - name: 'Apache', - serviceType: 'WEB', - serviceVendor: 'Apache', - serviceVersion: 'UNKNOWN', - port: { - host: host3, - portNumber: 10, - metaPortType: toMetaPortType(MetaPortTypeEnum.TCP), - }, - - }; - serviceNode31.group = 'service'; - serviceNode31.r = 30; - - const serviceNode32 = new Node('192.168.1.3-20-HTTP'); - serviceNode32.target = { - metaCryptoType: toMetaCryptoType(MetaCryptoTypeEnum.NONE), - key: 'HTTP', - name: 'Apache', - serviceType: 'WEB', - serviceVendor: 'Apache', - serviceVersion: 'UNKNOWN', - port: { - host: host3, - portNumber: 20, - metaPortType: toMetaPortType(MetaPortTypeEnum.TCP), - }, - - }; - serviceNode32.group = 'service'; - serviceNode32.r = 30; - - this.nodes.push( - hostNode, - hostNode2, - hostNode3, - serviceNode1, - serviceNode2, - serviceNode3, - serviceNode31, - serviceNode32, - ); - - this.links.push( - new Link(this.zoneNode, hostNode), - new Link(this.zoneNode, hostNode2), - new Link(this.zoneNode, hostNode3), - new Link(hostNode, serviceNode1), - new Link(hostNode, serviceNode2), - new Link(hostNode, serviceNode3), - new Link(hostNode3, serviceNode31), - new Link(hostNode3, serviceNode32), - ); + requestDiscover() { + DiscoverySession.requestDiscover(this.store, this.probeService); } } diff --git a/src/app/pages/home/home-page.module.ts b/src/app/pages/home/home-page.module.ts deleted file mode 100644 index e13afff..0000000 --- a/src/app/pages/home/home-page.module.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; - -import { CommonsUIModule } from '@overflow/commons/ui/commons-ui.module'; - -import { HomePageComponent } from './home-page.component'; -import { HomePageRoutingModule } from './home-page-routing.module'; -import { CommonsModule } from '../../../commons/commons.module'; - -@NgModule({ - imports: [ - CommonModule, - CommonsUIModule, - HomePageRoutingModule, - CommonsModule - ], - entryComponents: [ - ], - declarations: [HomePageComponent], -}) -export class HomePageModule { } diff --git a/src/app/pages/home/index.ts b/src/app/pages/home/index.ts new file mode 100644 index 0000000..af98644 --- /dev/null +++ b/src/app/pages/home/index.ts @@ -0,0 +1,5 @@ +import { HomePageComponent } from './home-page.component'; + +export const PAGES = [ + HomePageComponent, +]; diff --git a/src/app/pages/index.ts b/src/app/pages/index.ts new file mode 100644 index 0000000..1f581a6 --- /dev/null +++ b/src/app/pages/index.ts @@ -0,0 +1,8 @@ +import { PagesComponent } from './pages.component'; + +import { PAGES as HOME_PAGES } from './home'; + +export const PAGES = [ + PagesComponent, + ...HOME_PAGES, +]; diff --git a/src/app/pages/pages-routing.module.ts b/src/app/pages/pages-routing.module.ts deleted file mode 100644 index 6e8eed5..0000000 --- a/src/app/pages/pages-routing.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; -import { PagesComponent } from './pages.component'; - -const routes: Routes = [ - { - path: '', - component: PagesComponent, - children: [ - { path: '', redirectTo: 'home' }, - { path: 'home', loadChildren: './home/home-page.module#HomePageModule' }, - ] - } -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class PagesRoutingModule { } diff --git a/src/app/pages/pages.component.html b/src/app/pages/pages.component.html index 1be01f1..e53a4cb 100644 --- a/src/app/pages/pages.component.html +++ b/src/app/pages/pages.component.html @@ -1,9 +1,9 @@ - - + +
-
-
+