SEBSERV-435 gui and monitoring implementation

This commit is contained in:
anhefti 2023-10-05 16:27:11 +02:00
parent 1cbc97ef8f
commit a04c111b59
21 changed files with 524 additions and 246 deletions

View file

@ -217,6 +217,7 @@ public final class API {
public static final String EXAM_PROCTORING_ENDPOINT = EXAM_MONITORING_ENDPOINT + "/proctoring";
public static final String EXAM_PROCTORING_COLLECTING_ROOMS_SEGMENT = "/collecting-rooms";
public static final String EXAM_SCREEN_PROCTORING_GROUPS_SEGMENT = "/screenproctoring-groups";
public static final String EXAM_PROCTORING_OPEN_BREAK_OUT_ROOM_SEGMENT = "/open";
public static final String EXAM_PROCTORING_CLOSE_ROOM_SEGMENT = "/close";
public static final String EXAM_PROCTORING_NOTIFY_OPEN_ROOM_SEGMENT = "/notify-open-room";

View file

@ -15,12 +15,14 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.model.session.ScreenProctoringGroup;
@JsonIgnoreProperties(ignoreUnknown = true)
public class MonitoringFullPageData {
public static final String ATTR_CONNECTIONS_DATA = "monitoringConnectionData";
public static final String ATTR_PROCTORING_DATA = "proctoringData";
public static final String ATTR_SCREEN_PROCTORING_DATA = "screenProctoringData";
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_ID)
public final Long examId;
@ -28,15 +30,19 @@ public class MonitoringFullPageData {
public final MonitoringSEBConnectionData monitoringConnectionData;
@JsonProperty(ATTR_PROCTORING_DATA)
public final Collection<RemoteProctoringRoom> proctoringData;
@JsonProperty(ATTR_SCREEN_PROCTORING_DATA)
final Collection<ScreenProctoringGroup> screenProctoringData;
public MonitoringFullPageData(
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_ID) final Long examId,
@JsonProperty(ATTR_CONNECTIONS_DATA) final MonitoringSEBConnectionData monitoringConnectionData,
@JsonProperty(ATTR_PROCTORING_DATA) final Collection<RemoteProctoringRoom> proctoringData) {
@JsonProperty(ATTR_PROCTORING_DATA) final Collection<RemoteProctoringRoom> proctoringData,
@JsonProperty(ATTR_SCREEN_PROCTORING_DATA) final Collection<ScreenProctoringGroup> screenProctoringData) {
this.examId = examId;
this.monitoringConnectionData = monitoringConnectionData;
this.proctoringData = proctoringData;
this.screenProctoringData = screenProctoringData;
}
public Long getExamId() {
@ -51,6 +57,10 @@ public class MonitoringFullPageData {
return this.proctoringData;
}
public Collection<ScreenProctoringGroup> getScreenProctoringData() {
return this.screenProctoringData;
}
@Override
public int hashCode() {
final int prime = 31;
@ -79,12 +89,14 @@ public class MonitoringFullPageData {
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("OverallMonitroingData [examId=");
builder.append("MonitoringFullPageData [examId=");
builder.append(this.examId);
builder.append(", monitoringConnectionData=");
builder.append(this.monitoringConnectionData);
builder.append(", proctoringData=");
builder.append(this.proctoringData);
builder.append(", screenProctoringData=");
builder.append(this.screenProctoringData);
builder.append("]");
return builder.toString();
}

View file

@ -45,6 +45,9 @@ public class RAPSpringConfig {
@Value("${sebserver.gui.remote.proctoring.api-servler.endpoint:/remote-view-servlet}")
private String remoteProctoringViewServletEndpoint;
@Value("${sebserver.gui.screen.proctoring.api-servler.endpoint:/screen-proctoring}")
private String screenProctoringViewServletEndpoint;
@Bean
public StaticApplicationPropertyResolver staticApplicationPropertyResolver() {
return new StaticApplicationPropertyResolver();
@ -83,6 +86,17 @@ public class RAPSpringConfig {
this.remoteProctoringEndpoint + this.remoteProctoringViewServletEndpoint + "/*");
}
@Bean
public ServletRegistrationBean<ScreenProctoringServlet> servletScreenProctoringRegistrationBean(
final ApplicationContext applicationContext) {
final ScreenProctoringServlet proctoringServlet = applicationContext
.getBean(ScreenProctoringServlet.class);
return new ServletRegistrationBean<>(
proctoringServlet,
this.remoteProctoringEndpoint + this.screenProctoringViewServletEndpoint + "/*");
}
@Bean
public MessageSource messageSource() {
final ReloadableResourceBundleMessageSource reloadableResourceBundleMessageSource =

View file

@ -0,0 +1,80 @@
/*
* Copyright (c) 2023 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;
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.SEBServerAuthorizationContext;
@Component
@GuiProfile
public class ScreenProctoringServlet extends HttpServlet {
private static final long serialVersionUID = 4147410676185956971L;
@Override
protected void doGet(
final HttpServletRequest req,
final HttpServletResponse resp) throws ServletException, IOException {
final HttpSession httpSession = req.getSession();
final ServletContext servletContext = httpSession.getServletContext();
final WebApplicationContext webApplicationContext = WebApplicationContextUtils
.getRequiredWebApplicationContext(servletContext);
final UserInfo user = isAuthenticated(httpSession, webApplicationContext);
// TODO https://stackoverflow.com/questions/46582/response-redirect-with-post-instead-of-get
final String hello = "Hello";
// StringBuilder sb = new StringBuilder();
// sb.Append("<html>");
// sb.AppendFormat(@"<body onload='document.forms[""form""].submit()'>");
// sb.AppendFormat("<form name='form' action='{0}' method='post'>",postbackUrl);
// sb.AppendFormat("<input type='hidden' name='id' value='{0}'>", id);
// // Other params go here
// sb.Append("</form>");
// sb.Append("</body>");
// sb.Append("</html>");
resp.getOutputStream().println(hello);
}
private UserInfo isAuthenticated(
final HttpSession httpSession,
final WebApplicationContext webApplicationContext) {
final AuthorizationContextHolder authorizationContextHolder = webApplicationContext
.getBean(AuthorizationContextHolder.class);
final SEBServerAuthorizationContext authorizationContext = authorizationContextHolder
.getAuthorizationContext(httpSession);
if (!authorizationContext.isValid() || !authorizationContext.isLoggedIn()) {
throw new RuntimeException("No authentication found");
}
return authorizationContext.getLoggedInUser().getOrThrow();
}
}

View file

@ -48,6 +48,7 @@ public enum ActionCategory {
STATE_FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.statefilter"), 40),
GROUP_FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.groupfilter"), 50),
PROCTORING(new LocTextKey("sebserver.exam.overall.action.category.proctoring"), 60),
SCREEN_PROCTORING(new LocTextKey("sebserver.exam.overall.action.category.screenproctoring"), 65),
FINISHED_EXAM_LIST(new LocTextKey("sebserver.finished.exam.list.actions"), 1);

View file

@ -1035,11 +1035,11 @@ public enum ActionDefinition {
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.CLIENT_EVENT_LIST),
MONITOR_EXAM_NEW_PROCTOR_ROOM(
new LocTextKey("sebserver.monitoring.exam.action.newroom"),
ImageIcon.VISIBILITY,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.PROCTORING),
// MONITOR_EXAM_NEW_PROCTOR_ROOM(
// new LocTextKey("sebserver.monitoring.exam.action.newroom"),
// ImageIcon.VISIBILITY,
// PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
// ActionCategory.PROCTORING),
MONITOR_EXAM_VIEW_PROCTOR_ROOM(
new LocTextKey("sebserver.monitoring.exam.action.viewroom"),
ImageIcon.PROCTOR_ROOM,
@ -1056,6 +1056,12 @@ public enum ActionDefinition {
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.PROCTORING),
MONITOR_EXAM_VIEW_SCREEN_PROCTOR_GROUP(
new LocTextKey("sebserver.monitoring.exam.action.viewgroup"),
ImageIcon.SCREEN_PROC_ON,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.SCREEN_PROCTORING),
FINISHED_EXAM_VIEW_LIST(
new LocTextKey("sebserver.finished.action.list"),
PageStateDefinitionImpl.FINISHED_EXAM_LIST),

View file

@ -76,8 +76,6 @@ public class ScreenProctoringSettingsPopup {
new LocTextKey("sebserver.exam.sps.form.accountId");
private final static LocTextKey FORM_ACCOUNT_SECRET_SPS =
new LocTextKey("sebserver.exam.sps.form.accountSecret");
private final static LocTextKey FORM_COLLECT_STRATEGY =
new LocTextKey("sebserver.exam.sps.form.collect.strategy");
private final static LocTextKey SAVE_TEXT_KEY =
new LocTextKey("sebserver.exam.sps.form.saveSettings");

View file

@ -18,12 +18,15 @@ import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
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;
@ -38,7 +41,10 @@ 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.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringFeature;
import ch.ethz.seb.sebserver.gbl.model.exam.ScreenProctoringSettings;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.model.session.ScreenProctoringGroup;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
@ -60,8 +66,11 @@ import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetScreenProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.GetClientGroups;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.indicator.GetIndicators;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetCollectingRooms;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetScreenProctoringGroups;
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.FullPageMonitoringGUIUpdate;
@ -76,6 +85,8 @@ import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService
@GuiProfile
public class MonitoringRunningExam implements TemplateComposer {
private static final Logger log = LoggerFactory.getLogger(MonitoringRunningExam.class);
private static final LocTextKey EMPTY_SELECTION_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.connection.emptySelection");
private static final LocTextKey EMPTY_ACTIVE_SELECTION_TEXT_KEY =
@ -194,8 +205,7 @@ public class MonitoringRunningExam implements TemplateComposer {
ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION,
ActionDefinition.MONITOR_EXAM_QUIT_SELECTED,
ActionDefinition.MONITOR_EXAM_LOCK_SELECTED,
ActionDefinition.MONITOR_EXAM_DISABLE_SELECTED_CONNECTION,
ActionDefinition.MONITOR_EXAM_NEW_PROCTOR_ROOM));
ActionDefinition.MONITOR_EXAM_DISABLE_SELECTED_CONNECTION));
actionBuilder
@ -275,14 +285,19 @@ public class MonitoringRunningExam implements TemplateComposer {
.call()
.getOr(null);
if (proctoringSettings != null && proctoringSettings.enableProctoring) {
guiUpdates.add(createProctoringActions(
proctoringSettings,
currentUser.getProctoringGUIService(),
pageContext,
content,
actionBuilder));
}
final ScreenProctoringSettings screenProctoringSettings = this.restService
.getBuilder(GetScreenProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
.getOr(null);
guiUpdates.add(createProctoringActions(
proctoringSettings,
screenProctoringSettings,
currentUser.getProctoringGUIService(),
pageContext,
content));
}
// finally start the page update (server push)
@ -304,12 +319,20 @@ public class MonitoringRunningExam implements TemplateComposer {
private FullPageMonitoringGUIUpdate createProctoringActions(
final ProctoringServiceSettings proctoringSettings,
final ScreenProctoringSettings screenProctoringSettings,
final ProctoringGUIService proctoringGUIService,
final PageContext pageContext,
final Composite parent,
final PageActionBuilder actionBuilder) {
final Composite parent) {
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.TOWN_HALL)) {
final PageActionBuilder actionBuilder = this.pageService
.pageActionBuilder(pageContext.clearEntityKeys());
final boolean proctoringEnabled = proctoringSettings != null &&
BooleanUtils.toBoolean(proctoringSettings.enableProctoring);
final boolean screenProctoringEnabled = screenProctoringSettings != null &&
BooleanUtils.toBoolean(proctoringSettings.enableProctoring);
if (proctoringEnabled && proctoringSettings.enabledFeatures.contains(ProctoringFeature.TOWN_HALL)) {
final EntityKey entityKey = pageContext.getEntityKey();
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM)
.withEntityKey(entityKey)
@ -338,18 +361,44 @@ public class MonitoringRunningExam implements TemplateComposer {
}
}
this.monitoringProctoringService.initCollectingRoomActions(
pageContext,
actionBuilder,
proctoringSettings,
proctoringGUIService);
proctoringGUIService.clearCollectingRoomActionState();
final EntityKey entityKey = pageContext.getEntityKey();
final Collection<RemoteProctoringRoom> collectingRooms = (proctoringEnabled)
? this.pageService
.getRestService()
.getBuilder(GetCollectingRooms.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
.onError(error -> log.error("Failed to get collecting room data:", error))
.getOr(Collections.emptyList())
: Collections.emptyList();
return monitoringStatus -> this.monitoringProctoringService.updateCollectingRoomActions(
monitoringStatus.proctoringData(),
final Collection<ScreenProctoringGroup> screenProctoringGroups = (screenProctoringEnabled)
? this.pageService
.getRestService()
.getBuilder(GetScreenProctoringGroups.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
.onError(error -> log.error("Failed to get collecting room data:", error))
.getOr(Collections.emptyList())
: Collections.emptyList();
this.monitoringProctoringService.updateCollectingRoomActions(
collectingRooms,
screenProctoringGroups,
pageContext,
actionBuilder,
proctoringSettings,
proctoringGUIService);
proctoringGUIService,
screenProctoringSettings);
return monitoringStatus -> this.monitoringProctoringService
.updateCollectingRoomActions(
monitoringStatus.proctoringData(),
monitoringStatus.screenProctoringData(),
pageContext,
proctoringSettings,
proctoringGUIService,
screenProctoringSettings);
}
private FullPageMonitoringGUIUpdate createFilterActions(
@ -430,8 +479,7 @@ public class MonitoringRunningExam implements TemplateComposer {
.noEventPropagation()
.withSwitchAction(
actionBuilder.newAction(hideActionDef)
.withExec(
hideStateViewAction(filter, clientTable, status))
.withExec(hideStateViewAction(filter, clientTable, status))
.noEventPropagation()
.withNameAttributes(numOfConnections)
.create())
@ -443,8 +491,7 @@ public class MonitoringRunningExam implements TemplateComposer {
.noEventPropagation()
.withSwitchAction(
actionBuilder.newAction(showActionDef)
.withExec(
showStateViewAction(filter, clientTable, status))
.withExec(showStateViewAction(filter, clientTable, status))
.noEventPropagation()
.withNameAttributes(numOfConnections)
.create())

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2023 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.remote.webservice.api.session;
import java.util.Collection;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.session.ScreenProctoringGroup;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetScreenProctoringGroups extends RestCall<Collection<ScreenProctoringGroup>> {
public GetScreenProctoringGroups() {
super(new TypeKey<>(
CallType.GET_LIST,
EntityType.SCREEN_PROCTORING_GROUP,
new TypeReference<Collection<ScreenProctoringGroup>>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_PROCTORING_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_SCREEN_PROCTORING_GROUPS_SEGMENT);
}
}

View file

@ -16,6 +16,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.model.session.ClientMonitoringData;
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.model.session.ScreenProctoringGroup;
import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringFullPageData;
import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringSEBConnectionData;
@ -95,4 +96,13 @@ public interface MonitoringFilter {
}
}
default Collection<ScreenProctoringGroup> screenProctoringData() {
final MonitoringFullPageData monitoringFullPageData = getMonitoringFullPageData();
if (monitoringFullPageData != null) {
return monitoringFullPageData.getScreenProctoringData();
} else {
return null;
}
}
}

View file

@ -8,9 +8,9 @@
package ch.ethz.seb.sebserver.gui.service.session.proctoring;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import org.apache.commons.io.IOUtils;
@ -18,6 +18,7 @@ import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
import org.eclipse.rap.rwt.client.service.UrlLauncher;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
@ -27,7 +28,10 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.core.type.TypeReference;
@ -39,8 +43,10 @@ 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.exam.ProctoringServiceSettings.ProctoringFeature;
import ch.ethz.seb.sebserver.gbl.model.exam.ScreenProctoringSettings;
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.session.ScreenProctoringGroup;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gbl.util.Utils;
@ -55,7 +61,6 @@ 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.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.api.session.NotifyProctoringRoomOpened;
@ -93,6 +98,9 @@ public class MonitoringProctoringService {
private final Resource openRoomScriptRes;
private final String remoteProctoringEndpoint;
@Value("${sebserver.gui.screen.proctoring.api-servler.endpoint:/screen-proctoring}")
private String screenProctoringViewServletEndpoint;
public MonitoringProctoringService(
final PageService pageService,
final GuiServiceInfo guiServiceInfo,
@ -152,87 +160,124 @@ public class MonitoringProctoringService {
return action;
}
public void initCollectingRoomActions(
final PageContext pageContext,
final PageActionBuilder actionBuilder,
final ProctoringServiceSettings proctoringSettings,
final ProctoringGUIService proctoringGUIService) {
proctoringGUIService.clearCollectingRoomActionState();
final EntityKey entityKey = pageContext.getEntityKey();
final Collection<RemoteProctoringRoom> collectingRooms = this.pageService
.getRestService()
.getBuilder(GetCollectingRooms.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
.onError(error -> log.error("Failed to get collecting room data:", error))
.getOr(Collections.emptyList());
updateCollectingRoomActions(
collectingRooms,
pageContext,
actionBuilder,
proctoringSettings,
proctoringGUIService);
}
public void updateCollectingRoomActions(
final Collection<RemoteProctoringRoom> collectingRooms,
final Collection<ScreenProctoringGroup> screenProctoringGroups,
final PageContext pageContext,
final PageActionBuilder actionBuilder,
final ProctoringServiceSettings proctoringSettings,
final ProctoringGUIService proctoringGUIService) {
final EntityKey entityKey = pageContext.getEntityKey();
final I18nSupport i18nSupport = this.pageService.getI18nSupport();
final ProctoringGUIService proctoringGUIService,
final ScreenProctoringSettings screenProctoringSettings) {
collectingRooms
.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 -> openExamProctoringRoom(
proctoringGUIService,
proctoringSettings,
room,
_action))
.withNameAttributes(
room.subject,
room.roomSize,
proctoringSettings.collectingRoomSize)
.noEventPropagation()
.create();
this.pageService.publishAction(
action,
_treeItem -> proctoringGUIService.registerCollectingRoomAction(
room,
_treeItem,
collectingRoom -> showCollectingRoomPopup(pageContext, entityKey,
collectingRoom)));
processProctorRoomActionActivation(
proctoringGUIService.getCollectingRoomActionItem(room.name),
room, pageContext);
}
});
.forEach(room -> updateProctoringAction(
pageContext,
proctoringSettings,
proctoringGUIService,
room));
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.TOWN_HALL)) {
updateTownhallButton(proctoringGUIService, pageContext);
}
if (screenProctoringGroups != null) {
screenProctoringGroups
.stream()
.forEach(group -> updateScreenProctoringAction(
pageContext,
screenProctoringSettings,
proctoringGUIService,
group));
}
}
private void updateScreenProctoringAction(
final PageContext pageContext,
final ScreenProctoringSettings settings,
final ProctoringGUIService proctoringGUIService,
final ScreenProctoringGroup group) {
final PageActionBuilder actionBuilder = this.pageService
.pageActionBuilder(pageContext.clearEntityKeys());
final EntityKey entityKey = pageContext.getEntityKey();
final I18nSupport i18nSupport = this.pageService.getI18nSupport();
final TreeItem screeProcotringGroupAction = proctoringGUIService.getScreeProcotringGroupAction(group);
if (screeProcotringGroupAction != null) {
// update action
screeProcotringGroupAction.setText(i18nSupport.getText(new LocTextKey(
ActionDefinition.MONITOR_EXAM_VIEW_SCREEN_PROCTOR_GROUP.title.name,
group.name,
group.size)));
} else {
// create action
this.pageService.publishAction(
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_VIEW_SCREEN_PROCTOR_GROUP)
.withEntityKey(entityKey)
.withExec(_action -> openScreenProctoringTab(
settings,
group,
_action))
.withNameAttributes(
group.name,
group.size)
.noEventPropagation()
.create(),
_treeItem -> proctoringGUIService.registerScreeProcotringGroupAction(group, _treeItem));
}
}
private void updateProctoringAction(
final PageContext pageContext,
final ProctoringServiceSettings proctoringSettings,
final ProctoringGUIService proctoringGUIService,
final RemoteProctoringRoom room) {
final PageActionBuilder actionBuilder = this.pageService
.pageActionBuilder(pageContext.clearEntityKeys());
final EntityKey entityKey = pageContext.getEntityKey();
final I18nSupport i18nSupport = this.pageService.getI18nSupport();
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 -> openExamProctoringRoom(
proctoringGUIService,
proctoringSettings,
room,
_action))
.withNameAttributes(
room.subject,
room.roomSize,
proctoringSettings.collectingRoomSize)
.noEventPropagation()
.create();
this.pageService.publishAction(
action,
_treeItem -> proctoringGUIService.registerCollectingRoomAction(
room,
_treeItem,
collectingRoom -> showCollectingRoomPopup(pageContext, entityKey,
collectingRoom)));
processProctorRoomActionActivation(
proctoringGUIService.getCollectingRoomActionItem(room.name),
room, pageContext);
}
}
private void showCollectingRoomPopup(
@ -263,6 +308,28 @@ public class MonitoringProctoringService {
this.proctorRoomConnectionsPopup.show(pc, collectingRoom.subject);
}
private PageAction openScreenProctoringTab(
final ScreenProctoringSettings settings,
final ScreenProctoringGroup group,
final PageAction _action) {
final String serviceRedirect = settings.spsServiceURL + "/guilogin";
final ResponseEntity<Void> redirect = new RestTemplate().exchange(
serviceRedirect,
HttpMethod.GET,
null,
Void.class);
final URI redirectLocation = redirect.getHeaders().getLocation();
final UrlLauncher launcher = RWT.getClient().getService(UrlLauncher.class);
final String url = this.remoteProctoringEndpoint
+ this.screenProctoringViewServletEndpoint
+ "?group=" + group.uuid
+ "&loc=" + redirectLocation;
launcher.openURL(url);
return _action;
}
private PageAction openExamProctoringRoom(
final ProctoringGUIService proctoringGUIService,
final ProctoringServiceSettings proctoringSettings,

View file

@ -28,6 +28,7 @@ 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.model.session.ScreenProctoringGroup;
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;
@ -40,6 +41,7 @@ public class ProctoringGUIService {
private static final Logger log = LoggerFactory.getLogger(ProctoringGUIService.class);
public static final String SESSION_ATTR_PROCTORING_DATA = "SESSION_ATTR_PROCTORING_DATA";
public static final String SESSION_ATTR_SCREEN_PROCTORING_DATA = "SESSION_ATTR_SCREEN_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()";
@ -47,10 +49,23 @@ public class ProctoringGUIService {
final Map<String, RoomData> openWindows = new HashMap<>();
final Map<String, Pair<RemoteProctoringRoom, TreeItem>> collectingRoomsActionState;
final Map<String, TreeItem> screenProctoringGroupState;
public ProctoringGUIService(final RestService restService) {
this.restService = restService;
this.collectingRoomsActionState = new HashMap<>();
this.screenProctoringGroupState = new HashMap<>();
}
public void registerScreeProcotringGroupAction(
final ScreenProctoringGroup screenProctoringGroup,
final TreeItem actionItem) {
this.screenProctoringGroupState.put(screenProctoringGroup.uuid, actionItem);
}
public TreeItem getScreeProcotringGroupAction(final ScreenProctoringGroup screenProctoringGroup) {
return this.screenProctoringGroupState.get(screenProctoringGroup.uuid);
}
public boolean collectingRoomActionActive(final String name) {

View file

@ -90,20 +90,35 @@ public interface ExamAdminService {
/** This indicates if proctoring is set and enabled for a certain exam.
*
* @param examId the exam instance
* @return Result refer to proctoring is enabled flag or to an error when happened. */
default Result<Boolean> isProctoringEnabled(final Exam exam) {
* @return proctoring is enabled flag */
default boolean isProctoringEnabled(final Exam exam) {
if (exam == null || exam.id == null) {
return Result.ofRuntimeError("Invalid Exam model");
return false;
}
if (exam.additionalAttributesIncluded()) {
return Result.tryCatch(() -> {
return BooleanUtils.toBooleanObject(
exam.getAdditionalAttribute(ProctoringServiceSettings.ATTR_ENABLE_PROCTORING));
});
return BooleanUtils.toBoolean(
exam.getAdditionalAttribute(ProctoringServiceSettings.ATTR_ENABLE_PROCTORING));
}
return isProctoringEnabled(exam.id);
return isProctoringEnabled(exam.id).getOr(false);
}
/** This indicates if screen proctoring is set and enabled for a certain exam.
*
* @param examId the exam instance
* @return screen proctoring is enabled flag */
default boolean isScreenProctoringEnabled(final Exam exam) {
if (exam == null || exam.id == null) {
return false;
}
if (exam.additionalAttributesIncluded()) {
return BooleanUtils.toBoolean(
exam.getAdditionalAttribute(ProctoringServiceSettings.ATTR_ENABLE_PROCTORING));
}
return isProctoringEnabled(exam.id).getOr(false);
}
/** Updates needed additional attributes from assigned exam configuration for the exam
@ -117,6 +132,12 @@ public interface ExamAdminService {
* @return Result refer to proctoring is enabled flag or to an error when happened. */
Result<Boolean> isProctoringEnabled(final Long examId);
/** This indicates if screen proctoring is set and enabled for a certain exam.
*
* @param examId the exam identifier
* @return Result refer to screen proctoring is enabled flag or to an error when happened. */
Result<Boolean> isScreenProctoringEnabled(final Long examId);
/** Get the exam proctoring service implementation for specified exam.
*
* @param examId the exam identifier

View file

@ -33,6 +33,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.exam.ScreenProctoringSettings;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
@ -269,6 +270,23 @@ public class ExamAdminServiceImpl implements ExamAdminService {
});
}
@Override
public Result<Boolean> isScreenProctoringEnabled(final Long examId) {
return this.additionalAttributesDAO.getAdditionalAttribute(
EntityType.EXAM,
examId,
ScreenProctoringSettings.ATTR_ENABLE_SCREEN_PROCTORING)
.map(rec -> BooleanUtils.toBoolean(rec.getValue()))
.onErrorDo(error -> {
if (log.isDebugEnabled()) {
log.warn("Failed to verify screen proctoring enabled for exam: {}, {}",
examId,
error.getMessage());
}
return false;
});
}
@Override
public Result<RemoteProctoringService> getExamProctoringService(final Long examId) {
return getProctoringServiceSettings(examId)

View file

@ -8,10 +8,13 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.session;
import java.util.Collection;
import org.springframework.context.event.EventListener;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.ScreenProctoringSettings;
import ch.ethz.seb.sebserver.gbl.model.session.ScreenProctoringGroup;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl.ExamDeletionEvent;
@ -44,6 +47,12 @@ public interface ScreenProctoringService extends SessionUpdateTask {
* @return Result refer to the given Exam or to an error when happened */
Result<Exam> applyScreenProctoingForExam(Long examId);
/** Get list of all screen proctoring collecting groups for a particular exam.
*
* @param examId The exam identifier (PK)
* @return Result refer to the list of ScreenProctoringGroup or to an error when happened */
Result<Collection<ScreenProctoringGroup>> getCollectingGroups(Long examId);
/** Gets invoked after an exam has been changed and saved.
*
* @param exam the exam that has been changed and saved */

View file

@ -78,7 +78,7 @@ public class ExamSessionControlTask implements DisposableBean {
this.examTimePrefix,
this.examTimeSuffix);
this.updateMaster();
this.webserviceInfo.updateMaster();
SEBServerInit.INIT_LOGGER.info("------>");
SEBServerInit.INIT_LOGGER.info(
@ -105,8 +105,7 @@ public class ExamSessionControlTask implements DisposableBean {
initialDelay = 5000)
private void examSessionUpdateTask() {
updateMaster();
this.webserviceInfo.updateMaster();
if (!this.webserviceInfo.isMaster()) {
return;
}
@ -120,10 +119,6 @@ public class ExamSessionControlTask implements DisposableBean {
.forEach(SessionUpdateTask::processSessionUpdateTask);
}
private void updateMaster() {
this.webserviceInfo.updateMaster();
}
@Override
public void destroy() {
// TODO try to reset master

View file

@ -199,6 +199,11 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
});
}
@Override
public Result<Collection<ScreenProctoringGroup>> getCollectingGroups(final Long examId) {
return this.screenProctoringGroupDAO.getCollectingGroups(examId);
}
@Override
public Result<Exam> updateExamOnScreenProctoingService(final Long examId) {
return this.examDAO.byPK(examId)

View file

@ -60,7 +60,6 @@ import org.springframework.web.client.RestClientResponseException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import ch.ethz.seb.sebserver.ClientHttpRequestFactoryService;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
@ -108,10 +107,6 @@ public class ZoomProctoringService implements RemoteProctoringService {
private static final String ZOOM_ACCESS_TOKEN_HEADER =
"{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
@Deprecated
private static final String ZOOM_API_ACCESS_TOKEN_PAYLOAD =
"{\"iss\":\"%s\",\"exp\":%s}";
private static final Map<String, String> SEB_API_NAME_INSTRUCTION_NAME_MAPPING = Utils.immutableMapOf(Arrays.asList(
new Tuple<>(
API.EXAM_PROCTORING_ATTR_RECEIVE_AUDIO,
@ -137,7 +132,6 @@ public class ZoomProctoringService implements RemoteProctoringService {
.stream().collect(Collectors.toMap(Tuple::get_1, Tuple::get_2)));
private final ExamSessionService examSessionService;
private final ClientHttpRequestFactoryService clientHttpRequestFactoryService;
private final Cryptor cryptor;
private final AsyncService asyncService;
private final JSONMapper jsonMapper;
@ -150,7 +144,6 @@ public class ZoomProctoringService implements RemoteProctoringService {
public ZoomProctoringService(
final ExamSessionService examSessionService,
final ClientHttpRequestFactoryService clientHttpRequestFactoryService,
final Cryptor cryptor,
final AsyncService asyncService,
final JSONMapper jsonMapper,
@ -162,7 +155,6 @@ public class ZoomProctoringService implements RemoteProctoringService {
@Value("${sebserver.webservice.proctoring.zoom.tokenexpiry.seconds:86400}") final int tokenExpirySeconds) {
this.examSessionService = examSessionService;
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
this.cryptor = cryptor;
this.asyncService = asyncService;
this.jsonMapper = jsonMapper;
@ -706,13 +698,7 @@ public class ZoomProctoringService implements RemoteProctoringService {
}
private ZoomRestTemplate createNewRestTemplate(final ProctoringServiceSettings proctoringSettings) {
if (StringUtils.isNoneBlank(proctoringSettings.accountId)) {
log.info("Create new OAuthZoomRestTemplate for settings: {}", proctoringSettings);
return new OAuthZoomRestTemplate(this, proctoringSettings);
} else {
log.warn("Create new JWTZoomRestTemplate for settings: {}", proctoringSettings);
return new JWTZoomRestTemplate(this, proctoringSettings);
}
return new OAuthZoomRestTemplate(this, proctoringSettings);
}
private static abstract class ZoomRestTemplate {
@ -1054,94 +1040,6 @@ public class ZoomProctoringService implements RemoteProctoringService {
}
}
@Deprecated
private final static class JWTZoomRestTemplate extends ZoomRestTemplate {
public JWTZoomRestTemplate(
final ZoomProctoringService zoomProctoringService,
final ProctoringServiceSettings proctoringSettings) {
super(zoomProctoringService, proctoringSettings);
}
@Override
public void initConnection() {
if (this.restTemplate == null) {
this.credentials = new ClientCredentials(
this.proctoringSettings.appKey,
this.proctoringSettings.appSecret);
this.restTemplate = new RestTemplate(this.zoomProctoringService.clientHttpRequestFactoryService
.getClientHttpRequestFactory()
.getOrThrow());
}
}
@Override
public HttpHeaders getHeaders() {
final String jwt = this.createJWTForAPIAccess(
this.credentials,
System.currentTimeMillis() + Constants.MINUTE_IN_MILLIS);
final HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set(HttpHeaders.AUTHORIZATION, "Bearer " + jwt);
httpHeaders.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
return httpHeaders;
}
private String createJWTForAPIAccess(
final ClientCredentials credentials,
final Long expTime) {
try {
final CharSequence decryptedSecret = this.zoomProctoringService.cryptor
.decrypt(credentials.secret)
.getOrThrow();
final StringBuilder builder = new StringBuilder();
final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding();
final String jwtHeaderPart = urlEncoder
.encodeToString(ZOOM_ACCESS_TOKEN_HEADER.getBytes(StandardCharsets.UTF_8));
final String jwtPayload = String.format(
ZOOM_API_ACCESS_TOKEN_PAYLOAD
.replaceAll(" ", "")
.replaceAll("\n", ""),
credentials.clientIdAsString(),
expTime);
if (log.isTraceEnabled()) {
log.trace("Zoom API Token payload: {}", jwtPayload);
}
final String jwtPayloadPart = urlEncoder
.encodeToString(jwtPayload.getBytes(StandardCharsets.UTF_8));
final String message = jwtHeaderPart + "." + jwtPayloadPart;
final Mac sha256_HMAC = Mac.getInstance(TOKEN_ENCODE_ALG);
final SecretKeySpec secret_key = new SecretKeySpec(
Utils.toByteArray(decryptedSecret),
TOKEN_ENCODE_ALG);
sha256_HMAC.init(secret_key);
final String hash = urlEncoder
.encodeToString(sha256_HMAC.doFinal(Utils.toByteArray(message)));
builder.append(message)
.append(".")
.append(hash);
return builder.toString();
} catch (final Exception e) {
throw new RuntimeException("Failed to create JWT for Zoom API access: ", e);
}
}
}
private static final class ZoomCredentialsAccessTokenProvider extends OAuth2AccessTokenSupport
implements AccessTokenProvider {

View file

@ -52,6 +52,7 @@ import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction;
import ch.ethz.seb.sebserver.gbl.model.session.ClientNotification;
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.model.session.ScreenProctoringGroup;
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringFullPageData;
@ -66,11 +67,12 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
import ch.ethz.seb.sebserver.webservice.servicelayer.institution.SecurityKeyService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.RemoteProctoringRoomService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.RemoteProctoringRoomService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientNotificationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ScreenProctoringService;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
@WebServiceProfile
@ -90,6 +92,7 @@ public class ExamMonitoringController {
private final RemoteProctoringRoomService examProcotringRoomService;
private final ExamAdminService examAdminService;
private final SecurityKeyService securityKeyService;
private final ScreenProctoringService screenProctoringService;
private final Executor executor;
public ExamMonitoringController(
@ -101,6 +104,7 @@ public class ExamMonitoringController {
final RemoteProctoringRoomService examProcotringRoomService,
final SecurityKeyService securityKeyService,
final ExamAdminService examAdminService,
final ScreenProctoringService screenProctoringService,
@Qualifier(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME) final Executor executor) {
this.sebClientConnectionService = sebClientConnectionService;
@ -112,6 +116,7 @@ public class ExamMonitoringController {
this.examProcotringRoomService = examProcotringRoomService;
this.examAdminService = examAdminService;
this.securityKeyService = securityKeyService;
this.screenProctoringService = screenProctoringService;
this.executor = executor;
}
@ -314,6 +319,9 @@ public class ExamMonitoringController {
name = API.EXAM_MONITORING_CLIENT_GROUP_FILTER,
required = false) final String hiddenClientGroups) {
// TODO respond this within another Thread-pool (Executor)
// TODO try to cache some monitoring data throughout multiple requests (for about 2 sec.)
final Exam runningExam = checkPrivileges(institutionId, examId);
final MonitoringSEBConnectionData monitoringSEBConnectionData = this.examSessionService
@ -322,25 +330,28 @@ public class ExamMonitoringController {
createMonitoringFilter(hiddenStates, hiddenClientGroups))
.getOrThrow();
MonitoringFullPageData monitoringFullPageData;
if (this.examAdminService.isProctoringEnabled(runningExam).getOr(false)) {
final Collection<RemoteProctoringRoom> proctoringData = this.examProcotringRoomService
.getProctoringCollectingRooms(examId)
.getOrThrow();
final boolean proctoringEnabled = this.examAdminService.isProctoringEnabled(runningExam);
final boolean screenProctoringEnabled = this.examAdminService.isScreenProctoringEnabled(runningExam);
monitoringFullPageData = new MonitoringFullPageData(
examId,
monitoringSEBConnectionData,
proctoringData);
final Collection<RemoteProctoringRoom> proctoringData = (proctoringEnabled)
? this.examProcotringRoomService
.getProctoringCollectingRooms(examId)
.onError(error -> log.error("Failed to get RemoteProctoringRoom for exam: {}", examId, error))
.getOr(Collections.emptyList())
: Collections.emptyList();
} else {
monitoringFullPageData = new MonitoringFullPageData(
examId,
monitoringSEBConnectionData,
Collections.emptyList());
}
final Collection<ScreenProctoringGroup> screenProctoringData = (screenProctoringEnabled)
? this.screenProctoringService
.getCollectingGroups(examId)
.onError(error -> log.error("Failed to get ScreenProctoringGroup for exam: {}", examId, error))
.getOr(Collections.emptyList())
: Collections.emptyList();
return monitoringFullPageData;
return new MonitoringFullPageData(
examId,
monitoringSEBConnectionData,
proctoringData,
screenProctoringData);
}
@RequestMapping(

View file

@ -31,12 +31,14 @@ import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.model.session.ScreenProctoringGroup;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.RemoteProctoringRoomService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.RemoteProctoringRoomService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ScreenProctoringService;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
@WebServiceProfile
@ -51,17 +53,20 @@ public class ExamProctoringController {
private final ExamAdminService examAdminService;
private final AuthorizationService authorizationService;
private final ExamSessionService examSessionService;
private final ScreenProctoringService screenProctoringService;
public ExamProctoringController(
final RemoteProctoringRoomService examProcotringRoomService,
final ExamAdminService examAdminService,
final AuthorizationService authorizationService,
final ExamSessionService examSessionService) {
final ExamSessionService examSessionService,
final ScreenProctoringService screenProctoringService) {
this.examProcotringRoomService = examProcotringRoomService;
this.examAdminService = examAdminService;
this.authorizationService = authorizationService;
this.examSessionService = examSessionService;
this.screenProctoringService = screenProctoringService;
}
/** This is called by Spring to initialize the WebDataBinder and is used here to
@ -95,6 +100,25 @@ public class ExamProctoringController {
.getOrThrow();
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_SCREEN_PROCTORING_GROUPS_SEGMENT,
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public Collection<ScreenProctoringGroup> getScreenProctoringGroupsOfExam(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId) {
checkAccess(institutionId, examId);
return this.screenProctoringService
.getCollectingGroups(examId)
.getOrThrow();
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT,
method = RequestMethod.GET,

View file

@ -2173,14 +2173,17 @@ sebserver.monitoring.exam.list.title=Running Exams
sebserver.monitoring.exam.list.actions=
sebserver.monitoring.exam.action.detail.view=Back To Monitoring
sebserver.monitoring.exam.action.list.view=Monitoring
sebserver.monitoring.exam.action.viewroom=View {0} ( {1} / {2} )
sebserver.monitoring.exam.action.viewroom=View {0} ( {1} / {2} )
sebserver.monitoring.exam.action.viewgroup=View {0} ( {1} )
sebserver.exam.monitoring.action.category.statefilter=State Filter
sebserver.exam.monitoring.action.category.groupfilter=Client Group Filter
sebserver.exam.overall.action.category.proctoring=Proctoring
sebserver.exam.overall.action.category.proctoring=Live Proctoring
sebserver.monitoring.exam.action.proctoring.openTownhall=Open Townhall
sebserver.monitoring.exam.action.proctoring.showTownhall=Show Townhall
sebserver.monitoring.exam.action.proctoring.closeTownhall=Close Townhall
sebserver.exam.overall.action.category.screenproctoring=Screen Proctoring
sebserver.monitoring.exam.proctoring.room.all.name=Townhall Room
sebserver.monitoring.exam.proctoring.action.close=Close Window
sebserver.monitoring.exam.proctoring.action.broadcaston.audio=Start Audio Broadcast