added more actions and connection list to proctor rooms

This commit is contained in:
anhefti 2020-10-15 16:24:53 +02:00
parent 1d7d15c02c
commit daebd9b5f7
7 changed files with 221 additions and 86 deletions

View file

@ -84,4 +84,29 @@ public class RemoteProctoringRoom {
return builder.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final RemoteProctoringRoom other = (RemoteProctoringRoom) obj;
if (this.id == null) {
if (other.id != null)
return false;
} else if (!this.id.equals(other.id))
return false;
return true;
}
}

View file

@ -23,6 +23,7 @@ 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.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -90,6 +91,7 @@ public class MonitoringRunningExam implements TemplateComposer {
"}";
// @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 =
@ -108,12 +110,14 @@ public class MonitoringRunningExam implements TemplateComposer {
private final GuiServiceInfo guiServiceInfo;
private final long pollInterval;
private final String remoteProctoringEndpoint;
private final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup;
protected MonitoringRunningExam(
final ServerPushService serverPushService,
final PageService pageService,
final InstructionProcessor instructionProcessor,
final GuiServiceInfo guiServiceInfo,
final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup,
@Value("${sebserver.gui.webservice.poll-interval:1000}") final long pollInterval,
@Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint) {
@ -124,6 +128,7 @@ public class MonitoringRunningExam implements TemplateComposer {
this.guiServiceInfo = guiServiceInfo;
this.pollInterval = pollInterval;
this.remoteProctoringEndpoint = remoteProctoringEndpoint;
this.proctorRoomConnectionsPopup = proctorRoomConnectionsPopup;
}
@Override
@ -315,10 +320,11 @@ public class MonitoringRunningExam implements TemplateComposer {
.getOr(null);
if (proctoringSettings != null && proctoringSettings.enableProctoring) {
final Map<String, TreeItem> availableRoomNames = new HashMap<>();
final Map<RemoteProctoringRoom, TreeItem> availableRooms = new HashMap<>();
updateRoomActions(
entityKey,
availableRoomNames,
pageContext,
availableRooms,
actionBuilder,
proctoringSettings);
this.serverPushService.runServerPush(
@ -326,7 +332,8 @@ public class MonitoringRunningExam implements TemplateComposer {
5000,
context -> updateRoomActions(
entityKey,
availableRoomNames,
pageContext,
availableRooms,
actionBuilder,
proctoringSettings));
}
@ -334,7 +341,8 @@ public class MonitoringRunningExam implements TemplateComposer {
private void updateRoomActions(
final EntityKey entityKey,
final Map<String, TreeItem> rooms,
final PageContext pageContext,
final Map<RemoteProctoringRoom, TreeItem> rooms,
final PageActionBuilder actionBuilder,
final ProctoringSettings proctoringSettings) {
@ -345,9 +353,9 @@ public class MonitoringRunningExam implements TemplateComposer {
.getOrThrow()
.stream()
.forEach(room -> {
if (rooms.containsKey(room.name)) {
if (rooms.containsKey(room)) {
// update action
final TreeItem treeItem = rooms.get(room.name);
final TreeItem treeItem = rooms.get(room);
treeItem.setText(i18nSupport.getText(new LocTextKey(
ActionDefinition.MONITOR_EXAM_VIEW_PROCTOR_ROOM.title.name,
room.subject,
@ -367,10 +375,36 @@ public class MonitoringRunningExam implements TemplateComposer {
.noEventPropagation()
.create();
this.pageService.publishAction(action, treeItem -> rooms.put(room.name, treeItem));
this.pageService.publishAction(action, treeItem -> rooms.put(room, treeItem));
// check and add show connections in room action adaptation
if (!rooms.isEmpty()) {
final TreeItem treeItem = rooms.values().iterator().next();
final Tree tree = treeItem.getParent();
if (tree.getData(SHOW_CONNECTION_ACTION_APPLIED) == null) {
tree.addListener(SWT.Selection, event -> {
final TreeItem item = (TreeItem) event.item;
treeItem.getParent().deselectAll();
if (event.button == 3) {
rooms.entrySet()
.stream()
.filter(e -> e.getValue().equals(item))
.findFirst()
.ifPresent(e -> {
final PageContext pc = pageContext.copy()
.clearAttributes()
.withEntityKey(new EntityKey(e.getKey().getName(),
EntityType.REMOTE_PROCTORING_ROOM))
.withParentEntityKey(entityKey);
this.proctorRoomConnectionsPopup.show(pc, e.getKey().getSubject());
});
}
});
tree.setData(SHOW_CONNECTION_ACTION_APPLIED, true);
}
}
}
});
}
private PageAction showExamProctoringRoom(

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2020 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.content;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
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.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
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.impl.ModalInputDialog;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProctorRoomConnections;
@Lazy
@Component
@GuiProfile
public class ProctorRoomConnectionsPopup {
private static final LocTextKey TITLE_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.room.connections.title");
private final PageService pageService;
protected ProctorRoomConnectionsPopup(final PageService pageService) {
this.pageService = pageService;
}
public void show(final PageContext pageContext, final String roomSubject) {
final ModalInputDialog<Void> dialog = new ModalInputDialog<>(
pageContext.getParent().getShell(),
this.pageService.getWidgetFactory());
dialog.setLargeDialogWidth();
dialog.open(
new LocTextKey(TITLE_TEXT_KEY.name, roomSubject),
pageContext,
this::compose);
}
private void compose(final PageContext pageContext) {
final Composite parent = pageContext.getParent();
final Composite grid = this.pageService.getWidgetFactory().createPopupScrollComposite(parent);
final EntityKey entityKey = pageContext.getEntityKey();
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
this.pageService.getRestService().getBuilder(GetProctorRoomConnections.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);
});
}
}

View file

@ -42,19 +42,19 @@ import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
public class JitsiMeetProctoringView implements RemoteProctoringView {
private static final LocTextKey CLOSE_WINDOW_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.action.close");
new LocTextKey("sebserver.monitoring.exam.proctoring.action.close");
private static final LocTextKey BROADCAST_AUDIO_ON_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.action.broadcaston.audio");
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcaston.audio");
private static final LocTextKey BROADCAST_AUDIO_OFF_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.action.broadcastoff.audio");
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcastoff.audio");
private static final LocTextKey BROADCAST_VIDEO_ON_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.action.broadcaston.video");
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcaston.video");
private static final LocTextKey BROADCAST_VIDEO_OFF_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.action.broadcastoff.video");
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcastoff.video");
private static final LocTextKey CHAT_ON_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.action.broadcaston.chat");
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcaston.chat");
private static final LocTextKey CHAT_OFF_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.action.broadcastoff.chat");
new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcastoff.chat");
private final PageService pageService;
private final GuiServiceInfo guiServiceInfo;
@ -132,7 +132,8 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
broadcastVideoAction.addListener(SWT.Selection, event -> toggleBroadcastVideo(
proctoringWindowData.examId,
proctoringWindowData.connectionData.roomName,
broadcastVideoAction));
broadcastVideoAction,
broadcastAudioAction));
broadcastVideoAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState);
final Button chatAction = widgetFactory.buttonLocalized(footer, CHAT_ON_TEXT_KEY);
@ -172,24 +173,37 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
state.audio = !state.audio;
}
private void toggleBroadcastVideo(final String examId, final String roomName, final Button broadcastAction) {
private void toggleBroadcastVideo(
final String examId,
final String roomName,
final Button videoAction,
final Button audioAction) {
final BroadcastActionState state =
(BroadcastActionState) broadcastAction.getData(BroadcastActionState.KEY_NAME);
(BroadcastActionState) videoAction.getData(BroadcastActionState.KEY_NAME);
if (state.video) {
this.pageService.getPolyglotPageService().injectI18n(broadcastAction, BROADCAST_VIDEO_ON_TEXT_KEY);
this.pageService.getPolyglotPageService().injectI18n(audioAction, BROADCAST_AUDIO_ON_TEXT_KEY);
this.pageService.getPolyglotPageService().injectI18n(videoAction, BROADCAST_VIDEO_ON_TEXT_KEY);
this.pageService.getRestService().getBuilder(SendProctoringBroadcastOffInstruction.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomName)
.withFormParam(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO,
Constants.TRUE_STRING)
.withFormParam(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_VIDEO,
Constants.TRUE_STRING)
.call()
.getOrThrow();
} else {
this.pageService.getPolyglotPageService().injectI18n(broadcastAction, BROADCAST_VIDEO_OFF_TEXT_KEY);
this.pageService.getPolyglotPageService().injectI18n(audioAction, BROADCAST_AUDIO_OFF_TEXT_KEY);
this.pageService.getPolyglotPageService().injectI18n(videoAction, BROADCAST_VIDEO_OFF_TEXT_KEY);
this.pageService.getRestService().getBuilder(SendProctoringBroadcastOnInstruction.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomName)
.withFormParam(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO,
Constants.TRUE_STRING)
.withFormParam(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_VIDEO,
Constants.TRUE_STRING)
@ -197,6 +211,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
.getOrThrow();
}
state.video = !state.video;
state.audio = state.video;
}
private void toggleChat(final String examId, final String roomName, final Button broadcastAction) {

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2020 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.ClientConnection;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetProctorRoomConnections extends RestCall<Collection<ClientConnection>> {
public GetProctorRoomConnections() {
super(new TypeKey<>(
CallType.GET_LIST,
EntityType.CLIENT_CONNECTION,
new TypeReference<Collection<ClientConnection>>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_MONITORING_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT
+ API.PROCTORING_ROOM_CONNECTIONS_PATH_SEGMENT);
}
}

View file

@ -360,7 +360,7 @@ public class ExamMonitoringController {
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@RequestParam(
name = Domain.REMOTE_PROCTORING_ROOM.ATTR_ID,
required = true) final Long roomId) {
required = true) final String roomName) {
this.authorization.check(
PrivilegeType.READ,
@ -370,7 +370,7 @@ public class ExamMonitoringController {
this.authorization.checkRead(
this.examSessionService.getExamDAO().byPK(examId).getOrThrow());
return this.examProcotringRoomService.getRoomConnections(roomId)
return this.examProcotringRoomService.getRoomConnections(examId, roomName)
.getOrThrow();
}
@ -639,62 +639,6 @@ public class ExamMonitoringController {
.getOrThrow();
}
// @RequestMapping(
// path = API.MODEL_ID_VAR_PATH_SEGMENT
// + API.PROCTORING_PATH_SEGMENT
// + API.PROCTORING_LEAVE_ROOM_PATH_SEGMENT,
// method = RequestMethod.POST,
// produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
// public List<SEBProctoringConnectionData> leaveProctoringRoom(
// @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,
// @RequestParam(
// name = SEBProctoringConnectionData.ATTR_ROOM_NAME,
// required = true) final String roomName,
// @RequestParam(
// name = API.EXAM_API_SEB_CONNECTION_TOKEN,
// required = true) final String connectionTokens) {
//
// this.authorization.check(
// PrivilegeType.READ,
// EntityType.EXAM,
// institutionId);
//
// final ProctoringSettings settings = this.examSessionService
// .getRunningExam(examId)
// .flatMap(this.authorization::checkRead)
// .flatMap(this.examAdminService::getExamProctoring)
// .getOrThrow();
//
// final ExamProctoringService examProctoringService = this.examAdminService
// .getExamProctoringService(settings.serverType)
// .getOrThrow();
//
// if (StringUtils.isNotBlank(connectionTokens)) {
// return (connectionTokens.contains(Constants.LIST_SEPARATOR)
// ? Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR))
// : Arrays.asList(connectionTokens))
// .stream()
// .map(connectionToken -> {
// final SEBProctoringConnectionData data = examProctoringService
// .createClientPublicRoomConnection(settings, connectionToken, roomName, roomName)
// .getOrThrow();
//
// sendLeaveInstruction(examId, connectionTokens, data)
// .onError(error -> log.error(
// "Failed to send proctoring leave instruction for common room to client: {} ",
// connectionToken, error));
// return data;
//
// }).collect(Collectors.toList());
// }
//
// return Collections.emptyList();
// }
//**** Proctoring
//***********************************************************************************************

View file

@ -1437,17 +1437,19 @@ 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.close=Close Window
sebserver.monitoring.exam.action.broadcaston.audio=Start Audio Broadcast
sebserver.monitoring.exam.action.broadcastoff.audio=End Audio Broadcast
sebserver.monitoring.exam.action.broadcaston.video=Start Video Broadcast
sebserver.monitoring.exam.action.broadcastoff.video=End Video Broadcast
sebserver.monitoring.exam.action.broadcaston.chat=Enable Chat
sebserver.monitoring.exam.action.broadcastoff.chat=Disable Chat
sebserver.monitoring.exam.action.viewroom=View {0} | {1} / {2}
sebserver.monitoring.exam.action.viewroom=View {0} ( {1} / {2} )
sebserver.exam.monitoring.action.category.filter=Filter
sebserver.exam.overall.action.category.proctoring=Proctoring
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
sebserver.monitoring.exam.proctoring.action.broadcaston.video=Start Video Broadcast
sebserver.monitoring.exam.proctoring.action.broadcastoff.video=End Video Broadcast
sebserver.monitoring.exam.proctoring.action.broadcaston.chat=Enable Chat
sebserver.monitoring.exam.proctoring.action.broadcastoff.chat=Disable Chat
sebserver.monitoring.exam.proctoring.room.connections.title=SEB Connections in {0}
sebserver.monitoring.exam.info.pleaseSelect=At first please select an Exam from the list
sebserver.monitoring.exam.list.empty=There are currently no running exams