fixed various issues

This commit is contained in:
anhefti 2020-12-10 20:27:43 +01:00
parent 873391394a
commit 4c002b4ac2
19 changed files with 270 additions and 35 deletions

View file

@ -46,7 +46,7 @@ public final class LmsSetup implements GrantEntity, Activatable {
public enum LmsType { public enum LmsType {
MOCKUP(Features.COURSE_API), MOCKUP(Features.COURSE_API),
OPEN_EDX(Features.COURSE_API, Features.SEB_RESTRICTION), OPEN_EDX(Features.COURSE_API, Features.SEB_RESTRICTION),
MOODLE(Features.COURSE_API, Features.SEB_RESTRICTION); MOODLE(Features.COURSE_API /* , Features.SEB_RESTRICTION */);
public final EnumSet<Features> features; public final EnumSet<Features> features;

View file

@ -27,6 +27,7 @@ public final class LmsSetupTestResult {
public static final String ATTR_MISSING_ATTRIBUTE = "missingLMSSetupAttribute"; public static final String ATTR_MISSING_ATTRIBUTE = "missingLMSSetupAttribute";
public enum ErrorType { public enum ErrorType {
FEATURE_NOT_AVAILABLE,
MISSING_ATTRIBUTE, MISSING_ATTRIBUTE,
TOKEN_REQUEST, TOKEN_REQUEST,
QUIZ_ACCESS_API_REQUEST, QUIZ_ACCESS_API_REQUEST,
@ -118,6 +119,10 @@ public final class LmsSetupTestResult {
return new LmsSetupTestResult(new Error(ErrorType.QUIZ_RESTRICTION_API_REQUEST, message)); return new LmsSetupTestResult(new Error(ErrorType.QUIZ_RESTRICTION_API_REQUEST, message));
} }
public static LmsSetupTestResult ofQuizRestrictionNotAvailable() {
return new LmsSetupTestResult(new Error(ErrorType.FEATURE_NOT_AVAILABLE, "Restriction Feature Not Available"));
}
public final static class Error { public final static class Error {
@JsonProperty(ATTR_ERROR_TYPE) @JsonProperty(ATTR_ERROR_TYPE)

View file

@ -55,6 +55,7 @@ public final class ClientConnection implements GrantEntity {
public static final String FILTER_ATTR_EXAM_ID = Domain.CLIENT_CONNECTION.ATTR_EXAM_ID; public static final String FILTER_ATTR_EXAM_ID = Domain.CLIENT_CONNECTION.ATTR_EXAM_ID;
public static final String FILTER_ATTR_STATUS = Domain.CLIENT_CONNECTION.ATTR_STATUS; public static final String FILTER_ATTR_STATUS = Domain.CLIENT_CONNECTION.ATTR_STATUS;
public static final String FILTER_ATTR_SESSION_ID = Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID; public static final String FILTER_ATTR_SESSION_ID = Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID;
public static final String FILTER_ATTR_IP_STRING = Domain.CLIENT_CONNECTION.ATTR_CLIENT_ADDRESS;
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ID) @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ID)
public final Long id; public final Long id;

View file

@ -474,7 +474,8 @@ public class ExamForm implements TemplateComposer {
} }
final LmsSetupTestResult lmsSetupTestResult = result.get(); final LmsSetupTestResult lmsSetupTestResult = result.get();
return !lmsSetupTestResult.hasError(ErrorType.QUIZ_RESTRICTION_API_REQUEST); return !lmsSetupTestResult.hasError(ErrorType.QUIZ_RESTRICTION_API_REQUEST)
&& !lmsSetupTestResult.hasError(ErrorType.FEATURE_NOT_AVAILABLE);
} }
private void showConsistencyChecks(final Collection<APIMessage> result, final Composite parent) { private void showConsistencyChecks(final Collection<APIMessage> result, final Composite parent) {

View file

@ -0,0 +1,108 @@
/*
* 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.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
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.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
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.session.GetClientConnectionPage;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
import ch.ethz.seb.sebserver.gui.table.EntityTable;
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
@Lazy
@Component
@GuiProfile
public class MonitoringExamSearchPopup {
private static final LocTextKey TITLE_TEXT_KEY =
new LocTextKey("sebserver.monitoring.search.title");
private static final LocTextKey EMPTY_LIST_TEXT_KEY =
new LocTextKey("sebserver.monitoring.search.list.empty");
private static final LocTextKey TABLE_COLUMN_NAME =
new LocTextKey("sebserver.monitoring.search.list.name");
private final PageService pageService;
private final TableFilterAttribute nameFilter =
new TableFilterAttribute(CriteriaType.TEXT, ClientConnection.FILTER_ATTR_SESSION_ID);
protected MonitoringExamSearchPopup(final PageService pageService) {
this.pageService = pageService;
}
public void show(final PageContext pageContext) {
final ModalInputDialog<Void> dialog = new ModalInputDialog<>(
pageContext.getParent().getShell(),
this.pageService.getWidgetFactory());
dialog.setLargeDialogWidth();
dialog.open(
TITLE_TEXT_KEY,
pageContext,
pc -> this.compose(pc, dialog));
}
private void compose(final PageContext pageContext, final ModalInputDialog<Void> dialog) {
final EntityKey examKey = pageContext.getEntityKey();
final RestService restService = this.pageService.getRestService();
final PageActionBuilder actionBuilder = this.pageService
.pageActionBuilder(pageContext.clearEntityKeys());
final EntityTable<ClientConnection> table =
this.pageService.entityTableBuilder(restService.getRestCall(GetClientConnectionPage.class))
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
.withPaging(10)
.withStaticFilter(ClientConnection.FILTER_ATTR_EXAM_ID, examKey.modelId)
.withColumn(new ColumnDefinition<>(
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
TABLE_COLUMN_NAME,
ClientConnection::getUserSessionId)
.withFilter(this.nameFilter))
.withDefaultAction(t -> actionBuilder
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
.withParentEntityKey(examKey)
.withExec(action -> showClientConnection(action, dialog, t))
.create())
.compose(pageContext);
}
private PageAction showClientConnection(
final PageAction pageAction,
final ModalInputDialog<Void> dialog,
final EntityTable<ClientConnection> table) {
final ClientConnection singleSelectedROWData = table.getSingleSelectedROWData();
dialog.close();
return pageAction
.withEntityKey(new EntityKey(
singleSelectedROWData.id,
EntityType.CLIENT_CONNECTION))
.withAttribute(
Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
singleSelectedROWData.getConnectionToken());
}
}

View file

@ -125,16 +125,18 @@ public class MonitoringRunningExam implements TemplateComposer {
private final ResourceService resourceService; private final ResourceService resourceService;
private final InstructionProcessor instructionProcessor; private final InstructionProcessor instructionProcessor;
private final GuiServiceInfo guiServiceInfo; private final GuiServiceInfo guiServiceInfo;
private final MonitoringExamSearchPopup monitoringExamSearchPopup;
private final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup;
private final long pollInterval; private final long pollInterval;
private final long proctoringRoomUpdateInterval; private final long proctoringRoomUpdateInterval;
private final String remoteProctoringEndpoint; private final String remoteProctoringEndpoint;
private final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup;
protected MonitoringRunningExam( protected MonitoringRunningExam(
final ServerPushService serverPushService, final ServerPushService serverPushService,
final PageService pageService, final PageService pageService,
final InstructionProcessor instructionProcessor, final InstructionProcessor instructionProcessor,
final GuiServiceInfo guiServiceInfo, final GuiServiceInfo guiServiceInfo,
final MonitoringExamSearchPopup monitoringExamSearchPopup,
final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup, final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup,
@Value("${sebserver.gui.webservice.poll-interval:1000}") final long pollInterval, @Value("${sebserver.gui.webservice.poll-interval:1000}") final long pollInterval,
@Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint, @Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint,
@ -146,6 +148,7 @@ public class MonitoringRunningExam implements TemplateComposer {
this.instructionProcessor = instructionProcessor; this.instructionProcessor = instructionProcessor;
this.guiServiceInfo = guiServiceInfo; this.guiServiceInfo = guiServiceInfo;
this.pollInterval = pollInterval; this.pollInterval = pollInterval;
this.monitoringExamSearchPopup = monitoringExamSearchPopup;
this.remoteProctoringEndpoint = remoteProctoringEndpoint; this.remoteProctoringEndpoint = remoteProctoringEndpoint;
this.proctorRoomConnectionsPopup = proctorRoomConnectionsPopup; this.proctorRoomConnectionsPopup = proctorRoomConnectionsPopup;
this.proctoringRoomUpdateInterval = proctoringRoomUpdateInterval; this.proctoringRoomUpdateInterval = proctoringRoomUpdateInterval;
@ -243,6 +246,12 @@ public class MonitoringRunningExam implements TemplateComposer {
.noEventPropagation() .noEventPropagation()
.publishIf(privilege) .publishIf(privilege)
.newAction(ActionDefinition.MONITORING_EXAM_SEARCH_CONNECTIONS)
.withEntityKey(entityKey)
.withExec(this::openSearchPopup)
.noEventPropagation()
.publishIf(privilege)
.newAction(ActionDefinition.MONITOR_EXAM_QUIT_SELECTED) .newAction(ActionDefinition.MONITOR_EXAM_QUIT_SELECTED)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withConfirm(() -> CONFIRM_QUIT_SELECTED) .withConfirm(() -> CONFIRM_QUIT_SELECTED)
@ -391,6 +400,11 @@ public class MonitoringRunningExam implements TemplateComposer {
return townhall != null && townhall.id != null; return townhall != null && townhall.id != null;
} }
private PageAction openSearchPopup(final PageAction action) {
this.monitoringExamSearchPopup.show(action.pageContext());
return action;
}
private PageAction toggleTownhallRoom(final PageAction action) { private PageAction toggleTownhallRoom(final PageAction action) {
if (isTownhallRoomActive(action.getEntityKey().modelId)) { if (isTownhallRoomActive(action.getEntityKey().modelId)) {
closeTownhallRoom(action); closeTownhallRoom(action);

View file

@ -699,6 +699,17 @@ public enum ActionDefinition {
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM, PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.FILTER), ActionCategory.FILTER),
MONITORING_EXAM_SEARCH_CONNECTIONS(
new LocTextKey("sebserver.monitoring.search.action"),
ImageIcon.SEARCH,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.FORM),
MONITORING_EXAM_SEARCH_VIEW_CONNECTION(
new LocTextKey("sebserver.monitoring.search.action.view"),
ImageIcon.SEARCH,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.CLIENT_EVENT_LIST),
MONITOR_EXAM_NEW_PROCTOR_ROOM( MONITOR_EXAM_NEW_PROCTOR_ROOM(
new LocTextKey("sebserver.monitoring.exam.action.newroom"), new LocTextKey("sebserver.monitoring.exam.action.newroom"),
ImageIcon.VISIBILITY, ImageIcon.VISIBILITY,

View file

@ -170,16 +170,16 @@ public class ModalInputDialog<T> extends Dialog {
final Consumer<PageContext> contentComposer) { final Consumer<PageContext> contentComposer) {
// Create the info dialog window // Create the info dialog window
final Shell shell = new Shell(getParent(), getStyle()); this.shell = new Shell(getParent(), getStyle());
shell.setText(getText()); this.shell.setText(getText());
shell.setData(RWT.CUSTOM_VARIANT, CustomVariant.MESSAGE.key); this.shell.setData(RWT.CUSTOM_VARIANT, CustomVariant.MESSAGE.key);
shell.setText(this.widgetFactory.getI18nSupport().getText(title)); this.shell.setText(this.widgetFactory.getI18nSupport().getText(title));
shell.setLayout(new GridLayout()); this.shell.setLayout(new GridLayout());
final GridData gridData2 = new GridData(SWT.FILL, SWT.TOP, true, true); final GridData gridData2 = new GridData(SWT.FILL, SWT.TOP, true, true);
shell.setLayoutData(gridData2); this.shell.setLayoutData(gridData2);
final Composite main = new Composite(shell, SWT.NONE); final Composite main = new Composite(this.shell, SWT.NONE);
main.setLayout(new GridLayout()); main.setLayout(new GridLayout());
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true); final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
gridData.widthHint = this.dialogWidth; gridData.widthHint = this.dialogWidth;
@ -188,13 +188,19 @@ public class ModalInputDialog<T> extends Dialog {
contentComposer.accept(pageContext.copyOf(main)); contentComposer.accept(pageContext.copyOf(main));
gridData.heightHint = calcDialogHeight(main); gridData.heightHint = calcDialogHeight(main);
final Button close = this.widgetFactory.buttonLocalized(shell, CLOSE_TEXT_KEY); final Button close = this.widgetFactory.buttonLocalized(this.shell, CLOSE_TEXT_KEY);
final GridData data = new GridData(GridData.HORIZONTAL_ALIGN_CENTER); final GridData data = new GridData(GridData.HORIZONTAL_ALIGN_CENTER);
data.widthHint = this.buttonWidth; data.widthHint = this.buttonWidth;
close.setLayoutData(data); close.setLayoutData(data);
close.addListener(SWT.Selection, event -> shell.close()); close.addListener(SWT.Selection, event -> this.shell.close());
finishUp(shell); finishUp(this.shell);
}
public void close() {
if (this.shell != null) {
this.shell.close();
}
} }
private void finishUp(final Shell shell) { private void finishUp(final Shell shell) {

View file

@ -0,0 +1,41 @@
/*
* 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.model.Page;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class GetClientConnectionPage extends RestCall<Page<ClientConnection>> {
public GetClientConnectionPage() {
super(new TypeKey<>(
CallType.GET_PAGE,
EntityType.CLIENT_CONNECTION,
new TypeReference<Page<ClientConnection>>() {
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.SEB_CLIENT_CONNECTION_ENDPOINT);
}
}

View file

@ -205,6 +205,14 @@ public class FilterMap extends POSTMapper {
return getString(ClientConnection.FILTER_ATTR_STATUS); return getString(ClientConnection.FILTER_ATTR_STATUS);
} }
public String getClientConnectionUserId() {
return getSQLWildcard(ClientConnection.FILTER_ATTR_SESSION_ID);
}
public String getClientConnectionIPAddress() {
return getSQLWildcard(ClientConnection.FILTER_ATTR_IP_STRING);
}
public Long getClientEventConnectionId() { public Long getClientEventConnectionId() {
return getLong(ClientEvent.FILTER_ATTR_CONNECTION_ID); return getLong(ClientEvent.FILTER_ATTR_CONNECTION_ID);
} }

View file

@ -99,6 +99,12 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
.and( .and(
ClientConnectionRecordDynamicSqlSupport.status, ClientConnectionRecordDynamicSqlSupport.status,
isEqualToWhenPresent(filterMap.getClientConnectionStatus())) isEqualToWhenPresent(filterMap.getClientConnectionStatus()))
.and(
ClientConnectionRecordDynamicSqlSupport.examUserSessionId,
isLikeWhenPresent(filterMap.getClientConnectionUserId()))
.and(
ClientConnectionRecordDynamicSqlSupport.clientAddress,
isLikeWhenPresent(filterMap.getClientConnectionIPAddress()))
.build() .build()
.execute() .execute()
.stream() .stream()

View file

@ -118,7 +118,11 @@ public class LmsAPIServiceImpl implements LmsAPIService {
return testCourseAccessAPI; return testCourseAccessAPI;
} }
return template.testCourseRestrictionAPI(); if (template.lmsSetup().getLmsType().features.contains(LmsSetup.Features.SEB_RESTRICTION)) {
return template.testCourseRestrictionAPI();
} else {
return LmsSetupTestResult.ofQuizRestrictionNotAvailable();
}
} }
@Override @Override

View file

@ -149,7 +149,7 @@ public class MoodleCourseAccess extends CourseAccess {
if (restTemplateRequest.hasError()) { if (restTemplateRequest.hasError()) {
final String message = "Failed to gain access token from Moodle Rest API:\n tried token endpoints: " + final String message = "Failed to gain access token from Moodle Rest API:\n tried token endpoints: " +
this.moodleRestTemplateFactory.knownTokenAccessPaths; this.moodleRestTemplateFactory.knownTokenAccessPaths;
log.error(message, restTemplateRequest.getError().getMessage()); log.error(message + " cause: ", restTemplateRequest.getError());
return LmsSetupTestResult.ofTokenRequestError(message); return LmsSetupTestResult.ofTokenRequestError(message);
} }
@ -254,13 +254,15 @@ public class MoodleCourseAccess extends CourseAccess {
CourseQuizData.class); CourseQuizData.class);
final Map<String, CourseData> finalCourseDataRef = courseData; final Map<String, CourseData> finalCourseDataRef = courseData;
courseQuizData.quizzes if (courseQuizData.quizzes != null) {
.forEach(quiz -> { courseQuizData.quizzes
final CourseData course = finalCourseDataRef.get(quiz.course); .forEach(quiz -> {
if (course != null) { final CourseData course = finalCourseDataRef.get(quiz.course);
course.quizzes.add(quiz); if (course != null) {
} course.quizzes.add(quiz);
}); }
});
}
return courseData.values() return courseData.values()
.stream() .stream()
@ -273,12 +275,17 @@ public class MoodleCourseAccess extends CourseAccess {
} }
private Predicate<CourseData> getCourseFilter(final long from) { private Predicate<CourseData> getCourseFilter(final long from) {
final long now = DateTime.now(DateTimeZone.UTC).getMillis(); final long now = DateTime.now(DateTimeZone.UTC).getMillis() / 1000;
return course -> course.time_created == null return course -> {
|| course.time_created.longValue() > from if (course.end_date != null && course.end_date > 0 && course.end_date < now) {
|| (course.end_date == null return false;
|| (course.end_date <= 0 }
|| course.end_date > now)); if (course.time_created != null && course.time_created.longValue() < from) {
return false;
}
return true;
};
} }
private Collection<CourseData> getCoursesPage( private Collection<CourseData> getCoursesPage(
@ -287,7 +294,7 @@ public class MoodleCourseAccess extends CourseAccess {
final int size) throws JsonParseException, JsonMappingException, IOException { final int size) throws JsonParseException, JsonMappingException, IOException {
try { try {
final long aYearAgo = DateTime.now(DateTimeZone.UTC).minusYears(1).getMillis(); final long aYearAgo = DateTime.now(DateTimeZone.UTC).minusYears(1).getMillis() / 1000;
// get course ids per page // get course ids per page
final LinkedMultiValueMap<String, String> attributes = new LinkedMultiValueMap<>(); final LinkedMultiValueMap<String, String> attributes = new LinkedMultiValueMap<>();
attributes.add(MOODLE_COURSE_API_SEARCH_CRITERIA_NAME, "search"); attributes.add(MOODLE_COURSE_API_SEARCH_CRITERIA_NAME, "search");

View file

@ -64,7 +64,6 @@ public class MoodleCourseRestriction {
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION_CREATE = "seb_restriction_create"; private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION_CREATE = "seb_restriction_create";
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION_UPDATE = "seb_restriction_update"; private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION_UPDATE = "seb_restriction_update";
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION_DELETE = "seb_restriction_delete"; private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_WS_FUNCTION_DELETE = "seb_restriction_delete";
//private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_COURSE_ID = "courseId";
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_SHORT_NAME = "shortname"; private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_SHORT_NAME = "shortname";
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_ID_NUMBER = "idnumber"; private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_ID_NUMBER = "idnumber";
private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_QUIZ_ID = "quizId"; private static final String MOODLE_DEFAULT_COURSE_RESTRICTION_QUIZ_ID = "quizId";

View file

@ -29,6 +29,7 @@ import ch.ethz.seb.sebserver.gbl.model.user.ExamineeAccountDetails;
import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
public class MoodleLmsAPITemplate implements LmsAPITemplate { public class MoodleLmsAPITemplate implements LmsAPITemplate {
@ -60,7 +61,8 @@ public class MoodleLmsAPITemplate implements LmsAPITemplate {
@Override @Override
public LmsSetupTestResult testCourseRestrictionAPI() { public LmsSetupTestResult testCourseRestrictionAPI() {
return this.moodleCourseRestriction.initAPIAccess(); throw new NoSEBRestrictionException();
//return this.moodleCourseRestriction.initAPIAccess();
} }
@Override @Override

View file

@ -20,6 +20,8 @@ import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@ -50,6 +52,8 @@ import ch.ethz.seb.sebserver.gbl.util.Utils;
class MoodleRestTemplateFactory { class MoodleRestTemplateFactory {
private static final Logger log = LoggerFactory.getLogger(MoodleRestTemplateFactory.class);
final JSONMapper jsonMapper; final JSONMapper jsonMapper;
final LmsSetup lmsSetup; final LmsSetup lmsSetup;
final ClientCredentials credentials; final ClientCredentials credentials;
@ -113,6 +117,12 @@ class MoodleRestTemplateFactory {
return this.knownTokenAccessPaths return this.knownTokenAccessPaths
.stream() .stream()
.map(this::createRestTemplate) .map(this::createRestTemplate)
.map(result -> {
if (result.hasError()) {
log.error("Failed to get access token: ", result.getError());
}
return result;
})
.filter(Result::hasValue) .filter(Result::hasValue)
.findFirst() .findFirst()
.orElse(Result.ofRuntimeError( .orElse(Result.ofRuntimeError(

View file

@ -61,7 +61,7 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato
return errors.doubleValue(); return errors.doubleValue();
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to get indicator count from persistent storage: ", e); log.error("Failed to get indicator count from persistent storage: ", e);
return this.currentValue; return 0;
} }
} }

View file

@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator;
import static org.mybatis.dynamic.sql.SqlBuilder.*; import static org.mybatis.dynamic.sql.SqlBuilder.*;
import java.math.BigDecimal;
import java.util.List; import java.util.List;
import org.mybatis.dynamic.sql.SqlBuilder; import org.mybatis.dynamic.sql.SqlBuilder;
@ -64,12 +65,17 @@ public abstract class AbstractLogNumberIndicator extends AbstractLogIndicator {
.execute(); .execute();
if (execute == null || execute.isEmpty()) { if (execute == null || execute.isEmpty()) {
return this.currentValue; return 0;
} }
return execute.get(execute.size() - 1).getNumericValue().doubleValue(); final BigDecimal numericValue = execute.get(execute.size() - 1).getNumericValue();
if (numericValue != null) {
return numericValue.doubleValue();
} else {
return 0;
}
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to get indicator number from persistent storage: ", e); log.error("Failed to get indicator number from persistent storage: {}", e.getMessage());
return this.currentValue; return this.currentValue;
} }
} }

View file

@ -1498,6 +1498,12 @@ sebserver.monitoring.connection.form.status.tooltip=The current connection statu
sebserver.monitoring.connection.form.exam=Exam sebserver.monitoring.connection.form.exam=Exam
sebserver.monitoring.connection.form.exam.tooltip=The exam name sebserver.monitoring.connection.form.exam.tooltip=The exam name
sebserver.monitoring.search.title=Search Connections
sebserver.monitoring.search.action=Search
sebserver.monitoring.search.list.empty=No Client Connections available
sebserver.monitoring.search.list.name=Session or User Name
sebserver.monitoring.exam.connection.emptySelection=At first please select a Connection from the list sebserver.monitoring.exam.connection.emptySelection=At first please select a Connection from the list
sebserver.monitoring.exam.connection.emptySelection.active=At first please select an active Connection from the list sebserver.monitoring.exam.connection.emptySelection.active=At first please select an active Connection from the list
sebserver.monitoring.exam.connection.title=SEB Client Connection sebserver.monitoring.exam.connection.title=SEB Client Connection