SEBSERV-324 finished maintenance

This commit is contained in:
anhefti 2022-08-30 11:10:45 +02:00
parent 5aeb14ea8e
commit d648bcb167
32 changed files with 781 additions and 200 deletions

View file

@ -270,5 +270,4 @@ public class POSTMapper {
this.params.putIfAbsent(name, Arrays.asList(value));
return (T) this;
}
}

View file

@ -8,6 +8,9 @@
package ch.ethz.seb.sebserver.gbl.model.exam;
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@ -22,20 +25,12 @@ 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.util.Utils;
@JsonIgnoreProperties(ignoreUnknown = true)
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
}
@JsonProperty(CLIENT_GROUP.ATTR_ID)
public final Long id;
@ -66,7 +61,7 @@ public class ClientGroup implements ClientGroupData {
public final String ipRangeEnd;
@JsonProperty(ATTR_CLIENT_OS)
public final String clientOS;
public final ClientOS clientOS;
@JsonCreator
public ClientGroup(
@ -78,18 +73,18 @@ public class ClientGroup implements ClientGroupData {
@JsonProperty(CLIENT_GROUP.ATTR_ICON) final String icon,
@JsonProperty(ATTR_IP_RANGE_START) final String ipRangeStart,
@JsonProperty(ATTR_IP_RANGE_END) final String ipRangeEnd,
@JsonProperty(ATTR_CLIENT_OS) final String clientOS) {
@JsonProperty(ATTR_CLIENT_OS) final ClientOS clientOS) {
super();
this.id = id;
this.examId = examId;
this.name = name;
this.type = type;
this.type = type == null ? ClientGroupType.NONE : type;
this.color = color;
this.icon = icon;
this.ipRangeStart = ipRangeStart;
this.ipRangeEnd = ipRangeEnd;
this.clientOS = clientOS;
this.clientOS = clientOS == null ? ClientOS.NONE : clientOS;
}
public ClientGroup(
@ -105,7 +100,8 @@ public class ClientGroup implements ClientGroupData {
this.id = id;
this.examId = examId;
this.name = name;
this.type = type;
this.type = type == null ? ClientGroupType.NONE : type;
;
this.color = color;
this.icon = icon;
@ -114,19 +110,19 @@ public class ClientGroup implements ClientGroupData {
final String[] split = StringUtils.split(data, Constants.EMBEDDED_LIST_SEPARATOR);
this.ipRangeStart = split[0];
this.ipRangeEnd = split[1];
this.clientOS = null;
this.clientOS = ClientOS.NONE;
break;
}
case CLIENT_OS: {
this.ipRangeStart = null;
this.ipRangeEnd = null;
this.clientOS = data;
this.clientOS = Utils.enumFromString(data, ClientOS.class, ClientOS.NONE);
break;
}
default: {
this.ipRangeStart = null;
this.ipRangeEnd = null;
this.clientOS = null;
this.clientOS = ClientOS.NONE;
}
}
}
@ -140,7 +136,15 @@ public class ClientGroup implements ClientGroupData {
this.icon = postParams.getString(CLIENT_GROUP.ATTR_ICON);
this.ipRangeStart = postParams.getString(ATTR_IP_RANGE_START);
this.ipRangeEnd = postParams.getString(ATTR_IP_RANGE_END);
this.clientOS = postParams.getString(ATTR_CLIENT_OS);
this.clientOS = postParams.getEnum(ATTR_CLIENT_OS, ClientOS.class);
}
public static ClientGroup createNew(final String examId) {
try {
return new ClientGroup(null, Long.parseLong(examId), null, null, null, null, null, null, null);
} catch (final Exception e) {
return new ClientGroup(null, null, null, null, null, null, null, null, null);
}
}
@Override
@ -184,16 +188,32 @@ public class ClientGroup implements ClientGroupData {
@Override
public String getIpRangeStart() {
return this.ipRangeStart;
if (StringUtils.isBlank(this.ipRangeStart)) {
return null;
}
try {
return InetAddress.getByName(this.ipRangeStart).getHostAddress();
} catch (final UnknownHostException e) {
return null;
}
}
@Override
public String getIpRangeEnd() {
return this.ipRangeEnd;
if (StringUtils.isBlank(this.ipRangeEnd)) {
return null;
}
try {
return InetAddress.getByName(this.ipRangeEnd).getHostAddress();
} catch (final UnknownHostException e) {
return null;
}
}
@Override
public String getClientOS() {
public ClientOS getClientOS() {
return this.clientOS;
}
@ -204,7 +224,7 @@ public class ClientGroup implements ClientGroupData {
return this.ipRangeStart + Constants.EMBEDDED_LIST_SEPARATOR + this.ipRangeEnd;
}
case CLIENT_OS: {
return this.clientOS;
return this.clientOS.name();
}
default: {
return StringUtils.EMPTY;

View file

@ -9,10 +9,33 @@
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 {
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
}
public enum ClientOS {
NONE(null),
WINDOWS("Windows"),
MAC_OS("TODO"),
I_OS("TODO");
final String queryString;
private ClientOS(final String queryString) {
this.queryString = queryString;
}
}
Long getId();
ClientGroupType getType();
@ -25,5 +48,6 @@ public interface ClientGroupData extends Entity {
String getIpRangeEnd();
String getClientOS();
ClientOS getClientOS();
}

View file

@ -8,6 +8,9 @@
package ch.ethz.seb.sebserver.gbl.model.exam;
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@ -22,7 +25,6 @@ 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.exam.ClientGroup.ClientGroupType;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ClientGroupTemplate implements ClientGroupData {
@ -57,7 +59,7 @@ public class ClientGroupTemplate implements ClientGroupData {
public final String ipRangeEnd;
@JsonProperty(ClientGroup.ATTR_CLIENT_OS)
public final String clientOS;
public final ClientOS clientOS;
@JsonCreator
public ClientGroupTemplate(
@ -69,7 +71,7 @@ public class ClientGroupTemplate implements ClientGroupData {
@JsonProperty(CLIENT_GROUP.ATTR_ICON) final String icon,
@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) {
@JsonProperty(ClientGroup.ATTR_CLIENT_OS) final ClientOS clientOS) {
super();
this.id = id;
@ -93,7 +95,7 @@ public class ClientGroupTemplate implements ClientGroupData {
this.icon = postParams.getString(CLIENT_GROUP.ATTR_ICON);
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);
this.clientOS = postParams.getEnum(ClientGroup.ATTR_CLIENT_OS, ClientOS.class);
}
public ClientGroupTemplate(final Long id, final ClientGroupTemplate other) {
@ -150,16 +152,32 @@ public class ClientGroupTemplate implements ClientGroupData {
@Override
public String getIpRangeStart() {
return this.ipRangeStart;
if (StringUtils.isBlank(this.ipRangeStart)) {
return null;
}
try {
return InetAddress.getByName(this.ipRangeStart).getHostAddress();
} catch (final UnknownHostException e) {
return null;
}
}
@Override
public String getIpRangeEnd() {
return this.ipRangeEnd;
if (StringUtils.isBlank(this.ipRangeEnd)) {
return null;
}
try {
return InetAddress.getByName(this.ipRangeEnd).getHostAddress();
} catch (final UnknownHostException e) {
return null;
}
}
@Override
public String getClientOS() {
public ClientOS getClientOS() {
return this.clientOS;
}
@ -170,7 +188,7 @@ public class ClientGroupTemplate implements ClientGroupData {
return this.ipRangeStart + Constants.EMBEDDED_LIST_SEPARATOR + this.ipRangeEnd;
}
case CLIENT_OS: {
return this.clientOS;
return this.clientOS.name();
}
default: {
return StringUtils.EMPTY;

View file

@ -9,7 +9,7 @@
package ch.ethz.seb.sebserver.gbl.monitoring;
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.ClientGroupType;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
/** Defines a client connection to client group matcher for a specific client group type.

View file

@ -17,7 +17,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
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.ClientGroupType;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
@Lazy

View file

@ -14,7 +14,7 @@ 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.ClientGroup.ClientGroupType;
import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroupData.ClientGroupType;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.util.Utils;

View file

@ -193,6 +193,18 @@ public final class Utils {
.collect(Collectors.toList()));
}
public static <T extends Enum<T>> T enumFromString(
final String string,
final Class<T> enumClass,
final T defaultValue) {
try {
return Enum.valueOf(enumClass, string);
} catch (final Exception e) {
return defaultValue;
}
}
public static Collection<String> getListOfLines(final String list) {
if (list == null) {
return Collections.emptyList();

View file

@ -23,6 +23,7 @@ public enum ActionCategory {
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),
CLIENT_GROUP_LIST(new LocTextKey("sebserver.exam.clientgroup.list.actions"), 3),
SEB_CLIENT_CONFIG_LIST(new LocTextKey("sebserver.clientconfig.list.actions"), 1),
SEB_EXAM_CONFIG_LIST(new LocTextKey("sebserver.examconfig.list.actions"), 1),
SEB_CONFIG_TEMPLATE_LIST(new LocTextKey("sebserver.configtemplate.list.actions"), 1),

View file

@ -388,6 +388,33 @@ public enum ActionDefinition {
ImageIcon.CANCEL,
PageStateDefinitionImpl.EXAM_VIEW,
ActionCategory.FORM),
EXAM_CLIENT_GROUP_NEW(
new LocTextKey("sebserver.exam.clientgroup.action.list.new"),
ImageIcon.ADD_CLIENT_GROUP,
PageStateDefinitionImpl.CLIENT_GROUP_EDIT,
ActionCategory.CLIENT_GROUP_LIST),
EXAM_CLIENT_GROUP_MODIFY_FROM_LIST(
new LocTextKey("sebserver.exam.clientgroup.action.list.modify"),
ImageIcon.EDIT,
PageStateDefinitionImpl.CLIENT_GROUP_EDIT,
ActionCategory.CLIENT_GROUP_LIST),
EXAM_CLIENT_GROUP_DELETE_FROM_LIST(
new LocTextKey("sebserver.exam.clientgroup.action.list.delete"),
ImageIcon.DELETE,
PageStateDefinitionImpl.EXAM_VIEW,
ActionCategory.CLIENT_GROUP_LIST),
EXAM_CLIENT_GROUP_SAVE(
new LocTextKey("sebserver.exam.clientgroup.action.save"),
ImageIcon.SAVE,
PageStateDefinitionImpl.EXAM_VIEW,
ActionCategory.FORM),
EXAM_CLIENT_GROUP_CANCEL_MODIFY(
new LocTextKey("sebserver.overall.action.modify.cancel"),
ImageIcon.CANCEL,
PageStateDefinitionImpl.EXAM_VIEW,
ActionCategory.FORM),
EXAM_SEB_CLIENT_CONFIG_EXPORT(
new LocTextKey("sebserver.exam.action.createClientToStartExam"),
ImageIcon.EXPORT,
@ -471,7 +498,7 @@ public enum ActionDefinition {
CLIENT_GROUP_TEMPLATE_NEW(
new LocTextKey("sebserver.examtemplate.clientgroup.action.list.new"),
ImageIcon.CLIENT_GROUP,
ImageIcon.ADD_CLIENT_GROUP,
PageStateDefinitionImpl.CLIENT_GROUP_TEMPLATE_EDIT,
ActionCategory.CLIENT_GROUP_TEMPLATE_LIST),
CLIENT_GROUP_TEMPLATE_MODIFY_FROM_LIST(

View file

@ -254,14 +254,14 @@ public class ActionPane implements TemplateComposer {
final Template template = new Template();
final ImageCell imageCell = new ImageCell(template);
imageCell.setLeft(0, -8)
.setWidth(20)
.setWidth(24)
.setTop(0)
.setBottom(0, 0)
.setHorizontalAlignment(SWT.LEFT)
.setBackground(null);
imageCell.setBindingIndex(0);
final TextCell textCell = new TextCell(template);
textCell.setLeft(0, 20)
textCell.setLeft(0, 24)
.setWidth(SWT.DEFAULT)
.setTop(7)
.setBottom(0, 0)

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.ClientGroupForm;
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;
@ -66,6 +67,7 @@ public enum PageStateDefinitionImpl implements PageStateDefinition {
EXAM_VIEW(Type.FORM_VIEW, ExamForm.class, ActivityDefinition.EXAM),
EXAM_EDIT(Type.FORM_EDIT, ExamForm.class, ActivityDefinition.EXAM),
INDICATOR_EDIT(Type.FORM_EDIT, IndicatorForm.class, ActivityDefinition.EXAM),
CLIENT_GROUP_EDIT(Type.FORM_EDIT, ClientGroupForm.class, ActivityDefinition.EXAM),
EXAM_TEMPLATE_LIST(Type.LIST_VIEW, ExamTemplateList.class, ActivityDefinition.EXAM_TEMPLATE),
EXAM_TEMPLATE_VIEW(Type.LIST_VIEW, ExamTemplateForm.class, ActivityDefinition.EXAM_TEMPLATE),

View file

@ -0,0 +1,241 @@
/*
* 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.ClientGroupData.ClientGroupType;
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.QuizData;
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.GetExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.GetClientGroup;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.NewClientGroup;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.SaveClientGroup;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@Lazy
@Component
@GuiProfile
public class ClientGroupForm 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 LocTextKey FORM_OS_TYPE_KEY =
new LocTextKey("sebserver.exam.clientgroup.form.ostype");
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;
protected ClientGroupForm(final PageService pageService) {
this.pageService = pageService;
this.resourceService = pageService.getResourceService();
this.i18nSupport = pageService.getI18nSupport();
}
@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 Exam exam = restService
.getBuilder(GetExam.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 ClientGroup clientGroup = (isNew)
? ClientGroup.createNew(exam.getModelId())
: restService
.getBuilder(GetClientGroup.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
.onError(error -> pageContext.notifyLoadError(EntityType.CLIENT_GROUP, error))
.getOrThrow();
final boolean typeSet = clientGroup.type != null;
final String typeDescription = (typeSet)
? Utils.formatLineBreaks(
this.i18nSupport.getText(CLIENT_GROUP_TYPE_DESC_PREFIX + clientGroup.type.name()))
: Constants.EMPTY_NOTE;
// new PageContext with actual EntityKey
final PageContext formContext = pageContext.withEntityKey(clientGroup.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<ClientGroup> formHandle = this.pageService.formBuilder(
formContext.copyOf(content))
.readonly(isReadonly)
.putStaticValueIf(() -> !isNew,
Domain.CLIENT_GROUP.ATTR_ID,
clientGroup.getModelId())
.putStaticValue(
Domain.EXAM.ATTR_INSTITUTION_ID,
String.valueOf(exam.getInstitutionId()))
.putStaticValue(
Domain.CLIENT_GROUP.ATTR_EXAM_ID,
parentEntityKey.getModelId())
.addField(FormBuilder.text(
QuizData.QUIZ_ATTR_NAME,
FORM_EXAM_TEXT_KEY,
exam.name)
.readonly(true))
.addField(FormBuilder.text(
Domain.CLIENT_GROUP.ATTR_NAME,
FORM_NAME_TEXT_KEY,
clientGroup.name)
.mandatory(!isReadonly))
.addField(FormBuilder.colorSelection(
Domain.CLIENT_GROUP.ATTR_COLOR,
FORM_COLOR_TEXT_KEY,
clientGroup.color)
.withEmptyCellSeparation(false))
.addField(FormBuilder.singleSelection(
Domain.CLIENT_GROUP.ATTR_TYPE,
FORM_TYPE_TEXT_KEY,
clientGroup.type.name(),
this.resourceService::clientGroupTypeResources)
.withSelectionListener(form -> updateForm(form, this.i18nSupport))
.mandatory(!isReadonly))
.addField(FormBuilder.text(
TYPE_DESCRIPTION_FIELD_NAME,
FORM_DESC_TEXT_KEY,
typeDescription)
.asArea()
.readonly(true))
.addField(FormBuilder.text(
ClientGroup.ATTR_IP_RANGE_START,
FORM_IP_START_KEY,
clientGroup::getIpRangeStart)
.mandatory(!isReadonly)
.visibleIf(clientGroup.type == ClientGroupType.IP_V4_RANGE))
.addField(FormBuilder.text(
ClientGroup.ATTR_IP_RANGE_END,
FORM_IP_END_KEY,
clientGroup::getIpRangeEnd)
.mandatory(!isReadonly)
.visibleIf(clientGroup.type == ClientGroupType.IP_V4_RANGE))
.addField(FormBuilder.singleSelection(
ClientGroupTemplate.ATTR_CLIENT_OS,
FORM_OS_TYPE_KEY,
clientGroup.clientOS.name(),
this.resourceService::clientClientOSResources)
.visibleIf(clientGroup.type == ClientGroupType.CLIENT_OS)
.mandatory(!isReadonly))
.buildFor((isNew)
? restService.getRestCall(NewClientGroup.class)
: restService.getRestCall(SaveClientGroup.class));
// propagate content actions to action-pane
this.pageService.pageActionBuilder(formContext.clearEntityKeys())
.newAction(ActionDefinition.EXAM_CLIENT_GROUP_SAVE)
.withEntityKey(parentEntityKey)
.withExec(formHandle::processFormSave)
.ignoreMoveAwayFromEdit()
.publishIf(() -> !isReadonly)
.newAction(ActionDefinition.EXAM_CLIENT_GROUP_CANCEL_MODIFY)
.withEntityKey(parentEntityKey)
.withExec(this.pageService.backToCurrentFunction())
.publishIf(() -> !isReadonly);
}
public static void updateForm(final Form form, final I18nSupport i18nSupport) {
final String typeValue = form.getFieldValue(Domain.CLIENT_GROUP.ATTR_TYPE);
if (StringUtils.isNotBlank(typeValue)) {
final String text = 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);
form.setFieldVisible(false, ClientGroupTemplate.ATTR_CLIENT_OS);
if (type == ClientGroupType.IP_V4_RANGE) {
form.setFieldVisible(true, ClientGroup.ATTR_IP_RANGE_START);
form.setFieldVisible(true, ClientGroup.ATTR_IP_RANGE_END);
}
if (type == ClientGroupType.CLIENT_OS) {
form.setFieldVisible(true, ClientGroupTemplate.ATTR_CLIENT_OS);
}
} else {
form.setFieldValue(TYPE_DESCRIPTION_FIELD_NAME, Constants.EMPTY_NOTE);
}
}
}

View file

@ -8,7 +8,6 @@
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;
@ -19,14 +18,13 @@ 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.ClientGroupData.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;
@ -65,6 +63,8 @@ public class ClientGroupTemplateForm implements TemplateComposer {
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 LocTextKey FORM_OS_TYPE_KEY =
new LocTextKey("sebserver.exam.clientgroup.form.ostype");
private static final String CLIENT_GROUP_TYPE_DESC_PREFIX =
"sebserver.exam.clientgroup.type.description.";
@ -166,7 +166,7 @@ public class ClientGroupTemplateForm implements TemplateComposer {
FORM_TYPE_TEXT_KEY,
(clientGroupTemplate.type != null) ? clientGroupTemplate.type.name() : null,
this.resourceService::clientGroupTypeResources)
.withSelectionListener(this::updateForm)
.withSelectionListener(form -> ClientGroupForm.updateForm(form, this.i18nSupport))
.mandatory(!isReadonly))
.addField(FormBuilder.text(
@ -174,13 +174,13 @@ public class ClientGroupTemplateForm implements TemplateComposer {
FORM_DESC_TEXT_KEY,
typeDescription)
.asArea()
//.asHTML(true)
.readonly(true))
.addField(FormBuilder.text(
ClientGroup.ATTR_IP_RANGE_START,
FORM_IP_START_KEY,
clientGroupTemplate::getIpRangeStart)
.mandatory(!isReadonly)
.visibleIf(clientGroupTemplate.type != null
&& clientGroupTemplate.type == ClientGroupType.IP_V4_RANGE))
@ -188,9 +188,19 @@ public class ClientGroupTemplateForm implements TemplateComposer {
ClientGroup.ATTR_IP_RANGE_END,
FORM_IP_END_KEY,
clientGroupTemplate::getIpRangeEnd)
.mandatory(!isReadonly)
.visibleIf(clientGroupTemplate.type != null
&& clientGroupTemplate.type == ClientGroupType.IP_V4_RANGE))
.addField(FormBuilder.singleSelection(
ClientGroupTemplate.ATTR_CLIENT_OS,
FORM_OS_TYPE_KEY,
(clientGroupTemplate.clientOS != null) ? clientGroupTemplate.clientOS.name() : null,
this.resourceService::clientClientOSResources)
.visibleIf(clientGroupTemplate.type != null
&& clientGroupTemplate.type == ClientGroupType.CLIENT_OS)
.mandatory(!isReadonly))
.buildFor((isNew)
? restService.getRestCall(NewClientGroupTemplate.class)
: restService.getRestCall(SaveClientGroupTemplate.class));
@ -211,24 +221,4 @@ public class ClientGroupTemplateForm implements TemplateComposer {
}
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

@ -0,0 +1,172 @@
/*
* 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.BooleanUtils;
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.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.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageContext.AttributeKeys;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.DeleteClientGroup;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.clientgroup.GetClientGroupPage;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.table.EntityTable;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@Lazy
@Component
@GuiProfile
public class ExamClientGroupList implements TemplateComposer {
private final static LocTextKey CLIENT_GROUP_LIST_TITLE_KEY =
new LocTextKey("sebserver.exam.clientgroup.list.title");
private final static LocTextKey CLIENT_GROUP_LIST_TITLE_TOOLTIP_KEY =
new LocTextKey("sebserver.exam.clientgroup.list.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
private final static LocTextKey CLIENT_GROUP_TYPE_COLUMN_KEY =
new LocTextKey("sebserver.exam.clientgroup.list.column.type");
private final static LocTextKey CLIENT_GROUP_NAME_COLUMN_KEY =
new LocTextKey("sebserver.exam.clientgroup.list.column.name");
private final static LocTextKey CLIENT_GROUP_COLOR_COLUMN_KEY =
new LocTextKey("sebserver.exam.clientgroup.list.column.color");
private final static LocTextKey CLIENT_GROUP_DATA_COLUMN_KEY =
new LocTextKey("sebserver.exam.clientgroup.list.column.data");
private final static LocTextKey CLIENT_GROUP_EMPTY_SELECTION_TEXT_KEY =
new LocTextKey("sebserver.exam.clientgroup.list.pleaseSelect");
private static final LocTextKey CLIENT_GROUP_EMPTY_LIST_MESSAGE =
new LocTextKey("sebserver.exam.clientgroup.list.empty");
private final PageService pageService;
private final ResourceService resourceService;
private final WidgetFactory widgetFactory;
private final RestService restService;
public ExamClientGroupList(final PageService pageService) {
this.pageService = pageService;
this.resourceService = pageService.getResourceService();
this.widgetFactory = pageService.getWidgetFactory();
this.restService = pageService.getRestService();
}
@Override
public void compose(final PageContext pageContext) {
final Composite content = pageContext.getParent();
final EntityKey entityKey = pageContext.getEntityKey();
final boolean editable = BooleanUtils.toBoolean(
pageContext.getAttribute(ExamForm.ATTR_EDITABLE));
// List of ClientGroups
this.widgetFactory.addFormSubContextHeader(
content,
CLIENT_GROUP_LIST_TITLE_KEY,
CLIENT_GROUP_LIST_TITLE_TOOLTIP_KEY);
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(pageContext
.clearEntityKeys()
.removeAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA));
final EntityTable<ClientGroup> clientGroupTable =
this.pageService
.entityTableBuilder(this.restService.getRestCall(GetClientGroupPage.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,
ClientGroup::getName)
.widthProportion(2))
.withColumn(new ColumnDefinition<ClientGroup>(
Domain.CLIENT_GROUP.ATTR_TYPE,
CLIENT_GROUP_TYPE_COLUMN_KEY,
cgt -> this.resourceService.clientGroupTypeName(cgt))
.widthProportion(1))
.withColumn(new ColumnDefinition<ClientGroup>(
Domain.CLIENT_GROUP.ATTR_COLOR,
CLIENT_GROUP_COLOR_COLUMN_KEY,
cgt -> this.widgetFactory.getColorValueHTML(cgt))
.asMarkup()
.widthProportion(1))
.withColumn(new ColumnDefinition<ClientGroup>(
Domain.CLIENT_GROUP.ATTR_DATA,
CLIENT_GROUP_DATA_COLUMN_KEY,
cgt -> this.widgetFactory.clientGroupDataToHTML(cgt))
.asMarkup()
.widthProportion(3))
.withDefaultActionIf(
() -> editable,
() -> actionBuilder
.newAction(ActionDefinition.EXAM_CLIENT_GROUP_MODIFY_FROM_LIST)
.withParentEntityKey(entityKey)
.create())
.withSelectionListener(this.pageService.getSelectionPublisher(
pageContext,
ActionDefinition.EXAM_CLIENT_GROUP_MODIFY_FROM_LIST,
ActionDefinition.EXAM_CLIENT_GROUP_DELETE_FROM_LIST))
.compose(pageContext.copyOf(content));
actionBuilder
.newAction(ActionDefinition.EXAM_CLIENT_GROUP_MODIFY_FROM_LIST)
.withParentEntityKey(entityKey)
.withSelect(
clientGroupTable::getMultiSelection,
PageAction::applySingleSelectionAsEntityKey,
CLIENT_GROUP_EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> editable && clientGroupTable.hasAnyContent(), false)
.newAction(ActionDefinition.EXAM_CLIENT_GROUP_DELETE_FROM_LIST)
.withEntityKey(entityKey)
.withSelect(
clientGroupTable::getMultiSelection,
this::deleteSelectedClientGroup,
CLIENT_GROUP_EMPTY_SELECTION_TEXT_KEY)
.publishIf(() -> editable && clientGroupTable.hasAnyContent(), false)
.newAction(ActionDefinition.EXAM_CLIENT_GROUP_NEW)
.withParentEntityKey(entityKey)
.publishIf(() -> editable);
}
private PageAction deleteSelectedClientGroup(final PageAction action) {
final EntityKey clientGroupKey = action.getSingleSelection();
this.resourceService.getRestService()
.getBuilder(DeleteClientGroup.class)
.withURIVariable(API.PARAM_MODEL_ID, clientGroupKey.modelId)
.call();
return action;
}
}

View file

@ -151,7 +151,8 @@ public class ExamForm implements TemplateComposer {
private final RestService restService;
private final ExamDeletePopup examDeletePopup;
private final ExamFormConfigs examFormConfigs;
private final ExamFormIndicators examFormIndicators;
private final ExamIndicatorsList examIndicatorsList;
private final ExamClientGroupList examClientGroupList;
private final ExamCreateClientConfigPopup examCreateClientConfigPopup;
protected ExamForm(
@ -162,7 +163,8 @@ public class ExamForm implements TemplateComposer {
final DownloadService downloadService,
final ExamDeletePopup examDeletePopup,
final ExamFormConfigs examFormConfigs,
final ExamFormIndicators examFormIndicators,
final ExamIndicatorsList examIndicatorsList,
final ExamClientGroupList examClientGroupList,
final ExamCreateClientConfigPopup examCreateClientConfigPopup) {
this.pageService = pageService;
@ -173,7 +175,8 @@ public class ExamForm implements TemplateComposer {
this.restService = this.resourceService.getRestService();
this.examDeletePopup = examDeletePopup;
this.examFormConfigs = examFormConfigs;
this.examFormIndicators = examFormIndicators;
this.examIndicatorsList = examIndicatorsList;
this.examClientGroupList = examClientGroupList;
this.examCreateClientConfigPopup = examCreateClientConfigPopup;
this.consistencyMessageMapping = new HashMap<>();
@ -485,7 +488,15 @@ public class ExamForm implements TemplateComposer {
.withAttribute(ATTR_EXAM_STATUS, examStatus.name()));
// Indicators
this.examFormIndicators.compose(
this.examIndicatorsList.compose(
formContext
.copyOf(content)
.withAttribute(ATTR_READ_GRANT, String.valueOf(entityGrantCheck.r()))
.withAttribute(ATTR_EDITABLE, String.valueOf(editable))
.withAttribute(ATTR_EXAM_STATUS, examStatus.name()));
// Client Groups
this.examClientGroupList.compose(
formContext
.copyOf(content)
.withAttribute(ATTR_READ_GRANT, String.valueOf(entityGrantCheck.r()))

View file

@ -8,8 +8,6 @@
package ch.ethz.seb.sebserver.gui.content.exam;
import java.util.List;
import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.swt.widgets.Composite;
import org.springframework.context.annotation.Lazy;
@ -20,10 +18,7 @@ import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
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.Threshold;
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.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
@ -38,12 +33,13 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.indicator.De
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.indicator.GetIndicatorPage;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.table.EntityTable;
import ch.ethz.seb.sebserver.gui.widget.ThresholdList;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@Lazy
@Component
@GuiProfile
public class ExamFormIndicators implements TemplateComposer {
public class ExamIndicatorsList implements TemplateComposer {
private final static LocTextKey INDICATOR_LIST_TITLE_KEY =
new LocTextKey("sebserver.exam.indicator.list.title");
@ -65,7 +61,7 @@ public class ExamFormIndicators implements TemplateComposer {
private final WidgetFactory widgetFactory;
private final RestService restService;
public ExamFormIndicators(final PageService pageService) {
public ExamIndicatorsList(final PageService pageService) {
this.pageService = pageService;
this.resourceService = pageService.getResourceService();
this.widgetFactory = pageService.getWidgetFactory();
@ -111,7 +107,7 @@ public class ExamFormIndicators implements TemplateComposer {
.withColumn(new ColumnDefinition<Indicator>(
Domain.THRESHOLD.REFERENCE_NAME,
INDICATOR_THRESHOLD_COLUMN_KEY,
i -> thresholdsValue(i.thresholds, i.type))
i -> ThresholdList.thresholdsToHTML(i.thresholds, i.type))
.asMarkup()
.widthProportion(4))
.withDefaultActionIf(
@ -170,34 +166,4 @@ public class ExamFormIndicators implements TemplateComposer {
.getText(ResourceService.EXAM_INDICATOR_TYPE_PREFIX + indicator.type.name());
}
static String thresholdsValue(
final List<Threshold> thresholds,
final IndicatorType indicatorType) {
if (thresholds.isEmpty()) {
return Constants.EMPTY_NOTE;
}
final StringBuilder builder = thresholds
.stream()
.reduce(
new StringBuilder(),
(sb, threshold) -> sb
.append("<span style='padding: 2px 5px 2px 5px; background-color: #")
.append(threshold.color)
.append("; ")
.append((Utils.darkColorContrast(Utils.parseRGB(threshold.color)))
? "color: #4a4a4a; "
: "color: #FFFFFF;")
.append("'>")
.append(Indicator.getDisplayValue(indicatorType, threshold.value))
.append(" (")
.append(threshold.color)
.append(")")
.append("</span>")
.append(" | "),
StringBuilder::append);
builder.delete(builder.length() - 3, builder.length() - 1);
return builder.toString();
}
}

View file

@ -51,6 +51,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.EntityGrantCheck;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.table.EntityTable;
import ch.ethz.seb.sebserver.gui.widget.ThresholdList;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@Lazy
@ -101,6 +102,8 @@ public class ExamTemplateForm implements TemplateComposer {
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_DATA_COLUMN_KEY =
new LocTextKey("sebserver.examtemplate.clientgroup.list.column.data");
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 =
@ -287,7 +290,7 @@ public class ExamTemplateForm implements TemplateComposer {
.withColumn(new ColumnDefinition<IndicatorTemplate>(
Domain.THRESHOLD.REFERENCE_NAME,
INDICATOR_THRESHOLD_COLUMN_KEY,
it -> ExamFormIndicators.thresholdsValue(it.thresholds, it.type))
it -> ThresholdList.thresholdsToHTML(it.thresholds, it.type))
.asMarkup()
.widthProportion(4))
.withDefaultActionIf(
@ -347,15 +350,21 @@ public class ExamTemplateForm implements TemplateComposer {
CLIENT_GROUP_NAME_COLUMN_KEY,
ClientGroupTemplate::getName)
.widthProportion(2))
.withColumn(new ColumnDefinition<>(
.withColumn(new ColumnDefinition<ClientGroupTemplate>(
Domain.CLIENT_GROUP.ATTR_TYPE,
CLIENT_GROUP_TYPE_COLUMN_KEY,
this::clientGroupTypeName)
cgt -> this.resourceService.clientGroupTypeName(cgt))
.widthProportion(1))
.withColumn(new ColumnDefinition<>(
.withColumn(new ColumnDefinition<ClientGroupTemplate>(
Domain.CLIENT_GROUP.ATTR_COLOR,
CLIENT_GROUP_COLOR_COLUMN_KEY,
ClientGroupTemplate::getColor)
cgt -> this.widgetFactory.getColorValueHTML(cgt))
.asMarkup()
.widthProportion(1))
.withColumn(new ColumnDefinition<ClientGroupTemplate>(
Domain.CLIENT_GROUP.ATTR_DATA,
CLIENT_GROUP_DATA_COLUMN_KEY,
cgt -> this.widgetFactory.clientGroupDataToHTML(cgt))
.asMarkup()
.widthProportion(4))
.withDefaultActionIf(
@ -430,18 +439,8 @@ public class ExamTemplateForm implements TemplateComposer {
if (indicator.type == null) {
return Constants.EMPTY_NOTE;
}
return this.resourceService.getI18nSupport()
.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

@ -174,6 +174,15 @@ public final class Form implements FormBinding {
return this;
}
Form removeField(final String name) {
if (this.formFields.containsKey(name)) {
final List<FormFieldAccessor> list = this.formFields.remove(name);
list.forEach(ffa -> ffa.dispose());
}
return this;
}
public String getFieldValue(final String attributeName) {
final FormFieldAccessor fieldAccessor = this.formFields.getFirst(attributeName);
if (fieldAccessor == null) {
@ -464,6 +473,11 @@ public final class Form implements FormBinding {
this(label, control, null, false, errorLabel);
}
public void dispose() {
this.label.dispose();
this.input.dispose();
}
FormFieldAccessor(
final Control label,
final Control input,

View file

@ -36,7 +36,9 @@ 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.ClientGroupData;
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.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType;
@ -125,6 +127,7 @@ public class ResourceService {
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 CLIENT_OS_TYPE_PREFIX = "sebserver.overall.seb.os.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.";
@ -275,6 +278,19 @@ public class ResourceService {
.collect(Collectors.toList());
}
public List<Tuple<String>> clientClientOSResources() {
return Arrays.stream(ClientOS.values())
.filter(type -> type != ClientOS.NONE)
.map(type -> new Tuple3<>(
type.name(),
this.i18nSupport.getText(CLIENT_OS_TYPE_PREFIX + type.name(), type.name()),
Utils.formatLineBreaks(this.i18nSupport.getText(
CLIENT_OS_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())
@ -905,4 +921,13 @@ public class ResourceService {
.collect(Collectors.toList());
}
public String clientGroupTypeName(final ClientGroupData clientGroup) {
if (clientGroup.getType() == null) {
return Constants.EMPTY_NOTE;
}
return this.getI18nSupport()
.getText(ResourceService.EXAM_CLIENT_GROUP_TYPE_PREFIX + clientGroup.getType().name());
}
}

View file

@ -30,6 +30,7 @@ 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.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.widget.Selection.Type;
@ -129,6 +130,37 @@ public final class ThresholdList extends Composite {
return collect;
}
public static String thresholdsToHTML(
final List<Threshold> thresholds,
final IndicatorType indicatorType) {
if (thresholds.isEmpty()) {
return Constants.EMPTY_NOTE;
}
final StringBuilder builder = thresholds
.stream()
.reduce(
new StringBuilder(),
(sb, threshold) -> sb
.append("<span style='padding: 2px 5px 2px 5px; background-color: #")
.append(threshold.color)
.append("; ")
.append((Utils.darkColorContrast(Utils.parseRGB(threshold.color)))
? "color: #4a4a4a; "
: "color: #FFFFFF;")
.append("'>&nbsp;&nbsp;&nbsp;")
.append(Indicator.getDisplayValue(indicatorType, threshold.value))
.append("&nbsp;(#")
.append(threshold.color)
.append(")&nbsp;&nbsp;&nbsp;")
.append("</span>")
.append(" | "),
StringBuilder::append);
builder.delete(builder.length() - 3, builder.length() - 1);
return builder.toString();
}
private void removeInvalidListEntries() {
this.thresholds
.stream()

View file

@ -49,11 +49,14 @@ import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroupData;
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.profile.GuiProfile;
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.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.i18n.PolyglotPageService;
@ -130,7 +133,8 @@ public class WidgetFactory {
INSTITUTION("institution.png"),
LMS_SETUP("lmssetup.png"),
INDICATOR("indicator.png"),
CLIENT_GROUP("indicator.png"),
CLIENT_GROUP("clientgroup.png"),
ADD_CLIENT_GROUP("add_clientgroup.png"),
TEMPLATE("template.png"),
DISABLE("disable.png"),
SEND_QUIT("send-quit.png"),
@ -1029,4 +1033,45 @@ public class WidgetFactory {
executor.execute(builder.toString());
}
public String clientGroupDataToHTML(final ClientGroupData data) {
switch (data.getType()) {
case IP_V4_RANGE: {
final String ipRangeStart = StringUtils.isBlank(data.getIpRangeStart())
? Constants.EMPTY_NOTE
: data.getIpRangeStart();
final String ipRangeEnd = StringUtils.isBlank(data.getIpRangeEnd())
? Constants.EMPTY_NOTE
: data.getIpRangeEnd();
return ipRangeStart + "&nbsp;-&nbsp;" + ipRangeEnd;
}
case CLIENT_OS: {
return this.i18nSupport.getText(ResourceService.CLIENT_OS_TYPE_PREFIX + data.getClientOS().name());
}
default: {
return Constants.EMPTY_NOTE;
}
}
}
public String getColorValueHTML(final ClientGroupData data) {
final String color = data.getColor();
if (StringUtils.isBlank(color)) {
return Constants.EMPTY_NOTE;
}
return new StringBuilder().append("<span style='padding: 2px 5px 2px 5px; background-color: #")
.append(color)
.append("; ")
.append((Utils.darkColorContrast(Utils.parseRGB(color)))
? "color: #4a4a4a; "
: "color: #FFFFFF;")
.append("'>&nbsp;&nbsp;&nbsp;")
.append(" (#")
.append(color)
.append(")&nbsp;&nbsp;&nbsp;")
.append("</span>")
.toString();
}
}

View file

@ -29,7 +29,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.EntityDependency;
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.ClientGroupData.ClientGroupType;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.ClientGroupRecordDynamicSqlSupport;

View file

@ -601,11 +601,11 @@ public class ExamTemplateDAOImpl implements ExamTemplateDAO {
private void checkUniqueClientGroupName(
final ClientGroupTemplate clientGroupTemplate,
final Collection<ClientGroupTemplate> clinetGroups) {
final Collection<ClientGroupTemplate> clietnGroups) {
// check unique name
clinetGroups.stream()
.filter(it -> !Objects.equals(it, clientGroupTemplate)
clietnGroups.stream()
.filter(it -> !Objects.equals(it.id, clientGroupTemplate.id)
&& Objects.equals(it.name, clientGroupTemplate.name))
.findAny()
.ifPresent(it -> {

View file

@ -13,14 +13,16 @@ import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
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.ClientGroupData.ClientGroupType;
import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroupData.ClientOS;
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;
@ -149,14 +151,14 @@ public interface ExamAdminService {
* 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) {
public static <T extends ClientGroupData> T checkClientGroupConsistency(final T 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")));
"clientGroup:type:notNull")));
}
switch (type) {
@ -176,11 +178,13 @@ public interface ExamAdminService {
"clientGroup:type:typeInvalid")));
}
}
return clientGroup;
}
static void checkIPRange(final String ipRangeStart, final String ipRangeEnd) {
final long startIP = Utils.ipToLong(ipRangeStart);
if (startIP < 0) {
if (StringUtils.isBlank(ipRangeStart) || startIP < 0) {
throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError(
Domain.CLIENT_GROUP.TYPE_NAME,
@ -188,7 +192,7 @@ public interface ExamAdminService {
"clientGroup:ipRangeStart:invalidIP")));
}
final long endIP = Utils.ipToLong(ipRangeEnd);
if (endIP < 0) {
if (StringUtils.isBlank(ipRangeEnd) || endIP < 0) {
throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError(
Domain.CLIENT_GROUP.TYPE_NAME,
@ -211,8 +215,14 @@ public interface ExamAdminService {
}
static void checkClientOS(final String clientOS) {
// TODO
static void checkClientOS(final ClientOS clientOS) {
if (clientOS == null) {
throw new APIMessageException(APIMessage.fieldValidationError(
new FieldError(
Domain.CLIENT_GROUP.TYPE_NAME,
ClientGroupData.ATTR_CLIENT_OS,
"clientGroup:clientOS:notNull")));
}
}
}

View file

@ -29,6 +29,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionServic
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientGroupDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService;
@ -88,15 +89,14 @@ public class ClientGroupController extends EntityController<ClientGroup, ClientG
@Override
protected Result<ClientGroup> validForCreate(final ClientGroup entity) {
// TODO check consistency!?
return super.validForCreate(entity);
return super.validForCreate(entity)
.map(ExamAdminService::checkClientGroupConsistency);
}
@Override
protected Result<ClientGroup> validForSave(final ClientGroup entity) {
// TODO check consistency!?
return super.validForSave(entity);
return super.validForSave(entity)
.map(ExamAdminService::checkClientGroupConsistency);
}
@Override

View file

@ -118,57 +118,6 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
return ExamRecordDynamicSqlSupport.examRecord;
}
// @RequestMapping(
// method = RequestMethod.GET,
// consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
// produces = MediaType.APPLICATION_JSON_VALUE)
// @Override
// public Page<Exam> getPage(
// @RequestParam(
// name = API.PARAM_INSTITUTION_ID,
// required = true,
// defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
// @RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber,
// @RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize,
// @RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
// @RequestParam final MultiValueMap<String, String> allRequestParams,
// final HttpServletRequest request) {
//
// checkReadPrivilege(institutionId);
// this.authorization.check(
// PrivilegeType.READ,
// EntityType.EXAM,
// institutionId);
//
// if (StringUtils.isBlank(sort) ||
// (this.paginationService.isNativeSortingSupported(ExamRecordDynamicSqlSupport.examRecord, sort))) {
//
// System.out.println("*********************** sort, filter on DB");
//
// return super.getPage(institutionId, pageNumber, pageSize, sort, allRequestParams, request);
//
// } else {
//
// System.out.println("*********************** sort, filter on List");
//
// return super.getPage(institutionId, pageNumber, pageSize, sort, allRequestParams, request);
//
//// final Collection<Exam> exams = this.examDAO
//// .allMatching(new FilterMap(
//// allRequestParams,
//// request.getQueryString()),
//// this::hasReadAccess)
//// .getOrThrow();
////
//// return this.paginationService.buildPageFromList(
//// pageNumber,
//// pageSize,
//// sort,
//// exams,
//// pageSort(sort));
// }
// }
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_CHECK_IMPORTED_PATH_SEGMENT,

View file

@ -337,7 +337,7 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
null,
postMap.getLong(ClientGroupTemplate.ATTR_EXAM_TEMPLATE_ID),
postMap))
.map(this::checkClientGroupConsistency)
.map(ExamAdminService::checkClientGroupConsistency)
.flatMap(this.examTemplateDAO::createNewClientGroupTemplate)
.flatMap(this.userActivityLogDAO::logCreate)
.getOrThrow();
@ -359,7 +359,7 @@ public class ExamTemplateController extends EntityController<ExamTemplate, ExamT
this.checkModifyPrivilege(institutionId);
return this.beanValidationService
.validateBean(modifyData)
.map(this::checkClientGroupConsistency)
.map(ExamAdminService::checkClientGroupConsistency)
.flatMap(this.examTemplateDAO::saveClientGroupTemplate)
.flatMap(this.userActivityLogDAO::logModify)
.getOrThrow();
@ -494,11 +494,6 @@ 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

@ -80,6 +80,11 @@ sebserver.overall.activity.title.monitoring=Monitoring
sebserver.overall.batchaction.selected=Selected
sebserver.overall.seb.os.type.NONE=Undefined
sebserver.overall.seb.os.type.WINDOWS=Windows
sebserver.overall.seb.os.type.MAC_OS=MacOS
sebserver.overall.seb.os.type.I_OS=iOS
################################
# Form validation and messages
################################
@ -595,7 +600,7 @@ sebserver.exam.status.ARCHIVED=Archived
sebserver.exam.status.CORRUPT_NO_LMS_CONNECTION=Corrupt (No LMS Connection)
sebserver.exam.status.CORRUPT_INVALID_ID=Corrupt (Invalid Identifier)
sebserver.exam.configuration.list.actions=
sebserver.exam.configuration.list.actions=&nbsp;
sebserver.exam.configuration.list.title=Exam Configuration
sebserver.exam.configuration.list.title.tooltip=A list of all attached exam configuration for this exam
sebserver.exam.configuration.list.column.name=Name
@ -628,7 +633,7 @@ sebserver.exam.configuration.form.status.tooltip=The current status of the selec
sebserver.exam.configuration.form.encryptSecret.confirm=Confirm Password
sebserver.exam.configuration.form.encryptSecret.confirm.tooltip=Please confirm the encryption password if there is one
sebserver.exam.indicator.list.actions=
sebserver.exam.indicator.list.actions=&nbsp;
sebserver.exam.indicator.list.title=Indicators
sebserver.exam.indicator.list.title.tooltip=A list of indicators that are shown on the exam monitoring view
sebserver.exam.indicator.list.column.type=Type
@ -640,6 +645,20 @@ sebserver.exam.indicator.list.column.thresholds.tooltip=The thresholds of the in
sebserver.exam.indicator.list.empty=There is currently no indicator defined for this exam. Please create a new one
sebserver.exam.indicator.list.pleaseSelect=At first please select an indicator from the list
sebserver.exam.clientgroup.list.actions=&nbsp;
sebserver.exam.clientgroup.list.title=Client Groups
sebserver.exam.clientgroup.list.title.tooltip=A list of client groups that are shown on the exam monitoring view for the client connections that matches the specific group(s)
sebserver.exam.clientgroup.list.column.type=Type
sebserver.exam.clientgroup.list.column.type.tooltip=The type of the client group
sebserver.exam.clientgroup.list.column.name=Name
sebserver.exam.clientgroup.list.column.name.tooltip=The name of the client group
sebserver.exam.clientgroup.list.column.color=Color
sebserver.exam.clientgroup.list.column.color.tooltip=The color of the client group
sebserver.exam.clientgroup.list.column.data=Data
sebserver.exam.clientgroup.list.column.data.tooltip=The data of the client group that is specific to the type of the client group
sebserver.exam.clientgroup.list.empty=There is currently no client group defined for this exam.
sebserver.exam.clientgroup.list.pleaseSelect=At first please select a client group from the list
sebserver.exam.indicator.type.LAST_PING=Last Ping Time
sebserver.exam.indicator.type.ERROR_COUNT=Error-Log Counter
sebserver.exam.indicator.type.WARN_COUNT=Warning-Log Counter
@ -655,6 +674,7 @@ sebserver.exam.indicator.type.description.WLAN_STATUS=This indicator shows the p
sebserver.exam.clientgroup.type.IP_V4_RANGE=IP v4 Range
sebserver.exam.clientgroup.type.CLIENT_OS=SEB Client OS
sebserver.exam.clientgroup.type.description.NONE=No Client Group type is selected.<br/>Please select one.
sebserver.exam.clientgroup.type.description.IP_V4_RANGE=IP v4 Range TODO
sebserver.exam.clientgroup.type.description.CLIENT_OS=SEB Client OS TODO
@ -665,6 +685,11 @@ sebserver.exam.indicator.action.list.modify=Edit Selected Indicator
sebserver.exam.indicator.action.list.delete=Delete Selected Indicator
sebserver.exam.indicator.action.save=Save
sebserver.exam.clientgroup.action.list.new=Add Client Group
sebserver.exam.clientgroup.action.list.modify=Edit Selected Client Group
sebserver.exam.clientgroup.action.list.delete=Delete Selected Client Group
sebserver.exam.clientgroup.action.save=Save
sebserver.exam.indicator.form.title=Indicator
sebserver.exam.indicator.form.title.subtitle=
sebserver.exam.indicator.form.title.new=Add Indicator
@ -703,6 +728,8 @@ 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.clientgroup.form.ostype=Client OS Type
sebserver.exam.clientgroup.form.ostype.tooltip=The operating system type of the machine where SEB is running on
sebserver.exam.indicator.thresholds.list.title=Thresholds
@ -1797,6 +1824,8 @@ 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.column.data=Data
sebserver.examtemplate.clientgroup.list.column.data.tooltip=The data of the client group that is specific to the type 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

View file

@ -66,7 +66,7 @@ import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport.ErrorEntry;
import ch.ethz.seb.sebserver.gbl.model.Page;
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.ClientGroup.ClientGroupType;
import ch.ethz.seb.sebserver.gbl.model.exam.ClientGroupData.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;