diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java index f17b909b..e7ae3830 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java @@ -67,6 +67,7 @@ public final class ClientConnection implements GrantEntity { public static final String FILTER_ATTR_SESSION_ID = Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID; public static final String FILTER_ATTR_IP_STRING = Domain.CLIENT_CONNECTION.ATTR_CLIENT_ADDRESS; public static final String FILTER_ATTR_INFO = ATTR_INFO; + public static final String FILTER_ATTR_TOKEN_LIST = "CONNECTION_TOKENS"; @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ID) public final Long id; @@ -388,7 +389,7 @@ public final class ClientConnection implements GrantEntity { } public static Predicate getStatusPredicate(final ConnectionStatus... status) { - final EnumSet states = EnumSet.allOf(ConnectionStatus.class); + final EnumSet states = EnumSet.noneOf(ConnectionStatus.class); if (status != null) { Collections.addAll(states, status); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientInstruction.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientInstruction.java index aa111164..e9ca8908 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientInstruction.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientInstruction.java @@ -27,7 +27,8 @@ public final class ClientInstruction { SEB_QUIT, SEB_PROCTORING, SEB_RECONFIGURE_SETTINGS, - NOTIFICATION_CONFIRM + NOTIFICATION_CONFIRM, + SEB_FORCE_LOCK_SCREEN } public enum ProctoringInstructionMethod { @@ -70,6 +71,11 @@ public final class ClientInstruction { public static final String ZOOM_RECEIVE_VIDEO = "zoomReceiveVideo"; public static final String ZOOM_ALLOW_CHAT = "zoomFeatureFlagChat"; } + + public interface SEB_FORCE_LOCK_SCREEN { + public static final String MESSAGE = "message"; + public static final String IMAGE_URL = "imageURL"; + } } @JsonProperty(Domain.CLIENT_INSTRUCTION.ATTR_ID) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java index eed8b28a..451e70b9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java @@ -30,6 +30,9 @@ public enum ActionCategory { EXAM_MONITORING_NOTIFICATION_LIST(new LocTextKey( "sebserver.monitoring.exam.connection.notificationlist.actions"), 1), + EXAM_MONITORING_2(new LocTextKey( + "sebserver.monitoring.exam.connection.selected.actions"), 2), + EXAM_MONITORING_3(null, 3), CLIENT_EVENT_LIST(new LocTextKey("sebserver.monitoring.exam.connection.list.actions"), 1), LOGS_USER_ACTIVITY_LIST(new LocTextKey("sebserver.userlogs.list.actions"), 1), LOGS_SEB_CLIENT_LIST(new LocTextKey("sebserver.userlogs.list.actions"), 1), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index 5d35d579..dbd9bd0e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -769,7 +769,7 @@ public enum ActionDefinition { new LocTextKey("sebserver.monitoring.exam.connection.action.view"), ImageIcon.SHOW, PageStateDefinitionImpl.MONITORING_CLIENT_CONNECTION, - ActionCategory.CLIENT_EVENT_LIST), + ActionCategory.EXAM_MONITORING_3), MONITOR_EXAM_CLIENT_CONNECTION_QUIT( new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit"), ImageIcon.SEND_QUIT, @@ -795,12 +795,18 @@ public enum ActionDefinition { new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.selected"), ImageIcon.SEND_QUIT, PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, - ActionCategory.CLIENT_EVENT_LIST), + ActionCategory.EXAM_MONITORING_2), MONITOR_EXAM_QUIT_ALL( new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.all"), ImageIcon.SEND_QUIT, PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, ActionCategory.FORM), + MONITOR_EXAM_LOCK_SELECTED( + new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.lock.selected"), + ImageIcon.LOCK, + PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, + ActionCategory.EXAM_MONITORING_2), + MONITOR_EXAM_BACK_TO_OVERVIEW( new LocTextKey("sebserver.monitoring.exam.action.detail.view"), ImageIcon.SHOW, @@ -811,7 +817,7 @@ public enum ActionDefinition { new LocTextKey("sebserver.monitoring.exam.connection.action.disable"), ImageIcon.DISABLE, PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, - ActionCategory.CLIENT_EVENT_LIST), + ActionCategory.EXAM_MONITORING_3), MONITOR_EXAM_HIDE_REQUESTED_CONNECTION( new LocTextKey("sebserver.monitoring.exam.connection.action.hide.requested"), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ProctoringSettingsPopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ProctoringSettingsPopup.java index 9656ceca..00c4a11d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ProctoringSettingsPopup.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/exam/ProctoringSettingsPopup.java @@ -94,6 +94,7 @@ public class ProctoringSettingsPopup { .withAttribute( PageContext.AttributeKeys.FORCE_READ_ONLY, (modifyGrant) ? Constants.FALSE_STRING : Constants.TRUE_STRING); + final ModalInputDialog> dialog = new ModalInputDialog>( action.pageContext().getParent().getShell(), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java index 7c2fda35..13c4bbcb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java @@ -52,8 +52,8 @@ import ch.ethz.seb.sebserver.gui.service.push.UpdateErrorHandler; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; 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.GetIndicators; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.ConfirmPendingClientNotification; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionData; @@ -365,7 +365,7 @@ public class MonitoringClientConnection implements TemplateComposer { .withConfirm(() -> CONFIRM_QUIT) .withExec(action -> { this.instructionProcessor.propagateSEBQuitInstruction( - exam.id, + exam.getModelId(), connectionToken, pageContext); return action; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java index cb9e2174..7e5360fa 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java @@ -55,8 +55,8 @@ import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction; 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.GetIndicators; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators; 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; @@ -93,6 +93,7 @@ public class MonitoringRunningExam implements TemplateComposer { private final AsyncRunner asyncRunner; private final InstructionProcessor instructionProcessor; private final MonitoringExamSearchPopup monitoringExamSearchPopup; + private final SEBSendLockPopup sebSendLockPopup; private final MonitoringProctoringService monitoringProctoringService; private final boolean distributedSetup; private final long pollInterval; @@ -103,6 +104,7 @@ public class MonitoringRunningExam implements TemplateComposer { final AsyncRunner asyncRunner, final InstructionProcessor instructionProcessor, final MonitoringExamSearchPopup monitoringExamSearchPopup, + final SEBSendLockPopup sebSendLockPopup, final MonitoringProctoringService monitoringProctoringService, final GuiServiceInfo guiServiceInfo, @Value("${sebserver.gui.webservice.poll-interval:2000}") final long pollInterval) { @@ -117,6 +119,7 @@ public class MonitoringRunningExam implements TemplateComposer { this.pollInterval = pollInterval; this.distributedSetup = guiServiceInfo.isDistributedSetup(); this.monitoringExamSearchPopup = monitoringExamSearchPopup; + this.sebSendLockPopup = sebSendLockPopup; } @Override @@ -177,11 +180,48 @@ public class MonitoringRunningExam implements TemplateComposer { pageContext, 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)); actionBuilder + .newAction(ActionDefinition.MONITORING_EXAM_SEARCH_CONNECTIONS) + .withEntityKey(entityKey) + .withExec(this::openSearchPopup) + .noEventPropagation() + .publishIf(isExamSupporter) + + .newAction(ActionDefinition.MONITOR_EXAM_QUIT_ALL) + .withEntityKey(entityKey) + .withConfirm(() -> CONFIRM_QUIT_ALL) + .withExec(action -> this.quitSEBClients(action, clientTable, true)) + .noEventPropagation() + .publishIf(isExamSupporter) + + .newAction(ActionDefinition.MONITOR_EXAM_QUIT_SELECTED) + .withEntityKey(entityKey) + .withConfirm(() -> CONFIRM_QUIT_SELECTED) + .withSelect( + () -> this.selectionForInstruction(clientTable), + action -> this.quitSEBClients(action, clientTable, false), + EMPTY_ACTIVE_SELECTION_TEXT_KEY) + .noEventPropagation() + .publishIf(isExamSupporter, false) + + .newAction(ActionDefinition.MONITOR_EXAM_LOCK_SELECTED) + .withEntityKey(entityKey) + .withSelect( + () -> this.selectionForInstruction(clientTable), + action -> this.sebSendLockPopup.show( + action, + statesPredicate -> clientTable.getConnectionTokens( + statesPredicate, + true)), + EMPTY_ACTIVE_SELECTION_TEXT_KEY) + .noEventPropagation() + .publishIf(isExamSupporter, false) + .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION) .withParentEntityKey(entityKey) .withExec(pageAction -> { @@ -202,29 +242,6 @@ public class MonitoringRunningExam implements TemplateComposer { }) .publishIf(isExamSupporter, false) - .newAction(ActionDefinition.MONITOR_EXAM_QUIT_ALL) - .withEntityKey(entityKey) - .withConfirm(() -> CONFIRM_QUIT_ALL) - .withExec(action -> this.quitSEBClients(action, clientTable, true)) - .noEventPropagation() - .publishIf(isExamSupporter) - - .newAction(ActionDefinition.MONITORING_EXAM_SEARCH_CONNECTIONS) - .withEntityKey(entityKey) - .withExec(this::openSearchPopup) - .noEventPropagation() - .publishIf(isExamSupporter) - - .newAction(ActionDefinition.MONITOR_EXAM_QUIT_SELECTED) - .withEntityKey(entityKey) - .withConfirm(() -> CONFIRM_QUIT_SELECTED) - .withSelect( - () -> this.selectionForQuitInstruction(clientTable), - action -> this.quitSEBClients(action, clientTable, false), - EMPTY_ACTIVE_SELECTION_TEXT_KEY) - .noEventPropagation() - .publishIf(isExamSupporter, false) - .newAction(ActionDefinition.MONITOR_EXAM_DISABLE_SELECTED_CONNECTION) .withEntityKey(entityKey) .withConfirm(() -> CONFIRM_DISABLE_SELECTED) @@ -461,7 +478,7 @@ public class MonitoringRunningExam implements TemplateComposer { }; } - private Set selectionForQuitInstruction(final ClientConnectionTable clientTable) { + private Set selectionForInstruction(final ClientConnectionTable clientTable) { final Set connectionTokens = clientTable.getConnectionTokens( cc -> cc.status.clientActiveStatus, true); @@ -478,7 +495,7 @@ public class MonitoringRunningExam implements TemplateComposer { final boolean all) { this.instructionProcessor.propagateSEBQuitInstruction( - clientTable.getExam().id, + clientTable.getExam().getModelId(), statesPredicate -> clientTable.getConnectionTokens( statesPredicate, !all), @@ -489,6 +506,23 @@ public class MonitoringRunningExam implements TemplateComposer { return action; } +// private PageAction lockSEBClients( +// final PageAction action, +// final ClientConnectionTable clientTable, +// final boolean all) { +// +// this.instructionProcessor.propagateSEBLockInstruction( +// clientTable.getExam().getModelId(), +// statesPredicate -> clientTable.getConnectionTokens( +// statesPredicate, +// !all), +// action.pageContext()); +// +// clientTable.removeSelection(); +// clientTable.forceUpdateAll(); +// return action; +// } + private PageAction disableSEBClients( final PageAction action, final ClientConnectionTable clientTable, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/SEBSendLockPopup.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/SEBSendLockPopup.java new file mode 100644 index 00000000..eae97062 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/SEBSendLockPopup.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2022 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.monitoring; + +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import org.apache.tomcat.util.buf.StringUtils; +import org.eclipse.swt.widgets.Composite; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.Constants; +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.model.session.ClientInstruction; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.gui.form.FormBuilder; +import ch.ethz.seb.sebserver.gui.form.FormHandle; +import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; +import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer; +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.page.impl.PageAction; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionPage; +import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor; +import ch.ethz.seb.sebserver.gui.table.ColumnDefinition; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; + +@Lazy +@Component +@GuiProfile +public class SEBSendLockPopup { + + private static final LocTextKey TITLE_TEXT_KEY = + new LocTextKey("sebserver.monitoring.lock.title"); + private static final LocTextKey FORM_INFO_TITLE = + new LocTextKey("sebserver.monitoring.lock.form.info.title"); + private static final LocTextKey FORM_INFO = + new LocTextKey("sebserver.monitoring.lock.form.info"); + private static final LocTextKey FORM_MESSAGE = + new LocTextKey("sebserver.monitoring.lock.form.message"); + + private static final LocTextKey TABLE_TITLE = + new LocTextKey("sebserver.monitoring.lock.list.title"); + private static final LocTextKey TABLE_COLUMN_NAME = + new LocTextKey("sebserver.monitoring.lock.list.name"); + private static final LocTextKey TABLE_COLUMN_INFO = + new LocTextKey("sebserver.monitoring.lock.list.info"); + + private final PageService pageService; + private final InstructionProcessor instructionProcessor; + + protected SEBSendLockPopup( + final PageService pageService, + final InstructionProcessor instructionProcessor) { + + this.pageService = pageService; + this.instructionProcessor = instructionProcessor; + } + + public PageAction show( + final PageAction action, + final Function, Set> selectionFunction) { + + final PageContext pageContext = action.pageContext(); + final Set selection = selectionFunction.apply(ClientConnection.getStatusPredicate( + ConnectionStatus.CONNECTION_REQUESTED, + ConnectionStatus.ACTIVE)); + + if (selection == null || selection.isEmpty()) { + action + .pageContext() + .publishInfo(new LocTextKey("sebserver.monitoring.lock.noselection")); + return action; + } + + final String connectionTokens = StringUtils.join(selection, Constants.LIST_SEPARATOR_CHAR); + final boolean showList = selection.size() > 1; + final PopupComposer popupComposer = new PopupComposer( + this.pageService, + pageContext, + connectionTokens, + showList); + + final ModalInputDialog> dialog = + new ModalInputDialog>( + action.pageContext().getParent().getShell(), + this.pageService.getWidgetFactory()) + .setDialogWidth(800) + .setDialogHeight(showList ? 500 : 200); + + final Predicate> doLock = formHandle -> propagateLockInstruction( + connectionTokens, + pageContext, + formHandle); + + dialog.open( + TITLE_TEXT_KEY, + doLock, + Utils.EMPTY_EXECUTION, + popupComposer); + + return action; + } + + private final class PopupComposer implements ModalInputDialogComposer> { + + private final PageService pageService; + private final PageContext pageContext; + private final String connectionTokens; + private final boolean showList; + + protected PopupComposer( + final PageService pageService, + final PageContext pageContext, + final String connectionTokens, + final boolean showList) { + + this.pageService = pageService; + this.pageContext = pageContext; + this.connectionTokens = connectionTokens; + this.showList = showList; + } + + @Override + public Supplier> compose(final Composite parent) { + final EntityKey examKey = this.pageContext.getEntityKey(); + final RestService restService = this.pageService.getRestService(); + + final PageContext formContext = this.pageContext.copyOf(parent); + final FormHandle form = this.pageService.formBuilder(formContext) + .addField(FormBuilder.text( + "Info", + FORM_INFO_TITLE, + this.pageService.getI18nSupport().getText(FORM_INFO)) + .asArea(50) + .asHTML() + .readonly(true)) + .addField(FormBuilder.text( + ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_FORCE_LOCK_SCREEN.MESSAGE, + FORM_MESSAGE) + .asArea(50) + .asHTML()) + .build(); + + if (this.showList) { + this.pageService + .getWidgetFactory() + .labelLocalized(parent, CustomVariant.TEXT_H3, TABLE_TITLE); + + // table of selected SEB connections + this.pageService + .entityTableBuilder(restService.getRestCall(GetClientConnectionPage.class)) + .withStaticFilter( + ClientConnection.FILTER_ATTR_TOKEN_LIST, + this.connectionTokens) + .withStaticFilter( + ClientConnection.FILTER_ATTR_EXAM_ID, + examKey.modelId) + .withPaging(10) + + .withColumn(new ColumnDefinition<>( + Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID, + TABLE_COLUMN_NAME, + ClientConnection::getUserSessionId)) + + .withColumn(new ColumnDefinition<>( + ClientConnection.ATTR_INFO, + TABLE_COLUMN_INFO, + ClientConnection::getInfo)) + + .compose(formContext); + } + + return () -> form; + } + } + + private boolean propagateLockInstruction( + final String connectionTokens, + final PageContext pageContext, + final FormHandle formHandle) { + + final EntityKey examKey = pageContext.getEntityKey(); + final String lockMessage = formHandle + .getForm() + .getFieldValue(ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_FORCE_LOCK_SCREEN.MESSAGE); + + this.instructionProcessor.propagateSEBLockInstruction( + examKey.modelId, + lockMessage, + null, + connectionTokens, + pageContext); + + return true; + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/InstructionProcessor.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/InstructionProcessor.java index 473fdf15..14f67db6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/InstructionProcessor.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/InstructionProcessor.java @@ -9,6 +9,8 @@ package ch.ethz.seb.sebserver.gui.service.session; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; @@ -55,7 +57,7 @@ public class InstructionProcessor { } public void propagateSEBQuitInstruction( - final Long examId, + final String examId, final String connectionToken, final PageContext pageContext) { @@ -67,40 +69,89 @@ public class InstructionProcessor { } public void propagateSEBQuitInstruction( - final Long examId, + final String examId, final Function, Set> selectionFunction, final PageContext pageContext) { - final Set connectionTokens = selectionFunction - .apply(ClientConnection.getStatusPredicate( - ConnectionStatus.CONNECTION_REQUESTED, - ConnectionStatus.ACTIVE)); + try { + final Set connectionTokens = selectionFunction + .apply(ClientConnection.getStatusPredicate( + ConnectionStatus.CONNECTION_REQUESTED, + ConnectionStatus.ACTIVE)); - if (connectionTokens.isEmpty()) { - log.warn("Empty selection"); - return; + if (connectionTokens.isEmpty()) { + return; + } + + if (log.isDebugEnabled()) { + log.debug("Propagate SEB quit instruction for exam: {} and connections: {}", + examId, + connectionTokens); + } + + final ClientInstruction clientInstruction = new ClientInstruction( + null, + Long.valueOf(examId), + InstructionType.SEB_QUIT, + StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR), + null); + + processInstruction(() -> this.restService.getBuilder(PropagateInstruction.class) + .withURIVariable(API.PARAM_PARENT_MODEL_ID, String.valueOf(examId)) + .withBody(clientInstruction) + .call() + .getOrThrow(), + pageContext); + + } catch (final Exception e) { + pageContext.notifyUnexpectedError(e); } + } - if (log.isDebugEnabled()) { - log.debug("Propagate SEB quit instruction for exam: {} and connections: {}", - examId, - connectionTokens); + public void propagateSEBLockInstruction( + final String examId, + final String message, + final String imageURL, + final String connectionTokens, + final PageContext pageContext) { + + try { + + if (connectionTokens.isEmpty()) { + return; + } + + if (log.isDebugEnabled()) { + log.debug("Propagate SEB lock instruction for exam: {} and connections: {}", + examId, + connectionTokens); + } + + final Map attributes = new HashMap<>(); + if (StringUtils.isNotBlank(message)) { + attributes.put(ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_FORCE_LOCK_SCREEN.MESSAGE, message); + } + if (StringUtils.isNotBlank(imageURL)) { + attributes.put(ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_FORCE_LOCK_SCREEN.IMAGE_URL, imageURL); + } + + final ClientInstruction clientInstruction = new ClientInstruction( + null, + Long.valueOf(examId), + InstructionType.SEB_FORCE_LOCK_SCREEN, + connectionTokens, + attributes); + + processInstruction(() -> this.restService.getBuilder(PropagateInstruction.class) + .withURIVariable(API.PARAM_PARENT_MODEL_ID, examId) + .withBody(clientInstruction) + .call() + .getOrThrow(), + pageContext); + + } catch (final Exception e) { + pageContext.notifyUnexpectedError(e); } - - final ClientInstruction clientInstruction = new ClientInstruction( - null, - examId, - InstructionType.SEB_QUIT, - StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR), - null); - - processInstruction(() -> this.restService.getBuilder(PropagateInstruction.class) - .withURIVariable(API.PARAM_PARENT_MODEL_ID, String.valueOf(examId)) - .withBody(clientInstruction) - .call() - .getOrThrow(), - pageContext); - } public void disableConnection( @@ -113,6 +164,7 @@ public class InstructionProcessor { ConnectionStatus.CONNECTION_REQUESTED, ConnectionStatus.UNDEFINED, ConnectionStatus.CLOSED, + ConnectionStatus.ACTIVE, ConnectionStatus.AUTHENTICATED)); if (connectionTokens.isEmpty()) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/FilterMap.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/FilterMap.java index 1c9243bd..a9a5b3d5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/FilterMap.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/FilterMap.java @@ -9,6 +9,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.dao; import java.util.Arrays; +import java.util.List; import java.util.Set; import org.apache.commons.lang3.StringUtils; @@ -206,6 +207,15 @@ public class FilterMap extends POSTMapper { return Utils.toSQLWildcard(this.params.getFirst(name)); } + public List getClientConnectionTokenList() { + final String tokenList = getString(ClientConnection.FILTER_ATTR_TOKEN_LIST); + if (StringUtils.isBlank(tokenList)) { + return null; + } + + return Utils.asImmutableList(StringUtils.split(tokenList, Constants.LIST_SEPARATOR)); + } + public Long getClientConnectionExamId() { return getLong(ClientConnection.FILTER_ATTR_EXAM_ID); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java index 4acbe529..07be0c18 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java @@ -125,6 +125,9 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO { ClientConnectionRecordDynamicSqlSupport.institutionId, isEqualToWhenPresent(filterMap.getInstitutionId())); return whereClause + .and( + ClientConnectionRecordDynamicSqlSupport.connectionToken, + isInWhenPresent(filterMap.getClientConnectionTokenList())) .and( ClientConnectionRecordDynamicSqlSupport.examId, isEqualToWhenPresent(filterMap.getClientConnectionExamId())) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java index 4601aa41..0399de13 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java @@ -63,6 +63,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic ConnectionStatus.UNDEFINED, ConnectionStatus.CONNECTION_REQUESTED, ConnectionStatus.AUTHENTICATED, + ConnectionStatus.ACTIVE, ConnectionStatus.CLOSED); private final ExamSessionService examSessionService; diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 2133c5d4..0377c5b8 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -1873,6 +1873,8 @@ sebserver.monitoring.exam.connection.action.instruction.quit.all=Quit All SEB Cl sebserver.monitoring.exam.connection.action.instruction.quit.confirm=Are you sure to quit this SEB client connection? sebserver.monitoring.exam.connection.action.instruction.quit.selected.confirm=Are you sure to quit all selected, active SEB client connections? sebserver.monitoring.exam.connection.action.instruction.quit.all.confirm=Are you sure to quit all active SEB client connections? +sebserver.monitoring.exam.connection.action.instruction.lock.selected=Lock Selected SEB Clients +sebserver.monitoring.exam.connection.action.instruction.lock.confirm=Are you sure to lock this SEB client connection? sebserver.monitoring.exam.connection.action.instruction.disable.selected.confirm=Are you sure to disable all selected SEB client connections? sebserver.monitoring.exam.connection.action.instruction.disable.all.confirm=Are you sure to disable all active SEB client connections? sebserver.monitoring.exam.connection.action.disable=Mark As Canceled @@ -1891,6 +1893,7 @@ sebserver.monitoring.exam.connection.action.proctoring.examroom=Exam Room Procto sebserver.monitoring.exam.connection.action.openTownhall.confirm=You are about to open the town-hall room and force all SEB clients to join the town-hall room.
Are you sure to open the town-hall? sebserver.monitoring.exam.connection.action.closeTownhall.confirm=You are about to close the town-hall room and force all SEB clients to join it's proctoring room.
Are you sure to close the town-hall? sebserver.monitoring.exam.connection.action.singleroom.confirm=You are about to open the single/one to one room for this participant.
Are you sure you want to open the single room? +sebserver.monitoring.exam.connection.selected.actions=  sebserver.monitoring.exam.connection.notificationlist.actions= sebserver.monitoring.exam.connection.action.confirm.notification=Confirm Notification @@ -1936,6 +1939,16 @@ sebserver.monitoring.exam.connection.status.CLOSED=Closed sebserver.monitoring.exam.connection.status.ABORTED=Aborted sebserver.monitoring.exam.connection.status.DISABLED=Canceled +sebserver.monitoring.lock.title=Lock SEB Clients +sebserver.monitoring.lock.form.info.title=Info +sebserver.monitoring.lock.form.info=Please check all selected SEB connection
before sending lock by click on "OK" +sebserver.monitoring.lock.form.message=Lock Message +sebserver.monitoring.lock.form.message.tooltip=A message that will be displayed by the SEB with the lock screen to the user +sebserver.monitoring.lock.list.title=Selected SEB Client Connections +sebserver.monitoring.lock.list.name=SEB User Session Identifier +sebserver.monitoring.lock.list.info=SEB Connection Info +sebserver.monitoring.lock.noselection=Please select at least one active SEB client connection. + ################################ # Finished Exams ################################