UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

1,258 lines 112 kB
import * as i0 from '@angular/core';
import { inject, Injectable, LOCALE_ID, Pipe, input, computed, Component, Input, DestroyRef, EventEmitter, ViewChild, NgModule } from '@angular/core';
import * as i1$2 from '@c8y/ngx-components';
import { AlertService, Permissions, NavigatorNode, AppStateService, NavigatorService, IconDirective, C8yTranslatePipe, C8yTranslateModule, HeaderModule, HelpModule, BreadcrumbModule, ActionBarItemComponent, LoadingComponent, BaseColumn, DataGridModule, EmptyStateComponent, EmptyStateContextDirective, BytesPipe, ModalService, Status, C8yTranslateDirective, RelativeTimePipe, DataGridComponent, hookRoute, hookTab, hookNavigator, hookPreview } from '@c8y/ngx-components';
import { gettext } from '@c8y/ngx-components/gettext';
import * as i1 from '@c8y/client';
import { Service, UserService, FeatureService, ApplicationService, Paging } from '@c8y/client';
import { filter, map, take, switchMap, catchError, shareReplay, tap } from 'rxjs/operators';
import { from, BehaviorSubject, combineLatest, firstValueFrom } from 'rxjs';
import * as i1$1 from '@angular/common';
import { formatNumber, NgIf, NgClass, PercentPipe, CommonModule, DecimalPipe, AsyncPipe } from '@angular/common';
import * as i1$4 from '@angular/router';
import { RouterLink, ActivatedRoute } from '@angular/router';
import * as i1$3 from '@ngx-translate/core';
import { TranslateService } from '@ngx-translate/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { omit } from 'lodash-es';

/**
 * Currently known namespaces and their properties.
 */
const NAMESPACE_PROPS = {
    mqtt: {
        icon: 'c8y-device-protocols',
        label: gettext('MQTT Service')
    },
    'data-broker-fwd': {
        icon: 'c8y-data-broker',
        label: gettext('Data Broker')
    },
    relnotif: {
        icon: 'c8y-notification',
        label: gettext('Notifications 2.0')
    },
    'streaming-analytics': {
        icon: 'c8y-streaming-analytics',
        label: gettext('Streaming Analytics')
    }
};

class MessagingNamespacesService extends Service {
    constructor(client) {
        super(client);
        this.baseUrl = '/service/messaging-management';
        this.listUrl = 'tenants';
        this.alertService = inject(AlertService);
    }
    /**
     * Get namespace list for a tenant
     *
     * @param tenant Tenant id
     * @return Namespaces list
     */
    async getNamespaces(tenant) {
        const namespacesUrl = `/${this.listUrl}/${tenant}/namespaces`;
        return this.fetch(namespacesUrl).then(res => res.json());
    }
    /**
     * Get namespace.
     *
     * @param tenant Tenant ID.
     * @param namespace Name of namespace.
     * @return Namespace.
     */
    async getNamespace(tenant, namespace) {
        const namespaceUrl = `/${this.listUrl}/${tenant}/namespaces/${namespace}`;
        return this.fetch(namespaceUrl).then(res => res.json());
    }
    /**
     * Get namespace policies
     *
     * @param tenant Tenant id
     * @param namespace Name of namespace
     * @return Namespaces policies
     */
    async getNamespacePolicies(tenant, namespace) {
        const policiesUrl = `/${this.listUrl}/${tenant}/namespaces/${namespace}/policies`;
        return this.fetch(policiesUrl).then(res => res.json());
    }
    /**
     * Get single namespace details
     * @param tenant Tenant ID
     * @param namespaceName Namespace name
     * @return Namespace with details
     */
    async getNamespaceDetails(tenant, namespaceName) {
        const namespace = await this.getNamespace(tenant, namespaceName);
        const policies = await this.getNamespacePolicies(tenant, namespaceName);
        return {
            id: namespaceName,
            namespace,
            policies
        };
    }
    /**
     * Get namespaces with details
     *
     * @param tenant Tenant ID
     * @return Namespaces with details
     */
    async getNamespacesDetails(tenant) {
        try {
            const { namespaces } = await this.getNamespaces(tenant);
            return Promise.all(namespaces.map(async (namespace) => this.getNamespaceDetails(tenant, namespace.name)));
        }
        catch (error) {
            this.alertService.addServerFailure(error);
            return [];
        }
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingNamespacesService, deps: [{ token: i1.FetchClient }], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingNamespacesService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingNamespacesService, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }], ctorParameters: () => [{ type: i1.FetchClient }] });

const basePath = 'monitoring/messaging-service';
const MESSAGING_MANAGEMENT_FEATURE_KEY = 'messaging-management.api';
const MESSAGING_MANAGEMENT_MICROSERVICE_NAME = 'messaging-management';

class MessagingManagementGuard {
    constructor() {
        this.userService = inject(UserService);
        this.featureService = inject(FeatureService);
        this.applicationService = inject(ApplicationService);
        this.cachedResult = null;
    }
    async canActivate() {
        if (this.cachedResult !== null) {
            return this.cachedResult;
        }
        const currentUser = (await this.userService.current()).data;
        const hasRequiredRoles = this.userService.hasAnyRole(currentUser, [
            Permissions.ROLE_TENANT_STATISTICS_READ,
            Permissions.ROLE_TENANT_MANAGEMENT_ADMIN
        ]);
        const featureEnabled = await this.featureService
            .detail(MESSAGING_MANAGEMENT_FEATURE_KEY)
            .then(({ data }) => data.active)
            .catch(() => false);
        const microserviceSubscribed = await this.applicationService
            .isAvailable(MESSAGING_MANAGEMENT_MICROSERVICE_NAME)
            .then(({ data: subscribed }) => subscribed)
            .catch(() => false);
        this.cachedResult = hasRequiredRoles && featureEnabled && microserviceSubscribed;
        return this.cachedResult;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingManagementGuard, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingManagementGuard, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingManagementGuard, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }] });

const baseNode = new NavigatorNode({
    label: gettext('Messaging service'),
    icon: 'arrows-dotted-left-right',
    path: 'monitoring/messaging-service',
    priority: 100,
    parent: {
        label: gettext('Monitoring'),
        icon: 'monitoring'
    }
});
class MessagingNavigatorNodeFactory {
    constructor() {
        this.appState = inject(AppStateService);
        this.navigatorService = inject(NavigatorService);
        this.namespacesService = inject(MessagingNamespacesService);
        this.guard = inject(MessagingManagementGuard);
        this.currentTenantId = this.appState.currentTenant.pipe(filter(currentTenant => !!currentTenant), map(currentTenant => currentTenant.name), take(1));
        this.navigatorNode$ = from(this.guard.canActivate()).pipe(filter(allowed => allowed), switchMap(() => this.currentTenantId), switchMap(currentTenantId => this.namespacesService.getNamespaces(currentTenantId)), map(({ namespaces }) => {
            if (!namespaces?.length) {
                return [];
            }
            namespaces.forEach(namespace => {
                const label = NAMESPACE_PROPS[namespace.name]?.label || namespace.name;
                const childNode = new NavigatorNode({
                    label,
                    path: `monitoring/messaging-service/namespace/${namespace.name}`,
                    icon: NAMESPACE_PROPS[namespace.name]?.icon,
                    routerLinkExact: false
                });
                baseNode.add(childNode);
            });
            return baseNode;
        }), catchError(() => {
            return [];
        }), shareReplay(1));
    }
    get() {
        return this.navigatorNode$;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingNavigatorNodeFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingNavigatorNodeFactory, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingNavigatorNodeFactory, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }], ctorParameters: () => [] });

/**
 * In case of limits in Messaging management, value of '-1' means that there is no limit.
 */
class BacklogQuotaLimitPipe {
    constructor() {
        this.translateService = inject(TranslateService);
        this.locale = inject(LOCALE_ID);
    }
    transform(value) {
        if (value == null || isNaN(value)) {
            return '-';
        }
        else if (value === -1) {
            return this.translateService.instant(gettext('Unlimited` backlog quota`'));
        }
        else {
            return formatNumber(value, this.locale);
        }
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: BacklogQuotaLimitPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
    static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: BacklogQuotaLimitPipe, isStandalone: true, name: "backlogQuotaLimit" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: BacklogQuotaLimitPipe, decorators: [{
            type: Pipe,
            args: [{ name: 'backlogQuotaLimit', standalone: true }]
        }] });

/**
 * Usage component displays usage information in a form of e.g. "51% used".
 * It can be used in two ways:
 * 1. By providing `count` and `limit` inputs, it will calculate the usage percentage.
 * 2. By providing `percentage` input, it will use the provided percentage value.
 * Note: `percentage` input takes precedence over `count` and `limit` inputs.
 */
