SEBSERV-148 implementation und fixes
This commit is contained in:
parent
6ff8b703c9
commit
29bda22a40
12 changed files with 789 additions and 767 deletions
|
@ -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;
|
||||||
|
|
|
@ -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";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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. */
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue