From d6111ad3ac46259ff1adc75954160516eb6cea90 Mon Sep 17 00:00:00 2001 From: abergen Date: Thu, 16 Oct 2025 10:15:07 +0000 Subject: [PATCH 1/2] ITC-3600 - added network interfaces wizard frontend --- src/app/app.routes.ts | 4 +- .../network_module/network_module.routes.ts | 9 + .../networkinterfaces-wizard.interface.ts | 138 ++++++ .../networkinterfaces-wizard.service.ts | 60 +++ .../networkinterfaces.component.css | 13 + .../networkinterfaces.component.html | 395 ++++++++++++++++++ .../networkinterfaces.component.spec.ts | 23 + .../networkinterfaces.component.ts | 264 ++++++++++++ 8 files changed, 905 insertions(+), 1 deletion(-) create mode 100644 src/app/modules/network_module/network_module.routes.ts create mode 100644 src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces-wizard.interface.ts create mode 100644 src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces-wizard.service.ts create mode 100644 src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.css create mode 100644 src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.html create mode 100644 src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.spec.ts create mode 100644 src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 1a052dc6a..63bf9caa4 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -40,6 +40,7 @@ import { servicenowModuleRoutes } from './modules/servicenow_module/servicenow_m import { dellModuleRoutes } from './modules/dell_module/dell_module.routes'; import { proxmoxModuleRoutes } from './modules/proxmox_module/proxmox_module.routes'; import { ms365ModuleRoutes } from './modules/ms365_module/ms365_module.routes'; +import { networkModuleRoutes } from './modules/network_module/network_module.routes'; @Component({ selector: 'legacy-redirect', @@ -101,7 +102,8 @@ const moduleRoutes: Routes = [ ...servicenowModuleRoutes, ...dellModuleRoutes, ...proxmoxModuleRoutes, - ...ms365ModuleRoutes + ...ms365ModuleRoutes, + ...networkModuleRoutes ]; /*** Core routes ***/ const coreRoutes: Routes = [{ diff --git a/src/app/modules/network_module/network_module.routes.ts b/src/app/modules/network_module/network_module.routes.ts new file mode 100644 index 000000000..3fb127b66 --- /dev/null +++ b/src/app/modules/network_module/network_module.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { NetworkinterfacesComponent } from './pages/wizards/networkinterfaces/networkinterfaces.component'; + +export const networkModuleRoutes: Routes = [ + { + path: 'network_module/wizards/networkinterfaces/:hostId', + loadComponent: () => import('./pages/wizards/networkinterfaces/networkinterfaces.component').then(m => NetworkinterfacesComponent) + }, +]; diff --git a/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces-wizard.interface.ts b/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces-wizard.interface.ts new file mode 100644 index 000000000..e8e3929a0 --- /dev/null +++ b/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces-wizard.interface.ts @@ -0,0 +1,138 @@ +import { WizardGet, WizardPost } from '../../../../../pages/wizards/wizards.interface'; +import { GenericValidationError } from '../../../../../generic-responses'; + +// WIZARD GET +export interface NetworkinterfacesWizardGet extends WizardGet { + interfaceServicetemplate: InterfaceServicetemplate +} + + +export interface InterfaceServicetemplate { + id: number + uuid: string + template_name: string + name: string + container_id: number + servicetemplatetype_id: number + check_period_id: number + notify_period_id: number + description: string + command_id: number + check_command_args: string + checkcommand_info: string + eventhandler_command_id: number + timeperiod_id: number + check_interval: number + retry_interval: number + max_check_attempts: number + first_notification_delay: number + notification_interval: number + notify_on_warning: number + notify_on_unknown: number + notify_on_critical: number + notify_on_recovery: number + notify_on_flapping: number + notify_on_downtime: number + flap_detection_enabled: number + flap_detection_on_ok: number + flap_detection_on_warning: number + flap_detection_on_unknown: number + flap_detection_on_critical: number + low_flap_threshold: number + high_flap_threshold: number + process_performance_data: number + freshness_checks_enabled: number + freshness_threshold: any + passive_checks_enabled: number + event_handler_enabled: number + active_checks_enabled: number + retain_status_information: number + retain_nonstatus_information: number + notifications_enabled: number + notes: string + priority: number + tags: string + service_url: any + sla_relevant: number + is_volatile: number + check_freshness: number + created: string + modified: string + check_command: { + id: number + name: string + command_line: string + command_type: number + human_args: any + uuid: string + description: string + commandarguments: { + id: number + command_id: number + name: string + human_name: string + created: string + modified: string + }[] + } + servicetemplatecommandargumentvalues: Servicecommandargumentvalue[] +} + +export interface Servicecommandargumentvalue { + commandargument: Commandargument + commandargument_id: number + created: string + id: number + modified: string + servicetemplate_id: number + value: string +} + +export interface Commandargument { + command_id: number + created: string + human_name: string + id: number + modified: string + name: string +} + + + +// WIZARD POST +export interface NetworkinterfacesWizardPost extends WizardPost { + authPassword: string + authProtocol: string + interfaces: N0[] + privacyPassword: string + privacyProtocol: string + securityLevel: string + securityName: string + snmpCommunity: string + snmpVersion: string +} + +export interface N0 { + createService: boolean + description: string + host_id: number + name: string + servicecommandargumentvalues: Servicecommandargumentvalue[] + servicetemplate_id: number +} + +// SNMP Discovery +export interface NetworkinterfacesDiscovery { + interfaces: Interface[] + success: boolean + errors: GenericValidationError | undefined + _csrfToken: any +} + +export interface Interface { + key: number + value: { + number: string + name: string + } +} diff --git a/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces-wizard.service.ts b/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces-wizard.service.ts new file mode 100644 index 000000000..42cb275ee --- /dev/null +++ b/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces-wizard.service.ts @@ -0,0 +1,60 @@ +import { Injectable } from '@angular/core'; +import { catchError, map, Observable, of } from 'rxjs'; +import { WizardsService } from '../../../../../pages/wizards/wizards.service'; +import { GenericResponseWrapper, GenericValidationError } from '../../../../../generic-responses'; +import { + NetworkinterfacesDiscovery, + NetworkinterfacesWizardGet, + NetworkinterfacesWizardPost, +} from './networkinterfaces-wizard.interface'; + +@Injectable({ + providedIn: 'root' +}) +export class NetworkinterfacesWizardService extends WizardsService { + + public fetch(hostId: number): Observable { + return this.http.get(`${this.proxyPath}/network_module/wizards/networkinterfaces/${hostId}.json?angular=true`).pipe( + map((data: NetworkinterfacesWizardGet): NetworkinterfacesWizardGet => { + return data; + }) + ); + } + + public submit(post: NetworkinterfacesWizardPost): Observable { + return this.http.post(`${this.proxyPath}/network_module/wizards/networkinterfaces.json?angular=true`, post) + .pipe( + map(data => { + return { + success: true, + data: null + }; + }), + catchError((error: any) => { + const err = error.error.error as GenericValidationError; + return of({ + success: false, + data: err + }); + }) + ); + + } + + public executeNetworkinterfacesDiscovery(post: NetworkinterfacesWizardPost): Observable { + return this.http.post(`${this.proxyPath}/network_module/wizards/executeNetworkinterfacesDiscovery/${post.host_id}.json?angular=true`, post) + .pipe( + map((data: NetworkinterfacesDiscovery) => { + return data + }), + catchError((error: any) => { + const err = error.error.error as GenericValidationError; + return of({ + success: false, + data: err + }); + }) + ); + + } +} diff --git a/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.css b/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.css new file mode 100644 index 000000000..32c2f746d --- /dev/null +++ b/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.css @@ -0,0 +1,13 @@ + +.alert-icon { + margin-right: 10px; +} + +.accordion-button-padding-left { + padding-left: 1.3rem; /*var(--cui-accordion-btn-padding-x) [1.25rem] + border */ +} + +.lg-checkbox { + width: 20px; + height: 20px; +} diff --git a/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.html b/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.html new file mode 100644 index 000000000..f5c7b3504 --- /dev/null +++ b/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.html @@ -0,0 +1,395 @@ + + + + + + +
+ {{ t('Configuration Wizard: Network Interfaces') }} +
+
+ + +
+
+ + + {{ t('Host Information') }} + +
+
+ + + {{ t('Configure SNMP for Network Interfaces Monitoring') }} + +
+
+
+
+
+ + + +
+
+
+
+ + + +
+ +

