From 3080eebc1397c4cfe81ff08d0f6fd632963185c2 Mon Sep 17 00:00:00 2001
From: Lucie Kureckova <lkureckova@seznam.cz>
Date: Sun, 10 May 2020 17:36:47 +0200
Subject: [PATCH 1/4] Facilities - services destinations page * added services
 destinations page * added feature remove destinations * added feature add
 destination to service and facility

---
 .../facilities/facilities-routing.module.ts   |   5 +
 .../src/app/facilities/facilities.module.ts   |   4 +-
 .../facility-overview.component.ts            |  14 +-
 .../facility-resources.component.ts           |   5 +-
 ...ility-services-destinations.component.html |  23 +++
 ...ility-services-destinations.component.scss |   0
 ...acility-services-destinations.component.ts | 129 ++++++++++++++++
 .../destination-list.component.html           |  76 ++++++++++
 .../destination-list.component.scss           |   0
 .../destination-list.component.ts             |  98 ++++++++++++
 ...services-destination-dialog.component.html |  96 ++++++++++++
 ...services-destination-dialog.component.scss |   0
 ...d-services-destination-dialog.component.ts | 142 ++++++++++++++++++
 .../remove-destination-dialog.component.html  |  39 +++++
 .../remove-destination-dialog.component.scss  |   0
 .../remove-destination-dialog.component.ts    |  44 ++++++
 .../admin-gui/src/app/shared/shared.module.ts |  12 +-
 .../side-menu/side-menu-item.service.ts       |   5 +
 apps/admin-gui/src/assets/i18n/en.json        |  43 +++++-
 .../src/lib/table-config.service.ts           |   1 +
 .../services/src/lib/custom-icon.service.ts   |   6 +
 21 files changed, 729 insertions(+), 13 deletions(-)
 create mode 100644 apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-services-destinations/facility-services-destinations.component.html
 create mode 100644 apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-services-destinations/facility-services-destinations.component.scss
 create mode 100644 apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-services-destinations/facility-services-destinations.component.ts
 create mode 100644 apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.html
 create mode 100644 apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.scss
 create mode 100644 apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.ts
 create mode 100644 apps/admin-gui/src/app/shared/components/dialogs/add-services-destination-dialog/add-services-destination-dialog.component.html
 create mode 100644 apps/admin-gui/src/app/shared/components/dialogs/add-services-destination-dialog/add-services-destination-dialog.component.scss
 create mode 100644 apps/admin-gui/src/app/shared/components/dialogs/add-services-destination-dialog/add-services-destination-dialog.component.ts
 create mode 100644 apps/admin-gui/src/app/shared/components/dialogs/remove-destination-dialog/remove-destination-dialog.component.html
 create mode 100644 apps/admin-gui/src/app/shared/components/dialogs/remove-destination-dialog/remove-destination-dialog.component.scss
 create mode 100644 apps/admin-gui/src/app/shared/components/dialogs/remove-destination-dialog/remove-destination-dialog.component.ts

diff --git a/apps/admin-gui/src/app/facilities/facilities-routing.module.ts b/apps/admin-gui/src/app/facilities/facilities-routing.module.ts
index c58d0a40f..6e74802d9 100644
--- a/apps/admin-gui/src/app/facilities/facilities-routing.module.ts
+++ b/apps/admin-gui/src/app/facilities/facilities-routing.module.ts
@@ -24,6 +24,7 @@ import {
 import { ResourceGroupsComponent } from './pages/resource-detail-page/resource-groups/resource-groups.component';
 import { FacilityServiceConfigComponent } from './pages/facility-detail-page/facility-service-config/facility-service-config.component';
 import { FacilitySettingsManagersComponent } from './pages/facility-detail-page/facility-settings/facility-settings-managers/facility-settings-managers.component';
+import { FacilityServicesDestinationsComponent } from './pages/facility-detail-page/facility-services-destinations/facility-services-destinations.component';
 
 const routes: Routes = [
   {
@@ -53,6 +54,10 @@ const routes: Routes = [
         path: 'service-config',
         component: FacilityServiceConfigComponent,
         data: {animation: 'FacilityServiceConfigPage'}
+      },{
+        path: 'services-destinations',
+        component: FacilityServicesDestinationsComponent,
+        data: {animation: 'FacilityServicesDestinationsPage'}
       },
       {
         path: 'settings',
diff --git a/apps/admin-gui/src/app/facilities/facilities.module.ts b/apps/admin-gui/src/app/facilities/facilities.module.ts
index 58ce23038..26f9678a6 100644
--- a/apps/admin-gui/src/app/facilities/facilities.module.ts
+++ b/apps/admin-gui/src/app/facilities/facilities.module.ts
@@ -32,6 +32,7 @@ import { PerunSharedComponentsModule } from '@perun-web-apps/perun/components';
 import { FacilityServiceConfigComponent } from './pages/facility-detail-page/facility-service-config/facility-service-config.component';
 import { PerunFacilityServicesConfigModule } from '@perun-web-apps/perun/facility-services-config';
 import { FacilitySettingsManagersComponent } from './pages/facility-detail-page/facility-settings/facility-settings-managers/facility-settings-managers.component';
+import { FacilityServicesDestinationsComponent } from './pages/facility-detail-page/facility-services-destinations/facility-services-destinations.component';
 
 @NgModule({
   declarations: [
@@ -51,7 +52,8 @@ import { FacilitySettingsManagersComponent } from './pages/facility-detail-page/
     ResourceSettingsAttributesComponent,
     ResourceGroupsComponent,
     FacilityServiceConfigComponent,
-    FacilitySettingsManagersComponent
+    FacilitySettingsManagersComponent,
+    FacilityServicesDestinationsComponent
   ],
   imports: [
     CommonModule,
diff --git a/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-overview/facility-overview.component.ts b/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-overview/facility-overview.component.ts
index 4e7159b75..3b8df9a4d 100644
--- a/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-overview/facility-overview.component.ts
+++ b/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-overview/facility-overview.component.ts
@@ -48,14 +48,20 @@ export class FacilityOverviewComponent implements OnInit {
       },
       {
         cssIcon: 'perun-settings2',
-        url: `/facilities/${this.facility.id}/settings`,
-        label: 'MENU_ITEMS.FACILITY.SETTINGS',
+        url: `/facilities/${this.facility.id}/service-config`,
+        label: 'MENU_ITEMS.FACILITY.SERVICE_CONFIG',
+        style: 'facility-btn'
+      },
+      {
+        cssIcon: 'perun-service_destination',
+        url: `/facilities/${this.facility.id}/services-destinations`,
+        label: 'MENU_ITEMS.FACILITY.SERVICES_DESTINATIONS',
         style: 'facility-btn'
       },
       {
         cssIcon: 'perun-settings2',
-        url: `/facilities/${this.facility.id}/service-config`,
-        label: 'MENU_ITEMS.FACILITY.SERVICE_CONFIG',
+        url: `/facilities/${this.facility.id}/settings`,
+        label: 'MENU_ITEMS.FACILITY.SETTINGS',
         style: 'facility-btn'
       }
     ];
diff --git a/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-resources/facility-resources.component.ts b/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-resources/facility-resources.component.ts
index 60ba4998c..118d11641 100644
--- a/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-resources/facility-resources.component.ts
+++ b/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-resources/facility-resources.component.ts
@@ -1,5 +1,4 @@
 import { Component, HostBinding, Input, OnInit } from '@angular/core';
-import { SideMenuService } from '../../../../core/services/common/side-menu.service';
 import { ActivatedRoute } from '@angular/router';
 import { SelectionModel } from '@angular/cdk/collections';
 import { MatDialog } from '@angular/material/dialog';
@@ -26,9 +25,7 @@ export class FacilityResourcesComponent implements OnInit {
 
   constructor(private dialog: MatDialog,
               private facilitiesManager: FacilitiesManagerService,
-              private sideMenuService: SideMenuService,
               private tableConfigService: TableConfigService,
-              private facilityManager: FacilitiesManagerService,
               private route: ActivatedRoute) {
   }
 
@@ -49,7 +46,7 @@ export class FacilityResourcesComponent implements OnInit {
     this.route.parent.params.subscribe(parentParams => {
       const facilityId = parentParams['facilityId'];
 
-      this.facilityManager.getFacilityById(facilityId).subscribe(facility => {
+      this.facilitiesManager.getFacilityById(facilityId).subscribe(facility => {
         this.facility = facility;
 
         this.refreshTable();
diff --git a/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-services-destinations/facility-services-destinations.component.html b/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-services-destinations/facility-services-destinations.component.html
new file mode 100644
index 000000000..cd54430a5
--- /dev/null
+++ b/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-services-destinations/facility-services-destinations.component.html
@@ -0,0 +1,23 @@
+<div>
+  <h1 class="page-subtitle">{{'FACILITY_DETAIL.SERVICES_DESTINATIONS.TITLE' | translate}}</h1>
+  <perun-web-apps-refresh-button (refresh)="refreshTable()"></perun-web-apps-refresh-button>
+  <button mat-flat-button color="accent" (click)="addDestination()">
+    {{'FACILITY_DETAIL.SERVICES_DESTINATIONS.ADD' | translate}}
+  </button>
+  <button mat-flat-button color="warn" class="ml-2" [disabled]="selected.selected.length === 0"  (click)="removeDestination()">
+    {{'FACILITY_DETAIL.SERVICES_DESTINATIONS.REMOVE' | translate}}
+  </button>
+  <perun-web-apps-immediate-filter
+    [placeholder]="'FACILITY_DETAIL.SERVICES_DESTINATIONS.FILTER'"
+    (filter)="applyFilter($event)"></perun-web-apps-immediate-filter>
+  <mat-spinner class="ml-auto mr-auto" *ngIf="loading"></mat-spinner>
+  <app-perun-web-apps-destination-list
+    [pageSize]="pageSize"
+    (page)="pageChanged($event)"
+    *ngIf="!loading"
+    [filterValue]="filterValue"
+    [destinations]="destinations"
+    [selection]="selected"
+    [displayedColumns]="displayedColumns">
+  </app-perun-web-apps-destination-list>
+</div>
diff --git a/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-services-destinations/facility-services-destinations.component.scss b/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-services-destinations/facility-services-destinations.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-services-destinations/facility-services-destinations.component.ts b/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-services-destinations/facility-services-destinations.component.ts
new file mode 100644
index 000000000..1f83d1e49
--- /dev/null
+++ b/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-services-destinations/facility-services-destinations.component.ts
@@ -0,0 +1,129 @@
+import { Component, HostBinding, OnInit } from '@angular/core';
+import { MatDialog } from '@angular/material/dialog';
+import {
+  FacilitiesManagerService,
+  Facility, RichDestination,
+  RichResource,
+  ServicesManagerService
+} from '@perun-web-apps/perun/openapi';
+import {
+  TABLE_FACILITY_SERVICES_DESTINATION_LIST,
+  TableConfigService
+} from '@perun-web-apps/config/table-config';
+import { ActivatedRoute } from '@angular/router';
+import { SelectionModel } from '@angular/cdk/collections';
+import { PageEvent } from '@angular/material/paginator';
+import { RemoveDestinationDialogComponent } from '../../../../shared/components/dialogs/remove-destination-dialog/remove-destination-dialog.component';
+import { TranslateService } from '@ngx-translate/core';
+import { NotificatorService } from '../../../../core/services/common/notificator.service';
+import { AddServicesDestinationDialogComponent } from '../../../../shared/components/dialogs/add-services-destination-dialog/add-services-destination-dialog.component';
+
+@Component({
+  selector: 'app-perun-web-apps-facility-services-destinations',
+  templateUrl: './facility-services-destinations.component.html',
+  styleUrls: ['./facility-services-destinations.component.scss']
+})
+export class FacilityServicesDestinationsComponent implements OnInit {
+
+  static id = 'FacilityServicesDestinationsComponent';
+
+  // class used for animation
+  @HostBinding('class.router-component') true;
+
+  constructor(private dialog: MatDialog,
+              private facilitiesManager: FacilitiesManagerService,
+              private servicesManager: ServicesManagerService,
+              private tableConfigService: TableConfigService,
+              private translate: TranslateService,
+              private notificator: NotificatorService,
+              private route: ActivatedRoute) { }
+
+  facility: Facility;
+  destinations: RichDestination[];
+  selected = new SelectionModel<RichResource>(true, []);
+  displayedColumns: string[] = ['select', 'destinationId', 'service', 'destination', 'type', 'propagationType'];
+
+  filterValue = '';
+
+  loading: boolean;
+  pageSize: number;
+  tableId = TABLE_FACILITY_SERVICES_DESTINATION_LIST;
+
+  ngOnInit() {
+    this.loading = true;
+    this.pageSize = this.tableConfigService.getTablePageSize(this.tableId);
+
+    this.route.parent.params.subscribe(parentParams => {
+      const facilityId = parentParams['facilityId'];
+
+      this.facilitiesManager.getFacilityById(facilityId).subscribe(facility => {
+        this.facility = facility;
+
+        this.refreshTable();
+      });
+    });
+  }
+
+  refreshTable() {
+    this.loading = true;
+    this.servicesManager.getAllRichDestinationsForFacility(this.facility.id).subscribe( destinations => {
+      this.destinations = destinations;
+      this.selected.clear();
+      this.loading = false;
+    });
+  }
+
+  addDestination() {
+    const dialogRef = this.dialog.open(AddServicesDestinationDialogComponent, {
+      width: '600px',
+      data: {facility: this.facility, theme: 'facility-theme'}
+    });
+
+    dialogRef.afterClosed().subscribe(result => {
+      if (result) {
+        this.refreshTable();
+      }
+    });
+  }
+
+  removeDestination() {
+    const dialogRef = this.dialog.open(RemoveDestinationDialogComponent, {
+      width: '600px',
+      data: {destinations: this.selected.selected, theme: 'facility-theme'}
+    });
+
+    dialogRef.afterClosed().subscribe(result => {
+      if (result) {
+        this.loading = true;
+        this.deleteDestinations(this.selected.selected);
+      }
+    });
+  }
+
+  applyFilter(filterValue: string) {
+    this.filterValue = filterValue;
+  }
+
+  pageChanged(event: PageEvent) {
+    this.pageSize = event.pageSize;
+    this.tableConfigService.setTablePageSize(this.tableId, event.pageSize);
+  }
+
+  deleteDestinations(destinationsForDelete: RichDestination[]) {
+    if (destinationsForDelete.length === 0) {
+      this.translate.get('FACILITY_DETAIL.SERVICES_DESTINATIONS.REMOVE_SUCCESS').subscribe(successMessage => {
+        this.refreshTable();
+        this.notificator.showSuccess(successMessage);
+      });
+    } else {
+      const destination = destinationsForDelete[0];
+      this.servicesManager.removeDestination(destination.service.id,
+                                            destination.facility.id,
+                                            destination.destination,
+                                            destination.type).subscribe( () => {
+        destinationsForDelete.shift();
+        this.deleteDestinations(destinationsForDelete);
+      });
+    }
+  }
+}
diff --git a/apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.html b/apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.html
new file mode 100644
index 000000000..e33b6c2bc
--- /dev/null
+++ b/apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.html
@@ -0,0 +1,76 @@
+<div class="card mt-3" [class.hide-table]="exporting" [hidden]="dataSource.filteredData.length === 0 || destinations.length === 0">
+  <div class="card-body">
+    <perun-web-apps-table-options (end)="exporting = false" (start)="exporting = true" [exporter]="exporter" class="ml-auto"></perun-web-apps-table-options>
+    <table mat-table matTableExporter [dataSource]="dataSource" #exporter="matTableExporter" matSort matSortActive="id" matSortDirection="asc" matSortDisableClear
+           class="w-100">
+
+      <ng-container matColumnDef="select">
+        <th mat-header-cell *matHeaderCellDef>
+          <mat-checkbox color="primary"
+                        (change)="$event ? masterToggle() : null"
+                        [checked]="selection.hasValue() && isAllSelected()"
+                        [indeterminate]="selection.hasValue() && !isAllSelected()"
+                        [aria-label]="checkboxLabel()">
+          </mat-checkbox>
+        </th>
+        <td mat-cell *matCellDef="let resource" class="static-column-size">
+          <mat-checkbox color="primary"
+                        (click)="$event.stopPropagation()"
+                        (change)="$event ? selection.toggle(resource) : null"
+                        [checked]="selection.isSelected(resource)"
+                        [aria-label]="checkboxLabel(resource)">
+          </mat-checkbox>
+        </td>
+      </ng-container>
+
+      <ng-container matColumnDef="destinationId">
+        <th mat-header-cell *matHeaderCellDef
+            mat-sort-header>{{'FACILITY_DETAIL.SERVICES_DESTINATIONS.TABLE_DESTINATION_ID' | translate}}</th>
+        <td mat-cell class="static-column-size" *matCellDef="let destination">{{destination.id}}</td>
+      </ng-container>
+      <ng-container matColumnDef="service">
+        <th mat-header-cell *matHeaderCellDef
+            mat-sort-header>{{'FACILITY_DETAIL.SERVICES_DESTINATIONS.TABLE_SERVICE' | translate}}</th>
+        <td mat-cell *matCellDef="let destination">{{destination.service.name}}</td>
+      </ng-container>
+      <ng-container matColumnDef="destination">
+        <th mat-header-cell *matHeaderCellDef
+            mat-sort-header>{{'FACILITY_DETAIL.SERVICES_DESTINATIONS.TABLE_DESTINATION' | translate}}</th>
+        <td mat-cell *matCellDef="let destination">{{destination.destination}}</td>
+      </ng-container>
+      <ng-container matColumnDef="type">
+        <th mat-header-cell *matHeaderCellDef
+            mat-sort-header>{{'FACILITY_DETAIL.SERVICES_DESTINATIONS.TABLE_TYPE' | translate}}</th>
+        <td mat-cell *matCellDef="let destination">{{destination.type}}</td>
+      </ng-container>
+      <ng-container matColumnDef="propagationType">
+        <th mat-header-cell *matHeaderCellDef
+            mat-sort-header>{{'FACILITY_DETAIL.SERVICES_DESTINATIONS.TABLE_PROPAGATION_TYPE' | translate}}</th>
+        <td mat-cell *matCellDef="let destination">{{destination.propagationType}}</td>
+      </ng-container>
+
+      <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
+      <tr
+        mat-row
+        *matRowDef="let destination; columns: displayedColumns;"
+        class="dark-hover-list-item">
+      </tr>
+    </table>
+    <mat-paginator
+      [length]="destinations.length"
+      [pageSize]="pageSize"
+      (page)="page.emit($event)"
+      [pageSizeOptions]="pageSizeOptions">
+    </mat-paginator>
+  </div>
+</div>
+
+<mat-spinner *ngIf="exporting" class="ml-auto mr-auto"></mat-spinner>
+
+<app-alert *ngIf="dataSource.filteredData.length === 0 && destinations.length !== 0" color="warn">
+  {{'SHARED_LIB.UI.ALERTS.NO_FILTER_RESULTS_ALERT' | translate}}
+</app-alert>
+
+<app-alert *ngIf="destinations.length === 0" color="warn">
+  {{'FACILITY_DETAIL.SERVICES_DESTINATIONS.NO_DESTINATION' | translate}}
+</app-alert>
diff --git a/apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.scss b/apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.ts b/apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.ts
new file mode 100644
index 000000000..44e8af0a6
--- /dev/null
+++ b/apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.ts
@@ -0,0 +1,98 @@
+import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core';
+import { MatSort } from '@angular/material/sort';
+import { RichDestination, RichResource } from '@perun-web-apps/perun/openapi';
+import { SelectionModel } from '@angular/cdk/collections';
+import { MatPaginator, PageEvent } from '@angular/material/paginator';
+import { MatTableDataSource } from '@angular/material/table';
+import { TABLE_ITEMS_COUNT_OPTIONS } from '@perun-web-apps/perun/utils';
+
+@Component({
+  selector: 'app-perun-web-apps-destination-list',
+  templateUrl: './destination-list.component.html',
+  styleUrls: ['./destination-list.component.scss']
+})
+export class DestinationListComponent implements OnChanges {
+
+  constructor() { }
+
+  @Input()
+  destinations: RichDestination[] = [];
+  @Input()
+  selection = new SelectionModel<RichResource>(true, []);
+  @Input()
+  filterValue: string;
+  @Input()
+  pageSize = 10;
+  @Input()
+  displayedColumns: string[];
+
+  @Output()
+  page: EventEmitter<PageEvent> = new EventEmitter<PageEvent>();
+
+  @ViewChild(MatSort, { static: true }) set matSort(ms: MatSort) {
+    this.sort = ms;
+    this.setDataSource();
+  }
+
+  private sort: MatSort;
+
+  dataSource: MatTableDataSource<RichDestination>;
+
+  exporting = false;
+
+
+  @ViewChild(MatPaginator) paginator: MatPaginator;
+  pageSizeOptions = TABLE_ITEMS_COUNT_OPTIONS;
+
+  ngOnChanges(changes: SimpleChanges) {
+    this.dataSource = new MatTableDataSource<RichDestination>(this.destinations);
+    this.setDataSource();
+    this.dataSource.filter = this.filterValue.toLowerCase();
+  }
+
+  setDataSource() {
+    if (!!this.dataSource) {
+      this.dataSource.sort = this.sort;
+      this.dataSource.paginator = this.paginator;
+      this.dataSource.sortingDataAccessor = (item, property) => {
+        switch (property) {
+          case 'service': {
+            return item.service.name;
+          }
+          default: return item[property];
+        }
+      };
+      this.dataSource.filterPredicate = (data, filter) => {
+        const dataStr = data.service.name + data.id + data.destination + data.type + data.propagationType;
+        return dataStr.indexOf(filter) !== -1;
+      }
+    }
+  }
+
+  /** 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. */
+  masterToggle() {
+    this.isAllSelected() ?
+      this.selection.clear() :
+      this.dataSource.data.forEach(row => this.selection.select(row));
+  }
+
+  /** The label for the checkbox on the passed row */
+  checkboxLabel(row?: RichResource): string {
+    if (!row) {
+      return `${this.isAllSelected() ? 'select' : 'deselect'} all`;
+    }
+    return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row ${row.id + 1}`;
+  }
+
+  pageChanged(event: PageEvent) {
+    this.page.emit(event);
+  }
+
+}
diff --git a/apps/admin-gui/src/app/shared/components/dialogs/add-services-destination-dialog/add-services-destination-dialog.component.html b/apps/admin-gui/src/app/shared/components/dialogs/add-services-destination-dialog/add-services-destination-dialog.component.html
new file mode 100644
index 000000000..e16372886
--- /dev/null
+++ b/apps/admin-gui/src/app/shared/components/dialogs/add-services-destination-dialog/add-services-destination-dialog.component.html
@@ -0,0 +1,96 @@
+<div class="{{data.theme}}">
+
+  <h1 mat-dialog-title>{{'DIALOGS.ADD_SERVICE_DESTINATION.TITLE' | translate}}</h1>
+  <div mat-dialog-content>
+    <div [@openClose]="isInvalid() ? 'open' : 'closed'">
+      <app-alert color="error">{{'DIALOGS.ADD_SERVICE_DESTINATION.' + invalidNotification | translate}}</app-alert>
+    </div>
+    <div class="font-italic">{{'DIALOGS.ADD_SERVICE_DESTINATION.DESCRIPTION' | translate}}</div>
+    <div class="d-flex">
+      <span class="w-25 m-auto font-weight-bold">{{'DIALOGS.ADD_SERVICE_DESTINATION.SERVICE' | translate}}:</span>
+      <div class="w-75">
+        <mat-form-field class="w-100">
+          <mat-select [(ngModel)]="selectedService" required>
+            <mat-option *ngIf="services.length !== 0" value="all">{{'DIALOGS.ADD_SERVICE_DESTINATION.SELECTION_ALL' | translate}}</mat-option>
+            <mat-option *ngIf="services.length === 0" value="noService">{{'DIALOGS.ADD_SERVICE_DESTINATION.NO_SERVICE' | translate}}</mat-option>
+            <mat-option *ngFor="let service of services" [value]="service">
+              {{service.name}}
+            </mat-option>
+          </mat-select>
+          <mat-error *ngIf="selectedService == undefined">
+            {{'DIALOGS.ADD_SERVICE_DESTINATION.CHOOSE_SERVICE' | translate}}
+          </mat-error>
+        </mat-form-field>
+      </div>
+    </div>
+    <div class="d-flex">
+      <span class="w-25 m-auto font-weight-bold"></span>
+      <div class="w-75">
+        <mat-checkbox (change)="getServices()" [(ngModel)]="servicesOnFacility">{{'DIALOGS.ADD_SERVICE_DESTINATION.IS_SERVICES_ONLY_ON_FACILITY' | translate}}</mat-checkbox>
+      </div>
+    </div>
+    <div class="d-flex">
+      <span class="w-25 m-auto font-weight-bold">{{'DIALOGS.ADD_SERVICE_DESTINATION.TYPE' | translate}}:</span>
+      <div class="w-75">
+        <mat-form-field class="w-100">
+          <mat-select [(ngModel)]="selectedType">
+            <mat-option *ngFor="let type of types" [value]="type">
+              {{getTypeForView(type)}}
+            </mat-option>
+          </mat-select>
+        </mat-form-field>
+      </div>
+    </div>
+    <div class="d-flex">
+      <span class="w-25 m-auto font-weight-bold">{{getTypeForView(selectedType)}}:</span>
+      <div class="w-75">
+        <mat-form-field class="w-100">
+          <input
+            matInput
+            required
+            [disabled]="useFacilityHost && selectedType === 'host'"
+            [(ngModel)]="destination">
+          <mat-error *ngIf="!(useFacilityHost && selectedType === 'host')">
+            {{'DIALOGS.ADD_SERVICE_DESTINATION.REQUIRED_FIELD' | translate}}
+          </mat-error>
+        </mat-form-field>
+      </div>
+    </div>
+    <div class="d-flex" *ngIf="selectedType == 'host'">
+      <span class="w-25 m-auto font-weight-bold"></span>
+      <div class="w-75">
+        <mat-checkbox [(ngModel)]="useFacilityHost">{{'DIALOGS.ADD_SERVICE_DESTINATION.USE_FACILITY_HOST' | translate}}</mat-checkbox>
+      </div>
+    </div>
+    <div class="d-flex">
+      <span class="w-25 m-auto font-weight-bold">{{'DIALOGS.ADD_SERVICE_DESTINATION.PROPAGATION' | translate}}:</span>
+      <div class="w-75">
+        <mat-form-field class="w-100">
+          <mat-select [(ngModel)]="selectedPropagation">
+            <mat-option *ngFor="let propagation of propagations" [value]="propagation">
+              {{propagation}}
+            </mat-option>
+          </mat-select>
+        </mat-form-field>
+      </div>
+    </div>
+    <div class="font-italic">{{'DIALOGS.ADD_SERVICE_DESTINATION.PROPAGATION_TYPE_' + selectedPropagation | translate}}</div>
+  </div>
+  <div mat-dialog-actions>
+    <button
+      mat-flat-button
+      class="ml-auto"
+      (click)="onCancel()">
+      {{'DIALOGS.ADD_SERVICE_DESTINATION.CANCEL' | translate}}
+    </button>
+    <button
+      mat-flat-button
+      class="ml-2"
+      color="accent"
+      [disabled]="isInvalid()"
+      (click)="onSubmit()">
+      {{'DIALOGS.ADD_SERVICE_DESTINATION.ADD' | translate}}
+    </button>
+  </div>
+</div>
+
diff --git a/apps/admin-gui/src/app/shared/components/dialogs/add-services-destination-dialog/add-services-destination-dialog.component.scss b/apps/admin-gui/src/app/shared/components/dialogs/add-services-destination-dialog/add-services-destination-dialog.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/admin-gui/src/app/shared/components/dialogs/add-services-destination-dialog/add-services-destination-dialog.component.ts b/apps/admin-gui/src/app/shared/components/dialogs/add-services-destination-dialog/add-services-destination-dialog.component.ts
new file mode 100644
index 000000000..c9bbcc700
--- /dev/null
+++ b/apps/admin-gui/src/app/shared/components/dialogs/add-services-destination-dialog/add-services-destination-dialog.component.ts
@@ -0,0 +1,142 @@
+import { Component, Inject, OnInit } from '@angular/core';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+import { openClose } from '../../../animations/Animations';
+import {
+  DestinationPropagationType,
+  DestinationType,
+  FacilitiesManagerService,
+  Facility,
+  Host,
+  Service,
+  ServicesManagerService
+} from '@perun-web-apps/perun/openapi';
+
+export interface  AddServicesDestinationDialogData {
+  facility: Facility;
+  theme: string;
+}
+
+@Component({
+  selector: 'app-perun-web-apps-add-services-destination-dialog',
+  templateUrl: './add-services-destination-dialog.component.html',
+  styleUrls: ['./add-services-destination-dialog.component.scss'],
+  animations: [
+    openClose
+  ]
+})
+export class AddServicesDestinationDialogComponent implements OnInit {
+
+  constructor(public dialogRef: MatDialogRef<AddServicesDestinationDialogComponent>,
+              @Inject(MAT_DIALOG_DATA) public data:  AddServicesDestinationDialogData,
+              public facilitiesManager: FacilitiesManagerService,
+              public servicesManager: ServicesManagerService) { }
+
+
+  hosts: Host[];
+  servicesOnFacility: boolean;
+  services: Service[] = [];
+  selectedService: Service;
+  types: string[] = ['host', 'user@host', 'user@host:port','user@host-windows', 'host-windows-proxy',
+    'url', 'mail', 'semail', 'service-specific'];
+  selectedType = 'host';
+  propagations: string[] = ['PARALLEL', 'DUMMY'];
+  selectedPropagation  = 'PARALLEL';
+  destination = '';
+  useFacilityHost = false;
+  invalidNotification = '';
+
+  ngOnInit() {
+    this.facilitiesManager.getHosts(this.data.facility.id).subscribe( hosts => {
+      this.hosts = hosts;
+      this.servicesOnFacility = true;
+      this.getServices();
+    });
+  }
+
+  onCancel() {
+    this.dialogRef.close();
+  }
+
+  onSubmit() {
+    // @ts-ignore
+    if (this.selectedService === 'all') {
+      if (this.useFacilityHost) {
+        this.servicesManager.addDestinationsDefinedByHostsOnFacilityWithListOfServiceAndFacility(
+          {services: this.services, facility: this.data.facility.id}).subscribe( destination => {
+          this.dialogRef.close(true);
+        });
+      }
+      else {
+        this.servicesManager.addDestinationToMultipleServices({services: this.services, facility: this.data.facility.id,
+        destination: this.destination, type: this.selectedType as DestinationType,
+          propagationType: this.selectedPropagation as DestinationPropagationType}).subscribe( destination => {
+          this.dialogRef.close(true);
+        });
+      }
+    } else {
+      if (this.useFacilityHost) {
+        this.servicesManager.addDestinationsDefinedByHostsOnFacilityWithServiceAndFacility(
+          this.selectedService.id, this.data.facility.id
+        ).subscribe( destination => {
+          this.dialogRef.close(true);
+        });
+      }
+      else {
+        this.servicesManager.addDestination(this.selectedService.id, this.data.facility.id,
+          this.destination, this.selectedType as DestinationType,
+          this.selectedPropagation as DestinationPropagationType).subscribe( destination => {
+          this.dialogRef.close(true);
+        });
+      }
+    }
+  }
+
+  getServices() {
+    if (this.servicesOnFacility) {
+      this.servicesManager.getAssignedServices(this.data.facility.id).subscribe( services => {
+        this.services = services;
+
+      });
+    } else {
+      this.servicesManager.getServices().subscribe( services => {
+        this.services = services;
+      })
+    }
+    this.selectedService = undefined;
+  }
+
+  getTypeForView(type: string) {
+    if (type === 'semail') {
+      return 'Send Mail';
+    }
+    if (type === 'service-specific') {
+      return 'Service Specific';
+    }
+    return type;
+  }
+
+  isInvalid():boolean {
+    // @ts-ignore
+    if (this.selectedService === 'noService') {
+      this.invalidNotification = 'NO_SERVICE';
+      return true;
+    }
+    // @ts-ignore
+    if (this.selectedService === undefined) {
+      this.invalidNotification = 'CHOOSE_SERVICE';
+      return true;
+    }
+    if (this.destination === '' && !this.useFacilityHost) {
+      this.invalidNotification = 'REQUIRED_FIELD';
+      return true;
+    }
+    if (this.selectedType === 'mail'|| this.selectedType === 'semail') {
+      const regexp = new RegExp(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/);
+      if (!regexp.test(this.destination)) {
+        this.invalidNotification = 'TYPE_EMAIL';
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/apps/admin-gui/src/app/shared/components/dialogs/remove-destination-dialog/remove-destination-dialog.component.html b/apps/admin-gui/src/app/shared/components/dialogs/remove-destination-dialog/remove-destination-dialog.component.html
new file mode 100644
index 000000000..027db15a3
--- /dev/null
+++ b/apps/admin-gui/src/app/shared/components/dialogs/remove-destination-dialog/remove-destination-dialog.component.html
@@ -0,0 +1,39 @@
+<div class="{{data.theme}}">
+
+  <h1 mat-dialog-title>{{'DIALOGS.REMOVE_DESTINATIONS.TITLE' | translate}}</h1>
+  <div mat-dialog-content>
+    <app-alert
+      [color]="'warn'">{{'DIALOGS.REMOVE_DESTINATIONS.WARNING' | translate}}</app-alert>
+    <p>
+      {{'DIALOGS.REMOVE_DESTINATIONS.DESCRIPTION' | translate}}
+    </p>
+
+    <div class="font-weight-bold">
+      {{'DIALOGS.REMOVE_DESTINATIONS.ASK' | translate}}
+    </div>
+
+    <app-perun-web-apps-destination-list
+      [pageSize]="pageSize"
+      (page)="pageChanged($event)"
+      [destinations]="this.data.destinations"
+      [filterValue]="''"
+      [displayedColumns]="displayedColumns">
+    </app-perun-web-apps-destination-list>
+
+  </div>
+  <div mat-dialog-actions>
+    <button
+      mat-flat-button
+      class="ml-auto"
+      (click)="onCancel()">
+      {{'DIALOGS.REMOVE_DESTINATIONS.CANCEL' | translate}}
+    </button>
+    <button
+      mat-flat-button
+      class="ml-2"
+      color="warn"
+      (click)="onSubmit()">
+      {{'DIALOGS.REMOVE_DESTINATIONS.DELETE' | translate}}
+    </button>
+  </div>
+</div>
diff --git a/apps/admin-gui/src/app/shared/components/dialogs/remove-destination-dialog/remove-destination-dialog.component.scss b/apps/admin-gui/src/app/shared/components/dialogs/remove-destination-dialog/remove-destination-dialog.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/admin-gui/src/app/shared/components/dialogs/remove-destination-dialog/remove-destination-dialog.component.ts b/apps/admin-gui/src/app/shared/components/dialogs/remove-destination-dialog/remove-destination-dialog.component.ts
new file mode 100644
index 000000000..274ede261
--- /dev/null
+++ b/apps/admin-gui/src/app/shared/components/dialogs/remove-destination-dialog/remove-destination-dialog.component.ts
@@ -0,0 +1,44 @@
+import { Component, Inject, OnInit } from '@angular/core';
+import { RichDestination } from '@perun-web-apps/perun/openapi';
+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
+import { PageEvent } from '@angular/material/paginator';
+import { TABLE_FACILITY_SERVICES_DESTINATION_LIST, TableConfigService } from '@perun-web-apps/config/table-config';
+
+export interface RemoveDestinationDialogData {
+  destinations: RichDestination[];
+  theme: string;
+}
+
+@Component({
+  selector: 'app-perun-web-apps-remove-destination-dialog',
+  templateUrl: './remove-destination-dialog.component.html',
+  styleUrls: ['./remove-destination-dialog.component.scss']
+})
+export class RemoveDestinationDialogComponent implements OnInit {
+
+  constructor(public dialogRef: MatDialogRef<RemoveDestinationDialogComponent>,
+              @Inject(MAT_DIALOG_DATA) public data: RemoveDestinationDialogData,
+              private tableConfigService: TableConfigService) { }
+
+  displayedColumns: string[] = ['destinationId', 'service', 'destination', 'type', 'propagationType'];
+
+  pageSize: number;
+  tableId = TABLE_FACILITY_SERVICES_DESTINATION_LIST;
+
+  ngOnInit() {
+    this.pageSize = this.tableConfigService.getTablePageSize(this.tableId);
+  }
+
+  onCancel() {
+    this.dialogRef.close(false);
+  }
+
+  onSubmit() {
+    this.dialogRef.close(true);
+  }
+
+  pageChanged(event: PageEvent) {
+    this.pageSize = event.pageSize;
+    this.tableConfigService.setTablePageSize(this.tableId, event.pageSize);
+  }
+}
diff --git a/apps/admin-gui/src/app/shared/shared.module.ts b/apps/admin-gui/src/app/shared/shared.module.ts
index 247c4ffb3..684cf3db2 100644
--- a/apps/admin-gui/src/app/shared/shared.module.ts
+++ b/apps/admin-gui/src/app/shared/shared.module.ts
@@ -153,7 +153,9 @@ import { EditAttributeDialogComponent } from './components/dialogs/edit-attribut
 import { UserSettingsAppConfigurationComponent } from './components/user-detail-page/user-settings/user-settings-app-configuration/user-settings-app-configuration.component';
 import { ConfigTableConfigModule } from '@perun-web-apps/config/table-config';
 import { PerunPipesModule } from '@perun-web-apps/perun/pipes';
-
+import { RemoveDestinationDialogComponent } from './components/dialogs/remove-destination-dialog/remove-destination-dialog.component';
+import { DestinationListComponent } from './components/destination-list/destination-list.component';
+import { AddServicesDestinationDialogComponent } from './components/dialogs/add-services-destination-dialog/add-services-destination-dialog.component';
 @NgModule({
   imports: [
     CommonModule,
@@ -280,7 +282,8 @@ import { PerunPipesModule } from '@perun-web-apps/perun/pipes';
     ExtSourcesListComponent,
     ExtSourceTypePipe,
     ConfigTableConfigModule,
-    SideMenuRootItemComponent
+    SideMenuRootItemComponent,
+    DestinationListComponent
   ],
   declarations: [
     PerunNavComponent,
@@ -384,7 +387,10 @@ import { PerunPipesModule } from '@perun-web-apps/perun/pipes';
     ExtSourceTypePipe,
     UserRolesComponent,
     EditAttributeDialogComponent,
-    UserSettingsAppConfigurationComponent
+    UserSettingsAppConfigurationComponent,
+    RemoveDestinationDialogComponent,
+    DestinationListComponent,
+    AddServicesDestinationDialogComponent
   ],
   providers: [
     AnyToStringPipe,
diff --git a/apps/admin-gui/src/app/shared/side-menu/side-menu-item.service.ts b/apps/admin-gui/src/app/shared/side-menu/side-menu-item.service.ts
index 550722a36..f6cd17ce9 100644
--- a/apps/admin-gui/src/app/shared/side-menu/side-menu-item.service.ts
+++ b/apps/admin-gui/src/app/shared/side-menu/side-menu-item.service.ts
@@ -206,6 +206,11 @@ export class SideMenuItemService {
           url: [`/facilities/${facility.id}/service-config`],
           activatedRegex: 'facilities/\\d+/service-config'
         },
+        {
+          label: 'MENU_ITEMS.FACILITY.SERVICES_DESTINATIONS',
+          url: [`/facilities/${facility.id}/services-destinations`],
+          activatedRegex: 'facilities/\\d+/services-destinations'
+        },
         {
           label: 'MENU_ITEMS.FACILITY.SETTINGS',
           url: ['/facilities', facility.id, 'settings'],
diff --git a/apps/admin-gui/src/assets/i18n/en.json b/apps/admin-gui/src/assets/i18n/en.json
index 41dbd113b..630da4484 100644
--- a/apps/admin-gui/src/assets/i18n/en.json
+++ b/apps/admin-gui/src/assets/i18n/en.json
@@ -101,6 +101,19 @@
       "ALL": "All",
       "NO_GROUPS_ALERT": "No groups found."
     },
+    "SERVICES_DESTINATIONS": {
+      "TITLE": "Services Destinations",
+      "ADD": "Add",
+      "REMOVE": "Remove",
+      "FILTER": "Filter",
+      "TABLE_DESTINATION_ID": "Destination ID",
+      "TABLE_SERVICE": "Service",
+      "TABLE_DESTINATION": "Destination",
+      "TABLE_TYPE": "Type",
+      "TABLE_PROPAGATION_TYPE": "Propagation Type",
+      "NO_DESTINATION": "Facility has no services destinations. Service configuration can't be propagated.",
+      "REMOVE_SUCCESS": "Facilities were successfully removed"
+    },
     "SETTINGS": {
       "ATTRIBUTES": {
         "TITLE": "Attributes",
@@ -512,7 +525,8 @@
       "SETTINGS": "Settings",
       "ATTRIBUTES": "Attributes",
       "SERVICE_CONFIG": "Services configuration",
-      "MANAGERS": "Managers"
+      "MANAGERS": "Managers",
+      "SERVICES_DESTINATIONS": "Services destinations"
     },
     "RESOURCE": {
       "OVERVIEW": "Overview",
@@ -1085,6 +1099,33 @@
       "TOOLTIP_CONFIRM": "Fill every required field",
       "SHOW_KEYS": "Show keys",
       "SERVICES": "Dependent services:"
+    },
+    "REMOVE_DESTINATIONS": {
+      "TITLE": "Confirm delete action",
+      "WARNING": "Removing destination will stop propagation of service's configuration for this destination/service.",
+      "DESCRIPTION": "Following destinations will be removed.",
+      "ASK": "Do you want to proceed?",
+      "CANCEL": "Cancel",
+      "DELETE": "Delete"
+    },
+    "ADD_SERVICE_DESTINATION": {
+      "TITLE": "Add destination",
+      "DESCRIPTION": "Please add destinations for service configuration delivery. New service configuration can be performed directly on facility (dest. type HOST) or sent to URL or by an email.",
+      "SERVICE": "Service",
+      "SELECTION_ALL": "All",
+      "NO_SERVICE": "No service available",
+      "CHOOSE_SERVICE": "You must choose service",
+      "TYPE_EMAIL": "Please type valid email",
+      "IS_SERVICES_ONLY_ON_FACILITY": "Show only services on facility",
+      "TYPE": "Type",
+      "HOST": "Host",
+      "REQUIRED_FIELD": "Destination value can't be empty.",
+      "USE_FACILITY_HOST": "Use names of all facility's hosts",
+      "PROPAGATION": "Propagation",
+      "PROPAGATION_TYPE_PARALLEL": "PARALLEL - Data for all destinations and one service are pushed in parallel.",
+      "PROPAGATION_TYPE_DUMMY": "DUMMY - Service provisioning data is generated by Perun, but not pushed to destination. Destinations can pull data by themselves.",
+      "CANCEL": "Cancel",
+      "ADD": "Add"
     }
   },
   "MEMBERS_LIST": {
diff --git a/libs/config/table-config/src/lib/table-config.service.ts b/libs/config/table-config/src/lib/table-config.service.ts
index 5ea4af309..cd817f55b 100644
--- a/libs/config/table-config/src/lib/table-config.service.ts
+++ b/libs/config/table-config/src/lib/table-config.service.ts
@@ -67,3 +67,4 @@ export const TABLE_USER_DETAIL_ADMIN_GROUPS = '33';
 export const TABLE_GROUP_SETTINGS_RELATIONS = '34';
 export const TABLE_GROUP_SUBGROUPS = '35';
 export const TABLE_VO_GROUPS = '36';
+export const TABLE_FACILITY_SERVICES_DESTINATION_LIST = '37';
diff --git a/libs/perun/services/src/lib/custom-icon.service.ts b/libs/perun/services/src/lib/custom-icon.service.ts
index c445f9902..0257e5a28 100644
--- a/libs/perun/services/src/lib/custom-icon.service.ts
+++ b/libs/perun/services/src/lib/custom-icon.service.ts
@@ -111,10 +111,16 @@ export class CustomIconService {
       url: 'assets/img/PerunWebImages/external_sources-white.svg',
       name: 'perun-external-sources'
     },
+<<<<<<< HEAD
     // TESTING PURPOSES
     {
       url: 'assets/img/settings1-blue.svg',
       name: 'settings-blue'
+=======
+    {
+      url: 'assets/img/PerunWebImages/service_destination-blue.svg',
+      name: 'perun-service_destination'
+>>>>>>> f232c60... Facilities - services destinations page
     },
   ];
 
-- 
GitLab


From ba7ebb72c37f16dadf7c047c94f5653ffbf1cf76 Mon Sep 17 00:00:00 2001
From: Lucie Kureckova <lkureckova@seznam.cz>
Date: Sun, 10 May 2020 17:49:10 +0200
Subject: [PATCH 2/4] Facilities - services destinations page * resolve
 conflicts

---
 libs/perun/services/src/lib/custom-icon.service.ts | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/libs/perun/services/src/lib/custom-icon.service.ts b/libs/perun/services/src/lib/custom-icon.service.ts
index 0257e5a28..95a418b1e 100644
--- a/libs/perun/services/src/lib/custom-icon.service.ts
+++ b/libs/perun/services/src/lib/custom-icon.service.ts
@@ -111,16 +111,14 @@ export class CustomIconService {
       url: 'assets/img/PerunWebImages/external_sources-white.svg',
       name: 'perun-external-sources'
     },
-<<<<<<< HEAD
     // TESTING PURPOSES
     {
       url: 'assets/img/settings1-blue.svg',
       name: 'settings-blue'
-=======
+    },
     {
       url: 'assets/img/PerunWebImages/service_destination-blue.svg',
       name: 'perun-service_destination'
->>>>>>> f232c60... Facilities - services destinations page
     },
   ];
 
-- 
GitLab


From 5a14e6bce307eb1830e3fac8162a76e359d9fb21 Mon Sep 17 00:00:00 2001
From: Daniel Fecko <38562381+DanoFecko@users.noreply.github.com>
Date: Thu, 14 May 2020 09:41:38 +0200
Subject: [PATCH 3/4] Handling error when perunPrincipal.user is null (#219)

* Error message for case  when user doesn't exist

* Error message for case when perunPrincipal.user is null
---
 .../services/common/admin-gui-config.service.ts     |  1 -
 apps/admin-gui/src/assets/i18n/en.json              |  3 +++
 libs/general/src/index.ts                           |  1 +
 libs/general/src/lib/general.module.ts              |  8 +++++---
 .../user-dont-exist-dialog.component.html           |  1 +
 .../user-dont-exist-dialog.component.scss           |  0
 .../user-dont-exist-dialog.component.ts             | 13 +++++++++++++
 .../components/src/lib/perun-components.module.ts   |  5 ++++-
 libs/perun/services/src/lib/init-auth.service.ts    | 13 ++++++++++---
 9 files changed, 37 insertions(+), 8 deletions(-)
 create mode 100644 libs/general/src/lib/user-dont-exist-dialog/user-dont-exist-dialog.component.html
 create mode 100644 libs/general/src/lib/user-dont-exist-dialog/user-dont-exist-dialog.component.scss
 create mode 100644 libs/general/src/lib/user-dont-exist-dialog/user-dont-exist-dialog.component.ts

diff --git a/apps/admin-gui/src/app/core/services/common/admin-gui-config.service.ts b/apps/admin-gui/src/app/core/services/common/admin-gui-config.service.ts
index ad8f6aec0..067c978bd 100644
--- a/apps/admin-gui/src/app/core/services/common/admin-gui-config.service.ts
+++ b/apps/admin-gui/src/app/core/services/common/admin-gui-config.service.ts
@@ -107,7 +107,6 @@ export class AdminGuiConfigService {
   }
 
   private handlePrincipalErr(err: any) {
-    console.log("sdfsdf");
     this.translate.get('GENERAL.PRINCIPAL.ERROR.TITLE').subscribe(sdf => console.log(sdf));
     this.dialog.open(ServerDownDialogComponent, {
       data: {
diff --git a/apps/admin-gui/src/assets/i18n/en.json b/apps/admin-gui/src/assets/i18n/en.json
index 41dbd113b..a9dc34538 100644
--- a/apps/admin-gui/src/assets/i18n/en.json
+++ b/apps/admin-gui/src/assets/i18n/en.json
@@ -1336,6 +1336,9 @@
           "NAME": "Name",
           "VALUE": "Value",
           "DESCRIPTION": "Description"
+        },
+        "USER_DONT_EXIST": {
+          "TITLE": "Requested user (by ID or external identity) doesn't exist."
         }
       },
       "ORGANIZATIONS": {
diff --git a/libs/general/src/index.ts b/libs/general/src/index.ts
index 193737685..bff3eee99 100644
--- a/libs/general/src/index.ts
+++ b/libs/general/src/index.ts
@@ -1,2 +1,3 @@
 export * from './lib/general.module';
 export * from './lib/server-down-dialog/server-down-dialog.component';
+export * from './lib/user-dont-exist-dialog/user-dont-exist-dialog.component'
diff --git a/libs/general/src/lib/general.module.ts b/libs/general/src/lib/general.module.ts
index 74933ea85..08a7825a8 100644
--- a/libs/general/src/lib/general.module.ts
+++ b/libs/general/src/lib/general.module.ts
@@ -3,10 +3,12 @@ import { CommonModule } from '@angular/common';
 import { ServerDownDialogComponent } from './server-down-dialog/server-down-dialog.component';
 import { MatDialogModule } from '@angular/material/dialog';
 import { MatButtonModule } from '@angular/material/button';
+import { UserDontExistDialogComponent } from './user-dont-exist-dialog/user-dont-exist-dialog.component';
+import { TranslateModule } from '@ngx-translate/core';
 
 @NgModule({
-  imports: [CommonModule, MatDialogModule, MatButtonModule],
-  exports: [ServerDownDialogComponent],
-  declarations: [ServerDownDialogComponent]
+  imports: [CommonModule, MatDialogModule, MatButtonModule, TranslateModule],
+  exports: [ServerDownDialogComponent, UserDontExistDialogComponent],
+  declarations: [ServerDownDialogComponent, UserDontExistDialogComponent]
 })
 export class GeneralModule {}
diff --git a/libs/general/src/lib/user-dont-exist-dialog/user-dont-exist-dialog.component.html b/libs/general/src/lib/user-dont-exist-dialog/user-dont-exist-dialog.component.html
new file mode 100644
index 000000000..3d31c3db6
--- /dev/null
+++ b/libs/general/src/lib/user-dont-exist-dialog/user-dont-exist-dialog.component.html
@@ -0,0 +1 @@
+<h2 mat-dialog-content class="mt-2 mb-2">{{'SHARED_LIB.PERUN.COMPONENTS.USER_DONT_EXIST.TITLE' | translate}}</h2>
diff --git a/libs/general/src/lib/user-dont-exist-dialog/user-dont-exist-dialog.component.scss b/libs/general/src/lib/user-dont-exist-dialog/user-dont-exist-dialog.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/libs/general/src/lib/user-dont-exist-dialog/user-dont-exist-dialog.component.ts b/libs/general/src/lib/user-dont-exist-dialog/user-dont-exist-dialog.component.ts
new file mode 100644
index 000000000..d1ac4c104
--- /dev/null
+++ b/libs/general/src/lib/user-dont-exist-dialog/user-dont-exist-dialog.component.ts
@@ -0,0 +1,13 @@
+import { Component} from '@angular/core';
+import { MatDialogRef } from '@angular/material/dialog';
+
+@Component({
+  selector: 'perun-web-apps-user-dont-exist-dialog',
+  templateUrl: './user-dont-exist-dialog.component.html',
+  styleUrls: ['./user-dont-exist-dialog.component.scss']
+})
+export class UserDontExistDialogComponent {
+
+  constructor(public dialogRef: MatDialogRef<UserDontExistDialogComponent>) {
+  }
+}
diff --git a/libs/perun/components/src/lib/perun-components.module.ts b/libs/perun/components/src/lib/perun-components.module.ts
index 8c992982f..a929b0da5 100644
--- a/libs/perun/components/src/lib/perun-components.module.ts
+++ b/libs/perun/components/src/lib/perun-components.module.ts
@@ -37,6 +37,8 @@ import { MatRippleModule } from '@angular/material/core';
 import { AttributeValueListEditDialogComponent } from './attributes-list/attribute-value/attribute-value-list/attribute-value-list-edit-dialog/attribute-value-list-edit-dialog.component';
 import { AttributeValueListDeleteDialogComponent } from './attributes-list/attribute-value/attribute-value-list/attribute-value-list-delete-dialog/attribute-value-list-delete-dialog.component';
 import { PerunPipesModule } from '@perun-web-apps/perun/pipes';
+import { MatDialogModule } from '@angular/material/dialog';
+
 
 @NgModule({
   imports: [
@@ -61,7 +63,8 @@ import { PerunPipesModule } from '@perun-web-apps/perun/pipes';
     ReactiveFormsModule,
     MatInputModule,
     MatRippleModule,
-    PerunPipesModule
+    PerunPipesModule,
+    MatDialogModule
   ],
   declarations: [
     VoSelectTableComponent,
diff --git a/libs/perun/services/src/lib/init-auth.service.ts b/libs/perun/services/src/lib/init-auth.service.ts
index ee886e2d4..2d755dcbc 100644
--- a/libs/perun/services/src/lib/init-auth.service.ts
+++ b/libs/perun/services/src/lib/init-auth.service.ts
@@ -4,6 +4,8 @@ import { AuthService } from './auth.service';
 import { StoreService } from './store.service';
 import { GuiAuthResolver } from './gui-auth-resolver.service';
 import { AuthzResolverService } from '@perun-web-apps/perun/openapi';
+import { MatDialog } from '@angular/material/dialog';
+import { UserDontExistDialogComponent } from '@perun-web-apps/general';
 
 @Injectable({
   providedIn: 'root'
@@ -14,7 +16,8 @@ export class InitAuthService {
     private authService: AuthService,
     private storeService: StoreService,
     private authResolver: GuiAuthResolver,
-    private authzService: AuthzResolverService
+    private authzService: AuthzResolverService,
+    private dialog: MatDialog
   ) {
   }
 
@@ -40,8 +43,12 @@ export class InitAuthService {
     return this.authzService.getPerunPrincipal()
       .toPromise()
       .then(perunPrincipal => {
-        this.storeService.setPerunPrincipal(perunPrincipal);
-        this.authResolver.init(perunPrincipal);
+        if (perunPrincipal.user === null) {
+          this.dialog.open(UserDontExistDialogComponent, { disableClose: true });
+        } else {
+          this.storeService.setPerunPrincipal(perunPrincipal);
+          this.authResolver.init(perunPrincipal);
+        }
       });
   }
 }
-- 
GitLab


From 59fa84977fe333145b3a4bc7b1de4b2ca377869a Mon Sep 17 00:00:00 2001
From: xkureck <445264@mail.muni.cz>
Date: Fri, 15 May 2020 18:36:48 +0200
Subject: [PATCH 4/4] Facilities - services destinations page * fixed mat
 paginator * add successful notification when adding destinations * long lines
 were divided

---
 ...acility-services-destinations.component.ts | 10 ++++++---
 .../destination-list.component.html           | 19 ++++++++++++++---
 .../destination-list.component.ts             | 21 +++++++++++++++----
 apps/admin-gui/src/assets/i18n/en.json        |  3 ++-
 4 files changed, 42 insertions(+), 11 deletions(-)

diff --git a/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-services-destinations/facility-services-destinations.component.ts b/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-services-destinations/facility-services-destinations.component.ts
index 1f83d1e49..0926f4ab0 100644
--- a/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-services-destinations/facility-services-destinations.component.ts
+++ b/apps/admin-gui/src/app/facilities/pages/facility-detail-page/facility-services-destinations/facility-services-destinations.component.ts
@@ -50,9 +50,9 @@ export class FacilityServicesDestinationsComponent implements OnInit {
   tableId = TABLE_FACILITY_SERVICES_DESTINATION_LIST;
 
   ngOnInit() {
-    this.loading = true;
-    this.pageSize = this.tableConfigService.getTablePageSize(this.tableId);
 
+    this.pageSize = this.tableConfigService.getTablePageSize(this.tableId);
+    this.loading = true;
     this.route.parent.params.subscribe(parentParams => {
       const facilityId = parentParams['facilityId'];
 
@@ -81,7 +81,10 @@ export class FacilityServicesDestinationsComponent implements OnInit {
 
     dialogRef.afterClosed().subscribe(result => {
       if (result) {
-        this.refreshTable();
+        this.translate.get('FACILITY_DETAIL.SERVICES_DESTINATIONS.ADD_SUCCESS').subscribe(successMessage => {
+          this.refreshTable();
+          this.notificator.showSuccess(successMessage);
+        });
       }
     });
   }
@@ -105,6 +108,7 @@ export class FacilityServicesDestinationsComponent implements OnInit {
   }
 
   pageChanged(event: PageEvent) {
+    console.log(event.pageSize);
     this.pageSize = event.pageSize;
     this.tableConfigService.setTablePageSize(this.tableId, event.pageSize);
   }
diff --git a/apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.html b/apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.html
index e33b6c2bc..7fa53ade9 100644
--- a/apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.html
+++ b/apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.html
@@ -1,7 +1,20 @@
-<div class="card mt-3" [class.hide-table]="exporting" [hidden]="dataSource.filteredData.length === 0 || destinations.length === 0">
+<div class="card mt-3"
+     [class.hide-table]="exporting"
+     [hidden]="dataSource.filteredData.length === 0 || destinations.length === 0">
   <div class="card-body">
-    <perun-web-apps-table-options (end)="exporting = false" (start)="exporting = true" [exporter]="exporter" class="ml-auto"></perun-web-apps-table-options>
-    <table mat-table matTableExporter [dataSource]="dataSource" #exporter="matTableExporter" matSort matSortActive="id" matSortDirection="asc" matSortDisableClear
+    <perun-web-apps-table-options
+      (end)="exporting = false"
+      (start)="exporting = true"
+      [exporter]="exporter"
+      class="ml-auto">
+    </perun-web-apps-table-options>
+    <table mat-table matTableExporter
+           [dataSource]="dataSource"
+           #exporter="matTableExporter"
+           matSort
+           matSortActive="id"
+           matSortDirection="asc"
+           matSortDisableClear
            class="w-100">
 
       <ng-container matColumnDef="select">
diff --git a/apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.ts b/apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.ts
index 44e8af0a6..c4136ae3c 100644
--- a/apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.ts
+++ b/apps/admin-gui/src/app/shared/components/destination-list/destination-list.component.ts
@@ -1,4 +1,13 @@
-import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core';
+import {
+  AfterViewInit,
+  Component,
+  EventEmitter,
+  Input,
+  OnChanges,
+  Output,
+  SimpleChanges,
+  ViewChild
+} from '@angular/core';
 import { MatSort } from '@angular/material/sort';
 import { RichDestination, RichResource } from '@perun-web-apps/perun/openapi';
 import { SelectionModel } from '@angular/cdk/collections';
@@ -11,7 +20,7 @@ import { TABLE_ITEMS_COUNT_OPTIONS } from '@perun-web-apps/perun/utils';
   templateUrl: './destination-list.component.html',
   styleUrls: ['./destination-list.component.scss']
 })
-export class DestinationListComponent implements OnChanges {
+export class DestinationListComponent implements AfterViewInit, OnChanges {
 
   constructor() { }
 
@@ -53,7 +62,6 @@ export class DestinationListComponent implements OnChanges {
   setDataSource() {
     if (!!this.dataSource) {
       this.dataSource.sort = this.sort;
-      this.dataSource.paginator = this.paginator;
       this.dataSource.sortingDataAccessor = (item, property) => {
         switch (property) {
           case 'service': {
@@ -65,7 +73,8 @@ export class DestinationListComponent implements OnChanges {
       this.dataSource.filterPredicate = (data, filter) => {
         const dataStr = data.service.name + data.id + data.destination + data.type + data.propagationType;
         return dataStr.indexOf(filter) !== -1;
-      }
+      };
+      this.dataSource.paginator = this.paginator;
     }
   }
 
@@ -95,4 +104,8 @@ export class DestinationListComponent implements OnChanges {
     this.page.emit(event);
   }
 
+  ngAfterViewInit(): void {
+    this.dataSource.paginator = this.paginator;
+  }
+
 }
diff --git a/apps/admin-gui/src/assets/i18n/en.json b/apps/admin-gui/src/assets/i18n/en.json
index 5779f05bf..02412a1ce 100644
--- a/apps/admin-gui/src/assets/i18n/en.json
+++ b/apps/admin-gui/src/assets/i18n/en.json
@@ -112,7 +112,8 @@
       "TABLE_TYPE": "Type",
       "TABLE_PROPAGATION_TYPE": "Propagation Type",
       "NO_DESTINATION": "Facility has no services destinations. Service configuration can't be propagated.",
-      "REMOVE_SUCCESS": "Facilities were successfully removed"
+      "REMOVE_SUCCESS": "Destinations were successfully removed",
+      "ADD_SUCCESS": "Destinations were successfully added"
     },
     "SETTINGS": {
       "ATTRIBUTES": {
-- 
GitLab