import { Component, ViewChild } from '@angular/core'; import { ValidationErrors } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { faAngleRight, faPlayCircle, faPlus, faSave, faServer, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; import { NgbAccordion, NgbPanelChangeEvent } from '@ng-bootstrap/ng-bootstrap'; import { ServiceCheckFormComponent } from 'src/app/components/service-check-form/service-check-form.component'; import { ComponentService } from 'src/app/services/component.service'; import { ServiceApiService } from 'src/app/services/service-api.service'; import { ServerApiService } from 'src/app/services/server-api.service'; import { ToastService } from 'src/app/services/toast.service'; @Component({ selector: 'app-admin-service-checks-page', templateUrl: './admin-service-checks-page.component.html', styleUrls: ['./admin-service-checks-page.component.scss'], host: { class: 'd-flex flex-column h-100' } }) export class AdminServiceChecksPageComponent { public PANEL_ID_PFX = 'acc-svc-checks-'; public PANEL_ID_REG = /^acc-svc-checks-(-?\d+)$/; @ViewChild(ServiceCheckFormComponent) formRef!: ServiceCheckFormComponent; @ViewChild('acc') accordeonRef!: NgbAccordion; public serverConfigs?: ServerConfig[]; public serviceChecks: HttpCheckConfig[] = []; public loadingServiceChecks = false; public fa = { save: faSave, server: faServer, angleRight: faAngleRight, plus: faPlus, trash: faTrashAlt, play: faPlayCircle }; public activeId = 0; public params?: { serverID?: number; checkID?: number; activeIds?: string[] }; constructor( apiService: ServerApiService, private cmpService: ComponentService, route: ActivatedRoute, private router: Router, private serviceApi: ServiceApiService, private toastService: ToastService ) { apiService.serverConfigs$.subscribe(data => { this.serverConfigs = data; this.syncInit(); }); route.params.subscribe({ next: params => { if (!params) return; this.params = { serverID: params['serverID'] ? Number(params['serverID']) : undefined, checkID: params['checkID'] ? Number(params['checkID']) : undefined }; this.syncInit(); } }); } ngOnInit(): void {} async syncInit() { if (!this.params) return; if (!this.serverConfigs) return; if (this.serverConfigs.length > 0) { if (!this.params.serverID) { // redirect to first server's id this.router.navigateByUrl(`/admin/svc/${this.serverConfigs[0].id}`); return; } // set selected nav item and load service checks this.activeId = this.serverConfigs.findIndex(c => c.id === this.params?.serverID); await this.fetchServiceChecks(this.params.serverID); if (this.params.checkID) { // open accordeon panel if (this.params.checkID === -1) { this.addServiceCheck(this.params.serverID); } else { const activePanelId = `${this.PANEL_ID_PFX}${this.params.checkID}`; this.params.activeIds = [activePanelId]; setTimeout(() => this.scrollIntoView(activePanelId)); } } } } async fetchServiceChecks(serverId: number) { this.loadingServiceChecks = true; try { this.serviceChecks = await this.serviceApi.loadServiceChecks(serverId); } catch (error: any) { console.error(error); } this.loadingServiceChecks = false; } private scrollIntoView(panelId: string) { const contentPanel = document.getElementById(panelId); let parent: HTMLElement | null = null; while ((parent = contentPanel?.parentElement ?? null)) { if (parent.classList.contains('accordion-item')) break; } parent?.scrollIntoView(); } onAccordeonChange(event: NgbPanelChangeEvent) { if (event.nextState) { const checkId = this.PANEL_ID_REG.exec(event.panelId)?.[1]; if (this.params) this.params.checkID = Number(checkId); history.pushState(null, '', `/admin/svc/${this.params?.serverID}/${checkId}`); } else { if (this.params) this.params.checkID = undefined; history.pushState(null, '', `/admin/svc/${this.params?.serverID}`); } } async saveServiceCheck(event: Event) { event.stopPropagation(); try { if (!this.params || !this.params.serverID) return; const savedCheck = await this.formRef?.save(); const panelId = `${this.PANEL_ID_PFX}${savedCheck.id}`; if (this.params.checkID === -1) { await this.fetchServiceChecks(this.params.serverID); this.params.activeIds = [panelId]; this.onAccordeonChange({ nextState: true, panelId, preventDefault: () => {} }); } else { const idx = this.serviceChecks.findIndex(c => savedCheck.id === c.id); if (idx >= 0) this.serviceChecks[idx] = savedCheck; } } catch (error: any) { if (error.type === 'validation') { const invalid: ValidationErrors = error.errors; Object.entries(invalid).forEach(entry => { this.toastService.error(entry[1], entry[0], 180000); }); } console.error(error); } } addServiceCheck(serverId: number) { const panelId = `${this.PANEL_ID_PFX}-1`; this.serviceChecks.unshift({ id: -1, serverId, checks: [] as string[] } as HttpCheckConfig); if (this.params) { this.params.activeIds = [panelId]; } this.onAccordeonChange({ nextState: true, panelId, preventDefault: () => {} }); } async removeServiceCheck(event: Event, checkId: number) { event.stopPropagation(); try { if (!this.params?.serverID) return; if (checkId === -1) { const decision = await this.cmpService.openConfirmModal({ modalContent: { message: `Really delete unsaved service check?` } }); if (decision) this.serviceChecks.shift(); } else { const decision = await this.cmpService.openConfirmModal({ modalContent: { message: `Really delete service check #${checkId}?` } }); if (!decision) return; await this.serviceApi.deleteServiceCheck(this.params.serverID, checkId); const idx = this.serviceChecks.findIndex(c => c.id === checkId); if (idx >= 0) this.serviceChecks.splice(idx, 1); } this.params.activeIds = []; this.onAccordeonChange({ nextState: false, panelId: '', preventDefault: () => {} }); } catch (error) { console.error(error); } } public async testServiceCheck(event: Event) { event.stopPropagation(); await this.formRef?.test(); } }