fixed various issues
This commit is contained in:
		
							parent
							
								
									873391394a
								
							
						
					
					
						commit
						4c002b4ac2
					
				
					 19 changed files with 270 additions and 35 deletions
				
			
		| 
						 | 
				
			
			@ -46,7 +46,7 @@ public final class LmsSetup implements GrantEntity, Activatable {
 | 
			
		|||
    public enum LmsType {
 | 
			
		||||
        MOCKUP(Features.COURSE_API),
 | 
			
		||||
        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;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,6 +27,7 @@ public final class LmsSetupTestResult {
 | 
			
		|||
    public static final String ATTR_MISSING_ATTRIBUTE = "missingLMSSetupAttribute";
 | 
			
		||||
 | 
			
		||||
    public enum ErrorType {
 | 
			
		||||
        FEATURE_NOT_AVAILABLE,
 | 
			
		||||
        MISSING_ATTRIBUTE,
 | 
			
		||||
        TOKEN_REQUEST,
 | 
			
		||||
        QUIZ_ACCESS_API_REQUEST,
 | 
			
		||||
| 
						 | 
				
			
			@ -118,6 +119,10 @@ public final class LmsSetupTestResult {
 | 
			
		|||
        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 {
 | 
			
		||||
 | 
			
		||||
        @JsonProperty(ATTR_ERROR_TYPE)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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_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_IP_STRING = Domain.CLIENT_CONNECTION.ATTR_CLIENT_ADDRESS;
 | 
			
		||||
 | 
			
		||||
    @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ID)
 | 
			
		||||
    public final Long id;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -474,7 +474,8 @@ public class ExamForm implements TemplateComposer {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -125,16 +125,18 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
    private final ResourceService resourceService;
 | 
			
		||||
    private final InstructionProcessor instructionProcessor;
 | 
			
		||||
    private final GuiServiceInfo guiServiceInfo;
 | 
			
		||||
    private final MonitoringExamSearchPopup monitoringExamSearchPopup;
 | 
			
		||||
    private final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup;
 | 
			
		||||
    private final long pollInterval;
 | 
			
		||||
    private final long proctoringRoomUpdateInterval;
 | 
			
		||||
    private final String remoteProctoringEndpoint;
 | 
			
		||||
    private final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup;
 | 
			
		||||
 | 
			
		||||
    protected MonitoringRunningExam(
 | 
			
		||||
            final ServerPushService serverPushService,
 | 
			
		||||
            final PageService pageService,
 | 
			
		||||
            final InstructionProcessor instructionProcessor,
 | 
			
		||||
            final GuiServiceInfo guiServiceInfo,
 | 
			
		||||
            final MonitoringExamSearchPopup monitoringExamSearchPopup,
 | 
			
		||||
            final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup,
 | 
			
		||||
            @Value("${sebserver.gui.webservice.poll-interval:1000}") final long pollInterval,
 | 
			
		||||
            @Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint,
 | 
			
		||||
| 
						 | 
				
			
			@ -146,6 +148,7 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
        this.instructionProcessor = instructionProcessor;
 | 
			
		||||
        this.guiServiceInfo = guiServiceInfo;
 | 
			
		||||
        this.pollInterval = pollInterval;
 | 
			
		||||
        this.monitoringExamSearchPopup = monitoringExamSearchPopup;
 | 
			
		||||
        this.remoteProctoringEndpoint = remoteProctoringEndpoint;
 | 
			
		||||
        this.proctorRoomConnectionsPopup = proctorRoomConnectionsPopup;
 | 
			
		||||
        this.proctoringRoomUpdateInterval = proctoringRoomUpdateInterval;
 | 
			
		||||
| 
						 | 
				
			
			@ -243,6 +246,12 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
                .noEventPropagation()
 | 
			
		||||
                .publishIf(privilege)
 | 
			
		||||
 | 
			
		||||
                .newAction(ActionDefinition.MONITORING_EXAM_SEARCH_CONNECTIONS)
 | 
			
		||||
                .withEntityKey(entityKey)
 | 
			
		||||
                .withExec(this::openSearchPopup)
 | 
			
		||||
                .noEventPropagation()
 | 
			
		||||
                .publishIf(privilege)
 | 
			
		||||
 | 
			
		||||
                .newAction(ActionDefinition.MONITOR_EXAM_QUIT_SELECTED)
 | 
			
		||||
                .withEntityKey(entityKey)
 | 
			
		||||
                .withConfirm(() -> CONFIRM_QUIT_SELECTED)
 | 
			
		||||
| 
						 | 
				
			
			@ -391,6 +400,11 @@ public class MonitoringRunningExam implements TemplateComposer {
 | 
			
		|||
        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) {
 | 
			
		||||
        if (isTownhallRoomActive(action.getEntityKey().modelId)) {
 | 
			
		||||
            closeTownhallRoom(action);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -699,6 +699,17 @@ public enum ActionDefinition {
 | 
			
		|||
            PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
 | 
			
		||||
            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(
 | 
			
		||||
            new LocTextKey("sebserver.monitoring.exam.action.newroom"),
 | 
			
		||||
            ImageIcon.VISIBILITY,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -170,16 +170,16 @@ public class ModalInputDialog<T> extends Dialog {
 | 
			
		|||
            final Consumer<PageContext> contentComposer) {
 | 
			
		||||
 | 
			
		||||
        // Create the info dialog window
 | 
			
		||||
        final Shell shell = new Shell(getParent(), getStyle());
 | 
			
		||||
        shell.setText(getText());
 | 
			
		||||
        shell.setData(RWT.CUSTOM_VARIANT, CustomVariant.MESSAGE.key);
 | 
			
		||||
        shell.setText(this.widgetFactory.getI18nSupport().getText(title));
 | 
			
		||||
        shell.setLayout(new GridLayout());
 | 
			
		||||
        this.shell = new Shell(getParent(), getStyle());
 | 
			
		||||
        this.shell.setText(getText());
 | 
			
		||||
        this.shell.setData(RWT.CUSTOM_VARIANT, CustomVariant.MESSAGE.key);
 | 
			
		||||
        this.shell.setText(this.widgetFactory.getI18nSupport().getText(title));
 | 
			
		||||
        this.shell.setLayout(new GridLayout());
 | 
			
		||||
        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());
 | 
			
		||||
        final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, true);
 | 
			
		||||
        gridData.widthHint = this.dialogWidth;
 | 
			
		||||
| 
						 | 
				
			
			@ -188,13 +188,19 @@ public class ModalInputDialog<T> extends Dialog {
 | 
			
		|||
        contentComposer.accept(pageContext.copyOf(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);
 | 
			
		||||
        data.widthHint = this.buttonWidth;
 | 
			
		||||
        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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -205,6 +205,14 @@ public class FilterMap extends POSTMapper {
 | 
			
		|||
        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() {
 | 
			
		||||
        return getLong(ClientEvent.FILTER_ATTR_CONNECTION_ID);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -99,6 +99,12 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
 | 
			
		|||
                .and(
 | 
			
		||||
                        ClientConnectionRecordDynamicSqlSupport.status,
 | 
			
		||||
                        isEqualToWhenPresent(filterMap.getClientConnectionStatus()))
 | 
			
		||||
                .and(
 | 
			
		||||
                        ClientConnectionRecordDynamicSqlSupport.examUserSessionId,
 | 
			
		||||
                        isLikeWhenPresent(filterMap.getClientConnectionUserId()))
 | 
			
		||||
                .and(
 | 
			
		||||
                        ClientConnectionRecordDynamicSqlSupport.clientAddress,
 | 
			
		||||
                        isLikeWhenPresent(filterMap.getClientConnectionIPAddress()))
 | 
			
		||||
                .build()
 | 
			
		||||
                .execute()
 | 
			
		||||
                .stream()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -118,7 +118,11 @@ public class LmsAPIServiceImpl implements LmsAPIService {
 | 
			
		|||
            return testCourseAccessAPI;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return template.testCourseRestrictionAPI();
 | 
			
		||||
        if (template.lmsSetup().getLmsType().features.contains(LmsSetup.Features.SEB_RESTRICTION)) {
 | 
			
		||||
            return template.testCourseRestrictionAPI();
 | 
			
		||||
        } else {
 | 
			
		||||
            return LmsSetupTestResult.ofQuizRestrictionNotAvailable();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -149,7 +149,7 @@ public class MoodleCourseAccess extends CourseAccess {
 | 
			
		|||
        if (restTemplateRequest.hasError()) {
 | 
			
		||||
            final String message = "Failed to gain access token from Moodle Rest API:\n tried token endpoints: " +
 | 
			
		||||
                    this.moodleRestTemplateFactory.knownTokenAccessPaths;
 | 
			
		||||
            log.error(message, restTemplateRequest.getError().getMessage());
 | 
			
		||||
            log.error(message + " cause: ", restTemplateRequest.getError());
 | 
			
		||||
            return LmsSetupTestResult.ofTokenRequestError(message);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -254,13 +254,15 @@ public class MoodleCourseAccess extends CourseAccess {
 | 
			
		|||
                    CourseQuizData.class);
 | 
			
		||||
 | 
			
		||||
            final Map<String, CourseData> finalCourseDataRef = courseData;
 | 
			
		||||
            courseQuizData.quizzes
 | 
			
		||||
                    .forEach(quiz -> {
 | 
			
		||||
                        final CourseData course = finalCourseDataRef.get(quiz.course);
 | 
			
		||||
                        if (course != null) {
 | 
			
		||||
                            course.quizzes.add(quiz);
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
            if (courseQuizData.quizzes != null) {
 | 
			
		||||
                courseQuizData.quizzes
 | 
			
		||||
                        .forEach(quiz -> {
 | 
			
		||||
                            final CourseData course = finalCourseDataRef.get(quiz.course);
 | 
			
		||||
                            if (course != null) {
 | 
			
		||||
                                course.quizzes.add(quiz);
 | 
			
		||||
                            }
 | 
			
		||||
                        });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return courseData.values()
 | 
			
		||||
                    .stream()
 | 
			
		||||
| 
						 | 
				
			
			@ -273,12 +275,17 @@ public class MoodleCourseAccess extends CourseAccess {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    private Predicate<CourseData> getCourseFilter(final long from) {
 | 
			
		||||
        final long now = DateTime.now(DateTimeZone.UTC).getMillis();
 | 
			
		||||
        return course -> course.time_created == null
 | 
			
		||||
                || course.time_created.longValue() > from
 | 
			
		||||
                || (course.end_date == null
 | 
			
		||||
                        || (course.end_date <= 0
 | 
			
		||||
                                || course.end_date > now));
 | 
			
		||||
        final long now = DateTime.now(DateTimeZone.UTC).getMillis() / 1000;
 | 
			
		||||
        return course -> {
 | 
			
		||||
            if (course.end_date != null && course.end_date > 0 && course.end_date < now) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            if (course.time_created != null && course.time_created.longValue() < from) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Collection<CourseData> getCoursesPage(
 | 
			
		||||
| 
						 | 
				
			
			@ -287,7 +294,7 @@ public class MoodleCourseAccess extends CourseAccess {
 | 
			
		|||
            final int size) throws JsonParseException, JsonMappingException, IOException {
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
            final LinkedMultiValueMap<String, String> attributes = new LinkedMultiValueMap<>();
 | 
			
		||||
            attributes.add(MOODLE_COURSE_API_SEARCH_CRITERIA_NAME, "search");
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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_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_COURSE_ID = "courseId";
 | 
			
		||||
    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_QUIZ_ID = "quizId";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.webservice.servicelayer.dao.FilterMap;
 | 
			
		||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate;
 | 
			
		||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.NoSEBRestrictionException;
 | 
			
		||||
 | 
			
		||||
public class MoodleLmsAPITemplate implements LmsAPITemplate {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -60,7 +61,8 @@ public class MoodleLmsAPITemplate implements LmsAPITemplate {
 | 
			
		|||
 | 
			
		||||
    @Override
 | 
			
		||||
    public LmsSetupTestResult testCourseRestrictionAPI() {
 | 
			
		||||
        return this.moodleCourseRestriction.initAPIAccess();
 | 
			
		||||
        throw new NoSEBRestrictionException();
 | 
			
		||||
        //return this.moodleCourseRestriction.initAPIAccess();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,8 @@ import java.util.function.Function;
 | 
			
		|||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
import org.apache.commons.lang3.StringUtils;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import org.springframework.http.HttpEntity;
 | 
			
		||||
import org.springframework.http.HttpHeaders;
 | 
			
		||||
import org.springframework.http.HttpMethod;
 | 
			
		||||
| 
						 | 
				
			
			@ -50,6 +52,8 @@ import ch.ethz.seb.sebserver.gbl.util.Utils;
 | 
			
		|||
 | 
			
		||||
class MoodleRestTemplateFactory {
 | 
			
		||||
 | 
			
		||||
    private static final Logger log = LoggerFactory.getLogger(MoodleRestTemplateFactory.class);
 | 
			
		||||
 | 
			
		||||
    final JSONMapper jsonMapper;
 | 
			
		||||
    final LmsSetup lmsSetup;
 | 
			
		||||
    final ClientCredentials credentials;
 | 
			
		||||
| 
						 | 
				
			
			@ -113,6 +117,12 @@ class MoodleRestTemplateFactory {
 | 
			
		|||
        return this.knownTokenAccessPaths
 | 
			
		||||
                .stream()
 | 
			
		||||
                .map(this::createRestTemplate)
 | 
			
		||||
                .map(result -> {
 | 
			
		||||
                    if (result.hasError()) {
 | 
			
		||||
                        log.error("Failed to get access token: ", result.getError());
 | 
			
		||||
                    }
 | 
			
		||||
                    return result;
 | 
			
		||||
                })
 | 
			
		||||
                .filter(Result::hasValue)
 | 
			
		||||
                .findFirst()
 | 
			
		||||
                .orElse(Result.ofRuntimeError(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -61,7 +61,7 @@ public abstract class AbstractLogLevelCountIndicator extends AbstractLogIndicato
 | 
			
		|||
            return errors.doubleValue();
 | 
			
		||||
        } catch (final Exception e) {
 | 
			
		||||
            log.error("Failed to get indicator count from persistent storage: ", e);
 | 
			
		||||
            return this.currentValue;
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.indicator;
 | 
			
		|||
 | 
			
		||||
import static org.mybatis.dynamic.sql.SqlBuilder.*;
 | 
			
		||||
 | 
			
		||||
import java.math.BigDecimal;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import org.mybatis.dynamic.sql.SqlBuilder;
 | 
			
		||||
| 
						 | 
				
			
			@ -64,12 +65,17 @@ public abstract class AbstractLogNumberIndicator extends AbstractLogIndicator {
 | 
			
		|||
                    .execute();
 | 
			
		||||
 | 
			
		||||
            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) {
 | 
			
		||||
            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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.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.active=At first please select an active Connection from the list
 | 
			
		||||
sebserver.monitoring.exam.connection.title=SEB Client Connection
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue