diff --git a/angular.json b/angular.json index 08f7aad0..6d335901 100644 --- a/angular.json +++ b/angular.json @@ -78,7 +78,13 @@ "scripts": true, "styles": true, "vendor": true - } + }, + "aot": true, + "optimization": true, + "sourceMap": false, + "extractLicenses": true, + "vendorChunk": true, + "buildOptimizer": true }, "development": { "buildOptimizer": false, diff --git a/build.sh b/build.sh index 1bcbaa96..c90d0c16 100755 --- a/build.sh +++ b/build.sh @@ -14,4 +14,4 @@ npm install npm install -g @angular/cli@16.0.0-next.0 ng build ziti-console-lib # WARN: deployUrl deprecated since Angular 13, pending decommission in future ng CLI -ng build ziti-console --base-href "$1" --deploy-url "$1" +ng build ziti-console --base-href "$1" --deploy-url "$1" --configuration "production" diff --git a/docker-images/ziti-console-assets/Dockerfile b/docker-images/ziti-console-assets/Dockerfile index 7aa35641..33abf4fa 100644 --- a/docker-images/ziti-console-assets/Dockerfile +++ b/docker-images/ziti-console-assets/Dockerfile @@ -17,7 +17,7 @@ RUN npm install --omit=optional COPY . . RUN ng build ziti-console-lib -RUN ng build ziti-console --base-href ${BASE_HREF} +RUN ng build ziti-console --base-href ${BASE_HREF} --configuration "production" ARG NODE_VERSION=${NODE_VERSION} FROM node:${NODE_VERSION}-bookworm-slim diff --git a/package.json b/package.json index 6b2ada5b..5daddf1d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "io.netfoundry.zac", - "version": "3.4.5", + "version": "3.4.7", "description": "Ziti Administration Console", "main": "server.js", "type": "module", diff --git a/projects/app-ziti-console/src/app/app-routing.module.ts b/projects/app-ziti-console/src/app/app-routing.module.ts index 782b8754..031176c5 100644 --- a/projects/app-ziti-console/src/app/app-routing.module.ts +++ b/projects/app-ziti-console/src/app/app-routing.module.ts @@ -148,12 +148,14 @@ const routes: Routes = [ path: 'configs', component: ConfigurationsPageComponent, canActivate: mapToCanActivate([AuthenticationGuard]), + canDeactivate: [DeactivateGuardService], runGuardsAndResolvers: 'always', }, { path: 'configs/:id', component: ConfigurationFormComponent, canActivate: mapToCanActivate([AuthenticationGuard]), + canDeactivate: [DeactivateGuardService], runGuardsAndResolvers: 'always', }, { diff --git a/projects/app-ziti-console/src/app/guards/authentication.guard.ts b/projects/app-ziti-console/src/app/guards/authentication.guard.ts index 54af7dad..1c88bb26 100644 --- a/projects/app-ziti-console/src/app/guards/authentication.guard.ts +++ b/projects/app-ziti-console/src/app/guards/authentication.guard.ts @@ -16,7 +16,13 @@ import {CanActivateFn, Router} from '@angular/router'; import {Inject, Injectable, InjectionToken} from "@angular/core"; -import {SettingsService, LoginServiceClass, SETTINGS_SERVICE, ZAC_LOGIN_SERVICE} from "ziti-console-lib"; +import { + SettingsService, + LoginServiceClass, + SETTINGS_SERVICE, + ZAC_LOGIN_SERVICE, + GrowlerService, GrowlerModel +} from "ziti-console-lib"; // @ts-ignore const {growler} = window; @@ -27,7 +33,8 @@ export class AuthenticationGuard { constructor( @Inject(ZAC_LOGIN_SERVICE) private loginService: LoginServiceClass, @Inject(SETTINGS_SERVICE) private settingsSvc: SettingsService, - private router: Router + private router: Router, + private growlerService: GrowlerService ) { } @@ -35,6 +42,9 @@ export class AuthenticationGuard { const isAuthorized = this.loginService.hasSession(); if (!isAuthorized) { // messaging.error('not authorized'); + this.settingsSvc.set(this.settingsSvc.settings); + const gorwlerData: GrowlerModel = new GrowlerModel('warning', 'Invalid Session', 'Session Expired', 'Your session is no longer valid. Please login to continue.'); + this.growlerService.show(gorwlerData); this.router.navigate(['/login']); } diff --git a/projects/app-ziti-console/src/app/interceptors/ziti-api.interceptor.ts b/projects/app-ziti-console/src/app/interceptors/ziti-api.interceptor.ts index 6f03d1dd..fb81bc2d 100644 --- a/projects/app-ziti-console/src/app/interceptors/ziti-api.interceptor.ts +++ b/projects/app-ziti-console/src/app/interceptors/ziti-api.interceptor.ts @@ -18,10 +18,19 @@ import {Injectable, Inject} from '@angular/core'; import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http'; import {map} from 'rxjs/operators'; -import {BehaviorSubject, filter, finalize, Observable, of, switchMap, take} from 'rxjs'; -import {SettingsServiceClass, LoginServiceClass, SETTINGS_SERVICE, ZAC_LOGIN_SERVICE} from "ziti-console-lib"; +import {BehaviorSubject, filter, finalize, Observable, of, switchMap, take, EMPTY} from 'rxjs'; +import { + SettingsServiceClass, + LoginServiceClass, + SETTINGS_SERVICE, + ZAC_LOGIN_SERVICE, + GrowlerModel, GrowlerService +} from "ziti-console-lib"; import moment from "moment/moment"; import {Router} from "@angular/router"; +import {MatDialog} from "@angular/material/dialog"; + +import {defer} from 'lodash'; /** Pass untouched request through to the next request handler. */ @Injectable({ @@ -33,7 +42,10 @@ export class ZitiApiInterceptor implements HttpInterceptor { constructor(@Inject(SETTINGS_SERVICE) private settingsService: SettingsServiceClass, @Inject(ZAC_LOGIN_SERVICE) private loginService: LoginServiceClass, - private router: Router) { + private router: Router, + private growlerService: GrowlerService, + private dialogRef: MatDialog + ) { } @@ -61,25 +73,15 @@ export class ZitiApiInterceptor implements HttpInterceptor { switchMap(() => next.handle(this.addAuthToken(req))) ); } else { - // I need to request a new token - this.refreshTokenInProgress = true; - this.refreshTokenSubject.next(null); - return this.refreshAuthToken().pipe( - switchMap((token) => { - if (token) { - this.refreshTokenSubject.next(token); - return next.handle(this.addAuthToken(req)); - } else { - throw ('Error refreshing token'); - } - }), - finalize(() => (this.refreshTokenInProgress = false)) - ); + this.handleUnauthorized(); + return EMPTY; } } } private refreshAuthToken() { + this.refreshTokenInProgress = true; + this.refreshTokenSubject.next(null); const apiVersions = this.settingsService.apiVersions; const prefix = apiVersions["edge-management"].v1.path; const url = this.settingsService.settings.selectedEdgeController; @@ -93,6 +95,15 @@ export class ZitiApiInterceptor implements HttpInterceptor { return of(null); } + private handleUnauthorized() { + const gorwlerData: GrowlerModel = new GrowlerModel('warning', 'Invalid Session', 'Session Expired', 'Your session is no longer valid. Please login to continue.'); + this.growlerService.show(gorwlerData); + defer(() => { + this.dialogRef.closeAll(); + }); + this.router.navigate(['/login']); + } + private addAuthToken(request: any) { const session = this.settingsService.settings.session; return request.clone({setHeaders: {"zt-session": session.id, 'content-type': 'application/json', accept: 'application/json'}}); diff --git a/projects/app-ziti-console/src/app/login/login.component.html b/projects/app-ziti-console/src/app/login/login.component.html index 699045f3..4ccbca3b 100644 --- a/projects/app-ziti-console/src/app/login/login.component.html +++ b/projects/app-ziti-console/src/app/login/login.component.html @@ -18,7 +18,7 @@ { + this.svc.originIsController = result; + if (this.svc.originIsController) { + this.svc.originIsController = true; + this.edgeCreate = false; + this.selectedEdgeController = window.location.origin; + this.controllerHostname = window.location.hostname; + this.settingsService.addContoller(this.controllerHostname, this.selectedEdgeController); + } else { + this.edgeChanged(); + this.initSettings(); + } + }).finally(() => { + this.isLoading = false; + }); } async login() { @@ -161,8 +167,9 @@ export class LoginComponent implements OnInit, OnDestroy { this.userLogin = false; } const serviceUrl = localStorage.getItem("ziti-serviceUrl-value"); - if (this.originIsController) { + if (this.svc.originIsController) { // the origin is already selected as the target ziti controller so no need to set it again. + this.selectedEdgeController = settings.selectedEdgeController; return; } if (!isEmpty(serviceUrl)) { diff --git a/projects/ziti-console-lib/package.json b/projects/ziti-console-lib/package.json index f680b85a..e42ace44 100644 --- a/projects/ziti-console-lib/package.json +++ b/projects/ziti-console-lib/package.json @@ -1,6 +1,6 @@ { "name": "@openziti/ziti-console-lib", - "version": "0.4.5", + "version": "0.4.9", "repository": { "type": "git", "url": "https://github.com/openziti/ziti-console" diff --git a/projects/ziti-console-lib/src/lib/assets/images/visualizer/node-click.png b/projects/ziti-console-lib/src/lib/assets/images/visualizer/node-click.png new file mode 100644 index 00000000..3cf22201 Binary files /dev/null and b/projects/ziti-console-lib/src/lib/assets/images/visualizer/node-click.png differ diff --git a/projects/ziti-console-lib/src/lib/assets/styles/ziti.css b/projects/ziti-console-lib/src/lib/assets/styles/ziti.css index 708f8812..013ffb91 100644 --- a/projects/ziti-console-lib/src/lib/assets/styles/ziti.css +++ b/projects/ziti-console-lib/src/lib/assets/styles/ziti.css @@ -11,6 +11,7 @@ body { --black: #000; --shaded: #f6f7fb; --formBackground: #f6f7fb; + --visualizerBackground: #ffffff80; --background: #ffffff; --placeholder: #CDCDCD; --text: #000; diff --git a/projects/ziti-console-lib/src/lib/assets/svgs/cursor-pointer.svg b/projects/ziti-console-lib/src/lib/assets/svgs/cursor-pointer.svg new file mode 100644 index 00000000..f83fe6ee --- /dev/null +++ b/projects/ziti-console-lib/src/lib/assets/svgs/cursor-pointer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/projects/ziti-console-lib/src/lib/features/config-editor/config-editor.component.ts b/projects/ziti-console-lib/src/lib/features/config-editor/config-editor.component.ts index 60063d01..d396a5d7 100644 --- a/projects/ziti-console-lib/src/lib/features/config-editor/config-editor.component.ts +++ b/projects/ziti-console-lib/src/lib/features/config-editor/config-editor.component.ts @@ -236,6 +236,7 @@ export class ConfigEditorComponent implements OnInit { jsonDataChanged(event) { defer(() => { this.configDataChange.emit(this.configData); + this.updateFormView(this.items, this.configData); }); } } diff --git a/projects/ziti-console-lib/src/lib/features/confirm/confirm.component.html b/projects/ziti-console-lib/src/lib/features/confirm/confirm.component.html index acf5707c..e573c166 100644 --- a/projects/ziti-console-lib/src/lib/features/confirm/confirm.component.html +++ b/projects/ziti-console-lib/src/lib/features/confirm/confirm.component.html @@ -7,7 +7,7 @@ {{dataObj.subtitle}}
-
+
@@ -17,6 +17,9 @@
  • {{item}}
  • +
    + +
    diff --git a/projects/ziti-console-lib/src/lib/features/confirm/confirm.component.scss b/projects/ziti-console-lib/src/lib/features/confirm/confirm.component.scss index edc45565..70706574 100644 --- a/projects/ziti-console-lib/src/lib/features/confirm/confirm.component.scss +++ b/projects/ziti-console-lib/src/lib/features/confirm/confirm.component.scss @@ -20,7 +20,6 @@ .confirm-modal-icon { width: 120px; height: 120px; - background-image: url(../../assets/svgs/Confirm_Trash.svg); background-size: contain; z-index: 20; box-sizing: var(--shadow); @@ -46,6 +45,7 @@ filter: brightness(50%); } } + .confirm-message-sub-content, .confirm-message-content { color: var(--text); font-size: 14px; @@ -58,7 +58,7 @@ overflow: auto; display: flex; align-items: center; - justify-content: center; + justify-content: flex-start; color: var(--tableText); max-width: 400px; } diff --git a/projects/ziti-console-lib/src/lib/features/confirm/confirm.component.ts b/projects/ziti-console-lib/src/lib/features/confirm/confirm.component.ts index 86417944..0a8b3b48 100644 --- a/projects/ziti-console-lib/src/lib/features/confirm/confirm.component.ts +++ b/projects/ziti-console-lib/src/lib/features/confirm/confirm.component.ts @@ -35,8 +35,10 @@ export class ConfirmComponent { confirmLabel: '', cancelLabel: '', bulletList: [], + submessage: '', showCancelButton: false, - showCancelLink: false + showCancelLink: false, + imageUrl: '../../assets/svgs/Confirm_Trash.svg' } constructor(private dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any) { diff --git a/projects/ziti-console-lib/src/lib/features/data-table/data-table-filter.service.ts b/projects/ziti-console-lib/src/lib/features/data-table/data-table-filter.service.ts index 760294f9..bad00ba4 100644 --- a/projects/ziti-console-lib/src/lib/features/data-table/data-table-filter.service.ts +++ b/projects/ziti-console-lib/src/lib/features/data-table/data-table-filter.service.ts @@ -8,6 +8,8 @@ export type FilterObj = { value: any; label: string; type?: string; + hidden?: boolean; + verb?: string } @Injectable({ @@ -26,7 +28,7 @@ export class DataTableFilterService { } updateFilter(filterObj: FilterObj) { - if(isEmpty(filterObj.value) && isNaN(filterObj.value)) this.removeFilter(filterObj); + if(isEmpty(filterObj.value) && (isNaN(filterObj.value) || filterObj.value === '')) this.removeFilter(filterObj); else { let isFound = false; for (let idx = 0; idx < this.filters.length; idx++) { diff --git a/projects/ziti-console-lib/src/lib/features/data-table/data-table.component.html b/projects/ziti-console-lib/src/lib/features/data-table/data-table.component.html index e2669af3..37acd1bf 100644 --- a/projects/ziti-console-lib/src/lib/features/data-table/data-table.component.html +++ b/projects/ziti-console-lib/src/lib/features/data-table/data-table.component.html @@ -1,4 +1,4 @@ -
    +
    0; + if (_showTable && !this.resizeHandlerInit) { + this._initGridResizeHandler(); + } + return _showTable; + } + ngOnInit() { this.frameworkComponents = { cellSelectComponent: TableCellSelectComponent, @@ -471,6 +491,17 @@ export class DataTableComponent implements OnChanges, OnInit { return this.allowDownload && this._view !== 'process'; } + _initGridResizeHandler() { + if (!this.tableContainer) { + return; + } + const observer = new ResizeObserver(entries => { + this.resizeGridColumnsDebounced(); + }); + observer.observe(this.tableContainer.nativeElement); + this.resizeHandlerInit = true; + } + _initGridOptions() { this.gridOptions = { pagination: false, @@ -728,6 +759,18 @@ export class DataTableComponent implements OnChanges, OnInit { case 'services': this.entityTypeLabel = 'Services'; break; + case 'configurations': + this.entityTypeLabel = 'Configs'; + break; + case 'service-policies': + this.entityTypeLabel = 'Service Policies'; + break; + case 'edge-router-policies': + this.entityTypeLabel = 'Edge Router Policies'; + break; + case 'service-edge-router-policies': + this.entityTypeLabel = 'Service Edge Router Policies'; + break; } } diff --git a/projects/ziti-console-lib/src/lib/features/data-table/data-table.service.ts b/projects/ziti-console-lib/src/lib/features/data-table/data-table.service.ts index 0fb86feb..92f107e6 100644 --- a/projects/ziti-console-lib/src/lib/features/data-table/data-table.service.ts +++ b/projects/ziti-console-lib/src/lib/features/data-table/data-table.service.ts @@ -38,28 +38,28 @@ export class DataTableService { localStorage.setItem(`ziti_${this.tableId}_table_state`, tableStateCookie); } - onColumnsResized(tableId, gridObj, column) { - this.saveColumnWidths(tableId, gridObj, column); + onColumnsResized(column) { + this.saveColumnWidths(column); } - private saveColumnWidths(tableId, gridObj, column) { + private saveColumnWidths(column) { if (!column) { return; } const columnId = column.colId; const columnWidth = column.actualWidth; - let colWidthsCookie = localStorage.getItem(`ziti_${tableId}_column_widths`); + let colWidthsCookie = localStorage.getItem(`ziti_${this.tableId}_column_widths`); let colWidths: any = {}; if (!_.isEmpty(colWidthsCookie)) { colWidths = JSON.parse(colWidthsCookie); } colWidths[columnId] = columnWidth; colWidthsCookie = JSON.stringify(colWidths); - localStorage.setItem(`ziti_${tableId}_column_widths`, colWidthsCookie); + localStorage.setItem(`ziti_${this.tableId}_column_widths`, colWidthsCookie); - let tableStateCookie = localStorage.getItem(`ziti_${tableId}_table_state`); + let tableStateCookie = localStorage.getItem(`ziti_${this.tableId}_table_state`); let tableState; if (_.isEmpty(tableStateCookie)) { - tableState = gridObj.columnApi.getColumnState(); + tableState = this.gridObj.columnApi.getColumnState(); } else { tableState = JSON.parse(tableStateCookie); } @@ -70,8 +70,8 @@ export class DataTableService { } }); tableStateCookie = JSON.stringify(tableState); - localStorage.setItem(`ziti_${tableId}_table_state`, tableStateCookie); - _.forEach(gridObj.columnApi.columnModel.columnDefs, (colDef) => { + localStorage.setItem(`ziti_${this.tableId}_table_state`, tableStateCookie); + _.forEach(this.gridObj.columnApi.columnModel.columnDefs, (colDef) => { if (colDef.colId === columnId) { colDef.suppressSizeToFit = true; } diff --git a/projects/ziti-console-lib/src/lib/features/data-table/table-filter-bar/filter-bar.component.ts b/projects/ziti-console-lib/src/lib/features/data-table/table-filter-bar/filter-bar.component.ts index 84a17e72..f53458da 100644 --- a/projects/ziti-console-lib/src/lib/features/data-table/table-filter-bar/filter-bar.component.ts +++ b/projects/ziti-console-lib/src/lib/features/data-table/table-filter-bar/filter-bar.component.ts @@ -37,7 +37,9 @@ export class FilterBarComponent { constructor(public filterService: DataTableFilterService) { this.filterService.filtersChanged.subscribe(filters => { - this._filters = [...filters]; + this._filters = filters.filter((filter: any) => { + return filter.hidden !== true; + }); let tmp = ''; for (let idx = 0; idx < filters.length; idx++) { if (filters[idx].columnId === 'name') { diff --git a/projects/ziti-console-lib/src/lib/features/data-table/table-hidden-columns-bar/hidden-columns-bar.component.html b/projects/ziti-console-lib/src/lib/features/data-table/table-hidden-columns-bar/hidden-columns-bar.component.html index 2b8bca04..1e5547be 100644 --- a/projects/ziti-console-lib/src/lib/features/data-table/table-hidden-columns-bar/hidden-columns-bar.component.html +++ b/projects/ziti-console-lib/src/lib/features/data-table/table-hidden-columns-bar/hidden-columns-bar.component.html @@ -1,14 +1,16 @@ -
    -
    +
    +
    +
    -
    {{ col.headerName }}
    +
    {{ col.headerName }}
    +
    \ No newline at end of file diff --git a/projects/ziti-console-lib/src/lib/features/data-table/table-hidden-columns-bar/hidden-columns-bar.component.scss b/projects/ziti-console-lib/src/lib/features/data-table/table-hidden-columns-bar/hidden-columns-bar.component.scss index 0e544a67..9c7b2054 100644 --- a/projects/ziti-console-lib/src/lib/features/data-table/table-hidden-columns-bar/hidden-columns-bar.component.scss +++ b/projects/ziti-console-lib/src/lib/features/data-table/table-hidden-columns-bar/hidden-columns-bar.component.scss @@ -1,3 +1,12 @@ +.hidden-columns-bar-container { + align-items: center; + display: flex; + align-items: center; + justify-content: space-between; + flex-direction: row; + width: 100%; +} + .hidden-columns-list { align-items: center; display: flex; diff --git a/projects/ziti-console-lib/src/lib/features/dynamic-widgets/forwarding-config/forwarding-config.service.ts b/projects/ziti-console-lib/src/lib/features/dynamic-widgets/forwarding-config/forwarding-config.service.ts index 9f25f60a..89290170 100644 --- a/projects/ziti-console-lib/src/lib/features/dynamic-widgets/forwarding-config/forwarding-config.service.ts +++ b/projects/ziti-console-lib/src/lib/features/dynamic-widgets/forwarding-config/forwarding-config.service.ts @@ -26,9 +26,9 @@ export class ForwardingConfigService { getProperties(protocol, address, port, forwardProtocol, forwardAddress, forwardPort, allowedProtocols, allowedAddresses, allowedPortRanges) { const props = [ - {key: 'protocol', value: protocol}, - {key: 'address', value: address}, - {key: 'port', value: port}, + {key: 'protocol', value: forwardProtocol ? undefined : protocol}, + {key: 'address', value: forwardAddress ? undefined : address}, + {key: 'port', value: forwardPort ? undefined : port}, {key: 'forwardProtocol', value: forwardProtocol ? forwardProtocol : undefined}, {key: 'forwardAddress', value: forwardAddress ? forwardAddress : undefined}, {key: 'forwardPort', value: forwardPort ? forwardPort : undefined}, diff --git a/projects/ziti-console-lib/src/lib/features/extendable/extensions-noop.service.ts b/projects/ziti-console-lib/src/lib/features/extendable/extensions-noop.service.ts index a8829e8e..9e470951 100644 --- a/projects/ziti-console-lib/src/lib/features/extendable/extensions-noop.service.ts +++ b/projects/ziti-console-lib/src/lib/features/extendable/extensions-noop.service.ts @@ -25,6 +25,7 @@ export interface ExtensionService { closeAfterSave: boolean; moreActions?: any[]; listActions?: any[]; + extendOnInit(): void; extendAfterViewInits(extentionPoints: any): void; updateFormData(data: any): void; validateData(): Promise; @@ -44,6 +45,9 @@ export class ExtensionsNoopService implements ExtensionService { constructor() { } + extendOnInit(): void { + } + extendAfterViewInits(extentionPoints: any): void { } diff --git a/projects/ziti-console-lib/src/lib/features/json-view/json-view.component.ts b/projects/ziti-console-lib/src/lib/features/json-view/json-view.component.ts index 57d13107..3fc191ab 100644 --- a/projects/ziti-console-lib/src/lib/features/json-view/json-view.component.ts +++ b/projects/ziti-console-lib/src/lib/features/json-view/json-view.component.ts @@ -55,6 +55,8 @@ export class JsonViewComponent implements AfterViewInit, OnChanges { private schemaDefinitions: any; oldData: any; currentData: any; + editorInit = false; + constructor(private growlerService: GrowlerService) { this.onChangeDebounced = _.debounce(this.onChange.bind(this), 400); } @@ -74,10 +76,13 @@ export class JsonViewComponent implements AfterViewInit, OnChanges { } initEditor() { + if (!this.editorDiv || !this.data || this.editorInit) { + return; + } this.currentData = this.data; this.content = { text: undefined, - json: this.data + json: this.data || {} }; this.editor = new JSONEditor({ target: this.editorDiv.nativeElement, @@ -109,6 +114,7 @@ export class JsonViewComponent implements AfterViewInit, OnChanges { } } }); + this.editorInit = true; } updateEditor() { @@ -117,7 +123,7 @@ export class JsonViewComponent implements AfterViewInit, OnChanges { } this.oldData = _.cloneDeep(this.data); this.content = { - json: this.oldData + json: this.oldData || {} }; this.editor.update(this.content); } @@ -181,6 +187,10 @@ export class JsonViewComponent implements AfterViewInit, OnChanges { }); } } + } else if (changes['data']) { + if (!this.editorInit) { + this.initEditor(); + } } } diff --git a/projects/ziti-console-lib/src/lib/features/preview-list/preview-list.component.html b/projects/ziti-console-lib/src/lib/features/preview-list/preview-list.component.html index f77038ed..fb26eeab 100644 --- a/projects/ziti-console-lib/src/lib/features/preview-list/preview-list.component.html +++ b/projects/ziti-console-lib/src/lib/features/preview-list/preview-list.component.html @@ -3,9 +3,10 @@
    -
    -
    - {{ name }} +
    +
    + {{ item }} + {{ item.name }}
    diff --git a/projects/ziti-console-lib/src/lib/features/preview-list/preview-list.component.scss b/projects/ziti-console-lib/src/lib/features/preview-list/preview-list.component.scss index fb0d3676..af1b833c 100644 --- a/projects/ziti-console-lib/src/lib/features/preview-list/preview-list.component.scss +++ b/projects/ziti-console-lib/src/lib/features/preview-list/preview-list.component.scss @@ -53,6 +53,10 @@ align-items: center; gap: 5px; + a { + color: var(--primary); + } + .icon-clear { margin-top: 2px; &:hover { diff --git a/projects/ziti-console-lib/src/lib/features/preview-list/preview-list.component.ts b/projects/ziti-console-lib/src/lib/features/preview-list/preview-list.component.ts index 9306587c..3fc83fcd 100644 --- a/projects/ziti-console-lib/src/lib/features/preview-list/preview-list.component.ts +++ b/projects/ziti-console-lib/src/lib/features/preview-list/preview-list.component.ts @@ -15,6 +15,8 @@ */ import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {isEmpty} from 'lodash'; +import {Router} from "@angular/router"; @Component({ selector: 'lib-preview-list', @@ -22,51 +24,70 @@ import {Component, EventEmitter, Input, Output} from '@angular/core'; styleUrls: ['./preview-list.component.scss'] }) export class PreviewListComponent { - @Input() hideOption: string; + //@Input() hideOption: string; @Input() public label = ''; @Input() public clickable = false; @Input() isLoading = false; @Input() allNames = []; + @Input() items = []; @Input() allowRemove = false @Input() tooltip = ''; @Output() itemSelected = new EventEmitter(); @Output() itemRemoved = new EventEmitter(); - public names = []; + public previewItems = []; + private hideOption = 'asdf'; filterFor = ''; + constructor(private router: Router) {} + ngOnInit() { - this.names = []; - this.names.push(...this.allNames); - this.names = this.names.filter((item) => item !== this.hideOption); + this.previewItems = []; + if (!isEmpty(this.allNames)) { + this.previewItems.push(...this.allNames); + this.previewItems = this.previewItems.filter((item) => item !== this.hideOption); + } else { + this.previewItems.push(...this.items); + } this.sort(); } ngOnChanges() { - this.names = []; - this.names.push(...this.allNames); - this.names = this.names.filter((item) => item !== this.hideOption); + this.previewItems = []; + this.previewItems.push(...this.allNames); + this.previewItems = this.previewItems.filter((item) => item !== this.hideOption); this.sort(); } onKeydownEvent() { - this.names = []; + this.previewItems = []; for (let i = 0; i < this.allNames.length; i++) { - if (this.allNames[i].indexOf(this.filterFor) >= 0) { - this.names.push(this.allNames[i]); + if (!isEmpty(this.allNames)) { + if (this.allNames[i].indexOf(this.filterFor) >= 0) { + this.previewItems.push(this.allNames[i]); + } + } else { + if (this.items[i].name.indexOf(this.filterFor) >= 0) { + this.previewItems.push(this.items[i]); + } } } this.sort(); } sort() { - this.names.sort((item1, item2) => item1.localeCompare(item2)); + this.previewItems.sort((item1, item2) => (item1.name ? item1.name.localeCompare(item2.name) : item1.localeCompare(item2))); + } + + selected(item: string) { + if (this.clickable) this.itemSelected.emit(item); } - selected(name: string) { - if (this.clickable) this.itemSelected.emit(name); + remove(item) { + this.itemRemoved.emit(item); } - remove(name) { - this.itemRemoved.emit(name); + linkClicked(event, item) { + this.router.navigateByUrl(item.href); + event.preventDefault(); } } diff --git a/projects/ziti-console-lib/src/lib/features/projectable-forms/configuration/configuration-form.component.ts b/projects/ziti-console-lib/src/lib/features/projectable-forms/configuration/configuration-form.component.ts index 4b064c7b..874e2a14 100644 --- a/projects/ziti-console-lib/src/lib/features/projectable-forms/configuration/configuration-form.component.ts +++ b/projects/ziti-console-lib/src/lib/features/projectable-forms/configuration/configuration-form.component.ts @@ -36,6 +36,7 @@ import {SETTINGS_SERVICE, SettingsService} from "../../../services/settings.serv import {ZITI_DATA_SERVICE, ZitiDataService} from "../../../services/ziti-data.service"; import {ActivatedRoute, Router} from "@angular/router"; import {Config} from "../../../models/config"; +import {Location} from "@angular/common"; @Component({ selector: 'lib-configuration', @@ -74,8 +75,9 @@ export class ConfigurationFormComponent extends ProjectableForm implements OnIni @Inject(ZITI_DATA_SERVICE) override zitiService: ZitiDataService, protected override router: Router, protected override route: ActivatedRoute, + location: Location ) { - super(growlerService, extService, zitiService, router, route); + super(growlerService, extService, zitiService, router, route, location); } override ngOnInit(): void { @@ -180,7 +182,13 @@ export class ConfigurationFormComponent extends ProjectableForm implements OnIni this.isLoading = false; }); if (configId) { - this.closeModal(true, true); + this.initData = this.formData; + this._dataChange = false; + if (this.isModal) { + this.closeModal(true, true); + } else { + this.returnToListPage(); + } }; } diff --git a/projects/ziti-console-lib/src/lib/features/projectable-forms/edge-router-policy/edge-router-policy-form.component.ts b/projects/ziti-console-lib/src/lib/features/projectable-forms/edge-router-policy/edge-router-policy-form.component.ts index d2236b0c..3e2a4708 100644 --- a/projects/ziti-console-lib/src/lib/features/projectable-forms/edge-router-policy/edge-router-policy-form.component.ts +++ b/projects/ziti-console-lib/src/lib/features/projectable-forms/edge-router-policy/edge-router-policy-form.component.ts @@ -23,6 +23,7 @@ import {ExtensionService} from "../../extendable/extensions-noop.service"; import {GrowlerModel} from "../../messaging/growler.model"; import {EdgeRouterPolicy} from "../../../models/edge-router-policy"; import {ActivatedRoute, Router} from "@angular/router"; +import {Location} from "@angular/common"; @Component({ selector: 'lib-edge-router-policy-form', @@ -64,8 +65,9 @@ export class EdgeRouterPolicyFormComponent extends ProjectableForm implements On @Inject(EDGE_ROUTER_POLICY_EXTENSION_SERVICE) extService: ExtensionService, protected override router: Router, protected override route: ActivatedRoute, + location: Location ) { - super(growlerService, extService, zitiService, router, route); + super(growlerService, extService, zitiService, router, route, location); } override ngOnInit(): void { @@ -200,17 +202,18 @@ export class EdgeRouterPolicyFormComponent extends ProjectableForm implements On this.isLoading = true; this.applySelectedAttributes(); this.svc.save(this.formData).then((result) => { - if (result?.close) { - this.closeModal(true, true); - this.returnToListPage(); - } const data = result?.data?.id ? result.data : result; + this._dataChange = false; if (!isEmpty(data.id)) { this.formData = data || this.formData; this.initData = this.formData; } else { this.initData = this.formData; } + if (result?.close) { + this.closeModal(true, true); + this.returnToListPage(); + } }).finally(() => { this.isLoading = false; }); diff --git a/projects/ziti-console-lib/src/lib/features/projectable-forms/edge-router-policy/edge-router-policy-form.service.ts b/projects/ziti-console-lib/src/lib/features/projectable-forms/edge-router-policy/edge-router-policy-form.service.ts index 5da81c10..93f49d97 100644 --- a/projects/ziti-console-lib/src/lib/features/projectable-forms/edge-router-policy/edge-router-policy-form.service.ts +++ b/projects/ziti-console-lib/src/lib/features/projectable-forms/edge-router-policy/edge-router-policy-form.service.ts @@ -109,15 +109,7 @@ export class EdgeRouterPolicyFormService { this.associatedEdgeRouterNames = [...namedAttributes]; return; } - const filters = [ - { - columnId: "roleAttributes", - filterName: "Edge Router Attributes", - label: "", - type: "ATTRIBUTE", - value: roleAttributes - } - ]; + const filters = this.zitiService.getRoleFilter(roleAttributes); const paging = this.zitiService.DEFAULT_PAGING; paging.noSearch = false; this.zitiService.get('edge-routers', paging, filters).then((result: any) => { @@ -136,15 +128,7 @@ export class EdgeRouterPolicyFormService { this.associatedIdentityNames = [...namedAttributes]; return; } - const filters = [ - { - columnId: "roleAttributes", - filterName: "Identity Attributes", - label: "", - type: "ATTRIBUTE", - value: roleAttributes - } - ]; + const filters = this.zitiService.getRoleFilter(roleAttributes); const paging = this.zitiService.DEFAULT_PAGING; paging.noSearch = false; this.zitiService.get('identities', paging, filters).then((result: any) => { diff --git a/projects/ziti-console-lib/src/lib/features/projectable-forms/edge-router/edge-router-form.component.html b/projects/ziti-console-lib/src/lib/features/projectable-forms/edge-router/edge-router-form.component.html index 235bca27..a83eff70 100644 --- a/projects/ziti-console-lib/src/lib/features/projectable-forms/edge-router/edge-router-form.component.html +++ b/projects/ziti-console-lib/src/lib/features/projectable-forms/edge-router/edge-router-form.component.html @@ -85,7 +85,7 @@ [label]="'OPTIONAL'" class="form-field-advanced" > - +
    diff --git a/projects/ziti-console-lib/src/lib/features/projectable-forms/edge-router/edge-router-form.component.ts b/projects/ziti-console-lib/src/lib/features/projectable-forms/edge-router/edge-router-form.component.ts index fe86d3d6..41cbba1d 100644 --- a/projects/ziti-console-lib/src/lib/features/projectable-forms/edge-router/edge-router-form.component.ts +++ b/projects/ziti-console-lib/src/lib/features/projectable-forms/edge-router/edge-router-form.component.ts @@ -22,6 +22,7 @@ import {MatDialogRef} from "@angular/material/dialog"; import {ExtensionService} from "../../extendable/extensions-noop.service"; import {ActivatedRoute, Router} from "@angular/router"; import {EdgeRouter} from "../../../models/edge-router"; +import {Location} from "@angular/common"; @Component({ selector: 'lib-edge-router-form', @@ -61,8 +62,9 @@ export class EdgeRouterFormComponent extends ProjectableForm implements OnInit, @Inject(EDGE_ROUTER_EXTENSION_SERVICE) extService: ExtensionService, protected override router: Router, protected override route: ActivatedRoute, + location: Location ) { - super(growlerService, extService, zitiService, router, route); + super(growlerService, extService, zitiService, router, route, location); this.edgeRouterRoleAttributes = []; } diff --git a/projects/ziti-console-lib/src/lib/features/projectable-forms/identity/identity-form.component.ts b/projects/ziti-console-lib/src/lib/features/projectable-forms/identity/identity-form.component.ts index 1b0cb575..9cfc6875 100644 --- a/projects/ziti-console-lib/src/lib/features/projectable-forms/identity/identity-form.component.ts +++ b/projects/ziti-console-lib/src/lib/features/projectable-forms/identity/identity-form.component.ts @@ -37,6 +37,7 @@ import {IdentitiesPageService} from "../../../pages/identities/identities-page.s import {ExtensionService} from "../../extendable/extensions-noop.service"; import {ActivatedRoute, Router} from "@angular/router"; import {Identity} from "../../../models/identity"; +import {Location} from "@angular/common"; @Component({ selector: 'lib-identity-form', @@ -88,8 +89,9 @@ export class IdentityFormComponent extends ProjectableForm implements OnInit, On @Inject(IDENTITY_EXTENSION_SERVICE) extService: ExtensionService, protected override router: Router, protected override route: ActivatedRoute, + location: Location ) { - super(growlerService, extService, zitiService, router, route); + super(growlerService, extService, zitiService, router, route, location); this.identityRoleAttributes = []; } diff --git a/projects/ziti-console-lib/src/lib/features/projectable-forms/projectable-form.class.ts b/projects/ziti-console-lib/src/lib/features/projectable-forms/projectable-form.class.ts index 7b8885ab..12164bc9 100644 --- a/projects/ziti-console-lib/src/lib/features/projectable-forms/projectable-form.class.ts +++ b/projects/ziti-console-lib/src/lib/features/projectable-forms/projectable-form.class.ts @@ -28,7 +28,7 @@ import { ViewChild } from "@angular/core"; -import {defer, isEqual, unset, debounce, cloneDeep, isEmpty, slice} from "lodash"; +import {defer, isEqual, unset, debounce, cloneDeep, forEach, isArray, isEmpty, isObject, isNil, map, omitBy, slice} from "lodash"; import {GrowlerModel} from "../messaging/growler.model"; import {GrowlerService} from "../messaging/growler.service"; import {ExtensionService, SHAREDZ_EXTENSION} from "../extendable/extensions-noop.service"; @@ -36,6 +36,7 @@ import {Identity} from "../../models/identity"; import {ZITI_DATA_SERVICE, ZitiDataService} from "../../services/ziti-data.service"; import {ActivatedRoute, NavigationEnd, Router} from "@angular/router"; import {Subscription} from "rxjs"; +import {Location} from "@angular/common"; // @ts-ignore const {context, tags, resources, service, app} = window; @@ -71,6 +72,7 @@ export abstract class ProjectableForm extends ExtendableComponent implements DoC _dataChange = false; apiOptions = [{id: 'cli', label: 'Copy as CLI'}, {id: 'curl', label: 'Copy as CURL'}]; basePath = ''; + previousRoute; checkDataChangeDebounced = debounce(this.checkDataChange, 100, {maxWait: 100}); @@ -81,9 +83,11 @@ export abstract class ProjectableForm extends ExtendableComponent implements DoC @Inject(SHAREDZ_EXTENSION) protected extService: ExtensionService, @Inject(ZITI_DATA_SERVICE) protected zitiService: ZitiDataService, protected router?: Router, - protected route?: ActivatedRoute + protected route?: ActivatedRoute, + protected location?: Location ) { super(); + this.previousRoute = this.router.getCurrentNavigation().previousNavigation?.finalUrl?.toString(); this.subscription.add( this.route?.params?.subscribe(params => { const id = params['id']; @@ -231,7 +235,11 @@ export abstract class ProjectableForm extends ExtendableComponent implements DoC } returnToListPage() { - this.router?.navigateByUrl(`${this.basePath}`); + if (this.location && this.previousRoute) { + this.location.back(); + } else { + this.router?.navigateByUrl(`${this.basePath}`); + } } ngDoCheck() { @@ -239,7 +247,11 @@ export abstract class ProjectableForm extends ExtendableComponent implements DoC } protected checkDataChange() { - const dataChange = !isEqual(this.initData, this.formData); + let initData = cloneDeep(this.initData); + initData = this.omitEmptyData(initData); + let formData = cloneDeep(this.formData); + formData = this.omitEmptyData(formData); + const dataChange = !isEqual(initData, formData); if (dataChange !== this._dataChange) { this.dataChange.emit(dataChange); } @@ -247,6 +259,21 @@ export abstract class ProjectableForm extends ExtendableComponent implements DoC app.isDirty = false; } + omitEmptyData(object) { + forEach(object, (val, key) => { + if(this.omitDataIteratee(val)) { + unset(object, key); + } else if(isObject(val)) { + this.omitEmptyData(val); + } + }); + return object; + } + + omitDataIteratee(val) { + return isNil(val) || (isArray(val) && isEmpty(val)); + } + copyToClipboard(val) { navigator.clipboard.writeText(val); const growlerData = new GrowlerModel( diff --git a/projects/ziti-console-lib/src/lib/features/projectable-forms/service-edge-router-policy/service-edge-router-policy-form.component.html b/projects/ziti-console-lib/src/lib/features/projectable-forms/service-edge-router-policy/service-edge-router-policy-form.component.html index 65caefb0..29f77d19 100644 --- a/projects/ziti-console-lib/src/lib/features/projectable-forms/service-edge-router-policy/service-edge-router-policy-form.component.html +++ b/projects/ziti-console-lib/src/lib/features/projectable-forms/service-edge-router-policy/service-edge-router-policy-form.component.html @@ -23,7 +23,7 @@ /> { - if (result?.close) { - this.closeModal(true, true); - this.returnToListPage(); - } const data = result?.data?.id ? result.data : result; + this._dataChange = false; if (!isEmpty(data.id)) { this.formData = data || this.formData; this.initData = this.formData; } else { this.initData = this.formData; } + if (result?.close) { + this.closeModal(true, true); + this.returnToListPage(); + } }).finally(() => { this.isLoading = false; }); diff --git a/projects/ziti-console-lib/src/lib/features/projectable-forms/service-edge-router-policy/service-edge-router-policy-form.service.ts b/projects/ziti-console-lib/src/lib/features/projectable-forms/service-edge-router-policy/service-edge-router-policy-form.service.ts index 78ad3b96..1603609f 100644 --- a/projects/ziti-console-lib/src/lib/features/projectable-forms/service-edge-router-policy/service-edge-router-policy-form.service.ts +++ b/projects/ziti-console-lib/src/lib/features/projectable-forms/service-edge-router-policy/service-edge-router-policy-form.service.ts @@ -91,15 +91,7 @@ export class ServiceEdgeRouterPolicyFormService { this.associatedEdgeRouterNames = [...namedAttributes]; return; } - const filters = [ - { - columnId: "roleAttributes", - filterName: "Edge Router Attributes", - label: "", - type: "ATTRIBUTE", - value: roleAttributes - } - ]; + const filters = this.zitiService.getRoleFilter(roleAttributes); const paging = this.zitiService.DEFAULT_PAGING; paging.noSearch = false; this.zitiService.get('edge-routers', paging, filters).then((result: any) => { @@ -118,15 +110,7 @@ export class ServiceEdgeRouterPolicyFormService { this.associatedServiceNames = [...namedAttributes]; return; } - const filters = [ - { - columnId: "roleAttributes", - filterName: "Service Attributes", - label: "", - type: "ATTRIBUTE", - value: roleAttributes - } - ]; + const filters = this.zitiService.getRoleFilter(roleAttributes); const paging = this.zitiService.DEFAULT_PAGING; paging.noSearch = false; this.zitiService.get('services', paging, filters).then((result: any) => { diff --git a/projects/ziti-console-lib/src/lib/features/projectable-forms/service-policy/service-policy-form.component.ts b/projects/ziti-console-lib/src/lib/features/projectable-forms/service-policy/service-policy-form.component.ts index 096df9f7..194437cf 100644 --- a/projects/ziti-console-lib/src/lib/features/projectable-forms/service-policy/service-policy-form.component.ts +++ b/projects/ziti-console-lib/src/lib/features/projectable-forms/service-policy/service-policy-form.component.ts @@ -23,6 +23,7 @@ import {ExtensionService} from "../../extendable/extensions-noop.service"; import {ServicePolicy} from "../../../models/service-policy"; import {GrowlerModel} from "../../messaging/growler.model"; import {ActivatedRoute, Router} from "@angular/router"; +import {Location} from "@angular/common"; @Component({ selector: 'lib-service-policy-form', @@ -66,8 +67,9 @@ export class ServicePolicyFormComponent extends ProjectableForm implements OnIni @Inject(SERVICE_POLICY_EXTENSION_SERVICE) extService: ExtensionService, protected override router: Router, protected override route: ActivatedRoute, + location: Location ) { - super(growlerService, extService, zitiService, router, route); + super(growlerService, extService, zitiService, router, route, location); } override ngOnInit(): void { @@ -195,6 +197,7 @@ export class ServicePolicyFormComponent extends ProjectableForm implements OnIni this.closeModal(true, true); } const data = result?.data?.id ? result.data : result; + this._dataChange = false; if (!isEmpty(data.id)) { this.formData = data || this.formData; this.initData = this.formData; diff --git a/projects/ziti-console-lib/src/lib/features/projectable-forms/service-policy/service-policy-form.service.ts b/projects/ziti-console-lib/src/lib/features/projectable-forms/service-policy/service-policy-form.service.ts index 800a2108..e9587a14 100644 --- a/projects/ziti-console-lib/src/lib/features/projectable-forms/service-policy/service-policy-form.service.ts +++ b/projects/ziti-console-lib/src/lib/features/projectable-forms/service-policy/service-policy-form.service.ts @@ -123,15 +123,7 @@ export class ServicePolicyFormService { this.associatedServiceNames = [...namedAttributes]; return; } - const filters = [ - { - columnId: "roleAttributes", - filterName: "Service Attributes", - label: "", - type: "ATTRIBUTE", - value: roleAttributes - } - ]; + const filters = this.zitiService.getRoleFilter(roleAttributes); const paging = this.zitiService.DEFAULT_PAGING; paging.noSearch = false; this.zitiService.get('services', paging, filters).then((result: any) => { @@ -150,15 +142,7 @@ export class ServicePolicyFormService { this.associatedIdentityNames = [...namedAttributes]; return; } - const filters = [ - { - columnId: "roleAttributes", - filterName: "Identity Attributes", - label: "", - type: "ATTRIBUTE", - value: roleAttributes - } - ]; + const filters = this.zitiService.getRoleFilter(roleAttributes); const paging = this.zitiService.DEFAULT_PAGING; paging.noSearch = false; this.zitiService.get('identities', paging, filters).then((result: any) => { @@ -177,15 +161,7 @@ export class ServicePolicyFormService { this.associatedPostureCheckNames = [...namedAttributes]; return; } - const filters = [ - { - columnId: "roleAttributes", - filterName: "Posture Check Attributes", - label: "", - type: "ATTRIBUTE", - value: roleAttributes - } - ]; + const filters = this.zitiService.getRoleFilter(roleAttributes); const paging = this.zitiService.DEFAULT_PAGING; paging.noSearch = false; this.zitiService.get('posture-checks', paging, filters).then((result: any) => { diff --git a/projects/ziti-console-lib/src/lib/features/projectable-forms/service/service-form.component.html b/projects/ziti-console-lib/src/lib/features/projectable-forms/service/service-form.component.html index 7f9cb83c..823e7b22 100644 --- a/projects/ziti-console-lib/src/lib/features/projectable-forms/service/service-form.component.html +++ b/projects/ziti-console-lib/src/lib/features/projectable-forms/service/service-form.component.html @@ -184,28 +184,21 @@ > - - -
    diff --git a/projects/ziti-console-lib/src/lib/features/projectable-forms/service/service-form.component.ts b/projects/ziti-console-lib/src/lib/features/projectable-forms/service/service-form.component.ts index d7f2108b..e3984ee2 100644 --- a/projects/ziti-console-lib/src/lib/features/projectable-forms/service/service-form.component.ts +++ b/projects/ziti-console-lib/src/lib/features/projectable-forms/service/service-form.component.ts @@ -43,6 +43,7 @@ import {ExtensionService} from "../../extendable/extensions-noop.service"; import {ConfigEditorComponent} from "../../config-editor/config-editor.component"; import {Service} from "../../../models/service"; import {ActivatedRoute, Router} from "@angular/router"; +import {Location} from "@angular/common"; @Component({ selector: 'lib-service-form', @@ -107,8 +108,9 @@ export class ServiceFormComponent extends ProjectableForm implements OnInit, OnC @Inject(SERVICE_EXTENSION_SERVICE) extService: ExtensionService, protected override router: Router, protected override route: ActivatedRoute, + location: Location ) { - super(growlerService, extService, zitiService, router, route); + super(growlerService, extService, zitiService, router, route, location); this.formData = {}; } @@ -204,7 +206,7 @@ export class ServiceFormComponent extends ProjectableForm implements OnInit, OnC break; case 'close': this.resetForm(); - this.router?.navigateByUrl(`/services`); + this.returnToListPage(); break; case 'toggle-view': this.formView = action.data; diff --git a/projects/ziti-console-lib/src/lib/features/projectable-forms/service/service-form.service.ts b/projects/ziti-console-lib/src/lib/features/projectable-forms/service/service-form.service.ts index 49eaf54d..b74f9781 100644 --- a/projects/ziti-console-lib/src/lib/features/projectable-forms/service/service-form.service.ts +++ b/projects/ziti-console-lib/src/lib/features/projectable-forms/service/service-form.service.ts @@ -47,6 +47,7 @@ export class ServiceFormService { } formData: any = {}; + selectedConfig: any = {}; configData: any; selectedConfigId: any = ''; configs: any = []; @@ -56,7 +57,7 @@ export class ServiceFormService { selectedConfigName: any = ''; selectedConfigTypeId: any = ''; selectedConfigType: any = {}; - addedConfigNames: any = []; + addedConfigs: any = []; addedTerminatorNames: any = []; addedTerminators: any = []; configJsonView = false; @@ -82,6 +83,7 @@ export class ServiceFormService { associatedTerminators: any = []; associatedServicePolicies: any = []; associatedServicePolicyNames: any = []; + associatedServicePoliciesMap: any = {}; lColorArray = [ 'white', @@ -173,23 +175,24 @@ export class ServiceFormService { this.zitiService.getSubdata('services', this.formData.id, 'service-policies').then((result: any) => { this.associatedServicePolicies = result.data; this.associatedServicePolicyNames = this.associatedServicePolicies.map((policy) => { + this.associatedServicePoliciesMap[policy.name] = policy; return policy.name; }); }); } - previewConfig(configName) { + previewConfig(configName, router) { this.showCfgPreviewOption = true; - this.configData = this.associatedConfigsMap[configName].data; - this.selectedConfigName = this.associatedConfigsMap[configName].name; - this.selectedConfigId = 'preview'; - this.selectedConfigTypeId = this.associatedConfigsMap[configName].configTypeId; - this.configTypeChanged(false); - this.selectedConfigId = 'preview'; - this.newConfigName = this.selectedConfigName; - defer(() => { - this.selectedConfigId = 'preview'; - }) + this.selectedConfig = this.associatedConfigsMap[configName]; + router.navigate(['/configs/' + this.selectedConfig.id]); + return; + } + + previewPolicy(policyName, router) { + this.showCfgPreviewOption = true; + this.selectedConfig = this.associatedServicePoliciesMap[policyName]; + router.navigate(['/service-policies/' + this.selectedConfig.id]); + return; } addTerminators(serviceId) { @@ -235,9 +238,10 @@ export class ServiceFormService { this.zitiService.getSubdata('services', this.formData.id, 'configs').then((result: any) => { this.associatedConfigs = result.data; this.associatedConfigsMap = {}; - this.addedConfigNames = this.associatedConfigs.map((cfg) => { + this.addedConfigs = this.associatedConfigs.map((cfg) => { this.associatedConfigsMap[cfg.name] = cfg; - return cfg.name; + cfg.href = '/configs/' + cfg.id; + return cfg; }); }); } @@ -287,14 +291,15 @@ export class ServiceFormService { } updatedAddedConfigs() { - this.addedConfigNames = []; + this.addedConfigs = []; this.configs.forEach((availableConfig) => { const cfgExists = some(this.formData.configs, configId => { return availableConfig.id == configId; }); if (cfgExists) { - this.addedConfigNames.push(availableConfig.name); - this.addedConfigNames = [...this.addedConfigNames]; + availableConfig.href = '/configs/' + availableConfig.id; + this.addedConfigs.push(availableConfig); + this.addedConfigs = [...this.addedConfigs]; } }) } @@ -330,6 +335,7 @@ export class ServiceFormService { .then((result) => { const cfg = result?.data; newConfig.id = result?.id ? result.id : result?.data?.id; + newConfig.href = '/configs/' + newConfig.id; this.associatedConfigsMap[newConfig.name] = {data: newConfig.data, name: newConfig.name, configTypeId: newConfig.configTypeId}; return newConfig.id; }) @@ -351,8 +357,8 @@ export class ServiceFormService { if (!isEmpty(configId)) { this.saveDisabled = false; this.formData.configs.push(configId); - this.addedConfigNames.push(this.newConfigName); - this.addedConfigNames = [...this.addedConfigNames]; + this.addedConfigs.push(newConfig); + this.addedConfigs = [...this.addedConfigs]; this.getConfigs(); const growlerData = new GrowlerModel( 'success', @@ -376,15 +382,16 @@ export class ServiceFormService { } }); if (!configAdded) { - let configName = ''; + let newConfig; this.configs.forEach((config) => { if (config.id === addedConfigId) { - configName = config.name; + newConfig = config; } }); - if (!isEmpty(configName)) { - this.addedConfigNames.push(configName); - this.addedConfigNames = [...this.addedConfigNames]; + if (newConfig) { + newConfig.href = '/configs/' + newConfig.id; + this.addedConfigs.push(newConfig); + this.addedConfigs = [...this.addedConfigs]; } this.formData.configs.push(addedConfigId); this.selectedConfigTypeId = ''; @@ -402,10 +409,10 @@ export class ServiceFormService { } } - removeConfig(nameToRemove) { + removeConfig(configToRemove) { let configIdToRemove; this.configs.forEach((availableConfig) => { - if (availableConfig.name === nameToRemove) { + if (availableConfig.name === configToRemove.name) { configIdToRemove = availableConfig.id; } }); @@ -413,12 +420,12 @@ export class ServiceFormService { const newConfigs = filter(this.formData.configs, configId => { return configId !== configIdToRemove; }); - const newConfigNames = filter(this.addedConfigNames, (configName) => { - return configName !== nameToRemove; + const newConfigItems = filter(this.addedConfigs, (config) => { + return config.name !== configToRemove.name; }); this.formData.configs = newConfigs; - this.addedConfigNames = newConfigNames; - if (this.selectedConfigId === 'preview' && nameToRemove === this.selectedConfigName) { + this.addedConfigs = newConfigItems; + if (this.selectedConfigId === 'preview' && configToRemove.name === this.selectedConfigName) { this.selectedConfigId = ''; this.selectedConfigTypeId = ''; } diff --git a/projects/ziti-console-lib/src/lib/features/projectable-forms/service/simple-service/simple-service.component.html b/projects/ziti-console-lib/src/lib/features/projectable-forms/service/simple-service/simple-service.component.html index 78aa1fa0..4f5ace35 100644 --- a/projects/ziti-console-lib/src/lib/features/projectable-forms/service/simple-service/simple-service.component.html +++ b/projects/ziti-console-lib/src/lib/features/projectable-forms/service/simple-service/simple-service.component.html @@ -50,7 +50,7 @@
    @@ -117,7 +117,7 @@ diff --git a/projects/ziti-console-lib/src/lib/features/projectable-forms/service/simple-service/simple-service.component.ts b/projects/ziti-console-lib/src/lib/features/projectable-forms/service/simple-service/simple-service.component.ts index fae9e1fe..d865e52d 100644 --- a/projects/ziti-console-lib/src/lib/features/projectable-forms/service/simple-service/simple-service.component.ts +++ b/projects/ziti-console-lib/src/lib/features/projectable-forms/service/simple-service/simple-service.component.ts @@ -8,10 +8,12 @@ import {ExtensionService} from "../../../extendable/extensions-noop.service"; import {GrowlerModel} from "../../../messaging/growler.model"; import {MatDialog} from "@angular/material/dialog"; import {CreationSummaryDialogComponent} from "../../../creation-summary-dialog/creation-summary-dialog.component"; -import {isEmpty, isNil, isNaN, unset, cloneDeep, isEqual} from 'lodash'; +import {isEmpty, isNil, isNaN, unset, cloneDeep, isEqual, forEach} from 'lodash'; import {ValidationService} from '../../../../services/validation.service'; import {ServicesPageService} from "../../../../pages/services/services-page.service"; import {ActivatedRoute, Router} from "@angular/router"; +import {FilterObj} from "../../../data-table/data-table-filter.service"; +import {ConfirmComponent} from "../../../confirm/confirm.component"; // @ts-ignore const {app} = window; @@ -166,6 +168,9 @@ export class SimpleServiceComponent extends ProjectableForm { this.servicesPageService.getServiceRoleAttributes().then((result) => { this.serviceRoleAttributes = result.data; }); + this.servicesPageService.getIdentityRoleAttributes().then((result) => { + this.identityRoleAttributes = result.data; + }); } getIdentityNamedAttributes(filter?) { @@ -242,6 +247,10 @@ export class SimpleServiceComponent extends ProjectableForm { if (!this.validate()) { return; } + const hasConflicts: any = await this.checkNameConflicts(); + if (hasConflicts) { + return; + } const summaryData = this.showSummary(); this.serviceApiData.configs = []; await this.createInterceptConfig(summaryData); @@ -376,12 +385,100 @@ export class SimpleServiceComponent extends ProjectableForm { } else if (result === 'cancel') { /// Do nothing, stay on page } else { - this.closeModal(true, true, ''); + this.initDataModels(); + this._dataChange = false; + this.router?.navigateByUrl(`${this.basePath}`); } }); return summaryData; } + async checkNameConflicts() { + const filter: FilterObj = { + filterName: 'name', + columnId: 'name', + value: this.serviceApiData.name, + label: '', + type: 'TEXTINPUT', + hidden: false, + verb: '=' + } + + const results: any = { + serviceConflict: false, + interceptConflict: false, + hostConflict: false, + dialPolicyConflict: false, + bindPolicyConflict: false, + names: [] + }; + const paging = cloneDeep(this.zitiService.DEFAULT_PAGING); + paging.noSearch = false; + const serviceProm = this.zitiService.get('services', paging, [filter]).then((result) => { + if (result.data?.length > 0) { + results.serviceConflict = true; + results.names.push(this.serviceApiData.name); + } + }); + filter.value = this.interceptConfigApiData.name; + const interceptProm = this.zitiService.get('configs', paging, [filter]).then((result) => { + if (result.data?.length > 0) { + results.interceptConflict = true; + results.names.push(this.interceptConfigApiData.name); + } + }); + filter.value = this.hostConfigApiData.name; + const hostProm = this.zitiService.get('configs', paging, [filter]).then((result) => { + if (result.data?.length > 0) { + results.hostConflict = true; + results.names.push(this.hostConfigApiData.name); + } + }); + filter.value = this.dialPolicyApiData.name; + const dialProm = this.zitiService.get('service-policies', paging, [filter]).then((result) => { + if (result.data?.length > 0) { + results.dialPolicyConflict = true; + results.names.push(this.dialPolicyApiData.name); + } + }); + filter.value = this.bindPolicyApiData.name; + const bindProm = this.zitiService.get('service-policies', paging, [filter]).then((result) => { + if (result.data?.length > 0) { + results.bindPolicyConflict = true; + results.names.push(this.bindPolicyApiData.name); + } + }); + + return Promise.all([serviceProm, interceptProm, hostProm, dialProm, bindProm]).then(() => { + let hasConflict = results.names.length > 0; + if (hasConflict) { + this.errors.name = true; + this.showConflictWarning(results); + } + return hasConflict; + }); + } + + showConflictWarning(conflictData) { + + const data = { + appendId: 'SimpleServiceConflicts', + title: 'Name Conflict', + message: `The name you have entered has conflicts for the following entities`, + bulletList: conflictData.names, + submessage: `Please update the entered name and try again.`, + confirmLabel: 'Return', + showCancelLink: false, + imageUrl: '../../assets/svgs/Growl_Error.svg' + }; + this.dialogRef = this.dialogForm.open(ConfirmComponent, { + data: data, + autoFocus: false, + }); + this.dialogRef.afterClosed(() => { + this.nameFieldInput.nativeElement.focus(); + }) + } async createInterceptConfig(summaryData) { if (this.sdkOnlyDial) { return; @@ -660,6 +757,14 @@ export class SimpleServiceComponent extends ProjectableForm { --data-raw '${JSON.stringify(this.bindPolicyApiData)}' \\`; } + initDataModels() { + this.serviceApiData = this.initServiceApiData; + this.interceptConfigApiData.data = this.initInterceptConfigApiData.data; + this.hostConfigApiData.data = this.initHostConfigApiData.data; + this.dialPolicyApiData = this.initDialPolicyApiData; + this.bindPolicyApiData = this.initBindPolicyApiData; + } + override checkDataChange() { const serviceDataChange = !isEqual(this.initServiceApiData, this.serviceApiData); const interceptDataChange = !isEqual(this.initInterceptConfigApiData.data, this.interceptConfigApiData.data); diff --git a/projects/ziti-console-lib/src/lib/features/tag-selector/tag-selector.component.html b/projects/ziti-console-lib/src/lib/features/tag-selector/tag-selector.component.html index 29c145d2..9f246c84 100644 --- a/projects/ziti-console-lib/src/lib/features/tag-selector/tag-selector.component.html +++ b/projects/ziti-console-lib/src/lib/features/tag-selector/tag-selector.component.html @@ -57,6 +57,7 @@ (click)="$event.stopPropagation()" *ngFor="let item of _selectedNamedAttributes" [hidden]="item === this.hideOption" + class="select2-selection_choice_item" >
    Edge Router Policies +
    -
    No Results Found
    + {{ option }} @@ -76,6 +77,12 @@
    Service Router Policies
    + +
    +
    +
    View Associated Nodes
    +
    +
    diff --git a/projects/ziti-console-lib/src/lib/features/visualizer/network-visualizer/network-visualizer.component.scss b/projects/ziti-console-lib/src/lib/features/visualizer/network-visualizer/network-visualizer.component.scss index be23f73f..65e38601 100644 --- a/projects/ziti-console-lib/src/lib/features/visualizer/network-visualizer/network-visualizer.component.scss +++ b/projects/ziti-console-lib/src/lib/features/visualizer/network-visualizer/network-visualizer.component.scss @@ -26,8 +26,8 @@ padding-top: 1px; padding-left: 3px; padding-right: 3px; - background: #f5e9f2; - background-image: linear-gradient(#ecd3e6 2px, transparent 0), linear-gradient(90deg, #ecd3e6 2px, transparent 0), linear-gradient(#ecd3e6 1px, transparent 0), linear-gradient(90deg, #ecd3e6 1px, transparent 0); + background: #ebefff; + background-image: linear-gradient(#d1dcff 2px, transparent 0), linear-gradient(90deg, #d1dcff 2px, transparent 0), linear-gradient(#d1dcff 1px, transparent 0), linear-gradient(90deg, #d1dcff 1px, transparent 0); background-size: 40px 40px, 40px 40px, 10px 10px, 10px 10px; } @@ -35,7 +35,7 @@ height: 50px; font-size: 25px; font-weight: 600; - margin-left: 2px; + margin-left: 10px; margin-right: 0px; margin-top: 8px; margin-bottom: 0px; @@ -49,12 +49,11 @@ } .resource-type-select { - width: 230px; + width: 270px; flex-shrink: 0; border-radius: 27px; height: 51px; - margin-top: 20px; - margin-left: 20px; + margin-left: 5px; backdrop-filter: saturate(110%) blur(15px); background-color: rgba(255, 255, 255, 0.5) !important; border-color: var(--navigation); @@ -72,12 +71,13 @@ select { } .no-results-warning { - bottom: -20px; - left: 20px; + left: 440px; font-size: 15px; color: var(--tableText); padding-left: 20px; - padding-top: 5px; + padding-top: 0px; + height: fit-content; + top: 1px; } .resource-type-error { @@ -96,12 +96,12 @@ select { position: absolute; z-index: 99999; flex: 1 0 auto; - top:50px; + top: 60px; } .nf-net-vis-filter-container { - max-width: 300px; - margin-top: 20px; + max-width: 490px; + margin-top: 0px; margin-left: 10px; flex: 1 0 auto; } @@ -115,13 +115,21 @@ select { #ClearSearchFilter { position: absolute; - right: 2px; - top: 2px; + right: 15px; + top: 5px; width: 20px; height: 20px; - font-size: 25px; - color: var(--tableText); + font-size: 18px; + font-weight: 600; + color: var(--formBase); z-index: 99999999; + &:hover { + filter: brightness(90%); + } + &:active { + transform: translateY(1px); + filter: brightness(80%); + } } #popupTableTitle { @@ -184,12 +192,13 @@ select { .nf-topology-filter-input { height: 50px; + width: 485px; border-radius: 25px; border-color: var(--navigation); - background-color: var(--navigation); + border-color: var(--navigation); box-shadow: 0px 20px 23px 0px var(--shadow); - padding-left: 20px; - padding-right: 35px; + padding-left: 10px; + padding-right: 15px; } .nf-topology-select-container { @@ -216,16 +225,15 @@ select:focus { @supports ((-webkit-backdrop-filter: saturate(110%) blur(15px)) or (backdrop-filter: saturate(110%) blur(15px))) { .nf-topology-filter-input { - background: var(--glass) !important; - -webkit-backdrop-filter: saturate(110%) blur(15px); + background: var(--visualizerBackground) !important; + -webkit-backdrop-filter: saturate(110%) blur(5px); backdrop-filter: saturate(110%) blur(15px); } } .nf-topology-zoom-container { display: flex; - margin-left: 20px; - margin-top: 20px; + margin-left: 10px; flex: 1 0 auto; } @@ -349,28 +357,19 @@ select:focus { .topotip { display: none; position: fixed; - padding: 2px; background: var(--navigation); border: 0px; border-radius: 12px; pointer-events: none; box-shadow: 0px 20px 23px 0px var(--shadow); transition: background-color 0.3s; - padding: 10px; - font-size: 17px; - font-weight: 700; + font-size: 15px; + font-weight: 600; color: var(--tableText); z-index: 2000; opacity: 1; max-width: 400px; -} - -@supports ((-webkit-backdrop-filter: saturate(110%) blur(15px)) or (backdrop-filter: saturate(110%) blur(15px))) { - .topotip { - background: var(--glass); - -webkit-backdrop-filter: saturate(110%) blur(15px); - backdrop-filter: saturate(110%) blur(15px); - } + overflow: hidden; } .nodes { @@ -450,9 +449,16 @@ select:focus { cursor: pointer; } -.text-container { - cursor: pointer; -} +.node-text-container { + color: var(--tableText); + font-size: 24px; + font-weight: 600; + margin-left: 2px; + margin-top: 4px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } .link { fill: none; @@ -462,17 +468,22 @@ select:focus { } .nf-topology-legend { - width: 210px; + width: 230px; border-radius: 12px; margin-top: 10px; - margin-right: 20px; - position: fixed; + margin-right: 10px; + position: absolute; top: 0px; right: 0px; - background: var(--navigation); + background: #ffffff80 !important; + -webkit-backdrop-filter: saturate(110%) blur(5px); + backdrop-filter: saturate(110%) blur(15px); + border-color: var(--navigation); + border-style: solid; + border-width: 2px; display: inline-block; box-shadow: 0px 20px 23px 0px var(--shadow); - padding: 12px; + padding: 5px; padding-top: 5px; padding-bottom: 7px; } @@ -489,8 +500,14 @@ select:focus { width: 100%; height: 100%; display: inline-block; + position: relative; +} +.legend-itemA { + width: 100%; + height: 100%; + margin-left: -3px; + display: inline-block; } - .legend-circle { height: 12px; width: 12px; @@ -531,7 +548,28 @@ select:focus { padding-left: 7px; padding-bottom: 4px; } +.legend-icon-text { + display: inline-block; + font-size: 14px; + font-weight: 600; + color: var(--tableText); + padding-left: 22px; + padding-bottom: 4px; + &.legend-associated-nodes { + padding-left: 20px; + } +} +.legend-icon-pointer { + background-size: 16px 16px; + background-image: url(/assets/svgs/cursor-pointer.svg); + position: absolute; + z-index: 999999; + width: 20px; + height: 20px; + top: 10px; + left: 2px; +} .reset-button { height: 50px; width: 50px; @@ -545,6 +583,19 @@ select:focus { box-shadow: 0px 20px 23px 0px var(--shadow); transition: 0.3s; } +.node-expand-help { + border-radius: 20px; + border-width: 4px; + border-color: #08dc5a !important; + border-style: solid; + box-shadow: 0 20px 23px 0 var(--shadow); + width: 16px; + height: 16px; + position: absolute; + background-color: brown; + margin-left: -2px; + top: 2px; +} .reset-button:hover { filter: brightness(90%); @@ -907,10 +958,14 @@ path:not(.loader path, .highcharts-root path) { stroke-width: 12px; } -.node.edge-router-policy circle { - stroke: #ff8400 !important; +.node.edge-router circle { + stroke: #ffc000 !important; stroke-width: 12px; } +.node.edge-router-policy circle { + stroke: #ff8400 !important; + stroke-width: 12px; +} .node.service-edge-router-policy circle { stroke: #ff5900 !important; @@ -1029,5 +1084,43 @@ p-chips { stop-opacity: 0; } +.tool-tip-container { + font-size:14px; + font-family:Open Sans; + font-weight: 600; + font-color: var(--tableText); + + .prop-row { + display: grid; + grid-template-columns: 1fr 3fr; + grid-gap: 10px; + grid-template-areas: "propName propVal"; + height: -moz-fit-content; + height: fit-content; + min-height: 25px; + align-items: center; + padding: 0 10px; + &:nth-child(even) { + background-color: var(--formBackground); + } + .prop-name { + grid-area: propName; + font-weight: 700; + } + + .prop-val { + grid-area: propVal; + font-weight: 400; + } + } + .tooltip-help-text { + font-size: 12px; + padding-left: 10px; + padding-right: 10px; + display: flex; + height: 22px; + align-items: center; + } +} diff --git a/projects/ziti-console-lib/src/lib/features/visualizer/network-visualizer/network-visualizer.component.ts b/projects/ziti-console-lib/src/lib/features/visualizer/network-visualizer/network-visualizer.component.ts index e29f7b0d..cb3c9d84 100644 --- a/projects/ziti-console-lib/src/lib/features/visualizer/network-visualizer/network-visualizer.component.ts +++ b/projects/ziti-console-lib/src/lib/features/visualizer/network-visualizer/network-visualizer.component.ts @@ -17,12 +17,14 @@ import { ZITI_DATA_SERVICE, ZitiDataService, } from '../../../services/ziti-data.service'; +import {DataTableFilterService, FilterObj} from "../../data-table/data-table-filter.service"; import {catchError} from "rxjs/operators"; import {firstValueFrom, map, Observable} from "rxjs"; import { VisualizerServiceClass } from '../visualizer-service.class'; import * as d3 from 'd3'; import $ from 'jquery'; import _ from 'lodash'; +import {cloneDeep} from "lodash"; import { Subscription } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; import { take, tap } from 'rxjs/operators'; @@ -44,17 +46,31 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement title = 'Network Visualizer' public d3; uniqId = 0; + maxObjectsPerNode = 1001; treetooltip; fullScreen = false; isLoading = true; - noSearchResults = false; - services; - identities; - edgerouters; - service_policies; - router_policies; - service_router_policies; - configs; + noSearchResults = true; + services = []; + services_totalCount = 0; + all_services_fetched = false; + identities = []; + identities_totalCount = 0; + all_identities_fetched = false; + edgerouters = []; + edgerouters_totalCount = 0; + all_edgerouters_fetched = false; + service_policies = []; + service_policies_totalCount = 0; + all_service_policies_fetched = false; + edge_router_policies = []; + edge_router_policies_totalCount = 0; + all_edge_router_policies_fetched = false; + service_router_policies = []; + service_router_policies_totalCount = 0; + all_service_router_policies_fetched = false; + configs = []; + filerResponseMessage = 'search filter..'; baseNetworkGraph; networkGraph; currentNetwork; @@ -70,6 +86,7 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement autocompleteOptions; resourceTypeError = false; filterText = ''; + searchWord = ''; resourceType = ''; zoom; svg; @@ -91,6 +108,7 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement simulation; treeData; graphtooltip; + searchCache; constructor( @Inject(SETTINGS_SERVICE) public settingsService: SettingsService, @@ -109,16 +127,15 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement } getPagingObject(pagesize) { - const paging = { - searchOn: 'name', - noSearch: false, - filter: '', - total: pagesize, - page: 1, - sort: 'name', - order: 'asc', - }; - return paging; + return cloneDeep({ + filter: "", + noSearch: true, + order: "asc", + page: 1, + searchOn: "name", + sort: "name", + total: pagesize + }) } async getNetworkObjects() { @@ -132,11 +149,13 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement return await this.zitiService .get(`identities`, identities_paging, []) .then(async (result) => { - this.identities = result.data; + this.identities = result.data? result.data: []; + this.identities_totalCount = result.meta && result.meta.pagination? result.meta.pagination.totalCount : 0; if (!this.identities || this.identities.length === 0) { this.identities = []; this.isLoading = false; - } else { + } else if (this.identities_totalCount < this.maxObjectsPerNode ){ + this.all_identities_fetched = true; const pages = Math.ceil(result.meta.pagination.totalCount / pagesize); const promises = []; for (let page = 2; page <= pages; page++) { @@ -153,7 +172,9 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement }); promises.push(tmp_promise); } - Promise.all(promises).then(() => {}); + Promise.all(promises).then(() => { + // this.identities_totalCount = this.identities.length; + }); } }).catch(error => { this.logger.error('Error in fetching Identities', error.message); @@ -165,12 +186,13 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement return await this.zitiService .get(`services`, services_paging, []) .then( async (result) => { - this.services = result.data; - + this.services = result.data? result.data: []; + this.services_totalCount = result.meta && result.meta.pagination? result.meta.pagination.totalCount : 0; if (!this.services || this.services.length === 0) { this.services = []; this.isLoading = false; - } else { + } else if (this.services_totalCount < this.maxObjectsPerNode) { + this.all_services_fetched = true; const pages = Math.ceil(result.meta.pagination.totalCount / pagesize); const s_promises = []; for (let page = 2; page <= pages; page++) { @@ -188,7 +210,9 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement }); s_promises.push(tmp_promise); } - Promise.all(s_promises).then(() => { }); + Promise.all(s_promises).then(() => { + // this.services_totalCount = this.services.length; + }); } }).catch(error => { this.logger.error('Error in fetching services', error.message); @@ -200,12 +224,13 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement return await this.zitiService .get(`service-policies`, servicesPolicies_paging, []) .then( async (result) => { - this.service_policies = result.data; - + this.service_policies = result.data? result.data: []; + this.service_policies_totalCount = result.meta && result.meta.pagination? result.meta.pagination.totalCount : 0; if (!this.service_policies || this.service_policies.length === 0) { this.service_policies = []; this.isLoading = false; - } else { + } else if (this.service_policies_totalCount < this.maxObjectsPerNode) { + this.all_service_policies_fetched = true; const pages = Math.ceil(result.meta.pagination.totalCount / pagesize); const sp_promises = []; for (let page = 2; page <= pages; page++) { @@ -217,14 +242,16 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement [] ) .then((pageResult) => { - pageResult.data.forEach((serv) => { + pageResult.data && pageResult.data.forEach((serv) => { this.service_policies.push(serv); }); }); sp_promises.push(tmp_promise); } - Promise.all(sp_promises).then(() => { }); + Promise.all(sp_promises).then(() => { + // this.service_policies_totalCount = this.service_policies.length; + }); } }).catch(error => { this.logger.error('Error in fetching service-policies', error.message); @@ -236,11 +263,13 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement return await this.zitiService .get(`edge-routers`, routers_paging, []) .then( async (result) => { - this.edgerouters = result.data; + this.edgerouters = result.data? result.data: []; + this.edgerouters_totalCount = result.meta && result.meta.pagination? result.meta.pagination.totalCount : 0; if (!this.edgerouters || this.edgerouters.length === 0) { this.edgerouters = []; this.isLoading = false; - } else { + } else if (this.edgerouters_totalCount < this.maxObjectsPerNode) { + this.all_edgerouters_fetched = true; const pages = Math.ceil(result.meta.pagination.totalCount / pagesize); const r_promises = []; for (let page = 2; page <= pages; page++) { @@ -258,7 +287,9 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement }); r_promises.push(tmp_promise); } - Promise.all(r_promises).then(() => { }); + Promise.all(r_promises).then(() => { + // this.edgerouters_totalCount = this.edgerouters.length; + }); } }).catch(error => { this.logger.error('Error in fetching edge-routers', error.message); @@ -266,15 +297,17 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement } async findEdgeRouterPolicies(pagesize) { - this.router_policies = []; + this.edge_router_policies = []; const erpolicies_paging = this.getPagingObject(pagesize); return await this.zitiService .get(`edge-router-policies`, erpolicies_paging, []) .then(async (result) => { - this.router_policies = result.data; - if (!this.router_policies || this.router_policies.length === 0) { + this.edge_router_policies = result.data? result.data: []; + this.edge_router_policies_totalCount = result.meta && result.meta.pagination? result.meta.pagination.totalCount : 0; + if (!this.edge_router_policies || this.edge_router_policies.length === 0) { this.isLoading = false; - } else { + } else if (this.edge_router_policies_totalCount < this.maxObjectsPerNode) { + this.all_edge_router_policies_fetched = true; const pages = Math.ceil(result.meta.pagination.totalCount / pagesize); const r_promises = []; for (let page = 2; page <= pages; page++) { @@ -287,13 +320,15 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement ) .then((pageResult) => { pageResult.data.forEach((serv) => { - this.router_policies.push(serv); + this.edge_router_policies.push(serv); }); }); r_promises.push(tmp_promise); } - Promise.all(r_promises).then(() => { }); + Promise.all(r_promises).then(() => { + // this.edge_router_policies_totalCount = this.edge_router_policies.length; + }); } }).catch(error => { this.logger.error('Error in fetching edge-router-policies', error); @@ -305,11 +340,12 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement return await this.zitiService .get(`service-edge-router-policies`, serp_paging, []) .then(async (result) => { - this.service_router_policies = result.data; - + this.service_router_policies = result.data? result.data: []; + this.service_router_policies_totalCount = result.meta && result.meta.pagination? result.meta.pagination.totalCount : 0; if (!this.service_router_policies || this.service_router_policies.length === 0) { this.isLoading = false; - } else { + } else if (this.service_router_policies_totalCount < this.maxObjectsPerNode) { + this.all_service_router_policies_fetched = false; const pages = Math.ceil(result.meta.pagination.totalCount / pagesize); const r_promises = []; for (let page = 2; page <= pages; page++) { @@ -327,7 +363,9 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement }); r_promises.push(tmp_promise); } - Promise.all(r_promises).then(() => { }); + Promise.all(r_promises).then(() => { + // this.service_router_policies_totalCount = this.service_router_policies.length; + }); } }).catch(error => { this.logger.error('Error in fetching service-edge-router-policies', error); @@ -339,11 +377,11 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement return await this.zitiService .get(`configs`, configs_paging, []) .then(async (result) => { - this.configs = result.data; - + this.configs = result.data? result.data: []; + const configs_totalCount = result.meta && result.meta.pagination? result.meta.pagination.totalCount : 0; if (!this.configs || this.configs.length === 0) { this.configs = []; - } else { + } else if (configs_totalCount < this.maxObjectsPerNode) { const pages = Math.ceil(result.meta.pagination.totalCount / pagesize); const r_promises = []; for (let page = 2; page <= pages; page++) { @@ -361,10 +399,12 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement }); r_promises.push(tmp_promise); } - Promise.all(r_promises).then(() => { }); + Promise.all(r_promises).then(() => { + const configs_totalCount = this.configs.length; + }); } }).catch(error => { - this.logger.error('Error in fetching configs', error); + this.logger.error('Error in fetching configs', error); }); } @@ -396,17 +436,22 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement this.identities, this.service_policies, this.edgerouters, - this.router_policies, + this.edge_router_policies, this.service_router_policies, + this.services_totalCount, this.identities_totalCount, this.edgerouters_totalCount, + this.service_policies_totalCount, this.edge_router_policies_totalCount, + this.service_router_policies_totalCount, this.uniqId, - this.logger + this.logger, + this.all_service_policies_fetched, this.all_services_fetched, this.all_identities_fetched, this.all_edgerouters_fetched, + this.all_edge_router_policies_fetched, this.all_service_router_policies_fetched ); this.uniqId = treeObj.lastId; this.networkGraph = d3.hierarchy(treeObj, function (d) { return d.children; }); - this.baseNetworkGraph = this.networkGraph; + // this.baseNetworkGraph = _.cloneDeep(this.networkGraph); this.servicePoliciesTreeNodes = []; this.servicesTreeNodes = []; this.identitiesTreeNodes = []; @@ -415,65 +460,69 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement this.serviceEdgeRouterPolicyTreeNodes = []; this.servicePoliciesTreeNodes = this.fillAutoCompleteTreeNodes( - this.networkGraph.children[0], + this.service_policies, this.servicePoliciesTreeNodes, 'ServicePolicy' ); this.servicesTreeNodes = this.fillAutoCompleteTreeNodes( - this.networkGraph.children[1], + this.services, this.servicesTreeNodes, 'Service' ); this.identitiesTreeNodes = this.fillAutoCompleteTreeNodes( - this.networkGraph.children[2], + this.identities, this.identitiesTreeNodes, 'Identity' ); this.edgeroutersTreeNodes = this.fillAutoCompleteTreeNodes( - this.networkGraph.children[3], + this.edgerouters, this.edgeroutersTreeNodes, - 'Edge Router' + 'Router' ); + this.erPoliciesTreeNodes = this.fillAutoCompleteTreeNodes( - this.networkGraph.children[4], + this.edge_router_policies, this.erPoliciesTreeNodes, 'Router Policy' ); this.serviceEdgeRouterPolicyTreeNodes = this.fillAutoCompleteTreeNodes( - this.networkGraph.children[5], + this.service_router_policies, this.serviceEdgeRouterPolicyTreeNodes, 'Service Router Policy' ); this.initTopoView(); + this.baseNetworkGraph = _.cloneDeep(this.networkGraph); } catch (err:any) { this.logger.error(err.message); } this.isLoading = false; } - fillAutoCompleteTreeNodes(nodeObj, arr, typeName) { - const name = nodeObj.data.name; - // const id = nodeObj.data.id; - if (!name) { - return; - } - if (typeof nodeObj.data.type === 'undefined') { - // // - } else { - if (arr.indexOf(name) < 0 && nodeObj.data.type === typeName) { - arr.push(name); - } - } - const childNodes = nodeObj.children ? nodeObj.children : nodeObj._children; - for (let i = 0; childNodes && i < childNodes.length; i++) { - this.fillAutoCompleteTreeNodes(childNodes[i], arr, typeName); - } + fillAutoCompleteTreeNodes(resourcesArray, arr, typeName) { + resourcesArray && resourcesArray.forEach( (ob) => { + arr.push(ob.name); + }); + return arr; + } - return arr; - } + debounce(func, timeout = 150){ + let timer; + return (...args) => { + this.autocompleteOptions = []; + clearTimeout(timer); + timer = setTimeout(() => { func.apply(this, args); }, timeout); + }; + } + + nwSearchChange(event){ + if (this.filterText.length > 1 && event.key !=='Escape' && event.key !== 'ArrowRight' && event.key !=='ArrowLeft' && event.key !=='Enter') { + this.filerResponseMessage = 'search filter..'; + this.debounce(this.filterSearchArray()); + } + } autocompleteSearch(event) { const str = event ? event.option.value : ''; @@ -481,6 +530,8 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement } resourceTypeChanged() { + this.filerResponseMessage = 'search filter..'; + this.searchCache = []; this.resetSearchNode(); this.clearSearchLinkColors(); if (_.isEmpty(this.resourceType)) { @@ -491,18 +542,18 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement } this.filterText = ''; this.resourceTypeError = false; - - if (this.resourceType === 'service-policies') { + this.autocompleteOptions =[]; + if (this.resourceType === 'service-policies' && this.all_service_policies_fetched) { this.autocompleteOptions = this.servicePoliciesTreeNodes; - } else if (this.resourceType === 'services') { + } else if (this.resourceType === 'services' && this.all_services_fetched) { this.autocompleteOptions = this.servicesTreeNodes; - } else if (this.resourceType === 'identities') { + } else if (this.resourceType === 'identities' && this.all_identities_fetched) { this.autocompleteOptions = this.identitiesTreeNodes; - } else if (this.resourceType === 'edge-routers') { + } else if (this.resourceType === 'edge-routers' && this.all_edgerouters_fetched) { this.autocompleteOptions = this.edgeroutersTreeNodes; - } else if (this.resourceType === 'edge-router-policies') { + } else if (this.resourceType === 'edge-router-policies' && this.all_edge_router_policies_fetched) { this.autocompleteOptions = this.erPoliciesTreeNodes; - } else if (this.resourceType === 'service-edge-router-policies') { + } else if (this.resourceType === 'service-edge-router-policies' && this.all_service_router_policies_fetched) { this.autocompleteOptions = this.serviceEdgeRouterPolicyTreeNodes; } @@ -510,20 +561,18 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement this.searchResourceConditionCheck(this.filterText); } } + searchResourceConditionCheck(searchTxt) { if (this.resourceType === '' && searchTxt !== '') { - // if (searchTxt !== '') { this.resourceTypeError = true; return; } else { this.searchResourceInTree(searchTxt); } - } + searchResourceInTree(searchTxt) { - // this.closeTable(); this.noSearchResults = false; - this.resourceTypeError = false; this.resetTree(null); if (searchTxt === '') { @@ -531,29 +580,149 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement } else { this.resetSearchNode(); this.clearSearchLinkColors(); + let paths = []; let targetNodes = []; const nodeIds = []; - if (this.resourceType === 'service-policies') { - targetNodes = this.processSearchInPreorder(this.nodes[1], searchTxt, paths, targetNodes); - } else if (this.resourceType === 'edge-routers') { - targetNodes = this.processSearchInPreorder(this.nodes[4], searchTxt, paths, targetNodes); - } else if (this.resourceType === 'services') { - targetNodes = this.processSearchInPreorder(this.nodes[2], searchTxt, paths, targetNodes); - } else if (this.resourceType === 'identities') { - targetNodes = this.processSearchInPreorder(this.nodes[3], searchTxt, paths, targetNodes); - } else if (this.resourceType === 'edge-router-policies') { - targetNodes = this.processSearchInPreorder(this.nodes[5], searchTxt, paths, targetNodes); - } else if (this.resourceType === 'service-edge-router-policies') { - targetNodes = this.processSearchInPreorder(this.nodes[6], searchTxt, paths, targetNodes); + if (this.resourceType === 'service-policies' && !this.all_service_policies_fetched) { + const rawOb = this.searchCache.find((item) => item.name === searchTxt); + const id:any = this.topologyHelper.createServicePolicyNode(rawOb); + // id.clickProcess = "Not Yet"; + if(!this.isNodeExists(this.nodes[1], id)) { + this.nodes[1].data.children.push(id); + } + + this.networkGraph = d3.hierarchy(this.networkGraph.data, function (nd) { + return nd.children; + }); + this.networkGraph.children.forEach((nd,i) => { + if (i !==0) { + this.graphCollapse(nd); + } + } ); + this.service_policies = []; + this.service_policies[0] = rawOb; + this.updateTree(this.nodes[1]); + targetNodes = this.processSearchInPreorder(this.nodes[1], searchTxt, targetNodes); + } else if (this.resourceType === 'service-policies' && this.all_service_policies_fetched) { + targetNodes = this.processSearchInPreorder(this.nodes[1], searchTxt, targetNodes); + } else if (this.resourceType === 'edge-routers' && !this.all_edgerouters_fetched) { + const rawOb = this.searchCache.find((item) => item.name === searchTxt); + const id:any = this.topologyHelper.createEdgeRouterNode(rawOb); + // id.clickProcess = "Not Yet"; + if(!this.isNodeExists(this.nodes[4], id)) { + this.nodes[4].data.children.push(id); + } + this.networkGraph = d3.hierarchy(this.networkGraph.data, function (nd) { + return nd.children; + }); + this.networkGraph.children.forEach((nd,i) => { + if (i !== 3) { + this.graphCollapse(nd); + } + } ); + this.edgerouters = []; + this.edgerouters[0] = rawOb; + this.updateTree(this.nodes[4]); + + targetNodes = this.processSearchInPreorder(this.nodes[4], searchTxt, targetNodes); + } else if (this.resourceType === 'edge-routers' && this.all_edgerouters_fetched) { + targetNodes = this.processSearchInPreorder(this.nodes[4], searchTxt, targetNodes); + } else if (this.resourceType === 'services' && !this.all_services_fetched) { + const rawOb = this.searchCache.find((item) => item.name === searchTxt); + const id:any = this.topologyHelper.createServiceNode(rawOb); + id.clickProcess = "Not Yet"; + if(!this.isNodeExists(this.nodes[2], id)) { + this.nodes[2].data.children.push(id); + } + this.networkGraph = d3.hierarchy(this.networkGraph.data, function (nd) { + return nd.children; + }); + this.services = []; + this.services[0] = rawOb; + this.updateTree(this.nodes[2]); + targetNodes = this.processSearchInPreorder(this.nodes[2], searchTxt, targetNodes); + this.networkGraph.children.forEach((nd,i) => { + if ( i !== 1) { + this.graphCollapse(nd); + } + } ); + + } else if (this.resourceType === 'services' && this.all_services_fetched) { + targetNodes = this.processSearchInPreorder(this.nodes[2], searchTxt, targetNodes); + } else if (this.resourceType === 'identities' && !this.all_identities_fetched) { + const rawOb = this.searchCache.find((item) => item.name === searchTxt); + const id:any = this.topologyHelper.createIdentityNode(rawOb); + id.clickProcess = "Not Yet"; + if(!this.isNodeExists(this.nodes[3], id)) { + this.nodes[3].data.children.push(id); + } + this.networkGraph = d3.hierarchy(this.networkGraph.data, function (nd) { + return nd.children; + }); + + this.identities[0] = []; + this.identities[0] = rawOb; + this.updateTree(this.nodes[3]); + targetNodes = this.processSearchInPreorder(this.nodes[3], searchTxt, targetNodes); + this.networkGraph.children.forEach((nd,i) => { + if (i !== 2) { + this.graphCollapse(nd); + } + } ); + } else if (this.resourceType === 'identities' && this.all_identities_fetched) { + targetNodes = this.processSearchInPreorder(this.nodes[3], searchTxt, targetNodes); + } else if (this.resourceType === 'edge-router-policies' && !this.all_edge_router_policies_fetched) { + const rawOb = this.searchCache.find((item) => item.name === searchTxt); + const id:any = this.topologyHelper.createEdgeRouterPolicyNode(rawOb); + id.clickProcess = "Not Yet"; + + if(!this.isNodeExists(this.nodes[5], id)) { + this.nodes[5].data.children.push(id); + } + this.networkGraph = d3.hierarchy(this.networkGraph.data, function (nd) { + return nd.children; + }); + this.networkGraph.children.forEach((nd,i) => { + if (i !== 4) { + this.graphCollapse(nd); + } + } ); + this.networkGraph.children.forEach(nd => this.graphCollapse(nd)); + this.edge_router_policies = []; + this.edge_router_policies[0] = rawOb; + this.updateTree(this.nodes[5]); + targetNodes = this.processSearchInPreorder(this.nodes[5], searchTxt, targetNodes); + } else if (this.resourceType === 'edge-router-policies' && this.all_edge_router_policies_fetched) { + targetNodes = this.processSearchInPreorder(this.nodes[5], searchTxt, targetNodes); + } else if (this.resourceType === 'service-edge-router-policies' && !this.all_service_router_policies_fetched) { + const rawOb = this.searchCache.find((item) => item.name === searchTxt); + const id:any = this.topologyHelper.createServiceEdgeRouterPolicyNode(rawOb); + id.clickProcess = "Not Yet"; + if(!this.isNodeExists(this.nodes[6], id)) { + this.nodes[6].data.children.push(id); + } + this.networkGraph = d3.hierarchy(this.networkGraph.data, function (nd) { + return nd.children; + }); + this.networkGraph.children.forEach((nd,i) => { + if (i !== 5) { + this.graphCollapse(nd); + } + } ); + this.service_router_policies = []; + this.service_router_policies[0] = rawOb; + this.updateTree(this.nodes[6]); + targetNodes = this.processSearchInPreorder(this.nodes[6], searchTxt, targetNodes); + } else if (this.resourceType === 'service-edge-router-policies' && this.all_service_router_policies_fetched) { + targetNodes = this.processSearchInPreorder(this.nodes[6], searchTxt, targetNodes); } + // this.networkGraph.children.forEach(nd => this.graphCollapse(nd)); paths = this.explorePathsForSearchedNodes(targetNodes, paths, nodeIds); if (targetNodes && targetNodes.length <= 0) { this.noSearchResults = true; this.clearSearchLinkColors(); - // return false; } else { - //if (targetNodes) { this.openPaths(paths, true); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -562,32 +731,35 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement // const dataId = _.get(d, 'data.id', ''); const nodeName = _.get(targetNodes[0], 'data.name', ''); if (dataName.toLowerCase() === searchTxt.toLowerCase() && nodeName === dataName) { - d3.select(this).style('fill', 'red'); + d3.select(this).style('fill', '#a60303'); //#004447, 470000 } }); } - // else { - // this.noSearchResults = true; - // this.clearSearchLinkColors(); - // } + } } + + isNodeExists(rootNode:any, id) { + return rootNode.data.children.find( (nd) => nd.uuid ===id.uuid ); + } + explorePathsForSearchedNodes(targetNodes, paths, nodeIds) { - for (let i = 0; targetNodes && i < targetNodes.length; i++) { - let nd = targetNodes[i]; - paths.push(nd); - while (nd.parent) { - const nodeId = _.get(nd.parent, 'data.id'); - if (nodeIds.indexOf(nodeId) < 0) { - nodeIds.push(nodeId); - paths.push(nd.parent); - } - nd = nd.parent; + for (let i = 0; targetNodes && i < targetNodes.length; i++) { + let nd = targetNodes[i]; + paths.push(nd); + while (nd.parent) { + const nodeId = _.get(nd.parent, 'data.id'); + if (nodeIds.indexOf(nodeId) < 0) { + nodeIds.push(nodeId); + paths.push(nd.parent); } - } + nd = nd.parent; + } + } return paths; } - processSearchInPreorder(nodeObj, searchTxt, paths, targetNodes) { + + processSearchInPreorder(nodeObj, searchTxt, targetNodes) { const name = _.get(nodeObj, 'data.name'); // const nodeId = _.get(nodeObj, 'data.id'); const isRootNode = _.get(nodeObj, 'data.rootNode'); @@ -599,7 +771,7 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement } const childNodes = nodeObj.children ? nodeObj.children : nodeObj._children; for (let i = 0; childNodes && i < childNodes.length; i++) { - this.processSearchInPreorder(childNodes[i], searchTxt, paths, targetNodes); + this.processSearchInPreorder(childNodes[i], searchTxt, targetNodes); } return targetNodes; } @@ -729,7 +901,7 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement resetSearchNode() { if (this.searchRootNode) { this.networkGraph = _.cloneDeep(this.baseNetworkGraph); - this.updateTree(this.networkGraph); + this.updateTree(this.networkGraph, true); } } @@ -778,6 +950,36 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement } } } + findModelName() { + return this.resourceType; + } + async searchObjectsUsingApi() { + this.isLoading = true; + const model_name = this.findModelName(); + this.searchWord = _.clone(this.filterText); + const str = `${this.resourceType}?filter=%20name%20contains%20%22${this.searchWord}%22&limit=5&offset=0&sort=name%20%20asc`; + return await this.zitiService + .call(str) + .then((result:any) => { + this.isLoading = false; + this.autocompleteOptions = []; + this.searchCache = result.data; + result.data.forEach( (res) => { + /* const obj: { [key: string]: string } = { + id: res.id, + name: res.name, + }; */ + this.autocompleteOptions.push(res.name); + } ); + this.noSearchResults = true; + const countA = result.data?.length > 0? result.data.length : 0; + this.filerResponseMessage = "search word:'"+ this.searchWord +"', result: top "+ countA +" of "+result.meta.pagination.totalCount +" records"; + }).catch( (error) => { + this.isLoading = false; + this.logger.error(error.message); + }); + + } filterSearchArray() { if (_.isEmpty(this.resourceType)) { @@ -786,30 +988,43 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement return; } this.autocompleteOptions = []; - if (this.resourceType === 'service-policies') { + + if (this.resourceType === 'service-policies' && this.all_service_policies_fetched) { this.autocompleteOptions = this.servicePoliciesTreeNodes.filter((option) => option.toLowerCase().includes(this.filterText.toLowerCase()) ); - } else if (this.resourceType === 'services') { + } else if (this.resourceType === 'service-policies' && !this.all_service_policies_fetched) { + this.searchObjectsUsingApi(); + } else if (this.resourceType === 'services' && this.all_services_fetched) { this.autocompleteOptions = this.servicesTreeNodes.filter((option) => option.toLowerCase().includes(this.filterText.toLowerCase()) ); - } else if (this.resourceType === 'identities') { + } else if (this.resourceType === 'services' && !this.all_services_fetched) { + this.searchObjectsUsingApi(); + } else if (this.resourceType === 'identities' && this.all_identities_fetched) { this.autocompleteOptions = this.identitiesTreeNodes.filter((option) => option.toLowerCase().includes(this.filterText.toLowerCase()) ); - } else if (this.resourceType === 'edge-routers') { + } else if (this.resourceType === 'identities' && !this.all_identities_fetched) { + this.searchObjectsUsingApi(); + } else if (this.resourceType === 'edge-routers' && this.all_edgerouters_fetched) { this.autocompleteOptions = this.edgeroutersTreeNodes.filter((option) => option.toLowerCase().includes(this.filterText.toLowerCase()) ); - } else if (this.resourceType === 'edge-router-policies') { + } else if (this.resourceType === 'edge-routers' && !this.all_edgerouters_fetched) { + this.searchObjectsUsingApi(); + } else if (this.resourceType === 'edge-router-policies' && this.all_edge_router_policies_fetched) { this.autocompleteOptions = this.erPoliciesTreeNodes.filter((option) => option.toLowerCase().includes(this.filterText.toLowerCase()) ); - } else if (this.resourceType === 'service-edge-router-policies') { + } else if (this.resourceType === 'edge-router-policies' && !this.all_edge_router_policies_fetched) { + this.searchObjectsUsingApi(); + } else if (this.resourceType === 'service-edge-router-policies' && this.all_service_router_policies_fetched) { this.autocompleteOptions = this.serviceEdgeRouterPolicyTreeNodes.filter((option) => option.toLowerCase().includes(this.filterText.toLowerCase()) ); + } else if (this.resourceType === 'service-edge-router-policies' && !this.all_service_router_policies_fetched) { + this.searchObjectsUsingApi(); } } zoomIn() { @@ -818,19 +1033,62 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement zoomOut() { } - clearSearchFilter() { - this.resourceTypeError = false; - this.filterText = ''; - this.filterSearchArray(); - this.resetSearchNode(); - this.collapse(); - this.clearSearchLinkColors(); - } + clearSearchFilter() { + this.filterText = ''; + this.resetAutoCompleteOptions(); + this.resourceTypeError = false; + // this.filterSearchArray(); + this.resetSearchNode(); + this.collapse(); + this.clearSearchLinkColors(); + } + + resetAutoCompleteOptions(){ + this.searchCache = []; + this.filerResponseMessage = 'search filter..'; + if (this.resourceType === 'service-policies' && this.all_service_policies_fetched) { + this.autocompleteOptions = this.servicePoliciesTreeNodes.filter((option) => + this.filterText==='' || option.toLowerCase().includes(this.filterText.toLowerCase()) + ); + } else if (this.resourceType === 'service-policies' && !this.all_service_policies_fetched) { + this.autocompleteOptions = []; + } else if (this.resourceType === 'services' && this.all_services_fetched) { + this.autocompleteOptions = this.servicesTreeNodes.filter((option) => + this.filterText==='' || option.toLowerCase().includes(this.filterText.toLowerCase()) + ); + } else if (this.resourceType === 'services' && !this.all_services_fetched) { + this.autocompleteOptions = []; + } else if (this.resourceType === 'identities' && this.all_identities_fetched) { + this.autocompleteOptions = this.identitiesTreeNodes.filter((option) => + this.filterText==='' || option.toLowerCase().includes(this.filterText.toLowerCase()) + ); + } else if (this.resourceType === 'identities' && !this.all_identities_fetched) { + this.autocompleteOptions = []; + } else if (this.resourceType === 'edge-routers' && this.all_edgerouters_fetched) { + this.autocompleteOptions = this.edgeroutersTreeNodes.filter((option) => + this.filterText==='' || option.toLowerCase().includes(this.filterText.toLowerCase()) + ); + } else if (this.resourceType === 'edge-routers' && !this.all_edgerouters_fetched) { + this.autocompleteOptions = []; + } else if (this.resourceType === 'edge-router-policies' && this.all_edge_router_policies_fetched) { + this.autocompleteOptions = this.erPoliciesTreeNodes.filter((option) => + this.filterText==='' || option.toLowerCase().includes(this.filterText.toLowerCase()) + ); + } else if (this.resourceType === 'edge-router-policies' && !this.all_edge_router_policies_fetched) { + this.autocompleteOptions = []; + } else if (this.resourceType === 'service-edge-router-policies' && this.all_service_router_policies_fetched) { + this.autocompleteOptions = this.serviceEdgeRouterPolicyTreeNodes.filter((option) => + this.filterText==='' || option.toLowerCase().includes(this.filterText.toLowerCase()) + ); + } else if (this.resourceType === 'service-edge-router-policies' && !this.all_service_router_policies_fetched) { + this.autocompleteOptions = []; + } + } - clearSearchType() { + clearSearchType() { this.resourceType = ''; this.autocompleteOptions = []; - } + } resetZoom() { // const currentZoom = this.getZoomScale(); @@ -839,12 +1097,22 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement this.svg.attr('transform', transform); } + graphCollapse(d) { + if (d.data.rootNode === 'Yes') { + d.data.clickProcess = 'Not Yet'; + d._children = null; + } + if (d.children) { + d._children = d.children; + d._children && d._children?.find(nd => this.graphCollapse); + d.children = null; + } + } + initTopoView() { this.autocompleteOptions = []; const margin = { top: 20, right: 190, bottom: 30, left: 0 }; - // const width = 1600 - margin.left - margin.right; const width = window.innerWidth - margin.right; - // const height = 1600 - margin.top - margin.bottom; const height = window.innerHeight - margin.bottom; this.treetooltip = d3.select('#tooltip'); @@ -872,12 +1140,11 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement }); d3.selectAll('g > *').remove(); - - this.treemap = d3.tree().nodeSize([100, 20]); - + this.treemap = d3.tree().nodeSize([85, 5]); this.networkGraph.x = 100; // height / 4; this.networkGraph.y = 100; this.networkGraph.children.forEach(collapse); + // this.networkGraph.children.forEach( (nd:any) => this.graphCollapse(nd) ); this.updateTree(this.networkGraph); _.delay(() => { @@ -940,7 +1207,7 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement if (_.startsWith(d.data.name, 'Edge Routers') || _.includes(d.data.type, 'Edge Router')) { className = 'edge-router'; } - if (_.includes(d.data.name, 'Identities') || _.includes(d.data.type, 'Identity')) { + if (_.includes(d.data.name, 'Identities') || _.includes(d.data.name, 'Associated Identities') || _.includes(d.data.type, 'Identity') || _.includes(d.data.type, 'BrowZer Identity')) { className = 'endpoint'; } if (_.startsWith(d.data.name, 'Services') || _.startsWith(d.data.type, 'Service')) { @@ -949,10 +1216,10 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement if (_.startsWith(d.data.name, 'Bind-Services') || _.startsWith(d.data.name, 'Dial-Services')) { className = 'service'; } - if (_.startsWith(d.data.name, 'BindPolicies') || _.startsWith(d.data.name, 'DialPolicies') || _.startsWith(d.data.name, 'BindPolicy') ) { + if (_.startsWith(d.data.name, 'Bind-Policies') || _.startsWith(d.data.name, 'Dial-Policies') || _.startsWith(d.data.name, 'Bind Policy') ) { className = 'service-policy'; } - if (_.includes(d.data.name, 'Service Policies') || _.includes(d.data.type, 'ServicePolicy')) { + if (_.includes(d.data.name, 'Service Policies') || _.includes(d.data.type, 'Service Policy')) { className = 'service-policy'; } if (_.startsWith(d.data.name, 'Edge Router Policies') || _.startsWith(d.data.type, 'Router Policy')) { @@ -984,7 +1251,7 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement const rawEdgerouters = this.edgerouters; const rawEndpoints = this.identities; const rawAppwans = this.service_policies; - const edgeRouterPolicies = this.router_policies; + const edgeRouterPolicies = this.edge_router_policies; if (d && d.data && d.data.contextMenu === 'Yes') { d3.select('#topocontextmenu') @@ -996,7 +1263,7 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement return; } let obj = null; - if (d && d.data && d.data.type === 'Identity') { + if (d && d.data && (d.data.type === 'Identity' || d.data.type === 'BrowZer Identity' ) ) { if(d.data.status === "Registered") { enableLi('#liSingleUT'); enableLi('#liFLSingleRouter'); @@ -1017,14 +1284,7 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement disableLi('#liSingleLT'); disableLi('#liCL'); disableLi('#liLT'); - /* - obj = new Identity(); - for (let i = 0; i < rawEndpoints.length; i++) { - if (rawEndpoints[i].id === d.data.uuid) { - obj = rawEndpoints[i]; - } - } - */ + this.contextmenuNodeType = 'identity'; } else if (d && d.data && d.data.type === 'Edge Router') { if(d.data.status === "PROVISIONED") { @@ -1047,14 +1307,7 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement disableLi('#liCL'); disableLi('#liLT'); this.contextmenuNodeType = 'edge-router'; - /* - obj = new EdgeRouterV2(); - for (let i = 0; i < rawEdgerouters.length; i++) { - if (rawEdgerouters[i].id === d.data.uuid) { - obj = rawEdgerouters[i]; - } - } - */ + } else if (d && d.data && d.data.type === 'Service') { enableLi('#liMet'); enableLi('#liVC'); @@ -1069,15 +1322,8 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement disableLi('#liLT'); enableLi('#liSingleUT'); this.contextmenuNodeType = 'service'; - /* - obj = new PlatformService(); - for (let i = 0; i < this.services.length; i++) { - if (this.services[i].id === d.data.uuid) { - obj = this.services[i]; - } - } - */ - } else if (d && d.data && d.data.type === 'AppWAN') { + + } else if (d && d.data && d.data.type === 'Service Policy') { disableLi('#liMet'); enableLi('#liVC'); disableLi('#liIns'); @@ -1090,15 +1336,8 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement disableLi('#liCL'); disableLi('#liLT'); disableLi('#liSingleUT'); - /* - obj = new ServicePolicy(); - for (let i = 0; i < rawAppwans.length; i++) { - if (rawAppwans[i].id === d.data.uuid) { - obj = rawAppwans[i]; - } - } - */ - this.contextmenuNodeType = 'appwan'; + + this.contextmenuNodeType = 'service-policy'; } else if (d && d.data && d.data.type === 'Router Policy') { disableLi('#liMet'); disableLi('#liIns'); @@ -1113,14 +1352,7 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement disableLi('#liLT'); disableLi('#liSingleUT'); this.contextmenuNodeType = 'router-policy'; - /* - obj = new EdgeRouterPolicyV2(); - for (let i = 0; i < edgeRouterPolicies.length; i++) { - if (edgeRouterPolicies[i].id === d.data.uuid) { - obj = edgeRouterPolicies[i]; - } - } - */ + } else if (d && d.data && d.data.type === 'Network') { enableLi('#liMet'); disableLi('#liIns'); @@ -1150,14 +1382,7 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement disableLi('#liLT'); disableLi('#liSingleUT'); this.contextmenuNodeType = 'service-router-policy'; - /* - obj = new ServiceEdgeRouterPolicy(); - for (let i = 0; i < this.service_router_policies.length; i++) { - if (this.service_router_policies[i].id === d.data.uuid) { - obj = this.service_router_policies[i]; - } - } - */ + } this.contextmenuNode = obj; d3.select('#tooltip').style('display', 'none'); @@ -1170,14 +1395,31 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement .style('fill', function (d) { return d._children ? 'var(--tableText)' : '#fff'; }) - .on('mousemove', (event, source) => { - this.drawTreeTooltip(event, source); + .on('mousemove', (event, d) => { + this.drawTreeTooltip(event, d); }) .on('mouseout', this.removeTreeTooltip) .on('contextmenu', (event) => { event.preventDefault(); }) - .on('click', async (event, d) => { + .on('click', async ( event, d: any) => { + if (d.data.rootNode && d.data.rootNode === 'Yes' && d.data.clickProcess === 'Not Yet') { + d3.select(event.currentTarget).transition() + .duration(600) + .style("stroke-width", "10") + .transition() + .duration(600) + .style("stroke-width", "6") + .transition() + .duration(600) + .style("stroke-width", "3") + .transition() + .duration(600) + .style("stroke-width", "9") + .transition() + .duration(600) + .style("stroke-width", "12"); + } if (d && d.data && d.data.contextMenu === 'Yes') { d3.select('#topocontextmenu').style('display', 'none'); } @@ -1199,10 +1441,12 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement d.data && d.data.rootNode && d.data.rootNode === 'Yes' && - d.data.type === 'Identity' && + (d.data.type === 'Identity' || + d.data.type === 'BrowZer Identity') && !d.children && !d._children ) { + d.data.clickProcess = 'Completed'; const arr = await this.treeNodeProcessor.processIdentitiesForNodeClick( d, this.networkGraph, @@ -1213,8 +1457,6 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement ); this.uniqId = arr[1]; this.networkGraph = arr[0]; - - d.data.clickProcess = 'Completed'; this.networkGraph.children.find((nd) => { collapseFew(nd, this.openNodes); }); @@ -1227,6 +1469,7 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement !d.children && !d._children ) { + d.data.clickProcess = 'Completed'; const arr = await this.treeNodeProcessor.processServicesForNodeClick( d, this.networkGraph, @@ -1236,7 +1479,6 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement ); this.uniqId = arr[1]; this.networkGraph = arr[0]; - d.data.clickProcess = 'Completed'; this.networkGraph.children.find((nd) => { collapseFew(nd, this.openNodes); }); @@ -1245,10 +1487,11 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement d.data && d.data.rootNode && d.data.rootNode === 'Yes' && - d.data.type === 'ServicePolicy' && + d.data.type === 'Service Policy' && !d.children && !d._children ) { + d.data.clickProcess = 'Completed'; const arr = await this.treeNodeProcessor.processServicePoliciesForNodeClick( d, this.networkGraph, @@ -1258,7 +1501,6 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement ); this.uniqId = arr[1]; this.networkGraph = arr[0]; - d.data.clickProcess = 'Completed'; this.networkGraph.children.find((nd) => { collapseFew(nd, this.openNodes); }); @@ -1271,6 +1513,7 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement !d.children && !d._children ) { + d.data.clickProcess = 'Completed'; const arr = await this.treeNodeProcessor.processEdgeroutersForNodeClick( d, this.networkGraph, @@ -1280,7 +1523,6 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement ); this.uniqId = arr[1]; this.networkGraph = arr[0]; - d.data.clickProcess = 'Completed'; this.networkGraph.children.find((nd) => { collapseFew(nd, this.openNodes); }); @@ -1293,16 +1535,16 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement !d.children && !d._children ) { + d.data.clickProcess = 'Completed'; const arr = await this.treeNodeProcessor.processEdgeRouterPoliciesForNodeClick( d, this.networkGraph, - this.router_policies, + this.edge_router_policies, this.uniqId, this.zitiService ); this.uniqId = arr[1]; this.networkGraph = arr[0]; - d.data.clickProcess = 'Completed'; this.networkGraph.children.find((nd) => { collapseFew(nd, this.openNodes); }); @@ -1315,6 +1557,7 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement !d.children && !d._children ) { + d.data.clickProcess = 'Completed'; const arr = await this.treeNodeProcessor.processServiceEdgeRouterPoliciesForNodeClick( d, this.networkGraph, @@ -1325,7 +1568,6 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement this.uniqId = arr[1]; this.networkGraph = arr[0]; - d.data.clickProcess = 'Completed'; this.networkGraph.children.find((nd) => { collapseFew(nd, this.openNodes); }); @@ -1355,13 +1597,17 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement this.nodeEnter .append('foreignObject') .attr('height', '50') - .attr('width', '330') - .attr('x', '10') - .attr('y', '-25') + .attr('width', function (d) { + return d && d.data && d.data.name.length >= 20? '550': '330'; + }) + .attr('x', '20') + .attr('y', function (d) { + return d && d.data && d.data.rootNode==='Yes'? '-5': '-40'; + }) .append('xhtml:body') .style('background', 'transparent') .html(function (d) { - return `
    ${d.data.name}`; + return `
    ${d.data.name}
    `; }); this.nodeUpdate = this.nodeEnter.merge(this.node); @@ -1389,9 +1635,7 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement .remove(); this.nodeExit.select('circle').attr('r', 0); - this.nodeExit.select('text').style('fill-opacity', 10); - this.link = this.svg.selectAll('path.link').data(this.links, function (d) { return d.data.id; }); @@ -1467,19 +1711,6 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement }); } - function collapseDepth(d, id) { - // unused - if (d.children) { - if (d.data.id !== id) { - d._children = d.children; - d._children.forEach(collapseDepth); - d.children = null; - } else { - d.children.forEach(collapseDepth); - } - } - } - function collapseFew(_nd, openNodes) { if (_nd.data.id === '#all') { return; @@ -1522,56 +1753,6 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement this.svg.transition().duration(750).attr('transform', trans); } } - removeGraphTooltip() { - if (this.graphtooltip) { - this.graphtooltip.style('display', 'none'); - } - } - - drawGraphTooltip(event, d) { - document.getElementById('tooltip').innerHTML = this.readGraphKeyValues(d); - this.graphtooltip.style('display', 'block'); - const x = event.pageX - 400 + 'px'; - const y = event.pageY + 10 + 'px'; - this.graphtooltip.style('left', x); - this.graphtooltip.style('top', y); - } - - readGraphKeyValues(d) { - let info = - ''; - const keys = Object.keys(d.data); - keys.forEach( (k) => { - if ( - k === 'value' || - k === 'group' || - k === 'index' || - k === 'parent' || - k === 'depth' || - k === 'x' || - k === 'y' || - k === 'id' || - k === 'x0' || - k === 'y0' || - k === 'vy' || - k === 'vx' - ) { - // continue; - } else if (k === 'children') { - const kVal: any = d.data[k]; - info = info + ' ' + k + ' : ' + kVal.length + '
    '; - } else { - if (this.isAnObject(d.data[k])) { - const kVal: any = d.data[k]; - info = info + ' ' + k + ' : ' + kVal.name + '
    '; - } else { - const vo = d.data[k]; - info = info + ' ' + k + ' : ' + (vo !== null || vo !== '' ? d.data[k] : 'N/A') + '
    '; - } - } - }); - return info + '
    '; - } setColor(d) { if (d.status === 'Error' && d.group === 3) { @@ -1629,22 +1810,6 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement } } - nodeshape(d) { - if (d.group === '3') { - return 'rect'; - } else { - return 'circle'; - } - } - - linkWidth(d) { - if (d.group === 1) { - return 10; - } else { - return 2; - } - } - dragstarted(event, d) { if (!event.active) { this.simulation.alphaTarget(0.3).restart(); @@ -1682,10 +1847,14 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement document.getElementById('tooltip').innerHTML = this.readTreeKeyValues(event, s); } readTreeKeyValues(event, d) { - let info = ''; + const startIndex = d.data.name.indexOf('('); + const finIndex = d.data.name.indexOf(')'); + const countStr:number = startIndex >0? d.data.name.substring(startIndex + 1, finIndex): 0; + let info = `
    `; const keys = Object.keys(d.data); // mp.keys(); keys.forEach(function (k) { if ( + k === 'firstChild' || k === 'protocol' || k === 'contextMenu' || k === 'uuid' || @@ -1703,14 +1872,26 @@ export class NetworkVisualizerComponent extends VisualizerServiceClass implement k === 'y0' ) { // continue; + } else if (d[k] instanceof Array) { + info = + info + + '
    ' + + k + + ':
    ' + + d[k].join('
         ') + + '
    '; } else { - info = info + ' ' + k + ' : ' + d.data[k] + '
    '; + const propVal = d.data[k] || ''; + info = info + '
    ' + k + ':
    ' + propVal + '
    '; } }); - return info + ''; + if (d.data.firstChild && d.data.firstChild === 'Yes' && countStr > this.maxObjectsPerNode-1) { + info = info + 'Note: expand node disabled. Use search filter.'; + } else if (d.data.firstChild && d.data.firstChild === 'Yes' && countStr < this.maxObjectsPerNode){ + info = info + 'Note: click to expand child nodes '; + } + return info + '
    '; } - hide() {} - toggleModalSize() {}; } diff --git a/projects/ziti-console-lib/src/lib/features/visualizer/network-visualizer/network-visualizer.helper.ts b/projects/ziti-console-lib/src/lib/features/visualizer/network-visualizer/network-visualizer.helper.ts index 552a5fba..d324d2ea 100644 --- a/projects/ziti-console-lib/src/lib/features/visualizer/network-visualizer/network-visualizer.helper.ts +++ b/projects/ziti-console-lib/src/lib/features/visualizer/network-visualizer/network-visualizer.helper.ts @@ -18,7 +18,9 @@ export class Identity { children; contextMenu; online; - + osVersion; + createdAt; + updatedAt; constructor() { this.rootNode = 'No'; this.children = []; @@ -43,14 +45,29 @@ export class Service { attributes; utilization; contextMenu; - + createdAt; + updatedAt; + intercept; constructor() { this.rootNode = 'No'; this.children = []; this.contextMenu = 'Yes'; } } - +export class Config { + uuid; + id; + name; + data; + children; + contextMenu; + rootNode; + constructor() { + this.rootNode = 'No'; + this.children = []; + this.contextMenu = 'Yes'; + } +} export class Group { id; name; @@ -75,6 +92,8 @@ export class ServicePolicy { rootNode; children; contextMenu; + createdAt; + updatedAt; constructor() { this.rootNode = 'No'; @@ -101,10 +120,14 @@ export class ERPolicy { status; size; type; + semantic; + isSystem; rootNode; children; contextMenu; - + createdAt; + updatedAt; + data; constructor() { this.rootNode = 'No'; this.children = []; @@ -122,6 +145,8 @@ export class ServiceERPolicy { rootNode; children; contextMenu; + createdAt; + updatedAt; constructor() { this.rootNode = 'No'; @@ -148,6 +173,10 @@ export class ERouter { online; disabled; version; + tunnelerEnabled; + syncStatus; + createdAt; + updatedAt; constructor() { this.rootNode = 'No'; @@ -163,7 +192,9 @@ export class Children { size; type; children; - + data: Children; + rootNode = 'No'; + firstChild = 'No'; constructor() { this.type = 'Children'; this.children = []; @@ -175,21 +206,126 @@ export class Children { }) export class NetworkVisualizerHelper { + uniqIId:any =0; + + createUniqId() { + return ++this.uniqIId; + } + public createEdgeRouterNode(er, isRootNode = 'Yes') { + const erOb = new ERouter(); + // erOb.id = createId(); + erOb.uuid = er.id; + erOb.name = er.name; + erOb.verified = er.isVerified? 'Yes': 'No'; + //erOb.version = er.productVersion; + erOb.online = er.isOnline? 'Yes': 'No'; + erOb.disabled = er.disabled? 'Yes': 'No'; + erOb.type = 'Edge Router'; + erOb.rootNode = isRootNode; + erOb.tunnelerEnabled = er.tunnelerEnabled? er.tunnelerEnabled: 'No'; + erOb.syncStatus = er.syncStatus? er.syncStatus:'No'; + erOb.createdAt = er.createdAt; + erOb.updatedAt = er.updatedAt; + return erOb; + } + public createServiceEdgeRouterPolicyNode(rawObj, isRootNode = 'Yes') { + const serviceerpolicy = new ServiceERPolicy(); + // serviceerpolicy.id = createId(); + serviceerpolicy.uuid = rawObj.id; + serviceerpolicy.name = rawObj.name; + serviceerpolicy.type = 'Service Router Policy'; + serviceerpolicy.rootNode = isRootNode; + return serviceerpolicy; + } + public createEdgeRouterPolicyNode(rawObj, isRootNode = 'Yes') { + const erpolicy = new ERPolicy(); + // erpolicy.id = createId(); + erpolicy.uuid = rawObj.id; + erpolicy.name = rawObj.name; + erpolicy.type = 'Router Policy'; + erpolicy.rootNode = isRootNode; + erpolicy.isSystem = rawObj.isSystem; + erpolicy.semantic = rawObj.semantic; + return erpolicy; + } + public createServicePolicyNode(rawObj, isRootNode = 'Yes') { + const bindGrp = new Group(43423, 'Bind-Policies', 'Bind Service Policy'); + const dialGrp = new Group(43545, 'Dial-Policies', 'Dial Service Policy'); + + const spolicy:any = new ServicePolicy(); + // spolicy.id = createId(); + spolicy.id = 87956; + spolicy.uuid = rawObj.id; + spolicy.name = rawObj.name; + spolicy.type = 'Service Policy'; + spolicy.rootNode = isRootNode; + spolicy.clickProcess = "Not Yet"; + if(rawObj.type==='Bind') { + bindGrp.children.push(spolicy); + return bindGrp; + } else { + dialGrp.children.push(spolicy); + return dialGrp; + } + } + public createIdentityNode(rawObj, isRootNode = 'Yes') { + const endpoint = new Identity(); + endpoint.rootNode = isRootNode; + // endpoint.id = createId(); + endpoint.id = 5432; + endpoint.uuid = rawObj.id; + endpoint.name = rawObj.name; + endpoint.online = rawObj.hasApiSession === true ? 'Yes' : 'No'; + endpoint.type = 'Identity'; + endpoint.createdAt = rawObj.createdAt; + endpoint.updatedAt = rawObj.updatedAt; + endpoint.status = rawObj.sdkInfo !== null ? 'Registered' : 'Not Registered'; + if (rawObj.sdkInfo) { + endpoint.os = rawObj.sdkInfo.type? rawObj.sdkInfo.type: rawObj.sdkInfo.appId; + endpoint.osVersion = rawObj.sdkInfo.version; + } + if (rawObj.authPolicy && rawObj.authPolicy.name.includes("BrowZer")) { + endpoint.type = 'BrowZer Identity'; + } + return endpoint; + } + public createServiceNode(rawObj, isRootNode = 'Yes') { + const serviceobj = new Service(); + // serviceobj.id = createId(); + serviceobj.name = rawObj.name; + serviceobj.uuid = rawObj.id; + serviceobj.type = 'Service'; + serviceobj.rootNode = isRootNode; + const attributeswithName = []; + // attributeswithName.push('@' + rawObj.name); + rawObj && rawObj.roleAttributes && rawObj.roleAttributes.find((srattr) => { + attributeswithName.push(srattr); + }); + return serviceobj; + } + public getResourceTreeObj( networkName, networkStatus, services, - endpoints, - appwans, + identities, + servicePolicies, edgerouters, edgeRouterPolicies, serviceEdgeRouterPolicies, // networkProductVersion, + services_totalCount, identities_totalCount, edgerouters_totalCount, + service_policies_totalCount, edge_router_policies_totalCount, + service_router_policies_totalCount, uniqId, - logger + logger, + ...args: any ) { + this.uniqIId = uniqId; + function createId() { ++uniqId; + // ++this.uniqIId; return uniqId; } @@ -200,6 +336,7 @@ export class NetworkVisualizerHelper { children = []; contextMenu = 'Yes'; type = 'Network'; + rootNode = 'No'; // version = networkProductVersion; status; } @@ -207,18 +344,14 @@ export class NetworkVisualizerHelper { const rootJson = new RootJson(); rootJson.name = 'Network'; rootJson.status = 'Provisioned'; - function processIdentitiesForTree( identitiesChildren, - edgerouters, - endpoints, - appwans, - services, - edgeRouterPolicies, + identities, nx, ny ) { - identitiesChildren.name = identitiesChildren.name + ' (' + ny + ')'; + if(args[2]===false) return identitiesChildren; + // identitiesChildren.name = identitiesChildren.name + ' (' + ny + ')'; const unregisteredGrp = new Group( createId(), 'Identities[Unregistered]', @@ -230,7 +363,7 @@ export class NetworkVisualizerHelper { const mnopGrp = new Group(createId(), 'Identities [M-P]', 'Identity name starts with M to P'); const qrstGrp = new Group(createId(), 'Identities [Q-T]', 'Identity name starts with Q to T'); const uvwxGrp = new Group(createId(), 'Identities [U-X]', 'Identity name starts with U to X'); - const yzGrp = new Group(createId(), 'Identities [Y-Z]', 'Identity name starts with Y and Z'); + const yzGrp = new Group(createId(), 'Identities [Y-Z]', 'Identity name starts with Y and Z'); const oneTo9Grp = new Group(createId(), 'Identities [1-9]', 'Identity name starts with 0 to 9'); const specialCharGrp = new Group( createId(), @@ -242,16 +375,20 @@ export class NetworkVisualizerHelper { const endpoint = new Identity(); endpoint.rootNode = 'Yes'; endpoint.id = createId(); - endpoint.uuid = endpoints[j].id; - endpoint.name = endpoints[j].name; - endpoint.online = endpoints[j].hasApiSession === true ? 'Yes' : 'No'; + endpoint.uuid = identities[j].id; + endpoint.name = identities[j].name; + endpoint.online = identities[j].hasApiSession === true ? 'Yes' : 'No'; endpoint.type = 'Identity'; - endpoint.status = endpoints[j].osRelease !== null ? 'Registered' : 'Not Registered'; - if (endpoints[j].osRelease === null && endpoints[j].type && endpoints[j].type.includes('browzer')) { - endpoint.status = 'Registered'; - // endpoint.type = 'Browzer Endpoint'; + endpoint.createdAt = identities[j].createdAt; + endpoint.updatedAt = identities[j].updatedAt; + endpoint.status = identities[j].sdkInfo !== null ? 'Registered' : 'Not Registered'; + if (identities[j].sdkInfo) { + endpoint.os = identities[j].sdkInfo.type? identities[j].sdkInfo.type: identities[j].sdkInfo.appId; + endpoint.osVersion = identities[j].sdkInfo.version; + } + if (identities[j].authPolicy && identities[j].authPolicy.name.includes("BrowZer")) { + endpoint.type = 'BrowZer Identity'; } - endpoint.os = endpoints[j].os; const cha = endpoint.name.charAt(0).toLowerCase(); if (endpoint.status === 'Not Registered') { @@ -380,7 +517,7 @@ export class NetworkVisualizerHelper { for (let j = i; j < lastElement; j++) { chld.children.push(oneTo9Grp.children[j]); } - chld.name = chld.name + ' (' + chld.children.length + ')'; + // chld.name = chld.name + ' (' + chld.children.length + ')'; identitiesChildren.children.push(chld); i = i + 80; } @@ -391,16 +528,14 @@ export class NetworkVisualizerHelper { function processServicePoliciesForTree( servicePolicyChildren, - edgerouters, - identities, servicePolicies, - services, - edgeRouterPolicies, nx, ny ) { - const bindGrp = new Group(createId(), 'BindPolicies', 'Bind Service Policy'); - const dialGrp = new Group(createId(), 'DialPolicies', 'Dial Service Policy'); + if(args[0]===false) return servicePolicyChildren; + + const bindGrp = new Group(createId(), 'Bind-Policies', 'Bind Service Policy'); + const dialGrp = new Group(createId(), 'Dial-Policies', 'Dial Service Policy'); servicePolicies.forEach( (sp) => { @@ -408,7 +543,7 @@ export class NetworkVisualizerHelper { spolicy.id = createId(); spolicy.uuid = sp.id; spolicy.name = sp.name; - spolicy.type = 'ServicePolicy'; + spolicy.type = 'Service Policy'; spolicy.rootNode = 'Yes'; if(sp.type==='Bind') { bindGrp.children.push(spolicy); @@ -418,12 +553,11 @@ export class NetworkVisualizerHelper { } ); - bindGrp.name = bindGrp.name + '('+ bindGrp.children.length +')'; - servicePolicyChildren.children.push(bindGrp); - dialGrp.name = dialGrp.name + '('+ dialGrp.children.length +')'; - servicePolicyChildren.children.push(dialGrp); - servicePolicyChildren.name = servicePolicyChildren.name + ' (' + (bindGrp.children.length + dialGrp.children.length) + ')'; - return servicePolicyChildren; + bindGrp.name = bindGrp.name + '('+ bindGrp.children.length +')'; + servicePolicyChildren.children.push(bindGrp); + dialGrp.name = dialGrp.name + '('+ dialGrp.children.length +')'; + servicePolicyChildren.children.push(dialGrp); + return servicePolicyChildren; } function serviceGrouping(servicesChildren, servicestree) { @@ -590,20 +724,17 @@ export class NetworkVisualizerHelper { oneTo9Grp.name = oneTo9Grp.name + '(' + oneTo9Grp.children.length + ')'; servicesChildren.children.push(oneTo9Grp); } - servicesChildren.name = servicesChildren.name + '(' + servicestree.length + ')'; + // servicesChildren.name = servicesChildren.name + '(' + servicestree.length + ')'; return servicesChildren; } function processServicesForTree( servicesChildren, - edgerouters, - endpoints, - appwans, services, - edgeRouterPolicies, nx, ny ) { + if(args[1]===false) return servicesChildren; const servicestree = []; for (let i = nx; i < ny; i++) { const serviceobj = new Service(); @@ -656,14 +787,11 @@ export class NetworkVisualizerHelper { function processEdgeRouterPoliciesForTree( edgeRouterPoliciesChildren, - edgerouters, - endpoints, - appwans, - services, edgeRouterPolicies, nx, ny ) { + if(args[4]===false) return edgeRouterPoliciesChildren; for (let i = nx; i < ny; i++) { const erpolicy = new ERPolicy(); erpolicy.id = createId(); @@ -671,6 +799,8 @@ export class NetworkVisualizerHelper { erpolicy.name = edgeRouterPolicies[i].name; erpolicy.type = 'Router Policy'; erpolicy.rootNode = 'Yes'; + erpolicy.isSystem = edgeRouterPolicies[i].isSystem; + erpolicy.semantic = edgeRouterPolicies[i].semantic; edgeRouterPoliciesChildren.children.push(erpolicy); } // end of main for loop return edgeRouterPoliciesChildren; @@ -678,15 +808,11 @@ export class NetworkVisualizerHelper { function processServiceEdgeRouterPoliciesForTree( serviceEdgeRouterPoliciesChildren, - edgerouters, - endpoints, - appwans, - services, - edgeRouterPolicies, serviceEdgeRouterPolicies, nx, ny ) { + if(args[5]===false) return serviceEdgeRouterPoliciesChildren; for (let i = nx; i < ny; i++) { const serviceerpolicy = new ServiceERPolicy(); serviceerpolicy.id = createId(); @@ -702,13 +828,10 @@ export class NetworkVisualizerHelper { function processEdgeroutersForTree( edgerouterChildren, edgerouters, - endpoints, - appwans, - services, - edgeRouterPolicies, nx, ny ) { + if(args[3]===false) return edgerouterChildren; for (let i = nx; i < ny; i++) { const erOb = new ERouter(); erOb.id = createId(); @@ -722,13 +845,10 @@ export class NetworkVisualizerHelper { erOb.disabled = er.disabled? 'Yes': 'No'; erOb.type = 'Edge Router'; erOb.rootNode = 'Yes'; - if (er.hostId !== null) { - // erOb.provider = findProviderNameForHostId(er.hostId); - } else if (er.status === 'ERROR') { - erOb.provider = 'N/A'; - } else { - erOb.provider = 'Private DC'; - } + erOb.tunnelerEnabled = er.tunnelerEnabled? er.tunnelerEnabled: 'No'; + erOb.syncStatus = er.syncStatus? er.syncStatus:'No'; + erOb.createdAt = er.createdAt; + erOb.updatedAt = er.updatedAt; edgerouterChildren.children.push(erOb); } @@ -737,37 +857,39 @@ export class NetworkVisualizerHelper { } // createId() generates node Id 1,2,3,4,5 for the following five nodes. this id is used to search capability. - let appwansChildren = new Children(); - appwansChildren.id = createId(); - appwansChildren.name = 'Service Policies'; + let servicPoliciesChildren = new Children(); + servicPoliciesChildren.id = createId(); + servicPoliciesChildren.firstChild = 'Yes'; + servicPoliciesChildren.name = 'Service Policies('+ service_policies_totalCount +')'; let servicesChildren = new Children(); servicesChildren.id = createId(); - servicesChildren.name = 'Services'; + servicesChildren.firstChild = 'Yes'; + servicesChildren.name = 'Services('+ services_totalCount +')'; - let endpointsChildren = new Children(); - endpointsChildren.id = createId(); - endpointsChildren.name = 'Identities'; + let identitiesChildren = new Children(); + identitiesChildren.id = createId(); + identitiesChildren.firstChild = 'Yes'; + identitiesChildren.name = 'Identities('+ identities_totalCount +')'; let edgerouterChildren = new Children(); edgerouterChildren.id = createId(); - edgerouterChildren.name = 'Edge Routers'; + edgerouterChildren.firstChild = 'Yes'; + edgerouterChildren.name = 'Edge Routers('+ edgerouters_totalCount +')'; let edgeRouterPoliciesChildren = new Children(); edgeRouterPoliciesChildren.id = createId(); - edgeRouterPoliciesChildren.name = 'Edge Router Policies'; + edgeRouterPoliciesChildren.firstChild = 'Yes'; + edgeRouterPoliciesChildren.name = 'Edge Router Policies('+ edge_router_policies_totalCount +')'; let serviceEdgeRouterPolicesChildren = new Children(); serviceEdgeRouterPolicesChildren.id = createId(); - serviceEdgeRouterPolicesChildren.name = 'Service Edge Router Policies'; + serviceEdgeRouterPolicesChildren.firstChild = 'Yes'; + serviceEdgeRouterPolicesChildren.name = 'Service Edge Router Policies('+ service_router_policies_totalCount +')'; servicesChildren = processServicesForTree( servicesChildren, - edgerouters, - endpoints, - appwans, services, - edgeRouterPolicies, 0, services.length ); @@ -775,77 +897,43 @@ export class NetworkVisualizerHelper { edgerouterChildren = processEdgeroutersForTree( edgerouterChildren, edgerouters, - endpoints, - appwans, - services, - edgeRouterPolicies, 0, edgerouters.length ); - if (edgerouterChildren) { - edgerouterChildren.name = edgerouterChildren.name + ' (' + edgerouterChildren.children.length + ')'; - } - - appwansChildren = processServicePoliciesForTree( - appwansChildren, - edgerouters, - endpoints, - appwans, - services, - edgeRouterPolicies, + servicPoliciesChildren = processServicePoliciesForTree( + servicPoliciesChildren, + servicePolicies, 0, - appwans.length + servicePolicies.length ); edgeRouterPoliciesChildren = processEdgeRouterPoliciesForTree( edgeRouterPoliciesChildren, - edgerouters, - endpoints, - appwans, - services, edgeRouterPolicies, 0, edgeRouterPolicies.length ); - if (edgeRouterPoliciesChildren) { - edgeRouterPoliciesChildren.name = - 'Edge Router Policies (' + edgeRouterPoliciesChildren.children.length + ')'; - } - endpointsChildren = processIdentitiesForTree( - endpointsChildren, - edgerouters, - endpoints, - appwans, - services, - edgeRouterPolicies, + identitiesChildren = processIdentitiesForTree( + identitiesChildren, + identities, 0, - endpoints.length + identities.length ); - //serviceEdgeRouterPolicies + //serviceRouterPolicies serviceEdgeRouterPolicesChildren = processServiceEdgeRouterPoliciesForTree( serviceEdgeRouterPolicesChildren, - edgerouters, - endpoints, - appwans, - services, - edgeRouterPolicies, serviceEdgeRouterPolicies, 0, serviceEdgeRouterPolicies.length ); - if (edgeRouterPoliciesChildren) { - serviceEdgeRouterPolicesChildren.name = - 'Service Edge Router Policies (' + serviceEdgeRouterPolicesChildren.children.length + ')'; - } - - rootJson.children.push(appwansChildren); + rootJson.children.push(servicPoliciesChildren); rootJson.children.push(servicesChildren); - rootJson.children.push(endpointsChildren); + rootJson.children.push(identitiesChildren); rootJson.children.push(edgerouterChildren); rootJson.children.push(edgeRouterPoliciesChildren); rootJson.children.push(serviceEdgeRouterPolicesChildren); diff --git a/projects/ziti-console-lib/src/lib/features/visualizer/network-visualizer/network-visualizer.treenodeprocessor.ts b/projects/ziti-console-lib/src/lib/features/visualizer/network-visualizer/network-visualizer.treenodeprocessor.ts index 533853fd..affe8655 100644 --- a/projects/ziti-console-lib/src/lib/features/visualizer/network-visualizer/network-visualizer.treenodeprocessor.ts +++ b/projects/ziti-console-lib/src/lib/features/visualizer/network-visualizer/network-visualizer.treenodeprocessor.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import * as d3 from 'd3'; import _ from 'lodash'; -import { ServicePolicy, Attribute, Children, Identity, ERouter, ERPolicy, Group, Service } from './network-visualizer.helper'; +import { ServicePolicy, Attribute, Children, Identity, ERouter, ERPolicy, Group, Service, Config } from './network-visualizer.helper'; @Injectable({ providedIn: 'root', @@ -35,7 +35,7 @@ export class TreeNodeProcessor { const rawEp = this.findRawEndpoint(identityNode.data.name, rawEndpoints); let rawEpAttributes = []; if (rawEp.roleAttributes) {rawEpAttributes = rawEp.roleAttributes}; - rawEpAttributes.push('@' + rawEp.name); + for (let i = 0; i < rawEpAttributes.length; i++) { const attribute = new Attribute(); attribute.id = this.createId(); @@ -49,75 +49,63 @@ export class TreeNodeProcessor { bindServices.id = this.createId(); dialServices.id = this.createId(); let identityServicePolicies = []; - const services_url = this.getLinkForResource(rawEp, 'service-policies').replace('./', ''); - const pagesize = 500; - const services_paging = this.getPagingObject(pagesize); + // fetch associated routers + const url = this.getLinkForResource(rawEp, 'edge-routers').replace('./', ''); + let associatedRouters = []; + const paging = this.getPagingObject(pagesize); + const erChailds = new Children(); + erChailds.id = this.createId(); + erChailds.name= 'Associated Routers'; + erChailds.type= 'Associated Routers'; + const associatedRoutersPromise = await zitiService.get(url, paging, []) + .then( (result) => { + result.data.forEach( (re) => { + const tmp = new ERouter(); + tmp.id = this.createId(); + tmp.name = re.name; + tmp.type = 'Router'; + erChailds.children.push(tmp); + } ); + }); - const firstPromise = await zitiService - .get(services_url, services_paging, []) - .then( async (result) => { + const policies_url = this.getLinkForResource(rawEp, 'service-policies').replace('./', ''); + const policies_paging = this.getPagingObject(pagesize); + const firstPromise = await zitiService + .get(policies_url, policies_paging, []) + .then((result) => { identityServicePolicies = result.data; - const pages = Math.ceil(result.meta.pagination.totalCount / pagesize); - const promises = []; - for (let page = 2; page <= pages; page++) { - services_paging.page = page; - const tmp_promise = await zitiService - .get( - services_url, - services_paging, - [] - ) - .then((pageResult) => { - pageResult.data.forEach((serv) => { - identityServicePolicies.push(serv); - }); - }); - promises.push(tmp_promise); - wait_promises.push(tmp_promise); - } - Promise.all(promises).then( () => { - const sub_promises = []; - identityServicePolicies.find( async (isp) => { - const pagingOb = this.getPagingObject(pagesize); - const ser_url = this.getLinkForResource(isp, 'services').replace('./', ''); - const tmpPromise = await zitiService - .get(ser_url, pagingOb, []) - .then((result) => { - const tmp_services = []; - result.data.find( (re) => { - const serv = new Service(); - serv.id = this.createId(); - serv.name = re.name; - serv.type = 'Service'; - // tmp_services.push(serv); - if (isp.type === 'Dial') { - dialServices.children.push(serv); - } else { - bindServices.children.push(serv); - } - - } ); - - }); - sub_promises.push(tmpPromise); - wait_promises.push(tmpPromise); - - }); // loop - Promise.all(sub_promises).then(() => { - if(bindServices.children.length>0) { - bindServices.name = 'Bind-Services('+bindServices.children.length+')'; - identityNode.data.children.push(bindServices); - } - if(dialServices.children.length>0) { - dialServices.name = 'Dial-Services('+dialServices.children.length+')'; - identityNode.data.children.push(dialServices); - } - }); - }); - }); - wait_promises.push(firstPromise); + }); + + const promises = []; + identityServicePolicies.forEach( (sp) => { + const nodeArray = this.getServicesForAServicePolicy(sp, zitiService, 500); + promises.push(nodeArray); + }); + + await Promise.all(promises).then( (nodeArray) => { + nodeArray.forEach( (arOb) => { + if(arOb[0] === 'Bind' ) { + bindServices.children = this.arrayObjectCopyRightToLeft(bindServices.children, arOb[1]); + } else { + dialServices.children = this.arrayObjectCopyRightToLeft(dialServices.children, arOb[1]); + } + } ); + }); + Promise.all(wait_promises).then( () => { + if (erChailds.children.length >0 ) { + erChailds.name = erChailds.name+'('+erChailds.children.length+')'; + identityNode.data.children.push(erChailds); + } + if(bindServices.children.length>0) { + bindServices.name = 'Bind-Services('+bindServices.children.length+')'; + identityNode.data.children.push(bindServices); + } + if(dialServices.children.length>0) { + dialServices.name = 'Dial-Services('+dialServices.children.length+')'; + identityNode.data.children.push(dialServices); + } resolve('this is a promise'); }); @@ -143,27 +131,51 @@ export class TreeNodeProcessor { return serviceOb; } - addItemToArray(bindIdnetities, ob) { - if (!bindIdnetities.find((item) => item.uuid === ob.id)) { - const epOb = new Identity(); - epOb.id = this.createId(); - epOb.uuid = ob.id; - epOb.name = ob.name; - epOb.online = ob.hasApiSession === true ? 'Yes' : 'No'; - epOb.type = 'Identity'; - epOb.status = ob.os !== null ? 'Registered' : 'Not Registered'; - epOb.os = ob.os; - - bindIdnetities.push(epOb); + arrayObjectCopyRightToLeft(leftArr,rightArr) { + const leftArrIds = []; + leftArr.forEach( (ob) => { + leftArrIds.push(ob.id); + } ); + + rightArr.forEach( (ob) => { + if (!leftArr.includes(ob.id)) { + leftArr.push(ob); + } + } ); + + return leftArr; + } + + addItemToArray(Idnetities, ob) { + if ( ob.name && !Idnetities.find((item) => item.uuid === ob.id)) { + Idnetities.push(this.createIdentity(ob)); } - return bindIdnetities; + return Idnetities; } + createIdentity(ob) { + const endpoint = new Identity(); + endpoint.id = this.createId(); + endpoint.uuid = ob.id; + endpoint.name = ob.name; + endpoint.online = ob.hasApiSession === true ? 'Yes': 'No'; + endpoint.type = 'Identity'; + endpoint.status = ob.sdkInfo !== null ? 'Registered': 'Not Registered'; + if (ob.sdkInfo) { + endpoint.os = ob.sdkInfo.type? ob.sdkInfo.type: ob.sdkInfo.appId; + endpoint.osVersion = ob.sdkInfo.version; + } + if (ob.authPolicy && ob.authPolicy.name.includes("BrowZer")) { + endpoint.type = 'BrowZer Identity'; + } + endpoint.createdAt = ob.createdAt; + endpoint.updatedAt = ob.updatedAt; + return endpoint; + } getLinkForResource(ob, resource) { return ob['_links'][resource]['href']; } - //Bind Dial Services findServicesHostedOnEndpoint(rawEp, services, servicePolicies, zitiService) { const servicesArr = []; const childrenNoGroups = []; @@ -266,30 +278,57 @@ export class TreeNodeProcessor { return endpoints.find((ep) => ep.name === epName); } + serviceConfigAlias(configs) { + let intercept = []; + _.isArray(configs) && configs.forEach( (conf) => { + let addresses = ''; + let ports = ''; + if (conf.data.addresses) { + addresses = conf.data.addresses.toString(); + } else { + addresses = conf.data.address; + } + + if (_.isArray(conf.data.portRanges) ) { + conf.data.portRanges.forEach( (portsJson) => { + for (const key in portsJson) { + ports = ports + key +':'+portsJson[key] +' ' ; + } + ports = ports + ';'; + }); + } else { + ports = conf.data.port; + } + intercept.push(addresses); + intercept.push(ports); + }); + return intercept; + } + async processServicesForNodeClick(serviceNode, networkGraph, services, uniqId, zitiService) { const myPromise = await new Promise( async (resolve, reject) => { const wait_promises = []; this.uniqId = uniqId; const rawServiceObj = services.find((s) => s.name === serviceNode.data.name); - const attributeswithName = []; - attributeswithName.push('@' + rawServiceObj.name); + rawServiceObj.roleAttributes && rawServiceObj.roleAttributes.find((srattr) => { attributeswithName.push(srattr); }); const serviceAttributes = new Children(); - serviceAttributes.type = 'ServiceAttributes'; + serviceAttributes.name = 'Attributes'; + serviceAttributes.type = 'Attributes'; serviceAttributes.id = this.createId(); for (let k = 0; k < attributeswithName.length; k++) { const attrOb = new Attribute(); attrOb.id = this.createId(); attrOb.name = attributeswithName[k]; - attrOb.type = 'Service Attribute'; + attrOb.type = 'Attribute'; serviceAttributes.children.push(attrOb); } // end inner for loop if (serviceAttributes.children.length >0 ) { - serviceAttributes.name = 'Service Attributes('+ serviceAttributes.children.length + ')'; + serviceAttributes.name = serviceAttributes.name+'('+ serviceAttributes.children.length + ')'; serviceNode.data.children.push(serviceAttributes); } @@ -301,12 +340,70 @@ export class TreeNodeProcessor { .get(configs_url, pagingOb, []) .then((configs) => { service_configs = configs && configs.data ? configs.data : []; + const childs = new Children(); + childs.id = this.createId(); + childs.name = 'Service Configs'; + childs.type = 'Service Configs'; + const service_configs_Arr = []; + service_configs.forEach( (cfg) => { + const obj = new Config(); + obj.id = this.createId(); + obj.name = cfg.name; + obj.uuid = cfg.uuid; + obj.data = JSON.stringify(cfg.data); + service_configs_Arr.push(obj); + } ); + + if (service_configs_Arr.length >0 ) { + childs.children = service_configs_Arr; + childs.name = childs.name +'('+childs.children.length + ')'; + serviceNode.data.children.push(childs); + serviceNode.data.intercept = this.serviceConfigAlias(service_configs); + } }); + wait_promises.push(configPromise); const service_edge_router_policies_url = this.getLinkForResource( rawServiceObj, 'service-edge-router-policies' ); + let service_routers_urls = []; + const ser_promse = await zitiService + .get(service_edge_router_policies_url.replace('./', ''), pagingOb, []) + .then( (result) => { + result.data.forEach( (serp) => { + service_routers_urls.push( this.getLinkForResource(serp,'edge-routers') ); + } );; + }); + wait_promises.push(ser_promse); + let serp_page_Promises = []; + service_routers_urls.forEach((er_url) => { + const promse = zitiService + .get(er_url.replace('./', ''), pagingOb, []); + // .then(result => [policy.type, result]); + serp_page_Promises.push(promse); + }); + let associatedRouters = []; + await Promise.all(serp_page_Promises).then( (values:any) => { + values && values.forEach((res:any) => { + if (res && res.data.length > 0) { + res.data.forEach( (er)=> { + const erOb = new ERouter(); + erOb.id = this.createId(); + erOb.name = er.name; + er.type = 'Router'; + associatedRouters.push(erOb); + }); + } + }) + const tmp = new Children(); + tmp.name = "Associated Routers(" + associatedRouters.length +')'; + tmp.type = "Routers"; + tmp.children = associatedRouters; + tmp.id = this.createId(); + serviceNode.data.children.push(tmp); + }); + const service_policies_url = this.getLinkForResource( rawServiceObj, 'service-policies' @@ -314,47 +411,60 @@ export class TreeNodeProcessor { let bindPolicies = []; let bindIdnetities = []; + let dialIdnetities = []; + const promise2 = await zitiService .get(service_policies_url, pagingOb, []) .then((policies) => { - const identityPromises = []; - policies.data.forEach( async (policy) => { - if (policy.type === 'Bind') { - const bindIdentitiesUrl = this.getLinkForResource( - policy, - 'identities' - ); - - const promse = await zitiService - .get(bindIdentitiesUrl.replace('./', ''), pagingOb, []) - .then((res) => { - if (res && res.data.length > 0) { - res.data.forEach((rs) => { - this.addItemToArray(bindIdnetities, rs); - }); - } else { - this.addItemToArray(bindIdnetities, res.data); + bindPolicies = policies.data; + }); + + const pagePromises = []; + bindPolicies.forEach( (policy) => { + const bindIdentitiesUrl = this.getLinkForResource( + policy, + 'identities' + ); + const promse = zitiService + .get(bindIdentitiesUrl.replace('./', ''), pagingOb, []) + .then(result => [policy.type, result]); + pagePromises.push(promse); + }); + await Promise.all(pagePromises).then( (values:any) => { + values && values.forEach((res:any) => { + if (res && res[1].data.length > 0) { + res[1].data.forEach((rs) => { + if(res[0] === 'Bind') { + bindIdnetities = this.addItemToArray(bindIdnetities, rs); + } else if(res[0] === 'Dial'){ + dialIdnetities = this.addItemToArray(dialIdnetities, rs); } + }); + } else { + if(res[0] === 'Bind') { + bindIdnetities = this.addItemToArray(bindIdnetities, res[1].data); + } else if(res[0] === 'Dial'){ + dialIdnetities = this.addItemToArray(dialIdnetities, res[1].data); + } + } - }); - identityPromises.push(promse); - wait_promises.push(promse); - } - }); - Promise.all(identityPromises).then(() => { - const tmp = new Children(); + }); + + const tmp = new Children(); tmp.name = "Bind Identities(" + bindIdnetities.length +')'; tmp.type = "Identities"; tmp.children = bindIdnetities; tmp.id = this.createId(); serviceNode.data.children.push(tmp); - }); - }); - - wait_promises.push(promise2); - Promise.all(wait_promises).then( () => { + const tmpDial = new Children(); + tmpDial.name = "Dial Identities(" + dialIdnetities.length +')'; + tmpDial.type = "Identities"; + tmpDial.children = dialIdnetities; + tmpDial.id = this.createId(); + serviceNode.data.children.push(tmpDial); resolve('this is a promise'); - }); + }); + Promise.all(wait_promises).then( () => { }); }); // end of mypromise networkGraph = d3.hierarchy(networkGraph.data, function (nd) { @@ -365,30 +475,68 @@ export class TreeNodeProcessor { async processServicePoliciesForNodeClick(policyNode, networkGraph, servicePolicies, uniqId, zitiService) { - const myPromise = await new Promise( async (resolve, reject) => { const wait_promises = []; this.uniqId = uniqId; const rawSPolicy = servicePolicies.find((s) => s.name === policyNode.data.name); - const pagesize = 500; - const services_paging = this.getPagingObject(pagesize); - const bindIdentitiesUrl = this.getLinkForResource( rawSPolicy, 'identities' ); - const sp_promise = await zitiService.get(bindIdentitiesUrl, services_paging, []) + const childs = new Children(); + childs.name = "Attributes"; + childs.id = this.createId(); + + rawSPolicy.serviceRoles && rawSPolicy.serviceRoles.forEach( (ob) => { + const attr = new Attribute(); + attr.name = ob; + attr.id = this.createId(); + attr.type = "Service Attribute"; + childs.children.push(attr); + }); + rawSPolicy.identityRoles && rawSPolicy.identityRoles.forEach( (ob) => { + const attr = new Attribute(); + attr.name = ob; + attr.id = this.createId(); + attr.type = "Identity Attribute"; + childs.children.push(attr); + }); + childs.name = childs.name + '('+ childs.children.length + ')'; + policyNode.data.children.push(childs); + + const pagesize = 500; + const identities_paging = this.getPagingObject(pagesize); + const bindIdentitiesUrl = this.getLinkForResource( rawSPolicy, 'identities' ); + // this.logger.info('bindIdentitiesUrl',bindIdentitiesUrl); + const sp_promise = await zitiService.get(bindIdentitiesUrl, identities_paging, []) .then((ids) => { const childs =new Children() - childs.name = rawSPolicy.type+ 'Identities'; + childs.name = rawSPolicy.type+ '-Identities'; childs.id = this.createId(); ids.data.forEach( (idOb) => { - const ep = new Identity(); - ep.id = idOb.id; - ep.name = idOb.name; - ep.type = 'Identity'; - childs.children.push(ep); + childs.children.push(this.createIdentity(idOb) ); }); childs.name = childs.name + '(' + childs.children.length+')'; policyNode.data.children.push(childs); }); wait_promises.push(sp_promise); + + const services_paging = this.getPagingObject(pagesize); + const bindServicesUrl = this.getLinkForResource( rawSPolicy, 'services' ); // pull services + const _promise = await zitiService.get(bindServicesUrl, services_paging, []) + .then((result) => { + const childs =new Children() + childs.name = rawSPolicy.type+ '-Services'; + childs.id = this.createId(); + result.data.forEach( (idOb) => { + const sp = new Service(); + sp.id = idOb.id; + sp.name = idOb.name; + sp.type = 'Service'; + childs.children.push(sp); + }); + childs.name = childs.name + '(' + childs.children.length+')'; + policyNode.data.children.push(childs); + }); + + wait_promises.push(_promise); + Promise.all(wait_promises).then( () => { resolve('resolve.'); }); @@ -397,10 +545,27 @@ export class TreeNodeProcessor { networkGraph = d3.hierarchy(networkGraph.data, function (nd) { return nd.children; }); - return [networkGraph, this.uniqId]; } + async getServicesForAServicePolicy(policyObject, zitiService, pagesize=500) { + const childs = []; + const services_paging = this.getPagingObject(pagesize); + const bindServicesUrl = this.getLinkForResource( policyObject, 'services' ); + const _promise = await zitiService.get(bindServicesUrl, services_paging, []) + .then((result) => { + result.data.forEach( (idOb) => { + const sp = new Service(); + sp.id = this.createId(); + sp.uuid = idOb.id; + sp.name = idOb.name; + sp.type = 'Service'; + childs.push(sp); + }); + }); + return [policyObject.type, childs]; + } + async processEdgeroutersForNodeClick( edgerouterNode, networkGraph, @@ -454,6 +619,9 @@ export class TreeNodeProcessor { policy.uuid = pol.id; policy.name = pol.name; policy.type = 'Router Policy'; + policy.isSystem = pol.isSystem; + policy.semantic = pol.semantic; + policy.rootNode = 'No'; erpolChilds.children.push(policy); }); erpolChilds.name = "Edge Router Policies("+ erpolChilds.children.length +')'; @@ -641,11 +809,41 @@ export class TreeNodeProcessor { uniqId, zitiService ) { - const myPromise = await new Promise((resolve, reject) => { + const myPromise = await new Promise(async (resolve, reject) => { const wait_promises = []; this.uniqId = uniqId; const rawERPolicy = edgeRouterPolicies.find((s) => s.name === erPolicyNode.data.name); const pagesize = 500; + + const er_url = this.getLinkForResource(rawERPolicy, 'edge-routers').replace('./', ''); + let associatedRouters = []; + const paging = this.getPagingObject(pagesize); + const erChailds = new Children(); + erChailds.name= 'Associated Routers'; + erChailds.type= 'Associated Routers'; + erChailds.id = this.createId(); + const associatedRoutersPromise = await zitiService.get(er_url, paging, []) + .then( (result) => { + result.data.forEach( (er) => { + const tmp = new ERouter(); + tmp.id = this.createId(); + tmp.name = er.name; + tmp.type = 'Router'; + tmp.verified = er.isVerified? 'Yes': 'No'; + tmp.online = er.isOnline? 'Yes': 'No'; + tmp.disabled = er.disabled? 'Yes': 'No'; + tmp.rootNode = 'Yes'; + tmp.tunnelerEnabled = er.tunnelerEnabled? er.tunnelerEnabled:'No'; + tmp.syncStatus = er.syncStatus? er.syncStatus: 'No'; + tmp.createdAt = er.createdAt; + tmp.updatedAt = er.updatedAt; + erChailds.children.push(tmp); + } ); + erChailds.name = erChailds.name +'('+ erChailds.children.length +')'; + erPolicyNode.data.children.push(erChailds); + }); + wait_promises.push(associatedRoutersPromise); + const services_paging = this.getPagingObject(pagesize); const url = this.getLinkForResource(rawERPolicy, 'identities'); let ids = []; @@ -671,13 +869,8 @@ export class TreeNodeProcessor { Promise.all(promises).then(() => { const idChilds = new Children(); idChilds.id = this.createId(); - ids.forEach( (er) => { - const idOb = new Identity(); - idOb.id = this.createId(); - idOb.uuid = er.id; - idOb.name = er.name; - idOb.type = 'Identity'; - idChilds.children.push(idOb); + ids.forEach( (idOb) => { + idChilds.children.push(this.createIdentity(idOb)); }); idChilds.name = "Accessible Identities("+ idChilds.children.length +')'; erPolicyNode.data.children.push(idChilds); @@ -746,7 +939,7 @@ export class TreeNodeProcessor { serviceErPolicyNode.data.children.push(erChilds); }); }); - wait_promises.push(erpromise); + wait_promises.push(erpromise); const services_pagingB = this.getPagingObject(pagesize); @@ -789,7 +982,7 @@ export class TreeNodeProcessor { wait_promises.push(serpromise); Promise.all(wait_promises).then( () => { - resolve('this is a promise'); + resolve('Done'); } ); }); diff --git a/projects/ziti-console-lib/src/lib/pages/configurations/configurations-page.service.ts b/projects/ziti-console-lib/src/lib/pages/configurations/configurations-page.service.ts index 840d2960..33a6accc 100644 --- a/projects/ziti-console-lib/src/lib/pages/configurations/configurations-page.service.ts +++ b/projects/ziti-console-lib/src/lib/pages/configurations/configurations-page.service.ts @@ -56,6 +56,7 @@ export class ConfigurationsPageService extends ListPageServiceClass { protected override router: Router ) { super(settings, filterService, csvDownloadService, extService, router); + this.getConfigTypes(); } initTableColumns(): any { diff --git a/projects/ziti-console-lib/src/lib/pages/identities/identities-page.component.html b/projects/ziti-console-lib/src/lib/pages/identities/identities-page.component.html index eae19248..3d4d6e0e 100644 --- a/projects/ziti-console-lib/src/lib/pages/identities/identities-page.component.html +++ b/projects/ziti-console-lib/src/lib/pages/identities/identities-page.component.html @@ -3,7 +3,7 @@ [tabs]="tabs" [showAdd]="!itemsSelected" (actionClicked)="headerActionClicked($event)"> - + - ('ZAC_LOGIN_SERVICE'); export abstract class LoginServiceClass { + public originIsController; + abstract init(); abstract login(prefix: string, url: string, username: string, password: string); abstract observeLogin(serviceUrl: string, username: string, password: string); diff --git a/projects/ziti-console-lib/src/lib/services/ziti-controller-data.service.ts b/projects/ziti-console-lib/src/lib/services/ziti-controller-data.service.ts index 6afc061a..6217eb96 100644 --- a/projects/ziti-console-lib/src/lib/services/ziti-controller-data.service.ts +++ b/projects/ziti-console-lib/src/lib/services/ziti-controller-data.service.ts @@ -24,7 +24,7 @@ import {catchError} from "rxjs/operators"; import {HttpClient} from "@angular/common/http"; import {FilterObj} from "../features/data-table/data-table-filter.service"; import {ZitiDataService} from "./ziti-data.service"; -import {isEmpty, isArray, isNumber} from "lodash"; +import {isEmpty, isArray, isNumber, isBoolean} from "lodash"; import {Resolver} from "@stoplight/json-ref-resolver"; import moment from "moment"; @@ -235,11 +235,12 @@ export class ZitiControllerDataService extends ZitiDataService { let filterVal = ''; switch (filter.type) { case 'TEXTINPUT': - filterVal = `${filter.columnId} contains "${filter.value}"`; + const verb = filter.verb ? filter.verb : 'contains'; + filterVal = `${filter.columnId} ${verb} "${filter.value}"`; break; case 'SELECT': case 'COMBO': - const val = isNumber(filter.value) ? `${filter.value}` : `"${filter.value}"`; + const val = (isNumber(filter.value) || isBoolean(filter.value)) ? `${filter.value}` : `"${filter.value}"`; filterVal = `${filter.columnId} = ${val}`; break; case 'DATETIME': @@ -248,6 +249,9 @@ export class ZitiControllerDataService extends ZitiDataService { case 'ATTRIBUTE': filterVal = this.getAttributeFilter(filter.value, filter.columnId); break; + case 'BOOLEAN': + filterVal = `${filter.columnId}=${filter.value}`; + break; default: filterVal = `${filter.columnId} contains "${filter.value}"`; break; diff --git a/projects/ziti-console-lib/src/lib/services/ziti-data.service.ts b/projects/ziti-console-lib/src/lib/services/ziti-data.service.ts index bf9a54bf..42d5d35d 100644 --- a/projects/ziti-console-lib/src/lib/services/ziti-data.service.ts +++ b/projects/ziti-console-lib/src/lib/services/ziti-data.service.ts @@ -23,7 +23,7 @@ import {HttpClient} from "@angular/common/http"; import {FilterObj} from "../features/data-table/data-table-filter.service"; import { LoginServiceClass } from './login-service.class'; -import {cloneDeep} from "lodash"; +import {cloneDeep, isEmpty, sortedUniq} from "lodash"; export const ZITI_DATA_SERVICE = new InjectionToken('ZITI_DATA_SERVICE'); @@ -62,4 +62,26 @@ export abstract class ZitiDataService { abstract resetEnrollment(id: string, date: string): Promise; abstract reissueEnrollment(id: string, date: string): Promise; abstract schema(data: any): Promise; + + getRoleFilter(roleAttributes) { + let hasAll = false; + roleAttributes.forEach((attr) => { + if (attr === 'all') { + hasAll = true; + } + }); + let filters = []; + if (!hasAll) { + filters = [ + { + columnId: "roleAttributes", + filterName: "Attributes", + label: "", + type: "ATTRIBUTE", + value: roleAttributes + } + ]; + } + return filters; + } } diff --git a/projects/ziti-console-lib/src/lib/shared/list-page-component.class.ts b/projects/ziti-console-lib/src/lib/shared/list-page-component.class.ts index 7c3b863e..3a5dbf00 100644 --- a/projects/ziti-console-lib/src/lib/shared/list-page-component.class.ts +++ b/projects/ziti-console-lib/src/lib/shared/list-page-component.class.ts @@ -24,6 +24,7 @@ import {MatDialog} from "@angular/material/dialog"; import {defer, isEmpty} from "lodash"; import {ActivatedRoute} from "@angular/router"; +import {ExtensionService} from "../features/extendable/extensions-noop.service"; @Injectable() export abstract class ListPageComponent { @@ -53,7 +54,8 @@ export abstract class ListPageComponent { protected filterService: DataTableFilterService, public svc: ListPageServiceClass, protected consoleEvents: ConsoleEventsService, - protected dialogForm: MatDialog + protected dialogForm: MatDialog, + protected extensionService?: ExtensionService ) {} ngOnInit() { @@ -61,6 +63,7 @@ export abstract class ListPageComponent { this.svc.refreshData = this.refreshData.bind(this); this.columnDefs = this.svc.initTableColumns(); this.filterService.clearFilters(); + this.extensionService?.extendOnInit(); this.subscription.add( this.filterService.filtersChanged.subscribe(filters => { this.filterApplied = filters && filters.length > 0; diff --git a/projects/ziti-console-lib/src/lib/shared/list-page-service.class.ts b/projects/ziti-console-lib/src/lib/shared/list-page-service.class.ts index c8194a71..529f2c99 100644 --- a/projects/ziti-console-lib/src/lib/shared/list-page-service.class.ts +++ b/projects/ziti-console-lib/src/lib/shared/list-page-service.class.ts @@ -196,9 +196,9 @@ export abstract class ListPageServiceClass { public openEditForm(itemId = '', basePath?) { if (isEmpty(itemId)) { - itemId = '/create'; + itemId = 'create'; } basePath = basePath ? basePath : this.basePath; - this.router?.navigateByUrl(`${basePath}${itemId}`); + this.router?.navigateByUrl(`${basePath}/${itemId}`); } } diff --git a/projects/ziti-console-lib/src/public-api.ts b/projects/ziti-console-lib/src/public-api.ts index f21aa7f6..568e7880 100644 --- a/projects/ziti-console-lib/src/public-api.ts +++ b/projects/ziti-console-lib/src/public-api.ts @@ -33,6 +33,7 @@ export * from './lib/services/deactivate-guard.service'; export * from './lib/services/csv-download.service'; export * from './lib/features/projectable-forms/configuration/configuration-form.component'; export * from './lib/features/extendable/extendable.component'; +export * from './lib/features/data-table/data-table-filter.service'; export * from './lib/features/card-list/card-list.component'; export * from './lib/features/sidebars/side-toolbar/side-toolbar.component'; export * from './lib/features/sidebars/side-navbar/side-navbar.component'; diff --git a/release-notes.md b/release-notes.md index 28a7c804..ce35feda 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,4 +1,35 @@ +# app-ziti-console-v3.4.7 +# ziti-console-lib-v0.4.9 +* [Issue #476](https://github.com/openziti/ziti-console/issues/476) - Reduce application bundle size using production configuration +* [Issue #478](https://github.com/openziti/ziti-console/issues/478) - Fix display of identity "role" attributes on simple service form + +# ziti-console-lib-v0.4.8 +* [Issue #469](https://github.com/openziti/ziti-console/issues/469) - Validation for the names on simple service summary screen +* [Issue #434](https://github.com/openziti/ziti-console/issues/467) - Allow users to navigate to the config edit page via the "Associated Configs" pod + + +# app-ziti-console-v3.4.6 +# ziti-console-lib-v0.4.7 + +## Feature/Improvements +* [Issue #459](https://github.com/openziti/ziti-console/issues/459) - Enable extension of data-table filters via the ExtensionService class +* [Issue #434](https://github.com/openziti/ziti-console/issues/434) - Allow filtering of Identities list page via "isAdmin" + +## Bug Fixes +* [Issue #457](https://github.com/openziti/ziti-console/issues/457) - Table is not resized when browzer size changes + + +# ziti-console-lib-v0.4.6 + +## Bug Fixes +* [Issue #450](https://github.com/openziti/ziti-console/issues/450) - Hide the navigation bar when session expires and routing to login page +* [Issue #452](https://github.com/openziti/ziti-console/issues/452) - Correctly show associated entities when using the #all attribute +* [Issue #453](https://github.com/openziti/ziti-console/issues/453) - AppData is not persisting when adding via the JSON editor on edit forms +* [Issue #455](https://github.com/openziti/ziti-console/issues/455) - Prevent overflow of attribute items with long names in tag selector component +* [Issue #457](https://github.com/openziti/ziti-console/issues/457) - Table is not resized when browzer size changes + # app-ziti-console-v3.4.5 +## Bug Fixes * [Issue #444](https://github.com/openziti/ziti-console/issues/444) - BASE_HREF is wrongly quoted