class UsageComponent {
    constructor() {
        this.count = input(null, ...(ngDevMode ? [{ debugName: "count" }] : []));
        this.limit = input(null, ...(ngDevMode ? [{ debugName: "limit" }] : []));
        /**
         * Percentage of usage. Value range is from 0 to 100 (or more).
         * For example, if 10% is used, this value should be provided as 10 (not 0.1).
         */
        this.percentage = input(null, ...(ngDevMode ? [{ debugName: "percentage" }] : []));
        /**
         * Usage as a fraction (e.g. if 50% is used, usage value will be 0.5)
         */
        this.usage = computed(() => this.percentage() != null ? this.percentage() / 100 : this.getUsage(this.count(), this.limit()), ...(ngDevMode ? [{ debugName: "usage" }] : []));
        this.status = computed(() => this.getStatus(this.usage()), ...(ngDevMode ? [{ debugName: "status" }] : []));
        this.usageToDisplay = gettext('{{ percentageOfQuota }} used');
        this.statusMap = {
            danger: ['tag--danger'],
            warning: ['tag--warning'],
            success: ['tag--success']
        };
    }
    /**
     * Get usage as fraction of count and limit.
     * E.g. if count is 5 and limit is 10, returned usage will be 0.5
     * @param count Usage count
     * @param limit Usage limit
     * @returns Count divided by limit or null if count or limit is null or limit is -1 (indicates no limit)
     */
    getUsage(count, limit) {
        if (count == null || limit == null || limit === -1) {
            return null;
        }
        return count / limit;
    }
    getStatus(usage) {
        if (usage == null) {
            return null;
        }
        const percentage = usage * 100;
        if (percentage >= 80) {
            return 'danger';
        }
        else if (percentage >= 50) {
            return 'warning';
        }
        else {
            return 'success';
        }
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: UsageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.15", type: UsageComponent, isStandalone: true, selector: "app-usage", inputs: { count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null }, limit: { classPropertyName: "limit", publicName: "limit", isSignal: true, isRequired: false, transformFunction: null }, percentage: { classPropertyName: "percentage", publicName: "percentage", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "d-contents" }, ngImport: i0, template: "<div\n  class=\"tag no-pointer\"\n  [ngClass]=\"statusMap[status()]\"\n  *ngIf=\"usage() !== null\"\n>\n  <i\n    class=\"text-danger m-r-4 text-12\"\n    c8yIcon=\"exclamation-circle\"\n    *ngIf=\"status() === 'danger'\"\n  ></i>\n  <span>\n    {{ usageToDisplay | translate: { percentageOfQuota: (usage() | percent: '1.0-2') } }}\n  </span>\n</div>\n", dependencies: [{ kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: PercentPipe, name: "percent" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: UsageComponent, decorators: [{
            type: Component,
            args: [{ selector: 'app-usage', standalone: true, imports: [IconDirective, NgIf, NgClass, IconDirective, C8yTranslatePipe, PercentPipe], host: { class: 'd-contents' }, template: "<div\n  class=\"tag no-pointer\"\n  [ngClass]=\"statusMap[status()]\"\n  *ngIf=\"usage() !== null\"\n>\n  <i\n    class=\"text-danger m-r-4 text-12\"\n    c8yIcon=\"exclamation-circle\"\n    *ngIf=\"status() === 'danger'\"\n  ></i>\n  <span>\n    {{ usageToDisplay | translate: { percentageOfQuota: (usage() | percent: '1.0-2') } }}\n  </span>\n</div>\n" }]
        }], propDecorators: { count: [{ type: i0.Input, args: [{ isSignal: true, alias: "count", required: false }] }], limit: [{ type: i0.Input, args: [{ isSignal: true, alias: "limit", required: false }] }], percentage: [{ type: i0.Input, args: [{ isSignal: true, alias: "percentage", required: false }] }] } });

const DataType = {
    publishers: 'publishers',
    subscribers: 'subscribers',
    topics: 'topics'
};
class NamespaceItemCardComponent {
    constructor() {
        this.DATA_TYPE = DataType;
        this.ITEM_DETAILS = {
            publishers: { icon: 'output', title: gettext('Publishers') },
            subscribers: { icon: 'input', title: gettext('Subscribers') },
            topics: { icon: 'day-view', title: gettext('Topics') }
        };
        this.topicsLimitLabel = gettext('Limit: {{ backlogQuotaLimit }}');
        /**
         * The label of the service (already translated).
         */
        this.serviceLabel = '';
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NamespaceItemCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: NamespaceItemCardComponent, isStandalone: true, selector: "app-namespace-item-card", inputs: { serviceLabel: "serviceLabel", limit: "limit", dataType: "dataType", value: "value" }, host: { classAttribute: "card m-b-0 fit-w" }, ngImport: i0, template: "<div class=\"card-block text-default visible-xs text-center p-b-0\">\n  <span class=\"text-12 text-uppercase text-muted\">\n    {{ serviceLabel | translate }}\n  </span>\n</div>\n<div\n  class=\"card-block text-default p-t-sm-48\"\n  [ngClass]=\"{\n    'p-b-sm-48': limit === 0\n  }\"\n>\n  <div class=\"d-flex fit-w a-i-center gap-8 j-c-center\">\n    <i\n      class=\"icon-32 c8y-icon-duocolor\"\n      [c8yIcon]=\"ITEM_DETAILS[dataType].icon\"\n    ></i>\n    <span\n      class=\"h1\"\n      data-cy=\"namespace-item-card--value\"\n    >\n      {{ value | number }}\n    </span>\n    <span\n      class=\"a-s-baseline text-14 text-medium text-truncate\"\n      title=\"{{ ITEM_DETAILS[dataType].title | translate }}\"\n      data-cy=\"namespace-item-card--label\"\n    >\n      {{ ITEM_DETAILS[dataType].title | translate }}\n    </span>\n  </div>\n</div>\n@if (dataType === DATA_TYPE.topics && limit !== 0) {\n  <div class=\"card-footer d-flex gap-16 j-c-center a-i-center\">\n    <span\n      class=\"tag tag--default no-pointer\"\n      data-cy=\"namespace-item-card--limit\"\n    >\n      {{ topicsLimitLabel | translate: { backlogQuotaLimit: limit | backlogQuotaLimit } }}\n    </span>\n    <app-usage\n      data-cy=\"namespace-item-card--usage\"\n      [count]=\"value\"\n      [limit]=\"limit\"\n    ></app-usage>\n  </div>\n}\n", dependencies: [{ kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "ngmodule", type: C8yTranslateModule }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: UsageComponent, selector: "app-usage", inputs: ["count", "limit", "percentage"] }, { kind: "pipe", type: i1$2.C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: i1$1.DecimalPipe, name: "number" }, { kind: "pipe", type: BacklogQuotaLimitPipe, name: "backlogQuotaLimit" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NamespaceItemCardComponent, decorators: [{
            type: Component,
            args: [{ selector: 'app-namespace-item-card', standalone: true, imports: [IconDirective, C8yTranslateModule, CommonModule, BacklogQuotaLimitPipe, UsageComponent], host: { class: 'card m-b-0 fit-w' }, template: "<div class=\"card-block text-default visible-xs text-center p-b-0\">\n  <span class=\"text-12 text-uppercase text-muted\">\n    {{ serviceLabel | translate }}\n  </span>\n</div>\n<div\n  class=\"card-block text-default p-t-sm-48\"\n  [ngClass]=\"{\n    'p-b-sm-48': limit === 0\n  }\"\n>\n  <div class=\"d-flex fit-w a-i-center gap-8 j-c-center\">\n    <i\n      class=\"icon-32 c8y-icon-duocolor\"\n      [c8yIcon]=\"ITEM_DETAILS[dataType].icon\"\n    ></i>\n    <span\n      class=\"h1\"\n      data-cy=\"namespace-item-card--value\"\n    >\n      {{ value | number }}\n    </span>\n    <span\n      class=\"a-s-baseline text-14 text-medium text-truncate\"\n      title=\"{{ ITEM_DETAILS[dataType].title | translate }}\"\n      data-cy=\"namespace-item-card--label\"\n    >\n      {{ ITEM_DETAILS[dataType].title | translate }}\n    </span>\n  </div>\n</div>\n@if (dataType === DATA_TYPE.topics && limit !== 0) {\n  <div class=\"card-footer d-flex gap-16 j-c-center a-i-center\">\n    <span\n      class=\"tag tag--default no-pointer\"\n      data-cy=\"namespace-item-card--limit\"\n    >\n      {{ topicsLimitLabel | translate: { backlogQuotaLimit: limit | backlogQuotaLimit } }}\n    </span>\n    <app-usage\n      data-cy=\"namespace-item-card--usage\"\n      [count]=\"value\"\n      [limit]=\"limit\"\n    ></app-usage>\n  </div>\n}\n" }]
        }], propDecorators: { serviceLabel: [{
                type: Input
            }], limit: [{
                type: Input
            }], dataType: [{
                type: Input
            }], value: [{
                type: Input
            }] } });

class NamespaceItemComponent {
    constructor() {
        this.translateService = inject(TranslateService);
        this.namespaceName = '';
        this.namespaceLabel = '';
        this.icon = '';
        this.namespace = {};
        this.policies = {};
    }
    set _namespaceName(name) {
        this.namespaceName = name;
        this.icon = NAMESPACE_PROPS[name]?.icon;
        this.namespaceLabel = this.translateService.instant(NAMESPACE_PROPS[name]?.label) || name;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NamespaceItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: NamespaceItemComponent, isStandalone: true, selector: "app-namespace-item", inputs: { _namespaceName: ["namespaceName", "_namespaceName"], namespace: "namespace", policies: "policies" }, ngImport: i0, template: "<div class=\"d-flex-sm d-col-xs p-t-24 p-l-16 p-r-16 m-0 bg-level-1\">\n  <div\n    class=\"col-sm-3 m-b-24 col-xs-12 d-flex gap-16 text-default a-i-center j-c-center a-s-stretch\"\n  >\n    <div class=\"text-center d-col\">\n      <i\n        class=\"m-b-8 icon-40 c8y-icon-duocolor\"\n        [c8yIcon]=\"icon\"\n      ></i>\n      <span class=\"tag tag--info\">{{ 'Service' | translate }}</span>\n    </div>\n    <span\n      class=\"h4\"\n      data-cy=\"namespace-item--namespace-label\"\n    >\n      {{ namespaceLabel }}\n    </span>\n  </div>\n  <div class=\"col-sm-3 m-b-24 col-xs-12 a-i-stretch d-flex\">\n    <app-namespace-item-card\n      [serviceLabel]=\"namespaceLabel\"\n      [dataType]=\"'topics'\"\n      [limit]=\"namespace?.topics?.limit\"\n      [value]=\"namespace?.topics?.count\"\n    ></app-namespace-item-card>\n  </div>\n  <div class=\"col-sm-3 m-b-24 col-xs-12 a-i-stretch d-flex\">\n    <app-namespace-item-card\n      [serviceLabel]=\"namespaceLabel\"\n      [dataType]=\"'publishers'\"\n      [value]=\"namespace?.publishers?.count\"\n    ></app-namespace-item-card>\n  </div>\n  <div class=\"col-sm-3 m-b-24 col-xs-12 a-i-stretch d-flex\">\n    <app-namespace-item-card\n      [serviceLabel]=\"namespaceLabel\"\n      [dataType]=\"'subscribers'\"\n      [value]=\"namespace?.subscribers?.count\"\n    ></app-namespace-item-card>\n  </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: NamespaceItemCardComponent, selector: "app-namespace-item-card", inputs: ["serviceLabel", "limit", "dataType", "value"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NamespaceItemComponent, decorators: [{
            type: Component,
            args: [{ selector: 'app-namespace-item', standalone: true, imports: [CommonModule, IconDirective, NamespaceItemCardComponent, C8yTranslatePipe], template: "<div class=\"d-flex-sm d-col-xs p-t-24 p-l-16 p-r-16 m-0 bg-level-1\">\n  <div\n    class=\"col-sm-3 m-b-24 col-xs-12 d-flex gap-16 text-default a-i-center j-c-center a-s-stretch\"\n  >\n    <div class=\"text-center d-col\">\n      <i\n        class=\"m-b-8 icon-40 c8y-icon-duocolor\"\n        [c8yIcon]=\"icon\"\n      ></i>\n      <span class=\"tag tag--info\">{{ 'Service' | translate }}</span>\n    </div>\n    <span\n      class=\"h4\"\n      data-cy=\"namespace-item--namespace-label\"\n    >\n      {{ namespaceLabel }}\n    </span>\n  </div>\n  <div class=\"col-sm-3 m-b-24 col-xs-12 a-i-stretch d-flex\">\n    <app-namespace-item-card\n      [serviceLabel]=\"namespaceLabel\"\n      [dataType]=\"'topics'\"\n      [limit]=\"namespace?.topics?.limit\"\n      [value]=\"namespace?.topics?.count\"\n    ></app-namespace-item-card>\n  </div>\n  <div class=\"col-sm-3 m-b-24 col-xs-12 a-i-stretch d-flex\">\n    <app-namespace-item-card\n      [serviceLabel]=\"namespaceLabel\"\n      [dataType]=\"'publishers'\"\n      [value]=\"namespace?.publishers?.count\"\n    ></app-namespace-item-card>\n  </div>\n  <div class=\"col-sm-3 m-b-24 col-xs-12 a-i-stretch d-flex\">\n    <app-namespace-item-card\n      [serviceLabel]=\"namespaceLabel\"\n      [dataType]=\"'subscribers'\"\n      [value]=\"namespace?.subscribers?.count\"\n    ></app-namespace-item-card>\n  </div>\n</div>\n" }]
        }], propDecorators: { _namespaceName: [{
                type: Input,
                args: ['namespaceName']
            }], namespace: [{
                type: Input
            }], policies: [{
                type: Input
            }] } });

class NamespaceListComponent {
    constructor() {
        this.alertService = inject(AlertService);
        this.appState = inject(AppStateService);
        this.namespacesService = inject(MessagingNamespacesService);
        this.loading = true;
    }
    async ngOnInit() {
        await this.reload();
    }
    async reload() {
        this.loading = true;
        try {
            const currentTenantId = this.appState.currentTenant.value.name;
            this.namespacesDetails = await this.namespacesService.getNamespacesDetails(currentTenantId);
        }
        catch (e) {
            this.alertService.addServerFailure(e);
        }
        finally {
            this.loading = false;
        }
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NamespaceListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: NamespaceListComponent, isStandalone: true, selector: "app-namespace-list", ngImport: i0, template: "<c8y-title>{{ 'Messaging service' | translate }}</c8y-title>\n\n<c8y-breadcrumb>\n  <c8y-breadcrumb-item\n    [icon]=\"'monitoring'\"\n    [label]=\"'Monitoring' | translate\"\n  ></c8y-breadcrumb-item>\n  <c8y-breadcrumb-item [label]=\"'Messaging service' | translate\"></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n\n<c8y-action-bar-item [placement]=\"'right'\">\n  <li>\n    <a\n      class=\"btn btn-link\"\n      title=\"{{ 'Reload' | translate }}\"\n      (click)=\"reload()\"\n    >\n      <i\n        c8yIcon=\"refresh\"\n        [ngClass]=\"{ 'icon-spin': loading }\"\n      ></i>\n      {{ 'Reload' | translate }}\n    </a>\n  </li>\n</c8y-action-bar-item>\n\n<c8y-help src=\"/docs/standard-tenant/monitoring/#messaging-service\"></c8y-help>\n\n<div\n  class=\"interact-grid\"\n  *ngIf=\"!loading; else loadingTemplate\"\n>\n  <a\n    class=\"card\"\n    *ngFor=\"let namespace of namespacesDetails\"\n    [routerLink]=\"['namespace', namespace.id]\"\n  >\n    <app-namespace-item\n      [namespaceName]=\"namespace.id\"\n      [namespace]=\"namespace.namespace\"\n    ></app-namespace-item>\n  </a>\n</div>\n\n<ng-template #loadingTemplate>\n  <c8y-loading></c8y-loading>\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: HeaderModule }, { kind: "component", type: i1$2.TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { kind: "ngmodule", type: HelpModule }, { kind: "component", type: i1$2.HelpComponent, selector: "c8y-help", inputs: ["src", "isCollapsed", "priority", "icon"] }, { kind: "ngmodule", type: C8yTranslateModule }, { kind: "component", type: NamespaceItemComponent, selector: "app-namespace-item", inputs: ["namespaceName", "namespace", "policies"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: BreadcrumbModule }, { kind: "component", type: i1$2.BreadcrumbComponent, selector: "c8y-breadcrumb" }, { kind: "component", type: i1$2.BreadcrumbItemComponent, selector: "c8y-breadcrumb-item", inputs: ["icon", "translate", "label", "path", "injector"] }, { kind: "component", type: ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "pipe", type: i1$2.C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: NamespaceListComponent, decorators: [{
            type: Component,
            args: [{ selector: 'app-namespace-list', imports: [
                        CommonModule,
                        HeaderModule,
                        HelpModule,
                        C8yTranslateModule,
                        NamespaceItemComponent,
                        RouterLink,
                        BreadcrumbModule,
                        ActionBarItemComponent,
                        IconDirective,
                        LoadingComponent
                    ], standalone: true, template: "<c8y-title>{{ 'Messaging service' | translate }}</c8y-title>\n\n<c8y-breadcrumb>\n  <c8y-breadcrumb-item\n    [icon]=\"'monitoring'\"\n    [label]=\"'Monitoring' | translate\"\n  ></c8y-breadcrumb-item>\n  <c8y-breadcrumb-item [label]=\"'Messaging service' | translate\"></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n\n<c8y-action-bar-item [placement]=\"'right'\">\n  <li>\n    <a\n      class=\"btn btn-link\"\n      title=\"{{ 'Reload' | translate }}\"\n      (click)=\"reload()\"\n    >\n      <i\n        c8yIcon=\"refresh\"\n        [ngClass]=\"{ 'icon-spin': loading }\"\n      ></i>\n      {{ 'Reload' | translate }}\n    </a>\n  </li>\n</c8y-action-bar-item>\n\n<c8y-help src=\"/docs/standard-tenant/monitoring/#messaging-service\"></c8y-help>\n\n<div\n  class=\"interact-grid\"\n  *ngIf=\"!loading; else loadingTemplate\"\n>\n  <a\n    class=\"card\"\n    *ngFor=\"let namespace of namespacesDetails\"\n    [routerLink]=\"['namespace', namespace.id]\"\n  >\n    <app-namespace-item\n      [namespaceName]=\"namespace.id\"\n      [namespace]=\"namespace.namespace\"\n    ></app-namespace-item>\n  </a>\n</div>\n\n<ng-template #loadingTemplate>\n  <c8y-loading></c8y-loading>\n</ng-template>\n" }]
        }] });

