fixed quit JSON
This commit is contained in:
		
							parent
							
								
									857706c7b7
								
							
						
					
					
						commit
						1ecaa132a9
					
				
					 18 changed files with 427 additions and 220 deletions
				
			
		|  | @ -57,6 +57,7 @@ public final class Constants { | ||||||
|     public static final Character LIST_SEPARATOR_CHAR = COMMA; |     public static final Character LIST_SEPARATOR_CHAR = COMMA; | ||||||
|     public static final Character COMPLEX_VALUE_SEPARATOR = COLON; |     public static final Character COMPLEX_VALUE_SEPARATOR = COLON; | ||||||
| 
 | 
 | ||||||
|  |     public static final String NULL = "null"; | ||||||
|     public static final String PERCENTAGE_STRING = Constants.PERCENTAGE.toString(); |     public static final String PERCENTAGE_STRING = Constants.PERCENTAGE.toString(); | ||||||
|     public static final String LIST_SEPARATOR = COMMA.toString(); |     public static final String LIST_SEPARATOR = COMMA.toString(); | ||||||
|     public static final String EMBEDDED_LIST_SEPARATOR = PIPE.toString(); |     public static final String EMBEDDED_LIST_SEPARATOR = PIPE.toString(); | ||||||
|  |  | ||||||
|  | @ -157,6 +157,7 @@ public final class API { | ||||||
|     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_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 + "}"; | ||||||
|  |     public static final String EXAM_MONITORING_STATE_FILTER = "hidden-states"; | ||||||
| 
 | 
 | ||||||
|     public static final String SEB_CLIENT_CONNECTION_ENDPOINT = "/seb-client-connection"; |     public static final String SEB_CLIENT_CONNECTION_ENDPOINT = "/seb-client-connection"; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,155 @@ | ||||||
|  | /* | ||||||
|  |  * Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET) | ||||||
|  |  * | ||||||
|  |  * This Source Code Form is subject to the terms of the Mozilla Public | ||||||
|  |  * License, v. 2.0. If a copy of the MPL was not distributed with this | ||||||
|  |  * file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package ch.ethz.seb.sebserver.gui.content; | ||||||
|  | 
 | ||||||
|  | import org.eclipse.swt.widgets.Composite; | ||||||
|  | import org.springframework.beans.factory.annotation.Value; | ||||||
|  | import org.springframework.context.annotation.Lazy; | ||||||
|  | import org.springframework.stereotype.Component; | ||||||
|  | 
 | ||||||
|  | import ch.ethz.seb.sebserver.gbl.api.EntityType; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.Domain; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.Entity; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.model.user.UserRole; | ||||||
|  | import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; | ||||||
|  | import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition; | ||||||
|  | import ch.ethz.seb.sebserver.gui.service.ResourceService; | ||||||
|  | import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; | ||||||
|  | import ch.ethz.seb.sebserver.gui.service.page.PageContext; | ||||||
|  | import ch.ethz.seb.sebserver.gui.service.page.PageService; | ||||||
|  | import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder; | ||||||
|  | import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; | ||||||
|  | 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.seb.examconfig.GetExamConfigNodePage; | ||||||
|  | import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; | ||||||
|  | import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck; | ||||||
|  | import ch.ethz.seb.sebserver.gui.table.ColumnDefinition; | ||||||
|  | import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute; | ||||||
|  | import ch.ethz.seb.sebserver.gui.table.EntityTable; | ||||||
|  | import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType; | ||||||
|  | import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; | ||||||
|  | 
 | ||||||
|  | @Lazy | ||||||
|  | @Component | ||||||
|  | @GuiProfile | ||||||
|  | public class ConfigTemplateList implements TemplateComposer { | ||||||
|  | 
 | ||||||
|  |     private static final LocTextKey NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION = | ||||||
|  |             new LocTextKey("sebserver.examconfig.list.action.no.modify.privilege"); | ||||||
|  |     private static final LocTextKey TITLE_TEMPLATE_TEXT_KEY = | ||||||
|  |             new LocTextKey("sebserver.configtemplate.list.title"); | ||||||
|  |     private static final LocTextKey EMPTY_TEMPLATE_LIST_TEXT_KEY = | ||||||
|  |             new LocTextKey("sebserver.configtemplate.list.empty"); | ||||||
|  |     private static final LocTextKey INSTITUTION_TEXT_KEY = | ||||||
|  |             new LocTextKey("sebserver.examconfig.list.column.institution"); | ||||||
|  |     private static final LocTextKey NAME_TEXT_KEY = | ||||||
|  |             new LocTextKey("sebserver.examconfig.list.column.name"); | ||||||
|  |     private static final LocTextKey DESCRIPTION_TEXT_KEY = | ||||||
|  |             new LocTextKey("sebserver.examconfig.list.column.description"); | ||||||
|  |     private static final LocTextKey EMPTY_TEMPLATE_SELECTION_TEXT_KEY = | ||||||
|  |             new LocTextKey("sebserver.configtemplate.info.pleaseSelect"); | ||||||
|  | 
 | ||||||
|  |     private final PageService pageService; | ||||||
|  |     private final RestService restService; | ||||||
|  |     private final CurrentUser currentUser; | ||||||
|  |     private final ResourceService resourceService; | ||||||
|  |     private final int pageSize; | ||||||
|  | 
 | ||||||
|  |     private final TableFilterAttribute institutionFilter; | ||||||
|  |     private final TableFilterAttribute nameFilter = | ||||||
|  |             new TableFilterAttribute(CriteriaType.TEXT, Entity.FILTER_ATTR_NAME); | ||||||
|  |     private final TableFilterAttribute descFilter = | ||||||
|  |             new TableFilterAttribute(CriteriaType.TEXT, ConfigurationNode.FILTER_ATTR_DESCRIPTION); | ||||||
|  | 
 | ||||||
|  |     protected ConfigTemplateList( | ||||||
|  |             final PageService pageService, | ||||||
|  |             final RestService restService, | ||||||
|  |             final CurrentUser currentUser, | ||||||
|  |             @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) { | ||||||
|  | 
 | ||||||
|  |         this.pageService = pageService; | ||||||
|  |         this.restService = restService; | ||||||
|  |         this.currentUser = currentUser; | ||||||
|  |         this.resourceService = pageService.getResourceService(); | ||||||
|  |         this.pageSize = pageSize; | ||||||
|  | 
 | ||||||
|  |         this.institutionFilter = new TableFilterAttribute( | ||||||
|  |                 CriteriaType.SINGLE_SELECTION, | ||||||
|  |                 Entity.FILTER_ATTR_INSTITUTION, | ||||||
|  |                 this.resourceService::institutionResource); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void compose(final PageContext pageContext) { | ||||||
|  |         final WidgetFactory widgetFactory = this.pageService.getWidgetFactory(); | ||||||
|  |         final Composite content = widgetFactory.defaultPageLayout( | ||||||
|  |                 pageContext.getParent(), | ||||||
|  |                 TITLE_TEMPLATE_TEXT_KEY); | ||||||
|  | 
 | ||||||
|  |         final boolean isSEBAdmin = this.currentUser.get().hasRole(UserRole.SEB_SERVER_ADMIN); | ||||||
|  |         final PageActionBuilder pageActionBuilder = | ||||||
|  |                 this.pageService.pageActionBuilder(pageContext.clearEntityKeys()); | ||||||
|  | 
 | ||||||
|  |         final EntityTable<ConfigurationNode> templateTable = | ||||||
|  |                 this.pageService.entityTableBuilder( | ||||||
|  |                         TITLE_TEMPLATE_TEXT_KEY.name, | ||||||
|  |                         this.restService.getRestCall(GetExamConfigNodePage.class)) | ||||||
|  |                         .withStaticFilter( | ||||||
|  |                                 Domain.CONFIGURATION_NODE.ATTR_TYPE, | ||||||
|  |                                 ConfigurationType.TEMPLATE.name()) | ||||||
|  |                         .withEmptyMessage(EMPTY_TEMPLATE_LIST_TEXT_KEY) | ||||||
|  |                         .withPaging(this.pageSize) | ||||||
|  |                         .withColumnIf( | ||||||
|  |                                 () -> isSEBAdmin, | ||||||
|  |                                 () -> new ColumnDefinition<>( | ||||||
|  |                                         Domain.LMS_SETUP.ATTR_INSTITUTION_ID, | ||||||
|  |                                         INSTITUTION_TEXT_KEY, | ||||||
|  |                                         this.resourceService::localizedExamConfigInstitutionName) | ||||||
|  |                                                 .withFilter(this.institutionFilter) | ||||||
|  |                                                 .sortable()) | ||||||
|  |                         .withColumn(new ColumnDefinition<>( | ||||||
|  |                                 Domain.CONFIGURATION_NODE.ATTR_NAME, | ||||||
|  |                                 NAME_TEXT_KEY, | ||||||
|  |                                 ConfigurationNode::getName) | ||||||
|  |                                         .withFilter(this.nameFilter) | ||||||
|  |                                         .sortable()) | ||||||
|  |                         .withColumn(new ColumnDefinition<>( | ||||||
|  |                                 Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, | ||||||
|  |                                 DESCRIPTION_TEXT_KEY, | ||||||
|  |                                 ConfigurationNode::getDescription) | ||||||
|  |                                         .withFilter(this.descFilter) | ||||||
|  |                                         .sortable()) | ||||||
|  |                         .withDefaultAction(pageActionBuilder | ||||||
|  |                                 .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_VIEW_FROM_LIST) | ||||||
|  |                                 .create()) | ||||||
|  |                         .compose(pageContext.copyOf(content)); | ||||||
|  | 
 | ||||||
|  |         final GrantCheck examConfigGrant = this.currentUser.grantCheck(EntityType.CONFIGURATION_NODE); | ||||||
|  |         pageActionBuilder | ||||||
|  |                 // Exam Configuration template actions... | ||||||
|  |                 .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_NEW) | ||||||
|  |                 .publishIf(examConfigGrant::iw) | ||||||
|  | 
 | ||||||
|  |                 .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_VIEW_FROM_LIST) | ||||||
|  |                 .withSelect(templateTable::getSelection, PageAction::applySingleSelectionAsEntityKey, | ||||||
|  |                         EMPTY_TEMPLATE_SELECTION_TEXT_KEY) | ||||||
|  |                 .publishIf(() -> templateTable.hasAnyContent()) | ||||||
|  | 
 | ||||||
|  |                 .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_MODIFY_FROM_LIST) | ||||||
|  |                 .withSelect( | ||||||
|  |                         templateTable.getGrantedSelection(this.currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION), | ||||||
|  |                         PageAction::applySingleSelectionAsEntityKey, EMPTY_TEMPLATE_SELECTION_TEXT_KEY) | ||||||
|  |                 .publishIf(() -> examConfigGrant.im() && templateTable.hasAnyContent()); | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -192,43 +192,77 @@ public class MonitoringRunningExam implements TemplateComposer { | ||||||
|                         action -> this.disableSebClients(action, clientTable, false), |                         action -> this.disableSebClients(action, clientTable, false), | ||||||
|                         EMPTY_SELECTION_TEXT_KEY) |                         EMPTY_SELECTION_TEXT_KEY) | ||||||
|                 .noEventPropagation() |                 .noEventPropagation() | ||||||
|                 .publishIf(privilege) |                 .publishIf(privilege); | ||||||
| 
 |  | ||||||
|         ; |  | ||||||
| 
 |  | ||||||
|         clientTable.hideStatus(ConnectionStatus.DISABLED); |  | ||||||
| 
 | 
 | ||||||
|         if (privilege.getAsBoolean()) { |         if (privilege.getAsBoolean()) { | ||||||
| 
 | 
 | ||||||
|             actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION) |             if (clientTable.isStatusHidden(ConnectionStatus.CLOSED)) { | ||||||
|                     .withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED)) |                 actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION) | ||||||
|                     .noEventPropagation() |                         .withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED)) | ||||||
|                     .withSwitchAction( |                         .noEventPropagation() | ||||||
|                             actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION) |                         .withSwitchAction( | ||||||
|                                     .withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED)) |                                 actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION) | ||||||
|                                     .noEventPropagation() |                                         .withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED)) | ||||||
|                                     .create()) |                                         .noEventPropagation() | ||||||
|                     .publish(); |                                         .create()) | ||||||
|  |                         .publish(); | ||||||
|  |             } else { | ||||||
|  |                 actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION) | ||||||
|  |                         .withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED)) | ||||||
|  |                         .noEventPropagation() | ||||||
|  |                         .withSwitchAction( | ||||||
|  |                                 actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION) | ||||||
|  |                                         .withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED)) | ||||||
|  |                                         .noEventPropagation() | ||||||
|  |                                         .create()) | ||||||
|  |                         .publish(); | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION) |             if (clientTable.isStatusHidden(ConnectionStatus.CONNECTION_REQUESTED)) { | ||||||
|                     .withExec(hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED)) |                 actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION) | ||||||
|                     .noEventPropagation() |                         .withExec(showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED)) | ||||||
|                     .withSwitchAction( |                         .noEventPropagation() | ||||||
|                             actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION) |                         .withSwitchAction( | ||||||
|                                     .withExec(showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED)) |                                 actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION) | ||||||
|                                     .noEventPropagation() |                                         .withExec( | ||||||
|                                     .create()) |                                                 hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED)) | ||||||
|                     .publish(); |                                         .noEventPropagation() | ||||||
|  |                                         .create()) | ||||||
|  |                         .publish(); | ||||||
|  |             } else { | ||||||
|  |                 actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION) | ||||||
|  |                         .withExec(hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED)) | ||||||
|  |                         .noEventPropagation() | ||||||
|  |                         .withSwitchAction( | ||||||
|  |                                 actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION) | ||||||
|  |                                         .withExec( | ||||||
|  |                                                 showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED)) | ||||||
|  |                                         .noEventPropagation() | ||||||
|  |                                         .create()) | ||||||
|  |                         .publish(); | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION) |             if (clientTable.isStatusHidden(ConnectionStatus.DISABLED)) { | ||||||
|                     .withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED)) |                 actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION) | ||||||
|                     .noEventPropagation() |                         .withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED)) | ||||||
|                     .withSwitchAction( |                         .noEventPropagation() | ||||||
|                             actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION) |                         .withSwitchAction( | ||||||
|                                     .withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED)) |                                 actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION) | ||||||
|                                     .noEventPropagation() |                                         .withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED)) | ||||||
|                                     .create()) |                                         .noEventPropagation() | ||||||
|                     .publish(); |                                         .create()) | ||||||
|  |                         .publish(); | ||||||
|  |             } else { | ||||||
|  |                 actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION) | ||||||
|  |                         .withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED)) | ||||||
|  |                         .noEventPropagation() | ||||||
|  |                         .withSwitchAction( | ||||||
|  |                                 actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION) | ||||||
|  |                                         .withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED)) | ||||||
|  |                                         .noEventPropagation() | ||||||
|  |                                         .create()) | ||||||
|  |                         .publish(); | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ | ||||||
| package ch.ethz.seb.sebserver.gui.content; | package ch.ethz.seb.sebserver.gui.content; | ||||||
| 
 | 
 | ||||||
| import org.eclipse.swt.widgets.Composite; | import org.eclipse.swt.widgets.Composite; | ||||||
|  | import org.springframework.beans.factory.annotation.Value; | ||||||
| import org.springframework.context.annotation.Lazy; | import org.springframework.context.annotation.Lazy; | ||||||
| import org.springframework.stereotype.Component; | import org.springframework.stereotype.Component; | ||||||
| 
 | 
 | ||||||
|  | @ -46,12 +47,8 @@ public class SebExamConfigList implements TemplateComposer { | ||||||
|             new LocTextKey("sebserver.examconfig.list.action.no.modify.privilege"); |             new LocTextKey("sebserver.examconfig.list.action.no.modify.privilege"); | ||||||
|     private static final LocTextKey EMPTY_CONFIG_LIST_TEXT_KEY = |     private static final LocTextKey EMPTY_CONFIG_LIST_TEXT_KEY = | ||||||
|             new LocTextKey("sebserver.examconfig.list.empty"); |             new LocTextKey("sebserver.examconfig.list.empty"); | ||||||
|     private static final LocTextKey EMPTY_TEMPLATE_LIST_TEXT_KEY = |  | ||||||
|             new LocTextKey("sebserver.configtemplate.list.empty"); |  | ||||||
|     private static final LocTextKey TITLE_CONFIGURATION_TEXT_KEY = |     private static final LocTextKey TITLE_CONFIGURATION_TEXT_KEY = | ||||||
|             new LocTextKey("sebserver.examconfig.list.title"); |             new LocTextKey("sebserver.examconfig.list.title"); | ||||||
|     private static final LocTextKey TITLE_TEMPLATE_TEXT_KEY = |  | ||||||
|             new LocTextKey("sebserver.configtemplate.list.title"); |  | ||||||
|     private static final LocTextKey INSTITUTION_TEXT_KEY = |     private static final LocTextKey INSTITUTION_TEXT_KEY = | ||||||
|             new LocTextKey("sebserver.examconfig.list.column.institution"); |             new LocTextKey("sebserver.examconfig.list.column.institution"); | ||||||
|     private static final LocTextKey NAME_TEXT_KEY = |     private static final LocTextKey NAME_TEXT_KEY = | ||||||
|  | @ -62,28 +59,31 @@ public class SebExamConfigList implements TemplateComposer { | ||||||
|             new LocTextKey("sebserver.examconfig.list.column.status"); |             new LocTextKey("sebserver.examconfig.list.column.status"); | ||||||
|     private static final LocTextKey EMPTY_SELECTION_TEXT_KEY = |     private static final LocTextKey EMPTY_SELECTION_TEXT_KEY = | ||||||
|             new LocTextKey("sebserver.examconfig.info.pleaseSelect"); |             new LocTextKey("sebserver.examconfig.info.pleaseSelect"); | ||||||
|     private static final LocTextKey EMPTY_TEMPLATE_SELECTION_TEXT_KEY = |  | ||||||
|             new LocTextKey("sebserver.configtemplate.info.pleaseSelect"); |  | ||||||
| 
 | 
 | ||||||
|     private final TableFilterAttribute institutionFilter; |     private final TableFilterAttribute institutionFilter; | ||||||
|     private final TableFilterAttribute nameFilter = |     private final TableFilterAttribute nameFilter = | ||||||
|             new TableFilterAttribute(CriteriaType.TEXT, Entity.FILTER_ATTR_NAME); |             new TableFilterAttribute(CriteriaType.TEXT, Entity.FILTER_ATTR_NAME); | ||||||
|  |     private final TableFilterAttribute descFilter = | ||||||
|  |             new TableFilterAttribute(CriteriaType.TEXT, ConfigurationNode.FILTER_ATTR_DESCRIPTION); | ||||||
|     private final TableFilterAttribute statusFilter; |     private final TableFilterAttribute statusFilter; | ||||||
| 
 | 
 | ||||||
|     private final PageService pageService; |     private final PageService pageService; | ||||||
|     private final RestService restService; |     private final RestService restService; | ||||||
|     private final CurrentUser currentUser; |     private final CurrentUser currentUser; | ||||||
|     private final ResourceService resourceService; |     private final ResourceService resourceService; | ||||||
|  |     private final int pageSize; | ||||||
| 
 | 
 | ||||||
|     protected SebExamConfigList( |     protected SebExamConfigList( | ||||||
|             final PageService pageService, |             final PageService pageService, | ||||||
|             final RestService restService, |             final RestService restService, | ||||||
|             final CurrentUser currentUser) { |             final CurrentUser currentUser, | ||||||
|  |             @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) { | ||||||
| 
 | 
 | ||||||
|         this.pageService = pageService; |         this.pageService = pageService; | ||||||
|         this.restService = restService; |         this.restService = restService; | ||||||
|         this.currentUser = currentUser; |         this.currentUser = currentUser; | ||||||
|         this.resourceService = pageService.getResourceService(); |         this.resourceService = pageService.getResourceService(); | ||||||
|  |         this.pageSize = pageSize; | ||||||
| 
 | 
 | ||||||
|         this.institutionFilter = new TableFilterAttribute( |         this.institutionFilter = new TableFilterAttribute( | ||||||
|                 CriteriaType.SINGLE_SELECTION, |                 CriteriaType.SINGLE_SELECTION, | ||||||
|  | @ -99,7 +99,6 @@ public class SebExamConfigList implements TemplateComposer { | ||||||
|     @Override |     @Override | ||||||
|     public void compose(final PageContext pageContext) { |     public void compose(final PageContext pageContext) { | ||||||
|         final WidgetFactory widgetFactory = this.pageService.getWidgetFactory(); |         final WidgetFactory widgetFactory = this.pageService.getWidgetFactory(); | ||||||
| 
 |  | ||||||
|         final Composite content = widgetFactory.defaultPageLayout( |         final Composite content = widgetFactory.defaultPageLayout( | ||||||
|                 pageContext.getParent(), |                 pageContext.getParent(), | ||||||
|                 TITLE_CONFIGURATION_TEXT_KEY); |                 TITLE_CONFIGURATION_TEXT_KEY); | ||||||
|  | @ -115,7 +114,7 @@ public class SebExamConfigList implements TemplateComposer { | ||||||
|                                 Domain.CONFIGURATION_NODE.ATTR_TYPE, |                                 Domain.CONFIGURATION_NODE.ATTR_TYPE, | ||||||
|                                 ConfigurationType.EXAM_CONFIG.name()) |                                 ConfigurationType.EXAM_CONFIG.name()) | ||||||
|                         .withEmptyMessage(EMPTY_CONFIG_LIST_TEXT_KEY) |                         .withEmptyMessage(EMPTY_CONFIG_LIST_TEXT_KEY) | ||||||
|                         .withPaging(6) |                         .withPaging(this.pageSize) | ||||||
|                         .withColumnIf( |                         .withColumnIf( | ||||||
|                                 () -> isSEBAdmin, |                                 () -> isSEBAdmin, | ||||||
|                                 () -> new ColumnDefinition<>( |                                 () -> new ColumnDefinition<>( | ||||||
|  | @ -134,7 +133,7 @@ public class SebExamConfigList implements TemplateComposer { | ||||||
|                                 Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, |                                 Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, | ||||||
|                                 DESCRIPTION_TEXT_KEY, |                                 DESCRIPTION_TEXT_KEY, | ||||||
|                                 ConfigurationNode::getDescription) |                                 ConfigurationNode::getDescription) | ||||||
|                                         .withFilter(this.nameFilter) |                                         .withFilter(this.descFilter) | ||||||
|                                         .sortable()) |                                         .sortable()) | ||||||
|                         .withColumn(new ColumnDefinition<ConfigurationNode>( |                         .withColumn(new ColumnDefinition<ConfigurationNode>( | ||||||
|                                 Domain.CONFIGURATION_NODE.ATTR_STATUS, |                                 Domain.CONFIGURATION_NODE.ATTR_STATUS, | ||||||
|  | @ -147,45 +146,6 @@ public class SebExamConfigList implements TemplateComposer { | ||||||
|                                 .create()) |                                 .create()) | ||||||
|                         .compose(pageContext.copyOf(content)); |                         .compose(pageContext.copyOf(content)); | ||||||
| 
 | 
 | ||||||
|         // configuration template table |  | ||||||
|         widgetFactory.label(content, ""); |  | ||||||
|         widgetFactory.labelLocalizedTitle( |  | ||||||
|                 content, |  | ||||||
|                 TITLE_TEMPLATE_TEXT_KEY); |  | ||||||
|         widgetFactory.labelSeparator(content); |  | ||||||
| 
 |  | ||||||
|         final EntityTable<ConfigurationNode> templateTable = |  | ||||||
|                 this.pageService.entityTableBuilder(this.restService.getRestCall(GetExamConfigNodePage.class)) |  | ||||||
|                         .withStaticFilter( |  | ||||||
|                                 Domain.CONFIGURATION_NODE.ATTR_TYPE, |  | ||||||
|                                 ConfigurationType.TEMPLATE.name()) |  | ||||||
|                         .withEmptyMessage(EMPTY_TEMPLATE_LIST_TEXT_KEY) |  | ||||||
|                         .withPaging(6) |  | ||||||
|                         .withColumnIf( |  | ||||||
|                                 () -> isSEBAdmin, |  | ||||||
|                                 () -> new ColumnDefinition<>( |  | ||||||
|                                         Domain.LMS_SETUP.ATTR_INSTITUTION_ID, |  | ||||||
|                                         INSTITUTION_TEXT_KEY, |  | ||||||
|                                         this.resourceService::localizedExamConfigInstitutionName) |  | ||||||
|                                                 .withFilter(this.institutionFilter) |  | ||||||
|                                                 .sortable()) |  | ||||||
|                         .withColumn(new ColumnDefinition<>( |  | ||||||
|                                 Domain.CONFIGURATION_NODE.ATTR_NAME, |  | ||||||
|                                 NAME_TEXT_KEY, |  | ||||||
|                                 ConfigurationNode::getName) |  | ||||||
|                                         .withFilter(this.nameFilter) |  | ||||||
|                                         .sortable()) |  | ||||||
|                         .withColumn(new ColumnDefinition<>( |  | ||||||
|                                 Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, |  | ||||||
|                                 DESCRIPTION_TEXT_KEY, |  | ||||||
|                                 ConfigurationNode::getDescription) |  | ||||||
|                                         .withFilter(this.nameFilter) |  | ||||||
|                                         .sortable()) |  | ||||||
|                         .withDefaultAction(pageActionBuilder |  | ||||||
|                                 .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_VIEW_FROM_LIST) |  | ||||||
|                                 .create()) |  | ||||||
|                         .compose(pageContext.copyOf(content)); |  | ||||||
| 
 |  | ||||||
|         final GrantCheck examConfigGrant = this.currentUser.grantCheck(EntityType.CONFIGURATION_NODE); |         final GrantCheck examConfigGrant = this.currentUser.grantCheck(EntityType.CONFIGURATION_NODE); | ||||||
|         pageActionBuilder |         pageActionBuilder | ||||||
|                 // Exam Configuration actions... |                 // Exam Configuration actions... | ||||||
|  | @ -214,20 +174,7 @@ public class SebExamConfigList implements TemplateComposer { | ||||||
|                 .noEventPropagation() |                 .noEventPropagation() | ||||||
|                 .publishIf(() -> examConfigGrant.im()) |                 .publishIf(() -> examConfigGrant.im()) | ||||||
| 
 | 
 | ||||||
|                 // Exam Configuration template actions... |         ; | ||||||
|                 .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_NEW) |  | ||||||
|                 .publishIf(examConfigGrant::iw) |  | ||||||
| 
 |  | ||||||
|                 .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_VIEW_FROM_LIST) |  | ||||||
|                 .withSelect(templateTable::getSelection, PageAction::applySingleSelectionAsEntityKey, |  | ||||||
|                         EMPTY_TEMPLATE_SELECTION_TEXT_KEY) |  | ||||||
|                 .publishIf(() -> templateTable.hasAnyContent()) |  | ||||||
| 
 |  | ||||||
|                 .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_MODIFY_FROM_LIST) |  | ||||||
|                 .withSelect( |  | ||||||
|                         templateTable.getGrantedSelection(this.currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION), |  | ||||||
|                         PageAction::applySingleSelectionAsEntityKey, EMPTY_TEMPLATE_SELECTION_TEXT_KEY) |  | ||||||
|                 .publishIf(() -> examConfigGrant.im() && templateTable.hasAnyContent()); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -21,7 +21,7 @@ public enum ActionCategory { | ||||||
|     INDICATOR_LIST(new LocTextKey("sebserver.exam.indicator.list.actions"), 2), |     INDICATOR_LIST(new LocTextKey("sebserver.exam.indicator.list.actions"), 2), | ||||||
|     SEB_CLIENT_CONFIG_LIST(new LocTextKey("sebserver.clientconfig.list.actions"), 1), |     SEB_CLIENT_CONFIG_LIST(new LocTextKey("sebserver.clientconfig.list.actions"), 1), | ||||||
|     SEB_EXAM_CONFIG_LIST(new LocTextKey("sebserver.examconfig.list.actions"), 1), |     SEB_EXAM_CONFIG_LIST(new LocTextKey("sebserver.examconfig.list.actions"), 1), | ||||||
|     SEB_CONFIG_TEMPLATE_LIST(new LocTextKey("sebserver.configtemplate.list.actions"), 2), |     SEB_CONFIG_TEMPLATE_LIST(new LocTextKey("sebserver.configtemplate.list.actions"), 1), | ||||||
|     SEB_CONFIG_TEMPLATE_ATTRIBUTE_LIST(new LocTextKey("sebserver.configtemplate.attr.list.actions"), 1), |     SEB_CONFIG_TEMPLATE_ATTRIBUTE_LIST(new LocTextKey("sebserver.configtemplate.attr.list.actions"), 1), | ||||||
|     RUNNING_EXAM_LIST(new LocTextKey("sebserver.monitoring.exam.list.actions"), 1), |     RUNNING_EXAM_LIST(new LocTextKey("sebserver.monitoring.exam.list.actions"), 1), | ||||||
|     CLIENT_EVENT_LIST(new LocTextKey("sebserver.monitoring.exam.connection.list.actions"), 1), |     CLIENT_EVENT_LIST(new LocTextKey("sebserver.monitoring.exam.connection.list.actions"), 1), | ||||||
|  |  | ||||||
|  | @ -467,6 +467,9 @@ public enum ActionDefinition { | ||||||
|             PageStateDefinitionImpl.SEB_EXAM_CONFIG_EDIT, |             PageStateDefinitionImpl.SEB_EXAM_CONFIG_EDIT, | ||||||
|             ActionCategory.FORM), |             ActionCategory.FORM), | ||||||
| 
 | 
 | ||||||
|  |     SEB_EXAM_CONFIG_TEMPLATE_LIST( | ||||||
|  |             new LocTextKey("sebserver.configtemplate.action.list"), | ||||||
|  |             PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_LIST), | ||||||
|     SEB_EXAM_CONFIG_TEMPLATE_NEW( |     SEB_EXAM_CONFIG_TEMPLATE_NEW( | ||||||
|             new LocTextKey("sebserver.configtemplate.action.list.new"), |             new LocTextKey("sebserver.configtemplate.action.list.new"), | ||||||
|             ImageIcon.TEMPLATE, |             ImageIcon.TEMPLATE, | ||||||
|  |  | ||||||
|  | @ -214,6 +214,18 @@ public class ActivitiesPane implements TemplateComposer { | ||||||
|                                 .create()); |                                 .create()); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             // SEB Exam Config Template | ||||||
|  |             if (examConfigRead) { | ||||||
|  |                 final TreeItem examConfigTemplate = this.widgetFactory.treeItemLocalized( | ||||||
|  |                         sebConfigs, | ||||||
|  |                         ActivityDefinition.SEB_EXAM_CONFIG_TEMPLATE.displayName); | ||||||
|  |                 injectActivitySelection( | ||||||
|  |                         examConfigTemplate, | ||||||
|  |                         actionBuilder | ||||||
|  |                                 .newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_LIST) | ||||||
|  |                                 .create()); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             sebConfigs.setExpanded(this.currentUser.get().hasAnyRole(UserRole.EXAM_ADMIN)); |             sebConfigs.setExpanded(this.currentUser.get().hasAnyRole(UserRole.EXAM_ADMIN)); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -23,6 +23,7 @@ public enum ActivityDefinition implements Activity { | ||||||
|     SEB_CONFIGURATION(new LocTextKey("sebserver.overall.activity.title.sebconfig")), |     SEB_CONFIGURATION(new LocTextKey("sebserver.overall.activity.title.sebconfig")), | ||||||
|     SEB_CLIENT_CONFIG(new LocTextKey("sebserver.clientconfig.action.list")), |     SEB_CLIENT_CONFIG(new LocTextKey("sebserver.clientconfig.action.list")), | ||||||
|     SEB_EXAM_CONFIG(new LocTextKey("sebserver.examconfig.action.list")), |     SEB_EXAM_CONFIG(new LocTextKey("sebserver.examconfig.action.list")), | ||||||
|  |     SEB_EXAM_CONFIG_TEMPLATE(new LocTextKey("sebserver.configtemplate.action.list")), | ||||||
|     MONITORING(new LocTextKey("sebserver.overall.activity.title.monitoring")), |     MONITORING(new LocTextKey("sebserver.overall.activity.title.monitoring")), | ||||||
|     MONITORING_EXAMS(new LocTextKey("sebserver.monitoring.action.list")), |     MONITORING_EXAMS(new LocTextKey("sebserver.monitoring.action.list")), | ||||||
|     SEB_CLIENT_LOGS(new LocTextKey("sebserver.logs.activity.seblogs")); |     SEB_CLIENT_LOGS(new LocTextKey("sebserver.logs.activity.seblogs")); | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gui.content.activity; | ||||||
| 
 | 
 | ||||||
| import ch.ethz.seb.sebserver.gui.content.ConfigTemplateAttributeForm; | import ch.ethz.seb.sebserver.gui.content.ConfigTemplateAttributeForm; | ||||||
| import ch.ethz.seb.sebserver.gui.content.ConfigTemplateForm; | import ch.ethz.seb.sebserver.gui.content.ConfigTemplateForm; | ||||||
|  | import ch.ethz.seb.sebserver.gui.content.ConfigTemplateList; | ||||||
| import ch.ethz.seb.sebserver.gui.content.ExamForm; | import ch.ethz.seb.sebserver.gui.content.ExamForm; | ||||||
| import ch.ethz.seb.sebserver.gui.content.ExamList; | import ch.ethz.seb.sebserver.gui.content.ExamList; | ||||||
| import ch.ethz.seb.sebserver.gui.content.IndicatorForm; | import ch.ethz.seb.sebserver.gui.content.IndicatorForm; | ||||||
|  | @ -68,8 +69,12 @@ public enum PageStateDefinitionImpl implements PageStateDefinition { | ||||||
|     SEB_EXAM_CONFIG_EDIT(Type.FORM_IN_TIME_EDIT, SebExamConfigSettingsForm.class, ActivityDefinition.SEB_EXAM_CONFIG), |     SEB_EXAM_CONFIG_EDIT(Type.FORM_IN_TIME_EDIT, SebExamConfigSettingsForm.class, ActivityDefinition.SEB_EXAM_CONFIG), | ||||||
|     SEB_EXAM_CONFIG_VIEW(Type.FORM_VIEW, SebExamConfigSettingsForm.class, ActivityDefinition.SEB_EXAM_CONFIG), |     SEB_EXAM_CONFIG_VIEW(Type.FORM_VIEW, SebExamConfigSettingsForm.class, ActivityDefinition.SEB_EXAM_CONFIG), | ||||||
| 
 | 
 | ||||||
|     SEB_EXAM_CONFIG_TEMPLATE_VIEW(Type.FORM_VIEW, ConfigTemplateForm.class, ActivityDefinition.SEB_EXAM_CONFIG), |     SEB_EXAM_CONFIG_TEMPLATE_LIST(Type.LIST_VIEW, ConfigTemplateList.class, | ||||||
|     SEB_EXAM_CONFIG_TEMPLATE_EDIT(Type.FORM_EDIT, ConfigTemplateForm.class, ActivityDefinition.SEB_EXAM_CONFIG), |             ActivityDefinition.SEB_EXAM_CONFIG_TEMPLATE), | ||||||
|  |     SEB_EXAM_CONFIG_TEMPLATE_VIEW(Type.FORM_VIEW, ConfigTemplateForm.class, | ||||||
|  |             ActivityDefinition.SEB_EXAM_CONFIG_TEMPLATE), | ||||||
|  |     SEB_EXAM_CONFIG_TEMPLATE_EDIT(Type.FORM_EDIT, ConfigTemplateForm.class, | ||||||
|  |             ActivityDefinition.SEB_EXAM_CONFIG_TEMPLATE), | ||||||
| 
 | 
 | ||||||
|     SEB_EXAM_CONFIG_TEMPLATE_ATTRIBUTE_EDIT( |     SEB_EXAM_CONFIG_TEMPLATE_ATTRIBUTE_EDIT( | ||||||
|             Type.FORM_EDIT, |             Type.FORM_EDIT, | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ | ||||||
| package ch.ethz.seb.sebserver.gui.service.session; | package ch.ethz.seb.sebserver.gui.service.session; | ||||||
| 
 | 
 | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
|  | import java.util.Arrays; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| import java.util.Comparator; | import java.util.Comparator; | ||||||
|  | @ -65,6 +66,7 @@ public final class ClientConnectionTable { | ||||||
| 
 | 
 | ||||||
|     private static final int BOTTOM_PADDING = 20; |     private static final int BOTTOM_PADDING = 20; | ||||||
|     private static final int NUMBER_OF_NONE_INDICATOR_COLUMNS = 3; |     private static final int NUMBER_OF_NONE_INDICATOR_COLUMNS = 3; | ||||||
|  |     private static final String USER_SESSION_STATUS_FILTER_ATTRIBUTE = "USER_SESSION_STATUS_FILTER_ATTRIBUTE"; | ||||||
| 
 | 
 | ||||||
|     private static final String INDICATOR_NAME_TEXT_KEY_PREFIX = |     private static final String INDICATOR_NAME_TEXT_KEY_PREFIX = | ||||||
|             "sebserver.monitoring.connection.list.column.indicator."; |             "sebserver.monitoring.connection.list.column.indicator."; | ||||||
|  | @ -117,6 +119,7 @@ public final class ClientConnectionTable { | ||||||
|                 NUMBER_OF_NONE_INDICATOR_COLUMNS); |                 NUMBER_OF_NONE_INDICATOR_COLUMNS); | ||||||
| 
 | 
 | ||||||
|         this.statusFilter = EnumSet.noneOf(ConnectionStatus.class); |         this.statusFilter = EnumSet.noneOf(ConnectionStatus.class); | ||||||
|  |         loadStatusFilter(); | ||||||
| 
 | 
 | ||||||
|         this.table = this.widgetFactory.tableLocalized(tableRoot, SWT.MULTI | SWT.V_SCROLL); |         this.table = this.widgetFactory.tableLocalized(tableRoot, SWT.MULTI | SWT.V_SCROLL); | ||||||
|         final GridLayout gridLayout = new GridLayout(3 + indicators.size(), true); |         final GridLayout gridLayout = new GridLayout(3 + indicators.size(), true); | ||||||
|  | @ -169,10 +172,45 @@ public final class ClientConnectionTable { | ||||||
| 
 | 
 | ||||||
|     public void hideStatus(final ConnectionStatus status) { |     public void hideStatus(final ConnectionStatus status) { | ||||||
|         this.statusFilter.add(status); |         this.statusFilter.add(status); | ||||||
|  |         saveStatusFilter(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void showStatus(final ConnectionStatus status) { |     public void showStatus(final ConnectionStatus status) { | ||||||
|         this.statusFilter.remove(status); |         this.statusFilter.remove(status); | ||||||
|  |         saveStatusFilter(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void saveStatusFilter() { | ||||||
|  |         try { | ||||||
|  |             this.resourceService | ||||||
|  |                     .getCurrentUser() | ||||||
|  |                     .putAttribute( | ||||||
|  |                             USER_SESSION_STATUS_FILTER_ATTRIBUTE, | ||||||
|  |                             StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR)); | ||||||
|  |         } catch (final Exception e) { | ||||||
|  |             log.warn("Failed to save status filter to user session"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void loadStatusFilter() { | ||||||
|  |         try { | ||||||
|  |             final String attribute = this.resourceService | ||||||
|  |                     .getCurrentUser() | ||||||
|  |                     .getAttribute(USER_SESSION_STATUS_FILTER_ATTRIBUTE); | ||||||
|  |             if (attribute != null) { | ||||||
|  |                 this.statusFilter.clear(); | ||||||
|  |                 Arrays.asList(StringUtils.split(attribute, Constants.LIST_SEPARATOR)) | ||||||
|  |                         .forEach(name -> this.statusFilter.add(ConnectionStatus.valueOf(name))); | ||||||
|  | 
 | ||||||
|  |             } else { | ||||||
|  |                 this.statusFilter.clear(); | ||||||
|  |                 this.statusFilter.add(ConnectionStatus.DISABLED); | ||||||
|  |             } | ||||||
|  |         } catch (final Exception e) { | ||||||
|  |             log.warn("Failed to load status filter to user session"); | ||||||
|  |             this.statusFilter.clear(); | ||||||
|  |             this.statusFilter.add(ConnectionStatus.DISABLED); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void withDefaultAction(final PageAction pageAction, final PageService pageService) { |     public void withDefaultAction(final PageAction pageAction, final PageService pageService) { | ||||||
|  |  | ||||||
|  | @ -10,52 +10,45 @@ package ch.ethz.seb.sebserver.gui.widget; | ||||||
| 
 | 
 | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.HashMap; | import java.util.Collection; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; |  | ||||||
| import java.util.Map.Entry; |  | ||||||
| import java.util.Optional; | import java.util.Optional; | ||||||
| import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||||
| 
 | 
 | ||||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||||
|  | import org.eclipse.rap.rwt.widgets.DropDown; | ||||||
| import org.eclipse.swt.SWT; | import org.eclipse.swt.SWT; | ||||||
| import org.eclipse.swt.layout.GridData; | import org.eclipse.swt.layout.GridData; | ||||||
| import org.eclipse.swt.layout.GridLayout; | import org.eclipse.swt.layout.GridLayout; | ||||||
| import org.eclipse.swt.widgets.Combo; |  | ||||||
| import org.eclipse.swt.widgets.Composite; | import org.eclipse.swt.widgets.Composite; | ||||||
| import org.eclipse.swt.widgets.Control; | import org.eclipse.swt.widgets.Control; | ||||||
| import org.eclipse.swt.widgets.Event; | import org.eclipse.swt.widgets.Event; | ||||||
| import org.eclipse.swt.widgets.Label; | import org.eclipse.swt.widgets.Label; | ||||||
| import org.eclipse.swt.widgets.Listener; | import org.eclipse.swt.widgets.Listener; | ||||||
|  | import org.eclipse.swt.widgets.Text; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import ch.ethz.seb.sebserver.gbl.Constants; | import ch.ethz.seb.sebserver.gbl.Constants; | ||||||
| import ch.ethz.seb.sebserver.gbl.util.Tuple; | import ch.ethz.seb.sebserver.gbl.util.Tuple; | ||||||
| import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; |  | ||||||
| import ch.ethz.seb.sebserver.gui.service.page.PageService; | import ch.ethz.seb.sebserver.gui.service.page.PageService; | ||||||
| import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon; |  | ||||||
| 
 | 
 | ||||||
| public final class MultiSelectionCombo extends Composite implements Selection { | public final class MultiSelectionCombo extends Composite implements Selection { | ||||||
| 
 | 
 | ||||||
|     private static final Logger log = LoggerFactory.getLogger(MultiSelectionCombo.class); |     private static final Logger log = LoggerFactory.getLogger(MultiSelectionCombo.class); | ||||||
|     private static final long serialVersionUID = -7787134114963647332L; |     private static final long serialVersionUID = -7787134114963647332L; | ||||||
|     private static final int ACTION_COLUMN_WIDTH = 20; |  | ||||||
| 
 |  | ||||||
|     private static final LocTextKey DEFAULT_ADD_TOOLTIP_KEY = new LocTextKey("sebserver.overall.add"); |  | ||||||
|     private static final LocTextKey DEFAULT_REMOVE_TOOLTIP_KEY = new LocTextKey("sebserver.overall.remove"); |  | ||||||
| 
 | 
 | ||||||
|     private final WidgetFactory widgetFactory; |     private final WidgetFactory widgetFactory; | ||||||
|     private final Combo combo; |  | ||||||
|     private final LocTextKey addTextKey; |  | ||||||
|     private final LocTextKey removeTextKey; |  | ||||||
| 
 | 
 | ||||||
|     private final List<Tuple<Control>> selectionControls = new ArrayList<>(); |     private final List<Control> selectionControls = new ArrayList<>(); | ||||||
|  | 
 | ||||||
|  |     private final List<Tuple<String>> valueMapping = new ArrayList<>(); | ||||||
|  |     private final List<Tuple<String>> availableValues = new ArrayList<>(); | ||||||
|     private final List<Tuple<String>> selectedValues = new ArrayList<>(); |     private final List<Tuple<String>> selectedValues = new ArrayList<>(); | ||||||
|     private final Map<String, String> mapping = new HashMap<>(); |  | ||||||
| 
 | 
 | ||||||
|     private final GridData comboCell; |     private final DropDown dropDown; | ||||||
|     private final GridData actionCell; |     private final Text textInput; | ||||||
|  |     private final GridData textCell; | ||||||
|     private final Composite updateAnchor; |     private final Composite updateAnchor; | ||||||
| 
 | 
 | ||||||
|     private Listener listener = null; |     private Listener listener = null; | ||||||
|  | @ -68,14 +61,8 @@ public final class MultiSelectionCombo extends Composite implements Selection { | ||||||
| 
 | 
 | ||||||
|         super(parent, SWT.NONE); |         super(parent, SWT.NONE); | ||||||
|         this.widgetFactory = widgetFactory; |         this.widgetFactory = widgetFactory; | ||||||
|         this.addTextKey = (locTextPrefix != null) |  | ||||||
|                 ? new LocTextKey(locTextPrefix + ".add") |  | ||||||
|                 : DEFAULT_ADD_TOOLTIP_KEY; |  | ||||||
|         this.removeTextKey = (locTextPrefix != null) |  | ||||||
|                 ? new LocTextKey(locTextPrefix + ".remove") |  | ||||||
|                 : DEFAULT_REMOVE_TOOLTIP_KEY; |  | ||||||
| 
 | 
 | ||||||
|         final GridLayout gridLayout = new GridLayout(2, false); |         final GridLayout gridLayout = new GridLayout(); | ||||||
|         gridLayout.verticalSpacing = 1; |         gridLayout.verticalSpacing = 1; | ||||||
|         gridLayout.marginLeft = 0; |         gridLayout.marginLeft = 0; | ||||||
|         gridLayout.marginHeight = 0; |         gridLayout.marginHeight = 0; | ||||||
|  | @ -84,22 +71,43 @@ public final class MultiSelectionCombo extends Composite implements Selection { | ||||||
|         setLayout(gridLayout); |         setLayout(gridLayout); | ||||||
| 
 | 
 | ||||||
|         this.addListener(SWT.Resize, this::adaptColumnWidth); |         this.addListener(SWT.Resize, this::adaptColumnWidth); | ||||||
|  |         this.textInput = widgetFactory.textInput(this); | ||||||
|  |         this.textCell = new GridData(SWT.LEFT, SWT.CENTER, true, true); | ||||||
|  |         this.textInput.setLayoutData(this.textCell); | ||||||
|  |         this.dropDown = new DropDown(this.textInput, SWT.NONE); | ||||||
|  |         this.textInput.addListener(SWT.FocusIn, event -> { | ||||||
|  |             openDropDown(); | ||||||
|  |         }); | ||||||
|  |         this.textInput.addListener(SWT.Modify, event -> { | ||||||
|  |             openDropDown(); | ||||||
|  |         }); | ||||||
|  |         this.dropDown.addListener(SWT.Selection, event -> { | ||||||
|  |             final int selectionIndex = this.dropDown.getSelectionIndex(); | ||||||
|  |             if (selectionIndex >= 0) { | ||||||
|  |                 final String selectedItem = this.dropDown.getItems()[selectionIndex]; | ||||||
|  |                 addSelection(itemForName(selectedItem)); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
| 
 | 
 | ||||||
|         this.combo = new Combo(this, SWT.NONE); |  | ||||||
|         this.comboCell = new GridData(SWT.FILL, SWT.CENTER, true, false); |  | ||||||
|         this.combo.setLayoutData(this.comboCell); |  | ||||||
| 
 |  | ||||||
|         final Label imageButton = widgetFactory.imageButton( |  | ||||||
|                 ImageIcon.ADD_BOX, |  | ||||||
|                 this, |  | ||||||
|                 this.addTextKey, |  | ||||||
|                 this::addComboSelection); |  | ||||||
|         this.actionCell = new GridData(SWT.LEFT, SWT.CENTER, true, false); |  | ||||||
|         this.actionCell.widthHint = ACTION_COLUMN_WIDTH; |  | ||||||
|         imageButton.setLayoutData(this.actionCell); |  | ||||||
|         this.updateAnchor = updateAnchor; |         this.updateAnchor = updateAnchor; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private void openDropDown() { | ||||||
|  |         final String text = this.textInput.getText(); | ||||||
|  |         if (text == null) { | ||||||
|  |             this.dropDown.setVisible(false); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         final Collection<String> items = this.availableValues | ||||||
|  |                 .stream() | ||||||
|  |                 .filter(it -> it._2 != null && it._2.startsWith(text)) | ||||||
|  |                 .map(t -> t._2) | ||||||
|  |                 .collect(Collectors.toList()); | ||||||
|  |         this.dropDown.setItems(items.toArray(new String[items.size()])); | ||||||
|  |         this.dropDown.setSelectionIndex(0); | ||||||
|  |         this.dropDown.setVisible(true); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public Type type() { |     public Type type() { | ||||||
|         return Type.MULTI_COMBO; |         return Type.MULTI_COMBO; | ||||||
|  | @ -112,8 +120,8 @@ public final class MultiSelectionCombo extends Composite implements Selection { | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void applyNewMapping(final List<Tuple<String>> mapping) { |     public void applyNewMapping(final List<Tuple<String>> mapping) { | ||||||
|         this.mapping.putAll(mapping.stream() |         this.valueMapping.clear(); | ||||||
|                 .collect(Collectors.toMap(t -> t._1, t -> t._2))); |         this.valueMapping.addAll(mapping); | ||||||
|         this.clear(); |         this.clear(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -126,9 +134,22 @@ public final class MultiSelectionCombo extends Composite implements Selection { | ||||||
| 
 | 
 | ||||||
|         Arrays.asList(StringUtils.split(keys, Constants.LIST_SEPARATOR)) |         Arrays.asList(StringUtils.split(keys, Constants.LIST_SEPARATOR)) | ||||||
|                 .stream() |                 .stream() | ||||||
|  |                 .map(this::itemForName) | ||||||
|                 .forEach(this::addSelection); |                 .forEach(this::addSelection); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private Tuple<String> itemForName(final String name) { | ||||||
|  |         final Optional<Tuple<String>> findFirst = this.availableValues | ||||||
|  |                 .stream() | ||||||
|  |                 .filter(it -> it._2 != null && it._2.equals(name)) | ||||||
|  |                 .findFirst(); | ||||||
|  |         if (findFirst.isPresent()) { | ||||||
|  |             return findFirst.get(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public String getSelectionValue() { |     public String getSelectionValue() { | ||||||
|         if (this.selectedValues.isEmpty()) { |         if (this.selectedValues.isEmpty()) { | ||||||
|  | @ -151,61 +172,28 @@ public final class MultiSelectionCombo extends Composite implements Selection { | ||||||
|         this.selectedValues.clear(); |         this.selectedValues.clear(); | ||||||
|         this.selectionControls |         this.selectionControls | ||||||
|                 .stream() |                 .stream() | ||||||
|                 .forEach(t -> { |                 .forEach(Control::dispose); | ||||||
|                     t._1.dispose(); |  | ||||||
|                     t._2.dispose(); |  | ||||||
|                 }); |  | ||||||
|         this.selectionControls.clear(); |         this.selectionControls.clear(); | ||||||
|         this.combo.setItems(this.mapping.values().toArray(new String[this.mapping.size()])); |         this.availableValues.clear(); | ||||||
|  |         this.availableValues.addAll(this.valueMapping); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void addComboSelection(final Event event) { |     private void addSelection(final Tuple<String> item) { | ||||||
|         final int selectionIndex = this.combo.getSelectionIndex(); |         if (item == null) { | ||||||
|         if (selectionIndex < 0) { |  | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         final String itemName = this.combo.getItem(selectionIndex); |         this.selectedValues.add(item); | ||||||
|         if (itemName == null) { |         final Label label = this.widgetFactory.label(this, item._2); | ||||||
|             return; |         label.setData(OPTION_VALUE, item._2); | ||||||
|         } |         final GridData textCell = new GridData(SWT.LEFT, SWT.CENTER, true, true); | ||||||
|  |         label.setLayoutData(textCell); | ||||||
|  |         label.addListener(SWT.MouseDoubleClick, event -> { | ||||||
|  |             removeComboSelection(event); | ||||||
|  |         }); | ||||||
|  |         this.selectionControls.add(label); | ||||||
| 
 | 
 | ||||||
|         final Optional<Entry<String, String>> findFirst = this.mapping.entrySet() |         this.availableValues.remove(item); | ||||||
|                 .stream() |  | ||||||
|                 .filter(entity -> entity.getValue().equals(itemName)) |  | ||||||
|                 .findFirst(); |  | ||||||
| 
 |  | ||||||
|         if (!findFirst.isPresent()) { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         addSelection(findFirst.get().getKey()); |  | ||||||
|         if (this.listener != null) { |  | ||||||
|             this.listener.handleEvent(event); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void addSelection(final String itemKey) { |  | ||||||
|         final String itemName = this.mapping.get(itemKey); |  | ||||||
|         if (itemName == null) { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         this.selectedValues.add(new Tuple<>(itemKey, itemName)); |  | ||||||
|         final Label label = this.widgetFactory.label(this, itemName); |  | ||||||
|         final Label imageButton = this.widgetFactory.imageButton( |  | ||||||
|                 ImageIcon.REMOVE_BOX, |  | ||||||
|                 this, |  | ||||||
|                 this.removeTextKey, |  | ||||||
|                 this::removeComboSelection); |  | ||||||
|         imageButton.setData(OPTION_VALUE, itemName); |  | ||||||
|         final GridData actionCell = new GridData(SWT.LEFT, SWT.CENTER, true, false); |  | ||||||
|         actionCell.widthHint = ACTION_COLUMN_WIDTH; |  | ||||||
|         imageButton.setLayoutData(actionCell); |  | ||||||
| 
 |  | ||||||
|         this.selectionControls.add(new Tuple<>(label, imageButton)); |  | ||||||
| 
 |  | ||||||
|         this.combo.remove(itemName); |  | ||||||
|         PageService.updateScrolledComposite(this); |         PageService.updateScrolledComposite(this); | ||||||
|         this.updateAnchor.layout(true, true); |         this.updateAnchor.layout(true, true); | ||||||
| 
 | 
 | ||||||
|  | @ -217,22 +205,20 @@ public final class MultiSelectionCombo extends Composite implements Selection { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         final String selectionKey = (String) event.widget.getData(OPTION_VALUE); |         final String selectionKey = (String) event.widget.getData(OPTION_VALUE); | ||||||
|         final Optional<Tuple<Control>> findFirst = this.selectionControls.stream() |         final Optional<Control> findFirst = this.selectionControls.stream() | ||||||
|                 .filter(t -> selectionKey.equals(t._2.getData(OPTION_VALUE))) |                 .filter(t -> selectionKey.equals(t.getData(OPTION_VALUE))) | ||||||
|                 .findFirst(); |                 .findFirst(); | ||||||
|         if (!findFirst.isPresent()) { |         if (!findFirst.isPresent()) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         final Tuple<Control> tuple = findFirst.get(); |         final Control control = findFirst.get(); | ||||||
|         final int indexOf = this.selectionControls.indexOf(tuple); |         final int indexOf = this.selectionControls.indexOf(control); | ||||||
|         this.selectionControls.remove(tuple); |         this.selectionControls.remove(control); | ||||||
| 
 |         control.dispose(); | ||||||
|         tuple._1.dispose(); |  | ||||||
|         tuple._2.dispose(); |  | ||||||
| 
 | 
 | ||||||
|         final Tuple<String> value = this.selectedValues.remove(indexOf); |         final Tuple<String> value = this.selectedValues.remove(indexOf); | ||||||
|         this.combo.add(value._2, this.combo.getItemCount()); |         this.availableValues.add(value); | ||||||
| 
 | 
 | ||||||
|         PageService.updateScrolledComposite(this); |         PageService.updateScrolledComposite(this); | ||||||
|         this.updateAnchor.layout(true, true); |         this.updateAnchor.layout(true, true); | ||||||
|  | @ -244,7 +230,7 @@ public final class MultiSelectionCombo extends Composite implements Selection { | ||||||
|     private void adaptColumnWidth(final Event event) { |     private void adaptColumnWidth(final Event event) { | ||||||
|         try { |         try { | ||||||
|             final int currentTableWidth = this.getClientArea().width; |             final int currentTableWidth = this.getClientArea().width; | ||||||
|             this.comboCell.widthHint = currentTableWidth - ACTION_COLUMN_WIDTH; |             this.textCell.widthHint = currentTableWidth; | ||||||
|             this.layout(); |             this.layout(); | ||||||
|         } catch (final Exception e) { |         } catch (final Exception e) { | ||||||
|             log.warn("Failed to adaptColumnWidth: ", e); |             log.warn("Failed to adaptColumnWidth: ", e); | ||||||
|  |  | ||||||
|  | @ -136,9 +136,12 @@ public interface ExamSessionService { | ||||||
|      * a subset of them. |      * a subset of them. | ||||||
|      * |      * | ||||||
|      * @param examId The exam identifier |      * @param examId The exam identifier | ||||||
|  |      * @param filter a filter predicate to apply | ||||||
|      * @return collection of ClientConnectionData of all active SEB client connections |      * @return collection of ClientConnectionData of all active SEB client connections | ||||||
|      *         of a running exam */ |      *         of a running exam */ | ||||||
|     Result<Collection<ClientConnectionData>> getConnectionData(Long examId); |     Result<Collection<ClientConnectionData>> getConnectionData( | ||||||
|  |             Long examId, | ||||||
|  |             Predicate<ClientConnectionData> filter); | ||||||
| 
 | 
 | ||||||
|     /** Use this to check if the current cached running exam is up to date |     /** Use this to check if the current cached running exam is up to date | ||||||
|      * and if not to flush the cache. |      * and if not to flush the cache. | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; | ||||||
| 
 | 
 | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
|  | import java.util.Objects; | ||||||
| import java.util.function.Function; | import java.util.function.Function; | ||||||
| import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||||
| 
 | 
 | ||||||
|  | @ -224,12 +225,7 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void checkActiveClientConnections(final Exam exam) { |     private void checkActiveClientConnections(final Exam exam) { | ||||||
|         if (this.examSessionService.getConnectionData(exam.id) |         if (this.examSessionService.hasActiveSebClientConnections(exam.id)) { | ||||||
|                 .getOrThrow() |  | ||||||
|                 .stream() |  | ||||||
|                 .filter(ExamSessionService::isActiveConnection) |  | ||||||
|                 .count() > 0) { |  | ||||||
| 
 |  | ||||||
|             throw new APIMessage.APIMessageException( |             throw new APIMessage.APIMessageException( | ||||||
|                     ErrorMessage.INTEGRITY_VALIDATION, |                     ErrorMessage.INTEGRITY_VALIDATION, | ||||||
|                     "Integrity violation: There are currently active SEB Client connection."); |                     "Integrity violation: There are currently active SEB Client connection."); | ||||||
|  | @ -286,7 +282,7 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService { | ||||||
|         final long activeConnections = involvedExams |         final long activeConnections = involvedExams | ||||||
|                 .stream() |                 .stream() | ||||||
|                 .flatMap(examId -> { |                 .flatMap(examId -> { | ||||||
|                     return this.examSessionService.getConnectionData(examId) |                     return this.examSessionService.getConnectionData(examId, Objects::nonNull) | ||||||
|                             .getOrThrow() |                             .getOrThrow() | ||||||
|                             .stream(); |                             .stream(); | ||||||
|                 }) |                 }) | ||||||
|  |  | ||||||
|  | @ -164,12 +164,9 @@ public class ExamSessionServiceImpl implements ExamSessionService { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return this.getConnectionData(examId) |         return !this.getConnectionData(examId, ExamSessionService::isActiveConnection) | ||||||
|                 .getOrThrow() |                 .getOrThrow() | ||||||
|                 .stream() |                 .isEmpty(); | ||||||
|                 .filter(ExamSessionService::isActiveConnection) |  | ||||||
|                 .findFirst() |  | ||||||
|                 .isPresent(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|  | @ -313,14 +310,17 @@ public class ExamSessionServiceImpl implements ExamSessionService { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public Result<Collection<ClientConnectionData>> getConnectionData(final Long examId) { |     public Result<Collection<ClientConnectionData>> getConnectionData( | ||||||
|  |             final Long examId, | ||||||
|  |             final Predicate<ClientConnectionData> filter) { | ||||||
|  | 
 | ||||||
|         return Result.tryCatch(() -> { |         return Result.tryCatch(() -> { | ||||||
|             return this.clientConnectionDAO |             return this.clientConnectionDAO | ||||||
|                     .getConnectionTokens(examId) |                     .getConnectionTokens(examId) | ||||||
|                     .getOrThrow() |                     .getOrThrow() | ||||||
|                     .stream() |                     .stream() | ||||||
|                     .map(this.examSessionCacheService::getActiveClientConnection) |                     .map(this.examSessionCacheService::getActiveClientConnection) | ||||||
|                     .filter(data -> data != null) |                     .filter(filter) | ||||||
|                     .collect(Collectors.toList()); |                     .collect(Collectors.toList()); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -123,8 +123,9 @@ public class SebInstructionServiceImpl implements SebInstructionService { | ||||||
|                 log.error("Failed to delete SEB client instruction on persistent storage: ", delete.getError()); |                 log.error("Failed to delete SEB client instruction on persistent storage: ", delete.getError()); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // {"instruction":"%s", "attributes":{%s}} |             // {"instruction":"%s", "attributes":%s} | ||||||
|             return new StringBuilder() |             final String attributes = clientInstruction.getAttributes(); | ||||||
|  |             final StringBuilder sBuilder = new StringBuilder() | ||||||
|                     .append(Constants.CURLY_BRACE_OPEN) |                     .append(Constants.CURLY_BRACE_OPEN) | ||||||
|                     .append(Constants.DOUBLE_QUOTE) |                     .append(Constants.DOUBLE_QUOTE) | ||||||
|                     .append(JSON_INST) |                     .append(JSON_INST) | ||||||
|  | @ -137,10 +138,16 @@ public class SebInstructionServiceImpl implements SebInstructionService { | ||||||
|                     .append(Constants.DOUBLE_QUOTE) |                     .append(Constants.DOUBLE_QUOTE) | ||||||
|                     .append(JSON_ATTR) |                     .append(JSON_ATTR) | ||||||
|                     .append(Constants.DOUBLE_QUOTE) |                     .append(Constants.DOUBLE_QUOTE) | ||||||
|                     .append(Constants.COLON) |                     .append(Constants.COLON); | ||||||
|                     .append(Constants.CURLY_BRACE_OPEN) |             if (attributes == null || attributes.isEmpty()) { | ||||||
|                     .append(clientInstruction.getAttributes()) |                 sBuilder.append(Constants.NULL); | ||||||
|                     .append(Constants.CURLY_BRACE_CLOSE) | 
 | ||||||
|  |             } else { | ||||||
|  |                 sBuilder.append(Constants.CURLY_BRACE_OPEN) | ||||||
|  |                         .append(attributes) | ||||||
|  |                         .append(Constants.CURLY_BRACE_CLOSE); | ||||||
|  |             } | ||||||
|  |             return sBuilder | ||||||
|                     .append(Constants.CURLY_BRACE_CLOSE) |                     .append(Constants.CURLY_BRACE_CLOSE) | ||||||
|                     .toString(); |                     .toString(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -10,7 +10,9 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; | ||||||
| 
 | 
 | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
|  | import java.util.EnumSet; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.Objects; | ||||||
| 
 | 
 | ||||||
| import javax.validation.Valid; | import javax.validation.Valid; | ||||||
| 
 | 
 | ||||||
|  | @ -23,6 +25,7 @@ import org.springframework.web.bind.WebDataBinder; | ||||||
| import org.springframework.web.bind.annotation.InitBinder; | import org.springframework.web.bind.annotation.InitBinder; | ||||||
| import org.springframework.web.bind.annotation.PathVariable; | import org.springframework.web.bind.annotation.PathVariable; | ||||||
| import org.springframework.web.bind.annotation.RequestBody; | import org.springframework.web.bind.annotation.RequestBody; | ||||||
|  | import org.springframework.web.bind.annotation.RequestHeader; | ||||||
| import org.springframework.web.bind.annotation.RequestMapping; | import org.springframework.web.bind.annotation.RequestMapping; | ||||||
| import org.springframework.web.bind.annotation.RequestMethod; | import org.springframework.web.bind.annotation.RequestMethod; | ||||||
| import org.springframework.web.bind.annotation.RequestParam; | import org.springframework.web.bind.annotation.RequestParam; | ||||||
|  | @ -35,6 +38,7 @@ import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.Domain; | import ch.ethz.seb.sebserver.gbl.model.Domain; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.Page; | import ch.ethz.seb.sebserver.gbl.model.Page; | ||||||
| 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.session.ClientConnection.ConnectionStatus; | ||||||
| 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.ClientInstruction; | import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction; | ||||||
| import ch.ethz.seb.sebserver.gbl.model.user.UserRole; | import ch.ethz.seb.sebserver.gbl.model.user.UserRole; | ||||||
|  | @ -153,7 +157,8 @@ public class ExamMonitoringController { | ||||||
|                     name = API.PARAM_INSTITUTION_ID, |                     name = API.PARAM_INSTITUTION_ID, | ||||||
|                     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_MODEL_ID, required = true) final Long examId) { |             @PathVariable(name = API.PARAM_MODEL_ID, required = true) final Long examId, | ||||||
|  |             @RequestHeader(name = API.EXAM_MONITORING_STATE_FILTER, required = false) final String hiddenStates) { | ||||||
| 
 | 
 | ||||||
|         // check overall privilege |         // check overall privilege | ||||||
|         this.authorization.checkRole( |         this.authorization.checkRole( | ||||||
|  | @ -169,8 +174,20 @@ public class ExamMonitoringController { | ||||||
|                     this.authorization.getUserService().getCurrentUser().getUserInfo()); |                     this.authorization.getUserService().getCurrentUser().getUserInfo()); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         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[0])); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         return this.examSessionService |         return this.examSessionService | ||||||
|                 .getConnectionData(examId) |                 .getConnectionData( | ||||||
|  |                         examId, | ||||||
|  |                         filterStates.isEmpty() | ||||||
|  |                                 ? Objects::nonNull | ||||||
|  |                                 : conn -> conn != null && filterStates.contains(conn.clientConnection.status)) | ||||||
|                 .getOrThrow(); |                 .getOrThrow(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -21,7 +21,7 @@ sebserver.overall.action.cancel=Cancel | ||||||
| sebserver.overall.action.close=Close | sebserver.overall.action.close=Close | ||||||
| sebserver.overall.action.goAwayFromEditPageConfirm=Are you sure you want to leave this page? Unsaved data will be lost. | sebserver.overall.action.goAwayFromEditPageConfirm=Are you sure you want to leave this page? Unsaved data will be lost. | ||||||
| sebserver.overall.action.category.varia= | sebserver.overall.action.category.varia= | ||||||
| sebserver.overall.action.category.filter=Connection Status Filter | sebserver.overall.action.category.filter= | ||||||
| 
 | 
 | ||||||
| sebserver.overall.status.active=Active | sebserver.overall.status.active=Active | ||||||
| sebserver.overall.status.inactive=Inactive | sebserver.overall.status.inactive=Inactive | ||||||
|  | @ -380,7 +380,7 @@ sebserver.exam.status.UP_COMING=Up Coming | ||||||
| sebserver.exam.status.RUNNING=Running | sebserver.exam.status.RUNNING=Running | ||||||
| sebserver.exam.status.FINISHED=Finished | sebserver.exam.status.FINISHED=Finished | ||||||
| 
 | 
 | ||||||
| sebserver.exam.configuration.list.actions=SEB Exam Configuration | sebserver.exam.configuration.list.actions= | ||||||
| sebserver.exam.configuration.list.title=SEB Exam Configuration | sebserver.exam.configuration.list.title=SEB Exam Configuration | ||||||
| sebserver.exam.configuration.list.column.name=Name | sebserver.exam.configuration.list.column.name=Name | ||||||
| sebserver.exam.configuration.list.column.description=Description | sebserver.exam.configuration.list.column.description=Description | ||||||
|  | @ -404,7 +404,7 @@ sebserver.exam.configuration.form.description=Description | ||||||
| sebserver.exam.configuration.form.status=Status | sebserver.exam.configuration.form.status=Status | ||||||
| sebserver.exam.configuration.form.encryptSecret.confirm=Confirm Password | sebserver.exam.configuration.form.encryptSecret.confirm=Confirm Password | ||||||
| 
 | 
 | ||||||
| sebserver.exam.indicator.list.actions=Indicator | sebserver.exam.indicator.list.actions= | ||||||
| sebserver.exam.indicator.list.title=Indicators | sebserver.exam.indicator.list.title=Indicators | ||||||
| sebserver.exam.indicator.list.column.type=Type | sebserver.exam.indicator.list.column.type=Type | ||||||
| sebserver.exam.indicator.list.column.name=Name | sebserver.exam.indicator.list.column.name=Name | ||||||
|  | @ -1011,6 +1011,7 @@ sebserver.examconfig.props.validation.WindowsSizeValidator=Invalid number | ||||||
| # SEB Exam Configuration Template | # SEB Exam Configuration Template | ||||||
| ################################ | ################################ | ||||||
| 
 | 
 | ||||||
|  | sebserver.configtemplate.action.list=Configuration Templates | ||||||
| sebserver.configtemplate.list.title=Configuration Templates | sebserver.configtemplate.list.title=Configuration Templates | ||||||
| sebserver.configtemplate.list.empty=There is currently no SEB-Exam configuration template available. Please create a new one | sebserver.configtemplate.list.empty=There is currently no SEB-Exam configuration template available. Please create a new one | ||||||
| sebserver.configtemplate.list.actions= | sebserver.configtemplate.list.actions= | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 anhefti
						anhefti