UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

243 lines (237 loc) 22.5 kB
import { NgClass } from '@angular/common'; import * as i0 from '@angular/core'; import { inject, Pipe, Component } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { FeatureService } from '@c8y/client'; import { Permissions, AppStateService, AlertService, ContextRouteService, TitleComponent, DataGridComponent, EmptyStateComponent, CellRendererDefDirective, ColumnDirective, HeaderCellRendererDefDirective, C8yTranslatePipe, C8yTranslateDirective } from '@c8y/ngx-components'; import { gettext } from '@c8y/ngx-components/gettext'; import { PopoverDirective } from 'ngx-bootstrap/popover'; import * as i1 from '@angular/forms'; import { FormsModule } from '@angular/forms'; class CanToggleStatusOfFeatureTogglePipe { constructor() { this.permissions = inject(Permissions); this.appState = inject(AppStateService); } transform(value) { if (!this.permissions.hasRole(Permissions.ROLE_TENANT_MANAGEMENT_ADMIN)) { return false; } if (this.appState.currentTenant.value?.name === 'management') { return true; } if (value.phase === 'PUBLIC_PREVIEW' || value.phase === 'GENERALLY_AVAILABLE') { return true; } return false; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CanToggleStatusOfFeatureTogglePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); } static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.19", ngImport: i0, type: CanToggleStatusOfFeatureTogglePipe, isStandalone: true, name: "canToggleStatusOfFeatureToggle" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: CanToggleStatusOfFeatureTogglePipe, decorators: [{ type: Pipe, args: [{ name: 'canToggleStatusOfFeatureToggle' }] }] }); class FeatureToggleListComponent { constructor() { this.columns = [ { name: 'name', header: gettext('Name'), sortable: true, path: 'name' }, { name: 'description', header: gettext('Description'), sortable: true, path: 'description' }, { name: 'key', header: gettext('Toggle key'), sortable: true, path: 'key' }, { name: 'status', header: gettext('Status'), sortable: true, path: 'active' }, { name: 'phase', header: gettext('Phase'), sortable: true, path: 'phase' }, { name: 'strategy', header: gettext('Strategy'), sortable: true, path: 'strategy' } ]; this.actionControls = [ { name: 'clearOverride', icon: 'reset', type: 'clearOverride', text: gettext('Reset the toggle to the default state depending on the phase'), callback: async (item, reload) => { await this.removeTenantOverride(item); reload(); }, showIf: (item) => item.strategy !== 'DEFAULT' && this.permissions.hasRole('ROLE_TENANT_MANAGEMENT_ADMIN') && (this.isManagementTenant || item.phase === 'PUBLIC_PREVIEW' || item.phase === 'GENERALLY_AVAILABLE') } ]; this.items = []; this.pagination = { pageSize: 50, currentPage: 1 }; this.displayOptions = { bordered: false, striped: true, filter: true, gridHeader: true, hover: true }; this.hasPrivatePreviewFeatures = false; this.phaseDetails = { PUBLIC_PREVIEW: { class: 'tag--info', name: gettext('Public preview`feature phase`'), description: gettext('The feature is available for public preview and can be used by all users. It is disabled by default.') }, PRIVATE_PREVIEW: { class: 'tag--default', name: gettext('Private preview`feature phase`'), description: gettext('The feature is in private preview and available to a selected set of users. It is disabled by default.') }, GENERALLY_AVAILABLE: { class: 'tag--success', name: gettext('Generally available`feature phase`'), description: gettext('The feature is generally available and can be used by all users. It is enabled by default.') } }; this.phaseKeysOrder = [ 'PRIVATE_PREVIEW', 'PUBLIC_PREVIEW', 'GENERALLY_AVAILABLE' ]; this.strategyDetails = [ { key: 'DEFAULT', class: 'tag--success', name: gettext('Default`strategy`'), description: gettext('The feature toggle is using the default state based on its phase.') }, { key: 'TENANT', class: 'tag--info', name: gettext('Custom`strategy`'), description: gettext('The feature toggle has been customized. When the phase of the feature changes, the custom state remains unchanged.') } ]; this.activatedRoute = inject(ActivatedRoute, { optional: true }); this.featureToggle = inject(FeatureService); this.alertService = inject(AlertService); this.permissions = inject(Permissions); this.isManagementTenant = inject(AppStateService).currentTenant.value?.name === 'management'; this.contextRouteService = inject(ContextRouteService); this.items = this.activatedRoute?.snapshot.data['features'] || []; } async ngOnInit() { await this.reload(); } async reload() { let { data: toggles } = await this.featureToggle.list({ pageSize: 1000 }); toggles = toggles.filter(toggle => toggle.phase !== 'IN_DEVELOPMENT' && (toggle.phase !== 'PRIVATE_PREVIEW' || toggle.active === true)); this.hasPrivatePreviewFeatures = toggles.some(toggle => toggle.phase === 'PRIVATE_PREVIEW'); const contextData = this.contextRouteService.getContextData(this.activatedRoute); if (!contextData) { this.items = toggles.map(toggle => ({ ...toggle, id: toggle.key })); return; } const tenantId = contextData.contextData.id; const promises = toggles.map(toggle => { return this.featureToggle.detailByTenant(toggle.key).then(({ data }) => { const override = data.find(ft => ft.tenantId === tenantId); if (override) { return { ...toggle, id: toggle.key, strategy: 'TENANT', active: override.active }; } return { ...toggle, id: toggle.key, strategy: 'DEFAULT', active: toggle.phase === 'GENERALLY_AVAILABLE' }; }); }); const results = await Promise.all(promises); this.items = results; } async updateFeature(feature, newActiveValue) { try { const contextData = this.contextRouteService.getContextData(this.activatedRoute); if (contextData) { const tenantId = contextData.contextData.id; await this.featureToggle.updateFeatureByTenant({ key: feature.key, active: newActiveValue }, tenantId); } else { await this.featureToggle.updateFeature({ key: feature.key, active: newActiveValue }); } if (newActiveValue) { this.alertService.success(gettext('Feature enabled.')); } else { this.alertService.success(gettext('Feature disabled.')); } } catch (e) { console.error('Error updating feature:', e); if (newActiveValue) { this.alertService.warning(gettext('Failed to enable feature.')); } else { this.alertService.warning(gettext('Failed to disable feature.')); } } await this.reload(); } async removeTenantOverride(feature) { try { const contextData = this.contextRouteService.getContextData(this.activatedRoute); if (contextData) { const tenantId = contextData.contextData.id; await this.featureToggle.removeTenantOverrideByTenant(feature, tenantId); } else { await this.featureToggle.removeTenantOverride(feature); } this.alertService.success(gettext('Feature reset to default state.')); } catch (e) { console.error('Error removing feature override:', e); this.alertService.warning(gettext('Failed to reset feature state.')); } await this.reload(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FeatureToggleListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FeatureToggleListComponent, isStandalone: true, selector: "c8y-feature-toggle-list", ngImport: i0, template: "<c8y-title>{{ 'Feature toggles' | translate }}</c8y-title>\n\n<div class=\"content-fullpage d-flex d-col border-top border-bottom\">\n <c8y-data-grid\n [title]=\"'Feature toggles' | translate\"\n [actionControls]=\"actionControls\"\n [columns]=\"columns\"\n [rows]=\"items\"\n [pagination]=\"pagination\"\n [displayOptions]=\"displayOptions\"\n (onReload)=\"reload()\"\n >\n <c8y-column name=\"name\">\n <ng-container *c8yCellRendererDef=\"let context\">\n <strong>{{ context.value || '--' | translate }}</strong>\n </ng-container>\n </c8y-column>\n <c8y-column name=\"description\">\n <ng-container *c8yCellRendererDef=\"let context\">\n <p>{{ context.value || '--' | translate }}</p>\n </ng-container>\n </c8y-column>\n <c8y-column name=\"status\">\n <ng-container *c8yCellRendererDef=\"let context\">\n <label class=\"c8y-switch c8y-switch--inline\">\n <input\n type=\"checkbox\"\n [ngModel]=\"context.value\"\n [disabled]=\"!(context.item | canToggleStatusOfFeatureToggle)\"\n (ngModelChange)=\"updateFeature(context.item, !context.value)\"\n [ngModelOptions]=\"{ standalone: true }\"\n />\n <span></span>\n <span class=\"text-12 a-s-center\">\n @if (context.value) {\n {{ 'Enabled' | translate }}\n } @else {\n {{ 'Disabled' | translate }}\n }\n </span>\n </label>\n </ng-container>\n </c8y-column>\n <c8y-column name=\"phase\">\n <ng-template #phasePopoverHelp>\n <p>\n <strong>{{ 'Feature phases' | translate }}</strong>\n </p>\n @for (phaseKey of phaseKeysOrder; track phaseKey) {\n @if (phaseKey !== 'PRIVATE_PREVIEW' || hasPrivatePreviewFeatures) {\n <span\n class=\"tag chip m-b-4\"\n [ngClass]=\"phaseDetails[phaseKey].class\"\n >\n {{ phaseDetails[phaseKey].name | translate }}\n </span>\n <p class=\"text-12 m-b-8\">\n {{ phaseDetails[phaseKey].description | translate }}\n </p>\n }\n }\n </ng-template>\n <ng-container *c8yHeaderCellRendererDef>\n {{ 'Phase' | translate }}\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"phasePopoverHelp\"\n placement=\"bottom\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </ng-container>\n <ng-container *c8yCellRendererDef=\"let context\">\n <span\n class=\"tag\"\n [ngClass]=\"phaseDetails[context.value]?.class || 'tag--warning'\"\n >\n {{ phaseDetails[context.value]?.name || context.value | translate }}\n </span>\n </ng-container>\n </c8y-column>\n <c8y-column name=\"strategy\">\n <ng-template #strategyPopoverHelp>\n <p>\n <strong>{{ 'Feature strategies' | translate }}</strong>\n </p>\n @for (strategy of strategyDetails; track strategy) {\n <span\n class=\"tag chip m-b-4\"\n [ngClass]=\"strategy.class\"\n >\n {{ strategy.name | translate }}\n </span>\n <p class=\"text-12 m-b-8\">\n {{ strategy.description | translate }}\n </p>\n }\n </ng-template>\n <ng-container *c8yHeaderCellRendererDef>\n {{ 'Strategy' | translate }}\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"strategyPopoverHelp\"\n placement=\"bottom\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </ng-container>\n <ng-container *c8yCellRendererDef=\"let context\">\n @for (strategy of strategyDetails; track strategy) {\n @if (context.value === strategy.key) {\n <span\n class=\"tag\"\n [ngClass]=\"strategy.class\"\n >\n {{ strategy.name | translate }}\n </span>\n }\n }\n </ng-container>\n </c8y-column>\n <c8y-ui-empty-state\n [icon]=\"'toggle-on'\"\n [title]=\"'No feature toggles available.' | translate\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n </c8y-data-grid>\n</div>\n", dependencies: [{ kind: "component", type: TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { kind: "component", type: DataGridComponent, selector: "c8y-data-grid", inputs: ["title", "loadMoreItemsLabel", "loadingItemsLabel", "showSearch", "refresh", "loading", "columns", "rows", "pagination", "childNodePagination", "infiniteScroll", "serverSideDataCallback", "selectable", "singleSelection", "selectionPrimaryKey", "displayOptions", "actionControls", "bulkActionControls", "headerActionControls", "searchText", "configureColumnsEnabled", "showCounterWarning", "activeClassName", "expandableRows", "treeGrid", "hideReload", "childNodesProperty", "parentNodeLabelProperty"], outputs: ["rowMouseOver", "rowMouseLeave", "rowClick", "onConfigChange", "onBeforeFilter", "onBeforeSearch", "onFilter", "itemsSelect", "onReload", "onAddCustomColumn", "onRemoveCustomColumn", "onColumnFilterReset", "onSort", "onPageSizeChange", "onColumnReordered", "onColumnVisibilityChange"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "directive", type: CellRendererDefDirective, selector: "[c8yCellRendererDef]" }, { kind: "directive", type: ColumnDirective, selector: "c8y-column", inputs: ["name"] }, { 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: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: HeaderCellRendererDefDirective, selector: "[c8yHeaderCellRendererDef]" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: CanToggleStatusOfFeatureTogglePipe, name: "canToggleStatusOfFeatureToggle" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FeatureToggleListComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-feature-toggle-list', imports: [ TitleComponent, C8yTranslateDirective, C8yTranslatePipe, DataGridComponent, EmptyStateComponent, CellRendererDefDirective, ColumnDirective, PopoverDirective, NgClass, FormsModule, CanToggleStatusOfFeatureTogglePipe, HeaderCellRendererDefDirective ], template: "<c8y-title>{{ 'Feature toggles' | translate }}</c8y-title>\n\n<div class=\"content-fullpage d-flex d-col border-top border-bottom\">\n <c8y-data-grid\n [title]=\"'Feature toggles' | translate\"\n [actionControls]=\"actionControls\"\n [columns]=\"columns\"\n [rows]=\"items\"\n [pagination]=\"pagination\"\n [displayOptions]=\"displayOptions\"\n (onReload)=\"reload()\"\n >\n <c8y-column name=\"name\">\n <ng-container *c8yCellRendererDef=\"let context\">\n <strong>{{ context.value || '--' | translate }}</strong>\n </ng-container>\n </c8y-column>\n <c8y-column name=\"description\">\n <ng-container *c8yCellRendererDef=\"let context\">\n <p>{{ context.value || '--' | translate }}</p>\n </ng-container>\n </c8y-column>\n <c8y-column name=\"status\">\n <ng-container *c8yCellRendererDef=\"let context\">\n <label class=\"c8y-switch c8y-switch--inline\">\n <input\n type=\"checkbox\"\n [ngModel]=\"context.value\"\n [disabled]=\"!(context.item | canToggleStatusOfFeatureToggle)\"\n (ngModelChange)=\"updateFeature(context.item, !context.value)\"\n [ngModelOptions]=\"{ standalone: true }\"\n />\n <span></span>\n <span class=\"text-12 a-s-center\">\n @if (context.value) {\n {{ 'Enabled' | translate }}\n } @else {\n {{ 'Disabled' | translate }}\n }\n </span>\n </label>\n </ng-container>\n </c8y-column>\n <c8y-column name=\"phase\">\n <ng-template #phasePopoverHelp>\n <p>\n <strong>{{ 'Feature phases' | translate }}</strong>\n </p>\n @for (phaseKey of phaseKeysOrder; track phaseKey) {\n @if (phaseKey !== 'PRIVATE_PREVIEW' || hasPrivatePreviewFeatures) {\n <span\n class=\"tag chip m-b-4\"\n [ngClass]=\"phaseDetails[phaseKey].class\"\n >\n {{ phaseDetails[phaseKey].name | translate }}\n </span>\n <p class=\"text-12 m-b-8\">\n {{ phaseDetails[phaseKey].description | translate }}\n </p>\n }\n }\n </ng-template>\n <ng-container *c8yHeaderCellRendererDef>\n {{ 'Phase' | translate }}\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"phasePopoverHelp\"\n placement=\"bottom\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </ng-container>\n <ng-container *c8yCellRendererDef=\"let context\">\n <span\n class=\"tag\"\n [ngClass]=\"phaseDetails[context.value]?.class || 'tag--warning'\"\n >\n {{ phaseDetails[context.value]?.name || context.value | translate }}\n </span>\n </ng-container>\n </c8y-column>\n <c8y-column name=\"strategy\">\n <ng-template #strategyPopoverHelp>\n <p>\n <strong>{{ 'Feature strategies' | translate }}</strong>\n </p>\n @for (strategy of strategyDetails; track strategy) {\n <span\n class=\"tag chip m-b-4\"\n [ngClass]=\"strategy.class\"\n >\n {{ strategy.name | translate }}\n </span>\n <p class=\"text-12 m-b-8\">\n {{ strategy.description | translate }}\n </p>\n }\n </ng-template>\n <ng-container *c8yHeaderCellRendererDef>\n {{ 'Strategy' | translate }}\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"strategyPopoverHelp\"\n placement=\"bottom\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </ng-container>\n <ng-container *c8yCellRendererDef=\"let context\">\n @for (strategy of strategyDetails; track strategy) {\n @if (context.value === strategy.key) {\n <span\n class=\"tag\"\n [ngClass]=\"strategy.class\"\n >\n {{ strategy.name | translate }}\n </span>\n }\n }\n </ng-container>\n </c8y-column>\n <c8y-ui-empty-state\n [icon]=\"'toggle-on'\"\n [title]=\"'No feature toggles available.' | translate\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n </c8y-data-grid>\n</div>\n" }] }], ctorParameters: () => [] }); const childRoutes = [ { path: '', pathMatch: 'full', component: FeatureToggleListComponent } ]; /** * Generated bundle index. Do not edit. */ export { FeatureToggleListComponent, childRoutes }; //# sourceMappingURL=c8y-ngx-components-feature-toggles-list.mjs.map