SEBSERV-139 implementing GUI

This commit is contained in:
anhefti 2020-08-06 17:01:38 +02:00
parent 6eedcbb4a0
commit 548d4d132f
13 changed files with 261 additions and 17 deletions

View file

@ -126,7 +126,6 @@ public final class API {
public static final String EXAM_ADMINISTRATION_CHECK_RESTRICTION_PATH_SEGMENT = "/check-seb-restriction"; public static final String EXAM_ADMINISTRATION_CHECK_RESTRICTION_PATH_SEGMENT = "/check-seb-restriction";
public static final String EXAM_ADMINISTRATION_CHECK_IMPORTED_PATH_SEGMENT = "/check-imported"; public static final String EXAM_ADMINISTRATION_CHECK_IMPORTED_PATH_SEGMENT = "/check-imported";
public static final String EXAM_ADMINISTRATION_SEB_RESTRICTION_CHAPTERS_PATH_SEGMENT = "/chapters"; public static final String EXAM_ADMINISTRATION_SEB_RESTRICTION_CHAPTERS_PATH_SEGMENT = "/chapters";
public static final String EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT = "/proctoring"; public static final String EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT = "/proctoring";
public static final String EXAM_INDICATOR_ENDPOINT = "/indicator"; public static final String EXAM_INDICATOR_ENDPOINT = "/indicator";

View file

@ -18,8 +18,10 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.webservice.servicelayer.validation.ValidProctoringSettings;
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@ValidProctoringSettings
public class ProctoringSettings implements Entity { public class ProctoringSettings implements Entity {
public enum ServerType { public enum ServerType {
@ -42,7 +44,7 @@ public class ProctoringSettings implements Entity {
public final ServerType serverType; public final ServerType serverType;
@JsonProperty(ATTR_SERVER_URL) @JsonProperty(ATTR_SERVER_URL)
@URL(message = "examProctoring:serverURL:invalidURL") @URL(message = "proctoringSettings:serverURL:invalidURL")
public final String serverURL; public final String serverURL;
@JsonProperty(ATTR_APP_KEY) @JsonProperty(ATTR_APP_KEY)

View file

@ -33,6 +33,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; 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;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus; import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
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.QuizData;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetupTestResult; 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.institution.LmsSetupTestResult.ErrorType;
@ -57,6 +58,7 @@ 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.CheckExamConsistency;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckSEBRestriction; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckSEBRestriction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam; 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.GetLmsSetup;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetup; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetup;
@ -119,6 +121,7 @@ public class ExamForm implements TemplateComposer {
private final PageService pageService; private final PageService pageService;
private final ResourceService resourceService; private final ResourceService resourceService;
private final ExamSEBRestrictionSettings examSEBRestrictionSettings; private final ExamSEBRestrictionSettings examSEBRestrictionSettings;
private final ExamProctoringSettings examProctoringSettings;
private final WidgetFactory widgetFactory; private final WidgetFactory widgetFactory;
private final RestService restService; private final RestService restService;
private final ExamDeletePopup examDeletePopup; private final ExamDeletePopup examDeletePopup;
@ -128,6 +131,7 @@ public class ExamForm implements TemplateComposer {
protected ExamForm( protected ExamForm(
final PageService pageService, final PageService pageService,
final ExamSEBRestrictionSettings examSEBRestrictionSettings, final ExamSEBRestrictionSettings examSEBRestrictionSettings,
final ExamProctoringSettings examProctoringSettings,
final ExamToConfigBindingForm examToConfigBindingForm, final ExamToConfigBindingForm examToConfigBindingForm,
final DownloadService downloadService, final DownloadService downloadService,
final ExamDeletePopup examDeletePopup, final ExamDeletePopup examDeletePopup,
@ -137,6 +141,7 @@ public class ExamForm implements TemplateComposer {
this.pageService = pageService; this.pageService = pageService;
this.resourceService = pageService.getResourceService(); this.resourceService = pageService.getResourceService();
this.examSEBRestrictionSettings = examSEBRestrictionSettings; this.examSEBRestrictionSettings = examSEBRestrictionSettings;
this.examProctoringSettings = examProctoringSettings;
this.widgetFactory = pageService.getWidgetFactory(); this.widgetFactory = pageService.getWidgetFactory();
this.restService = this.resourceService.getRestService(); this.restService = this.resourceService.getRestService();
this.examDeletePopup = examDeletePopup; this.examDeletePopup = examDeletePopup;
@ -336,6 +341,13 @@ public class ExamForm implements TemplateComposer {
? this.restService.getRestCall(ImportAsExam.class) ? this.restService.getRestCall(ImportAsExam.class)
: this.restService.getRestCall(SaveExam.class)); : this.restService.getRestCall(SaveExam.class));
final boolean proctoringEnabled = this.restService
.getBuilder(GetProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
.map(ProctoringSettings::getEnableProctoring)
.getOr(false);
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(formContext final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(formContext
.clearEntityKeys() .clearEntityKeys()
.removeAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA)); .removeAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA));
@ -384,6 +396,18 @@ public class ExamForm implements TemplateComposer {
.publishIf(() -> sebRestrictionAvailable && readonly && modifyGrant && !importFromQuizData .publishIf(() -> sebRestrictionAvailable && readonly && modifyGrant && !importFromQuizData
&& BooleanUtils.isTrue(isRestricted)) && BooleanUtils.isTrue(isRestricted))
.newAction(ActionDefinition.EXAM_PROCTORING_ON)
.withEntityKey(entityKey)
.withExec(this.examProctoringSettings.settingsFunction(this.pageService))
.noEventPropagation()
.publishIf(() -> proctoringEnabled && readonly)
.newAction(ActionDefinition.EXAM_PROCTORING_OFF)
.withEntityKey(entityKey)
.withExec(this.examProctoringSettings.settingsFunction(this.pageService))
.noEventPropagation()
.publishIf(() -> !proctoringEnabled && readonly)
.newAction(ActionDefinition.EXAM_DELETE) .newAction(ActionDefinition.EXAM_DELETE)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(this.examDeletePopup.deleteWizardFunction(pageContext)) .withExec(this.examDeletePopup.deleteWizardFunction(pageContext))

View file

@ -16,12 +16,16 @@ import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; 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;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Utils; 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.Form;
import ch.ethz.seb.sebserver.gui.form.FormBuilder; import ch.ethz.seb.sebserver.gui.form.FormBuilder;
import ch.ethz.seb.sebserver.gui.form.FormHandle; import ch.ethz.seb.sebserver.gui.form.FormHandle;
@ -30,12 +34,16 @@ 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.ModalInputDialogComposer;
import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog; 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.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; 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.GetProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveProctoringSettings; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveProctoringSettings;
@Lazy
@Component
@GuiProfile
public class ExamProctoringSettings { public class ExamProctoringSettings {
private static final Logger log = LoggerFactory.getLogger(ExamProctoringSettings.class); private static final Logger log = LoggerFactory.getLogger(ExamProctoringSettings.class);
@ -55,10 +63,6 @@ public class ExamProctoringSettings {
private final static LocTextKey SEB_PROCTORING_FORM_SECRET = private final static LocTextKey SEB_PROCTORING_FORM_SECRET =
new LocTextKey("sebserver.exam.proctoring.form.secret"); new LocTextKey("sebserver.exam.proctoring.form.secret");
public ExamProctoringSettings() {
// TODO Auto-generated constructor stub
}
Function<PageAction, PageAction> settingsFunction(final PageService pageService) { Function<PageAction, PageAction> settingsFunction(final PageService pageService) {
return action -> { return action -> {
@ -75,7 +79,7 @@ public class ExamProctoringSettings {
pageService, pageService,
action.pageContext()); action.pageContext());
final Predicate<FormHandle<?>> doBind = formHandle -> doCreate( final Predicate<FormHandle<?>> doBind = formHandle -> doSaveSettings(
pageService, pageService,
pageContext, pageContext,
formHandle); formHandle);
@ -90,7 +94,7 @@ public class ExamProctoringSettings {
}; };
} }
private boolean doCreate( private boolean doSaveSettings(
final PageService pageService, final PageService pageService,
final PageContext pageContext, final PageContext pageContext,
final FormHandle<?> formHandle) { final FormHandle<?> formHandle) {
@ -105,6 +109,8 @@ public class ExamProctoringSettings {
ProctoringSettings examProctoring = null; ProctoringSettings examProctoring = null;
try { try {
final Form form = formHandle.getForm(); final Form form = formHandle.getForm();
form.clearErrors();
final boolean enabled = BooleanUtils.toBoolean( final boolean enabled = BooleanUtils.toBoolean(
form.getFieldValue(ProctoringSettings.ATTR_ENABLE_PROCTORING)); form.getFieldValue(ProctoringSettings.ATTR_ENABLE_PROCTORING));
final ServerType serverType = ServerType.valueOf( final ServerType serverType = ServerType.valueOf(
@ -126,7 +132,7 @@ public class ExamProctoringSettings {
return false; return false;
} }
return !pageService final boolean saveOk = !pageService
.getRestService() .getRestService()
.getBuilder(SaveProctoringSettings.class) .getBuilder(SaveProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId) .withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
@ -134,6 +140,19 @@ public class ExamProctoringSettings {
.call() .call()
.onError(formHandle::handleError) .onError(formHandle::handleError)
.hasError(); .hasError();
if (saveOk) {
final PageAction action = pageService.pageActionBuilder(pageContext)
.newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
.create();
pageService.firePageEvent(
new ActionEvent(action),
action.pageContext());
return true;
}
return false;
} }
private final class SEBProctoringPropertiesForm private final class SEBProctoringPropertiesForm
@ -196,9 +215,24 @@ public class ExamProctoringSettings {
ProctoringSettings.ATTR_SERVER_TYPE, ProctoringSettings.ATTR_SERVER_TYPE,
SEB_PROCTORING_FORM_TYPE, SEB_PROCTORING_FORM_TYPE,
proctoringSettings.serverType.name(), proctoringSettings.serverType.name(),
this.pageService.getResourceService()::examProctoringTypeResources)) resourceService::examProctoringTypeResources))
// TODO .addField(FormBuilder.text(
ProctoringSettings.ATTR_SERVER_URL,
SEB_PROCTORING_FORM_URL,
proctoringSettings.serverURL))
.addField(FormBuilder.text(
ProctoringSettings.ATTR_APP_KEY,
SEB_PROCTORING_FORM_APPKEY,
proctoringSettings.appKey))
.addField(FormBuilder.password(
ProctoringSettings.ATTR_APP_SECRET,
SEB_PROCTORING_FORM_SECRET,
(proctoringSettings.appSecret != null)
? String.valueOf(proctoringSettings.appSecret)
: null))
.build(); .build();

View file

@ -24,6 +24,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey; 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;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
@ -45,8 +46,10 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionData; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionData;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProctorURLForClient;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionDetails; import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionDetails;
import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor; import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor;
@ -237,6 +240,13 @@ public class MonitoringClientConnection implements TemplateComposer {
.compose(pageContext.copyOf(content)); .compose(pageContext.copyOf(content));
final boolean proctoringEnabled = restService
.getBuilder(GetProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
.call()
.map(ProctoringSettings::getEnableProctoring)
.getOr(false);
actionBuilder actionBuilder
.newAction(ActionDefinition.MONITOR_EXAM_BACK_TO_OVERVIEW) .newAction(ActionDefinition.MONITOR_EXAM_BACK_TO_OVERVIEW)
.withEntityKey(parentEntityKey) .withEntityKey(parentEntityKey)
@ -256,15 +266,16 @@ public class MonitoringClientConnection implements TemplateComposer {
connectionData.clientConnection.status == ConnectionStatus.ACTIVE) connectionData.clientConnection.status == ConnectionStatus.ACTIVE)
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_PROCTORING) .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_PROCTORING)
.withExec(this::openProctorScreen) .withEntityKey(parentEntityKey)
.withExec(action -> this.openProctorScreen(action, connectionToken))
.noEventPropagation() .noEventPropagation()
.publish() .publishIf(() -> proctoringEnabled)
; ;
} }
private PageAction openProctorScreen(final PageAction action) { private PageAction openProctorScreen(final PageAction action, final String connectionToken) {
// //
// final ProctorDialog dialog = new ProctorDialog(action.pageContext().getParent().getShell()); // final ProctorDialog dialog = new ProctorDialog(action.pageContext().getParent().getShell());
// dialog.open(EVENT_LIST_TITLE_KEY, // dialog.open(EVENT_LIST_TITLE_KEY,
@ -274,6 +285,12 @@ public class MonitoringClientConnection implements TemplateComposer {
// urlLauncher.openURL( // urlLauncher.openURL(
// "https://seb-jitsi.ethz.ch/TestRoomABC?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb250ZXh0Ijp7InVzZXIiOnsiYXZhdGFyIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9qb2huLWRvZSIsIm5hbWUiOiJEaXNwbGF5IE5hbWUiLCJlbWFpbCI6Im5hbWVAZXhhbXBsZS5jb20ifX0sImF1ZCI6InNlYi1qaXRzaSIsImlzcyI6InNlYi1qaXRzaSIsInN1YiI6Im1lZXQuaml0c2kiLCJyb29tIjoiKiJ9.SD9Zs78mMFqxS1tpalPTykYYaubIYsj_406WAOhcqxQ"); // "https://seb-jitsi.ethz.ch/TestRoomABC?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb250ZXh0Ijp7InVzZXIiOnsiYXZhdGFyIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9qb2huLWRvZSIsIm5hbWUiOiJEaXNwbGF5IE5hbWUiLCJlbWFpbCI6Im5hbWVAZXhhbXBsZS5jb20ifX0sImF1ZCI6InNlYi1qaXRzaSIsImlzcyI6InNlYi1qaXRzaSIsInN1YiI6Im1lZXQuaml0c2kiLCJyb29tIjoiKiJ9.SD9Zs78mMFqxS1tpalPTykYYaubIYsj_406WAOhcqxQ");
final String proctorURL = this.pageService.getRestService().getBuilder(GetProctorURLForClient.class)
.withURIVariable(API.PARAM_MODEL_ID, action.getEntityKey().modelId)
.withURIVariable(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken)
.call()
.getOrThrow();
final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class); final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
javaScriptExecutor.execute( javaScriptExecutor.execute(
"window.open(" "window.open("

View file

@ -297,6 +297,16 @@ public enum ActionDefinition {
ImageIcon.LOCK, ImageIcon.LOCK,
PageStateDefinitionImpl.EXAM_VIEW, PageStateDefinitionImpl.EXAM_VIEW,
ActionCategory.FORM), ActionCategory.FORM),
EXAM_PROCTORING_ON(
new LocTextKey("sebserver.exam.proctoring.actions.open"),
ImageIcon.VISIBILITY,
PageStateDefinitionImpl.EXAM_VIEW,
ActionCategory.FORM),
EXAM_PROCTORING_OFF(
new LocTextKey("sebserver.exam.proctoring.actions.open"),
ImageIcon.VISIBILITY_OFF,
PageStateDefinitionImpl.EXAM_VIEW,
ActionCategory.FORM),
EXAM_CONFIGURATION_NEW( EXAM_CONFIGURATION_NEW(
new LocTextKey("sebserver.exam.configuration.action.list.new"), new LocTextKey("sebserver.exam.configuration.action.list.new"),

View file

@ -39,6 +39,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction.PermissionComponent; import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction.PermissionComponent;
import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction.WhiteListPath; import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction.WhiteListPath;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; 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;
@ -372,7 +373,7 @@ public class ResourceService {
} }
public List<Tuple<String>> examProctoringTypeResources() { public List<Tuple<String>> examProctoringTypeResources() {
return Arrays.stream(ExamType.values()) return Arrays.stream(ServerType.values())
.map(type -> new Tuple3<>( .map(type -> new Tuple3<>(
type.name(), type.name(),
this.i18nSupport.getText(EXAM_PROCTORING_TYPE_PREFIX + type.name()), this.i18nSupport.getText(EXAM_PROCTORING_TYPE_PREFIX + type.name()),

View file

@ -36,7 +36,7 @@ public class SaveProctoringSettings extends RestCall<Exam> {
MediaType.APPLICATION_JSON_UTF8, MediaType.APPLICATION_JSON_UTF8,
API.EXAM_ADMINISTRATION_ENDPOINT API.EXAM_ADMINISTRATION_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT + API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_SEB_RESTRICTION_PATH_SEGMENT); + API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT);
} }
} }

View file

@ -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.session;
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.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetProctorURLForClient extends RestCall<String> {
public GetProctorURLForClient() {
super(new TypeKey<>(
CallType.GET_SINGLE,
EntityType.EXAM_PROCTOR_DATA,
new TypeReference<String>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_JSON_UTF8,
API.EXAM_ADMINISTRATION_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT
+ API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT);
}
}

View file

@ -0,0 +1,81 @@
/*
* 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.webservice.servicelayer.validation;
import java.net.InetAddress;
import java.net.URI;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.apache.commons.lang3.StringUtils;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ServerType;
public class ProctoringSettingsValidator implements ConstraintValidator<ValidProctoringSettings, ProctoringSettings> {
@Override
public boolean isValid(final ProctoringSettings value, final ConstraintValidatorContext context) {
if (value == null) {
return false;
}
if (value.enableProctoring) {
if (value.serverType == ServerType.JITSI_MEET) {
boolean passed = true;
if (StringUtils.isBlank(value.serverURL)) {
context.disableDefaultConstraintViolation();
context
.buildConstraintViolationWithTemplate("proctoringSettings:serverURL:notNull")
.addPropertyNode("serverURL").addConstraintViolation();
passed = false;
}
try {
if (!InetAddress.getByName(new URI(value.serverURL).getHost()).isReachable(5000)) {
context.disableDefaultConstraintViolation();
context
.buildConstraintViolationWithTemplate("proctoringSettings:serverURL:serverNotAvailable")
.addPropertyNode("serverURL").addConstraintViolation();
passed = false;
}
} catch (final Exception e) {
context.disableDefaultConstraintViolation();
context
.buildConstraintViolationWithTemplate("proctoringSettings:serverURL:serverNotAvailable")
.addPropertyNode("serverURL").addConstraintViolation();
passed = false;
}
if (StringUtils.isBlank(value.appKey)) {
context.disableDefaultConstraintViolation();
context
.buildConstraintViolationWithTemplate("proctoringSettings:appKey:notNull")
.addPropertyNode("appKey").addConstraintViolation();
passed = false;
}
if (StringUtils.isBlank(value.appSecret)) {
context.disableDefaultConstraintViolation();
context
.buildConstraintViolationWithTemplate("proctoringSettings:appSecret:notNull")
.addPropertyNode("appSecret").addConstraintViolation();
passed = false;
}
return passed;
}
}
return true;
}
}

View file

@ -0,0 +1,32 @@
/*
* 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.webservice.servicelayer.validation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = ProctoringSettingsValidator.class)
@Documented
public @interface ValidProctoringSettings {
String message() default "{mandatoryWhenEnabled}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View file

@ -429,7 +429,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
+ API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT + API.EXAM_ADMINISTRATION_PROCTOR_PATH_SEGMENT
+ API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT, + API.EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT,
method = RequestMethod.GET, method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE) produces = MediaType.TEXT_PLAIN_VALUE)
public String getExamProctoringURL( public String getExamProctoringURL(
@RequestParam( @RequestParam(
name = API.PARAM_INSTITUTION_ID, name = API.PARAM_INSTITUTION_ID,

View file

@ -90,6 +90,7 @@ sebserver.form.validation.fieldError.password.mismatch=The retyped password does
sebserver.form.validation.fieldError.invalidURL=The input does not match the URL pattern. sebserver.form.validation.fieldError.invalidURL=The input does not match the URL pattern.
sebserver.form.validation.fieldError.exists=This name already exists. Please choose another one. sebserver.form.validation.fieldError.exists=This name already exists. Please choose another one.
sebserver.form.validation.fieldError.email=Invalid mail address sebserver.form.validation.fieldError.email=Invalid mail address
sebserver.form.validation.fieldError.serverNotAvailable=No service seems to be available within the given URL
sebserver.error.unexpected=Unexpected Error sebserver.error.unexpected=Unexpected Error
sebserver.page.message=Information sebserver.page.message=Information
sebserver.dialog.confirm.title=Confirmation sebserver.dialog.confirm.title=Confirmation
@ -1396,6 +1397,7 @@ sebserver.monitoring.exam.connection.action.hide.disabled=Hide Canceled
sebserver.monitoring.exam.connection.action.show.disabled=Show Canceled sebserver.monitoring.exam.connection.action.show.disabled=Show Canceled
sebserver.monitoring.exam.connection.action.hide.undefined=Hide Undefined sebserver.monitoring.exam.connection.action.hide.undefined=Hide Undefined
sebserver.monitoring.exam.connection.action.show.undefined=Show Undefined sebserver.monitoring.exam.connection.action.show.undefined=Show Undefined
sebserver.monitoring.exam.connection.action.proctoring=Proctoring
sebserver.monitoring.exam.connection.eventlist.title=Events sebserver.monitoring.exam.connection.eventlist.title=Events
sebserver.monitoring.exam.connection.eventlist.title.tooltip=All events and logs sent by the SEB Client sebserver.monitoring.exam.connection.eventlist.title.tooltip=All events and logs sent by the SEB Client