SEBSERV-204 fixes and improvements
This commit is contained in:
parent
c7952b32bc
commit
6043d752a6
18 changed files with 244 additions and 138 deletions
|
@ -36,9 +36,6 @@ public class ProctoringServiceSettings implements Entity {
|
|||
ONE_TO_ONE,
|
||||
BROADCAST,
|
||||
ENABLE_CHAT,
|
||||
WAITING_ROOM,
|
||||
SEND_REJOIN_COLLECTING_ROOM,
|
||||
RESET_BROADCAST_ON_LAVE
|
||||
}
|
||||
|
||||
public static final String ATTR_ENABLE_PROCTORING = "enableProctoring";
|
||||
|
|
|
@ -73,6 +73,12 @@ public class RAPConfiguration implements ApplicationConfiguration {
|
|||
properties.put(WebClient.FAVICON, "fav_icon");
|
||||
|
||||
application.addEntryPoint(guiEntrypoint, new RAPSpringEntryPointFactory(), properties);
|
||||
|
||||
properties.put(WebClient.PAGE_TITLE, "SEB Server Proctoring");
|
||||
properties.put(WebClient.BODY_HTML, "<big>Loading Application<big>");
|
||||
properties.put(WebClient.THEME_ID, DEFAULT_THEME_NAME);
|
||||
properties.put(WebClient.FAVICON, "fav_icon");
|
||||
|
||||
application.addEntryPoint(proctoringEntrypoint, new RAPRemoteProcotringEntryPointFactory(), properties);
|
||||
|
||||
} catch (final RuntimeException re) {
|
||||
|
|
|
@ -15,6 +15,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
|||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
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.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
|
@ -43,6 +44,8 @@ public class MonitoringExamSearchPopup {
|
|||
new LocTextKey("sebserver.monitoring.search.list.name");
|
||||
private static final LocTextKey TABLE_COLUMN_IP_ADDRESS =
|
||||
new LocTextKey("sebserver.monitoring.search.list.ip");
|
||||
private static final LocTextKey TABLE_COLUMN_STATUS =
|
||||
new LocTextKey("sebserver.monitoring.search.list.status");
|
||||
|
||||
private final PageService pageService;
|
||||
|
||||
|
@ -50,9 +53,16 @@ public class MonitoringExamSearchPopup {
|
|||
new TableFilterAttribute(CriteriaType.TEXT, ClientConnection.FILTER_ATTR_SESSION_ID);
|
||||
private final TableFilterAttribute ipFilter =
|
||||
new TableFilterAttribute(CriteriaType.TEXT, ClientConnection.FILTER_ATTR_IP_STRING);
|
||||
private final TableFilterAttribute statusFilter;
|
||||
|
||||
protected MonitoringExamSearchPopup(final PageService pageService) {
|
||||
this.pageService = pageService;
|
||||
|
||||
this.statusFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
ClientConnection.FILTER_ATTR_STATUS,
|
||||
ConnectionStatus.ACTIVE.name(),
|
||||
pageService.getResourceService()::localizedClientConnectionStatusResources);
|
||||
}
|
||||
|
||||
public void show(final PageContext pageContext) {
|
||||
|
@ -60,6 +70,7 @@ public class MonitoringExamSearchPopup {
|
|||
pageContext.getParent().getShell(),
|
||||
this.pageService.getWidgetFactory());
|
||||
dialog.setLargeDialogWidth();
|
||||
dialog.setDialogHeight(380);
|
||||
dialog.open(
|
||||
TITLE_TEXT_KEY,
|
||||
pageContext,
|
||||
|
@ -90,6 +101,12 @@ public class MonitoringExamSearchPopup {
|
|||
ClientConnection::getClientAddress)
|
||||
.withFilter(this.ipFilter))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CLIENT_CONNECTION.ATTR_STATUS,
|
||||
TABLE_COLUMN_STATUS,
|
||||
ClientConnection::getStatus)
|
||||
.withFilter(this.statusFilter))
|
||||
|
||||
.withDefaultAction(t -> actionBuilder
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
|
||||
.withParentEntityKey(examKey)
|
||||
|
|
|
@ -8,21 +8,28 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gui.content;
|
||||
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
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.impl.ModalInputDialog;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetCollectingRoomConnections;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
|
@ -31,6 +38,10 @@ public class ProctorRoomConnectionsPopup {
|
|||
|
||||
private static final LocTextKey TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.proctoring.room.connections.title");
|
||||
private static final LocTextKey EMPTY_LIST_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.search.list.empty");
|
||||
private static final LocTextKey TABLE_COLUMN_NAME =
|
||||
new LocTextKey("sebserver.monitoring.search.list.name");
|
||||
|
||||
private final PageService pageService;
|
||||
|
||||
|
@ -43,29 +54,60 @@ public class ProctorRoomConnectionsPopup {
|
|||
pageContext.getParent().getShell(),
|
||||
this.pageService.getWidgetFactory());
|
||||
dialog.setLargeDialogWidth();
|
||||
dialog.setDialogHeight(380);
|
||||
dialog.open(
|
||||
new LocTextKey(TITLE_TEXT_KEY.name, roomSubject),
|
||||
pageContext,
|
||||
this::compose);
|
||||
c -> this.compose(c, dialog));
|
||||
}
|
||||
|
||||
private void compose(final PageContext pageContext) {
|
||||
final Composite parent = pageContext.getParent();
|
||||
final Composite grid = this.pageService.getWidgetFactory().createPopupScrollComposite(parent);
|
||||
private void compose(final PageContext pageContext, final ModalInputDialog<Void> dialog) {
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
|
||||
|
||||
this.pageService.getRestService()
|
||||
final PageActionBuilder actionBuilder = this.pageService
|
||||
.pageActionBuilder(pageContext.clearEntityKeys());
|
||||
|
||||
final List<ClientConnection> connections = new ArrayList<>(this.pageService.getRestService()
|
||||
.getBuilder(GetCollectingRoomConnections.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
|
||||
.withQueryParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.forEach(connection -> {
|
||||
final Label label = new Label(grid, SWT.NONE);
|
||||
label.setText(connection.userSessionId);
|
||||
});
|
||||
.getOrThrow());
|
||||
|
||||
this.pageService.staticListTableBuilder(connections, EntityType.CLIENT_CONNECTION)
|
||||
|
||||
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
||||
.withPaging(10)
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
|
||||
TABLE_COLUMN_NAME,
|
||||
ClientConnection::getUserSessionId))
|
||||
|
||||
.withDefaultAction(t -> actionBuilder
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
|
||||
.withParentEntityKey(parentEntityKey)
|
||||
.withExec(action -> showClientConnection(action, dialog, t))
|
||||
.create())
|
||||
|
||||
.compose(pageContext);
|
||||
}
|
||||
|
||||
private PageAction showClientConnection(
|
||||
final PageAction pageAction,
|
||||
final ModalInputDialog<Void> dialog,
|
||||
final EntityTable<ClientConnection> table) {
|
||||
|
||||
final ClientConnection singleSelectedROWData = table.getSingleSelectedROWData();
|
||||
dialog.close();
|
||||
return pageAction
|
||||
.withEntityKey(new EntityKey(
|
||||
singleSelectedROWData.id,
|
||||
EntityType.CLIENT_CONNECTION))
|
||||
.withAttribute(
|
||||
Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
|
||||
singleSelectedROWData.getConnectionToken());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -567,6 +567,14 @@ public class ResourceService {
|
|||
.getText(SEB_CONNECTION_STATUS_KEY_PREFIX + name, name);
|
||||
}
|
||||
|
||||
public List<Tuple<String>> localizedClientConnectionStatusResources() {
|
||||
return Arrays.stream(ConnectionStatus.values())
|
||||
.map(type -> new Tuple<>(type.name(),
|
||||
this.i18nSupport.getText(SEB_CONNECTION_STATUS_KEY_PREFIX + type.name())))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public String localizedExamTypeName(final ExamConfigurationMap examMap) {
|
||||
if (examMap.examType == null) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
|
|
|
@ -17,6 +17,7 @@ import org.eclipse.swt.layout.RowData;
|
|||
import org.eclipse.swt.layout.RowLayout;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
@ -78,6 +79,11 @@ public class JitsiMeetProctoringView extends AbstractProctoringView {
|
|||
final GridData headerCell = new GridData(SWT.FILL, SWT.FILL, true, true);
|
||||
content.setLayoutData(headerCell);
|
||||
|
||||
final Label title = this.pageService
|
||||
.getWidgetFactory()
|
||||
.label(content, proctoringWindowData.connectionData.subject);
|
||||
title.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, true));
|
||||
|
||||
parent.addListener(SWT.Dispose, event -> closeRoom(proctoringGUIService, proctoringWindowData));
|
||||
|
||||
final String url = this.guiServiceInfo
|
||||
|
|
|
@ -49,6 +49,7 @@ public class ModalInputDialog<T> extends Dialog {
|
|||
private int dialogWidth = DEFAULT_DIALOG_WIDTH;
|
||||
private int dialogHeight = DEFAULT_DIALOG_HEIGHT;
|
||||
private int buttonWidth = DEFAULT_DIALOG_BUTTON_WIDTH;
|
||||
private boolean forceHeight = false;
|
||||
|
||||
public ModalInputDialog(
|
||||
final Shell parent,
|
||||
|
@ -75,6 +76,7 @@ public class ModalInputDialog<T> extends Dialog {
|
|||
|
||||
public ModalInputDialog<T> setDialogHeight(final int dialogHeight) {
|
||||
this.dialogHeight = dialogHeight;
|
||||
this.forceHeight = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -215,6 +217,9 @@ public class ModalInputDialog<T> extends Dialog {
|
|||
}
|
||||
|
||||
private int calcDialogHeight(final Composite main) {
|
||||
if (this.forceHeight) {
|
||||
return this.dialogHeight;
|
||||
}
|
||||
final int actualHeight = main.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
|
||||
final int displayHeight = main.getDisplay().getClientArea().height;
|
||||
final int availableHeight = (displayHeight < actualHeight + 100)
|
||||
|
|
|
@ -17,6 +17,7 @@ import org.eclipse.swt.layout.RowData;
|
|||
import org.eclipse.swt.layout.RowLayout;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
@ -78,6 +79,11 @@ public class ZoomProctoringView extends AbstractProctoringView {
|
|||
final GridData headerCell = new GridData(SWT.FILL, SWT.FILL, true, true);
|
||||
content.setLayoutData(headerCell);
|
||||
|
||||
final Label title = this.pageService
|
||||
.getWidgetFactory()
|
||||
.label(content, proctoringWindowData.connectionData.subject);
|
||||
title.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false));
|
||||
|
||||
parent.addListener(SWT.Dispose, event -> closeRoom(proctoringGUIService, proctoringWindowData));
|
||||
|
||||
final String url = this.guiServiceInfo
|
||||
|
|
|
@ -10,7 +10,6 @@ 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;
|
||||
|
@ -48,7 +47,6 @@ 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.push.ServerPushContext;
|
||||
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;
|
||||
|
@ -81,6 +79,7 @@ public class MonitoringProctoringService {
|
|||
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" +
|
||||
"existingWin.document.title = '%s';\n" +
|
||||
"if(existingWin.location.href === 'about:blank'){\n" +
|
||||
" existingWin.location.href = '%s%s';\n" +
|
||||
" existingWin.focus();\n" +
|
||||
|
@ -208,7 +207,7 @@ public class MonitoringProctoringService {
|
|||
if (actualRoomSize <= 0) {
|
||||
return _action;
|
||||
}
|
||||
return showExamProctoringRoom(proctoringSettings, room, _action);
|
||||
return openExamProctoringRoom(proctoringSettings, room, _action);
|
||||
})
|
||||
.withNameAttributes(
|
||||
room.subject,
|
||||
|
@ -239,60 +238,48 @@ public class MonitoringProctoringService {
|
|||
updateTownhallButton(proctoringGUIService, pageContext);
|
||||
}
|
||||
|
||||
public PageAction openExamCollectionProctorScreen(
|
||||
final PageAction action,
|
||||
final ClientConnectionData connectionData) {
|
||||
private PageAction openExamProctoringRoom(
|
||||
final ProctoringServiceSettings proctoringSettings,
|
||||
final RemoteProctoringRoom room,
|
||||
final PageAction action) {
|
||||
|
||||
try {
|
||||
final String examId = action.getEntityKey().modelId;
|
||||
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();
|
||||
|
||||
final ProctoringServiceSettings proctoringSettings = this.pageService.getRestService()
|
||||
.getBuilder(GetProctoringSettings.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, examId)
|
||||
ProctoringGUIService.setCurrentProctoringWindowData(
|
||||
String.valueOf(proctoringSettings.examId),
|
||||
proctoringConnectionData);
|
||||
|
||||
final String script = String.format(
|
||||
OPEN_ROOM_SCRIPT,
|
||||
room.name,
|
||||
800,
|
||||
1200,
|
||||
room.name,
|
||||
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
|
||||
this.remoteProctoringEndpoint);
|
||||
|
||||
RWT.getClient()
|
||||
.getService(JavaScriptExecutor.class)
|
||||
.execute(script);
|
||||
|
||||
final boolean newWindow = this.pageService.getCurrentUser()
|
||||
.getProctoringGUIService()
|
||||
.registerProctoringWindow(String.valueOf(room.examId), room.name, room.name);
|
||||
|
||||
if (newWindow) {
|
||||
this.pageService.getRestService()
|
||||
.getBuilder(NotifyProctoringRoomOpened.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId))
|
||||
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name)
|
||||
.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);
|
||||
}
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to open popup for collecting room: ", e);
|
||||
action.pageContext().notifyError(CLOSE_COLLECTING_ERROR, e);
|
||||
.onError(error -> log.error("Failed to notify proctoring room opened: ", error));
|
||||
}
|
||||
|
||||
return action;
|
||||
|
@ -333,6 +320,7 @@ public class MonitoringProctoringService {
|
|||
connectionToken,
|
||||
420,
|
||||
640,
|
||||
connectionData.clientConnection.userSessionId,
|
||||
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
|
||||
this.remoteProctoringEndpoint);
|
||||
javaScriptExecutor.execute(script);
|
||||
|
@ -374,6 +362,7 @@ public class MonitoringProctoringService {
|
|||
windowName,
|
||||
800,
|
||||
1200,
|
||||
"Town-Hall",
|
||||
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
|
||||
this.remoteProctoringEndpoint);
|
||||
javaScriptExecutor.execute(script);
|
||||
|
@ -460,47 +449,4 @@ public class MonitoringProctoringService {
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
this.pageService.getRestService().getBuilder(NotifyProctoringRoomOpened.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId))
|
||||
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name)
|
||||
.call()
|
||||
.onError(error -> log.error("Failed to notify proctoring room opened: ", error));
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -117,12 +117,14 @@ public class ProctoringGUIService {
|
|||
this.collectingRoomsActionState.clear();
|
||||
}
|
||||
|
||||
public void registerProctoringWindow(
|
||||
public boolean registerProctoringWindow(
|
||||
final String examId,
|
||||
final String windowName,
|
||||
final String roomName) {
|
||||
|
||||
this.openWindows.put(windowName, new RoomData(roomName, examId));
|
||||
return this.openWindows.putIfAbsent(
|
||||
windowName,
|
||||
new RoomData(roomName, examId)) == null;
|
||||
}
|
||||
|
||||
public String getTownhallWindowName(final String examId) {
|
||||
|
|
|
@ -121,9 +121,15 @@ public interface RemoteProctoringRoomDAO {
|
|||
|
||||
/** Get currently active break-out rooms for given connectionToken
|
||||
*
|
||||
* @param examId The exam identifier of the connection
|
||||
* @param connectionTokens The connection token of the client connection
|
||||
* @return Result refer to active break-out rooms or to an error when happened */
|
||||
Result<Collection<RemoteProctoringRoom>> getBreakoutRooms(String connectionToken);
|
||||
|
||||
/** Get a list of client connection tokens of connections that currently are in
|
||||
* break-out rooms, including the town-hall room
|
||||
*
|
||||
* @param examId The exam identifier of the connection
|
||||
* @return Result refer to active break-out rooms or to an error when happened */
|
||||
Result<Collection<String>> getConnectionsInBreakoutRooms(Long examId);
|
||||
|
||||
}
|
||||
|
|
|
@ -322,6 +322,24 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
|
|||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<Collection<String>> getConnectionsInBreakoutRooms(final Long examId) {
|
||||
return Result.tryCatch(() -> this.remoteProctoringRoomRecordMapper
|
||||
.selectByExample()
|
||||
.where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId))
|
||||
.and(RemoteProctoringRoomRecordDynamicSqlSupport.breakOutConnections, isNotNull())
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.flatMap(room -> Arrays.asList(
|
||||
StringUtils.split(
|
||||
room.getBreakOutConnections(),
|
||||
Constants.LIST_SEPARATOR_CHAR))
|
||||
.stream())
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private RemoteProctoringRoom toDomainModel(final RemoteProctoringRoomRecord record) {
|
||||
final String breakOutConnections = record.getBreakOutConnections();
|
||||
final Collection<String> connections = StringUtils.isNotBlank(breakOutConnections)
|
||||
|
|
|
@ -33,13 +33,20 @@ public interface ExamProctoringRoomService {
|
|||
* @return Result refer to the resulting collection of ClientConnection or to an error when happened */
|
||||
Result<Collection<ClientConnection>> getRoomConnections(Long roomId);
|
||||
|
||||
/** Get a collection of all ClientConnection that are currently connected to a specified collecting room.
|
||||
/** Get a collection of all ClientConnection that are registered to a specified collecting room.
|
||||
*
|
||||
* @param examId The exam identifier of the room
|
||||
* @param roomName The room name
|
||||
* @return Result refer to the resulting collection of ClientConnection or to an error when happened */
|
||||
Result<Collection<ClientConnection>> getCollectingRoomConnections(Long examId, String roomName);
|
||||
|
||||
/** Get a collection of all ClientConnection that are currently connected to a specified collecting room.
|
||||
*
|
||||
* @param examId The exam identifier of the room
|
||||
* @param roomName The room name
|
||||
* @return Result refer to the resulting collection of ClientConnection or to an error when happened */
|
||||
Result<Collection<ClientConnection>> getActiveCollectingRoomConnections(Long examId, String roomName);
|
||||
|
||||
/** This is internally used to update client connections that are flagged for updating
|
||||
* the proctoring room assignment.
|
||||
* This attaches or detaches client connections from or to proctoring rooms of an exam in one batch.
|
||||
|
|
|
@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -18,6 +19,7 @@ import java.util.stream.Collectors;
|
|||
import org.apache.commons.lang3.StringUtils;
|
||||
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.Service;
|
||||
|
||||
|
@ -27,7 +29,6 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
|||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
|
||||
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.session.ClientConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.InstructionType;
|
||||
|
@ -55,19 +56,22 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
|||
private final ExamAdminService examAdminService;
|
||||
private final ExamSessionService examSessionService;
|
||||
private final SEBClientInstructionService sebInstructionService;
|
||||
private final boolean sendBroadcastReset;
|
||||
|
||||
public ExamProctoringRoomServiceImpl(
|
||||
final RemoteProctoringRoomDAO remoteProctoringRoomDAO,
|
||||
final ClientConnectionDAO clientConnectionDAO,
|
||||
final ExamAdminService examAdminService,
|
||||
final ExamSessionService examSessionService,
|
||||
final SEBClientInstructionService sebInstructionService) {
|
||||
final SEBClientInstructionService sebInstructionService,
|
||||
@Value("${sebserver.webservice.proctoring.resetBroadcastOnLeav:true}") final boolean sendBroadcastReset) {
|
||||
|
||||
this.remoteProctoringRoomDAO = remoteProctoringRoomDAO;
|
||||
this.clientConnectionDAO = clientConnectionDAO;
|
||||
this.examAdminService = examAdminService;
|
||||
this.examSessionService = examSessionService;
|
||||
this.sebInstructionService = sebInstructionService;
|
||||
this.sendBroadcastReset = sendBroadcastReset;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -81,8 +85,34 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
|||
}
|
||||
|
||||
@Override
|
||||
public Result<Collection<ClientConnection>> getCollectingRoomConnections(final Long examId, final String roomName) {
|
||||
return this.clientConnectionDAO.getCollectingRoomConnections(examId, roomName);
|
||||
public Result<Collection<ClientConnection>> getCollectingRoomConnections(
|
||||
final Long examId,
|
||||
final String roomName) {
|
||||
|
||||
return this.clientConnectionDAO
|
||||
.getCollectingRoomConnections(examId, roomName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Collection<ClientConnection>> getActiveCollectingRoomConnections(
|
||||
final Long examId,
|
||||
final String roomName) {
|
||||
|
||||
final Collection<String> currentlyInBreakoutRooms = this.remoteProctoringRoomDAO
|
||||
.getConnectionsInBreakoutRooms(examId)
|
||||
.getOrElse(() -> Collections.emptyList());
|
||||
|
||||
if (currentlyInBreakoutRooms.isEmpty()) {
|
||||
return this.clientConnectionDAO
|
||||
.getCollectingRoomConnections(examId, roomName);
|
||||
} else {
|
||||
return this.clientConnectionDAO
|
||||
.getCollectingRoomConnections(examId, roomName)
|
||||
.map(connections -> connections
|
||||
.stream()
|
||||
.filter(cc -> !currentlyInBreakoutRooms.contains(cc.connectionToken))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -357,8 +387,8 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
|||
.notifyBreakOutRoomOpened(proctoringSettings, room)
|
||||
.getOrThrow();
|
||||
} else {
|
||||
final Collection<ClientConnection> clientConnections = this.clientConnectionDAO
|
||||
.getCollectingRoomConnections(examId, roomName)
|
||||
final Collection<ClientConnection> clientConnections = this
|
||||
.getActiveCollectingRoomConnections(examId, roomName)
|
||||
.getOrThrow();
|
||||
|
||||
examProctoringService
|
||||
|
@ -379,7 +409,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
|||
.getOrThrow();
|
||||
|
||||
// Send default settings to clients if fearture is enabled
|
||||
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.RESET_BROADCAST_ON_LAVE)) {
|
||||
if (this.sendBroadcastReset) {
|
||||
this.sendReconfigurationInstructions(
|
||||
examId,
|
||||
connectionTokens,
|
||||
|
@ -410,14 +440,14 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
|||
final ExamProctoringService examProctoringService) {
|
||||
|
||||
// get all connections of the room
|
||||
final List<String> connectionTokens = this.getCollectingRoomConnections(examId, roomName)
|
||||
final List<String> connectionTokens = this.getActiveCollectingRoomConnections(examId, roomName)
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.map(cc -> cc.connectionToken)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Send default settings to clients if feature is enabled
|
||||
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.RESET_BROADCAST_ON_LAVE)) {
|
||||
if (this.sendBroadcastReset) {
|
||||
this.sendReconfigurationInstructions(
|
||||
examId,
|
||||
connectionTokens,
|
||||
|
@ -460,7 +490,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
|||
final RemoteProctoringRoom remoteProctoringRoom) {
|
||||
|
||||
// Send default settings to clients if feature is enabled
|
||||
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.RESET_BROADCAST_ON_LAVE)) {
|
||||
if (this.sendBroadcastReset) {
|
||||
this.sendReconfigurationInstructions(
|
||||
examId,
|
||||
remoteProctoringRoom.breakOutConnections,
|
||||
|
@ -517,7 +547,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
|||
} else if (!room.breakOutConnections.isEmpty()) {
|
||||
connectionTokens.addAll(room.breakOutConnections);
|
||||
} else {
|
||||
connectionTokens.addAll(this.getCollectingRoomConnections(examId, roomName)
|
||||
connectionTokens.addAll(this.getActiveCollectingRoomConnections(examId, roomName)
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.map(cc -> cc.connectionToken)
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.joda.time.DateTimeZone;
|
|||
import org.joda.time.Interval;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
@ -57,7 +58,6 @@ import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker;
|
|||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
|
||||
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.ProctoringServiceSettings.ProctoringServerType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||
|
@ -126,6 +126,8 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
|
||||
private final AuthorizationService authorizationService;
|
||||
private final SEBClientInstructionService sebInstructionService;
|
||||
private final boolean enableWaitingRoom;
|
||||
private final boolean sendRejoinForCollectingRoom;
|
||||
|
||||
public ZoomProctoringService(
|
||||
final ExamSessionService examSessionService,
|
||||
|
@ -135,7 +137,9 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
final JSONMapper jsonMapper,
|
||||
final RemoteProctoringRoomDAO remoteProctoringRoomDAO,
|
||||
final AuthorizationService authorizationService,
|
||||
final SEBClientInstructionService sebInstructionService) {
|
||||
final SEBClientInstructionService sebInstructionService,
|
||||
@Value("${sebserver.webservice.proctoring.enableWaitingRoom:false}") final boolean enableWaitingRoom,
|
||||
@Value("${sebserver.webservice.proctoring.sendRejoinForCollectingRoom:true}") final boolean sendRejoinForCollectingRoom) {
|
||||
|
||||
this.examSessionService = examSessionService;
|
||||
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
|
||||
|
@ -146,6 +150,8 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
this.remoteProctoringRoomDAO = remoteProctoringRoomDAO;
|
||||
this.authorizationService = authorizationService;
|
||||
this.sebInstructionService = sebInstructionService;
|
||||
this.enableWaitingRoom = enableWaitingRoom;
|
||||
this.sendRejoinForCollectingRoom = sendRejoinForCollectingRoom;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -444,13 +450,13 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
if (!proctoringSettings.enabledFeatures.contains(ProctoringFeature.SEND_REJOIN_COLLECTING_ROOM)) {
|
||||
// do nothing if the rejoin feature is not enabled
|
||||
if (!this.sendRejoinForCollectingRoom) {
|
||||
// does nothing if the rejoin feature is not enabled
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.remoteProctoringRoomDAO.isTownhallRoomActive(proctoringSettings.examId)) {
|
||||
// do nothing if the town-hall of this exam is open. The clients will automatically join
|
||||
// does nothing if the town-hall of this exam is open. The clients will automatically join
|
||||
// the meeting once the town-hall has been closed
|
||||
return;
|
||||
}
|
||||
|
@ -522,7 +528,7 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
subject,
|
||||
duration,
|
||||
meetingPwd,
|
||||
proctoringSettings.enabledFeatures.contains(ProctoringFeature.WAITING_ROOM));
|
||||
this.enableWaitingRoom);
|
||||
|
||||
final MeetingResponse meetingResponse = this.jsonMapper.readValue(
|
||||
createMeeting.getBody(),
|
||||
|
|
|
@ -138,8 +138,7 @@ public interface ZoomRoomRequestResponse {
|
|||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static class Settings {
|
||||
@JsonProperty final boolean host_video = false;
|
||||
@JsonProperty final boolean participant_video = false;
|
||||
@JsonProperty final boolean mute_upon_entry = true;
|
||||
@JsonProperty final boolean mute_upon_entry = false;
|
||||
@JsonProperty final boolean join_before_host;
|
||||
@JsonProperty final int jbh_time = 0;
|
||||
@JsonProperty final boolean use_pmi = false;
|
||||
|
|
|
@ -66,3 +66,7 @@ sebserver.webservice.lms.openedx.api.token.request.paths=/oauth2/access_token
|
|||
sebserver.webservice.lms.moodle.api.token.request.paths=/login/token.php
|
||||
sebserver.webservice.lms.address.alias=
|
||||
|
||||
sebserver.webservice.proctoring.resetBroadcastOnLeav=true
|
||||
sebserver.webservice.proctoring.zoom.enableWaitingRoom=false
|
||||
sebserver.webservice.proctoring.zoom.sendRejoinForCollectingRoom=true
|
||||
|
||||
|
|
|
@ -1633,7 +1633,7 @@ 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.monitoring.exam.proctoring.room.all.name=Exam Room
|
||||
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
|
||||
sebserver.monitoring.exam.proctoring.action.broadcastoff.audio=End Audio Broadcast
|
||||
|
@ -1679,6 +1679,7 @@ sebserver.monitoring.search.action=Search
|
|||
sebserver.monitoring.search.list.empty=No Client Connections available
|
||||
sebserver.monitoring.search.list.name=Session or User Name
|
||||
sebserver.monitoring.search.list.ip=IP Address
|
||||
sebserver.monitoring.search.list.status=Status
|
||||
|
||||
|
||||
sebserver.monitoring.exam.connection.emptySelection=At first please select a Connection from the list
|
||||
|
|
Loading…
Reference in a new issue