class TimeToLivePipe {
    constructor(translateService) {
        this.translateService = translateService;
    }
    /**
     * Transform time in seconds to human readable format.
     * @param seconds time in seconds
     * @returns human readable time period
     */
    transform(seconds) {
        if (seconds == null || isNaN(seconds)) {
            return '-';
        }
        if (seconds === -1) {
            return this.translateService.instant(gettext('Unlimited` time-to-live period`'));
        }
        const minutes = Math.floor(seconds / 60);
        const hours = Math.floor(minutes / 60);
        const days = Math.floor(hours / 24);
        if (days >= 1) {
            const remainingHours = hours % 24;
            if (days === 1) {
                if (remainingHours === 0) {
                    return this.translateService.instant(gettext('1 day'));
                }
                if (remainingHours === 1) {
                    return this.translateService.instant(gettext('1 day 1 hour'));
                }
                return this.translateService.instant(gettext('1 day {{hours}} hours'), {
                    hours: remainingHours
                });
            }
            else {
                if (remainingHours === 0) {
                    return this.translateService.instant(gettext('{{days}} days'), { days });
                }
                if (remainingHours === 1) {
                    return this.translateService.instant(gettext('{{days}} days 1 hour'), { days });
                }
                return this.translateService.instant(gettext('{{days}} days {{hours}} hours'), {
                    days,
                    hours: remainingHours
                });
            }
        }
        if (hours > 0) {
            const remainingMinutes = minutes % 60;
            if (hours === 1) {
                if (remainingMinutes === 0) {
                    return this.translateService.instant(gettext('1 hour'));
                }
                if (remainingMinutes === 1) {
                    return this.translateService.instant(gettext('1 hour 1 minute'));
                }
                return this.translateService.instant(gettext('1 hour {{minutes}} minutes'), {
                    minutes: remainingMinutes
                });
            }
            else {
                if (remainingMinutes === 0) {
                    return this.translateService.instant(gettext('{{hours}} hours'), { hours });
                }
                if (remainingMinutes === 1) {
                    return this.translateService.instant(gettext('{{hours}} hours 1 minute'), { hours });
                }
                return this.translateService.instant(gettext('{{hours}} hours {{minutes}} minutes'), {
                    hours,
                    minutes: remainingMinutes
                });
            }
        }
        if (minutes > 0) {
            const remainingSeconds = seconds % 60;
            if (minutes === 1) {
                if (remainingSeconds === 0) {
                    return this.translateService.instant(gettext('1 minute'));
                }
                if (remainingSeconds === 1) {
                    return this.translateService.instant(gettext('1 minute 1 second'));
                }
                return this.translateService.instant(gettext('1 minute {{seconds}} seconds'), {
                    seconds: remainingSeconds
                });
            }
            else {
                if (remainingSeconds === 0) {
                    return this.translateService.instant(gettext('{{minutes}} minutes'), { minutes });
                }
                if (remainingSeconds === 1) {
                    return this.translateService.instant(gettext('{{minutes}} minutes 1 second'), {
                        minutes
                    });
                }
                return this.translateService.instant(gettext('{{minutes}} minutes {{seconds}} seconds'), {
                    minutes,
                    seconds: remainingSeconds
                });
            }
        }
        if (seconds === 1) {
            return this.translateService.instant(gettext('1 second'));
        }
        else {
            return this.translateService.instant(gettext('{{seconds}} seconds'), { seconds });
        }
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TimeToLivePipe, deps: [{ token: i1$3.TranslateService }], target: i0.ɵɵFactoryTarget.Pipe }); }
    static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: TimeToLivePipe, isStandalone: true, name: "timeToLive" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TimeToLivePipe, decorators: [{
            type: Pipe,
            args: [{
                    name: 'timeToLive',
                    standalone: true
                }]
        }], ctorParameters: () => [{ type: i1$3.TranslateService }] });

class MessagingTopicsService extends Service {
    constructor(client) {
        super(client);
        this.baseUrl = '/service/messaging-management';
        this.listUrl = 'tenants';
    }
    async list(filter) {
        const headers = { accept: 'application/json' };
        const { tenant, namespace, ...params } = filter;
        const url = `/${this.listUrl}/${tenant}/namespaces/${namespace}/topics`;
        const res = await this.fetch(url, this.changeFetchOptions({ headers, params }, url));
        const topicList = (await res.json());
        const data = topicList.topics;
        const paging = this.getPaging(topicList, filter);
        return { res, data, paging };
    }
    async detail(filter) {
        const headers = { accept: 'application/json' };
        const { tenant, namespace, topic, type } = filter;
        const url = `/${this.listUrl}/${tenant}/namespaces/${namespace}/topics/${topic}/types/${type}`;
        const res = await this.fetch(url, this.changeFetchOptions({ headers }, url));
        const data = await res.json();
        return { res, data };
    }
    getPaging(topicList, filter) {
        if (topicList.pageStatistics) {
            const { currentPage, totalPages } = topicList.pageStatistics;
            const statistics = {
                ...topicList.pageStatistics,
                nextPage: currentPage < totalPages ? currentPage + 1 : null,
                prevPage: currentPage > 1 ? currentPage - 1 : null
            };
            return new Paging(this, statistics, filter);
        }
        return null;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingTopicsService, deps: [{ token: i1.FetchClient }], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingTopicsService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingTopicsService, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }], ctorParameters: () => [{ type: i1.FetchClient }] });

class TopicsDataGridService {
    constructor() {
        this.topicsService = inject(MessagingTopicsService);
    }
    getColumns() {
        return [
            this.createColumn({
                name: 'name',
                header: gettext('Name'),
                path: 'name',
                filterable: true,
                filteringConfig: {
                    fields: [
                        {
                            key: 'name',
                            type: 'input',
                            props: {
                                label: gettext('Filter topics by partial name'),
                                placeholder: 'myTopic',
                                required: true
                            }
                        }
                    ],
                    getFilter(model) {
                        return model.name;
                    }
                }
            }),
            this.createColumn({
                name: 'msgRateIn',
                header: gettext('Message rate in (msg/s)'),
                path: 'msgRateIn'
            }),
            this.createColumn({
                name: 'msgRateOut',
                header: gettext('Message rate out (msg/s)'),
                path: 'msgRateOut'
            }),
            this.createColumn({
                name: 'subscribers',
                header: gettext('Subscribers'),
                path: 'subscribers'
            }),
            this.createColumn({
                name: 'backlogSize',
                header: gettext('Message backlog'),
                path: 'backlogSize'
            }),
            this.createColumn({
                name: 'backlogUsagePercentage',
                header: gettext('Used backlog'),
                path: 'backlogUsagePercentage',
                sortOrder: 'desc'
            })
        ];
    }
    async getServerSideData(tenantId, namespaceId, dataSourceModifier) {
        const topicFilters = this.getTopicFilters(tenantId, namespaceId, dataSourceModifier);
        const { res, data, paging } = await this.topicsService.list(topicFilters);
        const filteredSize = paging.totalElements;
        const size = (await this.topicsService.list({
            ...topicFilters,
            currentPage: 1,
            pageSize: 1
        })).paging.totalPages;
        return {
            res,
            data,
            paging,
            size,
            filteredSize
        };
    }
    createColumn(columnProps) {
        const column = new BaseColumn();
        Object.assign(column, columnProps);
        return column;
    }
    getTopicFilters(tenantId, namespaceId, dataSourceModifier) {
        const topicFilters = {
            tenant: tenantId,
            namespace: namespaceId,
            currentPage: dataSourceModifier.pagination.currentPage,
            pageSize: dataSourceModifier.pagination.pageSize
        };
        return dataSourceModifier.columns.reduce((topicFilters, column) => {
            if (column.filterable) {
                if (column.filterPredicate) {
                    topicFilters[column.path] = column.filterPredicate;
                }
                if (column.externalFilterQuery) {
                    topicFilters[column.path] = column.filteringConfig.getFilter(column.externalFilterQuery);
                }
            }
            if (column.sortable && column.sortOrder) {
                const sortPath = column.sortingConfig?.pathSortingConfigs?.[0]?.path || column.path;
                topicFilters.sort = `${sortPath},${column.sortOrder}`;
            }
            return topicFilters;
        }, topicFilters);
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TopicsDataGridService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TopicsDataGridService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TopicsDataGridService, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }] });

