...
 
Commits (10)
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -12,43 +12,43 @@
},
"private": true,
"dependencies": {
"@angular/animations": "7.2.1",
"@angular/cdk": "^7.2.1",
"@angular/common": "7.2.1",
"@angular/compiler": "7.2.1",
"@angular/core": "7.2.1",
"@angular/forms": "7.2.1",
"@angular/http": "7.2.1",
"@angular/material": "^7.2.1",
"@angular/platform-browser": "7.2.1",
"@angular/platform-browser-dynamic": "7.2.1",
"@stomp/ng2-stompjs": "^7.1.0",
"core-js": "^2.6.2",
"@angular/animations": "7.2.3",
"@angular/cdk": "^7.3.0",
"@angular/common": "7.2.3",
"@angular/compiler": "7.2.3",
"@angular/core": "7.2.3",
"@angular/forms": "7.2.3",
"@angular/http": "7.2.3",
"@angular/material": "^7.3.0",
"@angular/platform-browser": "7.2.3",
"@angular/platform-browser-dynamic": "7.2.3",
"@stomp/ng2-stompjs": "^7.2.0",
"core-js": "^2.6.3",
"hammerjs": "^2.0.8",
"ngx-cookie-service": "^2.1.0",
"rxjs": "^6.3.3",
"rxjs": "^6.4.0",
"tslib": "^1.9.0",
"zone.js": "^0.8.28"
"zone.js": "~0.8.29"
},
"devDependencies": {
"@angular-devkit/build-angular": "^0.12.2",
"@angular/cli": "^7.2.2",
"@angular/compiler-cli": "7.2.1",
"@types/jasmine": "^3.3.5",
"@angular-devkit/build-angular": "^0.13.0",
"@angular/cli": "7.3.0",
"@angular/compiler-cli": "7.2.3",
"@types/jasmine": "^3.3.8",
"@types/jasminewd2": "^2.0.6",
"@types/node": "^10.12.18",
"@types/node": "^10.12.20",
"jasmine-core": "~3.3.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~3.1.4",
"karma": "~4.0.0",
"karma-chrome-launcher": "~2.2.0",
"karma-cli": "~2.0.0",
"karma-coverage-istanbul-reporter": "^2.0.4",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"npm": "^6.5.0",
"npm": "^6.7.0",
"protractor": "~5.4.2",
"ts-node": "~7.0.1",
"ts-node": "~8.0.2",
"tslint": "~5.12.1",
"typescript": "^3.2.2"
"typescript": "3.2.4"
}
}
<div *ngIf="(userService.subscribeUser() | async) as user" class="mat-typography" style="margin-bottom: 50px;">
<div *ngIf="(userService.userSrc() | async) as user" class="mat-typography" style="margin-bottom: 50px;">
<h2>User Info</h2>
<p>
You can inspect the user information we received about you.
......
<header feudal-header></header>
<div class="mat-typography" class="body" >
<div *ngIf="(userService.subscribeUser() | async) as user" style="max-width: 800px;" class="centered">
<div *ngIf="(userService.subscribeSSHKeys() | async)?.length > 0; else noCredentials">
<div style="margin-bottom: 50px;">
<h3>Your services</h3>
<mat-accordion *ngIf="(userService.subscribeServices() | async) as services; else noServices">
<app-service *ngFor="let service of services" [service]="service"></app-service>
</mat-accordion>
<ng-template #noServices>
<p>
You are not permitted to use any services.
</p>
</ng-template>
</div>
<div>
<h3>Your Virtual Organisations</h3>
<mat-accordion *ngIf="(userService.subscribeVOs() | async) as vos; else noVOs">
<app-vo-data *ngFor="let vo of vos" [vo]="vo"></app-vo-data>
</mat-accordion>
<ng-template #noVOs>
<p>
You are not member in any Virtual Organisations.
</p>
</ng-template>
</div>
</div>
<ng-template #noCredentials>
<app-account></app-account>
<!--
<div class="mat-typography">
<div style="margin-bottom: 50px;">
<p>
Before you can start using FEUDAL you need to upload an SSH public key.
Please do so below.
</p>
<p>
Once you have uploaded a key, the SSH Key Management will be available in the toolbar above.
</p>
<div *ngIf="(userService.combiSrc() | async) as combi" style="max-width: 800px;" class="centered">
<div *ngIf="combi.user != undefined">
<div *ngIf="(userService.sshKeysSrc() | async)?.length > 0; else noCredentials">
<div style="margin-bottom: 50px;">
<h3>Your services</h3>
<div>
<mat-accordion *ngIf="combi.user.services.length > 0; else noServices">
<app-service *ngFor="let service of services" [service]="service"></app-service>
</mat-accordion>
</div>
<ng-template #noServices>
<p>
You are not permitted to use any services.
</p>
</ng-template>
</div>
<div>
<h3>Your Virtual Organisations</h3>
<div>
<mat-accordion *ngIf="userService.extractVOs(combi).length > 0; else noVOs">
<app-vo-data *ngFor="let vo of userService.extractVOs(combi)" [vo]="vo"></app-vo-data>
</mat-accordion>
</div>
<ng-template #noVOs>
<p>
You are not member in any Virtual Organisations or all your Virtual Organisations are empty.
</p>
</ng-template>
</div>
</div>
<ng-template #noCredentials>
<app-account></app-account>
</ng-template>
</div>
<app-sshkeys></app-sshkeys>
</div>
-->
</ng-template>
</div>
</div>
</div>
<footer feudal-footer class="footer"></footer>
<div *ngIf="(userService.subscribeUser() | async) as user" class="mat-typography" style="margin-bottom: 50px;">
<div *ngIf="(userService.userSrc() | async) as user" class="mat-typography" style="margin-bottom: 50px;">
<h2>User Info</h2>
<p>
You can inspect the user information we received about you.
......
.header-bar {
width: 100%;
}
.header-bar>* {
float: right;
}
<mat-toolbar color="primary" class="mat-typography">
<mat-toolbar-row>
<h1>
Federated User Credential Deployment Portal
</h1>
<div class="header-bar">
<span *ngIf="(userService.subscribeUser() | async) as user; else notLoggedIn">
<button mat-button mat-icon-button (click)="dialog.openAccount()">
<mat-icon>settings</mat-icon>
</button>
<button *ngIf="user.profile_name != undefined" mat-button (click)="profileDialog.open(user)">
{{ user.profile_name }}
</button>
<button *ngIf="user.profile_name == undefined" mat-button (click)="profileDialog.open(user)">
Profile
</button>
<button mat-raised-button color="accent" (click)="userService.logout()">
Logout
</button>
</span>
<ng-template #notLoggedIn>
<span *ngIf="(idps$ | async) as idps">
<form *ngIf="(selectedIdP$ | async) as selectedIdP"
(ngSubmit)="userService.login(selectedIdP)" #loginForm="ngForm">
<mat-form-field>
<mat-select name="idp" required [(ngModel)]="selectedIdP">
<mat-option *ngFor="let idp of idps" [value]="idp">
{{ idp.name }}
</mat-option>
</mat-select>
</mat-form-field>
<button mat-raised-button [disabled]="!loginForm.form.valid" color="accent" type="submit">Login</button>
</form>
</span>
</ng-template>
</div>
</mat-toolbar-row>
<mat-toolbar-row style="display: flex; justify-content: space-between; width: 100%;">
<div>
<h1>
Federated User Credential Deployment Portal
</h1>
</div>
<div>
<span *ngIf="(userService.userSrc() | async) as user; else notLoggedIn">
<button mat-button mat-icon-button (click)="dialog.openAccount()">
<mat-icon>settings</mat-icon>
</button>
<button *ngIf="user.profile_name != undefined" mat-button (click)="profileDialog.open(user)">
{{ user.profile_name }}
</button>
<button *ngIf="user.profile_name == undefined" mat-button (click)="profileDialog.open(user)">
Profile
</button>
<button mat-raised-button color="accent" (click)="userService.logout()">
Logout
</button>
</span>
<ng-template #notLoggedIn>
<span *ngIf="(userService.connectIdPInfo() | async) as idpInfo">
<form (ngSubmit)="userService.login(selected)" #loginForm="ngForm">
<mat-form-field>
<mat-select name="idp" required [(ngModel)]="selected" [compareWith]="idpCompare">
<mat-option *ngFor="let idp of idpInfo.idps" [value]="idp">
{{ idp.name }}
</mat-option>
</mat-select>
</mat-form-field>
<button mat-raised-button [disabled]="!loginForm.form.valid" color="accent" type="submit">
Login
</button>
</form>
</span>
</ng-template>
</div>
</mat-toolbar-row>
</mat-toolbar>
......@@ -3,7 +3,6 @@ import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { UserService } from '../user.service';
import { IdpService } from '../idp.service';
import { DialogService } from '../dialogues/dialog.service';
import { ProfileDialogService } from '../dialogues/profile-dialog.service';
import { IdP } from '../types/types.module';
......@@ -15,19 +14,24 @@ import { IdP } from '../types/types.module';
})
export class HeaderComponent implements OnInit {
public idps$: Observable<IdP[]>;
public selectedIdP$: Observable<IdP>;
selected: IdP;
constructor(
private idpService: IdpService,
public dialog: DialogService,
public profileDialog: ProfileDialogService,
public userService: UserService,
) {
this.idps$ = this.idpService.subscribeIdps();
this.selectedIdP$ = this.idpService.subscribeSelectedIdp();
}
ngOnInit() {
this.userService.connectIdPInfo().subscribe(
info => {
this.selected = info.selectedIdP;
}
);
}
idpCompare(a: IdP, b: IdP): boolean {
return a != undefined && b != undefined && a.id == b.id;
}
}
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { CookieService } from 'ngx-cookie-service';
import { environment } from '../environments/environment';
import { IdP, AuthInfo } from './types/types.module';
@Injectable({
providedIn: 'root'
})
export class IdpService {
idps: IdP[];
idps$: BehaviorSubject<IdP[]>;
selectedIdp: IdP;
selectedIdp$: BehaviorSubject<IdP>;
constructor(
public http: HttpClient,
public cookieService: CookieService,
) {
this.initializeDataService();
}
private initializeDataService() {
if (!this.idps$) {
this.idps$ = <BehaviorSubject<IdP[]>> new BehaviorSubject(undefined);
}
if (!this.selectedIdp$) {
this.selectedIdp$ = <BehaviorSubject<IdP>> new BehaviorSubject(undefined);
}
this.apiCall();
}
private apiCall(): void {
// initialize the subject with data
this.http.get('/backend/auth/v1/info').subscribe(
(info: AuthInfo) => {
this.idps = info.idps;
this.idps$.next(this.idps);
let preferredIdP: IdP = this.idps.find((idp: IdP) => idp.id === this.getIdPPreference());
let defaultIdP: IdP = this.idps.find((idp: IdP) => idp.id === info.default);
if (preferredIdP) {
this.selectedIdp = preferredIdP;
} else if (defaultIdP) {
this.selectedIdp = defaultIdP;
} else if (this.idps.length > 0) {
this.selectedIdp = this.idps[0];
} else {
console.log("No IdPs available. Unable to login");
}
this.selectedIdp$.next(this.selectedIdp);
}
);
}
private getIdPPreference(): number {
return Number(this.cookieService.get(environment.idpCookieName));
}
public subscribeIdps(): Observable<IdP[]> {
return this.idps$.asObservable();
}
public subscribeSelectedIdp(): Observable<IdP> {
return this.selectedIdp$.asObservable();
}
public setIdPPreference(idp: IdP) {
this.cookieService.set(environment.idpCookieName, String(idp.id));
}
}
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { IdP } from '../types/types.module';
export const prefsKey = "feudalPrefs";
export interface Prefs {
showEmptyVOs?: boolean;
preferredIdP?: IdP;
};
@Injectable({
......@@ -20,16 +23,25 @@ export class PreferencesService {
}
public save(prefs: Prefs): void {
console.log("Saved prefs:", prefs);
localStorage.setItem(prefsKey, JSON.stringify(prefs));
console.log("Saved preferences:", prefs);
}
public load(): void {
this.prefs = JSON.parse(localStorage.getItem(prefsKey));
if (this.prefs != null && this.prefs != undefined) {
this.prefs$.next(this.prefs);
const loaded = JSON.parse(localStorage.getItem(prefsKey));
if (loaded != null) {
this.prefs = loaded;
this.prefs$.next(loaded);
console.log("Loaded preferences:", this.prefs);
} else {
console.log("No preferences to load");
}
console.log("Loaded prefs:", this.prefs);
}
public setPreferredIdP(idp: IdP): void {
this.prefs.preferredIdP = idp;
this.save(this.prefs);
this.prefs$.next(this.prefs);
}
public connect(): Observable<Prefs> {
......
......@@ -19,7 +19,7 @@
</tr>
</thead>
<tbody>
<tr deployment-state-tr [service]="service" [site]="site"></tr>
<tr deployment-state-tr [service]="service"></tr>
</tbody>
</table>
</div>
......
......@@ -16,8 +16,6 @@ export class ServiceComponent implements OnInit {
@Input() service: Service;
public site: Site;
public deployment$: Observable<Deployment>;
public deploymentState$: Observable<DeploymentState>;
......@@ -28,7 +26,6 @@ export class ServiceComponent implements OnInit {
) { }
ngOnInit() {
this.site = this.service.site;
this.deploymentState$ = this.userService.subscribeStateFor(this.service);
this.deployment$ = this.userService.subscribeDeployment(
(dep: Deployment) => dep.service ? dep.service.id == this.service.id : false
......
......@@ -12,7 +12,7 @@ export class SSHKeysDataSource implements DataSource<SSHKey> {
) {}
connect(collectionViewer: CollectionViewer): Observable<SSHKey[]> {
return this.userService.subscribeSSHKeys();
return this.userService.sshKeysSrc();
}
disconnect(collectionViewer: CollectionViewer): void {
......
<td>
<span matTooltip="Site {{ site.name }} provides the service {{ service.name }} for you">
<mat-icon>account_balance</mat-icon>
{{ site.name }}
</span>
<span matTooltip="Site {{ service.site.name }} provides the service {{ service.name }} for you">
<mat-icon>account_balance</mat-icon>
{{ service.site.name }}
</span>
</td>
<td>
<span matTooltip="{{ service.description }}">
<mat-icon>web</mat-icon>
{{ service.name }}
</span>
<span matTooltip="{{ service.description }}">
<mat-icon>web</mat-icon>
{{ service.name }}
</span>
</td>
<ng-container *ngIf="(state$ | async) as state; else noStateItem">
<td [matTooltip]="tooltip(state)">
<span [ngSwitch]="state.state" class="spaced">
<mat-icon *ngSwitchCase="'deployed'">call_made</mat-icon>
<mat-icon *ngSwitchCase="'questionnaire'">warning</mat-icon>
<mat-progress-spinner *ngSwitchCase="'deployment_pending'" diameter="24" mode="indeterminate"></mat-progress-spinner>
<mat-progress-spinner *ngSwitchCase="'removal_pending'" diameter="24" mode="indeterminate"></mat-progress-spinner>
<mat-icon *ngSwitchCase="'not_deployed'" mat-icon-button>call_received</mat-icon>
<mat-icon *ngSwitchCase="'failed'" mat-icon-button>error</mat-icon>
<mat-icon *ngSwitchCase="'rejected'" mat-icon-button>error</mat-icon>
<mat-icon *ngSwitchDefault mat-icon-button>call_received</mat-icon>
</span>
{{ lang.printState(state.state) }}
</td>
<td [matTooltip]="tooltip(state)">
<span [ngSwitch]="state.state" class="spaced">
<mat-icon *ngSwitchCase="'deployed'">call_made</mat-icon>
<mat-icon *ngSwitchCase="'questionnaire'">warning</mat-icon>
<mat-progress-spinner *ngSwitchCase="'deployment_pending'" diameter="24" mode="indeterminate"></mat-progress-spinner>
<mat-progress-spinner *ngSwitchCase="'removal_pending'" diameter="24" mode="indeterminate"></mat-progress-spinner>
<mat-icon *ngSwitchCase="'not_deployed'" mat-icon-button>call_received</mat-icon>
<mat-icon *ngSwitchCase="'failed'" mat-icon-button>error</mat-icon>
<mat-icon *ngSwitchCase="'rejected'" mat-icon-button>error</mat-icon>
<mat-icon *ngSwitchDefault mat-icon-button>call_received</mat-icon>
</span>
{{ lang.printState(state.state) }}
</td>
<!-- state dependent buttons -->
<td *ngIf="state.state == 'deployed'">
<button (click)="dialog.openCredentials(state$)" mat-raised-button class="mat-elevation-z6">
Credentials
</button>
</td>
<td *ngIf="state.state == 'questionnaire'">
<button (click)="dialog.openQuestionnaire(state$)" mat-raised-button class="mat-elevation-z6">
Questionnaire
</button>
</td>
<td *ngIf="state.state == 'failed'">
<button (click)="dialog.openMessage(state)" mat-raised-button class="mat-elevation-z6">
Failure
</button>
</td>
<td *ngIf="state.state == 'rejected'">
<button (click)="dialog.openMessage(state)" mat-raised-button class="mat-elevation-z6">
Rejected
</button>
</td>
<td *ngIf="!state.is_pending && state.is_credential_pending">
<span>
<mat-progress-spinner diameter="24" mode="indeterminate"></mat-progress-spinner>
SSH Keys pending
</span>
</td>
<td *ngIf="(state.questionnaire | ObjKeys).length > 0 && state.state == 'deployed'"
matTooltip="Change previously submitted answers">
<button (click)="dialog.openQuestionnaire(state$)" mat-icon-button>
<mat-icon>edit</mat-icon>
</button>
</td>
<!-- state dependent buttons -->
<td *ngIf="state.state == 'deployed'">
<button (click)="dialog.openCredentials(state$)" mat-raised-button class="mat-elevation-z6">
Credentials
</button>
</td>
<td *ngIf="state.state == 'questionnaire'">
<button (click)="dialog.openQuestionnaire(state$)" mat-raised-button class="mat-elevation-z6">
Questionnaire
</button>
</td>
<td *ngIf="state.state == 'failed'">
<button (click)="dialog.openMessage(state)" mat-raised-button class="mat-elevation-z6">
Failure
</button>
</td>
<td *ngIf="state.state == 'rejected'">
<button (click)="dialog.openMessage(state)" mat-raised-button class="mat-elevation-z6">
Rejected
</button>
</td>
<td *ngIf="!state.is_pending && state.is_credential_pending">
<span>
<mat-progress-spinner diameter="24" mode="indeterminate"></mat-progress-spinner>
SSH Keys pending
</span>
</td>
<td *ngIf="(state.questionnaire | ObjKeys).length > 0 && state.state == 'deployed'"
matTooltip="Change previously submitted answers">
<button (click)="dialog.openQuestionnaire(state$)" mat-icon-button>
<mat-icon>edit</mat-icon>
</button>
</td>
</ng-container>
<ng-template #noStateItem>
<td>
<span>
<mat-icon mat-icon-button matTooltip="Access to this service was never requested.">call_received</mat-icon>
</span>
{{ lang.printState('not_deployed') }}
</td>
<td>
<span>
<mat-icon mat-icon-button matTooltip="Access to this service was never requested.">call_received</mat-icon>
</span>
{{ lang.printState('not_deployed') }}
</td>
</ng-template>
......@@ -14,7 +14,6 @@ import { Site, Service, DeploymentState } from '../types/types.module';
})
export class StateComponent implements OnInit {
@Input() site: Site;
@Input() service: Service;
public state$: Observable<DeploymentState>;
......@@ -39,15 +38,15 @@ export class StateComponent implements OnInit {
case "deployed":
return `The credentials are deployed for the service ${ this.service.name }. Click to see details.`;
case "deployment_pending":
return `Waiting for the deployment of the credentials to the site ${ this.site.name }`;
return `Waiting for the deployment of the credentials to the site ${ this.service.site.name }`;
case "removal_pending":
return `Waiting for the removal of the credentials from the site ${ this.site.name }`;
return `Waiting for the removal of the credentials from the site ${ this.service.site.name }`;
case "questionnaire":
return `Site ${ this.site.name } needs more data to deploy the keys. Please click to submit the data.`;
return `Site ${ this.service.site.name } needs more data to deploy the keys. Please click to submit the data.`;
case "failed":
return `Site ${ this.site.name } failed to deploy the credentials. The deployment will be retried. Click for details.`;
return `Site ${ this.service.site.name } failed to deploy the credentials. The deployment will be retried. Click for details.`;
case "rejected":
return `Site ${ this.site.name } rejected the deployment of the credentials. Click for details.`;
return `Site ${ this.service.site.name } rejected the deployment of the credentials. Click for details.`;
default:
return "Access to this service was never requested.";
}
......
......@@ -6,9 +6,11 @@ import { CommonModule } from '@angular/common';
export interface IdP {
id: number;
name: string;
issuer_uri: string;
}
export interface AuthInfo {
export interface IdPInfo {
selectedIdP?: IdP;
idps: IdP[];
default: number;
}
......
This diff is collapsed.
......@@ -13,8 +13,12 @@
<p *ngIf="vo.description != ''">
{{ vo.description }}
</p>
<div *ngIf="(services$$ | async) as services; else noServiceList">
<div *ngIf="services != undefined && services.length > 0; else noServiceList">
<!--
<div>
<div *ngIf="groups != undefined && groups.length > 0; else noServiceList">
-->
<div *ngIf="(groupedServices$ | async) as grouped; else noServiceList">
<div *ngIf="grouped != undefined && grouped.length > 0; else noServiceList">
<p>
These services are provided for members of this VO.
</p>
......@@ -27,9 +31,7 @@
</tr>
</thead>
<tbody>
<ng-container *ngFor="let site of sites$$ | async">
<tr *ngFor="let service of servicesAtSite(site) | async" deployment-state-tr [service]="service" [site]="site"></tr>
</ng-container>
<tr *ngFor="let service of grouped" deployment-state-tr [service]="service"></tr>
</tbody>
</table>
</div>
......
import { Component, OnInit, Input } from '@angular/core';
import { Observable, BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';
import { Observable, GroupedObservable, of, from, EMPTY } from 'rxjs';
import { map, tap, filter, flatMap, groupBy, toArray } from 'rxjs/operators';
import { UserService } from '../user.service';
import { LanguageService } from '../language.service';
import { DialogService } from '../dialogues/dialog.service';
import { VO, Site, Service, Deployment, DeploymentState } from '../types/types.module';
import { User, VO, Site, Service, Deployment, DeploymentState } from '../types/types.module';
@Component({
selector: 'app-vo-data',
......@@ -17,15 +17,11 @@ export class VoDataComponent implements OnInit {
@Input() vo: VO;
private services: Service[];
private services$ = new BehaviorSubject<Service[]>([]);
public services$$: Observable<Service[]>;
private sites: Map<number, Site> = new Map();
private sites$ = new BehaviorSubject<Site[]>([]);
public sites$$: Observable<Site[]>;
public user$: Observable<User>;
public deployment$: Observable<Deployment>;
// service grouped by site
public groupedServices$: Observable<Service[]>;
constructor(
public userService: UserService,
......@@ -35,58 +31,21 @@ export class VoDataComponent implements OnInit {
}
ngOnInit(): void {
this.services$$ = this.services$.asObservable();
this.sites$$ = this.sites$.asObservable();
this.deployment$ = this.userService.subscribeDeployment(
(dep: Deployment) => dep.vo ? dep.vo.id == this.vo.id : false
);
// this is just used when no deployment exists
this.userService.subscribeServices().subscribe(
(services: Service[]) => {
this.services = services;
this.services$.next(this.services);
}
);
this.deployment$.subscribe(
(dep: Deployment) => {
if (dep != undefined) {
this.services = dep.services;
this.services$.next(this.services);
this.groupedServices$ = this.userService.userSrc().pipe(
map(user => {
if (user != undefined && user.services != undefined) {
const voServices = user.services.filter(
service => service.vos.some(svo => svo.id == this.vo.id),
);
const groupedServices = voServices.sort(service => service.site.id);
return groupedServices;
}
}
);
this.services$$.subscribe(
(services: Service[]) => {
services.map(
(s: Service) => this.sites.set(s.site.id, s.site)
);
const uniqueSites = []
this.sites.forEach(
site => uniqueSites.push(site)
);
this.sites$.next(uniqueSites);
}
);
}
ngOnDestroy(): void {
this.services$.complete();
this.sites$.complete();
}
public servicesAtSite(site: Site): Observable<Service[]> {
return this.services$$.pipe(
map(
(services: Service[]) => services.filter(
(service: Service) => service.site.id === site.id
),
),
return [];
}),
);
}
}
......@@ -105,6 +105,10 @@ mat-checkbox {
margin-right: 15px;
}
.mat-form-field-infix {
width: 220px !important;
}
.scrolling-dialog .mat-dialog-container {
margin: 70px 0px 6px 0px;
height: unset;
......