monitoring improvements

This commit is contained in:
anhefti 2019-12-19 16:44:42 +01:00
parent 25ab3106aa
commit 0e528a3c86
27 changed files with 382 additions and 165 deletions

View file

@ -22,13 +22,19 @@ import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
public final class ClientConnection implements GrantEntity { public final class ClientConnection implements GrantEntity {
public enum ConnectionStatus { public enum ConnectionStatus {
UNDEFINED, UNDEFINED(false),
CONNECTION_REQUESTED, CONNECTION_REQUESTED(false),
AUTHENTICATED, AUTHENTICATED(true),
ESTABLISHED, ACTIVE(true),
CLOSED, CLOSED(false),
ABORTED, DISABLED(false);
DISABLED
public final boolean establishedStatus;
private ConnectionStatus(final boolean establishedStatus) {
this.establishedStatus = establishedStatus;
}
} }
public static final ClientConnection EMPTY_CLIENT_CONNECTION = new ClientConnection( public static final ClientConnection EMPTY_CLIENT_CONNECTION = new ClientConnection(

View file

@ -16,6 +16,8 @@ import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
public class ClientConnectionData { public class ClientConnectionData {
@ -24,6 +26,8 @@ public class ClientConnectionData {
public final ClientConnection clientConnection; public final ClientConnection clientConnection;
@JsonProperty("indicatorValues") @JsonProperty("indicatorValues")
public final List<? extends IndicatorValue> indicatorValues; public final List<? extends IndicatorValue> indicatorValues;
@JsonIgnore
public final boolean missingPing;
@JsonCreator @JsonCreator
protected ClientConnectionData( protected ClientConnectionData(
@ -32,6 +36,12 @@ public class ClientConnectionData {
this.clientConnection = clientConnection; this.clientConnection = clientConnection;
this.indicatorValues = Utils.immutableListOf(indicatorValues); this.indicatorValues = Utils.immutableListOf(indicatorValues);
this.missingPing = clientConnection.status == ConnectionStatus.ACTIVE &&
this.indicatorValues.stream()
.filter(ind -> ind.getType() == IndicatorType.LAST_PING)
.findFirst()
.map(ind -> (long) ind.getValue())
.orElse(0L) > 5000;
} }
protected ClientConnectionData( protected ClientConnectionData(
@ -40,6 +50,7 @@ public class ClientConnectionData {
this.clientConnection = clientConnection; this.clientConnection = clientConnection;
this.indicatorValues = Utils.immutableListOf(indicatorValues); this.indicatorValues = Utils.immutableListOf(indicatorValues);
this.missingPing = false;
} }
@JsonIgnore @JsonIgnore

View file

@ -211,6 +211,10 @@ public class MonitoringClientConnection implements TemplateComposer {
.clearAttributes() .clearAttributes()
.clearEntityKeys()) .clearEntityKeys())
.newAction(ActionDefinition.MONITOR_EXAM_FROM_DETAILS)
.withEntityKey(parentEntityKey)
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER))
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_QUIT) .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_QUIT)
.withConfirm(() -> CONFIRM_QUIT) .withConfirm(() -> CONFIRM_QUIT)
.withExec(action -> { .withExec(action -> {
@ -221,10 +225,6 @@ public class MonitoringClientConnection implements TemplateComposer {
return action; return action;
}) })
.noEventPropagation() .noEventPropagation()
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER))
.newAction(ActionDefinition.MONITOR_EXAM_FROM_DETAILS)
.withEntityKey(parentEntityKey)
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER)); .publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER));
} }

View file

