@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
243 lines (237 loc) • 22.5 kB
JavaScript
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