From 5f9a2c6fe056e656d41cf3c2618ed87b5319c776 Mon Sep 17 00:00:00 2001 From: anhefti Date: Mon, 8 Jul 2019 14:54:17 +0200 Subject: [PATCH] SEBSERV-63 monitoring and running exam list page --- .../seb/sebserver/gui/content/ExamList.java | 30 ++-- .../gui/content/InstitutionList.java | 8 +- .../sebserver/gui/content/LmsSetupList.java | 12 +- .../gui/content/MonitoringRunningExam.java | 33 +++++ .../content/MonitoringRunningExamList.java | 137 ++++++++++++++++++ .../gui/content/QuizDiscoveryList.java | 4 +- .../gui/content/SebClientConfigList.java | 10 +- .../gui/content/SebExamConfigList.java | 2 +- .../gui/content/UserAccountList.java | 13 +- .../gui/content/action/ActionCategory.java | 1 + .../gui/content/action/ActionDefinition.java | 9 +- .../gui/content/activity/ActivitiesPane.java | 11 ++ .../content/activity/ActivityDefinition.java | 3 +- .../content/activity/PageStateDefinition.java | 7 +- .../gui/service/ResourceService.java | 33 +++-- .../api/session/GetRunningExamPage.java | 41 ++++++ .../seb/sebserver/gui/table/TableFilter.java | 5 +- .../servicelayer/PaginationServiceImpl.java | 3 + .../session/ExamSessionService.java | 13 +- .../session/impl/ExamSessionServiceImpl.java | 14 ++ .../weblayer/api/APIExceptionHandler.java | 1 + .../weblayer/api/EntityController.java | 3 +- .../api/ExamAdministrationController.java | 69 +++++---- .../weblayer/api/ExamMonitiroingEndpoint.java | 17 --- .../api/ExamMonitoringController.java | 135 +++++++++++++++++ src/main/resources/messages.properties | 24 ++- src/main/resources/static/css/sebserver.css | 4 +- 27 files changed, 546 insertions(+), 96 deletions(-) create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExamList.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetRunningExamPage.java delete mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitiroingEndpoint.java create mode 100644 src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamList.java index 91e5e4a9..999eb7b8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamList.java @@ -17,7 +17,6 @@ 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.EntityType; import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; @@ -49,6 +48,8 @@ import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; @GuiProfile public class ExamList implements TemplateComposer { + private static final LocTextKey PAGE_TITLE_KEY = + new LocTextKey("sebserver.exam.list.title"); private static final LocTextKey NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUION = new LocTextKey("sebserver.exam.list.action.no.modify.privilege"); private final static LocTextKey EMPTY_SELECTION_TEXT_KEY = @@ -69,6 +70,7 @@ public class ExamList implements TemplateComposer { new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME); private final TableFilterAttribute startTimeFilter = new TableFilterAttribute(CriteriaType.DATE, QuizData.FILTER_ATTR_START_TIME); + private final TableFilterAttribute typeFilter; private final PageService pageService; private final ResourceService resourceService; @@ -77,16 +79,21 @@ public class ExamList implements TemplateComposer { protected ExamList( final PageService pageService, final ResourceService resourceService, - @Value("${sebserver.gui.list.page.size}") final Integer pageSize) { + @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) { this.pageService = pageService; this.resourceService = resourceService; - this.pageSize = (pageSize != null) ? pageSize : 20; + this.pageSize = pageSize; this.lmsFilter = new TableFilterAttribute( CriteriaType.SINGLE_SELECTION, LmsSetup.FILTER_ATTR_LMS_SETUP, this.resourceService::lmsSetupResource); + + this.typeFilter = new TableFilterAttribute( + CriteriaType.SINGLE_SELECTION, + Exam.FILTER_ATTR_TYPE, + this.resourceService::examTypeResources); } @Override @@ -100,9 +107,10 @@ public class ExamList implements TemplateComposer { // content page layout with title final Composite content = widgetFactory.defaultPageLayout( pageContext.getParent(), - new LocTextKey("sebserver.exam.list.title")); + PAGE_TITLE_KEY); - final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(pageContext.clearEntityKeys()); + final PageActionBuilder actionBuilder = this.pageService + .pageActionBuilder(pageContext.clearEntityKeys()); // table final EntityTable table = @@ -132,7 +140,8 @@ public class ExamList implements TemplateComposer { .withColumn(new ColumnDefinition<>( Domain.EXAM.ATTR_TYPE, COLUMN_TITLE_TYPE_KEY, - this::examTypeName) + this.resourceService::examTypeName) + .withFilter(this.typeFilter) .sortable()) .withDefaultAction(actionBuilder .newAction(ActionDefinition.EXAM_VIEW_FROM_LIST) @@ -177,13 +186,4 @@ public class ExamList implements TemplateComposer { .apply(String.valueOf(exam.lmsSetupId)); } - private String examTypeName(final Exam exam) { - if (exam.type == null) { - return Constants.EMPTY_NOTE; - } - - return this.resourceService.getI18nSupport() - .getText(ResourceService.EXAM_TYPE_PREFIX + exam.type.name()); - } - } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionList.java index 5b7f6a5b..dc4c4939 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/InstitutionList.java @@ -9,6 +9,7 @@ package ch.ethz.seb.sebserver.gui.content; import org.eclipse.swt.widgets.Composite; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; @@ -51,15 +52,18 @@ public class InstitutionList implements TemplateComposer { private final PageService pageService; private final RestService restService; private final CurrentUser currentUser; + private final int pageSize; protected InstitutionList( final PageService pageService, final RestService restService, - final CurrentUser currentUser) { + final CurrentUser currentUser, + @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) { this.pageService = pageService; this.restService = restService; this.currentUser = currentUser; + this.pageSize = pageSize; } @Override @@ -75,7 +79,7 @@ public class InstitutionList implements TemplateComposer { final EntityTable table = this.pageService.entityTableBuilder(this.restService.getRestCall(GetInstitutionPage.class)) .withEmptyMessage(EMPTY_LIST_TEXT_KEY) - .withPaging(3) + .withPaging(this.pageSize) .withColumn(new ColumnDefinition<>( Domain.INSTITUTION.ATTR_NAME, NAME_TEXT_KEY, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupList.java index b3d30af8..ed59bf0d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/LmsSetupList.java @@ -20,6 +20,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; +import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; 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; @@ -66,6 +67,7 @@ public class LmsSetupList implements TemplateComposer { private final TableFilterAttribute nameFilter = new TableFilterAttribute(CriteriaType.TEXT, Entity.FILTER_ATTR_NAME); private final TableFilterAttribute typeFilter; + private final TableFilterAttribute activityFilter; private final PageService pageService; private final ResourceService resourceService; @@ -74,11 +76,11 @@ public class LmsSetupList implements TemplateComposer { protected LmsSetupList( final PageService pageService, final ResourceService resourceService, - @Value("${sebserver.gui.list.page.size}") final Integer pageSize) { + @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) { this.pageService = pageService; this.resourceService = resourceService; - this.pageSize = (pageSize != null) ? pageSize : 20; + this.pageSize = pageSize; this.institutionFilter = new TableFilterAttribute( CriteriaType.SINGLE_SELECTION, @@ -89,6 +91,11 @@ public class LmsSetupList implements TemplateComposer { CriteriaType.SINGLE_SELECTION, Domain.LMS_SETUP.ATTR_LMS_TYPE, this.resourceService::lmsTypeResources); + + this.activityFilter = new TableFilterAttribute( + CriteriaType.SINGLE_SELECTION, + UserInfo.FILTER_ATTR_ACTIVE, + this.resourceService::activityResources); } @Override @@ -135,6 +142,7 @@ public class LmsSetupList implements TemplateComposer { Domain.LMS_SETUP.ATTR_ACTIVE, ACTIVITY_TEXT_KEY, LmsSetup::getActive) + .withFilter(this.activityFilter) .sortable()) .withDefaultAction(actionBuilder .newAction(ActionDefinition.LMS_SETUP_VIEW_FROM_LIST) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java new file mode 100644 index 00000000..a81478d1 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019 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.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.page.PageContext; +import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; + +@Lazy +@Component +@GuiProfile +public class MonitoringRunningExam implements TemplateComposer { + + public MonitoringRunningExam() { + // TODO Auto-generated constructor stub + } + + @Override + public void compose(final PageContext pageContext) { + // TODO Auto-generated method stub + + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExamList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExamList.java new file mode 100644 index 00000000..8a3cd873 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExamList.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2019 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.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.model.Domain; +import ch.ethz.seb.sebserver.gbl.model.exam.Exam; +import ch.ethz.seb.sebserver.gbl.model.exam.QuizData; +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.I18nSupport; +import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; +import ch.ethz.seb.sebserver.gui.service.page.PageContext; +import ch.ethz.seb.sebserver.gui.service.page.PageService; +import ch.ethz.seb.sebserver.gui.service.page.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.session.GetRunningExamPage; +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.ColumnDefinition.TableFilterAttribute; +import ch.ethz.seb.sebserver.gui.table.EntityTable; +import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; + +@Lazy +@Component +@GuiProfile +public class MonitoringRunningExamList implements TemplateComposer { + + private static final LocTextKey PAGE_TITLE_KEY = + new LocTextKey("sebserver.monitoring.exam.list.title"); + private final static LocTextKey EMPTY_SELECTION_TEXT_KEY = + new LocTextKey("sebserver.monitoring.exam.info.pleaseSelect"); + private final static LocTextKey COLUMN_TITLE_NAME_KEY = + new LocTextKey("sebserver.monitoring.exam.list.column.name"); + private final static LocTextKey COLUMN_TITLE_TYPE_KEY = + new LocTextKey("sebserver.monitoring.exam.list.column.type"); + private final static LocTextKey EMPTY_LIST_TEXT_KEY = + new LocTextKey("sebserver.monitoring.exam.list.empty"); + + private final TableFilterAttribute nameFilter = + new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME); + private final TableFilterAttribute typeFilter; + + private final PageService pageService; + private final ResourceService resourceService; + private final int pageSize; + + protected MonitoringRunningExamList( + final PageService pageService, + final ResourceService resourceService, + @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) { + + this.pageService = pageService; + this.resourceService = resourceService; + this.pageSize = pageSize; + + this.typeFilter = new TableFilterAttribute( + CriteriaType.SINGLE_SELECTION, + Exam.FILTER_ATTR_TYPE, + this.resourceService::examTypeResources); + } + + @Override + public void compose(final PageContext pageContext) { + final WidgetFactory widgetFactory = this.pageService.getWidgetFactory(); + final CurrentUser currentUser = this.resourceService.getCurrentUser(); + final RestService restService = this.resourceService.getRestService(); + final I18nSupport i18nSupport = this.resourceService.getI18nSupport(); + + // content page layout with title + final Composite content = widgetFactory.defaultPageLayout( + pageContext.getParent(), + PAGE_TITLE_KEY); + + final PageActionBuilder actionBuilder = this.pageService + .pageActionBuilder(pageContext.clearEntityKeys()); + + // table + final EntityTable table = + this.pageService.entityTableBuilder(restService.getRestCall(GetRunningExamPage.class)) + .withEmptyMessage(EMPTY_LIST_TEXT_KEY) + .withPaging(this.pageSize) + .withColumn(new ColumnDefinition<>( + QuizData.QUIZ_ATTR_NAME, + COLUMN_TITLE_NAME_KEY, + Exam::getName) + .withFilter(this.nameFilter) + .sortable()) + .withColumn(new ColumnDefinition<>( + Domain.EXAM.ATTR_TYPE, + COLUMN_TITLE_TYPE_KEY, + this.resourceService::examTypeName) + .withFilter(this.typeFilter) + .sortable()) + .withColumn(new ColumnDefinition<>( + QuizData.QUIZ_ATTR_START_TIME, + new LocTextKey( + "sebserver.monitoring.exam.list.column.startTime", + i18nSupport.getUsersTimeZoneTitleSuffix()), + Exam::getStartTime) + .sortable()) + .withColumn(new ColumnDefinition<>( + QuizData.QUIZ_ATTR_END_TIME, + new LocTextKey( + "sebserver.monitoring.exam.list.column.endTime", + i18nSupport.getUsersTimeZoneTitleSuffix()), + Exam::getEndTime) + .sortable()) + .withDefaultAction(actionBuilder + .newAction(ActionDefinition.MONITOR_EXAM) + .create()) + .compose(content); + + actionBuilder + + .newAction(ActionDefinition.MONITOR_EXAM) + .withSelect(table::getSelection, PageAction::applySingleSelection, EMPTY_SELECTION_TEXT_KEY) + .publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER)); + + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizDiscoveryList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizDiscoveryList.java index 81632524..6757d880 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizDiscoveryList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/QuizDiscoveryList.java @@ -94,12 +94,12 @@ public class QuizDiscoveryList implements TemplateComposer { protected QuizDiscoveryList( final PageService pageService, final ResourceService resourceService, - @Value("${sebserver.gui.list.page.size}") final Integer pageSize) { + @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) { this.pageService = pageService; this.widgetFactory = pageService.getWidgetFactory(); this.resourceService = resourceService; - this.pageSize = (pageSize != null) ? pageSize : 20; + this.pageSize = pageSize; this.lmsFilter = new TableFilterAttribute( CriteriaType.SINGLE_SELECTION, diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientConfigList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientConfigList.java index 1f0b0c0d..6ad49655 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientConfigList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebClientConfigList.java @@ -22,6 +22,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig; +import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; 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; @@ -72,6 +73,7 @@ public class SebClientConfigList implements TemplateComposer { DateTime.now(DateTimeZone.UTC) .minusYears(1) .toString(Constants.DEFAULT_DATE_TIME_FORMAT)); + private final TableFilterAttribute activityFilter; private final PageService pageService; private final RestService restService; @@ -83,7 +85,7 @@ public class SebClientConfigList implements TemplateComposer { final PageService pageService, final RestService restService, final CurrentUser currentUser, - @Value("${sebserver.gui.list.page.size}") final Integer pageSize) { + @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) { this.pageService = pageService; this.restService = restService; @@ -95,6 +97,11 @@ public class SebClientConfigList implements TemplateComposer { CriteriaType.SINGLE_SELECTION, Entity.FILTER_ATTR_INSTITUTION, this.resourceService::institutionResource); + + this.activityFilter = new TableFilterAttribute( + CriteriaType.SINGLE_SELECTION, + UserInfo.FILTER_ATTR_ACTIVE, + this.resourceService::activityResources); } @Override @@ -139,6 +146,7 @@ public class SebClientConfigList implements TemplateComposer { Domain.SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE, ACTIVE_TEXT_KEY, SebClientConfig::getActive) + .withFilter(this.activityFilter) .sortable()) .withDefaultAction(pageActionBuilder .newAction(ActionDefinition.SEB_CLIENT_CONFIG_VIEW_FROM_LIST) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigList.java index 9cf52df1..cb652054 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/SebExamConfigList.java @@ -73,7 +73,7 @@ public class SebExamConfigList implements TemplateComposer { final PageService pageService, final RestService restService, final CurrentUser currentUser, - @Value("${sebserver.gui.list.page.size}") final Integer pageSize) { + @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) { this.pageService = pageService; this.restService = restService; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountList.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountList.java index 0c9d08e3..a222bddd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountList.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/UserAccountList.java @@ -73,6 +73,7 @@ public class UserAccountList implements TemplateComposer { private final TableFilterAttribute mailFilter = new TableFilterAttribute(CriteriaType.TEXT, UserInfo.FILTER_ATTR_EMAIL); private final TableFilterAttribute languageFilter; + private final TableFilterAttribute activityFilter; // dependencies private final PageService pageService; @@ -82,11 +83,11 @@ public class UserAccountList implements TemplateComposer { protected UserAccountList( final PageService pageService, final ResourceService resourceService, - @Value("${sebserver.gui.list.page.size}") final Integer pageSize) { + @Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) { this.pageService = pageService; this.resourceService = resourceService; - this.pageSize = (pageSize != null) ? pageSize : 20; + this.pageSize = pageSize; this.institutionFilter = new TableFilterAttribute( CriteriaType.SINGLE_SELECTION, @@ -97,6 +98,11 @@ public class UserAccountList implements TemplateComposer { CriteriaType.SINGLE_SELECTION, UserInfo.FILTER_ATTR_LANGUAGE, this.resourceService::languageResources); + + this.activityFilter = new TableFilterAttribute( + CriteriaType.SINGLE_SELECTION, + UserInfo.FILTER_ATTR_ACTIVE, + this.resourceService::activityResources); } @Override @@ -154,12 +160,13 @@ public class UserAccountList implements TemplateComposer { .withFilter(this.languageFilter) .localized() .sortable() - .widthProportion(2)) + .widthProportion(1)) .withColumn(new ColumnDefinition<>( Domain.USER.ATTR_ACTIVE, ACTIVE_TEXT_KEY, UserInfo::getActive) .sortable() + .withFilter(this.activityFilter) .widthProportion(1)) .withDefaultAction(actionBuilder .newAction(ActionDefinition.USER_ACCOUNT_VIEW_FROM_LIST) diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java index cf81b3f0..a48e18cb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionCategory.java @@ -21,6 +21,7 @@ public enum ActionCategory { INDICATOR_LIST(new LocTextKey("sebserver.exam.indicator.list.actions"), 2), SEB_CLIENT_CONFIG_LIST(new LocTextKey("sebserver.clientconfig.list.actions"), 1), SEB_EXAM_CONFIG_LIST(new LocTextKey("sebserver.examconfig.list.actions"), 1), + RUNNING_EXAM_LIST(new LocTextKey("sebserver.monitoring.exam.list.actions"), 1), VARIA(new LocTextKey("sebserver.overall.action.category.varia"), 100), ; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java index aa57ac8c..cff7ea78 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/action/ActionDefinition.java @@ -388,7 +388,14 @@ public enum ActionDefinition { PageStateDefinition.SEB_EXAM_CONFIG_EDIT, ActionCategory.SEB_EXAM_CONFIG_LIST), - ; + RUNNING_EXAM_VIEW_LIST( + new LocTextKey("sebserver.monitoring.action.list"), + PageStateDefinition.MONITORING_RUNNING_EXAM_LIST), + MONITOR_EXAM( + new LocTextKey("sebserver.monitoring.exam.action.list.view"), + ImageIcon.SHOW, + PageStateDefinition.MONITORING_RUNNING_EXAM, + ActionCategory.RUNNING_EXAM_LIST); public final LocTextKey title; public final ImageIcon icon; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java index c66d9c6f..70d3aeb8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivitiesPane.java @@ -202,7 +202,18 @@ public class ActivitiesPane implements TemplateComposer { .newAction(ActionDefinition.SEB_EXAM_CONFIG_LIST) .create()); } + } + // Monitoring exams + if (this.currentUser.get().hasRole(UserRole.EXAM_SUPPORTER)) { + final TreeItem clientConfig = this.widgetFactory.treeItemLocalized( + navigation, + ActivityDefinition.MONITORING_EXAMS.displayName); + injectActivitySelection( + clientConfig, + actionBuilder + .newAction(ActionDefinition.RUNNING_EXAM_VIEW_LIST) + .create()); } // TODO other activities diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivityDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivityDefinition.java index 2413e1e0..81e4b281 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivityDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/ActivityDefinition.java @@ -19,7 +19,8 @@ public enum ActivityDefinition implements Activity { EXAM(new LocTextKey("sebserver.exam.action.list")), SEB_CONFIGURATION(new LocTextKey("sebserver.sebconfig.activity.name")), SEB_CLIENT_CONFIG(new LocTextKey("sebserver.clientconfig.action.list")), - SEB_EXAM_CONFIG(new LocTextKey("sebserver.examconfig.action.list")); + SEB_EXAM_CONFIG(new LocTextKey("sebserver.examconfig.action.list")), + MONITORING_EXAMS(new LocTextKey("sebserver.monitoring.action.list")); public final LocTextKey displayName; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinition.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinition.java index f81e754b..1d77cc29 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinition.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/activity/PageStateDefinition.java @@ -16,12 +16,14 @@ import ch.ethz.seb.sebserver.gui.content.InstitutionForm; import ch.ethz.seb.sebserver.gui.content.InstitutionList; import ch.ethz.seb.sebserver.gui.content.LmsSetupForm; import ch.ethz.seb.sebserver.gui.content.LmsSetupList; +import ch.ethz.seb.sebserver.gui.content.MonitoringRunningExam; +import ch.ethz.seb.sebserver.gui.content.MonitoringRunningExamList; import ch.ethz.seb.sebserver.gui.content.QuizDiscoveryList; import ch.ethz.seb.sebserver.gui.content.SebClientConfigForm; import ch.ethz.seb.sebserver.gui.content.SebClientConfigList; -import ch.ethz.seb.sebserver.gui.content.SebExamConfigSettingsForm; import ch.ethz.seb.sebserver.gui.content.SebExamConfigList; import ch.ethz.seb.sebserver.gui.content.SebExamConfigPropForm; +import ch.ethz.seb.sebserver.gui.content.SebExamConfigSettingsForm; import ch.ethz.seb.sebserver.gui.content.UserAccountChangePasswordForm; import ch.ethz.seb.sebserver.gui.content.UserAccountForm; import ch.ethz.seb.sebserver.gui.content.UserAccountList; @@ -62,6 +64,9 @@ public enum PageStateDefinition implements PageState { SEB_EXAM_CONFIG_PROP_EDIT(Type.FORM_EDIT, SebExamConfigPropForm.class, ActivityDefinition.SEB_EXAM_CONFIG), SEB_EXAM_CONFIG_EDIT(Type.FORM_VIEW, SebExamConfigSettingsForm.class, ActivityDefinition.SEB_EXAM_CONFIG), + MONITORING_RUNNING_EXAM_LIST(Type.LIST_VIEW, MonitoringRunningExamList.class, ActivityDefinition.MONITORING_EXAMS), + MONITORING_RUNNING_EXAM(Type.FORM_VIEW, MonitoringRunningExam.class, ActivityDefinition.MONITORING_EXAMS) + ; public final Type type; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java index 659f3755..94dd908f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java @@ -8,6 +8,7 @@ package ch.ethz.seb.sebserver.gui.service; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -25,6 +26,7 @@ import org.springframework.stereotype.Service; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.EntityName; +import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType; import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; @@ -93,6 +95,13 @@ public class ResourceService { .collect(Collectors.toList()); } + public List> activityResources() { + final List> result = new ArrayList<>(); + result.add(new Tuple<>("true", this.i18nSupport.getText("sebserver.overall.status.active"))); + result.add(new Tuple<>("false", this.i18nSupport.getText("sebserver.overall.status.inactive"))); + return result; + } + public List> lmsTypeResources() { return Arrays.asList(LmsType.values()) .stream() @@ -119,20 +128,6 @@ public class ResourceService { .collect(Collectors.toList()); } -// public Function getExamConfigurationNameFunction() { -// final Map idNameMap = getExamConfigurationSelection() -// .getOr(Collections.emptyList()) -// .stream() -// .collect(Collectors.toMap(e -> e.modelId, e -> e.name)); -// -// return id -> { -// if (!idNameMap.containsKey(id)) { -// return Constants.EMPTY_NOTE; -// } -// return idNameMap.get(id); -// }; -// } - public List> userRoleResources() { return UserRole.publicRolesForUser(this.currentUser.get()) .stream() @@ -229,6 +224,7 @@ public class ResourceService { public List> examTypeResources() { return Arrays.asList(ExamType.values()) .stream() + .filter(type -> type != ExamType.UNDEFINED) .map(type -> new Tuple<>( type.name(), this.i18nSupport.getText(EXAM_TYPE_PREFIX + type.name()))) @@ -313,4 +309,13 @@ public class ResourceService { .call(); } + public String examTypeName(final Exam exam) { + if (exam.type == null) { + return Constants.EMPTY_NOTE; + } + + return this.i18nSupport + .getText(ResourceService.EXAM_TYPE_PREFIX + exam.type.name()); + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetRunningExamPage.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetRunningExamPage.java new file mode 100644 index 00000000..6d06d912 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/api/session/GetRunningExamPage.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 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.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 GetRunningExamPage extends RestCall> { + + public GetRunningExamPage() { + super(new TypeKey<>( + CallType.GET_PAGE, + EntityType.EXAM, + new TypeReference>() { + }), + HttpMethod.GET, + MediaType.APPLICATION_FORM_URLENCODED, + API.EXAM_MONITORING_ENDPOINT); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/table/TableFilter.java b/src/main/java/ch/ethz/seb/sebserver/gui/table/TableFilter.java index 00fd82ef..69f53da5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/table/TableFilter.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/table/TableFilter.java @@ -92,9 +92,10 @@ public class TableFilter { .map(FilterComponent::reset) .collect(Collectors.toList())); - final FilterComponent lastComp = this.components.get(this.components.size() - 1); - if (lastComp instanceof TableFilter.NullFilter) { + FilterComponent lastComp = this.components.get(this.components.size() - 1); + while (lastComp instanceof TableFilter.NullFilter) { this.components.remove(lastComp); + lastComp = this.components.get(this.components.size() - 1); } addActions(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationServiceImpl.java index 7c80a4d8..b726cda0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/PaginationServiceImpl.java @@ -269,6 +269,9 @@ public class PaginationServiceImpl implements PaginationService { configurationNodeTableMap.put( Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION, ConfigurationNodeRecordDynamicSqlSupport.description.name()); + configurationNodeTableMap.put( + Domain.CONFIGURATION_NODE.ATTR_STATUS, + ConfigurationNodeRecordDynamicSqlSupport.status.name()); this.sortColumnMapping.put( ConfigurationNodeRecordDynamicSqlSupport.configurationNodeRecord.name(), configurationNodeTableMap); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java index 8c59b82f..09736c00 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamSessionService.java @@ -10,10 +10,12 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session; import java.io.OutputStream; import java.util.Collection; +import java.util.function.Predicate; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.util.Result; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; /** A Service to handle running exam sessions */ public interface ExamSessionService { @@ -33,13 +35,22 @@ public interface ExamSessionService { * @return Result referencing the running Exam or an error if the Exam not exists or is not currently running */ Result getRunningExam(Long examId); - /** Gets all all currently running Exams for a particular Institution. + /** Gets all currently running Exams for a particular Institution. * * @param institutionId the Institution identifier * @return Result referencing the list of all currently running Exams of the institution or to an error if * happened. */ Result> getRunningExamsForInstitution(Long institutionId); + /** Gets all currently running Exams for a particular FilterMap. + * + * @param filterMap the FilterMap containing the filter attributes + * @param predicate additional filter predicate + * @return Result referencing the list of all currently running Exams or to an error if happened. */ + Result> getFilteredRunningExams( + FilterMap filterMap, + Predicate predicate); + /** Streams the default SEB Exam Configuration to a ClientConnection with given connectionToken. * * @param connectionToken The connection token that identifiers the ClientConnection diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java index 4bae21ae..69a5e3d0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java @@ -13,6 +13,7 @@ import java.io.OutputStream; import java.util.Collection; import java.util.Collections; import java.util.NoSuchElementException; +import java.util.function.Predicate; import java.util.stream.Collectors; import org.slf4j.Logger; @@ -30,6 +31,7 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; @Lazy @@ -96,6 +98,18 @@ public class ExamSessionServiceImpl implements ExamSessionService { .collect(Collectors.toList())); } + @Override + public Result> getFilteredRunningExams( + final FilterMap filterMap, + final Predicate predicate) { + + return this.examDAO.allMatching(filterMap, predicate) + .map(col -> col.stream() + .map(exam -> this.examSessionCacheService.getRunningExam(exam.id)) + .filter(exam -> exam != null) + .collect(Collectors.toList())); + } + @Override public void streamDefaultExamConfig( final String connectionToken, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java index 8bb4dd38..b0553168 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java @@ -126,6 +126,7 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler { final Exception ex, final WebRequest request) { + log.error("Unexpected internal error catched at the API endpoint: ", ex); return APIMessage.ErrorMessage.UNEXPECTED .createErrorResponse(ex.getMessage()); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java index 15885632..adba27a0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/EntityController.java @@ -144,7 +144,8 @@ public abstract class EntityController { pageSize, sort, getSQLTableOfEntity().name(), - () -> getAll(filterMap)).getOrThrow(); + () -> getAll(filterMap)) + .getOrThrow(); } // ****************** diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java index e2ca3e31..c3dcb45f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java @@ -120,39 +120,54 @@ public class ExamAdministrationController extends ActivatableEntityController exams = new ArrayList<>( - this.examDAO.allMatching(new FilterMap(allRequestParams)).getOrThrow()); + this.examDAO + .allMatching(new FilterMap(allRequestParams)) + .getOrThrow()); - if (!StringUtils.isBlank(sort)) { - final String sortBy = PageSortOrder.decode(sort); - if (sortBy.equals(QuizData.QUIZ_ATTR_NAME)) { - Collections.sort(exams, (exam1, exam2) -> exam1.name.compareTo(exam2.name)); - } - if (sortBy.equals(QuizData.FILTER_ATTR_START_TIME)) { - Collections.sort(exams, (exam1, exam2) -> exam1.startTime.compareTo(exam2.startTime)); - } - } - - if (PageSortOrder.DESCENDING == PageSortOrder.getSortOrder(sort)) { - Collections.reverse(exams); - } - - final int start = (pageNum - 1) * pSize; - int end = start + pageSize; - if (exams.size() < end) { - end = exams.size(); - } - return new Page<>( - exams.size() / pSize, - pageNum, + return buildSortedExamPage( + this.paginationService.getPageNumber(pageNumber), + this.paginationService.getPageSize(pageSize), sort, - exams.subList(start, end)); + exams); } } + public static Page buildSortedExamPage( + final Integer pageNumber, + final Integer pageSize, + final String sort, + final List exams) { + + if (!StringUtils.isBlank(sort)) { + final String sortBy = PageSortOrder.decode(sort); + if (sortBy.equals(Exam.FILTER_ATTR_NAME)) { + Collections.sort(exams, (exam1, exam2) -> exam1.name.compareTo(exam2.name)); + } + if (sortBy.equals(Exam.FILTER_ATTR_TYPE)) { + Collections.sort(exams, (exam1, exam2) -> exam1.type.compareTo(exam2.type)); + } + if (sortBy.equals(QuizData.FILTER_ATTR_START_TIME)) { + Collections.sort(exams, (exam1, exam2) -> exam1.startTime.compareTo(exam2.startTime)); + } + } + + if (PageSortOrder.DESCENDING == PageSortOrder.getSortOrder(sort)) { + Collections.reverse(exams); + } + + final int start = (pageNumber - 1) * pageSize; + int end = start + pageSize; + if (exams.size() < end) { + end = exams.size(); + } + return new Page<>( + exams.size() / pageSize, + pageNumber, + sort, + exams.subList(start, end)); + } + @Override protected Exam createNew(final POSTMapper postParams) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitiroingEndpoint.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitiroingEndpoint.java deleted file mode 100644 index 3e06ee53..00000000 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitiroingEndpoint.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) 2019 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.weblayer.api; - -public class ExamMonitiroingEndpoint { - - public ExamMonitiroingEndpoint() { - // TODO Auto-generated constructor stub - } - -} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java new file mode 100644 index 00000000..bcf89b6e --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2019 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.weblayer.api; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.http.MediaType; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.api.EntityType; +import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType; +import ch.ethz.seb.sebserver.gbl.model.Page; +import ch.ethz.seb.sebserver.gbl.model.exam.Exam; +import ch.ethz.seb.sebserver.gbl.model.user.UserRole; +import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; +import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService; +import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService; +import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException; +import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService; +import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; + +@WebServiceProfile +@RestController +@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.EXAM_MONITORING_ENDPOINT) +public class ExamMonitoringController { + + private final ExamSessionService examSessionService; + private final AuthorizationService authorization; + private final PaginationService paginationService; + + public ExamMonitoringController( + final ExamSessionService examSessionService, + final AuthorizationService authorization, + final PaginationService paginationService) { + + this.examSessionService = examSessionService; + this.authorization = authorization; + this.paginationService = paginationService; + } + + /** This is called by Spring to initialize the WebDataBinder and is used here to + * initialize the default value binding for the institutionId request-parameter + * that has the current users insitutionId as default. + * + * See also UserService.addUsersInstitutionDefaultPropertySupport */ + @InitBinder + public void initBinder(final WebDataBinder binder) throws Exception { + this.authorization + .getUserService() + .addUsersInstitutionDefaultPropertySupport(binder); + } + + // ****************** + // * GET (getAll) + // ****************** + + /** Get a page of all currently running exams + * + * GET /{api}/{entity-type-endpoint-name} + * + * GET /admin-api/v1/monitoring + * GET /admin-api/v1/monitoring?page_number=2&page_size=10&sort=-name + * GET /admin-api/v1/monitoring?name=seb&active=true + * + * @param institutionId The institution identifier of the request. + * Default is the institution identifier of the institution of the current user + * @param pageNumber the number of the page that is requested + * @param pageSize the size of the page that is requested + * @param sort the sort parameter to sort the list of entities before paging + * the sort parameter is the name of the entity-model attribute to sort with a leading '-' sign for + * descending sort order + * @param allRequestParams a MultiValueMap of all request parameter that is used for filtering + * @return Page of domain-model-entities of specified type */ + @RequestMapping( + method = RequestMethod.GET, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, + produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public Page getPage( + @RequestParam( + name = API.PARAM_INSTITUTION_ID, + required = true, + defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId, + @RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber, + @RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize, + @RequestParam(name = Page.ATTR_SORT, required = false) final String sort, + @RequestParam final MultiValueMap allRequestParams) { + + // check if user has EXAM_SUPPORTER privilege. + final SEBServerUser currentUser = this.authorization + .getUserService() + .getCurrentUser(); + + if (!currentUser.getUserRoles().contains(UserRole.EXAM_SUPPORTER)) { + throw new PermissionDeniedException( + EntityType.EXAM, + PrivilegeType.READ, + currentUser.getUserInfo().uuid); + } + + final FilterMap filterMap = new FilterMap(allRequestParams); + + // if current user has no read access for specified entity type within other institution + // then the current users institutionId is put as a SQL filter criteria attribute to extends query performance + if (!this.authorization.hasGrant(PrivilegeType.READ, EntityType.EXAM)) { + filterMap.putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId)); + } + + final List exams = new ArrayList<>(this.examSessionService + .getFilteredRunningExams(filterMap, exam -> true) + .getOrThrow()); + + return ExamAdministrationController.buildSortedExamPage( + this.paginationService.getPageNumber(pageNumber), + this.paginationService.getPageSize(pageSize), + sort, + exams); + } + +} diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 932ac266..6e5fde3c 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -852,11 +852,29 @@ sebserver.examconfig.props.label.enableF10=Enable F10 sebserver.examconfig.props.label.enableF11=Enable F11 sebserver.examconfig.props.label.enableF12=Enable F12 - - sebserver.examconfig.props.validation.password.confirm=Please enter correct confirm password sebserver.examconfig.props.validation.unexpected=Unexpected error happened. Value was not set correctly sebserver.examconfig.props.validation.IntegerTypeValidator=Invalid number sebserver.examconfig.props.validation.DecimalTypeValidator=Invalid decimal number sebserver.examconfig.props.validation.ExitKeySequenceValidator=Key is already in sequence -sebserver.examconfig.props.validation.WindowsSizeValidator=Invalid number \ No newline at end of file +sebserver.examconfig.props.validation.WindowsSizeValidator=Invalid number + + +################################ +# Monitoring +################################ + +sebserver.monitoring.action.list=Monitoring +sebserver.monitoring.exam.list.title=Running Exams + +sebserver.monitoring.exam.list.actions=Selected Exam +sebserver.monitoring.exam.action.list.view=Monitoring + + +sebserver.monitoring.exam.info.pleaseSelect=Please select an exam first +sebserver.monitoring.exam.list.empty=There are currently no running exams +sebserver.monitoring.exam.list.column.name=Name +sebserver.monitoring.exam.list.column.type=Type +sebserver.monitoring.exam.list.column.startTime=Start Time +sebserver.monitoring.exam.list.column.endTime=End Time + diff --git a/src/main/resources/static/css/sebserver.css b/src/main/resources/static/css/sebserver.css index dd62c281..df13c199 100644 --- a/src/main/resources/static/css/sebserver.css +++ b/src/main/resources/static/css/sebserver.css @@ -281,11 +281,11 @@ Combo-Button { background-color: #ffffff; background-image: gradient(linear, left top, left bottom, from(#ffffff), to(#ffffff)); border: none; - width: 30px; + width: 20px; } Combo-Field { - padding: 3px 10px 1px 10px; + padding: 3px 0px 1px 10px; } Combo.error {