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;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
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.slf4j.Logger;
|
||||
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.exam.Exam;
|
||||
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.session.ClientConnection.ConnectionStatus;
|
||||
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.ClientNotification;
|
||||
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.profile.GuiProfile;
|
||||
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.service.ResourceService;
|
||||
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.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.GetCollectingRooms;
|
||||
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.session.ClientConnectionDetails;
|
||||
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.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
|
||||
|
@ -121,10 +113,9 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
private final I18nSupport i18nSupport;
|
||||
private final InstructionProcessor instructionProcessor;
|
||||
private final SEBClientEventDetailsPopup sebClientLogDetailsPopup;
|
||||
private final GuiServiceInfo guiServiceInfo;
|
||||
private final MonitoringProctoringService monitoringProctoringService;
|
||||
private final long pollInterval;
|
||||
private final int pageSize;
|
||||
private final String remoteProctoringEndpoint;
|
||||
|
||||
private final TableFilterAttribute typeFilter;
|
||||
private final TableFilterAttribute textFilter =
|
||||
|
@ -135,21 +126,19 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
final PageService pageService,
|
||||
final InstructionProcessor instructionProcessor,
|
||||
final SEBClientEventDetailsPopup sebClientLogDetailsPopup,
|
||||
final GuiServiceInfo guiServiceInfo,
|
||||
final MonitoringProctoringService monitoringProctoringService,
|
||||
@Value("${sebserver.gui.webservice.poll-interval:500}") final long pollInterval,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize,
|
||||
@Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint) {
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.serverPushService = serverPushService;
|
||||
this.pageService = pageService;
|
||||
this.resourceService = pageService.getResourceService();
|
||||
this.i18nSupport = this.resourceService.getI18nSupport();
|
||||
this.instructionProcessor = instructionProcessor;
|
||||
this.guiServiceInfo = guiServiceInfo;
|
||||
this.monitoringProctoringService = monitoringProctoringService;
|
||||
this.pollInterval = pollInterval;
|
||||
this.sebClientLogDetailsPopup = sebClientLogDetailsPopup;
|
||||
this.pageSize = pageSize;
|
||||
this.remoteProctoringEndpoint = remoteProctoringEndpoint;
|
||||
|
||||
this.typeFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
|
@ -380,20 +369,25 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
.getBuilder(GetProctoringSettings.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> log.error("Failed to get ProctoringSettings", error))
|
||||
.onError(error -> log.error("Failed to get ProctoringServiceSettings", error))
|
||||
.getOr(null);
|
||||
final ProctoringGUIService proctoringGUIService = currentUser.getProctoringGUIService();
|
||||
|
||||
if (procotringSettings != null && procotringSettings.enableProctoring) {
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_PROCTORING)
|
||||
.withEntityKey(parentEntityKey)
|
||||
.withExec(action -> this.openOneToOneRoom(action, connectionData))
|
||||
.withExec(action -> this.monitoringProctoringService.openOneToOneRoom(
|
||||
action,
|
||||
connectionData, proctoringGUIService))
|
||||
.noEventPropagation()
|
||||
.publish()
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_EXAM_ROOM_PROCTORING)
|
||||
.withEntityKey(parentEntityKey)
|
||||
.withExec(action -> this.openExamCollectionProctorScreen(action, connectionData))
|
||||
.withExec(action -> this.monitoringProctoringService.openExamCollectionProctorScreen(
|
||||
action,
|
||||
connectionData))
|
||||
.noEventPropagation()
|
||||
.publish();
|
||||
|
||||
|
@ -434,98 +428,6 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
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) {
|
||||
if (event == null || event.getClientTime() == null) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
|
|
|
@ -10,33 +10,22 @@ package ch.ethz.seb.sebserver.gui.content;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
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.graphics.Color;
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
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.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.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.exam.Exam;
|
||||
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.session.ClientConnection;
|
||||
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.RemoteProctoringRoom;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
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.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.ActionPane;
|
||||
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.page.PageContext;
|
||||
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.GetProctoringSettings;
|
||||
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.session.ClientConnectionTable;
|
||||
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.widget.Message;
|
||||
|
||||
|
@ -92,23 +73,6 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
|
||||
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 =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.emptySelection");
|
||||
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");
|
||||
private static final LocTextKey CONFIRM_DISABLE_SELECTED =
|
||||
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 PageService pageService;
|
||||
|
@ -128,23 +90,19 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
private final ResourceService resourceService;
|
||||
private final AsyncRunner asyncRunner;
|
||||
private final InstructionProcessor instructionProcessor;
|
||||
private final GuiServiceInfo guiServiceInfo;
|
||||
private final MonitoringExamSearchPopup monitoringExamSearchPopup;
|
||||
private final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup;
|
||||
private final MonitoringProctoringService monitoringProctoringService;
|
||||
private final long pollInterval;
|
||||
private final long proctoringRoomUpdateInterval;
|
||||
private final String remoteProctoringEndpoint;
|
||||
|
||||
protected MonitoringRunningExam(
|
||||
final ServerPushService serverPushService,
|
||||
final PageService pageService,
|
||||
final AsyncRunner asyncRunner,
|
||||
final InstructionProcessor instructionProcessor,
|
||||
final GuiServiceInfo guiServiceInfo,
|
||||
final MonitoringExamSearchPopup monitoringExamSearchPopup,
|
||||
final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup,
|
||||
final MonitoringProctoringService monitoringProctoringService,
|
||||
@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) {
|
||||
|
||||
this.serverPushService = serverPushService;
|
||||
|
@ -153,11 +111,9 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
this.resourceService = pageService.getResourceService();
|
||||
this.asyncRunner = asyncRunner;
|
||||
this.instructionProcessor = instructionProcessor;
|
||||
this.guiServiceInfo = guiServiceInfo;
|
||||
this.monitoringProctoringService = monitoringProctoringService;
|
||||
this.pollInterval = pollInterval;
|
||||
this.monitoringExamSearchPopup = monitoringExamSearchPopup;
|
||||
this.remoteProctoringEndpoint = remoteProctoringEndpoint;
|
||||
this.proctorRoomConnectionsPopup = proctorRoomConnectionsPopup;
|
||||
this.proctoringRoomUpdateInterval = proctoringRoomUpdateInterval;
|
||||
}
|
||||
|
||||
|
@ -308,11 +264,12 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(action -> this.toggleTownhallRoom(proctoringGUIService, action))
|
||||
.withExec(action -> this.monitoringProctoringService.toggleTownhallRoom(proctoringGUIService,
|
||||
action))
|
||||
.noEventPropagation()
|
||||
.publish();
|
||||
|
||||
if (isTownhallRoomActive(entityKey.modelId)) {
|
||||
if (this.monitoringProctoringService.isTownhallRoomActive(entityKey.modelId)) {
|
||||
this.pageService.firePageEvent(
|
||||
new ActionActivationEvent(
|
||||
true,
|
||||
|
@ -322,10 +279,8 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
pageContext);
|
||||
}
|
||||
|
||||
final Map<String, Pair<RemoteProctoringRoom, TreeItem>> availableRooms = new HashMap<>();
|
||||
updateRoomActions(
|
||||
this.monitoringProctoringService.initCollectingRoomActions(
|
||||
pageContext,
|
||||
availableRooms,
|
||||
actionBuilder,
|
||||
proctoringSettings,
|
||||
proctoringGUIService);
|
||||
|
@ -335,9 +290,8 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
Utils.truePredicate(),
|
||||
createServerPushUpdateErrorHandler(this.pageService, pageContext)),
|
||||
this.proctoringRoomUpdateInterval,
|
||||
context -> updateRoomActions(
|
||||
context -> this.monitoringProctoringService.updateCollectingRoomActions(
|
||||
pageContext,
|
||||
availableRooms,
|
||||
actionBuilder,
|
||||
proctoringSettings,
|
||||
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) {
|
||||
this.monitoringExamSearchPopup.show(action.pageContext());
|
||||
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(
|
||||
final ClientConnectionTable clientTable,
|
||||
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;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
|
||||
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.
|
||||
*
|
||||
* @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 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.profile.GuiProfile;
|
||||
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.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;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class JitsiMeetProctoringView implements RemoteProctoringView {
|
||||
public class JitsiMeetProctoringView extends AbstractProctoringView {
|
||||
|
||||
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(
|
||||
final PageService pageService,
|
||||
final GuiServiceInfo guiServiceInfo,
|
||||
@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) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.guiServiceInfo = guiServiceInfo;
|
||||
this.remoteProctoringEndpoint = remoteProctoringEndpoint;
|
||||
this.remoteProctoringViewServletEndpoint = remoteProctoringViewServletEndpoint;
|
||||
super(pageService, guiServiceInfo, remoteProctoringEndpoint, remoteProctoringViewServletEndpoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -118,19 +90,18 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
|
|||
final Composite footer = new Composite(content, SWT.NONE | SWT.NO_SCROLL);
|
||||
footer.setLayout(new RowLayout());
|
||||
final GridData footerLayout = new GridData(SWT.CENTER, SWT.BOTTOM, true, false);
|
||||
footerLayout.heightHint = 40;
|
||||
footer.setLayoutData(footerLayout);
|
||||
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
|
||||
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));
|
||||
|
||||
final BroadcastActionState broadcastActionState = new BroadcastActionState();
|
||||
|
||||
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(
|
||||
proctoringWindowData.examId,
|
||||
proctoringWindowData.connectionData.roomName,
|
||||
|
@ -138,7 +109,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
|
|||
broadcastAudioAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState);
|
||||
|
||||
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(
|
||||
proctoringWindowData.examId,
|
||||
proctoringWindowData.connectionData.roomName,
|
||||
|
@ -147,7 +118,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
|
|||
broadcastVideoAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState);
|
||||
|
||||
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(
|
||||
proctoringWindowData.examId,
|
||||
proctoringWindowData.connectionData.roomName,
|
||||
|
@ -155,100 +126,4 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
|
|||
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 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.profile.GuiProfile;
|
||||
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.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;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class ZoomProctoringView implements RemoteProctoringView {
|
||||
public class ZoomProctoringView extends AbstractProctoringView {
|
||||
|
||||
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(
|
||||
final PageService pageService,
|
||||
final GuiServiceInfo guiServiceInfo,
|
||||
@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) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.guiServiceInfo = guiServiceInfo;
|
||||
this.remoteProctoringEndpoint = remoteProctoringEndpoint;
|
||||
this.remoteProctoringViewServletEndpoint = remoteProctoringViewServletEndpoint;
|
||||
super(pageService, guiServiceInfo, remoteProctoringEndpoint, remoteProctoringViewServletEndpoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -82,18 +54,20 @@ public class ZoomProctoringView implements RemoteProctoringView {
|
|||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
|
||||
final ProctoringWindowData proctoringWindowData = ProctoringGUIService.getCurrentProctoringWindowData();
|
||||
|
||||
final Composite parent = pageContext.getParent();
|
||||
|
||||
final Composite content = new Composite(parent, SWT.NONE | SWT.NO_SCROLL);
|
||||
final GridLayout gridLayout = new GridLayout();
|
||||
final ProctoringGUIService proctoringGUIService = this.pageService
|
||||
.getCurrentUser()
|
||||
.getProctoringGUIService();
|
||||
|
||||
content.setLayout(gridLayout);
|
||||
final GridData headerCell = new GridData(SWT.FILL, SWT.FILL, true, true);
|
||||
content.setLayoutData(headerCell);
|
||||
|
||||
parent.addListener(SWT.Dispose, event -> closeRoom(proctoringWindowData));
|
||||
parent.addListener(SWT.Dispose, event -> closeRoom(proctoringGUIService, proctoringWindowData));
|
||||
|
||||
final String url = this.guiServiceInfo
|
||||
.getExternalServerURIBuilder()
|
||||
|
@ -116,19 +90,18 @@ public class ZoomProctoringView implements RemoteProctoringView {
|
|||
final Composite footer = new Composite(content, SWT.NONE | SWT.NO_SCROLL);
|
||||
footer.setLayout(new RowLayout());
|
||||
final GridData footerLayout = new GridData(SWT.CENTER, SWT.BOTTOM, true, false);
|
||||
footerLayout.heightHint = 40;
|
||||
footer.setLayoutData(footerLayout);
|
||||
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
|
||||
final Button closeAction = widgetFactory.buttonLocalized(footer, CLOSE_WINDOW_TEXT_KEY);
|
||||
closeAction.setLayoutData(new RowData(150, 30));
|
||||
closeAction.addListener(SWT.Selection, event -> closeRoom(proctoringWindowData));
|
||||
closeAction.setLayoutData(new RowData());
|
||||
closeAction.addListener(SWT.Selection, event -> closeRoom(proctoringGUIService, proctoringWindowData));
|
||||
|
||||
final BroadcastActionState broadcastActionState = new BroadcastActionState();
|
||||
|
||||
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(
|
||||
proctoringWindowData.examId,
|
||||
proctoringWindowData.connectionData.roomName,
|
||||
|
@ -136,7 +109,7 @@ public class ZoomProctoringView implements RemoteProctoringView {
|
|||
broadcastAudioAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState);
|
||||
|
||||
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(
|
||||
proctoringWindowData.examId,
|
||||
proctoringWindowData.connectionData.roomName,
|
||||
|
@ -145,7 +118,7 @@ public class ZoomProctoringView implements RemoteProctoringView {
|
|||
broadcastVideoAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState);
|
||||
|
||||
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(
|
||||
proctoringWindowData.examId,
|
||||
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.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.tomcat.util.buf.StringUtils;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
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.LoggerFactory;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
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.gui.service.remote.webservice.api.RestService;
|
||||
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);
|
||||
|
||||
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 final RestService restService;
|
||||
|
||||
final Map<String, RoomData> openWindows = new HashMap<>();
|
||||
final Map<String, Pair<RemoteProctoringRoom, TreeItem>> collectingRoomsActionState;
|
||||
|
||||
public ProctoringGUIService(final 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(
|
||||
|
@ -50,11 +124,12 @@ public class ProctoringGUIService {
|
|||
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()
|
||||
.filter(room -> room.isTownhall && room.examId.equals(examId))
|
||||
.findFirst()
|
||||
.isPresent();
|
||||
.map(room -> room.roomName)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public static ProctoringWindowData getCurrentProctoringWindowData() {
|
||||
|
@ -104,7 +179,6 @@ public class ProctoringGUIService {
|
|||
|
||||
public Result<ProctoringRoomConnection> openTownhallRoom(
|
||||
final String examId,
|
||||
final String windowName,
|
||||
final String subject) {
|
||||
|
||||
return this.restService.getBuilder(OpenTownhallRoom.class)
|
||||
|
@ -112,7 +186,7 @@ public class ProctoringGUIService {
|
|||
.withFormParam(ProctoringRoomConnection.ATTR_SUBJECT, subject)
|
||||
.call()
|
||||
.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;
|
||||
});
|
||||
}
|
||||
|
@ -134,6 +208,7 @@ public class ProctoringGUIService {
|
|||
}
|
||||
|
||||
public void clear() {
|
||||
this.collectingRoomsActionState.clear();
|
||||
if (!this.openWindows.isEmpty()) {
|
||||
this.openWindows
|
||||
.entrySet()
|
||||
|
|
|
@ -123,12 +123,16 @@ public class ZoomWindowScriptResolver implements ProctoringWindowScriptResolver
|
|||
+ " console.log(res)\n"
|
||||
+ " },\n"
|
||||
+ " success: function () {\n"
|
||||
+ " console.log(\"INIT SUCCESS\")\n"
|
||||
+ " ZoomMtg.join({\n"
|
||||
+ " signature: signature,\n"
|
||||
+ " apiKey: API_KEY,\n"
|
||||
+ " meetingNumber: config.meetingNumber,\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"
|
||||
+ " console.warn(\"JOIN ERROR\")\n"
|
||||
+ " console.log(res)\n"
|
||||
|
|
|
@ -83,8 +83,8 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
"{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
|
||||
private static final String ZOOM_API_ACCESS_TOKEN_PAYLOAD =
|
||||
"{\"iss\":\"%s\",\"exp\":%s}";
|
||||
private static final String ZOOM_MEETING_ACCESS_TOKEN_PAYLOAD =
|
||||
"{\"app_key\":\"%s\",\"iat\":%s,\"exp\":%s,\"tpc\":\"%s\",\"pwd\":\"%s\"}";
|
||||
// private static final String ZOOM_MEETING_ACCESS_TOKEN_PAYLOAD =
|
||||
// "{\"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(
|
||||
new Tuple<>(
|
||||
|
@ -156,7 +156,7 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
|
||||
final ClientCredentials credentials = new ClientCredentials(
|
||||
proctoringSettings.appKey,
|
||||
proctoringSettings.appSecret);
|
||||
this.cryptor.decrypt(proctoringSettings.appSecret));
|
||||
|
||||
final ResponseEntity<String> result = this.zoomRestTemplate
|
||||
.testServiceConnection(
|
||||
|
@ -172,6 +172,19 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
// Remove this before finish up the Zoom integration
|
||||
|
||||
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) {
|
||||
log.error("Failed to dev-cleanup rooms: ", e);
|
||||
|
@ -261,7 +274,7 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
roomName,
|
||||
subject,
|
||||
jwt,
|
||||
this.cryptor.decrypt(credentials.accessToken),
|
||||
credentials.accessToken,
|
||||
credentials.clientId,
|
||||
String.valueOf(additionalZoomRoomData.meeting_id),
|
||||
this.authorizationService.getUserService().getCurrentUser().getUsername());
|
||||
|
@ -307,7 +320,7 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
roomName,
|
||||
subject,
|
||||
jwt,
|
||||
this.cryptor.decrypt(credentials.accessToken),
|
||||
credentials.accessToken,
|
||||
credentials.clientId,
|
||||
String.valueOf(additionalZoomRoomData.meeting_id),
|
||||
clientConnection.clientConnection.userSessionId);
|
||||
|
@ -372,8 +385,13 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
roomData.getAdditionalRoomData(),
|
||||
AdditionalZoomRoomData.class);
|
||||
|
||||
final ClientCredentials credentials = new ClientCredentials(
|
||||
proctoringSettings.appKey,
|
||||
proctoringSettings.appSecret);
|
||||
|
||||
this.deleteAdHocMeeting(
|
||||
proctoringSettings,
|
||||
credentials,
|
||||
roomName,
|
||||
additionalZoomRoomData.user_id)
|
||||
.getOrThrow();
|
||||
|
@ -444,22 +462,19 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
return new NewRoom(
|
||||
roomName,
|
||||
subject,
|
||||
this.cryptor.encrypt(meetingResponse.meetingPwd),
|
||||
meetingResponse.encryptedMeetingPwd,
|
||||
additionalZoomRoomDataString);
|
||||
});
|
||||
}
|
||||
|
||||
private Result<Void> deleteAdHocMeeting(
|
||||
final ProctoringServiceSettings proctoringSettings,
|
||||
final ClientCredentials credentials,
|
||||
final String meetingId,
|
||||
final String userId) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
final ClientCredentials credentials = new ClientCredentials(
|
||||
proctoringSettings.appKey,
|
||||
this.cryptor.decrypt(proctoringSettings.appSecret));
|
||||
|
||||
this.zoomRestTemplate.deleteMeeting(proctoringSettings.serverURL, credentials, meetingId);
|
||||
this.zoomRestTemplate.deleteUser(proctoringSettings.serverURL, credentials, userId);
|
||||
|
||||
|
@ -472,13 +487,7 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
|
||||
try {
|
||||
|
||||
CharSequence decryptedSecret = credentials.secret;
|
||||
try {
|
||||
decryptedSecret = this.cryptor.decrypt(credentials.secret);
|
||||
} catch (final Exception e) {
|
||||
log.debug("Testing zoom account connection");
|
||||
}
|
||||
|
||||
final CharSequence decryptedSecret = this.cryptor.decrypt(credentials.secret);
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding();
|
||||
|
||||
|
@ -698,7 +707,10 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
return exchange(url, HttpMethod.DELETE, credentials);
|
||||
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
@ -719,7 +731,10 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
return exchange(url, HttpMethod.DELETE, credentials);
|
||||
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
@ -741,7 +756,7 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
final HttpMethod method,
|
||||
final ClientCredentials credentials) {
|
||||
|
||||
return exchange(url, HttpMethod.GET, null, getHeaders(credentials));
|
||||
return exchange(url, method, null, getHeaders(credentials));
|
||||
}
|
||||
|
||||
private ResponseEntity<String> exchange(
|
||||
|
|
|
@ -148,7 +148,7 @@ public interface ZoomRoomRequestResponse {
|
|||
final String uuid;
|
||||
final String host_id;
|
||||
final CharSequence meetingPwd;
|
||||
final CharSequence encryptedPwd;
|
||||
final CharSequence encryptedMeetingPwd;
|
||||
|
||||
@JsonCreator
|
||||
public MeetingResponse(
|
||||
|
@ -161,7 +161,7 @@ public interface ZoomRoomRequestResponse {
|
|||
@JsonProperty("uuid") final String uuid,
|
||||
@JsonProperty("host_id") final String host_id,
|
||||
@JsonProperty("password") final CharSequence meetingPwd,
|
||||
@JsonProperty("encrypted_password") final CharSequence encryptedPwd) {
|
||||
@JsonProperty("encrypted_password") final CharSequence encryptedMeetingPwd) {
|
||||
|
||||
this.id = id;
|
||||
this.join_url = join_url;
|
||||
|
@ -172,7 +172,7 @@ public interface ZoomRoomRequestResponse {
|
|||
this.uuid = uuid;
|
||||
this.host_id = host_id;
|
||||
this.meetingPwd = meetingPwd;
|
||||
this.encryptedPwd = encryptedPwd;
|
||||
this.encryptedMeetingPwd = encryptedMeetingPwd;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,29 +8,20 @@
|
|||
|
||||
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 {
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testDateFormatting() {
|
||||
final String datestring = DateTime.now(DateTimeZone.UTC).toString("yyyy-MM-dd'T'HH:mm:ss");
|
||||
assertEquals("", datestring);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testGenPwd() {
|
||||
final CharSequence meetingPwd = UUID.randomUUID().toString().subSequence(0, 9);
|
||||
assertEquals("", meetingPwd);
|
||||
}
|
||||
// @Test
|
||||
// @Ignore
|
||||
// public void testDateFormatting() {
|
||||
// final String datestring = DateTime.now(DateTimeZone.UTC).toString("yyyy-MM-dd'T'HH:mm:ss");
|
||||
// assertEquals("", datestring);
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// @Ignore
|
||||
// public void testGenPwd() {
|
||||
// final CharSequence meetingPwd = UUID.randomUUID().toString().subSequence(0, 9);
|
||||
// assertEquals("", meetingPwd);
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue