SEBSERV-163 validation

This commit is contained in:
anhefti 2022-08-25 13:28:35 +02:00
parent c431279214
commit 800494b1b7
18 changed files with 762 additions and 46 deletions

View file

@ -250,6 +250,10 @@ public class APIMessage implements Serializable {
this.apiMessages = Arrays.asList(apiMessage);
}
public APIMessageException(final APIMessage... apiMessage) {
this.apiMessages = Arrays.asList(apiMessage);
}
public APIMessageException(final ErrorMessage errorMessage) {
super(errorMessage.systemMessage);
this.apiMessages = Arrays.asList(errorMessage.of());

View file

@ -11,21 +11,28 @@ package ch.ethz.seb.sebserver.gbl.model.exam;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.Domain.CLIENT_GROUP;
import ch.ethz.seb.sebserver.gbl.model.Entity;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ClientGroup implements Entity {
public class ClientGroup implements ClientGroupData {
public static final String FILTER_ATTR_EXAM_ID = "examId";
public static final String ATTR_IP_RANGE_START = "ipRangeStart";
public static final String ATTR_IP_RANGE_END = "ipRangeEnd";
public static final String ATTR_CLIENT_OS = "clientOS";
public enum ClientGroupType {
NONE,
IP_V4_RANGE,
CLIENT_OS
}
@ -52,8 +59,14 @@ public class ClientGroup implements Entity {
@JsonProperty(CLIENT_GROUP.ATTR_ICON)
public final String icon;
@JsonProperty(CLIENT_GROUP.ATTR_DATA)
public final String data;
@JsonProperty(ATTR_IP_RANGE_START)
public final String ipRangeStart;
@JsonProperty(ATTR_IP_RANGE_END)
public final String ipRangeEnd;
@JsonProperty(ATTR_CLIENT_OS)
public final String clientOS;
@JsonCreator
public ClientGroup(
@ -63,7 +76,9 @@ public class ClientGroup implements Entity {
@JsonProperty(CLIENT_GROUP.ATTR_TYPE) final ClientGroupType type,
@JsonProperty(CLIENT_GROUP.ATTR_COLOR) final String color,
@JsonProperty(CLIENT_GROUP.ATTR_ICON) final String icon,
@JsonProperty(CLIENT_GROUP.ATTR_DATA) final String data) {
@JsonProperty(ATTR_IP_RANGE_START) final String ipRangeStart,
@JsonProperty(ATTR_IP_RANGE_END) final String ipRangeEnd,
@JsonProperty(ATTR_CLIENT_OS) final String clientOS) {
super();
this.id = id;
@ -72,7 +87,48 @@ public class ClientGroup implements Entity {
this.type = type;
this.color = color;
this.icon = icon;
this.data = data;
this.ipRangeStart = ipRangeStart;
this.ipRangeEnd = ipRangeEnd;
this.clientOS = clientOS;
}
public ClientGroup(
final Long id,
final Long examId,
final String name,
final ClientGroupType type,
final String color,
final String icon,
final String data) {
super();
this.id = id;
this.examId = examId;
this.name = name;
this.type = type;
this.color = color;
this.icon = icon;
switch (type) {
case IP_V4_RANGE: {
final String[] split = StringUtils.split(data, Constants.EMBEDDED_LIST_SEPARATOR);
this.ipRangeStart = split[0];
this.ipRangeEnd = split[1];
this.clientOS = null;
break;
}
case CLIENT_OS: {
this.ipRangeStart = null;
this.ipRangeEnd = null;
this.clientOS = data;
break;
}
default: {
this.ipRangeStart = null;
this.ipRangeEnd = null;
this.clientOS = null;
}
}
}
public ClientGroup(final Long examId, final POSTMapper postParams) {
@ -82,7 +138,9 @@ public class ClientGroup implements Entity {
this.type = postParams.getEnum(CLIENT_GROUP.ATTR_TYPE, ClientGroupType.class);
this.color = postParams.getString(CLIENT_GROUP.ATTR_COLOR);
this.icon = postParams.getString(CLIENT_GROUP.ATTR_ICON);
this.data = postParams.getString(CLIENT_GROUP.ATTR_DATA);
this.ipRangeStart = postParams.getString(ATTR_IP_RANGE_START);
this.ipRangeEnd = postParams.getString(ATTR_IP_RANGE_END);
this.clientOS = postParams.getString(ATTR_CLIENT_OS);
}
@Override
@ -100,6 +158,7 @@ public class ClientGroup implements Entity {
return this.name;
}
@Override
public Long getId() {
return this.id;
}
@ -108,20 +167,49 @@ public class ClientGroup implements Entity {
return this.examId;
}
@Override
public ClientGroupType getType() {
return this.type;
}
@Override
public String getColor() {
return this.color;
}
@Override
public String getIcon() {
return this.icon;
}
@Override
public String getIpRangeStart() {
return this.ipRangeStart;
}
@Override
public String getIpRangeEnd() {
return this.ipRangeEnd;
}
@Override
public String getClientOS() {
return this.clientOS;
}
@JsonIgnore
public String getData() {
return this.data;
switch (this.type) {
case IP_V4_RANGE: {
return this.ipRangeStart + Constants.EMBEDDED_LIST_SEPARATOR + this.ipRangeEnd;
}
case CLIENT_OS: {
return this.clientOS;
}
default: {
return StringUtils.EMPTY;
}
}
}
@Override
@ -139,10 +227,42 @@ public class ClientGroup implements Entity {
builder.append(this.color);
builder.append(", icon=");
builder.append(this.icon);
builder.append(", data=");
builder.append(this.data);
builder.append(", ipRangeStart=");
builder.append(this.ipRangeStart);
builder.append(", ipRangeEnd=");
builder.append(this.ipRangeEnd);
builder.append(", clientOS=");
builder.append(this.clientOS);
builder.append("]");
return builder.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
result = prime * result + ((this.type == null) ? 0 : this.type.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final ClientGroup other = (ClientGroup) obj;
if (this.id == null) {
if (other.id != null)
return false;
} else if (!this.id.equals(other.id))
return false;
if (this.type != other.type)
return false;
return true;
}
}

View file

@ -0,0 +1,29 @@
/*
* 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.model.exam;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup.ClientGroupType;
public interface ClientGroupData extends Entity {
Long getId();
ClientGroupType getType();
String getColor();
String getIcon();
String getIpRangeStart();
String getIpRangeEnd();
String getClientOS();
}

View file

@ -11,18 +11,21 @@ package ch.ethz.seb.sebserver.gbl.model.exam;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
import ch.ethz.seb.sebserver.gbl.model.Domain.CLIENT_GROUP;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup.ClientGroupType;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ClientGroupTemplate implements Entity {
public class ClientGroupTemplate implements ClientGroupData {
public static final String ATTR_EXAM_TEMPLATE_ID = "examTemplateId";
@ -47,8 +50,14 @@ public class ClientGroupTemplate implements Entity {
@JsonProperty(CLIENT_GROUP.ATTR_ICON)
public final String icon;
@JsonProperty(CLIENT_GROUP.ATTR_DATA)
public final String data;
@JsonProperty(ClientGroup.ATTR_IP_RANGE_START)
public final String ipRangeStart;
@JsonProperty(ClientGroup.ATTR_IP_RANGE_END)
public final String ipRangeEnd;
@JsonProperty(ClientGroup.ATTR_CLIENT_OS)
public final String clientOS;
@JsonCreator
public ClientGroupTemplate(
@ -58,7 +67,9 @@ public class ClientGroupTemplate implements Entity {
@JsonProperty(CLIENT_GROUP.ATTR_TYPE) final ClientGroupType type,
@JsonProperty(CLIENT_GROUP.ATTR_COLOR) final String color,
@JsonProperty(CLIENT_GROUP.ATTR_ICON) final String icon,
@JsonProperty(CLIENT_GROUP.ATTR_DATA) final String data) {
@JsonProperty(ClientGroup.ATTR_IP_RANGE_START) final String ipRangeStart,
@JsonProperty(ClientGroup.ATTR_IP_RANGE_END) final String ipRangeEnd,
@JsonProperty(ClientGroup.ATTR_CLIENT_OS) final String clientOS) {
super();
this.id = id;
@ -67,7 +78,9 @@ public class ClientGroupTemplate implements Entity {
this.type = type;
this.color = color;
this.icon = icon;
this.data = data;
this.ipRangeStart = ipRangeStart;
this.ipRangeEnd = ipRangeEnd;
this.clientOS = clientOS;
}
public ClientGroupTemplate(final Long id, final Long examTemplateId, final POSTMapper postParams) {
@ -78,7 +91,9 @@ public class ClientGroupTemplate implements Entity {
this.type = postParams.getEnum(CLIENT_GROUP.ATTR_TYPE, ClientGroupType.class);
this.color = postParams.getString(CLIENT_GROUP.ATTR_COLOR);
this.icon = postParams.getString(CLIENT_GROUP.ATTR_ICON);
this.data = postParams.getString(CLIENT_GROUP.ATTR_DATA);
this.ipRangeStart = postParams.getString(ClientGroup.ATTR_IP_RANGE_START);
this.ipRangeEnd = postParams.getString(ClientGroup.ATTR_IP_RANGE_END);
this.clientOS = postParams.getString(ClientGroup.ATTR_CLIENT_OS);
}
public ClientGroupTemplate(final Long id, final ClientGroupTemplate other) {
@ -89,7 +104,9 @@ public class ClientGroupTemplate implements Entity {
this.type = other.type;
this.color = other.color;
this.icon = other.icon;
this.data = other.data;
this.ipRangeStart = other.ipRangeStart;
this.ipRangeEnd = other.ipRangeEnd;
this.clientOS = other.clientOS;
}
@Override
@ -107,34 +124,64 @@ public class ClientGroupTemplate implements Entity {
return this.name;
}
@Override
public Long getId() {
return this.id;
}
public Long getExamTempateId() {
return this.examTemplateId;
}
@Override
public ClientGroupType getType() {
return this.type;
}
@Override
public String getColor() {
return this.color;
}
@Override
public String getIcon() {
return this.icon;
}
public Long getExamTemplateId() {
return this.examTemplateId;
}
@Override
public String getIpRangeStart() {
return this.ipRangeStart;
}
@Override
public String getIpRangeEnd() {
return this.ipRangeEnd;
}
@Override
public String getClientOS() {
return this.clientOS;
}
@JsonIgnore
public String getData() {
return this.data;
switch (this.type) {
case IP_V4_RANGE: {
return this.ipRangeStart + Constants.EMBEDDED_LIST_SEPARATOR + this.ipRangeEnd;
}
case CLIENT_OS: {
return this.clientOS;
}
default: {
return StringUtils.EMPTY;
}
}
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("ClientGroup [id=");
builder.append("ClientGroupTemplate [id=");
builder.append(this.id);
builder.append(", examTemplateId=");
builder.append(this.examTemplateId);
@ -146,8 +193,12 @@ public class ClientGroupTemplate implements Entity {
builder.append(this.color);
builder.append(", icon=");
builder.append(this.icon);
builder.append(", data=");
builder.append(this.data);
builder.append(", ipRangeStart=");
builder.append(this.ipRangeStart);
builder.append(", ipRangeEnd=");
builder.append(this.ipRangeEnd);
builder.append(", clientOS=");
builder.append(this.clientOS);
builder.append("]");
return builder.toString();
}

View file

@ -8,13 +8,11 @@
package ch.ethz.seb.sebserver.gbl.monitoring;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup;
import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup.ClientGroupType;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
@ -34,15 +32,14 @@ public class IPv4RangeClientGroupMatcher implements ClientGroupConnectionMatcher
@Override
public boolean isInGroup(final ClientConnection clientConnection, final ClientGroup group) {
try {
final String[] split = StringUtils.split(group.data, Constants.LIST_SEPARATOR);
final long startIPAddress = Utils.ipToLong(split[0]);
final long endIPAddress = Utils.ipToLong(split[1]);
final long startIPAddress = Utils.ipToLong(group.ipRangeStart);
final long endIPAddress = Utils.ipToLong(group.ipRangeEnd);
final long inputIPAddress = Utils.ipToLong(clientConnection.clientAddress);
return (inputIPAddress >= startIPAddress && inputIPAddress <= endIPAddress);
} catch (final Exception e) {
log.error("Failed to verify ip range for group: {} connection: {}", group, clientConnection, e);
log.error("Failed to verify IP range for group: {} connection: {}", group, clientConnection, e);
return false;
}
}

View file

@ -20,6 +20,7 @@ public enum ActionCategory {
EXAM_LIST(new LocTextKey("sebserver.exam.list.actions"), 1),
EXAM_TEMPLATE_LIST(new LocTextKey("sebserver.examtemplate.list.actions"), 1),
INDICATOR_TEMPLATE_LIST(new LocTextKey("sebserver.examtemplate.indicator.list.actions"), 1),
CLIENT_GROUP_TEMPLATE_LIST(new LocTextKey("sebserver.examtemplate.clientgroup.list.actions"), 2),
EXAM_CONFIG_MAPPING_LIST(new LocTextKey("sebserver.exam.configuration.list.actions"), 1),
INDICATOR_LIST(new LocTextKey("sebserver.exam.indicator.list.actions"), 2),
SEB_CLIENT_CONFIG_LIST(new LocTextKey("sebserver.clientconfig.list.actions"), 1),

View file

@ -469,6 +469,32 @@ public enum ActionDefinition {
PageStateDefinitionImpl.EXAM_TEMPLATE_VIEW,
ActionCategory.FORM),
CLIENT_GROUP_TEMPLATE_NEW(
new LocTextKey("sebserver.examtemplate.clientgroup.action.list.new"),
ImageIcon.CLIENT_GROUP,
PageStateDefinitionImpl.CLIENT_GROUP_TEMPLATE_EDIT,
ActionCategory.CLIENT_GROUP_TEMPLATE_LIST),
CLIENT_GROUP_TEMPLATE_MODIFY_FROM_LIST(
new LocTextKey("sebserver.examtemplate.clientgroup.action.list.modify"),
ImageIcon.EDIT,
PageStateDefinitionImpl.CLIENT_GROUP_TEMPLATE_EDIT,
ActionCategory.CLIENT_GROUP_TEMPLATE_LIST),
CLIENT_GROUP_TEMPLATE_DELETE_FROM_LIST(
new LocTextKey("sebserver.examtemplate.clientgroup.action.list.delete"),
ImageIcon.DELETE,
PageStateDefinitionImpl.EXAM_TEMPLATE_VIEW,
ActionCategory.CLIENT_GROUP_TEMPLATE_LIST),
CLIENT_GROUP_TEMPLATE_SAVE(
new LocTextKey("sebserver.examtemplate.clientgroup.action.save"),
ImageIcon.SAVE,
PageStateDefinitionImpl.EXAM_TEMPLATE_VIEW,
ActionCategory.FORM),
CLIENT_GROUP_TEMPLATE_CANCEL_MODIFY(
new LocTextKey("sebserver.overall.action.modify.cancel"),
ImageIcon.CANCEL,
PageStateDefinitionImpl.EXAM_TEMPLATE_VIEW,
ActionCategory.FORM),
SEB_CLIENT_CONFIG_LIST(
new LocTextKey("sebserver.clientconfig.list.title"),
PageStateDefinitionImpl.SEB_CLIENT_CONFIG_LIST),

View file

@ -24,6 +24,7 @@ import ch.ethz.seb.sebserver.gui.content.configs.SEBClientConfigList;
import ch.ethz.seb.sebserver.gui.content.configs.SEBExamConfigForm;
import ch.ethz.seb.sebserver.gui.content.configs.SEBExamConfigList;
import ch.ethz.seb.sebserver.gui.content.configs.SEBSettingsForm;
import ch.ethz.seb.sebserver.gui.content.exam.ClientGroupTemplateForm;
import ch.ethz.seb.sebserver.gui.content.exam.ExamForm;
import ch.ethz.seb.sebserver.gui.content.exam.ExamList;
import ch.ethz.seb.sebserver.gui.content.exam.ExamTemplateForm;
@ -70,6 +71,7 @@ public enum PageStateDefinitionImpl implements PageStateDefinition {
EXAM_TEMPLATE_VIEW(Type.LIST_VIEW, ExamTemplateForm.class, ActivityDefinition.EXAM_TEMPLATE),
EXAM_TEMPLATE_EDIT(Type.FORM_EDIT, ExamTemplateForm.class, ActivityDefinition.EXAM_TEMPLATE),
INDICATOR_TEMPLATE_EDIT(Type.FORM_EDIT, IndicatorTemplateForm.class, ActivityDefinition.EXAM_TEMPLATE),
CLIENT_GROUP_TEMPLATE_EDIT(Type.FORM_EDIT, ClientGroupTemplateForm.class, ActivityDefinition.EXAM_TEMPLATE),
SEB_CLIENT_CONFIG_LIST(Type.LIST_VIEW, SEBClientConfigList.class, ActivityDefinition.SEB_CLIENT_CONFIG),
SEB_CLIENT_CONFIG_VIEW(Type.FORM_VIEW, SEBClientConfigForm.class, ActivityDefinition.SEB_CLIENT_CONFIG),

View file

@ -0,0 +1,234 @@
/*
* 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.gui.content.exam;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.widgets.Composite;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.Constants;
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.ClientGroup.ClientGroupType;
import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroupTemplate;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamTemplate;
import ch.ethz.seb.sebserver.gbl.model.exam.IndicatorTemplate;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.form.Form;
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
import ch.ethz.seb.sebserver.gui.form.FormHandle;
import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.GetClientGroupTemplate;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.NewClientGroupTemplate;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.SaveClientGroupTemplate;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.template.GetExamTemplate;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@Lazy
@Component
@GuiProfile
public class ClientGroupTemplateForm implements TemplateComposer {
private static final LocTextKey NEW_CLIENT_GROUP_TILE_TEXT_KEY =
new LocTextKey("sebserver.exam.clientgroup.form.title.new");
private static final LocTextKey CLIENT_GROUP_TILE_TEXT_KEY =
new LocTextKey("sebserver.exam.clientgroup.form.title");
private static final LocTextKey FORM_COLOR_TEXT_KEY =
new LocTextKey("sebserver.exam.clientgroup.form.color");
private static final LocTextKey FORM_TYPE_TEXT_KEY =
new LocTextKey("sebserver.exam.clientgroup.form.type");
private static final LocTextKey FORM_NAME_TEXT_KEY =
new LocTextKey("sebserver.exam.clientgroup.form.name");
private static final LocTextKey FORM_EXAM_TEXT_KEY =
new LocTextKey("sebserver.exam.clientgroup.form.exam");
private static final LocTextKey FORM_DESC_TEXT_KEY =
new LocTextKey("sebserver.exam.clientgroup.form.description");
private static final LocTextKey FORM_IP_START_KEY =
new LocTextKey("sebserver.exam.clientgroup.form.ipstart");
private static final LocTextKey FORM_IP_END_KEY =
new LocTextKey("sebserver.exam.clientgroup.form.ipend");
private static final String CLIENT_GROUP_TYPE_DESC_PREFIX =
"sebserver.exam.clientgroup.type.description.";
private static final String TYPE_DESCRIPTION_FIELD_NAME =
"typeDescription";
private final PageService pageService;
private final ResourceService resourceService;
private final I18nSupport i18nSupport;
public ClientGroupTemplateForm(
final PageService pageService,
final ResourceService resourceService,
final I18nSupport i18nSupport) {
super();
this.pageService = pageService;
this.resourceService = resourceService;
this.i18nSupport = i18nSupport;
}
@Override
public void compose(final PageContext pageContext) {
final RestService restService = this.resourceService.getRestService();
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
final EntityKey entityKey = pageContext.getEntityKey();
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
final boolean isNew = entityKey == null;
final boolean isReadonly = pageContext.isReadonly();
final ExamTemplate examTemplate = restService
.getBuilder(GetExamTemplate.class)
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
.call()
.onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error))
.getOrThrow();
// get data or create new. Handle error if happen
final ClientGroupTemplate clientGroupTemplate = (isNew)
? new ClientGroupTemplate(null, Long.parseLong(parentEntityKey.modelId),
null, null, null, null, null, null, null)
: restService
.getBuilder(GetClientGroupTemplate.class)
.withURIVariable(API.PARAM_PARENT_MODEL_ID, parentEntityKey.modelId)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
.onError(error -> pageContext.notifyLoadError(EntityType.CLIENT_GROUP, error))
.getOrThrow();
final boolean typeSet = clientGroupTemplate.type != null;
final String typeDescription = (typeSet)
? Utils.formatLineBreaks(
this.i18nSupport.getText(CLIENT_GROUP_TYPE_DESC_PREFIX + clientGroupTemplate.type.name()))
: Constants.EMPTY_NOTE;
// new PageContext with actual EntityKey
final PageContext formContext = pageContext.withEntityKey(clientGroupTemplate.getEntityKey());
// the default page layout
final LocTextKey titleKey = (isNew)
? NEW_CLIENT_GROUP_TILE_TEXT_KEY
: CLIENT_GROUP_TILE_TEXT_KEY;
final Composite content = widgetFactory.defaultPageLayout(
formContext.getParent(),
titleKey);
final FormHandle<ClientGroupTemplate> formHandle = this.pageService.formBuilder(
formContext.copyOf(content))
.readonly(isReadonly)
.putStaticValueIf(() -> !isNew,
Domain.CLIENT_GROUP.ATTR_ID,
clientGroupTemplate.getModelId())
.putStaticValue(
Domain.EXAM.ATTR_INSTITUTION_ID,
String.valueOf(examTemplate.getInstitutionId()))
.putStaticValue(
IndicatorTemplate.ATTR_EXAM_TEMPLATE_ID,
parentEntityKey.getModelId())
.addField(FormBuilder.text(
Domain.EXAM_TEMPLATE.ATTR_NAME,
FORM_EXAM_TEXT_KEY,
examTemplate.name)
.readonly(true))
.addField(FormBuilder.text(
Domain.CLIENT_GROUP.ATTR_NAME,
FORM_NAME_TEXT_KEY,
clientGroupTemplate.name)
.mandatory(!isReadonly))
.addField(FormBuilder.colorSelection(
Domain.CLIENT_GROUP.ATTR_COLOR,
FORM_COLOR_TEXT_KEY,
clientGroupTemplate.color)
.withEmptyCellSeparation(false))
.addField(FormBuilder.singleSelection(
Domain.CLIENT_GROUP.ATTR_TYPE,
FORM_TYPE_TEXT_KEY,
(clientGroupTemplate.type != null) ? clientGroupTemplate.type.name() : null,
this.resourceService::clientGroupTypeResources)
.withSelectionListener(this::updateForm)
.mandatory(!isReadonly))
.addField(FormBuilder.text(
TYPE_DESCRIPTION_FIELD_NAME,
FORM_DESC_TEXT_KEY,
typeDescription)
.asArea()
//.asHTML(true)
.readonly(true))
.addField(FormBuilder.text(
ClientGroup.ATTR_IP_RANGE_START,
FORM_IP_START_KEY,
clientGroupTemplate::getIpRangeStart)
.visibleIf(clientGroupTemplate.type != null
&& clientGroupTemplate.type == ClientGroupType.IP_V4_RANGE))
.addField(FormBuilder.text(
ClientGroup.ATTR_IP_RANGE_END,
FORM_IP_END_KEY,
clientGroupTemplate::getIpRangeEnd)
.visibleIf(clientGroupTemplate.type != null
&& clientGroupTemplate.type == ClientGroupType.IP_V4_RANGE))
.buildFor((isNew)
? restService.getRestCall(NewClientGroupTemplate.class)
: restService.getRestCall(SaveClientGroupTemplate.class));
// propagate content actions to action-pane
this.pageService.pageActionBuilder(formContext.clearEntityKeys())
.newAction(ActionDefinition.CLIENT_GROUP_TEMPLATE_SAVE)
.withEntityKey(parentEntityKey)
.withExec(formHandle::processFormSave)
.ignoreMoveAwayFromEdit()
.publishIf(() -> !isReadonly)
.newAction(ActionDefinition.CLIENT_GROUP_TEMPLATE_CANCEL_MODIFY)
.withEntityKey(parentEntityKey)
.withExec(this.pageService.backToCurrentFunction())
.publishIf(() -> !isReadonly);
}
private void updateForm(final Form form) {
final String typeValue = form.getFieldValue(Domain.CLIENT_GROUP.ATTR_TYPE);
if (StringUtils.isNotBlank(typeValue)) {
final String text = this.i18nSupport.getText(CLIENT_GROUP_TYPE_DESC_PREFIX + typeValue);
form.setFieldValue(
TYPE_DESCRIPTION_FIELD_NAME,
Utils.formatLineBreaks(text));
final ClientGroupType type = ClientGroupType.valueOf(typeValue);
form.setFieldVisible(false, ClientGroup.ATTR_IP_RANGE_START);
form.setFieldVisible(false, ClientGroup.ATTR_IP_RANGE_END);
if (type == ClientGroupType.IP_V4_RANGE) {
form.setFieldVisible(true, ClientGroup.ATTR_IP_RANGE_START);
form.setFieldVisible(true, ClientGroup.ATTR_IP_RANGE_END);
}
} else {
form.setFieldValue(TYPE_DESCRIPTION_FIELD_NAME, Constants.EMPTY_NOTE);
}
}
}

View file

@ -20,6 +20,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.ClientGroupTemplate;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.ExamTemplate;
import ch.ethz.seb.sebserver.gbl.model.exam.IndicatorTemplate;
@ -37,6 +38,8 @@ import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.DeleteClientGroupTemplate;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.GetClientGroupTemplatePage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.indicator.DeleteIndicatorTemplate;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.indicator.GetIndicatorTemplatePage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.template.DeleteExamTemplate;
@ -88,6 +91,21 @@ public class ExamTemplateForm implements TemplateComposer {
private static final LocTextKey INDICATOR_EMPTY_LIST_MESSAGE =
new LocTextKey("sebserver.examtemplate.indicator.list.empty");
private final static LocTextKey CLIENT_GROUP_LIST_TITLE_KEY =
new LocTextKey("sebserver.examtemplate.clientgroup.list.title");
private final static LocTextKey CLIENT_GROUP_LIST_TITLE_TOOLTIP_KEY =
new LocTextKey("sebserver.examtemplate.clientgroup.list.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
private final static LocTextKey CLIENT_GROUP_TYPE_COLUMN_KEY =
new LocTextKey("sebserver.examtemplate.clientgroup.list.column.type");
private final static LocTextKey CLIENT_GROUP_NAME_COLUMN_KEY =
new LocTextKey("sebserver.examtemplate.clientgroup.list.column.name");
private final static LocTextKey CLIENT_GROUP_COLOR_COLUMN_KEY =
new LocTextKey("sebserver.examtemplate.clientgroup.list.column.color");
private final static LocTextKey CLIENT_GROUP_EMPTY_SELECTION_TEXT_KEY =
new LocTextKey("sebserver.examtemplate.clientgroup.list.pleaseSelect");
private static final LocTextKey CLIENT_GROUP_EMPTY_LIST_MESSAGE =
new LocTextKey("sebserver.examtemplate.clientgroup.list.empty");
private static final LocTextKey EXAM_TEMPLATE_DELETE_CONFIRM =
new LocTextKey("sebserver.examtemplate.form.action.delete.confirm");
@ -307,6 +325,74 @@ public class ExamTemplateForm implements TemplateComposer {
.newAction(ActionDefinition.INDICATOR_TEMPLATE_NEW)
.withParentEntityKey(entityKey)
.publishIf(() -> userGrantCheck.m());
// List of Client Groups
this.widgetFactory.addFormSubContextHeader(
content,
CLIENT_GROUP_LIST_TITLE_KEY,
CLIENT_GROUP_LIST_TITLE_TOOLTIP_KEY);
final EntityTable<ClientGroupTemplate> clientGroupTable =
this.pageService
.entityTableBuilder(this.restService.getRestCall(GetClientGroupTemplatePage.class))
.withRestCallAdapter(builder -> builder.withURIVariable(
API.PARAM_PARENT_MODEL_ID,
entityKey.modelId))
.withEmptyMessage(CLIENT_GROUP_EMPTY_LIST_MESSAGE)
.withMarkup()
.withPaging(100)
.hideNavigation()
.withColumn(new ColumnDefinition<>(
Domain.CLIENT_GROUP.ATTR_NAME,
CLIENT_GROUP_NAME_COLUMN_KEY,
ClientGroupTemplate::getName)
.widthProportion(2))
.withColumn(new ColumnDefinition<>(
Domain.CLIENT_GROUP.ATTR_TYPE,
CLIENT_GROUP_TYPE_COLUMN_KEY,
this::clientGroupTypeName)
.widthProportion(1))
.withColumn(new ColumnDefinition<>(
Domain.CLIENT_GROUP.ATTR_COLOR,
CLIENT_GROUP_COLOR_COLUMN_KEY,
ClientGroupTemplate::getColor)
.asMarkup()
.widthProportion(4))
.withDefaultActionIf(
() -> userGrantCheck.m(),
() -> actionBuilder
.newAction(ActionDefinition.CLIENT_GROUP_TEMPLATE_MODIFY_FROM_LIST)
.withParentEntityKey(entityKey)
.create())
.withSelectionListener(this.pageService.getSelectionPublisher(
pageContext,
ActionDefinition.CLIENT_GROUP_TEMPLATE_MODIFY_FROM_LIST,
ActionDefinition.CLIENT_GROUP_TEMPLATE_DELETE_FROM_LIST))
.compose(pageContext.copyOf(content));
actionBuilder
.newAction(ActionDefinition.CLIENT_GROUP_TEMPLATE_MODIFY_FROM_LIST)
.withParentEntityKey(entityKey)
.withSelect(
indicatorTable::getMultiSelection,
PageAction::applySingleSelectionAsEntityKey,
CLIENT_GROUP_EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> userGrantCheck.m() && clientGroupTable.hasAnyContent(), false)
.newAction(ActionDefinition.CLIENT_GROUP_TEMPLATE_DELETE_FROM_LIST)
.withEntityKey(entityKey)
.withSelect(
indicatorTable::getMultiSelection,
this::deleteSelectedClientGroup,
CLIENT_GROUP_EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> userGrantCheck.m() && clientGroupTable.hasAnyContent(), false)
.newAction(ActionDefinition.CLIENT_GROUP_TEMPLATE_NEW)
.withParentEntityKey(entityKey)
.publishIf(() -> userGrantCheck.m());
}
}
@ -329,6 +415,17 @@ public class ExamTemplateForm implements TemplateComposer {
return action;
}
private PageAction deleteSelectedClientGroup(final PageAction action) {
final EntityKey entityKey = action.getEntityKey();
final EntityKey indicatorKey = action.getSingleSelection();
this.resourceService.getRestService()
.getBuilder(DeleteClientGroupTemplate.class)
.withURIVariable(API.PARAM_PARENT_MODEL_ID, entityKey.modelId)
.withURIVariable(API.PARAM_MODEL_ID, indicatorKey.modelId)
.call();
return action;
}
private String indicatorTypeName(final IndicatorTemplate indicator) {
if (indicator.type == null) {
return Constants.EMPTY_NOTE;
@ -338,4 +435,13 @@ public class ExamTemplateForm implements TemplateComposer {
.getText(ResourceService.EXAM_INDICATOR_TYPE_PREFIX + indicator.type.name());
}
private String clientGroupTypeName(final ClientGroupTemplate clientGroup) {
if (clientGroup.type == null) {
return Constants.EMPTY_NOTE;
}
return this.resourceService.getI18nSupport()
.getText(ResourceService.EXAM_CLIENT_GROUP_TYPE_PREFIX + clientGroup.type.name());
}
}

View file

@ -36,6 +36,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Activatable;
import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.EntityName;
import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroup.ClientGroupType;
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;
@ -123,6 +124,7 @@ public class ResourceService {
public static final String EXAM_TYPE_PREFIX = "sebserver.exam.type.";
public static final String USERACCOUNT_ROLE_PREFIX = "sebserver.useraccount.role.";
public static final String EXAM_INDICATOR_TYPE_PREFIX = "sebserver.exam.indicator.type.";
public static final String EXAM_CLIENT_GROUP_TYPE_PREFIX = "sebserver.exam.clientgroup.type.";
public static final String LMSSETUP_TYPE_PREFIX = "sebserver.lmssetup.type.";
public static final String CONFIG_ATTRIBUTE_TYPE_PREFIX = "sebserver.configtemplate.attr.type.";
public static final String SEB_RESTRICTION_WHITE_LIST_PREFIX = "sebserver.exam.form.sebrestriction.whiteListPaths.";
@ -260,6 +262,19 @@ public class ResourceService {
.collect(Collectors.toList());
}
public List<Tuple<String>> clientGroupTypeResources() {
return Arrays.stream(ClientGroupType.values())
.filter(type -> type != ClientGroupType.NONE)
.map(type -> new Tuple3<>(
type.name(),
this.i18nSupport.getText(EXAM_CLIENT_GROUP_TYPE_PREFIX + type.name(), type.name()),
Utils.formatLineBreaks(this.i18nSupport.getText(
EXAM_CLIENT_GROUP_TYPE_PREFIX + type.name() + Constants.TOOLTIP_TEXT_KEY_SUFFIX,
StringUtils.EMPTY))))
.sorted(RESOURCE_COMPARATOR_TUPLE_3)
.collect(Collectors.toList());
}
public List<Tuple<String>> examConfigurationSelectionResources() {
return getExamConfigurationSelection()
.getOr(Collections.emptyList())

View file

@ -130,6 +130,7 @@ public class WidgetFactory {
INSTITUTION("institution.png"),
LMS_SETUP("lmssetup.png"),
INDICATOR("indicator.png"),
CLIENT_GROUP("indicator.png"),
TEMPLATE("template.png"),
DISABLE("disable.png"),
SEND_QUIT("send-quit.png"),

View file

@ -129,7 +129,7 @@ public class ClientGroupDAOImpl implements ClientGroupDAO {
data.type.name(),
data.color,
data.icon,
data.data);
data.getData());
this.clientGroupRecordMapper.insert(newRecord);
@ -151,7 +151,7 @@ public class ClientGroupDAOImpl implements ClientGroupDAO {
data.type.name(),
data.color,
data.icon,
data.data);
data.getData());
this.clientGroupRecordMapper.updateByPrimaryKeySelective(newRecord);
return this.clientGroupRecordMapper.selectByPrimaryKey(data.id);

View file

@ -18,10 +18,14 @@ import org.springframework.validation.FieldError;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
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.ClientGroup.ClientGroupType;
import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroupData;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService;
public interface ExamAdminService {
@ -139,4 +143,76 @@ public interface ExamAdminService {
}
}
/** Used to check client group consistency for a given ClientGroup.
* Checks if correct entries for specific type
*
* If a check fails, the methods throws a APIMessageException with a FieldError to notify the caller
*
* @param clientGroup ClientGroup instance to check */
public static void checkClientGroupConsistency(final ClientGroupData clientGroup) {
final ClientGroupType type = clientGroup.getType();
if (type == null || type == ClientGroupType.NONE) {
throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError(
Domain.CLIENT_GROUP.TYPE_NAME,
Domain.CLIENT_GROUP.ATTR_TYPE,
"clientGroup:type:mandatory")));
}
switch (type) {
case IP_V4_RANGE: {
checkIPRange(clientGroup.getIpRangeStart(), clientGroup.getIpRangeEnd());
break;
}
case CLIENT_OS: {
checkClientOS(clientGroup.getClientOS());
break;
}
default: {
throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError(
Domain.CLIENT_GROUP.TYPE_NAME,
Domain.CLIENT_GROUP.ATTR_TYPE,
"clientGroup:type:typeInvalid")));
}
}
}
static void checkIPRange(final String ipRangeStart, final String ipRangeEnd) {
final long startIP = Utils.ipToLong(ipRangeStart);
if (startIP < 0) {
throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError(
Domain.CLIENT_GROUP.TYPE_NAME,
ClientGroup.ATTR_IP_RANGE_START,
"clientGroup:ipRangeStart:invalidIP")));
}
final long endIP = Utils.ipToLong(ipRangeEnd);
if (endIP < 0) {
throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError(
Domain.CLIENT_GROUP.TYPE_NAME,
ClientGroup.ATTR_IP_RANGE_END,
"clientGroup:ipRangeEnd:invalidIP")));
}
if (endIP <= startIP) {
throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError(
Domain.CLIENT_GROUP.TYPE_NAME,
ClientGroup.ATTR_IP_RANGE_START,
"clientGroup:ipRangeStart:invalidIPRange")),
APIMessage.fieldValidationError(
new FieldError(
Domain.CLIENT_GROUP.TYPE_NAME,
ClientGroup.ATTR_IP_RANGE_END,
"clientGroup:ipRangeEnd:invalidIPRange")));
}
}
static void checkClientOS(final String clientOS) {
// TODO
}
}