class TopicListViewComponent {
    constructor() {
        this.route = inject(ActivatedRoute);
        this.appState = inject(AppStateService);
        this.namespacesService = inject(MessagingNamespacesService);
        this.topicsDataGridService = inject(TopicsDataGridService);
        this.translateService = inject(TranslateService);
        this.destroyRef = inject(DestroyRef);
        this.loading$ = new BehaviorSubject(false);
        this.refresh = new EventEmitter();
        this.tenantId$ = this.appState.currentTenant.pipe(map(tenant => tenant.name));
        this.namespaceId$ = this.route.params.pipe(map(params => params['namespace']));
        this.namespaceLabel$ = this.namespaceId$.pipe(map(namespaceId => this.translateService.instant(NAMESPACE_PROPS[namespaceId].label)));
        this.icon$ = this.namespaceId$.pipe(map(namespaceId => NAMESPACE_PROPS[namespaceId].icon));
        this.namespaceDetails$ = combineLatest([this.tenantId$, this.namespaceId$, this.refresh]).pipe(tap(() => this.loading$.next(true)), switchMap(([tenantId, namespaceId]) => this.namespacesService.getNamespaceDetails(tenantId, namespaceId)), tap(() => this.loading$.next(false)), shareReplay(1));
        this.tableTitle = gettext('Topics');
        this.loadingItemsLabel = gettext('Loading topics...');
        this.loadMoreItemsLabel = gettext('Load more topics');
        this.noResultsMessage = gettext('No matching topics found.');
        this.noResultsSubtitle = gettext('Refine your search terms or check your spelling.');
        this.noDataMessage = gettext('No topics to display.');
        this.noDataSubtitle = gettext('Create new topics to monitor them here.');
        this.columns = this.topicsDataGridService.getColumns();
        this.pagination = {
            pageSize: 20,
            currentPage: 1
        };
        this.serverSideDataCallback = this.onDataSourceModifier.bind(this);
    }
    async onDataSourceModifier(dataSourceModifier) {
        return firstValueFrom(combineLatest([this.tenantId$, this.namespaceId$]).pipe(switchMap(([tenantId, namespaceId]) => this.topicsDataGridService.getServerSideData(tenantId, namespaceId, dataSourceModifier))));
    }
    ngAfterViewInit() {
        this.route.params
            .pipe(takeUntilDestroyed(this.destroyRef))
            .subscribe(() => this.refresh.emit());
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TopicListViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: TopicListViewComponent, isStandalone: true, selector: "app-topic-list-view", ngImport: i0, template: "<c8y-title>{{ namespaceLabel$ | async }}</c8y-title>\n\n<c8y-breadcrumb>\n  <c8y-breadcrumb-item\n    [icon]=\"'monitoring'\"\n    [label]=\"'Monitoring' | translate\"\n  ></c8y-breadcrumb-item>\n  <c8y-breadcrumb-item\n    [label]=\"'Messaging service' | translate\"\n    [path]=\"'/monitoring/messaging-service'\"\n  ></c8y-breadcrumb-item>\n  <c8y-breadcrumb-item [label]=\"namespaceLabel$ | async\"></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n\n<c8y-action-bar-item [placement]=\"'right'\">\n  <a\n    class=\"btn btn-link\"\n    title=\"{{ 'Reload' | translate }}\"\n    (click)=\"refresh.emit()\"\n  >\n    <i\n      c8yIcon=\"refresh\"\n      [ngClass]=\"{ 'icon-spin': loading$ | async }\"\n    ></i>\n    {{ 'Reload' | translate }}\n  </a>\n</c8y-action-bar-item>\n\n<div class=\"card content-fullpage d-flex d-col\">\n  <div class=\"bg-level-1 separator-bottom flex-no-shrink\">\n    <div\n      class=\"card-block\"\n      style=\"min-height: 172px\"\n    >\n      <div\n        class=\"col-md-4 m-b-24 col-xs-12 d-flex p-t-24 gap-16 text-default a-i-center a-s-stretch\"\n      >\n        <div class=\"text-center d-col\">\n          <i\n            class=\"m-b-8 icon-40 c8y-icon-duocolor\"\n            [c8yIcon]=\"icon$ | async\"\n          ></i>\n          <span class=\"tag tag--info\">{{ 'Service' | translate }}</span>\n        </div>\n        <span class=\"h4 text-break-all\">{{ namespaceLabel$ | async }}</span>\n      </div>\n      <div class=\"col-md-4\">\n        <fieldset class=\"c8y-fieldset c8y-fieldset--lg\">\n          <legend>\n            {{ 'Service usage/limits' | translate }}\n          </legend>\n\n          @if (loading$ | async) {\n            <c8y-loading></c8y-loading>\n          } @else {\n            @let namespace = (namespaceDetails$ | async)?.namespace;\n            @if (namespace) {\n              <ul class=\"list-unstyled small animated fadeIn\">\n                <li\n                  class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\"\n                  data-cy=\"topic-list-view--topics\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">{{ 'Topics' | translate }}</label>\n                  @if (namespace?.topics?.limit !== 0) {\n                    <app-usage\n                      [count]=\"namespace?.topics?.count\"\n                      [limit]=\"namespace?.topics?.limit\"\n                    ></app-usage>\n                    <span class=\"m-l-16\">\n                      {{ namespace?.topics?.count | number }} /\n                      {{ namespace?.topics?.limit | backlogQuotaLimit }}\n                    </span>\n                  } @else {\n                    <span class=\"m-l-16\">{{ namespace?.topics?.count | number }}</span>\n                  }\n                </li>\n\n                <li\n                  class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\"\n                  data-cy=\"topic-list-view--subscribers\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">{{ 'Subscribers' | translate }}</label>\n                  <span class=\"m-l-16\">\n                    {{ namespace?.subscribers?.count | number }}\n                  </span>\n                </li>\n\n                <li\n                  class=\"p-t-4 p-b-4 d-flex text-nowrap\"\n                  data-cy=\"topic-list-view--publishers\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">{{ 'Publishers' | translate }}</label>\n                  <span class=\"m-l-16\">\n                    {{ namespace?.publishers?.count | number }}\n                  </span>\n                </li>\n              </ul>\n            }\n          }\n        </fieldset>\n      </div>\n      <div class=\"col-md-4\">\n        <fieldset class=\"c8y-fieldset c8y-fieldset--lg\">\n          <legend>{{ 'Service message backlog limits' | translate }}</legend>\n\n          @if (loading$ | async) {\n            <c8y-loading></c8y-loading>\n          } @else {\n            @let policies = (namespaceDetails$ | async)?.policies;\n            @if (policies) {\n              <ul class=\"list-unstyled small animated fadeIn\">\n                <li\n                  class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\"\n                  data-cy=\"topic-list-view--backlog-quota\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">\n                    {{ 'Backlog quota (per topic)' | translate }}\n                  </label>\n                  <span\n                    title=\"{{\n                      policies?.backlogQuota?.limit > 0\n                        ? (policies.backlogQuota.limit | bytes: 0 : true)\n                        : '-'\n                    }}\"\n                  >\n                    {{\n                      policies?.backlogQuota?.limit > 0\n                        ? (policies.backlogQuota.limit | bytes: 0)\n                        : '-'\n                    }}\n                  </span>\n                </li>\n\n                <li\n                  class=\"p-t-4 p-b-4 d-flex text-nowrap\"\n                  data-cy=\"topic-list-view--backlog-ttl\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">\n                    {{ 'Backlog time to live (TTL)' | translate }}\n                  </label>\n                  <span>{{ policies?.messageTTL | timeToLive }}</span>\n                </li>\n              </ul>\n            }\n          }\n        </fieldset>\n      </div>\n    </div>\n  </div>\n\n  <c8y-data-grid\n    class=\"d-contents\"\n    [title]=\"tableTitle | translate\"\n    [loadingItemsLabel]=\"loadingItemsLabel | translate\"\n    [loadMoreItemsLabel]=\"loadMoreItemsLabel | translate\"\n    [columns]=\"columns\"\n    [pagination]=\"pagination\"\n    [serverSideDataCallback]=\"serverSideDataCallback\"\n    [refresh]=\"refresh\"\n    [hideReload]=\"true\"\n  >\n    <c8y-ui-empty-state\n      [icon]=\"stats?.size > 0 ? 'search' : 'day-view'\"\n      [title]=\"stats?.size > 0 ? (noResultsMessage | translate) : (noDataMessage | translate)\"\n      [subtitle]=\"stats?.size > 0 ? (noResultsSubtitle | translate) : (noDataSubtitle | translate)\"\n      *emptyStateContext=\"let stats\"\n      [horizontal]=\"stats?.size > 0\"\n    ></c8y-ui-empty-state>\n\n    <c8y-column name=\"name\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <a\n          title=\"{{ context.value }}\"\n          [routerLink]=\"['topic', context.item.id]\"\n        >\n          {{ context.value }}\n        </a>\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"msgRateIn\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <span title=\"{{ context.value | number }}\">\n          {{ context.value | number }}\n        </span>\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"msgRateOut\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <span title=\"{{ context.value | number }}\">\n          {{ context.value | number }}\n        </span>\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"subscribers\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <a\n          title=\"{{ context.value | number }}\"\n          [routerLink]=\"['topic', context.item.id, 'subscribers']\"\n        >\n          {{ context.value | number }}\n        </a>\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"backlogSize\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <span title=\"{{ context.value | bytes: 2 : true }}\">\n          {{ context.value | bytes: 2 }}\n        </span>\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"backlogUsagePercentage\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <span title=\"{{ context.value / 100 | percent: '1.0-2' }}\">\n          {{ context.value / 100 | percent: '1.0-2' }}\n        </span>\n      </ng-container>\n    </c8y-column>\n  </c8y-data-grid>\n</div>\n", dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: HeaderModule }, { kind: "component", type: i1$2.TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { kind: "ngmodule", type: DataGridModule }, { kind: "directive", type: i1$2.CellRendererDefDirective, selector: "[c8yCellRendererDef]" }, { kind: "directive", type: i1$2.ColumnDirective, selector: "c8y-column", inputs: ["name"] }, { kind: "component", type: i1$2.DataGridComponent, selector: "c8y-data-grid", inputs: ["title", "loadMoreItemsLabel", "loadingItemsLabel", "showSearch", "refresh", "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: UsageComponent, selector: "app-usage", inputs: ["count", "limit", "percentage"] }, { kind: "ngmodule", type: BreadcrumbModule }, { kind: "component", type: i1$2.BreadcrumbComponent, selector: "c8y-breadcrumb" }, { kind: "component", type: i1$2.BreadcrumbItemComponent, selector: "c8y-breadcrumb-item", inputs: ["icon", "translate", "label", "path", "injector"] }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "component", type: ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "directive", type: EmptyStateContextDirective, selector: "[emptyStateContext]" }, { kind: "pipe", type: DecimalPipe, name: "number" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: BacklogQuotaLimitPipe, name: "backlogQuotaLimit" }, { kind: "pipe", type: BytesPipe, name: "bytes" }, { kind: "pipe", type: TimeToLivePipe, name: "timeToLive" }, { kind: "pipe", type: PercentPipe, name: "percent" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TopicListViewComponent, decorators: [{
            type: Component,
            args: [{ imports: [
                        RouterLink,
                        HeaderModule,
                        DataGridModule,
                        DecimalPipe,
                        C8yTranslatePipe,
                        BacklogQuotaLimitPipe,
                        UsageComponent,
                        BytesPipe,
                        TimeToLivePipe,
                        BreadcrumbModule,
                        LoadingComponent,
                        NgIf,
                        ActionBarItemComponent,
                        NgClass,
                        IconDirective,
                        PercentPipe,
                        EmptyStateComponent,
                        EmptyStateContextDirective,
                        AsyncPipe
                    ], selector: 'app-topic-list-view', standalone: true, template: "<c8y-title>{{ namespaceLabel$ | async }}</c8y-title>\n\n<c8y-breadcrumb>\n  <c8y-breadcrumb-item\n    [icon]=\"'monitoring'\"\n    [label]=\"'Monitoring' | translate\"\n  ></c8y-breadcrumb-item>\n  <c8y-breadcrumb-item\n    [label]=\"'Messaging service' | translate\"\n    [path]=\"'/monitoring/messaging-service'\"\n  ></c8y-breadcrumb-item>\n  <c8y-breadcrumb-item [label]=\"namespaceLabel$ | async\"></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n\n<c8y-action-bar-item [placement]=\"'right'\">\n  <a\n    class=\"btn btn-link\"\n    title=\"{{ 'Reload' | translate }}\"\n    (click)=\"refresh.emit()\"\n  >\n    <i\n      c8yIcon=\"refresh\"\n      [ngClass]=\"{ 'icon-spin': loading$ | async }\"\n    ></i>\n    {{ 'Reload' | translate }}\n  </a>\n</c8y-action-bar-item>\n\n<div class=\"card content-fullpage d-flex d-col\">\n  <div class=\"bg-level-1 separator-bottom flex-no-shrink\">\n    <div\n      class=\"card-block\"\n      style=\"min-height: 172px\"\n    >\n      <div\n        class=\"col-md-4 m-b-24 col-xs-12 d-flex p-t-24 gap-16 text-default a-i-center a-s-stretch\"\n      >\n        <div class=\"text-center d-col\">\n          <i\n            class=\"m-b-8 icon-40 c8y-icon-duocolor\"\n            [c8yIcon]=\"icon$ | async\"\n          ></i>\n          <span class=\"tag tag--info\">{{ 'Service' | translate }}</span>\n        </div>\n        <span class=\"h4 text-break-all\">{{ namespaceLabel$ | async }}</span>\n      </div>\n      <div class=\"col-md-4\">\n        <fieldset class=\"c8y-fieldset c8y-fieldset--lg\">\n          <legend>\n            {{ 'Service usage/limits' | translate }}\n          </legend>\n\n          @if (loading$ | async) {\n            <c8y-loading></c8y-loading>\n          } @else {\n            @let namespace = (namespaceDetails$ | async)?.namespace;\n            @if (namespace) {\n              <ul class=\"list-unstyled small animated fadeIn\">\n                <li\n                  class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\"\n                  data-cy=\"topic-list-view--topics\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">{{ 'Topics' | translate }}</label>\n                  @if (namespace?.topics?.limit !== 0) {\n                    <app-usage\n                      [count]=\"namespace?.topics?.count\"\n                      [limit]=\"namespace?.topics?.limit\"\n                    ></app-usage>\n                    <span class=\"m-l-16\">\n                      {{ namespace?.topics?.count | number }} /\n                      {{ namespace?.topics?.limit | backlogQuotaLimit }}\n                    </span>\n                  } @else {\n                    <span class=\"m-l-16\">{{ namespace?.topics?.count | number }}</span>\n                  }\n                </li>\n\n                <li\n                  class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\"\n                  data-cy=\"topic-list-view--subscribers\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">{{ 'Subscribers' | translate }}</label>\n                  <span class=\"m-l-16\">\n                    {{ namespace?.subscribers?.count | number }}\n                  </span>\n                </li>\n\n                <li\n                  class=\"p-t-4 p-b-4 d-flex text-nowrap\"\n                  data-cy=\"topic-list-view--publishers\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">{{ 'Publishers' | translate }}</label>\n                  <span class=\"m-l-16\">\n                    {{ namespace?.publishers?.count | number }}\n                  </span>\n                </li>\n              </ul>\n            }\n          }\n        </fieldset>\n      </div>\n      <div class=\"col-md-4\">\n        <fieldset class=\"c8y-fieldset c8y-fieldset--lg\">\n          <legend>{{ 'Service message backlog limits' | translate }}</legend>\n\n          @if (loading$ | async) {\n            <c8y-loading></c8y-loading>\n          } @else {\n            @let policies = (namespaceDetails$ | async)?.policies;\n            @if (policies) {\n              <ul class=\"list-unstyled small animated fadeIn\">\n                <li\n                  class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\"\n                  data-cy=\"topic-list-view--backlog-quota\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">\n                    {{ 'Backlog quota (per topic)' | translate }}\n                  </label>\n                  <span\n                    title=\"{{\n                      policies?.backlogQuota?.limit > 0\n                        ? (policies.backlogQuota.limit | bytes: 0 : true)\n                        : '-'\n                    }}\"\n                  >\n                    {{\n                      policies?.backlogQuota?.limit > 0\n                        ? (policies.backlogQuota.limit | bytes: 0)\n                        : '-'\n                    }}\n                  </span>\n                </li>\n\n                <li\n                  class=\"p-t-4 p-b-4 d-flex text-nowrap\"\n                  data-cy=\"topic-list-view--backlog-ttl\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">\n                    {{ 'Backlog time to live (TTL)' | translate }}\n                  </label>\n                  <span>{{ policies?.messageTTL | timeToLive }}</span>\n                </li>\n              </ul>\n            }\n          }\n        </fieldset>\n      </div>\n    </div>\n  </div>\n\n  <c8y-data-grid\n    class=\"d-contents\"\n    [title]=\"tableTitle | translate\"\n    [loadingItemsLabel]=\"loadingItemsLabel | translate\"\n    [loadMoreItemsLabel]=\"loadMoreItemsLabel | translate\"\n    [columns]=\"columns\"\n    [pagination]=\"pagination\"\n    [serverSideDataCallback]=\"serverSideDataCallback\"\n    [refresh]=\"refresh\"\n    [hideReload]=\"true\"\n  >\n    <c8y-ui-empty-state\n      [icon]=\"stats?.size > 0 ? 'search' : 'day-view'\"\n      [title]=\"stats?.size > 0 ? (noResultsMessage | translate) : (noDataMessage | translate)\"\n      [subtitle]=\"stats?.size > 0 ? (noResultsSubtitle | translate) : (noDataSubtitle | translate)\"\n      *emptyStateContext=\"let stats\"\n      [horizontal]=\"stats?.size > 0\"\n    ></c8y-ui-empty-state>\n\n    <c8y-column name=\"name\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <a\n          title=\"{{ context.value }}\"\n          [routerLink]=\"['topic', context.item.id]\"\n        >\n          {{ context.value }}\n        </a>\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"msgRateIn\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <span title=\"{{ context.value | number }}\">\n          {{ context.value | number }}\n        </span>\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"msgRateOut\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <span title=\"{{ context.value | number }}\">\n          {{ context.value | number }}\n        </span>\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"subscribers\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <a\n          title=\"{{ context.value | number }}\"\n          [routerLink]=\"['topic', context.item.id, 'subscribers']\"\n        >\n          {{ context.value | number }}\n        </a>\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"backlogSize\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <span title=\"{{ context.value | bytes: 2 : true }}\">\n          {{ context.value | bytes: 2 }}\n        </span>\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"backlogUsagePercentage\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <span title=\"{{ context.value / 100 | percent: '1.0-2' }}\">\n          {{ context.value / 100 | percent: '1.0-2' }}\n        </span>\n      </ng-container>\n    </c8y-column>\n  </c8y-data-grid>\n</div>\n" }]
        }] });

