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 COMPLEX_VALUE_SEPARATOR = COLON; | ||||
| 
 | ||||
|     public static final String NULL = "null"; | ||||
|     public static final String PERCENTAGE_STRING = Constants.PERCENTAGE.toString(); | ||||
|     public static final String LIST_SEPARATOR = COMMA.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_SEB_CONNECTION_TOKEN_PATH_SEGMENT = | ||||
|             "/{" + 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"; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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,14 +192,21 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
|                         action -> this.disableSebClients(action, clientTable, false), | ||||
|                         EMPTY_SELECTION_TEXT_KEY) | ||||
|                 .noEventPropagation() | ||||
|                 .publishIf(privilege) | ||||
| 
 | ||||
|         ; | ||||
| 
 | ||||
|         clientTable.hideStatus(ConnectionStatus.DISABLED); | ||||
|                 .publishIf(privilege); | ||||
| 
 | ||||
|         if (privilege.getAsBoolean()) { | ||||
| 
 | ||||
|             if (clientTable.isStatusHidden(ConnectionStatus.CLOSED)) { | ||||
|                 actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION) | ||||
|                         .withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED)) | ||||
|                         .noEventPropagation() | ||||
|                         .withSwitchAction( | ||||
|                                 actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION) | ||||
|                                         .withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED)) | ||||
|                                         .noEventPropagation() | ||||
|                                         .create()) | ||||
|                         .publish(); | ||||
|             } else { | ||||
|                 actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION) | ||||
|                         .withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED)) | ||||
|                         .noEventPropagation() | ||||
|  | @ -209,17 +216,33 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
|                                         .noEventPropagation() | ||||
|                                         .create()) | ||||
|                         .publish(); | ||||
|             } | ||||
| 
 | ||||
|             if (clientTable.isStatusHidden(ConnectionStatus.CONNECTION_REQUESTED)) { | ||||
|                 actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION) | ||||
|                         .withExec(showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED)) | ||||
|                         .noEventPropagation() | ||||
|                         .withSwitchAction( | ||||
|                                 actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION) | ||||
|                                         .withExec( | ||||
|                                                 hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED)) | ||||
|                                         .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)) | ||||
|                                         .withExec( | ||||
|                                                 showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED)) | ||||
|                                         .noEventPropagation() | ||||
|                                         .create()) | ||||
|                         .publish(); | ||||
|             } | ||||
| 
 | ||||
|             if (clientTable.isStatusHidden(ConnectionStatus.DISABLED)) { | ||||
|                 actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION) | ||||
|                         .withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED)) | ||||
|                         .noEventPropagation() | ||||
|  | @ -229,6 +252,17 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
|                                         .noEventPropagation() | ||||
|                                         .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; | ||||
| 
 | ||||
| import org.eclipse.swt.widgets.Composite; | ||||
| import org.springframework.beans.factory.annotation.Value; | ||||
| import org.springframework.context.annotation.Lazy; | ||||
| import org.springframework.stereotype.Component; | ||||
| 
 | ||||
