Skip to content

Commit

Permalink
added menu toggle to manually enable read only mode
Browse files Browse the repository at this point in the history
  • Loading branch information
sei-aschlackman committed Apr 25, 2024
1 parent a98fb2f commit fb9a6bf
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 25 deletions.
3 changes: 2 additions & 1 deletion src/app/components/console/console.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@
[readOnly]="readOnly"
[vm]="vsphereVm$ | async"
[vmId]="vmId"
(readOnlyChanged)="onReadOnlyChanged($event)"
></app-options-bar>
<app-wmks [readOnly]="readOnly" [vmId]="vmId"></app-wmks>
<app-wmks [readOnly]="readOnly$ | async" [vmId]="vmId"></app-wmks>
}
}
}
Expand Down
13 changes: 11 additions & 2 deletions src/app/components/console/console.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.

import { Component, Input } from '@angular/core';
import { Observable } from 'rxjs';
import { BehaviorSubject, Observable } from 'rxjs';
import { Vm, VmType, VsphereVirtualMachine } from '../../generated/vm-api';
import { VmService } from '../../state/vm/vm.service';
import { VsphereQuery } from '../../state/vsphere/vsphere.query';
Expand All @@ -26,7 +26,7 @@ import { OptionsBar2Component } from '../options-bar2/options-bar2.component';
],
})
export class ConsoleComponent {
@Input() readOnly;
@Input() readOnly = false;

@Input() set vmId(value: string) {
this._vmId = value;
Expand All @@ -47,8 +47,17 @@ export class ConsoleComponent {
vsphereVm$: Observable<VsphereVirtualMachine>;
virtualMachine$: Observable<Vm>;

readOnlyInternal = false;
readOnlySubject = new BehaviorSubject(this.readOnly);
readOnly$ = this.readOnlySubject.asObservable();

constructor(
private vsphereQuery: VsphereQuery,
private vmService: VmService,
) {}

onReadOnlyChanged(event: boolean) {
this.readOnlyInternal = event;
this.readOnlySubject.next(this.readOnlyInternal || this.readOnly);
}
}
22 changes: 20 additions & 2 deletions src/app/components/options-bar/options-bar.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
<button class="z-3" mat-icon-button [matMenuTriggerFor]="mainMenu">
<mat-icon svgIcon="gear" alt="Gear"></mat-icon>
</button>
<span class="ms-1">{{ vm?.name }}</span>
<span class="ms-1"
>{{ vm?.name }}{{ readOnlyInternal ? ' (Read Only)' : '' }}</span
>
<mat-menu #mainMenu="matMenu">
<div>
<button mat-menu-item (click)="fullscreen()">Fullscreen</button>
Expand All @@ -20,6 +22,22 @@
</div>

@if (!readOnly) {
<button mat-menu-item (click)="$event.stopPropagation()">
<mat-slide-toggle
labelPosition="before"
color="primary"
[checked]="readOnly"
(click)="$event.stopPropagation()"
(change)="toggleReadOnly($event)"
>
<mat-label class="mat-mdc-menu-item-text me-2"
>Read Only</mat-label
>
</mat-slide-toggle>
</button>
}

@if (!readOnlyInternal) {
<div>
<!-- Resolution -->
@if (!vmService.model.isOwner) {
Expand Down Expand Up @@ -253,7 +271,7 @@ <h1 class="fs-6">{{ task.taskName }} ... {{ task.progress }}%</h1>
}
</div>

@if (!readOnly) {
@if (!readOnlyInternal) {
<div class="w-50 d-flex justify-content-center">
<button
mat-stroked-button
Expand Down
32 changes: 27 additions & 5 deletions src/app/components/options-bar/options-bar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
import { HttpErrorResponse } from '@angular/common/http';
import {
Component,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
Pipe,
PipeTransform,
} from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ActivatedRoute } from '@angular/router';
import { ComnAuthService, ComnSettingsService } from '@cmusei/crucible-common';
import { ComnSettingsService } from '@cmusei/crucible-common';
import { Observable, Subject } from 'rxjs';
import { take, takeUntil } from 'rxjs/operators';
import { VsphereVirtualMachine } from '../../generated/vm-api';
Expand All @@ -32,6 +34,11 @@ import { MatTooltip } from '@angular/material/tooltip';
import { MatIcon } from '@angular/material/icon';
import { MatMenuTrigger, MatMenu, MatMenuItem } from '@angular/material/menu';
import { MatIconButton, MatButton } from '@angular/material/button';
import {
MatSlideToggleChange,
MatSlideToggleModule,
} from '@angular/material/slide-toggle';
import { MatLabel } from '@angular/material/form-field';

declare var WMKS: any; // needed to check values
const MAX_COPY_RETRIES = 1;
Expand Down Expand Up @@ -64,13 +71,23 @@ export class KeysPipe implements PipeTransform {
MatButton,
AsyncPipe,
KeysPipe,
MatSlideToggleModule,
MatLabel,
],
})
export class OptionsBarComponent implements OnInit, OnDestroy {
@Input() vm: VsphereVirtualMachine;
@Input() vmId: string;
@Input() readOnly: boolean;

readOnlyManual: boolean;

get readOnlyInternal() {
return this.readOnlyManual || this.readOnly;
}

@Output() readOnlyChanged = new EventEmitter<boolean>();

opened = false;

// we could check permissions in api and set this value
Expand All @@ -88,7 +105,6 @@ export class OptionsBarComponent implements OnInit, OnDestroy {
vmResolutionsOptions: VmResolution[];
showConnectedUsers = false;
currentVmUsers$: Observable<string[]>;
private isDark = false;
private copyTryCount: number;
private destroy$ = new Subject();

Expand All @@ -99,11 +115,11 @@ export class OptionsBarComponent implements OnInit, OnDestroy {
private notificationService: NotificationService,
private route: ActivatedRoute,
private snackBar: MatSnackBar,
private authService: ComnAuthService,
private signalrService: SignalRService,
) {}

ngOnInit() {
this.readOnlyManual = this.readOnly;
this.vmResolutionsOptions = [
{ width: 1600, height: 1200 } as VmResolution,
{ width: 1024, height: 768 } as VmResolution,
Expand Down Expand Up @@ -205,7 +221,7 @@ export class OptionsBarComponent implements OnInit, OnDestroy {

disconnect() {
console.log('disconnect requested');
this.vmService.wmks.disconnect();
this.vmService.disconnect();
}

reconnect() {
Expand All @@ -216,7 +232,7 @@ export class OptionsBarComponent implements OnInit, OnDestroy {

connect() {
console.log('connect requested');
this.vmService.connect(this.vmId, this.readOnly);
this.vmService.connect(this.vmId, this.readOnlyInternal);
}

poweron() {
Expand Down Expand Up @@ -483,4 +499,10 @@ export class OptionsBarComponent implements OnInit, OnDestroy {
formatConnectedUserToolTip(users: string[] = null): string {
return users.toString().replace(/,/g, '\n');
}

toggleReadOnly(event: MatSlideToggleChange) {
this.readOnlyChanged.emit(event.checked);
this.readOnlyManual = event.checked;
this.vmService.setReadOnly(event.checked);
}
}
3 changes: 2 additions & 1 deletion src/app/components/wmks/wmks.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.

import {
ChangeDetectionStrategy,
Component,
ElementRef,
HostListener,
Expand All @@ -25,6 +26,7 @@ declare var WMKS: any; // needed to check values
styleUrls: ['./wmks.component.scss'],
standalone: true,
imports: [MatIcon, AsyncPipe],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class WmksComponent implements OnInit, OnDestroy {
@Input() readOnly: boolean;
Expand All @@ -38,7 +40,6 @@ export class WmksComponent implements OnInit, OnDestroy {
}

_vmId: string;

connectTimerSubscription: Subscription;

@ViewChild('wmksContainer') wmksContainer: ElementRef;
Expand Down
9 changes: 8 additions & 1 deletion src/app/state/vm/vm.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Copyright 2021 Carnegie Mellon University. All Rights Reserved.
*/

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { BehaviorSubject, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Vm, VmsService } from '../../generated/vm-api';
import { ProxmoxService } from '../../services/proxmox/proxmox.service';
Expand All @@ -20,6 +20,9 @@ export class VmService {
private proxmoxService: ProxmoxService,
) {}

readOnlySubject = new BehaviorSubject(false);
readOnly$ = this.readOnlySubject.asObservable();

get(id: string) {
return this.vmsService.getVm(id).pipe(
tap((vm: Vm) => {
Expand Down Expand Up @@ -65,4 +68,8 @@ export class VmService {
break;
}
}

setReadOnly(value: boolean) {
this.readOnlySubject.next(value);
}
}
40 changes: 27 additions & 13 deletions src/app/state/vsphere/vsphere.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ export class VsphereService {
);

private apiUrl: string;
private readOnlyObserver: MutationObserver;
private pointerEvents;
private tabIndex;

constructor(
private http: HttpClient,
Expand Down Expand Up @@ -195,7 +198,8 @@ export class VsphereService {
}

if (this.model.ticket) {
this.CreateWmks(readOnly);
this.CreateWmks();
this.setReadOnly(readOnly);
const state = this.wmks.getConnectionState();
if (state === WMKS.CONST.ConnectionState.DISCONNECTED) {
// console.log('connecting to ' + this.model.ticket);
Expand All @@ -214,21 +218,17 @@ export class VsphereService {
);
}

public CreateWmks(readOnly: boolean) {
this.wmks = WMKS.createWMKS('wmksContainer', {
changeResolution: this.model.isOwner,
rescale: true,
position: WMKS.CONST.Position.CENTER,
retryConnectionInterval:
this.settingsService.settings.WMKS.RetryConnectionInterval,
});
public setReadOnly(value: boolean) {
const elem = document.getElementById('mainCanvas');

if (value) {
this.pointerEvents = elem.style.pointerEvents;
this.tabIndex = elem.tabIndex;

if (readOnly) {
const elem = document.getElementById('mainCanvas');
elem.style.pointerEvents = 'none';
elem.tabIndex = -1;

const observer = new MutationObserver((mutations) => {
this.readOnlyObserver = new MutationObserver((mutations) => {
mutations.forEach((m) => {
if (
m.attributeName === 'style' &&
Expand All @@ -241,11 +241,25 @@ export class VsphereService {
});
});

observer.observe(elem, {
this.readOnlyObserver.observe(elem, {
attributes: true,
attributeFilter: ['style', 'tabindex'],
});
} else if (this.readOnlyObserver) {
this.readOnlyObserver.disconnect();
elem.style.pointerEvents = this.pointerEvents;
elem.tabIndex = this.tabIndex;
}
}

public CreateWmks() {
this.wmks = WMKS.createWMKS('wmksContainer', {
changeResolution: this.model.isOwner,
rescale: true,
position: WMKS.CONST.Position.CENTER,
retryConnectionInterval:
this.settingsService.settings.WMKS.RetryConnectionInterval,
});

this.wmks.register(
WMKS.CONST.Events.CONNECTION_STATE_CHANGE,
Expand Down

0 comments on commit fb9a6bf

Please sign in to comment.