SEBSERV-163 finished grouping with filter
This commit is contained in:
		
							parent
							
								
									ee0034c367
								
							
						
					
					
						commit
						b5704dea95
					
				
					 18 changed files with 459 additions and 142 deletions
				
			
		|  | @ -197,6 +197,7 @@ public final class API { | ||||||
|     public static final String EXAM_MONITORING_NOTIFICATION_ENDPOINT = "/notification"; |     public static final String EXAM_MONITORING_NOTIFICATION_ENDPOINT = "/notification"; | ||||||
|     public static final String EXAM_MONITORING_DISABLE_CONNECTION_ENDPOINT = "/disable-connection"; |     public static final String EXAM_MONITORING_DISABLE_CONNECTION_ENDPOINT = "/disable-connection"; | ||||||
|     public static final String EXAM_MONITORING_STATE_FILTER = "hidden-states"; |     public static final String EXAM_MONITORING_STATE_FILTER = "hidden-states"; | ||||||
|  |     public static final String EXAM_MONITORING_CLIENT_GROUP_FILTER = "hidden-client-group"; | ||||||
|     public static final String EXAM_MONITORING_FINISHED_ENDPOINT = "/finishedexams"; |     public static final String EXAM_MONITORING_FINISHED_ENDPOINT = "/finishedexams"; | ||||||
|     public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT = |     public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT = | ||||||
|             "/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}"; |             "/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}"; | ||||||
|  |  | ||||||
|  | @ -28,7 +28,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain.CLIENT_GROUP; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Utils; | import ch.ethz.seb.sebserver.gbl.util.Utils; | ||||||
| 
 | 
 | ||||||
| @JsonIgnoreProperties(ignoreUnknown = true) | @JsonIgnoreProperties(ignoreUnknown = true) | ||||||
| public class ClientGroup implements ClientGroupData { | public class ClientGroup implements ClientGroupData, Comparable<ClientGroup> { | ||||||
| 
 | 
 | ||||||
|     public static final String FILTER_ATTR_EXAM_ID = "examId"; |     public static final String FILTER_ATTR_EXAM_ID = "examId"; | ||||||
| 
 | 
 | ||||||
|  | @ -285,4 +285,9 @@ public class ClientGroup implements ClientGroupData { | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     public int compareTo(final ClientGroup o) { | ||||||
|  |         return o == null ? -1 : this.id.compareTo(o.id); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -84,6 +84,29 @@ public class ClientConnectionData implements GrantEntity { | ||||||
|         return this.groups != null && this.groups.contains(clientGroupId); |         return this.groups != null && this.groups.contains(clientGroupId); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @JsonIgnore | ||||||
|  |     public boolean containsAllClientGroup(final Set<Long> clientGroupIds) { | ||||||
|  |         if (this.groups == null || clientGroupIds == null || clientGroupIds.isEmpty()) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         return this.groups != null && this.groups.containsAll(clientGroupIds); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @JsonIgnore | ||||||
|  |     public boolean filter(final Set<Long> clientGroupIdsToHide) { | ||||||
|  |         if (this.groups == null || clientGroupIdsToHide == null || clientGroupIdsToHide.isEmpty()) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         for (final Long id : this.groups) { | ||||||
|  |             if (!clientGroupIdsToHide.contains(id)) { | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public EntityType entityType() { |     public EntityType entityType() { | ||||||
|         return this.clientConnection.entityType(); |         return this.clientConnection.entityType(); | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gbl.monitoring; | ||||||
| 
 | 
 | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
|  | import java.util.Map; | ||||||
| 
 | 
 | ||||||
| import com.fasterxml.jackson.annotation.JsonCreator; | import com.fasterxml.jackson.annotation.JsonCreator; | ||||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | import com.fasterxml.jackson.annotation.JsonIgnore; | ||||||
|  | @ -25,6 +26,7 @@ public class MonitoringSEBConnectionData { | ||||||
| 
 | 
 | ||||||
|     public static final String ATTR_CONNECTIONS = "connections"; |     public static final String ATTR_CONNECTIONS = "connections"; | ||||||
|     public static final String ATTR_STATUS_MAPPING = "statusMapping"; |     public static final String ATTR_STATUS_MAPPING = "statusMapping"; | ||||||
|  |     public static final String ATTR_CLIENT_GROUP_MAPPING = "clientGroupMapping"; | ||||||
| 
 | 
 | ||||||
|     @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_ID) |     @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_ID) | ||||||
|     public final Long examId; |     public final Long examId; | ||||||
|  | @ -32,16 +34,20 @@ public class MonitoringSEBConnectionData { | ||||||
|     public final Collection<ClientConnectionData> connections; |     public final Collection<ClientConnectionData> connections; | ||||||
|     @JsonProperty(ATTR_STATUS_MAPPING) |     @JsonProperty(ATTR_STATUS_MAPPING) | ||||||
|     public final int[] connectionsPerStatus; |     public final int[] connectionsPerStatus; | ||||||
|  |     @JsonProperty(ATTR_CLIENT_GROUP_MAPPING) | ||||||
|  |     public final Map<Long, Integer> connectionsPerClientGroup; | ||||||
| 
 | 
 | ||||||
|     @JsonCreator |     @JsonCreator | ||||||
|     public MonitoringSEBConnectionData( |     public MonitoringSEBConnectionData( | ||||||
|             @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_ID) final Long examId, |             @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_ID) final Long examId, | ||||||
|             @JsonProperty(ATTR_CONNECTIONS) final Collection<ClientConnectionData> connections, |             @JsonProperty(ATTR_CONNECTIONS) final Collection<ClientConnectionData> connections, | ||||||
|             @JsonProperty(ATTR_STATUS_MAPPING) final int[] connectionsPerStatus) { |             @JsonProperty(ATTR_STATUS_MAPPING) final int[] connectionsPerStatus, | ||||||
|  |             @JsonProperty(ATTR_CLIENT_GROUP_MAPPING) final Map<Long, Integer> connectionsPerClientGroup) { | ||||||
| 
 | 
 | ||||||
|         this.examId = examId; |         this.examId = examId; | ||||||
|         this.connections = connections; |         this.connections = connections; | ||||||
|         this.connectionsPerStatus = connectionsPerStatus; |         this.connectionsPerStatus = connectionsPerStatus; | ||||||
|  |         this.connectionsPerClientGroup = connectionsPerClientGroup; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public Long getExamId() { |     public Long getExamId() { | ||||||
|  | @ -64,6 +70,14 @@ public class MonitoringSEBConnectionData { | ||||||
|         return this.connectionsPerStatus[status.code]; |         return this.connectionsPerStatus[status.code]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @JsonIgnore | ||||||
|  |     public int getNumberOfConnection(final Long clientGroupId) { | ||||||
|  |         if (this.connectionsPerClientGroup == null || !this.connectionsPerClientGroup.containsKey(clientGroupId)) { | ||||||
|  |             return -1; | ||||||
|  |         } | ||||||
|  |         return this.connectionsPerClientGroup.get(clientGroupId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public int hashCode() { |     public int hashCode() { | ||||||
|         final int prime = 31; |         final int prime = 31; | ||||||
|  |  | ||||||
|  | @ -42,7 +42,8 @@ public enum ActionCategory { | ||||||
|     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), | ||||||
|     VARIA(new LocTextKey("sebserver.overall.action.category.varia"), 0), |     VARIA(new LocTextKey("sebserver.overall.action.category.varia"), 0), | ||||||
|     FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.filter"), 50), |     STATE_FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.statefilter"), 40), | ||||||
|  |     GROUP_FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.groupfilter"), 50), | ||||||
|     PROCTORING(new LocTextKey("sebserver.exam.overall.action.category.proctoring"), 60), |     PROCTORING(new LocTextKey("sebserver.exam.overall.action.category.proctoring"), 60), | ||||||
| 
 | 
 | ||||||
|     FINISHED_EXAM_LIST(new LocTextKey("sebserver.finished.exam.list.actions"), 1); |     FINISHED_EXAM_LIST(new LocTextKey("sebserver.finished.exam.list.actions"), 1); | ||||||
|  |  | ||||||
|  | @ -882,44 +882,54 @@ public enum ActionDefinition { | ||||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.hide.requested"), |             new LocTextKey("sebserver.monitoring.exam.connection.action.hide.requested"), | ||||||
|             ImageIcon.TOGGLE_OFF, |             ImageIcon.TOGGLE_OFF, | ||||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, |             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||||
|             ActionCategory.FILTER), |             ActionCategory.STATE_FILTER), | ||||||
|     MONITOR_EXAM_SHOW_REQUESTED_CONNECTION( |     MONITOR_EXAM_SHOW_REQUESTED_CONNECTION( | ||||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.show.requested"), |             new LocTextKey("sebserver.monitoring.exam.connection.action.show.requested"), | ||||||
|             ImageIcon.TOGGLE_ON, |             ImageIcon.TOGGLE_ON, | ||||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, |             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||||
|             ActionCategory.FILTER), |             ActionCategory.STATE_FILTER), | ||||||
| 
 | 
 | ||||||
|     MONITOR_EXAM_HIDE_ACTIVE_CONNECTION( |     MONITOR_EXAM_HIDE_ACTIVE_CONNECTION( | ||||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.hide.active"), |             new LocTextKey("sebserver.monitoring.exam.connection.action.hide.active"), | ||||||
|             ImageIcon.TOGGLE_OFF, |             ImageIcon.TOGGLE_OFF, | ||||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, |             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||||
|             ActionCategory.FILTER), |             ActionCategory.STATE_FILTER), | ||||||
|     MONITOR_EXAM_SHOW_ACTIVE_CONNECTION( |     MONITOR_EXAM_SHOW_ACTIVE_CONNECTION( | ||||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.show.active"), |             new LocTextKey("sebserver.monitoring.exam.connection.action.show.active"), | ||||||
|             ImageIcon.TOGGLE_ON, |             ImageIcon.TOGGLE_ON, | ||||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, |             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||||
|             ActionCategory.FILTER), |             ActionCategory.STATE_FILTER), | ||||||
| 
 |  | ||||||
|     MONITOR_EXAM_HIDE_CLOSED_CONNECTION( |     MONITOR_EXAM_HIDE_CLOSED_CONNECTION( | ||||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.hide.closed"), |             new LocTextKey("sebserver.monitoring.exam.connection.action.hide.closed"), | ||||||
|             ImageIcon.TOGGLE_OFF, |             ImageIcon.TOGGLE_OFF, | ||||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, |             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||||
|             ActionCategory.FILTER), |             ActionCategory.STATE_FILTER), | ||||||
|     MONITOR_EXAM_SHOW_CLOSED_CONNECTION( |     MONITOR_EXAM_SHOW_CLOSED_CONNECTION( | ||||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.show.closed"), |             new LocTextKey("sebserver.monitoring.exam.connection.action.show.closed"), | ||||||
|             ImageIcon.TOGGLE_ON, |             ImageIcon.TOGGLE_ON, | ||||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, |             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||||
|             ActionCategory.FILTER), |             ActionCategory.STATE_FILTER), | ||||||
|     MONITOR_EXAM_HIDE_DISABLED_CONNECTION( |     MONITOR_EXAM_HIDE_DISABLED_CONNECTION( | ||||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.hide.disabled"), |             new LocTextKey("sebserver.monitoring.exam.connection.action.hide.disabled"), | ||||||
|             ImageIcon.TOGGLE_OFF, |             ImageIcon.TOGGLE_OFF, | ||||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, |             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||||
|             ActionCategory.FILTER), |             ActionCategory.STATE_FILTER), | ||||||
|     MONITOR_EXAM_SHOW_DISABLED_CONNECTION( |     MONITOR_EXAM_SHOW_DISABLED_CONNECTION( | ||||||
|             new LocTextKey("sebserver.monitoring.exam.connection.action.show.disabled"), |             new LocTextKey("sebserver.monitoring.exam.connection.action.show.disabled"), | ||||||
|             ImageIcon.TOGGLE_ON, |             ImageIcon.TOGGLE_ON, | ||||||
|             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, |             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||||
|             ActionCategory.FILTER), |             ActionCategory.STATE_FILTER), | ||||||
|  | 
 | ||||||
|  |     MONITOR_EXAM_HIDE_CLIENT_GROUP_CONNECTION( | ||||||
|  |             new LocTextKey("sebserver.monitoring.exam.connection.action.hide.clientgroup"), | ||||||
|  |             ImageIcon.TOGGLE_OFF, | ||||||
|  |             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||||
|  |             ActionCategory.GROUP_FILTER), | ||||||
|  |     MONITOR_EXAM_SHOW_CLIENT_GROUP_CONNECTION( | ||||||
|  |             new LocTextKey("sebserver.monitoring.exam.connection.action.show.clientgroup"), | ||||||
|  |             ImageIcon.TOGGLE_ON, | ||||||
|  |             PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, | ||||||
|  |             ActionCategory.GROUP_FILTER), | ||||||
| 
 | 
 | ||||||
|     MONITORING_EXAM_SEARCH_CONNECTIONS( |     MONITORING_EXAM_SEARCH_CONNECTIONS( | ||||||
|             new LocTextKey("sebserver.monitoring.search.action"), |             new LocTextKey("sebserver.monitoring.search.action"), | ||||||
|  |  | ||||||
|  | @ -247,26 +247,7 @@ public class ActionPane implements TemplateComposer { | ||||||
|                 actionsTitle.setData(RWT.CUSTOM_VARIANT, "close"); |                 actionsTitle.setData(RWT.CUSTOM_VARIANT, "close"); | ||||||
|                 actionsTitle.setImage(WidgetFactory.ImageIcon.ACTIVE.getImage(parent.getDisplay())); |                 actionsTitle.setImage(WidgetFactory.ImageIcon.ACTIVE.getImage(parent.getDisplay())); | ||||||
|                 actionsTitle.setText("    " + titleText); |                 actionsTitle.setText("    " + titleText); | ||||||
|                 actionsTitle.addListener(SWT.MouseUp, event -> { |                 actionsTitle.addListener(SWT.MouseUp, event -> actionGroupExpand(composite, actionsTitle)); | ||||||
|                     try { |  | ||||||
|                         final Control contentControl = composite.getChildren()[1]; |  | ||||||
|                         if (contentControl.isVisible()) { |  | ||||||
|                             actionsTitle.setData(RWT.CUSTOM_VARIANT, "open"); |  | ||||||
|                             contentControl.setVisible(false); |  | ||||||
|                             final GridData l = (GridData) contentControl.getLayoutData(); |  | ||||||
|                             l.heightHint = 0; |  | ||||||
|                             composite.getParent().layout(true, true); |  | ||||||
|                         } else { |  | ||||||
|                             actionsTitle.setData(RWT.CUSTOM_VARIANT, "close"); |  | ||||||
|                             contentControl.setVisible(true); |  | ||||||
|                             final GridData l = (GridData) contentControl.getLayoutData(); |  | ||||||
|                             l.heightHint = SWT.DEFAULT; |  | ||||||
|                             composite.getParent().layout(true, true); |  | ||||||
|                         } |  | ||||||
|                     } catch (final Exception e) { |  | ||||||
|                         // just ignore |  | ||||||
|                     } |  | ||||||
|                 }); |  | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             actionsTitle.setLayoutData(titleLayout); |             actionsTitle.setLayoutData(titleLayout); | ||||||
|  | @ -319,6 +300,27 @@ public class ActionPane implements TemplateComposer { | ||||||
|         return actions; |         return actions; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private void actionGroupExpand(final Composite composite, final Label actionsTitle) { | ||||||
|  |         try { | ||||||
|  |             final Control contentControl = composite.getChildren()[1]; | ||||||
|  |             if (contentControl.isVisible()) { | ||||||
|  |                 actionsTitle.setData(RWT.CUSTOM_VARIANT, "open"); | ||||||
|  |                 contentControl.setVisible(false); | ||||||
|  |                 final GridData l = (GridData) contentControl.getLayoutData(); | ||||||
|  |                 l.heightHint = 0; | ||||||
|  |                 composite.getParent().layout(true, true); | ||||||
|  |             } else { | ||||||
|  |                 actionsTitle.setData(RWT.CUSTOM_VARIANT, "close"); | ||||||
|  |                 contentControl.setVisible(true); | ||||||
|  |                 final GridData l = (GridData) contentControl.getLayoutData(); | ||||||
|  |                 l.heightHint = SWT.DEFAULT; | ||||||
|  |                 composite.getParent().layout(true, true); | ||||||
|  |             } | ||||||
|  |         } catch (final Exception e) { | ||||||
|  |             // just ignore | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private void clearDisposedTrees(final Map<String, Tree> actionTrees) { |     private void clearDisposedTrees(final Map<String, Tree> actionTrees) { | ||||||
|         new ArrayList<>(actionTrees.entrySet()) |         new ArrayList<>(actionTrees.entrySet()) | ||||||
|                 .forEach(entry -> { |                 .forEach(entry -> { | ||||||
|  |  | ||||||
|  | @ -11,6 +11,8 @@ package ch.ethz.seb.sebserver.gui.content.monitoring; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.Map; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
| import java.util.function.BooleanSupplier; | import java.util.function.BooleanSupplier; | ||||||
| import java.util.function.Consumer; | import java.util.function.Consumer; | ||||||
|  | @ -64,7 +66,7 @@ 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; | ||||||
| import ch.ethz.seb.sebserver.gui.service.session.FullPageMonitoringUpdate; | import ch.ethz.seb.sebserver.gui.service.session.FullPageMonitoringUpdate; | ||||||
| import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor; | import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor; | ||||||
| import ch.ethz.seb.sebserver.gui.service.session.MonitoringStatus; | import ch.ethz.seb.sebserver.gui.service.session.MonitoringFilter; | ||||||
| import ch.ethz.seb.sebserver.gui.service.session.proctoring.MonitoringProctoringService; | import ch.ethz.seb.sebserver.gui.service.session.proctoring.MonitoringProctoringService; | ||||||
| import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; | import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; | ||||||
| 
 | 
 | ||||||
|  | @ -258,6 +260,7 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
| 
 | 
 | ||||||
|         if (isExamSupporter.getAsBoolean()) { |         if (isExamSupporter.getAsBoolean()) { | ||||||
|             guiUpdates.add(createFilterActions( |             guiUpdates.add(createFilterActions( | ||||||
|  |                     clientGroups, | ||||||
|                     fullPageMonitoringUpdate, |                     fullPageMonitoringUpdate, | ||||||
|                     actionBuilder, |                     actionBuilder, | ||||||
|                     clientTable, |                     clientTable, | ||||||
|  | @ -347,13 +350,14 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private FullPageMonitoringGUIUpdate createFilterActions( |     private FullPageMonitoringGUIUpdate createFilterActions( | ||||||
|             final MonitoringStatus monitoringStatus, |             final Collection<ClientGroup> clientGroups, | ||||||
|  |             final MonitoringFilter monitoringStatus, | ||||||
|             final PageActionBuilder actionBuilder, |             final PageActionBuilder actionBuilder, | ||||||
|             final ClientConnectionTable clientTable, |             final ClientConnectionTable clientTable, | ||||||
|             final BooleanSupplier isExamSupporter) { |             final BooleanSupplier isExamSupporter) { | ||||||
| 
 | 
 | ||||||
|         final StatusFilterGUIUpdate statusFilterGUIUpdate = |         final FilterGUIUpdate statusFilterGUIUpdate = | ||||||
|                 new StatusFilterGUIUpdate(this.pageService.getPolyglotPageService()); |                 new FilterGUIUpdate(this.pageService.getPolyglotPageService()); | ||||||
| 
 | 
 | ||||||
|         addFilterAction( |         addFilterAction( | ||||||
|                 monitoringStatus, |                 monitoringStatus, | ||||||
|  | @ -388,61 +392,118 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
|                 ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION, |                 ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION, | ||||||
|                 ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION); |                 ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION); | ||||||
| 
 | 
 | ||||||
|  |         if (clientGroups != null && !clientGroups.isEmpty()) { | ||||||
|  |             clientGroups.forEach(clientGroup -> { | ||||||
|  | 
 | ||||||
|  |                 addClientGroupFilterAction( | ||||||
|  |                         monitoringStatus, | ||||||
|  |                         statusFilterGUIUpdate, | ||||||
|  |                         actionBuilder, | ||||||
|  |                         clientTable, | ||||||
|  |                         clientGroup, | ||||||
|  |                         ActionDefinition.MONITOR_EXAM_SHOW_CLIENT_GROUP_CONNECTION, | ||||||
|  |                         ActionDefinition.MONITOR_EXAM_HIDE_CLIENT_GROUP_CONNECTION); | ||||||
|  | 
 | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         return statusFilterGUIUpdate; |         return statusFilterGUIUpdate; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void addFilterAction( |     private void addFilterAction( | ||||||
|             final MonitoringStatus monitoringStatus, |             final MonitoringFilter filter, | ||||||
|             final StatusFilterGUIUpdate statusFilterGUIUpdate, |             final FilterGUIUpdate filterGUIUpdate, | ||||||
|             final PageActionBuilder actionBuilder, |             final PageActionBuilder actionBuilder, | ||||||
|             final ClientConnectionTable clientTable, |             final ClientConnectionTable clientTable, | ||||||
|             final ConnectionStatus status, |             final ConnectionStatus status, | ||||||
|             final ActionDefinition showActionDef, |             final ActionDefinition showActionDef, | ||||||
|             final ActionDefinition hideActionDef) { |             final ActionDefinition hideActionDef) { | ||||||
| 
 | 
 | ||||||
|         final int numOfConnections = monitoringStatus.getNumOfConnections(status); |         final int numOfConnections = filter.getNumOfConnections(status); | ||||||
|         if (monitoringStatus.isStatusHidden(status)) { |         PageAction action; | ||||||
|             final PageAction showAction = actionBuilder.newAction(showActionDef) |         if (filter.isStatusHidden(status)) { | ||||||
|                     .withExec(showStateViewAction(monitoringStatus, clientTable, status)) |             action = actionBuilder.newAction(showActionDef) | ||||||
|  |                     .withExec(showStateViewAction(filter, clientTable, status)) | ||||||
|                     .noEventPropagation() |                     .noEventPropagation() | ||||||
|                     .withSwitchAction( |                     .withSwitchAction( | ||||||
|                             actionBuilder.newAction(hideActionDef) |                             actionBuilder.newAction(hideActionDef) | ||||||
|                                     .withExec( |                                     .withExec( | ||||||
|                                             hideStateViewAction(monitoringStatus, clientTable, status)) |                                             hideStateViewAction(filter, clientTable, status)) | ||||||
|                                     .noEventPropagation() |                                     .noEventPropagation() | ||||||
|                                     .withNameAttributes(numOfConnections) |                                     .withNameAttributes(numOfConnections) | ||||||
|                                     .create()) |                                     .create()) | ||||||
|                     .withNameAttributes(numOfConnections) |                     .withNameAttributes(numOfConnections) | ||||||
|                     .create(); |                     .create(); | ||||||
|             this.pageService.publishAction( |  | ||||||
|                     showAction, |  | ||||||
|                     treeItem -> statusFilterGUIUpdate.register(treeItem, status)); |  | ||||||
|         } else { |         } else { | ||||||
|             final PageAction hideAction = actionBuilder.newAction(hideActionDef) |             action = actionBuilder.newAction(hideActionDef) | ||||||
|                     .withExec(hideStateViewAction(monitoringStatus, clientTable, status)) |                     .withExec(hideStateViewAction(filter, clientTable, status)) | ||||||
|                     .noEventPropagation() |                     .noEventPropagation() | ||||||
|                     .withSwitchAction( |                     .withSwitchAction( | ||||||
|                             actionBuilder.newAction(showActionDef) |                             actionBuilder.newAction(showActionDef) | ||||||
|                                     .withExec( |                                     .withExec( | ||||||
|                                             showStateViewAction(monitoringStatus, clientTable, status)) |                                             showStateViewAction(filter, clientTable, status)) | ||||||
|                                     .noEventPropagation() |                                     .noEventPropagation() | ||||||
|                                     .withNameAttributes(numOfConnections) |                                     .withNameAttributes(numOfConnections) | ||||||
|                                     .create()) |                                     .create()) | ||||||
|                     .withNameAttributes(numOfConnections) |                     .withNameAttributes(numOfConnections) | ||||||
|                     .create(); |                     .create(); | ||||||
|             this.pageService.publishAction( |  | ||||||
|                     hideAction, |  | ||||||
|                     treeItem -> statusFilterGUIUpdate.register(treeItem, status)); |  | ||||||
|         } |         } | ||||||
|  |         this.pageService.publishAction( | ||||||
|  |                 action, | ||||||
|  |                 treeItem -> filterGUIUpdate.register(treeItem, status)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void addClientGroupFilterAction( | ||||||
|  |             final MonitoringFilter filter, | ||||||
|  |             final FilterGUIUpdate filterGUIUpdate, | ||||||
|  |             final PageActionBuilder actionBuilder, | ||||||
|  |             final ClientConnectionTable clientTable, | ||||||
|  |             final ClientGroup clientGroup, | ||||||
|  |             final ActionDefinition showActionDef, | ||||||
|  |             final ActionDefinition hideActionDef) { | ||||||
|  | 
 | ||||||
|  |         final int numOfConnections = filter.getNumOfConnections(clientGroup.id); | ||||||
|  |         PageAction action; | ||||||
|  |         if (filter.isClientGroupHidden(clientGroup.id)) { | ||||||
|  |             action = actionBuilder.newAction(showActionDef) | ||||||
|  |                     .withExec(showClientGroupAction(filter, clientTable, clientGroup.id)) | ||||||
|  |                     .noEventPropagation() | ||||||
|  |                     .withSwitchAction( | ||||||
|  |                             actionBuilder.newAction(hideActionDef) | ||||||
|  |                                     .withExec( | ||||||
|  |                                             hideClientGroupViewAction(filter, clientTable, clientGroup.id)) | ||||||
|  |                                     .noEventPropagation() | ||||||
|  |                                     .withNameAttributes(clientGroup.name, numOfConnections) | ||||||
|  |                                     .create()) | ||||||
|  |                     .withNameAttributes(clientGroup.name, numOfConnections) | ||||||
|  |                     .create(); | ||||||
|  |         } else { | ||||||
|  |             action = actionBuilder.newAction(hideActionDef) | ||||||
|  |                     .withExec(hideClientGroupViewAction(filter, clientTable, clientGroup.id)) | ||||||
|  |                     .noEventPropagation() | ||||||
|  |                     .withSwitchAction( | ||||||
|  |                             actionBuilder.newAction(showActionDef) | ||||||
|  |                                     .withExec( | ||||||
|  |                                             showClientGroupAction(filter, clientTable, clientGroup.id)) | ||||||
|  |                                     .noEventPropagation() | ||||||
|  |                                     .withNameAttributes(clientGroup.name, numOfConnections) | ||||||
|  |                                     .create()) | ||||||
|  |                     .withNameAttributes(clientGroup.name, numOfConnections) | ||||||
|  |                     .create(); | ||||||
|  |         } | ||||||
|  |         this.pageService.publishAction( | ||||||
|  |                 action, | ||||||
|  |                 treeItem -> filterGUIUpdate.register(treeItem, clientGroup.id)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** This holds the filter action items and implements the specific GUI update for it */ |     /** This holds the filter action items and implements the specific GUI update for it */ | ||||||
|     private class StatusFilterGUIUpdate implements FullPageMonitoringGUIUpdate { |     private class FilterGUIUpdate implements FullPageMonitoringGUIUpdate { | ||||||
| 
 | 
 | ||||||
|         private final PolyglotPageService polyglotPageService; |         private final PolyglotPageService polyglotPageService; | ||||||
|         private final TreeItem[] actionItemPerStateFilter = new TreeItem[ConnectionStatus.values().length]; |         private final TreeItem[] actionItemPerStateFilter = new TreeItem[ConnectionStatus.values().length]; | ||||||
|  |         private final Map<Long, TreeItem> actionItemPerClientGroup = new HashMap<>(); | ||||||
| 
 | 
 | ||||||
|         public StatusFilterGUIUpdate(final PolyglotPageService polyglotPageService) { |         public FilterGUIUpdate(final PolyglotPageService polyglotPageService) { | ||||||
|             this.polyglotPageService = polyglotPageService; |             this.polyglotPageService = polyglotPageService; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -450,8 +511,12 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
|             this.actionItemPerStateFilter[status.code] = item; |             this.actionItemPerStateFilter[status.code] = item; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         void register(final TreeItem item, final Long clientGroupId) { | ||||||
|  |             this.actionItemPerClientGroup.put(clientGroupId, item); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         @Override |         @Override | ||||||
|         public void update(final MonitoringStatus monitoringStatus) { |         public void update(final MonitoringFilter monitoringStatus) { | ||||||
|             final ConnectionStatus[] states = ConnectionStatus.values(); |             final ConnectionStatus[] states = ConnectionStatus.values(); | ||||||
|             for (int i = 0; i < states.length; i++) { |             for (int i = 0; i < states.length; i++) { | ||||||
|                 final ConnectionStatus state = states[i]; |                 final ConnectionStatus state = states[i]; | ||||||
|  | @ -463,6 +528,18 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
|                     this.polyglotPageService.injectI18n(treeItem, action.getTitle()); |                     this.polyglotPageService.injectI18n(treeItem, action.getTitle()); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|  |             if (!this.actionItemPerClientGroup.isEmpty()) { | ||||||
|  |                 this.actionItemPerClientGroup.entrySet().stream().forEach(entry -> { | ||||||
|  |                     final int numOfConnections = monitoringStatus.getNumOfConnections(entry.getKey()); | ||||||
|  |                     if (numOfConnections >= 0) { | ||||||
|  |                         final TreeItem treeItem = entry.getValue(); | ||||||
|  |                         final PageAction action = (PageAction) treeItem.getData(ActionPane.ACTION_EVENT_CALL_KEY); | ||||||
|  |                         action.setTitleArgument(1, numOfConnections); | ||||||
|  |                         this.polyglotPageService.injectI18n(treeItem, action.getTitle()); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -472,7 +549,7 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static Function<PageAction, PageAction> showStateViewAction( |     private static Function<PageAction, PageAction> showStateViewAction( | ||||||
|             final MonitoringStatus monitoringStatus, |             final MonitoringFilter monitoringStatus, | ||||||
|             final ClientConnectionTable clientTable, |             final ClientConnectionTable clientTable, | ||||||
|             final ConnectionStatus status) { |             final ConnectionStatus status) { | ||||||
| 
 | 
 | ||||||
|  | @ -484,7 +561,7 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static Function<PageAction, PageAction> hideStateViewAction( |     private static Function<PageAction, PageAction> hideStateViewAction( | ||||||
|             final MonitoringStatus monitoringStatus, |             final MonitoringFilter monitoringStatus, | ||||||
|             final ClientConnectionTable clientTable, |             final ClientConnectionTable clientTable, | ||||||
|             final ConnectionStatus status) { |             final ConnectionStatus status) { | ||||||
| 
 | 
 | ||||||
|  | @ -495,6 +572,30 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private static Function<PageAction, PageAction> showClientGroupAction( | ||||||
|  |             final MonitoringFilter monitoringStatus, | ||||||
|  |             final ClientConnectionTable clientTable, | ||||||
|  |             final Long clientGroupId) { | ||||||
|  | 
 | ||||||
|  |         return action -> { | ||||||
|  |             monitoringStatus.showClientGroup(clientGroupId); | ||||||
|  |             clientTable.removeSelection(); | ||||||
|  |             return action; | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static Function<PageAction, PageAction> hideClientGroupViewAction( | ||||||
|  |             final MonitoringFilter monitoringStatus, | ||||||
|  |             final ClientConnectionTable clientTable, | ||||||
|  |             final Long clientGroupId) { | ||||||
|  | 
 | ||||||
|  |         return action -> { | ||||||
|  |             monitoringStatus.hideClientGroup(clientGroupId); | ||||||
|  |             clientTable.removeSelection(); | ||||||
|  |             return action; | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private Set<EntityKey> selectionForInstruction(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, | ||||||
|  |  | ||||||
|  | @ -312,10 +312,10 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void update(final MonitoringStatus monitoringStatus) { |     public void update(final MonitoringFilter monitoringStatus) { | ||||||
|         final Collection<ClientConnectionData> connectionData = monitoringStatus.getConnectionData(); |         final Collection<ClientConnectionData> connectionData = monitoringStatus.getConnectionData(); | ||||||
|         final boolean sizeChanged = connectionData.size() != this.table.getItemCount(); |         final boolean sizeChanged = connectionData.size() != this.table.getItemCount(); | ||||||
|         final boolean needsSync = monitoringStatus.statusFilterChanged() || |         final boolean needsSync = monitoringStatus.filterChanged() || | ||||||
|                 this.forceUpdateAll || |                 this.forceUpdateAll || | ||||||
|                 sizeChanged || |                 sizeChanged || | ||||||
|                 (this.tableMapping != null && |                 (this.tableMapping != null && | ||||||
|  | @ -350,7 +350,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|             monitoringStatus.resetStatusFilterChanged(); |             monitoringStatus.resetFilterChanged(); | ||||||
|             this.toDelete.clear(); |             this.toDelete.clear(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -624,7 +624,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate | ||||||
|         private String getGroupInfo() { |         private String getGroupInfo() { | ||||||
|             final StringBuilder sb = new StringBuilder(); |             final StringBuilder sb = new StringBuilder(); | ||||||
|             ClientConnectionTable.this.clientGroupMapping.keySet().stream().forEach(key -> { |             ClientConnectionTable.this.clientGroupMapping.keySet().stream().forEach(key -> { | ||||||
|                 if (this.connectionData.groups.contains(key)) { |                 if (this.connectionData.groups != null && this.connectionData.groups.contains(key)) { | ||||||
|                     final ClientGroup clientGroup = ClientConnectionTable.this.clientGroupMapping.get(key); |                     final ClientGroup clientGroup = ClientConnectionTable.this.clientGroupMapping.get(key); | ||||||
|                     sb.append(WidgetFactory.getTextWithBackgroundHTML(clientGroup.name, clientGroup.color)); |                     sb.append(WidgetFactory.getTextWithBackgroundHTML(clientGroup.name, clientGroup.color)); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -11,6 +11,6 @@ package ch.ethz.seb.sebserver.gui.service.session; | ||||||
| @FunctionalInterface | @FunctionalInterface | ||||||
| public interface FullPageMonitoringGUIUpdate { | public interface FullPageMonitoringGUIUpdate { | ||||||
| 
 | 
 | ||||||
|     void update(MonitoringStatus monitoringStatus); |     void update(MonitoringFilter monitoringStatus); | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | @ -10,7 +10,10 @@ package ch.ethz.seb.sebserver.gui.service.session; | ||||||
| 
 | 
 | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
|  | import java.util.Collections; | ||||||
| import java.util.EnumSet; | import java.util.EnumSet; | ||||||
|  | import java.util.HashSet; | ||||||
|  | import java.util.Set; | ||||||
| 
 | 
 | ||||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||||
| import org.eclipse.swt.widgets.Composite; | import org.eclipse.swt.widgets.Composite; | ||||||
|  | @ -20,6 +23,8 @@ import org.slf4j.LoggerFactory; | ||||||
| import ch.ethz.seb.sebserver.gbl.Constants; | import ch.ethz.seb.sebserver.gbl.Constants; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.API; | import ch.ethz.seb.sebserver.gbl.api.API; | ||||||
| import ch.ethz.seb.sebserver.gbl.async.AsyncRunner; | import ch.ethz.seb.sebserver.gbl.async.AsyncRunner; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; | ||||||
| import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringFullPageData; | import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringFullPageData; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Utils; | import ch.ethz.seb.sebserver.gbl.util.Utils; | ||||||
|  | @ -29,6 +34,7 @@ import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext; | ||||||
| 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.push.UpdateErrorHandler; | 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.exam.clientgroup.GetClientGroups; | ||||||
| import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetMonitoringFullPageData; | import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetMonitoringFullPageData; | ||||||
| import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.DisposedOAuth2RestTemplateException; | import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.DisposedOAuth2RestTemplateException; | ||||||
| 
 | 
 | ||||||
|  | @ -36,11 +42,12 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.DisposedOAuth2Re | ||||||
|  * full page monitoring. |  * full page monitoring. | ||||||
|  * |  * | ||||||
|  * This handles server push and GUI update and also implements kind of circuit breaker and error handling */ |  * This handles server push and GUI update and also implements kind of circuit breaker and error handling */ | ||||||
| public class FullPageMonitoringUpdate implements MonitoringStatus { | public class FullPageMonitoringUpdate implements MonitoringFilter { | ||||||
| 
 | 
 | ||||||
|     static final Logger log = LoggerFactory.getLogger(FullPageMonitoringUpdate.class); |     static final Logger log = LoggerFactory.getLogger(FullPageMonitoringUpdate.class); | ||||||
| 
 | 
 | ||||||
|     private static final String USER_SESSION_STATUS_FILTER_ATTRIBUTE = "USER_SESSION_STATUS_FILTER_ATTRIBUTE"; |     private static final String USER_SESSION_STATUS_FILTER_ATTRIBUTE = "USER_SESSION_STATUS_FILTER"; | ||||||
|  |     private static final String USER_SESSION_GROUP_FILTER_ATTRIBUTE = "USER_SESSION_GROUP_FILTER"; | ||||||
| 
 | 
 | ||||||
|     private final ServerPushService serverPushService; |     private final ServerPushService serverPushService; | ||||||
|     private final PageService pageService; |     private final PageService pageService; | ||||||
|  | @ -49,9 +56,13 @@ public class FullPageMonitoringUpdate implements MonitoringStatus { | ||||||
|     private final Collection<FullPageMonitoringGUIUpdate> guiUpdates; |     private final Collection<FullPageMonitoringGUIUpdate> guiUpdates; | ||||||
| 
 | 
 | ||||||
|     private ServerPushContext pushContext; |     private ServerPushContext pushContext; | ||||||
|  | 
 | ||||||
|     private final EnumSet<ConnectionStatus> statusFilter; |     private final EnumSet<ConnectionStatus> statusFilter; | ||||||
|     private String statusFilterParam = ""; |     private String statusFilterParam = ""; | ||||||
|     private boolean statusFilterChanged = false; |     private final Set<Long> clientGroupFilter; | ||||||
|  |     private String clientGroupFilterParam = ""; | ||||||
|  |     private boolean filterChanged = false; | ||||||
|  | 
 | ||||||
|     private boolean updateInProgress = false; |     private boolean updateInProgress = false; | ||||||
|     private MonitoringFullPageData monitoringFullPageData = null; |     private MonitoringFullPageData monitoringFullPageData = null; | ||||||
| 
 | 
 | ||||||
|  | @ -72,7 +83,19 @@ public class FullPageMonitoringUpdate implements MonitoringStatus { | ||||||
|         this.guiUpdates = guiUpdates; |         this.guiUpdates = guiUpdates; | ||||||
| 
 | 
 | ||||||
|         this.statusFilter = EnumSet.noneOf(ConnectionStatus.class); |         this.statusFilter = EnumSet.noneOf(ConnectionStatus.class); | ||||||
|         loadStatusFilter(); |         loadFilter(); | ||||||
|  | 
 | ||||||
|  |         final Collection<ClientGroup> clientGroups = pageService.getRestService() | ||||||
|  |                 .getBuilder(GetClientGroups.class) | ||||||
|  |                 .withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, String.valueOf(examId)) | ||||||
|  |                 .call() | ||||||
|  |                 .getOr(Collections.emptyList()); | ||||||
|  | 
 | ||||||
|  |         if (clientGroups != null && !clientGroups.isEmpty()) { | ||||||
|  |             this.clientGroupFilter = new HashSet<>(); | ||||||
|  |         } else { | ||||||
|  |             this.clientGroupFilter = null; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void start(final PageContext pageContext, final Composite anchor, final long pollInterval) { |     public void start(final PageContext pageContext, final Composite anchor, final long pollInterval) { | ||||||
|  | @ -105,13 +128,13 @@ public class FullPageMonitoringUpdate implements MonitoringStatus { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public boolean statusFilterChanged() { |     public boolean filterChanged() { | ||||||
|         return this.statusFilterChanged; |         return this.filterChanged; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void resetStatusFilterChanged() { |     public void resetFilterChanged() { | ||||||
|         this.statusFilterChanged = false; |         this.filterChanged = false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|  | @ -122,13 +145,43 @@ public class FullPageMonitoringUpdate implements MonitoringStatus { | ||||||
|     @Override |     @Override | ||||||
|     public void hideStatus(final ConnectionStatus status) { |     public void hideStatus(final ConnectionStatus status) { | ||||||
|         this.statusFilter.add(status); |         this.statusFilter.add(status); | ||||||
|         saveStatusFilter(); |         saveFilter(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void showStatus(final ConnectionStatus status) { |     public void showStatus(final ConnectionStatus status) { | ||||||
|         this.statusFilter.remove(status); |         this.statusFilter.remove(status); | ||||||
|         saveStatusFilter(); |         saveFilter(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public boolean hasClientGroupFilter() { | ||||||
|  |         return this.clientGroupFilter != null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public boolean isClientGroupHidden(final Long clientGroupId) { | ||||||
|  |         return this.clientGroupFilter != null && this.clientGroupFilter.contains(clientGroupId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void hideClientGroup(final Long clientGroupId) { | ||||||
|  |         if (this.clientGroupFilter == null) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.clientGroupFilter.add(clientGroupId); | ||||||
|  |         saveFilter(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void showClientGroup(final Long clientGroupId) { | ||||||
|  |         if (this.clientGroupFilter == null) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.clientGroupFilter.remove(clientGroupId); | ||||||
|  |         saveFilter(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|  | @ -161,8 +214,15 @@ public class FullPageMonitoringUpdate implements MonitoringStatus { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void updateBusinessData() { |     private void updateBusinessData() { | ||||||
|         this.monitoringFullPageData = this.restCallBuilder | 
 | ||||||
|                 .withHeader(API.EXAM_MONITORING_STATE_FILTER, this.statusFilterParam) |         RestCall<MonitoringFullPageData>.RestCallBuilder restCallBuilder = this.restCallBuilder | ||||||
|  |                 .withHeader(API.EXAM_MONITORING_STATE_FILTER, this.statusFilterParam); | ||||||
|  |         if (hasClientGroupFilter()) { | ||||||
|  |             restCallBuilder = restCallBuilder | ||||||
|  |                     .withHeader(API.EXAM_MONITORING_CLIENT_GROUP_FILTER, this.clientGroupFilterParam); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.monitoringFullPageData = restCallBuilder | ||||||
|                 .call() |                 .call() | ||||||
|                 .get(error -> { |                 .get(error -> { | ||||||
|                     recoverFromDisposedRestTemplate(error); |                     recoverFromDisposedRestTemplate(error); | ||||||
|  | @ -182,22 +242,32 @@ public class FullPageMonitoringUpdate implements MonitoringStatus { | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void saveStatusFilter() { |     private void saveFilter() { | ||||||
|         try { |         try { | ||||||
|             this.pageService |             this.pageService | ||||||
|                     .getCurrentUser() |                     .getCurrentUser() | ||||||
|                     .putAttribute( |                     .putAttribute( | ||||||
|                             USER_SESSION_STATUS_FILTER_ATTRIBUTE, |                             USER_SESSION_STATUS_FILTER_ATTRIBUTE, | ||||||
|                             StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR)); |                             StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR)); | ||||||
|  |             if (hasClientGroupFilter()) { | ||||||
|  |                 this.pageService | ||||||
|  |                         .getCurrentUser() | ||||||
|  |                         .putAttribute( | ||||||
|  |                                 USER_SESSION_GROUP_FILTER_ATTRIBUTE, | ||||||
|  |                                 StringUtils.join(this.clientGroupFilter, Constants.LIST_SEPARATOR)); | ||||||
|  |             } | ||||||
|         } catch (final Exception e) { |         } catch (final Exception e) { | ||||||
|             log.warn("Failed to save status filter to user session"); |             log.warn("Failed to save status filter to user session"); | ||||||
|         } finally { |         } finally { | ||||||
|             this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR); |             this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR); | ||||||
|             this.statusFilterChanged = true; |             if (hasClientGroupFilter()) { | ||||||
|  |                 this.clientGroupFilterParam = StringUtils.join(this.clientGroupFilter, Constants.LIST_SEPARATOR); | ||||||
|  |             } | ||||||
|  |             this.filterChanged = true; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void loadStatusFilter() { |     private void loadFilter() { | ||||||
|         try { |         try { | ||||||
|             final String attribute = this.pageService |             final String attribute = this.pageService | ||||||
|                     .getCurrentUser() |                     .getCurrentUser() | ||||||
|  | @ -206,17 +276,33 @@ public class FullPageMonitoringUpdate implements MonitoringStatus { | ||||||
|             if (attribute != null) { |             if (attribute != null) { | ||||||
|                 Arrays.asList(StringUtils.split(attribute, Constants.LIST_SEPARATOR)) |                 Arrays.asList(StringUtils.split(attribute, Constants.LIST_SEPARATOR)) | ||||||
|                         .forEach(name -> this.statusFilter.add(ConnectionStatus.valueOf(name))); |                         .forEach(name -> this.statusFilter.add(ConnectionStatus.valueOf(name))); | ||||||
| 
 |  | ||||||
|             } else { |             } else { | ||||||
|                 this.statusFilter.add(ConnectionStatus.DISABLED); |                 this.statusFilter.add(ConnectionStatus.DISABLED); | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|  |             if (hasClientGroupFilter()) { | ||||||
|  |                 final String groups = this.pageService | ||||||
|  |                         .getCurrentUser() | ||||||
|  |                         .getAttribute(USER_SESSION_GROUP_FILTER_ATTRIBUTE); | ||||||
|  |                 this.statusFilter.clear(); | ||||||
|  |                 if (groups != null) { | ||||||
|  |                     Arrays.asList(StringUtils.split(groups, Constants.LIST_SEPARATOR)) | ||||||
|  |                             .forEach(id -> this.clientGroupFilter.add(Long.parseLong(id))); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         } catch (final Exception e) { |         } catch (final Exception e) { | ||||||
|             log.warn("Failed to load status filter to user session"); |             log.warn("Failed to load status filter to user session"); | ||||||
|             this.statusFilter.clear(); |             this.statusFilter.clear(); | ||||||
|             this.statusFilter.add(ConnectionStatus.DISABLED); |             this.statusFilter.add(ConnectionStatus.DISABLED); | ||||||
|  |             if (hasClientGroupFilter()) { | ||||||
|  |                 this.clientGroupFilter.clear(); | ||||||
|  |             } | ||||||
|         } finally { |         } finally { | ||||||
|             this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR); |             this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR); | ||||||
|             this.statusFilterChanged = true; |             if (hasClientGroupFilter()) { | ||||||
|  |                 this.clientGroupFilterParam = StringUtils.join(this.clientGroupFilter, Constants.LIST_SEPARATOR); | ||||||
|  |             } | ||||||
|  |             this.filterChanged = true; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -12,21 +12,22 @@ import java.util.Collection; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| import java.util.EnumSet; | import java.util.EnumSet; | ||||||
| 
 | 
 | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; | ||||||
| import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringFullPageData; |  | ||||||
| import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringSEBConnectionData; |  | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom; | import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringFullPageData; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringSEBConnectionData; | ||||||
| 
 | 
 | ||||||
| public interface MonitoringStatus { | public interface MonitoringFilter { | ||||||
| 
 | 
 | ||||||
|     EnumSet<ConnectionStatus> getStatusFilter(); |     EnumSet<ConnectionStatus> getStatusFilter(); | ||||||
| 
 | 
 | ||||||
|     String getStatusFilterParam(); |     String getStatusFilterParam(); | ||||||
| 
 | 
 | ||||||
|     boolean statusFilterChanged(); |     boolean filterChanged(); | ||||||
| 
 | 
 | ||||||
|     void resetStatusFilterChanged(); |     void resetFilterChanged(); | ||||||
| 
 | 
 | ||||||
|     boolean isStatusHidden(ConnectionStatus status); |     boolean isStatusHidden(ConnectionStatus status); | ||||||
| 
 | 
 | ||||||
|  | @ -34,6 +35,18 @@ public interface MonitoringStatus { | ||||||
| 
 | 
 | ||||||
|     void showStatus(ConnectionStatus status); |     void showStatus(ConnectionStatus status); | ||||||
| 
 | 
 | ||||||
|  |     boolean hasClientGroupFilter(); | ||||||
|  | 
 | ||||||
|  |     default boolean isClientGroupHidden(final ClientGroup clientGroup) { | ||||||
|  |         return isClientGroupHidden(clientGroup.id); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     boolean isClientGroupHidden(Long clientGroupId); | ||||||
|  | 
 | ||||||
|  |     void hideClientGroup(Long clientGroupId); | ||||||
|  | 
 | ||||||
|  |     void showClientGroup(Long clientGroupId); | ||||||
|  | 
 | ||||||
|     MonitoringFullPageData getMonitoringFullPageData(); |     MonitoringFullPageData getMonitoringFullPageData(); | ||||||
| 
 | 
 | ||||||
|     default MonitoringSEBConnectionData getMonitoringSEBConnectionData() { |     default MonitoringSEBConnectionData getMonitoringSEBConnectionData() { | ||||||
|  | @ -63,6 +76,15 @@ public interface MonitoringStatus { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     default int getNumOfConnections(final Long clientGroupId) { | ||||||
|  |         final MonitoringSEBConnectionData monitoringSEBConnectionData = getMonitoringSEBConnectionData(); | ||||||
|  |         if (monitoringSEBConnectionData != null) { | ||||||
|  |             return monitoringSEBConnectionData.getNumberOfConnection(clientGroupId); | ||||||
|  |         } else { | ||||||
|  |             return 0; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     default Collection<RemoteProctoringRoom> proctoringData() { |     default Collection<RemoteProctoringRoom> proctoringData() { | ||||||
|         final MonitoringFullPageData monitoringFullPageData = getMonitoringFullPageData(); |         final MonitoringFullPageData monitoringFullPageData = getMonitoringFullPageData(); | ||||||
|         if (monitoringFullPageData != null) { |         if (monitoringFullPageData != null) { | ||||||
|  | @ -194,6 +194,7 @@ public class ClientGroupDAOImpl implements ClientGroupDAO { | ||||||
|                 .stream() |                 .stream() | ||||||
|                 .map(this::toDomainModel) |                 .map(this::toDomainModel) | ||||||
|                 .flatMap(DAOLoggingSupport::logAndSkipOnError) |                 .flatMap(DAOLoggingSupport::logAndSkipOnError) | ||||||
|  |                 .sorted() | ||||||
|                 .collect(Collectors.toList())); |                 .collect(Collectors.toList())); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -39,6 +39,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService | ||||||
| @WebServiceProfile | @WebServiceProfile | ||||||
| public class ExamSessionCacheService { | public class ExamSessionCacheService { | ||||||
| 
 | 
 | ||||||
|  |     public static final Object CLIENT_CONECTION_CREATION_LOCK = new Object(); | ||||||
|  | 
 | ||||||
|     public static final String CACHE_NAME_RUNNING_EXAM = "RUNNING_EXAM"; |     public static final String CACHE_NAME_RUNNING_EXAM = "RUNNING_EXAM"; | ||||||
|     public static final String CACHE_NAME_ACTIVE_CLIENT_CONNECTION = "ACTIVE_CLIENT_CONNECTION"; |     public static final String CACHE_NAME_ACTIVE_CLIENT_CONNECTION = "ACTIVE_CLIENT_CONNECTION"; | ||||||
|     public static final String CACHE_NAME_SEB_CONFIG_EXAM = "SEB_CONFIG_EXAM"; |     public static final String CACHE_NAME_SEB_CONFIG_EXAM = "SEB_CONFIG_EXAM"; | ||||||
|  |  | ||||||
|  | @ -13,7 +13,9 @@ import java.io.OutputStream; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
|  | import java.util.HashMap; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
| import java.util.NoSuchElementException; | import java.util.NoSuchElementException; | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
|  | @ -34,6 +36,7 @@ import ch.ethz.seb.sebserver.gbl.Constants; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.APIMessage; | import ch.ethz.seb.sebserver.gbl.api.APIMessage; | ||||||
| import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage; | import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.Entity; | import ch.ethz.seb.sebserver.gbl.model.Entity; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.exam.Exam; | import ch.ethz.seb.sebserver.gbl.model.exam.Exam; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus; | import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; | import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; | ||||||
|  | @ -43,6 +46,7 @@ import ch.ethz.seb.sebserver.gbl.monitoring.MonitoringSEBConnectionData; | ||||||
| import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Result; | import ch.ethz.seb.sebserver.gbl.util.Result; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; | import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; | ||||||
|  | import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientGroupDAO; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO; | import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; | import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; | ||||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; | import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; | ||||||
|  | @ -64,6 +68,7 @@ public class ExamSessionServiceImpl implements ExamSessionService { | ||||||
|     private final IndicatorDAO indicatorDAO; |     private final IndicatorDAO indicatorDAO; | ||||||
|     private final ExamSessionCacheService examSessionCacheService; |     private final ExamSessionCacheService examSessionCacheService; | ||||||
|     private final ExamDAO examDAO; |     private final ExamDAO examDAO; | ||||||
|  |     private final ClientGroupDAO clientGroupDAO; | ||||||
|     private final ExamConfigurationMapDAO examConfigurationMapDAO; |     private final ExamConfigurationMapDAO examConfigurationMapDAO; | ||||||
|     private final CacheManager cacheManager; |     private final CacheManager cacheManager; | ||||||
|     private final SEBRestrictionService sebRestrictionService; |     private final SEBRestrictionService sebRestrictionService; | ||||||
|  | @ -76,6 +81,7 @@ public class ExamSessionServiceImpl implements ExamSessionService { | ||||||
|     protected ExamSessionServiceImpl( |     protected ExamSessionServiceImpl( | ||||||
|             final ExamSessionCacheService examSessionCacheService, |             final ExamSessionCacheService examSessionCacheService, | ||||||
|             final ExamDAO examDAO, |             final ExamDAO examDAO, | ||||||
|  |             final ClientGroupDAO clientGroupDAO, | ||||||
|             final ExamConfigurationMapDAO examConfigurationMapDAO, |             final ExamConfigurationMapDAO examConfigurationMapDAO, | ||||||
|             final ClientConnectionDAO clientConnectionDAO, |             final ClientConnectionDAO clientConnectionDAO, | ||||||
|             final IndicatorDAO indicatorDAO, |             final IndicatorDAO indicatorDAO, | ||||||
|  | @ -87,6 +93,7 @@ public class ExamSessionServiceImpl implements ExamSessionService { | ||||||
| 
 | 
 | ||||||
|         this.examSessionCacheService = examSessionCacheService; |         this.examSessionCacheService = examSessionCacheService; | ||||||
|         this.examDAO = examDAO; |         this.examDAO = examDAO; | ||||||
|  |         this.clientGroupDAO = clientGroupDAO; | ||||||
|         this.examConfigurationMapDAO = examConfigurationMapDAO; |         this.examConfigurationMapDAO = examConfigurationMapDAO; | ||||||
|         this.clientConnectionDAO = clientConnectionDAO; |         this.clientConnectionDAO = clientConnectionDAO; | ||||||
|         this.cacheManager = cacheManager; |         this.cacheManager = cacheManager; | ||||||
|  | @ -350,7 +357,7 @@ public class ExamSessionServiceImpl implements ExamSessionService { | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public ClientConnectionDataInternal getConnectionDataInternal(final String connectionToken) { |     public ClientConnectionDataInternal getConnectionDataInternal(final String connectionToken) { | ||||||
|         synchronized (this.examSessionCacheService) { |         synchronized (ExamSessionCacheService.CLIENT_CONECTION_CREATION_LOCK) { | ||||||
|             return this.examSessionCacheService.getClientConnection(connectionToken); |             return this.examSessionCacheService.getClientConnection(connectionToken); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -405,25 +412,33 @@ public class ExamSessionServiceImpl implements ExamSessionService { | ||||||
|             for (int i = 0; i < statusMapping.length; i++) { |             for (int i = 0; i < statusMapping.length; i++) { | ||||||
|                 statusMapping[i] = 0; |                 statusMapping[i] = 0; | ||||||
|             } |             } | ||||||
|  |             // needed to store connection numbers per client group too | ||||||
|  |             final Collection<ClientGroup> groups = this.clientGroupDAO.allForExam(examId).getOr(null); | ||||||
|  |             final Map<Long, Integer> clientGroupMapping = (groups != null && !groups.isEmpty()) | ||||||
|  |                     ? new HashMap<>() | ||||||
|  |                     : null; | ||||||
| 
 | 
 | ||||||
|             updateClientConnections(examId); |             updateClientConnections(examId); | ||||||
| 
 | 
 | ||||||
|             synchronized (this.examSessionCacheService) { |             final List<ClientConnectionData> filteredConnections = this.clientConnectionDAO | ||||||
|                 final List<ClientConnectionData> filteredConnections = this.clientConnectionDAO |                     .getConnectionTokens(examId) | ||||||
|                         .getConnectionTokens(examId) |                     .getOrThrow() | ||||||
|                         .getOrThrow() |                     .stream() | ||||||
|                         .stream() |                     .map(token -> getConnectionDataInternal(token)) | ||||||
|                         .map(token -> this.examSessionCacheService.getClientConnection(token)) |                     .filter(Objects::nonNull) | ||||||
|                         .filter(Objects::nonNull) |                     .map(c -> { | ||||||
|                         .map(c -> { |                         statusMapping[c.clientConnection.status.code]++; | ||||||
|                             statusMapping[c.clientConnection.status.code]++; |                         processClientGroupMapping(c.groups, clientGroupMapping); | ||||||
|                             return c; |                         return c; | ||||||
|                         }) |                     }) | ||||||
|                         .filter(filter) |                     .filter(filter) | ||||||
|                         .collect(Collectors.toList()); |                     .collect(Collectors.toList()); | ||||||
| 
 | 
 | ||||||
|                 return new MonitoringSEBConnectionData(examId, filteredConnections, statusMapping); |             return new MonitoringSEBConnectionData( | ||||||
|             } |                     examId, | ||||||
|  |                     filteredConnections, | ||||||
|  |                     statusMapping, | ||||||
|  |                     clientGroupMapping); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -557,4 +572,18 @@ public class ExamSessionServiceImpl implements ExamSessionService { | ||||||
|                 && Objects.equals(exam.name, runningExam.name); |                 && Objects.equals(exam.name, runningExam.name); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private void processClientGroupMapping(final Set<Long> groups, final Map<Long, Integer> clientGroupMapping) { | ||||||
|  |         if (groups == null || clientGroupMapping == null) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         groups.forEach(id -> { | ||||||
|  |             if (clientGroupMapping.containsKey(id)) { | ||||||
|  |                 clientGroupMapping.put(id, clientGroupMapping.get(id) + 1); | ||||||
|  |             } else { | ||||||
|  |                 clientGroupMapping.put(id, 1); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -692,8 +692,10 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic | ||||||
| 
 | 
 | ||||||
|     private void processPing(final String connectionToken, final long timestamp, final int pingNumber) { |     private void processPing(final String connectionToken, final long timestamp, final int pingNumber) { | ||||||
| 
 | 
 | ||||||
|         final ClientConnectionDataInternal activeClientConnection = |         ClientConnectionDataInternal activeClientConnection = null; | ||||||
|                 this.examSessionCacheService.getClientConnection(connectionToken); |         synchronized (ExamSessionCacheService.CLIENT_CONECTION_CREATION_LOCK) { | ||||||
|  |             activeClientConnection = this.examSessionCacheService.getClientConnection(connectionToken); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         if (activeClientConnection != null) { |         if (activeClientConnection != null) { | ||||||
|             activeClientConnection.notifyPing(timestamp, pingNumber); |             activeClientConnection.notifyPing(timestamp, pingNumber); | ||||||
|  |  | ||||||
|  | @ -11,7 +11,9 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| import java.util.EnumSet; | import java.util.EnumSet; | ||||||
|  | import java.util.HashSet; | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
|  | import java.util.Set; | ||||||
| import java.util.concurrent.Executor; | import java.util.concurrent.Executor; | ||||||
| import java.util.function.Predicate; | import java.util.function.Predicate; | ||||||
| 
 | 
 | ||||||
|  | @ -248,31 +250,17 @@ public class ExamMonitoringController { | ||||||
|                     required = true, |                     required = true, | ||||||
|                     defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, |                     defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, | ||||||
|             @PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId, |             @PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId, | ||||||
|             @RequestHeader(name = API.EXAM_MONITORING_STATE_FILTER, required = false) final String hiddenStates) { |             @RequestHeader(name = API.EXAM_MONITORING_STATE_FILTER, required = false) final String hiddenStates, | ||||||
|  |             @RequestHeader( | ||||||
|  |                     name = API.EXAM_MONITORING_CLIENT_GROUP_FILTER, | ||||||
|  |                     required = false) final String hiddenClientGroups) { | ||||||
| 
 | 
 | ||||||
|         checkPrivileges(institutionId, examId); |         checkPrivileges(institutionId, examId); | ||||||
| 
 | 
 | ||||||
|         final EnumSet<ConnectionStatus> filterStates = EnumSet.noneOf(ConnectionStatus.class); |  | ||||||
|         if (StringUtils.isNoneBlank(hiddenStates)) { |  | ||||||
|             final String[] split = StringUtils.split(hiddenStates, Constants.LIST_SEPARATOR); |  | ||||||
|             for (int i = 0; i < split.length; i++) { |  | ||||||
|                 filterStates.add(ConnectionStatus.valueOf(split[i])); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         final boolean active = filterStates.contains(ConnectionStatus.ACTIVE); |  | ||||||
|         if (active) { |  | ||||||
|             filterStates.remove(ConnectionStatus.ACTIVE); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return this.examSessionService |         return this.examSessionService | ||||||
|                 .getMonitoringSEBConnectionsData( |                 .getMonitoringSEBConnectionsData( | ||||||
|                         examId, |                         examId, | ||||||
|                         filterStates.isEmpty() |                         createMonitoringFilter(hiddenStates, hiddenClientGroups)) | ||||||
|                                 ? Objects::nonNull |  | ||||||
|                                 : active |  | ||||||
|                                         ? withActiveFilter(filterStates) |  | ||||||
|                                         : noneActiveFilter(filterStates)) |  | ||||||
|                 .getOrThrow().connections; |                 .getOrThrow().connections; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -288,31 +276,17 @@ public class ExamMonitoringController { | ||||||
|                     required = true, |                     required = true, | ||||||
|                     defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, |                     defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, | ||||||
|             @PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId, |             @PathVariable(name = API.PARAM_PARENT_MODEL_ID, required = true) final Long examId, | ||||||
|             @RequestHeader(name = API.EXAM_MONITORING_STATE_FILTER, required = false) final String hiddenStates) { |             @RequestHeader(name = API.EXAM_MONITORING_STATE_FILTER, required = false) final String hiddenStates, | ||||||
|  |             @RequestHeader( | ||||||
|  |                     name = API.EXAM_MONITORING_CLIENT_GROUP_FILTER, | ||||||
|  |                     required = false) final String hiddenClientGroups) { | ||||||
| 
 | 
 | ||||||
|         final Exam runningExam = checkPrivileges(institutionId, examId); |         final Exam runningExam = checkPrivileges(institutionId, examId); | ||||||
| 
 | 
 | ||||||
|         final EnumSet<ConnectionStatus> filterStates = EnumSet.noneOf(ConnectionStatus.class); |  | ||||||
|         if (StringUtils.isNoneBlank(hiddenStates)) { |  | ||||||
|             final String[] split = StringUtils.split(hiddenStates, Constants.LIST_SEPARATOR); |  | ||||||
|             for (int i = 0; i < split.length; i++) { |  | ||||||
|                 filterStates.add(ConnectionStatus.valueOf(split[i])); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         final boolean active = filterStates.contains(ConnectionStatus.ACTIVE); |  | ||||||
|         if (active) { |  | ||||||
|             filterStates.remove(ConnectionStatus.ACTIVE); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         final MonitoringSEBConnectionData monitoringSEBConnectionData = this.examSessionService |         final MonitoringSEBConnectionData monitoringSEBConnectionData = this.examSessionService | ||||||
|                 .getMonitoringSEBConnectionsData( |                 .getMonitoringSEBConnectionsData( | ||||||
|                         examId, |                         examId, | ||||||
|                         filterStates.isEmpty() |                         createMonitoringFilter(hiddenStates, hiddenClientGroups)) | ||||||
|                                 ? Objects::nonNull |  | ||||||
|                                 : active |  | ||||||
|                                         ? withActiveFilter(filterStates) |  | ||||||
|                                         : noneActiveFilter(filterStates)) |  | ||||||
|                 .getOrThrow(); |                 .getOrThrow(); | ||||||
| 
 | 
 | ||||||
|         if (this.examAdminService.isProctoringEnabled(runningExam).getOr(false)) { |         if (this.examAdminService.isProctoringEnabled(runningExam).getOr(false)) { | ||||||
|  | @ -509,4 +483,46 @@ public class ExamMonitoringController { | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private Predicate<ClientConnectionData> createMonitoringFilter( | ||||||
|  |             final String hiddenStates, | ||||||
|  |             final String hiddenClientGroups) { | ||||||
|  | 
 | ||||||
|  |         final EnumSet<ConnectionStatus> filterStates = EnumSet.noneOf(ConnectionStatus.class); | ||||||
|  |         if (StringUtils.isNotBlank(hiddenStates)) { | ||||||
|  |             final String[] split = StringUtils.split(hiddenStates, Constants.LIST_SEPARATOR); | ||||||
|  |             for (int i = 0; i < split.length; i++) { | ||||||
|  |                 filterStates.add(ConnectionStatus.valueOf(split[i])); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         final boolean active = filterStates.contains(ConnectionStatus.ACTIVE); | ||||||
|  |         if (active) { | ||||||
|  |             filterStates.remove(ConnectionStatus.ACTIVE); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         final Predicate<ClientConnectionData> stateFilter = filterStates.isEmpty() | ||||||
|  |                 ? Objects::nonNull | ||||||
|  |                 : active | ||||||
|  |                         ? withActiveFilter(filterStates) | ||||||
|  |                         : noneActiveFilter(filterStates); | ||||||
|  | 
 | ||||||
|  |         Set<Long> filterClientGroups = null; | ||||||
|  |         if (StringUtils.isNotBlank(hiddenClientGroups)) { | ||||||
|  |             filterClientGroups = new HashSet<>(); | ||||||
|  |             final String[] split = StringUtils.split(hiddenClientGroups, Constants.LIST_SEPARATOR); | ||||||
|  |             for (int i = 0; i < split.length; i++) { | ||||||
|  |                 filterClientGroups.add(Long.parseLong(split[i])); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         final Set<Long> _filterClientGroups = filterClientGroups; | ||||||
|  |         final Predicate<ClientConnectionData> filter = ccd -> { | ||||||
|  |             if (ccd == null) { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |             return stateFilter.test(ccd) && ccd.filter(_filterClientGroups); | ||||||
|  |         }; | ||||||
|  |         return filter; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1885,7 +1885,8 @@ sebserver.monitoring.exam.list.actions= | ||||||
| sebserver.monitoring.exam.action.detail.view=Back To Monitoring | sebserver.monitoring.exam.action.detail.view=Back To Monitoring | ||||||
| sebserver.monitoring.exam.action.list.view=Monitoring | sebserver.monitoring.exam.action.list.view=Monitoring | ||||||
| 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.monitoring.action.category.statefilter=State Filter | ||||||
|  | sebserver.exam.monitoring.action.category.groupfilter=Client Group Filter | ||||||
| sebserver.exam.overall.action.category.proctoring=Proctoring | sebserver.exam.overall.action.category.proctoring=Proctoring | ||||||
| sebserver.monitoring.exam.action.proctoring.openTownhall=Open Townhall | sebserver.monitoring.exam.action.proctoring.openTownhall=Open Townhall | ||||||
| sebserver.monitoring.exam.action.proctoring.showTownhall=Show Townhall | sebserver.monitoring.exam.action.proctoring.showTownhall=Show Townhall | ||||||
|  | @ -1971,6 +1972,8 @@ sebserver.monitoring.exam.connection.action.hide.disabled=Hide Canceled ( {0} ) | ||||||
| sebserver.monitoring.exam.connection.action.show.disabled=Show Canceled ( {0} ) | sebserver.monitoring.exam.connection.action.show.disabled=Show Canceled ( {0} ) | ||||||
| sebserver.monitoring.exam.connection.action.hide.undefined=Hide Undefined ( {0} ) | sebserver.monitoring.exam.connection.action.hide.undefined=Hide Undefined ( {0} ) | ||||||
| sebserver.monitoring.exam.connection.action.show.undefined=Show Undefined ( {0} ) | sebserver.monitoring.exam.connection.action.show.undefined=Show Undefined ( {0} ) | ||||||
|  | sebserver.monitoring.exam.connection.action.hide.clientgroup=Hide {0} ( {1} ) | ||||||
|  | sebserver.monitoring.exam.connection.action.show.clientgroup=Show {0} ( {1} ) | ||||||
| sebserver.monitoring.exam.connection.action.proctoring=Single Room Proctoring | sebserver.monitoring.exam.connection.action.proctoring=Single Room Proctoring | ||||||
| sebserver.monitoring.exam.connection.action.proctoring.examroom=Exam Room Proctoring | sebserver.monitoring.exam.connection.action.proctoring.examroom=Exam Room Proctoring | ||||||
| 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? | ||||||
|  | @ -1978,7 +1981,6 @@ sebserver.monitoring.exam.connection.action.closeTownhall.confirm=You are about | ||||||
| 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.actions.group2=  | sebserver.monitoring.exam.connection.actions.group2=  | ||||||
| sebserver.monitoring.exam.connection.actions.group3=  | sebserver.monitoring.exam.connection.actions.group3=  | ||||||
| 
 |  | ||||||
| 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 | ||||||
| sebserver.monitoring.exam.connection.action.confirm.notification.text=Are you sure you want to confirm this pending notification?<br/><br/>Note that this will send a notification confirmation instruction to the SEB client and remove this notification from the pending list. | sebserver.monitoring.exam.connection.action.confirm.notification.text=Are you sure you want to confirm this pending notification?<br/><br/>Note that this will send a notification confirmation instruction to the SEB client and remove this notification from the pending list. | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 anhefti
						anhefti