/**
 * If the topic is persistent or not. If false, the topic is not saved and will be deleted if there are no clients.
 */
var MessagingTopicType;
(function (MessagingTopicType) {
    MessagingTopicType["Persistent"] = "persistent";
    MessagingTopicType["NonPersistent"] = "non-persistent";
})(MessagingTopicType || (MessagingTopicType = {}));

class MessagingSubscribersService extends Service {
    constructor(client) {
        super(client);
        this.baseUrl = '/service/messaging-management';
        this.listUrl = 'tenants';
    }
    /**
     * Get the list of subscribers for a topic.
     *
     * @param filter Subscriber filters.
     */
    async list(filter) {
        const headers = { accept: 'application/json' };
        const params = omit(filter, ['tenant', 'namespace', 'topic', 'type']);
        const url = this.getBaseUrl(filter);
        const res = await this.fetch(url, this.changeFetchOptions({ headers, params }, url));
        const subscriberList = (await res.json());
        const data = subscriberList.subscribers;
        const paging = this.getPaging(subscriberList, filter);
        return { res, data, paging };
    }
    async delete(subscriberToDelete) {
        const method = 'DELETE';
        const url = this.getBaseUrl(subscriberToDelete) + `/${subscriberToDelete.name}`;
        const res = await this.fetch(url, this.changeFetchOptions({ method }, url));
        return { res, data: null };
    }
    getPaging(json, filter) {
        if (json.pageStatistics) {
            const { currentPage, totalPages } = json.pageStatistics;
            const statistics = {
                ...json.pageStatistics,
                nextPage: currentPage < totalPages ? currentPage + 1 : null,
                prevPage: currentPage > 1 ? currentPage - 1 : null
            };
            return new Paging(this, statistics, filter);
        }
        return null;
    }
    getBaseUrl({ tenant, namespace, topic, type }) {
        return `/${this.listUrl}/${tenant}/namespaces/${namespace}/topics/${topic}/types/${type}/subscribers`;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingSubscribersService, deps: [{ token: i1.FetchClient }], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingSubscribersService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingSubscribersService, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }], ctorParameters: () => [{ type: i1.FetchClient }] });