+ {{ t('SNMP Server Settings') }} +

+
+ +
+ + + + +
+ + @if (post.snmpVersion === '3') { +
+ + + + +
+ @if (post.securityLevel === '1') { + {{ t('Communication with authentication and privacy. The protocols used for Authentication are MD5 and SHA and for Privacy, DES (Data Encryption Standard) and AES (Advanced Encryption Standard).') }} + } + @if (post.securityLevel === '2') { + {{ t('Communication with authentication and without privacy. The protocols used for Authentication are MD5 and SHA (Secure Hash Algorithm).') }} + } + @if (post.securityLevel === '3') { + {{ t('Communication without authentication and privacy.') }} + } +
+
+ + +
+ + + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + + +
+ +
+ + + +
+ } + + @if (post.snmpVersion !== '3') { +
+ + + +
+ } +
+ + + +
+ +

+ {{ t('Network Interfaces') }} +

+ + {{ t('To avoiding duplicate service configuration, the existing services with the same name will be automatically deselected') }} + + +
+ +
+ + @if (data) { + + {{ t('Discovering Network Interfaces for Services') }} + + } +
+ {{ t('Scan this device and locate all of the interfaces that reside on the device. Only possible with valid SNMP configuration') }} +
+ + +
+ +
+ +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + @for (interface of post.interfaces; track $index) { +
      + @if (!interface.servicecommandargumentvalues.length && hasName(interface.name)) { + + +
      +
      + + +
      +
      +
      + } @else if (hasName(interface.name)) { + + + + +
      +
      + + +
      +
      +
      + +
      + @for ( + commandArgument of interface.servicecommandargumentvalues; track $index) { +
      + {{ commandArgument.commandargument.human_name }} + +
      + } +
      +
      +
      +
      +
      + } +
    + } +
