SEBSERV-139 gui implementations
This commit is contained in:
parent
6f9daddf53
commit
ec1630fa2f
18 changed files with 911 additions and 397 deletions
|
@ -9,6 +9,7 @@
|
|||
package ch.ethz.seb.sebserver.gbl.model.exam;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
@ -19,7 +20,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain;
|
|||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ExamProctoring implements Entity {
|
||||
public class ProctoringSettings implements Entity {
|
||||
|
||||
public enum ServerType {
|
||||
JITSI_MEET
|
||||
|
@ -33,19 +34,25 @@ public class ExamProctoring implements Entity {
|
|||
|
||||
@JsonProperty(Domain.EXAM.ATTR_ID)
|
||||
public final Long examId;
|
||||
|
||||
@JsonProperty(ATTR_ENABLE_PROCTORING)
|
||||
public final Boolean enableProctoring;
|
||||
|
||||
@JsonProperty(ATTR_SERVER_TYPE)
|
||||
public final ServerType serverType;
|
||||
|
||||
@JsonProperty(ATTR_SERVER_URL)
|
||||
@URL(message = "examProctoring:serverURL:invalidURL")
|
||||
public final String serverURL;
|
||||
|
||||
@JsonProperty(ATTR_APP_KEY)
|
||||
public final String appKey;
|
||||
|
||||
@JsonProperty(ATTR_APP_SECRET)
|
||||
public final CharSequence appSecret;
|
||||
|
||||
@JsonCreator
|
||||
public ExamProctoring(
|
||||
public ProctoringSettings(
|
||||
@JsonProperty(Domain.EXAM.ATTR_ID) final Long examId,
|
||||
@JsonProperty(ATTR_ENABLE_PROCTORING) final Boolean enableProctoring,
|
||||
@JsonProperty(ATTR_SERVER_TYPE) final ServerType serverType,
|
||||
|
@ -117,7 +124,7 @@ public class ExamProctoring implements Entity {
|
|||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
final ExamProctoring other = (ExamProctoring) obj;
|
||||
final ProctoringSettings other = (ProctoringSettings) obj;
|
||||
if (this.examId == null) {
|
||||
if (other.examId != null)
|
||||
return false;
|
|
@ -8,27 +8,20 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gui.content;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.rap.rwt.client.service.UrlLauncher;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
@ -40,15 +33,12 @@ import ch.ethz.seb.sebserver.gbl.model.Domain;
|
|||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
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.ExamConfigurationMap;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult.ErrorType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
|
@ -63,15 +53,10 @@ import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
|||
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.download.DownloadService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.download.SEBExamConfigDownload;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckExamConsistency;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckSEBRestriction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteExamConfigMapping;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteIndicator;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingsPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicatorPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetup;
|
||||
|
@ -79,8 +64,6 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizData;
|
|||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.ImportAsExam;
|
||||
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.WidgetFactory;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||
|
||||
|
@ -91,14 +74,15 @@ public class ExamForm implements TemplateComposer {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(ExamForm.class);
|
||||
|
||||
protected static final String ATTR_READ_GRANT = "ATTR_READ_GRANT";
|
||||
protected static final String ATTR_MODIFY_GRANT = "ATTR_MODIFY_GRANT";
|
||||
protected static final String ATTR_EXAM_STATUS = "ATTR_EXAM_STATUS";
|
||||
|
||||
public static final LocTextKey EXAM_FORM_TITLE_KEY =
|
||||
new LocTextKey("sebserver.exam.form.title");
|
||||
public static final LocTextKey EXAM_FORM_TITLE_IMPORT_KEY =
|
||||
new LocTextKey("sebserver.exam.form.title.import");
|
||||
private static final LocTextKey CONFIG_EMPTY_LIST_MESSAGE =
|
||||
new LocTextKey("sebserver.exam.configuration.list.empty");
|
||||
private static final LocTextKey INDICATOR_EMPTY_LIST_MESSAGE =
|
||||
new LocTextKey("sebserver.exam.indicator.list.empty");
|
||||
|
||||
private static final LocTextKey FORM_SUPPORTER_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.form.supporter");
|
||||
private static final LocTextKey FORM_STATUS_TEXT_KEY =
|
||||
|
@ -120,32 +104,6 @@ public class ExamForm implements TemplateComposer {
|
|||
private static final LocTextKey FORM_LMSSETUP_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.form.lmssetup");
|
||||
|
||||
private final static LocTextKey CONFIG_LIST_TITLE_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.list.title");
|
||||
private final static LocTextKey CONFIG_LIST_TITLE_TOOLTIP_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.list.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
|
||||
private final static LocTextKey CONFIG_NAME_COLUMN_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.list.column.name");
|
||||
private final static LocTextKey CONFIG_DESCRIPTION_COLUMN_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.list.column.description");
|
||||
private final static LocTextKey CONFIG_STATUS_COLUMN_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.list.column.status");
|
||||
private final static LocTextKey CONFIG_EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.list.pleaseSelect");
|
||||
|
||||
private final static LocTextKey INDICATOR_LIST_TITLE_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.list.title");
|
||||
private final static LocTextKey INDICATOR_LIST_TITLE_TOOLTIP_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.list.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
|
||||
private final static LocTextKey INDICATOR_TYPE_COLUMN_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.list.column.type");
|
||||
private final static LocTextKey INDICATOR_NAME_COLUMN_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.list.column.name");
|
||||
private final static LocTextKey INDICATOR_THRESHOLD_COLUMN_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.list.column.thresholds");
|
||||
private final static LocTextKey INDICATOR_EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.list.pleaseSelect");
|
||||
|
||||
private final static LocTextKey CONSISTENCY_MESSAGE_TITLE =
|
||||
new LocTextKey("sebserver.exam.consistency.title");
|
||||
private final static LocTextKey CONSISTENCY_MESSAGE_MISSING_SUPPORTER =
|
||||
|
@ -158,19 +116,14 @@ public class ExamForm implements TemplateComposer {
|
|||
new LocTextKey("sebserver.exam.consistency.missing-seb-restriction");
|
||||
|
||||
private final Map<String, LocTextKey> consistencyMessageMapping;
|
||||
|
||||
private final static LocTextKey CONFIRM_MESSAGE_REMOVE_CONFIG =
|
||||
new LocTextKey("sebserver.exam.confirm.remove-config");
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final ExamSEBRestrictionSettings examSEBRestrictionSettings;
|
||||
private final ExamToConfigBindingForm examToConfigBindingForm;
|
||||
private final DownloadService downloadService;
|
||||
private final String downloadFileName;
|
||||
private final WidgetFactory widgetFactory;
|
||||
private final RestService restService;
|
||||
private final ExamDeletePopup examDeletePopup;
|
||||
private final ExamFormConfigs examFormConfigs;
|
||||
private final ExamFormIndicators examFormIndicators;
|
||||
|
||||
protected ExamForm(
|
||||
final PageService pageService,
|
||||
|
@ -178,17 +131,17 @@ public class ExamForm implements TemplateComposer {
|
|||
final ExamToConfigBindingForm examToConfigBindingForm,
|
||||
final DownloadService downloadService,
|
||||
final ExamDeletePopup examDeletePopup,
|
||||
@Value("${sebserver.gui.seb.exam.config.download.filename}") final String downloadFileName) {
|
||||
final ExamFormConfigs examFormConfigs,
|
||||
final ExamFormIndicators examFormIndicators) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = pageService.getResourceService();
|
||||
this.examSEBRestrictionSettings = examSEBRestrictionSettings;
|
||||
this.examToConfigBindingForm = examToConfigBindingForm;
|
||||
this.downloadService = downloadService;
|
||||
this.downloadFileName = downloadFileName;
|
||||
this.widgetFactory = pageService.getWidgetFactory();
|
||||
this.restService = this.resourceService.getRestService();
|
||||
this.examDeletePopup = examDeletePopup;
|
||||
this.examFormConfigs = examFormConfigs;
|
||||
this.examFormIndicators = examFormIndicators;
|
||||
|
||||
this.consistencyMessageMapping = new HashMap<>();
|
||||
this.consistencyMessageMapping.put(
|
||||
|
@ -255,7 +208,6 @@ public class ExamForm implements TemplateComposer {
|
|||
final boolean modifyGrant = userGrantCheck.m();
|
||||
final boolean writeGrant = userGrantCheck.w();
|
||||
final ExamStatus examStatus = exam.getStatus();
|
||||
final boolean isExamRunning = examStatus == ExamStatus.RUNNING;
|
||||
final boolean editable = examStatus == ExamStatus.UP_COMING
|
||||
|| examStatus == ExamStatus.RUNNING
|
||||
&& currentUser.get().hasRole(UserRole.EXAM_ADMIN);
|
||||
|
@ -439,165 +391,21 @@ public class ExamForm implements TemplateComposer {
|
|||
|
||||
// additional data in read-only view
|
||||
if (readonly && !importFromQuizData) {
|
||||
// Configurations
|
||||
this.examFormConfigs.compose(
|
||||
formContext
|
||||
.copyOf(content)
|
||||
.withAttribute(ATTR_READ_GRANT, String.valueOf(userGrantCheck.r()))
|
||||
.withAttribute(ATTR_MODIFY_GRANT, String.valueOf(modifyGrant))
|
||||
.withAttribute(ATTR_EXAM_STATUS, examStatus.name()));
|
||||
|
||||
// List of SEB Configuration
|
||||
this.widgetFactory.addFormSubContextHeader(
|
||||
content,
|
||||
CONFIG_LIST_TITLE_KEY,
|
||||
CONFIG_LIST_TITLE_TOOLTIP_KEY);
|
||||
|
||||
final EntityTable<ExamConfigurationMap> configurationTable =
|
||||
this.pageService.entityTableBuilder(this.restService.getRestCall(GetExamConfigMappingsPage.class))
|
||||
.withRestCallAdapter(builder -> builder.withQueryParam(
|
||||
ExamConfigurationMap.FILTER_ATTR_EXAM_ID,
|
||||
entityKey.modelId))
|
||||
.withEmptyMessage(CONFIG_EMPTY_LIST_MESSAGE)
|
||||
.withPaging(1)
|
||||
.hideNavigation()
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CONFIGURATION_NODE.ATTR_NAME,
|
||||
CONFIG_NAME_COLUMN_KEY,
|
||||
ExamConfigurationMap::getConfigName)
|
||||
.widthProportion(2))
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
|
||||
CONFIG_DESCRIPTION_COLUMN_KEY,
|
||||
ExamConfigurationMap::getConfigDescription)
|
||||
.widthProportion(4))
|
||||
.withColumn(new ColumnDefinition<ExamConfigurationMap>(
|
||||
Domain.CONFIGURATION_NODE.ATTR_STATUS,
|
||||
CONFIG_STATUS_COLUMN_KEY,
|
||||
this.resourceService::localizedExamConfigStatusName)
|
||||
.widthProportion(1))
|
||||
.withDefaultActionIf(
|
||||
() -> modifyGrant,
|
||||
this::viewExamConfigPageAction)
|
||||
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
pageContext,
|
||||
ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP,
|
||||
ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST,
|
||||
ActionDefinition.EXAM_CONFIGURATION_EXPORT,
|
||||
ActionDefinition.EXAM_CONFIGURATION_GET_CONFIG_KEY))
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
final EntityKey configMapKey = (configurationTable.hasAnyContent())
|
||||
? new EntityKey(
|
||||
configurationTable.getFirstRowData().configurationNodeId,
|
||||
EntityType.CONFIGURATION_NODE)
|
||||
: null;
|
||||
|
||||
actionBuilder
|
||||
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_NEW)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withExec(this.examToConfigBindingForm.bindFunction())
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> modifyGrant && editable && !configurationTable.hasAnyContent())
|
||||
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withEntityKey(configMapKey)
|
||||
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent(), false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST)
|
||||
.withEntityKey(entityKey)
|
||||
.withSelect(
|
||||
getConfigMappingSelection(configurationTable),
|
||||
this::deleteExamConfigMapping,
|
||||
CONFIG_EMPTY_SELECTION_TEXT_KEY)
|
||||
.withConfirm(() -> {
|
||||
if (isExamRunning) {
|
||||
return CONFIRM_MESSAGE_REMOVE_CONFIG;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent() && editable, false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXPORT)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withSelect(
|
||||
getConfigSelection(configurationTable),
|
||||
this::downloadExamConfigAction,
|
||||
CONFIG_EMPTY_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> userGrantCheck.r() && configurationTable.hasAnyContent(), false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_GET_CONFIG_KEY)
|
||||
.withSelect(
|
||||
getConfigSelection(configurationTable),
|
||||
this::getExamConfigKey,
|
||||
CONFIG_EMPTY_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> userGrantCheck.r() && configurationTable.hasAnyContent(), false);
|
||||
|
||||
// List of Indicators
|
||||
this.widgetFactory.addFormSubContextHeader(
|
||||
content,
|
||||
INDICATOR_LIST_TITLE_KEY,
|
||||
INDICATOR_LIST_TITLE_TOOLTIP_KEY);
|
||||
|
||||
final EntityTable<Indicator> indicatorTable =
|
||||
this.pageService.entityTableBuilder(this.restService.getRestCall(GetIndicatorPage.class))
|
||||
.withRestCallAdapter(builder -> builder.withQueryParam(
|
||||
Indicator.FILTER_ATTR_EXAM_ID,
|
||||
entityKey.modelId))
|
||||
.withEmptyMessage(INDICATOR_EMPTY_LIST_MESSAGE)
|
||||
.withMarkup()
|
||||
.withPaging(5)
|
||||
.hideNavigation()
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.INDICATOR.ATTR_NAME,
|
||||
INDICATOR_NAME_COLUMN_KEY,
|
||||
Indicator::getName)
|
||||
.widthProportion(2))
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.INDICATOR.ATTR_TYPE,
|
||||
INDICATOR_TYPE_COLUMN_KEY,
|
||||
this::indicatorTypeName)
|
||||
.widthProportion(1))
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.THRESHOLD.REFERENCE_NAME,
|
||||
INDICATOR_THRESHOLD_COLUMN_KEY,
|
||||
ExamForm::thresholdsValue)
|
||||
.asMarkup()
|
||||
.widthProportion(4))
|
||||
.withDefaultActionIf(
|
||||
() -> modifyGrant,
|
||||
() -> actionBuilder
|
||||
.newAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST)
|
||||
.withParentEntityKey(entityKey)
|
||||
.create())
|
||||
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
pageContext,
|
||||
ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST,
|
||||
ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST))
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
actionBuilder
|
||||
|
||||
.newAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withSelect(
|
||||
indicatorTable::getSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent(), false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST)
|
||||
.withEntityKey(entityKey)
|
||||
.withSelect(
|
||||
indicatorTable::getSelection,
|
||||
this::deleteSelectedIndicator,
|
||||
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent(), false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_INDICATOR_NEW)
|
||||
.withParentEntityKey(entityKey)
|
||||
.publishIf(() -> modifyGrant);
|
||||
// Indicators
|
||||
this.examFormIndicators.compose(
|
||||
formContext
|
||||
.copyOf(content)
|
||||
.withAttribute(ATTR_READ_GRANT, String.valueOf(userGrantCheck.r()))
|
||||
.withAttribute(ATTR_MODIFY_GRANT, String.valueOf(modifyGrant))
|
||||
.withAttribute(ATTR_EXAM_STATUS, examStatus.name()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -656,95 +464,6 @@ public class ExamForm implements TemplateComposer {
|
|||
message));
|
||||
}
|
||||
|
||||
private PageAction viewExamConfigPageAction(final EntityTable<ExamConfigurationMap> table) {
|
||||
|
||||
return this.pageService.pageActionBuilder(table.getPageContext()
|
||||
.clearEntityKeys()
|
||||
.removeAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA))
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP)
|
||||
.withSelectionSupplier(() -> {
|
||||
final ExamConfigurationMap selectedROWData = table.getSingleSelectedROWData();
|
||||
final HashSet<EntityKey> result = new HashSet<>();
|
||||
if (selectedROWData != null) {
|
||||
result.add(new EntityKey(
|
||||
selectedROWData.configurationNodeId,
|
||||
EntityType.CONFIGURATION_NODE));
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.create();
|
||||
}
|
||||
|
||||
private PageAction downloadExamConfigAction(final PageAction action) {
|
||||
final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class);
|
||||
final EntityKey selection = action.getSingleSelection();
|
||||
if (selection != null) {
|
||||
final String downloadURL = this.downloadService.createDownloadURL(
|
||||
selection.modelId,
|
||||
action.pageContext().getParentEntityKey().modelId,
|
||||
SEBExamConfigDownload.class,
|
||||
this.downloadFileName);
|
||||
urlLauncher.openURL(downloadURL);
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
private Supplier<Set<EntityKey>> getConfigMappingSelection(
|
||||
final EntityTable<ExamConfigurationMap> configurationTable) {
|
||||
return () -> {
|
||||
final ExamConfigurationMap firstRowData = configurationTable.getFirstRowData();
|
||||
if (firstRowData == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
return new HashSet<>(Arrays.asList(firstRowData.getEntityKey()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Supplier<Set<EntityKey>> getConfigSelection(final EntityTable<ExamConfigurationMap> configurationTable) {
|
||||
return () -> {
|
||||
final ExamConfigurationMap firstRowData = configurationTable.getFirstRowData();
|
||||
if (firstRowData == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
return new HashSet<>(Arrays.asList(new EntityKey(
|
||||
firstRowData.configurationNodeId,
|
||||
EntityType.CONFIGURATION_NODE)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private PageAction deleteSelectedIndicator(final PageAction action) {
|
||||
final EntityKey indicatorKey = action.getSingleSelection();
|
||||
this.resourceService.getRestService()
|
||||
.getBuilder(DeleteIndicator.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, indicatorKey.modelId)
|
||||
.call();
|
||||
return action;
|
||||
}
|
||||
|
||||
private PageAction getExamConfigKey(final PageAction action) {
|
||||
final EntityKey examConfigMappingKey = action.getSingleSelection();
|
||||
if (examConfigMappingKey != null) {
|
||||
action.withEntityKey(examConfigMappingKey);
|
||||
return SEBExamConfigForm
|
||||
.getConfigKeyFunction(this.pageService)
|
||||
.apply(action);
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private PageAction deleteExamConfigMapping(final PageAction action) {
|
||||
final EntityKey examConfigMappingKey = action.getSingleSelection();
|
||||
this.resourceService.getRestService()
|
||||
.getBuilder(DeleteExamConfigMapping.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, examConfigMappingKey.modelId)
|
||||
.call()
|
||||
.onError(error -> action.pageContext().notifyRemoveError(EntityType.EXAM_CONFIGURATION_MAP, error));
|
||||
return action;
|
||||
}
|
||||
|
||||
private Result<Exam> getExistingExam(final PageContext pageContext) {
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
return this.restService.getBuilder(GetExam.class)
|
||||
|
@ -763,43 +482,6 @@ public class ExamForm implements TemplateComposer {
|
|||
.onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error));
|
||||
}
|
||||
|
||||
private String indicatorTypeName(final Indicator indicator) {
|
||||
if (indicator.type == null) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
|
||||
return this.resourceService.getI18nSupport()
|
||||
.getText(ResourceService.EXAM_INDICATOR_TYPE_PREFIX + indicator.type.name());
|
||||
}
|
||||
|
||||
private static String thresholdsValue(final Indicator indicator) {
|
||||
if (indicator.thresholds.isEmpty()) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
|
||||
final StringBuilder builder = indicator.thresholds
|
||||
.stream()
|
||||
.reduce(
|
||||
new StringBuilder(),
|
||||
(sb, threshold) -> sb
|
||||
.append("<span style='padding: 2px 5px 2px 5px; background-color: #")
|
||||
.append(threshold.color)
|
||||
.append("; ")
|
||||
.append((Utils.darkColor(Utils.parseRGB(threshold.color)))
|
||||
? "color: #4a4a4a; "
|
||||
: "color: #FFFFFF;")
|
||||
.append("'>")
|
||||
.append(Indicator.getDisplayValue(indicator.type, threshold.value))
|
||||
.append(" (")
|
||||
.append(threshold.color)
|
||||
.append(")")
|
||||
.append("</span>")
|
||||
.append(" | "),
|
||||
StringBuilder::append);
|
||||
builder.delete(builder.length() - 3, builder.length() - 1);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private Function<PageAction, PageAction> cancelModifyFunction() {
|
||||
final Function<PageAction, PageAction> backToCurrentFunction = this.pageService.backToCurrentFunction();
|
||||
return action -> {
|
||||
|
|
|
@ -0,0 +1,294 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gui.content;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.rap.rwt.client.service.UrlLauncher;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.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.Exam.ExamStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.service.ResourceService;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.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.download.DownloadService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.download.SEBExamConfigDownload;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteExamConfigMapping;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamConfigMappingsPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
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 ExamFormConfigs implements TemplateComposer {
|
||||
|
||||
private final static LocTextKey CONFIG_LIST_TITLE_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.list.title");
|
||||
private final static LocTextKey CONFIG_LIST_TITLE_TOOLTIP_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.list.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
|
||||
private final static LocTextKey CONFIG_NAME_COLUMN_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.list.column.name");
|
||||
private final static LocTextKey CONFIG_DESCRIPTION_COLUMN_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.list.column.description");
|
||||
private final static LocTextKey CONFIG_STATUS_COLUMN_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.list.column.status");
|
||||
private final static LocTextKey CONFIG_EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.list.pleaseSelect");
|
||||
private static final LocTextKey CONFIG_EMPTY_LIST_MESSAGE =
|
||||
new LocTextKey("sebserver.exam.configuration.list.empty");
|
||||
private final static LocTextKey CONFIRM_MESSAGE_REMOVE_CONFIG =
|
||||
new LocTextKey("sebserver.exam.confirm.remove-config");
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final ExamToConfigBindingForm examToConfigBindingForm;
|
||||
private final DownloadService downloadService;
|
||||
private final String downloadFileName;
|
||||
private final WidgetFactory widgetFactory;
|
||||
private final RestService restService;
|
||||
|
||||
protected ExamFormConfigs(
|
||||
final PageService pageService,
|
||||
final ExamSEBRestrictionSettings examSEBRestrictionSettings,
|
||||
final ExamToConfigBindingForm examToConfigBindingForm,
|
||||
final DownloadService downloadService,
|
||||
@Value("${sebserver.gui.seb.exam.config.download.filename}") final String downloadFileName) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = pageService.getResourceService();
|
||||
this.examToConfigBindingForm = examToConfigBindingForm;
|
||||
this.downloadService = downloadService;
|
||||
this.downloadFileName = downloadFileName;
|
||||
this.widgetFactory = pageService.getWidgetFactory();
|
||||
this.restService = pageService.getRestService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final Composite content = pageContext.getParent();
|
||||
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final boolean modifyGrant = BooleanUtils.toBoolean(
|
||||
pageContext.getAttribute(ExamForm.ATTR_MODIFY_GRANT));
|
||||
final boolean readGrant = BooleanUtils.toBoolean(
|
||||
pageContext.getAttribute(ExamForm.ATTR_READ_GRANT));
|
||||
final ExamStatus examStatus = ExamStatus.valueOf(
|
||||
pageContext.getAttribute(ExamForm.ATTR_EXAM_STATUS));
|
||||
final boolean isExamRunning = examStatus == ExamStatus.RUNNING;
|
||||
final boolean editable = examStatus == ExamStatus.UP_COMING
|
||||
|| examStatus == ExamStatus.RUNNING
|
||||
&& currentUser.get().hasRole(UserRole.EXAM_ADMIN);
|
||||
|
||||
// List of SEB Configuration
|
||||
this.widgetFactory.addFormSubContextHeader(
|
||||
content,
|
||||
CONFIG_LIST_TITLE_KEY,
|
||||
CONFIG_LIST_TITLE_TOOLTIP_KEY);
|
||||
|
||||
final EntityTable<ExamConfigurationMap> configurationTable =
|
||||
this.pageService.entityTableBuilder(this.restService.getRestCall(GetExamConfigMappingsPage.class))
|
||||
.withRestCallAdapter(builder -> builder.withQueryParam(
|
||||
ExamConfigurationMap.FILTER_ATTR_EXAM_ID,
|
||||
entityKey.modelId))
|
||||
.withEmptyMessage(CONFIG_EMPTY_LIST_MESSAGE)
|
||||
.withPaging(1)
|
||||
.hideNavigation()
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CONFIGURATION_NODE.ATTR_NAME,
|
||||
CONFIG_NAME_COLUMN_KEY,
|
||||
ExamConfigurationMap::getConfigName)
|
||||
.widthProportion(2))
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION,
|
||||
CONFIG_DESCRIPTION_COLUMN_KEY,
|
||||
ExamConfigurationMap::getConfigDescription)
|
||||
.widthProportion(4))
|
||||
.withColumn(new ColumnDefinition<ExamConfigurationMap>(
|
||||
Domain.CONFIGURATION_NODE.ATTR_STATUS,
|
||||
CONFIG_STATUS_COLUMN_KEY,
|
||||
this.resourceService::localizedExamConfigStatusName)
|
||||
.widthProportion(1))
|
||||
.withDefaultActionIf(
|
||||
() -> modifyGrant,
|
||||
this::viewExamConfigPageAction)
|
||||
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
pageContext,
|
||||
ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP,
|
||||
ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST,
|
||||
ActionDefinition.EXAM_CONFIGURATION_EXPORT,
|
||||
ActionDefinition.EXAM_CONFIGURATION_GET_CONFIG_KEY))
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
final EntityKey configMapKey = (configurationTable.hasAnyContent())
|
||||
? new EntityKey(
|
||||
configurationTable.getFirstRowData().configurationNodeId,
|
||||
EntityType.CONFIGURATION_NODE)
|
||||
: null;
|
||||
|
||||
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(pageContext
|
||||
.clearEntityKeys()
|
||||
.removeAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA));
|
||||
|
||||
actionBuilder
|
||||
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_NEW)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withExec(this.examToConfigBindingForm.bindFunction())
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> modifyGrant && editable && !configurationTable.hasAnyContent())
|
||||
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withEntityKey(configMapKey)
|
||||
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent(), false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST)
|
||||
.withEntityKey(entityKey)
|
||||
.withSelect(
|
||||
getConfigMappingSelection(configurationTable),
|
||||
this::deleteExamConfigMapping,
|
||||
CONFIG_EMPTY_SELECTION_TEXT_KEY)
|
||||
.withConfirm(() -> {
|
||||
if (isExamRunning) {
|
||||
return CONFIRM_MESSAGE_REMOVE_CONFIG;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent() && editable, false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXPORT)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withSelect(
|
||||
getConfigSelection(configurationTable),
|
||||
this::downloadExamConfigAction,
|
||||
CONFIG_EMPTY_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> readGrant && configurationTable.hasAnyContent(), false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_GET_CONFIG_KEY)
|
||||
.withSelect(
|
||||
getConfigSelection(configurationTable),
|
||||
this::getExamConfigKey,
|
||||
CONFIG_EMPTY_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> readGrant && configurationTable.hasAnyContent(), false);
|
||||
|
||||
}
|
||||
|
||||
private PageAction downloadExamConfigAction(final PageAction action) {
|
||||
final UrlLauncher urlLauncher = RWT.getClient().getService(UrlLauncher.class);
|
||||
final EntityKey selection = action.getSingleSelection();
|
||||
if (selection != null) {
|
||||
final String downloadURL = this.downloadService.createDownloadURL(
|
||||
selection.modelId,
|
||||
action.pageContext().getParentEntityKey().modelId,
|
||||
SEBExamConfigDownload.class,
|
||||
this.downloadFileName);
|
||||
urlLauncher.openURL(downloadURL);
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
private Supplier<Set<EntityKey>> getConfigMappingSelection(
|
||||
final EntityTable<ExamConfigurationMap> configurationTable) {
|
||||
return () -> {
|
||||
final ExamConfigurationMap firstRowData = configurationTable.getFirstRowData();
|
||||
if (firstRowData == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
return new HashSet<>(Arrays.asList(firstRowData.getEntityKey()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Supplier<Set<EntityKey>> getConfigSelection(final EntityTable<ExamConfigurationMap> configurationTable) {
|
||||
return () -> {
|
||||
final ExamConfigurationMap firstRowData = configurationTable.getFirstRowData();
|
||||
if (firstRowData == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
return new HashSet<>(Arrays.asList(new EntityKey(
|
||||
firstRowData.configurationNodeId,
|
||||
EntityType.CONFIGURATION_NODE)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private PageAction deleteExamConfigMapping(final PageAction action) {
|
||||
final EntityKey examConfigMappingKey = action.getSingleSelection();
|
||||
this.resourceService.getRestService()
|
||||
.getBuilder(DeleteExamConfigMapping.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, examConfigMappingKey.modelId)
|
||||
.call()
|
||||
.onError(error -> action.pageContext().notifyRemoveError(EntityType.EXAM_CONFIGURATION_MAP, error));
|
||||
return action;
|
||||
}
|
||||
|
||||
private PageAction getExamConfigKey(final PageAction action) {
|
||||
final EntityKey examConfigMappingKey = action.getSingleSelection();
|
||||
if (examConfigMappingKey != null) {
|
||||
action.withEntityKey(examConfigMappingKey);
|
||||
return SEBExamConfigForm
|
||||
.getConfigKeyFunction(this.pageService)
|
||||
.apply(action);
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private PageAction viewExamConfigPageAction(final EntityTable<ExamConfigurationMap> table) {
|
||||
|
||||
return this.pageService.pageActionBuilder(table.getPageContext()
|
||||
.clearEntityKeys()
|
||||
.removeAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA))
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_EXAM_CONFIG_VIEW_PROP)
|
||||
.withSelectionSupplier(() -> {
|
||||
final ExamConfigurationMap selectedROWData = table.getSingleSelectedROWData();
|
||||
final HashSet<EntityKey> result = new HashSet<>();
|
||||
if (selectedROWData != null) {
|
||||
result.add(new EntityKey(
|
||||
selectedROWData.configurationNodeId,
|
||||
EntityType.CONFIGURATION_NODE));
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.create();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gui.content;
|
||||
|
||||
import org.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.Indicator;
|
||||
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;
|
||||
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.DeleteIndicator;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicatorPage;
|
||||
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 ExamFormIndicators implements TemplateComposer {
|
||||
|
||||
private final static LocTextKey INDICATOR_LIST_TITLE_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.list.title");
|
||||
private final static LocTextKey INDICATOR_LIST_TITLE_TOOLTIP_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.list.title" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
|
||||
private final static LocTextKey INDICATOR_TYPE_COLUMN_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.list.column.type");
|
||||
private final static LocTextKey INDICATOR_NAME_COLUMN_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.list.column.name");
|
||||
private final static LocTextKey INDICATOR_THRESHOLD_COLUMN_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.list.column.thresholds");
|
||||
private final static LocTextKey INDICATOR_EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.list.pleaseSelect");
|
||||
private static final LocTextKey INDICATOR_EMPTY_LIST_MESSAGE =
|
||||
new LocTextKey("sebserver.exam.indicator.list.empty");
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final WidgetFactory widgetFactory;
|
||||
private final RestService restService;
|
||||
|
||||
public ExamFormIndicators(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 modifyGrant = BooleanUtils.toBoolean(
|
||||
pageContext.getAttribute(ExamForm.ATTR_MODIFY_GRANT));
|
||||
|
||||
// List of Indicators
|
||||
this.widgetFactory.addFormSubContextHeader(
|
||||
content,
|
||||
INDICATOR_LIST_TITLE_KEY,
|
||||
INDICATOR_LIST_TITLE_TOOLTIP_KEY);
|
||||
|
||||
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(pageContext
|
||||
.clearEntityKeys()
|
||||
.removeAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA));
|
||||
|
||||
final EntityTable<Indicator> indicatorTable =
|
||||
this.pageService.entityTableBuilder(this.restService.getRestCall(GetIndicatorPage.class))
|
||||
.withRestCallAdapter(builder -> builder.withQueryParam(
|
||||
Indicator.FILTER_ATTR_EXAM_ID,
|
||||
entityKey.modelId))
|
||||
.withEmptyMessage(INDICATOR_EMPTY_LIST_MESSAGE)
|
||||
.withMarkup()
|
||||
.withPaging(5)
|
||||
.hideNavigation()
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.INDICATOR.ATTR_NAME,
|
||||
INDICATOR_NAME_COLUMN_KEY,
|
||||
Indicator::getName)
|
||||
.widthProportion(2))
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.INDICATOR.ATTR_TYPE,
|
||||
INDICATOR_TYPE_COLUMN_KEY,
|
||||
this::indicatorTypeName)
|
||||
.widthProportion(1))
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.THRESHOLD.REFERENCE_NAME,
|
||||
INDICATOR_THRESHOLD_COLUMN_KEY,
|
||||
this::thresholdsValue)
|
||||
.asMarkup()
|
||||
.widthProportion(4))
|
||||
.withDefaultActionIf(
|
||||
() -> modifyGrant,
|
||||
() -> actionBuilder
|
||||
.newAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST)
|
||||
.withParentEntityKey(entityKey)
|
||||
.create())
|
||||
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
pageContext,
|
||||
ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST,
|
||||
ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST))
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
actionBuilder
|
||||
|
||||
.newAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withSelect(
|
||||
indicatorTable::getSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent(), false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST)
|
||||
.withEntityKey(entityKey)
|
||||
.withSelect(
|
||||
indicatorTable::getSelection,
|
||||
this::deleteSelectedIndicator,
|
||||
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent(), false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_INDICATOR_NEW)
|
||||
.withParentEntityKey(entityKey)
|
||||
.publishIf(() -> modifyGrant);
|
||||
|
||||
}
|
||||
|
||||
private PageAction deleteSelectedIndicator(final PageAction action) {
|
||||
final EntityKey indicatorKey = action.getSingleSelection();
|
||||
this.resourceService.getRestService()
|
||||
.getBuilder(DeleteIndicator.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, indicatorKey.modelId)
|
||||
.call();
|
||||
return action;
|
||||
}
|
||||
|
||||
private String indicatorTypeName(final Indicator indicator) {
|
||||
if (indicator.type == null) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
|
||||
return this.resourceService.getI18nSupport()
|
||||
.getText(ResourceService.EXAM_INDICATOR_TYPE_PREFIX + indicator.type.name());
|
||||
}
|
||||
|
||||
private String thresholdsValue(final Indicator indicator) {
|
||||
if (indicator.thresholds.isEmpty()) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
|
||||
final StringBuilder builder = indicator.thresholds
|
||||
.stream()
|
||||
.reduce(
|
||||
new StringBuilder(),
|
||||
(sb, threshold) -> sb
|
||||
.append("<span style='padding: 2px 5px 2px 5px; background-color: #")
|
||||
.append(threshold.color)
|
||||
.append("; ")
|
||||
.append((Utils.darkColor(Utils.parseRGB(threshold.color)))
|
||||
? "color: #4a4a4a; "
|
||||
: "color: #FFFFFF;")
|
||||
.append("'>")
|
||||
.append(Indicator.getDisplayValue(indicator.type, threshold.value))
|
||||
.append(" (")
|
||||
.append(threshold.color)
|
||||
.append(")")
|
||||
.append("</span>")
|
||||
.append(" | "),
|
||||
StringBuilder::append);
|
||||
builder.delete(builder.length() - 3, builder.length() - 1);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gui.content;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
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.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.ModalInputDialogComposer;
|
||||
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.impl.ModalInputDialog;
|
||||
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.GetProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveProctoringSettings;
|
||||
|
||||
public class ExamProctoringSettings {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ExamProctoringSettings.class);
|
||||
|
||||
private final static LocTextKey SEB_PROCTORING_FORM_TITLE =
|
||||
new LocTextKey("sebserver.exam.proctoring.form.title");
|
||||
private final static LocTextKey SEB_PROCTORING_FORM_INFO =
|
||||
new LocTextKey("sebserver.exam.proctoring.form.info");
|
||||
private final static LocTextKey SEB_PROCTORING_FORM_ENABLE =
|
||||
new LocTextKey("sebserver.exam.proctoring.form.enabled");
|
||||
private final static LocTextKey SEB_PROCTORING_FORM_TYPE =
|
||||
new LocTextKey("sebserver.exam.proctoring.form.type");
|
||||
private final static LocTextKey SEB_PROCTORING_FORM_URL =
|
||||
new LocTextKey("sebserver.exam.proctoring.form.url");
|
||||
private final static LocTextKey SEB_PROCTORING_FORM_APPKEY =
|
||||
new LocTextKey("sebserver.exam.proctoring.form.appkey");
|
||||
private final static LocTextKey SEB_PROCTORING_FORM_SECRET =
|
||||
new LocTextKey("sebserver.exam.proctoring.form.secret");
|
||||
|
||||
public ExamProctoringSettings() {
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
Function<PageAction, PageAction> settingsFunction(final PageService pageService) {
|
||||
|
||||
return action -> {
|
||||
|
||||
final PageContext pageContext = action.pageContext();
|
||||
final ModalInputDialog<FormHandle<?>> dialog =
|
||||
new ModalInputDialog<FormHandle<?>>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
pageService.getWidgetFactory())
|
||||
.setDialogWidth(740)
|
||||
.setDialogHeight(400);
|
||||
|
||||
final SEBProctoringPropertiesForm bindFormContext = new SEBProctoringPropertiesForm(
|
||||
pageService,
|
||||
action.pageContext());
|
||||
|
||||
final Predicate<FormHandle<?>> doBind = formHandle -> doCreate(
|
||||
pageService,
|
||||
pageContext,
|
||||
formHandle);
|
||||
|
||||
dialog.open(
|
||||
SEB_PROCTORING_FORM_TITLE,
|
||||
doBind,
|
||||
Utils.EMPTY_EXECUTION,
|
||||
bindFormContext);
|
||||
|
||||
return action;
|
||||
};
|
||||
}
|
||||
|
||||
private boolean doCreate(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext,
|
||||
final FormHandle<?> formHandle) {
|
||||
|
||||
final boolean isReadonly = BooleanUtils.toBoolean(
|
||||
pageContext.getAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY));
|
||||
if (isReadonly) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
ProctoringSettings examProctoring = null;
|
||||
try {
|
||||
final Form form = formHandle.getForm();
|
||||
final boolean enabled = BooleanUtils.toBoolean(
|
||||
form.getFieldValue(ProctoringSettings.ATTR_ENABLE_PROCTORING));
|
||||
final ServerType serverType = ServerType.valueOf(
|
||||
form.getFieldValue(ProctoringSettings.ATTR_SERVER_TYPE));
|
||||
|
||||
examProctoring = new ProctoringSettings(
|
||||
Long.parseLong(entityKey.modelId),
|
||||
enabled,
|
||||
serverType,
|
||||
form.getFieldValue(ProctoringSettings.ATTR_SERVER_URL),
|
||||
form.getFieldValue(ProctoringSettings.ATTR_APP_KEY),
|
||||
form.getFieldValue(ProctoringSettings.ATTR_APP_SECRET));
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Unexpected error while trying to get settings from form: ", e);
|
||||
}
|
||||
|
||||
if (examProctoring == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !pageService
|
||||
.getRestService()
|
||||
.getBuilder(SaveProctoringSettings.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.withBody(examProctoring)
|
||||
.call()
|
||||
.onError(formHandle::handleError)
|
||||
.hasError();
|
||||
}
|
||||
|
||||
private final class SEBProctoringPropertiesForm
|
||||
implements ModalInputDialogComposer<FormHandle<?>> {
|
||||
|
||||
private final PageService pageService;
|
||||
private final PageContext pageContext;
|
||||
|
||||
protected SEBProctoringPropertiesForm(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.pageContext = pageContext;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<FormHandle<?>> compose(final Composite parent) {
|
||||
final RestService restService = this.pageService.getRestService();
|
||||
final ResourceService resourceService = this.pageService.getResourceService();
|
||||
final EntityKey entityKey = this.pageContext.getEntityKey();
|
||||
final boolean isReadonly = BooleanUtils.toBoolean(
|
||||
this.pageContext.getAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY));
|
||||
|
||||
final Composite content = this.pageService
|
||||
.getWidgetFactory()
|
||||
.createPopupScrollComposite(parent);
|
||||
|
||||
final ProctoringSettings proctoringSettings = restService
|
||||
.getBuilder(GetProctoringSettings.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final PageContext formContext = this.pageContext
|
||||
.copyOf(content)
|
||||
.clearEntityKeys();
|
||||
|
||||
final FormHandle<ProctoringSettings> formHandle = this.pageService.formBuilder(
|
||||
formContext)
|
||||
.withDefaultSpanInput(6)
|
||||
.withEmptyCellSeparation(false)
|
||||
.readonly(isReadonly)
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
"Info",
|
||||
SEB_PROCTORING_FORM_INFO,
|
||||
this.pageService.getI18nSupport().getText(SEB_PROCTORING_FORM_INFO))
|
||||
.asArea(50)
|
||||
.asHTML()
|
||||
.readonly(true))
|
||||
|
||||
.addField(FormBuilder.checkbox(
|
||||
ProctoringSettings.ATTR_ENABLE_PROCTORING,
|
||||
SEB_PROCTORING_FORM_ENABLE,
|
||||
String.valueOf(proctoringSettings.enableProctoring)))
|
||||
|
||||
.addField(FormBuilder.singleSelection(
|
||||
ProctoringSettings.ATTR_SERVER_TYPE,
|
||||
SEB_PROCTORING_FORM_TYPE,
|
||||
proctoringSettings.serverType.name(),
|
||||
this.pageService.getResourceService()::examProctoringTypeResources))
|
||||
|
||||
// TODO
|
||||
|
||||
.build();
|
||||
|
||||
return () -> formHandle;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -20,6 +20,8 @@ import java.util.stream.Collectors;
|
|||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
@ -47,7 +49,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
|||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.ActivateSEBRestriction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeactivateSEBRestriction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetCourseChapters;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetSEBRestriction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetSEBRestrictionSettings;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveSEBRestriction;
|
||||
|
||||
@Lazy
|
||||
|
@ -55,6 +57,8 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveSEBRestr
|
|||
@GuiProfile
|
||||
public class ExamSEBRestrictionSettings {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ExamSEBRestrictionSettings.class);
|
||||
|
||||
private final static LocTextKey SEB_RESTRICTION_ERROR =
|
||||
new LocTextKey("sebserver.error.exam.seb.restriction");
|
||||
private final static LocTextKey SEB_RESTRICTION_FORM_TITLE =
|
||||
|
@ -153,7 +157,11 @@ public class ExamSEBRestrictionSettings {
|
|||
additionalAttributes);
|
||||
|
||||
} catch (final Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error("Unexpected error while trying to get settings from form: ", e);
|
||||
}
|
||||
|
||||
if (bodyValue == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !pageService
|
||||
|
@ -195,7 +203,7 @@ public class ExamSEBRestrictionSettings {
|
|||
.createPopupScrollComposite(parent);
|
||||
|
||||
final SEBRestriction sebRestriction = restService
|
||||
.getBuilder(GetSEBRestriction.class)
|
||||
.getBuilder(GetSEBRestrictionSettings.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
|
|
@ -20,9 +20,6 @@ import java.util.function.Function;
|
|||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Tuple3;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
@ -47,6 +44,7 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType;
|
|||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.TemplateAttribute;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.View;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
|
||||
|
@ -60,6 +58,8 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
|||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Tuple;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Tuple3;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
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.remote.webservice.api.RestService;
|
||||
|
@ -109,6 +109,7 @@ public class ResourceService {
|
|||
public static final String SEB_RESTRICTION_WHITE_LIST_PREFIX = "sebserver.exam.form.sebrestriction.whiteListPaths.";
|
||||
public static final String SEB_RESTRICTION_PERMISSIONS_PREFIX = "sebserver.exam.form.sebrestriction.permissions.";
|
||||
public static final String SEB_CLIENT_CONFIG_PURPOSE_PREFIX = "sebserver.clientconfig.config.purpose.";
|
||||
public static final String EXAM_PROCTORING_TYPE_PREFIX = "sebserver.exam.proctoring.type.servertype.";
|
||||
|
||||
public static final EnumSet<AttributeType> ATTRIBUTE_TYPES_NOT_DISPLAYED = EnumSet.of(
|
||||
AttributeType.LABEL,
|
||||
|
@ -370,6 +371,18 @@ public class ResourceService {
|
|||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Tuple<String>> examProctoringTypeResources() {
|
||||
return Arrays.stream(ExamType.values())
|
||||
.map(type -> new Tuple3<>(
|
||||
type.name(),
|
||||
this.i18nSupport.getText(EXAM_PROCTORING_TYPE_PREFIX + type.name()),
|
||||
Utils.formatLineBreaks(this.i18nSupport.getText(
|
||||
EXAM_PROCTORING_TYPE_PREFIX + type.name() + Constants.TOOLTIP_TEXT_KEY_SUFFIX,
|
||||
StringUtils.EMPTY))))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Tuple<String>> examConfigStatusResources() {
|
||||
return examConfigStatusResources(false);
|
||||
}
|
||||
|
@ -387,9 +400,9 @@ public class ResourceService {
|
|||
type.name(),
|
||||
this.i18nSupport.getText(EXAMCONFIG_STATUS_PREFIX + type.name()),
|
||||
Utils.formatLineBreaks(this.i18nSupport.getText(
|
||||
this.i18nSupport.getText(EXAMCONFIG_STATUS_PREFIX + type.name()) + Constants.TOOLTIP_TEXT_KEY_SUFFIX,
|
||||
StringUtils.EMPTY))
|
||||
))
|
||||
this.i18nSupport.getText(EXAMCONFIG_STATUS_PREFIX + type.name())
|
||||
+ Constants.TOOLTIP_TEXT_KEY_SUFFIX,
|
||||
StringUtils.EMPTY))))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class GetProctoringSettings extends RestCall<ProctoringSettings> {
|
||||
|
||||
public GetProctoringSettings() {
|
||||
super(new TypeKey<>(
|
||||
CallType.GET_SINGLE,
|
||||
EntityType.EXAM_PROCTOR_DATA,
|
||||
new TypeReference<ProctoringSettings>() {
|
||||
}),
|
||||
HttpMethod.GET,
|
||||
MediaType.APPLICATION_JSON_UTF8,
|
||||
API.EXAM_ADMINISTRATION_ENDPOINT
|
||||
+ API.MODEL_ID_VAR_PATH_SEGMENT
|
||||
+ API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT);
|
||||
}
|
||||
|
||||
}
|
|
@ -24,9 +24,9 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
|||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class GetSEBRestriction extends RestCall<SEBRestriction> {
|
||||
public class GetSEBRestrictionSettings extends RestCall<SEBRestriction> {
|
||||
|
||||
public GetSEBRestriction() {
|
||||
public GetSEBRestrictionSettings() {
|
||||
super(new TypeKey<>(
|
||||
CallType.GET_SINGLE,
|
||||
EntityType.EXAM_SEB_RESTRICTION,
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class SaveProctoringSettings extends RestCall<Exam> {
|
||||
|
||||
public SaveProctoringSettings() {
|
||||
super(new TypeKey<>(
|
||||
CallType.SAVE,
|
||||
EntityType.EXAM_PROCTOR_DATA,
|
||||
new TypeReference<Exam>() {
|
||||
}),
|
||||
HttpMethod.POST,
|
||||
MediaType.APPLICATION_JSON_UTF8,
|
||||
API.EXAM_ADMINISTRATION_ENDPOINT
|
||||
+ API.MODEL_ID_VAR_PATH_SEGMENT
|
||||
+ API.EXAM_ADMINISTRATION_SEB_RESTRICTION_PATH_SEGMENT);
|
||||
}
|
||||
|
||||
}
|
|
@ -28,7 +28,7 @@ public class SaveSEBRestriction extends RestCall<Exam> {
|
|||
|
||||
public SaveSEBRestriction() {
|
||||
super(new TypeKey<>(
|
||||
CallType.GET_SINGLE,
|
||||
CallType.SAVE,
|
||||
EntityType.EXAM_SEB_RESTRICTION,
|
||||
new TypeReference<Exam>() {
|
||||
}),
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
package ch.ethz.seb.sebserver.webservice.servicelayer.exam;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring.ServerType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
||||
public interface ExamAdminService {
|
||||
|
@ -38,7 +38,7 @@ public interface ExamAdminService {
|
|||
*
|
||||
* @param examId the exam instance
|
||||
* @return Result refer to ExamProctoring data for the exam. */
|
||||
default Result<ExamProctoring> getExamProctoring(final Exam exam) {
|
||||
default Result<ProctoringSettings> getExamProctoring(final Exam exam) {
|
||||
if (exam == null || exam.id == null) {
|
||||
return Result.ofRuntimeError("Invalid Exam model");
|
||||
}
|
||||
|
@ -49,14 +49,14 @@ public interface ExamAdminService {
|
|||
*
|
||||
* @param examId the exam identifier
|
||||
* @return Result refer to ExamProctoring data for the exam. */
|
||||
Result<ExamProctoring> getExamProctoring(Long examId);
|
||||
Result<ProctoringSettings> getExamProctoring(Long examId);
|
||||
|
||||
/** Save the given ExamProctoring data for an existing Exam.
|
||||
*
|
||||
* @param examId the exam identifier
|
||||
* @param examProctoring The ExamProctoring data to save for the exam
|
||||
* @return Result refer to saved ExamProctoring data or to an error when happened. */
|
||||
Result<ExamProctoring> saveExamProctoring(Long examId, ExamProctoring examProctoring);
|
||||
Result<ProctoringSettings> saveExamProctoring(Long examId, ProctoringSettings examProctoring);
|
||||
|
||||
/** This indicates if proctoring is set and enabled for a certain exam.
|
||||
*
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.exam;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring.ServerType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
||||
|
@ -17,15 +17,15 @@ public interface ExamProctoringService {
|
|||
|
||||
ServerType getType();
|
||||
|
||||
Result<Boolean> testExamProctoring(final ExamProctoring examProctoring);
|
||||
Result<Boolean> testExamProctoring(final ProctoringSettings examProctoring);
|
||||
|
||||
public Result<String> createProctoringURL(
|
||||
final ExamProctoring examProctoring,
|
||||
final ProctoringSettings examProctoring,
|
||||
final String connectionToken,
|
||||
final boolean server);
|
||||
|
||||
Result<String> createProctoringURL(
|
||||
final ExamProctoring examProctoring,
|
||||
final ProctoringSettings examProctoring,
|
||||
ClientConnection clientConnection,
|
||||
boolean server);
|
||||
|
||||
|
|
|
@ -28,8 +28,8 @@ import ch.ethz.seb.sebserver.gbl.Constants;
|
|||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring.ServerType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
|
||||
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.OpenEdxSEBRestriction;
|
||||
|
@ -156,56 +156,56 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Result<ExamProctoring> getExamProctoring(final Long examId) {
|
||||
public Result<ProctoringSettings> getExamProctoring(final Long examId) {
|
||||
return this.additionalAttributesDAO.getAdditionalAttributes(EntityType.EXAM, examId)
|
||||
.map(attrs -> attrs.stream()
|
||||
.collect(Collectors.toMap(
|
||||
attr -> attr.getName(),
|
||||
Function.identity())))
|
||||
.map(mapping -> {
|
||||
return new ExamProctoring(
|
||||
return new ProctoringSettings(
|
||||
examId,
|
||||
getEnabled(mapping),
|
||||
getServerType(mapping),
|
||||
getString(mapping, ExamProctoring.ATTR_SERVER_URL),
|
||||
getString(mapping, ExamProctoring.ATTR_APP_KEY),
|
||||
getString(mapping, ExamProctoring.ATTR_APP_SECRET));
|
||||
getString(mapping, ProctoringSettings.ATTR_SERVER_URL),
|
||||
getString(mapping, ProctoringSettings.ATTR_APP_KEY),
|
||||
getString(mapping, ProctoringSettings.ATTR_APP_SECRET));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<ExamProctoring> saveExamProctoring(final Long examId, final ExamProctoring examProctoring) {
|
||||
public Result<ProctoringSettings> saveExamProctoring(final Long examId, final ProctoringSettings examProctoring) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
examId,
|
||||
ExamProctoring.ATTR_ENABLE_PROCTORING,
|
||||
ProctoringSettings.ATTR_ENABLE_PROCTORING,
|
||||
String.valueOf(examProctoring.enableProctoring));
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
examId,
|
||||
ExamProctoring.ATTR_SERVER_TYPE,
|
||||
ProctoringSettings.ATTR_SERVER_TYPE,
|
||||
examProctoring.serverType.name());
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
examId,
|
||||
ExamProctoring.ATTR_SERVER_URL,
|
||||
ProctoringSettings.ATTR_SERVER_URL,
|
||||
examProctoring.serverURL);
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
examId,
|
||||
ExamProctoring.ATTR_APP_KEY,
|
||||
ProctoringSettings.ATTR_APP_KEY,
|
||||
examProctoring.appKey);
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
examId,
|
||||
ExamProctoring.ATTR_APP_SECRET,
|
||||
ProctoringSettings.ATTR_APP_SECRET,
|
||||
this.cryptor.encrypt(examProctoring.appSecret).toString());
|
||||
|
||||
return examProctoring;
|
||||
|
@ -217,7 +217,7 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
return this.additionalAttributesDAO.getAdditionalAttribute(
|
||||
EntityType.EXAM,
|
||||
examId,
|
||||
ExamProctoring.ATTR_ENABLE_PROCTORING)
|
||||
ProctoringSettings.ATTR_ENABLE_PROCTORING)
|
||||
.map(rec -> rec != null && BooleanUtils.toBoolean(rec.getValue()));
|
||||
}
|
||||
|
||||
|
@ -227,16 +227,16 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
}
|
||||
|
||||
private Boolean getEnabled(final Map<String, AdditionalAttributeRecord> mapping) {
|
||||
if (mapping.containsKey(ExamProctoring.ATTR_ENABLE_PROCTORING)) {
|
||||
return BooleanUtils.toBoolean(mapping.get(ExamProctoring.ATTR_ENABLE_PROCTORING).getValue());
|
||||
if (mapping.containsKey(ProctoringSettings.ATTR_ENABLE_PROCTORING)) {
|
||||
return BooleanUtils.toBoolean(mapping.get(ProctoringSettings.ATTR_ENABLE_PROCTORING).getValue());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private ServerType getServerType(final Map<String, AdditionalAttributeRecord> mapping) {
|
||||
if (mapping.containsKey(ExamProctoring.ATTR_SERVER_TYPE)) {
|
||||
return ServerType.valueOf(mapping.get(ExamProctoring.ATTR_SERVER_TYPE).getValue());
|
||||
if (mapping.containsKey(ProctoringSettings.ATTR_SERVER_TYPE)) {
|
||||
return ServerType.valueOf(mapping.get(ProctoringSettings.ATTR_SERVER_TYPE).getValue());
|
||||
} else {
|
||||
return ServerType.JITSI_MEET;
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@ import org.springframework.web.util.UriComponentsBuilder;
|
|||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring.ServerType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
@ -53,14 +53,14 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Result<Boolean> testExamProctoring(final ExamProctoring examProctoring) {
|
||||
public Result<Boolean> testExamProctoring(final ProctoringSettings examProctoring) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> createProctoringURL(
|
||||
final ExamProctoring examProctoring,
|
||||
final ProctoringSettings examProctoring,
|
||||
final String connectionToken,
|
||||
final boolean server) {
|
||||
|
||||
|
@ -76,7 +76,7 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
|
|||
|
||||
@Override
|
||||
public Result<String> createProctoringURL(
|
||||
final ExamProctoring examProctoring,
|
||||
final ProctoringSettings examProctoring,
|
||||
final ClientConnection clientConnection,
|
||||
final boolean server) {
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import java.util.stream.Collectors;
|
|||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring.ServerType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamProctoringService;
|
||||
|
|
|
@ -50,7 +50,7 @@ import ch.ethz.seb.sebserver.gbl.model.Page;
|
|||
import ch.ethz.seb.sebserver.gbl.model.PageSortOrder;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Chapters;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamProctoring;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.SEBRestriction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
|
@ -386,7 +386,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
+ API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT,
|
||||
method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public ExamProctoring getExamProctoring(
|
||||
public ProctoringSettings getExamProctoring(
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
|
@ -405,18 +405,22 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
+ API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT,
|
||||
method = RequestMethod.POST,
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public ExamProctoring saveExamProctoring(
|
||||
public Exam saveExamProctoring(
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
||||
@PathVariable(API.PARAM_MODEL_ID) final Long examId,
|
||||
@Valid @RequestBody final ExamProctoring examProctoring) {
|
||||
@Valid @RequestBody final ProctoringSettings examProctoring) {
|
||||
|
||||
checkModifyPrivilege(institutionId);
|
||||
return this.entityDAO.byPK(examId)
|
||||
.flatMap(this.authorization::checkModify)
|
||||
.flatMap(exam -> this.examAdminService.saveExamProctoring(examId, examProctoring))
|
||||
.map(exam -> {
|
||||
this.examAdminService.saveExamProctoring(examId, examProctoring);
|
||||
return exam;
|
||||
})
|
||||
.flatMap(this.userActivityLogDAO::logModify)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
|
|
|
@ -621,6 +621,23 @@ sebserver.exam.delete.confirm.title=Deletion Successful
|
|||
sebserver.exam.delete.confirm.message=The Exam ({0}) was successfully deleted.<br/>Also the following number dependencies where successfully deleted: {1}.<br/><br/>And there where {2} errors.
|
||||
sebserver.exam.delete.report.list.empty=No dependencies will be deleted.
|
||||
|
||||
sebserver.exam.proctoring.actions.open=Set Proctoring
|
||||
sebserver.exam.proctoring.form.title=Exam Proctoring Settings
|
||||
sebserver.exam.proctoring.form.info=This allows to integrate a supported external proctoring service.
|
||||
sebserver.exam.proctoring.form.enabled=Proctoring enabled
|
||||
sebserver.exam.proctoring.form.enabled.tooltip=Indicates whether the exam proctoring feature is enabled for this exam or not.
|
||||
sebserver.exam.proctoring.form.type=Type
|
||||
sebserver.exam.proctoring.form.type.tooltip=The type and server type of the external proctoring service.
|
||||
sebserver.exam.proctoring.form.url=Server URL
|
||||
sebserver.exam.proctoring.form.url.tooltip=The proctoring server URL
|
||||
sebserver.exam.proctoring.form.appkey=Application Key
|
||||
sebserver.exam.proctoring.form.appkey.tooltip=The application key of the proctoring service server
|
||||
sebserver.exam.proctoring.form.secret=Secret
|
||||
sebserver.exam.proctoring.form.secret.tooltip=The secret used to access the proctoring service
|
||||
|
||||
sebserver.exam.proctoring.type.servertype.JITSI_MEET=Jitsi Meet Server
|
||||
sebserver.exam.proctoring.type.servertype.JITSI_MEET.tooltip=Use a Jitsi Meet Server for proctoring
|
||||
|
||||
################################
|
||||
# Client configuration
|
||||
################################
|
||||
|
|
Loading…
Add table
Reference in a new issue