diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java index 1aa46112..cd5fd0c9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/Constants.java @@ -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(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java index c0f0c0ba..abef516e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/API.java @@ -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"; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ConfigTemplateList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ConfigTemplateList.java new file mode 100644 index 00000000..5694776d --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ConfigTemplateList.java @@ -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 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()); + + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java index a3343662..705d1527 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java @@ -192,43 +192,77 @@ 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()) { - 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(); + 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() + .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) - .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(); + 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)) + .noEventPropagation() + .create()) + .publish(); + } - actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION) - .withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED)) - .noEventPropagation() - .withSwitchAction( - actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION) - .withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED)) - .noEventPropagation() - .create()) - .publish(); + if (clientTable.isStatusHidden(ConnectionStatus.DISABLED)) { + actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION) + .withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED)) + .noEventPropagation() + .withSwitchAction( + actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION) + .withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED)) + .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(); + } } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigList.java index 547ad313..7c4ed06e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigList.java @@ -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( 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 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()); + ; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java index 75aa5ef6..ef198c48 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java @@ -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), diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index 51ddc951..ec26c6b1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -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, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java index c53f2018..35352c1f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java @@ -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)); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivityDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivityDefinition.java index ea29fe86..af5a0420 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivityDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivityDefinition.java @@ -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")); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java index 451f6e2c..493fb22c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinitionImpl.java @@ -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, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java index 9ceea8f4..2c4123ef 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java @@ -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) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCombo.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCombo.java index 1f006b87..47e322db 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCombo.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/MultiSelectionCombo.java @@ -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> selectionControls = new ArrayList<>(); + private final List selectionControls = new ArrayList<>(); + + private final List> valueMapping = new ArrayList<>(); + private final List> availableValues = new ArrayList<>(); private final List> selectedValues = new ArrayList<>(); - private final Map 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 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> 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 itemForName(final String name) { + final Optional> 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 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> 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> findFirst = this.selectionControls.stream() - .filter(t -> selectionKey.equals(t._2.getData(OPTION_VALUE))) + final Optional findFirst = this.selectionControls.stream() + .filter(t -> selectionKey.equals(t.getData(OPTION_VALUE))) .findFirst(); if (!findFirst.isPresent()) { return; } - final Tuple 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 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); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java index 4e4b5e29..01e0738c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java @@ -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> getConnectionData(Long examId); + Result> getConnectionData( + Long examId, + Predicate filter); /** Use this to check if the current cached running exam is up to date * and if not to flush the cache. diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java index 88f27db6..860868d9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java @@ -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(); }) diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java index 8eb34986..38dcd3c8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java @@ -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> getConnectionData(final Long examId) { + public Result> getConnectionData( + final Long examId, + final Predicate filter) { + return Result.tryCatch(() -> { return this.clientConnectionDAO .getConnectionTokens(examId) .getOrThrow() .stream() .map(this.examSessionCacheService::getActiveClientConnection) - .filter(data -> data != null) + .filter(filter) .collect(Collectors.toList()); }); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SebInstructionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SebInstructionServiceImpl.java index 99655545..781fabf9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SebInstructionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SebInstructionServiceImpl.java @@ -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(); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java index 01e2c3ae..92e1d403 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java @@ -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 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(); } diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index eb5fab60..a2690926 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -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=