SEBSERV-151 and SEBSERV-189

This commit is contained in:
anhefti 2022-06-14 16:51:46 +02:00
parent f0fa591348
commit 3cbfd80206
13 changed files with 404 additions and 61 deletions

View file

@ -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_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_IP_STRING = Domain.CLIENT_CONNECTION.ATTR_CLIENT_ADDRESS;
public static final String FILTER_ATTR_INFO = ATTR_INFO; 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) @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ID)
public final Long id; public final Long id;
@ -388,7 +389,7 @@ public final class ClientConnection implements GrantEntity {
} }
public static Predicate<ClientConnection> getStatusPredicate(final ConnectionStatus... status) { public static Predicate<ClientConnection> getStatusPredicate(final ConnectionStatus... status) {
final EnumSet<ConnectionStatus> states = EnumSet.allOf(ConnectionStatus.class); final EnumSet<ConnectionStatus> states = EnumSet.noneOf(ConnectionStatus.class);
if (status != null) { if (status != null) {
Collections.addAll(states, status); Collections.addAll(states, status);
} }

View file

@ -27,7 +27,8 @@ public final class ClientInstruction {
SEB_QUIT, SEB_QUIT,
SEB_PROCTORING, SEB_PROCTORING,
SEB_RECONFIGURE_SETTINGS, SEB_RECONFIGURE_SETTINGS,
NOTIFICATION_CONFIRM NOTIFICATION_CONFIRM,
SEB_FORCE_LOCK_SCREEN
} }
public enum ProctoringInstructionMethod { public enum ProctoringInstructionMethod {
@ -70,6 +71,11 @@ public final class ClientInstruction {
public static final String ZOOM_RECEIVE_VIDEO = "zoomReceiveVideo"; public static final String ZOOM_RECEIVE_VIDEO = "zoomReceiveVideo";
public static final String ZOOM_ALLOW_CHAT = "zoomFeatureFlagChat"; 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) @JsonProperty(Domain.CLIENT_INSTRUCTION.ATTR_ID)

View file

@ -30,6 +30,9 @@ public enum ActionCategory {
EXAM_MONITORING_NOTIFICATION_LIST(new LocTextKey( EXAM_MONITORING_NOTIFICATION_LIST(new LocTextKey(
"sebserver.monitoring.exam.connection.notificationlist.actions"), "sebserver.monitoring.exam.connection.notificationlist.actions"),
1), 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), CLIENT_EVENT_LIST(new LocTextKey("sebserver.monitoring.exam.connection.list.actions"), 1),
LOGS_USER_ACTIVITY_LIST(new LocTextKey("sebserver.userlogs.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), LOGS_SEB_CLIENT_LIST(new LocTextKey("sebserver.userlogs.list.actions"), 1),

View file

@ -769,7 +769,7 @@ public enum ActionDefinition {
new LocTextKey("sebserver.monitoring.exam.connection.action.view"), new LocTextKey("sebserver.monitoring.exam.connection.action.view"),
ImageIcon.SHOW, ImageIcon.SHOW,
PageStateDefinitionImpl.MONITORING_CLIENT_CONNECTION, PageStateDefinitionImpl.MONITORING_CLIENT_CONNECTION,
ActionCategory.CLIENT_EVENT_LIST), ActionCategory.EXAM_MONITORING_3),
MONITOR_EXAM_CLIENT_CONNECTION_QUIT( MONITOR_EXAM_CLIENT_CONNECTION_QUIT(
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit"), new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit"),
ImageIcon.SEND_QUIT, ImageIcon.SEND_QUIT,
@ -795,12 +795,18 @@ public enum ActionDefinition {
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.selected"), new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.selected"),
ImageIcon.SEND_QUIT, ImageIcon.SEND_QUIT,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.CLIENT_EVENT_LIST), ActionCategory.EXAM_MONITORING_2),
MONITOR_EXAM_QUIT_ALL( MONITOR_EXAM_QUIT_ALL(
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.all"), new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.all"),
ImageIcon.SEND_QUIT, ImageIcon.SEND_QUIT,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.FORM), 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( MONITOR_EXAM_BACK_TO_OVERVIEW(
new LocTextKey("sebserver.monitoring.exam.action.detail.view"), new LocTextKey("sebserver.monitoring.exam.action.detail.view"),
ImageIcon.SHOW, ImageIcon.SHOW,
@ -811,7 +817,7 @@ public enum ActionDefinition {
new LocTextKey("sebserver.monitoring.exam.connection.action.disable"), new LocTextKey("sebserver.monitoring.exam.connection.action.disable"),
ImageIcon.DISABLE, ImageIcon.DISABLE,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.CLIENT_EVENT_LIST), ActionCategory.EXAM_MONITORING_3),
MONITOR_EXAM_HIDE_REQUESTED_CONNECTION( MONITOR_EXAM_HIDE_REQUESTED_CONNECTION(
new LocTextKey("sebserver.monitoring.exam.connection.action.hide.requested"), new LocTextKey("sebserver.monitoring.exam.connection.action.hide.requested"),

View file

@ -94,6 +94,7 @@ public class ProctoringSettingsPopup {
.withAttribute( .withAttribute(
PageContext.AttributeKeys.FORCE_READ_ONLY, PageContext.AttributeKeys.FORCE_READ_ONLY,
(modifyGrant) ? Constants.FALSE_STRING : Constants.TRUE_STRING); (modifyGrant) ? Constants.FALSE_STRING : Constants.TRUE_STRING);
final ModalInputDialog<FormHandle<?>> dialog = final ModalInputDialog<FormHandle<?>> dialog =
new ModalInputDialog<FormHandle<?>>( new ModalInputDialog<FormHandle<?>>(
action.pageContext().getParent().getShell(), action.pageContext().getParent().getShell(),

View file

@ -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.RestCall;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; 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.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.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.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.ConfirmPendingClientNotification;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionData; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionData;
@ -365,7 +365,7 @@ public class MonitoringClientConnection implements TemplateComposer {
.withConfirm(() -> CONFIRM_QUIT) .withConfirm(() -> CONFIRM_QUIT)
.withExec(action -> { .withExec(action -> {
this.instructionProcessor.propagateSEBQuitInstruction( this.instructionProcessor.propagateSEBQuitInstruction(
exam.id, exam.getModelId(),
connectionToken, connectionToken,
pageContext); pageContext);
return action; return action;

View file

@ -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.push.ServerPushService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; 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.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.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.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable; import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable;
import ch.ethz.seb.sebserver.gui.service.session.FullPageMonitoringGUIUpdate; import ch.ethz.seb.sebserver.gui.service.session.FullPageMonitoringGUIUpdate;
@ -93,6 +93,7 @@ public class MonitoringRunningExam implements TemplateComposer {
private final AsyncRunner asyncRunner; private final AsyncRunner asyncRunner;
private final InstructionProcessor instructionProcessor; private final InstructionProcessor instructionProcessor;
private final MonitoringExamSearchPopup monitoringExamSearchPopup; private final MonitoringExamSearchPopup monitoringExamSearchPopup;
private final SEBSendLockPopup sebSendLockPopup;
private final MonitoringProctoringService monitoringProctoringService; private final MonitoringProctoringService monitoringProctoringService;
private final boolean distributedSetup; private final boolean distributedSetup;
private final long pollInterval; private final long pollInterval;
@ -103,6 +104,7 @@ public class MonitoringRunningExam implements TemplateComposer {
final AsyncRunner asyncRunner, final AsyncRunner asyncRunner,
final InstructionProcessor instructionProcessor, final InstructionProcessor instructionProcessor,
final MonitoringExamSearchPopup monitoringExamSearchPopup, final MonitoringExamSearchPopup monitoringExamSearchPopup,
final SEBSendLockPopup sebSendLockPopup,
final MonitoringProctoringService monitoringProctoringService, final MonitoringProctoringService monitoringProctoringService,
final GuiServiceInfo guiServiceInfo, final GuiServiceInfo guiServiceInfo,
@Value("${sebserver.gui.webservice.poll-interval:2000}") final long pollInterval) { @Value("${sebserver.gui.webservice.poll-interval:2000}") final long pollInterval) {
@ -117,6 +119,7 @@ public class MonitoringRunningExam implements TemplateComposer {
this.pollInterval = pollInterval; this.pollInterval = pollInterval;
this.distributedSetup = guiServiceInfo.isDistributedSetup(); this.distributedSetup = guiServiceInfo.isDistributedSetup();
this.monitoringExamSearchPopup = monitoringExamSearchPopup; this.monitoringExamSearchPopup = monitoringExamSearchPopup;
this.sebSendLockPopup = sebSendLockPopup;
} }
@Override @Override
@ -177,11 +180,48 @@ public class MonitoringRunningExam implements TemplateComposer {
pageContext, pageContext,
ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION, ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION,
ActionDefinition.MONITOR_EXAM_QUIT_SELECTED, ActionDefinition.MONITOR_EXAM_QUIT_SELECTED,
ActionDefinition.MONITOR_EXAM_LOCK_SELECTED,
ActionDefinition.MONITOR_EXAM_DISABLE_SELECTED_CONNECTION, ActionDefinition.MONITOR_EXAM_DISABLE_SELECTED_CONNECTION,
ActionDefinition.MONITOR_EXAM_NEW_PROCTOR_ROOM)); ActionDefinition.MONITOR_EXAM_NEW_PROCTOR_ROOM));
actionBuilder 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) .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
.withParentEntityKey(entityKey) .withParentEntityKey(entityKey)
.withExec(pageAction -> { .withExec(pageAction -> {
@ -202,29 +242,6 @@ public class MonitoringRunningExam implements TemplateComposer {
}) })
.publishIf(isExamSupporter, false) .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) .newAction(ActionDefinition.MONITOR_EXAM_DISABLE_SELECTED_CONNECTION)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withConfirm(() -> CONFIRM_DISABLE_SELECTED) .withConfirm(() -> CONFIRM_DISABLE_SELECTED)
@ -461,7 +478,7 @@ public class MonitoringRunningExam implements TemplateComposer {
}; };
} }
private Set<EntityKey> selectionForQuitInstruction(final ClientConnectionTable clientTable) { private Set<EntityKey> selectionForInstruction(final ClientConnectionTable clientTable) {
final Set<String> connectionTokens = clientTable.getConnectionTokens( final Set<String> connectionTokens = clientTable.getConnectionTokens(
cc -> cc.status.clientActiveStatus, cc -> cc.status.clientActiveStatus,
true); true);
@ -478,7 +495,7 @@ public class MonitoringRunningExam implements TemplateComposer {
final boolean all) { final boolean all) {
this.instructionProcessor.propagateSEBQuitInstruction( this.instructionProcessor.propagateSEBQuitInstruction(
clientTable.getExam().id, clientTable.getExam().getModelId(),
statesPredicate -> clientTable.getConnectionTokens( statesPredicate -> clientTable.getConnectionTokens(
statesPredicate, statesPredicate,
!all), !all),
@ -489,6 +506,23 @@ public class MonitoringRunningExam implements TemplateComposer {
return action; 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( private PageAction disableSEBClients(
final PageAction action, final PageAction action,
final ClientConnectionTable clientTable, final ClientConnectionTable clientTable,

View file

@ -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<Predicate<ClientConnection>, Set<String>> selectionFunction) {
final PageContext pageContext = action.pageContext();
final Set<String> 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<FormHandle<?>> dialog =
new ModalInputDialog<FormHandle<?>>(
action.pageContext().getParent().getShell(),
this.pageService.getWidgetFactory())
.setDialogWidth(800)
.setDialogHeight(showList ? 500 : 200);
final Predicate<FormHandle<?>> doLock = formHandle -> propagateLockInstruction(
connectionTokens,
pageContext,
formHandle);
dialog.open(
TITLE_TEXT_KEY,
doLock,
Utils.EMPTY_EXECUTION,
popupComposer);
return action;
}
private final class PopupComposer implements ModalInputDialogComposer<FormHandle<?>> {
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<FormHandle<?>> 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;
}
}

View file

@ -9,6 +9,8 @@
package ch.ethz.seb.sebserver.gui.service.session; package ch.ethz.seb.sebserver.gui.service.session;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -55,7 +57,7 @@ public class InstructionProcessor {
} }
public void propagateSEBQuitInstruction( public void propagateSEBQuitInstruction(
final Long examId, final String examId,
final String connectionToken, final String connectionToken,
final PageContext pageContext) { final PageContext pageContext) {
@ -67,40 +69,89 @@ public class InstructionProcessor {
} }
public void propagateSEBQuitInstruction( public void propagateSEBQuitInstruction(
final Long examId, final String examId,
final Function<Predicate<ClientConnection>, Set<String>> selectionFunction, final Function<Predicate<ClientConnection>, Set<String>> selectionFunction,
final PageContext pageContext) { final PageContext pageContext) {
final Set<String> connectionTokens = selectionFunction try {
.apply(ClientConnection.getStatusPredicate( final Set<String> connectionTokens = selectionFunction
ConnectionStatus.CONNECTION_REQUESTED, .apply(ClientConnection.getStatusPredicate(
ConnectionStatus.ACTIVE)); ConnectionStatus.CONNECTION_REQUESTED,
ConnectionStatus.ACTIVE));
if (connectionTokens.isEmpty()) { if (connectionTokens.isEmpty()) {
log.warn("Empty selection"); return;
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()) { public void propagateSEBLockInstruction(
log.debug("Propagate SEB quit instruction for exam: {} and connections: {}", final String examId,
examId, final String message,
connectionTokens); 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<String, String> 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( public void disableConnection(
@ -113,6 +164,7 @@ public class InstructionProcessor {
ConnectionStatus.CONNECTION_REQUESTED, ConnectionStatus.CONNECTION_REQUESTED,
ConnectionStatus.UNDEFINED, ConnectionStatus.UNDEFINED,
ConnectionStatus.CLOSED, ConnectionStatus.CLOSED,
ConnectionStatus.ACTIVE,
ConnectionStatus.AUTHENTICATED)); ConnectionStatus.AUTHENTICATED));
if (connectionTokens.isEmpty()) { if (connectionTokens.isEmpty()) {

View file

@ -9,6 +9,7 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.dao; package ch.ethz.seb.sebserver.webservice.servicelayer.dao;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -206,6 +207,15 @@ public class FilterMap extends POSTMapper {
return Utils.toSQLWildcard(this.params.getFirst(name)); return Utils.toSQLWildcard(this.params.getFirst(name));
} }
public List<String> 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() { public Long getClientConnectionExamId() {
return getLong(ClientConnection.FILTER_ATTR_EXAM_ID); return getLong(ClientConnection.FILTER_ATTR_EXAM_ID);
} }

View file

@ -125,6 +125,9 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
ClientConnectionRecordDynamicSqlSupport.institutionId, ClientConnectionRecordDynamicSqlSupport.institutionId,
isEqualToWhenPresent(filterMap.getInstitutionId())); isEqualToWhenPresent(filterMap.getInstitutionId()));
return whereClause return whereClause
.and(
ClientConnectionRecordDynamicSqlSupport.connectionToken,
isInWhenPresent(filterMap.getClientConnectionTokenList()))
.and( .and(
ClientConnectionRecordDynamicSqlSupport.examId, ClientConnectionRecordDynamicSqlSupport.examId,
isEqualToWhenPresent(filterMap.getClientConnectionExamId())) isEqualToWhenPresent(filterMap.getClientConnectionExamId()))

View file

@ -63,6 +63,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
ConnectionStatus.UNDEFINED, ConnectionStatus.UNDEFINED,
ConnectionStatus.CONNECTION_REQUESTED, ConnectionStatus.CONNECTION_REQUESTED,
ConnectionStatus.AUTHENTICATED, ConnectionStatus.AUTHENTICATED,
ConnectionStatus.ACTIVE,
ConnectionStatus.CLOSED); ConnectionStatus.CLOSED);
private final ExamSessionService examSessionService; private final ExamSessionService examSessionService;

View file

@ -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.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.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.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.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.instruction.disable.all.confirm=Are you sure to disable all active SEB client connections?
sebserver.monitoring.exam.connection.action.disable=Mark As Canceled 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.<br/>Are you sure to open the town-hall? 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.<br/>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.<br/>Are you sure to close 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.<br/>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.<br/>Are you sure you want to open the single room? sebserver.monitoring.exam.connection.action.singleroom.confirm=You are about to open the single/one to one room for this participant.<br/>Are you sure you want to open the single room?
sebserver.monitoring.exam.connection.selected.actions=&nbsp;
sebserver.monitoring.exam.connection.notificationlist.actions= sebserver.monitoring.exam.connection.notificationlist.actions=
sebserver.monitoring.exam.connection.action.confirm.notification=Confirm Notification 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.ABORTED=Aborted
sebserver.monitoring.exam.connection.status.DISABLED=Canceled 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<br/>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 # Finished Exams
################################ ################################