Skip to content

Commit

Permalink
Display of connected users in console (#612)
Browse files Browse the repository at this point in the history
  • Loading branch information
sei-chershberger authored Mar 3, 2023
1 parent 52cbc37 commit 76856b9
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 37 deletions.
44 changes: 43 additions & 1 deletion src/app/app.component.scss
Original file line number Diff line number Diff line change
@@ -1,2 +1,44 @@
// Copyright 2021 Carnegie Mellon University. All Rights Reserved.
// Copyright 2023 Carnegie Mellon University. All Rights Reserved.
// Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
/*
Note about src: url() and BaseHref:
When using BaseHref SCSS and angular pre-processors do not like relative paths
We can escape the angular pre-processors by adding `^` to the start of the URL
*/
@font-face {
font-family: 'open_sansregular';
src: url('^assets/fonts/opensans-regular.woff2') format('woff2'),
url('^assets/fonts/opensans-regular.woff') format('woff');
font-weight: normal;
font-style: normal;
}

@font-face {
font-family: 'open_sansbold';
src: url('^assets/fonts/opensans-bold.woff2') format('woff2'),
url('^assets/fonts/opensans-bold.woff') format('woff');
font-weight: normal;
font-style: normal;
}

@font-face {
font-family: 'open_sansitalic';
src: url('^assets/fonts/opensans-italic.woff2') format('woff2'),
url('^assets/fonts/opensans-italic.woff') format('woff');
font-weight: normal;
font-style: normal;
}

body,
a {
font-family: 'open_sansregular';
}

b {
font-family: 'open_sansbold';
}

i {
font-family: 'open_sansitalic';
}

6 changes: 4 additions & 2 deletions src/app/components/console-page/console-page.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import {
OnDestroy,
} from '@angular/core';
import { Title } from '@angular/platform-browser';
import { ComnAuthService } from '@cmusei/crucible-common';
import { RouterQuery } from '@datorama/akita-ng-router-store';
import { Observable, Subject } from 'rxjs';
import { map, takeUntil } from 'rxjs/operators';
import { map, take, takeUntil } from 'rxjs/operators';
import { SignalRService } from '../../services/signalr/signalr.service';
import { VmQuery } from '../../state/vm/vm.query';
import { VmService } from '../../state/vm/vm.service';
Expand All @@ -36,7 +37,8 @@ export class ConsolePageComponent implements OnInit, OnDestroy {
private signalrRService: SignalRService,
private vmQuery: VmQuery,
private titleService: Title,
private vmService: VmService
private vmService: VmService,
private authService: ComnAuthService,
) {}

ngOnInit() {
Expand Down
60 changes: 34 additions & 26 deletions src/app/components/options-bar/options-bar.component.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<!--
Copyright 2021 Carnegie Mellon University. All Rights Reserved.
Copyright 2023 Carnegie Mellon University. All Rights Reserved.
Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
-->

<div class="options-container background">
<div class="options-content background">
<button mat-icon-button class="mat-small" [matMenuTriggerFor]="mainMenu">
<div class="options-container options-content background" fxLayout="row" >
<div fxFlex="5" fxLayoutAlign="start center">
<button mat-icon-button class="mat-small item-position-top" [matMenuTriggerFor]="mainMenu">
<mat-icon class="text" svgIcon="gear" alt="Gear"></mat-icon>
<label class="vm-name text">{{ vm?.name }}</label>
</button>
<mat-menu #mainMenu="matMenu">
<div>
Expand Down Expand Up @@ -230,33 +231,40 @@
</mat-menu>
</div>
</mat-menu>

<label class="vm-name text">{{ vm?.name }}</label>
</div>
<div fxFlex="20">
<span *ngFor="let task of tasksInProgress">
<label class="vm-name text" *ngIf="task.state !== 'success'"
>{{ task.taskName }} ... {{ task.progress }}%</label
>
</span>
</div>

<div *ngIf="!readOnly" fxFlex fxLayoutAlign="center start">
<button
mat-stroked-button
class="mat-top-button text"
title="Copy from Virtual Machine's clipboard"
(click)="copyVmClipboard()"
>
Copy
<mat-icon svgIcon="ic_clipboard_copy"></mat-icon>
</button>
<button
mat-stroked-button
class="mat-top-button text"
title="Paste into the Virtual Machine"
(click)="pasteFromClipboard()"
>
Paste
<mat-icon svgIcon="ic_clipboard_paste"></mat-icon>
</button>
</div>
<div *ngIf="!readOnly" fxFlex="50" fxLayoutAlign="center start">
<button
mat-stroked-button
class="mat-top-button text"
title="Copy from Virtual Machine's clipboard"
(click)="copyVmClipboard()"
>
Copy
<mat-icon svgIcon="ic_clipboard_copy"></mat-icon>
</button>
<button
mat-stroked-button
class="mat-top-button text"
title="Paste into the Virtual Machine"
(click)="pasteFromClipboard()"
>
Paste
<mat-icon svgIcon="ic_clipboard_paste"></mat-icon>
</button>
</div>

<ng-container *ngIf="currentVmUsers$ | async as currentVmUsers">
<div *ngIf="showConnectedUsers && !readOnly" fxLayout="row"
fxFlex [title]="formatConnectedUserToolTip(currentVmUsers)" class="item-position-top">
<p class="connected-users">{{formatConnectedUser(currentVmUsers)}}</p>
</div>
</ng-container>
</div>
14 changes: 12 additions & 2 deletions src/app/components/options-bar/options-bar.component.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// Copyright 2021 Carnegie Mellon University. All Rights Reserved.
// Copyright 2023 Carnegie Mellon University. All Rights Reserved.
// Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.

.options-container {
position: absolute;
width: 100%;
top: 0px;
left: 0px;
Expand Down Expand Up @@ -55,3 +54,14 @@ a:visited {
line-height: 0px;
margin-left: 20px;
}

.item-position-top {
position:relative;
top:-10px;
}

.connected-users {
font-size: x-small;
font-family: 'open_sansregular';
margin-right: 5px;
}
38 changes: 34 additions & 4 deletions src/app/components/options-bar/options-bar.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2021 Carnegie Mellon University. All Rights Reserved.
// Copyright 2023 Carnegie Mellon University. All Rights Reserved.
// Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.

import { HttpErrorResponse } from '@angular/common/http';
Expand All @@ -13,7 +13,7 @@ import {
import { MatSnackBar } from '@angular/material/snack-bar';
import { ActivatedRoute } from '@angular/router';
import { ComnAuthService, ComnSettingsService } from '@cmusei/crucible-common';
import { Subject } from 'rxjs';
import { Observable, Subject } from 'rxjs';
import { take, takeUntil } from 'rxjs/operators';
import { VsphereVirtualMachine } from '../../generated/vm-api';
import { NotificationData } from '../../models/notification/notification-model';
Expand All @@ -25,6 +25,7 @@ import {
} from '../../models/vm/vm-model';
import { DialogService } from '../../services/dialog/dialog.service';
import { NotificationService } from '../../services/notification/notification.service';
import { SignalRService } from '../../services/signalr/signalr.service';
import { VsphereService } from '../../state/vsphere/vsphere.service';

declare var WMKS: any; // needed to check values
Expand Down Expand Up @@ -65,8 +66,9 @@ export class OptionsBarComponent implements OnInit, OnDestroy {
virtualMachineToolsStatus: any;
currentVmContainerResolution: VmResolution;
vmResolutionsOptions: VmResolution[];
showConnectedUsers = false;
currentVmUsers$: Observable<string[]>;
private isDark = false;

private copyTryCount: number;
private destroy$ = new Subject();

Expand All @@ -77,7 +79,8 @@ export class OptionsBarComponent implements OnInit, OnDestroy {
private notificationService: NotificationService,
private route: ActivatedRoute,
private snackBar: MatSnackBar,
private authService: ComnAuthService
private authService: ComnAuthService,
private signalrService: SignalRService
) {}

ngOnInit() {
Expand All @@ -87,9 +90,11 @@ export class OptionsBarComponent implements OnInit, OnDestroy {
{ width: 800, height: 600 } as VmResolution,
];

this.currentVmUsers$ = this.signalrService.currentVmUsers$.asObservable();
this.virtualMachineToolsStatus = VirtualMachineToolsStatus;
this.clipBoardText = '';
this.copyTryCount = 0;
this.showConnectedUsers = true;

this.notificationService.tasksInProgress.subscribe((data) => {
if (!!data && data.length > 0) {
Expand Down Expand Up @@ -434,4 +439,29 @@ export class OptionsBarComponent implements OnInit, OnDestroy {
.pipe(take(1))
.subscribe();
}

formatConnectedUser(users: string[] = null): string {
const MAX_USERS = 2;
let output = '';
let count = 0;
if (users && users.length > 0) {
for (const u of users) {
if (count >= MAX_USERS) {
output += ' and ' + (users.length - MAX_USERS).toString() + 'others...';
break;
}
output += u + ' ';
count++;
}
}
if (count > 0) {
output = 'Connected: ' + output;
}
return output;
}

formatConnectedUserToolTip(users: string[] = null): string {
return users.toString().replace(/,/g, '\n');
}

}
20 changes: 18 additions & 2 deletions src/app/services/signalr/signalr.service.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
/**
* Copyright 2021 Carnegie Mellon University. All Rights Reserved.
* Copyright 2023 Carnegie Mellon University. All Rights Reserved.
* Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
*/

import { Inject, Injectable } from '@angular/core';
import { ComnAuthService } from '@cmusei/crucible-common';
import * as signalR from '@microsoft/signalr';
import { BehaviorSubject, Observable } from 'rxjs';
import { BASE_PATH } from '../../generated/vm-api';
import { UserQuery } from '../../state/user/user.query';
import { UserService } from '../../state/user/user.service';
import { VsphereService } from '../../state/vsphere/vsphere.service';

@Injectable({
providedIn: 'root',
})
export class SignalRService {

public currentVmUsers$: BehaviorSubject<string[]>;
private hubConnection: signalR.HubConnection;
private connectionPromise: Promise<void>;

Expand All @@ -26,20 +30,23 @@ export class SignalRService {
private authService: ComnAuthService,
private vmService: VsphereService,
private userService: UserService,
private userQuery: UserQuery,
@Inject(BASE_PATH) basePath: string
) {
this.apiUrl = basePath;

this.authService.user$.subscribe(() => {
this.reconnect();
});

this.currentVmUsers$ = new BehaviorSubject<string[]>([]);
}

public startConnection(): Promise<void> {

if (this.connectionPromise) {
return this.connectionPromise;
}

this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(`${this.apiUrl}/hubs/vm`, {
accessTokenFactory: () => {
Expand Down Expand Up @@ -73,6 +80,7 @@ export class SignalRService {
if (this.userId && this.viewId) {
this.joinUser(this.userId, this.viewId);
}

}

public joinUser(userId: string, viewId: string) {
Expand Down Expand Up @@ -108,13 +116,21 @@ export class SignalRService {

private addHandlers() {
this.addUserHandlers();
this.addCurrentUsersHandlers();
}

private addUserHandlers() {
this.hubConnection.on('ActiveVirtualMachine', (vmId: string) => {
this.vmService.setActive(vmId);
});
}

private addCurrentUsersHandlers() {
this.hubConnection.on('CurrentVirtualMachineUsers', (vmId: string, users: string[]) => {
this.currentVmUsers$.next(users);
});
}

}

class RetryPolicy {
Expand Down

0 comments on commit 76856b9

Please sign in to comment.