@ -12,6 +12,7 @@ import java.util.Collection;
import java.util.Set; import java.util.Set;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
@ -184,39 +185,70 @@ public class MonitoringRunningExam implements TemplateComposer {
.noEventPropagation() .noEventPropagation()
.publishIf(privilege); .publishIf(privilege);
clientTable.hideStatus(ConnectionStatus.DISABLED);
if (privilege.getAsBoolean()) { if (privilege.getAsBoolean()) {
final PageAction showClosedConnections =
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
.withExec(action -> {
clientTable.showStatus(ConnectionStatus.CLOSED);
return action;
})
.noEventPropagation()
.create();
final PageAction hideClosedConnections =
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION) actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
.withExec(action -> { .withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
clientTable.hideStatus(ConnectionStatus.CLOSED);
return action;
})
.noEventPropagation() .noEventPropagation()
.withSwitchAction(showClosedConnections) .withSwitchAction(
.create(); 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();
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();
this.pageService.publishAction(clientTable.isStatusHidden(ConnectionStatus.CLOSED)
? showClosedConnections
: hideClosedConnections);
} }
} }
private static final Function<PageAction, PageAction> showStateViewAction(
final ClientConnectionTable clientTable,
final ConnectionStatus status) {
return action -> {
clientTable.showStatus(status);
return action;
};
}
private static final Function<PageAction, PageAction> hideStateViewAction(
final ClientConnectionTable clientTable,
final ConnectionStatus status) {
return action -> {
clientTable.hideStatus(status);
return action;
};
}
private PageAction quitSebClients( private PageAction quitSebClients(
final PageAction action, final PageAction action,
final ClientConnectionTable clientTable, final ClientConnectionTable clientTable,
final boolean all) { final boolean all) {
final Predicate<ClientConnection> activePredicate = ClientConnection final Predicate<ClientConnection> activePredicate = ClientConnection
.getStatusPredicate(ConnectionStatus.ESTABLISHED); .getStatusPredicate(ConnectionStatus.ACTIVE);
final Set<String> connectionTokens = clientTable.getConnectionTokens( final Set<String> connectionTokens = clientTable.getConnectionTokens(
activePredicate, activePredicate,

View file

@ -200,7 +200,7 @@ public class SebClientLogs implements TemplateComposer {
TIME_TEXT_KEY, TIME_TEXT_KEY,
this::getEventTime) this::getEventTime)
.withFilter(new TableFilterAttribute( .withFilter(new TableFilterAttribute(
CriteriaType.DATE_RANGE, CriteriaType.DATE_TIME_RANGE,
ClientEvent.FILTER_ATTR_SERVER_TIME_FROM_TO, ClientEvent.FILTER_ATTR_SERVER_TIME_FROM_TO,
Utils.toDateTimeUTC(Utils.getMillisecondsNow()) Utils.toDateTimeUTC(Utils.getMillisecondsNow())
.minusYears(1) .minusYears(1)

View file

@ -28,7 +28,7 @@ public enum ActionCategory {
LOGS_USER_ACTIVITY_LIST(new LocTextKey("sebserver.userlogs.list.actions"), 1), LOGS_USER_ACTIVITY_LIST(new LocTextKey("sebserver.userlogs.list.actions"), 1),
LOGS_SEB_CLIENT_LIST(new LocTextKey("sebserver.userlogs.list.actions"), 1), LOGS_SEB_CLIENT_LIST(new LocTextKey("sebserver.userlogs.list.actions"), 1),
VARIA(new LocTextKey("sebserver.overall.action.category.varia"), 100), VARIA(new LocTextKey("sebserver.overall.action.category.varia"), 100),
; FILTER(new LocTextKey("sebserver.overall.action.category.filter"), 50);
public final LocTextKey title; public final LocTextKey title;
public final int slotPosition; public final int slotPosition;

View file

@ -582,16 +582,36 @@ public enum ActionDefinition {
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.CLIENT_EVENT_LIST), ActionCategory.CLIENT_EVENT_LIST),
MONITOR_EXAM_HIDE_REQUESTED_CONNECTION(
new LocTextKey("sebserver.monitoring.exam.connection.action.hide.requested"),
ImageIcon.TOGGLE_OFF,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.FILTER),
MONITOR_EXAM_SHOW_REQUESTED_CONNECTION(
new LocTextKey("sebserver.monitoring.exam.connection.action.show.requested"),
ImageIcon.TOGGLE_ON,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.FILTER),
MONITOR_EXAM_HIDE_CLOSED_CONNECTION( MONITOR_EXAM_HIDE_CLOSED_CONNECTION(
new LocTextKey("sebserver.monitoring.exam.connection.action.hide.closed"), new LocTextKey("sebserver.monitoring.exam.connection.action.hide.closed"),
ImageIcon.TOGGLE_OFF, ImageIcon.TOGGLE_OFF,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.FORM), ActionCategory.FILTER),
MONITOR_EXAM_SHOW_CLOSED_CONNECTION( MONITOR_EXAM_SHOW_CLOSED_CONNECTION(
new LocTextKey("sebserver.monitoring.exam.connection.action.show.closed"), new LocTextKey("sebserver.monitoring.exam.connection.action.show.closed"),
ImageIcon.TOGGLE_ON, ImageIcon.TOGGLE_ON,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.FORM), ActionCategory.FILTER),
MONITOR_EXAM_HIDE_DISABLED_CONNECTION(
new LocTextKey("sebserver.monitoring.exam.connection.action.hide.disabled"),
ImageIcon.TOGGLE_OFF,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.FILTER),
MONITOR_EXAM_SHOW_DISABLED_CONNECTION(
new LocTextKey("sebserver.monitoring.exam.connection.action.show.disabled"),
ImageIcon.TOGGLE_ON,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.FILTER),
LOGS_USER_ACTIVITY_LIST( LOGS_USER_ACTIVITY_LIST(
new LocTextKey("sebserver.logs.activity.userlogs"), new LocTextKey("sebserver.logs.activity.userlogs"),

View file

@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.form;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT; import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
@ -27,6 +28,7 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
boolean isNumber = false; boolean isNumber = false;
Consumer<String> numberCheck = null; Consumer<String> numberCheck = null;
boolean isArea = false; boolean isArea = false;
boolean isColorbox = false;
TextFieldBuilder(final String name, final LocTextKey label, final String value) { TextFieldBuilder(final String name, final LocTextKey label, final String value) {
super(name, label, value); super(name, label, value);
@ -53,6 +55,11 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
return this; return this;
} }
public TextFieldBuilder asColorbox() {
this.isColorbox = true;
return this;
}
@Override @Override
void build(final FormBuilder builder) { void build(final FormBuilder builder) {
final boolean readonly = builder.readonly || this.readonly; final boolean readonly = builder.readonly || this.readonly;
@ -75,6 +82,9 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true); final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
if (this.isArea) { if (this.isArea) {
gridData.minimumHeight = WidgetFactory.TEXT_AREA_INPUT_MIN_HEIGHT; gridData.minimumHeight = WidgetFactory.TEXT_AREA_INPUT_MIN_HEIGHT;
} else if (this.isColorbox) {
gridData.minimumHeight = WidgetFactory.TEXT_INPUT_MIN_HEIGHT;
textInput.setData(RWT.CUSTOM_VARIANT, "colorbox");
} }
textInput.setLayoutData(gridData); textInput.setLayoutData(gridData);
if (StringUtils.isNoneBlank(this.value)) { if (StringUtils.isNoneBlank(this.value)) {

View file

@ -44,6 +44,7 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.Configuration
import ch.ethz.seb.sebserver.gbl.model.sebconfig.TemplateAttribute; import ch.ethz.seb.sebserver.gbl.model.sebconfig.TemplateAttribute;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.View; import ch.ethz.seb.sebserver.gbl.model.sebconfig.View;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog; import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
@ -73,6 +74,8 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
* combo-box content. */ * combo-box content. */
public class ResourceService { public class ResourceService {
private static final String MISSING_CLIENT_PING_NAME_KEY = "MISSING";
public static final Comparator<Tuple<String>> RESOURCE_COMPARATOR = (t1, t2) -> t1._2.compareTo(t2._2); public static final Comparator<Tuple<String>> RESOURCE_COMPARATOR = (t1, t2) -> t1._2.compareTo(t2._2);
public static final EnumSet<EntityType> ENTITY_TYPE_EXCLUDE_MAP = EnumSet.of( public static final EnumSet<EntityType> ENTITY_TYPE_EXCLUDE_MAP = EnumSet.of(
@ -436,6 +439,24 @@ public class ResourceService {
.getText(ResourceService.EXAMCONFIG_STATUS_PREFIX + config.configStatus.name()); .getText(ResourceService.EXAMCONFIG_STATUS_PREFIX + config.configStatus.name());
} }
public String localizedClientConnectionStatusName(final ClientConnectionData connectionData) {
if (connectionData == null) {
final String name = ConnectionStatus.UNDEFINED.name();
return this.i18nSupport.getText(
SEB_CONNECTION_STATUS_KEY_PREFIX + name,
name);
}
if (connectionData.missingPing) {
return this.i18nSupport.getText(
SEB_CONNECTION_STATUS_KEY_PREFIX + MISSING_CLIENT_PING_NAME_KEY,
MISSING_CLIENT_PING_NAME_KEY);
} else {
return localizedClientConnectionStatusName((connectionData.clientConnection != null)
? connectionData.clientConnection.status
: ConnectionStatus.UNDEFINED);
}
}
public String localizedClientConnectionStatusName(final ConnectionStatus status) { public String localizedClientConnectionStatusName(final ConnectionStatus status) {
String name; String name;
if (status != null) { if (status != null) {

View file

@ -323,17 +323,18 @@ public interface PageService {
} }
public PageActionBuilder newAction(final ActionDefinition definition) { public PageActionBuilder newAction(final ActionDefinition definition) {
pageContext = originalPageContext.copy(); final PageActionBuilder newBuilder = new PageActionBuilder(this.pageService, this.originalPageContext);
this.definition = definition; newBuilder.pageContext = originalPageContext.copy();
confirm = null; newBuilder.definition = definition;
successMessage = null; newBuilder.confirm = null;
selectionSupplier = null; newBuilder.successMessage = null;
noSelectionMessage = null; newBuilder.selectionSupplier = null;
exec = null; newBuilder.noSelectionMessage = null;
fireActionEvent = true; newBuilder.exec = null;
ignoreMoveAwayFromEdit = false; newBuilder.fireActionEvent = true;
switchAction = null; newBuilder.ignoreMoveAwayFromEdit = false;
return this; newBuilder.switchAction = null;
return newBuilder;
} }
public PageAction create() { public PageAction create() {

View file

@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gui.service.session;
import java.util.Collection; import java.util.Collection;
import java.util.EnumMap; import java.util.EnumMap;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Display;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -21,12 +22,12 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue; import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue;
import ch.ethz.seb.sebserver.gui.form.Form; import ch.ethz.seb.sebserver.gui.form.Form;
import ch.ethz.seb.sebserver.gui.form.FormBuilder; import ch.ethz.seb.sebserver.gui.form.FormBuilder;
import ch.ethz.seb.sebserver.gui.form.FormHandle; import ch.ethz.seb.sebserver.gui.form.FormHandle;
import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; 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.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.PageService;
@ -50,13 +51,15 @@ public class ClientConnectionDetails {
private static final int NUMBER_OF_NONE_INDICATOR_ROWS = 3; private static final int NUMBER_OF_NONE_INDICATOR_ROWS = 3;
private final PageService pageService; private final PageService pageService;
private final ResourceService resourceService;
private final Exam exam; private final Exam exam;
private final EnumMap<IndicatorType, IndicatorData> indicatorMapping; private final EnumMap<IndicatorType, IndicatorData> indicatorMapping;
private final RestCall<ClientConnectionData>.RestCallBuilder restCallBuilder; private final RestCall<ClientConnectionData>.RestCallBuilder restCallBuilder;
private final FormHandle<?> formhandle; private final FormHandle<?> formhandle;
private final StatusData statusColor; private final ColorData colorData;
private ClientConnectionData connectionData = null; private ClientConnectionData connectionData = null;
private boolean statusChanged = true;
public ClientConnectionDetails( public ClientConnectionDetails(
final PageService pageService, final PageService pageService,
@ -68,12 +71,14 @@ public class ClientConnectionDetails {
final Display display = pageContext.getRoot().getDisplay(); final Display display = pageContext.getRoot().getDisplay();
this.pageService = pageService; this.pageService = pageService;
this.resourceService = pageService.getResourceService();
this.exam = exam; this.exam = exam;
this.restCallBuilder = restCallBuilder; this.restCallBuilder = restCallBuilder;
this.statusColor = new StatusData(display); this.colorData = new ColorData(display);
this.indicatorMapping = IndicatorData.createFormIndicators( this.indicatorMapping = IndicatorData.createFormIndicators(
indicators, indicators,
display, display,
this.colorData,
NUMBER_OF_NONE_INDICATOR_ROWS); NUMBER_OF_NONE_INDICATOR_ROWS);
final FormBuilder formBuilder = this.pageService.formBuilder(pageContext, 4) final FormBuilder formBuilder = this.pageService.formBuilder(pageContext, 4)
@ -94,7 +99,8 @@ public class ClientConnectionDetails {
.addField(FormBuilder.text( .addField(FormBuilder.text(
Domain.CLIENT_CONNECTION.ATTR_STATUS, Domain.CLIENT_CONNECTION.ATTR_STATUS,
CONNECTION_STATUS_TEXT_KEY, CONNECTION_STATUS_TEXT_KEY,
Constants.EMPTY_NOTE)) Constants.EMPTY_NOTE)
.asColorbox())
.addEmptyCell(); .addEmptyCell();
this.indicatorMapping this.indicatorMapping
@ -105,6 +111,7 @@ public class ClientConnectionDetails {
indData.indicator.name, indData.indicator.name,
new LocTextKey(indData.indicator.name), new LocTextKey(indData.indicator.name),
Constants.EMPTY_NOTE) Constants.EMPTY_NOTE)
.asColorbox()
.withDefaultLabel(indData.indicator.name)) .withDefaultLabel(indData.indicator.name))
.addEmptyCell(); .addEmptyCell();
}); });
@ -113,12 +120,19 @@ public class ClientConnectionDetails {
} }
public void updateData(final ServerPushContext context) { public void updateData(final ServerPushContext context) {
this.connectionData = this.restCallBuilder final ClientConnectionData connectionData = this.restCallBuilder
.call() .call()
.get(error -> { .get(error -> {
log.error("Unexpected error while trying to get current client connection data: ", error); log.error("Unexpected error while trying to get current client connection data: ", error);
return null; return null;
}); });
if (this.connectionData != null && connectionData != null) {
this.statusChanged =
this.connectionData.clientConnection.status != connectionData.clientConnection.status ||
this.connectionData.missingPing != connectionData.missingPing;
}
this.connectionData = connectionData;
} }
public void updateGUI(final ServerPushContext context) { public void updateGUI(final ServerPushContext context) {
@ -135,13 +149,16 @@ public class ClientConnectionDetails {
Domain.CLIENT_CONNECTION.ATTR_CLIENT_ADDRESS, Domain.CLIENT_CONNECTION.ATTR_CLIENT_ADDRESS,
this.connectionData.clientConnection.clientAddress); this.connectionData.clientConnection.clientAddress);
if (this.statusChanged) {
// update status // update status
form.setFieldValue( form.setFieldValue(
Domain.CLIENT_CONNECTION.ATTR_STATUS, Domain.CLIENT_CONNECTION.ATTR_STATUS,
getStatusName()); this.resourceService.localizedClientConnectionStatusName(this.connectionData));
form.setFieldColor( final Color statusColor = this.colorData.getStatusColor(this.connectionData);
Domain.CLIENT_CONNECTION.ATTR_STATUS, final Color statusTextColor = this.colorData.getStatusTextColor(statusColor);
this.statusColor.getStatusColor(this.connectionData)); form.setFieldColor(Domain.CLIENT_CONNECTION.ATTR_STATUS, statusColor);
form.setFieldTextColor(Domain.CLIENT_CONNECTION.ATTR_STATUS, statusTextColor);
}
// update indicators // update indicators
this.connectionData.getIndicatorValues() this.connectionData.getIndicatorValues()
@ -151,7 +168,7 @@ public class ClientConnectionDetails {
final double value = indValue.getValue(); final double value = indValue.getValue();
final String displayValue = IndicatorValue.getDisplayValue(indValue); final String displayValue = IndicatorValue.getDisplayValue(indValue);
if (this.connectionData.clientConnection.status != ConnectionStatus.ESTABLISHED) { if (!this.connectionData.clientConnection.status.establishedStatus) {
form.setFieldValue( form.setFieldValue(
indData.indicator.name, indData.indicator.name,
@ -175,11 +192,4 @@ public class ClientConnectionDetails {
}); });
} }
String getStatusName() {
return this.pageService.getResourceService().localizedClientConnectionStatusName(
(this.connectionData != null && this.connectionData.clientConnection != null)
? this.connectionData.clientConnection.status
: ConnectionStatus.UNDEFINED);
}
} }

View file

@ -51,7 +51,7 @@ import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue; import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue;
import ch.ethz.seb.sebserver.gbl.util.Tuple; import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction; import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
@ -77,13 +77,13 @@ public final class ClientConnectionTable {
private static final int NUMBER_OF_NONE_INDICATOR_COLUMNS = 3; private static final int NUMBER_OF_NONE_INDICATOR_COLUMNS = 3;
private final PageService pageService;
private final WidgetFactory widgetFactory; private final WidgetFactory widgetFactory;
private final ResourceService resourceService;
private final Exam exam; private final Exam exam;
private final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder; private final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder;
private final EnumMap<IndicatorType, IndicatorData> indicatorMapping; private final EnumMap<IndicatorType, IndicatorData> indicatorMapping;
private final Table table; private final Table table;
private final StatusData statusData; private final ColorData colorData;
private final EnumSet<ConnectionStatus> statusFilter; private final EnumSet<ConnectionStatus> statusFilter;
private int tableWidth; private int tableWidth;
@ -101,13 +101,13 @@ public final class ClientConnectionTable {
final Collection<Indicator> indicators, final Collection<Indicator> indicators,
final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder) { final RestCall<Collection<ClientConnectionData>>.RestCallBuilder restCallBuilder) {
this.pageService = pageService;
this.widgetFactory = pageService.getWidgetFactory(); this.widgetFactory = pageService.getWidgetFactory();
this.resourceService = pageService.getResourceService();
this.exam = exam; this.exam = exam;
this.restCallBuilder = restCallBuilder; this.restCallBuilder = restCallBuilder;
final Display display = tableRoot.getDisplay(); final Display display = tableRoot.getDisplay();
this.statusData = new StatusData(display); this.colorData = new ColorData(display);
this.darkFontColor = new Color(display, Constants.BLACK_RGB); this.darkFontColor = new Color(display, Constants.BLACK_RGB);
this.lightFontColor = new Color(display, Constants.WHITE_RGB); this.lightFontColor = new Color(display, Constants.WHITE_RGB);
@ -115,6 +115,7 @@ public final class ClientConnectionTable {
this.indicatorMapping = IndicatorData.createFormIndicators( this.indicatorMapping = IndicatorData.createFormIndicators(
indicators, indicators,
display, display,
this.colorData,
NUMBER_OF_NONE_INDICATOR_COLUMNS); NUMBER_OF_NONE_INDICATOR_COLUMNS);
this.statusFilter = EnumSet.noneOf(ConnectionStatus.class); this.statusFilter = EnumSet.noneOf(ConnectionStatus.class);
@ -209,9 +210,8 @@ public final class ClientConnectionTable {
for (int i = 0; i < selectionIndices.length; i++) { for (int i = 0; i < selectionIndices.length; i++) {
final UpdatableTableItem updatableTableItem = final UpdatableTableItem updatableTableItem =
new ArrayList<>(this.tableMapping.values()) new ArrayList<>(this.tableMapping.values())
.get(selectionIndices[0]); .get(selectionIndices[i]);
if (filter.test(updatableTableItem.connectionData.clientConnection)) { if (filter.test(updatableTableItem.connectionData.clientConnection)) {
result.add(updatableTableItem.connectionData.clientConnection.connectionToken); result.add(updatableTableItem.connectionData.clientConnection.connectionToken);
} }
} }
@ -375,11 +375,10 @@ public final class ClientConnectionTable {
} }
void updateConnectionStatusColor(final TableItem tableItem) { void updateConnectionStatusColor(final TableItem tableItem) {
final Color statusColor = ClientConnectionTable.this.statusData.getStatusColor(this.connectionData); final Color statusColor = ClientConnectionTable.this.colorData.getStatusColor(this.connectionData);
final Color statusTextColor = ClientConnectionTable.this.colorData.getStatusTextColor(statusColor);
tableItem.setBackground(2, statusColor); tableItem.setBackground(2, statusColor);
tableItem.setForeground(2, Utils.darkColor(statusColor.getRGB()) tableItem.setForeground(2, statusTextColor);
? ClientConnectionTable.this.darkFontColor
: ClientConnectionTable.this.lightFontColor);
} }
void updateDuplicateColor(final TableItem tableItem) { void updateDuplicateColor(final TableItem tableItem) {
@ -392,7 +391,7 @@ public final class ClientConnectionTable {
final List<Long> list = final List<Long> list =
ClientConnectionTable.this.sessionIds.get(this.connectionData.clientConnection.userSessionId); ClientConnectionTable.this.sessionIds.get(this.connectionData.clientConnection.userSessionId);
if (list != null && list.size() > 1) { if (list != null && list.size() > 1) {
tableItem.setBackground(0, ClientConnectionTable.this.statusData.color3); tableItem.setBackground(0, ClientConnectionTable.this.colorData.color3);
tableItem.setForeground(0, ClientConnectionTable.this.lightFontColor); tableItem.setForeground(0, ClientConnectionTable.this.lightFontColor);
} else { } else {
tableItem.setBackground(0, null); tableItem.setBackground(0, null);
@ -409,14 +408,12 @@ public final class ClientConnectionTable {
return; return;
} }
final boolean fillEmpty = this.connectionData.clientConnection.status != ConnectionStatus.ESTABLISHED;
for (int i = 0; i < this.connectionData.indicatorValues.size(); i++) { for (int i = 0; i < this.connectionData.indicatorValues.size(); i++) {
final IndicatorValue indicatorValue = this.connectionData.indicatorValues.get(i); final IndicatorValue indicatorValue = this.connectionData.indicatorValues.get(i);
final IndicatorData indicatorData = final IndicatorData indicatorData =
ClientConnectionTable.this.indicatorMapping.get(indicatorValue.getType()); ClientConnectionTable.this.indicatorMapping.get(indicatorValue.getType());
if (fillEmpty) { if (!this.connectionData.clientConnection.status.establishedStatus) {
final String value = (indicatorData.indicator.type.showOnlyInActiveState) final String value = (indicatorData.indicator.type.showOnlyInActiveState)
? Constants.EMPTY_NOTE ? Constants.EMPTY_NOTE
: IndicatorValue.getDisplayValue(indicatorValue); : IndicatorValue.getDisplayValue(indicatorValue);
@ -470,7 +467,7 @@ public final class ClientConnectionTable {
} }
int statusWeight() { int statusWeight() {
return ClientConnectionTable.this.statusData.statusWeight(this.connectionData); return ClientConnectionTable.this.colorData.statusWeight(this.connectionData);
} }
int thresholdsWeight() { int thresholdsWeight() {
@ -478,10 +475,8 @@ public final class ClientConnectionTable {
} }
String getStatusName() { String getStatusName() {
return ClientConnectionTable.this.pageService.getResourceService().localizedClientConnectionStatusName( return ClientConnectionTable.this.resourceService
(this.connectionData != null && this.connectionData.clientConnection != null) .localizedClientConnectionStatusName(this.connectionData);
? this.connectionData.clientConnection.status
: ConnectionStatus.UNDEFINED);
} }
String getConnectionAddress() { String getConnectionAddress() {

View file

@ -12,20 +12,26 @@ import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Display;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.util.Utils;
public class StatusData { public class ColorData {
final Color darkColor;
final Color lightColor;
final Color defaultColor; final Color defaultColor;
final Color color1; final Color color1;
final Color color2; final Color color2;
final Color color3; final Color color3;
public StatusData(final Display display) { public ColorData(final Display display) {
this.defaultColor = new Color(display, new RGB(255, 255, 255), 255); this.defaultColor = new Color(display, new RGB(220, 220, 220), 255);
this.color1 = new Color(display, new RGB(34, 177, 76), 255); this.color1 = new Color(display, new RGB(34, 177, 76), 255);
this.color2 = new Color(display, new RGB(255, 194, 14), 255); this.color2 = new Color(display, new RGB(255, 194, 14), 255);
this.color3 = new Color(display, new RGB(237, 28, 36), 255); this.color3 = new Color(display, new RGB(237, 28, 36), 255);
this.darkColor = new Color(display, Constants.BLACK_RGB);
this.lightColor = new Color(display, Constants.WHITE_RGB);
} }
Color getStatusColor(final ClientConnectionData connectionData) { Color getStatusColor(final ClientConnectionData connectionData) {
@ -34,28 +40,30 @@ public class StatusData {
} }
switch (connectionData.clientConnection.status) { switch (connectionData.clientConnection.status) {
case ESTABLISHED: case ACTIVE:
return this.color1; return (connectionData.missingPing) ? this.color2 : this.color1;
case ABORTED: case DISABLED:
return this.color3;
default:
return this.color2; return this.color2;
default:
return this.defaultColor;
} }
} }
Color getStatusTextColor(final Color statusColor) {
return Utils.darkColor(statusColor.getRGB()) ? this.darkColor : this.lightColor;
}
int statusWeight(final ClientConnectionData connectionData) { int statusWeight(final ClientConnectionData connectionData) {
if (connectionData == null) { if (connectionData == null) {
return 100; return 100;
} }
switch (connectionData.clientConnection.status) { switch (connectionData.clientConnection.status) {
case ABORTED:
return 0;
case CONNECTION_REQUESTED: case CONNECTION_REQUESTED:
case AUTHENTICATED: case AUTHENTICATED:
return 1; return 1;
case ESTABLISHED: case ACTIVE:
return 2; return (connectionData.missingPing) ? 0 : 2;
case CLOSED: case CLOSED:
return 3; return 3;
default: default:

View file

@ -16,7 +16,6 @@ import java.util.EnumMap;
import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Display;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
@ -35,6 +34,7 @@ final class IndicatorData {
final Indicator indicator, final Indicator indicator,
final int index, final int index,
final int tableIndex, final int tableIndex,
final ColorData colorData,
final Display display) { final Display display) {
this.indicator = indicator; this.indicator = indicator;
@ -42,20 +42,21 @@ final class IndicatorData {
this.tableIndex = tableIndex; this.tableIndex = tableIndex;
this.defaultColor = new Color(display, Utils.toRGB(indicator.defaultColor), 255); this.defaultColor = new Color(display, Utils.toRGB(indicator.defaultColor), 255);
this.defaultTextColor = Utils.darkColor(this.defaultColor.getRGB()) this.defaultTextColor = Utils.darkColor(this.defaultColor.getRGB())
? new Color(display, Constants.BLACK_RGB) ? colorData.darkColor
: new Color(display, Constants.WHITE_RGB); : colorData.lightColor;
this.thresholdColor = new ThresholdColor[indicator.thresholds.size()]; this.thresholdColor = new ThresholdColor[indicator.thresholds.size()];
final ArrayList<Threshold> sortedThresholds = new ArrayList<>(indicator.thresholds); final ArrayList<Threshold> sortedThresholds = new ArrayList<>(indicator.thresholds);
Collections.sort(sortedThresholds, (t1, t2) -> t1.value.compareTo(t2.value)); Collections.sort(sortedThresholds, (t1, t2) -> t1.value.compareTo(t2.value));
for (int i = 0; i < indicator.thresholds.size(); i++) { for (int i = 0; i < indicator.thresholds.size(); i++) {
this.thresholdColor[i] = new ThresholdColor(sortedThresholds.get(i), display); this.thresholdColor[i] = new ThresholdColor(sortedThresholds.get(i), display, colorData);
} }
} }
static final EnumMap<IndicatorType, IndicatorData> createFormIndicators( static final EnumMap<IndicatorType, IndicatorData> createFormIndicators(
final Collection<Indicator> indicators, final Collection<Indicator> indicators,
final Display display, final Display display,
final ColorData colorData,
final int tableIndexOffset) { final int tableIndexOffset) {
final EnumMap<IndicatorType, IndicatorData> indicatorMapping = new EnumMap<>(IndicatorType.class); final EnumMap<IndicatorType, IndicatorData> indicatorMapping = new EnumMap<>(IndicatorType.class);
@ -65,6 +66,7 @@ final class IndicatorData {
indicator, indicator,
i, i,
i + tableIndexOffset, i + tableIndexOffset,
colorData,
display)); display));
i++; i++;
} }
@ -86,12 +88,12 @@ final class IndicatorData {
final Color color; final Color color;
final Color textColor; final Color textColor;
protected ThresholdColor(final Threshold threshold, final Display display) { protected ThresholdColor(final Threshold threshold, final Display display, final ColorData colorData) {
this.value = threshold.value; this.value = threshold.value;
this.color = new Color(display, Utils.toRGB(threshold.color), 255); this.color = new Color(display, Utils.toRGB(threshold.color), 255);
this.textColor = Utils.darkColor(this.color.getRGB()) this.textColor = Utils.darkColor(this.color.getRGB())
? new Color(display, Constants.BLACK_RGB) ? colorData.darkColor
: new Color(display, Constants.WHITE_RGB); : colorData.lightColor;
} }
} }

View file

@ -50,7 +50,8 @@ public class TableFilter<ROW extends Entity> {
TEXT, TEXT,
SINGLE_SELECTION, SINGLE_SELECTION,
DATE, DATE,
DATE_RANGE DATE_RANGE,
DATE_TIME_RANGE
} }
private final Composite composite; private final Composite composite;
@ -125,6 +126,8 @@ public class TableFilter<ROW extends Entity> {
return new Date(attribute); return new Date(attribute);
case DATE_RANGE: case DATE_RANGE:
return new DateRange(attribute); return new DateRange(attribute);
case DATE_TIME_RANGE:
return new DateRange(attribute, true);
default: default:
throw new IllegalArgumentException("Unsupported FilterAttributeType: " + attribute.type); throw new IllegalArgumentException("Unsupported FilterAttributeType: " + attribute.type);
} }
@ -476,17 +479,25 @@ public class TableFilter<ROW extends Entity> {
private Composite innerComposite; private Composite innerComposite;
private final GridData rw1 = new GridData(SWT.FILL, SWT.FILL, true, true); private final GridData rw1 = new GridData(SWT.FILL, SWT.FILL, true, true);
private DateTime fromSelector; private DateTime fromDateSelector;
private DateTime toSelector; private DateTime toDateSelector;
private DateTime fromTimeSelector;
private DateTime toTimeSelector;
private final boolean withTime;
DateRange(final TableFilterAttribute attribute) { DateRange(final TableFilterAttribute attribute) {
this(attribute, false);
}
DateRange(final TableFilterAttribute attribute, final boolean withTime) {
super(attribute); super(attribute);
this.withTime = withTime;
} }
@Override @Override
FilterComponent build(final Composite parent) { FilterComponent build(final Composite parent) {
this.innerComposite = new Composite(parent, SWT.NONE); this.innerComposite = new Composite(parent, SWT.NONE);
final GridLayout gridLayout = new GridLayout(2, false); final GridLayout gridLayout = new GridLayout((this.withTime) ? 3 : 2, false);
gridLayout.marginHeight = 0; gridLayout.marginHeight = 0;
gridLayout.marginWidth = 0; gridLayout.marginWidth = 0;
gridLayout.horizontalSpacing = 5; gridLayout.horizontalSpacing = 5;
@ -496,13 +507,25 @@ public class TableFilter<ROW extends Entity> {
TableFilter.this.entityTable.widgetFactory TableFilter.this.entityTable.widgetFactory
.labelLocalized(this.innerComposite, DATE_FROM_TEXT); .labelLocalized(this.innerComposite, DATE_FROM_TEXT);
this.fromSelector = new DateTime(this.innerComposite, SWT.DATE | SWT.BORDER); this.fromDateSelector =
this.fromSelector.setLayoutData(this.rw1); new DateTime(this.innerComposite, SWT.DATE | SWT.BORDER);
this.fromDateSelector.setLayoutData(this.rw1);
if (this.withTime) {
this.fromTimeSelector =
new DateTime(this.innerComposite, SWT.TIME | SWT.BORDER);
this.fromTimeSelector.setLayoutData(this.rw1);
}
TableFilter.this.entityTable.widgetFactory TableFilter.this.entityTable.widgetFactory
.labelLocalized(this.innerComposite, DATE_TO_TEXT); .labelLocalized(this.innerComposite, DATE_TO_TEXT);
this.toSelector = new DateTime(this.innerComposite, SWT.DATE | SWT.BORDER); this.toDateSelector =
this.toSelector.setLayoutData(this.rw1); new DateTime(this.innerComposite, SWT.DATE | SWT.BORDER);
this.toDateSelector.setLayoutData(this.rw1);
if (this.withTime) {
this.toTimeSelector =
new DateTime(this.innerComposite, SWT.TIME | SWT.BORDER);
this.toTimeSelector.setLayoutData(this.rw1);
}
return this; return this;
} }
@ -510,44 +533,66 @@ public class TableFilter<ROW extends Entity> {
@Override @Override
FilterComponent reset() { FilterComponent reset() {
final org.joda.time.DateTime now = org.joda.time.DateTime.now(DateTimeZone.UTC); final org.joda.time.DateTime now = org.joda.time.DateTime.now(DateTimeZone.UTC);
if (this.fromSelector != null) { if (this.fromDateSelector != null) {
try { try {
final org.joda.time.DateTime parse = org.joda.time.DateTime.parse(this.attribute.initValue); final org.joda.time.DateTime parse = org.joda.time.DateTime.parse(this.attribute.initValue);
this.fromSelector.setDate( this.fromDateSelector.setDate(
parse.getYear(), parse.getYear(),
parse.getMonthOfYear() - 1, parse.getMonthOfYear() - 1,
parse.getDayOfMonth()); parse.getDayOfMonth());
if (this.fromTimeSelector != null) {
this.fromTimeSelector.setTime(
parse.getHourOfDay(),
parse.getMinuteOfHour(),
parse.getSecondOfMinute());
}
} catch (final RuntimeException e) { } catch (final RuntimeException e) {
this.fromSelector.setDate( this.fromDateSelector.setDate(
now.getYear(), now.getYear(),
now.getMonthOfYear() - 1, now.getMonthOfYear() - 1,
now.getDayOfMonth()); now.getDayOfMonth());
if (this.fromTimeSelector != null) {
this.fromTimeSelector.setTime(
now.getHourOfDay(),
now.getMinuteOfHour(),
now.getSecondOfMinute());
} }
} }
if (this.toSelector != null) { }
this.toSelector.setDate( if (this.toDateSelector != null) {
this.toDateSelector.setDate(
now.getYear(), now.getYear(),
now.getMonthOfYear() - 1, now.getMonthOfYear() - 1,
now.getDayOfMonth()); now.getDayOfMonth());
if (this.toTimeSelector != null) {
this.toTimeSelector.setTime(
now.getHourOfDay(),
now.getMinuteOfHour(),
now.getSecondOfMinute());
}
} }
return this; return this;
} }
@Override @Override
String getValue() { String getValue() {
if (this.fromSelector != null && this.toSelector != null) { if (this.fromDateSelector != null && this.toDateSelector != null) {
final org.joda.time.DateTime fromDate = org.joda.time.DateTime.now(DateTimeZone.UTC) final org.joda.time.DateTime fromDate = org.joda.time.DateTime.now(DateTimeZone.UTC)
.withYear(this.fromSelector.getYear()) .withYear(this.fromDateSelector.getYear())
.withMonthOfYear(this.fromSelector.getMonth() + 1) .withMonthOfYear(this.fromDateSelector.getMonth() + 1)
.withDayOfMonth(this.fromSelector.getDay()) .withDayOfMonth(this.fromDateSelector.getDay())
.withTimeAtStartOfDay(); .withHourOfDay((this.fromTimeSelector != null) ? this.fromTimeSelector.getHours() : 0)
.withMinuteOfHour((this.fromTimeSelector != null) ? this.fromTimeSelector.getMinutes() : 0)
.withSecondOfMinute((this.fromTimeSelector != null) ? this.fromTimeSelector.getSeconds() : 0);
final org.joda.time.DateTime toDate = org.joda.time.DateTime.now(DateTimeZone.UTC) final org.joda.time.DateTime toDate = org.joda.time.DateTime.now(DateTimeZone.UTC)
.withYear(this.toSelector.getYear()) .withYear(this.toDateSelector.getYear())
.withMonthOfYear(this.toSelector.getMonth() + 1) .withMonthOfYear(this.toDateSelector.getMonth() + 1)
.withDayOfMonth(this.toSelector.getDay()) .withDayOfMonth(this.toDateSelector.getDay())
.withTime(23, 59, 59, 0); .withHourOfDay((this.toTimeSelector != null) ? this.toTimeSelector.getHours() : 0)
.withMinuteOfHour((this.toTimeSelector != null) ? this.toTimeSelector.getMinutes() : 0)
.withSecondOfMinute((this.toTimeSelector != null) ? this.toTimeSelector.getSeconds() : 0);
return fromDate.toString(Constants.STANDARD_DATE_TIME_FORMATTER) + return fromDate.toString(Constants.STANDARD_DATE_TIME_FORMATTER) +
Constants.EMBEDDED_LIST_SEPARATOR + Constants.EMBEDDED_LIST_SEPARATOR +
@ -559,14 +604,33 @@ public class TableFilter<ROW extends Entity> {
@Override @Override
void setValue(final String value) { void setValue(final String value) {
if (this.fromSelector != null && this.toSelector != null) { if (this.fromDateSelector != null && this.toDateSelector != null) {
try { try {
final String[] split = StringUtils.split(value, Constants.EMBEDDED_LIST_SEPARATOR); final String[] split = StringUtils.split(value, Constants.EMBEDDED_LIST_SEPARATOR);
final org.joda.time.DateTime fromDate = Utils.toDateTime(split[0]); final org.joda.time.DateTime fromDate = Utils.toDateTime(split[0]);
final org.joda.time.DateTime toDate = Utils.toDateTime(split[1]); final org.joda.time.DateTime toDate = Utils.toDateTime(split[1]);
this.fromSelector.setDate(fromDate.getYear(), fromDate.getMonthOfYear() - 1, this.fromDateSelector.setDate(
fromDate.getYear(),
fromDate.getMonthOfYear() - 1,
fromDate.getDayOfMonth()); fromDate.getDayOfMonth());
this.toSelector.setDate(toDate.getYear(), toDate.getMonthOfYear() - 1, toDate.getDayOfMonth()); if (this.fromTimeSelector != null) {
this.fromTimeSelector.setTime(
fromDate.getHourOfDay(),
fromDate.getMinuteOfHour(),
fromDate.getSecondOfMinute());
}
this.toDateSelector.setDate(
toDate.getYear(),
toDate.getMonthOfYear() - 1,
toDate.getDayOfMonth());
if (this.toTimeSelector != null) {
this.toTimeSelector.setTime(
toDate.getHourOfDay(),
toDate.getMinuteOfHour(),
toDate.getSecondOfMinute());
}
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to set date range filter attribute: ", e); log.error("Failed to set date range filter attribute: ", e);
} }

View file

@ -50,6 +50,7 @@ public interface ClientConnectionDAO extends EntityDAO<ClientConnection, ClientC
Result<ClientConnection> createNew(ClientConnection data); Result<ClientConnection> createNew(ClientConnection data);
@Override @Override
// TODO check if this is needed
@CacheEvict(cacheNames = CONNECTION_TOKENS_CACHE, allEntries = true) @CacheEvict(cacheNames = CONNECTION_TOKENS_CACHE, allEntries = true)
Result<ClientConnection> save(ClientConnection data); Result<ClientConnection> save(ClientConnection data);

View file

@ -95,7 +95,7 @@ public class AsyncBatchEventSaveStrategy implements EventHandlingStrategy {
runWorkers(); runWorkers();
try { try {
Thread.sleep(Constants.SECOND_IN_MILLIS); Thread.sleep(Constants.SECOND_IN_MILLIS / 2);
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to wait"); log.error("Failed to wait");
} }
@ -110,8 +110,7 @@ public class AsyncBatchEventSaveStrategy implements EventHandlingStrategy {
@Override @Override
public void accept(final ClientEventRecord record) { public void accept(final ClientEventRecord record) {
if (!this.workersRunning) { if (record == null || !this.workersRunning) {
log.error("Received ClientEvent on none enabled AsyncBatchEventSaveStrategy. ClientEvent is ignored");
return; return;
} }

View file

@ -24,6 +24,8 @@ public class ClientConnectionDataInternal extends ClientConnectionData {
final Collection<AbstractPingIndicator> pingMappings; final Collection<AbstractPingIndicator> pingMappings;
final EnumMap<EventType, Collection<ClientIndicator>> indicatorMapping; final EnumMap<EventType, Collection<ClientIndicator>> indicatorMapping;
PingIntervalClientIndicator pingIndicator = null;
protected ClientConnectionDataInternal( protected ClientConnectionDataInternal(
final ClientConnection clientConnection, final ClientConnection clientConnection,
final List<ClientIndicator> clientIndicators) { final List<ClientIndicator> clientIndicators) {
@ -34,8 +36,15 @@ public class ClientConnectionDataInternal extends ClientConnectionData {
this.pingMappings = new ArrayList<>(); this.pingMappings = new ArrayList<>();
for (final ClientIndicator clientIndicator : clientIndicators) { for (final ClientIndicator clientIndicator : clientIndicators) {
if (clientIndicator instanceof AbstractPingIndicator) { if (clientIndicator instanceof AbstractPingIndicator) {
if (clientIndicator instanceof PingIntervalClientIndicator) {
this.pingIndicator = (PingIntervalClientIndicator) clientIndicator;
if (!this.pingIndicator.hidden) {
this.pingMappings.add((AbstractPingIndicator) clientIndicator); this.pingMappings.add((AbstractPingIndicator) clientIndicator);
} }
} else {
this.pingMappings.add((AbstractPingIndicator) clientIndicator);
}
}
for (final EventType eventType : clientIndicator.observedEvents()) { for (final EventType eventType : clientIndicator.observedEvents()) {
this.indicatorMapping this.indicatorMapping
.computeIfAbsent(eventType, key -> new ArrayList<>()) .computeIfAbsent(eventType, key -> new ArrayList<>())

View file

@ -22,6 +22,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO;
@ -62,12 +63,18 @@ public class ClientIndicatorFactory {
.allForExam(clientConnection.examId) .allForExam(clientConnection.examId)
.getOrThrow(); .getOrThrow();
boolean pingIndicatorAvailable = false;
for (final Indicator indicatorDef : examIndicators) { for (final Indicator indicatorDef : examIndicators) {
try { try {
final ClientIndicator indicator = this.applicationContext final ClientIndicator indicator = this.applicationContext
.getBean(indicatorDef.type.name(), ClientIndicator.class); .getBean(indicatorDef.type.name(), ClientIndicator.class);
if (!pingIndicatorAvailable) {
pingIndicatorAvailable = indicatorDef.type == IndicatorType.LAST_PING;
}
indicator.init( indicator.init(
indicatorDef, indicatorDef,
clientConnection.id, clientConnection.id,
@ -79,6 +86,16 @@ public class ClientIndicatorFactory {
e); e);
} }
} }
// If there is no ping interval indicator set from the exam, we add a hidden one
// to at least create missing ping events and track missing state
if (!pingIndicatorAvailable) {
final PingIntervalClientIndicator pingIndicator = this.applicationContext
.getBean(PingIntervalClientIndicator.class);
pingIndicator.hidden = true;
result.add(pingIndicator);
}
} catch (final RuntimeException e) { } catch (final RuntimeException e) {
log.error("Failed to create ClientIndicator for ClientConnection: {}", clientConnection); log.error("Failed to create ClientIndicator for ClientConnection: {}", clientConnection);
throw e; throw e;

View file

@ -333,8 +333,7 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService {
} }
private static boolean isActiveConnection(final ClientConnectionData connection) { private static boolean isActiveConnection(final ClientConnectionData connection) {
if (connection.clientConnection.status == ConnectionStatus.ESTABLISHED if (connection.clientConnection.status.establishedStatus) {
|| connection.clientConnection.status == ConnectionStatus.AUTHENTICATED) {
return true; return true;
} }

View file

@ -112,7 +112,7 @@ class ExamSessionControlTask implements DisposableBean {
controlExamEnd(updateId); controlExamEnd(updateId);
} }
@Scheduled(fixedRateString = "${sebserver.webservice.api.seb.lostping.update:15000}") @Scheduled(fixedRateString = "${sebserver.webservice.api.seb.lostping.update:5000}")
public void pingEventUpdateTask() { public void pingEventUpdateTask() {
if (!this.lostPingUpdateActive) { if (!this.lostPingUpdateActive) {

View file

@ -39,6 +39,8 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator {
private long pingErrorThreshold; private long pingErrorThreshold;
private boolean isOnError = false; private boolean isOnError = false;
boolean hidden = false;
public PingIntervalClientIndicator(final ClientEventExtentionMapper clientEventExtentionMapper) { public PingIntervalClientIndicator(final ClientEventExtentionMapper clientEventExtentionMapper) {
super(clientEventExtentionMapper); super(clientEventExtentionMapper);
this.cachingEnabled = true; this.cachingEnabled = true;
@ -70,7 +72,7 @@ public final class PingIntervalClientIndicator extends AbstractPingIndicator {
@Override @Override
public double getValue() { public double getValue() {
final long now = DateTime.now(DateTimeZone.UTC).getMillis(); final long now = DateTime.now(DateTimeZone.UTC).getMillis();
return now - super.currentValue; return now - super.getValue();
} }
@Override @Override

View file

@ -73,6 +73,7 @@ public class SebClientConnectionServiceImpl implements SebClientConnectionServic
this.webserviceInfo = sebInstructionService.getWebserviceInfo(); this.webserviceInfo = sebInstructionService.getWebserviceInfo();
} }
@Override
public ExamSessionService getExamSessionService() { public ExamSessionService getExamSessionService() {
return this.examSessionService; return this.examSessionService;
} }
@ -220,7 +221,7 @@ public class SebClientConnectionServiceImpl implements SebClientConnectionServic
.getOrThrow(); .getOrThrow();
final ClientConnectionDataInternal activeClientConnection = final ClientConnectionDataInternal activeClientConnection =
cacheEvictAndLoad(connectionToken); realoadConnectionCache(connectionToken);
if (activeClientConnection == null) { if (activeClientConnection == null) {
log.warn("Failed to load ClientConnectionDataInternal into cache on update"); log.warn("Failed to load ClientConnectionDataInternal into cache on update");
@ -286,7 +287,7 @@ public class SebClientConnectionServiceImpl implements SebClientConnectionServic
clientConnection.id, clientConnection.id,
null, null,
(examId != null) ? examId : clientConnection.examId, (examId != null) ? examId : clientConnection.examId,
ConnectionStatus.ESTABLISHED, ConnectionStatus.ACTIVE,
null, null,
userSessionId, userSessionId,
null, null,
@ -298,7 +299,7 @@ public class SebClientConnectionServiceImpl implements SebClientConnectionServic
clientConnection.connectionToken == null || clientConnection.connectionToken == null ||
establishedClientConnection.examId == null || establishedClientConnection.examId == null ||
clientConnection.clientAddress == null || clientConnection.clientAddress == null ||
establishedClientConnection.status != ConnectionStatus.ESTABLISHED) { establishedClientConnection.status != ConnectionStatus.ACTIVE) {
log.error("ClientConnection integrity violation, clientConnection: {}, establishedClientConnection: {}", log.error("ClientConnection integrity violation, clientConnection: {}, establishedClientConnection: {}",
clientConnection, clientConnection,
@ -313,7 +314,7 @@ public class SebClientConnectionServiceImpl implements SebClientConnectionServic
checkExamIntegrity(updatedClientConnection.examId); checkExamIntegrity(updatedClientConnection.examId);
final ClientConnectionDataInternal activeClientConnection = final ClientConnectionDataInternal activeClientConnection =
cacheEvictAndLoad(connectionToken); realoadConnectionCache(connectionToken);
if (activeClientConnection == null) { if (activeClientConnection == null) {
log.warn("Failed to load ClientConnectionDataInternal into cache on update"); log.warn("Failed to load ClientConnectionDataInternal into cache on update");
@ -368,7 +369,7 @@ public class SebClientConnectionServiceImpl implements SebClientConnectionServic
updatedClientConnection = clientConnection; updatedClientConnection = clientConnection;
} }
evictCaches(connectionToken); realoadConnectionCache(connectionToken);
return updatedClientConnection; return updatedClientConnection;
}); });
} }
@ -406,7 +407,7 @@ public class SebClientConnectionServiceImpl implements SebClientConnectionServic
updatedClientConnection = clientConnection; updatedClientConnection = clientConnection;
} }
evictCaches(connectionToken); realoadConnectionCache(connectionToken);
return updatedClientConnection; return updatedClientConnection;
}); });
} }
@ -427,9 +428,9 @@ public class SebClientConnectionServiceImpl implements SebClientConnectionServic
.stream()) .stream())
.map(token -> cache.get(token, ClientConnectionDataInternal.class)) .map(token -> cache.get(token, ClientConnectionDataInternal.class))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.filter(connection -> connection.clientConnection.status == ConnectionStatus.ESTABLISHED) .filter(connection -> connection.pingIndicator != null &&
.flatMap(connection -> connection.pingMappings.stream()) connection.clientConnection.status.establishedStatus)
.map(ping -> ping.updateLogEvent()) .map(connection -> connection.pingIndicator.updateLogEvent())
.filter(Objects::nonNull) .filter(Objects::nonNull)
.forEach(this.eventHandlingStrategy::accept); .forEach(this.eventHandlingStrategy::accept);
@ -497,7 +498,6 @@ public class SebClientConnectionServiceImpl implements SebClientConnectionServic
} }
} }
// TODO maybe we need a stronger connectionToken but for now a simple UUID is used
private String createToken() { private String createToken() {
return UUID.randomUUID().toString(); return UUID.randomUUID().toString();
} }
@ -606,19 +606,12 @@ public class SebClientConnectionServiceImpl implements SebClientConnectionServic
.getOrThrow(); .getOrThrow();
} }
private void evictCaches(final String connectionToken) { private ClientConnectionDataInternal realoadConnectionCache(final String connectionToken) {
// evict cached ClientConnection // evict cached ClientConnection
this.examSessionCacheService.evictClientConnection(connectionToken); this.examSessionCacheService.evictClientConnection(connectionToken);
// evict also cached ping record // evict also cached ping record
this.examSessionCacheService.evictPingRecord(connectionToken); this.examSessionCacheService.evictPingRecord(connectionToken);
// and load updated ClientConnection into cache // and load updated ClientConnection into cache
this.examSessionCacheService.getActiveClientConnection(connectionToken);
}
private ClientConnectionDataInternal cacheEvictAndLoad(final String connectionToken) {
// evict cached ClientConnection
this.examSessionCacheService.evictClientConnection(connectionToken);
// and load updated ClientConnection into cache
return this.examSessionCacheService.getActiveClientConnection(connectionToken); return this.examSessionCacheService.getActiveClientConnection(connectionToken);
} }

View file

@ -18,6 +18,7 @@ sebserver.overall.action.cancel=Cancel
sebserver.overall.action.close=Close sebserver.overall.action.close=Close
sebserver.overall.action.goAwayFromEditPageConfirm=Are you sure you want to leave this page? Unsaved data will be lost. sebserver.overall.action.goAwayFromEditPageConfirm=Are you sure you want to leave this page? Unsaved data will be lost.
sebserver.overall.action.category.varia=Varia sebserver.overall.action.category.varia=Varia
sebserver.overall.action.category.filter=Connection Status Filter
sebserver.overall.status.active=Active sebserver.overall.status.active=Active
sebserver.overall.status.inactive=Inactive sebserver.overall.status.inactive=Inactive
@ -1065,14 +1066,20 @@ sebserver.monitoring.exam.connection.emptySelection=Please select a connection f
sebserver.monitoring.exam.connection.title=SEB Client Connection sebserver.monitoring.exam.connection.title=SEB Client Connection
sebserver.monitoring.exam.connection.list.actions=Selected Connection sebserver.monitoring.exam.connection.list.actions=Selected Connection
sebserver.monitoring.exam.connection.action.view=View Details sebserver.monitoring.exam.connection.action.view=View Details
sebserver.monitoring.exam.connection.action.instruction.quit=Send Quit sebserver.monitoring.exam.connection.action.instruction.quit=Send SEB Quit
sebserver.monitoring.exam.connection.action.instruction.quit.all=Send Quit sebserver.monitoring.exam.connection.action.instruction.quit.all=Send SEB Quit
sebserver.monitoring.exam.connection.action.instruction.quit.all.confirm=Are you sure to quit this SEB client connection? sebserver.monitoring.exam.connection.action.instruction.quit.all.confirm=Are you sure to quit this SEB client connection?
sebserver.monitoring.exam.connection.action.instruction.quit.selected.confirm=Are you sure to quit all selected, active SEB client connections? sebserver.monitoring.exam.connection.action.instruction.quit.selected.confirm=Are you sure to quit all selected, active SEB client connections?
sebserver.monitoring.exam.connection.action.instruction.quit.all.confirm=Are you sure to quit all active SEB client connections? sebserver.monitoring.exam.connection.action.instruction.quit.all.confirm=Are you sure to quit all active SEB client connections?
sebserver.monitoring.exam.connection.action.disable=Mark As Disabled sebserver.monitoring.exam.connection.action.disable=Mark As Disabled
sebserver.monitoring.exam.connection.action.hide.closed=Hide Closed Connections sebserver.monitoring.exam.connection.action.hide.requested=Hide Requested
sebserver.monitoring.exam.connection.action.show.closed=Show Closed Connections sebserver.monitoring.exam.connection.action.show.requested=Show Requested
sebserver.monitoring.exam.connection.action.hide.closed=Hide Closed
sebserver.monitoring.exam.connection.action.show.closed=Show Closed
sebserver.monitoring.exam.connection.action.hide.disabled=Hide Disabled
sebserver.monitoring.exam.connection.action.show.disabled=Show Disabled
sebserver.monitoring.exam.connection.action.hide.undefined=Hide Undefined
sebserver.monitoring.exam.connection.action.show.undefined=Show Undefined
sebserver.monitoring.exam.connection.eventlist.title=Events sebserver.monitoring.exam.connection.eventlist.title=Events
sebserver.monitoring.exam.connection.eventlist.empty=No event found sebserver.monitoring.exam.connection.eventlist.empty=No event found
@ -1092,7 +1099,7 @@ sebserver.monitoring.exam.connection.event.type.LAST_PING=Last Ping
sebserver.monitoring.exam.connection.status.UNDEFINED=Undefined sebserver.monitoring.exam.connection.status.UNDEFINED=Undefined
sebserver.monitoring.exam.connection.status.CONNECTION_REQUESTED=Connection Requested sebserver.monitoring.exam.connection.status.CONNECTION_REQUESTED=Connection Requested
sebserver.monitoring.exam.connection.status.AUTHENTICATED=Authenticated sebserver.monitoring.exam.connection.status.AUTHENTICATED=Authenticated
sebserver.monitoring.exam.connection.status.ESTABLISHED=Active sebserver.monitoring.exam.connection.status.ACTIVE=Active
sebserver.monitoring.exam.connection.status.CLOSED=Closed sebserver.monitoring.exam.connection.status.CLOSED=Closed
sebserver.monitoring.exam.connection.status.ABORTED=Aborted sebserver.monitoring.exam.connection.status.ABORTED=Aborted
sebserver.monitoring.exam.connection.status.DISABLED=Disabled sebserver.monitoring.exam.connection.status.DISABLED=Disabled

View file

@ -298,6 +298,16 @@ Text[MULTI]:read-only {
padding: 0px 0px 0px 0px; padding: 0px 0px 0px 0px;
} }
Text:disabled.colorbox,
Text:read-only.colorbox,
Text[MULTI]:disabled.colorbox,
Text[MULTI]:read-only.colorbox {
box-shadow: none;
border: none;
border-radius: 0;
padding: 0px 10px 0px 10px;
}
/* Combo default theme */ /* Combo default theme */
Combo, Combo[BORDER] { Combo, Combo[BORDER] {

View file

@ -104,8 +104,8 @@ public class HTTPClientBot {
this.numberOfConnections = Integer.parseInt(properties.getProperty("numberOfConnections", "4")); this.numberOfConnections = Integer.parseInt(properties.getProperty("numberOfConnections", "4"));
this.pingInterval = Long.parseLong(properties.getProperty("pingInterval", "200")); this.pingInterval = Long.parseLong(properties.getProperty("pingInterval", "200"));
this.establishDelay = Long.parseLong(properties.getProperty("establishDelay", "0")); this.establishDelay = Long.parseLong(properties.getProperty("establishDelay", "0"));
this.pingPause = Long.parseLong(properties.getProperty("pingPause", "0")); this.pingPause = Long.parseLong(properties.getProperty("pingPause", "20000"));
this.pingPauseDelay = Long.parseLong(properties.getProperty("pingPauseDelay", "0")); this.pingPauseDelay = Long.parseLong(properties.getProperty("pingPauseDelay", "5000"));
this.errorInterval = Long.parseLong(properties.getProperty("errorInterval", String.valueOf(TEN_SECONDS))); this.errorInterval = Long.parseLong(properties.getProperty("errorInterval", String.valueOf(TEN_SECONDS)));
this.warnInterval = Long.parseLong(properties.getProperty("errorInterval", String.valueOf(TEN_SECONDS / 2))); this.warnInterval = Long.parseLong(properties.getProperty("errorInterval", String.valueOf(TEN_SECONDS / 2)));
// this.runtime = Long.parseLong(properties.getProperty("runtime", String.valueOf(ONE_MINUTE))); // this.runtime = Long.parseLong(properties.getProperty("runtime", String.valueOf(ONE_MINUTE)));

View file

@ -295,7 +295,7 @@ public class SebConnectionTest extends ExamAPIIntegrationTester {
final ClientConnectionRecord clientConnectionRecord = records.get(0); final ClientConnectionRecord clientConnectionRecord = records.get(0);
assertEquals("1", String.valueOf(clientConnectionRecord.getInstitutionId())); assertEquals("1", String.valueOf(clientConnectionRecord.getInstitutionId()));
assertEquals("2", String.valueOf(clientConnectionRecord.getExamId())); assertEquals("2", String.valueOf(clientConnectionRecord.getExamId()));
assertEquals("ESTABLISHED", String.valueOf(clientConnectionRecord.getStatus())); assertEquals("ACTIVE", String.valueOf(clientConnectionRecord.getStatus()));
assertNotNull(clientConnectionRecord.getConnectionToken()); assertNotNull(clientConnectionRecord.getConnectionToken());
assertNotNull(clientConnectionRecord.getClientAddress()); assertNotNull(clientConnectionRecord.getClientAddress());
assertEquals("userSessionId", clientConnectionRecord.getExamUserSessionId()); assertEquals("userSessionId", clientConnectionRecord.getExamUserSessionId());