|  | @ -46,12 +47,8 @@ public class SebExamConfigList implements TemplateComposer { | |||
|             new LocTextKey("sebserver.examconfig.list.action.no.modify.privilege"); | ||||
|     private static final LocTextKey EMPTY_CONFIG_LIST_TEXT_KEY = | ||||
|             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 = | ||||
|             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 = | ||||
|             new LocTextKey("sebserver.examconfig.list.column.institution"); | ||||
|     private static final LocTextKey NAME_TEXT_KEY = | ||||
|  | @ -62,28 +59,31 @@ public class SebExamConfigList implements TemplateComposer { | |||
|             new LocTextKey("sebserver.examconfig.list.column.status"); | ||||
|     private static final LocTextKey EMPTY_SELECTION_TEXT_KEY = | ||||
|             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 nameFilter = | ||||
|             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 PageService pageService; | ||||
|     private final RestService restService; | ||||
|     private final CurrentUser currentUser; | ||||
|     private final ResourceService resourceService; | ||||
|     private final int pageSize; | ||||
| 
 | ||||
|     protected SebExamConfigList( | ||||
|             final PageService pageService, | ||||
|             final RestService restService, | ||||
|             final CurrentUser currentUser) { | ||||
|             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, | ||||
|  | @ -99,7 +99,6 @@ public class SebExamConfigList implements TemplateComposer { | |||
|     @Override | ||||
|     public void compose(final PageContext pageContext) { | ||||
|         final WidgetFactory widgetFactory = this.pageService.getWidgetFactory(); | ||||
| 
 | ||||
|         final Composite content = widgetFactory.defaultPageLayout( | ||||
|                 pageContext.getParent(), | ||||
|                 TITLE_CONFIGURATION_TEXT_KEY); | ||||
|  | @ -115,7 +114,7 @@ public class SebExamConfigList implements TemplateComposer { | |||
|                                 Domain.CONFIGURATION_NODE.ATTR_TYPE, | ||||
|                                 ConfigurationType.EXAM_CONFIG.name()) | ||||
|                         .withEmptyMessage(EMPTY_CONFIG_LIST_TEXT_KEY) | ||||
|                         .withPaging(6) | ||||
|                         .withPaging(this.pageSize) | ||||
|                         .withColumnIf( | ||||
|                                 () -> isSEBAdmin, | ||||
|                                 () -> new ColumnDefinition<>( | ||||
|  | @ -134,7 +133,7 @@ public class SebExamConfigList implements TemplateComposer { | |||
|                                 Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, | ||||
|                                 DESCRIPTION_TEXT_KEY, | ||||
|                                 ConfigurationNode::getDescription) | ||||
|                                         .withFilter(this.nameFilter) | ||||
|                                         .withFilter(this.descFilter) | ||||
|                                         .sortable()) | ||||
|                         .withColumn(new ColumnDefinition<ConfigurationNode>( | ||||
|                                 Domain.CONFIGURATION_NODE.ATTR_STATUS, | ||||
|  | @ -147,45 +146,6 @@ public class SebExamConfigList implements TemplateComposer { | |||
|                                 .create()) | ||||
|                         .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); | ||||
|         pageActionBuilder | ||||
|                 // Exam Configuration actions... | ||||
|  | @ -214,20 +174,7 @@ public class SebExamConfigList implements TemplateComposer { | |||
|                 .noEventPropagation() | ||||
|                 .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), | ||||
|     SEB_CLIENT_CONFIG_LIST(new LocTextKey("sebserver.clientconfig.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), | ||||
|     RUNNING_EXAM_LIST(new LocTextKey("sebserver.monitoring.exam.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, | ||||
|             ActionCategory.FORM), | ||||
| 
 | ||||
|     SEB_EXAM_CONFIG_TEMPLATE_LIST( | ||||
|             new LocTextKey("sebserver.configtemplate.action.list"), | ||||
|             PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_LIST), | ||||
|     SEB_EXAM_CONFIG_TEMPLATE_NEW( | ||||
|             new LocTextKey("sebserver.configtemplate.action.list.new"), | ||||
|             ImageIcon.TEMPLATE, | ||||
|  |  | |||
|  | @ -214,6 +214,18 @@ public class ActivitiesPane implements TemplateComposer { | |||
|                                 .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)); | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ public enum ActivityDefinition implements Activity { | |||
|     SEB_CONFIGURATION(new LocTextKey("sebserver.overall.activity.title.sebconfig")), | ||||
|     SEB_CLIENT_CONFIG(new LocTextKey("sebserver.clientconfig.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_EXAMS(new LocTextKey("sebserver.monitoring.action.list")), | ||||
|     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.ConfigTemplateForm; | ||||
| import ch.ethz.seb.sebserver.gui.content.ConfigTemplateList; | ||||
| import ch.ethz.seb.sebserver.gui.content.ExamForm; | ||||
| import ch.ethz.seb.sebserver.gui.content.ExamList; | ||||
| 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_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_EDIT(Type.FORM_EDIT, ConfigTemplateForm.class, ActivityDefinition.SEB_EXAM_CONFIG), | ||||
|     SEB_EXAM_CONFIG_TEMPLATE_LIST(Type.LIST_VIEW, ConfigTemplateList.class, | ||||
|             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( | ||||
|             Type.FORM_EDIT, | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ | |||
| package ch.ethz.seb.sebserver.gui.service.session; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.Comparator; | ||||
|  | @ -65,6 +66,7 @@ public final class ClientConnectionTable { | |||
| 
 | ||||
|     private static final int BOTTOM_PADDING = 20; | ||||
|     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 = | ||||
|             "sebserver.monitoring.connection.list.column.indicator."; | ||||
|  | @ -117,6 +119,7 @@ public final class ClientConnectionTable { | |||
|                 NUMBER_OF_NONE_INDICATOR_COLUMNS); | ||||
| 
 | ||||
|         this.statusFilter = EnumSet.noneOf(ConnectionStatus.class); | ||||
|         loadStatusFilter(); | ||||
| 
 | ||||
|         this.table = this.widgetFactory.tableLocalized(tableRoot, SWT.MULTI | SWT.V_SCROLL); | ||||
|         final GridLayout gridLayout = new GridLayout(3 + indicators.size(), true); | ||||
|  | @ -169,10 +172,45 @@ public final class ClientConnectionTable { | |||
| 
 | ||||
|     public void hideStatus(final ConnectionStatus status) { | ||||
|         this.statusFilter.add(status); | ||||
|         saveStatusFilter(); | ||||
|     } | ||||
| 
 | ||||
|     public void showStatus(final ConnectionStatus 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) { | ||||
|  |  | |||
|  | @ -10,52 +10,45 @@ package ch.ethz.seb.sebserver.gui.widget; | |||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.HashMap; | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Map.Entry; | ||||
| import java.util.Optional; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| import org.apache.commons.lang3.StringUtils; | ||||
| import org.eclipse.rap.rwt.widgets.DropDown; | ||||
| import org.eclipse.swt.SWT; | ||||
| import org.eclipse.swt.layout.GridData; | ||||
| import org.eclipse.swt.layout.GridLayout; | ||||
| import org.eclipse.swt.widgets.Combo; | ||||
| import org.eclipse.swt.widgets.Composite; | ||||
| import org.eclipse.swt.widgets.Control; | ||||
| import org.eclipse.swt.widgets.Event; | ||||
| import org.eclipse.swt.widgets.Label; | ||||
| import org.eclipse.swt.widgets.Listener; | ||||
| import org.eclipse.swt.widgets.Text; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| 
 | ||||
| import ch.ethz.seb.sebserver.gbl.Constants; | ||||
| 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.widget.WidgetFactory.ImageIcon; | ||||
| 
 | ||||
| public final class MultiSelectionCombo extends Composite implements Selection { | ||||
| 
 | ||||
|     private static final Logger log = LoggerFactory.getLogger(MultiSelectionCombo.class); | ||||
|     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 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 Map<String, String> mapping = new HashMap<>(); | ||||
| 
 | ||||
|     private final GridData comboCell; | ||||
|     private final GridData actionCell; | ||||
|     private final DropDown dropDown; | ||||
|     private final Text textInput; | ||||
|     private final GridData textCell; | ||||
|     private final Composite updateAnchor; | ||||
| 
 | ||||
|     private Listener listener = null; | ||||
|  | @ -68,14 +61,8 @@ public final class MultiSelectionCombo extends Composite implements Selection { | |||
| 
 | ||||
|         super(parent, SWT.NONE); | ||||
|         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.marginLeft = 0; | ||||
|         gridLayout.marginHeight = 0; | ||||
|  | @ -84,22 +71,43 @@ public final class MultiSelectionCombo extends Composite implements Selection { | |||
|         setLayout(gridLayout); | ||||
| 
 | ||||
|         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; | ||||
|     } | ||||
| 
 | ||||
|     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 | ||||
|     public Type type() { | ||||
|         return Type.MULTI_COMBO; | ||||
|  | @ -112,8 +120,8 @@ public final class MultiSelectionCombo extends Composite implements Selection { | |||
| 
 | ||||
|     @Override | ||||
|     public void applyNewMapping(final List<Tuple<String>> mapping) { | ||||
|         this.mapping.putAll(mapping.stream() | ||||
|                 .collect(Collectors.toMap(t -> t._1, t -> t._2))); | ||||
|         this.valueMapping.clear(); | ||||
|         this.valueMapping.addAll(mapping); | ||||
|         this.clear(); | ||||
|     } | ||||
| 
 | ||||
|  | @ -126,9 +134,22 @@ public final class MultiSelectionCombo extends Composite implements Selection { | |||
| 
 | ||||
|         Arrays.asList(StringUtils.split(keys, Constants.LIST_SEPARATOR)) | ||||
|                 .stream() | ||||
|                 .map(this::itemForName) | ||||
|                 .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 | ||||
|     public String getSelectionValue() { | ||||
|         if (this.selectedValues.isEmpty()) { | ||||
|  | @ -151,61 +172,28 @@ public final class MultiSelectionCombo extends Composite implements Selection { | |||
|         this.selectedValues.clear(); | ||||
|         this.selectionControls | ||||
|                 .stream() | ||||
|                 .forEach(t -> { | ||||
|                     t._1.dispose(); | ||||
|                     t._2.dispose(); | ||||
|                 }); | ||||
|                 .forEach(Control::dispose); | ||||
|         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) { | ||||
|         final int selectionIndex = this.combo.getSelectionIndex(); | ||||
|         if (selectionIndex < 0) { | ||||
|     private void addSelection(final Tuple<String> item) { | ||||
|         if (item == null) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         final String itemName = this.combo.getItem(selectionIndex); | ||||
|         if (itemName == null) { | ||||
|             return; | ||||
|         } | ||||
|         this.selectedValues.add(item); | ||||
|         final Label label = this.widgetFactory.label(this, item._2); | ||||
|         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() | ||||
|                 .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); | ||||
|         this.availableValues.remove(item); | ||||
|         PageService.updateScrolledComposite(this); | ||||
|         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 Optional<Tuple<Control>> findFirst = this.selectionControls.stream() | ||||
|                 .filter(t -> selectionKey.equals(t._2.getData(OPTION_VALUE))) | ||||
|         final Optional<Control> findFirst = this.selectionControls.stream() | ||||
|                 .filter(t -> selectionKey.equals(t.getData(OPTION_VALUE))) | ||||
|                 .findFirst(); | ||||
|         if (!findFirst.isPresent()) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         final Tuple<Control> tuple = findFirst.get(); | ||||
|         final int indexOf = this.selectionControls.indexOf(tuple); | ||||
|         this.selectionControls.remove(tuple); | ||||
| 
 | ||||
|         tuple._1.dispose(); | ||||
|         tuple._2.dispose(); | ||||
|         final Control control = findFirst.get(); | ||||
|         final int indexOf = this.selectionControls.indexOf(control); | ||||
|         this.selectionControls.remove(control); | ||||
|         control.dispose(); | ||||
| 
 | ||||
|         final Tuple<String> value = this.selectedValues.remove(indexOf); | ||||
|         this.combo.add(value._2, this.combo.getItemCount()); | ||||
|         this.availableValues.add(value); | ||||
| 
 | ||||
|         PageService.updateScrolledComposite(this); | ||||
|         this.updateAnchor.layout(true, true); | ||||
|  | @ -244,7 +230,7 @@ public final class MultiSelectionCombo extends Composite implements Selection { | |||
|     private void adaptColumnWidth(final Event event) { | ||||
|         try { | ||||
|             final int currentTableWidth = this.getClientArea().width; | ||||
|             this.comboCell.widthHint = currentTableWidth - ACTION_COLUMN_WIDTH; | ||||
|             this.textCell.widthHint = currentTableWidth; | ||||
|             this.layout(); | ||||
|         } catch (final Exception e) { | ||||
|             log.warn("Failed to adaptColumnWidth: ", e); | ||||
|  |  | |||
|  | @ -136,9 +136,12 @@ public interface ExamSessionService { | |||
|      * a subset of them. | ||||
|      * | ||||
|      * @param examId The exam identifier | ||||
|      * @param filter a filter predicate to apply | ||||
|      * @return collection of ClientConnectionData of all active SEB client connections | ||||
|      *         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 | ||||
|      * 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.Collections; | ||||
| import java.util.Objects; | ||||
| import java.util.function.Function; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
|  | @ -224,12 +225,7 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService { | |||
|     } | ||||
| 
 | ||||
|     private void checkActiveClientConnections(final Exam exam) { | ||||
|         if (this.examSessionService.getConnectionData(exam.id) | ||||
|                 .getOrThrow() | ||||
|                 .stream() | ||||
|                 .filter(ExamSessionService::isActiveConnection) | ||||
|                 .count() > 0) { | ||||
| 
 | ||||
|         if (this.examSessionService.hasActiveSebClientConnections(exam.id)) { | ||||
|             throw new APIMessage.APIMessageException( | ||||
|                     ErrorMessage.INTEGRITY_VALIDATION, | ||||
|                     "Integrity violation: There are currently active SEB Client connection."); | ||||
|  | @ -286,7 +282,7 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService { | |||
|         final long activeConnections = involvedExams | ||||
|                 .stream() | ||||
|                 .flatMap(examId -> { | ||||
|                     return this.examSessionService.getConnectionData(examId) | ||||
|                     return this.examSessionService.getConnectionData(examId, Objects::nonNull) | ||||
|                             .getOrThrow() | ||||
|                             .stream(); | ||||
|                 }) | ||||
|  |  | |||
|  | @ -164,12 +164,9 @@ public class ExamSessionServiceImpl implements ExamSessionService { | |||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return this.getConnectionData(examId) | ||||
|         return !this.getConnectionData(examId, ExamSessionService::isActiveConnection) | ||||
|                 .getOrThrow() | ||||
|                 .stream() | ||||
|                 .filter(ExamSessionService::isActiveConnection) | ||||
|                 .findFirst() | ||||
|                 .isPresent(); | ||||
|                 .isEmpty(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | @ -313,14 +310,17 @@ public class ExamSessionServiceImpl implements ExamSessionService { | |||
|     } | ||||
| 
 | ||||
|     @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 this.clientConnectionDAO | ||||
|                     .getConnectionTokens(examId) | ||||
|                     .getOrThrow() | ||||
|                     .stream() | ||||
|                     .map(this.examSessionCacheService::getActiveClientConnection) | ||||
|                     .filter(data -> data != null) | ||||
|                     .filter(filter) | ||||
|                     .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()); | ||||
|             } | ||||
| 
 | ||||
|             // {"instruction":"%s", "attributes":{%s}} | ||||
|             return new StringBuilder() | ||||
|             // {"instruction":"%s", "attributes":%s} | ||||
|             final String attributes = clientInstruction.getAttributes(); | ||||
|             final StringBuilder sBuilder = new StringBuilder() | ||||
|                     .append(Constants.CURLY_BRACE_OPEN) | ||||
|                     .append(Constants.DOUBLE_QUOTE) | ||||
|                     .append(JSON_INST) | ||||
|  | @ -137,10 +138,16 @@ public class SebInstructionServiceImpl implements SebInstructionService { | |||
|                     .append(Constants.DOUBLE_QUOTE) | ||||
|                     .append(JSON_ATTR) | ||||
|                     .append(Constants.DOUBLE_QUOTE) | ||||
|                     .append(Constants.COLON) | ||||
|                     .append(Constants.CURLY_BRACE_OPEN) | ||||
|                     .append(clientInstruction.getAttributes()) | ||||
|                     .append(Constants.CURLY_BRACE_CLOSE) | ||||
|                     .append(Constants.COLON); | ||||
|             if (attributes == null || attributes.isEmpty()) { | ||||
|                 sBuilder.append(Constants.NULL); | ||||
| 
 | ||||
|             } else { | ||||
|                 sBuilder.append(Constants.CURLY_BRACE_OPEN) | ||||
|                         .append(attributes) | ||||
|                         .append(Constants.CURLY_BRACE_CLOSE); | ||||
|             } | ||||
|             return sBuilder | ||||
|                     .append(Constants.CURLY_BRACE_CLOSE) | ||||
|                     .toString(); | ||||
|         } | ||||
|  |  | |||
|  | @ -10,7 +10,9 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api; | |||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.EnumSet; | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
| 
 | ||||
| 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.PathVariable; | ||||
| 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.RequestMethod; | ||||
| 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.Page; | ||||
| 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.ClientInstruction; | ||||
| import ch.ethz.seb.sebserver.gbl.model.user.UserRole; | ||||
|  | @ -153,7 +157,8 @@ public class ExamMonitoringController { | |||
|                     name = API.PARAM_INSTITUTION_ID, | ||||
|                     required = true, | ||||
|                     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 | ||||
|         this.authorization.checkRole( | ||||
|  | @ -169,8 +174,20 @@ public class ExamMonitoringController { | |||
|                     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 | ||||
|                 .getConnectionData(examId) | ||||
|                 .getConnectionData( | ||||
|                         examId, | ||||
|                         filterStates.isEmpty() | ||||
|                                 ? Objects::nonNull | ||||
|                                 : conn -> conn != null && filterStates.contains(conn.clientConnection.status)) | ||||
|                 .getOrThrow(); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ sebserver.overall.action.cancel=Cancel | |||
| 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.category.varia= | ||||
| sebserver.overall.action.category.filter=Connection Status Filter | ||||
| sebserver.overall.action.category.filter= | ||||
| 
 | ||||
| sebserver.overall.status.active=Active | ||||
| sebserver.overall.status.inactive=Inactive | ||||
|  | @ -380,7 +380,7 @@ sebserver.exam.status.UP_COMING=Up Coming | |||
| sebserver.exam.status.RUNNING=Running | ||||
| 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.column.name=Name | ||||
| 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.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.column.type=Type | ||||
| sebserver.exam.indicator.list.column.name=Name | ||||
|  | @ -1011,6 +1011,7 @@ sebserver.examconfig.props.validation.WindowsSizeValidator=Invalid number | |||
| # SEB Exam Configuration Template | ||||
| ################################ | ||||
| 
 | ||||
| sebserver.configtemplate.action.list=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.actions= | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 anhefti
						anhefti