class TopicSubscribersDataGridService {
    constructor() {
        this.subscribersService = inject(MessagingSubscribersService);
        this.alertService = inject(AlertService);
        this.modalService = inject(ModalService);
        this.translateService = inject(TranslateService);
    }
    getColumns() {
        return [
            this.createColumn({
                name: 'name',
                header: gettext('Name'),
                path: 'name',
                filterable: true,
                filteringConfig: {
                    fields: [
                        {
                            key: 'name',
                            type: 'input',
                            props: {
                                label: gettext('Filter subscribers by partial name'),
                                placeholder: 'mySubscriber',
                                required: true
                            }
                        }
                    ],
                    getFilter(model) {
                        return model.name;
                    }
                }
            }),
            this.createColumn({
                name: 'activeClients',
                header: gettext('Connected clients'),
                path: 'activeClients'
            }),
            this.createColumn({
                name: 'messageAckRate',
                header: gettext('Acknowledgement rate (msg/s)'),
                path: 'messageAckRate'
            }),
            this.createColumn({
                name: 'lastAcknowledgeDate',
                header: gettext('Last acknowledged'),
                path: 'lastAcknowledgeDate',
                sortingConfig: {
                    pathSortingConfigs: [
                        {
                            path: 'lastAcknowledgeTimestamp'
                        }
                    ]
                }
            }),
            this.createColumn({
                name: 'unackMsgBacklog',
                header: gettext('Unacknowledged messages'),
                path: 'unackMsgBacklog'
            }),
            this.createColumn({
                name: 'backlogUsagePercentage',
                header: gettext('Used backlog'),
                path: 'backlogUsagePercentage',
                sortOrder: 'desc'
            })
        ];
    }
    async getServerSideData(tenantId, namespaceId, topicId, topicType, dataSourceModifier) {
        const subscriberFilters = this.getSubscriberFilters(tenantId, namespaceId, topicId, topicType, dataSourceModifier);
        const { res, data, paging } = await this.subscribersService.list(subscriberFilters);
        const filteredSize = paging.totalElements;
        const size = (await this.subscribersService.list({
            ...subscriberFilters,
            currentPage: 1,
            pageSize: 1
        })).paging.totalPages;
        return {
            res,
            data: data.map(subscriber => ({
                ...subscriber,
                lastAcknowledgeDate: this.getLastAcknowledgeDate(subscriber),
                id: subscriber.name
            })),
            paging,
            size,
            filteredSize
        };
    }
    async unsubscribeSubscriber(subscriber, refreshCallback) {
        const confirmationTitle = gettext('Unsubscribe "{{ subscriberName }}"?');
        const confirmationMessage = subscriber.activeClients > 0
            ? gettext('You are about to unsubscribe "{{ subscriberName }}" which has active clients. Do you want to proceed?')
            : gettext('You are about to unsubscribe "{{ subscriberName }}". Do you want to proceed?');
        try {
            const subscriberName = subscriber.name;
            await this.modalService.confirm(this.translateService.instant(confirmationTitle, {
                subscriberName
            }), this.translateService.instant(confirmationMessage, {
                subscriberName
            }), Status.DANGER, {
                ok: gettext('Unsubscribe')
            });
        }
        catch (e) {
            // cancel
            return;
        }
        try {
            await this.subscribersService.delete(subscriber);
            this.alertService.success(this.translateService.instant(gettext('Subscriber "{{ subscriberName }}" unsubscribed.'), {
                subscriberName: subscriber.name
            }));
        }
        catch (error) {
            this.alertService.addServerFailure(error);
        }
        refreshCallback();
    }
    async bulkUnsubscribeSubscribers(subscribers, refreshCallback) {
        const anySubscriberHasActiveClients = subscribers.some(subscriber => subscriber.activeClients > 0);
        const confirmationTitle = gettext('Unsubscribe {{ numberOfSubscribers }} subscribers?');
        const confirmationMessage = anySubscriberHasActiveClients
            ? gettext('You are about to unsubscribe {{ numberOfSubscribers }} subscribers. Some of them have active clients. Do you want to proceed?')
            : gettext('You are about to unsubscribe {{ numberOfSubscribers }} subscribers. Do you want to proceed?');
        try {
            const numberOfSubscribers = subscribers.length;
            await this.modalService.confirm(this.translateService.instant(confirmationTitle, {
                numberOfSubscribers
            }), this.translateService.instant(confirmationMessage, {
                numberOfSubscribers
            }), Status.DANGER, {
                ok: gettext('Unsubscribe')
            });
        }
        catch (e) {
            // cancel
            return;
        }
        const results = await Promise.allSettled(subscribers.map(sub => this.subscribersService
            .delete(sub)
            .then(() => ({
            success: true,
            name: sub.name
        }))
            .catch(error => ({
            success: false,
            name: sub.name,
            error
        }))));
        const failed = results
            .filter(result => result.status === 'fulfilled' && result.value.success === false)
            .map(result => result.value);
        const succeeded = results
            .filter(result => result.status === 'fulfilled' && result.value.success === true)
            .map(result => result.value);
        if (failed.length === 0) {
            this.alertService.success(this.translateService.instant(gettext('{{ numberOfSubscribers }} subscribers unsubscribed.'), { numberOfSubscribers: succeeded.length }));
        }
        else if (succeeded.length === 0) {
            const errorMessage = this.translateService.instant(gettext('Failed to unsubscribe {{ numberOfSubscribers }} subscribers.'), {
                numberOfSubscribers: failed.length
            });
            const details = `${failed.map(f => `'${f.name}': ${f.error.data.message}`).join('\n')}`;
            this.alertService.danger(errorMessage, details);
        }
        else {
            // Some succeeded, some failed
            const nonUnsubscribedSubscribers = failed.map(s => s.name).join(', ');
            const message = this.translateService.instant(gettext(`Successfully unsubscribed {{ count }} subscribers.<br/>Failed to unsubscribe: {{ nonUnsubscribedSubscribers }}.`), { count: succeeded.length, nonUnsubscribedSubscribers });
            const details = `${failed.map(f => `'${f.name}': ${f.error.data.message}`).join('\n')}`;
            this.alertService.warning(message, details);
        }
        refreshCallback();
    }
    createColumn(columnProps) {
        const column = new BaseColumn();
        Object.assign(column, columnProps);
        return column;
    }
    getSubscriberFilters(tenantId, namespaceId, topicId, topicType, dataSourceModifier) {
        const subscriberFilters = {
            tenant: tenantId,
            namespace: namespaceId,
            topic: topicId,
            type: topicType,
            currentPage: dataSourceModifier.pagination.currentPage,
            pageSize: dataSourceModifier.pagination.pageSize
        };
        return dataSourceModifier.columns.reduce((subscriberFilters, column) => {
            if (column.filterable) {
                if (column.filterPredicate) {
                    subscriberFilters[column.path] = column.filterPredicate;
                }
                if (column.externalFilterQuery) {
                    subscriberFilters[column.path] = column.filteringConfig.getFilter(column.externalFilterQuery);
                }
            }
            if (column.sortable && column.sortOrder) {
                const sortPath = column.sortingConfig?.pathSortingConfigs?.[0]?.path || column.path;
                subscriberFilters.sort = `${sortPath},${column.sortOrder}`;
            }
            return subscriberFilters;
        }, subscriberFilters);
    }
    getLastAcknowledgeDate(subscriber) {
        const lastAcknowledgeDate = new Date(subscriber.lastAcknowledgeTimestamp);
        return lastAcknowledgeDate.getTime() > 0 ? lastAcknowledgeDate : null;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TopicSubscribersDataGridService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TopicSubscribersDataGridService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TopicSubscribersDataGridService, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }] });

