UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

599 lines (593 loc) 170 kB
import * as i0 from '@angular/core'; import { Injectable, ChangeDetectionStrategy, Component, inject, InjectionToken, Input, ViewChild, NgModule } from '@angular/core'; import * as i4 from 'ngx-bootstrap/modal'; import { BsModalService } from 'ngx-bootstrap/modal'; import { __decorate, __metadata } from 'tslib'; import * as i5 from '@ngx-formly/core'; import { FormlyModule } from '@ngx-formly/core'; import * as i1$2 from '@c8y/ngx-components'; import { gettext, ModalComponent, IconDirective, C8yStepper, C8yTranslateDirective, C8yStepperButtons, OperationResultComponent, ListGroupComponent, ListItemComponent, ListItemIconComponent, ListItemCollapseComponent, C8yTranslatePipe, memoize, hookGeneric, ExtensionPointWithoutStateForPlugins, fromTriggerOnce, OutletDirective, Status, TitleComponent, BreadcrumbComponent, BreadcrumbItemComponent, ActionBarItemComponent, ListDisplaySwitchComponent, IfAllowedDirective, HelpComponent, RequiredInputPlaceholderDirective, LoadMoreComponent, DatePipe, Permissions, NavigatorNode, FormGroupComponent, LoadingComponent, CommonModule, CoreModule, StepperModule, DynamicFormsModule, hookNavigator, DeviceBootstrapRealtimeService } from '@c8y/ngx-components'; import * as i6 from '@angular/forms'; import { FormGroup, FormsModule } from '@angular/forms'; import { Subject, BehaviorSubject, from, forkJoin, defer, Observable } from 'rxjs'; import { takeUntil, finalize, mergeMap, takeLast, map, tap, filter, switchMap, shareReplay, startWith } from 'rxjs/operators'; import * as i1$1 from '@c8y/client'; import { DeviceRegistrationStatus, DeviceRegistrationSecurityMode, ApplicationType } from '@c8y/client'; import * as i1 from '@angular/router'; import { RouterModule } from '@angular/router'; import { get, pick, sortBy, flatMap } from 'lodash-es'; import { CdkStep, STEP_STATE } from '@angular/cdk/stepper'; import { NgIf, NgClass, NgFor, AsyncPipe, JsonPipe, NgTemplateOutlet } from '@angular/common'; import { PopoverDirective, PopoverModule } from 'ngx-bootstrap/popover'; import * as i3 from '@ngx-translate/core'; import { flatten } from 'lodash'; import { BsDropdownDirective, BsDropdownToggleDirective, BsDropdownMenuDirective, BsDropdownModule } from 'ngx-bootstrap/dropdown'; import { saveAs } from 'file-saver'; class RegisterDeviceService { constructor(router, deviceRegService, alertService) { this.router = router; this.deviceRegService = deviceRegService; this.alertService = alertService; this._loading = new Subject(); this._limit = new BehaviorSubject({ isReached: false }); this._deviceRegistrationRequests = new BehaviorSubject({ data: [] }); this.deviceRegistrationRequests$ = this._deviceRegistrationRequests.asObservable(); this.loading$ = this._loading.asObservable(); this.limit$ = this._limit.asObservable(); this.deviceRegUrl = '/deviceregistration'; this.endSubscriptions = new Subject(); } isDeviceRegistration() { return get(this.router, 'url') === this.deviceRegUrl; } internalListUpdate(deviceRequests, pagingObject) { let { paging, data } = this._deviceRegistrationRequests.getValue(); if (pagingObject) { paging = pagingObject; } data = [...data, ...deviceRequests].filter(deviceReq => deviceReq.type !== 'c8y_DataBroker'); this._deviceRegistrationRequests.next({ data, paging }); } onDeviceBootstrap(bsData) { const { id, status } = bsData; this._deviceRegistrationRequests.next({ data: this.updateStatusById(id, status) }); } list(pageSize = 100) { this._loading.next(true); this._deviceRegistrationRequests.next({ data: [], paging: undefined }); from(this.deviceRegService.list({ pageSize, withTotalPages: true })) .pipe(takeUntil(this.endSubscriptions), finalize(() => this.limit())) .subscribe(res => { const { data, paging } = res; this.internalListUpdate(data, paging); this._loading.next(false); }, err => { this._loading.next(false); this.alertService.addServerFailure(err); }); } createMultiple(newDeviceRequests) { if (newDeviceRequests && newDeviceRequests.length > 0) { this._loading.next(true); const newRequests$ = newDeviceRequests.map(element => { return from(this.deviceRegService.create(element).catch((err) => ({ res: err.res, data: { ...err.data, id: element.id } }))); }); const groupedRequests = { success: [], failed: [] }; return forkJoin(newRequests$).pipe(mergeMap(resp => resp.map(el => { el.res.ok ? groupedRequests.success.push(el.data) : groupedRequests.failed.push(el.data); return groupedRequests; })), takeLast(1), finalize(() => { this.internalListUpdate(groupedRequests.success); this._loading.next(false); })); } } remove(id) { this._loading.next(true); from(this.deviceRegService.delete(id)) .pipe(takeUntil(this.endSubscriptions)) .subscribe(() => { this._deviceRegistrationRequests.next({ data: this.removeDeviceRegistrationRequestById(id) }); this._loading.next(false); this.alertService.success(gettext('Device registration cancelled.')); }, err => { this._loading.next(false); this.alertService.addServerFailure(err); }); } accept(request) { this._loading.next(true); const payload = pick(request, ['id', 'securityToken']); from(this.deviceRegService.accept(payload)) .pipe(takeUntil(this.endSubscriptions)) .subscribe(() => { this._deviceRegistrationRequests.next({ data: this.removeDeviceRegistrationRequestById(payload.id) }); this.limit(); this._loading.next(false); this.alertService.success(gettext('Device registration accepted.')); }, err => { this._loading.next(false); this.alertService.addServerFailure(err); }); } acceptAll() { const acceptedDeviceRequests = []; const failedDeviceRequests = []; this._loading.next(true); from(this.deviceRegService.acceptAll()) .pipe(takeUntil(this.endSubscriptions), map(({ data }) => { data.map(deviceRegistrationRequest => { if (deviceRegistrationRequest.successful) { acceptedDeviceRequests.push(deviceRegistrationRequest); this.removeDeviceRegistrationRequestById(deviceRegistrationRequest.id); } else { failedDeviceRequests.push(deviceRegistrationRequest); } }); return data; }), finalize(() => { // update rendered list with successful accepted device registrations // see: this.updateStatusById(...) this.internalListUpdate([]); this.limit(); this._loading.next(false); if (failedDeviceRequests.length > 0) { this.alertService.warning(gettext('Could not accept all pending registration requests.'), JSON.stringify({ failedDeviceRequests, acceptedDeviceRequests }, undefined, 2)); } else { this.alertService.success(gettext('Accepted all pending registration requests.')); } })) .subscribe(() => { // empty by design }, err => { this._loading.next(false); this.alertService.addServerFailure(err); }); } limit() { from(this.deviceRegService.limit()) .pipe(takeUntil(this.endSubscriptions)) .subscribe(res => this._limit.next(res.data), err => this.alertService.addServerFailure(err)); } getRequestByStatus(status) { return this._deviceRegistrationRequests.getValue().data.filter(req => req.status === status); } ngOnDestroy() { this.endSubscriptions.next(); this.endSubscriptions.complete(); } updateStatusById(id, status) { const items = this._deviceRegistrationRequests.getValue().data; const matchingElementIndex = items.findIndex(element => element.id === id); if (matchingElementIndex >= 0) { items[matchingElementIndex].status = status; } return items; } removeDeviceRegistrationRequestById(id) { const items = this._deviceRegistrationRequests.getValue().data; const matchingElementIndex = items.findIndex(element => element.id === id); if (matchingElementIndex >= 0) { items.splice(matchingElementIndex, 1); } this._loading.next(false); return items; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RegisterDeviceService, deps: [{ token: i1.Router }, { token: i1$1.DeviceRegistrationService }, { token: i1$2.AlertService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RegisterDeviceService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RegisterDeviceService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i1.Router }, { type: i1$1.DeviceRegistrationService }, { type: i1$2.AlertService }] }); class GeneralDeviceRegistrationComponent { constructor(tenantUIService, tenantService, registerDeviceService, inventoryService, cd, bsModalRef, gainsightService, featureCacheService) { this.tenantUIService = tenantUIService; this.tenantService = tenantService; this.registerDeviceService = registerDeviceService; this.inventoryService = inventoryService; this.cd = cd; this.bsModalRef = bsModalRef; this.gainsightService = gainsightService; this.featureCacheService = featureCacheService; this.MANAGEMENT = 'management'; this.FILTER = { withTotalPages: true, pageSize: 25 }; this.useEST$ = new BehaviorSubject(false); this.certificateAuthorityFeatureEnabled = this.featureCacheService.getFeatureState('certificate-authority'); this.form = new FormGroup({}); this.model = { devicesToCreate: [{}] }; this.options = { formState: { canLoadTenants: true, useEST: this.useEST$.getValue() } }; this.PRODUCT_EXPERIENCE = { EVENT: 'deviceRegistration', COMPONENT: 'single-general-registration', RESULT: { SUCCESS: 'registrationSuccess', FAILURE: 'registrationFailure' } }; this.success = []; this.failed = []; this.fields = [ { type: 'array', key: 'devicesToCreate', props: { addText: gettext('Add device'), addTextDataCy: 'add-device' }, fieldArray: { fieldGroup: [ { key: 'id', type: 'string', focus: true, props: { placeholder: '0123ab32fcd', label: gettext('Device ID'), required: true }, validators: { unique: { expression: (control) => { const found = control.root.get('devicesToCreate').controls .map(el => el.controls.id) .find(el => el !== control && el.value === control.value); return !found; }, message: () => gettext('Device ID duplicates are not allowed') } } }, { key: 'tenant', type: 'typeahead', expressions: { hide: field => { const formState = field.options?.formState; if (!formState?.canLoadTenants) { field.formControl.setValue(null); } return !formState?.canLoadTenants || false; } }, defaultValue: { id: this.MANAGEMENT }, props: { label: gettext('Add to tenant'), required: true, c8yForOptions: this.canLoadTenants$().pipe(filter(canLoad => canLoad), switchMap(() => this.getTenants$())), container: 'body', displayProperty: 'id', valueProperties: ['id'] }, hooks: { onInit: _field => this.canLoadTenants$().pipe(tap(canLoad => { this.options.formState.canLoadTenants = canLoad; this.cd.detectChanges(); })) } }, { key: 'group', type: 'typeahead', expressions: { 'props.disabled': (field) => { const formState = field.options?.formState; const model = field.model; if (formState?.canLoadTenants) { if (model?.tenant?.id !== this.MANAGEMENT) { field.formControl.setValue(null); } return !(model?.tenant?.id === this.MANAGEMENT); } delete field?.props?.description; return false; } }, props: { disabled: false, label: gettext('Add to group'), description: gettext('You can add device to specific group for management tenant only.'), container: 'body', displayProperty: 'name', valueProperties: ['id'], c8yForOptions: this.getGroups$() }, hooks: { onInit: _field => this.canLoadTenants$().pipe(tap(canLoad => { this.options.formState.canLoadTenants = canLoad; this.cd.detectChanges(); })) } }, { key: 'oneTimePassword', type: 'string', expressions: { hide: field => !field.options?.formState?.useEST }, props: { placeholder: 'TruDN3H45L0', label: gettext('One-time password'), required: true }, hooks: { onInit: _field => this.useEST$.pipe(tap(useEST => { this.options.formState.useEST = useEST; this.cd.detectChanges(); })) } } ] } } ]; this.result = new Promise((resolve, reject) => { this.onSuccessfulClosing = resolve; this.onCancel = reject; }); this.destroy$ = new Subject(); this.lastCreatedDevices = []; this.isLoading$ = this.registerDeviceService.loading$; } ngAfterViewInit() { this.cd.detectChanges(); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } registerDevice(eventObject) { this.create(eventObject); } fixErrors(event, failedRequests) { if (failedRequests && failedRequests.length > 0) { this.options.resetModel({ devicesToCreate: [ ...this.lastCreatedDevices.filter(el => failedRequests.map(data => data.id).includes(el.id)) ] }); this.cd.detectChanges(); } event?.stepper.previous(); } close() { this.bsModalRef.hide(); this.onSuccessfulClosing(); } cancel() { this.bsModalRef.hide(); this.onCancel(); } create(eventObject) { if (this.model?.devicesToCreate?.length > 0) { this.lastCreatedDevices = [...this.model.devicesToCreate]; const dataToSend = this.model.devicesToCreate.map((el) => { const { id, tenant, group, oneTimePassword } = el; let data = { id }; if (tenant?.id) { data = { ...data, tenantId: tenant.id }; } if (group?.id) { data = { ...data, groupId: group.id }; } if (oneTimePassword) { data = { ...data, enrollmentToken: oneTimePassword }; } return data; }); this.registerDeviceService .createMultiple(dataToSend) .pipe(takeUntil(this.destroy$)) .subscribe(requests => { this.success = requests.success ?? []; if (this.success.length > 0) { this.gainsightService.triggerEvent(this.PRODUCT_EXPERIENCE.EVENT, { result: this.PRODUCT_EXPERIENCE.RESULT.SUCCESS, component: this.PRODUCT_EXPERIENCE.COMPONENT }); } this.failed = requests.failed ?? []; if (this.failed.length > 0) { this.gainsightService.triggerEvent(this.PRODUCT_EXPERIENCE.EVENT, { result: this.PRODUCT_EXPERIENCE.RESULT.FAILURE, component: this.PRODUCT_EXPERIENCE.COMPONENT }); } if (eventObject) { eventObject.stepper.next(); } }); } } canLoadTenants$() { return defer(() => from(this.tenantUIService.isManagementTenant())).pipe(shareReplay(1)); } getTenants$() { return defer(() => from(this.tenantService.list(this.FILTER))).pipe(shareReplay(1)); } getGroups$() { return defer(() => from(this.inventoryService.listQuery({ __filter: { __has: 'c8y_IsDeviceGroup' }, __orderby: [{ name: 1 }] }, { ...this.FILTER }))).pipe(shareReplay(1)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: GeneralDeviceRegistrationComponent, deps: [{ token: i1$2.TenantUiService }, { token: i1$1.TenantService }, { token: RegisterDeviceService }, { token: i1$1.InventoryService }, { token: i0.ChangeDetectorRef }, { token: i4.BsModalRef }, { token: i1$2.GainsightService }, { token: i1$2.FeatureCacheService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: GeneralDeviceRegistrationComponent, isStandalone: true, selector: "c8y-general-device-registration", ngImport: i0, template: "<c8y-modal\n [title]=\"'Register devices' | translate\"\n [headerClasses]=\"'dialog-header'\"\n [customFooter]=\"true\"\n>\n <ng-container c8y-modal-title>\n <span [c8yIcon]=\"'c8y-device-connect'\"></span>\n </ng-container>\n <c8y-stepper\n [hideStepProgress]=\"true\"\n linear\n c8y-modal-body\n >\n <cdk-step [stepControl]=\"form\">\n <div class=\"text-center sticky-top bg-component\">\n <p\n class=\"text-medium text-16 separator-bottom p-16\"\n translate\n >\n Register general devices\n </p>\n <label\n class=\"c8y-switch m-24 a-i-center\"\n title=\"{{ 'Create device certificates during device registration' | translate }}\"\n for=\"useEST\"\n *ngIf=\"certificateAuthorityFeatureEnabled | async\"\n >\n <input\n id=\"useEST\"\n name=\"useEST\"\n type=\"checkbox\"\n [ngModel]=\"useEST$.getValue()\"\n (ngModelChange)=\"useEST$.next($event)\"\n />\n <span></span>\n <span class=\"control-label\">\n {{ 'Create device certificates during device registration' | translate }}\n </span>\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{\n 'The device registration process includes creating device certificates, which are issued by the tenant\\'s Certificate Authority (CA).'\n | translate\n }}\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </label>\n </div>\n <div>\n <formly-form\n class=\"formly-group-array-cols d-block p-l-24 p-b-24 min-height-fit p-r-8\"\n [form]=\"form\"\n [fields]=\"fields\"\n [model]=\"model\"\n [options]=\"options\"\n [ngClass]=\"{ 'p-t-24': !(certificateAuthorityFeatureEnabled | async) }\"\n ></formly-form>\n </div>\n <c8y-stepper-buttons\n class=\"sticky-bottom d-block p-t-16 p-b-16 separator-top bg-level-0\"\n (onNext)=\"registerDevice($event)\"\n (onCancel)=\"cancel()\"\n [showButtons]=\"{ cancel: true, next: true }\"\n [disabled]=\"!form?.valid\"\n [pending]=\"isLoading$ | async\"\n ></c8y-stepper-buttons>\n </cdk-step>\n <cdk-step state=\"final\">\n <div class=\"p-24 min-height-fit\">\n <c8y-operation-result\n class=\"lead\"\n type=\"success\"\n *ngIf=\"success.length === 1 && failed.length === 0\"\n text=\"{{ 'Device registered' | translate }}\"\n [size]=\"84\"\n [vertical]=\"true\"\n ></c8y-operation-result>\n <c8y-operation-result\n class=\"lead\"\n type=\"error\"\n *ngIf=\"success.length === 0 && failed.length === 1\"\n text=\"{{ 'Failed to register device' | translate }}\"\n [size]=\"84\"\n [vertical]=\"true\"\n ></c8y-operation-result>\n\n <ng-container *ngIf=\"success.length > 1 || failed.length > 1\">\n <c8y-operation-result\n class=\"lead\"\n type=\"success\"\n *ngIf=\"failed.length === 0\"\n [text]=\"\n '{{ successfulDevicesCount }} devices registered'\n | translate: { successfulDevicesCount: success.length }\n \"\n [size]=\"84\"\n [vertical]=\"true\"\n ></c8y-operation-result>\n <c8y-operation-result\n class=\"lead\"\n type=\"error\"\n *ngIf=\"success.length === 0\"\n [text]=\"\n '{{ failedDevicesCount }} devices failed to register'\n | translate: { failedDevicesCount: failed.length }\n \"\n [size]=\"84\"\n [vertical]=\"true\"\n ></c8y-operation-result>\n </ng-container>\n\n <div\n class=\"p-l-24 p-r-24 text-center\"\n data-cy=\"device-registration-failure-message\"\n *ngIf=\"success.length > 0 && failed.length > 0\"\n >\n <c8y-operation-result\n class=\"lead\"\n type=\"error\"\n text=\"{{ 'Several devices failed to register' | translate }}\"\n [size]=\"84\"\n [vertical]=\"true\"\n ></c8y-operation-result>\n <p\n class=\"p-b-16 text-danger\"\n ngNonBindable\n translate\n [translateParams]=\"{ count: failed.length, total: failed.length + success.length }\"\n >\n Registration failed for {{ count }} devices out of {{ total }}.\n </p>\n </div>\n\n <div\n class=\"m-b-8 p-l-24 p-r-24\"\n data-cy=\"device-registration-success-message\"\n *ngIf=\"success.length > 0\"\n >\n <span\n *ngIf=\"!(useEST$ | async)\"\n translate\n >\n Turn on the registered devices and wait for connections to be established. Once a device\n is connected, its status will change to \"Pending acceptance\". You will need to approve\n it by clicking on the \"Accept\" button.\n </span>\n <span\n *ngIf=\"useEST$ | async\"\n translate\n >\n The successfully enrolled devices can now request signed certificates and use them to\n connect and authenticate to the platform via certificate-based authentication.\n </span>\n </div>\n\n <c8y-list-group class=\"separator-top m-t-16\">\n <c8y-li *ngFor=\"let fail of failed\">\n <c8y-li-icon\n class=\"text-danger\"\n [icon]=\"'ban'\"\n ></c8y-li-icon>\n <p>{{ fail?.id }}</p>\n <small>{{ fail?.message | translate }}</small>\n <c8y-li-collapse>\n <pre><code>{{ fail?.details | json }}</code></pre>\n </c8y-li-collapse>\n </c8y-li>\n\n <c8y-li *ngFor=\"let s of success\">\n <c8y-li-icon\n class=\"text-success\"\n [icon]=\"'check-circle'\"\n ></c8y-li-icon>\n {{ s?.id }}\n </c8y-li>\n </c8y-list-group>\n </div>\n <c8y-stepper-buttons\n class=\"sticky-bottom d-block p-t-16 p-b-16 separator-top bg-level-0\"\n (onCustom)=\"close()\"\n (onBack)=\"fixErrors($event, failed)\"\n [showButtons]=\"{ back: failed.length > 0, custom: true }\"\n [labels]=\"{ back: 'Fix errors', custom: 'Close' }\"\n ></c8y-stepper-buttons>\n </cdk-step>\n </c8y-stepper>\n</c8y-modal>\n", dependencies: [{ kind: "component", type: ModalComponent, selector: "c8y-modal", inputs: ["disabled", "close", "dismiss", "title", "body", "customFooter", "headerClasses", "labels"], outputs: ["onDismiss", "onClose"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: C8yStepper, selector: "c8y-stepper", inputs: ["disableDefaultIcons", "disableProgressButtons", "customClasses", "hideStepProgress", "useStepLabelsAsTitlesOnly"], outputs: ["onStepChange"] }, { kind: "component", type: CdkStep, selector: "cdk-step", inputs: ["stepControl", "label", "errorMessage", "aria-label", "aria-labelledby", "state", "editable", "optional", "completed", "hasError"], outputs: ["interacted"], exportAs: ["cdkStep"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i6.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i6.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i6.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "ngmodule", type: FormlyModule }, { kind: "component", type: i5.FormlyForm, selector: "formly-form", inputs: ["form", "model", "fields", "options"], outputs: ["modelChange"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: C8yStepperButtons, selector: "c8y-stepper-buttons", inputs: ["labels", "pending", "disabled", "showButtons"], outputs: ["onCancel", "onNext", "onBack", "onCustom"] }, { kind: "component", type: OperationResultComponent, selector: "c8y-operation-result", inputs: ["text", "vertical", "size", "type"] }, { kind: "component", type: ListGroupComponent, selector: "c8y-list-group" }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: ListItemIconComponent, selector: "c8y-list-item-icon, c8y-li-icon", inputs: ["icon", "status"] }, { kind: "component", type: ListItemCollapseComponent, selector: "c8y-list-item-collapse, c8y-li-collapse", inputs: ["collapseWay"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: JsonPipe, name: "json" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } __decorate([ memoize(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Observable) ], GeneralDeviceRegistrationComponent.prototype, "canLoadTenants$", null); __decorate([ memoize(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Observable) ], GeneralDeviceRegistrationComponent.prototype, "getTenants$", null); __decorate([ memoize(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Observable) ], GeneralDeviceRegistrationComponent.prototype, "getGroups$", null); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: GeneralDeviceRegistrationComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-general-device-registration', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ ModalComponent, IconDirective, C8yStepper, CdkStep, C8yTranslateDirective, NgIf, FormsModule, PopoverDirective, FormlyModule, NgClass, C8yStepperButtons, OperationResultComponent, ListGroupComponent, NgFor, ListItemComponent, ListItemIconComponent, ListItemCollapseComponent, C8yTranslatePipe, AsyncPipe, JsonPipe ], template: "<c8y-modal\n [title]=\"'Register devices' | translate\"\n [headerClasses]=\"'dialog-header'\"\n [customFooter]=\"true\"\n>\n <ng-container c8y-modal-title>\n <span [c8yIcon]=\"'c8y-device-connect'\"></span>\n </ng-container>\n <c8y-stepper\n [hideStepProgress]=\"true\"\n linear\n c8y-modal-body\n >\n <cdk-step [stepControl]=\"form\">\n <div class=\"text-center sticky-top bg-component\">\n <p\n class=\"text-medium text-16 separator-bottom p-16\"\n translate\n >\n Register general devices\n </p>\n <label\n class=\"c8y-switch m-24 a-i-center\"\n title=\"{{ 'Create device certificates during device registration' | translate }}\"\n for=\"useEST\"\n *ngIf=\"certificateAuthorityFeatureEnabled | async\"\n >\n <input\n id=\"useEST\"\n name=\"useEST\"\n type=\"checkbox\"\n [ngModel]=\"useEST$.getValue()\"\n (ngModelChange)=\"useEST$.next($event)\"\n />\n <span></span>\n <span class=\"control-label\">\n {{ 'Create device certificates during device registration' | translate }}\n </span>\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{\n 'The device registration process includes creating device certificates, which are issued by the tenant\\'s Certificate Authority (CA).'\n | translate\n }}\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </label>\n </div>\n <div>\n <formly-form\n class=\"formly-group-array-cols d-block p-l-24 p-b-24 min-height-fit p-r-8\"\n [form]=\"form\"\n [fields]=\"fields\"\n [model]=\"model\"\n [options]=\"options\"\n [ngClass]=\"{ 'p-t-24': !(certificateAuthorityFeatureEnabled | async) }\"\n ></formly-form>\n </div>\n <c8y-stepper-buttons\n class=\"sticky-bottom d-block p-t-16 p-b-16 separator-top bg-level-0\"\n (onNext)=\"registerDevice($event)\"\n (onCancel)=\"cancel()\"\n [showButtons]=\"{ cancel: true, next: true }\"\n [disabled]=\"!form?.valid\"\n [pending]=\"isLoading$ | async\"\n ></c8y-stepper-buttons>\n </cdk-step>\n <cdk-step state=\"final\">\n <div class=\"p-24 min-height-fit\">\n <c8y-operation-result\n class=\"lead\"\n type=\"success\"\n *ngIf=\"success.length === 1 && failed.length === 0\"\n text=\"{{ 'Device registered' | translate }}\"\n [size]=\"84\"\n [vertical]=\"true\"\n ></c8y-operation-result>\n <c8y-operation-result\n class=\"lead\"\n type=\"error\"\n *ngIf=\"success.length === 0 && failed.length === 1\"\n text=\"{{ 'Failed to register device' | translate }}\"\n [size]=\"84\"\n [vertical]=\"true\"\n ></c8y-operation-result>\n\n <ng-container *ngIf=\"success.length > 1 || failed.length > 1\">\n <c8y-operation-result\n class=\"lead\"\n type=\"success\"\n *ngIf=\"failed.length === 0\"\n [text]=\"\n '{{ successfulDevicesCount }} devices registered'\n | translate: { successfulDevicesCount: success.length }\n \"\n [size]=\"84\"\n [vertical]=\"true\"\n ></c8y-operation-result>\n <c8y-operation-result\n class=\"lead\"\n type=\"error\"\n *ngIf=\"success.length === 0\"\n [text]=\"\n '{{ failedDevicesCount }} devices failed to register'\n | translate: { failedDevicesCount: failed.length }\n \"\n [size]=\"84\"\n [vertical]=\"true\"\n ></c8y-operation-result>\n </ng-container>\n\n <div\n class=\"p-l-24 p-r-24 text-center\"\n data-cy=\"device-registration-failure-message\"\n *ngIf=\"success.length > 0 && failed.length > 0\"\n >\n <c8y-operation-result\n class=\"lead\"\n type=\"error\"\n text=\"{{ 'Several devices failed to register' | translate }}\"\n [size]=\"84\"\n [vertical]=\"true\"\n ></c8y-operation-result>\n <p\n class=\"p-b-16 text-danger\"\n ngNonBindable\n translate\n [translateParams]=\"{ count: failed.length, total: failed.length + success.length }\"\n >\n Registration failed for {{ count }} devices out of {{ total }}.\n </p>\n </div>\n\n <div\n class=\"m-b-8 p-l-24 p-r-24\"\n data-cy=\"device-registration-success-message\"\n *ngIf=\"success.length > 0\"\n >\n <span\n *ngIf=\"!(useEST$ | async)\"\n translate\n >\n Turn on the registered devices and wait for connections to be established. Once a device\n is connected, its status will change to \"Pending acceptance\". You will need to approve\n it by clicking on the \"Accept\" button.\n </span>\n <span\n *ngIf=\"useEST$ | async\"\n translate\n >\n The successfully enrolled devices can now request signed certificates and use them to\n connect and authenticate to the platform via certificate-based authentication.\n </span>\n </div>\n\n <c8y-list-group class=\"separator-top m-t-16\">\n <c8y-li *ngFor=\"let fail of failed\">\n <c8y-li-icon\n class=\"text-danger\"\n [icon]=\"'ban'\"\n ></c8y-li-icon>\n <p>{{ fail?.id }}</p>\n <small>{{ fail?.message | translate }}</small>\n <c8y-li-collapse>\n <pre><code>{{ fail?.details | json }}</code></pre>\n </c8y-li-collapse>\n </c8y-li>\n\n <c8y-li *ngFor=\"let s of success\">\n <c8y-li-icon\n class=\"text-success\"\n [icon]=\"'check-circle'\"\n ></c8y-li-icon>\n {{ s?.id }}\n </c8y-li>\n </c8y-list-group>\n </div>\n <c8y-stepper-buttons\n class=\"sticky-bottom d-block p-t-16 p-b-16 separator-top bg-level-0\"\n (onCustom)=\"close()\"\n (onBack)=\"fixErrors($event, failed)\"\n [showButtons]=\"{ back: failed.length > 0, custom: true }\"\n [labels]=\"{ back: 'Fix errors', custom: 'Close' }\"\n ></c8y-stepper-buttons>\n </cdk-step>\n </c8y-stepper>\n</c8y-modal>\n" }] }], ctorParameters: () => [{ type: i1$2.TenantUiService }, { type: i1$1.TenantService }, { type: RegisterDeviceService }, { type: i1$1.InventoryService }, { type: i0.ChangeDetectorRef }, { type: i4.BsModalRef }, { type: i1$2.GainsightService }, { type: i1$2.FeatureCacheService }], propDecorators: { canLoadTenants$: [], getTenants$: [], getGroups$: [] } }); class GeneralDeviceRegistrationService { constructor() { this.modalService = inject(BsModalService); } async open(initialState) { const modalRef = this.modalService.show(GeneralDeviceRegistrationComponent, { class: 'modal-lg', ariaDescribedby: 'modal-body', ariaLabelledBy: 'modal-title', ignoreBackdropClick: true, initialState: { ...initialState } }); return await modalRef.content.result; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: GeneralDeviceRegistrationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: GeneralDeviceRegistrationService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: GeneralDeviceRegistrationService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class GeneralDeviceRegistrationButtonComponent { constructor(registrationService) { this.registrationService = registrationService; } async open() { try { await this.registrationService.open(); } catch { // modal was closed } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: GeneralDeviceRegistrationButtonComponent, deps: [{ token: GeneralDeviceRegistrationService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: GeneralDeviceRegistrationButtonComponent, isStandalone: true, selector: "c8y-general-device-registration-button", ngImport: i0, template: "<button title=\"{{ 'General' | translate }}\" type=\"button\" (click)=\"open()\">\n <i c8yIcon=\"c8y-device-connect\"></i>\n {{ 'General' | translate }}\n</button>\n", dependencies: [{ kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: GeneralDeviceRegistrationButtonComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-general-device-registration-button', imports: [IconDirective, C8yTranslatePipe], template: "<button title=\"{{ 'General' | translate }}\" type=\"button\" (click)=\"open()\">\n <i c8yIcon=\"c8y-device-connect\"></i>\n {{ 'General' | translate }}\n</button>\n" }] }], ctorParameters: () => [{ type: GeneralDeviceRegistrationService }] }); /** * A hook to use for Multi Provider extension. * @deprecated Consider using the `hookDeviceRegistration` function instead. */ const HOOK_DEVICE_REGISTRATION = new InjectionToken('HOOK_DEVICE_REGISTRATION'); /** * You can either provide a single `RegisterDeviceExtension` as parameter: * ```typescript * hookDeviceRegistration(...) * ``` * * Or an array to directly register multiple: * ```typescript * hookDeviceRegistration([...]) * ``` * * Or you provide an Service that implements `ExtensionFactory<RegisterDeviceExtension>` * ```typescript * export class MyDeviceRegistrationFactory implements ExtensionFactory<RegisterDeviceExtension> {...} * ... * hookDeviceRegistration(MyDeviceRegistrationFactory) * ``` * A typed alternative to `HOOK_DEVICE_REGISTRATION`. * @param registration The `RegisterDeviceExtension`'s or `ExtensionFactory` to be provided. * @returns An `Provider` to be provided in your module. */ function hookDeviceRegistration(registration, options) { return hookGeneric(registration, HOOK_DEVICE_REGISTRATION, options); } /** * A service which defines device registration options. */ class RegisterDeviceExtensionService extends ExtensionPointWithoutStateForPlugins { constructor(rootInjector, router, plugins) { super(rootInjector, plugins); this.router = router; this.items$ = this.setupItemsObservable(); } setupItemsObservable() { return fromTriggerOnce(this.router, this.refresh$, [ () => flatten(this.injectors.map(injector => injector.get(HOOK_DEVICE_REGISTRATION, [], { self: true }))), () => this.factories ]).pipe(startWith([]), shareReplay(1)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RegisterDeviceExtensionService, deps: [{ token: i0.Injector }, { token: i1.Router }, { token: i1$2.PluginsResolveService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RegisterDeviceExtensionService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RegisterDeviceExtensionService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i0.Injector }, { type: i1.Router }, { type: i1$2.PluginsResolveService }] }); class RegisterDeviceDropdownComponent { constructor(registerDeviceExtensionService, registerDeviceService) { this.registerDeviceExtensionService = registerDeviceExtensionService; this.registerDeviceService = registerDeviceService; this.single$ = this.registerDeviceExtensionService.items$.pipe(map(items => items.filter(item => item.category === 'single').sort((a, b) => b.priority - a.priority))); this.bulk$ = this.registerDeviceExtensionService.items$.pipe(map(items => items.filter(item => item.category === 'bulk').sort((a, b) => b.priority - a.priority))); this.limit$ = this.registerDeviceService.limit$.pipe(map(limit => limit.isReached)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RegisterDeviceDropdownComponent, deps: [{ token: RegisterDeviceExtensionService }, { token: RegisterDeviceService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: RegisterDeviceDropdownComponent, isStandalone: true, selector: "c8y-register-device-dropdown", ngImport: i0, template: "<div class=\"dropdown\" dropdown>\n <button\n *ngIf=\"!(limit$ | async); else disable\"\n title=\"{{ 'Register device' | translate }}\"\n type=\"button\"\n class=\"dropdown-toggle c8y-dropdown d-flex a-i-center\"\n dropdownToggle\n aria-haspopup=\"true\"\n data-cy=\"register-device--dropdown-button\"\n >\n <span class=\"text-truncate\" translate>Register device</span>\n <i [c8yIcon]=\"'caret-down'\" class=\"m-l-4 text-primary\"></i>\n </button>\n <ng-template #disable>\n <button\n title=\"{{ 'Device registration disabled' | translate }}\"\n type=\"button\"\n class=\"btn btn-clean d-flex p-l-8\"\n disabled\n >\n <span class=\"text-truncate\" translate>Register device</span>\n <i [c8yIcon]=\"'caret-down'\"></i>\n </button>\n </ng-template>\n\n <!-- dropdown for normal screen sizes -->\n <ul class=\"dropdown-menu dropdown-menu-right hidden-xs\" data-cy=\"register-device--dropdown\" *dropdownMenu>\n <ng-container *ngTemplateOutlet=\"dropdown\"></ng-container>\n </ul>\n\n <!-- fake dropdown for mobile screen sizes. *dropdownMenu is missing by design! -->\n <ul class=\"dropdown-menu dropdown-menu visible-xs\">\n <ng-container *ngTemplateOutlet=\"dropdown\"></ng-container>\n </ul>\n\n <ng-template #dropdown>\n <ng-container *ngIf=\"single$ | async as single\">\n <li class=\"dropdown-header\" *ngIf=\"single.length > 0\" translate data-cy=\"single-group\">Single registration</li>\n <li *ngFor=\"let item of single\">\n <ng-container *c8yOutlet=\"item.template\"></ng-container>\n </li>\n </ng-container>\n <ng-container *ngIf=\"bulk$ | async as bulk\">\n <li class=\"dropdown-header\" *ngIf=\"bulk.length > 0\" translate data-cy=\"bulk-group\">Bulk registration</li>\n <li *ngFor=\"let item of bulk\">\n <ng-container *c8yOutlet=\"item.template\"></ng-container>\n </li>\n </ng-container>\n </ng-template>\n</div>\n", dependencies: [{ kind: "directive", type: BsDropdownDirective, selector: "[bsDropdown], [dropdown]", inputs: ["placement", "triggers", "container", "dropup", "autoClose", "isAnimated", "insideClick", "isDisabled", "isOpen"], outputs: ["isOpenChange", "onShown", "onHidden"], exportAs: ["bs-dropdown"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: BsDropdownToggleDirective, selector: "[bsDropdownToggle],[dropdownToggle]", exportAs: ["bs-dropdown-toggle"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: BsDropdownMenuDirective, selector: "[bsDropdownMenu],[dropdownMenu]", exportAs: ["bs-dropdown-menu"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive",