SEBSERV-148 implementation und fixes

This commit is contained in:
anhefti 2021-03-31 08:21:03 +02:00
parent 6ff8b703c9
commit 29bda22a40
12 changed files with 789 additions and 767 deletions

View file

@ -8,14 +8,10 @@
package ch.ethz.seb.sebserver.gui.content; package ch.ethz.seb.sebserver.gui.content;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Optional;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -30,18 +26,15 @@ import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification; import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification;
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.GuiServiceInfo;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.service.ResourceService; import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport; import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
@ -61,12 +54,11 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctorin
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.ConfirmPendingClientNotification; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.ConfirmPendingClientNotification;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionData; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionData;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetCollectingRooms;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetPendingClientNotifications; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetPendingClientNotifications;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProctorRoomConnection;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionDetails; import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionDetails;
import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor; import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.MonitoringProctoringService;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition; import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute; import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
@ -121,10 +113,9 @@ public class MonitoringClientConnection implements TemplateComposer {
private final I18nSupport i18nSupport; private final I18nSupport i18nSupport;
private final InstructionProcessor instructionProcessor; private final InstructionProcessor instructionProcessor;
private final SEBClientEventDetailsPopup sebClientLogDetailsPopup; private final SEBClientEventDetailsPopup sebClientLogDetailsPopup;
private final GuiServiceInfo guiServiceInfo; private final MonitoringProctoringService monitoringProctoringService;
private final long pollInterval; private final long pollInterval;
private final int pageSize; private final int pageSize;
private final String remoteProctoringEndpoint;
private final TableFilterAttribute typeFilter; private final TableFilterAttribute typeFilter;
private final TableFilterAttribute textFilter = private final TableFilterAttribute textFilter =
@ -135,21 +126,19 @@ public class MonitoringClientConnection implements TemplateComposer {
final PageService pageService, final PageService pageService,
final InstructionProcessor instructionProcessor, final InstructionProcessor instructionProcessor,
final SEBClientEventDetailsPopup sebClientLogDetailsPopup, final SEBClientEventDetailsPopup sebClientLogDetailsPopup,
final GuiServiceInfo guiServiceInfo, final MonitoringProctoringService monitoringProctoringService,
@Value("${sebserver.gui.webservice.poll-interval:500}") final long pollInterval, @Value("${sebserver.gui.webservice.poll-interval:500}") final long pollInterval,
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize, @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
@Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint) {
this.serverPushService = serverPushService; this.serverPushService = serverPushService;
this.pageService = pageService; this.pageService = pageService;
this.resourceService = pageService.getResourceService(); this.resourceService = pageService.getResourceService();
this.i18nSupport = this.resourceService.getI18nSupport(); this.i18nSupport = this.resourceService.getI18nSupport();
this.instructionProcessor = instructionProcessor; this.instructionProcessor = instructionProcessor;
this.guiServiceInfo = guiServiceInfo; this.monitoringProctoringService = monitoringProctoringService;
this.pollInterval = pollInterval; this.pollInterval = pollInterval;
this.sebClientLogDetailsPopup = sebClientLogDetailsPopup; this.sebClientLogDetailsPopup = sebClientLogDetailsPopup;
this.pageSize = pageSize; this.pageSize = pageSize;
this.remoteProctoringEndpoint = remoteProctoringEndpoint;
this.typeFilter = new TableFilterAttribute( this.typeFilter = new TableFilterAttribute(
CriteriaType.SINGLE_SELECTION, CriteriaType.SINGLE_SELECTION,
@ -380,20 +369,25 @@ public class MonitoringClientConnection implements TemplateComposer {
.getBuilder(GetProctoringSettings.class) .getBuilder(GetProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId) .withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
.call() .call()
.onError(error -> log.error("Failed to get ProctoringSettings", error)) .onError(error -> log.error("Failed to get ProctoringServiceSettings", error))
.getOr(null); .getOr(null);
final ProctoringGUIService proctoringGUIService = currentUser.getProctoringGUIService();
if (procotringSettings != null && procotringSettings.enableProctoring) { if (procotringSettings != null && procotringSettings.enableProctoring) {
actionBuilder actionBuilder
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_PROCTORING) .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_PROCTORING)
.withEntityKey(parentEntityKey) .withEntityKey(parentEntityKey)
.withExec(action -> this.openOneToOneRoom(action, connectionData)) .withExec(action -> this.monitoringProctoringService.openOneToOneRoom(
action,
connectionData, proctoringGUIService))
.noEventPropagation() .noEventPropagation()
.publish() .publish()
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_EXAM_ROOM_PROCTORING) .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_EXAM_ROOM_PROCTORING)
.withEntityKey(parentEntityKey) .withEntityKey(parentEntityKey)
.withExec(action -> this.openExamCollectionProctorScreen(action, connectionData)) .withExec(action -> this.monitoringProctoringService.openExamCollectionProctorScreen(
action,
connectionData))
.noEventPropagation() .noEventPropagation()
.publish(); .publish();
@ -434,98 +428,6 @@ public class MonitoringClientConnection implements TemplateComposer {
connectionData.clientConnection.connectionToken); connectionData.clientConnection.connectionToken);
} }
private PageAction openExamCollectionProctorScreen(
final PageAction action,
final ClientConnectionData connectionData) {
final String examId = action.getEntityKey().modelId;
final ProctoringServiceSettings proctoringSettings = this.pageService.getRestService()
.getBuilder(GetProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.call()
.getOrThrow();
final Optional<RemoteProctoringRoom> roomOptional =
this.pageService.getRestService().getBuilder(GetCollectingRooms.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.call()
.getOrThrow()
.stream()
.filter(room -> room.id.equals(connectionData.clientConnection.remoteProctoringRoomId))
.findFirst();
if (roomOptional.isPresent()) {
final RemoteProctoringRoom room = roomOptional.get();
final ProctoringRoomConnection proctoringConnectionData = this.pageService
.getRestService()
.getBuilder(GetProctorRoomConnection.class)
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId))
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name)
.withQueryParam(ProctoringRoomConnection.ATTR_SUBJECT, Utils.encodeFormURL_UTF_8(room.subject))
.call()
.getOrThrow();
ProctoringGUIService.setCurrentProctoringWindowData(examId, proctoringConnectionData);
final String script = String.format(
MonitoringRunningExam.OPEN_ROOM_SCRIPT,
room.name,
800,
1200,
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
this.remoteProctoringEndpoint);
RWT.getClient()
.getService(JavaScriptExecutor.class)
.execute(script);
this.pageService.getCurrentUser()
.getProctoringGUIService()
.registerProctoringWindow(examId, room.name, room.name);
}
return action;
}
private PageAction openOneToOneRoom(
final PageAction action,
final ClientConnectionData connectionData) {
final String connectionToken = connectionData.clientConnection.connectionToken;
final String windowName = connectionToken + "_one2oneRooom";
final String examId = action.getEntityKey().modelId;
final ProctoringGUIService proctoringGUIService = this.pageService
.getCurrentUser()
.getProctoringGUIService();
if (!proctoringGUIService.hasWindow(windowName)) {
final ProctoringRoomConnection proctoringConnectionData = proctoringGUIService
.openBreakOutRoom(
examId,
windowName,
connectionData.clientConnection.userSessionId,
Arrays.asList(connectionToken))
.onError(error -> log.error(
"Failed to open single proctoring room for connection {} {}",
connectionToken,
error.getMessage()))
.getOr(null);
ProctoringGUIService.setCurrentProctoringWindowData(examId, windowName, proctoringConnectionData);
}
final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
final String script = String.format(
MonitoringRunningExam.OPEN_ROOM_SCRIPT,
windowName,
420,
640,
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
this.remoteProctoringEndpoint);
javaScriptExecutor.execute(script);
return action;
}
private String getClientTime(final ClientEvent event) { private String getClientTime(final ClientEvent event) {
if (event == null || event.getClientTime() == null) { if (event == null || event.getClientTime() == null) {
return Constants.EMPTY_NOTE; return Constants.EMPTY_NOTE;

View file

@ -10,33 +10,22 @@ package ch.ethz.seb.sebserver.gui.content;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.MessageBox; import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.async.AsyncRunner; import ch.ethz.seb.sebserver.gbl.async.AsyncRunner;
@ -44,22 +33,16 @@ import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Pair;
import ch.ethz.seb.sebserver.gbl.util.Tuple; import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.GuiServiceInfo;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.content.action.ActionPane;
import ch.ethz.seb.sebserver.gui.service.ResourceService; import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException; import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
@ -76,12 +59,10 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionDataList; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionDataList;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetCollectingRooms;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProctorRoomConnection;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.IsTownhallRoomAvailable;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable; import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable;
import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor; import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.MonitoringProctoringService;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService;
import ch.ethz.seb.sebserver.gui.widget.Message; import ch.ethz.seb.sebserver.gui.widget.Message;
@ -92,23 +73,6 @@ public class MonitoringRunningExam implements TemplateComposer {
private static final Logger log = LoggerFactory.getLogger(MonitoringRunningExam.class); private static final Logger log = LoggerFactory.getLogger(MonitoringRunningExam.class);
// @formatter:off
static final String OPEN_ROOM_SCRIPT =
"try {\n" +
"var existingWin = window.open('', '%s', 'height=%s,width=%s,location=no,scrollbars=yes,status=no,menubar=0,toolbar=no,titlebar=no,dialog=no');\n" +
"if(existingWin.location.href === 'about:blank'){\n" +
" existingWin.location.href = '%s%s';\n" +
" existingWin.focus();\n" +
"} else {\n" +
" existingWin.focus();\n" +
"}" +
"}\n" +
"catch(err) {\n" +
" alert(\"Unexpected Javascript Error happened: \" + err);\n"+
"}";
// @formatter:on
private static final String SHOW_CONNECTION_ACTION_APPLIED = "SHOW_CONNECTION_ACTION_APPLIED";
private static final LocTextKey EMPTY_SELECTION_TEXT_KEY = private static final LocTextKey EMPTY_SELECTION_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.connection.emptySelection"); new LocTextKey("sebserver.monitoring.exam.connection.emptySelection");
private static final LocTextKey EMPTY_ACTIVE_SELECTION_TEXT_KEY = private static final LocTextKey EMPTY_ACTIVE_SELECTION_TEXT_KEY =
@ -119,8 +83,6 @@ public class MonitoringRunningExam implements TemplateComposer {
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.all.confirm"); new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.all.confirm");
private static final LocTextKey CONFIRM_DISABLE_SELECTED = private static final LocTextKey CONFIRM_DISABLE_SELECTED =
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.disable.selected.confirm"); new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.disable.selected.confirm");
private static final LocTextKey EXAM_ROOM_NAME =
new LocTextKey("sebserver.monitoring.exam.proctoring.room.all.name");
private final ServerPushService serverPushService; private final ServerPushService serverPushService;
private final PageService pageService; private final PageService pageService;
@ -128,23 +90,19 @@ public class MonitoringRunningExam implements TemplateComposer {
private final ResourceService resourceService; private final ResourceService resourceService;
private final AsyncRunner asyncRunner; private final AsyncRunner asyncRunner;
private final InstructionProcessor instructionProcessor; private final InstructionProcessor instructionProcessor;
private final GuiServiceInfo guiServiceInfo;
private final MonitoringExamSearchPopup monitoringExamSearchPopup; private final MonitoringExamSearchPopup monitoringExamSearchPopup;
private final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup; private final MonitoringProctoringService monitoringProctoringService;
private final long pollInterval; private final long pollInterval;
private final long proctoringRoomUpdateInterval; private final long proctoringRoomUpdateInterval;
private final String remoteProctoringEndpoint;
protected MonitoringRunningExam( protected MonitoringRunningExam(
final ServerPushService serverPushService, final ServerPushService serverPushService,
final PageService pageService, final PageService pageService,
final AsyncRunner asyncRunner, final AsyncRunner asyncRunner,
final InstructionProcessor instructionProcessor, final InstructionProcessor instructionProcessor,
final GuiServiceInfo guiServiceInfo,
final MonitoringExamSearchPopup monitoringExamSearchPopup, final MonitoringExamSearchPopup monitoringExamSearchPopup,
final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup, final MonitoringProctoringService monitoringProctoringService,
@Value("${sebserver.gui.webservice.poll-interval:1000}") final long pollInterval, @Value("${sebserver.gui.webservice.poll-interval:1000}") final long pollInterval,
@Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint,
@Value("${sebserver.gui.remote.proctoring.rooms.update.poll-interval:5000}") final long proctoringRoomUpdateInterval) { @Value("${sebserver.gui.remote.proctoring.rooms.update.poll-interval:5000}") final long proctoringRoomUpdateInterval) {
this.serverPushService = serverPushService; this.serverPushService = serverPushService;
@ -153,11 +111,9 @@ public class MonitoringRunningExam implements TemplateComposer {
this.resourceService = pageService.getResourceService(); this.resourceService = pageService.getResourceService();
this.asyncRunner = asyncRunner; this.asyncRunner = asyncRunner;
this.instructionProcessor = instructionProcessor; this.instructionProcessor = instructionProcessor;
this.guiServiceInfo = guiServiceInfo; this.monitoringProctoringService = monitoringProctoringService;
this.pollInterval = pollInterval; this.pollInterval = pollInterval;
this.monitoringExamSearchPopup = monitoringExamSearchPopup; this.monitoringExamSearchPopup = monitoringExamSearchPopup;
this.remoteProctoringEndpoint = remoteProctoringEndpoint;
this.proctorRoomConnectionsPopup = proctorRoomConnectionsPopup;
this.proctoringRoomUpdateInterval = proctoringRoomUpdateInterval; this.proctoringRoomUpdateInterval = proctoringRoomUpdateInterval;
} }
@ -308,11 +264,12 @@ public class MonitoringRunningExam implements TemplateComposer {
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM) actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(action -> this.toggleTownhallRoom(proctoringGUIService, action)) .withExec(action -> this.monitoringProctoringService.toggleTownhallRoom(proctoringGUIService,
action))
.noEventPropagation() .noEventPropagation()
.publish(); .publish();
if (isTownhallRoomActive(entityKey.modelId)) { if (this.monitoringProctoringService.isTownhallRoomActive(entityKey.modelId)) {
this.pageService.firePageEvent( this.pageService.firePageEvent(
new ActionActivationEvent( new ActionActivationEvent(
true, true,
@ -322,10 +279,8 @@ public class MonitoringRunningExam implements TemplateComposer {
pageContext); pageContext);
} }
final Map<String, Pair<RemoteProctoringRoom, TreeItem>> availableRooms = new HashMap<>(); this.monitoringProctoringService.initCollectingRoomActions(
updateRoomActions(
pageContext, pageContext,
availableRooms,
actionBuilder, actionBuilder,
proctoringSettings, proctoringSettings,
proctoringGUIService); proctoringGUIService);
@ -335,9 +290,8 @@ public class MonitoringRunningExam implements TemplateComposer {
Utils.truePredicate(), Utils.truePredicate(),
createServerPushUpdateErrorHandler(this.pageService, pageContext)), createServerPushUpdateErrorHandler(this.pageService, pageContext)),
this.proctoringRoomUpdateInterval, this.proctoringRoomUpdateInterval,
context -> updateRoomActions( context -> this.monitoringProctoringService.updateCollectingRoomActions(
pageContext, pageContext,
availableRooms,
actionBuilder, actionBuilder,
proctoringSettings, proctoringSettings,
proctoringGUIService)); proctoringGUIService));
@ -437,296 +391,11 @@ public class MonitoringRunningExam implements TemplateComposer {
} }
} }
private boolean isTownhallRoomActive(final String examModelId) {
return !BooleanUtils.toBoolean(this.pageService
.getRestService()
.getBuilder(IsTownhallRoomAvailable.class)
.withURIVariable(API.PARAM_MODEL_ID, examModelId)
.call()
.getOr(Constants.FALSE_STRING));
}
private PageAction openSearchPopup(final PageAction action) { private PageAction openSearchPopup(final PageAction action) {
this.monitoringExamSearchPopup.show(action.pageContext()); this.monitoringExamSearchPopup.show(action.pageContext());
return action; return action;
} }
private PageAction toggleTownhallRoom(
final ProctoringGUIService proctoringGUIService,
final PageAction action) {
if (isTownhallRoomActive(action.getEntityKey().modelId)) {
closeTownhallRoom(proctoringGUIService, action);
this.pageService.firePageEvent(
new ActionActivationEvent(
true,
new Tuple<>(
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM)),
action.pageContext());
return action;
} else {
openTownhallRoom(proctoringGUIService, action);
this.pageService.firePageEvent(
new ActionActivationEvent(
true,
new Tuple<>(
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM)),
action.pageContext());
return action;
}
}
private PageAction openTownhallRoom(
final ProctoringGUIService proctoringGUIService,
final PageAction action) {
try {
final EntityKey examId = action.getEntityKey();
final String windowName = getTownhallWindowName(examId.modelId);
if (!proctoringGUIService.hasWindow(windowName)) {
final ProctoringRoomConnection proctoringConnectionData = proctoringGUIService
.openTownhallRoom(
examId.modelId,
windowName,
this.pageService.getI18nSupport().getText(EXAM_ROOM_NAME))
.onError(error -> log.error(
"Failed to open all collecting room for exam {} {}", examId.modelId,
error.getMessage()))
.getOrThrow();
ProctoringGUIService.setCurrentProctoringWindowData(
examId.modelId,
windowName,
proctoringConnectionData);
}
final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
final String script = String.format(
OPEN_ROOM_SCRIPT,
windowName,
800,
1200,
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
this.remoteProctoringEndpoint);
javaScriptExecutor.execute(script);
} catch (final Exception e) {
log.error("Failed to open popup for town-hall room: ", e);
}
return action;
}
private PageAction closeTownhallRoom(
final ProctoringGUIService proctoringGUIService,
final PageAction action) {
final String examId = action.getEntityKey().modelId;
try {
this.pageService
.getCurrentUser()
.getProctoringGUIService()
.closeRoomWindow(getTownhallWindowName(examId));
} catch (final Exception e) {
log.error("Failed to close proctoring town-hall room for exam: {}", examId);
}
return action;
}
private void updateTownhallButton(
final ProctoringGUIService proctoringGUIService,
final PageContext pageContext) {
final EntityKey entityKey = pageContext.getEntityKey();
if (isTownhallRoomActive(entityKey.modelId)) {
final boolean townhallRoomFromThisUser = proctoringGUIService
.isTownhallOpenForUser(entityKey.modelId);
if (townhallRoomFromThisUser) {
this.pageService.firePageEvent(
new ActionActivationEvent(
true,
new Tuple<>(
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM)),
pageContext);
} else {
this.pageService.firePageEvent(
new ActionActivationEvent(
false,
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM),
pageContext);
}
} else {
this.pageService.firePageEvent(
new ActionActivationEvent(
true,
new Tuple<>(
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM),
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM),
pageContext);
}
}
private void updateRoomActions(
final PageContext pageContext,
final Map<String, Pair<RemoteProctoringRoom, TreeItem>> rooms,
final PageActionBuilder actionBuilder,
final ProctoringServiceSettings proctoringSettings,
final ProctoringGUIService proctoringGUIService) {
final EntityKey entityKey = pageContext.getEntityKey();
updateTownhallButton(proctoringGUIService, pageContext);
final I18nSupport i18nSupport = this.pageService.getI18nSupport();
this.pageService
.getRestService()
.getBuilder(GetCollectingRooms.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
.onError(error -> log.error("Failed to update proctoring rooms on GUI {}", error.getMessage()))
.getOr(Collections.emptyList())
.stream()
.forEach(room -> {
if (rooms.containsKey(room.name)) {
// update action
final TreeItem treeItem = rooms.get(room.name).b;
rooms.put(room.name, new Pair<>(room, treeItem));
treeItem.setText(i18nSupport.getText(new LocTextKey(
ActionDefinition.MONITOR_EXAM_VIEW_PROCTOR_ROOM.title.name,
room.subject,
room.roomSize,
proctoringSettings.collectingRoomSize)));
processProctorRoomActionActivation(treeItem, room, pageContext);
} else {
// create new action
final PageAction action =
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_VIEW_PROCTOR_ROOM)
.withEntityKey(entityKey)
.withExec(_action -> {
final int actualRoomSize = getActualRoomSize(room, rooms);
if (actualRoomSize <= 0) {
return _action;
}
return showExamProctoringRoom(proctoringSettings, room, _action);
})
.withNameAttributes(
room.subject,
room.roomSize,
proctoringSettings.collectingRoomSize)
.noEventPropagation()
.create();
this.pageService.publishAction(
action,
_treeItem -> rooms.put(room.name, new Pair<>(room, _treeItem)));
addRoomConnectionsPopupListener(pageContext, rooms);
processProctorRoomActionActivation(rooms.get(room.name).b, room, pageContext);
}
});
}
private void processProctorRoomActionActivation(
final TreeItem treeItem,
final RemoteProctoringRoom room,
final PageContext pageContext) {
try {
final Display display = pageContext.getRoot().getDisplay();
final PageAction action = (PageAction) treeItem.getData(ActionPane.ACTION_EVENT_CALL_KEY);
final Image image = room.roomSize > 0
? action.definition.icon.getImage(display)
: action.definition.icon.getGreyedImage(display);
treeItem.setImage(image);
treeItem.setForeground(room.roomSize > 0 ? null : new Color(display, Constants.GREY_DISABLED));
} catch (final Exception e) {
log.warn("Failed to set Proctor-Room-Activation: ", e.getMessage());
}
}
private void addRoomConnectionsPopupListener(
final PageContext pageContext,
final Map<String, Pair<RemoteProctoringRoom, TreeItem>> rooms) {
if (!rooms.isEmpty()) {
final EntityKey entityKey = pageContext.getEntityKey();
final TreeItem treeItem = rooms.values().iterator().next().b;
final Tree tree = treeItem.getParent();
if (tree.getData(SHOW_CONNECTION_ACTION_APPLIED) == null) {
tree.addListener(SWT.Selection, event -> {
final TreeItem item = (TreeItem) event.item;
item.getParent().deselectAll();
if (event.button == 3) {
rooms.entrySet()
.stream()
.filter(e -> e.getValue().b.equals(item))
.findFirst()
.ifPresent(e -> {
final RemoteProctoringRoom room = e.getValue().a;
if (room.roomSize > 0) {
final PageContext pc = pageContext.copy()
.clearAttributes()
.withEntityKey(new EntityKey(room.name,
EntityType.REMOTE_PROCTORING_ROOM))
.withParentEntityKey(entityKey);
this.proctorRoomConnectionsPopup.show(pc, room.subject);
}
});
}
});
tree.setData(SHOW_CONNECTION_ACTION_APPLIED, true);
}
}
}
private int getActualRoomSize(
final RemoteProctoringRoom room,
final Map<String, Pair<RemoteProctoringRoom, TreeItem>> rooms) {
return rooms.get(room.name).a.roomSize;
}
private PageAction showExamProctoringRoom(
final ProctoringServiceSettings proctoringSettings,
final RemoteProctoringRoom room,
final PageAction action) {
final ProctoringRoomConnection proctoringConnectionData = this.pageService
.getRestService()
.getBuilder(GetProctorRoomConnection.class)
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId))
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name)
.withQueryParam(ProctoringRoomConnection.ATTR_SUBJECT, Utils.encodeFormURL_UTF_8(room.subject))
.call()
.getOrThrow();
ProctoringGUIService.setCurrentProctoringWindowData(
String.valueOf(proctoringSettings.examId),
proctoringConnectionData);
final String script = String.format(
OPEN_ROOM_SCRIPT,
room.name,
800,
1200,
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
this.remoteProctoringEndpoint);
RWT.getClient()
.getService(JavaScriptExecutor.class)
.execute(script);
this.pageService.getCurrentUser()
.getProctoringGUIService()
.registerProctoringWindow(String.valueOf(room.examId), room.name, room.name);
return action;
}
private static Function<PageAction, PageAction> showStateViewAction( private static Function<PageAction, PageAction> showStateViewAction(
final ClientConnectionTable clientTable, final ClientConnectionTable clientTable,
final ConnectionStatus status) { final ConnectionStatus status) {
@ -835,8 +504,4 @@ public class MonitoringRunningExam implements TemplateComposer {
}; };
} }
private String getTownhallWindowName(final String examId) {
return examId + "_townhall";
}
} }

View file

@ -9,9 +9,25 @@
package ch.ethz.seb.sebserver.gui.service.page; package ch.ethz.seb.sebserver.gui.service.page;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
public interface RemoteProctoringView extends TemplateComposer { public interface RemoteProctoringView extends TemplateComposer {
static final LocTextKey CLOSE_WINDOW_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.close");
static final LocTextKey BROADCAST_AUDIO_ON_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcaston.audio");
static final LocTextKey BROADCAST_AUDIO_OFF_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcastoff.audio");
static final LocTextKey BROADCAST_VIDEO_ON_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcaston.video");
static final LocTextKey BROADCAST_VIDEO_OFF_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcastoff.video");
static final LocTextKey CHAT_ON_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcaston.chat");
static final LocTextKey CHAT_OFF_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcastoff.chat");
/** Get the remote proctoring server type this remote proctoring view can handle. /** Get the remote proctoring server type this remote proctoring view can handle.
* *
* @return the remote proctoring server type this remote proctoring view can handle. */ * @return the remote proctoring server type this remote proctoring view can handle. */

View file

@ -0,0 +1,142 @@
/*
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.page.impl;
import org.eclipse.swt.widgets.Button;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gui.GuiServiceInfo;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.RemoteProctoringView;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.SendProctoringReconfigurationAttributes;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData;
public abstract class AbstractProctoringView implements RemoteProctoringView {
private static final Logger log = LoggerFactory.getLogger(AbstractProctoringView.class);
protected final PageService pageService;
protected final GuiServiceInfo guiServiceInfo;
protected final String remoteProctoringEndpoint;
protected final String remoteProctoringViewServletEndpoint;
protected AbstractProctoringView(
final PageService pageService,
final GuiServiceInfo guiServiceInfo,
final String remoteProctoringEndpoint,
final String remoteProctoringViewServletEndpoint) {
this.pageService = pageService;
this.guiServiceInfo = guiServiceInfo;
this.remoteProctoringEndpoint = remoteProctoringEndpoint;
this.remoteProctoringViewServletEndpoint = remoteProctoringViewServletEndpoint;
}
protected void sendReconfigurationAttributes(
final String examId,
final String roomName,
final BroadcastActionState state) {
this.pageService.getRestService().getBuilder(SendProctoringReconfigurationAttributes.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomName)
.withFormParam(
API.EXAM_PROCTORING_ATTR_RECEIVE_AUDIO,
state.audio ? Constants.TRUE_STRING : Constants.FALSE_STRING)
.withFormParam(
API.EXAM_PROCTORING_ATTR_RECEIVE_VIDEO,
state.video ? Constants.TRUE_STRING : Constants.FALSE_STRING)
.withFormParam(
API.EXAM_PROCTORING_ATTR_ALLOW_CHAT,
state.chat ? Constants.TRUE_STRING : Constants.FALSE_STRING)
.call()
.onError(error -> log.error("Failed to send broadcast attributes to clients in room: {} cause: {}",
roomName,
error.getMessage()));
}
protected void toggleBroadcastAudio(
final String examId,
final String roomName,
final Button broadcastAction) {
final BroadcastActionState state =
(BroadcastActionState) broadcastAction.getData(BroadcastActionState.KEY_NAME);
this.pageService.getPolyglotPageService().injectI18n(
broadcastAction,
state.audio ? BROADCAST_AUDIO_ON_TEXT_KEY : BROADCAST_AUDIO_OFF_TEXT_KEY);
state.audio = !state.audio;
sendReconfigurationAttributes(examId, roomName, state);
}
protected void toggleBroadcastVideo(
final String examId,
final String roomName,
final Button videoAction,
final Button audioAction) {
final BroadcastActionState state =
(BroadcastActionState) videoAction.getData(BroadcastActionState.KEY_NAME);
this.pageService.getPolyglotPageService().injectI18n(
audioAction,
state.video ? BROADCAST_AUDIO_ON_TEXT_KEY : BROADCAST_AUDIO_OFF_TEXT_KEY);
this.pageService.getPolyglotPageService().injectI18n(
videoAction,
state.video ? BROADCAST_VIDEO_ON_TEXT_KEY : BROADCAST_VIDEO_OFF_TEXT_KEY);
state.video = !state.video;
state.audio = state.video;
sendReconfigurationAttributes(examId, roomName, state);
}
protected void toggleChat(
final String examId,
final String roomName,
final Button broadcastAction) {
final BroadcastActionState state =
(BroadcastActionState) broadcastAction.getData(BroadcastActionState.KEY_NAME);
this.pageService.getPolyglotPageService().injectI18n(
broadcastAction,
state.chat ? CHAT_ON_TEXT_KEY : CHAT_OFF_TEXT_KEY);
state.chat = !state.chat;
sendReconfigurationAttributes(examId, roomName, state);
}
protected void closeRoom(
final ProctoringGUIService proctoringGUIService,
final ProctoringWindowData proctoringWindowData) {
try {
proctoringGUIService.closeRoomWindow(proctoringWindowData.windowName);
} catch (final Exception e) {
log.error("Failed to close proctoring window properly: ", e);
}
}
static final class BroadcastActionState {
public static final String KEY_NAME = "BroadcastActionState";
boolean audio = false;
boolean video = false;
boolean chat = false;
}
}

View file

@ -23,56 +23,28 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.GuiServiceInfo; import ch.ethz.seb.sebserver.gui.GuiServiceInfo;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.RemoteProctoringView;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.SendProctoringReconfigurationAttributes;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@Component @Component
@GuiProfile @GuiProfile
public class JitsiMeetProctoringView implements RemoteProctoringView { public class JitsiMeetProctoringView extends AbstractProctoringView {
private static final Logger log = LoggerFactory.getLogger(JitsiMeetProctoringView.class); private static final Logger log = LoggerFactory.getLogger(JitsiMeetProctoringView.class);
private static final LocTextKey CLOSE_WINDOW_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.close");
private static final LocTextKey BROADCAST_AUDIO_ON_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcaston.audio");
private static final LocTextKey BROADCAST_AUDIO_OFF_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcastoff.audio");
private static final LocTextKey BROADCAST_VIDEO_ON_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcaston.video");
private static final LocTextKey BROADCAST_VIDEO_OFF_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcastoff.video");
private static final LocTextKey CHAT_ON_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcaston.chat");
private static final LocTextKey CHAT_OFF_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcastoff.chat");
private final PageService pageService;
private final GuiServiceInfo guiServiceInfo;
private final String remoteProctoringEndpoint;
private final String remoteProctoringViewServletEndpoint;
public JitsiMeetProctoringView( public JitsiMeetProctoringView(
final PageService pageService, final PageService pageService,
final GuiServiceInfo guiServiceInfo, final GuiServiceInfo guiServiceInfo,
@Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint, @Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint,
@Value("${sebserver.gui.remote.proctoring.api-servler.endpoint:/remote-view-servlet}") final String remoteProctoringViewServletEndpoint) { @Value("${sebserver.gui.remote.proctoring.api-servler.endpoint:/remote-view-servlet}") final String remoteProctoringViewServletEndpoint) {
this.pageService = pageService; super(pageService, guiServiceInfo, remoteProctoringEndpoint, remoteProctoringViewServletEndpoint);
this.guiServiceInfo = guiServiceInfo;
this.remoteProctoringEndpoint = remoteProctoringEndpoint;
this.remoteProctoringViewServletEndpoint = remoteProctoringViewServletEndpoint;
} }
@Override @Override
@ -118,19 +90,18 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
final Composite footer = new Composite(content, SWT.NONE | SWT.NO_SCROLL); final Composite footer = new Composite(content, SWT.NONE | SWT.NO_SCROLL);
footer.setLayout(new RowLayout()); footer.setLayout(new RowLayout());
final GridData footerLayout = new GridData(SWT.CENTER, SWT.BOTTOM, true, false); final GridData footerLayout = new GridData(SWT.CENTER, SWT.BOTTOM, true, false);
footerLayout.heightHint = 40;
footer.setLayoutData(footerLayout); footer.setLayoutData(footerLayout);
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory(); final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
final Button closeAction = widgetFactory.buttonLocalized(footer, CLOSE_WINDOW_TEXT_KEY); final Button closeAction = widgetFactory.buttonLocalized(footer, CLOSE_WINDOW_TEXT_KEY);
closeAction.setLayoutData(new RowData(150, 30)); closeAction.setLayoutData(new RowData());
closeAction.addListener(SWT.Selection, event -> closeRoom(proctoringGUIService, proctoringWindowData)); closeAction.addListener(SWT.Selection, event -> closeRoom(proctoringGUIService, proctoringWindowData));
final BroadcastActionState broadcastActionState = new BroadcastActionState(); final BroadcastActionState broadcastActionState = new BroadcastActionState();
final Button broadcastAudioAction = widgetFactory.buttonLocalized(footer, BROADCAST_AUDIO_ON_TEXT_KEY); final Button broadcastAudioAction = widgetFactory.buttonLocalized(footer, BROADCAST_AUDIO_ON_TEXT_KEY);
broadcastAudioAction.setLayoutData(new RowData(150, 30)); broadcastAudioAction.setLayoutData(new RowData());
broadcastAudioAction.addListener(SWT.Selection, event -> toggleBroadcastAudio( broadcastAudioAction.addListener(SWT.Selection, event -> toggleBroadcastAudio(
proctoringWindowData.examId, proctoringWindowData.examId,
proctoringWindowData.connectionData.roomName, proctoringWindowData.connectionData.roomName,
@ -138,7 +109,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
broadcastAudioAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); broadcastAudioAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState);
final Button broadcastVideoAction = widgetFactory.buttonLocalized(footer, BROADCAST_VIDEO_ON_TEXT_KEY); final Button broadcastVideoAction = widgetFactory.buttonLocalized(footer, BROADCAST_VIDEO_ON_TEXT_KEY);
broadcastVideoAction.setLayoutData(new RowData(150, 30)); broadcastVideoAction.setLayoutData(new RowData());
broadcastVideoAction.addListener(SWT.Selection, event -> toggleBroadcastVideo( broadcastVideoAction.addListener(SWT.Selection, event -> toggleBroadcastVideo(
proctoringWindowData.examId, proctoringWindowData.examId,
proctoringWindowData.connectionData.roomName, proctoringWindowData.connectionData.roomName,
@ -147,7 +118,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
broadcastVideoAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); broadcastVideoAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState);
final Button chatAction = widgetFactory.buttonLocalized(footer, CHAT_ON_TEXT_KEY); final Button chatAction = widgetFactory.buttonLocalized(footer, CHAT_ON_TEXT_KEY);
chatAction.setLayoutData(new RowData(150, 30)); chatAction.setLayoutData(new RowData());
chatAction.addListener(SWT.Selection, event -> toggleChat( chatAction.addListener(SWT.Selection, event -> toggleChat(
proctoringWindowData.examId, proctoringWindowData.examId,
proctoringWindowData.connectionData.roomName, proctoringWindowData.connectionData.roomName,
@ -155,100 +126,4 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
chatAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); chatAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState);
} }
private void sendReconfigurationAttributes(
final String examId,
final String roomName,
final BroadcastActionState state) {
this.pageService.getRestService().getBuilder(SendProctoringReconfigurationAttributes.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomName)
.withFormParam(
API.EXAM_PROCTORING_ATTR_RECEIVE_AUDIO,
state.audio ? Constants.TRUE_STRING : Constants.FALSE_STRING)
.withFormParam(
API.EXAM_PROCTORING_ATTR_RECEIVE_VIDEO,
state.video ? Constants.TRUE_STRING : Constants.FALSE_STRING)
.withFormParam(
API.EXAM_PROCTORING_ATTR_ALLOW_CHAT,
state.chat ? Constants.TRUE_STRING : Constants.FALSE_STRING)
.call()
.onError(error -> log.error("Failed to send broadcast attributes to clients in room: {} cause: {}",
roomName,
error.getMessage()));
}
private void toggleBroadcastAudio(
final String examId,
final String roomName,
final Button broadcastAction) {
final BroadcastActionState state =
(BroadcastActionState) broadcastAction.getData(BroadcastActionState.KEY_NAME);
this.pageService.getPolyglotPageService().injectI18n(
broadcastAction,
state.audio ? BROADCAST_AUDIO_ON_TEXT_KEY : BROADCAST_AUDIO_OFF_TEXT_KEY);
state.audio = !state.audio;
sendReconfigurationAttributes(examId, roomName, state);
}
private void toggleBroadcastVideo(
final String examId,
final String roomName,
final Button videoAction,
final Button audioAction) {
final BroadcastActionState state =
(BroadcastActionState) videoAction.getData(BroadcastActionState.KEY_NAME);
this.pageService.getPolyglotPageService().injectI18n(
audioAction,
state.video ? BROADCAST_AUDIO_ON_TEXT_KEY : BROADCAST_AUDIO_OFF_TEXT_KEY);
this.pageService.getPolyglotPageService().injectI18n(
videoAction,
state.video ? BROADCAST_VIDEO_ON_TEXT_KEY : BROADCAST_VIDEO_OFF_TEXT_KEY);
state.video = !state.video;
state.audio = state.video;
sendReconfigurationAttributes(examId, roomName, state);
}
private void toggleChat(
final String examId,
final String roomName,
final Button broadcastAction) {
final BroadcastActionState state =
(BroadcastActionState) broadcastAction.getData(BroadcastActionState.KEY_NAME);
this.pageService.getPolyglotPageService().injectI18n(
broadcastAction,
state.chat ? CHAT_ON_TEXT_KEY : CHAT_OFF_TEXT_KEY);
state.chat = !state.chat;
sendReconfigurationAttributes(examId, roomName, state);
}
private void closeRoom(
final ProctoringGUIService proctoringGUIService,
final ProctoringWindowData proctoringWindowData) {
try {
proctoringGUIService.closeRoomWindow(proctoringWindowData.connectionData.roomName);
} catch (final Exception e) {
log.error("Failed to close proctoring window properly: ", e);
}
}
static final class BroadcastActionState {
public static final String KEY_NAME = "BroadcastActionState";
boolean audio = false;
boolean video = false;
boolean chat = false;
}
} }

View file

@ -23,56 +23,28 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.GuiServiceInfo; import ch.ethz.seb.sebserver.gui.GuiServiceInfo;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.RemoteProctoringView;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.SendProctoringReconfigurationAttributes;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService;
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@Component @Component
@GuiProfile @GuiProfile
public class ZoomProctoringView implements RemoteProctoringView { public class ZoomProctoringView extends AbstractProctoringView {
private static final Logger log = LoggerFactory.getLogger(ZoomProctoringView.class); private static final Logger log = LoggerFactory.getLogger(ZoomProctoringView.class);
private static final LocTextKey CLOSE_WINDOW_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.close");
private static final LocTextKey BROADCAST_AUDIO_ON_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcaston.audio");
private static final LocTextKey BROADCAST_AUDIO_OFF_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcastoff.audio");
private static final LocTextKey BROADCAST_VIDEO_ON_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcaston.video");
private static final LocTextKey BROADCAST_VIDEO_OFF_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcastoff.video");
private static final LocTextKey CHAT_ON_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcaston.chat");
private static final LocTextKey CHAT_OFF_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcastoff.chat");
private final PageService pageService;
private final GuiServiceInfo guiServiceInfo;
private final String remoteProctoringEndpoint;
private final String remoteProctoringViewServletEndpoint;
public ZoomProctoringView( public ZoomProctoringView(
final PageService pageService, final PageService pageService,
final GuiServiceInfo guiServiceInfo, final GuiServiceInfo guiServiceInfo,
@Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint, @Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint,
@Value("${sebserver.gui.remote.proctoring.api-servler.endpoint:/remote-view-servlet}") final String remoteProctoringViewServletEndpoint) { @Value("${sebserver.gui.remote.proctoring.api-servler.endpoint:/remote-view-servlet}") final String remoteProctoringViewServletEndpoint) {
this.pageService = pageService; super(pageService, guiServiceInfo, remoteProctoringEndpoint, remoteProctoringViewServletEndpoint);
this.guiServiceInfo = guiServiceInfo;
this.remoteProctoringEndpoint = remoteProctoringEndpoint;
this.remoteProctoringViewServletEndpoint = remoteProctoringViewServletEndpoint;
} }
@Override @Override
@ -82,18 +54,20 @@ public class ZoomProctoringView implements RemoteProctoringView {
@Override @Override
public void compose(final PageContext pageContext) { public void compose(final PageContext pageContext) {
final ProctoringWindowData proctoringWindowData = ProctoringGUIService.getCurrentProctoringWindowData(); final ProctoringWindowData proctoringWindowData = ProctoringGUIService.getCurrentProctoringWindowData();
final Composite parent = pageContext.getParent(); final Composite parent = pageContext.getParent();
final Composite content = new Composite(parent, SWT.NONE | SWT.NO_SCROLL); final Composite content = new Composite(parent, SWT.NONE | SWT.NO_SCROLL);
final GridLayout gridLayout = new GridLayout(); final GridLayout gridLayout = new GridLayout();
final ProctoringGUIService proctoringGUIService = this.pageService
.getCurrentUser()
.getProctoringGUIService();
content.setLayout(gridLayout); content.setLayout(gridLayout);
final GridData headerCell = new GridData(SWT.FILL, SWT.FILL, true, true); final GridData headerCell = new GridData(SWT.FILL, SWT.FILL, true, true);
content.setLayoutData(headerCell); content.setLayoutData(headerCell);
parent.addListener(SWT.Dispose, event -> closeRoom(proctoringWindowData)); parent.addListener(SWT.Dispose, event -> closeRoom(proctoringGUIService, proctoringWindowData));
final String url = this.guiServiceInfo final String url = this.guiServiceInfo
.getExternalServerURIBuilder() .getExternalServerURIBuilder()
@ -116,19 +90,18 @@ public class ZoomProctoringView implements RemoteProctoringView {
final Composite footer = new Composite(content, SWT.NONE | SWT.NO_SCROLL); final Composite footer = new Composite(content, SWT.NONE | SWT.NO_SCROLL);
footer.setLayout(new RowLayout()); footer.setLayout(new RowLayout());
final GridData footerLayout = new GridData(SWT.CENTER, SWT.BOTTOM, true, false); final GridData footerLayout = new GridData(SWT.CENTER, SWT.BOTTOM, true, false);
footerLayout.heightHint = 40;
footer.setLayoutData(footerLayout); footer.setLayoutData(footerLayout);
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory(); final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
final Button closeAction = widgetFactory.buttonLocalized(footer, CLOSE_WINDOW_TEXT_KEY); final Button closeAction = widgetFactory.buttonLocalized(footer, CLOSE_WINDOW_TEXT_KEY);
closeAction.setLayoutData(new RowData(150, 30)); closeAction.setLayoutData(new RowData());
closeAction.addListener(SWT.Selection, event -> closeRoom(proctoringWindowData)); closeAction.addListener(SWT.Selection, event -> closeRoom(proctoringGUIService, proctoringWindowData));
final BroadcastActionState broadcastActionState = new BroadcastActionState(); final BroadcastActionState broadcastActionState = new BroadcastActionState();
final Button broadcastAudioAction = widgetFactory.buttonLocalized(footer, BROADCAST_AUDIO_ON_TEXT_KEY); final Button broadcastAudioAction = widgetFactory.buttonLocalized(footer, BROADCAST_AUDIO_ON_TEXT_KEY);
broadcastAudioAction.setLayoutData(new RowData(150, 30)); broadcastAudioAction.setLayoutData(new RowData());
broadcastAudioAction.addListener(SWT.Selection, event -> toggleBroadcastAudio( broadcastAudioAction.addListener(SWT.Selection, event -> toggleBroadcastAudio(
proctoringWindowData.examId, proctoringWindowData.examId,
proctoringWindowData.connectionData.roomName, proctoringWindowData.connectionData.roomName,
@ -136,7 +109,7 @@ public class ZoomProctoringView implements RemoteProctoringView {
broadcastAudioAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); broadcastAudioAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState);
final Button broadcastVideoAction = widgetFactory.buttonLocalized(footer, BROADCAST_VIDEO_ON_TEXT_KEY); final Button broadcastVideoAction = widgetFactory.buttonLocalized(footer, BROADCAST_VIDEO_ON_TEXT_KEY);
broadcastVideoAction.setLayoutData(new RowData(150, 30)); broadcastVideoAction.setLayoutData(new RowData());
broadcastVideoAction.addListener(SWT.Selection, event -> toggleBroadcastVideo( broadcastVideoAction.addListener(SWT.Selection, event -> toggleBroadcastVideo(
proctoringWindowData.examId, proctoringWindowData.examId,
proctoringWindowData.connectionData.roomName, proctoringWindowData.connectionData.roomName,
@ -145,7 +118,7 @@ public class ZoomProctoringView implements RemoteProctoringView {
broadcastVideoAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); broadcastVideoAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState);
final Button chatAction = widgetFactory.buttonLocalized(footer, CHAT_ON_TEXT_KEY); final Button chatAction = widgetFactory.buttonLocalized(footer, CHAT_ON_TEXT_KEY);
chatAction.setLayoutData(new RowData(150, 30)); chatAction.setLayoutData(new RowData());
chatAction.addListener(SWT.Selection, event -> toggleChat( chatAction.addListener(SWT.Selection, event -> toggleChat(
proctoringWindowData.examId, proctoringWindowData.examId,
proctoringWindowData.connectionData.roomName, proctoringWindowData.connectionData.roomName,
@ -154,96 +127,4 @@ public class ZoomProctoringView implements RemoteProctoringView {
} }
private void sendReconfigurationAttributes(
final String examId,
final String roomName,
final BroadcastActionState state) {
this.pageService.getRestService().getBuilder(SendProctoringReconfigurationAttributes.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomName)
.withFormParam(
API.EXAM_PROCTORING_ATTR_RECEIVE_AUDIO,
state.audio ? Constants.TRUE_STRING : Constants.FALSE_STRING)
.withFormParam(
API.EXAM_PROCTORING_ATTR_RECEIVE_VIDEO,
state.video ? Constants.TRUE_STRING : Constants.FALSE_STRING)
.withFormParam(
API.EXAM_PROCTORING_ATTR_ALLOW_CHAT,
state.chat ? Constants.TRUE_STRING : Constants.FALSE_STRING)
.call()
.onError(error -> log.error("Failed to send broadcast attributes to clients in room: {} cause: {}",
roomName,
error.getMessage()));
}
private void toggleBroadcastAudio(
final String examId,
final String roomName,
final Button broadcastAction) {
final BroadcastActionState state =
(BroadcastActionState) broadcastAction.getData(BroadcastActionState.KEY_NAME);
this.pageService.getPolyglotPageService().injectI18n(
broadcastAction,
state.audio ? BROADCAST_AUDIO_ON_TEXT_KEY : BROADCAST_AUDIO_OFF_TEXT_KEY);
state.audio = !state.audio;
sendReconfigurationAttributes(examId, roomName, state);
}
private void toggleBroadcastVideo(
final String examId,
final String roomName,
final Button videoAction,
final Button audioAction) {
final BroadcastActionState state =
(BroadcastActionState) videoAction.getData(BroadcastActionState.KEY_NAME);
this.pageService.getPolyglotPageService().injectI18n(
audioAction,
state.video ? BROADCAST_AUDIO_ON_TEXT_KEY : BROADCAST_AUDIO_OFF_TEXT_KEY);
this.pageService.getPolyglotPageService().injectI18n(
videoAction,
state.video ? BROADCAST_VIDEO_ON_TEXT_KEY : BROADCAST_VIDEO_OFF_TEXT_KEY);
state.video = !state.video;
state.audio = state.video;
sendReconfigurationAttributes(examId, roomName, state);
}
private void toggleChat(
final String examId,
final String roomName,
final Button broadcastAction) {
final BroadcastActionState state =
(BroadcastActionState) broadcastAction.getData(BroadcastActionState.KEY_NAME);
this.pageService.getPolyglotPageService().injectI18n(
broadcastAction,
state.chat ? CHAT_ON_TEXT_KEY : CHAT_OFF_TEXT_KEY);
state.chat = !state.chat;
sendReconfigurationAttributes(examId, roomName, state);
}
private void closeRoom(final ProctoringWindowData proctoringWindowData) {
this.pageService
.getCurrentUser()
.getProctoringGUIService()
.closeRoomWindow(proctoringWindowData.windowName);
}
static final class BroadcastActionState {
public static final String KEY_NAME = "BroadcastActionState";
boolean audio = false;
boolean video = false;
boolean chat = false;
}
} }

View file

@ -0,0 +1,456 @@
/*
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gui.service.session.proctoring;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.TreeItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.GuiServiceInfo;
import ch.ethz.seb.sebserver.gui.content.ProctorRoomConnectionsPopup;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.content.action.ActionPane;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
import ch.ethz.seb.sebserver.gui.service.page.event.ActionActivationEvent;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetCollectingRooms;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProctorRoomConnection;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.IsTownhallRoomAvailable;
@Lazy
@Component
@GuiProfile
public class MonitoringProctoringService {
private static final Logger log = LoggerFactory.getLogger(MonitoringProctoringService.class);
private static final LocTextKey EXAM_ROOM_NAME =
new LocTextKey("sebserver.monitoring.exam.proctoring.room.all.name");
// @formatter:off
static final String OPEN_ROOM_SCRIPT =
"try {\n" +
"var existingWin = window.open('', '%s', 'height=%s,width=%s,location=no,scrollbars=yes,status=no,menubar=0,toolbar=no,titlebar=no,dialog=no');\n" +
"if(existingWin.location.href === 'about:blank'){\n" +
" existingWin.location.href = '%s%s';\n" +
" existingWin.focus();\n" +
"} else {\n" +
" existingWin.focus();\n" +
"}" +
"}\n" +
"catch(err) {\n" +
" alert(\"Unexpected Javascript Error happened: \" + err);\n"+
"}";
// @formatter:on
private final PageService pageService;
private final GuiServiceInfo guiServiceInfo;
private final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup;
private final String remoteProctoringEndpoint;
public MonitoringProctoringService(
final PageService pageService,
final GuiServiceInfo guiServiceInfo,
final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup,
@Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint) {
this.pageService = pageService;
this.guiServiceInfo = guiServiceInfo;
this.proctorRoomConnectionsPopup = proctorRoomConnectionsPopup;
this.remoteProctoringEndpoint = remoteProctoringEndpoint;
}
public boolean isTownhallRoomActive(final String examModelId) {
return !BooleanUtils.toBoolean(this.pageService
.getRestService()
.getBuilder(IsTownhallRoomAvailable.class)
.withURIVariable(API.PARAM_MODEL_ID, examModelId)
.call()
.getOr(Constants.FALSE_STRING));
}
public PageAction toggleTownhallRoom(
final ProctoringGUIService proctoringGUIService,
final PageAction action) {
if (isTownhallRoomActive(action.getEntityKey().modelId)) {
closeTownhallRoom(proctoringGUIService, action);
this.pageService.firePageEvent(
new ActionActivationEvent(
true,
new Tuple<>(
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM)),
action.pageContext());
return action;
} else {
openTownhallRoom(proctoringGUIService, action);
this.pageService.firePageEvent(
new ActionActivationEvent(
true,
new Tuple<>(
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM)),
action.pageContext());
return action;
}
}
public void initCollectingRoomActions(
final PageContext pageContext,
final PageActionBuilder actionBuilder,
final ProctoringServiceSettings proctoringSettings,
final ProctoringGUIService proctoringGUIService) {
proctoringGUIService.clearCollectingRoomActionState();
updateCollectingRoomActions(
pageContext,
actionBuilder,
proctoringSettings,
proctoringGUIService);
}
public void updateCollectingRoomActions(
final PageContext pageContext,
final PageActionBuilder actionBuilder,
final ProctoringServiceSettings proctoringSettings,
final ProctoringGUIService proctoringGUIService) {
final EntityKey entityKey = pageContext.getEntityKey();
final I18nSupport i18nSupport = this.pageService.getI18nSupport();
this.pageService
.getRestService()
.getBuilder(GetCollectingRooms.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
.onError(error -> log.error("Failed to update proctoring rooms on GUI {}", error.getMessage()))
.getOr(Collections.emptyList())
.stream()
.forEach(room -> {
if (proctoringGUIService.collectingRoomActionActive(room.name)) {
// update action
final TreeItem treeItem = proctoringGUIService.getCollectingRoomActionItem(room.name);
proctoringGUIService.registerCollectingRoomAction(room, treeItem);
treeItem.setText(i18nSupport.getText(new LocTextKey(
ActionDefinition.MONITOR_EXAM_VIEW_PROCTOR_ROOM.title.name,
room.subject,
room.roomSize,
proctoringSettings.collectingRoomSize)));
processProctorRoomActionActivation(treeItem, room, pageContext);
} else {
// create new action
final PageAction action =
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_VIEW_PROCTOR_ROOM)
.withEntityKey(entityKey)
.withExec(_action -> {
final int actualRoomSize = proctoringGUIService
.getActualCollectingRoomSize(room.name);
if (actualRoomSize <= 0) {
return _action;
}
return showExamProctoringRoom(proctoringSettings, room, _action);
})
.withNameAttributes(
room.subject,
room.roomSize,
proctoringSettings.collectingRoomSize)
.noEventPropagation()
.create();
this.pageService.publishAction(
action,
_treeItem -> proctoringGUIService.registerCollectingRoomAction(
room,
_treeItem,
collectingRoom -> {
final PageContext pc = pageContext.copy()
.clearAttributes()
.withEntityKey(new EntityKey(collectingRoom.name,
EntityType.REMOTE_PROCTORING_ROOM))
.withParentEntityKey(entityKey);
this.proctorRoomConnectionsPopup.show(pc, collectingRoom.subject);
}));
processProctorRoomActionActivation(
proctoringGUIService.getCollectingRoomActionItem(room.name),
room, pageContext);
}
});
updateTownhallButton(proctoringGUIService, pageContext);
}
public PageAction openExamCollectionProctorScreen(
final PageAction action,
final ClientConnectionData connectionData) {
final String examId = action.getEntityKey().modelId;
final ProctoringServiceSettings proctoringSettings = this.pageService.getRestService()
.getBuilder(GetProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.call()
.getOrThrow();
final Optional<RemoteProctoringRoom> roomOptional =
this.pageService.getRestService().getBuilder(GetCollectingRooms.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.call()
.getOrThrow()
.stream()
.filter(room -> room.id.equals(connectionData.clientConnection.remoteProctoringRoomId))
.findFirst();
if (roomOptional.isPresent()) {
final RemoteProctoringRoom room = roomOptional.get();
final ProctoringRoomConnection proctoringConnectionData = this.pageService
.getRestService()
.getBuilder(GetProctorRoomConnection.class)
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId))
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name)
.withQueryParam(ProctoringRoomConnection.ATTR_SUBJECT, Utils.encodeFormURL_UTF_8(room.subject))
.call()
.getOrThrow();
ProctoringGUIService.setCurrentProctoringWindowData(examId, proctoringConnectionData);
final String script = String.format(
MonitoringProctoringService.OPEN_ROOM_SCRIPT,
room.name,
800,
1200,
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
this.remoteProctoringEndpoint);
RWT.getClient()
.getService(JavaScriptExecutor.class)
.execute(script);
this.pageService.getCurrentUser()
.getProctoringGUIService()
.registerProctoringWindow(examId, room.name, room.name);
}
return action;
}
public PageAction openOneToOneRoom(
final PageAction action,
final ClientConnectionData connectionData,
final ProctoringGUIService proctoringGUIService) {
final String connectionToken = connectionData.clientConnection.connectionToken;
final String examId = action.getEntityKey().modelId;
if (!proctoringGUIService.hasWindow(connectionToken)) {
final ProctoringRoomConnection proctoringConnectionData = proctoringGUIService
.openBreakOutRoom(
examId,
connectionToken,
connectionData.clientConnection.userSessionId,
Arrays.asList(connectionToken))
.onError(error -> log.error(
"Failed to open single proctoring room for connection {} {}",
connectionToken,
error.getMessage()))
.getOr(null);
ProctoringGUIService.setCurrentProctoringWindowData(
examId,
connectionToken,
proctoringConnectionData);
}
final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
final String script = String.format(
MonitoringProctoringService.OPEN_ROOM_SCRIPT,
connectionToken,
420,
640,
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
this.remoteProctoringEndpoint);
javaScriptExecutor.execute(script);
return action;
}
private PageAction openTownhallRoom(
final ProctoringGUIService proctoringGUIService,
final PageAction action) {
try {
final EntityKey examId = action.getEntityKey();
if (proctoringGUIService.getTownhallWindowName(examId.modelId) == null) {
final ProctoringRoomConnection proctoringConnectionData = proctoringGUIService
.openTownhallRoom(
examId.modelId,
this.pageService.getI18nSupport().getText(EXAM_ROOM_NAME))
.onError(error -> log.error(
"Failed to open all collecting room for exam {} {}", examId.modelId,
error.getMessage()))
.getOrThrow();
ProctoringGUIService.setCurrentProctoringWindowData(
examId.modelId,
proctoringConnectionData.roomName,
proctoringConnectionData);
}
final String windowName = proctoringGUIService.getTownhallWindowName(examId.modelId);
final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
final String script = String.format(
OPEN_ROOM_SCRIPT,
windowName,
800,
1200,
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
this.remoteProctoringEndpoint);
javaScriptExecutor.execute(script);
} catch (final Exception e) {
log.error("Failed to open popup for town-hall room: ", e);
}
return action;
}
private PageAction closeTownhallRoom(
final ProctoringGUIService proctoringGUIService,
final PageAction action) {
final String examId = action.getEntityKey().modelId;
try {
this.pageService
.getCurrentUser()
.getProctoringGUIService()
.closeRoomWindow(proctoringGUIService.getTownhallWindowName(examId));
} catch (final Exception e) {
log.error("Failed to close proctoring town-hall room for exam: {}", examId);
}
return action;
}
private void updateTownhallButton(
final ProctoringGUIService proctoringGUIService,
final PageContext pageContext) {
final EntityKey entityKey = pageContext.getEntityKey();
if (isTownhallRoomActive(entityKey.modelId)) {
final boolean townhallRoomFromThisUser = proctoringGUIService
.getTownhallWindowName(entityKey.modelId) != null;
if (townhallRoomFromThisUser) {
this.pageService.firePageEvent(
new ActionActivationEvent(
true,
new Tuple<>(
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM)),
pageContext);
} else {
this.pageService.firePageEvent(
new ActionActivationEvent(
false,
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM),
pageContext);
}
} else {
this.pageService.firePageEvent(
new ActionActivationEvent(
proctoringGUIService.getNumberOfProctoringParticipants() > 0,
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM),
pageContext);
}
}
private void processProctorRoomActionActivation(
final TreeItem treeItem,
final RemoteProctoringRoom room,
final PageContext pageContext) {
try {
final Display display = pageContext.getRoot().getDisplay();
final PageAction action = (PageAction) treeItem.getData(ActionPane.ACTION_EVENT_CALL_KEY);
final Image image = room.roomSize > 0
? action.definition.icon.getImage(display)
: action.definition.icon.getGreyedImage(display);
treeItem.setImage(image);
treeItem.setForeground(room.roomSize > 0 ? null : new Color(display, Constants.GREY_DISABLED));
} catch (final Exception e) {
log.warn("Failed to set Proctor-Room-Activation: ", e.getMessage());
}
}
private PageAction showExamProctoringRoom(
final ProctoringServiceSettings proctoringSettings,
final RemoteProctoringRoom room,
final PageAction action) {
final ProctoringRoomConnection proctoringConnectionData = this.pageService
.getRestService()
.getBuilder(GetProctorRoomConnection.class)
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId))
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name)
.withQueryParam(ProctoringRoomConnection.ATTR_SUBJECT, Utils.encodeFormURL_UTF_8(room.subject))
.call()
.getOrThrow();
ProctoringGUIService.setCurrentProctoringWindowData(
String.valueOf(proctoringSettings.examId),
proctoringConnectionData);
final String script = String.format(
OPEN_ROOM_SCRIPT,
room.name,
800,
1200,
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
this.remoteProctoringEndpoint);
RWT.getClient()
.getService(JavaScriptExecutor.class)
.execute(script);
this.pageService.getCurrentUser()
.getProctoringGUIService()
.registerProctoringWindow(String.valueOf(room.examId), room.name, room.name);
return action;
}
}

View file

@ -11,16 +11,22 @@ package ch.ethz.seb.sebserver.gui.service.session.proctoring;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer;
import org.apache.tomcat.util.buf.StringUtils; import org.apache.tomcat.util.buf.StringUtils;
import org.eclipse.rap.rwt.RWT; import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.service.JavaScriptExecutor; import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection;
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.util.Pair;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.CloseProctoringRoom; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.CloseProctoringRoom;
@ -32,14 +38,82 @@ public class ProctoringGUIService {
private static final Logger log = LoggerFactory.getLogger(ProctoringGUIService.class); private static final Logger log = LoggerFactory.getLogger(ProctoringGUIService.class);
public static final String SESSION_ATTR_PROCTORING_DATA = "SESSION_ATTR_PROCTORING_DATA"; public static final String SESSION_ATTR_PROCTORING_DATA = "SESSION_ATTR_PROCTORING_DATA";
private static final String SHOW_CONNECTION_ACTION_APPLIED = "SHOW_CONNECTION_ACTION_APPLIED";
private static final String CLOSE_ROOM_SCRIPT = "var existingWin = window.open('', '%s'); existingWin.close()"; private static final String CLOSE_ROOM_SCRIPT = "var existingWin = window.open('', '%s'); existingWin.close()";
private final RestService restService; private final RestService restService;
final Map<String, RoomData> openWindows = new HashMap<>(); final Map<String, RoomData> openWindows = new HashMap<>();
final Map<String, Pair<RemoteProctoringRoom, TreeItem>> collectingRoomsActionState;
public ProctoringGUIService(final RestService restService) { public ProctoringGUIService(final RestService restService) {
this.restService = restService; this.restService = restService;
this.collectingRoomsActionState = new HashMap<>();
}
public boolean collectingRoomActionActive(final String name) {
return this.collectingRoomsActionState.containsKey(name);
}
public void registerCollectingRoomAction(
final RemoteProctoringRoom room,
final TreeItem actionItem) {
this.collectingRoomsActionState.put(room.name, new Pair<>(room, actionItem));
}
public void registerCollectingRoomAction(
final RemoteProctoringRoom room,
final TreeItem actionItem,
final Consumer<RemoteProctoringRoom> showConnectionsPopup) {
registerCollectingRoomAction(room, actionItem);
final Tree tree = actionItem.getParent();
if (tree.getData(SHOW_CONNECTION_ACTION_APPLIED) == null) {
tree.addListener(SWT.Selection, event -> {
final TreeItem item = (TreeItem) event.item;
item.getParent().deselectAll();
if (event.button == 3) {
final RemoteProctoringRoom remoteProctoringRoom = getRemoteProctoringRoom(item);
if (remoteProctoringRoom != null && remoteProctoringRoom.roomSize > 0) {
showConnectionsPopup.accept(remoteProctoringRoom);
//this.proctorRoomConnectionsPopup.show(pc, remoteProctoringRoom.subject);
}
}
});
tree.setData(SHOW_CONNECTION_ACTION_APPLIED, true);
}
}
public TreeItem getCollectingRoomActionItem(final String roomName) {
return this.collectingRoomsActionState.get(roomName).b;
}
private RemoteProctoringRoom getRemoteProctoringRoom(final TreeItem actionItem) {
return this.collectingRoomsActionState.values()
.stream()
.filter(pair -> pair.b.equals(actionItem))
.findFirst()
.map(pair -> pair.a)
.orElse(null);
}
public int getActualCollectingRoomSize(final String roomName) {
try {
return this.collectingRoomsActionState.get(roomName).a.roomSize;
} catch (final Exception e) {
log.error("Failed to get actual collecting room size for room: {} cause: ", roomName, e.getMessage());
return -1;
}
}
public int getNumberOfProctoringParticipants() {
return this.collectingRoomsActionState.values().stream()
.reduce(0, (acc, room) -> acc + room.a.roomSize, Integer::sum);
}
public void clearCollectingRoomActionState() {
this.collectingRoomsActionState.clear();
} }
public void registerProctoringWindow( public void registerProctoringWindow(
@ -50,11 +124,12 @@ public class ProctoringGUIService {
this.openWindows.put(windowName, new RoomData(roomName, examId)); this.openWindows.put(windowName, new RoomData(roomName, examId));
} }
public boolean isTownhallOpenForUser(final String examId) { public String getTownhallWindowName(final String examId) {
return this.openWindows.values().stream() return this.openWindows.values().stream()
.filter(room -> room.isTownhall && room.examId.equals(examId)) .filter(room -> room.isTownhall && room.examId.equals(examId))
.findFirst() .findFirst()
.isPresent(); .map(room -> room.roomName)
.orElse(null);
} }
public static ProctoringWindowData getCurrentProctoringWindowData() { public static ProctoringWindowData getCurrentProctoringWindowData() {
@ -104,7 +179,6 @@ public class ProctoringGUIService {
public Result<ProctoringRoomConnection> openTownhallRoom( public Result<ProctoringRoomConnection> openTownhallRoom(
final String examId, final String examId,
final String windowName,
final String subject) { final String subject) {
return this.restService.getBuilder(OpenTownhallRoom.class) return this.restService.getBuilder(OpenTownhallRoom.class)
@ -112,7 +186,7 @@ public class ProctoringGUIService {
.withFormParam(ProctoringRoomConnection.ATTR_SUBJECT, subject) .withFormParam(ProctoringRoomConnection.ATTR_SUBJECT, subject)
.call() .call()
.map(connection -> { .map(connection -> {
this.openWindows.put(windowName, new RoomData(connection.roomName, examId, true)); this.openWindows.put(connection.roomName, new RoomData(connection.roomName, examId, true));
return connection; return connection;
}); });
} }
@ -134,6 +208,7 @@ public class ProctoringGUIService {
} }
public void clear() { public void clear() {
this.collectingRoomsActionState.clear();
if (!this.openWindows.isEmpty()) { if (!this.openWindows.isEmpty()) {
this.openWindows this.openWindows
.entrySet() .entrySet()

View file

@ -123,12 +123,16 @@ public class ZoomWindowScriptResolver implements ProctoringWindowScriptResolver
+ " console.log(res)\n" + " console.log(res)\n"
+ " },\n" + " },\n"
+ " success: function () {\n" + " success: function () {\n"
+ " console.log(\"INIT SUCCESS\")\n"
+ " ZoomMtg.join({\n" + " ZoomMtg.join({\n"
+ " signature: signature,\n" + " signature: signature,\n"
+ " apiKey: API_KEY,\n" + " apiKey: API_KEY,\n"
+ " meetingNumber: config.meetingNumber,\n" + " meetingNumber: config.meetingNumber,\n"
+ " userName: config.userName,\n" + " userName: config.userName,\n"
+ " /* passWord: meetConfig.passWord, */\n" + " passWord: config.passWord,\n"
+ " success(res) {\n"
+ " console.log(\"JOIN SUCCESS\")\n"
+ " },\n"
+ " error(res) {\n" + " error(res) {\n"
+ " console.warn(\"JOIN ERROR\")\n" + " console.warn(\"JOIN ERROR\")\n"
+ " console.log(res)\n" + " console.log(res)\n"

View file

@ -83,8 +83,8 @@ public class ZoomProctoringService implements ExamProctoringService {
"{\"alg\":\"HS256\",\"typ\":\"JWT\"}"; "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
private static final String ZOOM_API_ACCESS_TOKEN_PAYLOAD = private static final String ZOOM_API_ACCESS_TOKEN_PAYLOAD =
"{\"iss\":\"%s\",\"exp\":%s}"; "{\"iss\":\"%s\",\"exp\":%s}";
private static final String ZOOM_MEETING_ACCESS_TOKEN_PAYLOAD = // private static final String ZOOM_MEETING_ACCESS_TOKEN_PAYLOAD =
"{\"app_key\":\"%s\",\"iat\":%s,\"exp\":%s,\"tpc\":\"%s\",\"pwd\":\"%s\"}"; // "{\"app_key\":\"%s\",\"iat\":%s,\"exp\":%s,\"tpc\":\"%s\",\"pwd\":\"%s\"}";
private static final Map<String, String> SEB_API_NAME_INSTRUCTION_NAME_MAPPING = Utils.immutableMapOf(Arrays.asList( private static final Map<String, String> SEB_API_NAME_INSTRUCTION_NAME_MAPPING = Utils.immutableMapOf(Arrays.asList(
new Tuple<>( new Tuple<>(
@ -156,7 +156,7 @@ public class ZoomProctoringService implements ExamProctoringService {
final ClientCredentials credentials = new ClientCredentials( final ClientCredentials credentials = new ClientCredentials(
proctoringSettings.appKey, proctoringSettings.appKey,
proctoringSettings.appSecret); this.cryptor.decrypt(proctoringSettings.appSecret));
final ResponseEntity<String> result = this.zoomRestTemplate final ResponseEntity<String> result = this.zoomRestTemplate
.testServiceConnection( .testServiceConnection(
@ -172,6 +172,19 @@ public class ZoomProctoringService implements ExamProctoringService {
// Remove this before finish up the Zoom integration // Remove this before finish up the Zoom integration
try { try {
final ProctoringServiceSettings encryptedSettings = new ProctoringServiceSettings(
proctoringSettings.examId,
proctoringSettings.enableProctoring,
proctoringSettings.serverType,
proctoringSettings.serverURL,
proctoringSettings.collectingRoomSize,
proctoringSettings.appKey,
this.cryptor.decrypt(proctoringSettings.appSecret));
disposeServiceRoomsForExam(
proctoringSettings.examId,
encryptedSettings)
.getOrThrow();
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to dev-cleanup rooms: ", e); log.error("Failed to dev-cleanup rooms: ", e);
@ -261,7 +274,7 @@ public class ZoomProctoringService implements ExamProctoringService {
roomName, roomName,
subject, subject,
jwt, jwt,
this.cryptor.decrypt(credentials.accessToken), credentials.accessToken,
credentials.clientId, credentials.clientId,
String.valueOf(additionalZoomRoomData.meeting_id), String.valueOf(additionalZoomRoomData.meeting_id),
this.authorizationService.getUserService().getCurrentUser().getUsername()); this.authorizationService.getUserService().getCurrentUser().getUsername());
@ -307,7 +320,7 @@ public class ZoomProctoringService implements ExamProctoringService {
roomName, roomName,
subject, subject,
jwt, jwt,
this.cryptor.decrypt(credentials.accessToken), credentials.accessToken,
credentials.clientId, credentials.clientId,
String.valueOf(additionalZoomRoomData.meeting_id), String.valueOf(additionalZoomRoomData.meeting_id),
clientConnection.clientConnection.userSessionId); clientConnection.clientConnection.userSessionId);
@ -372,8 +385,13 @@ public class ZoomProctoringService implements ExamProctoringService {
roomData.getAdditionalRoomData(), roomData.getAdditionalRoomData(),
AdditionalZoomRoomData.class); AdditionalZoomRoomData.class);
final ClientCredentials credentials = new ClientCredentials(
proctoringSettings.appKey,
proctoringSettings.appSecret);
this.deleteAdHocMeeting( this.deleteAdHocMeeting(
proctoringSettings, proctoringSettings,
credentials,
roomName, roomName,
additionalZoomRoomData.user_id) additionalZoomRoomData.user_id)
.getOrThrow(); .getOrThrow();
@ -444,22 +462,19 @@ public class ZoomProctoringService implements ExamProctoringService {
return new NewRoom( return new NewRoom(
roomName, roomName,
subject, subject,
this.cryptor.encrypt(meetingResponse.meetingPwd), meetingResponse.encryptedMeetingPwd,
additionalZoomRoomDataString); additionalZoomRoomDataString);
}); });
} }
private Result<Void> deleteAdHocMeeting( private Result<Void> deleteAdHocMeeting(
final ProctoringServiceSettings proctoringSettings, final ProctoringServiceSettings proctoringSettings,
final ClientCredentials credentials,
final String meetingId, final String meetingId,
final String userId) { final String userId) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> {
final ClientCredentials credentials = new ClientCredentials(
proctoringSettings.appKey,
this.cryptor.decrypt(proctoringSettings.appSecret));
this.zoomRestTemplate.deleteMeeting(proctoringSettings.serverURL, credentials, meetingId); this.zoomRestTemplate.deleteMeeting(proctoringSettings.serverURL, credentials, meetingId);
this.zoomRestTemplate.deleteUser(proctoringSettings.serverURL, credentials, userId); this.zoomRestTemplate.deleteUser(proctoringSettings.serverURL, credentials, userId);
@ -472,13 +487,7 @@ public class ZoomProctoringService implements ExamProctoringService {
try { try {
CharSequence decryptedSecret = credentials.secret; final CharSequence decryptedSecret = this.cryptor.decrypt(credentials.secret);
try {
decryptedSecret = this.cryptor.decrypt(credentials.secret);
} catch (final Exception e) {
log.debug("Testing zoom account connection");
}
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding(); final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding();
@ -698,7 +707,10 @@ public class ZoomProctoringService implements ExamProctoringService {
return exchange(url, HttpMethod.DELETE, credentials); return exchange(url, HttpMethod.DELETE, credentials);
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to delete Zoom ad-hoc meeting: {}", meetingId, e); log.error("Failed to delete Zoom ad-hoc meeting: {} cause: {} / {}",
meetingId,
e.getMessage(),
(e.getCause() != null) ? e.getCause().getMessage() : Constants.EMPTY_NOTE);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
} }
} }
@ -719,7 +731,10 @@ public class ZoomProctoringService implements ExamProctoringService {
return exchange(url, HttpMethod.DELETE, credentials); return exchange(url, HttpMethod.DELETE, credentials);
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to delete Zoom ad-hoc user with id: {}", userId, e); log.error("Failed to delete Zoom ad-hoc user with id: {} cause: {} / {}",
userId,
e.getMessage(),
(e.getCause() != null) ? e.getCause().getMessage() : Constants.EMPTY_NOTE);
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
} }
} }
@ -741,7 +756,7 @@ public class ZoomProctoringService implements ExamProctoringService {
final HttpMethod method, final HttpMethod method,
final ClientCredentials credentials) { final ClientCredentials credentials) {
return exchange(url, HttpMethod.GET, null, getHeaders(credentials)); return exchange(url, method, null, getHeaders(credentials));
} }
private ResponseEntity<String> exchange( private ResponseEntity<String> exchange(

View file

@ -148,7 +148,7 @@ public interface ZoomRoomRequestResponse {
final String uuid; final String uuid;
final String host_id; final String host_id;
final CharSequence meetingPwd; final CharSequence meetingPwd;
final CharSequence encryptedPwd; final CharSequence encryptedMeetingPwd;
@JsonCreator @JsonCreator
public MeetingResponse( public MeetingResponse(
@ -161,7 +161,7 @@ public interface ZoomRoomRequestResponse {
@JsonProperty("uuid") final String uuid, @JsonProperty("uuid") final String uuid,
@JsonProperty("host_id") final String host_id, @JsonProperty("host_id") final String host_id,
@JsonProperty("password") final CharSequence meetingPwd, @JsonProperty("password") final CharSequence meetingPwd,
@JsonProperty("encrypted_password") final CharSequence encryptedPwd) { @JsonProperty("encrypted_password") final CharSequence encryptedMeetingPwd) {
this.id = id; this.id = id;
this.join_url = join_url; this.join_url = join_url;
@ -172,7 +172,7 @@ public interface ZoomRoomRequestResponse {
this.uuid = uuid; this.uuid = uuid;
this.host_id = host_id; this.host_id = host_id;
this.meetingPwd = meetingPwd; this.meetingPwd = meetingPwd;
this.encryptedPwd = encryptedPwd; this.encryptedMeetingPwd = encryptedMeetingPwd;
} }
} }

View file

@ -8,29 +8,20 @@
package ch.ethz.seb.sebserver.gbl.util; package ch.ethz.seb.sebserver.gbl.util;
import static org.junit.Assert.assertEquals;
import java.util.UUID;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.Ignore;
import org.junit.jupiter.api.Test;
public class ReplTest { public class ReplTest {
@Test // @Test
@Ignore // @Ignore
public void testDateFormatting() { // public void testDateFormatting() {
final String datestring = DateTime.now(DateTimeZone.UTC).toString("yyyy-MM-dd'T'HH:mm:ss"); // final String datestring = DateTime.now(DateTimeZone.UTC).toString("yyyy-MM-dd'T'HH:mm:ss");
assertEquals("", datestring); // assertEquals("", datestring);
} // }
//
@Test // @Test
@Ignore // @Ignore
public void testGenPwd() { // public void testGenPwd() {
final CharSequence meetingPwd = UUID.randomUUID().toString().subSequence(0, 9); // final CharSequence meetingPwd = UUID.randomUUID().toString().subSequence(0, 9);
assertEquals("", meetingPwd); // assertEquals("", meetingPwd);
} // }
} }