View file

@ -363,7 +363,9 @@ public class ExamTemplateServiceImpl implements ExamTemplateService {
template.type,
template.color,
template.icon,
template.data))
template.ipRangeStart,
template.ipRangeEnd,
template.clientOS))
.onError(
error -> log.error("Failed to automatically create client group from template: {} for exam: {}",
template,

View file

@ -192,10 +192,7 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
null,
postMap.getLong(IndicatorTemplate.ATTR_EXAM_TEMPLATE_ID),
postMap))
.map(indicator -> {
ExamAdminService.checkThresholdConsistency(indicator.thresholds);
return indicator;
})
.map(this::checkIndicatorConsistency)
.flatMap(this.examTemplateDAO::createNewIndicatorTemplate)
.flatMap(this.userActivityLogDAO::logCreate)
.getOrThrow();
@ -217,10 +214,7 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
this.checkModifyPrivilege(institutionId);
return this.beanValidationService
.validateBean(modifyData)
.map(indicator -> {
ExamAdminService.checkThresholdConsistency(indicator.thresholds);
return indicator;
})
.map(this::checkIndicatorConsistency)
.flatMap(this.examTemplateDAO::saveIndicatorTemplate)
.flatMap(this.userActivityLogDAO::logModify)
.getOrThrow();
@ -343,6 +337,7 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
null,
postMap.getLong(ClientGroupTemplate.ATTR_EXAM_TEMPLATE_ID),
postMap))
.map(this::checkClientGroupConsistency)
.flatMap(this.examTemplateDAO::createNewClientGroupTemplate)
.flatMap(this.userActivityLogDAO::logCreate)
.getOrThrow();
@ -364,6 +359,7 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
this.checkModifyPrivilege(institutionId);
return this.beanValidationService
.validateBean(modifyData)
.map(this::checkClientGroupConsistency)
.flatMap(this.examTemplateDAO::saveClientGroupTemplate)
.flatMap(this.userActivityLogDAO::logModify)
.getOrThrow();
@ -498,4 +494,14 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
};
}
private ClientGroupTemplate checkClientGroupConsistency(final ClientGroupTemplate clientGroupTemplate) {
ExamAdminService.checkClientGroupConsistency(clientGroupTemplate);
return clientGroupTemplate;
}
private IndicatorTemplate checkIndicatorConsistency(final IndicatorTemplate indicatorTemplate) {
ExamAdminService.checkThresholdConsistency(indicatorTemplate.thresholds);
return indicatorTemplate;
}
}

