@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
460 lines (454 loc) • 43.8 kB
JavaScript
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 "Replacement device" 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 "Replacement device" 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