+
+ + +
+
+ +
diff --git a/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.spec.ts b/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.spec.ts new file mode 100644 index 000000000..9c792c8c1 --- /dev/null +++ b/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NetworkinterfacesComponent } from './networkinterfaces.component'; + +describe('NetworkinterfacesComponent', () => { + let component: NetworkinterfacesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NetworkinterfacesComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(NetworkinterfacesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.ts b/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.ts new file mode 100644 index 000000000..83fd1d353 --- /dev/null +++ b/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.ts @@ -0,0 +1,264 @@ +import { ChangeDetectionStrategy, Component, inject, ViewChild, ViewChildren } from '@angular/core'; +import { WizardsAbstractComponent } from '../../../../../pages/wizards/wizards-abstract/wizards-abstract.component'; +import { SelectKeyValueString } from '../../../../../layouts/primeng/select.interface'; +import { NetworkinterfacesWizardService } from './networkinterfaces-wizard.service'; +import { + InterfaceServicetemplate, + N0, + NetworkinterfacesWizardGet, + NetworkinterfacesWizardPost +} from './networkinterfaces-wizard.interface'; +import { RouterLink } from '@angular/router'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { + AccordionButtonDirective, + AccordionComponent, + AccordionItemComponent, + CardBodyComponent, + CardComponent, + CardHeaderComponent, + CardTitleDirective, + ColComponent, + FormCheckComponent, + FormCheckInputDirective, + FormCheckLabelDirective, + FormControlDirective, + FormLabelDirective, + InputGroupComponent, + InputGroupTextDirective, + RowComponent, + TemplateIdDirective +} from '@coreui/angular'; +import { TranslocoDirective, TranslocoPipe } from '@jsverse/transloco'; +import { RequiredIconComponent } from '../../../../../components/required-icon/required-icon.component'; +import { SelectComponent } from '../../../../../layouts/primeng/select/select/select.component'; +import { FormFeedbackComponent } from '../../../../../layouts/coreui/form-feedback/form-feedback.component'; +import { FormErrorDirective } from '../../../../../layouts/coreui/form-error.directive'; +import { FormsModule } from '@angular/forms'; +import { NgClass } from '@angular/common'; +import { + WizardsDynamicfieldsComponent +} from '../../../../../components/wizards/wizards-dynamicfields/wizards-dynamicfields.component'; +import { OitcAlertComponent } from '../../../../../components/alert/alert.component'; +import { ProgressBarModule } from 'primeng/progressbar'; +import { XsButtonDirective } from '../../../../../layouts/coreui/xsbutton-directive/xsbutton.directive'; +import { GenericResponseWrapper, GenericValidationError } from '../../../../../generic-responses'; +import { NgSelectComponent } from '@ng-select/ng-select'; +import { Service } from '../../../../../pages/wizards/wizards.interface'; +import { BackButtonDirective } from '../../../../../directives/back-button.directive'; + +@Component({ + selector: 'oitc-networkinterfaces', + imports: [ + RouterLink, + FaIconComponent, + CardComponent, + CardHeaderComponent, + CardBodyComponent, + TranslocoPipe, + RequiredIconComponent, + SelectComponent, + FormLabelDirective, + FormControlDirective, + WizardsDynamicfieldsComponent, + TranslocoDirective, + OitcAlertComponent, + ProgressBarModule, + XsButtonDirective, + CardTitleDirective, + ColComponent, + FormCheckInputDirective, + InputGroupComponent, + InputGroupTextDirective, + NgSelectComponent, + RowComponent, + BackButtonDirective, + FormFeedbackComponent, + FormErrorDirective, + FormsModule, + NgClass, + FormCheckComponent, + FormCheckLabelDirective, + AccordionComponent, + AccordionItemComponent, + TemplateIdDirective, + AccordionButtonDirective + ], + templateUrl: './networkinterfaces.component.html', + styleUrl: './networkinterfaces.component.css', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class NetworkinterfacesComponent extends WizardsAbstractComponent { + @ViewChildren('accordionItem') accordionItems: AccordionItemComponent[] = []; + @ViewChild(WizardsDynamicfieldsComponent) childComponentLocal!: WizardsDynamicfieldsComponent; + protected override WizardService: NetworkinterfacesWizardService = inject(NetworkinterfacesWizardService); + public checked: boolean = false; + public accordionClosed: boolean = true; + + protected override post: NetworkinterfacesWizardPost = { +// Default fields from the base wizard + host_id: 0, + services: [], +// Fields for the wizard + authPassword: '', + authProtocol: 'md5', + interfaces: [], + privacyPassword: '', + privacyProtocol: 'des', + securityLevel: '1', + securityName: '', + snmpCommunity: '', + snmpVersion: '2' + } as NetworkinterfacesWizardPost; + protected snmpVersions: SelectKeyValueString[] = [ + {value: '1', key: 'SNMP V 1'}, + {value: '2', key: 'SNMP V 2c'}, + {value: '3', key: 'SNMP V 3'}, + ] + protected searchedTags: string[] = []; + + + protected securityLevels: SelectKeyValueString[] = [ + {key: 'authPriv', value: '1'}, + {key: 'authNoPriv', value: '2'}, + {key: 'noAuthNoPriv', value: '3'}, + ]; + protected authProtocols: SelectKeyValueString[] = [ + {key: 'MD5', value: 'md5'}, + {key: 'SHA', value: 'sha'}, + ]; + protected privacyProtocols: SelectKeyValueString[] = [ + {key: 'DES', value: 'des'}, + {key: 'AES', value: 'aes'}, + {key: 'AES128', value: 'aes128'}, + {key: '3DES', value: '3des'}, + {key: '3DESDE', value: '3desde'}, + ]; + protected interfaceServicetemplate: InterfaceServicetemplate = {} as InterfaceServicetemplate; + + protected override wizardLoad(result: NetworkinterfacesWizardGet): void { + this.interfaceServicetemplate = result.interfaceServicetemplate; + + super.wizardLoad(result); + } + + public override submit(): void { + // let request = this.post; // Clone the original post object here! + let request: NetworkinterfacesWizardPost = JSON.parse(JSON.stringify(this.post)); + + // Remove all services from request where createService is false. + request.services = request.services.filter((service: Service) => { + return service.createService && this.childComponent.hasName(service.name); + }); + // Remove all interfaces from request where createService is false. + request.interfaces = request.interfaces.filter( + (networkInterface: N0) => networkInterface.createService && this.hasName(networkInterface.name) + ); + + this.subscriptions.add(this.WizardService.submit(request) + .subscribe((result: GenericResponseWrapper) => { + this.errors = {} as GenericValidationError; + if (result.success) { + const title: string = this.TranslocoService.translate('Success'); + const msg: string = this.TranslocoService.translate('Data saved successfully'); + + this.notyService.genericSuccess(msg, title); + this.router.navigate(['/services/notMonitored']); + this.cdr.markForCheck(); + return; + } + // Error + this.notyService.genericError(); + this.notyService.scrollContentDivToTop(); + const errorResponse: GenericValidationError = result.data as GenericValidationError; + if (result) { + this.errors = errorResponse; + + } + this.cdr.markForCheck(); + }) + ); + } + + protected toggleCheck(checked: boolean): void { + this.checked = checked; + this.post.interfaces.forEach((service: N0) => { + if (!this.hasName(service.name)) { + return; + } + service.createService = this.checked; + }); + this.cdr.markForCheck(); + } + + protected toggleAccordionClose(checked: boolean): void { + this.accordionClosed = checked; + this.accordionItems.forEach((accordionItem: AccordionItemComponent) => { + if ((accordionItem.visible && this.accordionClosed) || (!accordionItem.visible && !this.accordionClosed)) { + accordionItem.toggleItem(); + } + }); + this.cdr.markForCheck(); + } + + protected detectColor = function (label: string): string { + if (label.match(/warning/gi)) { + return 'warning'; + } + + if (label.match(/critical/gi)) { + return 'critical'; + } + + return ''; + }; + + protected hasName = (name: string): boolean => { + if (this.searchedTags.length === 0) { + return true; + } + return this.searchedTags.some((tag) => { + return name.toLowerCase().includes(tag.toLowerCase()); + }); + } + + protected runNetworkinterfacesDiscovery(): void { + this.post.interfaces = []; + this.cdr.markForCheck(); + this.WizardService.executeNetworkinterfacesDiscovery(this.post).subscribe((data: any) => { + this.errors = {} as GenericValidationError; + this.accordionClosed = true; + this.cdr.markForCheck(); + // Error + if (data && data.interfaces && data.interfaces.length && data.interfaces[0].value && data.interfaces[2]) { + for (let key in data.interfaces[2].value) { + let servicetemplatecommandargumentvalues = JSON.parse(JSON.stringify(this.interfaceServicetemplate.servicetemplatecommandargumentvalues)); + servicetemplatecommandargumentvalues[0].value = data.services[2].value[key].name; + let name = String(data.services[2].value[key].name); + this.post.interfaces.push( + { + createService: !this.isServiceAlreadyPresent(this.WizardGet.servicesNamesForExistCheck, name), + description: '', + host_id: this.post.host_id, + name: name, + servicecommandargumentvalues: servicetemplatecommandargumentvalues, + servicetemplate_id: this.interfaceServicetemplate.id + }); + } + this.childComponentLocal.cdr.markForCheck(); + this.cdr.markForCheck(); + return; + } + this.notyService.genericError(); + + const errorResponse: GenericValidationError = data.data as GenericValidationError; + if (data.data) { + this.errors = errorResponse; + if (this.errors.hasOwnProperty('snmpCommunity')) { + this.notyService.scrollContentDivToTop(); + } + } + this.cdr.markForCheck(); + }); + } +} From a251622e1c1182c8dee4f35046460a34e55cab09 Mon Sep 17 00:00:00 2001 From: abergen Date: Fri, 17 Oct 2025 08:31:32 +0000 Subject: [PATCH 2/2] ITC-3600 - fixed Network Interfaces Scan --- .../wizards/networkinterfaces/networkinterfaces.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.ts b/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.ts index 83fd1d353..ed1b83335 100644 --- a/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.ts +++ b/src/app/modules/network_module/pages/wizards/networkinterfaces/networkinterfaces.component.ts @@ -233,8 +233,8 @@ export class NetworkinterfacesComponent extends WizardsAbstractComponent { if (data && data.interfaces && data.interfaces.length && data.interfaces[0].value && data.interfaces[2]) { for (let key in data.interfaces[2].value) { let servicetemplatecommandargumentvalues = JSON.parse(JSON.stringify(this.interfaceServicetemplate.servicetemplatecommandargumentvalues)); - servicetemplatecommandargumentvalues[0].value = data.services[2].value[key].name; - let name = String(data.services[2].value[key].name); + servicetemplatecommandargumentvalues[0].value = data.interfaces[2].value[key].name; + let name = String(data.interfaces[2].value[key].name); this.post.interfaces.push( { createService: !this.isServiceAlreadyPresent(this.WizardGet.servicesNamesForExistCheck, name),