fixed quit JSON

This commit is contained in:
anhefti 2020-02-10 10:27:45 +01:00
parent 857706c7b7
commit 1ecaa132a9
18 changed files with 427 additions and 220 deletions

View file

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

View file

@ -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";

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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