class TopicSubscribersViewComponent {
    constructor() {
        this.route = inject(ActivatedRoute);
        this.appState = inject(AppStateService);
        this.namespacesService = inject(MessagingNamespacesService);
        this.topicsService = inject(MessagingTopicsService);
        this.topicSubscribersDataGridService = inject(TopicSubscribersDataGridService);
        this.translateService = inject(TranslateService);
        this.permissions = inject(Permissions);
        this.destroyRef = inject(DestroyRef);
        this.loading$ = new BehaviorSubject(false);
        this.refresh = new EventEmitter();
        this.tenantId$ = this.appState.currentTenant.pipe(map(tenant => tenant.name));
        this.namespaceId$ = this.route.params.pipe(map(params => params['namespace']));
        this.namespaceLabel$ = this.namespaceId$.pipe(map(namespaceId => this.translateService.instant(NAMESPACE_PROPS[namespaceId].label)));
        this.topicId$ = this.route.params.pipe(map(params => params['topic']));
        this.overview$ = combineLatest([this.tenantId$, this.namespaceId$, this.topicId$, this.refresh]).pipe(tap(() => this.loading$.next(true)), switchMap(async ([tenantId, namespaceId, topicId]) => ({
            topic: (await this.topicsService.detail({
                tenant: tenantId,
                namespace: namespaceId,
                topic: topicId,
                type: MessagingTopicType.Persistent
            })).data,
            policies: await this.namespacesService.getNamespacePolicies(tenantId, namespaceId)
        })), tap(() => this.loading$.next(false)), shareReplay(1));
        this.topicName$ = this.overview$.pipe(map(overview => overview.topic.name));
        this.tableTitle = gettext('Subscribers');
        this.loadingItemsLabel = gettext('Loading subscribers...');
        this.loadMoreItemsLabel = gettext('Load more subscribers');
        this.noResultsMessage = gettext('No matching subscribers found.');
        this.noResultsSubtitle = gettext('Refine your search terms or check your spelling.');
        this.noDataMessage = gettext('No subscribers to display.');
        this.noDataSubtitle = gettext('Create new subscribers to monitor them here.');
        this.columns = this.topicSubscribersDataGridService.getColumns();
        this.pagination = {
            pageSize: 20,
            currentPage: 1
        };
        this.selectable = true;
        this.actionControls = [
            {
                type: 'unsubscribeSubscriber',
                icon: 'unsubscribe',
                text: gettext('Unsubscribe'),
                callback: (subscriber) => this.unsubscribeSubscriber(subscriber),
                showIf: () => this.permissions.hasRole(Permissions.ROLE_TENANT_MANAGEMENT_ADMIN)
            }
        ];
        this.bulkActionControls = [
            {
                type: 'unsubscribeSubscriber',
                icon: 'unsubscribe',
                text: gettext('Unsubscribe'),
                callback: (subscribersNames) => this.bulkUnsubscribeSubscribers(subscribersNames),
                showIf: () => this.permissions.hasRole(Permissions.ROLE_TENANT_MANAGEMENT_ADMIN)
            }
        ];
        this.serverSideDataCallback = this.onDataSourceModifier.bind(this);
    }
    async onDataSourceModifier(dataSourceModifier) {
        return firstValueFrom(combineLatest([this.tenantId$, this.namespaceId$, this.topicId$]).pipe(switchMap(([tenantId, namespaceId, topicId]) => this.topicSubscribersDataGridService.getServerSideData(tenantId, namespaceId, topicId, MessagingTopicType.Persistent, dataSourceModifier))));
    }
    ngAfterViewInit() {
        this.refresh.emit();
    }
    async unsubscribeSubscriber(subscriber) {
        const tenant = await firstValueFrom(this.tenantId$);
        const namespace = await firstValueFrom(this.namespaceId$);
        const topic = await firstValueFrom(this.topicId$);
        this.topicSubscribersDataGridService.unsubscribeSubscriber({
            ...subscriber,
            tenant,
            namespace,
            topic,
            type: MessagingTopicType.Persistent
        }, () => this.refresh.emit());
    }
    async bulkUnsubscribeSubscribers(subscribersNames) {
        const tenant = await firstValueFrom(this.tenantId$);
        const namespace = await firstValueFrom(this.namespaceId$);
        const topic = await firstValueFrom(this.topicId$);
        const allSubscribers = await firstValueFrom(this.dataGrid.dataSource.data$);
        const subscribers = subscribersNames.map(subscriberName => {
            const subscriber = allSubscribers.find(subscriber => subscriber.name === subscriberName);
            return {
                ...subscriber,
                tenant,
                namespace,
                topic,
                type: MessagingTopicType.Persistent
            };
        });
        this.topicSubscribersDataGridService.bulkUnsubscribeSubscribers(subscribers, () => this.refresh.emit());
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TopicSubscribersViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: TopicSubscribersViewComponent, isStandalone: true, selector: "app-topic-subscribers-view", viewQueries: [{ propertyName: "dataGrid", first: true, predicate: DataGridComponent, descendants: true, static: true }], ngImport: i0, template: "<c8y-title>{{ topicName$ | async }}</c8y-title>\n\n<c8y-breadcrumb>\n  <c8y-breadcrumb-item\n    [icon]=\"'monitoring'\"\n    [label]=\"'Monitoring' | translate\"\n  ></c8y-breadcrumb-item>\n  <c8y-breadcrumb-item\n    [label]=\"'Messaging service' | translate\"\n    [path]=\"'/monitoring/messaging-service'\"\n  ></c8y-breadcrumb-item>\n  <c8y-breadcrumb-item\n    [label]=\"namespaceLabel$ | async\"\n    [path]=\"'/monitoring/messaging-service/namespace/' + (namespaceId$ | async)\"\n  ></c8y-breadcrumb-item>\n  <c8y-breadcrumb-item [label]=\"topicName$ | async\"></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n\n<c8y-action-bar-item [placement]=\"'right'\">\n  <a\n    class=\"btn btn-link\"\n    title=\"{{ 'Reload' | translate }}\"\n    (click)=\"refresh.emit()\"\n  >\n    <i\n      c8yIcon=\"refresh\"\n      [ngClass]=\"{ 'icon-spin': loading$ | async }\"\n    ></i>\n    {{ 'Reload' | translate }}\n  </a>\n</c8y-action-bar-item>\n\n<div class=\"card content-fullpage d-flex d-col\">\n  <div class=\"bg-level-1 separator-bottom flex-no-shrink\">\n    <div class=\"card-block\">\n      <div\n        class=\"col-md-4 m-b-24 col-xs-12 d-flex p-t-24 gap-16 text-default a-i-center a-s-stretch\"\n      >\n        <div class=\"text-center d-col\">\n          <i\n            class=\"m-b-8 icon-40 c8y-icon-duocolor\"\n            [c8yIcon]=\"'day-view'\"\n          ></i>\n          <span class=\"tag tag--default\">{{ 'Topic' | translate }}</span>\n        </div>\n        <span class=\"h4\">{{ topicName$ | async }}</span>\n      </div>\n      <div class=\"col-md-4\">\n        <fieldset class=\"c8y-fieldset c8y-fieldset--lg\">\n          <legend translate>Topic usage</legend>\n\n          @if (loading$ | async) {\n            <c8y-loading></c8y-loading>\n          } @else {\n            @let overview = overview$ | async;\n            @if (overview) {\n              <ul class=\"list-unstyled small animated fadeIn\">\n                <li\n                  class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\"\n                  data-cy=\"topic-subscribers-view--active-subscribers\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">{{ 'Active subscribers' | translate }}</label>\n                  <span class=\"m-l-16\">{{ overview.topic.activeSubscribers | number }}</span>\n                </li>\n\n                <li\n                  class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\"\n                  data-cy=\"topic-subscribers-view--total-subscribers\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">{{ 'Total subscribers' | translate }}</label>\n                  <span class=\"m-l-16\">{{ overview.topic.subscribers | number }}</span>\n                </li>\n\n                <li\n                  class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\"\n                  data-cy=\"topic-subscribers-view--total-unacknowledged-messages\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">\n                    {{ 'Total unacknowledged messages' | translate }}\n                  </label>\n                  <span class=\"m-l-16\">{{ overview.topic.unackMsgBacklog | number }}</span>\n                </li>\n\n                <li\n                  class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\"\n                  data-cy=\"topic-subscribers-view--message-rate-in\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">\n                    {{ 'Message rate in' | translate }}\n                  </label>\n                  <span\n                    class=\"m-l-16\"\n                    ngNonBindable\n                    translate\n                    [translateParams]=\"{\n                      messagesPerSecond: overview.topic.msgRateIn | number\n                    }\"\n                  >\n                    {{ messagesPerSecond }} msg/s\n                  </span>\n                </li>\n\n                <li\n                  class=\"p-t-4 p-b-4 d-flex text-nowrap\"\n                  data-cy=\"topic-subscribers-view--message-rate-out\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">\n                    {{ 'Message rate out' | translate }}\n                  </label>\n                  <span\n                    class=\"m-l-16\"\n                    ngNonBindable\n                    translate\n                    [translateParams]=\"{\n                      messagesPerSecond: overview.topic.msgRateOut | number\n                    }\"\n                  >\n                    {{ messagesPerSecond }} msg/s\n                  </span>\n                </li>\n              </ul>\n            }\n          }\n        </fieldset>\n      </div>\n\n      <div class=\"col-md-4\">\n        <fieldset class=\"c8y-fieldset c8y-fieldset--lg\">\n          <legend translate>Topic message backlog</legend>\n\n          @if (loading$ | async) {\n            <c8y-loading></c8y-loading>\n          } @else {\n            @let overview = overview$ | async;\n            @if (overview) {\n              <ul class=\"list-unstyled small animated fadeIn\">\n                <li\n                  class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\"\n                  data-cy=\"topic-subscribers-view--backlog-usage\"\n                >\n                  <label\n                    class=\"small m-b-0 m-r-auto\"\n                    translate\n                  >\n                    Backlog usage\n                  </label>\n                  <app-usage [percentage]=\"overview.topic.backlogUsagePercentage\"></app-usage>\n                  <span class=\"m-l-16\">{{ overview.topic.backlogSize | bytes }}</span>\n                </li>\n\n                <li\n                  class=\"p-t-4 p-b-4 d-flex text-nowrap\"\n                  data-cy=\"topic-subscribers-view--backlog-quota\"\n                >\n                  <label\n                    class=\"small m-b-0 m-r-auto\"\n                    translate\n                  >\n                    Backlog quota\n                  </label>\n                  <span class=\"m-l-16\">\n                    {{\n                      overview.policies.backlogQuota?.limit > 0\n                        ? (overview.policies.backlogQuota?.limit | bytes)\n                        : '-'\n                    }}\n                  </span>\n                </li>\n              </ul>\n            }\n          }\n        </fieldset>\n      </div>\n    </div>\n  </div>\n  <c8y-data-grid\n    class=\"content-fullpage d-flex d-col border-top border-bottom\"\n    [title]=\"tableTitle | translate\"\n    [loadingItemsLabel]=\"loadingItemsLabel | translate\"\n    [loadMoreItemsLabel]=\"loadMoreItemsLabel | translate\"\n    [columns]=\"columns\"\n    [pagination]=\"pagination\"\n    [serverSideDataCallback]=\"serverSideDataCallback\"\n    [refresh]=\"refresh\"\n    [hideReload]=\"true\"\n    [selectable]=\"selectable\"\n    [actionControls]=\"actionControls\"\n    [bulkActionControls]=\"bulkActionControls\"\n  >\n    <c8y-ui-empty-state\n      [icon]=\"stats?.size > 0 ? 'search' : 'input'\"\n      [title]=\"stats?.size > 0 ? (noResultsMessage | translate) : (noDataMessage | translate)\"\n      [subtitle]=\"stats?.size > 0 ? (noResultsSubtitle | translate) : (noDataSubtitle | translate)\"\n      *emptyStateContext=\"let stats\"\n      [horizontal]=\"stats?.size > 0\"\n    ></c8y-ui-empty-state>\n\n    <c8y-column name=\"name\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <span title=\"{{ context.value }}\">\n          {{ context.value }}\n        </span>\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"activeClients\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <span title=\"{{ context.value | number }}\">\n          {{ context.value | number }}\n        </span>\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"messageAckRate\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <span title=\"{{ context.value | number }}\">\n          {{ context.value | number }}\n        </span>\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"lastAcknowledgeDate\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        @if (context.value) {\n          <span title=\"{{ context.value }}\">\n            {{ context.value | relativeTime }}\n          </span>\n        } @else {\n          &ndash;\n        }\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"unackMsgBacklog\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <span title=\"{{ context.value | number }}\">\n          {{ context.value | number }}\n        </span>\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"backlogUsagePercentage\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <span title=\"{{ context.value / 100 | percent: '1.0-2' }}\">\n          {{ context.value / 100 | percent: '1.0-2' }}\n        </span>\n      </ng-container>\n    </c8y-column>\n  </c8y-data-grid>\n</div>\n", dependencies: [{ kind: "ngmodule", type: DataGridModule }, { kind: "directive", type: i1$2.CellRendererDefDirective, selector: "[c8yCellRendererDef]" }, { kind: "directive", type: i1$2.ColumnDirective, selector: "c8y-column", inputs: ["name"] }, { kind: "component", type: i1$2.DataGridComponent, selector: "c8y-data-grid", inputs: ["title", "loadMoreItemsLabel", "loadingItemsLabel", "showSearch", "refresh", "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: "ngmodule", type: HeaderModule }, { kind: "component", type: i1$2.TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: BreadcrumbModule }, { kind: "component", type: i1$2.BreadcrumbComponent, selector: "c8y-breadcrumb" }, { kind: "component", type: i1$2.BreadcrumbItemComponent, selector: "c8y-breadcrumb-item", inputs: ["icon", "translate", "label", "path", "injector"] }, { kind: "component", type: UsageComponent, selector: "app-usage", inputs: ["count", "limit", "percentage"] }, { kind: "component", type: ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "directive", type: EmptyStateContextDirective, selector: "[emptyStateContext]" }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "pipe", type: DecimalPipe, name: "number" }, { kind: "pipe", type: RelativeTimePipe, name: "relativeTime" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.PercentPipe, name: "percent" }, { kind: "pipe", type: BytesPipe, name: "bytes" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TopicSubscribersViewComponent, decorators: [{
            type: Component,
            args: [{ selector: 'app-topic-subscribers-view', standalone: true, imports: [
                        DataGridModule,
                        DecimalPipe,
                        HeaderModule,
                        C8yTranslateDirective,
                        RelativeTimePipe,
                        C8yTranslatePipe,
                        CommonModule,
                        BytesPipe,
                        BreadcrumbModule,
                        UsageComponent,
                        ActionBarItemComponent,
                        IconDirective,
                        EmptyStateComponent,
                        EmptyStateContextDirective,
                        LoadingComponent
                    ], template: "<c8y-title>{{ topicName$ | async }}</c8y-title>\n\n<c8y-breadcrumb>\n  <c8y-breadcrumb-item\n    [icon]=\"'monitoring'\"\n    [label]=\"'Monitoring' | translate\"\n  ></c8y-breadcrumb-item>\n  <c8y-breadcrumb-item\n    [label]=\"'Messaging service' | translate\"\n    [path]=\"'/monitoring/messaging-service'\"\n  ></c8y-breadcrumb-item>\n  <c8y-breadcrumb-item\n    [label]=\"namespaceLabel$ | async\"\n    [path]=\"'/monitoring/messaging-service/namespace/' + (namespaceId$ | async)\"\n  ></c8y-breadcrumb-item>\n  <c8y-breadcrumb-item [label]=\"topicName$ | async\"></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n\n<c8y-action-bar-item [placement]=\"'right'\">\n  <a\n    class=\"btn btn-link\"\n    title=\"{{ 'Reload' | translate }}\"\n    (click)=\"refresh.emit()\"\n  >\n    <i\n      c8yIcon=\"refresh\"\n      [ngClass]=\"{ 'icon-spin': loading$ | async }\"\n    ></i>\n    {{ 'Reload' | translate }}\n  </a>\n</c8y-action-bar-item>\n\n<div class=\"card content-fullpage d-flex d-col\">\n  <div class=\"bg-level-1 separator-bottom flex-no-shrink\">\n    <div class=\"card-block\">\n      <div\n        class=\"col-md-4 m-b-24 col-xs-12 d-flex p-t-24 gap-16 text-default a-i-center a-s-stretch\"\n      >\n        <div class=\"text-center d-col\">\n          <i\n            class=\"m-b-8 icon-40 c8y-icon-duocolor\"\n            [c8yIcon]=\"'day-view'\"\n          ></i>\n          <span class=\"tag tag--default\">{{ 'Topic' | translate }}</span>\n        </div>\n        <span class=\"h4\">{{ topicName$ | async }}</span>\n      </div>\n      <div class=\"col-md-4\">\n        <fieldset class=\"c8y-fieldset c8y-fieldset--lg\">\n          <legend translate>Topic usage</legend>\n\n          @if (loading$ | async) {\n            <c8y-loading></c8y-loading>\n          } @else {\n            @let overview = overview$ | async;\n            @if (overview) {\n              <ul class=\"list-unstyled small animated fadeIn\">\n                <li\n                  class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\"\n                  data-cy=\"topic-subscribers-view--active-subscribers\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">{{ 'Active subscribers' | translate }}</label>\n                  <span class=\"m-l-16\">{{ overview.topic.activeSubscribers | number }}</span>\n                </li>\n\n                <li\n                  class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\"\n                  data-cy=\"topic-subscribers-view--total-subscribers\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">{{ 'Total subscribers' | translate }}</label>\n                  <span class=\"m-l-16\">{{ overview.topic.subscribers | number }}</span>\n                </li>\n\n                <li\n                  class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\"\n                  data-cy=\"topic-subscribers-view--total-unacknowledged-messages\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">\n                    {{ 'Total unacknowledged messages' | translate }}\n                  </label>\n                  <span class=\"m-l-16\">{{ overview.topic.unackMsgBacklog | number }}</span>\n                </li>\n\n                <li\n                  class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\"\n                  data-cy=\"topic-subscribers-view--message-rate-in\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">\n                    {{ 'Message rate in' | translate }}\n                  </label>\n                  <span\n                    class=\"m-l-16\"\n                    ngNonBindable\n                    translate\n                    [translateParams]=\"{\n                      messagesPerSecond: overview.topic.msgRateIn | number\n                    }\"\n                  >\n                    {{ messagesPerSecond }} msg/s\n                  </span>\n                </li>\n\n                <li\n                  class=\"p-t-4 p-b-4 d-flex text-nowrap\"\n                  data-cy=\"topic-subscribers-view--message-rate-out\"\n                >\n                  <label class=\"small m-b-0 m-r-auto\">\n                    {{ 'Message rate out' | translate }}\n                  </label>\n                  <span\n                    class=\"m-l-16\"\n                    ngNonBindable\n                    translate\n                    [translateParams]=\"{\n                      messagesPerSecond: overview.topic.msgRateOut | number\n                    }\"\n                  >\n                    {{ messagesPerSecond }} msg/s\n                  </span>\n                </li>\n              </ul>\n            }\n          }\n        </fieldset>\n      </div>\n\n      <div class=\"col-md-4\">\n        <fieldset class=\"c8y-fieldset c8y-fieldset--lg\">\n          <legend translate>Topic message backlog</legend>\n\n          @if (loading$ | async) {\n            <c8y-loading></c8y-loading>\n          } @else {\n            @let overview = overview$ | async;\n            @if (overview) {\n              <ul class=\"list-unstyled small animated fadeIn\">\n                <li\n                  class=\"p-t-4 p-b-4 d-flex separator-bottom text-nowrap\"\n                  data-cy=\"topic-subscribers-view--backlog-usage\"\n                >\n                  <label\n                    class=\"small m-b-0 m-r-auto\"\n                    translate\n                  >\n                    Backlog usage\n                  </label>\n                  <app-usage [percentage]=\"overview.topic.backlogUsagePercentage\"></app-usage>\n                  <span class=\"m-l-16\">{{ overview.topic.backlogSize | bytes }}</span>\n                </li>\n\n                <li\n                  class=\"p-t-4 p-b-4 d-flex text-nowrap\"\n                  data-cy=\"topic-subscribers-view--backlog-quota\"\n                >\n                  <label\n                    class=\"small m-b-0 m-r-auto\"\n                    translate\n                  >\n                    Backlog quota\n                  </label>\n                  <span class=\"m-l-16\">\n                    {{\n                      overview.policies.backlogQuota?.limit > 0\n                        ? (overview.policies.backlogQuota?.limit | bytes)\n                        : '-'\n                    }}\n                  </span>\n                </li>\n              </ul>\n            }\n          }\n        </fieldset>\n      </div>\n    </div>\n  </div>\n  <c8y-data-grid\n    class=\"content-fullpage d-flex d-col border-top border-bottom\"\n    [title]=\"tableTitle | translate\"\n    [loadingItemsLabel]=\"loadingItemsLabel | translate\"\n    [loadMoreItemsLabel]=\"loadMoreItemsLabel | translate\"\n    [columns]=\"columns\"\n    [pagination]=\"pagination\"\n    [serverSideDataCallback]=\"serverSideDataCallback\"\n    [refresh]=\"refresh\"\n    [hideReload]=\"true\"\n    [selectable]=\"selectable\"\n    [actionControls]=\"actionControls\"\n    [bulkActionControls]=\"bulkActionControls\"\n  >\n    <c8y-ui-empty-state\n      [icon]=\"stats?.size > 0 ? 'search' : 'input'\"\n      [title]=\"stats?.size > 0 ? (noResultsMessage | translate) : (noDataMessage | translate)\"\n      [subtitle]=\"stats?.size > 0 ? (noResultsSubtitle | translate) : (noDataSubtitle | translate)\"\n      *emptyStateContext=\"let stats\"\n      [horizontal]=\"stats?.size > 0\"\n    ></c8y-ui-empty-state>\n\n    <c8y-column name=\"name\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <span title=\"{{ context.value }}\">\n          {{ context.value }}\n        </span>\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"activeClients\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <span title=\"{{ context.value | number }}\">\n          {{ context.value | number }}\n        </span>\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"messageAckRate\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <span title=\"{{ context.value | number }}\">\n          {{ context.value | number }}\n        </span>\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"lastAcknowledgeDate\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        @if (context.value) {\n          <span title=\"{{ context.value }}\">\n            {{ context.value | relativeTime }}\n          </span>\n        } @else {\n          &ndash;\n        }\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"unackMsgBacklog\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <span title=\"{{ context.value | number }}\">\n          {{ context.value | number }}\n        </span>\n      </ng-container>\n    </c8y-column>\n\n    <c8y-column name=\"backlogUsagePercentage\">\n      <ng-container *c8yCellRendererDef=\"let context\">\n        <span title=\"{{ context.value / 100 | percent: '1.0-2' }}\">\n          {{ context.value / 100 | percent: '1.0-2' }}\n        </span>\n      </ng-container>\n    </c8y-column>\n  </c8y-data-grid>\n</div>\n" }]
        }], propDecorators: { dataGrid: [{
                type: ViewChild,
                args: [DataGridComponent, { static: true }]
            }] } });

class TopicDetailsTabFactory {
    constructor(router) {
        this.router = router;
    }
    get() {
        const pattern = /\/namespace\/(.+)\/topic\/(.+)\/subscribers$/;
        const matches = this.router.url.match(pattern);
        if (matches) {
            const namespace = matches[1];
            const topic = matches[2];
            return [
                {
                    path: basePath + `/namespace/${namespace}/topic/${topic}/subscribers`,
                    priority: 500,
                    label: gettext('Subscribers')
                }
            ];
        }
        return [];
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TopicDetailsTabFactory, deps: [{ token: i1$4.Router }], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TopicDetailsTabFactory }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TopicDetailsTabFactory, decorators: [{
            type: Injectable
        }], ctorParameters: () => [{ type: i1$4.Router }] });

class MessagingManagementPreviewFactory {
    constructor() {
        this.applicationService = inject(ApplicationService);
    }
    async get() {
        const microserviceSubscribed = await this.applicationService
            .isAvailable(MESSAGING_MANAGEMENT_MICROSERVICE_NAME)
            .then(({ data: subscribed }) => subscribed)
            .catch(() => false);
        if (!microserviceSubscribed) {
            return null;
        }
        return {
            key: MESSAGING_MANAGEMENT_FEATURE_KEY,
            label: 'Messaging service - monitoring & management',
            description: () => import('@c8y/style/markdown-files/messaging-management-preview.md').then(m => m.default),
            settings: {
                reload: true
            }
        };
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingManagementPreviewFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingManagementPreviewFactory, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingManagementPreviewFactory, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }] });

