diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ClientGroupData.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ClientGroupData.java index 6988010f..35fb2586 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ClientGroupData.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ClientGroupData.java @@ -28,7 +28,7 @@ public interface ClientGroupData extends Entity { MAC_OS("TODO"), I_OS("TODO"); - final String queryString; + public final String queryString; private ClientOS(final String queryString) { this.queryString = queryString; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java index bf63cb31..5fb04e96 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java @@ -332,8 +332,10 @@ public final class ClientConnection implements GrantEntity { return false; } else if (!this.connectionToken.equals(other.connectionToken)) return false; + if (this.status != other.status) return false; + if (this.userSessionId == null) { if (other.userSessionId != null) return false; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java index 7bf0d265..c1d8b45c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnectionData.java @@ -12,6 +12,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Objects; import java.util.Set; import com.fasterxml.jackson.annotation.JsonCreator; @@ -156,6 +157,14 @@ public class ClientConnectionData implements GrantEntity { return false; } + if (!Objects.equals(this.groups, other.groups)) { + return false; + } + + return true; + } + + public boolean indicatorValuesEquals(final ClientConnectionData other) { if (this.indicatorValues.size() != other.indicatorValues.size()) { return false; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/ClientOSGroupMatcher.java b/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/ClientOSGroupMatcher.java new file mode 100644 index 00000000..ebbb7e8c --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/monitoring/ClientOSGroupMatcher.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 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.gbl.monitoring; + +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup; +import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroupData.ClientGroupType; +import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroupData.ClientOS; +import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; + +@Lazy +@Component +public class ClientOSGroupMatcher implements ClientGroupConnectionMatcher { + + @Override + public ClientGroupType matcherType() { + return ClientGroupType.CLIENT_OS; + } + + @Override + public boolean isInGroup(final ClientConnection clientConnection, final ClientGroup group) { + if (group == null + || group.type != ClientGroupType.CLIENT_OS + || clientConnection == null + || clientConnection.info == null) { + + return false; + } + + try { + final ClientOS osType = ClientOS.valueOf(group.getData()); + return clientConnection.info.contains(osType.queryString); + } catch (final Exception e) { + return false; + } + } +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java index 49bf3c4f..5a9aea6b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringClientConnection.java @@ -26,6 +26,7 @@ import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup; 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.ProctoringServiceSettings; @@ -55,6 +56,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.GetClientGroups; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.indicator.GetIndicators; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.ConfirmPendingClientNotification; @@ -191,6 +193,11 @@ public class MonitoringClientConnection implements TemplateComposer { .call() .getOrThrow(); + final Collection clientGroups = restService.getBuilder(GetClientGroups.class) + .withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, parentEntityKey.modelId) + .call() + .getOrThrow(); + final RestCall.RestCallBuilder getConnectionData = restService.getBuilder(GetClientConnectionData.class) .withURIVariable(API.PARAM_PARENT_MODEL_ID, exam.getModelId()) @@ -205,7 +212,8 @@ public class MonitoringClientConnection implements TemplateComposer { pageContext.copyOf(content), exam, getConnectionData, - indicators); + indicators, + clientGroups); // NOTIFICATIONS Supplier> _notificationTableSupplier = () -> null; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java index c7cbc9ba..08c44e6b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/monitoring/MonitoringRunningExam.java @@ -30,6 +30,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.async.AsyncRunner; import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup; 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.ProctoringServiceSettings; @@ -56,6 +57,7 @@ import ch.ethz.seb.sebserver.gui.service.push.ServerPushService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings; +import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.GetClientGroups; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.indicator.GetIndicators; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable; @@ -140,6 +142,11 @@ public class MonitoringRunningExam implements TemplateComposer { .call() .getOrThrow(); + final Collection clientGroups = this.restService.getBuilder(GetClientGroups.class) + .withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, entityKey.modelId) + .call() + .getOrThrow(); + final Composite content = this.pageService.getWidgetFactory().defaultPageLayout( pageContext.getParent(), new LocTextKey("sebserver.monitoring.exam", exam.name)); @@ -166,6 +173,7 @@ public class MonitoringRunningExam implements TemplateComposer { tablePane, exam, indicators, + clientGroups, this.distributedSetup); guiUpdates.add(clientTable); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java index a6c6a4fd..d6c55b94 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/Form.java @@ -128,6 +128,11 @@ public final class Form implements FormBinding { return this; } + Form putReadonlyField(final String name, final Control label, final Label field) { + this.formFields.add(name, createReadonlyAccessor(label, field)); + return this; + } + Form putReadonlyField(final String name, final Control label, final Browser field) { this.formFields.add(name, createReadonlyAccessor(label, field)); return this; @@ -308,7 +313,13 @@ public final class Form implements FormBinding { //@formatter:off private FormFieldAccessor createReadonlyAccessor(final Control label, final Text field) { return new FormFieldAccessor(label, field, null) { - @Override public String getStringValue() { return null; } + @Override public String getStringValue() { return field.getText(); } + @Override public void setStringValue(final String value) { field.setText( (value == null) ? StringUtils.EMPTY : value); } + }; + } + private FormFieldAccessor createReadonlyAccessor(final Control label, final Label field) { + return new FormFieldAccessor(label, field, null) { + @Override public String getStringValue() { return field.getText(); } @Override public void setStringValue(final String value) { field.setText( (value == null) ? StringUtils.EMPTY : value); } }; } @@ -320,7 +331,7 @@ public final class Form implements FormBinding { } private FormFieldAccessor createAccessor(final Control label, final Text text, final Label errorLabel) { return new FormFieldAccessor(label, text, errorLabel) { - @Override public String getStringValue() {return text.getText();} + @Override public String getStringValue() { return text.getText(); } @Override public void setStringValue(final String value) {text.setText(value);} }; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java index 8ebc39b7..9a236efa 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/form/TextFieldBuilder.java @@ -39,6 +39,7 @@ public final class TextFieldBuilder extends FieldBuilder { int areaMinHeight = WidgetFactory.TEXT_AREA_INPUT_MIN_HEIGHT; boolean isColorBox = false; boolean isHTML = false; + boolean isMarkup = false; TextFieldBuilder(final String name, final LocTextKey label, final String value) { super(name, label, value); @@ -76,6 +77,11 @@ public final class TextFieldBuilder extends FieldBuilder { return this; } + public TextFieldBuilder asMarkup() { + this.isMarkup = true; + return this; + } + public TextFieldBuilder asHTML(final int minHeight) { this.isHTML = true; this.areaMinHeight = minHeight; @@ -118,6 +124,15 @@ public final class TextFieldBuilder extends FieldBuilder { return; } + if (readonly && this.isMarkup) { + final Label label = new Label(fieldGrid, SWT.NONE); + label.setData(RWT.MARKUP_ENABLED, Boolean.TRUE); + label.setText(this.value); + label.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, true)); + builder.form.putReadonlyField(this.name, titleLabel, label); + return; + } + final String testKey = (this.label != null) ? this.label.name : this.name; final LocTextKey label = getARIALabel(builder); final Text textInput = (this.isNumber) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java index 9b83c269..4c493757 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java @@ -13,6 +13,7 @@ import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.apache.commons.lang3.BooleanUtils; import org.eclipse.swt.graphics.Color; @@ -22,6 +23,7 @@ import org.slf4j.LoggerFactory; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.model.Domain; +import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup; 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.QuizData; @@ -43,6 +45,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.DisposedOAuth2RestTemplateException; import ch.ethz.seb.sebserver.gui.service.session.IndicatorData.ThresholdColor; import ch.ethz.seb.sebserver.gui.table.EntityTable; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; public class ClientConnectionDetails { @@ -52,6 +55,8 @@ public class ClientConnectionDetails { new LocTextKey("sebserver.monitoring.connection.form.exam"); private final static LocTextKey CONNECTION_ID_TEXT_KEY = new LocTextKey("sebserver.monitoring.connection.form.id"); + private final static LocTextKey CONNECTION_GROUP_TEXT_KEY = + new LocTextKey("sebserver.monitoring.connection.form.group"); private final static LocTextKey CONNECTION_INFO_TEXT_KEY = new LocTextKey("sebserver.monitoring.connection.form.info"); private final static LocTextKey CONNECTION_STATUS_TEXT_KEY = @@ -62,6 +67,8 @@ public class ClientConnectionDetails { private final PageService pageService; private final ResourceService resourceService; private final Map indicatorMapping; + private final Map clientGroupMapping; + private final boolean hasClientGroups; private final RestCall.RestCallBuilder restCallBuilder; private final FormHandle formHandle; private final ColorData colorData; @@ -78,10 +85,11 @@ public class ClientConnectionDetails { final PageContext pageContext, final Exam exam, final RestCall.RestCallBuilder restCallBuilder, - final Collection indicators) { + final Collection indicators, + final Collection clientGroups) { final Display display = pageContext.getRoot().getDisplay(); - + this.hasClientGroups = clientGroups != null && !clientGroups.isEmpty(); this.pageService = pageService; this.resourceService = pageService.getResourceService(); this.restCallBuilder = restCallBuilder; @@ -92,6 +100,12 @@ public class ClientConnectionDetails { this.colorData, NUMBER_OF_NONE_INDICATOR_ROWS); + this.clientGroupMapping = clientGroups == null || clientGroups.isEmpty() + ? null + : clientGroups + .stream() + .collect(Collectors.toMap(cg -> cg.id, Function.identity())); + final FormBuilder formBuilder = pageService.formBuilder(pageContext) .readonly(true) .addField(FormBuilder.text( @@ -102,6 +116,12 @@ public class ClientConnectionDetails { Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID, CONNECTION_ID_TEXT_KEY, Constants.EMPTY_NOTE)) + .addFieldIf(() -> this.hasClientGroups, + () -> FormBuilder.text( + ClientConnectionData.ATTR_CLIENT_GROUPS, + CONNECTION_GROUP_TEXT_KEY, + Constants.EMPTY_NOTE) + .asMarkup()) .addField(FormBuilder.text( ClientConnection.ATTR_INFO, CONNECTION_INFO_TEXT_KEY, @@ -179,6 +199,13 @@ public class ClientConnectionDetails { ClientConnection.ATTR_INFO, this.connectionData.clientConnection.info); + if (this.hasClientGroups + && Constants.EMPTY_NOTE.equals(form.getFieldValue(ClientConnectionData.ATTR_CLIENT_GROUPS))) { + form.setFieldValue( + ClientConnectionData.ATTR_CLIENT_GROUPS, + getGroupInfo()); + } + if (this.missingChanged) { // update status form.setFieldValue( @@ -252,4 +279,20 @@ public class ClientConnectionDetails { } } + private String getGroupInfo() { + final StringBuilder sb = new StringBuilder(); + this.clientGroupMapping.keySet().stream().forEach(key -> { + if (this.connectionData.groups.contains(key)) { + final ClientGroup clientGroup = this.clientGroupMapping.get(key); + sb.append(WidgetFactory.getTextWithBackgroundHTML(clientGroup.name, clientGroup.color)); + } + }); + + if (sb.length() <= 0) { + return Constants.EMPTY_NOTE; + } else { + return sb.toString(); + } + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java index 79caa3c5..46a89254 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java @@ -26,6 +26,7 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; +import org.eclipse.rap.rwt.RWT; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Rectangle; @@ -44,12 +45,13 @@ import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.EntityKey; +import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup; 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.session.ClientConnection; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; -import ch.ethz.seb.sebserver.gbl.monitoring.IndicatorValue; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; +import ch.ethz.seb.sebserver.gbl.monitoring.IndicatorValue; import ch.ethz.seb.sebserver.gbl.util.Tuple; import ch.ethz.seb.sebserver.gui.service.ResourceService; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; @@ -60,31 +62,35 @@ import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate { - private static final int[] TABLE_PROPORTIONS = new int[] { 3, 3, 2, 1 }; - private static final int BOTTOM_PADDING = 20; - private static final int NUMBER_OF_NONE_INDICATOR_COLUMNS = 3; + //private static final int[] TABLE_PROPORTIONS = new int[] { 3, 3, 2, 1 }; + //private static final int NUMBER_OF_NONE_INDICATOR_COLUMNS = 3; private static final String INDICATOR_NAME_TEXT_KEY_PREFIX = "sebserver.exam.indicator.type.description."; private final static LocTextKey CONNECTION_ID_TEXT_KEY = new LocTextKey("sebserver.monitoring.connection.list.column.id"); private final static LocTextKey CONNECTION_ID_TOOLTIP_TEXT_KEY = - new LocTextKey("sebserver.monitoring.connection.list.column.id" + Constants.TOOLTIP_TEXT_KEY_SUFFIX); + new LocTextKey(CONNECTION_ID_TEXT_KEY + Constants.TOOLTIP_TEXT_KEY_SUFFIX); + private final static LocTextKey CONNECTION_GROUP_TEXT_KEY = + new LocTextKey("sebserver.monitoring.connection.list.column.group"); + private final static LocTextKey CONNECTION_GROUP_TOOLTIP_TEXT_KEY = + new LocTextKey(CONNECTION_GROUP_TEXT_KEY + Constants.TOOLTIP_TEXT_KEY_SUFFIX); private final static LocTextKey CONNECTION_INFO_TEXT_KEY = new LocTextKey("sebserver.monitoring.connection.list.column.info"); private final static LocTextKey CONNECTION_INFO_TOOLTIP_TEXT_KEY = - new LocTextKey("sebserver.monitoring.connection.list.column.info" + Constants.TOOLTIP_TEXT_KEY_SUFFIX); + new LocTextKey(CONNECTION_INFO_TEXT_KEY + Constants.TOOLTIP_TEXT_KEY_SUFFIX); private final static LocTextKey CONNECTION_STATUS_TEXT_KEY = new LocTextKey("sebserver.monitoring.connection.list.column.status"); private final static LocTextKey CONNECTION_STATUS_TOOLTIP_TEXT_KEY = - new LocTextKey("sebserver.monitoring.connection.list.column.status" + Constants.TOOLTIP_TEXT_KEY_SUFFIX); + new LocTextKey(CONNECTION_STATUS_TEXT_KEY + Constants.TOOLTIP_TEXT_KEY_SUFFIX); private final PageService pageService; private final Exam exam; private final boolean distributedSetup; private final Map indicatorMapping; + private final Map clientGroupMapping; private final Table table; private final ColorData colorData; private final Function localizedClientConnectionStatusNameFunction; @@ -99,6 +105,10 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate private final Color darkFontColor; private final Color lightFontColor; + private final boolean hasClientGroups; + private final int numberOfNoneIndicatorColumns; + private final int[] tableProportions; + private boolean forceUpdateAll = false; public ClientConnectionTable( @@ -106,6 +116,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate final Composite tableRoot, final Exam exam, final Collection indicators, + final Collection clientGroups, final boolean distributedSetup) { this.pageService = pageService; @@ -121,11 +132,23 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate this.darkFontColor = new Color(display, Constants.BLACK_RGB); this.lightFontColor = new Color(display, Constants.WHITE_RGB); + this.hasClientGroups = clientGroups != null && !clientGroups.isEmpty(); + this.numberOfNoneIndicatorColumns = this.hasClientGroups ? 4 : 3; + this.tableProportions = this.hasClientGroups + ? new int[] { 3, 2, 3, 2, 1 } + : new int[] { 3, 3, 2, 1 }; + this.indicatorMapping = IndicatorData.createFormIndicators( indicators, display, this.colorData, - NUMBER_OF_NONE_INDICATOR_COLUMNS); + this.numberOfNoneIndicatorColumns); + + this.clientGroupMapping = clientGroups == null || clientGroups.isEmpty() + ? null + : clientGroups + .stream() + .collect(Collectors.toMap(cg -> cg.id, Function.identity())); this.localizedClientConnectionStatusNameFunction = resourceService.localizedClientConnectionStatusNameFunction(); @@ -136,11 +159,11 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate gridLayout.marginWidth = 100; gridLayout.marginRight = 100; this.table.setLayout(gridLayout); - final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false); + final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); this.table.setLayoutData(gridData); this.table.setHeaderVisible(true); this.table.setLinesVisible(true); - + this.table.setData(RWT.MARKUP_ENABLED, Boolean.TRUE); this.table.addListener(SWT.Selection, event -> this.notifySelectionChange()); this.table.addListener(SWT.MouseUp, this::notifyTableInfoClick); @@ -148,6 +171,12 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate this.table, CONNECTION_ID_TEXT_KEY, CONNECTION_ID_TOOLTIP_TEXT_KEY); + if (this.clientGroupMapping != null && !this.clientGroupMapping.isEmpty()) { + widgetFactory.tableColumnLocalized( + this.table, + CONNECTION_GROUP_TEXT_KEY, + CONNECTION_GROUP_TOOLTIP_TEXT_KEY); + } widgetFactory.tableColumnLocalized( this.table, CONNECTION_INFO_TEXT_KEY, @@ -285,10 +314,10 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate @Override public void update(final MonitoringStatus monitoringStatus) { final Collection connectionData = monitoringStatus.getConnectionData(); - + final boolean sizeChanged = connectionData.size() != this.table.getItemCount(); final boolean needsSync = monitoringStatus.statusFilterChanged() || this.forceUpdateAll || - connectionData.size() != this.table.getItemCount() || + sizeChanged || (this.tableMapping != null && this.table != null && this.tableMapping.size() != this.table.getItemCount()) @@ -326,6 +355,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate } this.forceUpdateAll = false; + this.needsSort = sizeChanged; updateGUI(); } @@ -352,19 +382,21 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate if (this.tableWidth != area.width) { // proportions size - final int pSize = TABLE_PROPORTIONS[0] + - TABLE_PROPORTIONS[1] + - TABLE_PROPORTIONS[2] + - TABLE_PROPORTIONS[3] * this.indicatorMapping.size(); + final int pSize = this.tableProportions[0] + + this.tableProportions[1] + + this.tableProportions[2] + + this.tableProportions[3] + + (this.hasClientGroups ? this.tableProportions[4] : 0) + * this.indicatorMapping.size(); final int columnUnitSize = (pSize > 0) ? area.width / pSize - : area.width / TABLE_PROPORTIONS.length - 1 + this.indicatorMapping.size(); + : area.width / this.tableProportions.length - 1 + this.indicatorMapping.size(); final TableColumn[] columns = this.table.getColumns(); for (int i = 0; i < columns.length; i++) { - final int proportionFactor = (i < TABLE_PROPORTIONS.length) - ? TABLE_PROPORTIONS[i] - : TABLE_PROPORTIONS[TABLE_PROPORTIONS.length - 1]; + final int proportionFactor = (i < this.tableProportions.length) + ? this.tableProportions[i] + : this.tableProportions[this.tableProportions.length - 1]; columns[i].setWidth(proportionFactor * columnUnitSize); } this.tableWidth = area.width; @@ -412,7 +444,8 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate private final class UpdatableTableItem implements Comparable { final Long connectionId; - private boolean changed = false; + private boolean dataChanged = false; + private boolean indicatorValueChanged = false; private ClientConnectionData connectionData; private int thresholdsWeight; private int[] indicatorWeights = null; @@ -424,17 +457,34 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate } private void update(final TableItem tableItem, final boolean force) { - if (force || this.changed) { - update(tableItem); + if (force || this.dataChanged) { + updateData(tableItem); } - this.changed = false; + if (force || this.indicatorValueChanged) { + updateIndicatorValues(tableItem); + } + this.dataChanged = false; + this.indicatorValueChanged = false; } - private void update(final TableItem tableItem) { - updateData(tableItem); + private void updateData(final TableItem tableItem) { + tableItem.setText(0, getConnectionIdentifier()); + if (ClientConnectionTable.this.hasClientGroups) { + tableItem.setText(1, getGroupInfo()); + tableItem.setText(2, getConnectionInfo()); + tableItem.setText( + 3, + ClientConnectionTable.this.localizedClientConnectionStatusNameFunction + .apply(this.connectionData)); + } else { + tableItem.setText(1, getConnectionInfo()); + tableItem.setText( + 2, + ClientConnectionTable.this.localizedClientConnectionStatusNameFunction + .apply(this.connectionData)); + } if (this.connectionData != null) { updateConnectionStatusColor(tableItem); - updateIndicatorValues(tableItem); updateDuplicateColor(tableItem); updateNotifications(tableItem); } @@ -451,19 +501,12 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate } } - private void updateData(final TableItem tableItem) { - tableItem.setText(0, getConnectionIdentifier()); - tableItem.setText(1, getConnectionInfo()); - tableItem.setText( - 2, - ClientConnectionTable.this.localizedClientConnectionStatusNameFunction.apply(this.connectionData)); - } - private void updateConnectionStatusColor(final TableItem tableItem) { final Color statusColor = ClientConnectionTable.this.colorData.getStatusColor(this.connectionData); final Color statusTextColor = ClientConnectionTable.this.colorData.getStatusTextColor(statusColor); - tableItem.setBackground(2, statusColor); - tableItem.setForeground(2, statusTextColor); + final int index = ClientConnectionTable.this.hasClientGroups ? 3 : 2; + tableItem.setBackground(index, statusColor); + tableItem.setForeground(index, statusTextColor); } private void updateDuplicateColor(final TableItem tableItem) { @@ -578,6 +621,22 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate return Constants.EMPTY_NOTE; } + private String getGroupInfo() { + final StringBuilder sb = new StringBuilder(); + ClientConnectionTable.this.clientGroupMapping.keySet().stream().forEach(key -> { + if (this.connectionData.groups.contains(key)) { + final ClientGroup clientGroup = ClientConnectionTable.this.clientGroupMapping.get(key); + sb.append(WidgetFactory.getTextWithBackgroundHTML(clientGroup.name, clientGroup.color)); + } + }); + + if (sb.length() <= 0) { + return Constants.EMPTY_NOTE; + } else { + return sb.toString(); + } + } + String getConnectionIdentifier() { if (this.connectionData != null && this.connectionData.clientConnection.userSessionId != null) { return this.connectionData.clientConnection.userSessionId; @@ -587,15 +646,16 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate } void push(final ClientConnectionData connectionData) { - this.changed = this.connectionData == null || + this.dataChanged = this.connectionData == null || !this.connectionData.dataEquals(connectionData); - final boolean statusChanged = this.connectionData == null || - this.connectionData.clientConnection.status != connectionData.clientConnection.status; + this.indicatorValueChanged = this.connectionData == null || + (this.connectionData.clientConnection.status.clientActiveStatus + && !this.connectionData.indicatorValuesEquals(connectionData)); final boolean notificationChanged = this.connectionData == null || BooleanUtils.toBoolean(this.connectionData.pendingNotification) != BooleanUtils .toBoolean(connectionData.pendingNotification); - if (statusChanged || notificationChanged) { + if (this.dataChanged || notificationChanged) { ClientConnectionTable.this.needsSort = true; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java index df443bbc..7a9a15b1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/WidgetFactory.java @@ -1053,25 +1053,27 @@ public class WidgetFactory { } } - public String getColorValueHTML(final ClientGroupData data) { + public static String getTextWithBackgroundHTML(final String text, final String color) { + return new StringBuilder().append("") + .append(text) + // .append(" ") + .append("") + .toString(); + } + + public static String getColorValueHTML(final ClientGroupData data) { final String color = data.getColor(); if (StringUtils.isBlank(color)) { return Constants.EMPTY_NOTE; } - return new StringBuilder().append("   ") - .append(" (#") - .append(color) - .append(")   ") - .append("") - .toString(); - + return getTextWithBackgroundHTML(color, color); } } diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 25dcd4b6..fd7e3c63 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -1918,6 +1918,8 @@ sebserver.monitoring.exam=Monitoring Exam: {0} sebserver.monitoring.connection.list.column.id=User Name or Session sebserver.monitoring.connection.list.column.id.tooltip=The user session identifier or username sent by the SEB client after LMS login +sebserver.monitoring.connection.list.column.group=Client Group(s) +sebserver.monitoring.connection.list.column.group.tooltip=The client group(s) the connection belongs to sebserver.monitoring.connection.list.column.info=Connection Info sebserver.monitoring.connection.list.column.info.tooltip=Format: IP Address,SEB Version, OSName sebserver.monitoring.connection.list.column.status=Status @@ -1925,6 +1927,8 @@ sebserver.monitoring.connection.list.column.status.tooltip=The current connectio sebserver.monitoring.connection.form.id=User Name or Session sebserver.monitoring.connection.form.id.tooltip=The user session identifier or username sent by the SEB client after LMS login +sebserver.monitoring.connection.form.group=Client Group(s) +sebserver.monitoring.connection.form.group.tooltip=The client groups this SEB client belongs to sebserver.monitoring.connection.form.info=Connection Info sebserver.monitoring.connection.form.info.tooltip=Format: IP Address,SEB Version, OSName sebserver.monitoring.connection.form.status=Status diff --git a/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java b/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java index b18cfc90..170bf0f3 100644 --- a/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java +++ b/src/test/java/ch/ethz/seb/sebserver/gbl/model/ModelObjectJSONGenerator.java @@ -25,6 +25,7 @@ import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.model.exam.Chapters; +import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus; import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType; @@ -279,6 +280,13 @@ public class ModelObjectJSONGenerator { System.out.println(domainObject.getClass().getSimpleName() + ":"); System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject)); + ((ClientConnectionData) domainObject) + .addToClientGroup(new ClientGroup(1L, 1L, "group1", null, null, null, null)); + ((ClientConnectionData) domainObject) + .addToClientGroup(new ClientGroup(2L, 1L, "group2", null, null, null, null)); + System.out.println(domainObject.getClass().getSimpleName() + ":"); + System.out.println(writerWithDefaultPrettyPrinter.writeValueAsString(domainObject)); + domainObject = new ClientEvent(1L, 1L, EventType.WARN_LOG, System.currentTimeMillis(), System.currentTimeMillis(), 123.0, "text"); System.out.println(domainObject.getClass().getSimpleName() + ":");