@tangential/admin-console
Version:
A basic administration console
142 lines • 30.6 kB
JavaScript
import { LiveAnnouncer } from '@angular/cdk/a11y';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ViewChild, ViewEncapsulation } from '@angular/core';
import { AdminService, AuthUser } from '@tangential/authorization-service';
import { generatePushID } from '@tangential/core';
import { MatSort } from '@angular/material/sort';
import { SelectionModel } from '@angular/cdk/collections';
import { MatTableDataSource } from '@angular/material/table';
import { AdminConsoleParentPage } from '../_parent/admin-console-parent.page';
import * as i0 from "@angular/core";
import * as i1 from "@tangential/authorization-service";
import * as i2 from "../_parent/admin-console-parent.page";
import * as i3 from "@angular/cdk/a11y";
import * as i4 from "@angular/material/checkbox";
import * as i5 from "@tangential/components";
import * as i6 from "@angular/material/table";
import * as i7 from "@angular/material/sort";
import * as i8 from "@angular/common";
export class UserManagerPage {
constructor(adminService, parent, _liveAnnouncer, changeDetectorRef) {
this.adminService = adminService;
this.parent = parent;
this._liveAnnouncer = _liveAnnouncer;
this.changeDetectorRef = changeDetectorRef;
this.displayedColumns = ['select', '$key', 'displayName', 'email', 'lastSignInMils'];
this.dataSource = new MatTableDataSource();
this.selection = new SelectionModel(true, []);
this.rows = [];
this.selected = [];
this.columns = [
{ prop: '$key', name: 'Key', flexGrow: 1 },
{ prop: 'displayName', name: 'Display Name', flexGrow: 2 },
{ prop: 'email', name: 'Created', flexGrow: 2 },
{ prop: 'lastSignInMils', name: 'Last Sign In', flexGrow: 1 }
];
}
ngOnInit() {
this.parent.auth$.subscribe({
next: (v) => {
this.rows = v.users;
this.dataSource = new MatTableDataSource(v.users);
this.dataSource.sort = this.sort;
this.changeDetectorRef.markForCheck();
}
});
}
grantPermission(user, permission) {
this.adminService.grantPermissionOnUser(user, permission).catch((reason) => {
console.error('UserManagerComponent', 'could not grant permission', reason);
});
}
revokePermission(user, permission) {
console.log('UserManagerComponent', 'revokePermission');
this.adminService.revokePermissionOnUser(user, permission).catch((reason) => {
console.error('UserManagerComponent', 'could not revoke permission', reason);
});
}
grantRole(user, role) {
this.adminService.grantRoleOnUser(user, role).catch((reason) => {
console.error('UserManagerComponent', 'could not grant role', reason);
});
}
revokeRole(user, role) {
this.adminService.revokeRoleOnUser(user.$key, role.$key).catch((reason) => {
console.error('UserManagerComponent', 'could not revoke role', reason);
});
}
onAddItemAction() {
const user = new AuthUser(generatePushID());
user.displayName = 'New User ';
this.adminService.addUser(user).catch((reason) => {
console.error('UserManagerComponent', 'error adding user', reason);
throw new Error(reason);
});
}
onRemove(key) {
this.adminService.removeUser(key).catch((reason) => {
console.error('UserManagerComponent', 'error removing user', reason);
throw new Error(reason);
});
}
onRemoveSelectedAction(keys) {
keys.forEach((key) => {
this.adminService.removeUser(key).catch((reason) => {
console.error('UserManagerComponent', 'error removing user', reason);
throw new Error(reason);
});
});
}
onItemChange(user) {
this.adminService.updateUser(user).catch((reason) => {
console.error('UserManagerComponent', 'error updating user', reason);
throw new Error(reason);
});
}
/**
* Borrowed directly from Angular Material examples: https://material.angular.io/components/table/overview
*/
/** Whether the number of selected elements matches the total number of rows. */
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
/** Selects all rows if they are not all selected; otherwise clear selection. */
toggleAllRows() {
if (this.isAllSelected()) {
this.selection.clear();
return;
}
this.selection.select(...this.dataSource.data);
}
/** The label for the checkbox on the passed row */
checkboxLabel(row) {
if (!row) {
return `${this.isAllSelected() ? 'deselect' : 'select'} all`;
}
return `${this.selection.isSelected(row) ? 'deselect' : 'select'} ${row.displayName}`;
}
/** Announce the change in sort state for assistive technology. */
announceSortChange(sortState) {
// This example uses English messages. If your application supports
// multiple language, you would internationalize these strings.
// Furthermore, you can customize the message to add additional
// details about the values being sorted.
if (sortState.direction) {
this._liveAnnouncer.announce(`Sorted ${sortState.direction}ending`);
}
else {
this._liveAnnouncer.announce('Sorting cleared');
}
}
}
UserManagerPage.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.10", ngImport: i0, type: UserManagerPage, deps: [{ token: i1.AdminService }, { token: i2.AdminConsoleParentPage }, { token: i3.LiveAnnouncer }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
UserManagerPage.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.10", type: UserManagerPage, selector: "tanj-user-manager-page", viewQueries: [{ propertyName: "sort", first: true, predicate: MatSort, descendants: true }], ngImport: i0, template: "<tanj-page-body>\n <table mat-table matSort (matSortChange)=\"announceSortChange($event)\" [dataSource]=\"dataSource\" class=\"mat-elevation-z8 tng-user-mgr-table\">\n\n <!-- Checkbox Column -->\n <ng-container matColumnDef=\"select\">\n <th mat-header-cell *matHeaderCellDef>\n <mat-checkbox (change)=\"$event ? toggleAllRows() : null\"\n [checked]=\"selection.hasValue() && isAllSelected()\"\n [indeterminate]=\"selection.hasValue() && !isAllSelected()\"\n [aria-label]=\"checkboxLabel()\">\n </mat-checkbox>\n </th>\n <td mat-cell *matCellDef=\"let row\" >\n <mat-checkbox (click)=\"$event.stopPropagation()\"\n (change)=\"$event ? selection.toggle(row) : null\"\n [checked]=\"selection.isSelected(row)\"\n [aria-label]=\"checkboxLabel(row)\">\n </mat-checkbox>\n </td>\n </ng-container>\n\n <!-- UID Column -->\n <ng-container matColumnDef=\"$key\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription=\"Sort by UID\"> User ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.$key}} </td>\n </ng-container>\n\n <!-- User name Column -->\n <ng-container matColumnDef=\"displayName\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription=\"Sort by name\"> User Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.displayName}} </td>\n </ng-container>\n\n <!-- User Email Column -->\n <ng-container matColumnDef=\"email\" >\n <th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription=\"Sort by email\"> Email </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.email}} </td>\n </ng-container>\n\n <!-- Last Sign in Column -->\n <ng-container matColumnDef=\"lastSignInMils\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription=\"Sort by Last Sign In\"> Last Sign In </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.lastSignInMils | date:'yyyy-MM~dd, HH:mm:ss' }} </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" (click)=\"selection.toggle(row)\"></tr>\n </table>\n</tanj-page-body>\n", dependencies: [{ kind: "component", type: i4.MatCheckbox, selector: "mat-checkbox", inputs: ["disableRipple", "color", "tabIndex"], exportAs: ["matCheckbox"] }, { kind: "component", type: i5.PageBodyComponent, selector: "tanj-page-body", inputs: ["suppressHeaderShim", "flex", "layout", "layoutAlign"] }, { kind: "component", type: i6.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i6.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i6.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i6.MatColumnDef, selector: "[matColumnDef]", inputs: ["sticky", "matColumnDef"] }, { kind: "directive", type: i6.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i6.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i6.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i6.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i6.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i6.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "directive", type: i7.MatSort, selector: "[matSort]", inputs: ["matSortDisabled", "matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i7.MatSortHeader, selector: "[mat-sort-header]", inputs: ["disabled", "mat-sort-header", "arrowPosition", "start", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "pipe", type: i8.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.10", ngImport: i0, type: UserManagerPage, decorators: [{
type: Component,
args: [{ selector: 'tanj-user-manager-page', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<tanj-page-body>\n <table mat-table matSort (matSortChange)=\"announceSortChange($event)\" [dataSource]=\"dataSource\" class=\"mat-elevation-z8 tng-user-mgr-table\">\n\n <!-- Checkbox Column -->\n <ng-container matColumnDef=\"select\">\n <th mat-header-cell *matHeaderCellDef>\n <mat-checkbox (change)=\"$event ? toggleAllRows() : null\"\n [checked]=\"selection.hasValue() && isAllSelected()\"\n [indeterminate]=\"selection.hasValue() && !isAllSelected()\"\n [aria-label]=\"checkboxLabel()\">\n </mat-checkbox>\n </th>\n <td mat-cell *matCellDef=\"let row\" >\n <mat-checkbox (click)=\"$event.stopPropagation()\"\n (change)=\"$event ? selection.toggle(row) : null\"\n [checked]=\"selection.isSelected(row)\"\n [aria-label]=\"checkboxLabel(row)\">\n </mat-checkbox>\n </td>\n </ng-container>\n\n <!-- UID Column -->\n <ng-container matColumnDef=\"$key\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription=\"Sort by UID\"> User ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.$key}} </td>\n </ng-container>\n\n <!-- User name Column -->\n <ng-container matColumnDef=\"displayName\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription=\"Sort by name\"> User Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.displayName}} </td>\n </ng-container>\n\n <!-- User Email Column -->\n <ng-container matColumnDef=\"email\" >\n <th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription=\"Sort by email\"> Email </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.email}} </td>\n </ng-container>\n\n <!-- Last Sign in Column -->\n <ng-container matColumnDef=\"lastSignInMils\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription=\"Sort by Last Sign In\"> Last Sign In </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.lastSignInMils | date:'yyyy-MM~dd, HH:mm:ss' }} </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" (click)=\"selection.toggle(row)\"></tr>\n </table>\n</tanj-page-body>\n" }]
}], ctorParameters: function () { return [{ type: i1.AdminService }, { type: i2.AdminConsoleParentPage }, { type: i3.LiveAnnouncer }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { sort: [{
type: ViewChild,
args: [MatSort]
}] } });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXNlci1tYW5hZ2VyLnBhZ2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy90YW5nZW50aWFsL2FkbWluLWNvbnNvbGUvc3JjL2xpYi9wYWdlcy91c2Vycy91c2VyLW1hbmFnZXIucGFnZS50cyIsIi4uLy4uLy4uLy4uLy4uLy4uLy4uL3Byb2plY3RzL3RhbmdlbnRpYWwvYWRtaW4tY29uc29sZS9zcmMvbGliL3BhZ2VzL3VzZXJzL3VzZXItbWFuYWdlci5wYWdlLmh0bWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFDLGFBQWEsRUFBQyxNQUFNLG1CQUFtQixDQUFBO0FBQy9DLE9BQU8sRUFBQyx1QkFBdUIsRUFBRSxpQkFBaUIsRUFBRSxTQUFTLEVBQVUsU0FBUyxFQUFFLGlCQUFpQixFQUFDLE1BQU0sZUFBZSxDQUFBO0FBQ3pILE9BQU8sRUFBQyxZQUFZLEVBQTRCLFFBQVEsRUFBYyxNQUFNLG1DQUFtQyxDQUFBO0FBQy9HLE9BQU8sRUFBQyxjQUFjLEVBQUMsTUFBTSxrQkFBa0IsQ0FBQTtBQUMvQyxPQUFPLEVBQUMsT0FBTyxFQUFPLE1BQU0sd0JBQXdCLENBQUM7QUFDckQsT0FBTyxFQUFDLGNBQWMsRUFBQyxNQUFNLDBCQUEwQixDQUFDO0FBQ3hELE9BQU8sRUFBQyxrQkFBa0IsRUFBQyxNQUFNLHlCQUF5QixDQUFDO0FBQzNELE9BQU8sRUFBQyxzQkFBc0IsRUFBQyxNQUFNLHNDQUFzQyxDQUFBOzs7Ozs7Ozs7O0FBUzNFLE1BQU0sT0FBTyxlQUFlO0lBZTFCLFlBQW9CLFlBQTBCLEVBQzFCLE1BQThCLEVBQzlCLGNBQTZCLEVBQzdCLGlCQUFvQztRQUhwQyxpQkFBWSxHQUFaLFlBQVksQ0FBYztRQUMxQixXQUFNLEdBQU4sTUFBTSxDQUF3QjtRQUM5QixtQkFBYyxHQUFkLGNBQWMsQ0FBZTtRQUM3QixzQkFBaUIsR0FBakIsaUJBQWlCLENBQW1CO1FBakJ4RCxx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VyxFQUFFLENBQUM7SUFDeEYsQ0FBQztJQUVELGtFQUFrRTtJQUNsRSxrQkFBa0IsQ0FBQyxTQUFlO1FBQ2hDLG1FQUFtRTtRQUNuRSwrREFBK0Q7UUFDL0QsK0RBQStEO1FBQy9ELHlDQUF5QztRQUN6QyxJQUFJLFNBQVMsQ0FBQyxTQUFTLEVBQUU7WUFDdkIsSUFBSSxDQUFDLGNBQWMsQ0FBQyxRQUFRLENBQUMsVUFBVSxTQUFTLENBQUMsU0FBUyxRQUFRLENBQUMsQ0FBQztTQUNyRTthQUFNO1lBQ0wsSUFBSSxDQUFDLGNBQWMsQ0FBQyxRQUFRLENBQUMsaUJBQWlCLENBQUMsQ0FBQztTQUNqRDtJQUNILENBQUM7OzZHQXJJVSxlQUFlO2lHQUFmLGVBQWUsb0dBcUJmLE9BQU8sZ0RDckNwQixnMkVBaURBOzRGRGpDYSxlQUFlO2tCQU4zQixTQUFTOytCQUNTLHdCQUF3QixtQkFFeEIsdUJBQXVCLENBQUMsTUFBTSxpQkFDOUIsaUJBQWlCLENBQUMsSUFBSTtvTUF1Qm5CLElBQUk7c0JBQXZCLFNBQVM7dUJBQUMsT0FBTyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7TGl2ZUFubm91bmNlcn0gZnJvbSAnQGFuZ3VsYXIvY2RrL2ExMXknXG5pbXBvcnQge0NoYW5nZURldGVjdGlvblN0cmF0ZWd5LCBDaGFuZ2VEZXRlY3RvclJlZiwgQ29tcG9uZW50LCBPbkluaXQsIFZpZXdDaGlsZCwgVmlld0VuY2Fwc3VsYXRpb259IGZyb20gJ0Bhbmd1bGFyL2NvcmUnXG5pbXBvcnQge0FkbWluU2VydmljZSwgQXV0aFBlcm1pc3Npb24sIEF1dGhSb2xlLCBBdXRoVXNlciwgVXNlclNlcnZpY2V9IGZyb20gJ0B0YW5nZW50aWFsL2F1dGhvcml6YXRpb24tc2VydmljZSdcbmltcG9ydCB7Z2VuZXJhdGVQdXNoSUR9IGZyb20gJ0B0YW5nZW50aWFsL2NvcmUnXG5pbXBvcnQge01hdFNvcnQsIFNvcnR9IGZyb20gJ0Bhbmd1bGFyL21hdGVyaWFsL3NvcnQnO1xuaW1wb3J0IHtTZWxlY3Rpb25Nb2RlbH0gZnJvbSAnQGFuZ3VsYXIvY2RrL2NvbGxlY3Rpb25zJztcbmltcG9ydCB7TWF0VGFibGVEYXRhU291cmNlfSBmcm9tICdAYW5ndWxhci9tYXRlcmlhbC90YWJsZSc7XG5pbXBvcnQge0FkbWluQ29uc29sZVBhcmVudFBhZ2V9IGZyb20gJy4uL19wYXJlbnQvYWRtaW4tY29uc29sZS1wYXJlbnQucGFnZSdcblxuXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICAgICAgICAndGFuai11c2VyLW1hbmFnZXItcGFnZScsXG4gIHRlbXBsYXRlVXJsOiAgICAgJy4vdXNlci1tYW5hZ2VyLnBhZ2UuaHRtbCcsXG4gIGNoYW5nZURldGVjdGlvbjogQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3kuT25QdXNoLFxuICBlbmNhcHN1bGF0aW9uOiAgIFZpZXdFbmNhcHN1bGF0aW9uLk5vbmVcbn0pXG5leHBvcnQgY2xhc3MgVXNlck1hbmFnZXJQYWdlIGltcGxlbWVudHMgT25Jbml0IHtcbiAgZGlzcGxheWVkQ29sdW1uczogc3RyaW5nW10gPSBbJ3NlbGVjdCcsICcka2V5JywgJ2Rpc3BsYXlOYW1lJywgJ2VtYWlsJywgJ2xhc3RTaWduSW5NaWxzJ107XG4gIGRhdGFTb3VyY2UgPSBuZXcgTWF0VGFibGVEYXRhU291cmNlPEF1dGhVc2VyPigpO1xuICBzZWxlY3Rpb24gPSBuZXcgU2VsZWN0aW9uTW9kZWw8QXV0aFVzZXI+KHRydWUsIFtdKTtcblxuICByb3dzOiBBdXRoVXNlcltdID0gW11cbiAgc2VsZWN0ZWQgPSBbXVxuICBjb2x1bW5zID0gW1xuICAgIHtwcm9wOiAnJGtleScsIG5hbWU6ICdLZXknLCBmbGV4R3JvdzogMX0sXG4gICAge3Byb3A6ICdkaXNwbGF5TmFtZScsIG5hbWU6ICdEaXNwbGF5IE5hbWUnLCBmbGV4R3JvdzogMn0sXG4gICAge3Byb3A6ICdlbWFpbCcsIG5hbWU6ICdDcmVhdGVkJywgZmxleEdyb3c6IDJ9LFxuICAgIHtwcm9wOiAnbGFzdFNpZ25Jbk1pbHMnLCBuYW1lOiAnTGFzdCBTaWduIEluJywgZmxleEdyb3c6IDF9XG4gIF1cblxuXG4gIGNvbnN0cnVjdG9yKHByaXZhdGUgYWRtaW5TZXJ2aWNlOiBBZG1pblNlcnZpY2UsXG4gICAgICAgICAgICAgIHByaXZhdGUgcGFyZW50OiBBZG1pbkNvbnNvbGVQYXJlbnRQYWdlLFxuICAgICAgICAgICAgICBwcml2YXRlIF9saXZlQW5ub3VuY2VyOiBMaXZlQW5ub3VuY2VyLFxuICAgICAgICAgICAgICBwcml2YXRlIGNoYW5nZURldGVjdG9yUmVmOiBDaGFuZ2VEZXRlY3RvclJlZikge1xuICB9XG5cbiAgQFZpZXdDaGlsZChNYXRTb3J0KSBzb3J0OiBNYXRTb3J0O1xuXG4gIG5nT25Jbml0KCkge1xuICAgIHRoaXMucGFyZW50LmF1dGgkLnN1YnNjcmliZSh7XG4gICAgICBuZXh0OiAodikgPT4ge1xuICAgICAgICB0aGlzLnJvd3MgPSB2LnVzZXJzXG4gICAgICAgIHRoaXMuZGF0YVNvdXJjZSA9IG5ldyBNYXRUYWJsZURhdGFTb3VyY2U8QXV0aFVzZXI+KHYudXNlcnMpO1xuICAgICAgICB0aGlzLmRhdGFTb3VyY2Uuc29ydCA9IHRoaXMuc29ydDtcbiAgICAgICAgdGhpcy5jaGFuZ2VEZXRlY3RvclJlZi5tYXJrRm9yQ2hlY2soKVxuICAgICAgfVxuICAgIH0pXG4gIH1cblxuXG4gIGdyYW50UGVybWlzc2lvbih1c2VyOiBBdXRoVXNlciwgcGVybWlzc2lvbjogQXV0aFBlcm1pc3Npb24pIHtcbiAgICB0aGlzLmFkbWluU2VydmljZS5ncmFudFBlcm1pc3Npb25PblVzZXIodXNlciwgcGVybWlzc2lvbikuY2F0Y2goKHJlYXNvbikgPT4ge1xuICAgICAgY29uc29sZS5lcnJvcignVXNlck1hbmFnZXJDb21wb25lbnQnLCAnY291bGQgbm90IGdyYW50IHBlcm1pc3Npb24nLCByZWFzb24pXG4gICAgfSlcbiAgfVxuXG4gIHJldm9rZVBlcm1pc3Npb24odXNlcjogQXV0aFVzZXIsIHBlcm1pc3Npb246IEF1dGhQZXJtaXNzaW9uKSB7XG4gICAgY29uc29sZS5sb2coJ1VzZXJNYW5hZ2VyQ29tcG9uZW50JywgJ3Jldm9rZVBlcm1pc3Npb24nKVxuICAgIHRoaXMuYWRtaW5TZXJ2aWNlLnJldm9rZVBlcm1pc3Npb25PblVzZXIodXNlciwgcGVybWlzc2lvbikuY2F0Y2goKHJlYXNvbikgPT4ge1xuICAgICAgY29uc29sZS5lcnJvcignVXNlck1hbmFnZXJDb21wb25lbnQnLCAnY291bGQgbm90IHJldm9rZSBwZXJtaXNzaW9uJywgcmVhc29uKVxuICAgIH0pXG4gIH1cblxuICBncmFudFJvbGUodXNlcjogQXV0aFVzZXIsIHJvbGU6IEF1dGhSb2xlKSB7XG4gICAgdGhpcy5hZG1pblNlcnZpY2UuZ3JhbnRSb2xlT25Vc2VyKHVzZXIsIHJvbGUpLmNhdGNoKChyZWFzb24pID0+IHtcbiAgICAgIGNvbnNvbGUuZXJyb3IoJ1VzZXJNYW5hZ2VyQ29tcG9uZW50JywgJ2NvdWxkIG5vdCBncmFudCByb2xlJywgcmVhc29uKVxuICAgIH0pXG4gIH1cblxuICByZXZva2VSb2xlKHVzZXI6IEF1dGhVc2VyLCByb2xlOiBBdXRoUm9sZSkge1xuICAgIHRoaXMuYWRtaW5TZXJ2aWNlLnJldm9rZVJvbGVPblVzZXIodXNlci4ka2V5LCByb2xlLiRrZXkpLmNhdGNoKChyZWFzb24pID0+IHtcbiAgICAgIGNvbnNvbGUuZXJyb3IoJ1VzZXJNYW5hZ2VyQ29tcG9uZW50JywgJ2NvdWxkIG5vdCByZXZva2Ugcm9sZScsIHJlYXNvbilcbiAgICB9KVxuICB9XG5cblxuICBvbkFkZEl0ZW1BY3Rpb24oKSB7XG4gICAgY29uc3QgdXNlciA9IG5ldyBBdXRoVXNlcihnZW5lcmF0ZVB1c2hJRCgpKVxuICAgIHVzZXIuZGlzcGxheU5hbWUgPSAnTmV3IFVzZXIgJ1xuICAgIHRoaXMuYWRtaW5TZXJ2aWNlLmFkZFVzZXIodXNlcikuY2F0Y2goKHJlYXNvbikgPT4ge1xuICAgICAgY29uc29sZS5lcnJvcignVXNlck1hbmFnZXJDb21wb25lbnQnLCAnZXJyb3IgYWRkaW5nIHVzZXInLCByZWFzb24pXG4gICAgICB0aHJvdyBuZXcgRXJyb3IocmVhc29uKVxuICAgIH0pXG4gIH1cblxuICBvblJlbW92ZShrZXk6IHN0cmluZykge1xuICAgIHRoaXMuYWRtaW5TZXJ2aWNlLnJlbW92ZVVzZXIoa2V5KS5jYXRjaCgocmVhc29uKSA9PiB7XG4gICAgICBjb25zb2xlLmVycm9yKCdVc2VyTWFuYWdlckNvbXBvbmVudCcsICdlcnJvciByZW1vdmluZyB1c2VyJywgcmVhc29uKVxuICAgICAgdGhyb3cgbmV3IEVycm9yKHJlYXNvbilcbiAgICB9KVxuICB9XG5cbiAgb25SZW1vdmVTZWxlY3RlZEFjdGlvbihrZXlzOiBzdHJpbmdbXSkge1xuICAgIGtleXMuZm9yRWFjaCgoa2V5KSA9PiB7XG4gICAgICB0aGlzLmFkbWluU2VydmljZS5yZW1vdmVVc2VyKGtleSkuY2F0Y2goKHJlYXNvbikgPT4ge1xuICAgICAgICBjb25zb2xlLmVycm9yKCdVc2VyTWFuYWdlckNvbXBvbmVudCcsICdlcnJvciByZW1vdmluZyB1c2VyJywgcmVhc29uKVxuICAgICAgICB0aHJvdyBuZXcgRXJyb3IocmVhc29uKVxuICAgICAgfSlcbiAgICB9KVxuICB9XG5cblxuICBvbkl0ZW1DaGFuZ2UodXNlcjogQXV0aFVzZXIpIHtcbiAgICB0aGlzLmFkbWluU2VydmljZS51cGRhdGVVc2VyKHVzZXIpLmNhdGNoKChyZWFzb24pID0+IHtcbiAgICAgIGNvbnNvbGUuZXJyb3IoJ1VzZXJNYW5hZ2VyQ29tcG9uZW50JywgJ2Vycm9yIHVwZGF0aW5nIHVzZXInLCByZWFzb24pXG4gICAgICB0aHJvdyBuZXcgRXJyb3IocmVhc29uKVxuICAgIH0pXG5cbiAgfVxuXG4gIC8qKlxuICAgKiBCb3Jyb3dlZCBkaXJlY3RseSBmcm9tIEFuZ3VsYXIgTWF0ZXJpYWwgZXhhbXBsZXM6IGh0dHBzOi8vbWF0ZXJpYWwuYW5ndWxhci5pby9jb21wb25lbnRzL3RhYmxlL292ZXJ2aWV3XG4gICAqL1xuICAvKiogV2hldGhlciB0aGUgbnVtYmVyIG9mIHNlbGVjdGVkIGVsZW1lbnRzIG1hdGNoZXMgdGhlIHRvdGFsIG51bWJlciBvZiByb3dzLiAqL1xuICBpc0FsbFNlbGVjdGVkKCkge1xuICAgIGNvbnN0IG51bVNlbGVjdGVkID0gdGhpcy5zZWxlY3Rpb24uc2VsZWN0ZWQubGVuZ3RoO1xuICAgIGNvbnN0IG51bVJvd3MgPSB0aGlzLmRhdGFTb3VyY2UuZGF0YS5sZW5ndGg7XG4gICAgcmV0dXJuIG51bVNlbGVjdGVkID09PSBudW1Sb3dzO1xuICB9XG5cbiAgLyoqIFNlbGVjdHMgYWxsIHJvd3MgaWYgdGhleSBhcmUgbm90IGFsbCBzZWxlY3RlZDsgb3RoZXJ3aXNlIGNsZWFyIHNlbGVjdGlvbi4gKi9cbiAgdG9nZ2xlQWxsUm93cygpIHtcbiAgICBpZiAodGhpcy5pc0FsbFNlbGVjdGVkKCkpIHtcbiAgICAgIHRoaXMuc2VsZWN0aW9uLmNsZWFyKCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHRoaXMuc2VsZWN0aW9uLnNlbGVjdCguLi50aGlzLmRhdGFTb3VyY2UuZGF0YSk7XG4gIH1cblxuICAvKiogVGhlIGxhYmVsIGZvciB0aGUgY2hlY2tib3ggb24gdGhlIHBhc3NlZCByb3cgKi9cbiAgY2hlY2tib3hMYWJlbChyb3c/OiBBdXRoVXNlcik6IHN0cmluZyB7XG4gICAgaWYgKCFyb3cpIHtcbiAgICAgIHJldHVybiBgJHt0aGlzLmlzQWxsU2VsZWN0ZWQoKSA/ICdkZXNlbGVjdCcgOiAnc2VsZWN0J30gYWxsYDtcbiAgICB9XG4gICAgcmV0dXJuIGAke3RoaXMuc2VsZWN0aW9uLmlzU2VsZWN0ZWQocm93KSA/ICdkZXNlbGVjdCcgOiAnc2VsZWN0J30gJHtyb3cuZGlzcGxheU5hbWV9YDtcbiAgfVxuXG4gIC8qKiBBbm5vdW5jZSB0aGUgY2hhbmdlIGluIHNvcnQgc3RhdGUgZm9yIGFzc2lzdGl2ZSB0ZWNobm9sb2d5LiAqL1xuICBhbm5vdW5jZVNvcnRDaGFuZ2Uoc29ydFN0YXRlOiBTb3J0KSB7XG4gICAgLy8gVGhpcyBleGFtcGxlIHVzZXMgRW5nbGlzaCBtZXNzYWdlcy4gSWYgeW91ciBhcHBsaWNhdGlvbiBzdXBwb3J0c1xuICAgIC8vIG11bHRpcGxlIGxhbmd1YWdlLCB5b3Ugd291bGQgaW50ZXJuYXRpb25hbGl6ZSB0aGVzZSBzdHJpbmdzLlxuICAgIC8vIEZ1cnRoZXJtb3JlLCB5b3UgY2FuIGN1c3RvbWl6ZSB0aGUgbWVzc2FnZSB0byBhZGQgYWRkaXRpb25hbFxuICAgIC8vIGRldGFpbHMgYWJvdXQgdGhlIHZhbHVlcyBiZWluZyBzb3J0ZWQuXG4gICAgaWYgKHNvcnRTdGF0ZS5kaXJlY3Rpb24pIHtcbiAgICAgIHRoaXMuX2xpdmVBbm5vdW5jZXIuYW5ub3VuY2UoYFNvcnRlZCAke3NvcnRTdGF0ZS5kaXJlY3Rpb259ZW5kaW5nYCk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuX2xpdmVBbm5vdW5jZXIuYW5ub3VuY2UoJ1NvcnRpbmcgY2xlYXJlZCcpO1xuICAgIH1cbiAgfVxuXG59XG4iLCI8dGFuai1wYWdlLWJvZHk+XG4gIDx0YWJsZSBtYXQtdGFibGUgbWF0U29ydCAobWF0U29ydENoYW5nZSk9XCJhbm5vdW5jZVNvcnRDaGFuZ2UoJGV2ZW50KVwiICBbZGF0YVNvdXJjZV09XCJkYXRhU291cmNlXCIgY2xhc3M9XCJtYXQtZWxldmF0aW9uLXo4IHRuZy11c2VyLW1nci10YWJsZVwiPlxuXG4gICAgPCEtLSBDaGVja2JveCBDb2x1bW4gLS0+XG4gICAgPG5nLWNvbnRhaW5lciBtYXRDb2x1bW5EZWY9XCJzZWxlY3RcIj5cbiAgICAgIDx0aCBtYXQtaGVhZGVyLWNlbGwgKm1hdEhlYWRlckNlbGxEZWY+XG4gICAgICAgIDxtYXQtY2hlY2tib3ggKGNoYW5nZSk9XCIkZXZlbnQgPyB0b2dnbGVBbGxSb3dzKCkgOiBudWxsXCJcbiAgICAgICAgICAgICAgICAgICAgICBbY2hlY2tlZF09XCJzZWxlY3Rpb24uaGFzVmFsdWUoKSAmJiBpc0FsbFNlbGVjdGVkKClcIlxuICAgICAgICAgICAgICAgICAgICAgIFtpbmRldGVybWluYXRlXT1cInNlbGVjdGlvbi5oYXNWYWx1ZSgpICYmICFpc0FsbFNlbGVjdGVkKClcIlxuICAgICAgICAgICAgICAgICAgICAgIFthcmlhLWxhYmVsXT1cImNoZWNrYm94TGFiZWwoKVwiPlxuICAgICAgICA8L21hdC1jaGVja2JveD5cbiAgICAgIDwvdGg+XG4gICAgICA8dGQgbWF0LWNlbGwgKm1hdENlbGxEZWY9XCJsZXQgcm93XCIgPlxuICAgICAgICA8bWF0LWNoZWNrYm94IChjbGljayk9XCIkZXZlbnQuc3RvcFByb3BhZ2F0aW9uKClcIlxuICAgICAgICAgICAgICAgICAgICAgIChjaGFuZ2UpPVwiJGV2ZW50ID8gc2VsZWN0aW9uLnRvZ2dsZShyb3cpIDogbnVsbFwiXG4gICAgICAgICAgICAgICAgICAgICAgW2NoZWNrZWRdPVwic2VsZWN0aW9uLmlzU2VsZWN0ZWQocm93KVwiXG4gICAgICAgICAgICAgICAgICAgICAgW2FyaWEtbGFiZWxdPVwiY2hlY2tib3hMYWJlbChyb3cpXCI+XG4gICAgICAgIDwvbWF0LWNoZWNrYm94PlxuICAgICAgPC90ZD5cbiAgICA8L25nLWNvbnRhaW5lcj5cblxuICAgIDwhLS0gVUlEIENvbHVtbiAtLT5cbiAgICA8bmctY29udGFpbmVyIG1hdENvbHVtbkRlZj1cIiRrZXlcIj5cbiAgICAgIDx0aCBtYXQtaGVhZGVyLWNlbGwgKm1hdEhlYWRlckNlbGxEZWYgbWF0LXNvcnQtaGVhZGVyIHNvcnRBY3Rpb25EZXNjcmlwdGlvbj1cIlNvcnQgYnkgVUlEXCI+IFVzZXIgSUQgPC90aD5cbiAgICAgIDx0ZCBtYXQtY2VsbCAqbWF0Q2VsbERlZj1cImxldCBlbGVtZW50XCI+IHt7ZWxlbWVudC4ka2V5fX0gPC90ZD5cbiAgICA8L25nLWNvbnRhaW5lcj5cblxuICAgIDwhLS0gVXNlciBuYW1lIENvbHVtbiAtLT5cbiAgICA8bmctY29udGFpbmVyIG1hdENvbHVtbkRlZj1cImRpc3BsYXlOYW1lXCI+XG4gICAgICA8dGggbWF0LWhlYWRlci1jZWxsICptYXRIZWFkZXJDZWxsRGVmIG1hdC1zb3J0LWhlYWRlciBzb3J0QWN0aW9uRGVzY3JpcHRpb249XCJTb3J0IGJ5IG5hbWVcIj4gIFVzZXIgTmFtZSA8L3RoPlxuICAgICAgPHRkIG1hdC1jZWxsICptYXRDZWxsRGVmPVwibGV0IGVsZW1lbnRcIj4ge3tlbGVtZW50LmRpc3BsYXlOYW1lfX0gPC90ZD5cbiAgICA8L25nLWNvbnRhaW5lcj5cblxuICAgIDwhLS0gVXNlciBFbWFpbCBDb2x1bW4gLS0+XG4gICAgPG5nLWNvbnRhaW5lciBtYXRDb2x1bW5EZWY9XCJlbWFpbFwiID5cbiAgICAgIDx0aCBtYXQtaGVhZGVyLWNlbGwgKm1hdEhlYWRlckNlbGxEZWYgbWF0LXNvcnQtaGVhZGVyIHNvcnRBY3Rpb25EZXNjcmlwdGlvbj1cIlNvcnQgYnkgZW1haWxcIj4gRW1haWwgPC90aD5cbiAgICAgIDx0ZCBtYXQtY2VsbCAqbWF0Q2VsbERlZj1cImxldCBlbGVtZW50XCI+IHt7ZWxlbWVudC5lbWFpbH19IDwvdGQ+XG4gICAgPC9uZy1jb250YWluZXI+XG5cbiAgICA8IS0tIExhc3QgU2lnbiBpbiBDb2x1bW4gLS0+XG4gICAgPG5nLWNvbnRhaW5lciBtYXRDb2x1bW5EZWY9XCJsYXN0U2lnbkluTWlsc1wiPlxuICAgICAgPHRoIG1hdC1oZWFkZXItY2VsbCAqbWF0SGVhZGVyQ2VsbERlZiBtYXQtc29ydC1oZWFkZXIgc29ydEFjdGlvbkRlc2NyaXB0aW9uPVwiU29ydCBieSBMYXN0IFNpZ24gSW5cIj4gTGFzdCBTaWduIEluIDwvdGg+XG4gICAgICA8dGQgbWF0LWNlbGwgKm1hdENlbGxEZWY9XCJsZXQgZWxlbWVudFwiPiB7e2VsZW1lbnQubGFzdFNpZ25Jbk1pbHMgfCBkYXRlOid5eXl5LU1NfmRkLCBISDptbTpzcycgfX0gPC90ZD5cbiAgICA8L25nLWNvbnRhaW5lcj5cblxuICAgIDx0ciBtYXQtaGVhZGVyLXJvdyAqbWF0SGVhZGVyUm93RGVmPVwiZGlzcGxheWVkQ29sdW1uc1wiPjwvdHI+XG4gICAgPHRyIG1hdC1yb3cgKm1hdFJvd0RlZj1cImxldCByb3c7IGNvbHVtbnM6IGRpc3BsYXllZENvbHVtbnM7XCIgKGNsaWNrKT1cInNlbGVjdGlvbi50b2dnbGUocm93KVwiPjwvdHI+XG4gIDwvdGFibGU+XG48L3RhbmotcGFnZS1ib2R5PlxuIl19