@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
1 lines • 139 kB
Source Map (JSON)
{"version":3,"file":"c8y-ngx-components-protocol-lpwan.mjs","sources":["../../protocol-lpwan/multiple-lns-connectors/no-connections-found.component.ts","../../protocol-lpwan/multiple-lns-connectors/no-connections-found.component.html","../../protocol-lpwan/multiple-lns-connectors/multiple-lns-connector.model.ts","../../protocol-lpwan/multiple-lns-connectors/multiple-lns-connector.service.ts","../../protocol-lpwan/multiple-lns-connectors/connection-info-with-download-csv.component.ts","../../protocol-lpwan/multiple-lns-connectors/connection-info-with-download-csv.component.html","../../protocol-lpwan/multiple-lns-connectors/sigfox-multiple-lns-connector.component.ts","../../protocol-lpwan/multiple-lns-connectors/sigfox-multiple-lns-connector.component.html","../../protocol-lpwan/multiple-lns-connectors/actility-multiple-lns-connector.component.ts","../../protocol-lpwan/multiple-lns-connectors/actility-multiple-lns-connector.component.html","../../protocol-lpwan/multiple-lns-connectors/loriot/loriot-multiple-lns-connector.component.ts","../../protocol-lpwan/multiple-lns-connectors/loriot/loriot-multiple-lns-connector.component.html","../../protocol-lpwan/multiple-lns-connectors/multiple-lns-connectors.module.ts","../../protocol-lpwan/lpwan-set-device-protocol.service.ts","../../protocol-lpwan/lpwan-set-connections.component.ts","../../protocol-lpwan/lpwan-set-connections.component.html","../../protocol-lpwan/lpwan-set-device-protocol.component.ts","../../protocol-lpwan/lpwan-set-device-protocol.component.html","../../protocol-lpwan/lpwan-agent.guard.ts","../../protocol-lpwan/lpwan-protocol.module.ts","../../protocol-lpwan/c8y-ngx-components-protocol-lpwan.ts"],"sourcesContent":["import { Component, EventEmitter, Output, Input } from '@angular/core';\nimport { gettext, EmptyStateComponent, IconDirective, C8yTranslatePipe } from '@c8y/ngx-components';\n\n@Component({\n selector: 'no-connections-found',\n templateUrl: './no-connections-found.component.html',\n imports: [EmptyStateComponent, IconDirective, C8yTranslatePipe]\n})\nexport class NoConnectionsFoundComponent {\n @Output() onAction: EventEmitter<void> = new EventEmitter();\n @Input() header: string = gettext('Connections');\n addConnection() {\n this.onAction.emit();\n }\n}\n","<div class=\"card content-fullpage split-view--5-7\">\n <div class=\"card-header separator grid__col--fullspan\">\n <h4>{{ header | translate}}</h4>\n </div>\n <div class=\"inner-scroll split-view__list\">\n <div class=\"bg-level-1 flex-grow\">\n <div class=\"card-block large-padding\">\n <c8y-ui-empty-state\n [icon]=\"'plug'\"\n [title]=\"'No connections found.' | translate\"\n [subtitle]=\"'Click below to add a new connection.' | translate\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n </div>\n </div>\n\n <div class=\"card-footer separator\">\n <button\n title=\"{{ 'Add connection' | translate }}\"\n class=\"btn btn-primary\"\n (click)=\"addConnection()\"\n >\n <i [c8yIcon]=\"'plus-circle'\"></i>\n {{ 'Add connection' | translate }}\n </button>\n </div>\n </div>\n <div class=\"inner-scroll split-view__detail\">\n <div class=\"flex-grow\">\n <div class=\"card-block large-padding\">\n <c8y-ui-empty-state\n [icon]=\"'more-details'\"\n [title]=\"'No settings to display.' | translate\"\n [subtitle]=\"'Add a connection.' | translate\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n </div>\n </div>\n </div>\n</div>\n","export enum ConnectionType {\n ACTILITY,\n SIGFOX,\n LORIOT\n}\nexport interface Connection {\n name: string;\n baseUrl: string;\n username: string;\n password: string;\n description: string;\n}\nexport interface SigfoxConnection extends Connection {\n parentGroupId: string;\n}\nexport interface ActilityConnection extends Connection {\n profileId: string;\n routeApplicationServerId?: string;\n routeApplicationServerKey?: string;\n adminApiVersion: string;\n coreApiVersion: string;\n enterpriseConnection: boolean;\n}\n\nexport interface LoriotConnection extends Connection {\n providerType: string;\n}\n\nexport function isSigfoxConnection(\n connection: SigfoxConnection | ActilityConnection | LoriotConnection\n): connection is SigfoxConnection {\n return typeof (connection as SigfoxConnection).parentGroupId !== 'undefined';\n}\nexport function isActilityConnection(\n connection: SigfoxConnection | ActilityConnection | LoriotConnection\n): connection is ActilityConnection {\n return typeof (connection as ActilityConnection).profileId !== 'undefined';\n}\n\nexport function isLoriotConnection(\n connection: SigfoxConnection | ActilityConnection | LoriotConnection\n): connection is LoriotConnection {\n return typeof (connection as LoriotConnection).providerType !== 'undefined';\n}\n\nexport type LpwanState =\n | 'loadingConnection'\n | 'loadingError'\n | 'connectionAvailable'\n | 'connectionNotAvailable'\n | 'addConnections'\n | 'savedSuccessfully'\n | 'updateConnection';\n","import { Injectable } from '@angular/core';\nimport { FetchClient, IApplication, IFetchOptions, IFetchResponse } from '@c8y/client';\nimport { AlertService, AppStateService } from '@c8y/ngx-components';\nimport {\n ActilityConnection,\n isActilityConnection,\n isSigfoxConnection,\n SigfoxConnection,\n ConnectionType,\n LoriotConnection,\n isLoriotConnection\n} from './multiple-lns-connector.model';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class MultipleLnsConnectorService {\n private headers: any;\n constructor(\n private client: FetchClient,\n private appStateService: AppStateService,\n private alertService: AlertService\n ) {\n this.headers = { 'Content-Type': 'application/json' };\n }\n\n async list(connectionType: ConnectionType) {\n const url = `${this.getBaseUrlByType(connectionType)}/lns-connection`;\n const options: IFetchOptions = {\n method: 'GET',\n headers: this.headers\n };\n return this.client.fetch(url, options);\n }\n\n /**\n * Saves the connection.\n * @param connection The connection to be saved.\n * @param originalName The original name of the connection, required to perform an update.\n */\n async save(\n connection: ActilityConnection | SigfoxConnection | LoriotConnection,\n originalName: string = null\n ) {\n if (originalName) {\n return this.update(connection, originalName);\n }\n return this.create(connection);\n }\n\n async detail(\n connectionType: ConnectionType,\n connectionName: string\n ): Promise<ActilityConnection | SigfoxConnection | LoriotConnection | null> {\n const name = connectionName.toLocaleLowerCase();\n const url = `${this.getBaseUrlByType(connectionType)}/lns-connection/${encodeURIComponent(\n String(name)\n )}`;\n const options: IFetchOptions = {\n method: 'GET',\n headers: this.headers\n };\n const res = await this.client.fetch(url, options);\n if (res.status === 200) {\n return await res.json();\n }\n return null;\n }\n\n async exists(connectionType: ConnectionType, connectionName: string): Promise<boolean> {\n const connection = await this.detail(connectionType, connectionName);\n return connection !== null;\n }\n\n async create(connection: ActilityConnection | SigfoxConnection | LoriotConnection) {\n connection.name = connection.name.toLocaleLowerCase();\n const url = `${this.getBaseUrlByConnection(connection)}/lns-connection`;\n const options: IFetchOptions = {\n method: 'POST',\n headers: this.headers,\n body: JSON.stringify(connection)\n };\n return this.client.fetch(url, options);\n }\n\n async update(\n connection: ActilityConnection | SigfoxConnection | LoriotConnection,\n originalName: string\n ) {\n connection.name = connection.name.toLocaleLowerCase();\n const url = `${this.getBaseUrlByConnection(connection)}/lns-connection/${encodeURIComponent(\n String(originalName)\n )}`;\n const options: IFetchOptions = {\n method: 'PUT',\n headers: this.headers,\n body: JSON.stringify(connection)\n };\n return this.client.fetch(url, options);\n }\n\n getBaseUrlByConnection(connection: ActilityConnection | SigfoxConnection | LoriotConnection) {\n return isSigfoxConnection(connection)\n ? 'service/sigfox-agent'\n : isActilityConnection(connection)\n ? 'service/actility'\n : isLoriotConnection(connection)\n ? 'service/loriot'\n : '';\n }\n\n getBaseUrlByType(connectionType: ConnectionType) {\n return connectionType === ConnectionType.SIGFOX\n ? 'service/sigfox-agent'\n : connectionType === ConnectionType.ACTILITY\n ? 'service/actility'\n : connectionType === ConnectionType.LORIOT\n ? 'service/loriot'\n : '';\n }\n\n async delete(connection: ActilityConnection | SigfoxConnection | LoriotConnection) {\n const url = `${this.getBaseUrlByConnection(connection)}/lns-connection`;\n const options: IFetchOptions = {\n method: 'DELETE'\n };\n return this.client.fetch(`${url}/${encodeURIComponent(String(connection.name))}`, options);\n }\n\n getApplication(name: string): Partial<IApplication> {\n const { references } = this.appStateService.currentTenant.value.applications;\n return references.find(({ application }) => application.name === name).application;\n }\n\n async download(url: string): Promise<IFetchResponse> {\n try {\n const options: IFetchOptions = {\n method: 'GET'\n };\n return this.client.fetch(url, options);\n } catch (e) {\n this.alertService.addServerFailure(e);\n }\n }\n}\n","import { Component, Input } from '@angular/core';\nimport { BsModalRef } from 'ngx-bootstrap/modal';\nimport { MultipleLnsConnectorService } from './multiple-lns-connector.service';\nimport { saveAs } from 'file-saver';\nimport { IApplication } from '@c8y/client';\nimport { AlertService, gettext, C8yTranslatePipe } from '@c8y/ngx-components';\nimport { TranslateService } from '@ngx-translate/core';\n\n@Component({\n selector: 'connection-info-with-download-csv',\n templateUrl: './connection-info-with-download-csv.component.html',\n imports: [C8yTranslatePipe]\n})\nexport class ConnectionInfoWithDownloadCsvComponent {\n @Input() messageData: any;\n @Input() appData: IApplication;\n @Input() modalTitle: string;\n @Input() connectionName: string;\n\n constructor(\n private modal: BsModalRef,\n private connectorService: MultipleLnsConnectorService,\n private alertService: AlertService,\n private translateService: TranslateService\n ) {}\n\n dismiss() {\n this.modal.hide();\n }\n\n async download() {\n const url = `/service/${this.appData.contextPath}${this.messageData.attrs.URL}`;\n const res = await this.connectorService.download(url);\n if (res && res.status === 200) {\n const streamData = await res.blob();\n saveAs(\n streamData,\n this.translateService.instant(gettext('{{ connectionName }} - devices.csv'), {\n connectionName: this.connectionName\n })\n );\n } else {\n this.alertService.danger(gettext('A server error occurred.'));\n }\n }\n}\n","<div class=\"modal-dialog\">\n <div class=\"modal-content\">\n <div class=\"c8y-prompt alert alert-danger\">\n <h3 class=\"m-b-16\">\n <i class=\"dlt-c8y-icon-exclamation-circle\"></i>\n <span>{{ modalTitle | translate }}</span>\n </h3>\n <p class=\"text-break-word\">\n {{ messageData.message | translate }}\n </p>\n <br>\n <span class=\"btn-default\" (click)=\"download()\">{{\n 'Click the link to download the file with the affected devices.' | translate\n }}</span>\n <div class=\"alert-footer\">\n <button\n title=\"{{ 'Cancel' | translate }}\"\n type=\"button\"\n class=\"btn btn-default\"\n (click)=\"dismiss()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n </div>\n </div>\n </div>\n</div>","import { Component, OnInit, ViewChild } from '@angular/core';\nimport { NgForm, FormsModule } from '@angular/forms';\nimport {\n AlertService,\n gettext,\n ModalService,\n Status,\n LoadingComponent,\n TitleComponent,\n BreadcrumbComponent,\n BreadcrumbItemComponent,\n ListGroupComponent,\n ListItemComponent,\n ListItemIconComponent,\n IconDirective,\n FormGroupComponent,\n RequiredInputPlaceholderDirective,\n MessagesComponent,\n MessageDirective,\n C8yTranslateDirective,\n C8yTranslatePipe\n} from '@c8y/ngx-components';\nimport { TranslateService } from '@ngx-translate/core';\nimport { cloneDeep, escapeRegExp, find, head, orderBy } from 'lodash-es';\nimport { BsModalService } from 'ngx-bootstrap/modal';\nimport { ConnectionInfoWithDownloadCsvComponent } from './connection-info-with-download-csv.component';\nimport { ConnectionType, LpwanState, SigfoxConnection } from './multiple-lns-connector.model';\nimport { MultipleLnsConnectorService } from './multiple-lns-connector.service';\nimport { NgIf, NgFor } from '@angular/common';\nimport { NoConnectionsFoundComponent } from './no-connections-found.component';\n\n@Component({\n selector: 'sigfox-multiple-lns-connector',\n templateUrl: './sigfox-multiple-lns-connector.component.html',\n imports: [\n NgIf,\n LoadingComponent,\n TitleComponent,\n BreadcrumbComponent,\n BreadcrumbItemComponent,\n NoConnectionsFoundComponent,\n ListGroupComponent,\n NgFor,\n ListItemComponent,\n ListItemIconComponent,\n IconDirective,\n FormsModule,\n FormGroupComponent,\n RequiredInputPlaceholderDirective,\n MessagesComponent,\n MessageDirective,\n C8yTranslateDirective,\n C8yTranslatePipe\n ]\n})\nexport class SigfoxMultipleLnsConnectorComponent implements OnInit {\n state: LpwanState = 'loadingConnection';\n cloneDeep = cloneDeep;\n connection: SigfoxConnection;\n connections = Array<SigfoxConnection>();\n showPassword = false;\n @ViewChild('connectorsForm', { static: false }) connectorsForm: NgForm;\n cardHeader = gettext('Sigfox connections');\n allowedSpecialCharacters = '~!@$^(){}[]|:,<+=,.`_ -';\n namePattern = `^[a-zA-Z0-9 ${escapeRegExp(this.allowedSpecialCharacters)}]*$`;\n namePatternError = this.translateService.instant(\n gettext(\n 'Connection name can only contain letters, numbers, spaces, and the following symbols: {{ symbols }}'\n ),\n {\n symbols: this.allowedSpecialCharacters\n }\n );\n originalConnection: SigfoxConnection;\n constructor(\n private connectorService: MultipleLnsConnectorService,\n private alertService: AlertService,\n private translateService: TranslateService,\n private modalService: BsModalService,\n private modal: ModalService\n ) {}\n\n async ngOnInit() {\n await this.loadConnections();\n }\n\n async loadConnections() {\n const res = await this.connectorService.list(ConnectionType.SIGFOX);\n if (res && res.status !== 200) {\n const data = res.json ? await res.json() : undefined;\n this.alertService.addServerFailure({ data, res });\n this.state = 'loadingError';\n } else {\n const list = await res.json();\n this.connections = orderBy(list, ['name'], ['asc']);\n await this.setModel();\n }\n }\n\n async setModel(connectionObj = null) {\n const resetConnection = await this.resetEditedUnsavedConnection();\n this.connection = connectionObj\n ? connectionObj\n : this.state === 'savedSuccessfully'\n ? this.connection\n : (resetConnection ?? cloneDeep(head(this.connections)));\n this.state = 'updateConnection';\n this.showPassword = false;\n this.originalConnection = this.connection ? cloneDeep(this.connection) : undefined;\n }\n\n async resetEditedUnsavedConnection(): Promise<SigfoxConnection | undefined> {\n if (this.state !== 'updateConnection' || !this.originalConnection) {\n return;\n }\n\n const { name } = this.originalConnection;\n const originalData = find(this.connections, { name });\n if (originalData) {\n return cloneDeep(originalData);\n }\n }\n\n async addConnection() {\n await this.resetEditedUnsavedConnection();\n this.connection = {} as SigfoxConnection;\n this.originalConnection = {} as SigfoxConnection;\n this.state = 'addConnections';\n this.showPassword = true;\n }\n\n async save() {\n const checkForConnectionName =\n this.originalConnection && this.originalConnection.name && this.originalConnection.name !== ''\n ? this.originalConnection.name\n : this.connection.name;\n const isConnectionExist = await this.connectorService.exists(\n ConnectionType.SIGFOX,\n checkForConnectionName\n );\n if (this.state === 'addConnections' && isConnectionExist) {\n const mesg = this.translateService.instant(\n gettext(`Connection with name \"{{ name }}\" already exists.`),\n { name: this.connection.name }\n );\n this.alertService.danger(mesg);\n } else {\n return this.saveConnection();\n }\n }\n\n async deleteConnection(originalConnection) {\n const { name } = originalConnection;\n const mesg = this.translateService.instant(\n gettext(`You are about to delete the connection \"{{ name }}\". Do you want to proceed?`),\n { name }\n );\n\n try {\n await this.modal.confirm(gettext('Delete connection'), mesg, Status.DANGER, {\n ok: gettext('Delete'),\n cancel: gettext('Cancel')\n });\n\n await this.delete(originalConnection);\n } catch (error) {\n // empty catch block\n }\n }\n\n changePassword() {\n this.showPassword = !this.showPassword;\n if (this.connectorsForm.controls.password) {\n this.connectorsForm.controls.password.setValue(null);\n }\n }\n\n private async saveConnection() {\n const res = await this.connectorService.save(this.connection, this.originalConnection?.name);\n if (res && (res.status === 201 || res.status === 200)) {\n this.state = 'savedSuccessfully';\n this.alertService.success(gettext('Connection saved.'));\n await this.loadConnections();\n } else if (res && res.status === 500) {\n const data = res.json ? await res.json() : undefined;\n const app = this.connectorService.getApplication('sigfox-agent');\n const initialState = {\n messageData: data,\n appData: app,\n modalTitle: gettext('Failed to update the connection'),\n ariaDescribedby: 'modal-body',\n ariaLabelledBy: 'modal-title',\n connectionName: this.connection.name\n };\n this.modalService.show(ConnectionInfoWithDownloadCsvComponent, { initialState });\n } else {\n const data = res.json ? await res.json() : undefined;\n this.alertService.addServerFailure({ data, res });\n }\n }\n\n private async delete(originalConnection) {\n try {\n const response = await this.connectorService.delete(originalConnection);\n\n if (response.ok && response.status === 204) {\n this.alertService.success(gettext('Connection deleted.'));\n await this.loadConnections();\n } else if (response && response.status === 500) {\n const data = response.json ? await response.json() : undefined;\n const app = this.connectorService.getApplication('sigfox-agent');\n const initialState = {\n messageData: data,\n appData: app,\n ariaDescribedby: 'modal-body',\n ariaLabelledBy: 'modal-title',\n modalTitle: gettext('Failed to delete the connection'),\n connectionName: originalConnection.name\n };\n this.modalService.show(ConnectionInfoWithDownloadCsvComponent, { initialState });\n } else {\n const data = response.json ? await response.json() : undefined;\n this.alertService.addServerFailure({ data, response });\n }\n } catch (error) {\n // empty catch block\n }\n }\n}\n","<ng-container *ngIf=\"state === 'loadingConnection'; else renderListAndForm\">\n <c8y-loading></c8y-loading>\n</ng-container>\n<c8y-title>{{ 'Connectivity' | translate }}</c8y-title>\n\n<c8y-breadcrumb>\n <c8y-breadcrumb-item\n [icon]=\"'cog'\"\n [label]=\"'Settings' | translate\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n [icon]=\"'cog'\"\n [label]=\"'Connectivity' | translate\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n [icon]=\"'cog'\"\n [label]=\"'Sigfox' | translate\"\n ></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n\n<ng-template #renderListAndForm>\n <no-connections-found\n (onAction)=\"addConnection()\"\n *ngIf=\"connections.length === 0 && state !== 'addConnections'\"\n [header]=\"cardHeader | translate\"\n ></no-connections-found>\n <div>\n <div\n class=\"card content-fullpage split-view--5-7\"\n *ngIf=\"connections.length !== 0 || state === 'addConnections'\"\n >\n <div class=\"card-header separator grid__col--fullspan\">\n <div class=\"card-title\">{{ cardHeader | translate }}</div>\n </div>\n <div class=\"inner-scroll split-view__list\">\n <div class=\"bg-level-1 flex-grow\">\n <c8y-list-group class=\"nav c8y-nav-stacked\">\n <c8y-li\n class=\"c8y-stacked-item p-0\"\n [class.active]=\"connection.name === connectionBeingEdited\"\n *ngFor=\"let connection of connections; let index = index\"\n (click)=\"setModel(cloneDeep(connection))\"\n >\n <c8y-li-icon [icon]=\"'plug'\"></c8y-li-icon>\n <span title=\"{{ connection.name }}\">\n {{ connection.name }}\n </span>\n </c8y-li>\n\n <c8y-li\n class=\"c8y-nav-stacked active\"\n *ngIf=\"state === 'addConnections'\"\n (click)=\"addConnection()\"\n >\n <c8y-li-icon [icon]=\"'plug'\"></c8y-li-icon>\n {{ 'New connection' | translate }}\n </c8y-li>\n </c8y-list-group>\n </div>\n <div class=\"card-footer separator-top\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Add connection' | translate }}\"\n [disabled]=\"state === 'addConnections'\"\n (click)=\"addConnection()\"\n >\n <i [c8yIcon]=\"'plus-circle'\"></i>\n {{ 'Add connection' | translate }}\n </button>\n </div>\n </div>\n\n <!-- 'split-view__detail--selected' condition needs to be fixed. this is needed so that both columns are visible in tablet format -->\n\n <div\n class=\"inner-scroll split-view__detail\"\n ng-class=\"{ 'split-view__detail--selected': vm.selected && vm.jsonSchemaObjects }\"\n >\n <div class=\"card-header separator visible-sm visible-xs fit-w sticky-top\">\n <button\n class=\"btn btn-clean text-primary\"\n title=\"{{ 'Back' | translate }}\"\n ng-click=\"vm.deselect()\"\n >\n <i [c8yIcon]=\"'chevron-left'\"></i>\n <span>{{ 'Back' | translate }}</span>\n </button>\n </div>\n <form\n class=\"d-contents\"\n #connectorsForm=\"ngForm\"\n >\n <div class=\"flex-grow\">\n <div class=\"card-block large-padding\">\n <c8y-form-group>\n <label for=\"name\">\n {{ 'Name' | translate }}\n </label>\n <input\n class=\"form-control\"\n id=\"name\"\n name=\"name\"\n type=\"text\"\n required\n [placeholder]=\"'e.g. Sigfox connection' | translate\"\n [(ngModel)]=\"connection.name\"\n [pattern]=\"namePattern\"\n />\n <c8y-messages>\n <c8y-message\n name=\"pattern\"\n [text]=\"namePatternError\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n\n <c8y-form-group>\n <label for=\"description\">\n {{ 'Description' | translate }}\n </label>\n <input\n class=\"form-control\"\n id=\"description\"\n name=\"description\"\n type=\"text\"\n [placeholder]=\"\n 'e.g. This connection has a built-in functionality to…' | translate\n \"\n [(ngModel)]=\"connection.description\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <label for=\"baseUrl\">\n {{ 'URL' | translate }}\n </label>\n <input\n class=\"form-control\"\n id=\"baseUrl\"\n name=\"baseUrl\"\n type=\"text\"\n required\n [placeholder]=\"\n 'e.g. {{ example }}' | translate : { example: 'https://backend.sigfox.com/api' }\n \"\n [(ngModel)]=\"connection.baseUrl\"\n />\n </c8y-form-group>\n <c8y-form-group>\n <label for=\"parentGroupId\">\n {{ 'Parent group ID' | translate }}\n </label>\n <input\n class=\"form-control\"\n id=\"parentGroupId\"\n name=\"parentGroupId\"\n type=\"text\"\n required\n [placeholder]=\"\n 'e.g. {{ example }}' | translate : { example: '58c1793b9e93a15370f71caa' }\n \"\n [(ngModel)]=\"connection.parentGroupId\"\n pattern=\"[a-z\\d]+\"\n />\n <c8y-messages>\n <c8y-message\n name=\"pattern\"\n text=\"{{ 'Must be a valid Parent group ID' | translate }}\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n\n <c8y-form-group>\n <label for=\"username\">\n {{ 'Username' | translate }}\n </label>\n <input\n class=\"form-control\"\n id=\"username\"\n placeholder=\"{{ 'e.g. joe`LOCALIZE`' | translate }}\"\n name=\"username\"\n type=\"text\"\n required\n [(ngModel)]=\"connection.username\"\n />\n </c8y-form-group>\n <c8y-form-group *ngIf=\"showPassword\">\n <label for=\"password\">\n {{ 'Password' | translate }}\n </label>\n <input\n class=\"form-control\"\n id=\"password\"\n placeholder=\"{{ 'e.g. my_password' | translate }}\"\n name=\"password\"\n type=\"password\"\n required\n [(ngModel)]=\"connection.password\"\n />\n </c8y-form-group>\n\n <button\n class=\"btn btn-default\"\n name=\"changePassword\"\n type=\"button\"\n *ngIf=\"state === 'updateConnection'\"\n (click)=\"changePassword()\"\n >\n <span\n title=\"{{ 'Change password' | translate }}\"\n *ngIf=\"!showPassword\"\n >\n {{ 'Change password' | translate }}\n </span>\n <span\n title=\"{{ 'Cancel password change' | translate }}\"\n *ngIf=\"showPassword\"\n >\n {{ 'Cancel password change' | translate }}\n </span>\n </button>\n </div>\n </div>\n\n <div class=\"card-footer separator-top\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Cancel' | translate }}\"\n type=\"button\"\n (click)=\"setModel()\"\n translate\n >\n Cancel\n </button>\n <button\n class=\"btn btn-danger\"\n title=\"{{ 'Delete' | translate }}\"\n type=\"button\"\n data-cy=\"sigfox-multiple-lns-connector.component--delete-connectivity\"\n *ngIf=\"state === 'updateConnection'\"\n (click)=\"deleteConnection(originalConnection)\"\n translate\n >\n Delete\n </button>\n <button\n class=\"btn btn-primary\"\n title=\"{{ 'Save' | translate }}\"\n data-cy=\"sigfox-multiple-lns-connector.component--save-connectivity\"\n type=\"submit\"\n [disabled]=\"!connectorsForm.form.valid || connectorsForm.form.pristine\"\n (click)=\"save()\"\n translate\n >\n Save\n </button>\n </div>\n </form>\n </div>\n </div>\n </div>\n</ng-template>\n","import { Component, OnInit, ViewChild } from '@angular/core';\nimport {\n AlertService,\n gettext,\n ModalService,\n Status,\n LoadingComponent,\n TitleComponent,\n BreadcrumbComponent,\n BreadcrumbItemComponent,\n ListGroupComponent,\n ListItemComponent,\n ListItemIconComponent,\n IconDirective,\n FormGroupComponent,\n RequiredInputPlaceholderDirective,\n MessagesComponent,\n MessageDirective,\n C8yTranslateDirective,\n C8yTranslatePipe\n} from '@c8y/ngx-components';\nimport { MultipleLnsConnectorService } from './multiple-lns-connector.service';\nimport { head, orderBy, findIndex, escapeRegExp, cloneDeep } from 'lodash-es';\nimport { TranslateService } from '@ngx-translate/core';\nimport { ActilityConnection, ConnectionType, LpwanState } from './multiple-lns-connector.model';\nimport { BsModalService } from 'ngx-bootstrap/modal';\nimport { ConnectionInfoWithDownloadCsvComponent } from './connection-info-with-download-csv.component';\nimport { NgForm, FormsModule } from '@angular/forms';\nimport { NgIf, NgFor } from '@angular/common';\nimport { NoConnectionsFoundComponent } from './no-connections-found.component';\n\n@Component({\n selector: 'actility-multiple-lns-connector',\n templateUrl: './actility-multiple-lns-connector.component.html',\n imports: [\n NgIf,\n LoadingComponent,\n TitleComponent,\n BreadcrumbComponent,\n BreadcrumbItemComponent,\n NoConnectionsFoundComponent,\n ListGroupComponent,\n NgFor,\n ListItemComponent,\n ListItemIconComponent,\n IconDirective,\n FormsModule,\n FormGroupComponent,\n RequiredInputPlaceholderDirective,\n MessagesComponent,\n MessageDirective,\n C8yTranslateDirective,\n C8yTranslatePipe\n ]\n})\nexport class ActilityMultipleLnsConnectorComponent implements OnInit {\n state: LpwanState = 'loadingConnection';\n connection: ActilityConnection;\n connections = Array<ActilityConnection>();\n @ViewChild('connectorsForm', { static: false }) connectorsForm: NgForm;\n showPassword = false;\n cardHeader = gettext('Actility connections');\n allowedSpecialCharacters = '~!@$^(){}[]|:,<+=,.`_ -';\n namePattern = `^[a-zA-Z0-9 ${escapeRegExp(this.allowedSpecialCharacters)}]*$`;\n namePatternError = this.translateService.instant(\n gettext(\n 'Connection name can only contain letters, numbers, spaces, and the following symbols: {{ symbols }}'\n ),\n {\n symbols: this.allowedSpecialCharacters\n }\n );\n originalConnection: ActilityConnection;\n constructor(\n private connectorService: MultipleLnsConnectorService,\n private alertService: AlertService,\n private translateService: TranslateService,\n private modal: ModalService,\n private modalService: BsModalService\n ) {}\n\n async ngOnInit() {\n await this.loadConnections();\n }\n\n async loadConnections() {\n const res = await this.connectorService.list(ConnectionType.ACTILITY);\n if (res && res.status !== 200) {\n const data = res.json ? await res.json() : undefined;\n this.alertService.addServerFailure({ data, res });\n this.state = 'loadingError';\n } else {\n const list = await res.json();\n this.connections = orderBy(list, ['name'], ['asc']);\n await this.setModel();\n }\n }\n\n async setModel(connectionObj = null) {\n await this.resetEditedUnsavedConnection();\n this.connection = connectionObj\n ? connectionObj\n : this.state === 'savedSuccessfully'\n ? this.connection\n : head(this.connections);\n this.state = 'updateConnection';\n this.showPassword = false;\n this.originalConnection = this.connection ? cloneDeep(this.connection) : undefined;\n }\n\n async setAdminAndCoreApiVersion() {\n this.connection.adminApiVersion = 'latest';\n this.connection.coreApiVersion = 'latest';\n }\n\n async resetEditedUnsavedConnection() {\n if (this.state !== 'updateConnection' || !this.originalConnection) {\n return;\n }\n\n const { name } = this.originalConnection;\n const index = findIndex(this.connections, { name });\n if (index !== -1) {\n const originalData = (await this.connectorService.detail(\n ConnectionType.ACTILITY,\n name\n )) as ActilityConnection;\n this.connections[index] = originalData;\n }\n }\n\n async addConnection() {\n await this.resetEditedUnsavedConnection();\n this.connection = {} as ActilityConnection;\n this.originalConnection = {} as ActilityConnection;\n this.state = 'addConnections';\n this.showPassword = true;\n await this.setAdminAndCoreApiVersion();\n }\n\n async save() {\n const checkForConnectionName =\n this.originalConnection && this.originalConnection.name && this.originalConnection.name !== ''\n ? this.originalConnection.name\n : this.connection.name;\n const isConnectionExist = await this.connectorService.exists(\n ConnectionType.ACTILITY,\n checkForConnectionName\n );\n if (this.state === 'addConnections' && isConnectionExist) {\n const mesg = this.translateService.instant(\n gettext(`Connection with name \"{{ name }}\" already exists.`),\n { name: this.connection.name }\n );\n this.alertService.danger(mesg);\n } else {\n return this.saveConnection();\n }\n }\n\n async deleteConnection(originalConnection) {\n const { name } = originalConnection;\n const mesg = this.translateService.instant(\n gettext(`You are about to delete the connection \"{{ name }}\". Do you want to proceed?`),\n { name }\n );\n\n try {\n await this.modal.confirm(gettext('Delete connection'), mesg, Status.DANGER, {\n ok: gettext('Delete'),\n cancel: gettext('Cancel')\n });\n\n await this.delete(originalConnection);\n } catch (error) {\n // empty catch block\n }\n }\n\n changePassword() {\n this.showPassword = !this.showPassword;\n if (this.connectorsForm.controls.password) {\n this.connectorsForm.controls.password.setValue(null);\n }\n }\n\n async saveConnection() {\n const res = await this.connectorService.save(this.connection, this.originalConnection?.name);\n if (res && (res.status === 201 || res.status === 200)) {\n this.state = 'savedSuccessfully';\n this.alertService.success(gettext('Connection saved.'));\n await this.loadConnections();\n } else if (res && res.status === 500) {\n const data = res.json ? await res.json() : undefined;\n const app = this.connectorService.getApplication('actility');\n const initialState = {\n messageData: data,\n appData: app,\n modalTitle: gettext('Failed to update the connection'),\n ariaDescribedby: 'modal-body',\n ariaLabelledBy: 'modal-title',\n connectionName: this.connection.name\n };\n this.modalService.show(ConnectionInfoWithDownloadCsvComponent, { initialState });\n } else {\n const data = res.json ? await res.json() : undefined;\n this.alertService.addServerFailure({ data, res });\n }\n }\n\n private async delete(originalConnection) {\n try {\n const response = await this.connectorService.delete(originalConnection);\n\n if (response.ok && response.status === 204) {\n this.alertService.success(gettext('Connection deleted.'));\n await this.loadConnections();\n } else if (response && response.status === 500) {\n const data = response.json ? await response.json() : undefined;\n const app = this.connectorService.getApplication('actility');\n const initialState = {\n messageData: data,\n appData: app,\n ariaDescribedby: 'modal-body',\n ariaLabelledBy: 'modal-title',\n modalTitle: gettext('Failed to delete the connection'),\n connectionName: originalConnection.name\n };\n this.modalService.show(ConnectionInfoWithDownloadCsvComponent, { initialState });\n } else {\n const data = response.json ? await response.json() : undefined;\n this.alertService.addServerFailure({ data, response });\n }\n } catch (error) {\n // empty catch block\n }\n }\n}\n","<ng-container *ngIf=\"state === 'loadingConnection'; else renderListAndForm\">\n <c8y-loading></c8y-loading>\n</ng-container>\n\n<c8y-title>{{ 'Connectivity' | translate }}</c8y-title>\n\n<c8y-breadcrumb>\n <c8y-breadcrumb-item\n [label]=\"'Settings' | translate\"\n [icon]=\"'cog'\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n [icon]=\"'cog'\"\n [label]=\"'Connectivity' | translate\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n [icon]=\"'cog'\"\n [label]=\"'Actility' | translate\"\n ></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n\n<ng-template #renderListAndForm>\n <no-connections-found\n (onAction)=\"addConnection()\"\n *ngIf=\"connections.length === 0 && state !== 'addConnections'\"\n [header]=\"cardHeader | translate\"\n ></no-connections-found>\n <div>\n <div\n class=\"card content-fullpage split-view--5-7\"\n *ngIf=\"connections.length !== 0 || state === 'addConnections'\"\n >\n <div class=\"card-header separator grid__col--fullspan\">\n <div class=\"card-title\">{{ cardHeader | translate }}</div>\n </div>\n <div class=\"inner-scroll split-view__list\">\n <div class=\"bg-level-1 flex-grow\">\n <c8y-list-group class=\"nav c8y-nav-stacked\">\n <c8y-li\n class=\"c8y-stacked-item p-0\"\n [class.active]=\"connection.name === connectionBeingEdited\"\n *ngFor=\"let connection of connections; let index = index\"\n (click)=\"setModel(connection)\"\n >\n <c8y-li-icon [icon]=\"'plug'\"></c8y-li-icon>\n <span title=\"{{ connection.name }}\">\n {{ connection.name }}\n </span>\n </c8y-li>\n\n <c8y-li\n *ngIf=\"state === 'addConnections'\"\n class=\"c8y-nav-stacked active\"\n (click)=\"addConnection()\"\n >\n <c8y-li-icon [icon]=\"'plug'\"></c8y-li-icon>\n {{ 'New connection' | translate }}\n </c8y-li>\n </c8y-list-group>\n </div>\n <div class=\"card-footer separator-top\">\n <button\n [disabled]=\"state === 'addConnections'\"\n title=\"{{ 'Add connection' | translate }}\"\n class=\"btn btn-default\"\n (click)=\"addConnection()\"\n >\n <i [c8yIcon]=\"'plus-circle'\"></i>\n {{ 'Add connection' | translate }}\n </button>\n </div>\n </div>\n\n <!-- 'split-view__detail--selected' condition needs to be fixed. this is needed so that both columns are visible in tablet format -->\n\n <div\n class=\"inner-scroll split-view__detail\"\n ng-class=\"{ 'split-view__detail--selected': vm.selected && vm.jsonSchemaObjects }\"\n >\n <div class=\"card-header separator visible-sm visible-xs fit-w sticky-top\">\n <button\n title=\"{{ 'Back' | translate }}\"\n class=\"btn btn-clean text-primary\"\n ng-click=\"vm.deselect()\"\n >\n <i [c8yIcon]=\"'chevron-left'\"></i>\n <span>{{ 'Back' | translate }}</span>\n </button>\n </div>\n <form #connectorsForm=\"ngForm\" class=\"d-contents\">\n <div class=\"flex-grow\">\n <div class=\"card-block large-padding\">\n <c8y-form-group>\n <label for=\"name\">\n {{ 'Name' | translate }}\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n [placeholder]=\"'e.g. Actility connection' | translate\"\n id=\"name\"\n name=\"name\"\n [(ngModel)]=\"connection.name\"\n required\n [pattern]=\"namePattern\"\n />\n <c8y-messages>\n <c8y-message name=\"pattern\" [text]=\"namePatternError\"></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n\n <c8y-form-group>\n <label for=\"description\">\n {{ 'Description' | translate }}\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n [placeholder]=\"\n 'e.g. This connection has a built-in functionality to…' | translate\n \"\n id=\"description\"\n name=\"description\"\n [(ngModel)]=\"connection.description\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <label for=\"baseUrl\">\n {{ 'URL' | translate }}\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n [placeholder]=\"\n 'e.g. {{ example }}' | translate: { example: 'https://dx-api.thingpark.io' }\n \"\n id=\"baseUrl\"\n name=\"baseUrl\"\n [(ngModel)]=\"connection.baseUrl\"\n required\n />\n </c8y-form-group>\n <c8y-form-group>\n <label for=\"profileId\">\n {{ 'Profile ID' | translate }}\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 'dev1-api' }\"\n id=\"profileId\"\n name=\"profileId\"\n [(ngModel)]=\"connection.profileId\"\n required\n />\n </c8y-form-group>\n <!-- DM-1171 start-->\n <c8y-form-group>\n <label for=\"applicationServerId\">\n {{ 'Application server ID' | translate }}\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n [placeholder]=\"'e.g. Application Server' | translate\"\n id=\"applicationServerId\"\n name=\"applicationServerId\"\n [(ngModel)]=\"connection.routeApplicationServerId\"\n [required]=\"connection.routeApplicationServerKey ? 'required' : null\"\n />\n </c8y-form-group>\n <c8y-form-group>\n <label for=\"applicationServerKey\">\n {{ 'Application server key' | translate }}\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n [placeholder]=\"\n 'e.g. {{ example }}'\n | translate: { example: '0011AEDF0011AEDF0011AEDF0011AEDF' }\n \"\n id=\"applicationServerKey\"\n name=\"applicationServerKey\"\n [(ngModel)]=\"connection.routeApplicationServerKey\"\n [required]=\"connection.routeApplicationServerId ? 'required' : null\"\n pattern=\"[A-Fa-f0-9]{32}\"\n />\n <c8y-messages>\n <c8y-message\n name=\"pattern\"\n text=\"{{ 'Must be a valid server key' | translate }}\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n <c8y-form-group>\n <label for=\"adminApiVersion\">\n {{ 'Admin API version' | translate }}\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 'v102' }\"\n id=\"adminApiVersion\"\n name=\"adminApiVersion\"\n [(ngModel)]=\"connection.adminApiVersion\"\n required\n pattern=\"^v\\d+$|latest\"\n />\n <c8y-messages>\n <c8y-message\n name=\"pattern\"\n text=\"{{ 'Must be a valid API version' | translate }}\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n\n <c8y-form-group>\n <label for=\"coreApiVersion\">\n {{ 'Core API version' | translate }}\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n [placeholder]=\"'e.g. {{ example }}' | translate: { example: 'v121' }\"\n id=\"coreApiVersion\"\n name=\"coreApiVersion\"\n [(ngModel)]=\"connection.coreApiVersion\"\n required\n pattern=\"^v\\d+$|latest\"\n />\n <c8y-messages>\n <c8y-message\n name=\"pattern\"\n text=\"{{ 'Must be a valid API version' | translate }}\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n <!-- DM-1171 end-->\n <c8y-form-group>\n <label for=\"username\">\n {{ 'Username' | translate }}\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"{{ 'e.g. joe`LOCALIZE`' | translate }}\"\n id=\"username\"\n name=\"username\"\n [(ngModel)]=\"connection.username\"\n required\n />\n </c8y-form-group>\n\n <c8y-form-group *ngIf=\"showPassword\">\n <label for=\"password\">\n {{ 'Password' | translate }}\n </label>\n <input\n type=\"password\"\n class=\"form-control\"\n placeholder=\"{{ 'e.g. my_password' | translate }}\"\n id=\"password\"\n name=\"password\"\n [(ngModel)]=\"connection.password\"\n required\n />\n </c8y-form-group>\n \n <!-- DM-1254 begins -->\n <c8y-form-group>\n <label title=\"{{ 'Connection type' | translate }}\">\n {{ 'Connection type' | translate }}\n </label>\n <label title=\"{{ 'Thingpark Enterprise' }}\" class=\"c8y-radio radio-inline\">\n <input type=\"radio\" name=\"c8y-group\" [value]=\"true\"\n [(ngModel)]=\"connection.enterpriseConnection\" required>\n <span></span> \n <span>{{ 'Thingpark Enterprise' }}</span>\n </label>\n <label title=\"{{ 'Thingpark Wireless' }}\" class=\"c8y-radio radio-inline\">\n <input type=\"radio\" name=\"c8y-group\" [value]=\"false\"\n [(ngModel)]=\"connection.enterpriseConnection\" required>\n <span></span> \n <span>{{ 'Thingpark Wireless' }}</span>\n </label>\n </c8y-form-group>\n <!-- DM-1254 ends -->\n <button\n *ngIf=\"state === 'updateConnection'\"\n type=\"button\"\n class=\"btn btn-default\"\n name=\"changePassword\"\n (click)=\"changePassword()\"\n >\n <span title=\"{{ 'Change password' | translate }}\" *ngIf=\"!showPassword\">\n {{ 'Change password' | translate }}\n </span>\n <span title=\"{{ 'Cancel password change' | translate }}\" *ngIf=\"showPass