View file

@ -100,9 +100,12 @@ sebserver.form.validation.fieldError.exists=This name already exists. Please cho
sebserver.form.validation.fieldError.email=Invalid mail address
sebserver.form.validation.fieldError.serverNotAvailable=No service seems to be available within the given URL
sebserver.form.validation.fieldError.url.invalid=Invalid URL. The given URL cannot be reached.
sebserver.form.validation.fieldError.typeInvalid=This type is not implemented yet and cannot be used.
sebserver.form.validation.fieldError.url.noservice=The expected service is not available within the given URL and API access.
sebserver.form.validation.fieldError.thresholdDuplicate=There are duplicate threshold values.
sebserver.form.validation.fieldError.thresholdEmpty=There are missing values or colors for the threshold declaration
sebserver.form.validation.fieldError.invalidIP=Invalid IP v4. Please enter a valid IP-address (v4)
sebserver.form.validation.fieldError.invalidIPRange=Invalid IP-address range.
sebserver.error.unexpected=Unexpected Error
sebserver.page.message=Information
sebserver.dialog.confirm.title=Confirmation
@ -650,6 +653,11 @@ sebserver.exam.indicator.type.description.INFO_COUNT=This indicator shows the nu
sebserver.exam.indicator.type.description.BATTERY_STATUS=This indicator shows the percentage of the battery load level of a SEB Client.
sebserver.exam.indicator.type.description.WLAN_STATUS=This indicator shows the percentage of the WiFi connection status of a SEB Client.
sebserver.exam.clientgroup.type.IP_V4_RANGE=IP v4 Range
sebserver.exam.clientgroup.type.CLIENT_OS=SEB Client OS
sebserver.exam.clientgroup.type.description.IP_V4_RANGE=IP v4 Range TODO
sebserver.exam.clientgroup.type.description.CLIENT_OS=SEB Client OS TODO
sebserver.exam.indicator.info.pleaseSelect=At first please select an indicator from the list
sebserver.exam.indicator.action.list.new=Add Indicator
@ -677,6 +685,26 @@ sebserver.exam.indicator.form.thresholds=Thresholds
sebserver.exam.indicator.form.thresholds.tooltip=A list of value / color pairs that defines the thresholds of the indicator.<br/><br/>On the exam monitoring view a cell of the indicator is displayed in the specified color when the defined threshold value is reached
sebserver.exam.indicator.thresholds.select.color=Please select a color
sebserver.exam.clientgroup.form.title=Client Group
sebserver.exam.clientgroup.form.title.subtitle=
sebserver.exam.clientgroup.form.title.new=Add Client Group
sebserver.exam.clientgroup.form.exam=Exam
sebserver.exam.clientgroup.form.exam.tooltip=The exam this client group belongs to
sebserver.exam.clientgroup.form.name=Name
sebserver.exam.clientgroup.form.name.tooltip=The name of the client group.<br/><br/>This name is also displayed in the column cell of in the exam monitoring
sebserver.exam.clientgroup.form.type=Type
sebserver.exam.clientgroup.form.type.tooltip=The type of the client group<br/><br/>There are only a set of defined client group types to choose from.<br/>Choose one to see a detailed description for each client group type below.
sebserver.exam.clientgroup.form.description=Type Description
sebserver.exam.clientgroup.form.description.tooltip=A detailed description of the selected client group type
sebserver.exam.clientgroup.form.color=Color
sebserver.exam.clientgroup.form.color.tooltip=The color that is displayed on the exam monitoring for this client group
sebserver.exam.clientgroup.form.color.action=Please select a color
sebserver.exam.clientgroup.form.ipstart=Start of IP range
sebserver.exam.clientgroup.form.ipstart.tooltip=An IP v4 formatted PI address like 111.111.111.111 that defines the start of the range
sebserver.exam.clientgroup.form.ipend=End of IP range
sebserver.exam.clientgroup.form.ipend.tooltip=An IP v4 formatted PI address like 111.111.111.111 that defines the end of the range
sebserver.exam.indicator.thresholds.list.title=Thresholds
sebserver.exam.indicator.thresholds.list.value=Value
sebserver.exam.indicator.thresholds.list.value.tooltip=The threshold value
@ -1748,7 +1776,7 @@ sebserver.examtemplate.form.action.edit=Edit Exam Template
sebserver.examtemplate.form.action.delete=Delete Exam Template
sebserver.examtemplate.form.action.delete.confirm=Are you sure to delete this exam template?<br/><br/>Please note that a reference from a exam that uses this template will also be deleted but the exam itself is not affected.
sebserver.examtemplate.indicator.list.actions=
sebserver.examtemplate.indicator.list.actions=&nbsp;
sebserver.examtemplate.indicator.list.title=Indicators
sebserver.examtemplate.indicator.list.title.tooltip=A list of indicators that will automatically be created when importing a exam with this template
sebserver.examtemplate.indicator.list.column.type=Type
@ -1760,12 +1788,28 @@ sebserver.examtemplate.indicator.list.column.thresholds.tooltip=The thresholds o
sebserver.examtemplate.indicator.list.empty=There is currently no indicator defined for this exam template. Please create a new one
sebserver.examtemplate.indicator.list.pleaseSelect=At first please select an indicator from the list
sebserver.examtemplate.clientgroup.list.actions=&nbsp;
sebserver.examtemplate.clientgroup.list.title=Client Groups
sebserver.examtemplate.clientgroup.list.title.tooltip=A list of client groups that will automatically be created when importing a exam with this template
sebserver.examtemplate.clientgroup.list.column.type=Type
sebserver.examtemplate.clientgroup.list.column.type.tooltip=The type of the client group
sebserver.examtemplate.clientgroup.list.column.name=Name
sebserver.examtemplate.clientgroup.list.column.name.tooltip=The name of the client group
sebserver.examtemplate.clientgroup.list.column.color=Color
sebserver.examtemplate.clientgroup.list.column.color.tooltip=The color of the client group
sebserver.examtemplate.clientgroup.list.empty=There is currently no client group defined for this exam template. Please create a new one
sebserver.examtemplate.clientgroup.list.pleaseSelect=At first please select an client group from the list
sebserver.examtemplate.indicator.action.save=Save Indicator
sebserver.examtemplate.indicator.list.actions=
sebserver.examtemplate.indicator.action.list.new=Add Indicator
sebserver.examtemplate.indicator.action.list.modify=Edit Indicator
sebserver.examtemplate.indicator.action.list.delete=Delete Indicator
sebserver.examtemplate.clientgroup.action.save=Save Client Group
sebserver.examtemplate.clientgroup.action.list.new=Add Client Group
sebserver.examtemplate.clientgroup.action.list.modify=Edit Client Group
sebserver.examtemplate.clientgroup.action.list.delete=Delete Client Group
sebserver.examtemplate.proctoring.actions.open=Proctoring Settings
################################

View file

@ -1086,7 +1086,9 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
newClientGroup.type,
"000002",
newClientGroup.icon,
newClientGroup.data);
newClientGroup.ipRangeStart,
newClientGroup.ipRangeEnd,
newClientGroup.clientOS);
final Result<ClientGroup> savedClientGroupResult = restService
.getBuilder(SaveClientGroup.class)