UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

460 lines (454 loc) 43.8 kB
import * as i0 from '@angular/core'; import { Injectable, Input, ViewChild, HostBinding, Optional, Component } from '@angular/core'; import { gettext } from '@c8y/ngx-components/gettext'; import * as i3 from '@c8y/ngx-components'; import { toObservable, Status, CoreModule, C8yStepper, UserPreferencesConfigurationStrategy, DATA_GRID_CONFIGURATION_STRATEGY, DATA_GRID_CONFIGURATION_CONTEXT_PROVIDER } from '@c8y/ngx-components'; import * as i2 from '@c8y/ngx-components/device-grid'; import { DeviceGridModule, DeviceGridService } from '@c8y/ngx-components/device-grid'; import { DeviceSelectorModule } from '@c8y/ngx-components/operations/device-selector'; import * as i7 from 'ngx-bootstrap/collapse'; import { CollapseModule } from 'ngx-bootstrap/collapse'; import * as i8 from 'ngx-bootstrap/popover'; import { PopoverModule } from 'ngx-bootstrap/popover'; import * as i6 from 'ngx-bootstrap/tooltip'; import { TooltipModule } from 'ngx-bootstrap/tooltip'; import { shareReplay, distinctUntilChanged, tap, switchMap, catchError, map, scan, withLatestFrom, concatMap, share } from 'rxjs/operators'; import * as i1 from '@c8y/client'; import { QueriesUtil, AuditRecordType } from '@c8y/client'; import { pick, cloneDeep } from 'lodash-es'; import { BehaviorSubject, Subject, of, combineLatest, forkJoin, from, pipe } from 'rxjs'; import * as i4 from '@angular/common'; import * as i5 from '@angular/cdk/stepper'; const REPLACE_DEVICE_STEP_STATES = [ 'Pending', 'Executing', 'Skipped', 'Failed', 'Successful' ]; function isValidReplaceDeviceStepState(state) { return REPLACE_DEVICE_STEP_STATES.includes(state); } const REPLACE_DEVICE_STEP_STATE = { PENDING: gettext('Pending'), EXECUTING: gettext('Executing'), SKIPPED: gettext('Skipped'), FAILED: gettext('Failed'), SUCCESSFUL: gettext('Successful') }; const REPLACE_DEVICE_STEP_STATES_MAP = { Pending: { icon: 'clock-o', styleClass: 'text-info' }, Executing: { icon: 'refresh', styleClass: 'text-info' }, Skipped: { icon: 'skip', styleClass: 'text-info' }, Failed: { icon: 'exclamation-circle', styleClass: 'text-danger' }, Successful: { icon: 'check-circle', styleClass: 'text-success' } }; class ReplaceDeviceWizardService { static { this.NON_REENTRANT_STATES = [ 'Executing', 'Successful' ]; } get deviceToReplace$() { return this.deviceToReplaceObs$; } get replacementDeviceId$() { return this.replacementDeviceIdObs$; } constructor(inventory, identity, audit, event, user, appState, alert) { this.inventory = inventory; this.identity = identity; this.audit = audit; this.event = event; this.user = user; this.appState = appState; this.alert = alert; this.steps = []; this.deviceToReplaceSubject$ = new BehaviorSubject(null); this.deviceToReplaceObs$ = this.deviceToReplaceSubject$ .asObservable() .pipe(shareReplay()); this.replacementDeviceIdSubject$ = new Subject(); this.replacementDeviceIdObs$ = this.replacementDeviceIdSubject$ .asObservable() .pipe(shareReplay()); this.checkExternalId$ = new Subject(); this.externalIdsLoadingSubject$ = new BehaviorSubject(false); this.triggerDeviceReplacementSubject$ = new Subject(); this.deviceReplacementInProgressSubject$ = new Subject(); this.queriesUtil = new QueriesUtil(); this.externalIdsLoading$ = this.externalIdsLoadingSubject$.asObservable().pipe(shareReplay()); this.externalIds$ = this.replacementDeviceIdSubject$.pipe(distinctUntilChanged(), tap(() => this.externalIdsLoadingSubject$.next(true)), switchMap(deviceId => this.identity.list(deviceId)), tap(() => this.externalIdsLoadingSubject$.next(false)), tap(() => this.checkExternalId(null, false)), catchError(err => { this.alert.addServerFailure(err); return of(null); }), shareReplay()); this.externalIdsWithSelection$ = combineLatest([ this.externalIds$.pipe(map(result => result?.data), map(externalIds => externalIds?.map(id => ({ id, selected: true })))), this.checkExternalId$ ]).pipe(scan((acc, val) => { const [selectedIds, lastAction] = acc; const [_, checkAction] = val; if (!(lastAction?.checked === checkAction?.checked && this.areExtIdsEqual(lastAction?.id, checkAction?.id))) { selectedIds.forEach(id => (id.selected = this.areExtIdsEqual(id.id, checkAction.id) ? checkAction.checked : id.selected)); } return val; }), map(([externalIds]) => externalIds), shareReplay()); this.selectedExternalIds$ = this.externalIdsWithSelection$.pipe(map(ids => ids.filter(id => id.selected).map(id => id.id)), shareReplay()); this.defineSteps(); const toContext = ([_, deviceToReplace, replacementDeviceId, newExternalIds]) => ({ deviceToReplace, replacementDeviceId, newExternalIds }); this.deviceReplaced$ = this.triggerDeviceReplacementSubject$.pipe(tap(() => this.deviceReplacementInProgressSubject$.next(true)), withLatestFrom(this.deviceToReplace$, this.replacementDeviceId$, this.selectedExternalIds$), map(toContext), concatMap(context => this.steps .map(step => this.executeStep(step)) .reduce((ctx, step) => ctx.pipe(step), of(context))), tap(() => this.deviceReplacementInProgressSubject$.next(false)), map(() => !this.steps.some(step => step.state === 'Failed')), share()); this.deviceReplacementInProgress$ = this.deviceReplacementInProgressSubject$ .asObservable() .pipe(shareReplay()); } forDevice(deviceToReplace) { this.deviceToReplaceSubject$.next(deviceToReplace); } changeReplacementDeviceId(replacementDeviceId) { this.replacementDeviceIdSubject$.next(replacementDeviceId); } checkExternalId(id, checked) { this.checkExternalId$.next({ id, checked }); } replaceDevice() { this.triggerDeviceReplacementSubject$.next(); } retryStep(step) { this.steps.forEach(s => { if ((s.label === step?.label || !step) && !ReplaceDeviceWizardService.NON_REENTRANT_STATES.includes(s.state)) { s.skip = false; s.seed = s.context; s.state = 'Pending'; delete s.error; } else { s.skip = true; } }); this.replaceDevice(); } defineSteps() { this.steps = [ { label: gettext('Gather required data'), overrideContext: true, action: (context) => { const { deviceToReplace, replacementDeviceId, newExternalIds } = context; if (deviceToReplace.id === replacementDeviceId) { throw new Error(gettext('The device to replace and the replacement device cannot be one and the same device.')); } return forkJoin([ from(this.inventory.list({ query: this.queriesUtil.buildQuery({ owner: deviceToReplace.owner }) })).pipe(map(result => result?.data?.length === 1)), from(this.inventory.detail(replacementDeviceId)).pipe(map(result => result?.data)), from(this.identity.list(deviceToReplace.id)).pipe(map(result => result?.data)) ]).pipe(map(([deleteReplacedDeviceOwner, replacementDevice, oldExternalIds]) => ({ deviceToReplace, replacementDevice, newExternalIds, oldExternalIds, deleteReplacedDeviceOwner, time: new Date().toISOString() }))); } }, { label: gettext('Delete external IDs of replacement device'), action: (context) => { const { newExternalIds } = context; return forkJoin(newExternalIds.map(id => this.identity.delete(id))); } }, { label: gettext('Create new external IDs for the original device'), action: (context) => { const { newExternalIds, deviceToReplace } = context; return forkJoin(newExternalIds .map(extId => ({ ...pick(extId, ['type', 'externalId']), managedObject: { ...pick(deviceToReplace, ['id']) } })) .map(id => this.identity.create(id))); } }, { label: gettext('Delete old external IDs of original device'), action: (context) => { const { oldExternalIds } = context; return oldExternalIds?.length ? forkJoin(oldExternalIds.map(id => this.identity.delete(id))) : of(REPLACE_DEVICE_STEP_STATE.SKIPPED); }, info: { getMessage: (_, step) => step.state === 'Skipped' ? gettext('No existing external IDs were determined.') : undefined } }, { label: gettext('Change owner of original device`owner may be a human or system user`'), action: (context) => { const { deviceToReplace, replacementDevice, oldExternalIds, time } = context; return this.inventory.update({ id: deviceToReplace.id, owner: replacementDevice.owner, c8y_LastReplacement: { time, user: this.appState.currentUser.value.id, previousExternalIds: oldExternalIds.map(id => pick(id, ['externalId', 'type'])) } }); } }, { label: gettext('Delete old owner of original device'), action: (context) => { const { deleteReplacedDeviceOwner, deviceToReplace } = context; return deleteReplacedDeviceOwner ? this.user.delete(deviceToReplace.owner) : of(REPLACE_DEVICE_STEP_STATE.SKIPPED); }, info: { getMessage: (_, step) => step.state === 'Skipped' ? gettext('User was not deleted because it is assigned as an owner of other devices.') : undefined } }, { label: gettext('Delete replacement device'), action: (context) => { const { replacementDevice } = context; return this.inventory.delete(replacementDevice.id); } }, { label: gettext('Create event'), action: (context) => { const { deviceToReplace, oldExternalIds, newExternalIds, time } = context; return this.event.create({ source: { id: deviceToReplace.id }, text: `Device with external ID(s) ${this.extIdsToString(oldExternalIds)} was replaced by device with external ID(s) ${this.extIdsToString(newExternalIds)}`, time, type: 'c8y_DeviceReplaced' }); } }, { label: gettext('Create audit log'), action: (context) => { const { deviceToReplace, oldExternalIds, newExternalIds, time } = context; return this.audit.create({ activity: gettext('Device replaced'), source: { id: deviceToReplace.id }, text: `Device with external ID(s) ${this.extIdsToString(oldExternalIds)} was replaced by device with external ID(s) ${this.extIdsToString(newExternalIds)}`, time, type: AuditRecordType.INVENTORY, user: this.appState.currentUser.value.id }); } } ]; } executeStep(step) { return pipe(tap((ctx) => (step.state = step?.skip || ctx.skip ? step.state : REPLACE_DEVICE_STEP_STATE.EXECUTING)), concatMap((ctx) => { if (!step.context && !ctx.skip) { step.context = cloneDeep(ctx); } const context = cloneDeep(step.seed ?? ctx); return step?.skip || ctx.skip ? of(context) : toObservable(this.unwrapStepAction(context, step.action)).pipe(tap(result => (step.state = isValidReplaceDeviceStepState(result) ? result : REPLACE_DEVICE_STEP_STATE.SUCCESSFUL)), catchError(err => { step.state = REPLACE_DEVICE_STEP_STATE.FAILED; step.error = this.toError(err); context.skip = step.overrideContext; return of(context); }), tap(() => { if (typeof step.info?.getMessage === 'function') { step.info.msg = step.info.getMessage(context, step); } }), map(result => (step.overrideContext ? result : context))); })); } unwrapStepAction(context, action) { try { return action(context); } catch (err) { // bubble up any runtime errors return of({}).pipe(tap(() => { throw err; })); } } areExtIdsEqual(idA, idB) { return idA?.type === idB?.type && idA?.externalId === idB?.externalId; } extIdsToString(extnernalIds) { return extnernalIds?.map(id => `${id.externalId} [${id.type}]`).join(', '); } toError(err) { const { data, res, message } = err; let text = data?.message || message; let detailedData; if (data) { if (typeof data === 'object') { detailedData = data.exceptionMessage; } else if (typeof data === 'string') { detailedData = data; } } const hasRelevantMessage = !!(text || detailedData); if (!text) { text = gettext('A server error occurred.'); } if (res && !hasRelevantMessage) { detailedData = pick(res, ['status', 'statusText']); } return { text, detailedData }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ReplaceDeviceWizardService, deps: [{ token: i1.InventoryService }, { token: i1.IdentityService }, { token: i1.AuditService }, { token: i1.EventService }, { token: i1.UserService }, { token: i3.AppStateService }, { token: i3.AlertService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ReplaceDeviceWizardService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ReplaceDeviceWizardService, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: i1.InventoryService }, { type: i1.IdentityService }, { type: i1.AuditService }, { type: i1.EventService }, { type: i1.UserService }, { type: i3.AppStateService }, { type: i3.AlertService }] }); class ReplaceDeviceWizardComponent { static { this.DEFAULT_VISIBLE_COLUMNS = [ 'status', 'name', 'serialNumber', 'registrationDate', 'systemId', 'imei' ]; } set deviceToReplace(deviceToReplace) { this.service.forDevice(deviceToReplace); } constructor(service, deviceGridService, modal, drawerRef) { this.service = service; this.deviceGridService = deviceGridService; this.modal = modal; this.drawerRef = drawerRef; this.REPLACE_DEVICE_STEP_STATES_MAP = REPLACE_DEVICE_STEP_STATES_MAP; this.classes = 'd-contents'; this.baseQuery$ = this.service.deviceToReplace$.pipe(map(device => ({ __not: { id: { __eq: device?.id } } }))); this.columns = this.deviceGridService.getDefaultColumns().map(column => { column.visible = ReplaceDeviceWizardComponent.DEFAULT_VISIBLE_COLUMNS.includes(column.name); return column; }); } getGridConfigContext() { return { key: 'replace-device-wizard-grid', configFilter: { filter: false } }; } async replace() { try { await this.modal.confirm(gettext('Replace device'), gettext('You are about to replace a device. When the process is finished, the replacement device and its data will be deleted. Do you want to proceed?'), Status.DANGER, { ok: gettext('Replace'), cancel: gettext('Cancel') }); this.service.replaceDevice(); this.continue(); } catch (ex) { // Intentionally empty } } continue() { this.stepper.selected.completed = true; this.stepper.next(); } cancel() { this.close(); } close() { if (this.drawerRef) { this.drawerRef.close(); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ReplaceDeviceWizardComponent, deps: [{ token: ReplaceDeviceWizardService }, { token: i2.DeviceGridService }, { token: i3.ModalService }, { token: i3.BottomDrawerRef, optional: true }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: ReplaceDeviceWizardComponent, isStandalone: true, selector: "c8y-replace-device-wizard", inputs: { deviceToReplace: "deviceToReplace" }, host: { properties: { "class": "this.classes" } }, providers: [ DeviceGridService, ReplaceDeviceWizardService, { provide: DATA_GRID_CONFIGURATION_STRATEGY, useClass: UserPreferencesConfigurationStrategy }, { provide: DATA_GRID_CONFIGURATION_CONTEXT_PROVIDER, useExisting: ReplaceDeviceWizardComponent } ], viewQueries: [{ propertyName: "stepper", first: true, predicate: C8yStepper, descendants: true, static: true }], ngImport: i0, template: "<c8y-stepper\n class=\"d-contents c8y-stepper--no-btns\"\n [disableDefaultIcons]=\"{ edit: true, done: false }\"\n id=\"modal-body\"\n [customClasses]=\"[\n 'col-xs-10',\n 'col-xs-offset-1',\n 'col-sm-8',\n 'col-sm-offset-2',\n 'm-t-24',\n 'm-b-40',\n 'p-0',\n 'flex-no-shrink'\n ]\"\n linear\n>\n <cdk-step [label]=\"'Replacement device' | translate\">\n <div class=\"p-16 p-t-0 flex-no-shrink separator-bottom col-xs-12\">\n <div class=\"row\">\n <div class=\"col-md-6 col-md-offset-3 col-lg-4 col-lg-offset-4\">\n <div class=\"h4 text-center text-medium\">\n {{ 'Replacement device' | translate }}\n </div>\n </div>\n </div>\n </div>\n <c8y-device-grid\n class=\"flex-grow col-xs-12 no-gutter\"\n [title]=\"'Select replacement device' | translate\"\n [infiniteScroll]=\"'auto'\"\n [actionControls]=\"[]\"\n [columns]=\"columns\"\n [selectable]=\"true\"\n [singleSelection]=\"true\"\n [baseQuery]=\"baseQuery$ | async\"\n (itemsSelect)=\"replacementDeviceId = $event[0]\"\n ></c8y-device-grid>\n <c8y-stepper-buttons\n class=\"d-block card-footer p-24 separator\"\n [disabled]=\"!replacementDeviceId\"\n (onCancel)=\"cancel()\"\n (onNext)=\"service.changeReplacementDeviceId(replacementDeviceId); continue()\"\n ></c8y-stepper-buttons>\n </cdk-step>\n\n <cdk-step [label]=\"'Select external IDs' | translate\">\n <div class=\"p-16 p-t-0 flex-no-shrink col-xs-12\">\n <div class=\"row\">\n <div class=\"col-md-6 col-md-offset-3 col-lg-4 col-lg-offset-4\">\n <div class=\"h4 text-center text-medium\">\n {{ 'Select external IDs' | translate }}\n </div>\n </div>\n </div>\n </div>\n <c8y-li class=\"page-sticky-header hidden-sm hidden-xs\">\n <c8y-li-icon><i class=\"p-l-24\"></i></c8y-li-icon>\n <c8y-li-body class=\"content-flex-40\">\n <div class=\"col-4\">\n <span\n class=\"text-medium text-truncate\"\n title=\" {{ 'Type' | translate }} \"\n >\n {{ 'Type' | translate }}\n </span>\n </div>\n <div class=\"col-6\">\n <span\n class=\"text-medium text-truncate\"\n title=\"{{ 'External ID' | translate }}\"\n >\n {{ 'External ID' | translate }}\n </span>\n </div>\n </c8y-li-body>\n </c8y-li>\n <div\n class=\"col-xs-12 flex-grow no-gutter\"\n *ngIf=\"!(service.externalIdsLoading$ | async); else loading\"\n >\n <div\n class=\"card-inner-scroll fit-h\"\n *ngIf=\"(service.externalIdsWithSelection$ | async)?.length > 0; else noIds\"\n >\n <div class=\"card-block p-t-0 p-b-0\">\n <c8y-list-group>\n <c8y-li *ngFor=\"let extId of service.externalIdsWithSelection$ | async\">\n <c8y-li-checkbox\n [selected]=\"extId.selected\"\n (onSelect)=\"service.checkExternalId(extId.id, $event)\"\n ></c8y-li-checkbox>\n <c8y-li-body class=\"content-flex-40\">\n <div class=\"col-4 m-b-xs-8\">\n <div\n class=\"text-truncate\"\n title=\"{{ 'Type' | translate }}\"\n >\n <span\n class=\"text-label-small m-r-8 visible-xs visible-sm\"\n translate\n >\n Type\n </span>\n {{ extId.id.type }}\n </div>\n </div>\n <div class=\"col-6 m-b-xs-8\">\n <div\n class=\"text-truncate\"\n title=\"{{ 'External ID' | translate }}\"\n >\n <span\n class=\"text-label-small m-r-8 visible-xs visible-sm\"\n translate\n >\n External ID\n </span>\n {{ extId.id.externalId }}\n </div>\n </div>\n </c8y-li-body>\n </c8y-li>\n </c8y-list-group>\n </div>\n </div>\n <ng-template #noIds>\n <c8y-ui-empty-state\n [icon]=\"'barcode'\"\n [title]=\"'No external IDs assigned.'\"\n [subtitle]=\"\n 'The selected replacement device does not have external IDs assigned. Go back to &quot;Replacement device&quot; and select a different device.'\n | translate\n \"\n *ngIf=\"true\"\n ></c8y-ui-empty-state>\n </ng-template>\n </div>\n <ng-template #loading>\n <div class=\"d-flex d-col a-i-center j-c-center fit-h\">\n <c8y-loading></c8y-loading>\n </div>\n </ng-template>\n <c8y-stepper-buttons\n class=\"d-block card-footer p-24 separator\"\n [disabled]=\"\n (service.externalIdsLoading$ | async) || !(service.selectedExternalIds$ | async)?.length\n \"\n (onCancel)=\"cancel()\"\n (onNext)=\"replace()\"\n [labels]=\"{ next: ('Replace' | translate) }\"\n ></c8y-stepper-buttons>\n </cdk-step>\n\n <cdk-step [label]=\"'Replace' | translate\">\n <div class=\"p-16 p-t-0 flex-no-shrink col-xs-12 separator-bottom\">\n <div class=\"row\">\n <div class=\"col-md-6 col-md-offset-3 col-lg-4 col-lg-offset-4\">\n <div class=\"h4 text-center text-medium\">\n {{ 'Replace' | translate }}\n </div>\n </div>\n </div>\n </div>\n <div class=\"d-flex d-col a-i-center j-c-center inner-scroll\">\n <c8y-list-group class=\"col-sm-8 col-md-6\">\n <c8y-li\n *ngFor=\"let step of service.steps\"\n [attr.data-cy]=\"'c8y-replace-device-wizard--step-' + step.label\"\n >\n <c8y-li-icon>\n <i\n [c8yIcon]=\"REPLACE_DEVICE_STEP_STATES_MAP[step.state || 'Pending']?.icon\"\n [tooltip]=\"(step.state || 'Pending').toString() | translate\"\n [ngClass]=\"REPLACE_DEVICE_STEP_STATES_MAP[step.state || 'Pending']?.styleClass\"\n ></i>\n </c8y-li-icon>\n <c8y-li-body>\n <span [ngClass]=\"{ 'text-bold': !!step.error }\">{{ step.label | translate }}</span>\n <button\n class=\"btn-help btn-help--sm pull-right\"\n [attr.aria-label]=\"'Help' | translate\"\n [attr.aria-label]=\"step.info.msg | translate\"\n [popover]=\"step.info.msg | translate\"\n placement=\"right\"\n triggers=\"focus\"\n type=\"button\"\n *ngIf=\"!!step.info?.msg\"\n >\n <i c8yIcon=\"question-circle-o\"></i>\n </button>\n </c8y-li-body>\n <c8y-li-collapse\n #alert\n *ngIf=\"step.error\"\n >\n <span class=\"message\">{{ step.error?.text | translate }}</span>\n <p\n class=\"text-muted m-t-8\"\n *ngIf=\"step?.error?.detailedData\"\n >\n <button\n class=\"btn btn-clean\"\n (click)=\"alert.expanded = !alert.expanded\"\n >\n <i c8yIcon=\"chevron-down\"></i>\n <span *ngIf=\"!alert.expanded\">Show details</span>\n <span *ngIf=\"alert.expanded\">Hide details</span>\n </button>\n </p>\n <div\n [collapse]=\"!alert.expanded\"\n [isAnimated]=\"true\"\n >\n {{ step.error.detailedData | json }}\n </div>\n </c8y-li-collapse>\n <c8y-li-action\n icon=\"play\"\n label=\"{{ 'Retry this step`button`' | translate }}\"\n *ngIf=\"step.state === 'Failed' && !step.overrideContext\"\n (click)=\"service.retryStep(step)\"\n data-cy=\"c8y-replace-device-wizard--retry-step\"\n ></c8y-li-action>\n </c8y-li>\n </c8y-list-group>\n </div>\n\n <c8y-stepper-buttons\n class=\"d-block card-footer p-24 separator\"\n [pending]=\"service.deviceReplacementInProgress$ | async\"\n (onBack)=\"close()\"\n [labels]=\"{ back: 'Close' | translate }\"\n >\n <button\n class=\"btn btn-primary\"\n [title]=\"'Retry' | translate\"\n type=\"button\"\n *ngIf=\"(service.deviceReplaced$ | async) === false\"\n (click)=\"service.retryStep()\"\n [ngClass]=\"{ 'btn-pending': service.deviceReplacementInProgress$ | async }\"\n [disabled]=\"service.deviceReplacementInProgress$ | async\"\n data-cy=\"c8y-replace-device-wizard--btn-retry\"\n >\n {{ 'Retry' | translate }}\n </button>\n </c8y-stepper-buttons>\n </cdk-step>\n</c8y-stepper>\n", dependencies: [{ kind: "ngmodule", type: CoreModule }, { kind: "component", type: i3.EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "directive", type: i3.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i3.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i4.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i4.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "component", type: i3.C8yStepper, selector: "c8y-stepper", inputs: ["disableDefaultIcons", "disableProgressButtons", "customClasses", "hideStepProgress", "useStepLabelsAsTitlesOnly"], outputs: ["onStepChange"] }, { kind: "component", type: i5.CdkStep, selector: "cdk-step", inputs: ["stepControl", "label", "errorMessage", "aria-label", "aria-labelledby", "state", "editable", "optional", "completed", "hasError"], outputs: ["interacted"], exportAs: ["cdkStep"] }, { kind: "component", type: i3.C8yStepperButtons, selector: "c8y-stepper-buttons", inputs: ["labels", "pending", "disabled", "showButtons"], outputs: ["onCancel", "onNext", "onBack", "onCustom"] }, { kind: "component", type: i3.ListGroupComponent, selector: "c8y-list-group" }, { kind: "component", type: i3.ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: i3.ListItemIconComponent, selector: "c8y-list-item-icon, c8y-li-icon", inputs: ["icon", "status"] }, { kind: "component", type: i3.ListItemBodyComponent, selector: "c8y-list-item-body, c8y-li-body", inputs: ["body"] }, { kind: "component", type: i3.ListItemActionComponent, selector: "c8y-list-item-action, c8y-li-action", inputs: ["label", "icon", "disabled"], outputs: ["click"] }, { kind: "component", type: i3.ListItemCollapseComponent, selector: "c8y-list-item-collapse, c8y-li-collapse", inputs: ["collapseWay"] }, { kind: "component", type: i3.ListItemCheckboxComponent, selector: "c8y-list-item-checkbox, c8y-li-checkbox", inputs: ["selected", "indeterminate", "disabled", "displayAsSwitch"], outputs: ["onSelect"] }, { kind: "ngmodule", type: DeviceGridModule }, { kind: "component", type: i2.DeviceGridComponent, selector: "c8y-device-grid", inputs: ["dataCallback", "refresh", "title", "loadMoreItemsLabel", "loadingItemsLabel", "legacyConfigKey", "legacyFilterKey", "columns", "pagination", "infiniteScroll", "actionControls", "selectable", "singleSelection", "baseQuery", "bulkActionControls", "headerActionControls", "childDeviceGrid", "parentDeviceId", "withChildren", "showSearch", "activeClassName"], outputs: ["onColumnsChange", "onFilterChange", "onDeviceQueryStringChange", "itemsSelect"] }, { kind: "ngmodule", type: DeviceSelectorModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6.TooltipDirective, selector: "[tooltip], [tooltipHtml]", inputs: ["adaptivePosition", "tooltip", "placement", "triggers", "container", "containerClass", "boundariesElement", "isOpen", "isDisabled", "delay", "tooltipHtml", "tooltipPlacement", "tooltipIsOpen", "tooltipEnable", "tooltipAppendToBody", "tooltipAnimation", "tooltipClass", "tooltipContext", "tooltipPopupDelay", "tooltipFadeDuration", "tooltipTrigger"], outputs: ["tooltipChange", "onShown", "onHidden", "tooltipStateChanged"], exportAs: ["bs-tooltip"] }, { kind: "ngmodule", type: CollapseModule }, { kind: "directive", type: i7.CollapseDirective, selector: "[collapse]", inputs: ["display", "isAnimated", "collapse"], outputs: ["collapsed", "collapses", "expanded", "expands"], exportAs: ["bs-collapse"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "directive", type: i8.PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "pipe", type: i3.C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: i4.AsyncPipe, name: "async" }, { kind: "pipe", type: i4.JsonPipe, name: "json" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ReplaceDeviceWizardComponent, decorators: [{ type: Component, args: [{ standalone: true, selector: 'c8y-replace-device-wizard', imports: [ CoreModule, DeviceGridModule, DeviceSelectorModule, TooltipModule, CollapseModule, PopoverModule ], providers: [ DeviceGridService, ReplaceDeviceWizardService, { provide: DATA_GRID_CONFIGURATION_STRATEGY, useClass: UserPreferencesConfigurationStrategy }, { provide: DATA_GRID_CONFIGURATION_CONTEXT_PROVIDER, useExisting: ReplaceDeviceWizardComponent } ], template: "<c8y-stepper\n class=\"d-contents c8y-stepper--no-btns\"\n [disableDefaultIcons]=\"{ edit: true, done: false }\"\n id=\"modal-body\"\n [customClasses]=\"[\n 'col-xs-10',\n 'col-xs-offset-1',\n 'col-sm-8',\n 'col-sm-offset-2',\n 'm-t-24',\n 'm-b-40',\n 'p-0',\n 'flex-no-shrink'\n ]\"\n linear\n>\n <cdk-step [label]=\"'Replacement device' | translate\">\n <div class=\"p-16 p-t-0 flex-no-shrink separator-bottom col-xs-12\">\n <div class=\"row\">\n <div class=\"col-md-6 col-md-offset-3 col-lg-4 col-lg-offset-4\">\n <div class=\"h4 text-center text-medium\">\n {{ 'Replacement device' | translate }}\n </div>\n </div>\n </div>\n </div>\n <c8y-device-grid\n class=\"flex-grow col-xs-12 no-gutter\"\n [title]=\"'Select replacement device' | translate\"\n [infiniteScroll]=\"'auto'\"\n [actionControls]=\"[]\"\n [columns]=\"columns\"\n [selectable]=\"true\"\n [singleSelection]=\"true\"\n [baseQuery]=\"baseQuery$ | async\"\n (itemsSelect)=\"replacementDeviceId = $event[0]\"\n ></c8y-device-grid>\n <c8y-stepper-buttons\n class=\"d-block card-footer p-24 separator\"\n [disabled]=\"!replacementDeviceId\"\n (onCancel)=\"cancel()\"\n (onNext)=\"service.changeReplacementDeviceId(replacementDeviceId); continue()\"\n ></c8y-stepper-buttons>\n </cdk-step>\n\n <cdk-step [label]=\"'Select external IDs' | translate\">\n <div class=\"p-16 p-t-0 flex-no-shrink col-xs-12\">\n <div class=\"row\">\n <div class=\"col-md-6 col-md-offset-3 col-lg-4 col-lg-offset-4\">\n <div class=\"h4 text-center text-medium\">\n {{ 'Select external IDs' | translate }}\n </div>\n </div>\n </div>\n </div>\n <c8y-li class=\"page-sticky-header hidden-sm hidden-xs\">\n <c8y-li-icon><i class=\"p-l-24\"></i></c8y-li-icon>\n <c8y-li-body class=\"content-flex-40\">\n <div class=\"col-4\">\n <span\n class=\"text-medium text-truncate\"\n title=\" {{ 'Type' | translate }} \"\n >\n {{ 'Type' | translate }}\n </span>\n </div>\n <div class=\"col-6\">\n <span\n class=\"text-medium text-truncate\"\n title=\"{{ 'External ID' | translate }}\"\n >\n {{ 'External ID' | translate }}\n </span>\n </div>\n </c8y-li-body>\n </c8y-li>\n <div\n class=\"col-xs-12 flex-grow no-gutter\"\n *ngIf=\"!(service.externalIdsLoading$ | async); else loading\"\n >\n <div\n class=\"card-inner-scroll fit-h\"\n *ngIf=\"(service.externalIdsWithSelection$ | async)?.length > 0; else noIds\"\n >\n <div class=\"card-block p-t-0 p-b-0\">\n <c8y-list-group>\n <c8y-li *ngFor=\"let extId of service.externalIdsWithSelection$ | async\">\n <c8y-li-checkbox\n [selected]=\"extId.selected\"\n (onSelect)=\"service.checkExternalId(extId.id, $event)\"\n ></c8y-li-checkbox>\n <c8y-li-body class=\"content-flex-40\">\n <div class=\"col-4 m-b-xs-8\">\n <div\n class=\"text-truncate\"\n title=\"{{ 'Type' | translate }}\"\n >\n <span\n class=\"text-label-small m-r-8 visible-xs visible-sm\"\n translate\n >\n Type\n </span>\n {{ extId.id.type }}\n </div>\n </div>\n <div class=\"col-6 m-b-xs-8\">\n <div\n class=\"text-truncate\"\n title=\"{{ 'External ID' | translate }}\"\n >\n <span\n class=\"text-label-small m-r-8 visible-xs visible-sm\"\n translate\n >\n External ID\n </span>\n {{ extId.id.externalId }}\n </div>\n </div>\n </c8y-li-body>\n </c8y-li>\n </c8y-list-group>\n </div>\n </div>\n <ng-template #noIds>\n <c8y-ui-empty-state\n [icon]=\"'barcode'\"\n [title]=\"'No external IDs assigned.'\"\n [subtitle]=\"\n 'The selected replacement device does not have external IDs assigned. Go back to &quot;Replacement device&quot; and select a different device.'\n | translate\n \"\n *ngIf=\"true\"\n ></c8y-ui-empty-state>\n </ng-template>\n </div>\n <ng-template #loading>\n <div class=\"d-flex d-col a-i-center j-c-center fit-h\">\n <c8y-loading></c8y-loading>\n </div>\n </ng-template>\n <c8y-stepper-buttons\n class=\"d-block card-footer p-24 separator\"\n [disabled]=\"\n (service.externalIdsLoading$ | async) || !(service.selectedExternalIds$ | async)?.length\n \"\n (onCancel)=\"cancel()\"\n (onNext)=\"replace()\"\n [labels]=\"{ next: ('Replace' | translate) }\"\n ></c8y-stepper-buttons>\n </cdk-step>\n\n <cdk-step [label]=\"'Replace' | translate\">\n <div class=\"p-16 p-t-0 flex-no-shrink col-xs-12 separator-bottom\">\n <div class=\"row\">\n <div class=\"col-md-6 col-md-offset-3 col-lg-4 col-lg-offset-4\">\n <div class=\"h4 text-center text-medium\">\n {{ 'Replace' | translate }}\n </div>\n </div>\n </div>\n </div>\n <div class=\"d-flex d-col a-i-center j-c-center inner-scroll\">\n <c8y-list-group class=\"col-sm-8 col-md-6\">\n <c8y-li\n *ngFor=\"let step of service.steps\"\n [attr.data-cy]=\"'c8y-replace-device-wizard--step-' + step.label\"\n >\n <c8y-li-icon>\n <i\n [c8yIcon]=\"REPLACE_DEVICE_STEP_STATES_MAP[step.state || 'Pending']?.icon\"\n [tooltip]=\"(step.state || 'Pending').toString() | translate\"\n [ngClass]=\"REPLACE_DEVICE_STEP_STATES_MAP[step.state || 'Pending']?.styleClass\"\n ></i>\n </c8y-li-icon>\n <c8y-li-body>\n <span [ngClass]=\"{ 'text-bold': !!step.error }\">{{ step.label | translate }}</span>\n <button\n class=\"btn-help btn-help--sm pull-right\"\n [attr.aria-label]=\"'Help' | translate\"\n [attr.aria-label]=\"step.info.msg | translate\"\n [popover]=\"step.info.msg | translate\"\n placement=\"right\"\n triggers=\"focus\"\n type=\"button\"\n *ngIf=\"!!step.info?.msg\"\n >\n <i c8yIcon=\"question-circle-o\"></i>\n </button>\n </c8y-li-body>\n <c8y-li-collapse\n #alert\n *ngIf=\"step.error\"\n >\n <span class=\"message\">{{ step.error?.text | translate }}</span>\n <p\n class=\"text-muted m-t-8\"\n *ngIf=\"step?.error?.detailedData\"\n >\n <button\n class=\"btn btn-clean\"\n (click)=\"alert.expanded = !alert.expanded\"\n >\n <i c8yIcon=\"chevron-down\"></i>\n <span *ngIf=\"!alert.expanded\">Show details</span>\n <span *ngIf=\"alert.expanded\">Hide details</span>\n </button>\n </p>\n <div\n [collapse]=\"!alert.expanded\"\n [isAnimated]=\"true\"\n >\n {{ step.error.detailedData | json }}\n </div>\n </c8y-li-collapse>\n <c8y-li-action\n icon=\"play\"\n label=\"{{ 'Retry this step`button`' | translate }}\"\n *ngIf=\"step.state === 'Failed' && !step.overrideContext\"\n (click)=\"service.retryStep(step)\"\n data-cy=\"c8y-replace-device-wizard--retry-step\"\n ></c8y-li-action>\n </c8y-li>\n </c8y-list-group>\n </div>\n\n <c8y-stepper-buttons\n class=\"d-block card-footer p-24 separator\"\n [pending]=\"service.deviceReplacementInProgress$ | async\"\n (onBack)=\"close()\"\n [labels]=\"{ back: 'Close' | translate }\"\n >\n <button\n class=\"btn btn-primary\"\n [title]=\"'Retry' | translate\"\n type=\"button\"\n *ngIf=\"(service.deviceReplaced$ | async) === false\"\n (click)=\"service.retryStep()\"\n [ngClass]=\"{ 'btn-pending': service.deviceReplacementInProgress$ | async }\"\n [disabled]=\"service.deviceReplacementInProgress$ | async\"\n data-cy=\"c8y-replace-device-wizard--btn-retry\"\n >\n {{ 'Retry' | translate }}\n </button>\n </c8y-stepper-buttons>\n </cdk-step>\n</c8y-stepper>\n" }] }], ctorParameters: () => [{ type: ReplaceDeviceWizardService }, { type: i2.DeviceGridService }, { type: i3.ModalService }, { type: i3.BottomDrawerRef, decorators: [{ type: Optional }] }], propDecorators: { classes: [{ type: HostBinding, args: ['class'] }], stepper: [{ type: ViewChild, args: [C8yStepper, { static: true }] }], deviceToReplace: [{ type: Input }] } }); /** * Generated bundle index. Do not edit. */ export { REPLACE_DEVICE_STEP_STATE, REPLACE_DEVICE_STEP_STATES_MAP, ReplaceDeviceWizardComponent, ReplaceDeviceWizardService, isValidReplaceDeviceStepState }; //# sourceMappingURL=c8y-ngx-components-replace-device-replace-device-wizard.mjs.map