From fb9a6bfd4dbe6aeed739fcd90035cf89c94568e3 Mon Sep 17 00:00:00 2001 From: Andrew Schlackman <72105194+sei-aschlackman@users.noreply.github.com> Date: Wed, 24 Apr 2024 10:49:09 -0400 Subject: [PATCH] added menu toggle to manually enable read only mode --- .../components/console/console.component.html | 3 +- .../components/console/console.component.ts | 13 +++++- .../options-bar/options-bar.component.html | 22 +++++++++- .../options-bar/options-bar.component.ts | 32 ++++++++++++--- src/app/components/wmks/wmks.component.ts | 3 +- src/app/state/vm/vm.service.ts | 9 ++++- src/app/state/vsphere/vsphere.service.ts | 40 +++++++++++++------ 7 files changed, 97 insertions(+), 25 deletions(-) diff --git a/src/app/components/console/console.component.html b/src/app/components/console/console.component.html index 762cd61..e88f230 100644 --- a/src/app/components/console/console.component.html +++ b/src/app/components/console/console.component.html @@ -27,8 +27,9 @@ [readOnly]="readOnly" [vm]="vsphereVm$ | async" [vmId]="vmId" + (readOnlyChanged)="onReadOnlyChanged($event)" > - + } } } diff --git a/src/app/components/console/console.component.ts b/src/app/components/console/console.component.ts index 309c21d..aafd8eb 100644 --- a/src/app/components/console/console.component.ts +++ b/src/app/components/console/console.component.ts @@ -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'; @@ -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; @@ -47,8 +47,17 @@ export class ConsoleComponent { vsphereVm$: Observable; virtualMachine$: Observable; + 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); + } } diff --git a/src/app/components/options-bar/options-bar.component.html b/src/app/components/options-bar/options-bar.component.html index c481ba6..165cad7 100644 --- a/src/app/components/options-bar/options-bar.component.html +++ b/src/app/components/options-bar/options-bar.component.html @@ -9,7 +9,9 @@ - {{ vm?.name }} + {{ vm?.name }}{{ readOnlyInternal ? ' (Read Only)' : '' }} Fullscreen @@ -20,6 +22,22 @@ @if (!readOnly) { + + + Read Only + + + } + + @if (!readOnlyInternal) { @if (!vmService.model.isOwner) { @@ -253,7 +271,7 @@ {{ task.taskName }} ... {{ task.progress }}% } - @if (!readOnly) { + @if (!readOnlyInternal) { (); + opened = false; // we could check permissions in api and set this value @@ -88,7 +105,6 @@ export class OptionsBarComponent implements OnInit, OnDestroy { vmResolutionsOptions: VmResolution[]; showConnectedUsers = false; currentVmUsers$: Observable; - private isDark = false; private copyTryCount: number; private destroy$ = new Subject(); @@ -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, @@ -205,7 +221,7 @@ export class OptionsBarComponent implements OnInit, OnDestroy { disconnect() { console.log('disconnect requested'); - this.vmService.wmks.disconnect(); + this.vmService.disconnect(); } reconnect() { @@ -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() { @@ -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); + } } diff --git a/src/app/components/wmks/wmks.component.ts b/src/app/components/wmks/wmks.component.ts index 8f0b945..8120d61 100644 --- a/src/app/components/wmks/wmks.component.ts +++ b/src/app/components/wmks/wmks.component.ts @@ -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, @@ -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; @@ -38,7 +40,6 @@ export class WmksComponent implements OnInit, OnDestroy { } _vmId: string; - connectTimerSubscription: Subscription; @ViewChild('wmksContainer') wmksContainer: ElementRef; diff --git a/src/app/state/vm/vm.service.ts b/src/app/state/vm/vm.service.ts index 8dfaf70..189db13 100644 --- a/src/app/state/vm/vm.service.ts +++ b/src/app/state/vm/vm.service.ts @@ -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'; @@ -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) => { @@ -65,4 +68,8 @@ export class VmService { break; } } + + setReadOnly(value: boolean) { + this.readOnlySubject.next(value); + } } diff --git a/src/app/state/vsphere/vsphere.service.ts b/src/app/state/vsphere/vsphere.service.ts index 6c02fb2..12f8841 100644 --- a/src/app/state/vsphere/vsphere.service.ts +++ b/src/app/state/vsphere/vsphere.service.ts @@ -64,6 +64,9 @@ export class VsphereService { ); private apiUrl: string; + private readOnlyObserver: MutationObserver; + private pointerEvents; + private tabIndex; constructor( private http: HttpClient, @@ -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); @@ -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' && @@ -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,