@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
599 lines (593 loc) • 170 kB
JavaScript
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",