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_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<ClientConnection> getStatusPredicate(final ConnectionStatus... status) {
final EnumSet<ConnectionStatus> states = EnumSet.allOf(ConnectionStatus.class);
final EnumSet<ConnectionStatus> states = EnumSet.noneOf(ConnectionStatus.class);
if (status != null) {
Collections.addAll(states, status);
}

View file

@ -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)

View file

@ -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),

View file

@ -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"),

View file

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

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.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<EntityKey> selectionForQuitInstruction(final ClientConnectionTable clientTable) {
private Set<EntityKey> selectionForInstruction(final ClientConnectionTable clientTable) {
final Set<String> 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,

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;
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<Predicate<ClientConnection>, Set<String>> selectionFunction,
final PageContext pageContext) {
final Set<String> connectionTokens = selectionFunction
.apply(ClientConnection.getStatusPredicate(
ConnectionStatus.CONNECTION_REQUESTED,
ConnectionStatus.ACTIVE));
try {
final Set<String> 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<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(
@ -113,6 +164,7 @@ public class InstructionProcessor {
ConnectionStatus.CONNECTION_REQUESTED,
ConnectionStatus.UNDEFINED,
ConnectionStatus.CLOSED,
ConnectionStatus.ACTIVE,
ConnectionStatus.AUTHENTICATED));
if (connectionTokens.isEmpty()) {

View file

@ -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<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() {
return getLong(ClientConnection.FILTER_ATTR_EXAM_ID);
}

View file

@ -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()))

View file

@ -63,6 +63,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
ConnectionStatus.UNDEFINED,
ConnectionStatus.CONNECTION_REQUESTED,
ConnectionStatus.AUTHENTICATED,
ConnectionStatus.ACTIVE,
ConnectionStatus.CLOSED);
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.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.<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.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.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<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
################################