class MessagingManagementModule {
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingManagementModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
    static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: MessagingManagementModule }); }
    static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingManagementModule, providers: [
            hookRoute([
                {
                    path: basePath,
                    component: NamespaceListComponent,
                    canActivate: [MessagingManagementGuard]
                },
                {
                    path: basePath + '/namespace/:namespace',
                    component: TopicListViewComponent,
                    canActivate: [MessagingManagementGuard]
                },
                {
                    path: basePath + '/namespace/:namespace/topic/:topic',
                    redirectTo: basePath + '/namespace/:namespace/topic/:topic/subscribers',
                    pathMatch: 'full'
                },
                {
                    path: basePath + '/namespace/:namespace/topic/:topic/subscribers',
                    component: TopicSubscribersViewComponent,
                    canActivate: [MessagingManagementGuard]
                }
            ]),
            hookTab(TopicDetailsTabFactory),
            hookNavigator(MessagingNavigatorNodeFactory),
            hookPreview(MessagingManagementPreviewFactory)
        ] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: MessagingManagementModule, decorators: [{
            type: NgModule,
            args: [{
                    providers: [
                        hookRoute([
                            {
                                path: basePath,
                                component: NamespaceListComponent,
                                canActivate: [MessagingManagementGuard]
                            },
                            {
                                path: basePath + '/namespace/:namespace',
                                component: TopicListViewComponent,
                                canActivate: [MessagingManagementGuard]
                            },
                            {
                                path: basePath + '/namespace/:namespace/topic/:topic',
                                redirectTo: basePath + '/namespace/:namespace/topic/:topic/subscribers',
                                pathMatch: 'full'
                            },
                            {
                                path: basePath + '/namespace/:namespace/topic/:topic/subscribers',
                                component: TopicSubscribersViewComponent,
                                canActivate: [MessagingManagementGuard]
                            }
                        ]),
                        hookTab(TopicDetailsTabFactory),
                        hookNavigator(MessagingNavigatorNodeFactory),
                        hookPreview(MessagingManagementPreviewFactory)
                    ]
                }]
        }] });

/**
 * Generated bundle index. Do not edit.
 */

export { MessagingManagementModule };
//# sourceMappingURL=c8y-ngx-components-messaging-management.mjs.map